diff --git a/README.md b/README.md index 20cdefe..8fefcb0 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Toggle **This review** (impact subgraph) vs **Indexed architecture** (the repo m ### Pull requests -GitHub and Bitbucket via **Sign in** (OAuth) or a token in Settings. After connecting, **My repos** lists every repository the account can access. Pick one and jump to a branch-range review. Review still runs against a **local clone** — if that clone is already a Loadpath workspace, selecting the remote repo fills the local path from `git remote`. The screenshot uses a fixture PR so the tab is not empty. +GitHub, GitLab, and Bitbucket via **Sign in** (OAuth) or a token in Settings. GitHub Enterprise and self-hosted GitLab take a host in Settings. After connecting, **My repos** lists every repository the account can access. Pick one and **Review this PR** — Loadpath fetches the PR refs into a local clone if this machine does not already have one. Review still runs against that clone. The screenshot uses a fixture PR so the tab is not empty. ![Pull requests list](docs/screenshots/pull-requests.png) @@ -75,9 +75,11 @@ Browse the filesystem, pick a project root. Loadpath remembers recent workspaces ### Settings -Appearance (all 24 themes), GitHub / Bitbucket **OAuth sign-in** (or a classic PAT / app password), and AI providers (Anthropic, OpenAI, Grok/xAI, DeepSeek, Cursor-compatible, Ollama). Residual analysis only — Loadpath does not comment every hunk. +Appearance (all 24 themes), GitHub / GitLab / Bitbucket **OAuth sign-in** (or a classic PAT / app password), GitHub Enterprise host, and AI providers (Anthropic, OpenAI, Grok/xAI, DeepSeek, Cursor-compatible, Ollama). Residual analysis only — Loadpath does not comment every hunk. -GitHub uses device flow (`repo read:user read:org`). Create an OAuth App, enable Device Flow, then set `LOADPATH_GITHUB_CLIENT_ID` or paste the client ID in Settings. +GitHub uses device flow (`repo read:user read:org`). Create an OAuth App, enable Device Flow, then set `LOADPATH_GITHUB_CLIENT_ID` or paste the client ID in Settings. For GitHub Enterprise, set the host (API is `{host}/api/v3`). + +GitLab uses authorization code. Create an OAuth application whose callback is `http://127.0.0.1:7345/api/oauth/gitlab/callback`, then set `LOADPATH_GITLAB_CLIENT_ID` / `LOADPATH_GITLAB_CLIENT_SECRET` (or paste them in Settings). Self-managed GitLab takes a host. Bitbucket uses authorization code. Create an OAuth consumer whose callback is `http://127.0.0.1:7345/api/oauth/bitbucket/callback`, then set `LOADPATH_BITBUCKET_CLIENT_ID` / `LOADPATH_BITBUCKET_CLIENT_SECRET` or paste the key and secret in Settings. Access tokens are refreshed automatically. SCM sign-in and repo listing are local-only (loopback) so a tunneled MCP server does not expose private repos. @@ -167,6 +169,11 @@ loadpath architecture /path/to/repo # Review a git range against the index (three-dot / merge-base by default) loadpath review /path/to/repo --base HEAD~1 --head HEAD loadpath review /path/to/repo --base origin/main --head HEAD --no-reindex +loadpath review /path/to/repo --dirty # include the working tree +loadpath review /path/to/repo --fail-on blocker # CI merge gate (never|blocker|low|medium) + +# Walk sinks from one indexed node — no git range +loadpath whatif /path/to/repo django.field:billing.Invoice.total # Cross-platform app (API + visual graph + PR list + MCP /mcp with OAuth) loadpath serve --port 7345 @@ -198,7 +205,22 @@ loadpath index /tmp/acme-billing loadpath serve --open ``` -Then point the UI at `/tmp/acme-billing`, or pick it from the repo explorer. `loadpath serve` always boots the app; it does not take a repo path. Default range is `HEAD~1`…`HEAD`. +Then point the UI at `/tmp/acme-billing`, or pick it from the repo explorer. `loadpath serve` always boots the app; it does not take a repo path. Default range is `HEAD~1`…`HEAD`. Toggle **Include uncommitted** to walk the working tree. Click a node → **What if this changes** to walk sinks without a git range. The read-order list is a guided tour (prev/next highlights the file on the graph). + +## GitHub Action merge gate + +Use this repo as a composite action. It indexes the checkout, reviews `origin/$GITHUB_BASE_REF...HEAD`, optionally upserts the single Loadpath brief, and fails the job on architecture blockers (or on low/medium confidence if you ask). + +```yaml +- uses: Modsofthenation/PR-Reviewer@main + with: + fail-on: blocker # never | blocker | low | medium + comment: true +``` + +Outputs: `level`, `passed`, `title`, `contract_break` (`none` | `additive` | `breaking` | `drift`). + +CLI equivalent: `loadpath review . --fail-on blocker --comment --provider github --pr $N --repo $SLUG`. `--github-output` (or `GITHUB_OUTPUT`) writes the same fields for Actions. ## MCP (Cursor, Claude, ChatGPT, Gemini) @@ -225,7 +247,7 @@ MCP URL: `https://your-tunnel.example/mcp` (or `http://127.0.0.1:7345/mcp` on th **Cursor / Claude / ChatGPT / Gemini (HTTP + OAuth)** — add that MCP URL in the host’s connectors. The first connect opens a consent page on the Loadpath machine. -Tools: `list_workspaces`, `init_repo`, `index_repo`, `architecture`, `review`, `detect_repo`, `list_pull_requests`, `list_remote_repositories`, `post_review_comment`. `review` returns the load-path brief (confidence, sinks, reviewers) — not hunk comments. +Tools: `list_workspaces`, `init_repo`, `index_repo`, `architecture`, `review`, `detect_repo`, `list_pull_requests`, `list_remote_repositories`, `post_review_comment`, `what_if`, `review_pull_request`. `review` returns the load-path brief (confidence, sinks, reviewers, contract-break, auth, suggested tests, trend) — not hunk comments. `review_pull_request` fetches GitHub / GitLab / Bitbucket refs into a local clone first. Put `loadpath.yml` at the repo root (see [`loadpath.yml.example`](loadpath.yml.example) and [`fixtures/demo_monorepo/loadpath.yml`](fixtures/demo_monorepo/loadpath.yml)). The tool is opinionated about *your* architecture, not a generic module graph. @@ -240,6 +262,11 @@ AST is the default extractor. It is a **framework overlay**, not an import graph | Views | DRF ViewSets / APIViews, `serializer_class`, `get_serializer_class` (residual), `permission_classes`, `get_queryset`, `filterset_class`, `authentication_classes`, `pagination_class` | | Function views | `@api_view`, `@login_required`, `@csrf_exempt`, … | | Django Ninja | `@router.get/post/…` routes and views | +| FastAPI (same repo) | `@app.get/post/…` and Pydantic `BaseModel` — only when the file imports FastAPI, so Ninja is not stolen | +| GraphQL | Strawberry `@strawberry.type` / `@strawberry.field` and Graphene `ObjectType` / `Mutation`; client `gql` documents stitch by operation/selection name | +| Channels | `WebsocketConsumer` subclasses and `path(..., Consumer.as_asgi())` websocket routes | +| Templates + HTMX | `.html` files, `{% url %}` / include / extends, `hx-get/post/…` stitched to Django routes | +| Cache / flags / on_commit | `cache.get/set/delete`, waffle-style `flag_is_active`, `transaction.on_commit` as sinks | | URLs | `path` / `re_path`, DRF `router.register`, `include()` mount composition (`/api` + `invoices//` → `/api/invoices/{id}`) | | Signals | `@receiver`, `signal.connect()` residual, `AppConfig.ready()` residual | | Management commands | `BaseCommand` + `handle()`, including `.delay(` / `.send(` enqueue edges | @@ -253,7 +280,7 @@ AST is the default extractor. It is a **framework overlay**, not an import graph - Enqueue: `.delay(`, `.apply_async(` - Signatures / canvas: `.s(`, `.si(`, `chain` / `group` / `chord` (canvas is a residual; inner signatures are `enqueues` edges) - `current_app.send_task("billing.tasks.send_invoice_email")` — residual + inferred enqueue -- `transaction.on_commit(lambda: task.apply_async(...))` — residual, nested enqueue walked +- `transaction.on_commit(lambda: task.apply_async(...))` — `SIDE_EFFECT` sink + residual; nested enqueue walked - `CELERY_BEAT_SCHEDULE` / `beat_schedule` in settings ### Dramatiq @@ -276,7 +303,7 @@ AST is enough for review. If you need live `_meta` (db_table, resolved relations **React:** react-router tables, composition, TanStack Query `queryKey` + fetch/axios URL templates, Zod schemas, feature-folder imports, RTL `render()` as `tested_by`. -**Stitch (the moat):** OpenAPI from Spectacular/schema files first; generated clients (`generated/`, orval, openapi-typescript) as high-confidence `consumed_by_client`; fallback URL-template matching and serializer/Zod field overlap marked **inferred**. +**Stitch (the moat):** OpenAPI from Spectacular/schema files first; generated clients (`generated/`, orval, openapi-typescript) as high-confidence `consumed_by_client`; FastAPI routes and GraphQL operations stitch the same way; HTMX URLs match Django routes; fallback URL-template matching and serializer/Zod field overlap marked **inferred**. Frontend roots prefer `frontend/src`, `frontend`, `web/src`, `client/src`, `ui/src`, `src-ui/src` — not a Python package `src/` and not `docs` / docs-site trees. `app/` is a Django root candidate. @@ -315,7 +342,9 @@ Line coverage on changed files is the wrong metric. Loadpath scores the **impact | Architecture | No new cross-context edges; published seams hold | `crosses_context` or a leaked queryset seam | | Graph | Resolved edges | Many inferred/dynamic edges | -`high` / `medium` / `low` plus three reasons. Isolated leaf UI with green tests and no rule hits is labeled `loadpath:low-risk`. +`high` / `medium` / `low` plus three reasons. Isolated leaf UI with green tests and no rule hits is labeled `loadpath:low-risk`. The same PR/range stores a **confidence trend** so a second review on that range can say whether confidence rose, dropped, or the sink count moved. + +Auth is a first-class load path: permission_classes, get_queryset object scope, and websocket routes without a gate. Untested seams get **suggested tests** (pytest / RTL / GraphQL / Channels / HTMX). Contract diffs are labeled `additive`, `breaking`, or `drift`. ## Tests @@ -327,7 +356,7 @@ node --test desktop/*.test.mjs | Suite | What it covers | | --- | --- | -| `tests/unit/` | Django/React extractors, architecture rules, depth/seam survey, stitch, SCM/AI providers | +| `tests/unit/` | Django/React extractors, architecture rules, depth/seam survey, stitch, overlays (GraphQL/Channels/HTMX/FastAPI), SCM/AI providers | | `tests/integration/test_review_vertical_slice.py` | Serializer field change reaches InvoicePage/Zod, not MePage; reviewers `billing-team` | | `tests/e2e/test_cli_review.py` | `loadpath index` / `architecture` / `review` markdown, JSON, HTML | | `tests/e2e/test_api_flow.py` | health, index, architecture, review-from-index, graph, settings, GitHub + Bitbucket PR list | @@ -345,7 +374,7 @@ LOADPATH_SCREENSHOT_DIR=docs/screenshots python -m pytest tests/e2e/test_ui_scre ## Demo fixture -[`fixtures/demo_monorepo`](fixtures/demo_monorepo) is a billing/identity split: DRF ViewSet, FBV, Ninja ledger route, Celery tasks + beat + canvas, Dramatiq actor + GenericActor, management command, signal, FK string ref, React InvoicePage/Zod. +[`fixtures/demo_monorepo`](fixtures/demo_monorepo) is a billing/identity split: DRF ViewSet, FBV, Ninja ledger route, FastAPI sidecar gateway, Strawberry + Graphene schema, Channels consumer, Django template + HTMX board, cache keys / feature flags / `on_commit`, Celery tasks + beat + canvas, Dramatiq actor + GenericActor, management command, signal, FK string ref, React InvoicePage/Zod + a `gql` document. ## What this is not diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..5ea333d --- /dev/null +++ b/action.yml @@ -0,0 +1,62 @@ +name: "Loadpath Review" +description: "Review a pull request as a load path and fail the check on blockers or low confidence." +author: Loadpath +branding: + icon: git-pull-request + color: purple +inputs: + fail-on: + description: "Gate: never, blocker, low, or medium. blocker fails on architecture blockers; low also fails on low confidence; medium fails unless confidence is high." + required: false + default: "blocker" + comment: + description: "Post or update the single Loadpath brief on the pull request." + required: false + default: "true" + python-version: + description: "Python version used to install Loadpath." + required: false + default: "3.12" + extra-args: + description: "Extra arguments passed to `loadpath review`." + required: false + default: "" +outputs: + level: + description: "Confidence level (high, medium, low)." + value: ${{ steps.review.outputs.level }} + passed: + description: "Whether the gate passed." + value: ${{ steps.review.outputs.passed }} + title: + description: "Review title." + value: ${{ steps.review.outputs.title }} + contract_break: + description: "none, additive, breaking, or drift." + value: ${{ steps.review.outputs.contract_break }} +runs: + using: composite + steps: + - uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python-version }} + - name: Install Loadpath + shell: bash + run: python -m pip install --quiet "${{ github.action_path }}" + - name: Index + shell: bash + run: loadpath index . + - name: Review + id: review + shell: bash + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + ARGS=(review . --base "origin/${GITHUB_BASE_REF:-main}" --head HEAD --fail-on "${{ inputs.fail-on }}") + if [ "${{ inputs.comment }}" = "true" ] && [ -n "${{ github.event.pull_request.number }}" ]; then + ARGS+=(--comment --provider github --pr "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}") + fi + # shellcheck disable=SC2206 + EXTRA=(${{ inputs.extra-args }}) + loadpath "${ARGS[@]}" "${EXTRA[@]}" diff --git a/desktop/urls.mjs b/desktop/urls.mjs index 397733e..ed89b1a 100644 --- a/desktop/urls.mjs +++ b/desktop/urls.mjs @@ -7,15 +7,21 @@ export function isAllowedExternalUrl(url) { } if (parsed.protocol !== "https:") return false; if (parsed.username || parsed.password) return false; - const host = parsed.hostname.toLowerCase(); - return ( - host === "github.com" || - host.endsWith(".github.com") || - host === "bitbucket.org" || - host.endsWith(".bitbucket.org") || - host === "id.atlassian.com" || - host.endsWith(".atlassian.com") - ); + const host = parsed.hostname.toLowerCase(); + const githubLike = + host === "github.com" || + host.endsWith(".github.com") || + (host.startsWith("github.") && !host.startsWith("github.com.")); + const gitlabLike = + host === "gitlab.com" || + host.endsWith(".gitlab.com") || + (host.startsWith("gitlab.") && !host.startsWith("gitlab.com.")); + const bitbucketLike = + host === "bitbucket.org" || + host.endsWith(".bitbucket.org") || + host === "id.atlassian.com" || + host.endsWith(".atlassian.com"); + return githubLike || gitlabLike || bitbucketLike; } export function isAppOrigin(url, port) { diff --git a/desktop/urls.test.mjs b/desktop/urls.test.mjs index cd1e871..cb01926 100644 --- a/desktop/urls.test.mjs +++ b/desktop/urls.test.mjs @@ -4,10 +4,13 @@ import { describe, it } from "node:test"; import { isAllowedExternalUrl, isAppOrigin } from "./urls.mjs"; describe("isAllowedExternalUrl", () => { - it("allows GitHub and Bitbucket https PR links", () => { + it("allows GitHub, GitLab, and Bitbucket https PR links", () => { assert.equal(isAllowedExternalUrl("https://github.com/acme/demo/pull/12"), true); assert.equal(isAllowedExternalUrl("https://bitbucket.org/acme/demo/pull-requests/3"), true); + assert.equal(isAllowedExternalUrl("https://gitlab.com/acme/demo/-/merge_requests/4"), true); assert.equal(isAllowedExternalUrl("https://id.atlassian.com/login"), true); + assert.equal(isAllowedExternalUrl("https://github.mycompany.com/acme/demo/pull/1"), true); + assert.equal(isAllowedExternalUrl("https://gitlab.internal.example/acme/demo/-/merge_requests/2"), true); }); it("rejects credentials, other hosts, and non-https schemes", () => { diff --git a/fixtures/demo_monorepo/backend/billing/consumers.py b/fixtures/demo_monorepo/backend/billing/consumers.py new file mode 100644 index 0000000..001b27b --- /dev/null +++ b/fixtures/demo_monorepo/backend/billing/consumers.py @@ -0,0 +1,13 @@ +from channels.generic.websocket import AsyncJsonWebsocketConsumer + +from billing.models import Invoice + + +class InvoiceConsumer(AsyncJsonWebsocketConsumer): + async def connect(self): + await self.accept() + + async def receive_json(self, content, **kwargs): + invoice_id = content.get("id") + invoice = Invoice.objects.get(pk=invoice_id) + await self.send_json({"id": invoice.id, "total": str(invoice.total)}) diff --git a/fixtures/demo_monorepo/backend/billing/gateway.py b/fixtures/demo_monorepo/backend/billing/gateway.py new file mode 100644 index 0000000..61f5046 --- /dev/null +++ b/fixtures/demo_monorepo/backend/billing/gateway.py @@ -0,0 +1,18 @@ +from fastapi import FastAPI +from pydantic import BaseModel + +from billing.models import Invoice + +app = FastAPI() + + +class InvoiceOut(BaseModel): + id: int + total: float + status: str + + +@app.get("/internal/invoices/{id}", response_model=InvoiceOut) +def get_invoice(id: int) -> InvoiceOut: + row = Invoice.objects.get(pk=id) + return InvoiceOut(id=row.id, total=float(row.total), status=row.status) diff --git a/fixtures/demo_monorepo/backend/billing/routing.py b/fixtures/demo_monorepo/backend/billing/routing.py new file mode 100644 index 0000000..320d061 --- /dev/null +++ b/fixtures/demo_monorepo/backend/billing/routing.py @@ -0,0 +1,7 @@ +from django.urls import path + +from billing.consumers import InvoiceConsumer + +websocket_urlpatterns = [ + path("ws/invoices/", InvoiceConsumer.as_asgi()), +] diff --git a/fixtures/demo_monorepo/backend/billing/schema.py b/fixtures/demo_monorepo/backend/billing/schema.py new file mode 100644 index 0000000..294f816 --- /dev/null +++ b/fixtures/demo_monorepo/backend/billing/schema.py @@ -0,0 +1,37 @@ +import graphene +import strawberry + +from billing.models import Invoice + + +@strawberry.type +class InvoiceType: + id: int + total: float + status: str + + +@strawberry.type +class Query: + @strawberry.field + def invoice(self, id: int) -> InvoiceType: + row = Invoice.objects.get(pk=id) + return InvoiceType(id=row.id, total=float(row.total), status=row.status) + + +schema = strawberry.Schema(query=Query) + + +class CreditInvoice(graphene.Mutation): + class Arguments: + id = graphene.ID(required=True) + + ok = graphene.Boolean() + + def mutate(self, info, id): + Invoice.objects.filter(pk=id).update(status="credited") + return CreditInvoice(ok=True) + + +class Mutation(graphene.ObjectType): + credit_invoice = CreditInvoice.Field() diff --git a/fixtures/demo_monorepo/backend/billing/templates/billing/base.html b/fixtures/demo_monorepo/backend/billing/templates/billing/base.html new file mode 100644 index 0000000..ef872e6 --- /dev/null +++ b/fixtures/demo_monorepo/backend/billing/templates/billing/base.html @@ -0,0 +1,5 @@ + + + {% block content %}{% endblock %} + + diff --git a/fixtures/demo_monorepo/backend/billing/templates/billing/invoice_board.html b/fixtures/demo_monorepo/backend/billing/templates/billing/invoice_board.html new file mode 100644 index 0000000..507d1d6 --- /dev/null +++ b/fixtures/demo_monorepo/backend/billing/templates/billing/invoice_board.html @@ -0,0 +1,10 @@ +{% extends "billing/base.html" %} +{% block content %} +
+

Invoice board

+
+ Loading totals… +
+ Refresh +
+{% endblock %} diff --git a/fixtures/demo_monorepo/backend/billing/urls.py b/fixtures/demo_monorepo/backend/billing/urls.py index d25e864..d343f27 100644 --- a/fixtures/demo_monorepo/backend/billing/urls.py +++ b/fixtures/demo_monorepo/backend/billing/urls.py @@ -1,7 +1,7 @@ from django.urls import include, path from rest_framework.routers import DefaultRouter -from billing.views import InvoiceViewSet, invoice_totals +from billing.views import InvoiceBoardView, InvoiceViewSet, graphql_http, invoice_totals router = DefaultRouter() router.register("invoices", InvoiceViewSet, basename="invoice") @@ -9,6 +9,8 @@ urlpatterns = [ path("", include(router.urls)), path("invoices/totals/", invoice_totals, name="invoice-totals"), + path("invoices/board/", InvoiceBoardView.as_view(), name="invoice-board"), + path("graphql/", graphql_http, name="graphql"), path( "invoices//", InvoiceViewSet.as_view({"get": "retrieve", "put": "update"}), diff --git a/fixtures/demo_monorepo/backend/billing/views.py b/fixtures/demo_monorepo/backend/billing/views.py index d5f8a2c..bdb6e41 100644 --- a/fixtures/demo_monorepo/backend/billing/views.py +++ b/fixtures/demo_monorepo/backend/billing/views.py @@ -1,7 +1,11 @@ +from django.core.cache import cache +from django.db import transaction +from django.views.generic import TemplateView from rest_framework import viewsets from rest_framework.decorators import api_view, permission_classes from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response +from waffle import flag_is_active from billing.actors import rebuild_ledger from billing.models import Invoice @@ -24,13 +28,33 @@ def get_queryset(self): def perform_create(self, serializer): invoice = serializer.save() + cache.delete("invoice:list") send_invoice_email.delay(invoice.id) rebuild_ledger.send(invoice.id) rebuild_ledger.send_with_options(args=(invoice.id,)) + if flag_is_active(self.request, "async_ledger"): + transaction.on_commit(lambda: rebuild_ledger.send(invoice.id)) return invoice +class InvoiceBoardView(TemplateView): + template_name = "billing/invoice_board.html" + + @api_view(["GET"]) @permission_classes([IsAuthenticated]) def invoice_totals(request): + if flag_is_active(request, "new_totals"): + cached = cache.get("invoice:totals") + if cached is not None: + return Response({"count": cached}) + count = Invoice.objects.count() + cache.set("invoice:totals", count) + return Response({"count": count}) return Response({"count": Invoice.objects.count()}) + + +@api_view(["GET", "POST"]) +@permission_classes([IsAuthenticated]) +def graphql_http(request): + return Response({"data": {}}) diff --git a/fixtures/demo_monorepo/frontend/src/features/billing/InvoicePage.tsx b/fixtures/demo_monorepo/frontend/src/features/billing/InvoicePage.tsx index b73f66f..559e538 100644 --- a/fixtures/demo_monorepo/frontend/src/features/billing/InvoicePage.tsx +++ b/fixtures/demo_monorepo/frontend/src/features/billing/InvoicePage.tsx @@ -1,8 +1,19 @@ +import { gql } from "@apollo/client"; import { InvoiceForm } from "./InvoiceForm"; import { useInvoice } from "./useInvoice"; +const INVOICE_QUERY = gql` + query Invoice($id: ID!) { + invoice { + total + status + } + } +`; + export function InvoicePage() { const { data } = useInvoice("1"); + void INVOICE_QUERY; return (

Invoice {data?.id}

diff --git a/fixtures/demo_monorepo/loadpath.yml b/fixtures/demo_monorepo/loadpath.yml index 223ffc8..7e49888 100644 --- a/fixtures/demo_monorepo/loadpath.yml +++ b/fixtures/demo_monorepo/loadpath.yml @@ -8,6 +8,8 @@ contexts: - "POST /api/invoices" - "GET /api/invoices/{id}" - "PUT /api/invoices/{id}" + - "GET /api/invoices/totals" + - "POST /graphql" owners: [billing-team] identity: django_apps: [accounts] diff --git a/fixtures/demo_monorepo/openapi.yaml b/fixtures/demo_monorepo/openapi.yaml index 4749932..9d918dd 100644 --- a/fixtures/demo_monorepo/openapi.yaml +++ b/fixtures/demo_monorepo/openapi.yaml @@ -37,3 +37,21 @@ paths: responses: "200": description: ok + /api/invoices/totals/: + get: + operationId: invoiceTotals + responses: + "200": + description: ok + /internal/invoices/{id}: + get: + operationId: getInvoiceInternal + responses: + "200": + description: ok + /graphql: + post: + operationId: graphql + responses: + "200": + description: ok diff --git a/src/loadpath/architecture/snapshot.py b/src/loadpath/architecture/snapshot.py index 8cb50dd..7b3e8b5 100644 --- a/src/loadpath/architecture/snapshot.py +++ b/src/loadpath/architecture/snapshot.py @@ -30,6 +30,16 @@ NodeType.HOOK.value, NodeType.API_CLIENT.value, NodeType.FORM_SCHEMA.value, + NodeType.GRAPHQL_TYPE.value, + NodeType.GRAPHQL_OPERATION.value, + NodeType.FASTAPI_ROUTE.value, + NodeType.PYDANTIC_MODEL.value, + NodeType.CONSUMER.value, + NodeType.WEBSOCKET_ROUTE.value, + NodeType.TEMPLATE.value, + NodeType.CACHE_KEY.value, + NodeType.FEATURE_FLAG.value, + NodeType.SIDE_EFFECT.value, } diff --git a/src/loadpath/cli.py b/src/loadpath/cli.py index 0d079b3..1dfb79f 100644 --- a/src/loadpath/cli.py +++ b/src/loadpath/cli.py @@ -1,11 +1,13 @@ from __future__ import annotations +from contextlib import contextmanager from pathlib import Path -from typing import Optional +from typing import Any, Iterator, Optional import typer from rich.console import Console from rich.markdown import Markdown +from rich.progress import BarColumn, MofNCompleteColumn, Progress, SpinnerColumn, TextColumn, TimeElapsedColumn from loadpath import __version__ from loadpath.architecture.snapshot import architecture_report @@ -18,6 +20,50 @@ console = Console() +@contextmanager +def _index_progress() -> Iterator[Any]: + """Live CLI bar so a long extract is distinguishable from a hang.""" + if not console.is_terminal: + def _plain(event: dict[str, Any]) -> None: + msg = event.get("message") or event.get("phase") + if not msg: + return + phase = event.get("phase") + if phase == "extract" and event.get("current") and str(msg).startswith("Extracting "): + return + if phase in {"scan", "extract", "boot", "stitch", "done", "skipped"}: + console.print(f"[dim]{msg}[/dim]") + + yield _plain + return + + bar = Progress( + SpinnerColumn(), + TextColumn("[progress.description]{task.description}"), + BarColumn(), + MofNCompleteColumn(), + TimeElapsedColumn(), + console=console, + transient=False, + ) + task_id: Any = None + with bar: + def _cb(event: dict[str, Any]) -> None: + nonlocal task_id + desc = str(event.get("message") or event.get("phase") or "Indexing") + total = int(event.get("total") or 0) + done = int(event.get("done") or 0) + if task_id is None: + task_id = bar.add_task(desc, total=total if total else None) + return + kwargs: dict[str, Any] = {"description": desc, "completed": done} + if total: + kwargs["total"] = total + bar.update(task_id, **kwargs) + + yield _cb + + @app.callback() def _version(version: bool = typer.Option(False, "--version", help="Show version")) -> None: if version: @@ -43,24 +89,41 @@ def init( def index( repo: Path = typer.Argument(Path("."), exists=True, file_okay=False), full: bool = typer.Option(False, "--full", help="Re-extract every file"), + jobs: Optional[int] = typer.Option( + None, + "--jobs", + "-j", + help="Parallel extract workers (default: CPU count, 1 if few files). LOADPATH_INDEX_JOBS also works.", + ), ) -> None: """Index a Django + React monorepo into a SQLite graph.""" from loadpath.architecture.snapshot import summarize_index from loadpath.config import load_config from loadpath.settings import register_workspace - store = index_repo(repo, incremental=not full, draft_config=True) + with _index_progress() as on_progress: + store = index_repo( + repo, + incremental=not full, + draft_config=True, + progress=on_progress, + workers=jobs, + ) register_workspace(repo) summary = summarize_index(store, load_config(repo)) counts = summary["counts"] extracted = summary.get("files_extracted") or 0 skipped = summary.get("reindex_skipped") + workers = store.get_meta("index_workers") + elapsed = store.get_meta("index_elapsed_ms") + timing = f" in {elapsed}ms" if elapsed else "" + jobs_note = f" · {workers} extract workers" if workers and workers not in {"0", "1"} else "" if skipped: - console.print(f"Index already current ({counts['nodes']} nodes / {counts['edges']} edges) → {default_db_path(repo)}") + console.print(f"Index already current ({counts['nodes']} nodes / {counts['edges']} edges){timing} → {default_db_path(repo)}") else: console.print( f"Indexed {counts['nodes']} nodes / {counts['edges']} edges" - f" (extracted {extracted} files) → {default_db_path(repo)}" + f" (extracted {extracted} files){timing}{jobs_note} → {default_db_path(repo)}" ) contexts = ", ".join(summary["contexts"]) or "none" console.print(f"Contexts: {contexts}") @@ -87,11 +150,53 @@ def review( reindex: bool = typer.Option(True, "--reindex/--no-reindex", help="Refresh the index before walking the diff"), full: bool = typer.Option(False, "--full", help="Full reindex instead of incremental"), three_dot: bool = typer.Option(True, "--three-dot/--two-dot", help="PR-shaped range (merge-base...head)"), + dirty: bool = typer.Option(False, "--dirty", help="Include uncommitted files in the review"), + fail_on: str = typer.Option( + "never", + "--fail-on", + help="CI gate: never | blocker | low | medium", + ), + comment: bool = typer.Option(False, "--comment", help="Upsert the Loadpath brief on --pr"), + provider: Optional[str] = typer.Option(None, "--provider", help="github | gitlab | bitbucket"), + pr: Optional[int] = typer.Option(None, "--pr", help="Pull/merge request number to comment on or fetch"), + scm_repo: Optional[str] = typer.Option(None, "--repo", help="owner/name for --comment / --pr fetch"), + fetch_pr: bool = typer.Option(False, "--fetch-pr", help="Fetch --pr refs into this clone before reviewing"), + github_output: Optional[Path] = typer.Option(None, "--github-output", help="Append gate fields for GitHub Actions"), + jobs: Optional[int] = typer.Option( + None, + "--jobs", + "-j", + help="Parallel extract workers when reindexing (default: CPU count). LOADPATH_INDEX_JOBS also works.", + ), ) -> None: """Review a git range as clustered load paths + confidence brief.""" - payload = run_review( - repo, base=base, head=head, reindex=reindex, incremental=not full, three_dot=three_dot - ) + import os + + from loadpath.review.gate import FAIL_ON_CHOICES, gate_result, write_github_output + from loadpath.review.render import render_html, render_markdown + + repo_path = repo + if fetch_pr and pr and provider and scm_repo: + from loadpath.providers.pr_fetch import prepare_pull_request + + prepared = prepare_pull_request(provider, scm_repo, pr, repo_path=str(repo)) + repo_path = Path(prepared["repo_path"]) + base = prepared["base"] + head = prepared["head"] + console.print(f"Fetched {provider} #{pr} → {head} (base {base})") + + with _index_progress() as on_progress: + payload = run_review( + repo_path, + base=base, + head=head, + reindex=reindex, + incremental=not full, + three_dot=three_dot, + dirty=dirty, + progress=on_progress if reindex else None, + workers=jobs, + ) if format == "json": import json @@ -101,14 +206,78 @@ def review( else: text = render_markdown(payload) console.print(Markdown(text)) - if out is None: - return + if out is None and not comment and fail_on == "never" and github_output is None and not os.environ.get("GITHUB_OUTPUT"): + gate = gate_result(payload, fail_on) + raise typer.Exit(code=gate["exit_code"]) if out: out.write_text(text, encoding="utf-8") console.print(f"Wrote {out}") elif format != "markdown": console.print(text) + if comment: + from loadpath.providers.scm import provider_for + from loadpath.settings import AppSettings + + settings = AppSettings.load() + name = provider or "github" + number = pr + slug = scm_repo + if not number or not slug: + console.print("--comment needs --pr and --repo owner/name") + raise typer.Exit(code=1) + token = "" + username = "" + host = "" + if name == "github": + token = settings.github_token or os.environ.get("GITHUB_TOKEN") or os.environ.get("LOADPATH_GITHUB_TOKEN") or "" + host = settings.github_host + elif name == "gitlab": + token = settings.gitlab_token or os.environ.get("GITLAB_TOKEN") or os.environ.get("LOADPATH_GITLAB_TOKEN") or "" + host = settings.gitlab_host + else: + token = settings.bitbucket_token + username = settings.bitbucket_username + if not token: + console.print(f"No {name} token in settings or environment") + raise typer.Exit(code=1) + posted = provider_for(name, token, username=username, host=host).upsert_pull_request_comment( + slug, number, render_markdown(payload) + ) + action = "Updated" if posted.get("updated") else "Posted" + console.print(f"{action} Loadpath comment on {slug}#{number}") + + output_path = github_output or (Path(os.environ["GITHUB_OUTPUT"]) if os.environ.get("GITHUB_OUTPUT") else None) + gate = gate_result(payload, fail_on if fail_on in FAIL_ON_CHOICES else "never") + if output_path: + write_github_output(str(output_path), gate, payload) + if fail_on != "never" or gate["exit_code"]: + if not gate["passed"]: + console.print(gate["annotation"]) + raise typer.Exit(code=gate["exit_code"]) + + +@app.command("whatif") +def whatif_cmd( + repo: Path = typer.Argument(Path("."), exists=True, file_okay=False), + node: str = typer.Argument(..., help="Node id from the index (e.g. django.field:billing.Invoice.total)"), +) -> None: + """Walk sinks from one indexed node — no git range required.""" + from loadpath.review.whatif import simulate_node + + try: + payload = simulate_node(repo, node) + except (FileNotFoundError, KeyError) as exc: + console.print(str(exc)) + raise typer.Exit(code=1) from exc + console.print(f"{payload['title']}") + console.print(f"Confidence: {payload['confidence']['level']}") + sinks = payload.get("sinks") or [] + if sinks: + console.print("Sinks: " + ", ".join(s.get("name") or "" for s in sinks[:8])) + for sketch in (payload.get("suggested_tests") or [])[:3]: + console.print(f"Test: {sketch['title']}") + @app.command() def architecture( diff --git a/src/loadpath/extractors/__init__.py b/src/loadpath/extractors/__init__.py index 1eaca3e..de80cb8 100644 --- a/src/loadpath/extractors/__init__.py +++ b/src/loadpath/extractors/__init__.py @@ -2,5 +2,6 @@ from loadpath.extractors.django import extract_django_file, extract_django_paths from loadpath.extractors.react import extract_react_file +from loadpath.extractors.templates import extract_template_file -__all__ = ["extract_django_file", "extract_django_paths", "extract_react_file"] +__all__ = ["extract_django_file", "extract_django_paths", "extract_react_file", "extract_template_file"] diff --git a/src/loadpath/extractors/django.py b/src/loadpath/extractors/django.py index 1c14af0..eec8be2 100644 --- a/src/loadpath/extractors/django.py +++ b/src/loadpath/extractors/django.py @@ -58,6 +58,10 @@ "endpoints", "permissions", "throttles", + "consumers", + "schema", + "routing", + "gateway", } ADMIN_BASES = {"ModelAdmin", "StackedInline", "TabularInline"} CELERY_DECORATORS = {"shared_task", "task", "periodic_task"} @@ -70,6 +74,26 @@ CELERY_TASK_BASES = {"Task"} DRAMATIQ_TASK_BASES = {"GenericActor"} NINJA_HTTP = {"get", "post", "put", "patch", "delete", "api_operation"} +FASTAPI_HTTP = {"get", "post", "put", "patch", "delete", "options", "head", "trace", "api_route", "websocket"} +CONSUMER_BASES = { + "WebsocketConsumer", + "AsyncWebsocketConsumer", + "JsonWebsocketConsumer", + "AsyncJsonWebsocketConsumer", + "AsyncHttpConsumer", +} +GRAPHENE_BASES = {"ObjectType", "Mutation", "InputObjectType", "Interface", "ScalarType", "DjangoObjectType"} +STRAWBERRY_TYPE_DECS = {"type", "input", "interface", "enum"} +CACHE_METHODS = {"get", "set", "delete", "add", "get_or_set", "incr", "decr", "touch"} +FLAG_FUNCS = { + "flag_is_active", + "switch_is_active", + "sample_is_active", + "flag_enabled", + "is_flag_enabled", + "feature_enabled", + "is_feature_enabled", +} REL_FIELD_TYPES = { "ForeignKey", "OneToOneField", @@ -308,6 +332,12 @@ def visit_ClassDef(self, node: ast.ClassDef) -> None: ) elif self._task_class(node): pass + elif _has_base(node, CONSUMER_BASES) or node.name.endswith("Consumer"): + self._consumer(node) + elif self._graphql_class(node): + self._graphql_type(node) + elif _has_base(node, {"BaseModel"}) and not _has_base(node, MODEL_BASES): + self._pydantic_model(node) elif _has_base(node, ADMIN_BASES) or node.name.endswith("Admin"): self._admin(node) elif any(x.endswith("Config") for x in _bases(node)) or node.name.endswith("Config"): @@ -325,7 +355,10 @@ def visit_FunctionDef(self, node: ast.FunctionDef) -> None: self._maybe_test(node) self._maybe_service_fn(node) self._maybe_fbv(node) - self._maybe_ninja(node) + ninja = self._maybe_ninja(node) + if not ninja: + self._maybe_fastapi(node) + self._maybe_graphql_operation(node) self.generic_visit(node) visit_AsyncFunctionDef = visit_FunctionDef # type: ignore[assignment] @@ -355,10 +388,11 @@ def visit_Call(self, node: ast.Call) -> None: elif short in DRAMATIQ_ENQUEUE and self._looks_like_dramatiq_send(fname): self._enqueue(node, fname, broker="dramatiq") elif short == "on_commit": - self.graph.residuals.append( - f"transaction.on_commit() at {self.rel_path}:{node.lineno} — async work may be hidden in a lambda" - ) - self._enqueue_from_on_commit(node) + self._side_effect_on_commit(node) + elif short in CACHE_METHODS and self._looks_like_cache(fname): + self._cache_call(node, fname, short) + elif short in FLAG_FUNCS or self._looks_like_flag(fname): + self._flag_call(node, fname) elif short in {"raw", "execute"} or (short == "extra" and _kw(node, "where")): self.graph.residuals.append(f"Raw SQL ({fname}) in {self.rel_path}:{node.lineno}") elif short in {"select_related", "prefetch_related"}: @@ -518,6 +552,8 @@ def _view(self, node: ast.ClassDef) -> None: extra["authentication"] = _list_names(stmt.value) elif key == "pagination_class": extra["pagination"] = _name(stmt.value) + elif key == "template_name": + extra["template"] = _const_str(stmt.value) if isinstance(stmt, ast.FunctionDef) and stmt.name == "get_queryset": extra["get_queryset"] = True dumped = ast.dump(stmt) @@ -576,6 +612,10 @@ def _view(self, node: ast.ClassDef) -> None: self.add_edge(view.id, tid, EdgeType.HAS_PERMISSION) if extra.get("pagination"): extra["pagination_sink"] = True + if extra.get("template"): + tmpl = extra["template"] + tn = self.add_node(NodeType.TEMPLATE, Path(tmpl).name, tmpl, node.lineno, {"app": self.app}) + self.add_edge(view.id, tn.id, EdgeType.SERVES_TEMPLATE) if queryset_model: self.add_edge( view.id, @@ -838,8 +878,10 @@ def _maybe_fbv(self, node: ast.FunctionDef) -> None: extra = {"app": self.app, "fbv": True, "decorators": decs} view = self.add_node(NodeType.VIEW, node.name, qname, node.lineno, extra) self._note_cross_app_model_imports(view.id) + self._link_model_queries(view.id, node) - def _maybe_ninja(self, node: ast.FunctionDef) -> None: + def _maybe_ninja(self, node: ast.FunctionDef) -> bool: + hit = False for dec in node.decorator_list: if not isinstance(dec, ast.Call): continue @@ -849,6 +891,7 @@ def _maybe_ninja(self, node: ast.FunctionDef) -> None: continue if not any(tok in fname.lower() for tok in ("api", "router", "ninja")): continue + hit = True route = _const_str(dec.args[0]) if dec.args else None qname = f"{self.app}.{node.name}" view = self.add_node( @@ -867,6 +910,276 @@ def _maybe_ninja(self, node: ast.FunctionDef) -> None: {"app": self.app, "route": route, "ninja": True, "method": short.upper()}, ) self.add_edge(rn.id, view.id, EdgeType.PUBLISHES_ROUTE) + return hit + + def _owner_id(self) -> tuple[NodeType, str]: + owner = self.class_stack[-1] if self.class_stack else Path(self.rel_path).stem + owner_type = NodeType.VIEW + if "management/commands" in self.rel_path: + owner_type = NodeType.MANAGEMENT_COMMAND + owner = Path(self.rel_path).stem + elif Path(self.rel_path).name in {"tasks.py", "actors.py"}: + owner_type = NodeType.TASK + elif Path(self.rel_path).name in {"services.py", "use_cases.py"}: + owner_type = NodeType.SERVICE + return owner_type, f"{self.app}.{owner}" + + def _maybe_fastapi(self, node: ast.FunctionDef) -> None: + blob = " ".join(self.imports.values()) + " " + " ".join(self.from_imports.values()) + if "fastapi" not in blob.lower(): + return + for dec in node.decorator_list: + if not isinstance(dec, ast.Call): + continue + fname = _name(dec.func) or "" + short = fname.split(".")[-1] + if short not in FASTAPI_HTTP: + continue + route = _const_str(dec.args[0]) if dec.args else None + if not route: + continue + qname = f"{self.app}.{node.name}" + extra = { + "app": self.app, + "fastapi": True, + "method": "WS" if short == "websocket" else short.upper(), + "route": route, + } + view = self.add_node(NodeType.VIEW, node.name, qname, node.lineno, extra) + rn = self.add_node( + NodeType.FASTAPI_ROUTE, + f"{extra['method']} {route}", + f"{self.app}:{extra['method']} {route}", + node.lineno, + extra, + ) + self.add_edge(rn.id, view.id, EdgeType.PUBLISHES_ROUTE) + self._link_model_queries(view.id, node) + response_model = _name(_kw(dec, "response_model")) + if response_model: + self.add_edge( + rn.id, + node_id(NodeType.PYDANTIC_MODEL, f"{self.app}.{response_model.split('.')[-1]}"), + EdgeType.USES_SERIALIZER, + confidence=0.85, + ) + + def _graphql_class(self, node: ast.ClassDef) -> bool: + decs = _decorator_names(node) + if any("strawberry" in d.lower() and d.split(".")[-1] in STRAWBERRY_TYPE_DECS | {"type"} for d in decs): + return True + bases = {b.split(".")[-1] for b in _bases(node)} + if not (bases & GRAPHENE_BASES): + return False + blob = " ".join(self.imports.values()) + " " + " ".join(self.from_imports.values()) + return any(tok in blob.lower() for tok in ("graphene", "strawberry", "graphql")) + + def _graphql_type(self, node: ast.ClassDef) -> None: + qname = f"{self.app}.{node.name}" + kind = "type" + for d in _decorator_names(node): + if "strawberry" in d.lower(): + kind = d.split(".")[-1] + break + bases = {b.split(".")[-1] for b in _bases(node)} + if "Mutation" in bases: + kind = "mutation" + extra = {"app": self.app, "kind": kind, "graphql": True} + gql = self.add_node(NodeType.GRAPHQL_TYPE, node.name, qname, node.lineno, extra) + root = node.name in {"Query", "Mutation", "Subscription"} or kind == "mutation" + for stmt in node.body: + fname = None + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + fname = stmt.target.id + elif isinstance(stmt, ast.Assign) and stmt.targets and isinstance(stmt.targets[0], ast.Name): + cand = stmt.targets[0].id + if not cand.startswith("_") and cand not in {"Meta"}: + fname = cand + elif isinstance(stmt, ast.FunctionDef) and stmt.name == "mutate": + op = self.add_node( + NodeType.GRAPHQL_OPERATION, + node.name, + f"graphql.{node.name}", + stmt.lineno, + {"app": self.app, "kind": "mutation"}, + ) + self.add_edge(gql.id, op.id, EdgeType.PUBLISHES_GRAPHQL) + self._link_model_queries(op.id, stmt) + if not fname: + continue + field = self.add_node( + NodeType.GRAPHQL_FIELD, + fname, + f"{qname}.{fname}", + stmt.lineno, + {"app": self.app}, + ) + self.add_edge(gql.id, field.id, EdgeType.HAS_FIELD) + if root and not any( + n.type is NodeType.GRAPHQL_OPERATION and n.name == fname for n in self.graph.nodes + ): + op_kind = ( + "query" + if node.name == "Query" + else ("subscription" if node.name == "Subscription" else "mutation") + ) + op = self.add_node( + NodeType.GRAPHQL_OPERATION, + fname, + f"graphql.{fname}", + stmt.lineno, + {"app": self.app, "kind": op_kind}, + ) + self.add_edge(gql.id, op.id, EdgeType.PUBLISHES_GRAPHQL) + if kind == "mutation" or node.name in {"Query", "Mutation", "Subscription"}: + op_kind = "query" if node.name == "Query" else ("subscription" if node.name == "Subscription" else "mutation") + if not any(n.type is NodeType.GRAPHQL_OPERATION and n.name == node.name for n in self.graph.nodes): + op = self.add_node( + NodeType.GRAPHQL_OPERATION, + node.name, + f"graphql.{node.name}", + node.lineno, + {"app": self.app, "kind": op_kind}, + ) + self.add_edge(gql.id, op.id, EdgeType.PUBLISHES_GRAPHQL) + + def _maybe_graphql_operation(self, node: ast.FunctionDef) -> None: + for dec in node.decorator_list: + dname = _name(dec.func if isinstance(dec, ast.Call) else dec) or "" + if "strawberry" not in dname.lower(): + continue + short = dname.split(".")[-1] + if short not in {"mutation", "field", "subscription"}: + continue + kind = "query" if short == "field" else short + op = self.add_node( + NodeType.GRAPHQL_OPERATION, + node.name, + f"graphql.{node.name}", + node.lineno, + {"app": self.app, "kind": kind, "strawberry": True}, + ) + self._link_model_queries(op.id, node) + if self.class_stack: + self.add_edge( + node_id(NodeType.GRAPHQL_TYPE, f"{self.app}.{self.class_stack[-1]}"), + op.id, + EdgeType.PUBLISHES_GRAPHQL, + ) + + def _pydantic_model(self, node: ast.ClassDef) -> None: + qname = f"{self.app}.{node.name}" + extra: dict = {"app": self.app, "pydantic": True} + model = self.add_node(NodeType.PYDANTIC_MODEL, node.name, qname, node.lineno, extra) + for stmt in node.body: + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + fname = stmt.target.id + field = self.add_node( + NodeType.SERIALIZER_FIELD, + fname, + f"{qname}.{fname}", + stmt.lineno, + {"app": self.app, "pydantic": True}, + ) + self.add_edge(model.id, field.id, EdgeType.HAS_FIELD) + + def _consumer(self, node: ast.ClassDef) -> None: + qname = f"{self.app}.{node.name}" + extra = {"app": self.app, "bases": _bases(node), "websocket": True} + consumer = self.add_node(NodeType.CONSUMER, node.name, qname, node.lineno, extra) + self._link_model_queries(consumer.id, node) + + def _link_model_queries(self, owner_id: str, node: ast.AST) -> None: + seen: set[str] = set() + for child in ast.walk(node): + if not isinstance(child, ast.Attribute) or child.attr != "objects": + continue + model = _name(child.value) + if not model: + continue + short = model.split(".")[-1] + if not short[:1].isupper(): + continue + app = self.app + head = model.split(".")[0] + target = self.from_imports.get(head) or self.imports.get(head) or "" + if "models" in target.split("."): + parts = target.split(".") + if "models" in parts: + idx = parts.index("models") + if idx > 0 and parts[idx - 1] not in {"django", "db"}: + app = parts[idx - 1] + key = f"{app}.{short}" + if key in seen: + continue + seen.add(key) + self.add_edge( + owner_id, + node_id(NodeType.MODEL, key), + EdgeType.QUERIES_MODEL, + confidence=0.8, + ) + + def _looks_like_cache(self, fname: str) -> bool: + low = fname.lower() + return "cache" in low or low.startswith("caches[") + + def _looks_like_flag(self, fname: str) -> bool: + low = fname.lower() + if fname.split(".")[-1] not in FLAG_FUNCS | {"is_active", "is_enabled"}: + return False + return any(tok in low for tok in ("flag", "waffle", "feature", "unleash", "flags")) + + def _cache_call(self, node: ast.Call, fname: str, method: str) -> None: + key = _const_str(node.args[0]) if node.args else None + if not key: + return + qname = f"cache:{key}" + cache = self.add_node( + NodeType.CACHE_KEY, + key, + qname, + node.lineno, + {"app": self.app, "method": method}, + ) + owner_type, owner_q = self._owner_id() + etype = EdgeType.INVALIDATES_CACHE if method in {"set", "delete", "add"} else EdgeType.CALLS + self.add_edge(node_id(owner_type, owner_q), cache.id, etype, extra={"call": fname, "line": node.lineno}) + + def _flag_call(self, node: ast.Call, fname: str) -> None: + name = None + for arg in node.args: + name = _const_str(arg) + if name: + break + if not name: + name = _const_str(_kw(node, "name") or _kw(node, "flag") or _kw(node, "key")) + if not name: + return + flag = self.add_node( + NodeType.FEATURE_FLAG, + name, + f"flag:{name}", + node.lineno, + {"app": self.app, "call": fname}, + ) + owner_type, owner_q = self._owner_id() + self.add_edge(node_id(owner_type, owner_q), flag.id, EdgeType.CHECKS_FLAG, extra={"line": node.lineno}) + + def _side_effect_on_commit(self, node: ast.Call) -> None: + self.graph.residuals.append( + f"transaction.on_commit() at {self.rel_path}:{node.lineno} — async work may be hidden in a lambda" + ) + owner_type, owner_q = self._owner_id() + effect = self.add_node( + NodeType.SIDE_EFFECT, + "on_commit", + f"{owner_q}:on_commit:{node.lineno}", + node.lineno, + {"app": self.app, "kind": "on_commit"}, + ) + self.add_edge(node_id(owner_type, owner_q), effect.id, EdgeType.ON_COMMIT, extra={"line": node.lineno}) + self._enqueue_from_on_commit(node) def _maybe_receiver(self, node: ast.FunctionDef) -> None: for dec in node.decorator_list: @@ -987,19 +1300,24 @@ def _url_path(self, node: ast.Call) -> None: if route is None: return view_name = None + asgi = False if len(node.args) > 1: view_expr = node.args[1] - if isinstance(view_expr, ast.Call) and _name(view_expr.func) and "as_view" in (_name(view_expr.func) or ""): - view_name = (_name(view_expr.func) or "").replace(".as_view", "") - # mapping dict for viewsets + func_name = _name(view_expr.func) if isinstance(view_expr, ast.Call) else _name(view_expr) + func_name = func_name or "" + if isinstance(view_expr, ast.Call) and "as_view" in func_name: + view_name = func_name.replace(".as_view", "") if view_expr.args and isinstance(view_expr.args[0], ast.Dict): for k, v in zip(view_expr.args[0].keys, view_expr.args[0].values, strict=True): method = _const_str(k) action = _name(v) if method and action: pass + elif isinstance(view_expr, ast.Call) and "as_asgi" in func_name: + view_name = func_name.replace(".as_asgi", "") + asgi = True else: - view_name = _name(view_expr) + view_name = func_name or _name(view_expr) name = _const_str(_kw(node, "name")) include_mod = None if len(node.args) > 1: @@ -1009,21 +1327,25 @@ def _url_path(self, node: ast.Call) -> None: if fn.split(".")[-1] == "include": include_mod = self._include_target(view_expr) view_name = None + websocket = asgi or (route or "").lstrip("/").startswith("ws") extra = { "app": self.app, "route": route, "url_name": name, "view": view_name, "include": include_mod, + "websocket": websocket, } display, qname = self._route_identity(route, include_mod, name, node.lineno) - route_node = self.add_node(NodeType.ROUTE, display, qname, node.lineno, extra) + ntype = NodeType.WEBSOCKET_ROUTE if websocket else NodeType.ROUTE + route_node = self.add_node(ntype, display, qname, node.lineno, extra) if name: un = self.add_node(NodeType.URL_NAME, name, name, node.lineno, extra) self.add_edge(route_node.id, un.id, EdgeType.BELONGS_TO) if view_name: - vq = f"{self.app}.{view_name.split('.')[-1]}" - self.add_edge(route_node.id, node_id(NodeType.VIEW, vq), EdgeType.PUBLISHES_ROUTE) + short = view_name.split(".")[-1] + target_type = NodeType.CONSUMER if websocket else NodeType.VIEW + self.add_edge(route_node.id, node_id(target_type, f"{self.app}.{short}"), EdgeType.PUBLISHES_ROUTE) def _router_register(self, node: ast.Call) -> None: if len(node.args) < 2: diff --git a/src/loadpath/extractors/react.py b/src/loadpath/extractors/react.py index 114009a..4c5af2b 100644 --- a/src/loadpath/extractors/react.py +++ b/src/loadpath/extractors/react.py @@ -66,6 +66,12 @@ BOUNDARY_RE = re.compile(r"""\b(ErrorBoundary|Suspense)\b""") RTK_RE = re.compile(r"""createApi\s*\(\s*\{""") FEATURE_FOLDER_RE = re.compile(r"""features/([^/]+)""") +GQL_DOC_RE = re.compile( + r"""(?:gql|graphql)\s*(?:<[^>]*>)?\s*`([^`]+)`""", + re.S, +) +GQL_OP_RE = re.compile(r"""\b(query|mutation|subscription)\s+([A-Za-z_]\w*)""") +GQL_SELECTION_RE = re.compile(r"""\{\s*([A-Za-z_]\w*)""") def _feature_from_path(rel: str) -> str | None: @@ -303,6 +309,32 @@ def edge(src: str, dst: str, etype: EdgeType, confidence: float = 1.0, extra: di rn = add(NodeType.REACT_ROUTE, rpath, f"react.route:{rpath}", line, {"element": page_name}) edge(rn.id, node_id(NodeType.PAGE, f"{feature or 'app'}.{page_name}"), EdgeType.PUBLISHES_ROUTE) + for m in GQL_DOC_RE.finditer(source): + body = m.group(1) + line = source[: m.start()].count("\n") + 1 + ops = GQL_OP_RE.findall(body) + if not ops: + ops = [("query", f"{stem}Query")] + selections = GQL_SELECTION_RE.findall(body) + for kind, op_name in ops: + extra = { + "kind": kind.lower(), + "feature": feature, + "client": True, + "selections": [s for s in selections if s not in {"query", "mutation", "subscription"}][:8], + } + op = add( + NodeType.GRAPHQL_OPERATION, + op_name, + f"graphql.{op_name}", + line, + extra, + ) + for owner in hooks or components: + edge(owner.id, op.id, EdgeType.CALLS) + if feature: + edge(node_id(NodeType.FEATURE_MODULE, f"features.{feature}"), op.id, EdgeType.CALLS) + for m in ZOD_RE.finditer(source): name, body = m.group(1), m.group(2) line = source[: m.start()].count("\n") + 1 diff --git a/src/loadpath/extractors/templates.py b/src/loadpath/extractors/templates.py new file mode 100644 index 0000000..a332647 --- /dev/null +++ b/src/loadpath/extractors/templates.py @@ -0,0 +1,106 @@ +"""Django templates and HTMX attributes as sinks on the load path.""" + +from __future__ import annotations + +import re +from pathlib import Path + +from loadpath.config import LoadpathConfig +from loadpath.types import Edge, EdgeType, ExtractedGraph, Node, NodeType, node_id + +HTMX_RE = re.compile( + r"""\bhx-(get|post|put|patch|delete)\s*=\s*["']([^"']+)["']""", + re.I, +) +URL_TAG_RE = re.compile(r"""\{\%\s*url\s+['"]([^'"]+)['"]""") +INCLUDE_RE = re.compile(r"""\{\%\s*include\s+['"]([^'"]+)['"]""") +EXTENDS_RE = re.compile(r"""\{\%\s*extends\s+['"]([^'"]+)['"]""") +BLOCK_RE = re.compile(r"""\{\%\s*block\s+(\w+)""") + + +def _app_from_template(rel: str) -> str: + parts = Path(rel).parts + if "templates" in parts: + idx = parts.index("templates") + if idx > 0: + return parts[idx - 1] + if idx + 1 < len(parts): + return parts[idx + 1] + if len(parts) >= 2: + return parts[-2] + return "unknown" + + +def template_qname(rel: str) -> str: + """Django template name: path after `templates/`, else the relative file path.""" + parts = Path(rel.replace("\\", "/")).parts + if "templates" in parts: + idx = parts.index("templates") + tail = "/".join(parts[idx + 1 :]) + if tail: + return tail + return rel.replace("\\", "/") + + +def extract_template_file(rel_path: str, source: str, config: LoadpathConfig) -> ExtractedGraph: + rel = rel_path.replace("\\", "/") + graph = ExtractedGraph() + app = _app_from_template(rel) + context = config.context_for_django_app(app) + name = Path(rel).name + qname = template_qname(rel) + extra = { + "app": app, + "blocks": BLOCK_RE.findall(source)[:12], + "htmx": bool(HTMX_RE.search(source)), + } + template = Node( + id=node_id(NodeType.TEMPLATE, qname), + type=NodeType.TEMPLATE, + name=name, + qualified_name=qname, + file_path=rel, + start_line=1, + context=context, + extra=extra, + ) + graph.nodes.append(template) + + for tag in URL_TAG_RE.findall(source): + graph.edges.append( + Edge( + src=template.id, + dst=node_id(NodeType.URL_NAME, tag), + type=EdgeType.CALLS, + confidence=0.8, + extra={"url_name": tag}, + ) + ) + + for other in INCLUDE_RE.findall(source) + EXTENDS_RE.findall(source): + graph.edges.append( + Edge( + src=template.id, + dst=node_id(NodeType.TEMPLATE, other), + type=EdgeType.RENDERS, + confidence=0.9, + extra={"include": other}, + ) + ) + + for match in HTMX_RE.finditer(source): + method, url = match.group(1).upper(), match.group(2) + call_name = f"{method} {url}" + call = Node( + id=node_id(NodeType.HTMX_CALL, f"{rel}:{call_name}"), + type=NodeType.HTMX_CALL, + name=call_name, + qualified_name=f"{rel}:{call_name}", + file_path=rel, + start_line=source[: match.start()].count("\n") + 1, + context=context, + extra={"app": app, "method": method, "url": url}, + ) + graph.nodes.append(call) + graph.edges.append(Edge(src=template.id, dst=call.id, type=EdgeType.HTMX_CALLS)) + return graph diff --git a/src/loadpath/graph/store.py b/src/loadpath/graph/store.py index de2cd58..174585b 100644 --- a/src/loadpath/graph/store.py +++ b/src/loadpath/graph/store.py @@ -72,6 +72,8 @@ def __init__(self, db_path: Path) -> None: self.conn = sqlite3.connect(str(self.db_path)) self.conn.row_factory = sqlite3.Row self.conn.execute("PRAGMA foreign_keys = ON") + self.conn.execute("PRAGMA journal_mode = WAL") + self.conn.execute("PRAGMA synchronous = NORMAL") self.conn.executescript(SCHEMA) self.conn.commit() @@ -131,12 +133,13 @@ def prune_dangling_edges(self) -> None: "DELETE FROM edges WHERE src NOT IN (SELECT id FROM nodes) OR dst NOT IN (SELECT id FROM nodes)" ) - def upsert_graph(self, graph: ExtractedGraph) -> None: + def upsert_graph(self, graph: ExtractedGraph, *, commit: bool = True) -> None: for node in graph.nodes: self.upsert_node(node) for edge in graph.edges: self.upsert_edge(edge) - self.conn.commit() + if commit: + self.conn.commit() def upsert_node(self, node: Node) -> None: existing = self.get_node(node.id) @@ -300,11 +303,25 @@ def get_review(self, review_id: str) -> dict[str, Any] | None: "payload": json.loads(row["payload"]), } - def list_reviews(self) -> list[dict[str, Any]]: - rows = self.conn.execute( - "SELECT id, created_at, repo_root, base_ref, head_ref FROM reviews ORDER BY created_at DESC" - ).fetchall() - return [dict(r) for r in rows] + def list_reviews(self, *, include_payload: bool = False, limit: int = 40) -> list[dict[str, Any]]: + sql = "SELECT id, created_at, repo_root, base_ref, head_ref" + if include_payload: + sql += ", payload" + sql += " FROM reviews ORDER BY created_at DESC LIMIT ?" + rows = self.conn.execute(sql, (max(1, min(limit, 200)),)).fetchall() + out: list[dict[str, Any]] = [] + for row in rows: + item = { + "id": row["id"], + "created_at": row["created_at"], + "repo_root": row["repo_root"], + "base_ref": row["base_ref"], + "head_ref": row["head_ref"], + } + if include_payload: + item["payload"] = json.loads(row["payload"]) + out.append(item) + return out def counts(self) -> dict[str, int]: n = self.conn.execute("SELECT COUNT(*) AS c FROM nodes").fetchone()["c"] diff --git a/src/loadpath/index.py b/src/loadpath/index.py index 91bb699..15bcf78 100644 --- a/src/loadpath/index.py +++ b/src/loadpath/index.py @@ -1,21 +1,41 @@ from __future__ import annotations import hashlib +import multiprocessing +import os +import sys +import time +from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed +from concurrent.futures.process import BrokenProcessPool from datetime import datetime, timezone from pathlib import Path +from typing import Any, Callable from loadpath.config import LoadpathConfig, load_config from loadpath.extractors.django import extract_django_file from loadpath.extractors.react import extract_react_file +from loadpath.extractors.templates import extract_template_file from loadpath.extractors.django_boot import try_boot_models from loadpath.graph.store import GraphStore from loadpath.stitch.openapi import stitch from loadpath.types import GENERATED_PATH_MARKERS, ExtractedGraph, Node, NodeType, node_id PY_SKIP = {"migrations"} # still extract migrations, just not skip -INDEX_EXTENSIONS = {".py", ".ts", ".tsx", ".js", ".jsx"} +INDEX_EXTENSIONS = {".py", ".ts", ".tsx", ".js", ".jsx", ".html", ".htm"} # Bump when extractor/stitch node identity changes so incremental indexes rebuild. -INDEX_REVISION = "10" +INDEX_REVISION = "11" +_UPSERT_BATCH = 25 + +ProgressCallback = Callable[[dict[str, Any]], None] + + +class SourceFile: + __slots__ = ("path", "rel", "digest") + + def __init__(self, path: Path, rel: str, digest: str) -> None: + self.path = path + self.rel = rel + self.digest = digest def default_db_path(repo_root: Path) -> Path: @@ -65,9 +85,15 @@ def file_hash(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() +def _hash_job(path: str) -> str: + return file_hash(Path(path)) + + def language_for(path: Path) -> str: if path.suffix == ".py": return "python" + if path.suffix in {".html", ".htm"}: + return "html" if path.suffix in {".ts", ".tsx"}: return "typescript" return "javascript" @@ -96,15 +122,107 @@ def _sidecar_digest(repo_root: Path, config: LoadpathConfig) -> str: return digest.hexdigest() -def index_drift(store: GraphStore, repo_root: Path, config: LoadpathConfig) -> dict: - files = iter_source_files(repo_root, config) - present = {path.relative_to(repo_root).as_posix() for path in files} +def _emit(progress: ProgressCallback | None, event: dict[str, Any]) -> None: + if progress is None: + return + progress(event) + + +def _elapsed_ms(started: float) -> int: + return int((time.monotonic() - started) * 1000) + + +def default_workers(n_files: int) -> int: + env = os.environ.get("LOADPATH_INDEX_JOBS", "").strip() + if env.isdigit(): + return max(1, int(env)) + if getattr(sys, "frozen", False): + return 1 + if n_files < 80: + return 1 + cpu = os.cpu_count() or 2 + return max(1, min(8, cpu, n_files)) + + +def list_source_files( + repo_root: Path, + config: LoadpathConfig, + *, + hash_workers: int | None = None, + progress: ProgressCallback | None = None, + started: float | None = None, +) -> list[SourceFile]: + """Scan once and hash each source file. Hashing is I/O-bound, so threads help.""" + started = time.monotonic() if started is None else started + paths = iter_source_files(repo_root, config) + _emit( + progress, + { + "phase": "scan", + "done": 0, + "total": len(paths), + "elapsed_ms": _elapsed_ms(started), + "message": f"Scanning {len(paths)} source files", + }, + ) + if not paths: + return [] + workers = hash_workers if hash_workers is not None else (1 if len(paths) < 8 else min(8, len(paths))) + out: list[SourceFile] = [] + if workers <= 1: + for i, path in enumerate(paths, start=1): + rel = path.relative_to(repo_root).as_posix() + out.append(SourceFile(path, rel, file_hash(path))) + if i == 1 or i == len(paths) or i % 25 == 0: + _emit( + progress, + { + "phase": "scan", + "done": i, + "total": len(paths), + "current": rel, + "elapsed_ms": _elapsed_ms(started), + "message": f"Hashed {i}/{len(paths)} files", + }, + ) + return out + with ThreadPoolExecutor(max_workers=workers) as pool: + futs = {pool.submit(_hash_job, str(path)): path for path in paths} + done = 0 + for fut in as_completed(futs): + path = futs[fut] + rel = path.relative_to(repo_root).as_posix() + out.append(SourceFile(path, rel, fut.result())) + done += 1 + if done == 1 or done == len(paths) or done % 25 == 0: + _emit( + progress, + { + "phase": "scan", + "done": done, + "total": len(paths), + "current": rel, + "elapsed_ms": _elapsed_ms(started), + "message": f"Hashed {done}/{len(paths)} files", + }, + ) + by_path = {src.path: src for src in out} + return [by_path[path] for path in paths] + + +def index_drift( + store: GraphStore, + repo_root: Path, + config: LoadpathConfig, + files: list[SourceFile] | None = None, +) -> dict: + files = files if files is not None else list_source_files(repo_root, config) + present = {src.rel for src in files} indexed = set(store.indexed_paths()) changed: list[str] = [] - for path in files: - rel = path.relative_to(repo_root).as_posix() - if store.file_hash(rel) != file_hash(path): - changed.append(rel) + for src in files: + if store.file_hash(src.rel) != src.digest: + changed.append(src.rel) added = sorted(present - indexed) deleted = sorted(indexed - present) config_changed = _sidecar_digest(repo_root, config) != (store.get_meta("sidecar_hash") or "") @@ -133,6 +251,121 @@ def _boot_status(residuals: list[str], enabled: bool) -> tuple[str, str]: return "skipped", "django.setup() did not run" +def _extract_one(rel: str, path: Path, config: LoadpathConfig) -> ExtractedGraph: + source = path.read_text(encoding="utf-8", errors="replace") + if path.suffix in {".html", ".htm"}: + graph = extract_template_file(rel, source, config) + elif path.suffix == ".py": + graph = extract_django_file(rel, source, config) + else: + graph = extract_react_file(rel, source, config) + _assign_contexts(graph, config, rel) + return graph + + +def _extract_job(payload: tuple[str, str, LoadpathConfig]) -> tuple[str, ExtractedGraph | None, str | None]: + """Picklable worker: extract one file. Must stay at module level for spawn.""" + rel, path, config = payload + try: + return rel, _extract_one(rel, Path(path), config), None + except Exception as exc: # pragma: no cover - surfaced as residual + return rel, None, f"{type(exc).__name__}: {exc}" + + +def _extract_sequential( + files: list[SourceFile], + config: LoadpathConfig, + *, + started: float, + progress: ProgressCallback | None, + workers: int = 1, +) -> list[tuple[SourceFile, ExtractedGraph | None, str | None]]: + results: list[tuple[SourceFile, ExtractedGraph | None, str | None]] = [] + total = len(files) + for i, src in enumerate(files, start=1): + _emit( + progress, + { + "phase": "extract", + "done": i - 1, + "total": total, + "current": src.rel, + "workers": workers, + "elapsed_ms": _elapsed_ms(started), + "message": f"Extracting {src.rel} ({i}/{total})", + }, + ) + try: + results.append((src, _extract_one(src.rel, src.path, config), None)) + except Exception as exc: + results.append((src, None, f"{type(exc).__name__}: {exc}")) + _emit( + progress, + { + "phase": "extract", + "done": i, + "total": total, + "current": src.rel, + "workers": workers, + "elapsed_ms": _elapsed_ms(started), + "message": f"Extracted {src.rel} ({i}/{total})", + }, + ) + return results + + +def _extract_parallel( + files: list[SourceFile], + config: LoadpathConfig, + *, + workers: int, + started: float, + progress: ProgressCallback | None, +) -> list[tuple[SourceFile, ExtractedGraph | None, str | None]]: + by_rel = {src.rel: src for src in files} + total = len(files) + found: dict[str, tuple[ExtractedGraph | None, str | None]] = {} + ctx = multiprocessing.get_context("spawn") + _emit( + progress, + { + "phase": "extract", + "done": 0, + "total": total, + "workers": workers, + "elapsed_ms": _elapsed_ms(started), + "message": f"Extracting {total} files with {workers} workers", + }, + ) + with ProcessPoolExecutor(max_workers=workers, mp_context=ctx) as pool: + futs = { + pool.submit(_extract_job, (src.rel, str(src.path), config)): src.rel for src in files + } + done = 0 + for fut in as_completed(futs): + rel = futs[fut] + try: + _, graph, err = fut.result() + except Exception as exc: + graph, err = None, f"{type(exc).__name__}: {exc}" + found[rel] = (graph, err) + done += 1 + status = "Extracted" if graph is not None else "Failed" + _emit( + progress, + { + "phase": "extract", + "done": done, + "total": total, + "current": rel, + "workers": workers, + "elapsed_ms": _elapsed_ms(started), + "message": f"{status} {rel} ({done}/{total})", + }, + ) + return [(src, found[src.rel][0], found[src.rel][1]) for src in files] + + def index_repo( repo_root: Path, db_path: Path | None = None, @@ -140,8 +373,11 @@ def index_repo( incremental: bool = True, *, draft_config: bool = False, + progress: ProgressCallback | None = None, + workers: int | None = None, ) -> GraphStore: repo_root = repo_root.resolve() + started = time.monotonic() if draft_config: from loadpath.detect import write_draft_config @@ -152,41 +388,129 @@ def index_repo( store = GraphStore(db_path) store.set_meta("repo_root", str(repo_root)) - drift = index_drift(store, repo_root, config) + _emit( + progress, + { + "phase": "scan", + "done": 0, + "total": 0, + "elapsed_ms": 0, + "message": "Scanning source files", + }, + ) + files = list_source_files(repo_root, config, progress=progress, started=started) + n_cpu = workers if workers is not None else default_workers(len(files)) + drift = index_drift(store, repo_root, config, files=files) revision_changed = (store.get_meta("index_revision") or "") != INDEX_REVISION if incremental and store.file_count() > 0 and not drift["stale"] and not revision_changed: store.set_meta("reindex_skipped", "1") store.set_meta("files_extracted", "0") store.set_meta("files_skipped", str(drift["indexed_count"])) + store.set_meta("index_workers", "0") + store.set_meta("index_elapsed_ms", str(_elapsed_ms(started))) store.conn.commit() + _emit( + progress, + { + "phase": "skipped", + "done": drift["indexed_count"], + "total": drift["indexed_count"], + "skipped": drift["indexed_count"], + "workers": 0, + "elapsed_ms": _elapsed_ms(started), + "message": f"Index already current ({drift['indexed_count']} files unchanged)", + }, + ) return store residuals: list[str] = [] skipped: set[str] = set() - files = iter_source_files(repo_root, config) - present = {path.relative_to(repo_root).as_posix() for path in files} + present = {src.rel for src in files} extracted = 0 + used_workers = 1 for stale in store.indexed_paths(): if stale not in present: store.delete_file_nodes(stale) - for path in files: - rel = path.relative_to(repo_root).as_posix() - digest = file_hash(path) - if incremental and not revision_changed and store.file_hash(rel) == digest: - skipped.add(rel) + + to_extract: list[SourceFile] = [] + for src in files: + if incremental and not revision_changed and store.file_hash(src.rel) == src.digest: + skipped.add(src.rel) continue - store.delete_file_nodes(rel, drop_incoming=False) - source = path.read_text(encoding="utf-8", errors="replace") - if path.suffix == ".py": - graph = extract_django_file(rel, source, config) + to_extract.append(src) + + _emit( + progress, + { + "phase": "extract", + "done": 0, + "total": len(to_extract), + "workers": n_cpu, + "skipped": len(skipped), + "elapsed_ms": _elapsed_ms(started), + "message": ( + f"Extracting {len(to_extract)} of {len(files)} files" + if to_extract + else f"Nothing to extract ({len(skipped)} unchanged)" + ), + }, + ) + + results: list[tuple[SourceFile, ExtractedGraph | None, str | None]] = [] + if to_extract: + if n_cpu > 1 and len(to_extract) > 1: + try: + results = _extract_parallel( + to_extract, + config, + workers=n_cpu, + started=started, + progress=progress, + ) + used_workers = n_cpu + if results and all(graph is None for _, graph, _err in results): + results = _extract_sequential( + to_extract, config, started=started, progress=progress, workers=1 + ) + used_workers = 1 + except (BrokenProcessPool, OSError, PermissionError, RuntimeError): + results = _extract_sequential( + to_extract, config, started=started, progress=progress, workers=1 + ) + used_workers = 1 else: - graph = extract_react_file(rel, source, config) - _assign_contexts(graph, config, rel) - store.upsert_file(rel, digest, language_for(path)) - store.upsert_graph(graph) + results = _extract_sequential( + to_extract, config, started=started, progress=progress, workers=1 + ) + used_workers = 1 + + pending = 0 + for src, graph, err in results: + if err or graph is None: + residuals.append(f"Failed to extract {src.rel}: {err or 'unknown error'}") + continue + store.delete_file_nodes(src.rel, drop_incoming=False) + store.upsert_file(src.rel, src.digest, language_for(src.path)) + store.upsert_graph(graph, commit=False) residuals.extend(graph.residuals) extracted += 1 + pending += 1 + if pending >= _UPSERT_BATCH: + store.conn.commit() + pending = 0 + if pending: + store.conn.commit() + _emit( + progress, + { + "phase": "boot", + "done": 0, + "total": 1, + "elapsed_ms": _elapsed_ms(started), + "message": "Booting Django models (optional)", + }, + ) boot = ExtractedGraph() if config.boot_django: boot = try_boot_models(repo_root, config) @@ -194,6 +518,16 @@ def index_repo( residuals.extend(boot.residuals) boot_state, boot_detail = _boot_status(boot.residuals, config.boot_django) + _emit( + progress, + { + "phase": "stitch", + "done": 0, + "total": 1, + "elapsed_ms": _elapsed_ms(started), + "message": "Stitching contracts (OpenAPI, GraphQL, HTMX)", + }, + ) _ensure_context_nodes(store, config) stitch_residuals = stitch(store, config, repo_root) store.prune_dangling_edges() @@ -215,6 +549,7 @@ def index_repo( residuals = deduped else: residuals.extend(stitch_residuals) + elapsed = _elapsed_ms(started) store.set_meta("residuals", "\n".join(residuals)) store.set_meta("indexed_at", datetime.now(timezone.utc).isoformat()) store.set_meta("incremental", "1" if incremental else "0") @@ -226,7 +561,26 @@ def index_repo( store.set_meta("config_hash", _config_digest(repo_root)) store.set_meta("sidecar_hash", _sidecar_digest(repo_root, config)) store.set_meta("index_revision", INDEX_REVISION) + store.set_meta("index_workers", str(used_workers)) + store.set_meta("index_elapsed_ms", str(elapsed)) store.conn.commit() + _emit( + progress, + { + "phase": "done", + "done": extracted, + "total": len(files), + "skipped": len(skipped), + "workers": used_workers, + "elapsed_ms": elapsed, + "errors": sum(1 for line in residuals if line.startswith("Failed to extract ")), + "message": ( + f"Indexed {extracted} files ({len(skipped)} unchanged) in {elapsed}ms" + if extracted + else f"Indexed 0 files ({len(skipped)} unchanged) in {elapsed}ms" + ), + }, + ) return store @@ -234,7 +588,7 @@ def _assign_contexts(graph: ExtractedGraph, config: LoadpathConfig, rel: str) -> for node in graph.nodes: if node.context: continue - if node.type.value.startswith("django.") or node.type.value.startswith("react."): + if node.type.value.startswith(("django.", "react.", "fastapi.", "graphql.")): if rel.endswith(".py"): app = (node.extra or {}).get("app") if app: diff --git a/src/loadpath/mcp/compact.py b/src/loadpath/mcp/compact.py index 00f9b24..5df0bcf 100644 --- a/src/loadpath/mcp/compact.py +++ b/src/loadpath/mcp/compact.py @@ -42,6 +42,10 @@ def compact_review(review: dict[str, Any]) -> dict[str, Any]: "depth_note": review.get("depth_note"), "residuals": (review.get("residuals") or [])[:12], "low_risk": review.get("low_risk"), + "contract_break": review.get("contract_break"), + "auth": review.get("auth"), + "suggested_tests": (review.get("suggested_tests") or [])[:6], + "trend": review.get("trend"), "index": review.get("index"), "workspace": review.get("workspace"), } diff --git a/src/loadpath/mcp/server.py b/src/loadpath/mcp/server.py index abdd217..5546cac 100644 --- a/src/loadpath/mcp/server.py +++ b/src/loadpath/mcp/server.py @@ -65,16 +65,24 @@ def _register_tools(mcp: MCPServer) -> None: )(tools.detect_repo) mcp.tool( name="list_pull_requests", - description="List GitHub or Bitbucket pull requests using tokens stored in Loadpath settings.", + description="List GitHub, GitLab, or Bitbucket pull requests using tokens stored in Loadpath settings.", )(tools.list_pull_requests) mcp.tool( name="list_remote_repositories", - description="List GitHub or Bitbucket repositories the signed-in account can access.", + description="List GitHub, GitLab, or Bitbucket repositories the signed-in account can access.", )(tools.list_remote_repositories) mcp.tool( name="post_review_comment", description="Upsert the single Loadpath markdown brief on a pull request (updated in place).", )(tools.post_review_comment) + mcp.tool( + name="what_if", + description="Simulate a change from one indexed node and list sinks, auth, and suggested tests. No git range.", + )(tools.what_if) + mcp.tool( + name="review_pull_request", + description="Fetch a GitHub/GitLab/Bitbucket PR into a local clone and review the three-dot range.", + )(tools.review_pull_request) def create_mcp_server( diff --git a/src/loadpath/mcp/tools.py b/src/loadpath/mcp/tools.py index 4357bca..8104dc9 100644 --- a/src/loadpath/mcp/tools.py +++ b/src/loadpath/mcp/tools.py @@ -20,12 +20,22 @@ def _with_scm(provider: str, fn: Callable[[Any], _T]) -> _T | dict[str, str]: settings = AppSettings.load() - token = settings.github_token if provider == "github" else settings.bitbucket_token - username = settings.bitbucket_username + token = "" + username = "" + host = "" + if provider == "github": + token = settings.github_token + host = settings.github_host + elif provider == "gitlab": + token = settings.gitlab_token + host = settings.gitlab_host + else: + token = settings.bitbucket_token + username = settings.bitbucket_username if not token: return {"error": f"No {provider} token configured in Loadpath settings"} try: - return fn(provider_for(provider, token, username=username)) + return fn(provider_for(provider, token, username=username, host=host)) except httpx.HTTPStatusError as exc: if ( provider == "bitbucket" @@ -120,6 +130,7 @@ def review_range( reindex: bool = True, incremental: bool = True, three_dot: bool = True, + dirty: bool = False, ) -> dict[str, Any]: """Review a git range as a load path: sinks, confidence, reviewers. Not hunk comments.""" root = _repo(repo_path) @@ -133,6 +144,7 @@ def review_range( reindex=reindex, incremental=incremental, three_dot=three_dot, + dirty=dirty, ) except FileNotFoundError as exc: return {"error": str(exc)} @@ -156,7 +168,7 @@ def list_pull_requests( repo: str, state: str = "open", ) -> dict[str, Any]: - """List pull requests from GitHub or Bitbucket using tokens in ~/.loadpath/settings.json.""" + """List pull requests from GitHub, GitLab, or Bitbucket using tokens in ~/.loadpath/settings.json.""" result = _with_scm(provider, lambda scm: scm.list_pull_requests(repo, state=state)) if isinstance(result, dict) and result.get("error"): return result @@ -164,7 +176,7 @@ def list_pull_requests( def list_remote_repositories(provider: str) -> dict[str, Any]: - """List GitHub or Bitbucket repositories the saved token can access.""" + """List GitHub, GitLab, or Bitbucket repositories the saved token can access.""" settings = AppSettings.load() result = _with_scm(provider, lambda scm: (scm.list_repositories(), scm.current_user())) if isinstance(result, dict) and result.get("error"): @@ -188,3 +200,47 @@ def post_review_comment( lambda scm: scm.upsert_pull_request_comment(repo, number, markdown), ) return result + + +def what_if(repo_path: str, node_id: str) -> dict[str, Any]: + """Walk sinks from one indexed node without a git range.""" + root = _repo(repo_path) + if isinstance(root, dict): + return root + from loadpath.review.whatif import simulate_node + + try: + payload = simulate_node(root, node_id) + except (FileNotFoundError, KeyError) as exc: + return {"error": str(exc)} + payload.pop("nodes", None) + payload.pop("edges", None) + return payload + + +def review_pull_request( + provider: str, + repo: str, + number: int, + repo_path: str | None = None, +) -> dict[str, Any]: + """Fetch a PR/MR into a local clone and review the three-dot range.""" + from loadpath.providers.pr_fetch import prepare_pull_request + + try: + prepared = prepare_pull_request(provider, repo, number, repo_path=repo_path) + review = run_review( + Path(prepared["repo_path"]), + base=prepared["base"], + head=prepared["head"], + reindex=True, + incremental=True, + three_dot=True, + ) + except Exception as exc: # noqa: BLE001 + return {"error": str(exc)} + review["markdown"] = render_markdown(review) + register_workspace(Path(prepared["repo_path"])) + compact = compact_review(review) + compact["pull_request"] = prepared + return compact diff --git a/src/loadpath/progress.py b/src/loadpath/progress.py new file mode 100644 index 0000000..7c3a78f --- /dev/null +++ b/src/loadpath/progress.py @@ -0,0 +1,44 @@ +"""In-memory index progress for the local API/UI (and tests).""" + +from __future__ import annotations + +import threading +import time +from pathlib import Path +from typing import Any, Callable + +_lock = threading.Lock() +_STATE: dict[str, dict[str, Any]] = {} + + +def progress_key(repo_root: Path | str) -> str: + return str(Path(repo_root).expanduser().resolve()) + + +def record_progress(repo_root: Path | str, event: dict[str, Any]) -> None: + key = progress_key(repo_root) + payload = {**event, "repo_path": key, "updated_at": time.time()} + with _lock: + _STATE[key] = payload + + +def read_progress(repo_root: Path | str) -> dict[str, Any]: + key = progress_key(repo_root) + with _lock: + current = _STATE.get(key) + if current is None: + return { + "phase": "idle", + "done": 0, + "total": 0, + "message": "No index in progress", + "repo_path": key, + } + return dict(current) + + +def progress_callback(repo_root: Path | str) -> Callable[[dict[str, Any]], None]: + def _cb(event: dict[str, Any]) -> None: + record_progress(repo_root, event) + + return _cb diff --git a/src/loadpath/providers/oauth.py b/src/loadpath/providers/oauth.py index 9107a93..69cb59d 100644 --- a/src/loadpath/providers/oauth.py +++ b/src/loadpath/providers/oauth.py @@ -21,6 +21,7 @@ BITBUCKET_AUTHORIZE_URL = "https://bitbucket.org/site/oauth2/authorize" BITBUCKET_TOKEN_URL = "https://bitbucket.org/site/oauth2/access_token" BITBUCKET_SCOPES = "account repository pullrequest" +GITLAB_SCOPES = "api read_user read_repository" LOOPBACK_HOSTS = frozenset({"127.0.0.1", "localhost", "::1", "testserver", "testclient"}) GITHUB_DEVICE_HOSTS = frozenset({"github.com", "www.github.com"}) MAX_PENDING = 8 @@ -44,25 +45,30 @@ def is_loopback_request(host_header: str, origin: str = "") -> bool: return _hostname(host_header) in LOOPBACK_HOSTS -def _is_github_device_url(url: str) -> bool: +def _is_github_device_url(url: str, host: str = "github.com") -> bool: try: parsed = urlparse(url) except ValueError: return False if parsed.scheme != "https" or parsed.username or parsed.password: return False - if (parsed.hostname or "").lower() not in GITHUB_DEVICE_HOSTS: + hostname = (parsed.hostname or "").lower() + allowed = {host.lower(), f"www.{host.lower()}"} + if host.lower() in {"github.com", "www.github.com"}: + allowed |= GITHUB_DEVICE_HOSTS + if hostname not in allowed: return False return parsed.path.rstrip("/") == "/login/device" -def github_device_urls(data: dict[str, Any], user_code: str) -> tuple[str, str]: - verification = data.get("verification_uri") or "https://github.com/login/device" - if not _is_github_device_url(verification): +def github_device_urls(data: dict[str, Any], user_code: str, host: str = "github.com") -> tuple[str, str]: + origin = f"https://{host}" + verification = data.get("verification_uri") or f"{origin}/login/device" + if not _is_github_device_url(verification, host): raise ValueError("GitHub returned an unexpected verification URL") complete = data.get("verification_uri_complete") or "" - if not _is_github_device_url(complete): - complete = f"https://github.com/login/device?user_code={user_code}" + if not _is_github_device_url(complete, host): + complete = f"{origin}/login/device?user_code={user_code}" return verification, complete @@ -78,6 +84,22 @@ def _remember_pending(flow_id: str, row: dict[str, Any]) -> None: _pending[flow_id] = row +def github_web_host(settings: AppSettings | None = None) -> str: + settings = settings or AppSettings.load() + host = (os.environ.get("LOADPATH_GITHUB_HOST") or settings.github_host or "github.com").strip() + host = host.removeprefix("https://").removeprefix("http://").strip("/") + if host in {"", "api.github.com", "www.github.com"}: + return "github.com" + return host + + +def gitlab_web_host(settings: AppSettings | None = None) -> str: + settings = settings or AppSettings.load() + host = (os.environ.get("LOADPATH_GITLAB_HOST") or settings.gitlab_host or "gitlab.com").strip() + host = host.removeprefix("https://").removeprefix("http://").strip("/") + return host or "gitlab.com" + + def github_client_id(settings: AppSettings | None = None) -> str: settings = settings or AppSettings.load() return (os.environ.get("LOADPATH_GITHUB_CLIENT_ID") or settings.github_oauth_client_id or "").strip() @@ -92,15 +114,33 @@ def bitbucket_oauth_client(settings: AppSettings | None = None) -> tuple[str, st return client_id, secret +def gitlab_oauth_client(settings: AppSettings | None = None) -> tuple[str, str]: + settings = settings or AppSettings.load() + client_id = (os.environ.get("LOADPATH_GITLAB_CLIENT_ID") or settings.gitlab_oauth_client_id or "").strip() + secret = ( + os.environ.get("LOADPATH_GITLAB_CLIENT_SECRET") or settings.gitlab_oauth_client_secret or "" + ).strip() + return client_id, secret + + def oauth_status(settings: AppSettings | None = None) -> dict[str, Any]: settings = settings or AppSettings.load() bb_id, bb_secret = bitbucket_oauth_client(settings) + gl_id, gl_secret = gitlab_oauth_client(settings) return { "github": { "connected": bool(settings.github_token), "user": settings.github_user, "token_set": bool(settings.github_token), "oauth_ready": bool(github_client_id(settings)), + "host": github_web_host(settings), + }, + "gitlab": { + "connected": bool(settings.gitlab_token), + "user": settings.gitlab_user, + "token_set": bool(settings.gitlab_token), + "oauth_ready": bool(gl_id and gl_secret), + "host": gitlab_web_host(settings), }, "bitbucket": { "connected": bool(settings.bitbucket_token), @@ -124,8 +164,10 @@ def start_github_device(client: httpx.Client | None = None) -> dict[str, Any]: "or paste a GitHub OAuth App client ID in Settings (enable Device Flow on the app)." ) http = _client(client) + host = github_web_host(settings) + device_url = f"https://{host}/login/device/code" response = http.post( - GITHUB_DEVICE_CODE_URL, + device_url, data={"client_id": client_id, "scope": GITHUB_SCOPES}, headers={"Accept": "application/json"}, ) @@ -135,7 +177,7 @@ def start_github_device(client: httpx.Client | None = None) -> dict[str, Any]: user_code = data.get("user_code") or "" if not device_code or not user_code: raise ValueError("GitHub did not return a device code") - verification, complete = github_device_urls(data, user_code) + verification, complete = github_device_urls(data, user_code, host=host) flow_id = secrets.token_urlsafe(16) interval = int(data.get("interval") or 5) expires_in = int(data.get("expires_in") or 900) @@ -145,6 +187,7 @@ def start_github_device(client: httpx.Client | None = None) -> dict[str, Any]: "provider": "github", "device_code": device_code, "client_id": client_id, + "host": host, "interval": interval, "expires_at": time.time() + expires_in, }, @@ -169,8 +212,10 @@ def poll_github_device(flow_id: str, client: httpx.Client | None = None) -> dict _pending.pop(flow_id, None) return {"status": "expired"} http = _client(client) + host = pending.get("host") or "github.com" + token_url = f"https://{host}/login/oauth/access_token" response = http.post( - GITHUB_TOKEN_URL, + token_url, data={ "client_id": pending["client_id"], "device_code": pending["device_code"], @@ -203,7 +248,7 @@ def poll_github_device(flow_id: str, client: httpx.Client | None = None) -> dict raise ValueError("GitHub did not return an access token") with _lock: _pending.pop(flow_id, None) - settings = _store_login("github", token, client=http) + settings = _store_login("github", token, client=http, host=host) return {"status": "complete", **oauth_status(settings)["github"]} @@ -274,6 +319,78 @@ def finish_bitbucket_authorize( ) +def start_gitlab_authorize(redirect_uri: str) -> dict[str, Any]: + settings = AppSettings.load() + client_id, secret = gitlab_oauth_client(settings) + if not client_id or not secret: + raise ValueError( + "GitLab OAuth application is not configured. Set LOADPATH_GITLAB_CLIENT_ID and " + "LOADPATH_GITLAB_CLIENT_SECRET, or paste the application id and secret in Settings. " + f"Callback URL must be {redirect_uri}." + ) + host = gitlab_web_host(settings) + flow_id = secrets.token_urlsafe(16) + _remember_pending( + flow_id, + { + "provider": "gitlab", + "redirect_uri": redirect_uri, + "host": host, + "expires_at": time.time() + 600, + }, + ) + query = urlencode( + { + "client_id": client_id, + "response_type": "code", + "scope": GITLAB_SCOPES, + "state": flow_id, + "redirect_uri": redirect_uri, + } + ) + return {"flow_id": flow_id, "authorize_url": f"https://{host}/oauth/authorize?{query}"} + + +def finish_gitlab_authorize( + code: str, + state: str, + client: httpx.Client | None = None, +) -> AppSettings: + with _lock: + pending = _pending.pop(state, None) + if not pending or pending.get("provider") != "gitlab": + raise ValueError("Unknown or expired GitLab sign-in") + if pending["expires_at"] < time.time(): + raise ValueError("GitLab sign-in expired. Start again from Settings.") + settings = AppSettings.load() + client_id, secret = gitlab_oauth_client(settings) + host = pending.get("host") or gitlab_web_host(settings) + http = _client(client) + response = http.post( + f"https://{host}/oauth/token", + data={ + "grant_type": "authorization_code", + "code": code, + "redirect_uri": pending["redirect_uri"], + "client_id": client_id, + "client_secret": secret, + }, + headers={"Accept": "application/json"}, + ) + response.raise_for_status() + data = response.json() + token = data.get("access_token") or "" + if not token: + raise ValueError(data.get("error_description") or "GitLab did not return an access token") + return _store_login( + "gitlab", + token, + refresh_token=data.get("refresh_token") or "", + client=http, + host=host, + ) + + def refresh_bitbucket_access_token( settings: AppSettings | None = None, client: httpx.Client | None = None, @@ -311,6 +428,10 @@ def disconnect_scm(provider: str) -> AppSettings: settings.bitbucket_token = "" settings.bitbucket_user = "" settings.bitbucket_refresh_token = "" + elif provider == "gitlab": + settings.gitlab_token = "" + settings.gitlab_user = "" + settings.gitlab_refresh_token = "" else: raise ValueError(f"Unknown SCM provider: {provider}") settings.save() @@ -323,22 +444,33 @@ def _store_login( *, refresh_token: str = "", client: httpx.Client | None = None, + host: str = "", ) -> AppSettings: settings = AppSettings.load() username = "" if provider == "github": settings.github_token = token + if host: + settings.github_host = host if host != "github.com" else settings.github_host + elif provider == "gitlab": + settings.gitlab_token = token + if host: + settings.gitlab_host = host if host != "gitlab.com" else settings.gitlab_host + if refresh_token: + settings.gitlab_refresh_token = refresh_token else: settings.bitbucket_token = token settings.bitbucket_username = "" if refresh_token: settings.bitbucket_refresh_token = refresh_token try: - scm = provider_for(provider, token, username=username, client=client) + scm = provider_for(provider, token, username=username, client=client, host=host) profile = scm.current_user() login = profile.get("login") or "" if provider == "github": settings.github_user = login + elif provider == "gitlab": + settings.gitlab_user = login else: settings.bitbucket_user = login except httpx.HTTPError: diff --git a/src/loadpath/providers/pr_fetch.py b/src/loadpath/providers/pr_fetch.py new file mode 100644 index 0000000..d5b3a7d --- /dev/null +++ b/src/loadpath/providers/pr_fetch.py @@ -0,0 +1,210 @@ +"""Fetch a PR/MR into a local clone so review can walk it without a pre-synced checkout.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +from pathlib import Path +from typing import Any +from urllib.parse import quote + +from loadpath.providers.scm import PullRequest, provider_for +from loadpath.settings import AppSettings, register_workspace + +_GIT_TIMEOUT = 120 +FETCH_SPEC = { + "github": "pull/{n}/head", + "gitlab": "merge-requests/{n}/head", + "bitbucket": "pull-requests/{n}/from", +} + + +def redact_secrets(text: str, *secrets: str) -> str: + out = text + for secret in secrets: + if not secret: + continue + out = out.replace(secret, "***") + out = out.replace(quote(secret, safe=""), "***") + return out + + +def clones_root() -> Path: + return Path.home() / ".loadpath" / "clones" + + +def fetch_spec(provider: str, number: int) -> str: + template = FETCH_SPEC.get(provider) + if not template: + raise ValueError(f"Cannot fetch pull requests from {provider}") + return template.format(n=int(number)) + + +def local_pr_ref(number: int) -> str: + return f"refs/loadpath/pr-{int(number)}" + + +def _git(repo: Path, *args: str, env: dict[str, str] | None = None) -> str: + return subprocess.check_output( + ["git", "-C", str(repo), *args], + text=True, + stderr=subprocess.PIPE, + timeout=_GIT_TIMEOUT, + env=env, + ) + + +def _token_for(settings: AppSettings, provider: str) -> tuple[str, str]: + if provider == "github": + token = settings.github_token or os.environ.get("GITHUB_TOKEN") or os.environ.get("LOADPATH_GITHUB_TOKEN") or "" + return token, settings.github_host or "" + if provider == "gitlab": + token = settings.gitlab_token or os.environ.get("GITLAB_TOKEN") or os.environ.get("LOADPATH_GITLAB_TOKEN") or "" + return token, settings.gitlab_host or "" + if provider == "bitbucket": + return settings.bitbucket_token, "" + return "", "" + + +def clone_url(provider: str, slug: str, token: str, host: str = "") -> str: + if provider == "github": + web = host.strip() or "github.com" + if web in {"github.com", "www.github.com", "api.github.com"}: + web = "github.com" + if token: + return f"https://x-access-token:{quote(token, safe='')}@{web}/{slug}.git" + return f"https://{web}/{slug}.git" + if provider == "gitlab": + web = host.strip() or "gitlab.com" + if token: + return f"https://oauth2:{quote(token, safe='')}@{web}/{slug}.git" + return f"https://{web}/{slug}.git" + if provider == "bitbucket": + if token: + return f"https://x-token-auth:{quote(token, safe='')}@bitbucket.org/{slug}.git" + return f"https://bitbucket.org/{slug}.git" + raise ValueError(f"Unknown provider {provider}") + + +def ensure_clone(provider: str, slug: str, *, token: str = "", host: str = "") -> Path: + dest = clones_root() / provider / slug.replace("/", "__") + dest.parent.mkdir(parents=True, exist_ok=True) + env = os.environ.copy() + env.setdefault("GIT_TERMINAL_PROMPT", "0") + if dest.is_dir() and (dest / ".git").exists(): + try: + _git(dest, "fetch", "--all", "--prune", env=env) + except subprocess.CalledProcessError: + pass + return dest + url = clone_url(provider, slug, token, host) + try: + subprocess.run( + ["git", "clone", "--filter=blob:none", url, str(dest)], + env=env, + timeout=_GIT_TIMEOUT, + check=True, + stdout=subprocess.DEVNULL, + stderr=subprocess.PIPE, + text=True, + ) + except subprocess.CalledProcessError as exc: + detail = redact_secrets((exc.stderr or "").strip() or "git clone failed", token) + raise RuntimeError(detail) from None + return dest + + +def fetch_pull_ref(repo_root: Path, provider: str, number: int) -> str: + repo_root = repo_root.resolve() + spec = fetch_spec(provider, number) + local = local_pr_ref(number) + try: + _git(repo_root, "fetch", "origin", f"{spec}:{local}", "--force") + except subprocess.CalledProcessError as exc: + raise RuntimeError( + f"Could not fetch {provider} #{number} ({spec}). " + "Need a remote named origin that advertises pull/merge-request refs." + ) from exc + return local + + +def checkout_review_tree(repo_root: Path, local_ref: str, number: int) -> Path: + """Detached worktree at the fetched PR ref so index reads the PR, not the user's branch.""" + repo_root = repo_root.resolve() + dest = clones_root() / "worktrees" / f"{repo_root.name}-pr-{int(number)}" + dest.parent.mkdir(parents=True, exist_ok=True) + try: + _git(repo_root, "worktree", "remove", "--force", str(dest)) + except subprocess.CalledProcessError: + pass + if dest.exists(): + shutil.rmtree(dest, ignore_errors=True) + try: + _git(repo_root, "worktree", "prune") + except subprocess.CalledProcessError: + pass + _git(repo_root, "worktree", "add", "--detach", str(dest), local_ref) + return dest + + +def prepare_pull_request( + provider: str, + repo: str, + number: int, + *, + repo_path: str | None = None, +) -> dict[str, Any]: + """Return local path + git refs for reviewing a remote PR/MR.""" + settings = AppSettings.load() + token, host = _token_for(settings, provider) + scm = provider_for(provider, token, username=settings.bitbucket_username, host=host) if token else None + pr: PullRequest | None = None + if scm: + pr = scm.get_pull_request(repo, number) + root: Path | None = None + if repo_path: + candidate = Path(repo_path).expanduser().resolve() + if candidate.is_dir(): + root = candidate + if root is None: + from loadpath.providers.scm import attach_local_paths, RemoteRepo + + listed = [ + RemoteRepo(provider=provider, slug=repo, name=repo.split("/")[-1], owner=repo.split("/")[0], url="") + ] + attach_local_paths(listed, [w.path for w in settings.workspaces]) + if listed[0].local_path: + root = Path(listed[0].local_path) + cloned = False + if root is None: + if not token: + raise RuntimeError( + f"No local clone for {repo} and no {provider} token to clone it. " + "Sign in under Settings or pass a local repo path." + ) + root = ensure_clone(provider, repo, token=token, host=host) + cloned = True + register_workspace(root, name=repo.split("/")[-1]) + local_ref = fetch_pull_ref(root, provider, number) + review_root = checkout_review_tree(root, local_ref, number) + base_ref = (pr.target_branch if pr else None) or "origin/HEAD" + if pr and pr.target_branch: + try: + _git(root, "fetch", "origin", pr.target_branch) + base_ref = f"origin/{pr.target_branch}" + except subprocess.CalledProcessError: + base_ref = pr.target_branch + return { + "repo_path": str(review_root), + "provider": provider, + "repo": repo, + "number": number, + "base": base_ref, + "head": local_ref, + "cloned": cloned, + "title": pr.title if pr else f"{provider} #{number}", + "source_branch": pr.source_branch if pr else "", + "target_branch": pr.target_branch if pr else "", + "url": pr.url if pr else "", + } \ No newline at end of file diff --git a/src/loadpath/providers/scm.py b/src/loadpath/providers/scm.py index 23fa7f1..10995ee 100644 --- a/src/loadpath/providers/scm.py +++ b/src/loadpath/providers/scm.py @@ -8,10 +8,12 @@ import httpx -REPO_SLUG = re.compile(r"^[\w.-]+/[\w.-]+$") +# GitLab nested groups: group/subgroup/project. GitHub/Bitbucket stay owner/name. +# Reject "." / ".." so a slug cannot walk off the host path. +REPO_SLUG = re.compile(r"^[\w.-]+(?:/[\w.-]+)+$") LOADPATH_COMMENT_MARKER = "" REMOTE_HOST = re.compile( - r"(github\.com|bitbucket\.org)[:/](?P[\w.-]+/[\w.-]+?)(?:\.git)?/?$", + r"(?Pgithub\.com|bitbucket\.org|gitlab\.com|(?:[\w.-]+\.)?(?:github|gitlab)[\w.-]*)[:/](?P[\w.-]+(?:/[\w.-]+)+?)(?:\.git)?/?$", re.IGNORECASE, ) REPO_LIST_LIMIT = 500 @@ -25,24 +27,46 @@ def _marked_comment(markdown: str) -> str: +def _safe_slug_parts(parts: list[str]) -> list[str] | None: + cleaned = [p for p in parts if p] + if not cleaned or any(p in {".", ".."} for p in cleaned): + return None + return cleaned + + def require_repo_slug(repo: str) -> str: slug = repo.strip().strip("/") - if not REPO_SLUG.match(slug): + parts = _safe_slug_parts(slug.split("/")) + if parts is None or not REPO_SLUG.match(slug): raise ValueError("repo must be owner/name") return slug def parse_remote_url(url: str) -> tuple[str, str] | None: - """Return (provider, owner/name) for a GitHub or Bitbucket remote URL.""" + """Return (provider, owner/name) for GitHub, GitLab, or Bitbucket remotes.""" raw = (url or "").strip() match = REMOTE_HOST.search(raw) if not match: return None slug = match.group("slug").strip("/") + if slug.endswith(".git"): + slug = slug[:-4] + host = match.group("host").lower() + parts = _safe_slug_parts(slug.split("/")) + if not parts: + return None + if "gitlab" in host and len(parts) >= 2: + slug = "/".join(parts) + elif len(parts) >= 2: + slug = "/".join(parts[:2]) if not REPO_SLUG.match(slug): return None - host = match.group(1).lower() - provider = "github" if host == "github.com" else "bitbucket" + if "bitbucket" in host: + provider = "bitbucket" + elif "gitlab" in host: + provider = "gitlab" + else: + provider = "github" return provider, slug @@ -132,10 +156,15 @@ def upsert_pull_request_comment(self, repo: str, number: int, markdown: str) -> class GitHubProvider: name = "github" - def __init__(self, token: str, client: httpx.Client | None = None) -> None: + def __init__(self, token: str, client: httpx.Client | None = None, *, host: str = "") -> None: self.token = token self.client = client or httpx.Client(timeout=30.0) - self.base = "https://api.github.com" + self.host = _normalize_host(host) or "github.com" + if self.host in {"github.com", "www.github.com", "api.github.com"}: + self.host = "github.com" + self.base = "https://api.github.com" + else: + self.base = f"https://{self.host}/api/v3" def _headers(self) -> dict[str, str]: return { @@ -489,9 +518,182 @@ def upsert_pull_request_comment(self, repo: str, number: int, markdown: str) -> return {"id": str(data.get("id")), "url": url, "updated": False} -def provider_for(name: str, token: str, username: str = "", client: httpx.Client | None = None) -> SCMProvider: +def _normalize_host(host: str) -> str: + raw = (host or "").strip() + if raw.startswith("https://") or raw.startswith("http://"): + raw = raw.split("://", 1)[1] + return raw.strip("/").lower() + + +class GitLabProvider: + name = "gitlab" + + def __init__(self, token: str, client: httpx.Client | None = None, *, host: str = "") -> None: + self.token = token + self.client = client or httpx.Client(timeout=30.0) + self.host = _normalize_host(host) or "gitlab.com" + self.base = f"https://{self.host}/api/v4" + + def _headers(self) -> dict[str, str]: + return {"PRIVATE-TOKEN": self.token, "Accept": "application/json"} + + def _project(self, repo: str) -> str: + return require_repo_slug(repo).replace("/", "%2F") + + def current_user(self) -> dict[str, str]: + r = self.client.get(f"{self.base}/user", headers=self._headers()) + r.raise_for_status() + item = r.json() + login = item.get("username") or "" + return { + "login": login, + "name": item.get("name") or login, + "url": item.get("web_url") or "", + } + + def list_repositories(self, limit: int = REPO_LIST_LIMIT) -> list[RemoteRepo]: + cap = max(1, min(limit, REPO_LIST_LIMIT)) + out: list[RemoteRepo] = [] + page = 1 + while len(out) < cap: + per_page = min(100, cap - len(out)) + r = self.client.get( + f"{self.base}/projects", + params={ + "membership": "true", + "simple": "true", + "order_by": "last_activity_at", + "per_page": per_page, + "page": page, + }, + headers=self._headers(), + ) + r.raise_for_status() + batch = r.json() or [] + if not batch: + break + for item in batch: + slug = item.get("path_with_namespace") or "" + owner, _, name = slug.partition("/") + out.append( + RemoteRepo( + provider="gitlab", + slug=slug, + name=name or slug, + owner=owner, + url=item.get("web_url") or "", + private=item.get("visibility") != "public", + default_branch=item.get("default_branch") or "", + updated_at=item.get("last_activity_at") or "", + description=item.get("description") or "", + ) + ) + if len(batch) < per_page: + break + page += 1 + return out[:cap] + + def list_pull_requests(self, repo: str, state: str = "open") -> list[PullRequest]: + repo = require_repo_slug(repo) + gl_state = "opened" if state == "open" else state + r = self.client.get( + f"{self.base}/projects/{self._project(repo)}/merge_requests", + params={"state": gl_state, "per_page": 50, "order_by": "updated_at"}, + headers=self._headers(), + ) + r.raise_for_status() + out = [] + for item in r.json() or []: + out.append(self._mr(repo, item)) + return out + + def get_pull_request(self, repo: str, number: int) -> PullRequest: + repo = require_repo_slug(repo) + r = self.client.get( + f"{self.base}/projects/{self._project(repo)}/merge_requests/{number}", + headers=self._headers(), + ) + r.raise_for_status() + return self._mr(repo, r.json()) + + def get_diff(self, repo: str, number: int) -> str: + repo = require_repo_slug(repo) + r = self.client.get( + f"{self.base}/projects/{self._project(repo)}/merge_requests/{number}/diffs", + headers={**self._headers(), "Accept": "text/plain"}, + ) + if r.status_code == 406: + r = self.client.get( + f"{self.base}/projects/{self._project(repo)}/merge_requests/{number}.diff", + headers=self._headers(), + ) + r.raise_for_status() + return r.text + + def upsert_pull_request_comment(self, repo: str, number: int, markdown: str) -> dict[str, Any]: + repo = require_repo_slug(repo) + body = _marked_comment(markdown) + listed = self.client.get( + f"{self.base}/projects/{self._project(repo)}/merge_requests/{number}/notes", + params={"per_page": 100}, + headers=self._headers(), + ) + listed.raise_for_status() + existing_id = None + for item in listed.json() or []: + if LOADPATH_COMMENT_MARKER in (item.get("body") or ""): + existing_id = item.get("id") + break + if existing_id: + r = self.client.put( + f"{self.base}/projects/{self._project(repo)}/merge_requests/{number}/notes/{existing_id}", + headers=self._headers(), + json={"body": body}, + ) + r.raise_for_status() + data = r.json() + return {"id": str(data.get("id")), "url": "", "updated": True} + r = self.client.post( + f"{self.base}/projects/{self._project(repo)}/merge_requests/{number}/notes", + headers=self._headers(), + json={"body": body}, + ) + r.raise_for_status() + data = r.json() + return {"id": str(data.get("id")), "url": "", "updated": False} + + def _mr(self, repo: str, item: dict[str, Any]) -> PullRequest: + refs = item.get("diff_refs") or {} + return PullRequest( + provider="gitlab", + id=str(item.get("id") or item.get("iid")), + number=int(item.get("iid") or 0), + title=item.get("title") or "", + url=item.get("web_url") or "", + author=((item.get("author") or {}).get("username")) or "", + source_branch=item.get("source_branch") or "", + target_branch=item.get("target_branch") or "", + repo=repo, + state="open" if item.get("state") == "opened" else (item.get("state") or ""), + updated_at=item.get("updated_at") or "", + draft=bool(item.get("draft") or item.get("work_in_progress")), + head_sha=item.get("sha") or refs.get("head_sha") or "", + base_sha=refs.get("base_sha") or refs.get("start_sha") or "", + ) + + +def provider_for( + name: str, + token: str, + username: str = "", + client: httpx.Client | None = None, + *, + host: str = "", +) -> SCMProvider: if name == "github": - return GitHubProvider(token, client=client) + return GitHubProvider(token, client=client, host=host) if name == "bitbucket": return BitbucketProvider(token, username=username, client=client) + if name == "gitlab": + return GitLabProvider(token, client=client, host=host) raise ValueError(f"Unknown SCM provider: {name}") diff --git a/src/loadpath/review/auth.py b/src/loadpath/review/auth.py new file mode 100644 index 0000000..80b3297 --- /dev/null +++ b/src/loadpath/review/auth.py @@ -0,0 +1,93 @@ +"""Auth as a first-class load path: who can hit the sinks after this change.""" + +from __future__ import annotations + +from loadpath.graph.store import GraphStore +from loadpath.types import EdgeType, NodeType + +ANONYMOUS_OK = {"AllowAny", "IsAuthenticatedOrReadOnly"} +AUTHED = {"IsAuthenticated", "IsAdminUser", "IsOwner", "DjangoModelPermissions", "TokenHasReadWriteScope"} + + +def auth_path(store: GraphStore, impact_nodes: list[dict], impact_edges: list[dict]) -> dict: + views = [n for n in impact_nodes if n["type"] in {NodeType.VIEW.value, NodeType.VIEWSET_ACTION.value}] + routes = [ + n + for n in impact_nodes + if n["type"] + in { + NodeType.ROUTE.value, + NodeType.FASTAPI_ROUTE.value, + NodeType.WEBSOCKET_ROUTE.value, + NodeType.GRAPHQL_OPERATION.value, + } + ] + perms_by_view: dict[str, list[str]] = {} + for n in views: + extra = n.get("extra") or {} + perms_by_view[n["id"]] = list(extra.get("permissions") or extra.get("authentication") or []) + + for e in impact_edges: + if e["type"] != EdgeType.HAS_PERMISSION.value: + continue + dst = next((n for n in impact_nodes if n["id"] == e["dst"]), None) + if dst and dst["type"] == NodeType.PERMISSION.value: + perms_by_view.setdefault(e["src"], []) + if dst["name"] not in perms_by_view[e["src"]]: + perms_by_view[e["src"]].append(dst["name"]) + + sinks: list[dict] = [] + missing: list[dict] = [] + object_scope: list[dict] = [] + for view in views: + extra = view.get("extra") or {} + perms = perms_by_view.get(view["id"]) or [] + item = { + "id": view["id"], + "name": view["name"], + "file_path": view.get("file_path"), + "permissions": perms, + "authentication": extra.get("authentication") or [], + "get_queryset": bool(extra.get("get_queryset")), + } + sinks.append(item) + if not perms and not extra.get("fastapi") and not extra.get("ninja"): + missing.append(item) + if extra.get("get_queryset"): + object_scope.append(item) + + for route in routes: + extra = route.get("extra") or {} + if extra.get("websocket") and not extra.get("permissions"): + missing.append( + { + "id": route["id"], + "name": route["name"], + "file_path": route.get("file_path"), + "permissions": [], + "authentication": [], + "get_queryset": False, + } + ) + + note = _note(sinks, missing, object_scope) + return { + "sinks": sinks[:24], + "missing_permissions": missing[:12], + "object_scope": object_scope[:12], + "note": note, + } + + +def _note(sinks: list[dict], missing: list[dict], object_scope: list[dict]) -> str: + if not sinks: + return "No view/route auth surface on this path" + if missing: + names = ", ".join(m["name"] for m in missing[:3]) + return f"No permission_classes on {names}" + declared = sorted({p for s in sinks for p in s.get("permissions") or []}) + bit = ", ".join(declared[:4]) if declared else "undeclared" + extra = "" + if object_scope: + extra = f"; {len(object_scope)} view(s) use get_queryset for object scope" + return f"Sinks gated by {bit}{extra}" diff --git a/src/loadpath/review/cluster.py b/src/loadpath/review/cluster.py index 005a798..ddd8630 100644 --- a/src/loadpath/review/cluster.py +++ b/src/loadpath/review/cluster.py @@ -20,6 +20,12 @@ NodeType.FORM_SCHEMA, NodeType.HOOK, NodeType.COMPONENT, + NodeType.GRAPHQL_TYPE, + NodeType.GRAPHQL_OPERATION, + NodeType.FASTAPI_ROUTE, + NodeType.CONSUMER, + NodeType.TEMPLATE, + NodeType.PYDANTIC_MODEL, ] # Shared hubs that connect unrelated contexts if traversed. @@ -53,6 +59,12 @@ "destructive_migration", "changes_permission", "crosses_context", + "serves_template", + "htmx_calls", + "invalidates_cache", + "checks_flag", + "on_commit", + "publishes_graphql", } BACKWARD_TYPES = { @@ -71,6 +83,12 @@ "uses_query_key", "belongs_to", "destructive_migration", + "serves_template", + "htmx_calls", + "on_commit", + "publishes_graphql", + "invalidates_cache", + "checks_flag", } diff --git a/src/loadpath/review/contract.py b/src/loadpath/review/contract.py new file mode 100644 index 0000000..a5aa383 --- /dev/null +++ b/src/loadpath/review/contract.py @@ -0,0 +1,135 @@ +"""Classify serializer/OpenAPI/Zod/GraphQL diffs as additive, breaking, or drift.""" + +from __future__ import annotations + +import re + +from loadpath.review.diff import DiffSet +from loadpath.types import CONTRACT_TYPES, ContractBreakKind, NodeType + +REQUIRED_RE = re.compile( + r"""extra_kwargs\s*=\s*\{[^}]*['"](\w+)['"]\s*:\s*\{[^}]*required['"]?\s*:\s*True""", + re.S, +) +FIELD_ASSIGN_RE = re.compile(r"""^\s*(\w+)\s*=\s*\w*(Field|Serializer)\b""", re.M) +REMOVED_FIELD_RE = re.compile(r"""^\-\s*['"](\w+)['"]\s*,?\s*$""", re.M) +ADDED_FIELD_RE = re.compile(r"""^\+\s*['"](\w+)['"]\s*,?\s*$""", re.M) +ZOD_REQUIRED_RE = re.compile(r"""^\+\s*(\w+)\s*:\s*z\.""", re.M) +ZOD_OPTIONAL_RE = re.compile(r"""\.optional\(\)|\.nullish\(\)|nullish\(""") + + +def _touched(node: dict, changed_files: set[str]) -> bool: + if not changed_files: + return True + return (node.get("file_path") or "") in changed_files + + +def classify_contract_break(impact_nodes: list[dict], diff: DiffSet | None) -> dict: + kinds: set[str] = set() + reasons: list[str] = [] + fields: list[str] = [] + contract_nodes = [n for n in impact_nodes if n.get("type") in {t.value for t in CONTRACT_TYPES}] + if not contract_nodes: + return { + "kind": ContractBreakKind.NONE.value, + "reasons": [], + "fields": [], + } + + patch = "\n".join(f.patch or "" for f in (diff.files if diff else [])) + changed_files = {f.path for f in (diff.files if diff else []) if not f.skip} + required = { + m.group(1) + for m in REQUIRED_RE.finditer(patch) + if "+" in patch[max(0, m.start() - 80) : m.start() + 1] + } + # extra_kwargs required=True in added lines + added_required: list[str] = [] + for line in patch.splitlines(): + if line.startswith("+") and "required" in line and "True" in line: + names = re.findall(r"""['"](\w+)['"]""", line) + added_required.extend(names) + if line.startswith("+") and "required=True" in line.replace(" ", ""): + added_required.extend(re.findall(r"""['"](\w+)['"]""", line)) + + removed = REMOVED_FIELD_RE.findall(patch) + added = ADDED_FIELD_RE.findall(patch) + zod_added = ZOD_REQUIRED_RE.findall(patch) + + serializer_changed = any( + n["type"] in {NodeType.SERIALIZER.value, NodeType.SERIALIZER_FIELD.value, NodeType.PYDANTIC_MODEL.value} + and _touched(n, changed_files) + for n in contract_nodes + ) + graphql_changed = any( + n["type"].startswith("graphql.") and _touched(n, changed_files) for n in contract_nodes + ) + openapi_changed = any( + n["type"] == NodeType.OPENAPI_PATH.value and _touched(n, changed_files) for n in contract_nodes + ) + zod_changed = any( + n["type"] == NodeType.FORM_SCHEMA.value and _touched(n, changed_files) for n in contract_nodes + ) + + if added_required or required: + names = sorted({n for n in added_required if n not in {"extra_kwargs", "required"}} or required) + if names: + kinds.add(ContractBreakKind.BREAKING.value) + fields.extend(names) + reasons.append("Required contract field(s): " + ", ".join(f"`{n}`" for n in names[:6])) + + if removed and serializer_changed: + kinds.add(ContractBreakKind.BREAKING.value) + fields.extend(removed) + reasons.append("Removed published field(s): " + ", ".join(f"`{n}`" for n in removed[:6])) + + if added and serializer_changed and ContractBreakKind.BREAKING.value not in kinds: + kinds.add(ContractBreakKind.ADDITIVE.value) + fields.extend(added) + reasons.append("Added published field(s): " + ", ".join(f"`{n}`" for n in added[:6])) + + if graphql_changed: + kinds.add(ContractBreakKind.BREAKING.value if serializer_changed else ContractBreakKind.ADDITIVE.value) + reasons.append("GraphQL operation or type is on the impact path") + + if openapi_changed and serializer_changed: + kinds.add(ContractBreakKind.DRIFT.value) + reasons.append("OpenAPI path moved with the serializer — confirm generated clients") + + if zod_changed and serializer_changed: + ser_names = { + n["name"] + for n in impact_nodes + if n["type"] in {NodeType.SERIALIZER_FIELD.value, NodeType.FIELD.value} + } + schema_fields: set[str] = set() + for n in impact_nodes: + extra = n.get("extra") or {} + if n["type"] == NodeType.FORM_SCHEMA.value: + schema_fields.update(extra.get("fields") or []) + missing = sorted(ser_names - schema_fields - {"id", "pk"}) + if missing: + kinds.add(ContractBreakKind.DRIFT.value) + fields.extend(missing) + reasons.append("Zod/form schema does not include " + ", ".join(f"`{f}`" for f in missing[:6])) + elif zod_added: + kinds.add(ContractBreakKind.ADDITIVE.value) + + if not kinds and contract_nodes: + kinds.add(ContractBreakKind.ADDITIVE.value) + reasons.append("Public contract nodes changed without a detected field removal or required=True") + + order = [ + ContractBreakKind.BREAKING.value, + ContractBreakKind.DRIFT.value, + ContractBreakKind.ADDITIVE.value, + ContractBreakKind.NONE.value, + ] + kind = next((k for k in order if k in kinds), ContractBreakKind.NONE.value) + seen: set[str] = set() + uniq_fields = [] + for f in fields: + if f not in seen and f not in {"extra_kwargs", "required", "True", "False"}: + seen.add(f) + uniq_fields.append(f) + return {"kind": kind, "reasons": reasons[:4], "fields": uniq_fields[:12]} diff --git a/src/loadpath/review/diff.py b/src/loadpath/review/diff.py index bbfe6f1..afa06f3 100644 --- a/src/loadpath/review/diff.py +++ b/src/loadpath/review/diff.py @@ -49,6 +49,24 @@ def git_diff( head: str | None = None, *, three_dot: bool = True, + dirty: bool = False, +) -> DiffSet: + committed = _git_range_diff(repo_root, base, head, three_dot=three_dot) + if not dirty: + return committed + worktree = git_worktree_diff(repo_root) + merged = _merge_diffsets(committed, worktree) + merged.base = committed.base + merged.head = "WORKTREE" + return merged + + +def _git_range_diff( + repo_root: Path, + base: str, + head: str | None, + *, + three_dot: bool, ) -> DiffSet: repo_root = repo_root.resolve() specs: list[list[str]] = [] @@ -123,6 +141,60 @@ def git_diff( return DiffSet(files=files, base=base, head=head or "WORKTREE") +def git_worktree_diff(repo_root: Path) -> DiffSet: + """Uncommitted tracked changes plus untracked source files vs HEAD.""" + repo_root = repo_root.resolve() + tracked = _git_range_diff(repo_root, "HEAD", None, three_dot=False) + tracked.head = "WORKTREE" + files = {f.path: f for f in tracked.files} + try: + raw = subprocess.check_output( + ["git", "-C", str(repo_root), "ls-files", "--others", "--exclude-standard"], + text=True, + stderr=subprocess.DEVNULL, + ) + except (subprocess.CalledProcessError, FileNotFoundError): + raw = "" + for rel in raw.splitlines(): + path = rel.strip() + if not path or path in files: + continue + abs_path = repo_root / path + try: + text = abs_path.read_text(encoding="utf-8", errors="replace") + except OSError: + continue + added = text.count("\n") + (1 if text and not text.endswith("\n") else 0) + files[path] = FileDiff( + path=path, + status="A", + added=added, + deleted=0, + patch=f"diff --git a/{path} b/{path}\n--- /dev/null\n+++ b/{path}\n" + + "".join(f"+{line}\n" for line in text.splitlines()), + skip=is_skippable(path), + ) + return DiffSet(files=list(files.values()), base="HEAD", head="WORKTREE") + + +def _merge_diffsets(committed: DiffSet, worktree: DiffSet) -> DiffSet: + by_path = {f.path: f for f in committed.files} + for item in worktree.files: + existing = by_path.get(item.path) + if existing is None: + by_path[item.path] = item + continue + by_path[item.path] = FileDiff( + path=item.path, + status=item.status or existing.status, + added=existing.added + item.added, + deleted=existing.deleted + item.deleted, + patch=(existing.patch or "") + ("\n" if existing.patch and item.patch else "") + (item.patch or ""), + skip=existing.skip or item.skip, + ) + return DiffSet(files=list(by_path.values()), base=committed.base, head=worktree.head) + + def _split_patches(patch: str) -> dict[str, str]: out: dict[str, str] = {} current: list[str] = [] diff --git a/src/loadpath/review/engine.py b/src/loadpath/review/engine.py index 707a138..b45754a 100644 --- a/src/loadpath/review/engine.py +++ b/src/loadpath/review/engine.py @@ -12,8 +12,12 @@ from loadpath.index import default_db_path, index_drift, index_repo from loadpath.review.cluster import cluster_diff from loadpath.review.confidence import score_confidence +from loadpath.review.auth import auth_path +from loadpath.review.contract import classify_contract_break from loadpath.review.diff import DiffSet, git_diff from loadpath.review.evolution import analyze_evolution +from loadpath.review.suggested_tests import suggested_tests as sketches_for +from loadpath.review.trend import confidence_trend from loadpath.stitch.openapi import published_route from loadpath.workspace import git_dirty_paths, resolve_review_range from loadpath.types import ( @@ -46,6 +50,18 @@ NodeType.COMPONENT, NodeType.TEST, NodeType.REACT_TEST, + NodeType.GRAPHQL_TYPE, + NodeType.GRAPHQL_FIELD, + NodeType.GRAPHQL_OPERATION, + NodeType.FASTAPI_ROUTE, + NodeType.PYDANTIC_MODEL, + NodeType.CONSUMER, + NodeType.WEBSOCKET_ROUTE, + NodeType.TEMPLATE, + NodeType.HTMX_CALL, + NodeType.CACHE_KEY, + NodeType.FEATURE_FLAG, + NodeType.SIDE_EFFECT, ] @@ -60,6 +76,11 @@ def classify_change(impact_nodes: list[dict], findings: list, seeds: list[dict] or NodeType.OPENAPI_PATH.value in seed_types or NodeType.FORM_SCHEMA.value in seed_types or NodeType.ROUTE.value in seed_types + or NodeType.GRAPHQL_TYPE.value in seed_types + or NodeType.GRAPHQL_FIELD.value in seed_types + or NodeType.GRAPHQL_OPERATION.value in seed_types + or NodeType.FASTAPI_ROUTE.value in seed_types + or NodeType.PYDANTIC_MODEL.value in seed_types ): kinds.add(ChangeKind.PUBLIC_CONTRACT.value) if ( @@ -348,6 +369,9 @@ def run_review( *, three_dot: bool = True, draft_config: bool = False, + dirty: bool = False, + progress: object | None = None, + workers: int | None = None, ) -> dict: repo_root = repo_root.resolve() config = config or load_config(repo_root) @@ -359,6 +383,8 @@ def run_review( config=config, incremental=incremental, draft_config=draft_config, + progress=progress, + workers=workers, ) else: if not graph_db.is_file(): @@ -368,8 +394,8 @@ def run_review( store = GraphStore(graph_db) range_info = resolve_review_range(repo_root, base, head, three_dot=three_dot) - diff = diff or git_diff(repo_root, base, head, three_dot=three_dot) - dirty = git_dirty_paths(repo_root) + diff = diff or git_diff(repo_root, base, head, three_dot=three_dot, dirty=dirty) + dirty_paths = git_dirty_paths(repo_root) clusters, impact_nodes, impact_edges = cluster_diff(store, diff) impact_edges = linked_edges(impact_nodes, impact_edges) seed_ids = {n["id"] for n in store.nodes_in_files(diff.paths)} @@ -400,10 +426,17 @@ def run_review( detail = store.get_meta("django_boot_detail") or "django.setup() failed" confidence["reasons"] = [detail, *reasons][:3] kinds = classify_change(impact_nodes, scoped, seeds=store.nodes_in_files(diff.paths)) + contract = classify_contract_break(impact_nodes, diff) + auth = auth_path(store, impact_nodes, impact_edges) + if auth.get("missing_permissions") and ChangeKind.AUTH.value not in kinds: + kinds.append(ChangeKind.AUTH.value) + kinds.sort() read, skip = read_order_files(diff, impact_nodes) reviewers = suggested_reviewers(config, impact_nodes) knowledge = _knowledge_owners(evolution) low_risk = is_low_risk(kinds, confidence, scoped) + if contract["kind"] == "breaking": + low_risk = False labels = ["loadpath:" + confidence["level"]] if low_risk: labels.append("loadpath:low-risk") @@ -411,6 +444,10 @@ def run_review( labels.append("loadpath:cross-context") if ChangeKind.PUBLIC_CONTRACT.value in kinds: labels.append("loadpath:contract") + if contract["kind"] and contract["kind"] != "none": + labels.append("loadpath:contract-" + contract["kind"]) + if ChangeKind.AUTH.value in kinds: + labels.append("loadpath:auth") title = _title(clusters, kinds) sinks = _sink_summaries(impact_nodes, store) @@ -418,8 +455,9 @@ def run_review( arch_note = _arch_note(scoped, kinds, impact_nodes) depth_note = _depth_note(scoped) deepening = deepening_candidates(scoped) + sketches = sketches_for(confidence.get("untested_sinks") or [], impact_nodes) drift = index_drift(store, repo_root, config) - dirty_set = set(dirty) + dirty_set = set(dirty_paths) overlap = [p for p in diff.paths if p in dirty_set] payload = { @@ -446,6 +484,9 @@ def run_review( "depth_note": depth_note, "deepening": deepening, "evolution": evolution, + "contract_break": contract, + "auth": auth, + "suggested_tests": sketches, "nodes": impact_nodes, "edges": impact_edges, "counts": store.counts(), @@ -463,20 +504,32 @@ def run_review( "django_boot_detail": store.get_meta("django_boot_detail") or "", }, "workspace": { - "dirty": dirty[:40], - "dirty_count": len(dirty), + "dirty": dirty_paths[:40], + "dirty_count": len(dirty_paths), "dirty_overlaps_review": bool(overlap), "dirty_overlap": overlap[:20], + "dirty_included": dirty, "merge_base": range_info.get("merge_base"), "three_dot": three_dot, "base_sha": range_info.get("base_sha"), "head_sha": range_info.get("head_sha"), }, "headline": _headline( - confidence, title, sinks, tests_note, arch_note, residuals, reviewers, evolution, depth_note + confidence, + title, + sinks, + tests_note, + arch_note, + residuals, + reviewers, + evolution, + depth_note, + contract=contract, + auth=auth, ), } store.save_review(payload["id"], payload["created_at"], str(repo_root), diff.base, diff.head, payload) + payload["trend"] = confidence_trend(store, base=diff.base, head=diff.head) store.close() return payload @@ -500,6 +553,14 @@ def _sink_summaries(nodes: list[dict], store: GraphStore) -> list[dict]: NodeType.PERMISSION.value, NodeType.MIGRATION_OP.value, NodeType.RECEIVER.value, + NodeType.CONSUMER.value, + NodeType.WEBSOCKET_ROUTE.value, + NodeType.TEMPLATE.value, + NodeType.GRAPHQL_OPERATION.value, + NodeType.FASTAPI_ROUTE.value, + NodeType.CACHE_KEY.value, + NodeType.FEATURE_FLAG.value, + NodeType.SIDE_EFFECT.value, } out = [] for n in nodes: @@ -549,10 +610,22 @@ def _depth_note(findings: list) -> str: def _headline( - confidence, title, sinks, tests_note, arch_note, residuals, reviewers, evolution=None, depth_note="" + confidence, + title, + sinks, + tests_note, + arch_note, + residuals, + reviewers, + evolution=None, + depth_note="", + contract=None, + auth=None, ) -> str: sink_bits = [] routes = [s["name"] for s in sinks if s["type"] == NodeType.ROUTE.value] + fastapi_routes = [s["name"] for s in sinks if s["type"] == NodeType.FASTAPI_ROUTE.value] + gql = [s["name"] for s in sinks if s["type"] == NodeType.GRAPHQL_OPERATION.value] celery_tasks = [s["name"] for s in sinks if s["type"] == NodeType.TASK.value and s.get("broker") == "celery"] dramatiq_tasks = [s["name"] for s in sinks if s["type"] == NodeType.TASK.value and s.get("broker") == "dramatiq"] other_tasks = [ @@ -561,8 +634,14 @@ def _headline( if s["type"] == NodeType.TASK.value and s.get("broker") not in {"celery", "dramatiq"} ] pages = [s["name"] for s in sinks if s["type"] in {NodeType.PAGE.value, NodeType.FORM_SCHEMA.value}] + templates = [s["name"] for s in sinks if s["type"] == NodeType.TEMPLATE.value] + sockets = [s["name"] for s in sinks if s["type"] in {NodeType.CONSUMER.value, NodeType.WEBSOCKET_ROUTE.value}] if routes: sink_bits.append(", ".join(routes[:4])) + if fastapi_routes: + sink_bits.append("FastAPI " + ", ".join(fastapi_routes[:3])) + if gql: + sink_bits.append("GraphQL " + ", ".join(gql[:3])) if celery_tasks: sink_bits.append("Celery " + ", ".join(celery_tasks[:3])) if dramatiq_tasks: @@ -571,13 +650,21 @@ def _headline( sink_bits.append("async " + ", ".join(other_tasks[:3])) if pages: sink_bits.append("React " + ", ".join(pages[:4])) + if templates: + sink_bits.append("templates " + ", ".join(templates[:3])) + if sockets: + sink_bits.append("WS " + ", ".join(sockets[:3])) residual = residuals[0] if residuals else "none" owners = ", ".join(reviewers) if reviewers else "context owners" pressure = (evolution or {}).get("notes") or [] churn = pressure[0] if pressure else "none" + contract_bit = (contract or {}).get("kind") or "none" + auth_bit = (auth or {}).get("note") or "n/a" return ( f"Loadpath: {confidence['level'].upper()} — {title}\n" f"Sinks: {'; '.join(sink_bits) or 'none typed'}\n" + f"Contract: {contract_bit}\n" + f"Auth: {auth_bit}\n" f"Tests: {tests_note}\n" f"Architecture: {arch_note}\n" f"Depth: {depth_note or 'published seams hold'}\n" diff --git a/src/loadpath/review/gate.py b/src/loadpath/review/gate.py new file mode 100644 index 0000000..cabdf1d --- /dev/null +++ b/src/loadpath/review/gate.py @@ -0,0 +1,58 @@ +"""CI merge-gate: map a review to a process exit code.""" + +from __future__ import annotations + +FAIL_ON_CHOICES = ("never", "blocker", "low", "medium") + + +def gate_result(review: dict, fail_on: str = "blocker") -> dict: + fail_on = (fail_on or "blocker").lower() + if fail_on not in FAIL_ON_CHOICES: + fail_on = "blocker" + findings = [f for f in (review.get("findings") or []) if not f.get("waived")] + blockers = [f for f in findings if f.get("severity") == "blocker"] + level = (review.get("confidence") or {}).get("level") or "medium" + contract = (review.get("contract_break") or {}).get("kind") + reasons: list[str] = [] + code = 0 + if fail_on != "never" and blockers: + code = 2 + reasons.append(f"{len(blockers)} architecture blocker(s)") + if fail_on in {"low", "medium"} and contract == "breaking" and code == 0: + code = 4 + reasons.append("breaking public contract") + if fail_on in {"low", "medium"} and level == "low" and code == 0: + code = 3 + reasons.append("confidence is low") + if fail_on == "medium" and level == "medium" and code == 0: + code = 3 + reasons.append("confidence is medium") + passed = code == 0 + summary = "pass" if passed else "fail" + title = (review.get("title") or "change").strip() + return { + "passed": passed, + "exit_code": code, + "fail_on": fail_on, + "summary": summary, + "level": level, + "contract_break": contract, + "blockers": len(blockers), + "reasons": reasons, + "annotation": f"Loadpath {level.upper()} — {title}" + + (f" ({'; '.join(reasons)})" if reasons else ""), + } + + +def write_github_output(path: str, gate: dict, review: dict) -> None: + conf = review.get("confidence") or {} + lines = [ + f"level={gate.get('level') or ''}", + f"passed={str(gate['passed']).lower()}", + f"title={review.get('title') or ''}", + f"contract_break={gate.get('contract_break') or 'none'}", + f"sinks={conf.get('sinks') or 0}", + f"annotation={gate.get('annotation') or ''}", + ] + with open(path, "a", encoding="utf-8") as handle: + handle.write("\n".join(lines) + "\n") diff --git a/src/loadpath/review/render.py b/src/loadpath/review/render.py index e7e2ab8..33d6c41 100644 --- a/src/loadpath/review/render.py +++ b/src/loadpath/review/render.py @@ -52,6 +52,28 @@ def render_markdown(review: dict) -> str: lines += ["", "### Residual uncertainty (AI-eligible)"] for r in review["residuals"][:12]: lines.append(f"- {r}") + contract = review.get("contract_break") or {} + if contract.get("kind") and contract["kind"] != "none": + lines += ["", "### Contract", f"**{contract['kind']}**"] + for reason in contract.get("reasons") or []: + lines.append(f"- {reason}") + auth = review.get("auth") or {} + if auth.get("note"): + lines += ["", "### Auth", auth["note"]] + for item in (auth.get("missing_permissions") or [])[:6]: + lines.append(f"- missing permission_classes on `{item.get('name')}`") + sketches = review.get("suggested_tests") or [] + if sketches: + lines += ["", "### Suggested tests"] + for sketch in sketches[:6]: + lines.append(f"- **{sketch['title']}** ({sketch['kind']})") + lines.append("") + lines.append("```") + lines.append((sketch.get("body") or "").rstrip()) + lines.append("```") + trend = review.get("trend") or {} + if trend.get("note"): + lines += ["", "### Confidence trend", trend["note"]] if review["low_risk"]: lines += ["", "_Fast-track: `loadpath:low-risk`. Human review can be a glance._"] evolution = review.get("evolution") or {} @@ -99,6 +121,8 @@ def render_markdown(review: dict) -> str: "Uncommitted files overlap this review: " + ", ".join(f"`{p}`" for p in (workspace.get("dirty_overlap") or [])[:8]), ] + elif workspace.get("dirty_included"): + lines += ["", "### Working tree", "Uncommitted files are included in this review."] elif workspace.get("dirty_count"): lines += [ "", diff --git a/src/loadpath/review/suggested_tests.py b/src/loadpath/review/suggested_tests.py new file mode 100644 index 0000000..31c328c --- /dev/null +++ b/src/loadpath/review/suggested_tests.py @@ -0,0 +1,170 @@ +"""Turn untested seams into pytest / RTL / GraphQL / HTMX sketches.""" + +from __future__ import annotations + +from loadpath.stitch.openapi import published_route +from loadpath.types import NodeType + + +def suggested_tests(untested_sinks: list[dict], impact_nodes: list[dict]) -> list[dict]: + by_id = {n["id"]: n for n in impact_nodes} + out: list[dict] = [] + seen: set[str] = set() + for sink in untested_sinks: + node = by_id.get(sink.get("id") or "") or sink + ntype = node.get("type") or "" + name = node.get("name") or sink.get("name") or "sink" + key = f"{ntype}:{name}" + if key in seen: + continue + seen.add(key) + sketch = _sketch(ntype, name, node) + if not sketch: + continue + out.append(sketch) + return out[:8] + + +def _sketch(ntype: str, name: str, node: dict) -> dict | None: + extra = node.get("extra") or {} + file_path = node.get("file_path") or "" + if ntype == NodeType.ROUTE.value: + route = published_route(node) if "extra" in node or "name" in node else name + method = (extra.get("method") or "GET").upper() + call = "get" if method in {"GET", "HEAD"} else method.lower() + return { + "sink": name, + "type": ntype, + "kind": "pytest", + "title": f"Hit {route} through the published route", + "body": ( + f"def test_{_ident(name)}_via_route(client):\n" + f" response = client.{call}({route!r})\n" + f" assert response.status_code in {{200, 201, 204}}\n" + ), + } + if ntype == NodeType.FASTAPI_ROUTE.value: + route = extra.get("route") or name + method = (extra.get("method") or "GET").lower() + return { + "sink": name, + "type": ntype, + "kind": "pytest", + "title": f"Hit FastAPI {name}", + "body": ( + f"def test_{_ident(name)}_fastapi(client):\n" + f" response = client.{method}({route!r})\n" + f" assert response.status_code < 500\n" + ), + } + if ntype in {NodeType.PAGE.value, NodeType.FORM_SCHEMA.value}: + component = name if ntype == NodeType.PAGE.value else name.replace("Schema", "Form") + return { + "sink": name, + "type": ntype, + "kind": "rtl", + "title": f"Render {component} at the page seam", + "body": ( + f"import {{ render, screen }} from '@testing-library/react'\n" + f"import {{ {component} }} from './{component}'\n\n" + f"it('renders {component}', () => {{\n" + f" render(<{component} />)\n" + f" expect(screen.getByRole('form', {{ hidden: true }}) || document.body).toBeTruthy()\n" + f"}})\n" + ), + } + if ntype == NodeType.GRAPHQL_OPERATION.value: + kind = extra.get("kind") or "query" + return { + "sink": name, + "type": ntype, + "kind": "graphql", + "title": f"Exercise GraphQL {kind} {name}", + "body": ( + f"def test_{_ident(name)}_graphql(client):\n" + f" response = client.post('/graphql', {{'query': '{kind} {{ {name} {{ __typename }} }}'}})\n" + f" assert response.status_code == 200\n" + f" assert 'errors' not in response.json()\n" + ), + } + if ntype in {NodeType.CONSUMER.value, NodeType.WEBSOCKET_ROUTE.value}: + return { + "sink": name, + "type": ntype, + "kind": "channels", + "title": f"Connect to {name}", + "body": ( + f"async def test_{_ident(name)}_ws(communicator):\n" + f" connected, _ = await communicator.connect()\n" + f" assert connected\n" + f" await communicator.disconnect()\n" + ), + } + if ntype == NodeType.TEMPLATE.value: + return { + "sink": name, + "type": ntype, + "kind": "django", + "title": f"Render template {name}", + "body": ( + f"def test_{_ident(name)}_template(client):\n" + f" response = client.get('/') # route that serves {file_path or name}\n" + f" assert response.status_code == 200\n" + ), + } + if ntype == NodeType.CACHE_KEY.value: + return { + "sink": name, + "type": ntype, + "kind": "pytest", + "title": f"Assert cache key {name} after the write", + "body": ( + f"def test_{_ident(name)}_cache(client):\n" + f" # exercise the view that writes `{name}`\n" + f" client.get('/')\n" + f" assert cache.get({name!r}) is not None\n" + ), + } + if ntype == NodeType.FEATURE_FLAG.value: + return { + "sink": name, + "type": ntype, + "kind": "pytest", + "title": f"Both sides of flag {name}", + "body": ( + f"@pytest.mark.parametrize('on', [True, False])\n" + f"def test_{_ident(name)}_flag(client, on, monkeypatch):\n" + f" monkeypatch.setattr('waffle.flag_is_active', lambda *a, **k: on)\n" + f" response = client.get('/')\n" + f" assert response.status_code < 500\n" + ), + } + if ntype == NodeType.SIDE_EFFECT.value: + return { + "sink": name, + "type": ntype, + "kind": "pytest", + "title": f"on_commit side effect from {name}", + "body": ( + f"def test_{_ident(name)}_on_commit(django_capture_on_commit_callbacks):\n" + f" with django_capture_on_commit_callbacks(execute=True):\n" + f" ... # the write that schedules {name}\n" + ), + } + if ntype == NodeType.TASK.value: + return { + "sink": name, + "type": ntype, + "kind": "pytest", + "title": f"Call task {name} with a pk", + "body": ( + f"def test_{_ident(name)}_task():\n" + f" {name}(1) # pass a model pk, not a deserialized object\n" + ), + } + return None + + +def _ident(name: str) -> str: + cleaned = "".join(ch if ch.isalnum() else "_" for ch in name) + return cleaned.strip("_").lower() or "sink" diff --git a/src/loadpath/review/trend.py b/src/loadpath/review/trend.py new file mode 100644 index 0000000..ab62ccc --- /dev/null +++ b/src/loadpath/review/trend.py @@ -0,0 +1,73 @@ +"""Confidence history for the same repo / PR range.""" + +from __future__ import annotations + +from loadpath.graph.store import GraphStore + +_LEVEL_RANK = {"high": 2, "medium": 1, "low": 0} + + +def confidence_trend( + store: GraphStore, + *, + base: str | None = None, + head: str | None = None, + current: dict | None = None, + limit: int = 12, +) -> dict: + rows = store.list_reviews(include_payload=True, limit=40) + points: list[dict] = [] + for row in rows: + payload = row.get("payload") or {} + conf = payload.get("confidence") or {} + points.append( + { + "id": row["id"], + "created_at": row["created_at"], + "base": row.get("base_ref"), + "head": row.get("head_ref"), + "level": conf.get("level"), + "sinks": conf.get("sinks"), + "covered_sinks": conf.get("covered_sinks"), + "title": payload.get("title"), + "contract_break": (payload.get("contract_break") or {}).get("kind"), + } + ) + same_range = [p for p in points if (not base or p["base"] == base) and (not head or p["head"] == head)] + series = same_range or points + series = series[:limit] + if current: + current_point = { + "id": current.get("id"), + "created_at": current.get("created_at"), + "base": current.get("base"), + "head": current.get("head"), + "level": (current.get("confidence") or {}).get("level"), + "sinks": (current.get("confidence") or {}).get("sinks"), + "covered_sinks": (current.get("confidence") or {}).get("covered_sinks"), + "title": current.get("title"), + "contract_break": (current.get("contract_break") or {}).get("kind"), + "current": True, + } + if not series or series[0].get("id") != current_point.get("id"): + series = [current_point, *series] + note = _note(series) + return {"points": series[:limit], "note": note} + + +def _note(series: list[dict]) -> str: + levels = [p.get("level") for p in series if p.get("level")] + if len(levels) < 2: + return "First review stored for this range" + newest, previous = levels[0], levels[1] + if _LEVEL_RANK.get(newest, 1) < _LEVEL_RANK.get(previous, 1): + return f"Confidence dropped {previous} → {newest}" + if _LEVEL_RANK.get(newest, 1) > _LEVEL_RANK.get(previous, 1): + return f"Confidence rose {previous} → {newest}" + sinks_now = series[0].get("sinks") + sinks_prev = series[1].get("sinks") + if isinstance(sinks_now, int) and isinstance(sinks_prev, int) and sinks_now != sinks_prev: + delta = sinks_now - sinks_prev + sign = "+" if delta > 0 else "" + return f"Confidence stayed {newest}; sinks {sign}{delta}" + return f"Confidence unchanged at {newest}" diff --git a/src/loadpath/review/whatif.py b/src/loadpath/review/whatif.py new file mode 100644 index 0000000..22b11ce --- /dev/null +++ b/src/loadpath/review/whatif.py @@ -0,0 +1,104 @@ +"""Simulate a change from a node without a git range.""" + +from __future__ import annotations + +from pathlib import Path + +from loadpath.architecture.rules import evaluate +from loadpath.config import load_config +from loadpath.graph.store import GraphStore, linked_edges +from loadpath.index import default_db_path +from loadpath.review.auth import auth_path +from loadpath.review.cluster import impact_walk +from loadpath.review.confidence import score_confidence +from loadpath.review.contract import classify_contract_break +from loadpath.review.suggested_tests import suggested_tests as sketches_for +from loadpath.review.engine import ( + READ_ORDER, + _arch_note, + _depth_note, + _sink_summaries, + _tests_note, + classify_change, + collect_residuals, + suggested_reviewers, +) + + +def simulate_node(repo_root: Path, node_id: str, *, hops: int = 8) -> dict: + repo_root = repo_root.resolve() + config = load_config(repo_root) + db = default_db_path(repo_root) + if not db.is_file(): + raise FileNotFoundError(f"No index at {db}. Run `loadpath index` first.") + store = GraphStore(db) + seed = store.get_node(node_id) + if not seed: + store.close() + raise KeyError(f"Unknown node: {node_id}") + nodes, edges = impact_walk(store, {node_id}, hops=hops) + edges = linked_edges(nodes, edges) + findings = evaluate(store, config, changed_ids={node_id}) + impact_ids = {n["id"] for n in nodes} + scoped = [ + f + for f in findings + if (f.node_id and f.node_id in impact_ids) or f.node_id == node_id or not impact_ids + ] + residuals = collect_residuals(store, nodes, None) + confidence = score_confidence(store, nodes, edges, scoped, residuals) + auth = auth_path(store, nodes, edges) + sinks = _sink_summaries(nodes, store) + sketches = sketches_for(confidence.get("untested_sinks") or [], nodes) + kinds = classify_change(nodes, scoped, seeds=[seed]) + contract = classify_contract_break(nodes, None) + read_order = _read_order(nodes) + payload = { + "ok": True, + "what_if": True, + "id": f"whatif:{node_id}", + "node": seed, + "title": f"What if {seed.get('name')} changes", + "headline": f"What if {seed.get('name')} changes — {confidence.get('level')}", + "confidence": confidence, + "change_kinds": kinds, + "labels": [], + "low_risk": False, + "clusters": [], + "read_order": read_order, + "skip": [], + "sinks": sinks, + "nodes": nodes, + "edges": edges, + "findings": [f.to_dict() for f in scoped], + "residuals": residuals, + "auth": auth, + "contract_break": contract, + "suggested_tests": sketches, + "suggested_reviewers": suggested_reviewers(config, nodes), + "tests_note": _tests_note(confidence, nodes), + "architecture_note": _arch_note(scoped, kinds, nodes), + "depth_note": _depth_note(scoped), + "seed_type": seed.get("type"), + "counts": {"nodes": len(nodes), "edges": len(edges)}, + } + store.close() + return payload + + +def _read_order(nodes: list[dict]) -> list[dict]: + rank = {t.value: i for i, t in enumerate(READ_ORDER)} + by_file: dict[str, list[dict]] = {} + for n in nodes: + path = n.get("file_path") + if path: + by_file.setdefault(path, []).append(n) + out = [] + for path, group in by_file.items(): + why = ", ".join(sorted({n["type"].split(".")[-1] for n in group})[:4]) + file_rank = min(rank.get(n["type"], 40) for n in group) + out.append({"path": path, "why": why, "status": "M", "rank": file_rank}) + out.sort(key=lambda r: (r["rank"], r["path"])) + for item in out: + item.pop("rank", None) + return out diff --git a/src/loadpath/server/app.py b/src/loadpath/server/app.py index 8f16bf9..8d972c4 100644 --- a/src/loadpath/server/app.py +++ b/src/loadpath/server/app.py @@ -29,15 +29,18 @@ from loadpath.detect import detect_layout, write_draft_config from loadpath.graph.store import GraphStore from loadpath.index import default_db_path, index_repo +from loadpath.progress import progress_callback, read_progress from loadpath.providers.oauth import ( callback_html, disconnect_scm, finish_bitbucket_authorize, + finish_gitlab_authorize, is_loopback_request, oauth_status, poll_github_device, start_bitbucket_authorize, start_github_device, + start_gitlab_authorize, ) from loadpath.providers.scm import attach_local_paths, provider_for from loadpath.review.engine import run_review @@ -49,6 +52,7 @@ class IndexRequest(BaseModel): repo_path: str incremental: bool = True + jobs: int | None = None class ReviewRequest(BaseModel): @@ -58,6 +62,8 @@ class ReviewRequest(BaseModel): reindex: bool = True incremental: bool = True three_dot: bool = True + dirty: bool = False + jobs: int | None = None class InitRequest(BaseModel): @@ -77,6 +83,11 @@ class PRCommentRequest(BaseModel): class SettingsUpdate(BaseModel): github_token: str | None = None github_oauth_client_id: str | None = None + github_host: str | None = None + gitlab_token: str | None = None + gitlab_host: str | None = None + gitlab_oauth_client_id: str | None = None + gitlab_oauth_client_secret: str | None = None bitbucket_token: str | None = None bitbucket_username: str | None = None bitbucket_workspace: str | None = None @@ -109,6 +120,20 @@ class OAuthDisconnect(BaseModel): provider: str +class WhatIfRequest(BaseModel): + repo_path: str + node_id: str + + +class PRReviewRequest(BaseModel): + provider: str + repo: str + number: int + repo_path: str | None = None + reindex: bool = True + incremental: bool = True + + def require_repo_path(path: str | None) -> Path: if not (path or "").strip(): raise HTTPException(400, "repo_path is required") @@ -118,11 +143,13 @@ def require_repo_path(path: str | None) -> Path: return root -def _scm_credentials(settings: AppSettings, provider: str) -> tuple[str, str]: +def _scm_credentials(settings: AppSettings, provider: str) -> tuple[str, str, str]: if provider == "github": - return settings.github_token, "" + return settings.github_token, "", settings.github_host + if provider == "gitlab": + return settings.gitlab_token, "", settings.gitlab_host if provider == "bitbucket": - return settings.bitbucket_token, settings.bitbucket_username + return settings.bitbucket_token, settings.bitbucket_username, "" raise HTTPException(400, f"Unknown SCM provider: {provider}") @@ -130,11 +157,11 @@ def _call_scm(provider: str, fn): from loadpath.providers.oauth import refresh_bitbucket_access_token settings = AppSettings.load() - token, username = _scm_credentials(settings, provider) + token, username, host = _scm_credentials(settings, provider) if not token: raise HTTPException(400, f"No {provider} token configured") try: - return fn(provider_for(provider, token, username=username)) + return fn(provider_for(provider, token, username=username, host=host)) except httpx.HTTPStatusError as exc: if ( provider == "bitbucket" @@ -146,8 +173,8 @@ def _call_scm(provider: str, fn): settings = refresh_bitbucket_access_token(settings) except Exception as refresh_exc: # noqa: BLE001 raise HTTPException(502, str(refresh_exc)) from refresh_exc - token, username = _scm_credentials(settings, provider) - return fn(provider_for(provider, token, username=username)) + token, username, host = _scm_credentials(settings, provider) + return fn(provider_for(provider, token, username=username, host=host)) raise HTTPException(502, str(exc)) from exc @@ -232,6 +259,16 @@ def put_settings(body: SettingsUpdate) -> dict[str, Any]: current.github_token = body.github_token or "" if body.github_oauth_client_id is not None: current.github_oauth_client_id = body.github_oauth_client_id.strip() + if body.github_host is not None: + current.github_host = body.github_host.strip() + if _should_update_secret(body.gitlab_token, current.gitlab_token): + current.gitlab_token = body.gitlab_token or "" + if body.gitlab_host is not None: + current.gitlab_host = body.gitlab_host.strip() + if body.gitlab_oauth_client_id is not None: + current.gitlab_oauth_client_id = body.gitlab_oauth_client_id.strip() + if _should_update_secret(body.gitlab_oauth_client_secret, current.gitlab_oauth_client_secret): + current.gitlab_oauth_client_secret = body.gitlab_oauth_client_secret or "" if _should_update_secret(body.bitbucket_token, current.bitbucket_token): current.bitbucket_token = body.bitbucket_token or "" if body.bitbucket_username is not None: @@ -260,12 +297,23 @@ def put_settings(body: SettingsUpdate) -> dict[str, Any]: @app.post("/api/index") def api_index(body: IndexRequest) -> dict[str, Any]: root = require_repo_path(body.repo_path) - store = index_repo(root, incremental=body.incremental, draft_config=True) + store = index_repo( + root, + incremental=body.incremental, + draft_config=True, + progress=progress_callback(root), + workers=body.jobs, + ) register_workspace(root) summary = summarize_index(store, load_config(root)) store.close() return summary + @app.get("/api/index/progress") + def api_index_progress(repo_path: str) -> dict[str, Any]: + root = require_repo_path(repo_path) + return read_progress(root) + @app.get("/api/index") def api_index_status(repo_path: str) -> dict[str, Any]: root = require_repo_path(repo_path) @@ -321,6 +369,9 @@ def api_review(body: ReviewRequest) -> dict[str, Any]: reindex=body.reindex, incremental=body.incremental, three_dot=body.three_dot, + dirty=body.dirty, + progress=progress_callback(root) if body.reindex else None, + workers=body.jobs, ) except FileNotFoundError as exc: raise HTTPException(409, str(exc)) from exc @@ -389,7 +440,8 @@ def api_prs(body: PRListRequest) -> dict[str, Any]: username = body.username or settings.bitbucket_username if token: try: - scm = provider_for(body.provider, token, username=username) + _, _, host = _scm_credentials(settings, body.provider) + scm = provider_for(body.provider, token, username=username, host=host) prs = scm.list_pull_requests(body.repo, state=body.state) except ValueError as exc: raise HTTPException(400, str(exc)) from exc @@ -427,7 +479,8 @@ def api_pr_comment(body: PRCommentRequest) -> dict[str, Any]: raise HTTPException(400, "markdown is empty") if token: try: - scm = provider_for(body.provider, token, username=username) + _, _, host = _scm_credentials(settings, body.provider) + scm = provider_for(body.provider, token, username=username, host=host) return scm.upsert_pull_request_comment(body.repo, body.number, body.markdown) except ValueError as exc: raise HTTPException(400, str(exc)) from exc @@ -468,6 +521,9 @@ def _load(scm): if provider == "github" and current.github_user != login: current.github_user = login current.save() + elif provider == "gitlab" and current.gitlab_user != login: + current.gitlab_user = login + current.save() elif provider == "bitbucket" and current.bitbucket_user != login: current.bitbucket_user = login current.save() @@ -548,6 +604,48 @@ def api_bitbucket_oauth_callback(code: str = "", state: str = "", error: str = " ) ) + @app.get("/api/oauth/gitlab/start") + def api_gitlab_oauth_start(request: Request) -> dict[str, Any]: + require_loopback(request) + redirect_uri = f"{base.rstrip('/')}/api/oauth/gitlab/callback" + try: + return start_gitlab_authorize(redirect_uri) + except ValueError as exc: + raise HTTPException(400, str(exc)) from exc + + @app.get("/api/oauth/gitlab/callback") + def api_gitlab_oauth_callback(code: str = "", state: str = "", error: str = "") -> HTMLResponse: + if error: + return HTMLResponse( + callback_html(ok=False, title="GitLab sign-in cancelled", body=error.replace("_", " ")), + status_code=400, + ) + if not code or not state: + return HTMLResponse( + callback_html(ok=False, title="GitLab sign-in failed", body="Missing code or state."), + status_code=400, + ) + try: + settings = finish_gitlab_authorize(code, state) + except ValueError as exc: + return HTMLResponse( + callback_html(ok=False, title="GitLab sign-in failed", body=str(exc)), + status_code=400, + ) + except httpx.HTTPError as exc: + return HTMLResponse( + callback_html(ok=False, title="GitLab sign-in failed", body=str(exc)), + status_code=502, + ) + user = settings.gitlab_user or "your account" + return HTMLResponse( + callback_html( + ok=True, + title="Connected to GitLab", + body=f"Signed in as {user}. Return to Loadpath — your repositories are available on the Pull requests tab.", + ) + ) + @app.post("/api/oauth/disconnect") def api_oauth_disconnect(request: Request, body: OAuthDisconnect) -> dict[str, Any]: require_loopback(request) @@ -574,6 +672,49 @@ def api_residual(body: ResidualRequest) -> dict[str, Any]: raise HTTPException(502, str(exc)) from exc return {"note": text} + @app.post("/api/whatif") + def api_whatif(body: WhatIfRequest) -> dict[str, Any]: + root = require_repo_path(body.repo_path) + from loadpath.review.whatif import simulate_node + + try: + return simulate_node(root, body.node_id) + except FileNotFoundError as exc: + raise HTTPException(409, str(exc)) from exc + except KeyError as exc: + raise HTTPException(404, str(exc)) from exc + + @app.post("/api/prs/review") + def api_pr_review(request: Request, body: PRReviewRequest) -> dict[str, Any]: + require_loopback(request) + from loadpath.providers.pr_fetch import prepare_pull_request + + try: + prepared = prepare_pull_request( + body.provider, body.repo, body.number, repo_path=body.repo_path + ) + except Exception as exc: # noqa: BLE001 + raise HTTPException(400, str(exc)) from exc + root = Path(prepared["repo_path"]) + try: + review = run_review( + root, + base=prepared["base"], + head=prepared["head"], + reindex=body.reindex, + incremental=body.incremental, + three_dot=True, + progress=progress_callback(root) if body.reindex else None, + ) + except FileNotFoundError as exc: + raise HTTPException(409, str(exc)) from exc + except Exception as exc: # noqa: BLE001 + raise HTTPException(400, str(exc)) from exc + review["markdown"] = render_markdown(review) + review["pull_request"] = prepared + register_workspace(root) + return review + copy_mcp_routes(app, mcp_http) static_dir = package_dir() / "static" diff --git a/src/loadpath/settings.py b/src/loadpath/settings.py index c65c99c..545c1bd 100644 --- a/src/loadpath/settings.py +++ b/src/loadpath/settings.py @@ -40,6 +40,13 @@ class AppSettings(BaseModel): github_token: str = "" github_user: str = "" github_oauth_client_id: str = "" + github_host: str = "" + gitlab_token: str = "" + gitlab_user: str = "" + gitlab_host: str = "" + gitlab_oauth_client_id: str = "" + gitlab_oauth_client_secret: str = "" + gitlab_refresh_token: str = "" bitbucket_token: str = "" bitbucket_username: str = "" bitbucket_workspace: str = "" @@ -108,15 +115,24 @@ def register_workspace(path: Path, name: str | None = None) -> AppSettings: def public_settings(settings: AppSettings) -> dict[str, Any]: data = settings.model_dump() data["github_token"] = mask_secret(settings.github_token) + data["gitlab_token"] = mask_secret(settings.gitlab_token) + data["gitlab_refresh_token"] = mask_secret(settings.gitlab_refresh_token) + data["gitlab_oauth_client_secret"] = mask_secret(settings.gitlab_oauth_client_secret) data["bitbucket_token"] = mask_secret(settings.bitbucket_token) data["bitbucket_refresh_token"] = mask_secret(settings.bitbucket_refresh_token) data["bitbucket_oauth_client_secret"] = mask_secret(settings.bitbucket_oauth_client_secret) data["github_token_set"] = bool(settings.github_token) + data["gitlab_token_set"] = bool(settings.gitlab_token) + data["gitlab_oauth_client_secret_set"] = bool(settings.gitlab_oauth_client_secret) data["bitbucket_token_set"] = bool(settings.bitbucket_token) data["bitbucket_oauth_client_secret_set"] = bool(settings.bitbucket_oauth_client_secret) data["github_oauth_ready"] = bool( (os.environ.get("LOADPATH_GITHUB_CLIENT_ID") or settings.github_oauth_client_id or "").strip() ) + data["gitlab_oauth_ready"] = bool( + (os.environ.get("LOADPATH_GITLAB_CLIENT_ID") or settings.gitlab_oauth_client_id or "").strip() + and (os.environ.get("LOADPATH_GITLAB_CLIENT_SECRET") or settings.gitlab_oauth_client_secret or "").strip() + ) data["bitbucket_oauth_ready"] = bool( (os.environ.get("LOADPATH_BITBUCKET_CLIENT_ID") or settings.bitbucket_oauth_client_id or "").strip() and (os.environ.get("LOADPATH_BITBUCKET_CLIENT_SECRET") or settings.bitbucket_oauth_client_secret or "").strip() diff --git a/src/loadpath/static/assets/LayeredGraph3D-DQUYVbjc.js b/src/loadpath/static/assets/LayeredGraph3D-DLi5mNhS.js similarity index 99% rename from src/loadpath/static/assets/LayeredGraph3D-DQUYVbjc.js rename to src/loadpath/static/assets/LayeredGraph3D-DLi5mNhS.js index 55baad9..f032fa5 100644 --- a/src/loadpath/static/assets/LayeredGraph3D-DQUYVbjc.js +++ b/src/loadpath/static/assets/LayeredGraph3D-DLi5mNhS.js @@ -1,4 +1,4 @@ -import{r as un,l as tc,c as nc,a as ic,L as sc,j as ei,t as rc}from"./index-CE5vlBBA.js";/** +import{r as un,l as tc,c as nc,a as ic,L as sc,j as ei,t as rc}from"./index-DPfPes3Y.js";/** * @license * Copyright 2010-2026 Three.js Authors * SPDX-License-Identifier: MIT diff --git a/src/loadpath/static/assets/index-CE5vlBBA.js b/src/loadpath/static/assets/index-CE5vlBBA.js deleted file mode 100644 index 8918f39..0000000 --- a/src/loadpath/static/assets/index-CE5vlBBA.js +++ /dev/null @@ -1,62 +0,0 @@ -(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))l(a);new MutationObserver(a=>{for(const u of a)if(u.type==="childList")for(const d of u.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&l(d)}).observe(document,{childList:!0,subtree:!0});function o(a){const u={};return a.integrity&&(u.integrity=a.integrity),a.referrerPolicy&&(u.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?u.credentials="include":a.crossOrigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function l(a){if(a.ep)return;a.ep=!0;const u=o(a);fetch(a.href,u)}})();function xp(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var _u={exports:{}},co={},Su={exports:{}},Pe={};/** - * @license React - * react.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Vf;function Qy(){if(Vf)return Pe;Vf=1;var t=Symbol.for("react.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),l=Symbol.for("react.strict_mode"),a=Symbol.for("react.profiler"),u=Symbol.for("react.provider"),d=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),g=Symbol.for("react.suspense"),m=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),x=Symbol.iterator;function v(M){return M===null||typeof M!="object"?null:(M=x&&M[x]||M["@@iterator"],typeof M=="function"?M:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},k=Object.assign,j={};function N(M,T,re){this.props=M,this.context=T,this.refs=j,this.updater=re||_}N.prototype.isReactComponent={},N.prototype.setState=function(M,T){if(typeof M!="object"&&typeof M!="function"&&M!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,M,T,"setState")},N.prototype.forceUpdate=function(M){this.updater.enqueueForceUpdate(this,M,"forceUpdate")};function S(){}S.prototype=N.prototype;function P(M,T,re){this.props=M,this.context=T,this.refs=j,this.updater=re||_}var E=P.prototype=new S;E.constructor=P,k(E,N.prototype),E.isPureReactComponent=!0;var C=Array.isArray,R=Object.prototype.hasOwnProperty,D={current:null},O={key:!0,ref:!0,__self:!0,__source:!0};function U(M,T,re){var ie,ce={},fe=null,de=null;if(T!=null)for(ie in T.ref!==void 0&&(de=T.ref),T.key!==void 0&&(fe=""+T.key),T)R.call(T,ie)&&!O.hasOwnProperty(ie)&&(ce[ie]=T[ie]);var K=arguments.length-2;if(K===1)ce.children=re;else if(1>>1,T=A[M];if(0>>1;Ma(ce,B))fea(de,ce)?(A[M]=de,A[fe]=B,M=fe):(A[M]=ce,A[ie]=B,M=ie);else if(fea(de,B))A[M]=de,A[fe]=B,M=fe;else break e}}return z}function a(A,z){var B=A.sortIndex-z.sortIndex;return B!==0?B:A.id-z.id}if(typeof performance=="object"&&typeof performance.now=="function"){var u=performance;t.unstable_now=function(){return u.now()}}else{var d=Date,f=d.now();t.unstable_now=function(){return d.now()-f}}var g=[],m=[],y=1,x=null,v=3,_=!1,k=!1,j=!1,N=typeof setTimeout=="function"?setTimeout:null,S=typeof clearTimeout=="function"?clearTimeout:null,P=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function E(A){for(var z=o(m);z!==null;){if(z.callback===null)l(m);else if(z.startTime<=A)l(m),z.sortIndex=z.expirationTime,r(g,z);else break;z=o(m)}}function C(A){if(j=!1,E(A),!k)if(o(g)!==null)k=!0,H(R);else{var z=o(m);z!==null&&W(C,z.startTime-A)}}function R(A,z){k=!1,j&&(j=!1,S(U),U=-1),_=!0;var B=v;try{for(E(z),x=o(g);x!==null&&(!(x.expirationTime>z)||A&&!X());){var M=x.callback;if(typeof M=="function"){x.callback=null,v=x.priorityLevel;var T=M(x.expirationTime<=z);z=t.unstable_now(),typeof T=="function"?x.callback=T:x===o(g)&&l(g),E(z)}else l(g);x=o(g)}if(x!==null)var re=!0;else{var ie=o(m);ie!==null&&W(C,ie.startTime-z),re=!1}return re}finally{x=null,v=B,_=!1}}var D=!1,O=null,U=-1,q=5,te=-1;function X(){return!(t.unstable_now()-teA||125M?(A.sortIndex=B,r(m,A),o(g)===null&&A===o(m)&&(j?(S(U),U=-1):j=!0,W(C,B-M))):(A.sortIndex=T,r(g,A),k||_||(k=!0,H(R))),A},t.unstable_shouldYield=X,t.unstable_wrapCallback=function(A){var z=v;return function(){var B=v;v=z;try{return A.apply(this,arguments)}finally{v=B}}}})(Nu)),Nu}var Gf;function e0(){return Gf||(Gf=1,Eu.exports=Jy()),Eu.exports}/** - * @license React - * react-dom.production.min.js - * - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Qf;function t0(){if(Qf)return jt;Qf=1;var t=Mo(),r=e0();function o(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,i=1;i"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),g=Object.prototype.hasOwnProperty,m=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,y={},x={};function v(e){return g.call(x,e)?!0:g.call(y,e)?!1:m.test(e)?x[e]=!0:(y[e]=!0,!1)}function _(e,n,i,s){if(i!==null&&i.type===0)return!1;switch(typeof n){case"function":case"symbol":return!0;case"boolean":return s?!1:i!==null?!i.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function k(e,n,i,s){if(n===null||typeof n>"u"||_(e,n,i,s))return!0;if(s)return!1;if(i!==null)switch(i.type){case 3:return!n;case 4:return n===!1;case 5:return isNaN(n);case 6:return isNaN(n)||1>n}return!1}function j(e,n,i,s,c,h,w){this.acceptsBooleans=n===2||n===3||n===4,this.attributeName=s,this.attributeNamespace=c,this.mustUseProperty=i,this.propertyName=e,this.type=n,this.sanitizeURL=h,this.removeEmptyString=w}var N={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){N[e]=new j(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var n=e[0];N[n]=new j(n,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){N[e]=new j(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){N[e]=new j(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){N[e]=new j(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){N[e]=new j(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){N[e]=new j(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){N[e]=new j(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){N[e]=new j(e,5,!1,e.toLowerCase(),null,!1,!1)});var S=/[\-:]([a-z])/g;function P(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var n=e.replace(S,P);N[n]=new j(n,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var n=e.replace(S,P);N[n]=new j(n,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var n=e.replace(S,P);N[n]=new j(n,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){N[e]=new j(e,1,!1,e.toLowerCase(),null,!1,!1)}),N.xlinkHref=new j("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){N[e]=new j(e,1,!1,e.toLowerCase(),null,!0,!0)});function E(e,n,i,s){var c=N.hasOwnProperty(n)?N[n]:null;(c!==null?c.type!==0:s||!(2I||c[w]!==h[I]){var L=` -`+c[w].replace(" at new "," at ");return e.displayName&&L.includes("")&&(L=L.replace("",e.displayName)),L}while(1<=w&&0<=I);break}}}finally{re=!1,Error.prepareStackTrace=i}return(e=e?e.displayName||e.name:"")?T(e):""}function ce(e){switch(e.tag){case 5:return T(e.type);case 16:return T("Lazy");case 13:return T("Suspense");case 19:return T("SuspenseList");case 0:case 2:case 15:return e=ie(e.type,!1),e;case 11:return e=ie(e.type.render,!1),e;case 1:return e=ie(e.type,!0),e;default:return""}}function fe(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case O:return"Fragment";case D:return"Portal";case q:return"Profiler";case U:return"StrictMode";case J:return"Suspense";case b:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case X:return(e.displayName||"Context")+".Consumer";case te:return(e._context.displayName||"Context")+".Provider";case ee:var n=e.render;return e=e.displayName,e||(e=n.displayName||n.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Y:return n=e.displayName||null,n!==null?n:fe(e.type)||"Memo";case H:n=e._payload,e=e._init;try{return fe(e(n))}catch{}}return null}function de(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:return(n.displayName||"Context")+".Consumer";case 10:return(n._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=n.render,e=e.displayName||e.name||"",n.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return n;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return fe(n);case 8:return n===U?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n}return null}function K(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function le(e){var n=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(n==="checkbox"||n==="radio")}function he(e){var n=le(e)?"checked":"value",i=Object.getOwnPropertyDescriptor(e.constructor.prototype,n),s=""+e[n];if(!e.hasOwnProperty(n)&&typeof i<"u"&&typeof i.get=="function"&&typeof i.set=="function"){var c=i.get,h=i.set;return Object.defineProperty(e,n,{configurable:!0,get:function(){return c.call(this)},set:function(w){s=""+w,h.call(this,w)}}),Object.defineProperty(e,n,{enumerable:i.enumerable}),{getValue:function(){return s},setValue:function(w){s=""+w},stopTracking:function(){e._valueTracker=null,delete e[n]}}}}function _e(e){e._valueTracker||(e._valueTracker=he(e))}function ve(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var i=n.getValue(),s="";return e&&(s=le(e)?e.checked?"true":"false":e.value),e=s,e!==i?(n.setValue(e),!0):!1}function ge(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Ne(e,n){var i=n.checked;return B({},n,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:i??e._wrapperState.initialChecked})}function Me(e,n){var i=n.defaultValue==null?"":n.defaultValue,s=n.checked!=null?n.checked:n.defaultChecked;i=K(n.value!=null?n.value:i),e._wrapperState={initialChecked:s,initialValue:i,controlled:n.type==="checkbox"||n.type==="radio"?n.checked!=null:n.value!=null}}function be(e,n){n=n.checked,n!=null&&E(e,"checked",n,!1)}function Le(e,n){be(e,n);var i=K(n.value),s=n.type;if(i!=null)s==="number"?(i===0&&e.value===""||e.value!=i)&&(e.value=""+i):e.value!==""+i&&(e.value=""+i);else if(s==="submit"||s==="reset"){e.removeAttribute("value");return}n.hasOwnProperty("value")?Ae(e,n.type,i):n.hasOwnProperty("defaultValue")&&Ae(e,n.type,K(n.defaultValue)),n.checked==null&&n.defaultChecked!=null&&(e.defaultChecked=!!n.defaultChecked)}function $e(e,n,i){if(n.hasOwnProperty("value")||n.hasOwnProperty("defaultValue")){var s=n.type;if(!(s!=="submit"&&s!=="reset"||n.value!==void 0&&n.value!==null))return;n=""+e._wrapperState.initialValue,i||n===e.value||(e.value=n),e.defaultValue=n}i=e.name,i!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,i!==""&&(e.name=i)}function Ae(e,n,i){(n!=="number"||ge(e.ownerDocument)!==e)&&(i==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+i&&(e.defaultValue=""+i))}var et=Array.isArray;function Ye(e,n,i,s){if(e=e.options,n){n={};for(var c=0;c"+n.valueOf().toString()+"",n=vt.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;n.firstChild;)e.appendChild(n.firstChild)}});function $t(e,n){if(n){var i=e.firstChild;if(i&&i===e.lastChild&&i.nodeType===3){i.nodeValue=n;return}}e.textContent=n}var vn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Ci=["Webkit","ms","Moz","O"];Object.keys(vn).forEach(function(e){Ci.forEach(function(n){n=n+e.charAt(0).toUpperCase()+e.substring(1),vn[n]=vn[e]})});function Dr(e,n,i){return n==null||typeof n=="boolean"||n===""?"":i||typeof n!="number"||n===0||vn.hasOwnProperty(e)&&vn[e]?(""+n).trim():n+"px"}function $r(e,n){e=e.style;for(var i in n)if(n.hasOwnProperty(i)){var s=i.indexOf("--")===0,c=Dr(i,n[i],s);i==="float"&&(i="cssFloat"),s?e.setProperty(i,c):e[i]=c}}var ji=B({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function or(e,n){if(n){if(ji[e]&&(n.children!=null||n.dangerouslySetInnerHTML!=null))throw Error(o(137,e));if(n.dangerouslySetInnerHTML!=null){if(n.children!=null)throw Error(o(60));if(typeof n.dangerouslySetInnerHTML!="object"||!("__html"in n.dangerouslySetInnerHTML))throw Error(o(61))}if(n.style!=null&&typeof n.style!="object")throw Error(o(62))}}function In(e,n){if(e.indexOf("-")===-1)return typeof n.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var sr=null;function lr(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var Tn=null,Ot=null,Xt=null;function Or(e){if(e=Qi(e)){if(typeof Tn!="function")throw Error(o(280));var n=e.stateNode;n&&(n=ss(n),Tn(e.stateNode,e.type,n))}}function Fr(e){Ot?Xt?Xt.push(e):Xt=[e]:Ot=e}function Hr(){if(Ot){var e=Ot,n=Xt;if(Xt=Ot=null,Or(e),n)for(e=0;e>>=0,e===0?32:31-(Fl(e)/Hl|0)|0}var Ur=64,Wr=4194304;function dr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function xn(e,n){var i=e.pendingLanes;if(i===0)return 0;var s=0,c=e.suspendedLanes,h=e.pingedLanes,w=i&268435455;if(w!==0){var I=w&~c;I!==0?s=dr(I):(h&=w,h!==0&&(s=dr(h)))}else w=i&~c,w!==0?s=dr(w):h!==0&&(s=dr(h));if(s===0)return 0;if(n!==0&&n!==s&&(n&c)===0&&(c=s&-s,h=n&-n,c>=h||c===16&&(h&4194240)!==0))return n;if((s&4)!==0&&(s|=i&16),n=e.entangledLanes,n!==0)for(e=e.entanglements,n&=s;0i;i++)n.push(e);return n}function hr(e,n,i){e.pendingLanes|=n,n!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,n=31-It(n),e[n]=i}function Ul(e,n){var i=e.pendingLanes&~n;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=n,e.mutableReadLanes&=n,e.entangledLanes&=n,n=e.entanglements;var s=e.eventTimes;for(e=e.expirationTimes;0=Fi),Ac=" ",zc=!1;function Dc(e,n){switch(e){case"keyup":return Um.indexOf(n.keyCode)!==-1;case"keydown":return n.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function $c(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Qr=!1;function Ym(e,n){switch(e){case"compositionend":return $c(n);case"keypress":return n.which!==32?null:(zc=!0,Ac);case"textInput":return e=n.data,e===Ac&&zc?null:e;default:return null}}function Xm(e,n){if(Qr)return e==="compositionend"||!ta&&Dc(e,n)?(e=Mc(),Qo=Ql=On=null,Qr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(n.ctrlKey||n.altKey||n.metaKey)||n.ctrlKey&&n.altKey){if(n.char&&1=n)return{node:i,offset:n-e};e=s}e:{for(;i;){if(i.nextSibling){i=i.nextSibling;break e}i=i.parentNode}i=void 0}i=Wc(i)}}function Xc(e,n){return e&&n?e===n?!0:e&&e.nodeType===3?!1:n&&n.nodeType===3?Xc(e,n.parentNode):"contains"in e?e.contains(n):e.compareDocumentPosition?!!(e.compareDocumentPosition(n)&16):!1:!1}function Gc(){for(var e=window,n=ge();n instanceof e.HTMLIFrameElement;){try{var i=typeof n.contentWindow.location.href=="string"}catch{i=!1}if(i)e=n.contentWindow;else break;n=ge(e.document)}return n}function ia(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&(n==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||n==="textarea"||e.contentEditable==="true")}function ny(e){var n=Gc(),i=e.focusedElem,s=e.selectionRange;if(n!==i&&i&&i.ownerDocument&&Xc(i.ownerDocument.documentElement,i)){if(s!==null&&ia(i)){if(n=s.start,e=s.end,e===void 0&&(e=n),"selectionStart"in i)i.selectionStart=n,i.selectionEnd=Math.min(e,i.value.length);else if(e=(n=i.ownerDocument||document)&&n.defaultView||window,e.getSelection){e=e.getSelection();var c=i.textContent.length,h=Math.min(s.start,c);s=s.end===void 0?h:Math.min(s.end,c),!e.extend&&h>s&&(c=s,s=h,h=c),c=Yc(i,h);var w=Yc(i,s);c&&w&&(e.rangeCount!==1||e.anchorNode!==c.node||e.anchorOffset!==c.offset||e.focusNode!==w.node||e.focusOffset!==w.offset)&&(n=n.createRange(),n.setStart(c.node,c.offset),e.removeAllRanges(),h>s?(e.addRange(n),e.extend(w.node,w.offset)):(n.setEnd(w.node,w.offset),e.addRange(n)))}}for(n=[],e=i;e=e.parentNode;)e.nodeType===1&&n.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof i.focus=="function"&&i.focus(),i=0;i=document.documentMode,qr=null,oa=null,Ui=null,sa=!1;function Qc(e,n,i){var s=i.window===i?i.document:i.nodeType===9?i:i.ownerDocument;sa||qr==null||qr!==ge(s)||(s=qr,"selectionStart"in s&&ia(s)?s={start:s.selectionStart,end:s.selectionEnd}:(s=(s.ownerDocument&&s.ownerDocument.defaultView||window).getSelection(),s={anchorNode:s.anchorNode,anchorOffset:s.anchorOffset,focusNode:s.focusNode,focusOffset:s.focusOffset}),Ui&&Vi(Ui,s)||(Ui=s,s=rs(oa,"onSelect"),0ti||(e.current=va[ti],va[ti]=null,ti--)}function Oe(e,n){ti++,va[ti]=e.current,e.current=n}var Vn={},ht=Bn(Vn),St=Bn(!1),gr=Vn;function ni(e,n){var i=e.type.contextTypes;if(!i)return Vn;var s=e.stateNode;if(s&&s.__reactInternalMemoizedUnmaskedChildContext===n)return s.__reactInternalMemoizedMaskedChildContext;var c={},h;for(h in i)c[h]=n[h];return s&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=c),c}function kt(e){return e=e.childContextTypes,e!=null}function ls(){He(St),He(ht)}function cd(e,n,i){if(ht.current!==Vn)throw Error(o(168));Oe(ht,n),Oe(St,i)}function dd(e,n,i){var s=e.stateNode;if(n=n.childContextTypes,typeof s.getChildContext!="function")return i;s=s.getChildContext();for(var c in s)if(!(c in n))throw Error(o(108,de(e)||"Unknown",c));return B({},i,s)}function as(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Vn,gr=ht.current,Oe(ht,e),Oe(St,St.current),!0}function fd(e,n,i){var s=e.stateNode;if(!s)throw Error(o(169));i?(e=dd(e,n,gr),s.__reactInternalMemoizedMergedChildContext=e,He(St),He(ht),Oe(ht,e)):He(St),Oe(St,i)}var _n=null,us=!1,xa=!1;function hd(e){_n===null?_n=[e]:_n.push(e)}function py(e){us=!0,hd(e)}function Un(){if(!xa&&_n!==null){xa=!0;var e=0,n=ze;try{var i=_n;for(ze=1;e>=w,c-=w,Sn=1<<32-It(n)+c|i<je?(at=Ee,Ee=null):at=Ee.sibling;var Re=oe(G,Ee,Q[je],ue);if(Re===null){Ee===null&&(Ee=at);break}e&&Ee&&Re.alternate===null&&n(G,Ee),F=h(Re,F,je),ke===null?we=Re:ke.sibling=Re,ke=Re,Ee=at}if(je===Q.length)return i(G,Ee),Ue&&yr(G,je),we;if(Ee===null){for(;jeje?(at=Ee,Ee=null):at=Ee.sibling;var Jn=oe(G,Ee,Re.value,ue);if(Jn===null){Ee===null&&(Ee=at);break}e&&Ee&&Jn.alternate===null&&n(G,Ee),F=h(Jn,F,je),ke===null?we=Jn:ke.sibling=Jn,ke=Jn,Ee=at}if(Re.done)return i(G,Ee),Ue&&yr(G,je),we;if(Ee===null){for(;!Re.done;je++,Re=Q.next())Re=ae(G,Re.value,ue),Re!==null&&(F=h(Re,F,je),ke===null?we=Re:ke.sibling=Re,ke=Re);return Ue&&yr(G,je),we}for(Ee=s(G,Ee);!Re.done;je++,Re=Q.next())Re=pe(Ee,G,je,Re.value,ue),Re!==null&&(e&&Re.alternate!==null&&Ee.delete(Re.key===null?je:Re.key),F=h(Re,F,je),ke===null?we=Re:ke.sibling=Re,ke=Re);return e&&Ee.forEach(function(Gy){return n(G,Gy)}),Ue&&yr(G,je),we}function Ze(G,F,Q,ue){if(typeof Q=="object"&&Q!==null&&Q.type===O&&Q.key===null&&(Q=Q.props.children),typeof Q=="object"&&Q!==null){switch(Q.$$typeof){case R:e:{for(var we=Q.key,ke=F;ke!==null;){if(ke.key===we){if(we=Q.type,we===O){if(ke.tag===7){i(G,ke.sibling),F=c(ke,Q.props.children),F.return=G,G=F;break e}}else if(ke.elementType===we||typeof we=="object"&&we!==null&&we.$$typeof===H&&xd(we)===ke.type){i(G,ke.sibling),F=c(ke,Q.props),F.ref=qi(G,ke,Q),F.return=G,G=F;break e}i(G,ke);break}else n(G,ke);ke=ke.sibling}Q.type===O?(F=Nr(Q.props.children,G.mode,ue,Q.key),F.return=G,G=F):(ue=Ds(Q.type,Q.key,Q.props,null,G.mode,ue),ue.ref=qi(G,F,Q),ue.return=G,G=ue)}return w(G);case D:e:{for(ke=Q.key;F!==null;){if(F.key===ke)if(F.tag===4&&F.stateNode.containerInfo===Q.containerInfo&&F.stateNode.implementation===Q.implementation){i(G,F.sibling),F=c(F,Q.children||[]),F.return=G,G=F;break e}else{i(G,F);break}else n(G,F);F=F.sibling}F=mu(Q,G.mode,ue),F.return=G,G=F}return w(G);case H:return ke=Q._init,Ze(G,F,ke(Q._payload),ue)}if(et(Q))return ye(G,F,Q,ue);if(z(Q))return xe(G,F,Q,ue);hs(G,Q)}return typeof Q=="string"&&Q!==""||typeof Q=="number"?(Q=""+Q,F!==null&&F.tag===6?(i(G,F.sibling),F=c(F,Q),F.return=G,G=F):(i(G,F),F=gu(Q,G.mode,ue),F.return=G,G=F),w(G)):i(G,F)}return Ze}var si=wd(!0),_d=wd(!1),ps=Bn(null),gs=null,li=null,Na=null;function Ca(){Na=li=gs=null}function ja(e){var n=ps.current;He(ps),e._currentValue=n}function ba(e,n,i){for(;e!==null;){var s=e.alternate;if((e.childLanes&n)!==n?(e.childLanes|=n,s!==null&&(s.childLanes|=n)):s!==null&&(s.childLanes&n)!==n&&(s.childLanes|=n),e===i)break;e=e.return}}function ai(e,n){gs=e,Na=li=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&n)!==0&&(Et=!0),e.firstContext=null)}function Bt(e){var n=e._currentValue;if(Na!==e)if(e={context:e,memoizedValue:n,next:null},li===null){if(gs===null)throw Error(o(308));li=e,gs.dependencies={lanes:0,firstContext:e}}else li=li.next=e;return n}var vr=null;function Ma(e){vr===null?vr=[e]:vr.push(e)}function Sd(e,n,i,s){var c=n.interleaved;return c===null?(i.next=i,Ma(n)):(i.next=c.next,c.next=i),n.interleaved=i,En(e,s)}function En(e,n){e.lanes|=n;var i=e.alternate;for(i!==null&&(i.lanes|=n),i=e,e=e.return;e!==null;)e.childLanes|=n,i=e.alternate,i!==null&&(i.childLanes|=n),i=e,e=e.return;return i.tag===3?i.stateNode:null}var Wn=!1;function Pa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function kd(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Nn(e,n){return{eventTime:e,lane:n,tag:0,payload:null,callback:null,next:null}}function Yn(e,n,i){var s=e.updateQueue;if(s===null)return null;if(s=s.shared,(Ie&2)!==0){var c=s.pending;return c===null?n.next=n:(n.next=c.next,c.next=n),s.pending=n,En(e,i)}return c=s.interleaved,c===null?(n.next=n,Ma(s)):(n.next=c.next,c.next=n),s.interleaved=n,En(e,i)}function ms(e,n,i){if(n=n.updateQueue,n!==null&&(n=n.shared,(i&4194240)!==0)){var s=n.lanes;s&=e.pendingLanes,i|=s,n.lanes=i,Yr(e,i)}}function Ed(e,n){var i=e.updateQueue,s=e.alternate;if(s!==null&&(s=s.updateQueue,i===s)){var c=null,h=null;if(i=i.firstBaseUpdate,i!==null){do{var w={eventTime:i.eventTime,lane:i.lane,tag:i.tag,payload:i.payload,callback:i.callback,next:null};h===null?c=h=w:h=h.next=w,i=i.next}while(i!==null);h===null?c=h=n:h=h.next=n}else c=h=n;i={baseState:s.baseState,firstBaseUpdate:c,lastBaseUpdate:h,shared:s.shared,effects:s.effects},e.updateQueue=i;return}e=i.lastBaseUpdate,e===null?i.firstBaseUpdate=n:e.next=n,i.lastBaseUpdate=n}function ys(e,n,i,s){var c=e.updateQueue;Wn=!1;var h=c.firstBaseUpdate,w=c.lastBaseUpdate,I=c.shared.pending;if(I!==null){c.shared.pending=null;var L=I,Z=L.next;L.next=null,w===null?h=Z:w.next=Z,w=L;var se=e.alternate;se!==null&&(se=se.updateQueue,I=se.lastBaseUpdate,I!==w&&(I===null?se.firstBaseUpdate=Z:I.next=Z,se.lastBaseUpdate=L))}if(h!==null){var ae=c.baseState;w=0,se=Z=L=null,I=h;do{var oe=I.lane,pe=I.eventTime;if((s&oe)===oe){se!==null&&(se=se.next={eventTime:pe,lane:0,tag:I.tag,payload:I.payload,callback:I.callback,next:null});e:{var ye=e,xe=I;switch(oe=n,pe=i,xe.tag){case 1:if(ye=xe.payload,typeof ye=="function"){ae=ye.call(pe,ae,oe);break e}ae=ye;break e;case 3:ye.flags=ye.flags&-65537|128;case 0:if(ye=xe.payload,oe=typeof ye=="function"?ye.call(pe,ae,oe):ye,oe==null)break e;ae=B({},ae,oe);break e;case 2:Wn=!0}}I.callback!==null&&I.lane!==0&&(e.flags|=64,oe=c.effects,oe===null?c.effects=[I]:oe.push(I))}else pe={eventTime:pe,lane:oe,tag:I.tag,payload:I.payload,callback:I.callback,next:null},se===null?(Z=se=pe,L=ae):se=se.next=pe,w|=oe;if(I=I.next,I===null){if(I=c.shared.pending,I===null)break;oe=I,I=oe.next,oe.next=null,c.lastBaseUpdate=oe,c.shared.pending=null}}while(!0);if(se===null&&(L=ae),c.baseState=L,c.firstBaseUpdate=Z,c.lastBaseUpdate=se,n=c.shared.interleaved,n!==null){c=n;do w|=c.lane,c=c.next;while(c!==n)}else h===null&&(c.shared.lanes=0);_r|=w,e.lanes=w,e.memoizedState=ae}}function Nd(e,n,i){if(e=n.effects,n.effects=null,e!==null)for(n=0;ni?i:4,e(!0);var s=Aa.transition;Aa.transition={};try{e(!1),n()}finally{ze=i,Aa.transition=s}}function Ud(){return Vt().memoizedState}function vy(e,n,i){var s=qn(e);if(i={lane:s,action:i,hasEagerState:!1,eagerState:null,next:null},Wd(e))Yd(n,i);else if(i=Sd(e,n,i,s),i!==null){var c=wt();Jt(i,e,s,c),Xd(i,n,s)}}function xy(e,n,i){var s=qn(e),c={lane:s,action:i,hasEagerState:!1,eagerState:null,next:null};if(Wd(e))Yd(n,c);else{var h=e.alternate;if(e.lanes===0&&(h===null||h.lanes===0)&&(h=n.lastRenderedReducer,h!==null))try{var w=n.lastRenderedState,I=h(w,i);if(c.hasEagerState=!0,c.eagerState=I,Gt(I,w)){var L=n.interleaved;L===null?(c.next=c,Ma(n)):(c.next=L.next,L.next=c),n.interleaved=c;return}}catch{}finally{}i=Sd(e,n,c,s),i!==null&&(c=wt(),Jt(i,e,s,c),Xd(i,n,s))}}function Wd(e){var n=e.alternate;return e===Qe||n!==null&&n===Qe}function Yd(e,n){eo=ws=!0;var i=e.pending;i===null?n.next=n:(n.next=i.next,i.next=n),e.pending=n}function Xd(e,n,i){if((i&4194240)!==0){var s=n.lanes;s&=e.pendingLanes,i|=s,n.lanes=i,Yr(e,i)}}var ks={readContext:Bt,useCallback:pt,useContext:pt,useEffect:pt,useImperativeHandle:pt,useInsertionEffect:pt,useLayoutEffect:pt,useMemo:pt,useReducer:pt,useRef:pt,useState:pt,useDebugValue:pt,useDeferredValue:pt,useTransition:pt,useMutableSource:pt,useSyncExternalStore:pt,useId:pt,unstable_isNewReconciler:!1},wy={readContext:Bt,useCallback:function(e,n){return hn().memoizedState=[e,n===void 0?null:n],e},useContext:Bt,useEffect:zd,useImperativeHandle:function(e,n,i){return i=i!=null?i.concat([e]):null,_s(4194308,4,Od.bind(null,n,e),i)},useLayoutEffect:function(e,n){return _s(4194308,4,e,n)},useInsertionEffect:function(e,n){return _s(4,2,e,n)},useMemo:function(e,n){var i=hn();return n=n===void 0?null:n,e=e(),i.memoizedState=[e,n],e},useReducer:function(e,n,i){var s=hn();return n=i!==void 0?i(n):n,s.memoizedState=s.baseState=n,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},s.queue=e,e=e.dispatch=vy.bind(null,Qe,e),[s.memoizedState,e]},useRef:function(e){var n=hn();return e={current:e},n.memoizedState=e},useState:Ld,useDebugValue:Ba,useDeferredValue:function(e){return hn().memoizedState=e},useTransition:function(){var e=Ld(!1),n=e[0];return e=yy.bind(null,e[1]),hn().memoizedState=e,[n,e]},useMutableSource:function(){},useSyncExternalStore:function(e,n,i){var s=Qe,c=hn();if(Ue){if(i===void 0)throw Error(o(407));i=i()}else{if(i=n(),lt===null)throw Error(o(349));(wr&30)!==0||Md(s,n,i)}c.memoizedState=i;var h={value:i,getSnapshot:n};return c.queue=h,zd(Id.bind(null,s,h,e),[e]),s.flags|=2048,ro(9,Pd.bind(null,s,h,i,n),void 0,null),i},useId:function(){var e=hn(),n=lt.identifierPrefix;if(Ue){var i=kn,s=Sn;i=(s&~(1<<32-It(s)-1)).toString(32)+i,n=":"+n+"R"+i,i=to++,0<\/script>",e=e.removeChild(e.firstChild)):typeof s.is=="string"?e=w.createElement(i,{is:s.is}):(e=w.createElement(i),i==="select"&&(w=e,s.multiple?w.multiple=!0:s.size&&(w.size=s.size))):e=w.createElementNS(e,i),e[dn]=n,e[Gi]=s,pf(e,n,!1,!1),n.stateNode=e;e:{switch(w=In(i,s),i){case"dialog":Fe("cancel",e),Fe("close",e),c=s;break;case"iframe":case"object":case"embed":Fe("load",e),c=s;break;case"video":case"audio":for(c=0;chi&&(n.flags|=128,s=!0,io(h,!1),n.lanes=4194304)}else{if(!s)if(e=vs(w),e!==null){if(n.flags|=128,s=!0,i=e.updateQueue,i!==null&&(n.updateQueue=i,n.flags|=4),io(h,!0),h.tail===null&&h.tailMode==="hidden"&&!w.alternate&&!Ue)return gt(n),null}else 2*Xe()-h.renderingStartTime>hi&&i!==1073741824&&(n.flags|=128,s=!0,io(h,!1),n.lanes=4194304);h.isBackwards?(w.sibling=n.child,n.child=w):(i=h.last,i!==null?i.sibling=w:n.child=w,h.last=w)}return h.tail!==null?(n=h.tail,h.rendering=n,h.tail=n.sibling,h.renderingStartTime=Xe(),n.sibling=null,i=Ge.current,Oe(Ge,s?i&1|2:i&1),n):(gt(n),null);case 22:case 23:return fu(),s=n.memoizedState!==null,e!==null&&e.memoizedState!==null!==s&&(n.flags|=8192),s&&(n.mode&1)!==0?(At&1073741824)!==0&&(gt(n),n.subtreeFlags&6&&(n.flags|=8192)):gt(n),null;case 24:return null;case 25:return null}throw Error(o(156,n.tag))}function by(e,n){switch(_a(n),n.tag){case 1:return kt(n.type)&&ls(),e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 3:return ui(),He(St),He(ht),La(),e=n.flags,(e&65536)!==0&&(e&128)===0?(n.flags=e&-65537|128,n):null;case 5:return Ta(n),null;case 13:if(He(Ge),e=n.memoizedState,e!==null&&e.dehydrated!==null){if(n.alternate===null)throw Error(o(340));oi()}return e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 19:return He(Ge),null;case 4:return ui(),null;case 10:return ja(n.type._context),null;case 22:case 23:return fu(),null;case 24:return null;default:return null}}var js=!1,mt=!1,My=typeof WeakSet=="function"?WeakSet:Set,me=null;function di(e,n){var i=e.ref;if(i!==null)if(typeof i=="function")try{i(null)}catch(s){Ke(e,n,s)}else i.current=null}function eu(e,n,i){try{i()}catch(s){Ke(e,n,s)}}var yf=!1;function Py(e,n){if(fa=Xo,e=Gc(),ia(e)){if("selectionStart"in e)var i={start:e.selectionStart,end:e.selectionEnd};else e:{i=(i=e.ownerDocument)&&i.defaultView||window;var s=i.getSelection&&i.getSelection();if(s&&s.rangeCount!==0){i=s.anchorNode;var c=s.anchorOffset,h=s.focusNode;s=s.focusOffset;try{i.nodeType,h.nodeType}catch{i=null;break e}var w=0,I=-1,L=-1,Z=0,se=0,ae=e,oe=null;t:for(;;){for(var pe;ae!==i||c!==0&&ae.nodeType!==3||(I=w+c),ae!==h||s!==0&&ae.nodeType!==3||(L=w+s),ae.nodeType===3&&(w+=ae.nodeValue.length),(pe=ae.firstChild)!==null;)oe=ae,ae=pe;for(;;){if(ae===e)break t;if(oe===i&&++Z===c&&(I=w),oe===h&&++se===s&&(L=w),(pe=ae.nextSibling)!==null)break;ae=oe,oe=ae.parentNode}ae=pe}i=I===-1||L===-1?null:{start:I,end:L}}else i=null}i=i||{start:0,end:0}}else i=null;for(ha={focusedElem:e,selectionRange:i},Xo=!1,me=n;me!==null;)if(n=me,e=n.child,(n.subtreeFlags&1028)!==0&&e!==null)e.return=n,me=e;else for(;me!==null;){n=me;try{var ye=n.alternate;if((n.flags&1024)!==0)switch(n.tag){case 0:case 11:case 15:break;case 1:if(ye!==null){var xe=ye.memoizedProps,Ze=ye.memoizedState,G=n.stateNode,F=G.getSnapshotBeforeUpdate(n.elementType===n.type?xe:qt(n.type,xe),Ze);G.__reactInternalSnapshotBeforeUpdate=F}break;case 3:var Q=n.stateNode.containerInfo;Q.nodeType===1?Q.textContent="":Q.nodeType===9&&Q.documentElement&&Q.removeChild(Q.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(o(163))}}catch(ue){Ke(n,n.return,ue)}if(e=n.sibling,e!==null){e.return=n.return,me=e;break}me=n.return}return ye=yf,yf=!1,ye}function oo(e,n,i){var s=n.updateQueue;if(s=s!==null?s.lastEffect:null,s!==null){var c=s=s.next;do{if((c.tag&e)===e){var h=c.destroy;c.destroy=void 0,h!==void 0&&eu(n,i,h)}c=c.next}while(c!==s)}}function bs(e,n){if(n=n.updateQueue,n=n!==null?n.lastEffect:null,n!==null){var i=n=n.next;do{if((i.tag&e)===e){var s=i.create;i.destroy=s()}i=i.next}while(i!==n)}}function tu(e){var n=e.ref;if(n!==null){var i=e.stateNode;switch(e.tag){case 5:e=i;break;default:e=i}typeof n=="function"?n(e):n.current=e}}function vf(e){var n=e.alternate;n!==null&&(e.alternate=null,vf(n)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(n=e.stateNode,n!==null&&(delete n[dn],delete n[Gi],delete n[ya],delete n[fy],delete n[hy])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function xf(e){return e.tag===5||e.tag===3||e.tag===4}function wf(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||xf(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function nu(e,n,i){var s=e.tag;if(s===5||s===6)e=e.stateNode,n?i.nodeType===8?i.parentNode.insertBefore(e,n):i.insertBefore(e,n):(i.nodeType===8?(n=i.parentNode,n.insertBefore(e,i)):(n=i,n.appendChild(e)),i=i._reactRootContainer,i!=null||n.onclick!==null||(n.onclick=os));else if(s!==4&&(e=e.child,e!==null))for(nu(e,n,i),e=e.sibling;e!==null;)nu(e,n,i),e=e.sibling}function ru(e,n,i){var s=e.tag;if(s===5||s===6)e=e.stateNode,n?i.insertBefore(e,n):i.appendChild(e);else if(s!==4&&(e=e.child,e!==null))for(ru(e,n,i),e=e.sibling;e!==null;)ru(e,n,i),e=e.sibling}var ct=null,Kt=!1;function Xn(e,n,i){for(i=i.child;i!==null;)_f(e,n,i),i=i.sibling}function _f(e,n,i){if(Pt&&typeof Pt.onCommitFiberUnmount=="function")try{Pt.onCommitFiberUnmount(Vr,i)}catch{}switch(i.tag){case 5:mt||di(i,n);case 6:var s=ct,c=Kt;ct=null,Xn(e,n,i),ct=s,Kt=c,ct!==null&&(Kt?(e=ct,i=i.stateNode,e.nodeType===8?e.parentNode.removeChild(i):e.removeChild(i)):ct.removeChild(i.stateNode));break;case 18:ct!==null&&(Kt?(e=ct,i=i.stateNode,e.nodeType===8?ma(e.parentNode,i):e.nodeType===1&&ma(e,i),Di(e)):ma(ct,i.stateNode));break;case 4:s=ct,c=Kt,ct=i.stateNode.containerInfo,Kt=!0,Xn(e,n,i),ct=s,Kt=c;break;case 0:case 11:case 14:case 15:if(!mt&&(s=i.updateQueue,s!==null&&(s=s.lastEffect,s!==null))){c=s=s.next;do{var h=c,w=h.destroy;h=h.tag,w!==void 0&&((h&2)!==0||(h&4)!==0)&&eu(i,n,w),c=c.next}while(c!==s)}Xn(e,n,i);break;case 1:if(!mt&&(di(i,n),s=i.stateNode,typeof s.componentWillUnmount=="function"))try{s.props=i.memoizedProps,s.state=i.memoizedState,s.componentWillUnmount()}catch(I){Ke(i,n,I)}Xn(e,n,i);break;case 21:Xn(e,n,i);break;case 22:i.mode&1?(mt=(s=mt)||i.memoizedState!==null,Xn(e,n,i),mt=s):Xn(e,n,i);break;default:Xn(e,n,i)}}function Sf(e){var n=e.updateQueue;if(n!==null){e.updateQueue=null;var i=e.stateNode;i===null&&(i=e.stateNode=new My),n.forEach(function(s){var c=Oy.bind(null,e,s);i.has(s)||(i.add(s),s.then(c,c))})}}function Zt(e,n){var i=n.deletions;if(i!==null)for(var s=0;sc&&(c=w),s&=~h}if(s=c,s=Xe()-s,s=(120>s?120:480>s?480:1080>s?1080:1920>s?1920:3e3>s?3e3:4320>s?4320:1960*Ty(s/1960))-s,10e?16:e,Qn===null)var s=!1;else{if(e=Qn,Qn=null,Rs=0,(Ie&6)!==0)throw Error(o(331));var c=Ie;for(Ie|=4,me=e.current;me!==null;){var h=me,w=h.child;if((me.flags&16)!==0){var I=h.deletions;if(I!==null){for(var L=0;LXe()-su?kr(e,0):ou|=i),Ct(e,n)}function Af(e,n){n===0&&((e.mode&1)===0?n=1:(n=Wr,Wr<<=1,(Wr&130023424)===0&&(Wr=4194304)));var i=wt();e=En(e,n),e!==null&&(hr(e,n,i),Ct(e,i))}function $y(e){var n=e.memoizedState,i=0;n!==null&&(i=n.retryLane),Af(e,i)}function Oy(e,n){var i=0;switch(e.tag){case 13:var s=e.stateNode,c=e.memoizedState;c!==null&&(i=c.retryLane);break;case 19:s=e.stateNode;break;default:throw Error(o(314))}s!==null&&s.delete(n),Af(e,i)}var zf;zf=function(e,n,i){if(e!==null)if(e.memoizedProps!==n.pendingProps||St.current)Et=!0;else{if((e.lanes&i)===0&&(n.flags&128)===0)return Et=!1,Cy(e,n,i);Et=(e.flags&131072)!==0}else Et=!1,Ue&&(n.flags&1048576)!==0&&pd(n,ds,n.index);switch(n.lanes=0,n.tag){case 2:var s=n.type;Cs(e,n),e=n.pendingProps;var c=ni(n,ht.current);ai(n,i),c=Da(null,n,s,e,c,i);var h=$a();return n.flags|=1,typeof c=="object"&&c!==null&&typeof c.render=="function"&&c.$$typeof===void 0?(n.tag=1,n.memoizedState=null,n.updateQueue=null,kt(s)?(h=!0,as(n)):h=!1,n.memoizedState=c.state!==null&&c.state!==void 0?c.state:null,Pa(n),c.updater=Es,n.stateNode=c,c._reactInternals=n,Ua(n,s,e,i),n=Ga(null,n,s,!0,h,i)):(n.tag=0,Ue&&h&&wa(n),xt(null,n,c,i),n=n.child),n;case 16:s=n.elementType;e:{switch(Cs(e,n),e=n.pendingProps,c=s._init,s=c(s._payload),n.type=s,c=n.tag=Hy(s),e=qt(s,e),c){case 0:n=Xa(null,n,s,e,i);break e;case 1:n=af(null,n,s,e,i);break e;case 11:n=nf(null,n,s,e,i);break e;case 14:n=rf(null,n,s,qt(s.type,e),i);break e}throw Error(o(306,s,""))}return n;case 0:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:qt(s,c),Xa(e,n,s,c,i);case 1:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:qt(s,c),af(e,n,s,c,i);case 3:e:{if(uf(n),e===null)throw Error(o(387));s=n.pendingProps,h=n.memoizedState,c=h.element,kd(e,n),ys(n,s,null,i);var w=n.memoizedState;if(s=w.element,h.isDehydrated)if(h={element:s,isDehydrated:!1,cache:w.cache,pendingSuspenseBoundaries:w.pendingSuspenseBoundaries,transitions:w.transitions},n.updateQueue.baseState=h,n.memoizedState=h,n.flags&256){c=ci(Error(o(423)),n),n=cf(e,n,s,i,c);break e}else if(s!==c){c=ci(Error(o(424)),n),n=cf(e,n,s,i,c);break e}else for(Lt=Hn(n.stateNode.containerInfo.firstChild),Rt=n,Ue=!0,Qt=null,i=_d(n,null,s,i),n.child=i;i;)i.flags=i.flags&-3|4096,i=i.sibling;else{if(oi(),s===c){n=Cn(e,n,i);break e}xt(e,n,s,i)}n=n.child}return n;case 5:return Cd(n),e===null&&ka(n),s=n.type,c=n.pendingProps,h=e!==null?e.memoizedProps:null,w=c.children,pa(s,c)?w=null:h!==null&&pa(s,h)&&(n.flags|=32),lf(e,n),xt(e,n,w,i),n.child;case 6:return e===null&&ka(n),null;case 13:return df(e,n,i);case 4:return Ia(n,n.stateNode.containerInfo),s=n.pendingProps,e===null?n.child=si(n,null,s,i):xt(e,n,s,i),n.child;case 11:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:qt(s,c),nf(e,n,s,c,i);case 7:return xt(e,n,n.pendingProps,i),n.child;case 8:return xt(e,n,n.pendingProps.children,i),n.child;case 12:return xt(e,n,n.pendingProps.children,i),n.child;case 10:e:{if(s=n.type._context,c=n.pendingProps,h=n.memoizedProps,w=c.value,Oe(ps,s._currentValue),s._currentValue=w,h!==null)if(Gt(h.value,w)){if(h.children===c.children&&!St.current){n=Cn(e,n,i);break e}}else for(h=n.child,h!==null&&(h.return=n);h!==null;){var I=h.dependencies;if(I!==null){w=h.child;for(var L=I.firstContext;L!==null;){if(L.context===s){if(h.tag===1){L=Nn(-1,i&-i),L.tag=2;var Z=h.updateQueue;if(Z!==null){Z=Z.shared;var se=Z.pending;se===null?L.next=L:(L.next=se.next,se.next=L),Z.pending=L}}h.lanes|=i,L=h.alternate,L!==null&&(L.lanes|=i),ba(h.return,i,n),I.lanes|=i;break}L=L.next}}else if(h.tag===10)w=h.type===n.type?null:h.child;else if(h.tag===18){if(w=h.return,w===null)throw Error(o(341));w.lanes|=i,I=w.alternate,I!==null&&(I.lanes|=i),ba(w,i,n),w=h.sibling}else w=h.child;if(w!==null)w.return=h;else for(w=h;w!==null;){if(w===n){w=null;break}if(h=w.sibling,h!==null){h.return=w.return,w=h;break}w=w.return}h=w}xt(e,n,c.children,i),n=n.child}return n;case 9:return c=n.type,s=n.pendingProps.children,ai(n,i),c=Bt(c),s=s(c),n.flags|=1,xt(e,n,s,i),n.child;case 14:return s=n.type,c=qt(s,n.pendingProps),c=qt(s.type,c),rf(e,n,s,c,i);case 15:return of(e,n,n.type,n.pendingProps,i);case 17:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:qt(s,c),Cs(e,n),n.tag=1,kt(s)?(e=!0,as(n)):e=!1,ai(n,i),Qd(n,s,c),Ua(n,s,c,i),Ga(null,n,s,!0,e,i);case 19:return hf(e,n,i);case 22:return sf(e,n,i)}throw Error(o(156,n.tag))};function Df(e,n){return $o(e,n)}function Fy(e,n,i,s){this.tag=e,this.key=i,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=n,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=s,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Wt(e,n,i,s){return new Fy(e,n,i,s)}function pu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function Hy(e){if(typeof e=="function")return pu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===ee)return 11;if(e===Y)return 14}return 2}function Zn(e,n){var i=e.alternate;return i===null?(i=Wt(e.tag,n,e.key,e.mode),i.elementType=e.elementType,i.type=e.type,i.stateNode=e.stateNode,i.alternate=e,e.alternate=i):(i.pendingProps=n,i.type=e.type,i.flags=0,i.subtreeFlags=0,i.deletions=null),i.flags=e.flags&14680064,i.childLanes=e.childLanes,i.lanes=e.lanes,i.child=e.child,i.memoizedProps=e.memoizedProps,i.memoizedState=e.memoizedState,i.updateQueue=e.updateQueue,n=e.dependencies,i.dependencies=n===null?null:{lanes:n.lanes,firstContext:n.firstContext},i.sibling=e.sibling,i.index=e.index,i.ref=e.ref,i}function Ds(e,n,i,s,c,h){var w=2;if(s=e,typeof e=="function")pu(e)&&(w=1);else if(typeof e=="string")w=5;else e:switch(e){case O:return Nr(i.children,c,h,n);case U:w=8,c|=8;break;case q:return e=Wt(12,i,n,c|2),e.elementType=q,e.lanes=h,e;case J:return e=Wt(13,i,n,c),e.elementType=J,e.lanes=h,e;case b:return e=Wt(19,i,n,c),e.elementType=b,e.lanes=h,e;case W:return $s(i,c,h,n);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case te:w=10;break e;case X:w=9;break e;case ee:w=11;break e;case Y:w=14;break e;case H:w=16,s=null;break e}throw Error(o(130,e==null?e:typeof e,""))}return n=Wt(w,i,n,c),n.elementType=e,n.type=s,n.lanes=h,n}function Nr(e,n,i,s){return e=Wt(7,e,s,n),e.lanes=i,e}function $s(e,n,i,s){return e=Wt(22,e,s,n),e.elementType=W,e.lanes=i,e.stateNode={isHidden:!1},e}function gu(e,n,i){return e=Wt(6,e,null,n),e.lanes=i,e}function mu(e,n,i){return n=Wt(4,e.children!==null?e.children:[],e.key,n),n.lanes=i,n.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},n}function By(e,n,i,s,c){this.tag=n,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=fr(0),this.expirationTimes=fr(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=fr(0),this.identifierPrefix=s,this.onRecoverableError=c,this.mutableSourceEagerHydrationData=null}function yu(e,n,i,s,c,h,w,I,L){return e=new By(e,n,i,I,L),n===1?(n=1,h===!0&&(n|=8)):n=0,h=Wt(3,null,null,n),e.current=h,h.stateNode=e,h.memoizedState={element:s,isDehydrated:i,cache:null,transitions:null,pendingSuspenseBoundaries:null},Pa(h),e}function Vy(e,n,i){var s=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(r){console.error(r)}}return t(),ku.exports=t0(),ku.exports}var Kf;function n0(){if(Kf)return Ws;Kf=1;var t=wp();return Ws.createRoot=t.createRoot,Ws.hydrateRoot=t.hydrateRoot,Ws}var r0=n0();function i0(t,r="Request failed"){const o=(t||"").trim();if(!o)return r;try{const a=JSON.parse(o).detail;if(typeof a=="string"&&a.trim())return a;if(Array.isArray(a)){const u=a.map(d=>typeof d=="string"?d:d&&typeof d=="object"&&"msg"in d?String(d.msg):"").filter(Boolean);if(u.length)return u.join("; ")}}catch{}return o}async function Je(t,r){const o=await fetch(t,{...r,headers:{"Content-Type":"application/json",...(r==null?void 0:r.headers)||{}}});if(!o.ok){const l=await o.text();throw new Error(i0(l,o.statusText||"Request failed"))}return o.json()}const o0=["github_token","bitbucket_token","bitbucket_oauth_client_secret","ai_api_key","ai_model","ai_base_url"],We={health:()=>Je("/api/health"),settings:()=>Je("/api/settings"),saveSettings:t=>{const r={...t};for(const o of o0)r[o]===""&&delete r[o];return Je("/api/settings",{method:"PUT",body:JSON.stringify(r)})},repos:()=>Je("/api/repos"),browse:t=>Je(`/api/fs${t?`?path=${encodeURIComponent(t)}`:""}`),gitRefs:(t,r=50)=>Je(`/api/git/refs?repo_path=${encodeURIComponent(t)}&limit=${r}`),index:(t,r=!0)=>Je("/api/index",{method:"POST",body:JSON.stringify({repo_path:t,incremental:r})}),indexStatus:t=>Je(`/api/index?repo_path=${encodeURIComponent(t)}`),architecture:t=>Je(`/api/architecture?repo_path=${encodeURIComponent(t)}`),review:(t,r,o,l=!0)=>Je("/api/review",{method:"POST",body:JSON.stringify({repo_path:t,base:r,head:o||null,reindex:l,incremental:!0,three_dot:!0})}),init:(t,r=!1)=>Je("/api/init",{method:"POST",body:JSON.stringify({repo_path:t,overwrite:r})}),postComment:(t,r,o,l)=>Je("/api/prs/comment",{method:"POST",body:JSON.stringify({provider:t,repo:r,number:o,markdown:l})}),graph:(t,r="full")=>Je(`/api/graph?repo_path=${encodeURIComponent(t)}&scope=${r}`),prs:(t,r,o="open")=>Je("/api/prs",{method:"POST",body:JSON.stringify({provider:t,repo:r,state:o})}),scmRepos:t=>Je(`/api/scm/repos?provider=${encodeURIComponent(t)}`),oauthStatus:()=>Je("/api/oauth/status"),githubOAuthStart:()=>Je("/api/oauth/github/start",{method:"POST",body:"{}"}),githubOAuthPoll:t=>Je("/api/oauth/github/poll",{method:"POST",body:JSON.stringify({flow_id:t})}),bitbucketOAuthStart:()=>Je("/api/oauth/bitbucket/start"),oauthDisconnect:t=>Je("/api/oauth/disconnect",{method:"POST",body:JSON.stringify({provider:t})}),residual:t=>Je("/api/ai/residual",{method:"POST",body:JSON.stringify({review:t})})};function vo(t){return t.replaceAll("_"," ")}function s0(t){return t.replaceAll("_"," ")}function vl(t){return t.split(".").pop()||t}function l0(t){if(!t)return"";const r=new Date(t);return Number.isNaN(r.getTime())?t:r.toLocaleString()}function a0(t){return t.split(/[\\/]/).filter(Boolean).pop()||t}function jr(t){return t.replace(/([/\\._:@-])/g,"$1​")}function zr({className:t,children:r}){return p.jsx("svg",{className:t,width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:r})}function u0({className:t}){return p.jsxs(zr,{className:t,children:[p.jsx("path",{d:"M3 3.5h6.5L13 7v5.5H3z"}),p.jsx("path",{d:"M9.5 3.5V7H13"}),p.jsx("path",{d:"M5.5 9.5h5M5.5 11.5h3.5"})]})}function c0({className:t}){return p.jsxs(zr,{className:t,children:[p.jsx("rect",{x:"2.5",y:"2.5",width:"4.5",height:"4.5",rx:"0.8"}),p.jsx("rect",{x:"9",y:"2.5",width:"4.5",height:"4.5",rx:"0.8"}),p.jsx("rect",{x:"2.5",y:"9",width:"4.5",height:"4.5",rx:"0.8"}),p.jsx("rect",{x:"9",y:"9",width:"4.5",height:"4.5",rx:"0.8"})]})}function d0({className:t}){return p.jsxs(zr,{className:t,children:[p.jsx("circle",{cx:"4",cy:"8",r:"1.6"}),p.jsx("circle",{cx:"12",cy:"4",r:"1.6"}),p.jsx("circle",{cx:"12",cy:"12",r:"1.6"}),p.jsx("path",{d:"M5.5 7.2 10.4 4.8M5.5 8.8 10.4 11.2"})]})}function f0({className:t}){return p.jsxs(zr,{className:t,children:[p.jsx("circle",{cx:"4.5",cy:"4",r:"1.4"}),p.jsx("circle",{cx:"4.5",cy:"12",r:"1.4"}),p.jsx("circle",{cx:"11.5",cy:"12",r:"1.4"}),p.jsx("path",{d:"M4.5 5.5v5M4.5 8h4.2a3 3 0 0 1 3 3"})]})}function h0({className:t}){return p.jsxs(zr,{className:t,children:[p.jsx("circle",{cx:"8",cy:"8",r:"2.1"}),p.jsx("path",{d:"M8 2.5v1.6M8 11.9v1.6M2.5 8h1.6M11.9 8h1.6M4.1 4.1l1.1 1.1M10.8 10.8l1.1 1.1M11.9 4.1l-1.1 1.1M5.2 10.8l-1.1 1.1"})]})}function _p({className:t}){return p.jsx(zr,{className:t,children:p.jsx("path",{d:"M2.5 4.5h4L8 6h5.5v6.5h-11z"})})}function p0({className:t}){return p.jsx(zr,{className:t,children:p.jsx("path",{d:"M4 6.5 8 10.5 12 6.5"})})}const g0="modulepreload",m0=function(t,r){return new URL(t,r).href},Zf={},y0=function(r,o,l){let a=Promise.resolve();if(o&&o.length>0){let d=function(y){return Promise.all(y.map(x=>Promise.resolve(x).then(v=>({status:"fulfilled",value:v}),v=>({status:"rejected",reason:v}))))};const f=document.getElementsByTagName("link"),g=document.querySelector("meta[property=csp-nonce]"),m=(g==null?void 0:g.nonce)||(g==null?void 0:g.getAttribute("nonce"));a=d(o.map(y=>{if(y=m0(y,l),y in Zf)return;Zf[y]=!0;const x=y.endsWith(".css"),v=x?'[rel="stylesheet"]':"";if(!!l)for(let j=f.length-1;j>=0;j--){const N=f[j];if(N.href===y&&(!x||N.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${y}"]${v}`))return;const k=document.createElement("link");if(k.rel=x?"stylesheet":g0,x||(k.as="script"),k.crossOrigin="",k.href=y,m&&k.setAttribute("nonce",m),document.head.appendChild(k),x)return new Promise((j,N)=>{k.addEventListener("load",j),k.addEventListener("error",()=>N(new Error(`Unable to preload CSS for ${y}`)))})}))}function u(d){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=d,window.dispatchEvent(f),!f.defaultPrevented)throw d}return a.then(d=>{for(const f of d||[])f.status==="rejected"&&u(f.reason);return r().catch(u)})};function rt(t){if(typeof t=="string"||typeof t=="number")return""+t;let r="";if(Array.isArray(t))for(let o=0,l;o{}};function xl(){for(var t=0,r=arguments.length,o={},l;t=0&&(l=o.slice(a+1),o=o.slice(0,a)),o&&!r.hasOwnProperty(o))throw new Error("unknown type: "+o);return{type:o,name:l}})}nl.prototype=xl.prototype={constructor:nl,on:function(t,r){var o=this._,l=x0(t+"",o),a,u=-1,d=l.length;if(arguments.length<2){for(;++u0)for(var o=new Array(a),l=0,a,u;l=0&&(r=t.slice(0,o))!=="xmlns"&&(t=t.slice(o+1)),eh.hasOwnProperty(r)?{space:eh[r],local:t}:t}function _0(t){return function(){var r=this.ownerDocument,o=this.namespaceURI;return o===Fu&&r.documentElement.namespaceURI===Fu?r.createElement(t):r.createElementNS(o,t)}}function S0(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function Sp(t){var r=wl(t);return(r.local?S0:_0)(r)}function k0(){}function nc(t){return t==null?k0:function(){return this.querySelector(t)}}function E0(t){typeof t!="function"&&(t=nc(t));for(var r=this._groups,o=r.length,l=new Array(o),a=0;a=E&&(E=P+1);!(R=N[E])&&++E=0;)(d=l[a])&&(u&&d.compareDocumentPosition(u)^4&&u.parentNode.insertBefore(d,u),u=d);return this}function Q0(t){t||(t=q0);function r(x,v){return x&&v?t(x.__data__,v.__data__):!x-!v}for(var o=this._groups,l=o.length,a=new Array(l),u=0;ur?1:t>=r?0:NaN}function K0(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function Z0(){return Array.from(this)}function J0(){for(var t=this._groups,r=0,o=t.length;r1?this.each((r==null?cv:typeof r=="function"?fv:dv)(t,r,o??"")):xi(this.node(),t)}function xi(t,r){return t.style.getPropertyValue(r)||jp(t).getComputedStyle(t,null).getPropertyValue(r)}function pv(t){return function(){delete this[t]}}function gv(t,r){return function(){this[t]=r}}function mv(t,r){return function(){var o=r.apply(this,arguments);o==null?delete this[t]:this[t]=o}}function yv(t,r){return arguments.length>1?this.each((r==null?pv:typeof r=="function"?mv:gv)(t,r)):this.node()[t]}function bp(t){return t.trim().split(/^|\s+/)}function rc(t){return t.classList||new Mp(t)}function Mp(t){this._node=t,this._names=bp(t.getAttribute("class")||"")}Mp.prototype={add:function(t){var r=this._names.indexOf(t);r<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var r=this._names.indexOf(t);r>=0&&(this._names.splice(r,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Pp(t,r){for(var o=rc(t),l=-1,a=r.length;++l=0&&(o=r.slice(l+1),r=r.slice(0,l)),{type:r,name:o}})}function Wv(t){return function(){var r=this.__on;if(r){for(var o=0,l=-1,a=r.length,u;o()=>t;function Hu(t,{sourceEvent:r,subject:o,target:l,identifier:a,active:u,x:d,y:f,dx:g,dy:m,dispatch:y}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:r,enumerable:!0,configurable:!0},subject:{value:o,enumerable:!0,configurable:!0},target:{value:l,enumerable:!0,configurable:!0},identifier:{value:a,enumerable:!0,configurable:!0},active:{value:u,enumerable:!0,configurable:!0},x:{value:d,enumerable:!0,configurable:!0},y:{value:f,enumerable:!0,configurable:!0},dx:{value:g,enumerable:!0,configurable:!0},dy:{value:m,enumerable:!0,configurable:!0},_:{value:y}})}Hu.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};function tx(t){return!t.ctrlKey&&!t.button}function nx(){return this.parentNode}function rx(t,r){return r??{x:t.x,y:t.y}}function ix(){return navigator.maxTouchPoints||"ontouchstart"in this}function zp(){var t=tx,r=nx,o=rx,l=ix,a={},u=xl("start","drag","end"),d=0,f,g,m,y,x=0;function v(C){C.on("mousedown.drag",_).filter(l).on("touchstart.drag",N).on("touchmove.drag",S,ex).on("touchend.drag touchcancel.drag",P).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function _(C,R){if(!(y||!t.call(this,C,R))){var D=E(this,r.call(this,C,R),C,R,"mouse");D&&(zt(C.view).on("mousemove.drag",k,xo).on("mouseup.drag",j,xo),Lp(C.view),Cu(C),m=!1,f=C.clientX,g=C.clientY,D("start",C))}}function k(C){if(yi(C),!m){var R=C.clientX-f,D=C.clientY-g;m=R*R+D*D>x}a.mouse("drag",C)}function j(C){zt(C.view).on("mousemove.drag mouseup.drag",null),Ap(C.view,m),yi(C),a.mouse("end",C)}function N(C,R){if(t.call(this,C,R)){var D=C.changedTouches,O=r.call(this,C,R),U=D.length,q,te;for(q=0;q>8&15|r>>4&240,r>>4&15|r&240,(r&15)<<4|r&15,1):o===8?Xs(r>>24&255,r>>16&255,r>>8&255,(r&255)/255):o===4?Xs(r>>12&15|r>>8&240,r>>8&15|r>>4&240,r>>4&15|r&240,((r&15)<<4|r&15)/255):null):(r=sx.exec(t))?new bt(r[1],r[2],r[3],1):(r=lx.exec(t))?new bt(r[1]*255/100,r[2]*255/100,r[3]*255/100,1):(r=ax.exec(t))?Xs(r[1],r[2],r[3],r[4]):(r=ux.exec(t))?Xs(r[1]*255/100,r[2]*255/100,r[3]*255/100,r[4]):(r=cx.exec(t))?lh(r[1],r[2]/100,r[3]/100,1):(r=dx.exec(t))?lh(r[1],r[2]/100,r[3]/100,r[4]):th.hasOwnProperty(t)?ih(th[t]):t==="transparent"?new bt(NaN,NaN,NaN,0):null}function ih(t){return new bt(t>>16&255,t>>8&255,t&255,1)}function Xs(t,r,o,l){return l<=0&&(t=r=o=NaN),new bt(t,r,o,l)}function px(t){return t instanceof Io||(t=Ir(t)),t?(t=t.rgb(),new bt(t.r,t.g,t.b,t.opacity)):new bt}function Bu(t,r,o,l){return arguments.length===1?px(t):new bt(t,r,o,l??1)}function bt(t,r,o,l){this.r=+t,this.g=+r,this.b=+o,this.opacity=+l}ic(bt,Bu,Dp(Io,{brighter(t){return t=t==null?al:Math.pow(al,t),new bt(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?wo:Math.pow(wo,t),new bt(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new bt(Mr(this.r),Mr(this.g),Mr(this.b),ul(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:oh,formatHex:oh,formatHex8:gx,formatRgb:sh,toString:sh}));function oh(){return`#${br(this.r)}${br(this.g)}${br(this.b)}`}function gx(){return`#${br(this.r)}${br(this.g)}${br(this.b)}${br((isNaN(this.opacity)?1:this.opacity)*255)}`}function sh(){const t=ul(this.opacity);return`${t===1?"rgb(":"rgba("}${Mr(this.r)}, ${Mr(this.g)}, ${Mr(this.b)}${t===1?")":`, ${t})`}`}function ul(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function Mr(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function br(t){return t=Mr(t),(t<16?"0":"")+t.toString(16)}function lh(t,r,o,l){return l<=0?t=r=o=NaN:o<=0||o>=1?t=r=NaN:r<=0&&(t=NaN),new tn(t,r,o,l)}function $p(t){if(t instanceof tn)return new tn(t.h,t.s,t.l,t.opacity);if(t instanceof Io||(t=Ir(t)),!t)return new tn;if(t instanceof tn)return t;t=t.rgb();var r=t.r/255,o=t.g/255,l=t.b/255,a=Math.min(r,o,l),u=Math.max(r,o,l),d=NaN,f=u-a,g=(u+a)/2;return f?(r===u?d=(o-l)/f+(o0&&g<1?0:d,new tn(d,f,g,t.opacity)}function mx(t,r,o,l){return arguments.length===1?$p(t):new tn(t,r,o,l??1)}function tn(t,r,o,l){this.h=+t,this.s=+r,this.l=+o,this.opacity=+l}ic(tn,mx,Dp(Io,{brighter(t){return t=t==null?al:Math.pow(al,t),new tn(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?wo:Math.pow(wo,t),new tn(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,r=isNaN(t)||isNaN(this.s)?0:this.s,o=this.l,l=o+(o<.5?o:1-o)*r,a=2*o-l;return new bt(ju(t>=240?t-240:t+120,a,l),ju(t,a,l),ju(t<120?t+240:t-120,a,l),this.opacity)},clamp(){return new tn(ah(this.h),Gs(this.s),Gs(this.l),ul(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=ul(this.opacity);return`${t===1?"hsl(":"hsla("}${ah(this.h)}, ${Gs(this.s)*100}%, ${Gs(this.l)*100}%${t===1?")":`, ${t})`}`}}));function ah(t){return t=(t||0)%360,t<0?t+360:t}function Gs(t){return Math.max(0,Math.min(1,t||0))}function ju(t,r,o){return(t<60?r+(o-r)*t/60:t<180?o:t<240?r+(o-r)*(240-t)/60:r)*255}const oc=t=>()=>t;function yx(t,r){return function(o){return t+o*r}}function vx(t,r,o){return t=Math.pow(t,o),r=Math.pow(r,o)-t,o=1/o,function(l){return Math.pow(t+l*r,o)}}function xx(t){return(t=+t)==1?Op:function(r,o){return o-r?vx(r,o,t):oc(isNaN(r)?o:r)}}function Op(t,r){var o=r-t;return o?yx(t,o):oc(isNaN(t)?r:t)}const cl=(function t(r){var o=xx(r);function l(a,u){var d=o((a=Bu(a)).r,(u=Bu(u)).r),f=o(a.g,u.g),g=o(a.b,u.b),m=Op(a.opacity,u.opacity);return function(y){return a.r=d(y),a.g=f(y),a.b=g(y),a.opacity=m(y),a+""}}return l.gamma=t,l})(1);function wx(t,r){r||(r=[]);var o=t?Math.min(r.length,t.length):0,l=r.slice(),a;return function(u){for(a=0;ao&&(u=r.slice(o,u),f[d]?f[d]+=u:f[++d]=u),(l=l[0])===(a=a[0])?f[d]?f[d]+=a:f[++d]=a:(f[++d]=null,g.push({i:d,x:gn(l,a)})),o=bu.lastIndex;return o180?y+=360:y-m>180&&(m+=360),v.push({i:x.push(a(x)+"rotate(",null,l)-2,x:gn(m,y)})):y&&x.push(a(x)+"rotate("+y+l)}function f(m,y,x,v){m!==y?v.push({i:x.push(a(x)+"skewX(",null,l)-2,x:gn(m,y)}):y&&x.push(a(x)+"skewX("+y+l)}function g(m,y,x,v,_,k){if(m!==x||y!==v){var j=_.push(a(_)+"scale(",null,",",null,")");k.push({i:j-4,x:gn(m,x)},{i:j-2,x:gn(y,v)})}else(x!==1||v!==1)&&_.push(a(_)+"scale("+x+","+v+")")}return function(m,y){var x=[],v=[];return m=t(m),y=t(y),u(m.translateX,m.translateY,y.translateX,y.translateY,x,v),d(m.rotate,y.rotate,x,v),f(m.skewX,y.skewX,x,v),g(m.scaleX,m.scaleY,y.scaleX,y.scaleY,x,v),m=y=null,function(_){for(var k=-1,j=v.length,N;++k=0&&t._call.call(void 0,r),t=t._next;--wi}function dh(){Tr=(fl=So.now())+_l,wi=po=0;try{Ax()}finally{wi=0,Dx(),Tr=0}}function zx(){var t=So.now(),r=t-fl;r>Vp&&(_l-=r,fl=t)}function Dx(){for(var t,r=dl,o,l=1/0;r;)r._call?(l>r._time&&(l=r._time),t=r,r=r._next):(o=r._next,r._next=null,r=t?t._next=o:dl=o);go=t,Wu(l)}function Wu(t){if(!wi){po&&(po=clearTimeout(po));var r=t-Tr;r>24?(t<1/0&&(po=setTimeout(dh,t-So.now()-_l)),fo&&(fo=clearInterval(fo))):(fo||(fl=So.now(),fo=setInterval(zx,Vp)),wi=1,Up(dh))}}function fh(t,r,o){var l=new hl;return r=r==null?0:+r,l.restart(a=>{l.stop(),t(a+r)},r,o),l}var $x=xl("start","end","cancel","interrupt"),Ox=[],Yp=0,hh=1,Yu=2,il=3,ph=4,Xu=5,ol=6;function Sl(t,r,o,l,a,u){var d=t.__transition;if(!d)t.__transition={};else if(o in d)return;Fx(t,o,{name:r,index:l,group:a,on:$x,tween:Ox,time:u.time,delay:u.delay,duration:u.duration,ease:u.ease,timer:null,state:Yp})}function lc(t,r){var o=sn(t,r);if(o.state>Yp)throw new Error("too late; already scheduled");return o}function yn(t,r){var o=sn(t,r);if(o.state>il)throw new Error("too late; already running");return o}function sn(t,r){var o=t.__transition;if(!o||!(o=o[r]))throw new Error("transition not found");return o}function Fx(t,r,o){var l=t.__transition,a;l[r]=o,o.timer=Wp(u,0,o.time);function u(m){o.state=hh,o.timer.restart(d,o.delay,o.time),o.delay<=m&&d(m-o.delay)}function d(m){var y,x,v,_;if(o.state!==hh)return g();for(y in l)if(_=l[y],_.name===o.name){if(_.state===il)return fh(d);_.state===ph?(_.state=ol,_.timer.stop(),_.on.call("interrupt",t,t.__data__,_.index,_.group),delete l[y]):+yYu&&l.state=0&&(r=r.slice(0,o)),!r||r==="start"})}function mw(t,r,o){var l,a,u=gw(r)?lc:yn;return function(){var d=u(this,t),f=d.on;f!==l&&(a=(l=f).copy()).on(r,o),d.on=a}}function yw(t,r){var o=this._id;return arguments.length<2?sn(this.node(),o).on.on(t):this.each(mw(o,t,r))}function vw(t){return function(){var r=this.parentNode;for(var o in this.__transition)if(+o!==t)return;r&&r.removeChild(this)}}function xw(){return this.on("end.remove",vw(this._id))}function ww(t){var r=this._name,o=this._id;typeof t!="function"&&(t=nc(t));for(var l=this._groups,a=l.length,u=new Array(a),d=0;d()=>t;function Ww(t,{sourceEvent:r,target:o,transform:l,dispatch:a}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:r,enumerable:!0,configurable:!0},target:{value:o,enumerable:!0,configurable:!0},transform:{value:l,enumerable:!0,configurable:!0},_:{value:a}})}function Mn(t,r,o){this.k=t,this.x=r,this.y=o}Mn.prototype={constructor:Mn,scale:function(t){return t===1?this:new Mn(this.k*t,this.x,this.y)},translate:function(t,r){return t===0&r===0?this:new Mn(this.k,this.x+this.k*t,this.y+this.k*r)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var kl=new Mn(1,0,0);qp.prototype=Mn.prototype;function qp(t){for(;!t.__zoom;)if(!(t=t.parentNode))return kl;return t.__zoom}function Mu(t){t.stopImmediatePropagation()}function ho(t){t.preventDefault(),t.stopImmediatePropagation()}function Yw(t){return(!t.ctrlKey||t.type==="wheel")&&!t.button}function Xw(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t,t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]):[[0,0],[t.clientWidth,t.clientHeight]]}function gh(){return this.__zoom||kl}function Gw(t){return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function Qw(){return navigator.maxTouchPoints||"ontouchstart"in this}function qw(t,r,o){var l=t.invertX(r[0][0])-o[0][0],a=t.invertX(r[1][0])-o[1][0],u=t.invertY(r[0][1])-o[0][1],d=t.invertY(r[1][1])-o[1][1];return t.translate(a>l?(l+a)/2:Math.min(0,l)||Math.max(0,a),d>u?(u+d)/2:Math.min(0,u)||Math.max(0,d))}function Kp(){var t=Yw,r=Xw,o=qw,l=Gw,a=Qw,u=[0,1/0],d=[[-1/0,-1/0],[1/0,1/0]],f=250,g=rl,m=xl("start","zoom","end"),y,x,v,_=500,k=150,j=0,N=10;function S(b){b.property("__zoom",gh).on("wheel.zoom",U,{passive:!1}).on("mousedown.zoom",q).on("dblclick.zoom",te).filter(a).on("touchstart.zoom",X).on("touchmove.zoom",ee).on("touchend.zoom touchcancel.zoom",J).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}S.transform=function(b,Y,H,W){var A=b.selection?b.selection():b;A.property("__zoom",gh),b!==A?R(b,Y,H,W):A.interrupt().each(function(){D(this,arguments).event(W).start().zoom(null,typeof Y=="function"?Y.apply(this,arguments):Y).end()})},S.scaleBy=function(b,Y,H,W){S.scaleTo(b,function(){var A=this.__zoom.k,z=typeof Y=="function"?Y.apply(this,arguments):Y;return A*z},H,W)},S.scaleTo=function(b,Y,H,W){S.transform(b,function(){var A=r.apply(this,arguments),z=this.__zoom,B=H==null?C(A):typeof H=="function"?H.apply(this,arguments):H,M=z.invert(B),T=typeof Y=="function"?Y.apply(this,arguments):Y;return o(E(P(z,T),B,M),A,d)},H,W)},S.translateBy=function(b,Y,H,W){S.transform(b,function(){return o(this.__zoom.translate(typeof Y=="function"?Y.apply(this,arguments):Y,typeof H=="function"?H.apply(this,arguments):H),r.apply(this,arguments),d)},null,W)},S.translateTo=function(b,Y,H,W,A){S.transform(b,function(){var z=r.apply(this,arguments),B=this.__zoom,M=W==null?C(z):typeof W=="function"?W.apply(this,arguments):W;return o(kl.translate(M[0],M[1]).scale(B.k).translate(typeof Y=="function"?-Y.apply(this,arguments):-Y,typeof H=="function"?-H.apply(this,arguments):-H),z,d)},W,A)};function P(b,Y){return Y=Math.max(u[0],Math.min(u[1],Y)),Y===b.k?b:new Mn(Y,b.x,b.y)}function E(b,Y,H){var W=Y[0]-H[0]*b.k,A=Y[1]-H[1]*b.k;return W===b.x&&A===b.y?b:new Mn(b.k,W,A)}function C(b){return[(+b[0][0]+ +b[1][0])/2,(+b[0][1]+ +b[1][1])/2]}function R(b,Y,H,W){b.on("start.zoom",function(){D(this,arguments).event(W).start()}).on("interrupt.zoom end.zoom",function(){D(this,arguments).event(W).end()}).tween("zoom",function(){var A=this,z=arguments,B=D(A,z).event(W),M=r.apply(A,z),T=H==null?C(M):typeof H=="function"?H.apply(A,z):H,re=Math.max(M[1][0]-M[0][0],M[1][1]-M[0][1]),ie=A.__zoom,ce=typeof Y=="function"?Y.apply(A,z):Y,fe=g(ie.invert(T).concat(re/ie.k),ce.invert(T).concat(re/ce.k));return function(de){if(de===1)de=ce;else{var K=fe(de),le=re/K[2];de=new Mn(le,T[0]-K[0]*le,T[1]-K[1]*le)}B.zoom(null,de)}})}function D(b,Y,H){return!H&&b.__zooming||new O(b,Y)}function O(b,Y){this.that=b,this.args=Y,this.active=0,this.sourceEvent=null,this.extent=r.apply(b,Y),this.taps=0}O.prototype={event:function(b){return b&&(this.sourceEvent=b),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(b,Y){return this.mouse&&b!=="mouse"&&(this.mouse[1]=Y.invert(this.mouse[0])),this.touch0&&b!=="touch"&&(this.touch0[1]=Y.invert(this.touch0[0])),this.touch1&&b!=="touch"&&(this.touch1[1]=Y.invert(this.touch1[0])),this.that.__zoom=Y,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(b){var Y=zt(this.that).datum();m.call(b,this.that,new Ww(b,{sourceEvent:this.sourceEvent,target:S,transform:this.that.__zoom,dispatch:m}),Y)}};function U(b,...Y){if(!t.apply(this,arguments))return;var H=D(this,Y).event(b),W=this.__zoom,A=Math.max(u[0],Math.min(u[1],W.k*Math.pow(2,l.apply(this,arguments)))),z=en(b);if(H.wheel)(H.mouse[0][0]!==z[0]||H.mouse[0][1]!==z[1])&&(H.mouse[1]=W.invert(H.mouse[0]=z)),clearTimeout(H.wheel);else{if(W.k===A)return;H.mouse=[z,W.invert(z)],sl(this),H.start()}ho(b),H.wheel=setTimeout(B,k),H.zoom("mouse",o(E(P(W,A),H.mouse[0],H.mouse[1]),H.extent,d));function B(){H.wheel=null,H.end()}}function q(b,...Y){if(v||!t.apply(this,arguments))return;var H=b.currentTarget,W=D(this,Y,!0).event(b),A=zt(b.view).on("mousemove.zoom",T,!0).on("mouseup.zoom",re,!0),z=en(b,H),B=b.clientX,M=b.clientY;Lp(b.view),Mu(b),W.mouse=[z,this.__zoom.invert(z)],sl(this),W.start();function T(ie){if(ho(ie),!W.moved){var ce=ie.clientX-B,fe=ie.clientY-M;W.moved=ce*ce+fe*fe>j}W.event(ie).zoom("mouse",o(E(W.that.__zoom,W.mouse[0]=en(ie,H),W.mouse[1]),W.extent,d))}function re(ie){A.on("mousemove.zoom mouseup.zoom",null),Ap(ie.view,W.moved),ho(ie),W.event(ie).end()}}function te(b,...Y){if(t.apply(this,arguments)){var H=this.__zoom,W=en(b.changedTouches?b.changedTouches[0]:b,this),A=H.invert(W),z=H.k*(b.shiftKey?.5:2),B=o(E(P(H,z),W,A),r.apply(this,Y),d);ho(b),f>0?zt(this).transition().duration(f).call(R,B,W,b):zt(this).call(S.transform,B,W,b)}}function X(b,...Y){if(t.apply(this,arguments)){var H=b.touches,W=H.length,A=D(this,Y,b.changedTouches.length===W).event(b),z,B,M,T;for(Mu(b),B=0;B`Seems like you have not used ${t==="svelte"?"SvelteFlowProvider":"ReactFlowProvider"} as an ancestor. Help: https://${t}flow.dev/error#001`,error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:t=>`Node type "${t}" not found. Using fallback type "default".`,error004:()=>"The parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:t=>`The old edge with id=${t} does not exist.`,error009:t=>`Marker type "${t}" doesn't exist.`,error008:(t,{id:r,sourceHandle:o,targetHandle:l})=>`Couldn't create edge for ${t} handle id: "${t==="source"?o:l}", edge id: ${r}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:t=>`Edge type "${t}" not found. Using fallback type "default".`,error012:t=>`Node with id "${t}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,error013:(t="react")=>`It seems that you haven't loaded the styles. Please import '@xyflow/${t}/dist/style.css' or base.css to make sure everything is working properly.`,error014:()=>"useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.",error015:()=>"It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs.",error016:t=>`Edge with id "${t}" does not exist, it may have been removed. This can happen when an edge is deleted before the "onEdgeClick" handler is called.`},ko=[[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY],[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY]],Zp=["Enter"," ","Escape"],Jp={"node.a11yDescription.default":"Press enter or space to select a node. Press delete to remove it and escape to cancel.","node.a11yDescription.keyboardDisabled":"Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.","node.a11yDescription.ariaLiveMessage":({direction:t,x:r,y:o})=>`Moved selected node ${t}. New position, x: ${r}, y: ${o}`,"edge.a11yDescription.default":"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.","controls.ariaLabel":"Control Panel","controls.zoomIn.ariaLabel":"Zoom In","controls.zoomOut.ariaLabel":"Zoom Out","controls.fitView.ariaLabel":"Fit View","controls.interactive.ariaLabel":"Toggle Interactivity","minimap.ariaLabel":"Mini Map","handle.ariaLabel":"Handle"};var _i;(function(t){t.Strict="strict",t.Loose="loose"})(_i||(_i={}));var Pr;(function(t){t.Free="free",t.Vertical="vertical",t.Horizontal="horizontal"})(Pr||(Pr={}));var Eo;(function(t){t.Partial="partial",t.Full="full"})(Eo||(Eo={}));const eg={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null,pointer:null};var rr;(function(t){t.Bezier="default",t.Straight="straight",t.Step="step",t.SmoothStep="smoothstep",t.SimpleBezier="simplebezier"})(rr||(rr={}));var No;(function(t){t.Arrow="arrow",t.ArrowClosed="arrowclosed"})(No||(No={}));var Se;(function(t){t.Left="left",t.Top="top",t.Right="right",t.Bottom="bottom"})(Se||(Se={}));const mh={[Se.Left]:Se.Right,[Se.Right]:Se.Left,[Se.Top]:Se.Bottom,[Se.Bottom]:Se.Top};function tg(t){return t===null?null:t?"valid":"invalid"}const ng=t=>!!t&&typeof t=="object"&&"id"in t&&"source"in t&&"target"in t,Kw=t=>!!t&&typeof t=="object"&&"id"in t&&"position"in t&&!("source"in t)&&!("target"in t),uc=t=>!!t&&typeof t=="object"&&"id"in t&&"internals"in t&&!("source"in t)&&!("target"in t),To=(t,r=[0,0])=>{const{width:o,height:l}=ln(t),a=t.origin??r,u=o*a[0],d=l*a[1];return{x:t.position.x-u,y:t.position.y-d}},Zw=(t,r={nodeOrigin:[0,0]})=>{if(t.length===0)return{x:0,y:0,width:0,height:0};let o=!1;const l=t.reduce((a,u)=>{const d=typeof u=="string";let f=!r.nodeLookup&&!d?u:void 0;return r.nodeLookup&&(f=d?r.nodeLookup.get(u):uc(u)?u:r.nodeLookup.get(u.id)),f?(o=!0,El(a,pl(f,r.nodeOrigin))):a},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return o?Nl(l):{x:0,y:0,width:0,height:0}},Ro=(t,r={})=>{let o={x:1/0,y:1/0,x2:-1/0,y2:-1/0},l=!1;return t.forEach(a=>{(r.filter===void 0||r.filter(a))&&(o=El(o,pl(a)),l=!0)}),l?Nl(o):{x:0,y:0,width:0,height:0}},cc=(t,r,[o,l,a]=[0,0,1],u=!1,d=!1)=>{const f=(r.x-o)/a,g=(r.y-l)/a,m=r.width/a,y=r.height/a,x=[];for(const v of t.values()){const{measured:_,selectable:k=!0,hidden:j=!1}=v;if(d&&!k||j)continue;const N=_.width??v.width??v.initialWidth??0,S=_.height??v.height??v.initialHeight??0,{x:P,y:E}=v.internals.positionAbsolute,C=sg(f,g,m,y,P,E,N,S),R=N*S,D=u&&C>0;(!v.internals.handleBounds||D||C>=R||v.dragging)&&x.push(v)}return x},Jw=(t,r)=>{const o=new Set;return t.forEach(l=>{o.add(l.id)}),r.filter(l=>o.has(l.source)||o.has(l.target))};function e1(t,r){const o=new Map,l=r!=null&&r.nodes?new Set(r.nodes.map(a=>a.id)):null;return t.forEach(a=>{let u;if(r!=null&&r.includeHiddenNodes){const{width:d,height:f}=ln(a);u=d>0&&f>0}else u=!!(a.measured.width&&a.measured.height&&!a.hidden);u&&(!l||l.has(a.id))&&o.set(a.id,a)}),o}async function t1({nodes:t,width:r,height:o,panZoom:l,minZoom:a,maxZoom:u},d){if(t.size===0)return!0;const f=e1(t,d),g=Ro(f),m=fc(g,r,o,(d==null?void 0:d.minZoom)??a,(d==null?void 0:d.maxZoom)??u,(d==null?void 0:d.padding)??.1);return await l.setViewport(m,{duration:d==null?void 0:d.duration,ease:d==null?void 0:d.ease,interpolate:d==null?void 0:d.interpolate}),!0}function rg({nodeId:t,nextPosition:r,nodeLookup:o,nodeOrigin:l=[0,0],nodeExtent:a,onError:u}){const d=o.get(t),f=d.parentId?o.get(d.parentId):void 0,{x:g,y:m}=f?f.internals.positionAbsolute:{x:0,y:0},y=d.origin??l;let x=d.extent||a;if(d.extent==="parent"&&!d.expandParent)if(!f)u==null||u("005",on.error005());else{const{width:_,height:k}=ln(f);_&&k&&(x=[[g,m],[g+_,m+k]])}else f&&Lr(d.extent)&&(x=[[d.extent[0][0]+g,d.extent[0][1]+m],[d.extent[1][0]+g,d.extent[1][1]+m]]);const v=Lr(x)?Rr(r,x,d.measured):r;return(d.measured.width===void 0||d.measured.height===void 0)&&(u==null||u("015",on.error015())),{position:{x:v.x-g+(d.measured.width??0)*y[0],y:v.y-m+(d.measured.height??0)*y[1]},positionAbsolute:v}}async function n1({nodesToRemove:t=[],edgesToRemove:r=[],nodes:o,edges:l,onBeforeDelete:a}){const u=new Set(t.map(v=>v.id)),d=[];for(const v of o){if(v.deletable===!1)continue;const _=u.has(v.id),k=!_&&v.parentId&&d.find(j=>j.id===v.parentId);(_||k)&&d.push(v)}const f=new Set(r.map(v=>v.id)),g=l.filter(v=>v.deletable!==!1),y=Jw(d,g);for(const v of g)f.has(v.id)&&!y.find(k=>k.id===v.id)&&y.push(v);if(!a)return{edges:y,nodes:d};const x=await a({nodes:d,edges:y});return typeof x=="boolean"?x?{edges:y,nodes:d}:{edges:[],nodes:[]}:x}const Si=(t,r=0,o=1)=>Math.min(Math.max(t,r),o),Rr=(t={x:0,y:0},r,o)=>({x:Si(t.x,r[0][0],r[1][0]-((o==null?void 0:o.width)??0)),y:Si(t.y,r[0][1],r[1][1]-((o==null?void 0:o.height)??0))});function ig(t,r,o){const{width:l,height:a}=ln(o),{x:u,y:d}=o.internals.positionAbsolute;return Rr(t,[[u,d],[u+l,d+a]],r)}const yh=(t,r,o)=>to?-Si(Math.abs(t-o),1,r)/r:0,dc=(t,r,o=15,l=40)=>{const a=yh(t.x,l,r.width-l)*o,u=yh(t.y,l,r.height-l)*o;return[a,u]},El=(t,r)=>({x:Math.min(t.x,r.x),y:Math.min(t.y,r.y),x2:Math.max(t.x2,r.x2),y2:Math.max(t.y2,r.y2)}),Gu=({x:t,y:r,width:o,height:l})=>({x:t,y:r,x2:t+o,y2:r+l}),Nl=({x:t,y:r,x2:o,y2:l})=>({x:t,y:r,width:o-t,height:l-r}),Co=(t,r=[0,0])=>{var a,u;const{x:o,y:l}=uc(t)?t.internals.positionAbsolute:To(t,r);return{x:o,y:l,width:((a=t.measured)==null?void 0:a.width)??t.width??t.initialWidth??0,height:((u=t.measured)==null?void 0:u.height)??t.height??t.initialHeight??0}},pl=(t,r=[0,0])=>{var a,u;const{x:o,y:l}=uc(t)?t.internals.positionAbsolute:To(t,r);return{x:o,y:l,x2:o+(((a=t.measured)==null?void 0:a.width)??t.width??t.initialWidth??0),y2:l+(((u=t.measured)==null?void 0:u.height)??t.height??t.initialHeight??0)}},og=(t,r)=>Nl(El(Gu(t),Gu(r))),sg=(t,r,o,l,a,u,d,f)=>{const g=Math.max(0,Math.min(t+o,a+d)-Math.max(t,a)),m=Math.max(0,Math.min(r+l,u+f)-Math.max(r,u));return Math.ceil(g*m)},gl=(t,r)=>sg(t.x,t.y,t.width,t.height,r.x,r.y,r.width,r.height),vh=t=>nn(t.width)&&nn(t.height)&&nn(t.x)&&nn(t.y),nn=t=>!isNaN(t)&&isFinite(t),lg=(t,r)=>(o,l)=>{},Lo=(t,r=[1,1])=>({x:r[0]*Math.round(t.x/r[0]),y:r[1]*Math.round(t.y/r[1])}),Ao=({x:t,y:r},[o,l,a],u=!1,d=[1,1])=>{const f={x:(t-o)/a,y:(r-l)/a};return u?Lo(f,d):f},ki=({x:t,y:r},[o,l,a])=>({x:t*a+o,y:r*a+l});function gi(t,r){if(typeof t=="number")return Math.floor((r-r/(1+t))*.5);if(typeof t=="string"&&t.endsWith("px")){const o=parseFloat(t);if(!Number.isNaN(o))return Math.floor(o)}if(typeof t=="string"&&t.endsWith("%")){const o=parseFloat(t);if(!Number.isNaN(o))return Math.floor(r*o*.01)}return console.error(`The padding value "${t}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}function r1(t,r,o){if(typeof t=="string"||typeof t=="number"){const l=gi(t,o),a=gi(t,r);return{top:l,right:a,bottom:l,left:a,x:a*2,y:l*2}}if(typeof t=="object"){const l=gi(t.top??t.y??0,o),a=gi(t.bottom??t.y??0,o),u=gi(t.left??t.x??0,r),d=gi(t.right??t.x??0,r);return{top:l,right:d,bottom:a,left:u,x:u+d,y:l+a}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}function i1(t,r,o,l,a,u){const{x:d,y:f}=ki(t,[r,o,l]),{x:g,y:m}=ki({x:t.x+t.width,y:t.y+t.height},[r,o,l]),y=a-g,x=u-m;return{left:Math.floor(d),top:Math.floor(f),right:Math.floor(y),bottom:Math.floor(x)}}const fc=(t,r,o,l,a,u)=>{const d=r1(u,r,o),f=(r-d.x)/t.width,g=(o-d.y)/t.height,m=Math.min(f,g),y=Si(m,l,a),x=t.x+t.width/2,v=t.y+t.height/2,_=r/2-x*y,k=o/2-v*y,j=i1(t,_,k,y,r,o),N={left:Math.min(j.left-d.left,0),top:Math.min(j.top-d.top,0),right:Math.min(j.right-d.right,0),bottom:Math.min(j.bottom-d.bottom,0)};return{x:_-N.left+N.right,y:k-N.top+N.bottom,zoom:y}},jo=()=>{var t;return typeof navigator<"u"&&((t=navigator==null?void 0:navigator.userAgent)==null?void 0:t.indexOf("Mac"))>=0};function Lr(t){return t!=null&&t!=="parent"}function ln(t){var r,o;return{width:((r=t.measured)==null?void 0:r.width)??t.width??t.initialWidth??0,height:((o=t.measured)==null?void 0:o.height)??t.height??t.initialHeight??0}}function ag(t){var r,o;return(((r=t.measured)==null?void 0:r.width)??t.width??t.initialWidth)!==void 0&&(((o=t.measured)==null?void 0:o.height)??t.height??t.initialHeight)!==void 0}function ug(t,r={width:0,height:0},o,l,a){const u={...t},d=l.get(o);if(d){const f=d.origin||a;u.x+=d.internals.positionAbsolute.x-(r.width??0)*f[0],u.y+=d.internals.positionAbsolute.y-(r.height??0)*f[1]}return u}function xh(t,r){if(t.size!==r.size)return!1;for(const o of t)if(!r.has(o))return!1;return!0}function o1(){let t,r;return{promise:new Promise((l,a)=>{t=l,r=a}),resolve:t,reject:r}}function s1(t){return{...Jp,...t||{}}}function yo(t,{snapGrid:r=[0,0],snapToGrid:o=!1,transform:l,containerBounds:a}){const{x:u,y:d}=rn(t),f=Ao({x:u-((a==null?void 0:a.left)??0),y:d-((a==null?void 0:a.top)??0)},l),{x:g,y:m}=o?Lo(f,r):f;return{xSnapped:g,ySnapped:m,...f}}const hc=t=>({width:t.offsetWidth,height:t.offsetHeight}),cg=t=>{var r;return((r=t==null?void 0:t.getRootNode)==null?void 0:r.call(t))||(window==null?void 0:window.document)},l1=["INPUT","SELECT","TEXTAREA"];function dg(t){var l,a;const r=((a=(l=t.composedPath)==null?void 0:l.call(t))==null?void 0:a[0])||t.target;return(r==null?void 0:r.nodeType)!==1?!1:l1.includes(r.nodeName)||r.hasAttribute("contenteditable")||!!r.closest(".nokey")}const fg=t=>"clientX"in t,rn=(t,r)=>{var u,d;const o=fg(t),l=o?t.clientX:(u=t.touches)==null?void 0:u[0].clientX,a=o?t.clientY:(d=t.touches)==null?void 0:d[0].clientY;return{x:l-((r==null?void 0:r.left)??0),y:a-((r==null?void 0:r.top)??0)}},wh=(t,r,o,l,a)=>{const u=r.querySelectorAll(`.${t}`);return!u||!u.length?null:Array.from(u).map(d=>{const f=d.getBoundingClientRect();return{id:d.getAttribute("data-handleid"),type:t,nodeId:a,position:d.getAttribute("data-handlepos"),x:(f.left-o.left)/l,y:(f.top-o.top)/l,...hc(d)}})};function hg({sourceX:t,sourceY:r,targetX:o,targetY:l,sourceControlX:a,sourceControlY:u,targetControlX:d,targetControlY:f}){const g=t*.125+a*.375+d*.375+o*.125,m=r*.125+u*.375+f*.375+l*.125,y=Math.abs(g-t),x=Math.abs(m-r);return[g,m,y,x]}function Ks(t,r){return t>=0?.5*t:r*25*Math.sqrt(-t)}function _h({pos:t,x1:r,y1:o,x2:l,y2:a,c:u}){switch(t){case Se.Left:return[r-Ks(r-l,u),o];case Se.Right:return[r+Ks(l-r,u),o];case Se.Top:return[r,o-Ks(o-a,u)];case Se.Bottom:return[r,o+Ks(a-o,u)]}}function pg({sourceX:t,sourceY:r,sourcePosition:o=Se.Bottom,targetX:l,targetY:a,targetPosition:u=Se.Top,curvature:d=.25}){const[f,g]=_h({pos:o,x1:t,y1:r,x2:l,y2:a,c:d}),[m,y]=_h({pos:u,x1:l,y1:a,x2:t,y2:r,c:d}),[x,v,_,k]=hg({sourceX:t,sourceY:r,targetX:l,targetY:a,sourceControlX:f,sourceControlY:g,targetControlX:m,targetControlY:y});return[`M${t},${r} C${f},${g} ${m},${y} ${l},${a}`,x,v,_,k]}function gg({sourceX:t,sourceY:r,targetX:o,targetY:l}){const a=Math.abs(o-t)/2,u=o0}const c1=({source:t,sourceHandle:r,target:o,targetHandle:l})=>`xy-edge__${t}${r||""}-${o}${l||""}`,d1=(t,r)=>r.some(o=>o.source===t.source&&o.target===t.target&&(o.sourceHandle===t.sourceHandle||!o.sourceHandle&&!t.sourceHandle)&&(o.targetHandle===t.targetHandle||!o.targetHandle&&!t.targetHandle)),f1=(t,r,o={})=>{var u;if(!t.source||!t.target)return(u=o.onError)==null||u.call(o,"006",on.error006()),r;const l=o.getEdgeId||c1;let a;return ng(t)?a={...t}:a={...t,id:l(t)},d1(a,r)?r:(a.sourceHandle===null&&delete a.sourceHandle,a.targetHandle===null&&delete a.targetHandle,r.concat(a))};function mg({sourceX:t,sourceY:r,targetX:o,targetY:l}){const[a,u,d,f]=gg({sourceX:t,sourceY:r,targetX:o,targetY:l});return[`M ${t},${r}L ${o},${l}`,a,u,d,f]}const Sh={[Se.Left]:{x:-1,y:0},[Se.Right]:{x:1,y:0},[Se.Top]:{x:0,y:-1},[Se.Bottom]:{x:0,y:1}},h1=({source:t,sourcePosition:r=Se.Bottom,target:o})=>r===Se.Left||r===Se.Right?t.xMath.sqrt(Math.pow(r.x-t.x,2)+Math.pow(r.y-t.y,2));function p1({source:t,sourcePosition:r=Se.Bottom,target:o,targetPosition:l=Se.Top,center:a,offset:u,stepPosition:d}){const f=Sh[r],g=Sh[l],m={x:t.x+f.x*u,y:t.y+f.y*u},y={x:o.x+g.x*u,y:o.y+g.y*u},x=h1({source:m,sourcePosition:r,target:y}),v=x.x!==0?"x":"y",_=x[v];let k=[],j,N;const S={x:0,y:0},P={x:0,y:0},[,,E,C]=gg({sourceX:t.x,sourceY:t.y,targetX:o.x,targetY:o.y});if(f[v]*g[v]===-1){v==="x"?(j=a.x??m.x+(y.x-m.x)*d,N=a.y??(m.y+y.y)/2):(j=a.x??(m.x+y.x)/2,N=a.y??m.y+(y.y-m.y)*d);const U=[{x:j,y:m.y},{x:j,y:y.y}],q=[{x:m.x,y:N},{x:y.x,y:N}];f[v]===_?k=v==="x"?U:q:k=v==="x"?q:U}else{const U=[{x:m.x,y:y.y}],q=[{x:y.x,y:m.y}];if(v==="x"?k=f.x===_?q:U:k=f.y===_?U:q,r===l){const b=Math.abs(t[v]-o[v]);if(b<=u){const Y=Math.min(u-1,u-b);f[v]===_?S[v]=(m[v]>t[v]?-1:1)*Y:P[v]=(y[v]>o[v]?-1:1)*Y}}if(r!==l){const b=v==="x"?"y":"x",Y=f[v]===g[b],H=m[b]>y[b],W=m[b]=J?(j=(te.x+X.x)/2,N=k[0].y):(j=k[0].x,N=(te.y+X.y)/2)}const R={x:m.x+S.x,y:m.y+S.y},D={x:y.x+P.x,y:y.y+P.y};return[[t,...R.x!==k[0].x||R.y!==k[0].y?[R]:[],...k,...D.x!==k[k.length-1].x||D.y!==k[k.length-1].y?[D]:[],o],j,N,E,C]}function g1(t,r,o,l){const a=Math.min(kh(t,r)/2,kh(r,o)/2,l),{x:u,y:d}=r;if(t.x===u&&u===o.x||t.y===d&&d===o.y)return`L${u} ${d}`;if(t.y===d){const m=t.xo.id===r):t[0])||null}function qu(t,r){return t?typeof t=="string"?t:`${r?`${r}__`:""}${Object.keys(t).sort().map(l=>`${l}=${t[l]}`).join("&")}`:""}function y1(t,{id:r,defaultColor:o,defaultMarkerStart:l,defaultMarkerEnd:a}){const u=new Set;return t.reduce((d,f)=>([f.markerStart||l,f.markerEnd||a].forEach(g=>{if(g&&typeof g=="object"){const m=qu(g,r);u.has(m)||(d.push({id:m,color:g.color||o,...g}),u.add(m))}}),d),[]).sort((d,f)=>d.id.localeCompare(f.id))}const yg=1e3,v1=10,pc={nodeOrigin:[0,0],nodeExtent:ko,elevateNodesOnSelect:!0,zIndexMode:"basic",defaults:{}},x1={...pc,checkEquality:!0};function gc(t,r){const o={...t};for(const l in r)r[l]!==void 0&&(o[l]=r[l]);return o}function w1(t,r,o){const l=gc(pc,o);for(const a of t.values())if(a.parentId)yc(a,t,r,l);else{const u=To(a,l.nodeOrigin),d=Lr(a.extent)?a.extent:l.nodeExtent,f=Rr(u,d,ln(a));a.internals.positionAbsolute=f}}function _1(t,r){if(!t.handles)return t.measured?r==null?void 0:r.internals.handleBounds:void 0;const o=[],l=[];for(const a of t.handles){const u={id:a.id,width:a.width??1,height:a.height??1,nodeId:t.id,x:a.x,y:a.y,position:a.position,type:a.type};a.type==="source"?o.push(u):a.type==="target"&&l.push(u)}return{source:o,target:l}}function mc(t){return t==="manual"}function Ku(t,r,o,l={}){var y,x;const a=gc(x1,l),u={i:0},d=new Map(r),f=a!=null&&a.elevateNodesOnSelect&&!mc(a.zIndexMode)?yg:0;let g=t.length>0,m=!1;r.clear(),o.clear();for(const v of t){let _=d.get(v.id);if(a.checkEquality&&v===(_==null?void 0:_.internals.userNode))r.set(v.id,_);else{const k=To(v,a.nodeOrigin),j=Lr(v.extent)?v.extent:a.nodeExtent,N=Rr(k,j,ln(v));_={...a.defaults,...v,measured:{width:(y=v.measured)==null?void 0:y.width,height:(x=v.measured)==null?void 0:x.height},internals:{positionAbsolute:N,handleBounds:_1(v,_),z:vg(v,f,a.zIndexMode),userNode:v}},r.set(v.id,_)}(_.measured===void 0||_.measured.width===void 0||_.measured.height===void 0)&&!_.hidden&&(g=!1),v.parentId&&yc(_,r,o,l,u),m||(m=v.selected??!1)}return{nodesInitialized:g,hasSelectedNodes:m}}function S1(t,r){if(!t.parentId)return;const o=r.get(t.parentId);o?o.set(t.id,t):r.set(t.parentId,new Map([[t.id,t]]))}function yc(t,r,o,l,a){const{elevateNodesOnSelect:u,nodeOrigin:d,nodeExtent:f,zIndexMode:g}=gc(pc,l),m=t.parentId,y=r.get(m);if(!y){console.warn(`Parent node ${m} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);return}S1(t,o),a&&!y.parentId&&y.internals.rootParentIndex===void 0&&g==="auto"&&(y.internals.rootParentIndex=++a.i,y.internals.z=y.internals.z+a.i*v1),a&&y.internals.rootParentIndex!==void 0&&(a.i=y.internals.rootParentIndex);const x=u&&!mc(g)?yg:0,{x:v,y:_,z:k}=k1(t,y,d,f,x,g),{positionAbsolute:j}=t.internals,N=v!==j.x||_!==j.y;(N||k!==t.internals.z)&&r.set(t.id,{...t,internals:{...t.internals,positionAbsolute:N?{x:v,y:_}:j,z:k}})}function vg(t,r,o){const l=nn(t.zIndex)?t.zIndex:0;return mc(o)?l:l+(t.selected?r:0)}function k1(t,r,o,l,a,u){const{x:d,y:f}=r.internals.positionAbsolute,g=ln(t),m=To(t,o),y=Lr(t.extent)?Rr(m,t.extent,g):m;let x=Rr({x:d+y.x,y:f+y.y},l,g);t.extent==="parent"&&(x=ig(x,g,r));const v=vg(t,a,u),_=r.internals.z??0;return{x:x.x,y:x.y,z:_>=v?_+1:v}}function vc(t,r,o,l=[0,0]){var d;const a=[],u=new Map;for(const f of t){const g=r.get(f.parentId);if(!g)continue;const m=((d=u.get(f.parentId))==null?void 0:d.expandedRect)??Co(g),y=og(m,f.rect);u.set(f.parentId,{expandedRect:y,parent:g})}return u.size>0&&u.forEach(({expandedRect:f,parent:g},m)=>{var E;const y=g.internals.positionAbsolute,x=ln(g),v=g.origin??l,_=f.x0||k>0||S||P)&&(a.push({id:m,type:"position",position:{x:g.position.x-_+S,y:g.position.y-k+P}}),(E=o.get(m))==null||E.forEach(C=>{t.some(R=>R.id===C.id)||a.push({id:C.id,type:"position",position:{x:C.position.x+_,y:C.position.y+k}})})),(x.width0){const _=vc(v,r,o,a);m.push(..._)}return{changes:m,updatedInternals:g}}async function N1({delta:t,panZoom:r,transform:o,translateExtent:l,width:a,height:u}){if(!r||!t.x&&!t.y)return!1;const d=await r.setViewportConstrained({x:o[0]+t.x,y:o[1]+t.y,zoom:o[2]},[[0,0],[a,u]],l);return!!d&&(d.x!==o[0]||d.y!==o[1]||d.k!==o[2])}function jh(t,r,o,l,a,u){let d=a;const f=l.get(d)||new Map;l.set(d,f.set(o,r)),d=`${a}-${t}`;const g=l.get(d)||new Map;if(l.set(d,g.set(o,r)),u){d=`${a}-${t}-${u}`;const m=l.get(d)||new Map;l.set(d,m.set(o,r))}}function xg(t,r,o){t.clear(),r.clear();for(const l of o){const{source:a,target:u,sourceHandle:d=null,targetHandle:f=null}=l,g={edgeId:l.id,source:a,target:u,sourceHandle:d,targetHandle:f},m=`${a}-${d}--${u}-${f}`,y=`${u}-${f}--${a}-${d}`;jh("source",g,y,t,a,d),jh("target",g,m,t,u,f),r.set(l.id,l)}}function wg(t,r){if(!t.parentId)return!1;const o=r.get(t.parentId);return o?o.selected?!0:wg(o,r):!1}function bh(t,r,o){var a;let l=t;do{if((a=l==null?void 0:l.matches)!=null&&a.call(l,r))return!0;if(l===o)return!1;l=l==null?void 0:l.parentElement}while(l);return!1}function C1(t,r,o,l){const a=new Map;for(const[u,d]of t)if((d.selected||d.id===l)&&(!d.parentId||!wg(d,t))&&(d.draggable||r&&typeof d.draggable>"u")){const f=t.get(u);f&&a.set(u,{id:u,position:f.position||{x:0,y:0},distance:{x:o.x-f.internals.positionAbsolute.x,y:o.y-f.internals.positionAbsolute.y},extent:f.extent,parentId:f.parentId,origin:f.origin,expandParent:f.expandParent,internals:{positionAbsolute:f.internals.positionAbsolute||{x:0,y:0}},measured:{width:f.measured.width??0,height:f.measured.height??0}})}return a}function Pu({nodeId:t,dragItems:r,nodeLookup:o,dragging:l=!0}){var d,f,g;const a=[];for(const[m,y]of r){const x=(d=o.get(m))==null?void 0:d.internals.userNode;x&&a.push({...x,position:y.position,dragging:l})}if(!t)return[a[0],a];const u=(f=o.get(t))==null?void 0:f.internals.userNode;return[u?{...u,position:((g=r.get(t))==null?void 0:g.position)||u.position,dragging:l}:a[0],a]}function j1({dragItems:t,snapGrid:r,x:o,y:l}){const a=t.values().next().value;if(!a)return null;const u={x:o-a.distance.x,y:l-a.distance.y},d=Lo(u,r);return{x:d.x-u.x,y:d.y-u.y}}function b1({onNodeMouseDown:t,getStoreItems:r,onDragStart:o,onDrag:l,onDragStop:a}){let u={x:null,y:null},d=0,f=new Map,g=!1,m={x:0,y:0},y=null,x=!1,v=null,_=!1,k=!1,j=null;function N({noDragClassName:P,handleSelector:E,domNode:C,isSelectable:R,nodeId:D,nodeClickDistance:O=0}){v=zt(C);function U({x:ee,y:J}){const{nodeLookup:b,nodeExtent:Y,snapGrid:H,snapToGrid:W,nodeOrigin:A,onNodeDrag:z,onSelectionDrag:B,onError:M,updateNodePositions:T}=r();u={x:ee,y:J};let re=!1;const ie=f.size>1,ce=ie&&Y?Gu(Ro(f)):null,fe=ie&&W?j1({dragItems:f,snapGrid:H,x:ee,y:J}):null;for(const[de,K]of f){if(!b.has(de))continue;let le={x:ee-K.distance.x,y:J-K.distance.y};W&&(le=fe?{x:Math.round(le.x+fe.x),y:Math.round(le.y+fe.y)}:Lo(le,H));let he=null;if(ie&&Y&&!K.extent&&ce){const{positionAbsolute:ge}=K.internals,Ne=ge.x-ce.x+Y[0][0],Me=ge.x+K.measured.width-ce.x2+Y[1][0],be=ge.y-ce.y+Y[0][1],Le=ge.y+K.measured.height-ce.y2+Y[1][1];he=[[Ne,be],[Me,Le]]}const{position:_e,positionAbsolute:ve}=rg({nodeId:de,nextPosition:le,nodeLookup:b,nodeExtent:he||Y,nodeOrigin:A,onError:M});re=re||K.position.x!==_e.x||K.position.y!==_e.y,K.position=_e,K.internals.positionAbsolute=ve}if(k=k||re,!!re&&(T(f,!0),j&&(l||z||!D&&B))){const[de,K]=Pu({nodeId:D,dragItems:f,nodeLookup:b});l==null||l(j,f,de,K),z==null||z(j,de,K),D||B==null||B(j,K)}}async function q(){if(!y)return;const{transform:ee,panBy:J,autoPanSpeed:b,autoPanOnNodeDrag:Y}=r();if(!Y){g=!1,cancelAnimationFrame(d);return}const[H,W]=dc(m,y,b);(H!==0||W!==0)&&(u.x=(u.x??0)-H/ee[2],u.y=(u.y??0)-W/ee[2],await J({x:H,y:W})&&U(u)),d=requestAnimationFrame(q)}function te(ee){var ie;const{nodeLookup:J,multiSelectionActive:b,nodesDraggable:Y,transform:H,snapGrid:W,snapToGrid:A,selectNodesOnDrag:z,onNodeDragStart:B,onSelectionDragStart:M,unselectNodesAndEdges:T}=r();x=!0,(!z||!R)&&!b&&D&&((ie=J.get(D))!=null&&ie.selected||T()),R&&z&&D&&(t==null||t(D));const re=yo(ee.sourceEvent,{transform:H,snapGrid:W,snapToGrid:A,containerBounds:y});if(u=re,f=C1(J,Y,re,D),f.size>0&&(o||B||!D&&M)){const[ce,fe]=Pu({nodeId:D,dragItems:f,nodeLookup:J});o==null||o(ee.sourceEvent,f,ce,fe),B==null||B(ee.sourceEvent,ce,fe),D||M==null||M(ee.sourceEvent,fe)}}const X=zp().clickDistance(O).on("start",ee=>{const{domNode:J,nodeDragThreshold:b,transform:Y,snapGrid:H,snapToGrid:W}=r();y=(J==null?void 0:J.getBoundingClientRect())||null,_=!1,k=!1,j=ee.sourceEvent,b===0&&te(ee),u=yo(ee.sourceEvent,{transform:Y,snapGrid:H,snapToGrid:W,containerBounds:y}),m=rn(ee.sourceEvent,y)}).on("drag",ee=>{const{autoPanOnNodeDrag:J,transform:b,snapGrid:Y,snapToGrid:H,nodeDragThreshold:W,nodeLookup:A}=r(),z=yo(ee.sourceEvent,{transform:b,snapGrid:Y,snapToGrid:H,containerBounds:y});if(j=ee.sourceEvent,(ee.sourceEvent.type==="touchmove"&&ee.sourceEvent.touches.length>1||D&&!A.has(D))&&(_=!0),!_){if(!g&&J&&x&&(g=!0,q()),!x){const B=rn(ee.sourceEvent,y),M=B.x-m.x,T=B.y-m.y;Math.sqrt(M*M+T*T)>W&&te(ee)}(u.x!==z.xSnapped||u.y!==z.ySnapped)&&f&&x&&(m=rn(ee.sourceEvent,y),U(z))}}).on("end",ee=>{if(!x||_){_&&f.size>0&&r().updateNodePositions(f,!1);return}if(g=!1,x=!1,cancelAnimationFrame(d),f.size>0){const{nodeLookup:J,updateNodePositions:b,onNodeDragStop:Y,onSelectionDragStop:H}=r();if(k&&(b(f,!1),k=!1),a||Y||!D&&H){const[W,A]=Pu({nodeId:D,dragItems:f,nodeLookup:J,dragging:!1});a==null||a(ee.sourceEvent,f,W,A),Y==null||Y(ee.sourceEvent,W,A),D||H==null||H(ee.sourceEvent,A)}}}).filter(ee=>{const J=ee.target;return!ee.button&&(!P||!bh(J,`.${P}`,C))&&(!E||bh(J,E,C))});v.call(X)}function S(){v==null||v.on(".drag",null)}return{update:N,destroy:S}}function M1(t,r,o){const l=[],a={x:t.x-o,y:t.y-o,width:o*2,height:o*2};for(const u of r.values())gl(a,Co(u))>0&&l.push(u);return l}const P1=250;function I1(t,r,o,l){var f,g;let a=[],u=1/0;const d=M1(t,o,r+P1);for(const m of d){const y=[...((f=m.internals.handleBounds)==null?void 0:f.source)??[],...((g=m.internals.handleBounds)==null?void 0:g.target)??[]];for(const x of y){if(l.nodeId===x.nodeId&&l.type===x.type&&l.id===x.id)continue;const{x:v,y:_}=Ar(m,x,x.position,!0),k=Math.sqrt(Math.pow(v-t.x,2)+Math.pow(_-t.y,2));k>r||(k1){const m=l.type==="source"?"target":"source";return a.find(y=>y.type===m)??a[0]}return a[0]}function _g(t,r,o,l,a,u=!1){var m,y,x;const d=l.get(t);if(!d)return null;const f=a==="strict"?(m=d.internals.handleBounds)==null?void 0:m[r]:[...((y=d.internals.handleBounds)==null?void 0:y.source)??[],...((x=d.internals.handleBounds)==null?void 0:x.target)??[]],g=(o?f==null?void 0:f.find(v=>v.id===o):f==null?void 0:f[0])??null;return g&&u?{...g,...Ar(d,g,g.position,!0)}:g}function Sg(t,r){return t||(r!=null&&r.classList.contains("target")?"target":r!=null&&r.classList.contains("source")?"source":null)}function T1(t,r){let o=null;return r?o=!0:t&&!r&&(o=!1),o}const kg=()=>!0;function R1(t,{connectionMode:r,connectionRadius:o,handleId:l,nodeId:a,edgeUpdaterType:u,isTarget:d,domNode:f,nodeLookup:g,lib:m,autoPanOnConnect:y,flowId:x,panBy:v,cancelConnection:_,onConnectStart:k,onConnect:j,onConnectEnd:N,isValidConnection:S=kg,onReconnectEnd:P,updateConnection:E,getTransform:C,getFromHandle:R,autoPanSpeed:D,dragThreshold:O=1,handleDomNode:U}){const q=cg(t.target);let te=0,X;const{x:ee,y:J}=rn(t),b=Sg(u,U),Y=f==null?void 0:f.getBoundingClientRect();let H=!1;if(!Y||!b)return;const W=_g(a,b,l,g,r);if(!W)return;let A=rn(t,Y),z=!1,B=null,M=!1,T=null;function re(){if(!y||!Y)return;const[_e,ve]=dc(A,Y,D);v({x:_e,y:ve}),te=requestAnimationFrame(re)}const ie={...W,nodeId:a,type:b,position:W.position},ce=g.get(a);let de={inProgress:!0,isValid:null,from:Ar(ce,ie,Se.Left,!0),fromHandle:ie,fromPosition:ie.position,fromNode:ce,to:A,toHandle:null,toPosition:mh[ie.position],toNode:null,pointer:A};function K(){H=!0,E(de),k==null||k(t,{nodeId:a,handleId:l,handleType:b})}O===0&&K();function le(_e){if(!H){const{x:Le,y:$e}=rn(_e),Ae=Le-ee,et=$e-J;if(!(Ae*Ae+et*et>O*O))return;K()}if(!R()||!ie){he(_e);return}const ve=C();A=rn(_e,Y),X=I1(Ao(A,ve,!1,[1,1]),o,g,ie),z||(re(),z=!0);const ge=Eg(_e,{handle:X,connectionMode:r,fromNodeId:a,fromHandleId:l,fromType:d?"target":"source",isValidConnection:S,doc:q,lib:m,flowId:x,nodeLookup:g});T=ge.handleDomNode,B=ge.connection,M=T1(!!X,ge.isValid);const Ne=g.get(a),Me=Ne?Ar(Ne,ie,Se.Left,!0):de.from,be={...de,from:Me,isValid:M,to:ge.toHandle&&M?ki({x:ge.toHandle.x,y:ge.toHandle.y},ve):A,toHandle:ge.toHandle,toPosition:M&&ge.toHandle?ge.toHandle.position:mh[ie.position],toNode:ge.toHandle?g.get(ge.toHandle.nodeId):null,pointer:A};E(be),de=be}function he(_e){if(!("touches"in _e&&_e.touches.length>0)){if(H){(X||T)&&B&&M&&(j==null||j(B));const{inProgress:ve,...ge}=de,Ne={...ge,toPosition:de.toHandle?de.toPosition:null};N==null||N(_e,Ne),u&&(P==null||P(_e,Ne))}_(),cancelAnimationFrame(te),z=!1,M=!1,B=null,T=null,q.removeEventListener("mousemove",le),q.removeEventListener("mouseup",he),q.removeEventListener("touchmove",le),q.removeEventListener("touchend",he)}}q.addEventListener("mousemove",le),q.addEventListener("mouseup",he),q.addEventListener("touchmove",le),q.addEventListener("touchend",he)}function Eg(t,{handle:r,connectionMode:o,fromNodeId:l,fromHandleId:a,fromType:u,doc:d,lib:f,flowId:g,isValidConnection:m=kg,nodeLookup:y}){const x=u==="target",v=r?d.querySelector(`.${f}-flow__handle[data-id="${g}-${r==null?void 0:r.nodeId}-${r==null?void 0:r.id}-${r==null?void 0:r.type}"]`):null,{x:_,y:k}=rn(t),j=d.elementFromPoint(_,k),N=j!=null&&j.classList.contains(`${f}-flow__handle`)?j:v,S={handleDomNode:N,isValid:!1,connection:null,toHandle:null};if(N){const P=Sg(void 0,N),E=N.getAttribute("data-nodeid"),C=N.getAttribute("data-handleid"),R=N.classList.contains("connectable"),D=N.classList.contains("connectableend");if(!E||!P)return S;const O={source:x?E:l,sourceHandle:x?C:a,target:x?l:E,targetHandle:x?a:C};S.connection=O;const q=R&&D&&(o===_i.Strict?x&&P==="source"||!x&&P==="target":E!==l||C!==a);S.isValid=q&&m(O),S.toHandle=_g(E,P,C,y,o,!0)}return S}const Zu={onPointerDown:R1,isValid:Eg};function L1({domNode:t,panZoom:r,getTransform:o,getViewScale:l}){const a=zt(t);function u({translateExtent:f,width:g,height:m,zoomStep:y=1,pannable:x=!0,zoomable:v=!0,inversePan:_=!1}){const k=E=>{if(E.sourceEvent.type!=="wheel"||!r)return;const C=o(),R=E.sourceEvent.ctrlKey&&jo()?10:1,D=-E.sourceEvent.deltaY*(E.sourceEvent.deltaMode===1?.05:E.sourceEvent.deltaMode?1:.002)*y,O=C[2]*Math.pow(2,D*R);r.scaleTo(O)};let j=[0,0];const N=E=>{(E.sourceEvent.type==="mousedown"||E.sourceEvent.type==="touchstart")&&(j=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY])},S=E=>{const C=o();if(E.sourceEvent.type!=="mousemove"&&E.sourceEvent.type!=="touchmove"||!r)return;const R=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY],D=[R[0]-j[0],R[1]-j[1]];j=R;const O=l()*Math.max(C[2],Math.log(C[2]))*(_?-1:1),U={x:C[0]-D[0]*O,y:C[1]-D[1]*O},q=[[0,0],[g,m]];r.setViewportConstrained({x:U.x,y:U.y,zoom:C[2]},q,f)},P=Kp().on("start",N).on("zoom",x?S:null).on("zoom.wheel",v?k:null);a.call(P,{})}function d(){a.on("zoom",null)}return{update:u,destroy:d,pointer:en}}const Cl=t=>({x:t.x,y:t.y,zoom:t.k}),Iu=({x:t,y:r,zoom:o})=>kl.translate(t,r).scale(o),nr=(t,r)=>t.target.closest(`.${r}`),Ng=(t,r)=>r===2&&Array.isArray(t)&&t.includes(2),A1=t=>((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2,Tu=(t,r=0,o=A1,l=()=>{})=>{const a=typeof r=="number"&&r>0;return a||l(),a?t.transition().duration(r).ease(o).on("end",l):t},Cg=t=>{const r=t.ctrlKey&&jo()?10:1;return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*r};function z1({zoomPanValues:t,noWheelClassName:r,d3Selection:o,d3Zoom:l,panOnScrollMode:a,panOnScrollSpeed:u,zoomOnPinch:d,onPanZoomStart:f,onPanZoom:g,onPanZoomEnd:m}){return y=>{if(nr(y,r))return y.ctrlKey&&y.preventDefault(),!1;y.preventDefault(),y.stopImmediatePropagation();const x=o.property("__zoom").k||1;if(y.ctrlKey&&d){const N=en(y),S=Cg(y),P=x*Math.pow(2,S);l.scaleTo(o,P,N,y);return}const v=y.deltaMode===1?20:1;let _=a===Pr.Vertical?0:y.deltaX*v,k=a===Pr.Horizontal?0:y.deltaY*v;!jo()&&y.shiftKey&&a!==Pr.Vertical&&(_=y.deltaY*v,k=0),l.translateBy(o,-(_/x)*u,-(k/x)*u,{internal:!0});const j=Cl(o.property("__zoom"));clearTimeout(t.panScrollTimeout),t.isPanScrolling?g==null||g(y,j):(t.isPanScrolling=!0,f==null||f(y,j)),t.panScrollTimeout=setTimeout(()=>{m==null||m(y,j),t.isPanScrolling=!1},150)}}function D1({noWheelClassName:t,preventScrolling:r,d3ZoomHandler:o}){return function(l,a){const u=l.type==="wheel",d=!r&&u&&!l.ctrlKey,f=nr(l,t);if(l.ctrlKey&&u&&f&&l.preventDefault(),d||f)return null;l.preventDefault(),o.call(this,l,a)}}function $1({zoomPanValues:t,onDraggingChange:r,onPanZoomStart:o}){return l=>{var u,d,f;if((u=l.sourceEvent)!=null&&u.internal)return;const a=Cl(l.transform);t.mouseButton=((d=l.sourceEvent)==null?void 0:d.button)||0,t.isZoomingOrPanning=!0,t.prevViewport=a,((f=l.sourceEvent)==null?void 0:f.type)==="mousedown"&&r(!0),o&&(o==null||o(l.sourceEvent,a))}}function O1({zoomPanValues:t,panOnDrag:r,onPaneContextMenu:o,onTransformChange:l,onPanZoom:a}){return u=>{var d,f;t.usedRightMouseButton=!!(o&&Ng(r,t.mouseButton??0)),(d=u.sourceEvent)!=null&&d.sync||l([u.transform.x,u.transform.y,u.transform.k]),a&&!((f=u.sourceEvent)!=null&&f.internal)&&(a==null||a(u.sourceEvent,Cl(u.transform)))}}function F1({zoomPanValues:t,panOnDrag:r,panOnScroll:o,onDraggingChange:l,onPanZoomEnd:a,onPaneContextMenu:u}){return d=>{var f;if(!((f=d.sourceEvent)!=null&&f.internal)&&(t.isZoomingOrPanning=!1,u&&Ng(r,t.mouseButton??0)&&!t.usedRightMouseButton&&d.sourceEvent&&u(d.sourceEvent),t.usedRightMouseButton=!1,l(!1),a)){const g=Cl(d.transform);t.prevViewport=g,clearTimeout(t.timerId),t.timerId=setTimeout(()=>{a==null||a(d.sourceEvent,g)},o?150:0)}}}function H1({panActivationKeyPressed:t,zoomActivationKeyPressed:r,zoomOnScroll:o,zoomOnPinch:l,panOnDrag:a,panOnScroll:u,zoomOnDoubleClick:d,userSelectionActive:f,noWheelClassName:g,noPanClassName:m,lib:y,connectionInProgress:x}){return v=>{var S;const _=r||o,k=l&&v.ctrlKey,j=v.type==="wheel";if(v.button===1&&v.type==="mousedown"&&(nr(v,`${y}-flow__node`)||nr(v,`${y}-flow__edge`)||nr(v,`${y}-flow__selection`)||nr(v,`${y}-flow__nodesselection`)))return!0;if(!a&&!_&&!u&&!d&&!l||f||x&&!j||nr(v,g)&&j||nr(v,m)&&(!j||u&&j&&!r)||!l&&v.ctrlKey&&j)return!1;if(!l&&v.type==="touchstart"&&((S=v.touches)==null?void 0:S.length)>1)return v.preventDefault(),!1;if(!_&&!u&&!k&&j||!a&&(v.type==="mousedown"||v.type==="touchstart")||Array.isArray(a)&&!a.includes(v.button)&&v.type==="mousedown")return!1;const N=Array.isArray(a)&&a.includes(v.button)||!v.button||v.button<=1;return(!v.ctrlKey||j||t)&&N}}function B1({domNode:t,minZoom:r,maxZoom:o,translateExtent:l,viewport:a,onPanZoom:u,onPanZoomStart:d,onPanZoomEnd:f,onDraggingChange:g}){const m={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},y=t.getBoundingClientRect();let x=[[0,0],[y.width,y.height]];const v=typeof ResizeObserver<"u"?new ResizeObserver(J=>{const b=J[0];b&&(x=[[0,0],[b.contentRect.width,b.contentRect.height]])}):null;v==null||v.observe(t);const _=Kp().extent(()=>x).scaleExtent([r,o]).translateExtent(l),k=zt(t).call(_);C({x:a.x,y:a.y,zoom:Si(a.zoom,r,o)},[[0,0],[y.width,y.height]],l);const j=k.on("wheel.zoom"),N=k.on("dblclick.zoom");_.wheelDelta(Cg);async function S(J,b){return k?new Promise(Y=>{_==null||_.interpolate((b==null?void 0:b.interpolate)==="linear"?mo:rl).transform(Tu(k,b==null?void 0:b.duration,b==null?void 0:b.ease,()=>Y(!0)),J)}):!1}function P({noWheelClassName:J,noPanClassName:b,onPaneContextMenu:Y,userSelectionActive:H,panOnScroll:W,panOnDrag:A,panOnScrollMode:z,panOnScrollSpeed:B,preventScrolling:M,zoomOnPinch:T,zoomOnScroll:re,zoomOnDoubleClick:ie,panActivationKeyPressed:ce=!1,zoomActivationKeyPressed:fe,lib:de,onTransformChange:K,connectionInProgress:le,paneClickDistance:he,selectionOnDrag:_e}){H&&!m.isZoomingOrPanning&&E();const ve=W&&!fe&&!H;_.clickDistance(_e?1/0:!nn(he)||he<0?0:he);const ge=ve?z1({zoomPanValues:m,noWheelClassName:J,d3Selection:k,d3Zoom:_,panOnScrollMode:z,panOnScrollSpeed:B,zoomOnPinch:T,onPanZoomStart:d,onPanZoom:u,onPanZoomEnd:f}):D1({noWheelClassName:J,preventScrolling:M,d3ZoomHandler:j});k.on("wheel.zoom",ge,{passive:!1});const Ne=$1({zoomPanValues:m,onDraggingChange:g,onPanZoomStart:d});_.on("start",Ne);const Me=O1({zoomPanValues:m,panOnDrag:A,onPaneContextMenu:!!Y,onPanZoom:u,onTransformChange:K});_.on("zoom",Me);const be=F1({zoomPanValues:m,panOnDrag:A,panOnScroll:W,onPaneContextMenu:Y,onPanZoomEnd:f,onDraggingChange:g});_.on("end",be);const Le=H1({panActivationKeyPressed:ce,zoomActivationKeyPressed:fe,panOnDrag:A,zoomOnScroll:re,panOnScroll:W,zoomOnDoubleClick:ie,zoomOnPinch:T,userSelectionActive:H,noPanClassName:b,noWheelClassName:J,lib:de,connectionInProgress:le});_.filter(Le),ie?k.on("dblclick.zoom",N):k.on("dblclick.zoom",null)}function E(){_.on("zoom",null)}async function C(J,b,Y){const H=Iu(J),W=_==null?void 0:_.constrain()(H,b,Y);return W&&await S(W),W}async function R(J,b){const Y=Iu(J);return await S(Y,b),Y}function D(J){if(k){const b=Iu(J),Y=k.property("__zoom");(Y.k!==J.zoom||Y.x!==J.x||Y.y!==J.y)&&(_==null||_.transform(k,b,null,{sync:!0}))}}function O(){const J=k?qp(k.node()):{x:0,y:0,k:1};return{x:J.x,y:J.y,zoom:J.k}}async function U(J,b){return k?new Promise(Y=>{_==null||_.interpolate((b==null?void 0:b.interpolate)==="linear"?mo:rl).scaleTo(Tu(k,b==null?void 0:b.duration,b==null?void 0:b.ease,()=>Y(!0)),J)}):!1}async function q(J,b){return k?new Promise(Y=>{_==null||_.interpolate((b==null?void 0:b.interpolate)==="linear"?mo:rl).scaleBy(Tu(k,b==null?void 0:b.duration,b==null?void 0:b.ease,()=>Y(!0)),J)}):!1}function te(J){_==null||_.scaleExtent(J)}function X(J){_==null||_.translateExtent(J)}function ee(J){const b=!nn(J)||J<0?0:J;_==null||_.clickDistance(b)}return{update:P,destroy:E,setViewport:R,setViewportConstrained:C,getViewport:O,scaleTo:U,scaleBy:q,setScaleExtent:te,setTranslateExtent:X,syncViewport:D,setClickDistance:ee}}var Ei;(function(t){t.Line="line",t.Handle="handle"})(Ei||(Ei={}));function V1({width:t,prevWidth:r,height:o,prevHeight:l,affectsX:a,affectsY:u}){const d=t-r,f=o-l,g=[d>0?1:d<0?-1:0,f>0?1:f<0?-1:0];return d&&a&&(g[0]=g[0]*-1),f&&u&&(g[1]=g[1]*-1),g}function Mh(t){const r=t.includes("right")||t.includes("left"),o=t.includes("bottom")||t.includes("top"),l=t.includes("left"),a=t.includes("top");return{isHorizontal:r,isVertical:o,affectsX:l,affectsY:a}}function er(t,r){return Math.max(0,r-t)}function tr(t,r){return Math.max(0,t-r)}function Zs(t,r,o){return Math.max(0,r-t,t-o)}function Ph(t,r){return t?!r:r}function U1(t,r,o,l,a,u,d,f){let{affectsX:g,affectsY:m}=r;const{isHorizontal:y,isVertical:x}=r,v=y&&x,{xSnapped:_,ySnapped:k}=o,{minWidth:j,maxWidth:N,minHeight:S,maxHeight:P}=l,{x:E,y:C,width:R,height:D,aspectRatio:O}=t;let U=Math.floor(y?_-t.pointerX:0),q=Math.floor(x?k-t.pointerY:0);const te=R+(g?-U:U),X=D+(m?-q:q),ee=-u[0]*R,J=-u[1]*D;let b=Zs(te,j,N),Y=Zs(X,S,P);if(d){let A=0,z=0;g&&U<0?A=er(E+U+ee,d[0][0]):!g&&U>0&&(A=tr(E+te+ee,d[1][0])),m&&q<0?z=er(C+q+J,d[0][1]):!m&&q>0&&(z=tr(C+X+J,d[1][1])),b=Math.max(b,A),Y=Math.max(Y,z)}if(f){let A=0,z=0;g&&U>0?A=tr(E+U,f[0][0]):!g&&U<0&&(A=er(E+te,f[1][0])),m&&q>0?z=tr(C+q,f[0][1]):!m&&q<0&&(z=er(C+X,f[1][1])),b=Math.max(b,A),Y=Math.max(Y,z)}if(a){if(y){const A=Zs(te/O,S,P)*O;if(b=Math.max(b,A),d){let z=0;!g&&!m||g&&!m&&v?z=tr(C+J+te/O,d[1][1])*O:z=er(C+J+(g?U:-U)/O,d[0][1])*O,b=Math.max(b,z)}if(f){let z=0;!g&&!m||g&&!m&&v?z=er(C+te/O,f[1][1])*O:z=tr(C+(g?U:-U)/O,f[0][1])*O,b=Math.max(b,z)}}if(x){const A=Zs(X*O,j,N)/O;if(Y=Math.max(Y,A),d){let z=0;!g&&!m||m&&!g&&v?z=tr(E+X*O+ee,d[1][0])/O:z=er(E+(m?q:-q)*O+ee,d[0][0])/O,Y=Math.max(Y,z)}if(f){let z=0;!g&&!m||m&&!g&&v?z=er(E+X*O,f[1][0])/O:z=tr(E+(m?q:-q)*O,f[0][0])/O,Y=Math.max(Y,z)}}}q=q+(q<0?Y:-Y),U=U+(U<0?b:-b),a&&(v?te>X*O?q=(Ph(g,m)?-U:U)/O:U=(Ph(g,m)?-q:q)*O:y?(q=U/O,m=g):(U=q*O,g=m));const H=g?E+U:E,W=m?C+q:C;return{width:R+(g?-U:U),height:D+(m?-q:q),x:u[0]*U*(g?-1:1)+H,y:u[1]*q*(m?-1:1)+W}}const jg={width:0,height:0,x:0,y:0},W1={...jg,pointerX:0,pointerY:0,aspectRatio:1};function Y1(t,r,o){const l=r.position.x+t.position.x,a=r.position.y+t.position.y,u=t.measured.width??0,d=t.measured.height??0,f=o[0]*u,g=o[1]*d;return[[l-f,a-g],[l+u-f,a+d-g]]}function X1({domNode:t,nodeId:r,getStoreItems:o,onChange:l,onEnd:a}){const u=zt(t);let d={controlDirection:Mh("bottom-right"),boundaries:{minWidth:0,minHeight:0,maxWidth:Number.MAX_VALUE,maxHeight:Number.MAX_VALUE},resizeDirection:void 0,keepAspectRatio:!1};function f({controlPosition:m,boundaries:y,keepAspectRatio:x,resizeDirection:v,onResizeStart:_,onResize:k,onResizeEnd:j,shouldResize:N}){let S={...jg},P={...W1};d={boundaries:y,resizeDirection:v,keepAspectRatio:x,controlDirection:Mh(m)};let E,C=null,R=[],D,O,U,q=!1;const te=zp().on("start",X=>{const{nodeLookup:ee,transform:J,snapGrid:b,snapToGrid:Y,nodeOrigin:H,paneDomNode:W}=o();if(E=ee.get(r),!E)return;C=(W==null?void 0:W.getBoundingClientRect())??null;const{xSnapped:A,ySnapped:z}=yo(X.sourceEvent,{transform:J,snapGrid:b,snapToGrid:Y,containerBounds:C});S={width:E.measured.width??0,height:E.measured.height??0,x:E.position.x??0,y:E.position.y??0},P={...S,pointerX:A,pointerY:z,aspectRatio:S.width/S.height},D=void 0,O=Lr(E.extent)?E.extent:void 0,E.parentId&&(E.extent==="parent"||E.expandParent)&&(D=ee.get(E.parentId)),D&&E.extent==="parent"&&(O=[[0,0],[D.measured.width,D.measured.height]]),R=[],U=void 0;for(const[B,M]of ee)if(M.parentId===r&&(R.push({id:B,position:{...M.position},extent:M.extent}),M.extent==="parent"||M.expandParent)){const T=Y1(M,E,M.origin??H);U?U=[[Math.min(T[0][0],U[0][0]),Math.min(T[0][1],U[0][1])],[Math.max(T[1][0],U[1][0]),Math.max(T[1][1],U[1][1])]]:U=T}_==null||_(X,{...S})}).on("drag",X=>{const{transform:ee,snapGrid:J,snapToGrid:b,nodeOrigin:Y}=o(),H=yo(X.sourceEvent,{transform:ee,snapGrid:J,snapToGrid:b,containerBounds:C}),W=[];if(!E)return;const{x:A,y:z,width:B,height:M}=S,T={},re=E.origin??Y,{width:ie,height:ce,x:fe,y:de}=U1(P,d.controlDirection,H,d.boundaries,d.keepAspectRatio,re,O,U),K=ie!==B,le=ce!==M,he=fe!==A&&K,_e=de!==z&≤if(!he&&!_e&&!K&&!le)return;if((he||_e||re[0]===1||re[1]===1)&&(T.x=he?fe:S.x,T.y=_e?de:S.y,S.x=T.x,S.y=T.y,R.length>0)){const Me=fe-A,be=de-z;for(const Le of R)Le.position={x:Le.position.x-Me+re[0]*(ie-B),y:Le.position.y-be+re[1]*(ce-M)},W.push(Le)}if((K||le)&&(T.width=K&&(!d.resizeDirection||d.resizeDirection==="horizontal")?ie:S.width,T.height=le&&(!d.resizeDirection||d.resizeDirection==="vertical")?ce:S.height,S.width=T.width,S.height=T.height),D&&E.expandParent){const Me=re[0]*(T.width??0);T.x&&T.x{q&&(j==null||j(X,{...S}),a==null||a({...S}),q=!1)});u.call(te)}function g(){u.on(".drag",null)}return{update:f,destroy:g}}var Ru={exports:{}},Lu={},Au={exports:{}},zu={};/** - * @license React - * use-sync-external-store-shim.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Ih;function G1(){if(Ih)return zu;Ih=1;var t=Mo();function r(x,v){return x===v&&(x!==0||1/x===1/v)||x!==x&&v!==v}var o=typeof Object.is=="function"?Object.is:r,l=t.useState,a=t.useEffect,u=t.useLayoutEffect,d=t.useDebugValue;function f(x,v){var _=v(),k=l({inst:{value:_,getSnapshot:v}}),j=k[0].inst,N=k[1];return u(function(){j.value=_,j.getSnapshot=v,g(j)&&N({inst:j})},[x,_,v]),a(function(){return g(j)&&N({inst:j}),x(function(){g(j)&&N({inst:j})})},[x]),d(_),_}function g(x){var v=x.getSnapshot;x=x.value;try{var _=v();return!o(x,_)}catch{return!0}}function m(x,v){return v()}var y=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?m:f;return zu.useSyncExternalStore=t.useSyncExternalStore!==void 0?t.useSyncExternalStore:y,zu}var Th;function Q1(){return Th||(Th=1,Au.exports=G1()),Au.exports}/** - * @license React - * use-sync-external-store-shim/with-selector.production.js - * - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */var Rh;function q1(){if(Rh)return Lu;Rh=1;var t=Mo(),r=Q1();function o(m,y){return m===y&&(m!==0||1/m===1/y)||m!==m&&y!==y}var l=typeof Object.is=="function"?Object.is:o,a=r.useSyncExternalStore,u=t.useRef,d=t.useEffect,f=t.useMemo,g=t.useDebugValue;return Lu.useSyncExternalStoreWithSelector=function(m,y,x,v,_){var k=u(null);if(k.current===null){var j={hasValue:!1,value:null};k.current=j}else j=k.current;k=f(function(){function S(D){if(!P){if(P=!0,E=D,D=v(D),_!==void 0&&j.hasValue){var O=j.value;if(_(O,D))return C=O}return C=D}if(O=C,l(E,D))return O;var U=v(D);return _!==void 0&&_(O,U)?(E=D,O):(E=D,C=U)}var P=!1,E,C,R=x===void 0?null:x;return[function(){return S(y())},R===null?void 0:function(){return S(R())}]},[y,x,v,_]);var N=a(m,k[0],k[1]);return d(function(){j.hasValue=!0,j.value=N},[N]),g(N),N},Lu}var Lh;function K1(){return Lh||(Lh=1,Ru.exports=q1()),Ru.exports}var Z1=K1();const J1=xp(Z1),e_={},Ah=t=>{let r;const o=new Set,l=(y,x)=>{const v=typeof y=="function"?y(r):y;if(!Object.is(v,r)){const _=r;r=x??(typeof v!="object"||v===null)?v:Object.assign({},r,v),o.forEach(k=>k(r,_))}},a=()=>r,g={setState:l,getState:a,getInitialState:()=>m,subscribe:y=>(o.add(y),()=>o.delete(y)),destroy:()=>{(e_?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),o.clear()}},m=r=t(l,a,g);return g},t_=t=>t?Ah(t):Ah,{useDebugValue:n_}=Zy,{useSyncExternalStoreWithSelector:r_}=J1,i_=t=>t;function bg(t,r=i_,o){const l=r_(t.subscribe,t.getState,t.getServerState||t.getInitialState,r,o);return n_(l),l}const zh=(t,r)=>{const o=t_(t),l=(a,u=r)=>bg(o,a,u);return Object.assign(l,o),l},o_=(t,r)=>t?zh(t,r):zh;function qe(t,r){if(Object.is(t,r))return!0;if(typeof t!="object"||t===null||typeof r!="object"||r===null)return!1;if(t instanceof Map&&r instanceof Map){if(t.size!==r.size)return!1;for(const[l,a]of t)if(!Object.is(a,r.get(l)))return!1;return!0}if(t instanceof Set&&r instanceof Set){if(t.size!==r.size)return!1;for(const l of t)if(!r.has(l))return!1;return!0}const o=Object.keys(t);if(o.length!==Object.keys(r).length)return!1;for(const l of o)if(!Object.prototype.hasOwnProperty.call(r,l)||!Object.is(t[l],r[l]))return!1;return!0}wp();const jl=$.createContext(null),s_=jl.Provider,Mg=on.error001("react");function Te(t,r){const o=$.useContext(jl);if(o===null)throw new Error(Mg);return bg(o,t,r)}function Be(){const t=$.useContext(jl);if(t===null)throw new Error(Mg);return $.useMemo(()=>({getState:t.getState,setState:t.setState,subscribe:t.subscribe}),[t])}const Dh={display:"none"},l_={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},Pg="react-flow__node-desc",Ig="react-flow__edge-desc",a_="react-flow__aria-live",u_=t=>t.ariaLiveMessage,c_=t=>t.ariaLabelConfig;function d_({rfId:t}){const r=Te(u_);return p.jsx("div",{id:`${a_}-${t}`,"aria-live":"assertive","aria-atomic":"true",style:l_,children:r})}function f_({rfId:t,disableKeyboardA11y:r}){const o=Te(c_);return p.jsxs(p.Fragment,{children:[p.jsx("div",{id:`${Pg}-${t}`,style:Dh,children:r?o["node.a11yDescription.default"]:o["node.a11yDescription.keyboardDisabled"]}),p.jsx("div",{id:`${Ig}-${t}`,style:Dh,children:o["edge.a11yDescription.default"]}),!r&&p.jsx(d_,{rfId:t})]})}const bl=$.forwardRef(({position:t="top-left",children:r,className:o,style:l,...a},u)=>{const d=`${t}`.split("-");return p.jsx("div",{className:rt(["react-flow__panel",o,...d]),style:l,ref:u,...a,children:r})});bl.displayName="Panel";const $h="https://reactflow.dev?utm_source=attribution";function h_({proOptions:t,position:r="bottom-right"}){return t!=null&&t.hideAttribution?null:p.jsx(bl,{position:r,className:"react-flow__attribution","data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: ${$h}`,children:p.jsx("a",{href:$h,target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const p_=t=>{const r=[],o=[];for(const[,l]of t.nodeLookup)l.selected&&r.push(l.internals.userNode);for(const[,l]of t.edgeLookup)l.selected&&o.push(l);return{selectedNodes:r,selectedEdges:o}},Js=t=>t.id;function g_(t,r){return qe(t.selectedNodes.map(Js),r.selectedNodes.map(Js))&&qe(t.selectedEdges.map(Js),r.selectedEdges.map(Js))}function m_({onSelectionChange:t}){const r=Be(),{selectedNodes:o,selectedEdges:l}=Te(p_,g_);return $.useEffect(()=>{const a={nodes:o,edges:l};t==null||t(a),r.getState().onSelectionChangeHandlers.forEach(u=>u(a))},[o,l,t]),null}const y_=t=>!!t.onSelectionChangeHandlers;function v_({onSelectionChange:t}){const r=Te(y_);return t||r?p.jsx(m_,{onSelectionChange:t}):null}const Tg=[0,0],x_={x:0,y:0,zoom:1},w_=["nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraggable","autoPanOnNodeFocus","nodesConnectable","nodesFocusable","edgesFocusable","edgesReconnectable","elevateNodesOnSelect","elevateEdgesOnSelect","minZoom","maxZoom","nodeExtent","onNodesChange","onEdgesChange","elementsSelectable","connectionMode","snapGrid","snapToGrid","translateExtent","connectOnClick","defaultEdgeOptions","fitView","fitViewOptions","onNodesDelete","onEdgesDelete","onDelete","onNodeDrag","onNodeDragStart","onNodeDragStop","onSelectionDrag","onSelectionDragStart","onSelectionDragStop","onMoveStart","onMove","onMoveEnd","noPanClassName","nodeOrigin","autoPanOnConnect","autoPanOnNodeDrag","onError","connectionRadius","isValidConnection","selectNodesOnDrag","nodeDragThreshold","connectionDragThreshold","onBeforeDelete","debug","autoPanSpeed","ariaLabelConfig","zIndexMode"],Oh=[...w_,"rfId"],__=t=>({setNodes:t.setNodes,setEdges:t.setEdges,setMinZoom:t.setMinZoom,setMaxZoom:t.setMaxZoom,setTranslateExtent:t.setTranslateExtent,setNodeExtent:t.setNodeExtent,reset:t.reset,setDefaultNodesAndEdges:t.setDefaultNodesAndEdges}),Fh={translateExtent:ko,nodeOrigin:Tg,minZoom:.5,maxZoom:2,elementsSelectable:!0,noPanClassName:"nopan",rfId:"1"};function S_(t){const{setNodes:r,setEdges:o,setMinZoom:l,setMaxZoom:a,setTranslateExtent:u,setNodeExtent:d,reset:f,setDefaultNodesAndEdges:g}=Te(__,qe),m=Be();$.useEffect(()=>(g(t.defaultNodes,t.defaultEdges),()=>{y.current=Fh,f()}),[]);const y=$.useRef(Fh);return $.useEffect(()=>{for(const x of Oh){const v=t[x],_=y.current[x];v!==_&&(typeof t[x]>"u"||(x==="nodes"?r(v):x==="edges"?o(v):x==="minZoom"?l(v):x==="maxZoom"?a(v):x==="translateExtent"?u(v):x==="nodeExtent"?d(v):x==="ariaLabelConfig"?m.setState({ariaLabelConfig:s1(v)}):x==="fitView"?m.setState({fitViewQueued:v}):x==="fitViewOptions"?m.setState({fitViewOptions:v}):m.setState({[x]:v})))}y.current=t},Oh.map(x=>t[x])),null}function Hh(){return typeof window>"u"||!window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)")}function k_(t){var l;const[r,o]=$.useState(t==="system"?null:t);return $.useEffect(()=>{if(t!=="system"){o(t);return}const a=Hh(),u=()=>o(a!=null&&a.matches?"dark":"light");return u(),a==null||a.addEventListener("change",u),()=>{a==null||a.removeEventListener("change",u)}},[t]),r!==null?r:(l=Hh())!=null&&l.matches?"dark":"light"}const Bh=typeof document<"u"?document:null;function bo(t=null,r={target:Bh,actInsideInputWithModifier:!0}){const[o,l]=$.useState(!1),a=$.useRef(!1),u=$.useRef(new Set([])),[d,f]=$.useMemo(()=>{if(t!==null){const m=(Array.isArray(t)?t:[t]).filter(x=>typeof x=="string").map(x=>x.replace(/\+/g,` -`).replace(` - -`,` -+`).split(` -`)),y=m.reduce((x,v)=>x.concat(...v),[]);return[m,y]}return[[],[]]},[t]);return $.useEffect(()=>{const g=(r==null?void 0:r.target)??Bh,m=(r==null?void 0:r.actInsideInputWithModifier)??!0;if(t!==null){const y=_=>{var N,S;if(a.current=_.ctrlKey||_.metaKey||_.shiftKey||_.altKey,(!a.current||a.current&&!m)&&dg(_))return!1;const j=Uh(_.code,f);if(u.current.add(_[j]),Vh(d,u.current,!1)){const P=((S=(N=_.composedPath)==null?void 0:N.call(_))==null?void 0:S[0])||_.target,E=(P==null?void 0:P.nodeName)==="BUTTON"||(P==null?void 0:P.nodeName)==="A";r.preventDefault!==!1&&(a.current||!E)&&_.preventDefault(),l(!0)}},x=_=>{const k=Uh(_.code,f);Vh(d,u.current,!0)?(l(!1),u.current.clear()):u.current.delete(_[k]),_.key==="Meta"&&u.current.clear(),a.current=!1},v=()=>{u.current.clear(),l(!1)};return g==null||g.addEventListener("keydown",y),g==null||g.addEventListener("keyup",x),window.addEventListener("blur",v),window.addEventListener("contextmenu",v),()=>{g==null||g.removeEventListener("keydown",y),g==null||g.removeEventListener("keyup",x),window.removeEventListener("blur",v),window.removeEventListener("contextmenu",v)}}},[t,l]),o}function Vh(t,r,o){return t.filter(l=>o||l.length===r.size).some(l=>l.every(a=>r.has(a)))}function Uh(t,r){return r.includes(t)?"code":"key"}const E_=()=>{const t=Be();return $.useMemo(()=>({zoomIn:async r=>{const{panZoom:o}=t.getState();return o?o.scaleBy(1.2,r):!1},zoomOut:async r=>{const{panZoom:o}=t.getState();return o?o.scaleBy(1/1.2,r):!1},zoomTo:async(r,o)=>{const{panZoom:l}=t.getState();return l?l.scaleTo(r,o):!1},getZoom:()=>t.getState().transform[2],setViewport:async(r,o)=>{const{transform:[l,a,u],panZoom:d}=t.getState();return d?(await d.setViewport({x:r.x??l,y:r.y??a,zoom:r.zoom??u},o),!0):!1},getViewport:()=>{const[r,o,l]=t.getState().transform;return{x:r,y:o,zoom:l}},setCenter:async(r,o,l)=>t.getState().setCenter(r,o,l),fitBounds:async(r,o)=>{const{width:l,height:a,minZoom:u,maxZoom:d,panZoom:f}=t.getState(),g=fc(r,l,a,u,d,(o==null?void 0:o.padding)??.1);return f?(await f.setViewport(g,{duration:o==null?void 0:o.duration,ease:o==null?void 0:o.ease,interpolate:o==null?void 0:o.interpolate}),!0):!1},screenToFlowPosition:(r,o={})=>{const{transform:l,snapGrid:a,snapToGrid:u,domNode:d}=t.getState();if(!d)return r;const{x:f,y:g}=d.getBoundingClientRect(),m={x:r.x-f,y:r.y-g},y=o.snapGrid??a,x=o.snapToGrid??u;return Ao(m,l,x,y)},flowToScreenPosition:r=>{const{transform:o,domNode:l}=t.getState();if(!l)return r;const{x:a,y:u}=l.getBoundingClientRect(),d=ki(r,o);return{x:d.x+a,y:d.y+u}}}),[])};function Rg(t,r){const o=[],l=new Map,a=[];for(const u of t)if(u.type==="add"){a.push(u);continue}else if(u.type==="remove"||u.type==="replace")l.set(u.id,[u]);else{const d=l.get(u.id);d?d.push(u):l.set(u.id,[u])}for(const u of r){const d=l.get(u.id);if(!d){o.push(u);continue}if(d[0].type==="remove")continue;if(d[0].type==="replace"){o.push({...d[0].item});continue}const f={...u};for(const g of d)N_(g,f);o.push(f)}return a.length&&a.forEach(u=>{u.index!==void 0?o.splice(u.index,0,{...u.item}):o.push({...u.item})}),o}function N_(t,r){switch(t.type){case"select":{r.selected=t.selected;break}case"position":{typeof t.position<"u"&&(r.position=t.position),typeof t.dragging<"u"&&(r.dragging=t.dragging);break}case"dimensions":{typeof t.dimensions<"u"&&(r.measured={...t.dimensions},t.setAttributes&&((t.setAttributes===!0||t.setAttributes==="width")&&(r.width=t.dimensions.width),(t.setAttributes===!0||t.setAttributes==="height")&&(r.height=t.dimensions.height))),typeof t.resizing=="boolean"&&(r.resizing=t.resizing);break}}}function C_(t,r){return Rg(t,r)}function j_(t,r){return Rg(t,r)}function Cr(t,r){return{id:t,type:"select",selected:r}}function mi(t,r=new Set,o=!1){const l=[];for(const[a,u]of t){const d=r.has(a);!(u.selected===void 0&&!d)&&u.selected!==d&&(o&&(u.selected=d),l.push(Cr(u.id,d)))}return l}function Wh({items:t=[],lookup:r}){var a;const o=[],l=new Map(t.map(u=>[u.id,u]));for(const[u,d]of t.entries()){const f=r.get(d.id),g=((a=f==null?void 0:f.internals)==null?void 0:a.userNode)??f;g!==void 0&&g!==d&&o.push({id:d.id,item:d,type:"replace"}),g===void 0&&o.push({item:d,type:"add",index:u})}for(const[u]of r)l.get(u)===void 0&&o.push({id:u,type:"remove"});return o}function Yh(t){return{id:t.id,type:"remove"}}const b_=lg();function M_(t,r,o={}){return f1(t,r,{...o,onError:o.onError??b_})}const Xh=t=>Kw(t),P_=t=>ng(t);function Lg(t){return $.forwardRef(t)}const Ag=typeof window<"u"?$.useLayoutEffect:$.useEffect;function Gh(t){const[r,o]=$.useState(BigInt(0)),[l]=$.useState(()=>I_(()=>o(a=>a+BigInt(1))));return Ag(()=>{const a=l.get();a.length&&(t(a),l.reset())},[r]),l}function I_(t){let r=[];return{get:()=>r,reset:()=>{r=[]},push:o=>{r.push(o),t()}}}const zg=$.createContext(null);function T_({children:t}){const r=Be(),o=$.useCallback(f=>{const{nodes:g=[],setNodes:m,hasDefaultNodes:y,onNodesChange:x,nodeLookup:v,fitViewQueued:_,onNodesChangeMiddlewareMap:k}=r.getState();let j=g;for(const S of f)j=typeof S=="function"?S(j):S;let N=Wh({items:j,lookup:v});for(const S of k.values())N=S(N);y&&m(j),N.length>0?x==null||x(N):_&&window.requestAnimationFrame(()=>{const{fitViewQueued:S,nodes:P,setNodes:E}=r.getState();S&&E(P)})},[]),l=Gh(o),a=$.useCallback(f=>{const{edges:g=[],setEdges:m,hasDefaultEdges:y,onEdgesChange:x,edgeLookup:v}=r.getState();let _=g;for(const k of f)_=typeof k=="function"?k(_):k;y?m(_):x&&x(Wh({items:_,lookup:v}))},[]),u=Gh(a),d=$.useMemo(()=>({nodeQueue:l,edgeQueue:u}),[]);return p.jsx(zg.Provider,{value:d,children:t})}function R_(){const t=$.useContext(zg);if(!t)throw new Error("useBatchContext must be used within a BatchProvider");return t}const L_=t=>!!t.panZoom;function Ml(){const t=E_(),r=Be(),o=R_(),l=Te(L_),a=$.useMemo(()=>{const u=x=>r.getState().nodeLookup.get(x),d=x=>{o.nodeQueue.push(x)},f=x=>{o.edgeQueue.push(x)},g=x=>{var S,P;const{nodeLookup:v,nodeOrigin:_}=r.getState(),k=Xh(x)?x:v.get(x.id),j=k.parentId?ug(k.position,k.measured,k.parentId,v,_):k.position,N={...k,position:j,width:((S=k.measured)==null?void 0:S.width)??k.width,height:((P=k.measured)==null?void 0:P.height)??k.height};return Co(N)},m=(x,v,_={replace:!1})=>{d(k=>k.map(j=>{if(j.id===x){const N=typeof v=="function"?v(j):v;return _.replace&&Xh(N)?N:{...j,...N}}return j}))},y=(x,v,_={replace:!1})=>{f(k=>k.map(j=>{if(j.id===x){const N=typeof v=="function"?v(j):v;return _.replace&&P_(N)?N:{...j,...N}}return j}))};return{getNodes:()=>r.getState().nodes.map(x=>({...x})),getNode:x=>{var v;return(v=u(x))==null?void 0:v.internals.userNode},getInternalNode:u,getEdges:()=>{const{edges:x=[]}=r.getState();return x.map(v=>({...v}))},getEdge:x=>r.getState().edgeLookup.get(x),setNodes:d,setEdges:f,addNodes:x=>{const v=Array.isArray(x)?x:[x];o.nodeQueue.push(_=>[..._,...v])},addEdges:x=>{const v=Array.isArray(x)?x:[x];o.edgeQueue.push(_=>[..._,...v])},toObject:()=>{const{nodes:x=[],edges:v=[],transform:_}=r.getState(),[k,j,N]=_;return{nodes:x.map(S=>({...S})),edges:v.map(S=>({...S})),viewport:{x:k,y:j,zoom:N}}},deleteElements:async({nodes:x=[],edges:v=[]})=>{const{nodes:_,edges:k,onNodesDelete:j,onEdgesDelete:N,triggerNodeChanges:S,triggerEdgeChanges:P,onDelete:E,onBeforeDelete:C}=r.getState(),{nodes:R,edges:D}=await n1({nodesToRemove:x,edgesToRemove:v,nodes:_,edges:k,onBeforeDelete:C}),O=D.length>0,U=R.length>0;if(O){const q=D.map(Yh);N==null||N(D),P(q)}if(U){const q=R.map(Yh);j==null||j(R),S(q)}return(U||O)&&(E==null||E({nodes:R,edges:D})),{deletedNodes:R,deletedEdges:D}},getIntersectingNodes:(x,v=!0,_)=>{const k=vh(x),j=k?x:g(x),N=_!==void 0;return j?(_||r.getState().nodes).filter(S=>{const P=r.getState().nodeLookup.get(S.id);if(P&&!k&&(S.id===x.id||!P.internals.positionAbsolute))return!1;const E=Co(N?S:P),C=gl(E,j);return v&&C>0||C>=E.width*E.height||C>=j.width*j.height}):[]},isNodeIntersecting:(x,v,_=!0)=>{const j=vh(x)?x:g(x);if(!j)return!1;const N=gl(j,v);return _&&N>0||N>=v.width*v.height||N>=j.width*j.height},updateNode:m,updateNodeData:(x,v,_={replace:!1})=>{m(x,k=>{const j=typeof v=="function"?v(k):v;return _.replace?{...k,data:j}:{...k,data:{...k.data,...j}}},_)},updateEdge:y,updateEdgeData:(x,v,_={replace:!1})=>{y(x,k=>{const j=typeof v=="function"?v(k):v;return _.replace?{...k,data:j}:{...k,data:{...k.data,...j}}},_)},getNodesBounds:x=>{const{nodeLookup:v,nodeOrigin:_}=r.getState();return Zw(x,{nodeLookup:v,nodeOrigin:_})},getHandleConnections:({type:x,id:v,nodeId:_})=>{var k;return Array.from(((k=r.getState().connectionLookup.get(`${_}-${x}${v?`-${v}`:""}`))==null?void 0:k.values())??[])},getNodeConnections:({type:x,handleId:v,nodeId:_})=>{var k;return Array.from(((k=r.getState().connectionLookup.get(`${_}${x?v?`-${x}-${v}`:`-${x}`:""}`))==null?void 0:k.values())??[])},fitView:async x=>{const v=r.getState().fitViewResolver??o1();return r.setState({fitViewQueued:!0,fitViewOptions:x,fitViewResolver:v}),o.nodeQueue.push(_=>[..._]),v.promise}}},[]);return $.useMemo(()=>({...a,...t,viewportInitialized:l}),[l])}const Qh=t=>t.selected,A_=typeof window<"u"?window:void 0;function z_({deleteKeyCode:t,multiSelectionKeyCode:r}){const o=Be(),{deleteElements:l}=Ml(),a=bo(t,{actInsideInputWithModifier:!1}),u=bo(r,{target:A_});$.useEffect(()=>{if(a){const{edges:d,nodes:f}=o.getState();l({nodes:f.filter(Qh),edges:d.filter(Qh)}),o.setState({nodesSelectionActive:!1})}},[a]),$.useEffect(()=>{o.setState({multiSelectionActive:u})},[u])}function D_(t){const r=Be();$.useEffect(()=>{const o=()=>{var a,u,d,f;if(!t.current||!(((u=(a=t.current).checkVisibility)==null?void 0:u.call(a))??!0))return!1;const l=hc(t.current);(l.height===0||l.width===0)&&((f=(d=r.getState()).onError)==null||f.call(d,"004",on.error004())),r.setState({width:l.width||500,height:l.height||500})};if(t.current){o(),window.addEventListener("resize",o);const l=new ResizeObserver(()=>o());return l.observe(t.current),()=>{window.removeEventListener("resize",o),l&&t.current&&l.unobserve(t.current)}}},[])}const Pl={position:"absolute",width:"100%",height:"100%",top:0,left:0},$_=t=>({userSelectionActive:t.userSelectionActive,lib:t.lib,connectionInProgress:t.connection.inProgress});function O_({onPaneContextMenu:t,zoomOnScroll:r=!0,zoomOnPinch:o=!0,panOnScroll:l=!1,panActivationKeyPressed:a,panOnScrollSpeed:u=.5,panOnScrollMode:d=Pr.Free,zoomOnDoubleClick:f=!0,panOnDrag:g=!0,defaultViewport:m,translateExtent:y,minZoom:x,maxZoom:v,zoomActivationKeyCode:_,preventScrolling:k=!0,children:j,noWheelClassName:N,noPanClassName:S,onViewportChange:P,isControlledViewport:E,paneClickDistance:C,selectionOnDrag:R}){const D=Be(),O=$.useRef(null),{userSelectionActive:U,lib:q,connectionInProgress:te}=Te($_,qe),X=bo(_),ee=$.useRef();D_(O);const J=$.useCallback(b=>{P==null||P({x:b[0],y:b[1],zoom:b[2]}),E||D.setState({transform:b})},[P,E]);return $.useEffect(()=>{if(O.current){ee.current=B1({domNode:O.current,minZoom:x,maxZoom:v,translateExtent:y,viewport:m,onDraggingChange:W=>D.setState(A=>A.paneDragging===W?A:{paneDragging:W}),onPanZoomStart:(W,A)=>{const{onViewportChangeStart:z,onMoveStart:B}=D.getState();B==null||B(W,A),z==null||z(A)},onPanZoom:(W,A)=>{const{onViewportChange:z,onMove:B}=D.getState();B==null||B(W,A),z==null||z(A)},onPanZoomEnd:(W,A)=>{const{onViewportChangeEnd:z,onMoveEnd:B}=D.getState();B==null||B(W,A),z==null||z(A)}});const{x:b,y:Y,zoom:H}=ee.current.getViewport();return D.setState({panZoom:ee.current,transform:[b,Y,H],domNode:O.current.closest(".react-flow")}),()=>{var W;(W=ee.current)==null||W.destroy()}}},[]),$.useEffect(()=>{var b;(b=ee.current)==null||b.update({onPaneContextMenu:t,zoomOnScroll:r,zoomOnPinch:o,panOnScroll:l,panActivationKeyPressed:a,panOnScrollSpeed:u,panOnScrollMode:d,zoomOnDoubleClick:f,panOnDrag:g,zoomActivationKeyPressed:X,preventScrolling:k,noPanClassName:S,userSelectionActive:U,noWheelClassName:N,lib:q,onTransformChange:J,connectionInProgress:te,selectionOnDrag:R,paneClickDistance:C})},[t,r,o,l,a,u,d,f,g,X,k,S,U,N,q,J,te,R,C]),p.jsx("div",{className:"react-flow__renderer",ref:O,style:Pl,children:j})}const F_=t=>({userSelectionActive:t.userSelectionActive,userSelectionRect:t.userSelectionRect});function H_(){const{userSelectionActive:t,userSelectionRect:r}=Te(F_,qe);return t&&r?p.jsx("div",{className:"react-flow__selection react-flow__container",style:{width:r.width,height:r.height,transform:`translate(${r.x}px, ${r.y}px)`}}):null}const Du=(t,r)=>o=>{o.target===r.current&&(t==null||t(o))},B_=t=>({userSelectionActive:t.userSelectionActive,elementsSelectable:t.elementsSelectable,dragging:t.paneDragging,panBy:t.panBy,autoPanSpeed:t.autoPanSpeed});function V_({isSelecting:t,selectionKeyPressed:r,selectionMode:o=Eo.Full,panOnDrag:l,autoPanOnSelection:a,paneClickDistance:u,selectionOnDrag:d,onSelectionStart:f,onSelectionEnd:g,onPaneClick:m,onPaneContextMenu:y,onPaneScroll:x,onPaneMouseEnter:v,onPaneMouseMove:_,onPaneMouseLeave:k,children:j}){const N=$.useRef(0),S=Be(),{userSelectionActive:P,elementsSelectable:E,dragging:C,panBy:R,autoPanSpeed:D}=Te(B_,qe),O=E&&(t||P),U=$.useRef(null),q=$.useRef(),te=$.useRef(new Set),X=$.useRef(new Set),ee=$.useRef(!1),J=$.useRef(!1),b=$.useRef({x:0,y:0}),Y=$.useRef(!1),H=K=>{if(J.current||ee.current||S.getState().connection.inProgress){J.current=!1,ee.current=!1;return}m==null||m(K),S.getState().resetSelectedElements(),S.setState({nodesSelectionActive:!1})},W=K=>{if(Array.isArray(l)&&(l!=null&&l.includes(2))){K.preventDefault();return}y==null||y(K)},A=x?K=>x(K):void 0,z=K=>{J.current&&(K.stopPropagation(),J.current=!1)},B=K=>{var Le,$e;if(K.pointerType==="touch"&&l!==!1&&!r)return;const{domNode:le,transform:he}=S.getState();if(q.current=le==null?void 0:le.getBoundingClientRect(),!q.current)return;const _e=K.target===U.current;if(!_e&&!!K.target.closest(".nokey")||!t||!(d&&_e||r)||K.button!==0||!K.isPrimary)return;($e=(Le=K.target)==null?void 0:Le.setPointerCapture)==null||$e.call(Le,K.pointerId),J.current=!1;const{x:Ne,y:Me}=rn(K.nativeEvent,q.current),be=Ao({x:Ne,y:Me},he);S.setState({userSelectionRect:{width:0,height:0,startX:be.x,startY:be.y,x:Ne,y:Me}}),_e||(K.stopPropagation(),K.preventDefault())};function M(K,le){const{userSelectionRect:he}=S.getState();if(!he)return;const{transform:_e,nodeLookup:ve,edgeLookup:ge,connectionLookup:Ne,triggerNodeChanges:Me,triggerEdgeChanges:be,defaultEdgeOptions:Le}=S.getState(),$e={x:he.startX,y:he.startY},{x:Ae,y:et}=ki($e,_e),Ye={startX:$e.x,startY:$e.y,x:Kft.id)),X.current=new Set;const _t=(Le==null?void 0:Le.selectable)??!0;for(const ft of te.current){const tt=Ne.get(ft);if(tt)for(const{edgeId:ut}of tt.values()){const vt=ge.get(ut);vt&&(vt.selectable??_t)&&X.current.add(ut)}}if(!xh(Mt,te.current)){const ft=mi(ve,te.current,!0);Me(ft)}if(!xh(yt,X.current)){const ft=mi(ge,X.current);be(ft)}S.setState({userSelectionRect:Ye,userSelectionActive:!0,nodesSelectionActive:!1})}function T(){if(!a||!q.current)return;const[K,le]=dc(b.current,q.current,D);R({x:K,y:le}).then(he=>{if(!J.current||!he){N.current=requestAnimationFrame(T);return}const{x:_e,y:ve}=b.current;M(_e,ve),N.current=requestAnimationFrame(T)})}const re=()=>{cancelAnimationFrame(N.current),N.current=0,Y.current=!1};$.useEffect(()=>()=>re(),[]);const ie=K=>{const{userSelectionRect:le,transform:he,resetSelectedElements:_e}=S.getState();if(!q.current||!le)return;const{x:ve,y:ge}=rn(K.nativeEvent,q.current);b.current={x:ve,y:ge};const Ne=ki({x:le.startX,y:le.startY},he);if(!J.current){const Me=r?0:u;if(Math.hypot(ve-Ne.x,ge-Ne.y)<=Me)return;_e(),f==null||f(K)}J.current=!0,Y.current||(T(),Y.current=!0),M(ve,ge)},ce=K=>{var le,he;if(!O){K.target===U.current&&S.getState().connection.inProgress&&(ee.current=!0);return}K.button===0&&((he=(le=K.target)==null?void 0:le.releasePointerCapture)==null||he.call(le,K.pointerId),!P&&K.target===U.current&&S.getState().userSelectionRect&&(H==null||H(K)),S.setState({userSelectionActive:!1,userSelectionRect:null}),J.current&&(g==null||g(K),S.setState({nodesSelectionActive:te.current.size>0})),re())},fe=K=>{var le,he;(he=(le=K.target)==null?void 0:le.releasePointerCapture)==null||he.call(le,K.pointerId),re()},de=l===!0||Array.isArray(l)&&l.includes(0);return p.jsxs("div",{className:rt(["react-flow__pane",{draggable:de,dragging:C,selection:t}]),onClick:O?void 0:Du(H,U),onContextMenu:Du(W,U),onWheel:Du(A,U),onPointerEnter:O?void 0:v,onPointerMove:O?ie:_,onPointerUp:ce,onPointerCancel:O?fe:void 0,onPointerDownCapture:O?B:void 0,onClickCapture:O?z:void 0,onPointerLeave:k,ref:U,style:Pl,children:[j,p.jsx(H_,{})]})}function Ju({id:t,store:r,unselect:o=!1,nodeRef:l}){const{addSelectedNodes:a,unselectNodesAndEdges:u,multiSelectionActive:d,nodeLookup:f,onError:g}=r.getState(),m=f.get(t);if(!m){g==null||g("012",on.error012(t));return}r.setState({nodesSelectionActive:!1}),m.selected?(o||m.selected&&d)&&(u({nodes:[m],edges:[]}),requestAnimationFrame(()=>{var y;return(y=l==null?void 0:l.current)==null?void 0:y.blur()})):a([t])}function Dg({nodeRef:t,disabled:r=!1,noDragClassName:o,handleSelector:l,nodeId:a,isSelectable:u,nodeClickDistance:d}){const f=Be(),[g,m]=$.useState(!1),y=$.useRef();return $.useEffect(()=>{if(!r)return y.current=b1({getStoreItems:()=>f.getState(),onNodeMouseDown:x=>{Ju({id:x,store:f,nodeRef:t})},onDragStart:()=>{m(!0)},onDragStop:()=>{m(!1)}}),()=>{var x;(x=y.current)==null||x.destroy(),y.current=void 0}},[r,f,t]),$.useEffect(()=>{r||!t.current||!y.current||y.current.update({noDragClassName:o,handleSelector:l,domNode:t.current,isSelectable:u,nodeId:a,nodeClickDistance:d})},[o,l,r,u,t,a,d]),g}const U_=t=>r=>r.selected&&(r.draggable||t&&typeof r.draggable>"u");function $g(){const t=Be();return $.useCallback(o=>{const{nodeExtent:l,snapToGrid:a,snapGrid:u,nodesDraggable:d,onError:f,updateNodePositions:g,nodeLookup:m,nodeOrigin:y}=t.getState(),x=new Map,v=U_(d),_=a?u[0]:5,k=a?u[1]:5,j=o.direction.x*_*o.factor,N=o.direction.y*k*o.factor;for(const[,S]of m){if(!v(S))continue;let P={x:S.internals.positionAbsolute.x+j,y:S.internals.positionAbsolute.y+N};a&&(P=Lo(P,u));const{position:E,positionAbsolute:C}=rg({nodeId:S.id,nextPosition:P,nodeLookup:m,nodeExtent:l,nodeOrigin:y,onError:f});S.position=E,S.internals.positionAbsolute=C,x.set(S.id,S)}g(x)},[])}const xc=$.createContext(null),W_=xc.Provider;xc.Consumer;const Og=()=>$.useContext(xc),Y_=t=>({connectOnClick:t.connectOnClick,noPanClassName:t.noPanClassName,rfId:t.rfId}),Fg=$.createContext(null);function X_({children:t}){const r=Te(Y_,qe);return p.jsx(Fg.Provider,{value:r,children:t})}function G_(){const t=$.useContext(Fg);if(!t)throw new Error("useHandleConfig must be used within a HandleConfigProvider");return t}const Q_={connectingFrom:!1,connectingTo:!1,clickConnecting:!1,isPossibleEndHandle:!0,connectionInProcess:!1,clickConnectionInProcess:!1,valid:!1},q_=(t,r,o)=>l=>{const{connectionClickStartHandle:a,connectionMode:u,connection:d}=l,{fromHandle:f,toHandle:g,isValid:m}=d;if(!f&&!a)return Q_;const y=(g==null?void 0:g.nodeId)===t&&(g==null?void 0:g.id)===r&&(g==null?void 0:g.type)===o;return{connectingFrom:(f==null?void 0:f.nodeId)===t&&(f==null?void 0:f.id)===r&&(f==null?void 0:f.type)===o,connectingTo:y,clickConnecting:(a==null?void 0:a.nodeId)===t&&(a==null?void 0:a.id)===r&&(a==null?void 0:a.type)===o,isPossibleEndHandle:u===_i.Strict?(f==null?void 0:f.type)!==o:t!==(f==null?void 0:f.nodeId)||r!==(f==null?void 0:f.id),connectionInProcess:!!f,clickConnectionInProcess:!!a,valid:y&&m}};function K_({type:t="source",position:r=Se.Top,isValidConnection:o,isConnectable:l=!0,isConnectableStart:a=!0,isConnectableEnd:u=!0,id:d,onConnect:f,children:g,className:m,onMouseDown:y,onTouchStart:x,...v},_){var Y,H;const k=d||null,j=t==="target",N=Be(),S=Og(),{connectOnClick:P,noPanClassName:E,rfId:C}=G_(),{connectingFrom:R,connectingTo:D,clickConnecting:O,isPossibleEndHandle:U,connectionInProcess:q,clickConnectionInProcess:te,valid:X}=Te(q_(S,k,t),qe);S||(H=(Y=N.getState()).onError)==null||H.call(Y,"010",on.error010());const ee=W=>{const{defaultEdgeOptions:A,onConnect:z,hasDefaultEdges:B}=N.getState(),M={...A,...W};if(B){const{edges:T,setEdges:re,onError:ie}=N.getState();re(M_(M,T,{onError:ie}))}z==null||z(M),f==null||f(M)},J=W=>{if(!S)return;const A=fg(W.nativeEvent);if(a&&(A&&W.button===0||!A)){const z=N.getState();Zu.onPointerDown(W.nativeEvent,{handleDomNode:W.currentTarget,autoPanOnConnect:z.autoPanOnConnect,connectionMode:z.connectionMode,connectionRadius:z.connectionRadius,domNode:z.domNode,nodeLookup:z.nodeLookup,lib:z.lib,isTarget:j,handleId:k,nodeId:S,flowId:z.rfId,panBy:z.panBy,cancelConnection:z.cancelConnection,onConnectStart:z.onConnectStart,onConnectEnd:(...B)=>{var M,T;return(T=(M=N.getState()).onConnectEnd)==null?void 0:T.call(M,...B)},updateConnection:z.updateConnection,onConnect:ee,isValidConnection:o||((...B)=>{var M,T;return((T=(M=N.getState()).isValidConnection)==null?void 0:T.call(M,...B))??!0}),getTransform:()=>N.getState().transform,getFromHandle:()=>N.getState().connection.fromHandle,autoPanSpeed:z.autoPanSpeed,dragThreshold:z.connectionDragThreshold})}A?y==null||y(W):x==null||x(W)},b=W=>{const{onClickConnectStart:A,onClickConnectEnd:z,connectionClickStartHandle:B,connectionMode:M,isValidConnection:T,lib:re,rfId:ie,nodeLookup:ce,connection:fe}=N.getState();if(!S||!B&&!a)return;if(!B){A==null||A(W.nativeEvent,{nodeId:S,handleId:k,handleType:t}),N.setState({connectionClickStartHandle:{nodeId:S,type:t,id:k}});return}const de=cg(W.target),K=o||T,{connection:le,isValid:he}=Zu.isValid(W.nativeEvent,{handle:{nodeId:S,id:k,type:t},connectionMode:M,fromNodeId:B.nodeId,fromHandleId:B.id||null,fromType:B.type,isValidConnection:K,flowId:ie,doc:de,lib:re,nodeLookup:ce});he&&le&&ee(le);const _e=structuredClone(fe);delete _e.inProgress,_e.toPosition=_e.toHandle?_e.toHandle.position:null,z==null||z(W,_e),N.setState({connectionClickStartHandle:null})};return p.jsx("div",{"data-handleid":k,"data-nodeid":S,"data-handlepos":r,"data-id":`${C}-${S}-${k}-${t}`,className:rt(["react-flow__handle",`react-flow__handle-${r}`,"nodrag",E,m,{source:!j,target:j,connectable:l,connectablestart:a,connectableend:u,clickconnecting:O,connectingfrom:R,connectingto:D,valid:X,connectionindicator:l&&(!q||U)&&(q||te?u:a)}]),onMouseDown:J,onTouchStart:J,onClick:P?b:void 0,ref:_,...v,children:g})}const Ni=$.memo(Lg(K_));function Z_({data:t,isConnectable:r,sourcePosition:o=Se.Bottom}){return p.jsxs(p.Fragment,{children:[t==null?void 0:t.label,p.jsx(Ni,{type:"source",position:o,isConnectable:r})]})}function J_({data:t,isConnectable:r,targetPosition:o=Se.Top,sourcePosition:l=Se.Bottom}){return p.jsxs(p.Fragment,{children:[p.jsx(Ni,{type:"target",position:o,isConnectable:r}),t==null?void 0:t.label,p.jsx(Ni,{type:"source",position:l,isConnectable:r})]})}function eS(){return null}function tS({data:t,isConnectable:r,targetPosition:o=Se.Top}){return p.jsxs(p.Fragment,{children:[p.jsx(Ni,{type:"target",position:o,isConnectable:r}),t==null?void 0:t.label]})}const ml={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}},qh={input:Z_,default:J_,output:tS,group:eS};function nS(t){var r,o,l,a;return t.internals.handleBounds===void 0?{width:t.width??t.initialWidth??((r=t.style)==null?void 0:r.width),height:t.height??t.initialHeight??((o=t.style)==null?void 0:o.height)}:{width:t.width??((l=t.style)==null?void 0:l.width),height:t.height??((a=t.style)==null?void 0:a.height)}}const rS=t=>{const{width:r,height:o,x:l,y:a}=Ro(t.nodeLookup,{filter:u=>!!u.selected});return{width:nn(r)?r:null,height:nn(o)?o:null,userSelectionActive:t.userSelectionActive,transformString:`translate(${t.transform[0]}px,${t.transform[1]}px) scale(${t.transform[2]}) translate(${l}px,${a}px)`}};function iS({onSelectionContextMenu:t,noPanClassName:r,disableKeyboardA11y:o}){const l=Be(),{width:a,height:u,transformString:d,userSelectionActive:f}=Te(rS,qe),g=$g(),m=$.useRef(null);$.useEffect(()=>{var _;o||(_=m.current)==null||_.focus({preventScroll:!0})},[o]);const y=!f&&a!==null&&u!==null;if(Dg({nodeRef:m,disabled:!y}),!y)return null;const x=t?_=>{const k=l.getState().nodes.filter(j=>j.selected);t(_,k)}:void 0,v=_=>{Object.prototype.hasOwnProperty.call(ml,_.key)&&(_.preventDefault(),g({direction:ml[_.key],factor:_.shiftKey?4:1}))};return p.jsx("div",{className:rt(["react-flow__nodesselection","react-flow__container",r]),style:{transform:d},children:p.jsx("div",{ref:m,className:"react-flow__nodesselection-rect",onContextMenu:x,tabIndex:o?void 0:-1,onKeyDown:o?void 0:v,style:{width:a,height:u}})})}const Kh=typeof window<"u"?window:void 0,oS=t=>({nodesSelectionActive:t.nodesSelectionActive,userSelectionActive:t.userSelectionActive});function Hg({children:t,onPaneClick:r,onPaneMouseEnter:o,onPaneMouseMove:l,onPaneMouseLeave:a,onPaneContextMenu:u,onPaneScroll:d,paneClickDistance:f,deleteKeyCode:g,selectionKeyCode:m,selectionOnDrag:y,selectionMode:x,onSelectionStart:v,onSelectionEnd:_,multiSelectionKeyCode:k,panActivationKeyCode:j,zoomActivationKeyCode:N,elementsSelectable:S,zoomOnScroll:P,zoomOnPinch:E,panOnScroll:C,panOnScrollSpeed:R,panOnScrollMode:D,zoomOnDoubleClick:O,panOnDrag:U,autoPanOnSelection:q,defaultViewport:te,translateExtent:X,minZoom:ee,maxZoom:J,preventScrolling:b,onSelectionContextMenu:Y,noWheelClassName:H,noPanClassName:W,disableKeyboardA11y:A,onViewportChange:z,isControlledViewport:B}){const{nodesSelectionActive:M,userSelectionActive:T}=Te(oS,qe),re=bo(m,{target:Kh}),ie=bo(j,{target:Kh}),ce=ie||U,fe=ie||C,de=y&&ce!==!0,K=re||T||de;return z_({deleteKeyCode:g,multiSelectionKeyCode:k}),p.jsx(O_,{onPaneContextMenu:u,elementsSelectable:S,zoomOnScroll:P,zoomOnPinch:E,panOnScroll:fe,panActivationKeyPressed:ie,panOnScrollSpeed:R,panOnScrollMode:D,zoomOnDoubleClick:O,panOnDrag:!re&&ce,defaultViewport:te,translateExtent:X,minZoom:ee,maxZoom:J,zoomActivationKeyCode:N,preventScrolling:b,noWheelClassName:H,noPanClassName:W,onViewportChange:z,isControlledViewport:B,paneClickDistance:f,selectionOnDrag:de,children:p.jsxs(V_,{onSelectionStart:v,onSelectionEnd:_,onPaneClick:r,onPaneMouseEnter:o,onPaneMouseMove:l,onPaneMouseLeave:a,onPaneContextMenu:u,onPaneScroll:d,panOnDrag:ce,autoPanOnSelection:q,isSelecting:!!K,selectionMode:x,selectionKeyPressed:re,paneClickDistance:f,selectionOnDrag:de,children:[t,M&&p.jsx(iS,{onSelectionContextMenu:Y,noPanClassName:W,disableKeyboardA11y:A})]})})}Hg.displayName="FlowRenderer";const sS=$.memo(Hg),lS=t=>r=>t?cc(r.nodeLookup,{x:0,y:0,width:r.width,height:r.height},r.transform,!0).map(o=>o.id):Array.from(r.nodeLookup.keys());function aS(t){return Te($.useCallback(lS(t),[t]),qe)}const uS=t=>t.updateNodeInternals;function cS(){const t=Te(uS),[r]=$.useState(()=>typeof ResizeObserver>"u"?null:new ResizeObserver(o=>{const l=new Map;o.forEach(a=>{const u=a.target.getAttribute("data-id");l.set(u,{id:u,nodeElement:a.target,force:!0})}),t(l)}));return $.useEffect(()=>()=>{r==null||r.disconnect()},[r]),r}function dS({node:t,nodeType:r,hasDimensions:o,resizeObserver:l}){const a=Be(),u=$.useRef(null),d=$.useRef(null),f=$.useRef(t.sourcePosition),g=$.useRef(t.targetPosition),m=$.useRef(r),y=o&&!!t.internals.handleBounds;return $.useEffect(()=>{u.current&&!t.hidden&&(!y||d.current!==u.current)&&(d.current&&(l==null||l.unobserve(d.current)),l==null||l.observe(u.current),d.current=u.current)},[y,t.hidden]),$.useEffect(()=>()=>{d.current&&(l==null||l.unobserve(d.current),d.current=null)},[]),$.useEffect(()=>{if(u.current){const x=m.current!==r,v=f.current!==t.sourcePosition,_=g.current!==t.targetPosition;(x||v||_)&&(m.current=r,f.current=t.sourcePosition,g.current=t.targetPosition,a.getState().updateNodeInternals(new Map([[t.id,{id:t.id,nodeElement:u.current,force:!0}]])))}},[t.id,r,t.sourcePosition,t.targetPosition]),u}function fS({id:t,onClick:r,onMouseEnter:o,onMouseMove:l,onMouseLeave:a,onContextMenu:u,onDoubleClick:d,nodesDraggable:f,elementsSelectable:g,nodesConnectable:m,nodesFocusable:y,resizeObserver:x,noDragClassName:v,noPanClassName:_,disableKeyboardA11y:k,rfId:j,nodeTypes:N,nodeClickDistance:S,onError:P}){const{node:E,internals:C,isParent:R}=Te(K=>{const le=K.nodeLookup.get(t),he=K.parentLookup.has(t);return{node:le,internals:le.internals,isParent:he}},qe);let D=E.type||"default",O=(N==null?void 0:N[D])||qh[D];O===void 0&&(P==null||P("003",on.error003(D)),D="default",O=(N==null?void 0:N.default)||qh.default);const U=!!(E.draggable||f&&typeof E.draggable>"u"),q=!!(E.selectable||g&&typeof E.selectable>"u"),te=!!(E.connectable||m&&typeof E.connectable>"u"),X=!!(E.focusable||y&&typeof E.focusable>"u"),ee=Be(),J=ag(E),b=dS({node:E,nodeType:D,hasDimensions:J,resizeObserver:x}),Y=Dg({nodeRef:b,disabled:E.hidden||!U,noDragClassName:v,handleSelector:E.dragHandle,nodeId:t,isSelectable:q,nodeClickDistance:S}),H=$g();if(E.hidden)return null;const W=ln(E),A=nS(E),z=q||U||r||o||l||a,B=o?K=>o(K,{...C.userNode}):void 0,M=l?K=>l(K,{...C.userNode}):void 0,T=a?K=>a(K,{...C.userNode}):void 0,re=u?K=>u(K,{...C.userNode}):void 0,ie=d?K=>d(K,{...C.userNode}):void 0,ce=K=>{const{selectNodesOnDrag:le,nodeDragThreshold:he}=ee.getState();q&&(!le||!U||he>0)&&Ju({id:t,store:ee,nodeRef:b}),r&&r(K,{...C.userNode})},fe=K=>{if(!(dg(K.nativeEvent)||k)){if(Zp.includes(K.key)&&q){const le=K.key==="Escape";Ju({id:t,store:ee,unselect:le,nodeRef:b})}else if(U&&E.selected&&Object.prototype.hasOwnProperty.call(ml,K.key)){K.preventDefault();const{ariaLabelConfig:le}=ee.getState();ee.setState({ariaLiveMessage:le["node.a11yDescription.ariaLiveMessage"]({direction:K.key.replace("Arrow","").toLowerCase(),x:~~C.positionAbsolute.x,y:~~C.positionAbsolute.y})}),H({direction:ml[K.key],factor:K.shiftKey?4:1})}}},de=()=>{var Ne;if(k||!((Ne=b.current)!=null&&Ne.matches(":focus-visible")))return;const{transform:K,width:le,height:he,autoPanOnNodeFocus:_e,setCenter:ve}=ee.getState();if(!_e)return;cc(new Map([[t,E]]),{x:0,y:0,width:le,height:he},K,!0).length>0||ve(E.position.x+W.width/2,E.position.y+W.height/2,{zoom:K[2]})};return p.jsx("div",{className:rt(["react-flow__node",`react-flow__node-${D}`,{[_]:U},E.className,{selected:E.selected,selectable:q,parent:R,draggable:U,dragging:Y}]),ref:b,style:{zIndex:C.z,transform:`translate(${C.positionAbsolute.x}px,${C.positionAbsolute.y}px)`,pointerEvents:z?"all":"none",visibility:J?"visible":"hidden",...E.style,...A},"data-id":t,"data-testid":`rf__node-${t}`,onMouseEnter:B,onMouseMove:M,onMouseLeave:T,onContextMenu:re,onClick:ce,onDoubleClick:ie,onKeyDown:X?fe:void 0,tabIndex:X?0:void 0,onFocus:X?de:void 0,role:E.ariaRole??(X?"group":void 0),"aria-roledescription":"node","aria-describedby":k?void 0:`${Pg}-${j}`,"aria-label":E.ariaLabel,...E.domAttributes,children:p.jsx(W_,{value:t,children:p.jsx(O,{id:t,data:E.data,type:D,positionAbsoluteX:C.positionAbsolute.x,positionAbsoluteY:C.positionAbsolute.y,selected:E.selected??!1,selectable:q,draggable:U,deletable:E.deletable??!0,isConnectable:te,sourcePosition:E.sourcePosition,targetPosition:E.targetPosition,dragging:Y,dragHandle:E.dragHandle,zIndex:C.z,parentId:E.parentId,...W})})})}var hS=$.memo(fS);const pS=t=>({nodesConnectable:t.nodesConnectable,nodesFocusable:t.nodesFocusable,elementsSelectable:t.elementsSelectable,onError:t.onError});function Bg(t){const{nodesConnectable:r,nodesFocusable:o,elementsSelectable:l,onError:a}=Te(pS,qe),u=aS(t.onlyRenderVisibleElements),d=cS();return p.jsx("div",{className:"react-flow__nodes",style:Pl,children:u.map(f=>p.jsx(hS,{id:f,nodeTypes:t.nodeTypes,nodeExtent:t.nodeExtent,onClick:t.onNodeClick,onMouseEnter:t.onNodeMouseEnter,onMouseMove:t.onNodeMouseMove,onMouseLeave:t.onNodeMouseLeave,onContextMenu:t.onNodeContextMenu,onDoubleClick:t.onNodeDoubleClick,noDragClassName:t.noDragClassName,noPanClassName:t.noPanClassName,rfId:t.rfId,disableKeyboardA11y:t.disableKeyboardA11y,resizeObserver:d,nodesDraggable:t.nodesDraggable??!0,nodesConnectable:r,nodesFocusable:o,elementsSelectable:l,nodeClickDistance:t.nodeClickDistance,onError:a},f))})}Bg.displayName="NodeRenderer";const gS=$.memo(Bg);function mS(t){return Te($.useCallback(o=>{if(!t)return o.edges.map(a=>a.id);const l=[];if(o.width&&o.height)for(const a of o.edges){const u=o.nodeLookup.get(a.source),d=o.nodeLookup.get(a.target);u&&d&&u1({sourceNode:u,targetNode:d,width:o.width,height:o.height,transform:o.transform})&&l.push(a.id)}return l},[t]),qe)}const yS=({color:t="none",strokeWidth:r=1})=>{const o={strokeWidth:r,...t&&{stroke:t}};return p.jsx("polyline",{className:"arrow",style:o,strokeLinecap:"round",fill:"none",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4"})},vS=({color:t="none",strokeWidth:r=1})=>{const o={strokeWidth:r,...t&&{stroke:t,fill:t}};return p.jsx("polyline",{className:"arrowclosed",style:o,strokeLinecap:"round",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4 -5,-4"})},Zh={[No.Arrow]:yS,[No.ArrowClosed]:vS};function xS(t){const r=Be();return $.useMemo(()=>{var a,u;return Object.prototype.hasOwnProperty.call(Zh,t)?Zh[t]:((u=(a=r.getState()).onError)==null||u.call(a,"009",on.error009(t)),null)},[t])}const wS=({id:t,type:r,color:o,width:l=12.5,height:a=12.5,markerUnits:u="strokeWidth",strokeWidth:d,orient:f="auto-start-reverse"})=>{const g=xS(r);return g?p.jsx("marker",{className:"react-flow__arrowhead",id:t,markerWidth:`${l}`,markerHeight:`${a}`,viewBox:"-10 -10 20 20",markerUnits:u,orient:f,refX:"0",refY:"0",children:p.jsx(g,{color:o,strokeWidth:d})}):null},Vg=({defaultColor:t,rfId:r})=>{const o=Te(u=>u.edges),l=Te(u=>u.defaultEdgeOptions),a=$.useMemo(()=>y1(o,{id:r,defaultColor:t,defaultMarkerStart:l==null?void 0:l.markerStart,defaultMarkerEnd:l==null?void 0:l.markerEnd}),[o,l,r,t]);return a.length?p.jsx("svg",{className:"react-flow__marker","aria-hidden":"true",children:p.jsx("defs",{children:a.map(u=>p.jsx(wS,{id:u.id,type:u.type,color:u.color,width:u.width,height:u.height,markerUnits:u.markerUnits,strokeWidth:u.strokeWidth,orient:u.orient},u.id))})}):null};Vg.displayName="MarkerDefinitions";var _S=$.memo(Vg);function Ug({x:t,y:r,label:o,labelStyle:l,labelShowBg:a=!0,labelBgStyle:u,labelBgPadding:d=[2,4],labelBgBorderRadius:f=2,children:g,className:m,...y}){const[x,v]=$.useState({x:1,y:0,width:0,height:0}),_=rt(["react-flow__edge-textwrapper",m]),k=$.useRef(null);return $.useEffect(()=>{if(k.current){const j=k.current.getBBox();v({x:j.x,y:j.y,width:j.width,height:j.height})}},[o]),o?p.jsxs("g",{transform:`translate(${t-x.width/2} ${r-x.height/2})`,className:_,visibility:x.width?"visible":"hidden",...y,children:[a&&p.jsx("rect",{width:x.width+2*d[0],x:-d[0],y:-d[1],height:x.height+2*d[1],className:"react-flow__edge-textbg",style:u,rx:f,ry:f}),p.jsx("text",{className:"react-flow__edge-text",y:x.height/2,dy:"0.3em",ref:k,style:l,children:o}),g]}):null}Ug.displayName="EdgeText";const SS=$.memo(Ug);function Il({path:t,labelX:r,labelY:o,label:l,labelStyle:a,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:g,interactionWidth:m=20,...y}){return p.jsxs(p.Fragment,{children:[p.jsx("path",{...y,d:t,fill:"none",className:rt(["react-flow__edge-path",y.className])}),m?p.jsx("path",{d:t,fill:"none",strokeOpacity:0,strokeWidth:m,className:"react-flow__edge-interaction"}):null,l&&nn(r)&&nn(o)?p.jsx(SS,{x:r,y:o,label:l,labelStyle:a,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:g}):null]})}function Jh({pos:t,x1:r,y1:o,x2:l,y2:a}){return t===Se.Left||t===Se.Right?[.5*(r+l),o]:[r,.5*(o+a)]}function Wg({sourceX:t,sourceY:r,sourcePosition:o=Se.Bottom,targetX:l,targetY:a,targetPosition:u=Se.Top}){const[d,f]=Jh({pos:o,x1:t,y1:r,x2:l,y2:a}),[g,m]=Jh({pos:u,x1:l,y1:a,x2:t,y2:r}),[y,x,v,_]=hg({sourceX:t,sourceY:r,targetX:l,targetY:a,sourceControlX:d,sourceControlY:f,targetControlX:g,targetControlY:m});return[`M${t},${r} C${d},${f} ${g},${m} ${l},${a}`,y,x,v,_]}function Yg(t){return $.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,sourcePosition:d,targetPosition:f,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:_,style:k,markerEnd:j,markerStart:N,interactionWidth:S})=>{const[P,E,C]=Wg({sourceX:o,sourceY:l,sourcePosition:d,targetX:a,targetY:u,targetPosition:f}),R=t.isInternal?void 0:r;return p.jsx(Il,{id:R,path:P,labelX:E,labelY:C,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:_,style:k,markerEnd:j,markerStart:N,interactionWidth:S})})}const kS=Yg({isInternal:!1}),Xg=Yg({isInternal:!0});kS.displayName="SimpleBezierEdge";Xg.displayName="SimpleBezierEdgeInternal";function Gg(t){return $.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,sourcePosition:_=Se.Bottom,targetPosition:k=Se.Top,markerEnd:j,markerStart:N,pathOptions:S,interactionWidth:P})=>{const[E,C,R]=Qu({sourceX:o,sourceY:l,sourcePosition:_,targetX:a,targetY:u,targetPosition:k,borderRadius:S==null?void 0:S.borderRadius,offset:S==null?void 0:S.offset,stepPosition:S==null?void 0:S.stepPosition}),D=t.isInternal?void 0:r;return p.jsx(Il,{id:D,path:E,labelX:C,labelY:R,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,markerEnd:j,markerStart:N,interactionWidth:P})})}const Qg=Gg({isInternal:!1}),qg=Gg({isInternal:!0});Qg.displayName="SmoothStepEdge";qg.displayName="SmoothStepEdgeInternal";function Kg(t){return $.memo(({id:r,...o})=>{var a;const l=t.isInternal?void 0:r;return p.jsx(Qg,{...o,id:l,pathOptions:$.useMemo(()=>{var u;return{borderRadius:0,offset:(u=o.pathOptions)==null?void 0:u.offset}},[(a=o.pathOptions)==null?void 0:a.offset])})})}const ES=Kg({isInternal:!1}),Zg=Kg({isInternal:!0});ES.displayName="StepEdge";Zg.displayName="StepEdgeInternal";function Jg(t){return $.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,markerEnd:_,markerStart:k,interactionWidth:j})=>{const[N,S,P]=mg({sourceX:o,sourceY:l,targetX:a,targetY:u}),E=t.isInternal?void 0:r;return p.jsx(Il,{id:E,path:N,labelX:S,labelY:P,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,markerEnd:_,markerStart:k,interactionWidth:j})})}const NS=Jg({isInternal:!1}),em=Jg({isInternal:!0});NS.displayName="StraightEdge";em.displayName="StraightEdgeInternal";function tm(t){return $.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,sourcePosition:d=Se.Bottom,targetPosition:f=Se.Top,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:_,style:k,markerEnd:j,markerStart:N,pathOptions:S,interactionWidth:P})=>{const[E,C,R]=pg({sourceX:o,sourceY:l,sourcePosition:d,targetX:a,targetY:u,targetPosition:f,curvature:S==null?void 0:S.curvature}),D=t.isInternal?void 0:r;return p.jsx(Il,{id:D,path:E,labelX:C,labelY:R,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:_,style:k,markerEnd:j,markerStart:N,interactionWidth:P})})}const CS=tm({isInternal:!1}),nm=tm({isInternal:!0});CS.displayName="BezierEdge";nm.displayName="BezierEdgeInternal";const ep={default:nm,straight:em,step:Zg,smoothstep:qg,simplebezier:Xg},tp={sourceX:null,sourceY:null,targetX:null,targetY:null,sourcePosition:null,targetPosition:null,zIndex:void 0},jS=(t,r,o)=>o===Se.Left?t-r:o===Se.Right?t+r:t,bS=(t,r,o)=>o===Se.Top?t-r:o===Se.Bottom?t+r:t,np="react-flow__edgeupdater";function rp({position:t,centerX:r,centerY:o,radius:l=10,onMouseDown:a,onMouseEnter:u,onMouseOut:d,type:f}){return p.jsx("circle",{onMouseDown:a,onMouseEnter:u,onMouseOut:d,className:rt([np,`${np}-${f}`]),cx:jS(r,l,t),cy:bS(o,l,t),r:l,stroke:"transparent",fill:"transparent"})}function MS({isReconnectable:t,reconnectRadius:r,edge:o,sourceX:l,sourceY:a,targetX:u,targetY:d,sourcePosition:f,targetPosition:g,onReconnect:m,onReconnectStart:y,onReconnectEnd:x,setReconnecting:v,setUpdateHover:_}){const k=Be(),j=(C,R)=>{if(C.button!==0)return;const{autoPanOnConnect:D,domNode:O,connectionMode:U,connectionRadius:q,lib:te,onConnectStart:X,cancelConnection:ee,nodeLookup:J,rfId:b,panBy:Y,updateConnection:H}=k.getState(),W=R.type==="target",A=(M,T)=>{v(!1),x==null||x(M,o,R.type,T)},z=M=>m==null?void 0:m(o,M),B=(M,T)=>{v(!0),y==null||y(C,o,R.type),X==null||X(M,T)};Zu.onPointerDown(C.nativeEvent,{autoPanOnConnect:D,connectionMode:U,connectionRadius:q,domNode:O,handleId:R.id,nodeId:R.nodeId,nodeLookup:J,isTarget:W,edgeUpdaterType:R.type,lib:te,flowId:b,cancelConnection:ee,panBy:Y,isValidConnection:(...M)=>{var T,re;return((re=(T=k.getState()).isValidConnection)==null?void 0:re.call(T,...M))??!0},onConnect:z,onConnectStart:B,onConnectEnd:(...M)=>{var T,re;return(re=(T=k.getState()).onConnectEnd)==null?void 0:re.call(T,...M)},onReconnectEnd:A,updateConnection:H,getTransform:()=>k.getState().transform,getFromHandle:()=>k.getState().connection.fromHandle,dragThreshold:k.getState().connectionDragThreshold,handleDomNode:C.currentTarget})},N=C=>j(C,{nodeId:o.target,id:o.targetHandle??null,type:"target"}),S=C=>j(C,{nodeId:o.source,id:o.sourceHandle??null,type:"source"}),P=()=>_(!0),E=()=>_(!1);return p.jsxs(p.Fragment,{children:[(t===!0||t==="source")&&p.jsx(rp,{position:f,centerX:l,centerY:a,radius:r,onMouseDown:N,onMouseEnter:P,onMouseOut:E,type:"source"}),(t===!0||t==="target")&&p.jsx(rp,{position:g,centerX:u,centerY:d,radius:r,onMouseDown:S,onMouseEnter:P,onMouseOut:E,type:"target"})]})}function PS({id:t,edgesFocusable:r,edgesReconnectable:o,elementsSelectable:l,onClick:a,onDoubleClick:u,onContextMenu:d,onMouseEnter:f,onMouseMove:g,onMouseLeave:m,reconnectRadius:y,onReconnect:x,onReconnectStart:v,onReconnectEnd:_,rfId:k,edgeTypes:j,noPanClassName:N,onError:S,disableKeyboardA11y:P}){let E=Te(ve=>ve.edgeLookup.get(t));const C=Te(ve=>ve.defaultEdgeOptions);E=C?{...C,...E}:E;let R=E.type||"default",D=(j==null?void 0:j[R])||ep[R];D===void 0&&(S==null||S("011",on.error011(R)),R="default",D=(j==null?void 0:j.default)||ep.default);const O=!!(E.focusable||r&&typeof E.focusable>"u"),U=typeof x<"u"&&(E.reconnectable||o&&typeof E.reconnectable>"u"),q=!!(E.selectable||l&&typeof E.selectable>"u"),te=$.useRef(null),[X,ee]=$.useState(!1),[J,b]=$.useState(!1),Y=Be(),{zIndex:H=E.zIndex,sourceX:W,sourceY:A,targetX:z,targetY:B,sourcePosition:M,targetPosition:T}=Te($.useCallback(ve=>{const ge=ve.nodeLookup.get(E.source),Ne=ve.nodeLookup.get(E.target);if(!ge||!Ne)return tp;const Me=m1({id:t,sourceNode:ge,targetNode:Ne,sourceHandle:E.sourceHandle||null,targetHandle:E.targetHandle||null,connectionMode:ve.connectionMode,onError:S}),be=a1({selected:E.selected,zIndex:E.zIndex,sourceNode:ge,targetNode:Ne,elevateOnSelect:ve.elevateEdgesOnSelect,zIndexMode:ve.zIndexMode});return{...Me||tp,zIndex:be}},[E.source,E.target,E.sourceHandle,E.targetHandle,E.selected,E.zIndex,S]),qe),re=$.useMemo(()=>E.markerStart?`url('#${qu(E.markerStart,k)}')`:void 0,[E.markerStart,k]),ie=$.useMemo(()=>E.markerEnd?`url('#${qu(E.markerEnd,k)}')`:void 0,[E.markerEnd,k]);if(E.hidden||W===null||A===null||z===null||B===null)return null;const ce=ve=>{var be;const{addSelectedEdges:ge,unselectNodesAndEdges:Ne,multiSelectionActive:Me}=Y.getState();q&&(Y.setState({nodesSelectionActive:!1}),E.selected&&Me?(Ne({nodes:[],edges:[E]}),(be=te.current)==null||be.blur()):ge([t])),a&&a(ve,E)},fe=u?ve=>{u(ve,{...E})}:void 0,de=d?ve=>{d(ve,{...E})}:void 0,K=f?ve=>{f(ve,{...E})}:void 0,le=g?ve=>{g(ve,{...E})}:void 0,he=m?ve=>{m(ve,{...E})}:void 0,_e=ve=>{var ge;if(!P&&Zp.includes(ve.key)&&q){const{unselectNodesAndEdges:Ne,addSelectedEdges:Me}=Y.getState();ve.key==="Escape"?((ge=te.current)==null||ge.blur(),Ne({edges:[E]})):Me([t])}};return p.jsx("svg",{style:{zIndex:H},children:p.jsxs("g",{className:rt(["react-flow__edge",`react-flow__edge-${R}`,E.className,N,{selected:E.selected,animated:E.animated,inactive:!q&&!a,updating:X,selectable:q}]),onClick:ce,onDoubleClick:fe,onContextMenu:de,onMouseEnter:K,onMouseMove:le,onMouseLeave:he,onKeyDown:O?_e:void 0,tabIndex:O?0:void 0,role:E.ariaRole??(O?"group":"img"),"aria-roledescription":"edge","data-id":t,"data-testid":`rf__edge-${t}`,"aria-label":E.ariaLabel===null?void 0:E.ariaLabel||`Edge from ${E.source} to ${E.target}`,"aria-describedby":O?`${Ig}-${k}`:void 0,ref:te,...E.domAttributes,children:[!J&&p.jsx(D,{id:t,source:E.source,target:E.target,type:E.type,selected:E.selected,animated:E.animated,selectable:q,deletable:E.deletable??!0,label:E.label,labelStyle:E.labelStyle,labelShowBg:E.labelShowBg,labelBgStyle:E.labelBgStyle,labelBgPadding:E.labelBgPadding,labelBgBorderRadius:E.labelBgBorderRadius,sourceX:W,sourceY:A,targetX:z,targetY:B,sourcePosition:M,targetPosition:T,data:E.data,style:E.style,sourceHandleId:E.sourceHandle,targetHandleId:E.targetHandle,markerStart:re,markerEnd:ie,pathOptions:"pathOptions"in E?E.pathOptions:void 0,interactionWidth:E.interactionWidth}),U&&p.jsx(MS,{edge:E,isReconnectable:U,reconnectRadius:y,onReconnect:x,onReconnectStart:v,onReconnectEnd:_,sourceX:W,sourceY:A,targetX:z,targetY:B,sourcePosition:M,targetPosition:T,setUpdateHover:ee,setReconnecting:b})]})})}var IS=$.memo(PS);const TS=t=>({edgesFocusable:t.edgesFocusable,edgesReconnectable:t.edgesReconnectable,elementsSelectable:t.elementsSelectable,connectionMode:t.connectionMode,onError:t.onError});function rm({defaultMarkerColor:t,onlyRenderVisibleElements:r,rfId:o,edgeTypes:l,noPanClassName:a,onReconnect:u,onEdgeContextMenu:d,onEdgeMouseEnter:f,onEdgeMouseMove:g,onEdgeMouseLeave:m,onEdgeClick:y,reconnectRadius:x,onEdgeDoubleClick:v,onReconnectStart:_,onReconnectEnd:k,disableKeyboardA11y:j}){const{edgesFocusable:N,edgesReconnectable:S,elementsSelectable:P,onError:E}=Te(TS,qe),C=mS(r);return p.jsxs("div",{className:"react-flow__edges",children:[p.jsx(_S,{defaultColor:t,rfId:o}),C.map(R=>p.jsx(IS,{id:R,edgesFocusable:N,edgesReconnectable:S,elementsSelectable:P,noPanClassName:a,onReconnect:u,onContextMenu:d,onMouseEnter:f,onMouseMove:g,onMouseLeave:m,onClick:y,reconnectRadius:x,onDoubleClick:v,onReconnectStart:_,onReconnectEnd:k,rfId:o,onError:E,edgeTypes:l,disableKeyboardA11y:j},R))]})}rm.displayName="EdgeRenderer";const RS=$.memo(rm),ip=t=>`translate(${t[0]}px,${t[1]}px) scale(${t[2]})`;function LS({children:t}){const r=Be(),o=$.useRef(null),[l]=$.useState(()=>r.getState().transform);return Ag(()=>{let a=null;const u=()=>{const d=r.getState().transform;a&&d[0]===a[0]&&d[1]===a[1]&&d[2]===a[2]||(a=d,o.current&&(o.current.style.transform=ip(d)))};return u(),r.subscribe(u)},[r]),p.jsx("div",{ref:o,className:"react-flow__viewport xyflow__viewport react-flow__container",style:{transform:ip(l)},children:t})}function AS(t){const r=Ml(),o=$.useRef(!1);$.useEffect(()=>{!o.current&&r.viewportInitialized&&t&&(setTimeout(()=>t(r),1),o.current=!0)},[t,r.viewportInitialized])}const zS=t=>{var r;return(r=t.panZoom)==null?void 0:r.syncViewport};function DS(t){const r=Te(zS),o=Be();return $.useEffect(()=>{t&&(r==null||r(t),o.setState({transform:[t.x,t.y,t.zoom]}))},[t,r]),null}function $S(t){return t.connection.inProgress?{...t.connection,to:Ao(t.connection.to,t.transform)}:{...t.connection}}function OS(t){return $S}function FS(t){const r=OS();return Te(r,qe)}const HS=t=>({nodesConnectable:t.nodesConnectable,isValid:t.connection.isValid,inProgress:t.connection.inProgress,width:t.width,height:t.height});function BS({containerStyle:t,style:r,type:o,component:l}){const{nodesConnectable:a,width:u,height:d,isValid:f,inProgress:g}=Te(HS,qe);return!(u&&a&&g)?null:p.jsx("svg",{style:t,width:u,height:d,className:"react-flow__connectionline react-flow__container",children:p.jsx("g",{className:rt(["react-flow__connection",tg(f)]),children:p.jsx(im,{style:r,type:o,CustomComponent:l,isValid:f})})})}const im=({style:t,type:r=rr.Bezier,CustomComponent:o,isValid:l})=>{const{inProgress:a,from:u,fromNode:d,fromHandle:f,fromPosition:g,to:m,toNode:y,toHandle:x,toPosition:v,pointer:_}=FS();if(!a)return;if(o)return p.jsx(o,{connectionLineType:r,connectionLineStyle:t,fromNode:d,fromHandle:f,fromX:u.x,fromY:u.y,toX:m.x,toY:m.y,fromPosition:g,toPosition:v,connectionStatus:tg(l),toNode:y,toHandle:x,pointer:_});let k="";const j={sourceX:u.x,sourceY:u.y,sourcePosition:g,targetX:m.x,targetY:m.y,targetPosition:v};switch(r){case rr.Bezier:[k]=pg(j);break;case rr.SimpleBezier:[k]=Wg(j);break;case rr.Step:[k]=Qu({...j,borderRadius:0});break;case rr.SmoothStep:[k]=Qu(j);break;default:[k]=mg(j)}return p.jsx("path",{d:k,fill:"none",className:"react-flow__connection-path",style:t})};im.displayName="ConnectionLine";const VS={};function op(t=VS){$.useRef(t),Be(),$.useEffect(()=>{},[t])}function US(){Be(),$.useRef(!1),$.useEffect(()=>{},[])}function om({nodeTypes:t,edgeTypes:r,onInit:o,onNodeClick:l,onEdgeClick:a,onNodeDoubleClick:u,onEdgeDoubleClick:d,onNodeMouseEnter:f,onNodeMouseMove:g,onNodeMouseLeave:m,onNodeContextMenu:y,onSelectionContextMenu:x,onSelectionStart:v,onSelectionEnd:_,connectionLineType:k,connectionLineStyle:j,connectionLineComponent:N,connectionLineContainerStyle:S,selectionKeyCode:P,selectionOnDrag:E,selectionMode:C,multiSelectionKeyCode:R,panActivationKeyCode:D,zoomActivationKeyCode:O,deleteKeyCode:U,onlyRenderVisibleElements:q,elementsSelectable:te,defaultViewport:X,translateExtent:ee,minZoom:J,maxZoom:b,preventScrolling:Y,defaultMarkerColor:H,zoomOnScroll:W,zoomOnPinch:A,panOnScroll:z,panOnScrollSpeed:B,panOnScrollMode:M,zoomOnDoubleClick:T,panOnDrag:re,autoPanOnSelection:ie,onPaneClick:ce,onPaneMouseEnter:fe,onPaneMouseMove:de,onPaneMouseLeave:K,onPaneScroll:le,onPaneContextMenu:he,paneClickDistance:_e,nodeClickDistance:ve,onEdgeContextMenu:ge,onEdgeMouseEnter:Ne,onEdgeMouseMove:Me,onEdgeMouseLeave:be,reconnectRadius:Le,onReconnect:$e,onReconnectStart:Ae,onReconnectEnd:et,noDragClassName:Ye,noWheelClassName:Mt,noPanClassName:yt,disableKeyboardA11y:_t,nodeExtent:ft,rfId:tt,viewport:ut,onViewportChange:vt,nodesDraggable:Yt}){return op(t),op(r),US(),AS(o),DS(ut),p.jsx(sS,{onPaneClick:ce,onPaneMouseEnter:fe,onPaneMouseMove:de,onPaneMouseLeave:K,onPaneContextMenu:he,onPaneScroll:le,paneClickDistance:_e,deleteKeyCode:U,selectionKeyCode:P,selectionOnDrag:E,selectionMode:C,onSelectionStart:v,onSelectionEnd:_,multiSelectionKeyCode:R,panActivationKeyCode:D,zoomActivationKeyCode:O,elementsSelectable:te,zoomOnScroll:W,zoomOnPinch:A,zoomOnDoubleClick:T,panOnScroll:z,panOnScrollSpeed:B,panOnScrollMode:M,panOnDrag:re,autoPanOnSelection:ie,defaultViewport:X,translateExtent:ee,minZoom:J,maxZoom:b,onSelectionContextMenu:x,preventScrolling:Y,noDragClassName:Ye,noWheelClassName:Mt,noPanClassName:yt,disableKeyboardA11y:_t,onViewportChange:vt,isControlledViewport:!!ut,children:p.jsxs(LS,{children:[p.jsx(RS,{edgeTypes:r,onEdgeClick:a,onEdgeDoubleClick:d,onReconnect:$e,onReconnectStart:Ae,onReconnectEnd:et,onlyRenderVisibleElements:q,onEdgeContextMenu:ge,onEdgeMouseEnter:Ne,onEdgeMouseMove:Me,onEdgeMouseLeave:be,reconnectRadius:Le,defaultMarkerColor:H,noPanClassName:yt,disableKeyboardA11y:_t,rfId:tt}),p.jsx(BS,{style:j,type:k,component:N,containerStyle:S}),p.jsx("div",{className:"react-flow__edgelabel-renderer"}),p.jsx(gS,{nodeTypes:t,onNodeClick:l,onNodeDoubleClick:u,onNodeMouseEnter:f,onNodeMouseMove:g,onNodeMouseLeave:m,onNodeContextMenu:y,nodeClickDistance:ve,onlyRenderVisibleElements:q,noPanClassName:yt,noDragClassName:Ye,disableKeyboardA11y:_t,nodeExtent:ft,rfId:tt,nodesDraggable:Yt}),p.jsx("div",{className:"react-flow__viewport-portal"})]})})}om.displayName="GraphView";const WS=$.memo(om),YS=lg(),sp=({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,width:a,height:u,fitView:d,fitViewOptions:f,minZoom:g=.5,maxZoom:m=2,nodeOrigin:y,nodeExtent:x,zIndexMode:v="basic"}={})=>{const _=new Map,k=new Map,j=new Map,N=new Map,S=l??r??[],P=o??t??[],E=y??[0,0],C=x??ko;xg(j,N,S);const{nodesInitialized:R}=Ku(P,_,k,{nodeOrigin:E,nodeExtent:C,zIndexMode:v});let D=[0,0,1];if(d&&a&&u){const O=Ro(_,{filter:X=>!!((X.width||X.initialWidth)&&(X.height||X.initialHeight))}),{x:U,y:q,zoom:te}=fc(O,a,u,g,m,(f==null?void 0:f.padding)??.1);D=[U,q,te]}return{rfId:"1",width:a??0,height:u??0,transform:D,nodes:P,nodesInitialized:R,nodeLookup:_,parentLookup:k,edges:S,edgeLookup:N,connectionLookup:j,onNodesChange:null,onEdgesChange:null,hasDefaultNodes:o!==void 0,hasDefaultEdges:l!==void 0,panZoom:null,minZoom:g,maxZoom:m,translateExtent:ko,nodeExtent:C,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionMode:_i.Strict,domNode:null,paneDragging:!1,noPanClassName:"nopan",nodeOrigin:E,nodeDragThreshold:1,connectionDragThreshold:1,snapGrid:[15,15],snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesReconnectable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,elevateEdgesOnSelect:!0,selectNodesOnDrag:!0,multiSelectionActive:!1,fitViewQueued:d??!1,fitViewOptions:f,fitViewResolver:null,connection:{...eg},connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:"",autoPanOnConnect:!0,autoPanOnNodeDrag:!0,autoPanOnNodeFocus:!0,autoPanSpeed:15,connectionRadius:20,onError:YS,isValidConnection:void 0,onSelectionChangeHandlers:[],lib:"react",debug:!1,ariaLabelConfig:Jp,zIndexMode:v,onNodesChangeMiddlewareMap:new Map,onEdgesChangeMiddlewareMap:new Map}},XS=({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,width:a,height:u,fitView:d,fitViewOptions:f,minZoom:g,maxZoom:m,nodeOrigin:y,nodeExtent:x,zIndexMode:v})=>o_((_,k)=>{async function j(){const{nodeLookup:N,panZoom:S,fitViewOptions:P,fitViewResolver:E,width:C,height:R,minZoom:D,maxZoom:O}=k();S&&(await t1({nodes:N,width:C,height:R,panZoom:S,minZoom:D,maxZoom:O},P),E==null||E.resolve(!0),_({fitViewResolver:null}))}return{...sp({nodes:t,edges:r,width:a,height:u,fitView:d,fitViewOptions:f,minZoom:g,maxZoom:m,nodeOrigin:y,nodeExtent:x,defaultNodes:o,defaultEdges:l,zIndexMode:v}),setNodes:N=>{const{nodeLookup:S,parentLookup:P,nodeOrigin:E,nodeExtent:C,elevateNodesOnSelect:R,fitViewQueued:D,zIndexMode:O,nodesSelectionActive:U}=k(),{nodesInitialized:q,hasSelectedNodes:te}=Ku(N,S,P,{nodeOrigin:E,nodeExtent:C,elevateNodesOnSelect:R,checkEquality:!0,zIndexMode:O}),X=U&&te;D&&q?(j(),_({nodes:N,nodesInitialized:q,fitViewQueued:!1,fitViewOptions:void 0,nodesSelectionActive:X})):_({nodes:N,nodesInitialized:q,nodesSelectionActive:X})},setEdges:N=>{const{connectionLookup:S,edgeLookup:P}=k();xg(S,P,N),_({edges:N})},setDefaultNodesAndEdges:(N,S)=>{if(N){const{setNodes:P}=k();P(N),_({hasDefaultNodes:!0})}if(S){const{setEdges:P}=k();P(S),_({hasDefaultEdges:!0})}},updateNodeInternals:N=>{const{triggerNodeChanges:S,nodeLookup:P,parentLookup:E,domNode:C,nodeOrigin:R,nodeExtent:D,debug:O,fitViewQueued:U,zIndexMode:q}=k(),{changes:te,updatedInternals:X}=E1(N,P,E,C,R,D,q);X&&(w1(P,E,{nodeOrigin:R,nodeExtent:D,zIndexMode:q}),U?(j(),_({fitViewQueued:!1,fitViewOptions:void 0})):_({}),(te==null?void 0:te.length)>0&&(O&&console.log("React Flow: trigger node changes",te),S==null||S(te)))},updateNodePositions:(N,S=!1)=>{const P=[];let E=[];const{nodeLookup:C,triggerNodeChanges:R,connection:D,updateConnection:O,onNodesChangeMiddlewareMap:U}=k();for(const[q,te]of N){const X=C.get(q),ee=!!(X!=null&&X.expandParent&&(X!=null&&X.parentId)&&(te!=null&&te.position)),J={id:q,type:"position",position:ee?{x:Math.max(0,te.position.x),y:Math.max(0,te.position.y)}:te.position,dragging:S};if(X&&D.inProgress&&D.fromNode.id===X.id){const b=Ar(X,D.fromHandle,Se.Left,!0);O({...D,from:b})}ee&&X.parentId&&P.push({id:q,parentId:X.parentId,rect:{...te.internals.positionAbsolute,width:te.measured.width??0,height:te.measured.height??0}}),E.push(J)}if(P.length>0){const{parentLookup:q,nodeOrigin:te}=k(),X=vc(P,C,q,te);E.push(...X)}for(const q of U.values())E=q(E);R(E)},triggerNodeChanges:N=>{const{onNodesChange:S,setNodes:P,nodes:E,hasDefaultNodes:C,debug:R}=k();if(N!=null&&N.length){if(C){const D=C_(N,E);P(D)}R&&console.log("React Flow: trigger node changes",N),S==null||S(N)}},triggerEdgeChanges:N=>{const{onEdgesChange:S,setEdges:P,edges:E,hasDefaultEdges:C,debug:R}=k();if(N!=null&&N.length){if(C){const D=j_(N,E);P(D)}R&&console.log("React Flow: trigger edge changes",N),S==null||S(N)}},addSelectedNodes:N=>{const{multiSelectionActive:S,edgeLookup:P,nodeLookup:E,triggerNodeChanges:C,triggerEdgeChanges:R}=k();if(S){const D=N.map(O=>Cr(O,!0));C(D);return}C(mi(E,new Set([...N]),!0)),R(mi(P))},addSelectedEdges:N=>{const{multiSelectionActive:S,edgeLookup:P,nodeLookup:E,triggerNodeChanges:C,triggerEdgeChanges:R}=k();if(S){const D=N.map(O=>Cr(O,!0));R(D);return}R(mi(P,new Set([...N]))),C(mi(E,new Set,!0))},unselectNodesAndEdges:({nodes:N,edges:S}={})=>{const{edges:P,nodes:E,nodeLookup:C,triggerNodeChanges:R,triggerEdgeChanges:D}=k(),O=N||E,U=S||P,q=[];for(const X of O){if(!X.selected)continue;const ee=C.get(X.id);ee&&(ee.selected=!1),q.push(Cr(X.id,!1))}const te=[];for(const X of U)X.selected&&te.push(Cr(X.id,!1));R(q),D(te)},setMinZoom:N=>{const{panZoom:S,maxZoom:P}=k();S==null||S.setScaleExtent([N,P]),_({minZoom:N})},setMaxZoom:N=>{const{panZoom:S,minZoom:P}=k();S==null||S.setScaleExtent([P,N]),_({maxZoom:N})},setTranslateExtent:N=>{var S;(S=k().panZoom)==null||S.setTranslateExtent(N),_({translateExtent:N})},resetSelectedElements:()=>{const{edges:N,nodes:S,triggerNodeChanges:P,triggerEdgeChanges:E,elementsSelectable:C}=k();if(!C)return;const R=S.reduce((O,U)=>U.selected?[...O,Cr(U.id,!1)]:O,[]),D=N.reduce((O,U)=>U.selected?[...O,Cr(U.id,!1)]:O,[]);P(R),E(D)},setNodeExtent:N=>{const{nodes:S,nodeLookup:P,parentLookup:E,nodeOrigin:C,elevateNodesOnSelect:R,nodeExtent:D,zIndexMode:O}=k();N[0][0]===D[0][0]&&N[0][1]===D[0][1]&&N[1][0]===D[1][0]&&N[1][1]===D[1][1]||(Ku(S,P,E,{nodeOrigin:C,nodeExtent:N,elevateNodesOnSelect:R,checkEquality:!1,zIndexMode:O}),_({nodeExtent:N}))},panBy:N=>{const{transform:S,width:P,height:E,panZoom:C,translateExtent:R}=k();return N1({delta:N,panZoom:C,transform:S,translateExtent:R,width:P,height:E})},setCenter:async(N,S,P)=>{const{width:E,height:C,maxZoom:R,panZoom:D}=k();if(!D)return!1;const O=typeof(P==null?void 0:P.zoom)<"u"?P.zoom:R;return await D.setViewport({x:E/2-N*O,y:C/2-S*O,zoom:O},{duration:P==null?void 0:P.duration,ease:P==null?void 0:P.ease,interpolate:P==null?void 0:P.interpolate}),!0},cancelConnection:()=>{_({connection:{...eg}})},updateConnection:N=>{_({connection:N})},reset:()=>_({...sp()})}},Object.is);function sm({initialNodes:t,initialEdges:r,defaultNodes:o,defaultEdges:l,initialWidth:a,initialHeight:u,initialMinZoom:d,initialMaxZoom:f,initialFitViewOptions:g,fitView:m,nodeOrigin:y,nodeExtent:x,zIndexMode:v,children:_}){const[k]=$.useState(()=>XS({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,width:a,height:u,fitView:m,minZoom:d,maxZoom:f,fitViewOptions:g,nodeOrigin:y,nodeExtent:x,zIndexMode:v}));return p.jsx(s_,{value:k,children:p.jsx(T_,{children:p.jsx(X_,{children:_})})})}function GS({children:t,nodes:r,edges:o,defaultNodes:l,defaultEdges:a,width:u,height:d,fitView:f,fitViewOptions:g,minZoom:m,maxZoom:y,nodeOrigin:x,nodeExtent:v,zIndexMode:_}){return $.useContext(jl)?p.jsx(p.Fragment,{children:t}):p.jsx(sm,{initialNodes:r,initialEdges:o,defaultNodes:l,defaultEdges:a,initialWidth:u,initialHeight:d,fitView:f,initialFitViewOptions:g,initialMinZoom:m,initialMaxZoom:y,nodeOrigin:x,nodeExtent:v,zIndexMode:_,children:t})}const QS={width:"100%",height:"100%",overflow:"hidden",position:"relative",zIndex:0};function qS({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,className:a,nodeTypes:u,edgeTypes:d,onNodeClick:f,onEdgeClick:g,onInit:m,onMove:y,onMoveStart:x,onMoveEnd:v,onConnect:_,onConnectStart:k,onConnectEnd:j,onClickConnectStart:N,onClickConnectEnd:S,onNodeMouseEnter:P,onNodeMouseMove:E,onNodeMouseLeave:C,onNodeContextMenu:R,onNodeDoubleClick:D,onNodeDragStart:O,onNodeDrag:U,onNodeDragStop:q,onNodesDelete:te,onEdgesDelete:X,onDelete:ee,onSelectionChange:J,onSelectionDragStart:b,onSelectionDrag:Y,onSelectionDragStop:H,onSelectionContextMenu:W,onSelectionStart:A,onSelectionEnd:z,onBeforeDelete:B,connectionMode:M,connectionLineType:T=rr.Bezier,connectionLineStyle:re,connectionLineComponent:ie,connectionLineContainerStyle:ce,deleteKeyCode:fe="Backspace",selectionKeyCode:de="Shift",selectionOnDrag:K=!1,selectionMode:le=Eo.Full,panActivationKeyCode:he="Space",multiSelectionKeyCode:_e=jo()?"Meta":"Control",zoomActivationKeyCode:ve=jo()?"Meta":"Control",snapToGrid:ge,snapGrid:Ne,onlyRenderVisibleElements:Me=!1,selectNodesOnDrag:be,nodesDraggable:Le,autoPanOnNodeFocus:$e,nodesConnectable:Ae,nodesFocusable:et,nodeOrigin:Ye=Tg,edgesFocusable:Mt,edgesReconnectable:yt,elementsSelectable:_t=!0,defaultViewport:ft=x_,minZoom:tt=.5,maxZoom:ut=2,translateExtent:vt=ko,preventScrolling:Yt=!0,nodeExtent:$t,defaultMarkerColor:vn="#b1b1b7",zoomOnScroll:Ci=!0,zoomOnPinch:Dr=!0,panOnScroll:$r=!1,panOnScrollSpeed:ji=.5,panOnScrollMode:or=Pr.Free,zoomOnDoubleClick:In=!0,panOnDrag:sr=!0,onPaneClick:lr,onPaneMouseEnter:Tn,onPaneMouseMove:Ot,onPaneMouseLeave:Xt,onPaneScroll:Or,onPaneContextMenu:Fr,paneClickDistance:Hr=1,nodeClickDistance:ar=0,children:ur,onReconnect:Rn,onReconnectStart:cr,onReconnectEnd:an,onEdgeContextMenu:Ln,onEdgeDoubleClick:un,onEdgeMouseEnter:V,onEdgeMouseMove:ne,onEdgeMouseLeave:Ce,reconnectRadius:De=10,onNodesChange:Ve,onEdgesChange:bi,noDragClassName:Rl="nodrag",noWheelClassName:Ll="nowheel",noPanClassName:cn="nopan",fitView:Mi,fitViewOptions:Pi,connectOnClick:Al,attributionPosition:zo,proOptions:Do,defaultEdgeOptions:$o,elevateNodesOnSelect:Oo=!0,elevateEdgesOnSelect:zl=!1,disableKeyboardA11y:Fo=!1,autoPanOnConnect:Xe,autoPanOnNodeDrag:Dl,autoPanOnSelection:Ii=!0,autoPanSpeed:Ho,connectionRadius:Br,isValidConnection:$l,onError:Bo,style:Vr,id:Pt,nodeDragThreshold:Ol,connectionDragThreshold:It,viewport:Fl,onViewportChange:Hl,width:Bl,height:Ur,colorMode:Wr="light",debug:dr,onScroll:xn,ariaLabelConfig:Vl,zIndexMode:Vo="basic",...Ti},Uo){const fr=Pt||"1",hr=k_(Wr),Ul=$.useCallback(Yr=>{Yr.currentTarget.scrollTo({top:0,left:0,behavior:"instant"}),xn==null||xn(Yr)},[xn]);return p.jsx("div",{"data-testid":"rf__wrapper",...Ti,onScroll:Ul,style:{...Vr,...QS},ref:Uo,className:rt(["react-flow",a,hr]),id:Pt,role:"application",children:p.jsxs(GS,{nodes:t,edges:r,width:Bl,height:Ur,fitView:Mi,fitViewOptions:Pi,minZoom:tt,maxZoom:ut,nodeOrigin:Ye,nodeExtent:$t,zIndexMode:Vo,children:[p.jsx(S_,{nodes:t,edges:r,defaultNodes:o,defaultEdges:l,onConnect:_,onConnectStart:k,onConnectEnd:j,onClickConnectStart:N,onClickConnectEnd:S,nodesDraggable:Le,autoPanOnNodeFocus:$e,nodesConnectable:Ae,nodesFocusable:et,edgesFocusable:Mt,edgesReconnectable:yt,elementsSelectable:_t,elevateNodesOnSelect:Oo,elevateEdgesOnSelect:zl,minZoom:tt,maxZoom:ut,nodeExtent:$t,onNodesChange:Ve,onEdgesChange:bi,snapToGrid:ge,snapGrid:Ne,connectionMode:M,translateExtent:vt,connectOnClick:Al,defaultEdgeOptions:$o,fitView:Mi,fitViewOptions:Pi,onNodesDelete:te,onEdgesDelete:X,onDelete:ee,onNodeDragStart:O,onNodeDrag:U,onNodeDragStop:q,onSelectionDrag:Y,onSelectionDragStart:b,onSelectionDragStop:H,onMove:y,onMoveStart:x,onMoveEnd:v,noPanClassName:cn,nodeOrigin:Ye,rfId:fr,autoPanOnConnect:Xe,autoPanOnNodeDrag:Dl,autoPanSpeed:Ho,onError:Bo,connectionRadius:Br,isValidConnection:$l,selectNodesOnDrag:be,nodeDragThreshold:Ol,connectionDragThreshold:It,onBeforeDelete:B,debug:dr,ariaLabelConfig:Vl,zIndexMode:Vo}),p.jsx(WS,{onInit:m,onNodeClick:f,onEdgeClick:g,onNodeMouseEnter:P,onNodeMouseMove:E,onNodeMouseLeave:C,onNodeContextMenu:R,onNodeDoubleClick:D,nodeTypes:u,edgeTypes:d,connectionLineType:T,connectionLineStyle:re,connectionLineComponent:ie,connectionLineContainerStyle:ce,selectionKeyCode:de,selectionOnDrag:K,selectionMode:le,deleteKeyCode:fe,multiSelectionKeyCode:_e,panActivationKeyCode:he,zoomActivationKeyCode:ve,onlyRenderVisibleElements:Me,defaultViewport:ft,translateExtent:vt,minZoom:tt,maxZoom:ut,preventScrolling:Yt,zoomOnScroll:Ci,zoomOnPinch:Dr,zoomOnDoubleClick:In,panOnScroll:$r,panOnScrollSpeed:ji,panOnScrollMode:or,panOnDrag:sr,autoPanOnSelection:Ii,onPaneClick:lr,onPaneMouseEnter:Tn,onPaneMouseMove:Ot,onPaneMouseLeave:Xt,onPaneScroll:Or,onPaneContextMenu:Fr,paneClickDistance:Hr,nodeClickDistance:ar,onSelectionContextMenu:W,onSelectionStart:A,onSelectionEnd:z,onReconnect:Rn,onReconnectStart:cr,onReconnectEnd:an,onEdgeContextMenu:Ln,onEdgeDoubleClick:un,onEdgeMouseEnter:V,onEdgeMouseMove:ne,onEdgeMouseLeave:Ce,reconnectRadius:De,defaultMarkerColor:vn,noDragClassName:Rl,noWheelClassName:Ll,noPanClassName:cn,rfId:fr,disableKeyboardA11y:Fo,nodeExtent:$t,viewport:Fl,onViewportChange:Hl,nodesDraggable:Le}),p.jsx(v_,{onSelectionChange:J}),ur,p.jsx(h_,{proOptions:Do,position:zo}),p.jsx(f_,{rfId:fr,disableKeyboardA11y:Fo})]})})}var KS=Lg(qS);function ZS({dimensions:t,lineWidth:r,variant:o,className:l}){return p.jsx("path",{strokeWidth:r,d:`M${t[0]/2} 0 V${t[1]} M0 ${t[1]/2} H${t[0]}`,className:rt(["react-flow__background-pattern",o,l])})}function JS({radius:t,className:r}){return p.jsx("circle",{cx:t,cy:t,r:t,className:rt(["react-flow__background-pattern","dots",r])})}var ir;(function(t){t.Lines="lines",t.Dots="dots",t.Cross="cross"})(ir||(ir={}));const ek={[ir.Dots]:1,[ir.Lines]:1,[ir.Cross]:6},tk=t=>({transform:t.transform,patternId:`pattern-${t.rfId}`});function lm({id:t,variant:r=ir.Dots,gap:o=20,size:l,lineWidth:a=1,offset:u=0,color:d,bgColor:f,style:g,className:m,patternClassName:y}){const x=$.useRef(null),{transform:v,patternId:_}=Te(tk,qe),k=l||ek[r],j=r===ir.Dots,N=r===ir.Cross,S=Array.isArray(o)?o:[o,o],P=[S[0]*v[2]||1,S[1]*v[2]||1],E=k*v[2],C=Array.isArray(u)?u:[u,u],R=N?[E,E]:P,D=[C[0]*v[2]+R[0]/2,C[1]*v[2]+R[1]/2],O=`${_}${t||""}`;return p.jsxs("svg",{className:rt(["react-flow__background",m]),style:{...g,...Pl,"--xy-background-color-props":f,"--xy-background-pattern-color-props":d},ref:x,"data-testid":"rf__background",children:[p.jsx("pattern",{id:O,x:v[0]%P[0],y:v[1]%P[1],width:P[0],height:P[1],patternUnits:"userSpaceOnUse",patternTransform:`translate(-${D[0]},-${D[1]})`,children:j?p.jsx(JS,{radius:E/2,className:y}):p.jsx(ZS,{dimensions:R,lineWidth:a,variant:r,className:y})}),p.jsx("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:`url(#${O})`})]})}lm.displayName="Background";const nk=$.memo(lm);function rk(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32",children:p.jsx("path",{d:"M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"})})}function ik(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 5",children:p.jsx("path",{d:"M0 0h32v4.2H0z"})})}function ok(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 30",children:p.jsx("path",{d:"M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"})})}function sk(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:p.jsx("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"})})}function lk(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:p.jsx("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"})})}function el({children:t,className:r,...o}){return p.jsx("button",{type:"button",className:rt(["react-flow__controls-button",r]),...o,children:t})}const ak=t=>({isInteractive:t.nodesDraggable||t.nodesConnectable||t.elementsSelectable,minZoomReached:t.transform[2]<=t.minZoom,maxZoomReached:t.transform[2]>=t.maxZoom,ariaLabelConfig:t.ariaLabelConfig});function am({style:t,showZoom:r=!0,showFitView:o=!0,showInteractive:l=!0,fitViewOptions:a,onZoomIn:u,onZoomOut:d,onFitView:f,onInteractiveChange:g,className:m,children:y,position:x="bottom-left",orientation:v="vertical","aria-label":_}){const k=Be(),{isInteractive:j,minZoomReached:N,maxZoomReached:S,ariaLabelConfig:P}=Te(ak,qe),{zoomIn:E,zoomOut:C,fitView:R}=Ml(),D=()=>{E(),u==null||u()},O=()=>{C(),d==null||d()},U=()=>{R(a),f==null||f()},q=()=>{k.setState({nodesDraggable:!j,nodesConnectable:!j,elementsSelectable:!j}),g==null||g(!j)},te=v==="horizontal"?"horizontal":"vertical";return p.jsxs(bl,{className:rt(["react-flow__controls",te,m]),position:x,style:t,"data-testid":"rf__controls","aria-label":_??P["controls.ariaLabel"],children:[r&&p.jsxs(p.Fragment,{children:[p.jsx(el,{onClick:D,className:"react-flow__controls-zoomin",title:P["controls.zoomIn.ariaLabel"],"aria-label":P["controls.zoomIn.ariaLabel"],disabled:S,children:p.jsx(rk,{})}),p.jsx(el,{onClick:O,className:"react-flow__controls-zoomout",title:P["controls.zoomOut.ariaLabel"],"aria-label":P["controls.zoomOut.ariaLabel"],disabled:N,children:p.jsx(ik,{})})]}),o&&p.jsx(el,{className:"react-flow__controls-fitview",onClick:U,title:P["controls.fitView.ariaLabel"],"aria-label":P["controls.fitView.ariaLabel"],children:p.jsx(ok,{})}),l&&p.jsx(el,{className:"react-flow__controls-interactive",onClick:q,title:P["controls.interactive.ariaLabel"],"aria-label":P["controls.interactive.ariaLabel"],children:j?p.jsx(lk,{}):p.jsx(sk,{})}),y]})}am.displayName="Controls";const uk=$.memo(am);function ck({id:t,x:r,y:o,width:l,height:a,style:u,color:d,strokeColor:f,strokeWidth:g,className:m,borderRadius:y,shapeRendering:x,selected:v,onClick:_}){const{background:k,backgroundColor:j}=u||{},N=d||k||j;return p.jsx("rect",{className:rt(["react-flow__minimap-node",{selected:v},m]),x:r,y:o,rx:y,ry:y,width:l,height:a,style:{fill:N,stroke:f,strokeWidth:g},shapeRendering:x,onClick:_?S=>_(S,t):void 0})}const dk=$.memo(ck),fk=t=>t.nodes.map(r=>r.id),$u=t=>t instanceof Function?t:()=>t;function hk({nodeStrokeColor:t,nodeColor:r,nodeClassName:o="",nodeBorderRadius:l=5,nodeStrokeWidth:a,nodeComponent:u=dk,onClick:d}){const f=Te(fk,qe),g=$u(r),m=$u(t),y=$u(o),x=typeof window>"u"||window.chrome?"crispEdges":"geometricPrecision";return p.jsx(p.Fragment,{children:f.map(v=>p.jsx(gk,{id:v,nodeColorFunc:g,nodeStrokeColorFunc:m,nodeClassNameFunc:y,nodeBorderRadius:l,nodeStrokeWidth:a,NodeComponent:u,onClick:d,shapeRendering:x},v))})}function pk({id:t,nodeColorFunc:r,nodeStrokeColorFunc:o,nodeClassNameFunc:l,nodeBorderRadius:a,nodeStrokeWidth:u,shapeRendering:d,NodeComponent:f,onClick:g}){const{node:m,x:y,y:x,width:v,height:_}=Te(k=>{const j=k.nodeLookup.get(t);if(!j)return{node:void 0,x:0,y:0,width:0,height:0};const N=j.internals.userNode,{x:S,y:P}=j.internals.positionAbsolute,{width:E,height:C}=ln(N);return{node:N,x:S,y:P,width:E,height:C}},qe);return!m||m.hidden||!ag(m)?null:p.jsx(f,{x:y,y:x,width:v,height:_,style:m.style,selected:!!m.selected,className:l(m),color:r(m),borderRadius:a,strokeColor:o(m),strokeWidth:u,shapeRendering:d,onClick:g,id:m.id})}const gk=$.memo(pk);var mk=$.memo(hk);const yk=200,vk=150,xk=t=>!t.hidden,wk=t=>{const r={x:-t.transform[0]/t.transform[2],y:-t.transform[1]/t.transform[2],width:t.width/t.transform[2],height:t.height/t.transform[2]};return{viewBB:r,boundingRect:t.nodeLookup.size>0?og(Ro(t.nodeLookup,{filter:xk}),r):r,rfId:t.rfId,panZoom:t.panZoom,translateExtent:t.translateExtent,flowWidth:t.width,flowHeight:t.height,ariaLabelConfig:t.ariaLabelConfig}},lp=(t,r)=>t.x===r.x&&t.y===r.y&&t.width===r.width&&t.height===r.height,_k=(t,r)=>lp(t.viewBB,r.viewBB)&&lp(t.boundingRect,r.boundingRect)&&t.rfId===r.rfId&&t.panZoom===r.panZoom&&t.translateExtent===r.translateExtent&&t.flowWidth===r.flowWidth&&t.flowHeight===r.flowHeight&&t.ariaLabelConfig===r.ariaLabelConfig,Sk="react-flow__minimap-desc";function um({style:t,className:r,nodeStrokeColor:o,nodeColor:l,nodeClassName:a="",nodeBorderRadius:u=5,nodeStrokeWidth:d,nodeComponent:f,bgColor:g,maskColor:m,maskStrokeColor:y,maskStrokeWidth:x,position:v="bottom-right",onClick:_,onNodeClick:k,pannable:j=!1,zoomable:N=!1,ariaLabel:S,inversePan:P,zoomStep:E=1,offsetScale:C=5}){const R=Be(),D=$.useRef(null),{boundingRect:O,viewBB:U,rfId:q,panZoom:te,translateExtent:X,flowWidth:ee,flowHeight:J,ariaLabelConfig:b}=Te(wk,_k),Y=(t==null?void 0:t.width)??yk,H=(t==null?void 0:t.height)??vk,W=O.width/Y,A=O.height/H,z=Math.max(W,A),B=z*Y,M=z*H,T=C*z,re=O.x-(B-O.width)/2-T,ie=O.y-(M-O.height)/2-T,ce=B+T*2,fe=M+T*2,de=`${Sk}-${q}`,K=$.useRef(0),le=$.useRef();K.current=z,$.useEffect(()=>{if(D.current&&te)return le.current=L1({domNode:D.current,panZoom:te,getTransform:()=>R.getState().transform,getViewScale:()=>K.current}),()=>{var ge;(ge=le.current)==null||ge.destroy()}},[te]),$.useEffect(()=>{var ge;(ge=le.current)==null||ge.update({translateExtent:X,width:ee,height:J,inversePan:P,pannable:j,zoomStep:E,zoomable:N})},[j,N,P,E,X,ee,J]);const he=_?ge=>{var be;const[Ne,Me]=((be=le.current)==null?void 0:be.pointer(ge))||[0,0];_(ge,{x:Ne,y:Me})}:void 0,_e=k?$.useCallback((ge,Ne)=>{const Me=R.getState().nodeLookup.get(Ne).internals.userNode;k(ge,Me)},[]):void 0,ve=S??b["minimap.ariaLabel"];return p.jsx(bl,{position:v,style:{...t,"--xy-minimap-background-color-props":typeof g=="string"?g:void 0,"--xy-minimap-mask-background-color-props":typeof m=="string"?m:void 0,"--xy-minimap-mask-stroke-color-props":typeof y=="string"?y:void 0,"--xy-minimap-mask-stroke-width-props":typeof x=="number"?x*z:void 0,"--xy-minimap-node-background-color-props":typeof l=="string"?l:void 0,"--xy-minimap-node-stroke-color-props":typeof o=="string"?o:void 0,"--xy-minimap-node-stroke-width-props":typeof d=="number"?d:void 0},className:rt(["react-flow__minimap",r]),"data-testid":"rf__minimap",children:p.jsxs("svg",{width:Y,height:H,viewBox:`${re} ${ie} ${ce} ${fe}`,className:"react-flow__minimap-svg",role:"img","aria-labelledby":de,ref:D,onClick:he,children:[ve&&p.jsx("title",{id:de,children:ve}),p.jsx(mk,{onClick:_e,nodeColor:l,nodeStrokeColor:o,nodeBorderRadius:u,nodeClassName:a,nodeStrokeWidth:d,nodeComponent:f}),p.jsx("path",{className:"react-flow__minimap-mask",d:`M${re-T},${ie-T}h${ce+T*2}v${fe+T*2}h${-ce-T*2}z - M${U.x},${U.y}h${U.width}v${U.height}h${-U.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}um.displayName="MiniMap";const kk=$.memo(um),Ek=t=>r=>t?`${Math.max(1/r.transform[2],1)}`:void 0,Nk={[Ei.Line]:"right",[Ei.Handle]:"bottom-right"};function Ck({nodeId:t,position:r,variant:o=Ei.Handle,className:l,style:a=void 0,children:u,color:d,minWidth:f=10,minHeight:g=10,maxWidth:m=Number.MAX_VALUE,maxHeight:y=Number.MAX_VALUE,keepAspectRatio:x=!1,resizeDirection:v,autoScale:_=!0,shouldResize:k,onResizeStart:j,onResize:N,onResizeEnd:S}){const P=Og(),E=typeof t=="string"?t:P,C=Be(),R=$.useRef(null),D=o===Ei.Handle,O=Te($.useCallback(Ek(D&&_),[D,_]),qe),U=$.useRef(null),q=r??Nk[o];$.useEffect(()=>{if(!(!R.current||!E))return U.current||(U.current=X1({domNode:R.current,nodeId:E,getStoreItems:()=>{const{nodeLookup:X,transform:ee,snapGrid:J,snapToGrid:b,nodeOrigin:Y,domNode:H}=C.getState();return{nodeLookup:X,transform:ee,snapGrid:J,snapToGrid:b,nodeOrigin:Y,paneDomNode:H}},onChange:(X,ee)=>{const{triggerNodeChanges:J,nodeLookup:b,parentLookup:Y,nodeOrigin:H}=C.getState(),W=[],A={x:X.x,y:X.y},z=b.get(E);if(z&&z.expandParent&&z.parentId){const B=z.origin??H,M=X.width??z.measured.width??0,T=X.height??z.measured.height??0,re={id:z.id,parentId:z.parentId,rect:{width:M,height:T,...ug({x:X.x??z.position.x,y:X.y??z.position.y},{width:M,height:T},z.parentId,b,B)}},ie=vc([re],b,Y,H);W.push(...ie),A.x=X.x?Math.max(B[0]*M,X.x):void 0,A.y=X.y?Math.max(B[1]*T,X.y):void 0}if(A.x!==void 0&&A.y!==void 0){const B={id:E,type:"position",position:{...A}};W.push(B)}if(X.width!==void 0&&X.height!==void 0){const M={id:E,type:"dimensions",resizing:!0,setAttributes:v?v==="horizontal"?"width":"height":!0,dimensions:{width:X.width,height:X.height}};W.push(M)}for(const B of ee){const M={...B,type:"position"};W.push(M)}J(W)},onEnd:({width:X,height:ee})=>{const J={id:E,type:"dimensions",resizing:!1,dimensions:{width:X,height:ee}};C.getState().triggerNodeChanges([J])}})),U.current.update({controlPosition:q,boundaries:{minWidth:f,minHeight:g,maxWidth:m,maxHeight:y},keepAspectRatio:x,resizeDirection:v,onResizeStart:j,onResize:N,onResizeEnd:S,shouldResize:k}),()=>{var X;(X=U.current)==null||X.destroy()}},[q,f,g,m,y,x,j,N,S,k]);const te=q.split("-");return p.jsx("div",{className:rt(["react-flow__resize-control","nodrag",...te,o,l]),ref:R,style:{...a,scale:O,...d&&{[D?"backgroundColor":"borderColor"]:d}},children:u})}$.memo(Ck);const jk={"arch.context":0,"django.app":0,"django.route":1,"django.url_name":2,"django.view":3,"django.viewset_action":3,"django.permission":3,"django.throttle":3,"django.serializer":4,"django.form":4,"django.serializer_field":5,"django.service":5,"django.model":6,"django.field":7,"django.relation":7,"django.task":8,"django.receiver":8,"django.signal":8,"django.test":8,"django.migration_op":8,"django.admin":8,"django.management_command":8,"openapi.path":9,"react.api_client":10,"react.query_key":11,"react.hook":11,"react.feature":11,"react.route":12,"react.page":12,"react.component":13,"react.form_schema":14,"react.test":14,"react.context":13};function Tl(t){return jk[t]??8}const ec=208,tc=64,bk=88,Mk=28,Pk=8;function Ik(t){if(!t.length)return Number.NaN;const r=[...t].sort((l,a)=>l-a),o=Math.floor(r.length/2);return r.length%2?r[o]:(r[o-1]+r[o])/2}function Tk(t,r=[]){const o=new Map;if(!t.length)return o;const l=new Map;for(const S of t){const P=Tl(S.type),E=l.get(P)??[];E.push(S),l.set(P,E)}const u=[...l.keys()].sort((S,P)=>S-P).map(S=>[...l.get(S)??[]].sort((P,E)=>P.name.localeCompare(E.name)||P.id.localeCompare(E.id))),d=new Set(t.map(S=>S.id)),f=new Map,g=new Map;for(const S of t)f.set(S.id,[]),g.set(S.id,[]);for(const S of r)!d.has(S.src)||!d.has(S.dst)||S.src===S.dst||(g.get(S.src).push(S.dst),f.get(S.dst).push(S.src));const m=new Map;u.forEach((S,P)=>{for(const E of S)m.set(E.id,P)});const y=new Map,x=()=>{for(const S of u)S.forEach((P,E)=>y.set(P.id,E))};x();const v=(S,P)=>{const E=S.map((C,R)=>{const D=P(C.id).map(U=>y.get(U)).filter(U=>U!==void 0),O=Ik(D);return{n:C,bary:Number.isNaN(O)?R:O,name:C.name,id:C.id}});return E.sort((C,R)=>C.bary-R.bary||C.name.localeCompare(R.name)||C.id.localeCompare(R.id)),E.map(C=>C.n)},_=S=>P=>m.get(P)===S;for(let S=0;S(f.get(E)??[]).filter(_(P-1))),x();for(let P=u.length-2;P>=0;P--)u[P]=v(u[P],E=>(g.get(E)??[]).filter(_(P+1))),x()}const k=ec+bk,j=tc+Mk,N=Math.max(...u.map(S=>S.length),1);return u.forEach((S,P)=>{const E=(N-S.length)*j/2;S.forEach((C,R)=>{o.set(C.id,{x:P*k,y:E+R*j})})}),o}const cm=90,Rk=new Set(["django.field","django.serializer_field","django.relation","django.test","react.test","django.url_name","django.throttle"]),ap={"arch.context":"#edf2f4","django.app":"#8d99ae","django.route":"#4cc9f0","django.view":"#4895ef","django.viewset_action":"#4361ee","django.permission":"#7b8cde","django.serializer":"#f4a261","django.form":"#e9c46a","django.serializer_field":"#e9c46a","django.service":"#90be6d","django.model":"#2a9d8f","django.field":"#8ac926","django.task":"#e76f51","django.receiver":"#e85d04","django.signal":"#f4a261","django.test":"#6c757d","django.admin":"#adb5bd","django.migration_op":"#9d4edd","openapi.path":"#00bbf9","react.api_client":"#ff6b6b","react.query_key":"#adb5bd","react.hook":"#7b2cbf","react.feature":"#9d4edd","react.route":"#c77dff","react.page":"#c77dff","react.component":"#9d4edd","react.form_schema":"#ffd166","react.test":"#6c757d"},Lk=Math.PI*(3-Math.sqrt(5)),dm=220,Ak=26,zk={0:"context",1:"routes",2:"url names",3:"views",4:"serializers",5:"services",6:"models",7:"fields",8:"jobs / signals",9:"openapi",10:"api client",11:"hooks",12:"pages",13:"components",14:"forms / tests"};function fm(t){return t.startsWith("react.")?"react":t.startsWith("openapi.")?"stitch":t.startsWith("arch.")?"arch":"django"}function yE(t){return ap[t]?ap[t]:t.startsWith("react.")?"#9d4edd":t.startsWith("openapi.")?"#00bbf9":"#4a5568"}function Dk(t){return t>=cm?"3d":"2d"}function $k(t){return t>=cm?"overview":"full"}function Ok(t,r,o=1){const l=new Set([t]);let a=new Set([t]);for(let u=0;uo.families.has(fm(f.type)));o.detail==="overview"&&(l=l.filter(f=>!Rk.has(f.type)));const a=new Set(l.map(f=>f.id)),u=r.filter(f=>a.has(f.src)&&a.has(f.dst)),d=o.focusId?Ok(o.focusId,u,1):new Set;if(o.neighborhoodOnly&&o.focusId&&d.size){l=l.filter(g=>d.has(g.id));const f=new Set(l.map(g=>g.id));return{nodes:l,edges:u.filter(g=>f.has(g.src)&&f.has(g.dst)),neighborIds:d}}return{nodes:l,edges:u,neighborIds:d}}function vE(t){const r=new Map;for(const l of t){const a=Tl(l.type),u=r.get(a)??[];u.push(l),r.set(a,u)}const o=new Map;for(const[l,a]of r){a.sort((d,f)=>d.name.localeCompare(f.name));const u=l*dm;a.forEach((d,f)=>{if(a.length===1){o.set(d.id,{x:u,y:0,z:0});return}const g=Ak*Math.sqrt(f+1),m=f*Lk;o.set(d.id,{x:u,y:g*Math.cos(m),z:g*Math.sin(m)})})}return o}function xE(t){const r=new Map;for(const o of t){const l=Tl(o.type);r.set(l,(r.get(l)||0)+1)}return[...r.entries()].sort((o,l)=>o[0]-l[0]).map(([o,l])=>({layer:o,x:o*dm,count:l}))}const tl=16,Hk=12,Bk=new Set(["django.route","react.route","react.page","django.task","django.migration_op","django.permission","django.throttle","django.admin","django.management_command","openapi.path"]),Vk=new Set(["django.serializer","django.serializer_field","django.form","openapi.path","react.form_schema","django.route"]),up={"arch.context":"Ownership boundary from loadpath.yml — the context this code belongs to.","django.app":"Django app package that owns models, views, and jobs.","django.route":"HTTP URL that publishes a view. A sink: this is where a change becomes a public request.","django.url_name":"Named URL used by reverse() / {% url %} lookups.","django.view":"Request handler (class-based view, function view, or ViewSet).","django.viewset_action":"One ViewSet action (list, create, retrieve, update, destroy).","django.permission":"Auth gate on a view — who is allowed to hit this path.","django.throttle":"Rate-limit class attached to a view.","django.serializer":"Request/response contract: which fields go in and come out.","django.form":"Django form or django-filter FilterSet — the typed input contract.","django.serializer_field":"One field on a serializer or form — the typed slot on the contract.","django.service":"Internal service or use-case. Work that is not itself an HTTP sink.","django.model":"ORM model. Schema and relations live here.","django.field":"Model column. Type, indexes, and relations are the contract of the table.","django.relation":"Model-to-model relation (FK / M2M / O2O).","django.task":"Celery or Dramatiq job. Once enqueued, this is a sink.","django.receiver":"Signal handler that runs after a model event.","django.signal":"Django signal that receivers subscribe to.","django.test":"Backend test that mentions symbols on this path.","django.admin":"Django admin class for a model.","django.migration_op":"Schema migration operation (CreateModel, AlterField, …).","django.management_command":"manage.py command — an operational sink.","openapi.path":"Generated OpenAPI operation. The typed HTTP contract between stacks.","react.api_client":"Frontend fetch or generated client call to an API path.","react.query_key":"React Query cache key. Invalidation and reads share this name.","react.hook":"Data hook wrapping query or mutation calls.","react.feature":"Frontend feature module (folder).","react.route":"Client-side route. A sink: this is a URL the user can open.","react.page":"Page or screen component rendered by a route.","react.component":"UI component.","react.form_schema":"Zod (or similar) schema — typed form inputs on the client.","react.test":"Frontend test covering a page, hook, or component.","react.context":"React context provider."},Uk={field_type:"Type",fields:"Fields",form_fields:"Form fields",permissions:"Permissions",throttles:"Throttles",authentication:"Authentication",pagination:"Pagination",filterset:"Filterset",bases:"Extends",on_delete:"on_delete",related_name:"related_name",unique:"Unique",db_index:"Indexed",relation:"Relation field",looks_idempotent_on_pk:"Idempotent on pk",broker:"Broker",route:"Route",url_name:"URL name",view:"View",include:"Includes",mounted_at:"Mounted at",full_path:"Full path",method:"Method",path:"Path",operation_id:"Operation",raw:"URL",kind:"Schema",exclude:"Excludes",queryset_in_serializer:"Queryset in serializer",get_queryset:"Custom get_queryset",get_serializer_class:"Dynamic serializer",dynamic:"Dynamic",fbv:"Function view",ninja:"Django Ninja",django_form:"Django form",mutation:"Mutation",has_error_boundary:"Error boundary",invalidation:"Cache invalidation",inferred:"Inferred stitch",generated:"Generated",shared:"Shared module",element:"Renders",model_name:"Model",field_name:"Field",op:"Operation",app:"App",feature:"Feature",from_view:"From view",mentions:"Mentions",nodeid:"Test id",task:"Task",to:"Related to"},cp=["field_type","method","path","operation_id","raw","route","mounted_at","full_path","url_name","view","element","fields","form_fields","exclude","kind","bases","permissions","authentication","throttles","pagination","filterset","on_delete","related_name","to","unique","db_index","relation","looks_idempotent_on_pk","broker","task","model_name","field_name","op","app","feature","from_view","include","fbv","ninja","django_form","mutation","has_error_boundary","invalidation","inferred","generated","shared","queryset_in_serializer","get_queryset","get_serializer_class","dynamic","mentions","nodeid"],dp=new Set(["referenced","placeholder","booted","line","call","from","import","local","source","file","plain_handler","string_ref","pagination_sink","match","via","generated_client","django","react","superseded_by_generated","foreign_app","imported"]),Wk=new Set(["looks_idempotent_on_pk"]),Yk=new Set(["inferred","generated","mutation","fbv","ninja","filterset"]);function Xk(t){return up[t]?up[t]:t.startsWith("react.")?"A React node on the load path.":t.startsWith("django.")?"A Django node on the load path.":t.startsWith("openapi.")?"A stitch node between Django and React.":"A node on the architecture graph."}function Gk(t,r,o){const l=new Map(r.map(x=>[x.id,x])),a=[];Bk.has(t.type)&&a.push("sink"),Vk.has(t.type)&&a.push("contract");const u=t.extra??{};u.inferred&&a.push("inferred"),u.generated&&a.push("generated"),u.mutation&&a.push("mutation"),u.fbv&&a.push("function view"),u.ninja&&a.push("ninja"),u.filterset===!0&&a.push("filterset");const d=o.filter(x=>x.dst===t.id),f=o.filter(x=>x.src===t.id),g=d.slice(0,tl).map(x=>fp(x,l,x.src)),m=f.slice(0,tl).map(x=>fp(x,l,x.dst)),y=t.file_path?`${t.file_path}${t.start_line?`:${t.start_line}`:""}`:void 0;return{type:t.type,typeLabel:vo(vl(t.type)),layer:zk[Tl(t.type)]??"other",purpose:Xk(t.type),name:t.name,qualifiedName:t.qualified_name,file:y,context:t.context,roles:a,facts:Qk(u).filter(x=>!(x.key==="app"&&x.value===t.context)),inputs:g,outputs:m,extraInputs:Math.max(0,d.length-tl),extraOutputs:Math.max(0,f.length-tl)}}function fp(t,r,o){const l=r.get(o),a=o.includes(":")?o.slice(o.indexOf(":")+1):o;return{id:o,name:(l==null?void 0:l.name)||a,type:(l==null?void 0:l.type)||"",typeLabel:l?vo(vl(l.type)):"",edgeType:t.type,edgeLabel:vo(t.type),inferred:t.confidence<.8}}function Qk(t){const r=[...cp.filter(a=>a in t),...Object.keys(t).filter(a=>!cp.includes(a)&&!dp.has(a))],o=[],l=new Set;for(const a of r){if(l.has(a)||dp.has(a)||Yk.has(a))continue;l.add(a);const u=qk(a,t[a]);u!=null&&o.push({key:a,label:Uk[a]??vo(a),value:u})}return o}function qk(t,r){if(r==null)return null;if(typeof r=="boolean")return!r&&!Wk.has(t)?null:r?"yes":"no";if(typeof r=="number")return String(r);if(typeof r=="string")return r.trim()||null;if(Array.isArray(r)){const o=r.map(u=>typeof u=="string"||typeof u=="number"?String(u):"").filter(Boolean);if(!o.length)return null;const l=o.slice(0,Hk),a=o.length-l.length;return a>0?`${l.join(", ")} +${a} more`:l.join(", ")}return null}const Kk=new Set,Zk=$.lazy(()=>y0(()=>import("./LayeredGraph3D-DQUYVbjc.js"),[],import.meta.url).then(t=>({default:t.LayeredGraph3D}))),Jk={cheap:"var(--edge-cheap)",expensive:"var(--edge-expensive)",critical:"var(--edge-critical)"};function eE({data:t,selected:r}){return p.jsxs("div",{className:r?"lp-node selected":"lp-node",children:[p.jsx(Ni,{type:"target",position:Se.Left,isConnectable:!1}),p.jsx("div",{className:"t",children:vl(t.type)}),p.jsx("div",{className:"n",title:t.name,children:jr(t.name)}),p.jsx(Ni,{type:"source",position:Se.Right,isConnectable:!1})]})}const tE={load:eE},nE=new Set(["django","react","stitch","arch"]);function rE({topologyKey:t}){const{fitView:r}=Ml();return $.useEffect(()=>{let o=0;const l=requestAnimationFrame(()=>{o=requestAnimationFrame(()=>{r({padding:.2,maxZoom:1.15})})});return()=>{cancelAnimationFrame(l),cancelAnimationFrame(o)}},[r,t]),null}function iE(t,r,o=null){const l=new Map(t.map(f=>[f.id,f])),a=Tk(t,r),u=t.map(f=>({id:f.id,type:"load",position:a.get(f.id)??{x:0,y:0},data:{name:f.name,type:f.type,file:f.file_path},selected:o===f.id,sourcePosition:Se.Right,targetPosition:Se.Left,width:ec,height:tc,style:{width:ec,height:tc}})),d=r.filter(f=>l.has(f.src)&&l.has(f.dst)).map(f=>{const g=Jk[f.weight]||"var(--edge-cheap)",m=!!(o&&(f.src===o||f.dst===o));return{id:f.id,source:f.src,target:f.dst,type:"smoothstep",animated:f.weight==="critical",style:{stroke:g,strokeWidth:f.weight==="critical"?2.4:1.2,strokeDasharray:f.confidence<.8?"6 4":void 0},markerEnd:{type:No.ArrowClosed,width:14,height:14,color:g},label:m?f.type.replaceAll("_"," "):void 0,labelStyle:m?{fill:"var(--ink)",fontSize:10,fontWeight:600}:void 0,labelBgStyle:m?{fill:"var(--graph-bg)",fillOpacity:.92}:void 0,labelBgPadding:m?[3,5]:void 0,labelBgBorderRadius:m?4:void 0}});return{rfNodes:u,rfEdges:d}}function hp({node:t,nodes:r,edges:o,onClose:l}){const a=Gk(t,r,o);return $.useEffect(()=>{const u=d=>{d.key==="Escape"&&l()};return window.addEventListener("keydown",u),()=>window.removeEventListener("keydown",u)},[l]),p.jsxs("aside",{className:"inspector","data-testid":"graph-inspector",children:[p.jsxs("div",{className:"inspector-head",children:[p.jsx("div",{className:"t",children:a.typeLabel}),p.jsx("div",{className:"inspector-roles",children:a.roles.map(u=>p.jsx("span",{className:"inspector-chip",children:u},u))}),p.jsx("button",{type:"button",className:"inspector-close","data-testid":"graph-inspector-close","aria-label":"Close inspector",onClick:l,children:"×"})]}),p.jsx("div",{className:"n",children:jr(a.name)}),p.jsx("p",{className:"inspector-purpose","data-testid":"graph-inspector-purpose",children:a.purpose}),a.context?p.jsx("div",{className:"muted",children:jr(a.context)}):null,a.file?p.jsx("div",{className:"file",children:jr(a.file)}):null,p.jsx("div",{className:"muted",children:jr(a.qualifiedName)}),p.jsxs("div",{className:"muted inspector-layer",children:["layer · ",a.layer]}),a.facts.length?p.jsx("dl",{className:"inspector-facts","data-testid":"graph-inspector-facts",children:a.facts.map(u=>p.jsxs("div",{className:"inspector-fact",children:[p.jsx("dt",{children:u.label}),p.jsx("dd",{children:jr(u.value)})]},u.key))}):null,p.jsx(pp,{title:"Inputs",testId:"graph-inspector-inputs",links:a.inputs,extra:a.extraInputs,empty:"Nothing in this graph points here."}),p.jsx(pp,{title:"Outputs",testId:"graph-inspector-outputs",links:a.outputs,extra:a.extraOutputs,empty:"This node does not point at anything in this graph."})]})}function pp({title:t,testId:r,links:o,extra:l,empty:a}){return p.jsxs("section",{className:"inspector-section","data-testid":r,children:[p.jsxs("h3",{children:[t,p.jsx("span",{className:"count",children:o.length+l})]}),o.length?p.jsx("ul",{children:o.map((u,d)=>p.jsxs("li",{children:[p.jsx("span",{className:"inspector-link-name",title:u.name,children:jr(u.name)}),p.jsxs("span",{className:"inspector-link-meta",children:[u.typeLabel?`${u.typeLabel} · `:"",u.edgeLabel,u.inferred?" · inferred":""]})]},`${u.edgeType}:${u.id}:${d}`))}):p.jsx("p",{className:"muted",children:a}),l?p.jsxs("p",{className:"muted",children:["+",l," more"]}):null]})}function Ou({nodes:t,edges:r}){const[o,l]=$.useState(null),[a,u]=$.useState(null),[d,f]=$.useState(null),[g,m]=$.useState(new Set(nE)),[y,x]=$.useState(!1),v=typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,_=a??Dk(t.length),k=d??$k(t.length),j=y&&_==="3d"?o:null,N=$.useMemo(()=>Fk(t,r,{detail:k,families:g,focusId:j,neighborhoodOnly:!!j}),[t,r,k,g,j]),S=$.useMemo(()=>`${N.nodes.map(X=>X.id).join("\0")}|${N.edges.map(X=>X.id).join("\0")}`,[N.nodes,N.edges]),P=$.useMemo(()=>new Map(N.nodes.map(X=>[X.id,X])),[N.nodes]),E=o?P.get(o)??null:null,{rfNodes:C,rfEdges:R}=$.useMemo(()=>{const X=iE(N.nodes,N.edges,o);return v&&(X.rfEdges=X.rfEdges.map(ee=>({...ee,animated:!1}))),X},[N.nodes,N.edges,o,v]);$.useEffect(()=>{o&&!P.has(o)&&l(null)},[P,o]);const D=(X,ee)=>{l(ee.id)},O=()=>{l(null),x(!1)},U=X=>{m(ee=>{const J=new Set(ee);if(J.has(X)){if(J.size===1)return ee;J.delete(X)}else J.add(X);return J})},q=$.useMemo(()=>{const X=new Set;for(const ee of t)X.add(fm(ee.type));return X},[t]),te=t.length-N.nodes.length;return p.jsxs("div",{className:"impact-graph",style:{flex:1,minHeight:0,position:"relative",display:"flex",flexDirection:"column"},children:[p.jsxs("div",{className:"graph-toolbar","data-testid":"graph-toolbar",children:[p.jsxs("div",{className:"seg","aria-label":"Graph projection",children:[p.jsx("button",{type:"button","data-testid":"graph-view-2d",className:_==="2d"?"active":"","aria-pressed":_==="2d",onClick:()=>u("2d"),children:"2D map"}),p.jsx("button",{type:"button","data-testid":"graph-view-3d",className:_==="3d"?"active":"","aria-pressed":_==="3d",onClick:()=>u("3d"),children:"3D layers"})]}),p.jsxs("div",{className:"seg","aria-label":"Graph detail",children:[p.jsx("button",{type:"button","data-testid":"graph-detail-overview",className:k==="overview"?"active":"","aria-pressed":k==="overview",onClick:()=>f("overview"),children:"Overview"}),p.jsx("button",{type:"button","data-testid":"graph-detail-full",className:k==="full"?"active":"","aria-pressed":k==="full",onClick:()=>f("full"),children:"Full"})]}),p.jsx("div",{className:"seg","aria-label":"Graph families",children:["django","stitch","react"].filter(X=>q.has(X)).map(X=>p.jsx("button",{type:"button","data-testid":`graph-family-${X}`,className:g.has(X)?"active":"","aria-pressed":g.has(X),onClick:()=>U(X),children:X},X))}),_==="3d"?p.jsx("button",{type:"button",className:y?"chip-btn active":"chip-btn","data-testid":"graph-neighborhood",disabled:!o,onClick:()=>x(X=>!X),children:y?"Neighborhood":"Focus neighbors"}):null,p.jsxs("span",{className:"muted graph-count",children:[N.nodes.length," nodes · ",N.edges.length," edges",te?` · ${te} hidden`:""]})]}),p.jsx("div",{className:"graph-stage",children:_==="3d"?p.jsxs("div",{className:"graph-3d","data-testid":"graph-3d",children:[p.jsx("p",{className:"graph-3d-hint",children:"Architecture layers are stacked in depth (Django → stitch → React). Drag to orbit, scroll to zoom, click a node to inspect it."}),p.jsx($.Suspense,{fallback:p.jsx("p",{className:"muted graph-3d-hint",children:"Loading 3D layers…"}),children:p.jsx(Zk,{nodes:N.nodes,edges:N.edges,selectedId:o,neighborIds:j?N.neighborIds:Kk,onSelect:X=>{l(X),X||x(!1)}})}),E?p.jsx(hp,{node:E,nodes:t,edges:r,onClose:O}):null]}):p.jsxs(sm,{children:[p.jsxs(KS,{nodes:C,edges:R,nodeTypes:tE,fitView:!1,minZoom:.25,nodesDraggable:!1,nodesConnectable:!1,elementsSelectable:!0,deleteKeyCode:null,onNodeClick:D,onPaneClick:O,proOptions:{hideAttribution:!1},"data-testid":"impact-graph",children:[p.jsx(rE,{topologyKey:S}),p.jsx(nk,{}),p.jsx(kk,{pannable:!0,zoomable:!0,ariaLabel:"Impact graph overview",nodeColor:"var(--muted)",nodeStrokeColor:"transparent",nodeStrokeWidth:0,maskColor:"rgba(0, 0, 0, 0.45)",maskStrokeColor:"var(--accent)",maskStrokeWidth:1.4,bgColor:"var(--graph-bg)",style:{width:184,height:128}}),p.jsx(uk,{})]}),E?p.jsx(hp,{node:E,nodes:t,edges:r,onClose:O}):null]})})]})}const gp=[{value:"HEAD",label:"HEAD",group:"preset"},{value:"HEAD~1",label:"HEAD~1",group:"preset"}],oE=["preset","branch","tag","commit"];function sE(t){var a;if(!(t!=null&&t.git))return[...gp];const r=((a=t.presets)!=null&&a.length?t.presets:gp.map(u=>u.value)).map(u=>({value:u,label:u,group:"preset"})),o=new Set(r.map(u=>u.value)),l=[...r];for(const u of t.branches||[])o.has(u.name)||(o.add(u.name),l.push({value:u.name,label:u.current?`${u.name} (current)`:u.name,detail:u.subject,group:"branch"}));for(const u of t.tags||[])o.has(u.name)||(o.add(u.name),l.push({value:u.name,label:u.name,detail:u.subject,group:"tag"}));for(const u of t.commits||[])o.has(u.sha)||(o.add(u.sha),l.push({value:u.sha,label:u.short,detail:u.subject,group:"commit"}));return l}function lE(t,r){const o=r.trim().toLowerCase();return o?t.filter(l=>l.value.toLowerCase().includes(o)||l.label.toLowerCase().includes(o)||(l.detail||"").toLowerCase().includes(o)):t}function aE(t){return oE.map(r=>({group:r,items:t.filter(o=>o.group===r)})).filter(r=>r.items.length>0)}function uE(t){return t==="preset"?"Common":t==="branch"?"Branches":t==="tag"?"Tags":"Recent commits"}function mp({value:t,onChange:r,placeholder:o,testId:l,menuTestId:a,refs:u,onNeedRefs:d}){const f=$.useId(),g=$.useRef(null),[m,y]=$.useState(!1),[x,v]=$.useState(null),[_,k]=$.useState(0),j=$.useMemo(()=>{const C=sE(u);return x===null?C:lE(C,x)},[u,x]),N=$.useMemo(()=>aE(j),[j]);$.useEffect(()=>{m&&d()},[m,d]),$.useEffect(()=>{k(0)},[x,m]);const S=()=>{y(!1),v(null)},P=C=>{r(C.value),S()},E=C=>{if(C.key==="ArrowDown"){if(C.preventDefault(),!m){y(!0);return}k(R=>Math.min(R+1,Math.max(j.length-1,0)))}else if(C.key==="ArrowUp"){if(C.preventDefault(),!m)return;k(R=>Math.max(R-1,0))}else if(C.key==="Enter"&&m){C.preventDefault();const R=j[_];R&&P(R)}else C.key==="Escape"&&m&&(C.preventDefault(),S())};return p.jsxs("div",{className:"combo",ref:g,onBlur:C=>{C.currentTarget.contains(C.relatedTarget)||S()},children:[p.jsxs("div",{className:"combo-row",children:[p.jsx("input",{"data-testid":l,value:t,placeholder:o,spellCheck:!1,role:"combobox","aria-expanded":m,"aria-controls":f,"aria-autocomplete":"list",onChange:C=>{r(C.target.value),m&&v(C.target.value)},onKeyDown:E}),p.jsx("button",{type:"button",className:"icon-btn combo-toggle","data-testid":`${l}-toggle`,"aria-label":"Show recent refs","aria-expanded":m,onMouseDown:C=>C.preventDefault(),onClick:()=>m?S():y(!0),children:p.jsx(p0,{})})]}),m?p.jsx("div",{className:"combo-menu",id:f,role:"listbox","data-testid":a,children:N.length===0?p.jsx("div",{className:"combo-empty muted",children:"No matching refs — the typed value is kept"}):N.map(C=>p.jsxs("div",{className:"combo-group",children:[p.jsx("div",{className:"combo-heading",children:uE(C.group)}),C.items.map(R=>{const D=j.indexOf(R);return p.jsxs("button",{type:"button",role:"option","aria-selected":D===_,className:D===_?"combo-option active":"combo-option","data-testid":`ref-option-${R.group}`,onMouseDown:O=>O.preventDefault(),onMouseEnter:()=>k(D),onClick:()=>P(R),children:[p.jsx("span",{className:"combo-label",children:R.label}),R.detail?p.jsx("span",{className:"combo-detail",children:R.detail}):null]},`${R.group}:${R.value}`)})]},C.group))}):null]})}function cE({initialPath:t,onSelect:r,onClose:o}){const[l,a]=$.useState(null),[u,d]=$.useState(t),[f,g]=$.useState(null),[m,y]=$.useState(""),[x,v]=$.useState(!1),_=$.useRef(null),k=$.useRef(0),j=async E=>{const C=k.current+1;k.current=C,v(!0);try{const R=await We.browse(E);if(k.current!==C)return;a(R),d(R.path),g(R.is_git?R.path:null),y("")}catch(R){if(k.current!==C)return;y(R instanceof Error?R.message:String(R))}finally{k.current===C&&v(!1)}};$.useEffect(()=>{var E,C;j(t),(E=_.current)==null||E.focus(),(C=_.current)==null||C.select()},[t]);const N=f||(l==null?void 0:l.path)||u,S=f&&f!==(l==null?void 0:l.path)?f.split(/[\\/]/).filter(Boolean).pop():l!=null&&l.is_git?"this repository":"this folder",P=E=>{E.key==="Escape"&&(E.preventDefault(),o())};return p.jsx("div",{className:"modal-backdrop","data-testid":"repo-explorer","data-overlay":"true",onClick:o,onKeyDown:P,children:p.jsxs("div",{className:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"explorer-title",onClick:E=>E.stopPropagation(),children:[p.jsxs("div",{className:"modal-head",children:[p.jsxs("div",{children:[p.jsx("h2",{id:"explorer-title",children:"Select repository"}),p.jsx("p",{className:"muted",children:"Browse to a git root, or paste the full path."})]}),p.jsx("button",{type:"button",className:"btn ghost","data-testid":"explorer-cancel",onClick:o,children:"Cancel"})]}),p.jsxs("form",{className:"explorer-path",onSubmit:E=>{E.preventDefault(),j(u)},children:[p.jsx("input",{ref:_,"data-testid":"explorer-path",value:u,onChange:E=>d(E.target.value),spellCheck:!1,"aria-label":"Directory path"}),p.jsx("button",{type:"button",className:"btn",disabled:!(l!=null&&l.parent),onClick:()=>(l==null?void 0:l.parent)&&void j(l.parent),children:"Up"}),p.jsx("button",{type:"button",className:"btn",onClick:()=>l&&void j(l.home),children:"Home"}),p.jsx("button",{type:"submit",className:"btn",children:"Go"})]}),m?p.jsx("div",{className:"error",role:"alert",children:m}):null,p.jsx("div",{className:"explorer-list",role:"listbox","aria-label":"Folders","aria-busy":x,children:l!=null&&l.entries.length?l.entries.map(E=>{const C=f===E.path;return p.jsxs("button",{type:"button",role:"option","aria-selected":C,className:C?"explorer-row active":"explorer-row","data-testid":"explorer-entry","data-path":E.path,onClick:()=>g(E.path),onDoubleClick:()=>void j(E.path),children:[p.jsx(_p,{}),p.jsx("span",{className:"explorer-name",children:E.name}),E.is_git?p.jsx("span",{className:"chip git-badge",children:"git"}):null]},E.path)}):p.jsx("div",{className:"muted explorer-empty",children:x?"Loading…":"No folders here"})}),p.jsxs("div",{className:"modal-foot",children:[p.jsx("span",{className:"muted explorer-current",title:N,children:N}),p.jsxs("button",{type:"button",className:"btn primary","data-testid":"explorer-use",disabled:!N,onClick:()=>N&&r(N),children:["Use ",S]})]})]})})}const yl=[{id:"obsidian",label:"Obsidian",group:"dark"},{id:"nord",label:"Nord",group:"dark"},{id:"solarized-dark",label:"Solarized Dark",group:"dark"},{id:"forest",label:"Forest",group:"dark"},{id:"rose",label:"Rose Pine",group:"dark"},{id:"amber",label:"Midnight Amber",group:"dark"},{id:"volcano",label:"Volcano",group:"dark"},{id:"lavender",label:"Lavender",group:"dark"},{id:"neon-noir",label:"Neon Noir",group:"dark"},{id:"synthwave",label:"Synthwave",group:"dark"},{id:"phosphor",label:"Phosphor",group:"dark"},{id:"aurora",label:"Aurora",group:"dark"},{id:"biolume",label:"Biolume",group:"dark"},{id:"carbon",label:"Carbon",group:"dark"},{id:"paper",label:"Paper",group:"light"},{id:"solarized-light",label:"Solarized Light",group:"light"},{id:"seafoam",label:"Seafoam",group:"light"},{id:"high-contrast",label:"High Contrast",group:"light"},{id:"sakura",label:"Sakura",group:"light"},{id:"citrus",label:"Citrus",group:"light"},{id:"peach",label:"Peach Fuzz",group:"light"},{id:"candy",label:"Cotton Candy",group:"light"},{id:"sky",label:"Clear Sky",group:"light"},{id:"coral",label:"Coral Reef",group:"light"}],dE="obsidian",hm="loadpath.theme";function fE(t){return yl.some(r=>r.id===t)}function pm(){try{const t=localStorage.getItem(hm)||"";if(fE(t))return t}catch{}return dE}function hE(t){var r;return((r=yl.find(o=>o.id===t))==null?void 0:r.group)==="light"?"light":"dark"}function gm(t){document.documentElement.dataset.theme=t,document.documentElement.style.colorScheme=hE(t);try{localStorage.setItem(hm,t)}catch{}}const yp=[{id:"review",label:"Review",testId:"tab-review",shortcut:"1",icon:u0},{id:"architecture",label:"Architecture",testId:"tab-architecture",shortcut:"2",icon:c0},{id:"graph",label:"Impact graph",testId:"tab-graph",shortcut:"3",icon:d0},{id:"prs",label:"Pull requests",testId:"tab-prs",shortcut:"4",icon:f0},{id:"settings",label:"Settings",testId:"tab-settings",shortcut:"5",icon:h0}];function vp(t,r,o){let l;try{l=new URL(t)}catch{return}if(l.protocol!=="https:"||l.username||l.password)return;const a=l.hostname.toLowerCase();a!==r&&!a.endsWith(`.${r}`)||l.pathname.startsWith(o)&&window.open(l.toString(),"_blank","noopener,noreferrer")}function pE(){var ar,ur,Rn,cr,an,Ln,un;const[t,r]=$.useState("review"),[o,l]=$.useState(localStorage.getItem("loadpath.repo")||""),[a,u]=$.useState(localStorage.getItem("loadpath.base")||"HEAD~1"),[d,f]=$.useState(localStorage.getItem("loadpath.head")||"HEAD"),[g,m]=$.useState(null),[y,x]=$.useState(null),[v,_]=$.useState([]),[k,j]=$.useState("review"),[N,S]=$.useState(""),[P,E]=$.useState(""),[C,R]=$.useState(!1),[D,O]=$.useState(""),[U,q]=$.useState({}),[te,X]=$.useState([]),[ee,J]=$.useState([]),[b,Y]=$.useState(localStorage.getItem("loadpath.scmRepo")||""),[H,W]=$.useState(localStorage.getItem("loadpath.provider")||"github"),[A,z]=$.useState(localStorage.getItem("loadpath.prNumber")||""),[B,M]=$.useState(""),[T,re]=$.useState(pm),[ie,ce]=$.useState(!1),[fe,de]=$.useState(!1),[K,le]=$.useState(null),[he,_e]=$.useState(null),[ve,ge]=$.useState(!1),Ne=$.useRef(o);Ne.current=o;const Me=$.useRef(!1);Me.current=fe;const be=$.useRef(""),Le=V=>{re(V),gm(V)},$e=$.useRef(""),Ae=V=>{$e.current=V,E(V)};$.useEffect(()=>{We.settings().then(q).catch(()=>{}).finally(()=>ce(!0)),We.repos().then(V=>_(V.repos)).catch(()=>{})},[]);const et=()=>o.trim()?!0:(S("Point at a local repository path first."),!1);$.useEffect(()=>{if(t!=="architecture"||!o.trim())return;const V=o;let ne=!1;return We.architecture(V).then(Ce=>{!ne&&Ne.current===V&&x(Ce)}).catch(()=>{}),()=>{ne=!0}},[t,o]);const Ye=V=>{Ne.current=V,l(V),localStorage.setItem("loadpath.repo",V),V.trim()!==be.current&&(be.current="",le(null))},Mt=$.useCallback(V=>{const ne=(V??Ne.current).trim();return!ne||be.current===ne?Promise.resolve():(be.current=ne,We.gitRefs(ne).then(Ce=>{Ne.current.trim()===ne&&le(Ce)}).catch(()=>{be.current===ne&&(be.current="",le(null))}))},[]),yt=(V,ne)=>{u(V),f(ne),localStorage.setItem("loadpath.base",V),localStorage.setItem("loadpath.head",ne)},_t=(V,ne,Ce)=>{W(V),Y(ne),localStorage.setItem("loadpath.provider",V),localStorage.setItem("loadpath.scmRepo",ne),Ce!==void 0&&(z(Ce),localStorage.setItem("loadpath.prNumber",Ce))},ft=V=>V==="github"?!!U.github_token_set:!!U.bitbucket_token_set,tt=$.useCallback(async(V=H)=>{var ne;try{const Ce=await We.scmRepos(V);J(Ce.repos),(ne=Ce.user)!=null&&ne.login&&q(De=>({...De,...V==="github"?{github_user:Ce.user.login}:{bitbucket_user:Ce.user.login}}))}catch{J([])}},[H]);$.useEffect(()=>{if(t!=="prs")return;let V=!1;return tt(H).catch(()=>{V||J([])}),()=>{V=!0}},[t,H,tt]),$.useEffect(()=>{if(!he)return;let V=!1,ne=0;const Ce=async()=>{try{const De=await We.githubOAuthPoll(he.flow_id);if(V)return;if(De.status==="complete"){_e(null);const Ve=await We.settings();q(Ve),O(De.user?`Signed in to GitHub as ${De.user}`:"Signed in to GitHub"),tt("github");return}if(De.status==="pending"||De.status==="slow_down"){ne=window.setTimeout(Ce,Math.max(De.interval||he.interval,5)*1e3);return}_e(null),S(De.status==="denied"?"GitHub sign-in was denied.":"GitHub sign-in expired. Try again.")}catch(De){if(V)return;_e(null),S(De instanceof Error?De.message:String(De))}};return ne=window.setTimeout(Ce,Math.max(he.interval,5)*1e3),()=>{V=!0,window.clearTimeout(ne)}},[he,tt]),$.useEffect(()=>{if(!ve)return;let V=!1,ne=0;const Ce=Date.now(),De=async()=>{try{const Ve=await We.oauthStatus();if(V)return;if(Ve.bitbucket.connected){ge(!1);const bi=await We.settings();q(bi),O(Ve.bitbucket.user?`Signed in to Bitbucket as ${Ve.bitbucket.user}`:"Signed in to Bitbucket"),tt("bitbucket");return}if(Date.now()-Ce>18e4){ge(!1),S("Bitbucket sign-in timed out. Finish in the browser, or try again.");return}ne=window.setTimeout(De,1500)}catch(Ve){if(V)return;ge(!1),S(Ve instanceof Error?Ve.message:String(Ve))}};return ne=window.setTimeout(De,1500),()=>{V=!0,window.clearTimeout(ne)}},[ve,tt]);const ut=async(V=o)=>{if(!V.trim())return null;const ne=await We.architecture(V);return Ne.current===V&&x(ne),ne},vt=async V=>{const ne=V.trim();if(!(!ne||ne===Ne.current)){if($e.current){S("Wait for the current job to finish before switching workspace.");return}S(""),O(""),m(null),x(null),j("architecture"),Ye(ne),R(!0),Ae(`Loading ${a0(ne)}…`);try{await Promise.all([ut(ne),Mt(ne)])}catch(Ce){Ne.current===ne&&S(Ce instanceof Error?Ce.message:String(Ce))}finally{Ne.current===ne&&(Ae(""),R(!1))}}},Yt=async()=>{if(!$e.current&&et()){S(""),O(""),Ae("Tracing load path…"),Ye(o),yt(a,d);try{const V=await We.review(o,a,d,!0);m(V),j("review"),r("review"),await We.repos().then(ne=>_(ne.repos)).catch(()=>{}),await ut(o)}catch(V){S(V instanceof Error?V.message:String(V))}finally{Ae("")}}},$t=async(V=!0)=>{if(!$e.current&&et()){S(""),O(""),Ae(V?"Indexing…":"Full reindex…"),Ye(o);try{await We.index(o,V);const ne=await ut(o);await We.repos().then(Ce=>_(Ce.repos)).catch(()=>{}),ne!=null&&ne.indexed&&(j("architecture"),r("architecture"))}catch(ne){S(ne instanceof Error?ne.message:String(ne))}finally{Ae("")}}},vn=async()=>{if(!$e.current&&et()){S(""),O(""),Ae("Detecting layout…"),Ye(o);try{const V=await We.init(o);O(V.message),await We.repos().then(ne=>_(ne.repos)).catch(()=>{})}catch(V){S(V instanceof Error?V.message:String(V))}finally{Ae("")}}},Ci=async()=>{if(g!=null&&g.markdown)try{await navigator.clipboard.writeText(g.markdown),O("Copied markdown brief")}catch(V){S(V instanceof Error?V.message:String(V))}},Dr=async()=>{if(!$e.current){if(!(g!=null&&g.markdown)||!b||!A){S("Pick a pull request first (Pull requests tab), then post the brief.");return}Ae("Posting Loadpath brief…");try{const V=await We.postComment(H,b,Number(A),g.markdown);O(V.updated?"Updated the Loadpath PR comment":"Posted the Loadpath PR comment")}catch(V){S(V instanceof Error?V.message:String(V))}finally{Ae("")}}},$r=async()=>{if(!$e.current){S(""),Ae("Fetching pull requests…");try{const V=await We.prs(H,b);X(V.pull_requests);const ne=ee.find(Ce=>Ce.slug.toLowerCase()===b.trim().toLowerCase());ne!=null&&ne.local_path&&Ye(ne.local_path)}catch(V){S(V instanceof Error?V.message:String(V))}finally{Ae("")}}},ji=async()=>{S("");try{const V=await We.githubOAuthStart();_e(V),vp(V.verification_uri_complete,"github.com","/login/device")}catch(V){S(V instanceof Error?V.message:String(V))}},or=async()=>{S("");try{const V=await We.bitbucketOAuthStart();ge(!0),vp(V.authorize_url,"bitbucket.org","/site/oauth2/authorize")}catch(V){ge(!1),S(V instanceof Error?V.message:String(V))}},In=async V=>{S("");try{q(await We.oauthDisconnect(V)),H===V&&J([]),O(`Disconnected ${V}`)}catch(ne){S(ne instanceof Error?ne.message:String(ne))}},sr=async V=>{V.preventDefault();const ne=new FormData(V.currentTarget),Ce={github_token:String(ne.get("github_token")||""),github_oauth_client_id:String(ne.get("github_oauth_client_id")||""),bitbucket_token:String(ne.get("bitbucket_token")||""),bitbucket_username:String(ne.get("bitbucket_username")||""),bitbucket_oauth_client_id:String(ne.get("bitbucket_oauth_client_id")||""),bitbucket_oauth_client_secret:String(ne.get("bitbucket_oauth_client_secret")||""),ai_provider:String(ne.get("ai_provider")||"none"),ai_api_key:String(ne.get("ai_api_key")||""),ai_model:String(ne.get("ai_model")||""),ai_base_url:String(ne.get("ai_base_url")||"")},De=v.length?{...Ce,workspaces:v.map(Ve=>({path:Ve.path,name:Ve.name}))}:Ce;try{q(await We.saveSettings(De)),O("Settings saved on this machine")}catch(Ve){S(Ve instanceof Error?Ve.message:String(Ve))}},lr=async()=>{if(!(!g||$e.current)){Ae("Residual analysis…");try{const V=await We.residual(g);M(V.note)}catch(V){S(V instanceof Error?V.message:String(V))}finally{Ae("")}}},Tn=$.useRef(Yt);Tn.current=Yt;const Ot=$.useRef(t);Ot.current=t,$.useEffect(()=>{const V=ne=>{if(Me.current){ne.key==="Escape"&&(ne.preventDefault(),de(!1));return}const Ce=ne.target;if(Ce&&(Ce.tagName==="INPUT"||Ce.tagName==="TEXTAREA"||Ce.tagName==="SELECT"||Ce.isContentEditable)){ne.key==="Escape"&&Ce.blur();return}if(ne.key==="Escape"){S(""),O("");return}const De=yp.find(Ve=>Ve.shortcut===ne.key);if(De&&!ne.metaKey&&!ne.ctrlKey&&!ne.altKey&&r(De.id),(ne.metaKey||ne.ctrlKey)&&ne.key==="Enter"){if(Ot.current==="settings"||Ot.current==="prs"||$e.current)return;ne.preventDefault(),Tn.current()}};return window.addEventListener("keydown",V),()=>window.removeEventListener("keydown",V)},[]);const Xt=$.useMemo(()=>k==="architecture"?(y==null?void 0:y.nodes)??[]:(g==null?void 0:g.nodes)??[],[k,y,g]),Or=$.useMemo(()=>k==="architecture"?(y==null?void 0:y.edges)??[]:(g==null?void 0:g.edges)??[],[k,y,g]),Fr=g!=null&&g.index?`${g.index.counts.nodes} nodes · ${g.index.counts.edges} edges`:y!=null&&y.indexed?`${y.counts.nodes} nodes · ${y.counts.edges} edges`:"Not indexed",Hr=((g==null?void 0:g.findings)||[]).filter(V=>!V.waived);return p.jsxs("div",{className:"app",children:[p.jsx("a",{className:"skip",href:"#main",children:"Skip to content"}),p.jsxs("nav",{className:"rail","data-testid":"rail","aria-label":"Primary",children:[p.jsxs("div",{className:"brand",children:[p.jsx("div",{className:"brand-mark",children:"Loadpath"}),p.jsx("div",{className:"brand-sub",children:"Load-path review"})]}),yp.map(V=>{const ne=V.icon,Ce=t===V.id;return p.jsxs("button",{type:"button","data-testid":V.testId,className:Ce?"nav-item active":"nav-item","aria-current":Ce?"page":void 0,"aria-label":V.label,onClick:()=>r(V.id),children:[p.jsx(ne,{}),p.jsx("span",{children:V.label})]},V.id)}),p.jsxs("div",{className:"theme-pick",children:[p.jsx("label",{htmlFor:"theme-select",children:"Theme"}),p.jsx("select",{id:"theme-select","data-testid":"theme-select",value:T,onChange:V=>Le(V.target.value),children:["dark","light"].map(V=>p.jsx("optgroup",{label:V==="dark"?"Dark":"Light",children:yl.filter(ne=>ne.group===V).map(ne=>p.jsx("option",{value:ne.id,children:ne.label},ne.id))},V))})]}),p.jsxs("div",{className:"rail-foot",children:[p.jsx("div",{className:"muted",role:"status",children:P||Fr}),p.jsxs("div",{className:"kbd-hint",children:[p.jsx("kbd",{children:"1"}),"–",p.jsx("kbd",{children:"5"})," tabs · ",p.jsx("kbd",{children:"Ctrl"}),"+",p.jsx("kbd",{children:"Enter"})," review"]})]})]}),p.jsxs("div",{className:"main",id:"main",children:[P?p.jsxs("div",{className:"progress",role:"status","aria-live":"polite","aria-busy":"true","data-testid":"progress",children:[p.jsx("i",{}),p.jsx("span",{className:"sr-only",children:P})]}):null,p.jsxs("header",{className:"topbar","data-testid":"topbar",children:[v.length>0?p.jsxs("label",{className:"field workspace",children:[p.jsx("span",{children:"Workspace"}),p.jsxs("select",{"data-testid":"workspace-select",value:v.some(V=>V.path===o)?o:"",disabled:!!P,"aria-busy":C,onChange:V=>{V.target.value&&vt(V.target.value)},children:[p.jsx("option",{value:"",children:"Indexed repos…"}),v.map(V=>p.jsxs("option",{value:V.path,children:[V.name,V.indexed?` (${V.counts.nodes})`:""]},V.path))]})]}):null,p.jsxs("label",{className:"field path",children:[p.jsx("span",{children:"Repository"}),p.jsxs("div",{className:"path-row",children:[p.jsx("input",{"data-testid":"repo-path",placeholder:"Local monorepo path",value:o,onChange:V=>{const ne=V.target.value;l(ne),ne.trim()!==be.current&&(be.current="",le(null))},spellCheck:!1}),p.jsx("button",{type:"button",className:"icon-btn","data-testid":"btn-browse-repo","aria-label":"Browse for a local repository",onClick:()=>de(!0),children:p.jsx(_p,{})})]})]}),p.jsxs("label",{className:"field ref",children:[p.jsx("span",{children:"Base"}),p.jsx(mp,{testId:"base-ref",menuTestId:"base-ref-menu",value:a,onChange:V=>yt(V,d),placeholder:"base",refs:K,onNeedRefs:Mt})]}),p.jsxs("label",{className:"field ref",children:[p.jsx("span",{children:"Head"}),p.jsx(mp,{testId:"head-ref",menuTestId:"head-ref-menu",value:d,onChange:V=>yt(a,V),placeholder:"head",refs:K,onNeedRefs:Mt})]}),p.jsxs("div",{className:"topbar-actions",children:[p.jsx("button",{type:"button","data-testid":"btn-init",disabled:!!P,onClick:vn,children:"Draft config"}),p.jsx("button",{type:"button","data-testid":"btn-index",disabled:!!P,onClick:()=>$t(!0),children:"Index"}),p.jsx("button",{type:"button","data-testid":"btn-review",className:"btn primary",disabled:!!P,onClick:Yt,children:"Review"})]})]}),p.jsxs("div",{className:"alerts",children:[N?p.jsxs("div",{className:"error","data-testid":"error",role:"alert",children:[p.jsx("span",{children:N}),p.jsx("button",{type:"button",className:"dismiss",onClick:()=>S(""),"aria-label":"Dismiss error",children:"×"})]}):null,D?p.jsxs("div",{className:"banner","data-testid":"status-note",children:[p.jsx("span",{children:D}),p.jsx("button",{type:"button",className:"dismiss",onClick:()=>O(""),"aria-label":"Dismiss",children:"×"})]}):null,((ar=g==null?void 0:g.index)!=null&&ar.stale||y!=null&&y.stale)&&(t==="review"||t==="architecture")?p.jsx("div",{className:"banner stale","data-testid":"index-stale",children:"Index is stale — files changed since the last extract. Index again before trusting this walk."}):null,((ur=g==null?void 0:g.index)==null?void 0:ur.django_boot)==="failed"||(y==null?void 0:y.django_boot)==="failed"?p.jsx("div",{className:"banner warn","data-testid":"django-boot-failed",children:((Rn=g==null?void 0:g.index)==null?void 0:Rn.django_boot_detail)||(y==null?void 0:y.django_boot_detail)||"django.setup() failed"}):null,(cr=g==null?void 0:g.workspace)!=null&&cr.dirty_overlaps_review&&t==="review"?p.jsxs("div",{className:"banner warn","data-testid":"dirty-tree",children:["Uncommitted files overlap this review: ",(g.workspace.dirty_overlap||[]).slice(0,6).join(", ")]}):null]}),p.jsxs("div",{className:"stage","aria-busy":C,children:[C?p.jsxs("div",{className:"empty workspace-loading","data-testid":"workspace-loading",children:[p.jsx("h2",{children:P}),p.jsx("p",{children:"Fetching the indexed graph for this repository."})]}):null,!C&&t==="review"&&p.jsxs("div",{className:"content","data-testid":"review-layout",children:[p.jsx("aside",{className:"brief","data-testid":"brief",children:g?p.jsx(gE,{review:g,findings:Hr,aiNote:B,busy:!!P,onAskAi:lr,onCopy:Ci,onPost:Dr}):p.jsxs("div",{className:"empty","data-testid":"review-empty",children:[p.jsx("h2",{children:"Trace the force of this diff"}),p.jsx("p",{children:"The graph is the architecture. The brief is where this change travels — not a hunk list."}),p.jsxs("ol",{children:[p.jsx("li",{children:"Point at a Django + React monorepo, or pick an indexed workspace."}),p.jsxs("li",{children:["Index it. Missing ",p.jsx("code",{children:"loadpath.yml"})," is drafted from ",p.jsx("code",{children:"manage.py"})," and"," ",p.jsx("code",{children:"src/features"}),"."]}),p.jsx("li",{children:"Review a git range, or open a pull request so base/head become a three-dot merge-base."})]})]})}),p.jsx("div",{className:"graph-wrap","data-testid":"review-graph",children:g?p.jsx(Ou,{nodes:g.nodes,edges:g.edges}):null})]}),!C&&t==="architecture"&&p.jsxs("div",{className:"content","data-testid":"architecture-panel",children:[p.jsx("aside",{className:"brief","data-testid":"architecture-brief",children:y!=null&&y.indexed?p.jsx(mE,{architecture:y,busy:!!P,onReindex:()=>$t(!1),onReview:Yt}):p.jsx("p",{className:"muted","data-testid":"architecture-empty",children:"Index this repo to build the architecture graph. Review then walks that same graph for a git range — it does not start from a hunk list."})}),p.jsx("div",{className:"graph-wrap","data-testid":"architecture-graph",children:y!=null&&y.indexed?p.jsx(Ou,{nodes:y.nodes,edges:y.edges}):null})]}),!C&&t==="graph"&&p.jsxs("div",{className:"graph-wrap","data-testid":"graph-full",style:{height:"100%"},children:[p.jsxs("div",{className:"graph-modes",children:[p.jsxs("div",{className:"seg","aria-label":"Graph scope",children:[p.jsx("button",{type:"button","aria-pressed":k==="review","data-testid":"graph-mode-review",className:k==="review"?"active":"",onClick:()=>j("review"),children:"This review"}),p.jsx("button",{type:"button","aria-pressed":k==="architecture","data-testid":"graph-mode-architecture",className:k==="architecture"?"active":"",onClick:()=>j("architecture"),children:"Indexed architecture"})]}),p.jsxs("div",{className:"legend","aria-hidden":"true",children:[p.jsxs("span",{children:[p.jsx("i",{})," cheap"]}),p.jsxs("span",{children:[p.jsx("i",{className:"exp"})," expensive"]}),p.jsxs("span",{children:[p.jsx("i",{className:"crit"})," critical"]}),p.jsxs("span",{children:[p.jsx("i",{className:"dash"})," inferred"]})]})]}),Xt.length?p.jsx(Ou,{nodes:Xt,edges:Or}):p.jsx("p",{className:"empty","data-testid":"graph-empty",children:"Index the repo or run a review first. Click a node to inspect it."})]}),!C&&t==="prs"&&p.jsxs("div",{className:"pr-list","data-testid":"pr-list",children:[p.jsxs("div",{className:"pr-toolbar",children:[p.jsxs("label",{className:"field provider",children:[p.jsx("span",{children:"Provider"}),p.jsxs("select",{"data-testid":"pr-provider",value:H,onChange:V=>_t(V.target.value,b,A),children:[p.jsx("option",{value:"github",children:"GitHub"}),p.jsx("option",{value:"bitbucket",children:"Bitbucket"})]})]}),p.jsxs("label",{className:"field",children:[p.jsx("span",{children:"Repository"}),p.jsx("input",{"data-testid":"pr-repo",placeholder:ee.length?"Search your repos":"owner/repo",value:b,onChange:V=>_t(H,V.target.value,A),list:"scm-repos",spellCheck:!1}),p.jsx("datalist",{id:"scm-repos",children:ee.map(V=>p.jsxs("option",{value:V.slug,children:[V.private?"private":"public",V.local_path?" · local":""]},V.slug))})]}),p.jsx("button",{type:"button","data-testid":"btn-refresh-repos",className:"btn",disabled:!!P||!ft(H),onClick:()=>{tt(H)},children:"My repos"}),p.jsx("button",{type:"button","data-testid":"btn-list-prs",className:"btn",disabled:!!P,onClick:$r,children:"List PRs"})]}),ee.length>0?p.jsxs("p",{className:"muted scm-count","data-testid":"scm-repo-count",children:[ee.length," ",H," repositor",ee.length===1?"y":"ies",H==="github"&&U.github_user?` · @${String(U.github_user)}`:"",H==="bitbucket"&&U.bitbucket_user?` · ${String(U.bitbucket_user)}`:""]}):null,te.length===0?p.jsxs("div",{className:"empty","data-testid":"pr-empty",children:[p.jsx("h2",{children:"No pull requests loaded"}),p.jsx("p",{children:"Sign in under Settings (or paste a token), load your repositories, then list open PRs. Reviewing a PR fills base and head from its SHAs."})]}):te.map(V=>p.jsxs("article",{className:"pr","data-testid":`pr-${V.number}`,children:[p.jsxs("h3",{children:["#",V.number," ",V.title]}),p.jsxs("div",{className:"pr-meta muted",children:[p.jsx("span",{className:`chip ${V.draft?"":"open"}`,children:V.draft?"draft":V.state}),p.jsx("span",{children:V.author}),p.jsxs("span",{children:[V.source_branch," → ",V.target_branch]})]}),p.jsxs("div",{className:"pr-actions",children:[p.jsxs("a",{href:V.url,target:"_blank",rel:"noreferrer",children:["Open on ",V.provider]}),p.jsx("button",{type:"button",className:"btn primary","data-testid":`pr-review-${V.number}`,onClick:()=>{yt(V.base_sha||V.target_branch,V.head_sha||V.source_branch),_t(V.provider,V.repo,String(V.number));const ne=ee.find(Ce=>Ce.slug.toLowerCase()===V.repo.toLowerCase());ne!=null&&ne.local_path&&Ye(ne.local_path),r("review")},children:"Review this range"})]})]},`${V.provider}-${V.number}`))]}),!C&&t==="settings"&&ie&&p.jsxs("form",{className:"settings","data-testid":"settings-form",onSubmit:sr,children:[p.jsxs("div",{children:[p.jsx("h1",{children:"Settings"}),p.jsx("p",{className:"muted",children:"Tokens stay on this machine in ~/.loadpath/settings.json. AI runs only on residual uncertainty the graph could not close."})]}),p.jsxs("section",{className:"settings-card",children:[p.jsx("h2",{children:"Appearance"}),p.jsx("p",{className:"muted",children:"Local to this browser. High contrast is a first-class theme, not an afterthought."}),p.jsx("div",{className:"theme-grid","data-testid":"theme-grid",children:yl.map(V=>p.jsxs("button",{type:"button","data-theme":V.id,className:T===V.id?"theme-swatch active":"theme-swatch","data-testid":`theme-${V.id}`,onClick:()=>Le(V.id),children:[p.jsx("div",{className:"swatch-bar","aria-hidden":"true"}),p.jsx("div",{className:"name",children:V.label}),p.jsx("div",{className:"group",children:V.group})]},V.id))})]}),p.jsxs("section",{className:"settings-card",children:[p.jsx("h2",{children:"Source control"}),p.jsx("p",{className:"muted",children:"Sign in with OAuth to list every repository the account can access. Tokens stay in ~/.loadpath/settings.json. A classic PAT still works if you prefer not to register an OAuth app."}),p.jsxs("div",{className:"scm-login","data-testid":"scm-github",children:[p.jsxs("div",{children:[p.jsx("strong",{children:"GitHub"}),p.jsx("p",{className:"muted",children:U.github_token_set?U.github_user?`Signed in as @${String(U.github_user)}`:"Token saved on this machine":"Not connected"})]}),p.jsx("div",{className:"btn-row",children:U.github_token_set?p.jsx("button",{type:"button",className:"btn","data-testid":"btn-github-disconnect",onClick:()=>void In("github"),children:"Disconnect"}):p.jsx("button",{type:"button",className:"btn primary","data-testid":"btn-github-login",disabled:!!he||!U.github_oauth_ready,onClick:()=>void ji(),children:he?"Waiting for GitHub…":"Sign in with GitHub"})})]}),he?p.jsxs("p",{className:"oauth-code","data-testid":"github-user-code",children:["Enter ",p.jsx("code",{children:he.user_code})," at GitHub if the browser did not fill it in."]}):null,U.github_oauth_ready?null:p.jsx("p",{className:"muted",children:"Sign-in needs a GitHub OAuth App with Device Flow enabled. Set LOADPATH_GITHUB_CLIENT_ID or paste the client ID below."}),p.jsx("label",{htmlFor:"github_oauth_client_id",children:"GitHub OAuth client ID"}),p.jsx("input",{id:"github_oauth_client_id",name:"github_oauth_client_id","data-testid":"github-oauth-client-id",placeholder:"Ov23…",defaultValue:String(U.github_oauth_client_id||""),autoComplete:"off"}),p.jsx("label",{htmlFor:"github_token",children:"GitHub token (optional PAT)"}),p.jsx("input",{id:"github_token",name:"github_token",type:"password",placeholder:"ghp_…",autoComplete:"off"}),p.jsxs("div",{className:"scm-login","data-testid":"scm-bitbucket",children:[p.jsxs("div",{children:[p.jsx("strong",{children:"Bitbucket"}),p.jsx("p",{className:"muted",children:U.bitbucket_token_set?U.bitbucket_user?`Signed in as ${String(U.bitbucket_user)}`:"Token saved on this machine":"Not connected"})]}),p.jsx("div",{className:"btn-row",children:U.bitbucket_token_set?p.jsx("button",{type:"button",className:"btn","data-testid":"btn-bitbucket-disconnect",onClick:()=>void In("bitbucket"),children:"Disconnect"}):p.jsx("button",{type:"button",className:"btn primary","data-testid":"btn-bitbucket-login",disabled:ve||!U.bitbucket_oauth_ready,onClick:()=>void or(),children:ve?"Waiting for Bitbucket…":"Sign in with Bitbucket"})})]}),U.bitbucket_oauth_ready?null:p.jsxs("p",{className:"muted",children:["Sign-in needs a Bitbucket OAuth consumer (key + secret). Callback URL:"," ",p.jsx("code",{children:"/api/oauth/bitbucket/callback"})," on this app origin."]}),p.jsx("label",{htmlFor:"bitbucket_oauth_client_id",children:"Bitbucket OAuth key"}),p.jsx("input",{id:"bitbucket_oauth_client_id",name:"bitbucket_oauth_client_id","data-testid":"bitbucket-oauth-client-id",defaultValue:String(U.bitbucket_oauth_client_id||""),autoComplete:"off"}),p.jsx("label",{htmlFor:"bitbucket_oauth_client_secret",children:"Bitbucket OAuth secret"}),p.jsx("input",{id:"bitbucket_oauth_client_secret",name:"bitbucket_oauth_client_secret",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"bitbucket_token",children:"Bitbucket token (optional app password)"}),p.jsx("input",{id:"bitbucket_token",name:"bitbucket_token",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"bitbucket_username",children:"Bitbucket username (app passwords)"}),p.jsx("input",{id:"bitbucket_username",name:"bitbucket_username",defaultValue:String(U.bitbucket_username||"")})]}),p.jsxs("section",{className:"settings-card",children:[p.jsx("h2",{children:"Residual AI"}),p.jsx("label",{htmlFor:"ai_provider",children:"Provider"}),p.jsxs("select",{id:"ai_provider",name:"ai_provider",defaultValue:String(((an=U.ai)==null?void 0:an.provider)||"none"),children:[p.jsx("option",{value:"none",children:"none (graph only)"}),p.jsx("option",{value:"anthropic",children:"Anthropic"}),p.jsx("option",{value:"openai",children:"OpenAI"}),p.jsx("option",{value:"grok",children:"Grok / xAI"}),p.jsx("option",{value:"deepseek",children:"DeepSeek"}),p.jsx("option",{value:"cursor",children:"Cursor-compatible (OpenAI protocol)"}),p.jsx("option",{value:"ollama",children:"Ollama local"})]}),p.jsx("label",{htmlFor:"ai_api_key",children:"API key"}),p.jsx("input",{id:"ai_api_key",name:"ai_api_key",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"ai_model",children:"Model"}),p.jsx("input",{id:"ai_model",name:"ai_model","data-testid":"ai-model",placeholder:"optional override",defaultValue:String(((Ln=U.ai)==null?void 0:Ln.model)||"")}),p.jsx("label",{htmlFor:"ai_base_url",children:"Base URL"}),p.jsx("input",{id:"ai_base_url",name:"ai_base_url","data-testid":"ai-base-url",placeholder:"optional, OpenAI-compatible",defaultValue:String(((un=U.ai)==null?void 0:un.base_url)||"")}),p.jsx("button",{className:"btn primary",type:"submit","data-testid":"btn-save-settings",children:"Save"})]})]})]})]}),fe?p.jsx(cE,{initialPath:o,onClose:()=>de(!1),onSelect:V=>{if($e.current){S("Wait for the current job to finish before switching workspace.");return}de(!1),vt(V)}}):null]})}function gE({review:t,findings:r,aiNote:o,busy:l,onAskAi:a,onCopy:u,onPost:d}){var g,m,y,x,v,_,k,j;const f=[...new Set(t.confidence.reasons||[])];return p.jsxs(p.Fragment,{children:[p.jsxs("div",{className:`merge-box ${t.confidence.level}`,children:[p.jsxs("div",{className:`level ${t.confidence.level}`,children:[t.confidence.level.toUpperCase()," — ",t.title]}),f.length?p.jsx("ul",{className:"reasons",children:f.map(N=>p.jsx("li",{children:N},N))}):null,t.low_risk?p.jsx("span",{className:"chip",children:"low-risk"}):null,t.change_kinds.map(N=>p.jsx("span",{className:"chip",children:vo(N)},N))]}),p.jsxs("div",{className:"metrics",children:[p.jsxs("div",{className:"metric",children:[p.jsxs("div",{className:"n",children:[t.confidence.covered_sinks,"/",t.confidence.sinks]}),p.jsx("div",{className:"l",children:"Sinks tested"})]}),p.jsxs("div",{className:"metric",children:[p.jsx("div",{className:"n",children:r.length}),p.jsx("div",{className:"l",children:"Findings"})]}),p.jsxs("div",{className:"metric",children:[p.jsx("div",{className:"n",children:t.residuals.length}),p.jsx("div",{className:"l",children:"Residuals"})]})]}),p.jsx("pre",{className:"headline",children:t.headline}),t.index?p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Index ",p.jsx("span",{className:"count",children:t.index.counts.nodes})]}),p.jsxs("div",{className:"muted",children:["Walked ",t.index.counts.nodes," nodes / ",t.index.counts.edges," edges",t.index.reindex_skipped?" from an unchanged index":t.index.reindexed?" after an incremental refresh":" from the existing index",t.index.django_boot&&t.index.django_boot!=="off"?` · Django boot ${t.index.django_boot}`:"",(g=t.workspace)!=null&&g.three_dot?" · three-dot range":""]})]}):null,p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Read this ",p.jsx("span",{className:"count",children:t.read_order.length})]}),t.read_order.map((N,S)=>p.jsxs("div",{className:"read-item",children:[p.jsxs("span",{className:"file",children:[S+1,". ",N.path]}),p.jsx("div",{className:"why",children:N.why})]},N.path))]}),p.jsxs("details",{className:"section",children:[p.jsxs("summary",{children:["Clusters ",p.jsx("span",{className:"count",children:t.clusters.length})]}),t.clusters.map(N=>p.jsxs("div",{className:"muted",children:[p.jsx("strong",{children:N.title})," — ",N.files.join(", ")]},N.id))]}),p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Architecture ",p.jsx("span",{className:"count",children:r.length})]}),r.length===0?p.jsx("div",{className:"muted",children:t.architecture_note}):r.map(N=>p.jsxs("div",{className:"finding",children:[p.jsx("span",{className:`chip ${N.severity}`,children:N.severity}),N.message]},N.rule+N.message))]}),p.jsx(mm,{cards:t.deepening}),p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Residual ",p.jsx("span",{className:"count",children:t.residuals.length})]}),p.jsx("p",{className:"muted",children:"AI is only used here, on what the graph could not close."}),t.residuals.map(N=>p.jsx("div",{className:"residual muted",children:N},N))]}),(y=(m=t.evolution)==null?void 0:m.notes)!=null&&y.length||(v=(x=t.evolution)==null?void 0:x.hotspots)!=null&&v.some(N=>N.commits)?p.jsxs("details",{className:"section",children:[p.jsx("summary",{children:"Churn & coupling"}),(((_=t.evolution)==null?void 0:_.notes)||[]).map(N=>p.jsx("div",{className:"muted",children:N},N)),(((k=t.evolution)==null?void 0:k.hotspots)||[]).filter(N=>N.commits).slice(0,6).map(N=>p.jsxs("div",{className:"muted",children:[p.jsx("span",{className:"file",children:N.path})," — ",N.commits," commits, bus factor ",N.bus_factor]},N.path))]}):null,p.jsxs("div",{className:"btn-row",children:[p.jsx("button",{type:"button",className:"btn",disabled:l,onClick:a,children:"Ask configured model"}),p.jsx("button",{type:"button",className:"btn","data-testid":"btn-copy-markdown",onClick:u,children:"Copy markdown"}),p.jsx("button",{type:"button",className:"btn","data-testid":"btn-post-comment",onClick:d,children:"Post to PR"})]}),o?p.jsx("pre",{className:"headline",children:o}):null,p.jsx("div",{className:"kicker",children:"Reviewers"}),p.jsx("div",{className:"muted",children:t.suggested_reviewers.join(", ")||"—"}),(j=t.knowledge_owners)!=null&&j.length?p.jsxs("div",{className:"muted",children:["Knowledge: ",t.knowledge_owners.join(", ")]}):null]})}function mE({architecture:t,busy:r,onReindex:o,onReview:l}){const a=t.findings.filter(u=>!u.waived);return p.jsxs(p.Fragment,{children:[p.jsxs("div",{className:"merge-box high",children:[p.jsxs("div",{className:"level high",children:["INDEXED — ",t.counts.nodes," nodes"]}),p.jsxs("div",{className:"muted",style:{marginTop:8},children:[t.indexed_at?`Last index ${l0(t.indexed_at)}`:"Indexed",t.incremental?" · incremental":" · full",t.stale?" · stale":"",t.django_boot&&t.django_boot!=="off"?` · Django boot ${t.django_boot}`:""]}),p.jsxs("span",{className:"chip",children:[t.counts.edges," edges"]}),t.has_config?p.jsx("span",{className:"chip",children:"loadpath.yml"}):null]}),p.jsxs("details",{className:"section",open:!0,children:[p.jsx("summary",{children:"Bounded contexts"}),Object.values(t.contexts).map(u=>p.jsxs("div",{className:"muted",children:[p.jsx("strong",{children:u.name})," — ",(u.django_apps||[]).join(", ")||"no apps"," ·"," ",(u.owners||[]).join(", ")||"unowned"]},u.name))]}),p.jsxs("details",{className:"section",children:[p.jsxs("summary",{children:["Rules ",p.jsx("span",{className:"count",children:(t.rules||[]).length})]}),(t.rules||[]).map(u=>p.jsx("div",{className:"muted",children:u},u))]}),p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Findings ",p.jsx("span",{className:"count",children:a.length})]}),a.length===0?p.jsx("div",{className:"muted",children:"No architecture rule hits on the full graph."}):a.map(u=>p.jsxs("div",{className:"finding",children:[p.jsx("span",{className:`chip ${u.severity}`,children:u.severity}),u.message]},u.rule+u.message))]}),p.jsx(mm,{cards:t.deepening}),p.jsxs("details",{className:"section",open:!0,children:[p.jsx("summary",{children:"Types"}),p.jsx("table",{className:"type-table",children:p.jsx("tbody",{children:Object.entries(t.type_counts||{}).sort((u,d)=>d[1]-u[1]).slice(0,12).map(([u,d])=>p.jsxs("tr",{children:[p.jsx("td",{children:vl(u)}),p.jsx("td",{children:d})]},u))})})]}),p.jsxs("div",{className:"btn-row",children:[p.jsx("button",{type:"button",className:"btn",disabled:r,onClick:o,"data-testid":"btn-full-reindex",children:"Full reindex"}),p.jsx("button",{type:"button",className:"btn primary",disabled:r,onClick:l,children:"Review against this index"})]})]})}function mm({cards:t}){const r=t||[];return r.length?p.jsxs("details",{className:"section",open:!0,"data-testid":"deepening-list",children:[p.jsxs("summary",{children:["Depth ",p.jsx("span",{className:"count",children:r.length})]}),p.jsx("p",{className:"muted",children:"Deepening opportunities: more behaviour behind a smaller interface, at a real seam."}),r.map(o=>p.jsxs("div",{className:"finding","data-testid":"deepening-card",children:[p.jsx("span",{className:`chip ${o.strength}`,children:s0(o.strength)}),o.top?p.jsx("span",{className:"chip",children:"top"}):null,p.jsx("strong",{children:o.title}),p.jsx("div",{className:"why",children:o.message}),o.deletion_test?p.jsxs("div",{className:"muted",children:["Deletion test: ",o.deletion_test]}):null,o.before&&o.after?p.jsxs("div",{className:"muted",children:[o.before," → ",o.after]}):null]},o.rule+o.title))]}):null}gm(pm());r0.createRoot(document.getElementById("root")).render(p.jsx($.StrictMode,{children:p.jsx(pE,{})}));export{zk as L,xE as a,yE as c,p as j,vE as l,$ as r,vl as t}; diff --git a/src/loadpath/static/assets/index-B1geo4g4.css b/src/loadpath/static/assets/index-CJdGI30d.css similarity index 64% rename from src/loadpath/static/assets/index-B1geo4g4.css rename to src/loadpath/static/assets/index-CJdGI30d.css index bd35962..7d82260 100644 --- a/src/loadpath/static/assets/index-B1geo4g4.css +++ b/src/loadpath/static/assets/index-CJdGI30d.css @@ -1 +1 @@ -.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}:root,[data-theme=obsidian]{--bg: #070b10;--bg-2: #0d141c;--surface: #121a24;--line: #1e2c3c;--ink: #e7eef6;--muted: #8b9bb0;--high: #2a9d8f;--medium: #e9c46a;--low: #e76f51;--critical: #e85d04;--accent: #4cc9f0;--rail-from: #0b1219;--rail-to: #070b10;--rail-active: #15202c;--btn: #173044;--btn-line: #24506c;--btn-primary: #134e4a;--btn-primary-line: #2a9d8f;--node-bg: #101822;--node-line: #2a3d52;--graph-bg: #070b10;--graph-grid: rgba(42, 80, 120, .09);--edge-cheap: #4a5568;--edge-expensive: #f4a261;--edge-critical: #e85d04;--shadow: rgba(76, 201, 240, .08)}[data-theme=nord]{--bg: #2e3440;--bg-2: #3b4252;--surface: #434c5e;--line: #4c566a;--ink: #eceff4;--muted: #d8dee9;--high: #a3be8c;--medium: #ebcb8b;--low: #bf616a;--critical: #d08770;--accent: #88c0d0;--rail-from: #3b4252;--rail-to: #2e3440;--rail-active: #4c566a;--btn: #434c5e;--btn-line: #81a1c1;--btn-primary: #5e81ac;--btn-primary-line: #88c0d0;--node-bg: #3b4252;--node-line: #81a1c1;--graph-bg: #2e3440;--graph-grid: rgba(136, 192, 208, .12);--edge-cheap: #4c566a;--edge-expensive: #d08770;--edge-critical: #bf616a;--shadow: rgba(136, 192, 208, .12)}[data-theme=solarized-dark]{--bg: #002b36;--bg-2: #073642;--surface: #0a3944;--line: #16444f;--ink: #eee8d5;--muted: #93a1a1;--high: #859900;--medium: #b58900;--low: #dc322f;--critical: #cb4b16;--accent: #2aa198;--rail-from: #073642;--rail-to: #002b36;--rail-active: #16444f;--btn: #073642;--btn-line: #268bd2;--btn-primary: #0a4a42;--btn-primary-line: #2aa198;--node-bg: #073642;--node-line: #268bd2;--graph-bg: #002b36;--graph-grid: rgba(42, 161, 152, .12);--edge-cheap: #586e75;--edge-expensive: #cb4b16;--edge-critical: #dc322f;--shadow: rgba(42, 161, 152, .12)}[data-theme=forest]{--bg: #0e1510;--bg-2: #152019;--surface: #1b2a20;--line: #2c4334;--ink: #e4f0e6;--muted: #8eaa96;--high: #6ab04c;--medium: #c8a951;--low: #e17055;--critical: #d35400;--accent: #7bed9f;--rail-from: #152019;--rail-to: #0e1510;--rail-active: #1f3326;--btn: #1f3326;--btn-line: #3d6b4f;--btn-primary: #1e4d32;--btn-primary-line: #6ab04c;--node-bg: #16241b;--node-line: #3d6b4f;--graph-bg: #0e1510;--graph-grid: rgba(123, 237, 159, .1);--edge-cheap: #3d6b4f;--edge-expensive: #c8a951;--edge-critical: #d35400;--shadow: rgba(123, 237, 159, .1)}[data-theme=rose]{--bg: #191724;--bg-2: #1f1d2e;--surface: #26233a;--line: #403d52;--ink: #e0def4;--muted: #908caa;--high: #9ccfd8;--medium: #f6c177;--low: #eb6f92;--critical: #eb6f92;--accent: #c4a7e7;--rail-from: #1f1d2e;--rail-to: #191724;--rail-active: #26233a;--btn: #26233a;--btn-line: #c4a7e7;--btn-primary: #3a2f4d;--btn-primary-line: #c4a7e7;--node-bg: #1f1d2e;--node-line: #524f67;--graph-bg: #191724;--graph-grid: rgba(196, 167, 231, .12);--edge-cheap: #524f67;--edge-expensive: #f6c177;--edge-critical: #eb6f92;--shadow: rgba(196, 167, 231, .12)}[data-theme=amber]{--bg: #120e0a;--bg-2: #1c1610;--surface: #261e16;--line: #3d2f22;--ink: #f4e6d0;--muted: #b59a78;--high: #c4d6a0;--medium: #e9b44c;--low: #d8572a;--critical: #c0392b;--accent: #f0a05a;--rail-from: #1c1610;--rail-to: #120e0a;--rail-active: #2b2218;--btn: #2b2218;--btn-line: #8a5a2b;--btn-primary: #4a3418;--btn-primary-line: #f0a05a;--node-bg: #1c1610;--node-line: #8a5a2b;--graph-bg: #120e0a;--graph-grid: rgba(240, 160, 90, .12);--edge-cheap: #5c4a38;--edge-expensive: #e9b44c;--edge-critical: #d8572a;--shadow: rgba(240, 160, 90, .12)}[data-theme=volcano]{--bg: #14090a;--bg-2: #1e0e10;--surface: #2a1416;--line: #4a2226;--ink: #fde8e4;--muted: #c48b86;--high: #7bed9f;--medium: #f6c90e;--low: #ff6b6b;--critical: #ff3b3b;--accent: #ff7b54;--rail-from: #1e0e10;--rail-to: #14090a;--rail-active: #32181b;--btn: #32181b;--btn-line: #ff7b54;--btn-primary: #5a1f18;--btn-primary-line: #ff7b54;--node-bg: #1e0e10;--node-line: #7a3330;--graph-bg: #14090a;--graph-grid: rgba(255, 123, 84, .12);--edge-cheap: #5a3330;--edge-expensive: #ff7b54;--edge-critical: #ff3b3b;--shadow: rgba(255, 123, 84, .14)}[data-theme=lavender]{--bg: #12101c;--bg-2: #1a1730;--surface: #221e3c;--line: #3b3560;--ink: #efeaff;--muted: #b3a7d6;--high: #80ffdb;--medium: #ffd166;--low: #ff6b9d;--critical: #ff4d6d;--accent: #c77dff;--rail-from: #1a1730;--rail-to: #12101c;--rail-active: #2a2550;--btn: #2a2550;--btn-line: #c77dff;--btn-primary: #3d2a66;--btn-primary-line: #c77dff;--node-bg: #1a1730;--node-line: #5a4d8a;--graph-bg: #12101c;--graph-grid: rgba(199, 125, 255, .12);--edge-cheap: #5a4d8a;--edge-expensive: #ffd166;--edge-critical: #ff4d6d;--shadow: rgba(199, 125, 255, .14)}[data-theme=neon-noir]{--bg: #05060a;--bg-2: #0a0c14;--surface: #10131c;--line: #1e2436;--ink: #f0f4ff;--muted: #8b93b0;--high: #39ff88;--medium: #ffe66d;--low: #ff2d95;--critical: #ff3d5a;--accent: #00f0ff;--rail-from: #0a0c14;--rail-to: #05060a;--rail-active: #151a2a;--btn: #151a2a;--btn-line: #00f0ff;--btn-primary: #063a40;--btn-primary-line: #00f0ff;--node-bg: #0a0c14;--node-line: #2a3550;--graph-bg: #05060a;--graph-grid: rgba(0, 240, 255, .12);--edge-cheap: #3a4560;--edge-expensive: #ff2d95;--edge-critical: #ff3d5a;--shadow: rgba(0, 240, 255, .22)}[data-theme=synthwave]{--bg: #1a0a2e;--bg-2: #240b3d;--surface: #2d1250;--line: #4a1d7a;--ink: #ffe6fb;--muted: #c49ad8;--high: #00f5d4;--medium: #ffd60a;--low: #ff6b9d;--critical: #ff006e;--accent: #ff2bd6;--rail-from: #240b3d;--rail-to: #1a0a2e;--rail-active: #3a1570;--btn: #3a1570;--btn-line: #ff2bd6;--btn-primary: #5a0a4a;--btn-primary-line: #ff2bd6;--node-bg: #240b3d;--node-line: #7b2cbf;--graph-bg: #1a0a2e;--graph-grid: rgba(255, 43, 214, .16);--edge-cheap: #5a3a80;--edge-expensive: #ff9e00;--edge-critical: #ff006e;--shadow: rgba(255, 43, 214, .24)}[data-theme=phosphor]{--bg: #020804;--bg-2: #061208;--surface: #0a1a0e;--line: #163c1e;--ink: #c8ffc8;--muted: #5aaa5a;--high: #39ff14;--medium: #c8f542;--low: #ffb000;--critical: #ff5e00;--accent: #00ff66;--rail-from: #061208;--rail-to: #020804;--rail-active: #0e2414;--btn: #0e2414;--btn-line: #00ff66;--btn-primary: #0a3a18;--btn-primary-line: #00ff66;--node-bg: #061208;--node-line: #1e6a32;--graph-bg: #020804;--graph-grid: rgba(0, 255, 102, .12);--edge-cheap: #1e5a2a;--edge-expensive: #c8f542;--edge-critical: #ff5e00;--shadow: rgba(0, 255, 102, .2)}[data-theme=aurora]{--bg: #071018;--bg-2: #0c1c28;--surface: #122636;--line: #1e3d52;--ink: #e8fff6;--muted: #7eb8a8;--high: #5fffcf;--medium: #ffe566;--low: #ff7eb6;--critical: #ff4d6d;--accent: #7cffb2;--rail-from: #0c1c28;--rail-to: #071018;--rail-active: #163044;--btn: #163044;--btn-line: #7cffb2;--btn-primary: #0e3d3a;--btn-primary-line: #7cffb2;--node-bg: #0c1c28;--node-line: #2a6a78;--graph-bg: #071018;--graph-grid: rgba(124, 255, 178, .12);--edge-cheap: #2a5a68;--edge-expensive: #c9a0ff;--edge-critical: #ff4d6d;--shadow: rgba(124, 255, 178, .18)}[data-theme=biolume]{--bg: #02141c;--bg-2: #042430;--surface: #073040;--line: #0a4a5c;--ink: #e6fffb;--muted: #6eb8b0;--high: #5dffb0;--medium: #ffe066;--low: #ff79c6;--critical: #ff4d6d;--accent: #18e7d4;--rail-from: #042430;--rail-to: #02141c;--rail-active: #0a3848;--btn: #0a3848;--btn-line: #18e7d4;--btn-primary: #0a4a48;--btn-primary-line: #18e7d4;--node-bg: #042430;--node-line: #1a7080;--graph-bg: #02141c;--graph-grid: rgba(24, 231, 212, .12);--edge-cheap: #1a5a68;--edge-expensive: #ff79c6;--edge-critical: #ff4d6d;--shadow: rgba(24, 231, 212, .2)}[data-theme=carbon]{--bg: #0d0d0f;--bg-2: #16161a;--surface: #1e1e24;--line: #33333c;--ink: #f2f2f4;--muted: #9a9aa8;--high: #3dd68c;--medium: #f0c040;--low: #ff5a5a;--critical: #ff2a2a;--accent: #ff2a2a;--rail-from: #16161a;--rail-to: #0d0d0f;--rail-active: #24242c;--btn: #24242c;--btn-line: #ff2a2a;--btn-primary: #4a1212;--btn-primary-line: #ff2a2a;--node-bg: #16161a;--node-line: #4a4a55;--graph-bg: #0d0d0f;--graph-grid: rgba(255, 42, 42, .1);--edge-cheap: #4a4a55;--edge-expensive: #ff8a3d;--edge-critical: #ff2a2a;--shadow: rgba(255, 42, 42, .18)}[data-theme=paper]{--bg: #f6f1e8;--bg-2: #efe6d6;--surface: #fffaf2;--line: #d9cbb6;--ink: #2b241c;--muted: #6f6456;--high: #2a7a4b;--medium: #b5811a;--low: #c0392b;--critical: #a93226;--accent: #1d6a7a;--rail-from: #efe6d6;--rail-to: #e7dcc8;--rail-active: #e2d3bb;--btn: #fffaf2;--btn-line: #c9b79a;--btn-primary: #d7eee0;--btn-primary-line: #2a7a4b;--node-bg: #fffaf2;--node-line: #c9b79a;--graph-bg: #f6f1e8;--graph-grid: rgba(29, 106, 122, .1);--edge-cheap: #b7a48c;--edge-expensive: #c0392b;--edge-critical: #a93226;--shadow: rgba(43, 36, 28, .08)}[data-theme=solarized-light]{--bg: #fdf6e3;--bg-2: #eee8d5;--surface: #f5efdc;--line: #d6cba9;--ink: #657b83;--muted: #93a1a1;--high: #859900;--medium: #b58900;--low: #dc322f;--critical: #cb4b16;--accent: #268bd2;--rail-from: #eee8d5;--rail-to: #e6dfc8;--rail-active: #e0d9c0;--btn: #fdf6e3;--btn-line: #93a1a1;--btn-primary: #e8efc8;--btn-primary-line: #859900;--node-bg: #fdf6e3;--node-line: #93a1a1;--graph-bg: #fdf6e3;--graph-grid: rgba(38, 139, 210, .12);--edge-cheap: #93a1a1;--edge-expensive: #cb4b16;--edge-critical: #dc322f;--shadow: rgba(101, 123, 131, .1)}[data-theme=seafoam]{--bg: #eef7f4;--bg-2: #dff0ea;--surface: #ffffff;--line: #b7d5cc;--ink: #17332c;--muted: #4d7268;--high: #1b8a5a;--medium: #c48a14;--low: #c44536;--critical: #9b2d22;--accent: #1d9a8a;--rail-from: #dff0ea;--rail-to: #cfe6de;--rail-active: #c4ddd4;--btn: #ffffff;--btn-line: #8fbfb2;--btn-primary: #d4f0e4;--btn-primary-line: #1b8a5a;--node-bg: #ffffff;--node-line: #8fbfb2;--graph-bg: #eef7f4;--graph-grid: rgba(29, 154, 138, .12);--edge-cheap: #8fbfb2;--edge-expensive: #c48a14;--edge-critical: #c44536;--shadow: rgba(23, 51, 44, .08)}[data-theme=high-contrast]{--bg: #ffffff;--bg-2: #f2f2f2;--surface: #ffffff;--line: #111111;--ink: #000000;--muted: #222222;--high: #007a33;--medium: #8a5a00;--low: #b00000;--critical: #9b0000;--accent: #0033cc;--rail-from: #f2f2f2;--rail-to: #e6e6e6;--rail-active: #d9d9d9;--btn: #ffffff;--btn-line: #000000;--btn-primary: #d9f2e3;--btn-primary-line: #007a33;--node-bg: #ffffff;--node-line: #000000;--graph-bg: #ffffff;--graph-grid: rgba(0, 0, 0, .12);--edge-cheap: #444444;--edge-expensive: #8a5a00;--edge-critical: #b00000;--shadow: rgba(0, 0, 0, .12)}[data-theme=sakura]{--bg: #fff0f5;--bg-2: #ffe4ee;--surface: #fff7fa;--line: #f5b8cc;--ink: #4a1830;--muted: #a05a78;--high: #1a8a5c;--medium: #c48a14;--low: #d63d6e;--critical: #b01040;--accent: #e84a8a;--rail-from: #ffe4ee;--rail-to: #f8d4e0;--rail-active: #f5c8d8;--btn: #fff7fa;--btn-line: #e89ab0;--btn-primary: #ffd6e6;--btn-primary-line: #e84a8a;--node-bg: #fff7fa;--node-line: #e89ab0;--graph-bg: #fff0f5;--graph-grid: rgba(232, 74, 138, .12);--edge-cheap: #d4a0b0;--edge-expensive: #d63d6e;--edge-critical: #b01040;--shadow: rgba(74, 24, 48, .1)}[data-theme=citrus]{--bg: #fffce8;--bg-2: #fff3b0;--surface: #fffef5;--line: #e8d44a;--ink: #2a2a08;--muted: #6a6a20;--high: #2a8a20;--medium: #d4a000;--low: #e85d04;--critical: #c0392b;--accent: #5aad14;--rail-from: #fff3b0;--rail-to: #ffe98a;--rail-active: #ffe066;--btn: #fffef5;--btn-line: #d4c030;--btn-primary: #e8f5b8;--btn-primary-line: #5aad14;--node-bg: #fffef5;--node-line: #d4c030;--graph-bg: #fffce8;--graph-grid: rgba(90, 173, 20, .14);--edge-cheap: #c4b040;--edge-expensive: #e85d04;--edge-critical: #c0392b;--shadow: rgba(42, 42, 8, .1)}[data-theme=peach]{--bg: #fff3eb;--bg-2: #ffe0cc;--surface: #fffaf6;--line: #f0c4a8;--ink: #3a2218;--muted: #8a5a48;--high: #2a8a5c;--medium: #d48a14;--low: #e85d3a;--critical: #c0392b;--accent: #ff6b35;--rail-from: #ffe0cc;--rail-to: #ffd4b8;--rail-active: #ffc8a8;--btn: #fffaf6;--btn-line: #e8a888;--btn-primary: #ffe0cc;--btn-primary-line: #ff6b35;--node-bg: #fffaf6;--node-line: #e8a888;--graph-bg: #fff3eb;--graph-grid: rgba(255, 107, 53, .12);--edge-cheap: #d4a088;--edge-expensive: #e85d3a;--edge-critical: #c0392b;--shadow: rgba(58, 34, 24, .1)}[data-theme=candy]{--bg: #f4f0ff;--bg-2: #e8dcff;--surface: #fbf8ff;--line: #d4c0f0;--ink: #2a1848;--muted: #6a5890;--high: #1a8a6a;--medium: #c48a14;--low: #e84a8a;--critical: #c01060;--accent: #ff5eb1;--rail-from: #e8dcff;--rail-to: #ddd0ff;--rail-active: #d4c4ff;--btn: #fbf8ff;--btn-line: #c4a8e8;--btn-primary: #ffd6ec;--btn-primary-line: #ff5eb1;--node-bg: #fbf8ff;--node-line: #c4a8e8;--graph-bg: #f4f0ff;--graph-grid: rgba(255, 94, 177, .14);--edge-cheap: #b0a0d0;--edge-expensive: #e84a8a;--edge-critical: #c01060;--shadow: rgba(42, 24, 72, .1)}[data-theme=sky]{--bg: #e8f4ff;--bg-2: #cfe8ff;--surface: #f5faff;--line: #90c8f0;--ink: #0a2848;--muted: #3a6080;--high: #0a8a4a;--medium: #c48a14;--low: #e85d3a;--critical: #c0392b;--accent: #0077ff;--rail-from: #cfe8ff;--rail-to: #b8dcff;--rail-active: #a8d4ff;--btn: #f5faff;--btn-line: #70b0e0;--btn-primary: #cfe8ff;--btn-primary-line: #0077ff;--node-bg: #f5faff;--node-line: #70b0e0;--graph-bg: #e8f4ff;--graph-grid: rgba(0, 119, 255, .12);--edge-cheap: #80b0d0;--edge-expensive: #e85d3a;--edge-critical: #c0392b;--shadow: rgba(10, 40, 72, .1)}[data-theme=coral]{--bg: #fff1ee;--bg-2: #ffddd6;--surface: #fff8f6;--line: #f0b0a4;--ink: #3a1814;--muted: #8a5048;--high: #0d9488;--medium: #d48a14;--low: #e85d4a;--critical: #c0392b;--accent: #0d9488;--rail-from: #ffddd6;--rail-to: #ffd0c6;--rail-active: #ffc4b8;--btn: #fff8f6;--btn-line: #e89888;--btn-primary: #d4f4ee;--btn-primary-line: #0d9488;--node-bg: #fff8f6;--node-line: #e89888;--graph-bg: #fff1ee;--graph-grid: rgba(13, 148, 136, .14);--edge-cheap: #d4a098;--edge-expensive: #e85d4a;--edge-critical: #c0392b;--shadow: rgba(58, 24, 20, .1)}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}:root{--radius: 6px;--radius-lg: 10px;--control-h: 32px;--font: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;--focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);--space: 8px}body{background:var(--bg);color:var(--ink);font-family:var(--font);font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased}button,input,select,textarea{font-family:inherit;font-size:inherit;color:inherit}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,a:focus-visible,summary:focus-visible{outline:none;box-shadow:var(--focus-ring)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.skip{position:absolute;left:12px;top:-40px;z-index:50;background:var(--surface);color:var(--ink);border:1px solid var(--accent);border-radius:var(--radius);padding:8px 12px}.skip:focus{top:12px}.app{display:grid;grid-template-columns:232px 1fr;height:100%}.rail{border-right:1px solid var(--line);background:linear-gradient(180deg,var(--rail-from),var(--rail-to));padding:16px 12px;display:flex;flex-direction:column;gap:2px;min-width:0}.brand{display:flex;flex-direction:column;gap:2px;padding:4px 8px 16px}.brand-mark{font-family:var(--mono);letter-spacing:.16em;font-size:11px;text-transform:uppercase;color:var(--accent);font-weight:600}.brand-sub{font-size:11px;color:var(--muted)}.nav-item{display:flex;align-items:center;gap:10px;background:transparent;border:0;text-align:left;padding:8px 10px;border-radius:var(--radius);color:var(--muted);cursor:pointer;width:100%}.nav-item:hover{background:color-mix(in srgb,var(--rail-active) 70%,transparent);color:var(--ink)}.nav-item.active{background:var(--rail-active);color:var(--ink);font-weight:500}.nav-item svg{flex-shrink:0}.theme-pick{margin-top:14px;display:grid;gap:4px;padding:0 2px}.theme-pick label{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:500}.theme-pick select,.field select,.field input,.topbar input,.topbar select,.settings input,.settings select,.explorer-path input{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:0 10px;height:var(--control-h);width:100%}.rail-foot{margin-top:auto;padding:12px 8px 4px;border-top:1px solid var(--line);display:grid;gap:6px}.kbd-hint{font-size:11px;color:var(--muted)}kbd{font-family:var(--mono);font-size:10px;border:1px solid var(--line);border-radius:4px;padding:0 4px;background:var(--surface)}.main{display:flex;flex-direction:column;min-width:0;min-height:0;position:relative;background:var(--bg)}.progress{position:absolute;top:0;left:0;right:0;height:2px;overflow:hidden;z-index:30;background:color-mix(in srgb,var(--accent) 20%,transparent)}.progress i{display:block;height:100%;width:32%;background:var(--accent);animation:indeterminate 1.1s ease-in-out infinite}@keyframes indeterminate{0%{transform:translate(-120%)}to{transform:translate(400%)}}.topbar{display:flex;gap:10px;align-items:flex-end;padding:10px 16px;border-bottom:1px solid var(--line);background:var(--bg-2);flex-wrap:wrap}.field{display:grid;gap:4px;min-width:0}.field>span{font-size:11px;color:var(--muted);font-weight:500}.field.path{flex:1;min-width:180px}.field.ref{width:188px;flex:0 0 188px}.field.workspace{width:180px;flex:0 0 180px}.path-row,.combo-row{display:flex;min-width:0}.path-row input,.combo-row input{flex:1;min-width:0}.combo{position:relative;min-width:0}.combo-row input{border-top-right-radius:0;border-bottom-right-radius:0}.topbar .icon-btn{width:var(--control-h);padding:0;flex:0 0 var(--control-h)}.combo-toggle{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0}.combo-menu{position:absolute;top:calc(100% + 4px);right:0;left:auto;min-width:340px;max-width:min(480px,70vw);max-height:360px;overflow:auto;z-index:40;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:0 12px 32px var(--shadow);padding:6px 0}.combo-heading{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600;padding:8px 12px 4px}.combo-menu .combo-option{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;background:transparent;border:0;border-radius:0;height:auto;padding:6px 12px;color:var(--ink);cursor:pointer}.combo-menu .combo-option:hover,.combo-menu .combo-option.active{background:var(--rail-active)}.combo-label{font-family:var(--mono);font-size:12px}.combo-detail{color:var(--muted);font-size:12px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.combo-empty{padding:10px 12px}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;background:color-mix(in srgb,var(--bg) 72%,transparent);display:grid;place-items:center;padding:24px}.modal{width:min(720px,100%);max-height:min(640px,90vh);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:0 16px 48px var(--shadow);display:flex;flex-direction:column;overflow:hidden}.modal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 16px 10px;border-bottom:1px solid var(--line)}.modal-head h2{font-size:15px}.modal-head .muted{margin:4px 0 0}.explorer-path{display:flex;gap:8px;padding:12px 16px;border-bottom:1px solid var(--line)}.explorer-path input{flex:1;min-width:0}.explorer-list{flex:1;overflow:auto;padding:8px;min-height:220px}.explorer-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:transparent;border:0;border-radius:var(--radius);padding:8px 10px;color:var(--ink);cursor:pointer;height:auto}.explorer-row:hover,.explorer-row.active{background:var(--rail-active)}.explorer-name{min-width:0;overflow:hidden;text-overflow:ellipsis}.git-badge{margin-left:auto;margin-bottom:0}.explorer-empty{padding:18px 10px}.modal-foot{display:flex;align-items:center;gap:12px;padding:12px 16px;border-top:1px solid var(--line)}.explorer-current{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:12px}.topbar input,.topbar select{min-width:0}.topbar-actions{display:flex;gap:8px;margin-left:auto;align-items:center;padding-bottom:0}.btn,.topbar button{background:var(--btn);border:1px solid var(--btn-line);border-radius:var(--radius);height:var(--control-h);padding:0 12px;cursor:pointer;font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;white-space:nowrap}.btn:hover,.topbar button:hover{filter:brightness(1.08)}.btn:disabled,.topbar button:disabled{opacity:.55;cursor:not-allowed;filter:none}.btn.primary{background:var(--btn-primary);border-color:var(--btn-primary-line)}.btn.ghost{background:transparent}.alerts{display:grid;gap:8px;padding:10px 16px 0}.alerts:empty{display:none;padding:0}.stage{flex:1;min-height:0;display:flex;flex-direction:column}.content{flex:1;min-height:0;display:grid;grid-template-columns:minmax(340px,420px) 1fr}.brief{overflow:auto;border-right:1px solid var(--line);padding:16px 18px 24px;background:radial-gradient(circle at 0 0,color-mix(in srgb,var(--accent) 8%,transparent),transparent 42%),var(--bg)}.graph-wrap{position:relative;min-height:0;display:flex;flex-direction:column}.impact-graph{flex:1;min-height:0;position:relative;display:flex;flex-direction:column}.graph-toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--bg-2)}.graph-count{margin-left:auto;font-size:11px}.chip-btn{background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;color:var(--muted);cursor:pointer;height:26px}.chip-btn:hover{color:var(--ink)}.chip-btn.active{background:var(--rail-active);color:var(--ink)}.chip-btn:disabled{opacity:.45;cursor:not-allowed}.graph-stage{flex:1;min-height:0;position:relative;display:flex;flex-direction:column}.graph-stage .react-flow,.graph-3d,.graph-3d-host,.graph-3d-canvas-host{flex:1;width:100%;height:100%;min-height:280px}.graph-3d{position:relative;background:var(--graph-bg);display:flex;flex-direction:column}.graph-3d-host{position:relative;min-height:0;display:flex;flex-direction:column}.graph-3d-canvas-host{position:relative;min-height:0;background:var(--graph-bg)}.graph-3d canvas{display:block;width:100%;height:100%}.graph-3d-tip{position:absolute;pointer-events:none;z-index:2;max-width:320px;padding:6px 8px;border-radius:var(--radius);background:var(--surface);border:1px solid var(--line);color:var(--ink);font-size:11px;line-height:1.35;box-shadow:0 8px 24px var(--shadow)}.graph-3d-tip .t{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.graph-3d-tip .n{font-weight:600}.graph-3d-hint{position:absolute;left:10px;bottom:10px;z-index:2;margin:0;font-size:11px;color:var(--muted);max-width:min(420px,calc(100% - 24px));pointer-events:none}.graph-wrap .react-flow{background-color:var(--graph-bg);background-image:linear-gradient(var(--graph-grid) 1px,transparent 1px),linear-gradient(90deg,var(--graph-grid) 1px,transparent 1px);background-size:24px 24px}.react-flow__minimap{background:var(--graph-bg)!important;border:1px solid var(--line)!important;border-radius:var(--radius);overflow:hidden;box-shadow:0 8px 24px var(--shadow)}.react-flow__minimap-node{fill:var(--muted);stroke:none}.react-flow__minimap-node.selected{fill:var(--accent)}.react-flow__minimap-mask{fill:#00000073!important;stroke:var(--accent)!important}.react-flow__controls{box-shadow:none!important}.react-flow__controls-button{background:var(--surface)!important;border-bottom:1px solid var(--line)!important;fill:var(--ink)!important}h1{font-size:18px;margin:0 0 6px;font-weight:600}h2{font-size:13px;margin:0;font-weight:600}.merge-box{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-lg);padding:12px 14px;margin-bottom:12px}.merge-box.high{border-color:color-mix(in srgb,var(--high) 55%,var(--line))}.merge-box.medium{border-color:color-mix(in srgb,var(--medium) 55%,var(--line))}.merge-box.low{border-color:color-mix(in srgb,var(--low) 55%,var(--line))}.level{font-family:var(--mono);font-weight:600;font-size:14px}.level.high{color:var(--high)}.level.medium{color:var(--medium)}.level.low{color:var(--low)}.merge-title{margin-top:4px;font-size:13px;color:var(--ink)}.reasons{margin:10px 0 0;padding:0 0 0 18px;color:var(--muted)}.reasons li{margin:0 0 4px}.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:0 0 14px}.metric{border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);padding:8px 10px}.metric .n{font-family:var(--mono);font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}.metric .l{font-size:11px;color:var(--muted);margin-top:2px}.section{border-top:1px solid var(--line);padding:8px 0 4px}.section>summary{cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.07em;font-weight:600;padding:6px 0}.section>summary::-webkit-details-marker{display:none}.section>summary .count{font-family:var(--mono);letter-spacing:0;text-transform:none;border:1px solid var(--line);border-radius:999px;padding:0 7px;height:18px;display:inline-flex;align-items:center;font-size:11px}.kicker{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin:16px 0 6px;font-weight:600}.chip{display:inline-flex;align-items:center;font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--line);margin:0 6px 6px 0;color:var(--muted);background:var(--surface)}.chip.blocker{color:var(--low);border-color:var(--low)}.chip.warning{color:var(--medium);border-color:var(--medium)}.chip.strong{color:var(--low);border-color:var(--low)}.chip.worth_exploring{color:var(--medium);border-color:var(--medium)}.chip.speculative{color:var(--muted);border-color:var(--line)}.chip.open{color:var(--high);border-color:color-mix(in srgb,var(--high) 50%,var(--line))}.file{font-family:var(--mono);font-size:12px;color:var(--accent)}.read-item,.finding,.residual{padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--line) 70%,transparent)}.read-item:last-child,.finding:last-child{border-bottom:0}.read-item .why{color:var(--muted);font-size:12px;margin-top:2px}.muted{color:var(--muted);font-size:13px;line-height:1.45}.error{color:var(--low);padding:8px 12px;border:1px solid var(--low);background:color-mix(in srgb,var(--low) 10%,var(--surface));border-radius:var(--radius);display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.banner{padding:8px 12px;border-radius:var(--radius);border:1px solid var(--line);background:var(--surface);font-size:13px;line-height:1.45;display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.banner.warn{border-color:var(--medium);color:var(--medium)}.banner.stale{border-color:var(--low);color:var(--low)}.banner .dismiss{background:transparent;border:0;color:inherit;cursor:pointer;height:auto;padding:0 2px;opacity:.7}.empty{padding:24px 8px;color:var(--muted);font-size:13px;line-height:1.55}.workspace-loading{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:220px;padding:48px 16px}.empty h2{font-size:16px;color:var(--ink);margin-bottom:8px}.empty ol{margin:12px 0 0 18px;padding:0}.empty code,code{font-family:var(--mono);font-size:12px;color:var(--accent)}.btn-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.pr-list{padding:16px;overflow:auto}.pr-toolbar{display:flex;gap:8px;align-items:flex-end;margin-bottom:14px}.pr-toolbar .field{flex:1}.pr-toolbar .field.provider{flex:0 0 140px}.scm-count{margin:0 0 12px;font-size:12px}.scm-login{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:10px 0;border-top:1px solid var(--line)}.scm-login:first-of-type{border-top:0;padding-top:0}.scm-login .btn-row{margin-top:0}.scm-login p{margin:2px 0 0}.oauth-code{font-size:13px;margin:0}.oauth-code code{font-size:14px;letter-spacing:.08em}.pr{border:1px solid var(--line);background:var(--surface);padding:12px 14px;border-radius:var(--radius-lg);margin-bottom:10px}.pr h3{margin:0 0 4px;font-size:14px;font-weight:600}.pr-meta{display:flex;flex-wrap:wrap;gap:8px 12px;align-items:center;margin:6px 0 10px}.pr-actions{display:flex;gap:8px;align-items:center}.settings{padding:24px;max-width:760px;overflow:auto;display:grid;gap:16px}.settings-card{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-lg);padding:16px;display:grid;gap:8px}.settings-card h2{font-size:14px;margin-bottom:2px}.settings label{font-size:12px;color:var(--muted);font-weight:500}.theme-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px}.theme-swatch{border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:var(--radius);padding:10px;text-align:left;cursor:pointer;height:auto;box-shadow:0 1px 8px var(--shadow)}.theme-swatch.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent),0 1px 8px var(--shadow)}.theme-swatch .swatch-bar{height:6px;border-radius:3px;margin-bottom:8px;background:linear-gradient(90deg,var(--accent),var(--high),var(--medium),var(--low))}.theme-swatch .name{font-size:13px;font-weight:600}.theme-swatch .group{font-size:11px;color:var(--muted)}.headline{white-space:pre-wrap;font-family:var(--mono);font-size:12px;color:var(--muted);margin:8px 0 0}.graph-modes{display:flex;gap:8px;align-items:center;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--bg-2)}.seg{display:inline-flex;border:1px solid var(--line);border-radius:999px;padding:2px;background:var(--surface)}.seg button,.graph-modes button{background:transparent;border:0;border-radius:999px;padding:4px 12px;color:var(--muted);cursor:pointer;height:26px}.seg button.active,.graph-modes button.active{background:var(--rail-active);color:var(--ink)}.legend{margin-left:auto;display:flex;gap:12px;color:var(--muted);font-size:11px}.legend i{display:inline-block;width:14px;height:2px;margin-right:6px;vertical-align:middle;background:var(--edge-cheap)}.legend i.exp{background:var(--edge-expensive)}.legend i.crit{background:var(--edge-critical);height:3px}.legend i.dash{border-top:2px dashed var(--muted);background:none;height:0}.inspector{position:absolute;top:12px;right:12px;z-index:5;width:300px;max-width:calc(100% - 24px);max-height:calc(100% - 24px);overflow-x:hidden;overflow-y:auto;overflow-wrap:break-word;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-lg);padding:10px 12px;box-shadow:0 8px 24px var(--shadow);font-size:12px}.inspector .t{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}.inspector .n,.inspector .file,.inspector .muted{overflow-wrap:break-word}.inspector .n{font-weight:600;margin:4px 0}.inspector-head{display:flex;align-items:flex-start;gap:8px}.inspector-roles{display:flex;flex-wrap:wrap;justify-content:flex-end;flex:1;gap:4px}.inspector-chip{font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:1px 6px;border-radius:999px;border:1px solid var(--line);color:var(--muted);white-space:nowrap}.inspector-close{flex:0 0 auto;width:24px;height:24px;padding:0;border:1px solid var(--line);border-radius:var(--radius);background:transparent;color:var(--muted);font-size:16px;line-height:1;cursor:pointer}.inspector-close:hover{color:var(--ink)}.inspector-purpose{margin:6px 0 8px;color:var(--ink);font-size:12px;line-height:1.4}.inspector-layer{margin-top:4px;font-size:11px}.inspector-facts{margin:10px 0 0;padding-top:8px;border-top:1px solid var(--line)}.inspector-fact{display:grid;grid-template-columns:minmax(64px,92px) minmax(0,1fr);gap:8px;padding:3px 0;align-items:start}.inspector-fact dt{color:var(--muted);font-size:11px;margin:0}.inspector-fact dd{margin:0;overflow-wrap:break-word}.inspector-section{margin-top:10px;padding-top:8px;border-top:1px solid var(--line)}.inspector-section h3{margin:0 0 6px;display:flex;align-items:center;justify-content:space-between;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.07em;font-weight:600}.inspector-section .count{font-family:var(--mono);letter-spacing:0;text-transform:none;border:1px solid var(--line);border-radius:999px;padding:0 7px;height:18px;display:inline-flex;align-items:center;font-size:11px}.inspector-section ul{list-style:none;margin:0;padding:0}.inspector-section li{padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--line) 70%,transparent)}.inspector-section li:last-child{border-bottom:0}.inspector-link-name{display:block;font-weight:600;overflow-wrap:break-word}.inspector-link-meta{display:block;color:var(--muted);font-size:11px}.lp-node{padding:8px 10px;border-radius:var(--radius);border:1px solid var(--node-line);background:var(--node-bg);width:208px;max-width:100%;height:64px;box-sizing:border-box;box-shadow:0 0 0 1px var(--shadow);overflow:visible;position:relative}.lp-node .t{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.lp-node .n{font-size:13px;font-weight:600;line-height:1.2;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow-wrap:anywhere}.lp-node.selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.react-flow__node-load .react-flow__handle{width:8px;height:8px;border:none;background:transparent;opacity:0}.type-table{width:100%;border-collapse:collapse;font-size:12px}.type-table td{padding:3px 0}.type-table td:last-child{text-align:right;font-family:var(--mono);font-variant-numeric:tabular-nums;color:var(--muted)}@media(prefers-reduced-motion:reduce){.progress i{animation:none;width:100%}*{scroll-behavior:auto!important}}@media(max-width:960px){.app{grid-template-columns:56px 1fr}.brand-sub,.nav-item span,.theme-pick,.kbd-hint,.rail-foot .muted{display:none}.nav-item{justify-content:center;padding:10px}.content{grid-template-columns:1fr}.brief{border-right:0;border-bottom:1px solid var(--line);max-height:42vh}} +.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__pane.draggable{cursor:grab}.react-flow__pane.dragging{cursor:grabbing}.react-flow__pane.selection{cursor:pointer}.react-flow__viewport{transform-origin:0 0;z-index:2;pointer-events:none}.react-flow__renderer{z-index:4}.react-flow__selection{z-index:6}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible{outline:none}.react-flow__edge-path{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default));stroke-width:var(--xy-edge-stroke-width, var(--xy-edge-stroke-width-default));fill:none}.react-flow__connection-path{stroke:var(--xy-connectionline-stroke, var(--xy-connectionline-stroke-default));stroke-width:var(--xy-connectionline-stroke-width, var(--xy-connectionline-stroke-width-default));fill:none}.react-flow .react-flow__edges{position:absolute}.react-flow .react-flow__edges svg{overflow:visible;position:absolute;pointer-events:none}.react-flow__edge{pointer-events:visibleStroke}.react-flow__edge.selectable{cursor:pointer}.react-flow__edge.animated path{stroke-dasharray:5;animation:dashdraw .5s linear infinite}.react-flow__edge.animated path.react-flow__edge-interaction{stroke-dasharray:none;animation:none}.react-flow__edge.inactive{pointer-events:none}.react-flow__edge.selected,.react-flow__edge:focus,.react-flow__edge:focus-visible{outline:none}.react-flow__edge.selected .react-flow__edge-path,.react-flow__edge.selectable:focus .react-flow__edge-path,.react-flow__edge.selectable:focus-visible .react-flow__edge-path{stroke:var(--xy-edge-stroke-selected, var(--xy-edge-stroke-selected-default))}.react-flow__edge-textwrapper{pointer-events:all}.react-flow__edge .react-flow__edge-text{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__arrowhead polyline{stroke:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__arrowhead polyline.arrowclosed{fill:var(--xy-edge-stroke, var(--xy-edge-stroke-default))}.react-flow__connection{pointer-events:none}.react-flow__connection .animated{stroke-dasharray:5;animation:dashdraw .5s linear infinite}svg.react-flow__connectionline{z-index:1001;overflow:visible;position:absolute}.react-flow__nodes{pointer-events:none;transform-origin:0 0}.react-flow__node{position:absolute;-webkit-user-select:none;-moz-user-select:none;user-select:none;pointer-events:all;transform-origin:0 0;box-sizing:border-box;cursor:default}.react-flow__node.selectable{cursor:pointer}.react-flow__node.draggable{cursor:grab;pointer-events:all}.react-flow__node.draggable.dragging{cursor:grabbing}.react-flow__nodesselection{z-index:3;transform-origin:left top;pointer-events:none}.react-flow__nodesselection-rect{position:absolute;pointer-events:all;cursor:grab}.react-flow__handle{position:absolute;pointer-events:none;min-width:5px;min-height:5px;width:6px;height:6px;background-color:var(--xy-handle-background-color, var(--xy-handle-background-color-default));border:1px solid var(--xy-handle-border-color, var(--xy-handle-border-color-default));border-radius:100%}.react-flow__handle.connectingfrom{pointer-events:all}.react-flow__handle.connectionindicator{pointer-events:all;cursor:crosshair}.react-flow__handle-bottom{top:auto;left:50%;bottom:0;transform:translate(-50%,50%)}.react-flow__handle-top{top:0;left:50%;transform:translate(-50%,-50%)}.react-flow__handle-left{top:50%;left:0;transform:translate(-50%,-50%)}.react-flow__handle-right{top:50%;right:0;transform:translate(50%,-50%)}.react-flow__edgeupdater{cursor:move;pointer-events:all}.react-flow__pane.selection .react-flow__panel{pointer-events:none}.react-flow__panel{position:absolute;z-index:5;margin:15px}.react-flow__panel.top{top:0}.react-flow__panel.bottom{bottom:0}.react-flow__panel.top.center,.react-flow__panel.bottom.center{left:50%;transform:translate(-15px) translate(-50%)}.react-flow__panel.left{left:0}.react-flow__panel.right{right:0}.react-flow__panel.left.center,.react-flow__panel.right.center{top:50%;transform:translateY(-15px) translateY(-50%)}.react-flow__attribution{font-size:10px;background:var(--xy-attribution-background-color, var(--xy-attribution-background-color-default));padding:2px 3px;margin:0}.react-flow__attribution a{text-decoration:none;color:#999}@keyframes dashdraw{0%{stroke-dashoffset:10}}.react-flow__edgelabel-renderer{position:absolute;width:100%;height:100%;pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;left:0;top:0}.react-flow__viewport-portal{position:absolute;width:100%;height:100%;left:0;top:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__minimap{background:var( --xy-minimap-background-color-props, var(--xy-minimap-background-color, var(--xy-minimap-background-color-default)) )}.react-flow__minimap-svg{display:block}.react-flow__minimap-mask{fill:var( --xy-minimap-mask-background-color-props, var(--xy-minimap-mask-background-color, var(--xy-minimap-mask-background-color-default)) );stroke:var( --xy-minimap-mask-stroke-color-props, var(--xy-minimap-mask-stroke-color, var(--xy-minimap-mask-stroke-color-default)) );stroke-width:var( --xy-minimap-mask-stroke-width-props, var(--xy-minimap-mask-stroke-width, var(--xy-minimap-mask-stroke-width-default)) )}.react-flow__minimap-node{fill:var( --xy-minimap-node-background-color-props, var(--xy-minimap-node-background-color, var(--xy-minimap-node-background-color-default)) );stroke:var( --xy-minimap-node-stroke-color-props, var(--xy-minimap-node-stroke-color, var(--xy-minimap-node-stroke-color-default)) );stroke-width:var( --xy-minimap-node-stroke-width-props, var(--xy-minimap-node-stroke-width, var(--xy-minimap-node-stroke-width-default)) )}.react-flow__background-pattern.dots{fill:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-dots-color-default)) )}.react-flow__background-pattern.lines{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-lines-color-default)) )}.react-flow__background-pattern.cross{stroke:var( --xy-background-pattern-color-props, var(--xy-background-pattern-color, var(--xy-background-pattern-cross-color-default)) )}.react-flow__controls{display:flex;flex-direction:column;box-shadow:var(--xy-controls-box-shadow, var(--xy-controls-box-shadow-default))}.react-flow__controls.horizontal{flex-direction:row}.react-flow__controls-button{display:flex;justify-content:center;align-items:center;height:26px;width:26px;padding:4px;border:none;background:var(--xy-controls-button-background-color, var(--xy-controls-button-background-color-default));border-bottom:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) );color:var( --xy-controls-button-color-props, var(--xy-controls-button-color, var(--xy-controls-button-color-default)) );cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none}.react-flow__controls-button svg{width:100%;max-width:12px;max-height:12px;fill:currentColor}.react-flow__edge.updating .react-flow__edge-path{stroke:#777}.react-flow__edge-text{font-size:10px}.react-flow__node.selectable:focus,.react-flow__node.selectable:focus-visible{outline:none}.react-flow__node-input,.react-flow__node-default,.react-flow__node-output,.react-flow__node-group{padding:10px;border-radius:var(--xy-node-border-radius, var(--xy-node-border-radius-default));width:150px;font-size:12px;color:var(--xy-node-color, var(--xy-node-color-default));text-align:center;border:var(--xy-node-border, var(--xy-node-border-default));background-color:var(--xy-node-background-color, var(--xy-node-background-color-default))}.react-flow__node-input.selectable:hover,.react-flow__node-default.selectable:hover,.react-flow__node-output.selectable:hover,.react-flow__node-group.selectable:hover{box-shadow:var(--xy-node-boxshadow-hover, var(--xy-node-boxshadow-hover-default))}.react-flow__node-input.selectable.selected,.react-flow__node-input.selectable:focus,.react-flow__node-input.selectable:focus-visible,.react-flow__node-default.selectable.selected,.react-flow__node-default.selectable:focus,.react-flow__node-default.selectable:focus-visible,.react-flow__node-output.selectable.selected,.react-flow__node-output.selectable:focus,.react-flow__node-output.selectable:focus-visible,.react-flow__node-group.selectable.selected,.react-flow__node-group.selectable:focus,.react-flow__node-group.selectable:focus-visible{box-shadow:var(--xy-node-boxshadow-selected, var(--xy-node-boxshadow-selected-default))}.react-flow__node-group{background-color:var(--xy-node-group-background-color, var(--xy-node-group-background-color-default))}.react-flow__nodesselection-rect,.react-flow__selection{background:var(--xy-selection-background-color, var(--xy-selection-background-color-default));border:var(--xy-selection-border, var(--xy-selection-border-default))}.react-flow__nodesselection-rect:focus,.react-flow__nodesselection-rect:focus-visible,.react-flow__selection:focus,.react-flow__selection:focus-visible{outline:none}.react-flow__controls-button:hover{background:var( --xy-controls-button-background-color-hover-props, var(--xy-controls-button-background-color-hover, var(--xy-controls-button-background-color-hover-default)) );color:var( --xy-controls-button-color-hover-props, var(--xy-controls-button-color-hover, var(--xy-controls-button-color-hover-default)) )}.react-flow__controls-button:disabled{pointer-events:none}.react-flow__controls-button:disabled svg{fill-opacity:.4}.react-flow__controls-button:last-child{border-bottom:none}.react-flow__controls.horizontal .react-flow__controls-button{border-bottom:none;border-right:1px solid var( --xy-controls-button-border-color-props, var(--xy-controls-button-border-color, var(--xy-controls-button-border-color-default)) )}.react-flow__controls.horizontal .react-flow__controls-button:last-child{border-right:none}.react-flow__resize-control{position:absolute}.react-flow__resize-control.left,.react-flow__resize-control.right{cursor:ew-resize}.react-flow__resize-control.top,.react-flow__resize-control.bottom{cursor:ns-resize}.react-flow__resize-control.top.left,.react-flow__resize-control.bottom.right{cursor:nwse-resize}.react-flow__resize-control.bottom.left,.react-flow__resize-control.top.right{cursor:nesw-resize}.react-flow__resize-control.handle{width:5px;height:5px;border:1px solid #fff;border-radius:1px;background-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));translate:-50% -50%}.react-flow__resize-control.handle.left{left:0;top:50%}.react-flow__resize-control.handle.right{left:100%;top:50%}.react-flow__resize-control.handle.top{left:50%;top:0}.react-flow__resize-control.handle.bottom{left:50%;top:100%}.react-flow__resize-control.handle.top.left,.react-flow__resize-control.handle.bottom.left{left:0}.react-flow__resize-control.handle.top.right,.react-flow__resize-control.handle.bottom.right{left:100%}.react-flow__resize-control.line{border-color:var(--xy-resize-background-color, var(--xy-resize-background-color-default));border-width:0;border-style:solid}.react-flow__resize-control.line.left,.react-flow__resize-control.line.right{width:1px;transform:translate(-50%);top:0;height:100%}.react-flow__resize-control.line.left{left:0;border-left-width:1px}.react-flow__resize-control.line.right{left:100%;border-right-width:1px}.react-flow__resize-control.line.top,.react-flow__resize-control.line.bottom{height:1px;transform:translateY(-50%);left:0;width:100%}.react-flow__resize-control.line.top{top:0;border-top-width:1px}.react-flow__resize-control.line.bottom{border-bottom-width:1px;top:100%}.react-flow__edge-textbg{fill:var(--xy-edge-label-background-color, var(--xy-edge-label-background-color-default))}.react-flow__edge-text{fill:var(--xy-edge-label-color, var(--xy-edge-label-color-default))}:root,[data-theme=obsidian]{--bg: #070b10;--bg-2: #0d141c;--surface: #121a24;--line: #1e2c3c;--ink: #e7eef6;--muted: #8b9bb0;--high: #2a9d8f;--medium: #e9c46a;--low: #e76f51;--critical: #e85d04;--accent: #4cc9f0;--rail-from: #0b1219;--rail-to: #070b10;--rail-active: #15202c;--btn: #173044;--btn-line: #24506c;--btn-primary: #134e4a;--btn-primary-line: #2a9d8f;--node-bg: #101822;--node-line: #2a3d52;--graph-bg: #070b10;--graph-grid: rgba(42, 80, 120, .09);--edge-cheap: #4a5568;--edge-expensive: #f4a261;--edge-critical: #e85d04;--shadow: rgba(76, 201, 240, .08)}[data-theme=nord]{--bg: #2e3440;--bg-2: #3b4252;--surface: #434c5e;--line: #4c566a;--ink: #eceff4;--muted: #d8dee9;--high: #a3be8c;--medium: #ebcb8b;--low: #bf616a;--critical: #d08770;--accent: #88c0d0;--rail-from: #3b4252;--rail-to: #2e3440;--rail-active: #4c566a;--btn: #434c5e;--btn-line: #81a1c1;--btn-primary: #5e81ac;--btn-primary-line: #88c0d0;--node-bg: #3b4252;--node-line: #81a1c1;--graph-bg: #2e3440;--graph-grid: rgba(136, 192, 208, .12);--edge-cheap: #4c566a;--edge-expensive: #d08770;--edge-critical: #bf616a;--shadow: rgba(136, 192, 208, .12)}[data-theme=solarized-dark]{--bg: #002b36;--bg-2: #073642;--surface: #0a3944;--line: #16444f;--ink: #eee8d5;--muted: #93a1a1;--high: #859900;--medium: #b58900;--low: #dc322f;--critical: #cb4b16;--accent: #2aa198;--rail-from: #073642;--rail-to: #002b36;--rail-active: #16444f;--btn: #073642;--btn-line: #268bd2;--btn-primary: #0a4a42;--btn-primary-line: #2aa198;--node-bg: #073642;--node-line: #268bd2;--graph-bg: #002b36;--graph-grid: rgba(42, 161, 152, .12);--edge-cheap: #586e75;--edge-expensive: #cb4b16;--edge-critical: #dc322f;--shadow: rgba(42, 161, 152, .12)}[data-theme=forest]{--bg: #0e1510;--bg-2: #152019;--surface: #1b2a20;--line: #2c4334;--ink: #e4f0e6;--muted: #8eaa96;--high: #6ab04c;--medium: #c8a951;--low: #e17055;--critical: #d35400;--accent: #7bed9f;--rail-from: #152019;--rail-to: #0e1510;--rail-active: #1f3326;--btn: #1f3326;--btn-line: #3d6b4f;--btn-primary: #1e4d32;--btn-primary-line: #6ab04c;--node-bg: #16241b;--node-line: #3d6b4f;--graph-bg: #0e1510;--graph-grid: rgba(123, 237, 159, .1);--edge-cheap: #3d6b4f;--edge-expensive: #c8a951;--edge-critical: #d35400;--shadow: rgba(123, 237, 159, .1)}[data-theme=rose]{--bg: #191724;--bg-2: #1f1d2e;--surface: #26233a;--line: #403d52;--ink: #e0def4;--muted: #908caa;--high: #9ccfd8;--medium: #f6c177;--low: #eb6f92;--critical: #eb6f92;--accent: #c4a7e7;--rail-from: #1f1d2e;--rail-to: #191724;--rail-active: #26233a;--btn: #26233a;--btn-line: #c4a7e7;--btn-primary: #3a2f4d;--btn-primary-line: #c4a7e7;--node-bg: #1f1d2e;--node-line: #524f67;--graph-bg: #191724;--graph-grid: rgba(196, 167, 231, .12);--edge-cheap: #524f67;--edge-expensive: #f6c177;--edge-critical: #eb6f92;--shadow: rgba(196, 167, 231, .12)}[data-theme=amber]{--bg: #120e0a;--bg-2: #1c1610;--surface: #261e16;--line: #3d2f22;--ink: #f4e6d0;--muted: #b59a78;--high: #c4d6a0;--medium: #e9b44c;--low: #d8572a;--critical: #c0392b;--accent: #f0a05a;--rail-from: #1c1610;--rail-to: #120e0a;--rail-active: #2b2218;--btn: #2b2218;--btn-line: #8a5a2b;--btn-primary: #4a3418;--btn-primary-line: #f0a05a;--node-bg: #1c1610;--node-line: #8a5a2b;--graph-bg: #120e0a;--graph-grid: rgba(240, 160, 90, .12);--edge-cheap: #5c4a38;--edge-expensive: #e9b44c;--edge-critical: #d8572a;--shadow: rgba(240, 160, 90, .12)}[data-theme=volcano]{--bg: #14090a;--bg-2: #1e0e10;--surface: #2a1416;--line: #4a2226;--ink: #fde8e4;--muted: #c48b86;--high: #7bed9f;--medium: #f6c90e;--low: #ff6b6b;--critical: #ff3b3b;--accent: #ff7b54;--rail-from: #1e0e10;--rail-to: #14090a;--rail-active: #32181b;--btn: #32181b;--btn-line: #ff7b54;--btn-primary: #5a1f18;--btn-primary-line: #ff7b54;--node-bg: #1e0e10;--node-line: #7a3330;--graph-bg: #14090a;--graph-grid: rgba(255, 123, 84, .12);--edge-cheap: #5a3330;--edge-expensive: #ff7b54;--edge-critical: #ff3b3b;--shadow: rgba(255, 123, 84, .14)}[data-theme=lavender]{--bg: #12101c;--bg-2: #1a1730;--surface: #221e3c;--line: #3b3560;--ink: #efeaff;--muted: #b3a7d6;--high: #80ffdb;--medium: #ffd166;--low: #ff6b9d;--critical: #ff4d6d;--accent: #c77dff;--rail-from: #1a1730;--rail-to: #12101c;--rail-active: #2a2550;--btn: #2a2550;--btn-line: #c77dff;--btn-primary: #3d2a66;--btn-primary-line: #c77dff;--node-bg: #1a1730;--node-line: #5a4d8a;--graph-bg: #12101c;--graph-grid: rgba(199, 125, 255, .12);--edge-cheap: #5a4d8a;--edge-expensive: #ffd166;--edge-critical: #ff4d6d;--shadow: rgba(199, 125, 255, .14)}[data-theme=neon-noir]{--bg: #05060a;--bg-2: #0a0c14;--surface: #10131c;--line: #1e2436;--ink: #f0f4ff;--muted: #8b93b0;--high: #39ff88;--medium: #ffe66d;--low: #ff2d95;--critical: #ff3d5a;--accent: #00f0ff;--rail-from: #0a0c14;--rail-to: #05060a;--rail-active: #151a2a;--btn: #151a2a;--btn-line: #00f0ff;--btn-primary: #063a40;--btn-primary-line: #00f0ff;--node-bg: #0a0c14;--node-line: #2a3550;--graph-bg: #05060a;--graph-grid: rgba(0, 240, 255, .12);--edge-cheap: #3a4560;--edge-expensive: #ff2d95;--edge-critical: #ff3d5a;--shadow: rgba(0, 240, 255, .22)}[data-theme=synthwave]{--bg: #1a0a2e;--bg-2: #240b3d;--surface: #2d1250;--line: #4a1d7a;--ink: #ffe6fb;--muted: #c49ad8;--high: #00f5d4;--medium: #ffd60a;--low: #ff6b9d;--critical: #ff006e;--accent: #ff2bd6;--rail-from: #240b3d;--rail-to: #1a0a2e;--rail-active: #3a1570;--btn: #3a1570;--btn-line: #ff2bd6;--btn-primary: #5a0a4a;--btn-primary-line: #ff2bd6;--node-bg: #240b3d;--node-line: #7b2cbf;--graph-bg: #1a0a2e;--graph-grid: rgba(255, 43, 214, .16);--edge-cheap: #5a3a80;--edge-expensive: #ff9e00;--edge-critical: #ff006e;--shadow: rgba(255, 43, 214, .24)}[data-theme=phosphor]{--bg: #020804;--bg-2: #061208;--surface: #0a1a0e;--line: #163c1e;--ink: #c8ffc8;--muted: #5aaa5a;--high: #39ff14;--medium: #c8f542;--low: #ffb000;--critical: #ff5e00;--accent: #00ff66;--rail-from: #061208;--rail-to: #020804;--rail-active: #0e2414;--btn: #0e2414;--btn-line: #00ff66;--btn-primary: #0a3a18;--btn-primary-line: #00ff66;--node-bg: #061208;--node-line: #1e6a32;--graph-bg: #020804;--graph-grid: rgba(0, 255, 102, .12);--edge-cheap: #1e5a2a;--edge-expensive: #c8f542;--edge-critical: #ff5e00;--shadow: rgba(0, 255, 102, .2)}[data-theme=aurora]{--bg: #071018;--bg-2: #0c1c28;--surface: #122636;--line: #1e3d52;--ink: #e8fff6;--muted: #7eb8a8;--high: #5fffcf;--medium: #ffe566;--low: #ff7eb6;--critical: #ff4d6d;--accent: #7cffb2;--rail-from: #0c1c28;--rail-to: #071018;--rail-active: #163044;--btn: #163044;--btn-line: #7cffb2;--btn-primary: #0e3d3a;--btn-primary-line: #7cffb2;--node-bg: #0c1c28;--node-line: #2a6a78;--graph-bg: #071018;--graph-grid: rgba(124, 255, 178, .12);--edge-cheap: #2a5a68;--edge-expensive: #c9a0ff;--edge-critical: #ff4d6d;--shadow: rgba(124, 255, 178, .18)}[data-theme=biolume]{--bg: #02141c;--bg-2: #042430;--surface: #073040;--line: #0a4a5c;--ink: #e6fffb;--muted: #6eb8b0;--high: #5dffb0;--medium: #ffe066;--low: #ff79c6;--critical: #ff4d6d;--accent: #18e7d4;--rail-from: #042430;--rail-to: #02141c;--rail-active: #0a3848;--btn: #0a3848;--btn-line: #18e7d4;--btn-primary: #0a4a48;--btn-primary-line: #18e7d4;--node-bg: #042430;--node-line: #1a7080;--graph-bg: #02141c;--graph-grid: rgba(24, 231, 212, .12);--edge-cheap: #1a5a68;--edge-expensive: #ff79c6;--edge-critical: #ff4d6d;--shadow: rgba(24, 231, 212, .2)}[data-theme=carbon]{--bg: #0d0d0f;--bg-2: #16161a;--surface: #1e1e24;--line: #33333c;--ink: #f2f2f4;--muted: #9a9aa8;--high: #3dd68c;--medium: #f0c040;--low: #ff5a5a;--critical: #ff2a2a;--accent: #ff2a2a;--rail-from: #16161a;--rail-to: #0d0d0f;--rail-active: #24242c;--btn: #24242c;--btn-line: #ff2a2a;--btn-primary: #4a1212;--btn-primary-line: #ff2a2a;--node-bg: #16161a;--node-line: #4a4a55;--graph-bg: #0d0d0f;--graph-grid: rgba(255, 42, 42, .1);--edge-cheap: #4a4a55;--edge-expensive: #ff8a3d;--edge-critical: #ff2a2a;--shadow: rgba(255, 42, 42, .18)}[data-theme=paper]{--bg: #f6f1e8;--bg-2: #efe6d6;--surface: #fffaf2;--line: #d9cbb6;--ink: #2b241c;--muted: #6f6456;--high: #2a7a4b;--medium: #b5811a;--low: #c0392b;--critical: #a93226;--accent: #1d6a7a;--rail-from: #efe6d6;--rail-to: #e7dcc8;--rail-active: #e2d3bb;--btn: #fffaf2;--btn-line: #c9b79a;--btn-primary: #d7eee0;--btn-primary-line: #2a7a4b;--node-bg: #fffaf2;--node-line: #c9b79a;--graph-bg: #f6f1e8;--graph-grid: rgba(29, 106, 122, .1);--edge-cheap: #b7a48c;--edge-expensive: #c0392b;--edge-critical: #a93226;--shadow: rgba(43, 36, 28, .08)}[data-theme=solarized-light]{--bg: #fdf6e3;--bg-2: #eee8d5;--surface: #f5efdc;--line: #d6cba9;--ink: #657b83;--muted: #93a1a1;--high: #859900;--medium: #b58900;--low: #dc322f;--critical: #cb4b16;--accent: #268bd2;--rail-from: #eee8d5;--rail-to: #e6dfc8;--rail-active: #e0d9c0;--btn: #fdf6e3;--btn-line: #93a1a1;--btn-primary: #e8efc8;--btn-primary-line: #859900;--node-bg: #fdf6e3;--node-line: #93a1a1;--graph-bg: #fdf6e3;--graph-grid: rgba(38, 139, 210, .12);--edge-cheap: #93a1a1;--edge-expensive: #cb4b16;--edge-critical: #dc322f;--shadow: rgba(101, 123, 131, .1)}[data-theme=seafoam]{--bg: #eef7f4;--bg-2: #dff0ea;--surface: #ffffff;--line: #b7d5cc;--ink: #17332c;--muted: #4d7268;--high: #1b8a5a;--medium: #c48a14;--low: #c44536;--critical: #9b2d22;--accent: #1d9a8a;--rail-from: #dff0ea;--rail-to: #cfe6de;--rail-active: #c4ddd4;--btn: #ffffff;--btn-line: #8fbfb2;--btn-primary: #d4f0e4;--btn-primary-line: #1b8a5a;--node-bg: #ffffff;--node-line: #8fbfb2;--graph-bg: #eef7f4;--graph-grid: rgba(29, 154, 138, .12);--edge-cheap: #8fbfb2;--edge-expensive: #c48a14;--edge-critical: #c44536;--shadow: rgba(23, 51, 44, .08)}[data-theme=high-contrast]{--bg: #ffffff;--bg-2: #f2f2f2;--surface: #ffffff;--line: #111111;--ink: #000000;--muted: #222222;--high: #007a33;--medium: #8a5a00;--low: #b00000;--critical: #9b0000;--accent: #0033cc;--rail-from: #f2f2f2;--rail-to: #e6e6e6;--rail-active: #d9d9d9;--btn: #ffffff;--btn-line: #000000;--btn-primary: #d9f2e3;--btn-primary-line: #007a33;--node-bg: #ffffff;--node-line: #000000;--graph-bg: #ffffff;--graph-grid: rgba(0, 0, 0, .12);--edge-cheap: #444444;--edge-expensive: #8a5a00;--edge-critical: #b00000;--shadow: rgba(0, 0, 0, .12)}[data-theme=sakura]{--bg: #fff0f5;--bg-2: #ffe4ee;--surface: #fff7fa;--line: #f5b8cc;--ink: #4a1830;--muted: #a05a78;--high: #1a8a5c;--medium: #c48a14;--low: #d63d6e;--critical: #b01040;--accent: #e84a8a;--rail-from: #ffe4ee;--rail-to: #f8d4e0;--rail-active: #f5c8d8;--btn: #fff7fa;--btn-line: #e89ab0;--btn-primary: #ffd6e6;--btn-primary-line: #e84a8a;--node-bg: #fff7fa;--node-line: #e89ab0;--graph-bg: #fff0f5;--graph-grid: rgba(232, 74, 138, .12);--edge-cheap: #d4a0b0;--edge-expensive: #d63d6e;--edge-critical: #b01040;--shadow: rgba(74, 24, 48, .1)}[data-theme=citrus]{--bg: #fffce8;--bg-2: #fff3b0;--surface: #fffef5;--line: #e8d44a;--ink: #2a2a08;--muted: #6a6a20;--high: #2a8a20;--medium: #d4a000;--low: #e85d04;--critical: #c0392b;--accent: #5aad14;--rail-from: #fff3b0;--rail-to: #ffe98a;--rail-active: #ffe066;--btn: #fffef5;--btn-line: #d4c030;--btn-primary: #e8f5b8;--btn-primary-line: #5aad14;--node-bg: #fffef5;--node-line: #d4c030;--graph-bg: #fffce8;--graph-grid: rgba(90, 173, 20, .14);--edge-cheap: #c4b040;--edge-expensive: #e85d04;--edge-critical: #c0392b;--shadow: rgba(42, 42, 8, .1)}[data-theme=peach]{--bg: #fff3eb;--bg-2: #ffe0cc;--surface: #fffaf6;--line: #f0c4a8;--ink: #3a2218;--muted: #8a5a48;--high: #2a8a5c;--medium: #d48a14;--low: #e85d3a;--critical: #c0392b;--accent: #ff6b35;--rail-from: #ffe0cc;--rail-to: #ffd4b8;--rail-active: #ffc8a8;--btn: #fffaf6;--btn-line: #e8a888;--btn-primary: #ffe0cc;--btn-primary-line: #ff6b35;--node-bg: #fffaf6;--node-line: #e8a888;--graph-bg: #fff3eb;--graph-grid: rgba(255, 107, 53, .12);--edge-cheap: #d4a088;--edge-expensive: #e85d3a;--edge-critical: #c0392b;--shadow: rgba(58, 34, 24, .1)}[data-theme=candy]{--bg: #f4f0ff;--bg-2: #e8dcff;--surface: #fbf8ff;--line: #d4c0f0;--ink: #2a1848;--muted: #6a5890;--high: #1a8a6a;--medium: #c48a14;--low: #e84a8a;--critical: #c01060;--accent: #ff5eb1;--rail-from: #e8dcff;--rail-to: #ddd0ff;--rail-active: #d4c4ff;--btn: #fbf8ff;--btn-line: #c4a8e8;--btn-primary: #ffd6ec;--btn-primary-line: #ff5eb1;--node-bg: #fbf8ff;--node-line: #c4a8e8;--graph-bg: #f4f0ff;--graph-grid: rgba(255, 94, 177, .14);--edge-cheap: #b0a0d0;--edge-expensive: #e84a8a;--edge-critical: #c01060;--shadow: rgba(42, 24, 72, .1)}[data-theme=sky]{--bg: #e8f4ff;--bg-2: #cfe8ff;--surface: #f5faff;--line: #90c8f0;--ink: #0a2848;--muted: #3a6080;--high: #0a8a4a;--medium: #c48a14;--low: #e85d3a;--critical: #c0392b;--accent: #0077ff;--rail-from: #cfe8ff;--rail-to: #b8dcff;--rail-active: #a8d4ff;--btn: #f5faff;--btn-line: #70b0e0;--btn-primary: #cfe8ff;--btn-primary-line: #0077ff;--node-bg: #f5faff;--node-line: #70b0e0;--graph-bg: #e8f4ff;--graph-grid: rgba(0, 119, 255, .12);--edge-cheap: #80b0d0;--edge-expensive: #e85d3a;--edge-critical: #c0392b;--shadow: rgba(10, 40, 72, .1)}[data-theme=coral]{--bg: #fff1ee;--bg-2: #ffddd6;--surface: #fff8f6;--line: #f0b0a4;--ink: #3a1814;--muted: #8a5048;--high: #0d9488;--medium: #d48a14;--low: #e85d4a;--critical: #c0392b;--accent: #0d9488;--rail-from: #ffddd6;--rail-to: #ffd0c6;--rail-active: #ffc4b8;--btn: #fff8f6;--btn-line: #e89888;--btn-primary: #d4f4ee;--btn-primary-line: #0d9488;--node-bg: #fff8f6;--node-line: #e89888;--graph-bg: #fff1ee;--graph-grid: rgba(13, 148, 136, .14);--edge-cheap: #d4a098;--edge-expensive: #e85d4a;--edge-critical: #c0392b;--shadow: rgba(58, 24, 20, .1)}*{box-sizing:border-box}html,body,#root{height:100%;margin:0}:root{--radius: 6px;--radius-lg: 10px;--control-h: 32px;--font: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;--mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace;--focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--accent);--space: 8px}body{background:var(--bg);color:var(--ink);font-family:var(--font);font-size:13px;line-height:1.45;-webkit-font-smoothing:antialiased}button,input,select,textarea{font-family:inherit;font-size:inherit;color:inherit}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,a:focus-visible,summary:focus-visible{outline:none;box-shadow:var(--focus-ring)}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.skip{position:absolute;left:12px;top:-40px;z-index:50;background:var(--surface);color:var(--ink);border:1px solid var(--accent);border-radius:var(--radius);padding:8px 12px}.skip:focus{top:12px}.app{display:grid;grid-template-columns:232px 1fr;height:100%}.rail{border-right:1px solid var(--line);background:linear-gradient(180deg,var(--rail-from),var(--rail-to));padding:16px 12px;display:flex;flex-direction:column;gap:2px;min-width:0}.brand{display:flex;flex-direction:column;gap:2px;padding:4px 8px 16px}.brand-mark{font-family:var(--mono);letter-spacing:.16em;font-size:11px;text-transform:uppercase;color:var(--accent);font-weight:600}.brand-sub{font-size:11px;color:var(--muted)}.nav-item{display:flex;align-items:center;gap:10px;background:transparent;border:0;text-align:left;padding:8px 10px;border-radius:var(--radius);color:var(--muted);cursor:pointer;width:100%}.nav-item:hover{background:color-mix(in srgb,var(--rail-active) 70%,transparent);color:var(--ink)}.nav-item.active{background:var(--rail-active);color:var(--ink);font-weight:500}.nav-item svg{flex-shrink:0}.theme-pick{margin-top:14px;display:grid;gap:4px;padding:0 2px}.theme-pick label{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);font-weight:500}.theme-pick select,.field select,.field input,.topbar input,.topbar select,.settings input,.settings select,.explorer-path input{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);padding:0 10px;height:var(--control-h);width:100%}.rail-foot{margin-top:auto;padding:12px 8px 4px;border-top:1px solid var(--line);display:grid;gap:6px}.kbd-hint{font-size:11px;color:var(--muted)}kbd{font-family:var(--mono);font-size:10px;border:1px solid var(--line);border-radius:4px;padding:0 4px;background:var(--surface)}.main{display:flex;flex-direction:column;min-width:0;min-height:0;position:relative;background:var(--bg)}.progress{position:absolute;top:0;left:0;right:0;height:2px;overflow:hidden;z-index:30;background:color-mix(in srgb,var(--accent) 20%,transparent)}.progress i{display:block;height:100%;width:32%;background:var(--accent);animation:indeterminate 1.1s ease-in-out infinite}.progress.determinate i{width:0;animation:none;transform:none;transition:width .2s ease-out}@keyframes indeterminate{0%{transform:translate(-120%)}to{transform:translate(400%)}}.topbar{display:flex;gap:10px;align-items:flex-end;padding:10px 16px;border-bottom:1px solid var(--line);background:var(--bg-2);flex-wrap:wrap}.field{display:grid;gap:4px;min-width:0}.field>span{font-size:11px;color:var(--muted);font-weight:500}.field.path{flex:1;min-width:180px}.field.ref{width:188px;flex:0 0 188px}.field.workspace{width:180px;flex:0 0 180px}.path-row,.combo-row{display:flex;min-width:0}.path-row input,.combo-row input{flex:1;min-width:0}.combo{position:relative;min-width:0}.combo-row input{border-top-right-radius:0;border-bottom-right-radius:0}.topbar .icon-btn{width:var(--control-h);padding:0;flex:0 0 var(--control-h)}.combo-toggle{border-top-left-radius:0;border-bottom-left-radius:0;border-left:0}.combo-menu{position:absolute;top:calc(100% + 4px);right:0;left:auto;min-width:340px;max-width:min(480px,70vw);max-height:360px;overflow:auto;z-index:40;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:0 12px 32px var(--shadow);padding:6px 0}.combo-heading{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600;padding:8px 12px 4px}.combo-menu .combo-option{display:flex;flex-direction:column;align-items:flex-start;gap:2px;width:100%;text-align:left;background:transparent;border:0;border-radius:0;height:auto;padding:6px 12px;color:var(--ink);cursor:pointer}.combo-menu .combo-option:hover,.combo-menu .combo-option.active{background:var(--rail-active)}.combo-label{font-family:var(--mono);font-size:12px}.combo-detail{color:var(--muted);font-size:12px;line-height:1.35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:100%}.combo-empty{padding:10px 12px}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;background:color-mix(in srgb,var(--bg) 72%,transparent);display:grid;place-items:center;padding:24px}.modal{width:min(720px,100%);max-height:min(640px,90vh);background:var(--bg-2);border:1px solid var(--line);border-radius:var(--radius-lg);box-shadow:0 16px 48px var(--shadow);display:flex;flex-direction:column;overflow:hidden}.modal-head{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 16px 10px;border-bottom:1px solid var(--line)}.modal-head h2{font-size:15px}.modal-head .muted{margin:4px 0 0}.explorer-path{display:flex;gap:8px;padding:12px 16px;border-bottom:1px solid var(--line)}.explorer-path input{flex:1;min-width:0}.explorer-list{flex:1;overflow:auto;padding:8px;min-height:220px}.explorer-row{display:flex;align-items:center;gap:8px;width:100%;text-align:left;background:transparent;border:0;border-radius:var(--radius);padding:8px 10px;color:var(--ink);cursor:pointer;height:auto}.explorer-row:hover,.explorer-row.active{background:var(--rail-active)}.explorer-name{min-width:0;overflow:hidden;text-overflow:ellipsis}.git-badge{margin-left:auto;margin-bottom:0}.explorer-empty{padding:18px 10px}.modal-foot{display:flex;align-items:center;gap:12px;padding:12px 16px;border-top:1px solid var(--line)}.explorer-current{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--mono);font-size:12px}.topbar input,.topbar select{min-width:0}.topbar-actions{display:flex;gap:8px;margin-left:auto;align-items:center;padding-bottom:0}.btn,.topbar button{background:var(--btn);border:1px solid var(--btn-line);border-radius:var(--radius);height:var(--control-h);padding:0 12px;cursor:pointer;font-weight:500;display:inline-flex;align-items:center;justify-content:center;gap:6px;white-space:nowrap}.btn:hover,.topbar button:hover{filter:brightness(1.08)}.btn:disabled,.topbar button:disabled{opacity:.55;cursor:not-allowed;filter:none}.btn.primary{background:var(--btn-primary);border-color:var(--btn-primary-line)}.btn.ghost{background:transparent}.alerts{display:grid;gap:8px;padding:10px 16px 0}.alerts:empty{display:none;padding:0}.stage{flex:1;min-height:0;display:flex;flex-direction:column}.content{flex:1;min-height:0;display:grid;grid-template-columns:minmax(340px,420px) 1fr}.brief{overflow:auto;border-right:1px solid var(--line);padding:16px 18px 24px;background:radial-gradient(circle at 0 0,color-mix(in srgb,var(--accent) 8%,transparent),transparent 42%),var(--bg)}.graph-wrap{position:relative;min-height:0;display:flex;flex-direction:column}.impact-graph{flex:1;min-height:0;position:relative;display:flex;flex-direction:column}.graph-toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--bg-2)}.graph-count{margin-left:auto;font-size:11px}.chip-btn{background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:4px 12px;color:var(--muted);cursor:pointer;height:26px}.chip-btn:hover{color:var(--ink)}.chip-btn.active{background:var(--rail-active);color:var(--ink)}.chip-btn:disabled{opacity:.45;cursor:not-allowed}.graph-stage{flex:1;min-height:0;position:relative;display:flex;flex-direction:column}.graph-stage .react-flow,.graph-3d,.graph-3d-host,.graph-3d-canvas-host{flex:1;width:100%;height:100%;min-height:280px}.graph-3d{position:relative;background:var(--graph-bg);display:flex;flex-direction:column}.graph-3d-host{position:relative;min-height:0;display:flex;flex-direction:column}.graph-3d-canvas-host{position:relative;min-height:0;background:var(--graph-bg)}.graph-3d canvas{display:block;width:100%;height:100%}.graph-3d-tip{position:absolute;pointer-events:none;z-index:2;max-width:320px;padding:6px 8px;border-radius:var(--radius);background:var(--surface);border:1px solid var(--line);color:var(--ink);font-size:11px;line-height:1.35;box-shadow:0 8px 24px var(--shadow)}.graph-3d-tip .t{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.graph-3d-tip .n{font-weight:600}.graph-3d-hint{position:absolute;left:10px;bottom:10px;z-index:2;margin:0;font-size:11px;color:var(--muted);max-width:min(420px,calc(100% - 24px));pointer-events:none}.graph-wrap .react-flow{background-color:var(--graph-bg);background-image:linear-gradient(var(--graph-grid) 1px,transparent 1px),linear-gradient(90deg,var(--graph-grid) 1px,transparent 1px);background-size:24px 24px}.react-flow__minimap{background:var(--graph-bg)!important;border:1px solid var(--line)!important;border-radius:var(--radius);overflow:hidden;box-shadow:0 8px 24px var(--shadow)}.react-flow__minimap-node{fill:var(--muted);stroke:none}.react-flow__minimap-node.selected{fill:var(--accent)}.react-flow__minimap-mask{fill:#00000073!important;stroke:var(--accent)!important}.react-flow__controls{box-shadow:none!important}.react-flow__controls-button{background:var(--surface)!important;border-bottom:1px solid var(--line)!important;fill:var(--ink)!important}h1{font-size:18px;margin:0 0 6px;font-weight:600}h2{font-size:13px;margin:0;font-weight:600}.merge-box{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-lg);padding:12px 14px;margin-bottom:12px}.merge-box.high{border-color:color-mix(in srgb,var(--high) 55%,var(--line))}.merge-box.medium{border-color:color-mix(in srgb,var(--medium) 55%,var(--line))}.merge-box.low{border-color:color-mix(in srgb,var(--low) 55%,var(--line))}.level{font-family:var(--mono);font-weight:600;font-size:14px}.level.high{color:var(--high)}.level.medium{color:var(--medium)}.level.low{color:var(--low)}.merge-title{margin-top:4px;font-size:13px;color:var(--ink)}.reasons{margin:10px 0 0;padding:0 0 0 18px;color:var(--muted)}.reasons li{margin:0 0 4px}.metrics{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin:0 0 14px}.metric{border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);padding:8px 10px}.metric .n{font-family:var(--mono);font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}.metric .l{font-size:11px;color:var(--muted);margin-top:2px}.section{border-top:1px solid var(--line);padding:8px 0 4px}.section>summary{cursor:pointer;list-style:none;display:flex;align-items:center;justify-content:space-between;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.07em;font-weight:600;padding:6px 0}.section>summary::-webkit-details-marker{display:none}.section>summary .count{font-family:var(--mono);letter-spacing:0;text-transform:none;border:1px solid var(--line);border-radius:999px;padding:0 7px;height:18px;display:inline-flex;align-items:center;font-size:11px}.kicker{color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.08em;margin:16px 0 6px;font-weight:600}.chip{display:inline-flex;align-items:center;font-size:11px;padding:2px 8px;border-radius:999px;border:1px solid var(--line);margin:0 6px 6px 0;color:var(--muted);background:var(--surface)}.chip.blocker{color:var(--low);border-color:var(--low)}.chip.warning{color:var(--medium);border-color:var(--medium)}.chip.strong{color:var(--low);border-color:var(--low)}.chip.worth_exploring{color:var(--medium);border-color:var(--medium)}.chip.speculative{color:var(--muted);border-color:var(--line)}.chip.open{color:var(--high);border-color:color-mix(in srgb,var(--high) 50%,var(--line))}.file{font-family:var(--mono);font-size:12px;color:var(--accent)}.read-item,.finding,.residual{padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--line) 70%,transparent)}.read-item:last-child,.finding:last-child{border-bottom:0}.read-item.tour-current{background:color-mix(in srgb,var(--accent) 12%,var(--surface));border-color:var(--accent)}.tour-row{margin-top:8px;align-items:center}.field.dirty .chip-btn{height:32px}.muted{color:var(--muted);font-size:13px;line-height:1.45}.error{color:var(--low);padding:8px 12px;border:1px solid var(--low);background:color-mix(in srgb,var(--low) 10%,var(--surface));border-radius:var(--radius);display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.banner{padding:8px 12px;border-radius:var(--radius);border:1px solid var(--line);background:var(--surface);font-size:13px;line-height:1.45;display:flex;justify-content:space-between;gap:12px;align-items:flex-start}.banner.warn{border-color:var(--medium);color:var(--medium)}.banner.stale{border-color:var(--low);color:var(--low)}.banner .dismiss{background:transparent;border:0;color:inherit;cursor:pointer;height:auto;padding:0 2px;opacity:.7}.empty{padding:24px 8px;color:var(--muted);font-size:13px;line-height:1.55}.workspace-loading{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;min-height:220px;padding:48px 16px}.empty h2{font-size:16px;color:var(--ink);margin-bottom:8px}.empty ol{margin:12px 0 0 18px;padding:0}.empty code,code{font-family:var(--mono);font-size:12px;color:var(--accent)}.btn-row{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}.pr-list{padding:16px;overflow:auto}.pr-toolbar{display:flex;gap:8px;align-items:flex-end;margin-bottom:14px}.pr-toolbar .field{flex:1}.pr-toolbar .field.provider{flex:0 0 140px}.scm-count{margin:0 0 12px;font-size:12px}.scm-login{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:10px 0;border-top:1px solid var(--line)}.scm-login:first-of-type{border-top:0;padding-top:0}.scm-login .btn-row{margin-top:0}.scm-login p{margin:2px 0 0}.oauth-code{font-size:13px;margin:0}.oauth-code code{font-size:14px;letter-spacing:.08em}.pr{border:1px solid var(--line);background:var(--surface);padding:12px 14px;border-radius:var(--radius-lg);margin-bottom:10px}.pr h3{margin:0 0 4px;font-size:14px;font-weight:600}.pr-meta{display:flex;flex-wrap:wrap;gap:8px 12px;align-items:center;margin:6px 0 10px}.pr-actions{display:flex;gap:8px;align-items:center}.settings{padding:24px;max-width:760px;overflow:auto;display:grid;gap:16px}.settings-card{border:1px solid var(--line);background:var(--surface);border-radius:var(--radius-lg);padding:16px;display:grid;gap:8px}.settings-card h2{font-size:14px;margin-bottom:2px}.settings label{font-size:12px;color:var(--muted);font-weight:500}.theme-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:8px}.theme-swatch{border:1px solid var(--line);background:var(--bg);color:var(--ink);border-radius:var(--radius);padding:10px;text-align:left;cursor:pointer;height:auto;box-shadow:0 1px 8px var(--shadow)}.theme-swatch.active{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent),0 1px 8px var(--shadow)}.theme-swatch .swatch-bar{height:6px;border-radius:3px;margin-bottom:8px;background:linear-gradient(90deg,var(--accent),var(--high),var(--medium),var(--low))}.theme-swatch .name{font-size:13px;font-weight:600}.theme-swatch .group{font-size:11px;color:var(--muted)}.headline{white-space:pre-wrap;font-family:var(--mono);font-size:12px;color:var(--muted);margin:8px 0 0}.graph-modes{display:flex;gap:8px;align-items:center;padding:8px 14px;border-bottom:1px solid var(--line);background:var(--bg-2)}.seg{display:inline-flex;border:1px solid var(--line);border-radius:999px;padding:2px;background:var(--surface)}.seg button,.graph-modes button{background:transparent;border:0;border-radius:999px;padding:4px 12px;color:var(--muted);cursor:pointer;height:26px}.seg button.active,.graph-modes button.active{background:var(--rail-active);color:var(--ink)}.legend{margin-left:auto;display:flex;gap:12px;color:var(--muted);font-size:11px}.legend i{display:inline-block;width:14px;height:2px;margin-right:6px;vertical-align:middle;background:var(--edge-cheap)}.legend i.exp{background:var(--edge-expensive)}.legend i.crit{background:var(--edge-critical);height:3px}.legend i.dash{border-top:2px dashed var(--muted);background:none;height:0}.inspector{position:absolute;top:12px;right:12px;z-index:5;width:300px;max-width:calc(100% - 24px);max-height:calc(100% - 24px);overflow-x:hidden;overflow-y:auto;overflow-wrap:break-word;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-lg);padding:10px 12px;box-shadow:0 8px 24px var(--shadow);font-size:12px}.inspector .t{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}.inspector .n,.inspector .file,.inspector .muted{overflow-wrap:break-word}.inspector .n{font-weight:600;margin:4px 0}.inspector-head{display:flex;align-items:flex-start;gap:8px}.inspector-roles{display:flex;flex-wrap:wrap;justify-content:flex-end;flex:1;gap:4px}.inspector-chip{font-size:10px;text-transform:uppercase;letter-spacing:.04em;padding:1px 6px;border-radius:999px;border:1px solid var(--line);color:var(--muted);white-space:nowrap}.inspector-close{flex:0 0 auto;width:24px;height:24px;padding:0;border:1px solid var(--line);border-radius:var(--radius);background:transparent;color:var(--muted);font-size:16px;line-height:1;cursor:pointer}.inspector-close:hover{color:var(--ink)}.inspector-purpose{margin:6px 0 8px;color:var(--ink);font-size:12px;line-height:1.4}.inspector-layer{margin-top:4px;font-size:11px}.inspector-facts{margin:10px 0 0;padding-top:8px;border-top:1px solid var(--line)}.inspector-fact{display:grid;grid-template-columns:minmax(64px,92px) minmax(0,1fr);gap:8px;padding:3px 0;align-items:start}.inspector-fact dt{color:var(--muted);font-size:11px;margin:0}.inspector-fact dd{margin:0;overflow-wrap:break-word}.inspector-section{margin-top:10px;padding-top:8px;border-top:1px solid var(--line)}.inspector-section h3{margin:0 0 6px;display:flex;align-items:center;justify-content:space-between;color:var(--muted);font-size:11px;text-transform:uppercase;letter-spacing:.07em;font-weight:600}.inspector-section .count{font-family:var(--mono);letter-spacing:0;text-transform:none;border:1px solid var(--line);border-radius:999px;padding:0 7px;height:18px;display:inline-flex;align-items:center;font-size:11px}.inspector-section ul{list-style:none;margin:0;padding:0}.inspector-section li{padding:4px 0;border-bottom:1px solid color-mix(in srgb,var(--line) 70%,transparent)}.inspector-section li:last-child{border-bottom:0}.inspector-link-name{display:block;font-weight:600;overflow-wrap:break-word}.inspector-link-meta{display:block;color:var(--muted);font-size:11px}.lp-node{padding:8px 10px;border-radius:var(--radius);border:1px solid var(--node-line);background:var(--node-bg);width:208px;max-width:100%;height:64px;box-sizing:border-box;box-shadow:0 0 0 1px var(--shadow);overflow:visible;position:relative}.lp-node .t{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}.lp-node .n{font-size:13px;font-weight:600;line-height:1.2;overflow:hidden;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow-wrap:anywhere}.lp-node.selected{border-color:var(--accent);box-shadow:0 0 0 1px var(--accent)}.react-flow__node-load .react-flow__handle{width:8px;height:8px;border:none;background:transparent;opacity:0}.type-table{width:100%;border-collapse:collapse;font-size:12px}.type-table td{padding:3px 0}.type-table td:last-child{text-align:right;font-family:var(--mono);font-variant-numeric:tabular-nums;color:var(--muted)}@media(prefers-reduced-motion:reduce){.progress i{animation:none;width:100%}.progress.determinate i{width:var(--progress, 100%)}*{scroll-behavior:auto!important}}@media(max-width:960px){.app{grid-template-columns:56px 1fr}.brand-sub,.nav-item span,.theme-pick,.kbd-hint,.rail-foot .muted{display:none}.nav-item{justify-content:center;padding:10px}.content{grid-template-columns:1fr}.brief{border-right:0;border-bottom:1px solid var(--line);max-height:42vh}} diff --git a/src/loadpath/static/assets/index-DPfPes3Y.js b/src/loadpath/static/assets/index-DPfPes3Y.js new file mode 100644 index 0000000..afaf04c --- /dev/null +++ b/src/loadpath/static/assets/index-DPfPes3Y.js @@ -0,0 +1,62 @@ +(function(){const r=document.createElement("link").relList;if(r&&r.supports&&r.supports("modulepreload"))return;for(const a of document.querySelectorAll('link[rel="modulepreload"]'))l(a);new MutationObserver(a=>{for(const u of a)if(u.type==="childList")for(const d of u.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&l(d)}).observe(document,{childList:!0,subtree:!0});function o(a){const u={};return a.integrity&&(u.integrity=a.integrity),a.referrerPolicy&&(u.referrerPolicy=a.referrerPolicy),a.crossOrigin==="use-credentials"?u.credentials="include":a.crossOrigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function l(a){if(a.ep)return;a.ep=!0;const u=o(a);fetch(a.href,u)}})();function xp(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}var _u={exports:{}},wo={},Su={exports:{}},Te={};/** + * @license React + * react.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Wf;function Qy(){if(Wf)return Te;Wf=1;var t=Symbol.for("react.element"),r=Symbol.for("react.portal"),o=Symbol.for("react.fragment"),l=Symbol.for("react.strict_mode"),a=Symbol.for("react.profiler"),u=Symbol.for("react.provider"),d=Symbol.for("react.context"),f=Symbol.for("react.forward_ref"),g=Symbol.for("react.suspense"),m=Symbol.for("react.memo"),y=Symbol.for("react.lazy"),x=Symbol.iterator;function v(M){return M===null||typeof M!="object"?null:(M=x&&M[x]||M["@@iterator"],typeof M=="function"?M:null)}var S={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},k=Object.assign,j={};function b(M,R,re){this.props=M,this.context=R,this.refs=j,this.updater=re||S}b.prototype.isReactComponent={},b.prototype.setState=function(M,R){if(typeof M!="object"&&typeof M!="function"&&M!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,M,R,"setState")},b.prototype.forceUpdate=function(M){this.updater.enqueueForceUpdate(this,M,"forceUpdate")};function _(){}_.prototype=b.prototype;function P(M,R,re){this.props=M,this.context=R,this.refs=j,this.updater=re||S}var E=P.prototype=new _;E.constructor=P,k(E,b.prototype),E.isPureReactComponent=!0;var N=Array.isArray,L=Object.prototype.hasOwnProperty,A={current:null},V={key:!0,ref:!0,__self:!0,__source:!0};function z(M,R,re){var ie,ce={},fe=null,de=null;if(R!=null)for(ie in R.ref!==void 0&&(de=R.ref),R.key!==void 0&&(fe=""+R.key),R)L.call(R,ie)&&!V.hasOwnProperty(ie)&&(ce[ie]=R[ie]);var K=arguments.length-2;if(K===1)ce.children=re;else if(1>>1,R=T[M];if(0>>1;Ma(ce,B))fea(de,ce)?(T[M]=de,T[fe]=B,M=fe):(T[M]=ce,T[ie]=B,M=ie);else if(fea(de,B))T[M]=de,T[fe]=B,M=fe;else break e}}return F}function a(T,F){var B=T.sortIndex-F.sortIndex;return B!==0?B:T.id-F.id}if(typeof performance=="object"&&typeof performance.now=="function"){var u=performance;t.unstable_now=function(){return u.now()}}else{var d=Date,f=d.now();t.unstable_now=function(){return d.now()-f}}var g=[],m=[],y=1,x=null,v=3,S=!1,k=!1,j=!1,b=typeof setTimeout=="function"?setTimeout:null,_=typeof clearTimeout=="function"?clearTimeout:null,P=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function E(T){for(var F=o(m);F!==null;){if(F.callback===null)l(m);else if(F.startTime<=T)l(m),F.sortIndex=F.expirationTime,r(g,F);else break;F=o(m)}}function N(T){if(j=!1,E(T),!k)if(o(g)!==null)k=!0,U(L);else{var F=o(m);F!==null&&Y(N,F.startTime-T)}}function L(T,F){k=!1,j&&(j=!1,_(z),z=-1),S=!0;var B=v;try{for(E(F),x=o(g);x!==null&&(!(x.expirationTime>F)||T&&!J());){var M=x.callback;if(typeof M=="function"){x.callback=null,v=x.priorityLevel;var R=M(x.expirationTime<=F);F=t.unstable_now(),typeof R=="function"?x.callback=R:x===o(g)&&l(g),E(F)}else l(g);x=o(g)}if(x!==null)var re=!0;else{var ie=o(m);ie!==null&&Y(N,ie.startTime-F),re=!1}return re}finally{x=null,v=B,S=!1}}var A=!1,V=null,z=-1,G=5,ee=-1;function J(){return!(t.unstable_now()-eeT||125M?(T.sortIndex=B,r(m,T),o(g)===null&&T===o(m)&&(j?(_(z),z=-1):j=!0,Y(N,B-M))):(T.sortIndex=R,r(g,T),k||S||(k=!0,U(L))),T},t.unstable_shouldYield=J,t.unstable_wrapCallback=function(T){var F=v;return function(){var B=v;v=F;try{return T.apply(this,arguments)}finally{v=B}}}})(Nu)),Nu}var qf;function t0(){return qf||(qf=1,Eu.exports=e0()),Eu.exports}/** + * @license React + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Qf;function n0(){if(Qf)return Ct;Qf=1;var t=$o(),r=t0();function o(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,i=1;i"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),g=Object.prototype.hasOwnProperty,m=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,y={},x={};function v(e){return g.call(x,e)?!0:g.call(y,e)?!1:m.test(e)?x[e]=!0:(y[e]=!0,!1)}function S(e,n,i,s){if(i!==null&&i.type===0)return!1;switch(typeof n){case"function":case"symbol":return!0;case"boolean":return s?!1:i!==null?!i.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function k(e,n,i,s){if(n===null||typeof n>"u"||S(e,n,i,s))return!0;if(s)return!1;if(i!==null)switch(i.type){case 3:return!n;case 4:return n===!1;case 5:return isNaN(n);case 6:return isNaN(n)||1>n}return!1}function j(e,n,i,s,c,h,w){this.acceptsBooleans=n===2||n===3||n===4,this.attributeName=s,this.attributeNamespace=c,this.mustUseProperty=i,this.propertyName=e,this.type=n,this.sanitizeURL=h,this.removeEmptyString=w}var b={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){b[e]=new j(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var n=e[0];b[n]=new j(n,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){b[e]=new j(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){b[e]=new j(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){b[e]=new j(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){b[e]=new j(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){b[e]=new j(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){b[e]=new j(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){b[e]=new j(e,5,!1,e.toLowerCase(),null,!1,!1)});var _=/[\-:]([a-z])/g;function P(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var n=e.replace(_,P);b[n]=new j(n,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var n=e.replace(_,P);b[n]=new j(n,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var n=e.replace(_,P);b[n]=new j(n,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){b[e]=new j(e,1,!1,e.toLowerCase(),null,!1,!1)}),b.xlinkHref=new j("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){b[e]=new j(e,1,!1,e.toLowerCase(),null,!0,!0)});function E(e,n,i,s){var c=b.hasOwnProperty(n)?b[n]:null;(c!==null?c.type!==0:s||!(2I||c[w]!==h[I]){var $=` +`+c[w].replace(" at new "," at ");return e.displayName&&$.includes("")&&($=$.replace("",e.displayName)),$}while(1<=w&&0<=I);break}}}finally{re=!1,Error.prepareStackTrace=i}return(e=e?e.displayName||e.name:"")?R(e):""}function ce(e){switch(e.tag){case 5:return R(e.type);case 16:return R("Lazy");case 13:return R("Suspense");case 19:return R("SuspenseList");case 0:case 2:case 15:return e=ie(e.type,!1),e;case 11:return e=ie(e.type.render,!1),e;case 1:return e=ie(e.type,!0),e;default:return""}}function fe(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case V:return"Fragment";case A:return"Portal";case G:return"Profiler";case z:return"StrictMode";case q:return"Suspense";case C:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case J:return(e.displayName||"Context")+".Consumer";case ee:return(e._context.displayName||"Context")+".Provider";case ne:var n=e.render;return e=e.displayName,e||(e=n.displayName||n.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case W:return n=e.displayName||null,n!==null?n:fe(e.type)||"Memo";case U:n=e._payload,e=e._init;try{return fe(e(n))}catch{}}return null}function de(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:return(n.displayName||"Context")+".Consumer";case 10:return(n._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=n.render,e=e.displayName||e.name||"",n.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return n;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return fe(n);case 8:return n===z?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof n=="function")return n.displayName||n.name||null;if(typeof n=="string")return n}return null}function K(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function le(e){var n=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(n==="checkbox"||n==="radio")}function me(e){var n=le(e)?"checked":"value",i=Object.getOwnPropertyDescriptor(e.constructor.prototype,n),s=""+e[n];if(!e.hasOwnProperty(n)&&typeof i<"u"&&typeof i.get=="function"&&typeof i.set=="function"){var c=i.get,h=i.set;return Object.defineProperty(e,n,{configurable:!0,get:function(){return c.call(this)},set:function(w){s=""+w,h.call(this,w)}}),Object.defineProperty(e,n,{enumerable:i.enumerable}),{getValue:function(){return s},setValue:function(w){s=""+w},stopTracking:function(){e._valueTracker=null,delete e[n]}}}}function ke(e){e._valueTracker||(e._valueTracker=me(e))}function xe(e){if(!e)return!1;var n=e._valueTracker;if(!n)return!0;var i=n.getValue(),s="";return e&&(s=le(e)?e.checked?"true":"false":e.value),e=s,e!==i?(n.setValue(e),!0):!1}function pe(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function be(e,n){var i=n.checked;return B({},n,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:i??e._wrapperState.initialChecked})}function Pe(e,n){var i=n.defaultValue==null?"":n.defaultValue,s=n.checked!=null?n.checked:n.defaultChecked;i=K(n.value!=null?n.value:i),e._wrapperState={initialChecked:s,initialValue:i,controlled:n.type==="checkbox"||n.type==="radio"?n.checked!=null:n.value!=null}}function Ce(e,n){n=n.checked,n!=null&&E(e,"checked",n,!1)}function Re(e,n){Ce(e,n);var i=K(n.value),s=n.type;if(i!=null)s==="number"?(i===0&&e.value===""||e.value!=i)&&(e.value=""+i):e.value!==""+i&&(e.value=""+i);else if(s==="submit"||s==="reset"){e.removeAttribute("value");return}n.hasOwnProperty("value")?nt(e,n.type,i):n.hasOwnProperty("defaultValue")&&nt(e,n.type,K(n.defaultValue)),n.checked==null&&n.defaultChecked!=null&&(e.defaultChecked=!!n.defaultChecked)}function tt(e,n,i){if(n.hasOwnProperty("value")||n.hasOwnProperty("defaultValue")){var s=n.type;if(!(s!=="submit"&&s!=="reset"||n.value!==void 0&&n.value!==null))return;n=""+e._wrapperState.initialValue,i||n===e.value||(e.value=n),e.defaultValue=n}i=e.name,i!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,i!==""&&(e.name=i)}function nt(e,n,i){(n!=="number"||pe(e.ownerDocument)!==e)&&(i==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+i&&(e.defaultValue=""+i))}var Je=Array.isArray;function Ke(e,n,i,s){if(e=e.options,n){n={};for(var c=0;c"+n.valueOf().toString()+"",n=mt.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;n.firstChild;)e.appendChild(n.firstChild)}});function ht(e,n){if(n){var i=e.firstChild;if(i&&i===e.lastChild&&i.nodeType===3){i.nodeValue=n;return}}e.textContent=n}var Gt={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},_n=["Webkit","ms","Moz","O"];Object.keys(Gt).forEach(function(e){_n.forEach(function(n){n=n+e.charAt(0).toUpperCase()+e.substring(1),Gt[n]=Gt[e]})});function zn(e,n,i){return n==null||typeof n=="boolean"||n===""?"":i||typeof n!="number"||n===0||Gt.hasOwnProperty(e)&&Gt[e]?(""+n).trim():n+"px"}function Gr(e,n){e=e.style;for(var i in n)if(n.hasOwnProperty(i)){var s=i.indexOf("--")===0,c=zn(i,n[i],s);i==="float"&&(i="cssFloat"),s?e.setProperty(i,c):e[i]=c}}var It=B({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Sn(e,n){if(n){if(It[e]&&(n.children!=null||n.dangerouslySetInnerHTML!=null))throw Error(o(137,e));if(n.dangerouslySetInnerHTML!=null){if(n.children!=null)throw Error(o(60));if(typeof n.dangerouslySetInnerHTML!="object"||!("__html"in n.dangerouslySetInnerHTML))throw Error(o(61))}if(n.style!=null&&typeof n.style!="object")throw Error(o(62))}}function Dn(e,n){if(e.indexOf("-")===-1)return typeof n.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var un=null;function $n(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var gr=null,cn=null,dn=null;function Xr(e){if(e=io(e)){if(typeof gr!="function")throw Error(o(280));var n=e.stateNode;n&&(n=cs(n),gr(e.stateNode,e.type,n))}}function qr(e){cn?dn?dn.push(e):dn=[e]:cn=e}function Qr(){if(cn){var e=cn,n=dn;if(dn=cn=null,Xr(e),n)for(e=0;e>>=0,e===0?32:31-(Fl(e)/Hl|0)|0}var ii=64,oi=4194304;function _r(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function En(e,n){var i=e.pendingLanes;if(i===0)return 0;var s=0,c=e.suspendedLanes,h=e.pingedLanes,w=i&268435455;if(w!==0){var I=w&~c;I!==0?s=_r(I):(h&=w,h!==0&&(s=_r(h)))}else w=i&~c,w!==0?s=_r(w):h!==0&&(s=_r(h));if(s===0)return 0;if(n!==0&&n!==s&&(n&c)===0&&(c=s&-s,h=n&-n,c>=h||c===16&&(h&4194240)!==0))return n;if((s&4)!==0&&(s|=i&16),n=e.entangledLanes,n!==0)for(e=e.entanglements,n&=s;0i;i++)n.push(e);return n}function kr(e,n,i){e.pendingLanes|=n,n!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,n=31-Lt(n),e[n]=i}function Wl(e,n){var i=e.pendingLanes&~n;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=n,e.mutableReadLanes&=n,e.entangledLanes&=n,n=e.entanglements;var s=e.eventTimes;for(e=e.expirationTimes;0=qi),zc=" ",Dc=!1;function $c(e,n){switch(e){case"keyup":return Um.indexOf(n.keyCode)!==-1;case"keydown":return n.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Oc(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var ui=!1;function Gm(e,n){switch(e){case"compositionend":return Oc(n);case"keypress":return n.which!==32?null:(Dc=!0,zc);case"textInput":return e=n.data,e===zc&&Dc?null:e;default:return null}}function Xm(e,n){if(ui)return e==="compositionend"||!ta&&$c(e,n)?(e=Pc(),Jo=ql=qn=null,ui=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(n.ctrlKey||n.altKey||n.metaKey)||n.ctrlKey&&n.altKey){if(n.char&&1=n)return{node:i,offset:n-e};e=s}e:{for(;i;){if(i.nextSibling){i=i.nextSibling;break e}i=i.parentNode}i=void 0}i=Yc(i)}}function Xc(e,n){return e&&n?e===n?!0:e&&e.nodeType===3?!1:n&&n.nodeType===3?Xc(e,n.parentNode):"contains"in e?e.contains(n):e.compareDocumentPosition?!!(e.compareDocumentPosition(n)&16):!1:!1}function qc(){for(var e=window,n=pe();n instanceof e.HTMLIFrameElement;){try{var i=typeof n.contentWindow.location.href=="string"}catch{i=!1}if(i)e=n.contentWindow;else break;n=pe(e.document)}return n}function ia(e){var n=e&&e.nodeName&&e.nodeName.toLowerCase();return n&&(n==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||n==="textarea"||e.contentEditable==="true")}function ry(e){var n=qc(),i=e.focusedElem,s=e.selectionRange;if(n!==i&&i&&i.ownerDocument&&Xc(i.ownerDocument.documentElement,i)){if(s!==null&&ia(i)){if(n=s.start,e=s.end,e===void 0&&(e=n),"selectionStart"in i)i.selectionStart=n,i.selectionEnd=Math.min(e,i.value.length);else if(e=(n=i.ownerDocument||document)&&n.defaultView||window,e.getSelection){e=e.getSelection();var c=i.textContent.length,h=Math.min(s.start,c);s=s.end===void 0?h:Math.min(s.end,c),!e.extend&&h>s&&(c=s,s=h,h=c),c=Gc(i,h);var w=Gc(i,s);c&&w&&(e.rangeCount!==1||e.anchorNode!==c.node||e.anchorOffset!==c.offset||e.focusNode!==w.node||e.focusOffset!==w.offset)&&(n=n.createRange(),n.setStart(c.node,c.offset),e.removeAllRanges(),h>s?(e.addRange(n),e.extend(w.node,w.offset)):(n.setEnd(w.node,w.offset),e.addRange(n)))}}for(n=[],e=i;e=e.parentNode;)e.nodeType===1&&n.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof i.focus=="function"&&i.focus(),i=0;i=document.documentMode,ci=null,oa=null,Ji=null,sa=!1;function Qc(e,n,i){var s=i.window===i?i.document:i.nodeType===9?i:i.ownerDocument;sa||ci==null||ci!==pe(s)||(s=ci,"selectionStart"in s&&ia(s)?s={start:s.selectionStart,end:s.selectionEnd}:(s=(s.ownerDocument&&s.ownerDocument.defaultView||window).getSelection(),s={anchorNode:s.anchorNode,anchorOffset:s.anchorOffset,focusNode:s.focusNode,focusOffset:s.focusOffset}),Ji&&Zi(Ji,s)||(Ji=s,s=ls(oa,"onSelect"),0gi||(e.current=va[gi],va[gi]=null,gi--)}function Be(e,n){gi++,va[gi]=e.current,e.current=n}var Jn={},yt=Zn(Jn),kt=Zn(!1),Nr=Jn;function mi(e,n){var i=e.type.contextTypes;if(!i)return Jn;var s=e.stateNode;if(s&&s.__reactInternalMemoizedUnmaskedChildContext===n)return s.__reactInternalMemoizedMaskedChildContext;var c={},h;for(h in i)c[h]=n[h];return s&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=n,e.__reactInternalMemoizedMaskedChildContext=c),c}function Et(e){return e=e.childContextTypes,e!=null}function ds(){We(kt),We(yt)}function dd(e,n,i){if(yt.current!==Jn)throw Error(o(168));Be(yt,n),Be(kt,i)}function fd(e,n,i){var s=e.stateNode;if(n=n.childContextTypes,typeof s.getChildContext!="function")return i;s=s.getChildContext();for(var c in s)if(!(c in n))throw Error(o(108,de(e)||"Unknown",c));return B({},i,s)}function fs(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Jn,Nr=yt.current,Be(yt,e),Be(kt,kt.current),!0}function hd(e,n,i){var s=e.stateNode;if(!s)throw Error(o(169));i?(e=fd(e,n,Nr),s.__reactInternalMemoizedMergedChildContext=e,We(kt),We(yt),Be(yt,e)):We(kt),Be(kt,i)}var jn=null,hs=!1,xa=!1;function pd(e){jn===null?jn=[e]:jn.push(e)}function gy(e){hs=!0,pd(e)}function er(){if(!xa&&jn!==null){xa=!0;var e=0,n=Oe;try{var i=jn;for(Oe=1;e>=w,c-=w,bn=1<<32-Lt(n)+c|i<je?(dt=Ne,Ne=null):dt=Ne.sibling;var De=oe(X,Ne,Q[je],ue);if(De===null){Ne===null&&(Ne=dt);break}e&&Ne&&De.alternate===null&&n(X,Ne),H=h(De,H,je),Ee===null?we=De:Ee.sibling=De,Ee=De,Ne=dt}if(je===Q.length)return i(X,Ne),Ge&&br(X,je),we;if(Ne===null){for(;jeje?(dt=Ne,Ne=null):dt=Ne.sibling;var ur=oe(X,Ne,De.value,ue);if(ur===null){Ne===null&&(Ne=dt);break}e&&Ne&&ur.alternate===null&&n(X,Ne),H=h(ur,H,je),Ee===null?we=ur:Ee.sibling=ur,Ee=ur,Ne=dt}if(De.done)return i(X,Ne),Ge&&br(X,je),we;if(Ne===null){for(;!De.done;je++,De=Q.next())De=ae(X,De.value,ue),De!==null&&(H=h(De,H,je),Ee===null?we=De:Ee.sibling=De,Ee=De);return Ge&&br(X,je),we}for(Ne=s(X,Ne);!De.done;je++,De=Q.next())De=he(Ne,X,je,De.value,ue),De!==null&&(e&&De.alternate!==null&&Ne.delete(De.key===null?je:De.key),H=h(De,H,je),Ee===null?we=De:Ee.sibling=De,Ee=De);return e&&Ne.forEach(function(qy){return n(X,qy)}),Ge&&br(X,je),we}function et(X,H,Q,ue){if(typeof Q=="object"&&Q!==null&&Q.type===V&&Q.key===null&&(Q=Q.props.children),typeof Q=="object"&&Q!==null){switch(Q.$$typeof){case L:e:{for(var we=Q.key,Ee=H;Ee!==null;){if(Ee.key===we){if(we=Q.type,we===V){if(Ee.tag===7){i(X,Ee.sibling),H=c(Ee,Q.props.children),H.return=X,X=H;break e}}else if(Ee.elementType===we||typeof we=="object"&&we!==null&&we.$$typeof===U&&wd(we)===Ee.type){i(X,Ee.sibling),H=c(Ee,Q.props),H.ref=oo(X,Ee,Q),H.return=X,X=H;break e}i(X,Ee);break}else n(X,Ee);Ee=Ee.sibling}Q.type===V?(H=Ar(Q.props.children,X.mode,ue,Q.key),H.return=X,X=H):(ue=Hs(Q.type,Q.key,Q.props,null,X.mode,ue),ue.ref=oo(X,H,Q),ue.return=X,X=ue)}return w(X);case A:e:{for(Ee=Q.key;H!==null;){if(H.key===Ee)if(H.tag===4&&H.stateNode.containerInfo===Q.containerInfo&&H.stateNode.implementation===Q.implementation){i(X,H.sibling),H=c(H,Q.children||[]),H.return=X,X=H;break e}else{i(X,H);break}else n(X,H);H=H.sibling}H=mu(Q,X.mode,ue),H.return=X,X=H}return w(X);case U:return Ee=Q._init,et(X,H,Ee(Q._payload),ue)}if(Je(Q))return ye(X,H,Q,ue);if(F(Q))return ve(X,H,Q,ue);ys(X,Q)}return typeof Q=="string"&&Q!==""||typeof Q=="number"?(Q=""+Q,H!==null&&H.tag===6?(i(X,H.sibling),H=c(H,Q),H.return=X,X=H):(i(X,H),H=gu(Q,X.mode,ue),H.return=X,X=H),w(X)):i(X,H)}return et}var wi=_d(!0),Sd=_d(!1),vs=Zn(null),xs=null,_i=null,Na=null;function ja(){Na=_i=xs=null}function ba(e){var n=vs.current;We(vs),e._currentValue=n}function Ca(e,n,i){for(;e!==null;){var s=e.alternate;if((e.childLanes&n)!==n?(e.childLanes|=n,s!==null&&(s.childLanes|=n)):s!==null&&(s.childLanes&n)!==n&&(s.childLanes|=n),e===i)break;e=e.return}}function Si(e,n){xs=e,Na=_i=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&n)!==0&&(Nt=!0),e.firstContext=null)}function Vt(e){var n=e._currentValue;if(Na!==e)if(e={context:e,memoizedValue:n,next:null},_i===null){if(xs===null)throw Error(o(308));_i=e,xs.dependencies={lanes:0,firstContext:e}}else _i=_i.next=e;return n}var Cr=null;function Ma(e){Cr===null?Cr=[e]:Cr.push(e)}function kd(e,n,i,s){var c=n.interleaved;return c===null?(i.next=i,Ma(n)):(i.next=c.next,c.next=i),n.interleaved=i,Mn(e,s)}function Mn(e,n){e.lanes|=n;var i=e.alternate;for(i!==null&&(i.lanes|=n),i=e,e=e.return;e!==null;)e.childLanes|=n,i=e.alternate,i!==null&&(i.childLanes|=n),i=e,e=e.return;return i.tag===3?i.stateNode:null}var tr=!1;function Pa(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ed(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Pn(e,n){return{eventTime:e,lane:n,tag:0,payload:null,callback:null,next:null}}function nr(e,n,i){var s=e.updateQueue;if(s===null)return null;if(s=s.shared,(Ae&2)!==0){var c=s.pending;return c===null?n.next=n:(n.next=c.next,c.next=n),s.pending=n,Mn(e,i)}return c=s.interleaved,c===null?(n.next=n,Ma(s)):(n.next=c.next,c.next=n),s.interleaved=n,Mn(e,i)}function ws(e,n,i){if(n=n.updateQueue,n!==null&&(n=n.shared,(i&4194240)!==0)){var s=n.lanes;s&=e.pendingLanes,i|=s,n.lanes=i,si(e,i)}}function Nd(e,n){var i=e.updateQueue,s=e.alternate;if(s!==null&&(s=s.updateQueue,i===s)){var c=null,h=null;if(i=i.firstBaseUpdate,i!==null){do{var w={eventTime:i.eventTime,lane:i.lane,tag:i.tag,payload:i.payload,callback:i.callback,next:null};h===null?c=h=w:h=h.next=w,i=i.next}while(i!==null);h===null?c=h=n:h=h.next=n}else c=h=n;i={baseState:s.baseState,firstBaseUpdate:c,lastBaseUpdate:h,shared:s.shared,effects:s.effects},e.updateQueue=i;return}e=i.lastBaseUpdate,e===null?i.firstBaseUpdate=n:e.next=n,i.lastBaseUpdate=n}function _s(e,n,i,s){var c=e.updateQueue;tr=!1;var h=c.firstBaseUpdate,w=c.lastBaseUpdate,I=c.shared.pending;if(I!==null){c.shared.pending=null;var $=I,Z=$.next;$.next=null,w===null?h=Z:w.next=Z,w=$;var se=e.alternate;se!==null&&(se=se.updateQueue,I=se.lastBaseUpdate,I!==w&&(I===null?se.firstBaseUpdate=Z:I.next=Z,se.lastBaseUpdate=$))}if(h!==null){var ae=c.baseState;w=0,se=Z=$=null,I=h;do{var oe=I.lane,he=I.eventTime;if((s&oe)===oe){se!==null&&(se=se.next={eventTime:he,lane:0,tag:I.tag,payload:I.payload,callback:I.callback,next:null});e:{var ye=e,ve=I;switch(oe=n,he=i,ve.tag){case 1:if(ye=ve.payload,typeof ye=="function"){ae=ye.call(he,ae,oe);break e}ae=ye;break e;case 3:ye.flags=ye.flags&-65537|128;case 0:if(ye=ve.payload,oe=typeof ye=="function"?ye.call(he,ae,oe):ye,oe==null)break e;ae=B({},ae,oe);break e;case 2:tr=!0}}I.callback!==null&&I.lane!==0&&(e.flags|=64,oe=c.effects,oe===null?c.effects=[I]:oe.push(I))}else he={eventTime:he,lane:oe,tag:I.tag,payload:I.payload,callback:I.callback,next:null},se===null?(Z=se=he,$=ae):se=se.next=he,w|=oe;if(I=I.next,I===null){if(I=c.shared.pending,I===null)break;oe=I,I=oe.next,oe.next=null,c.lastBaseUpdate=oe,c.shared.pending=null}}while(!0);if(se===null&&($=ae),c.baseState=$,c.firstBaseUpdate=Z,c.lastBaseUpdate=se,n=c.shared.interleaved,n!==null){c=n;do w|=c.lane,c=c.next;while(c!==n)}else h===null&&(c.shared.lanes=0);Ir|=w,e.lanes=w,e.memoizedState=ae}}function jd(e,n,i){if(e=n.effects,n.effects=null,e!==null)for(n=0;ni?i:4,e(!0);var s=Aa.transition;Aa.transition={};try{e(!1),n()}finally{Oe=i,Aa.transition=s}}function Ud(){return Wt().memoizedState}function xy(e,n,i){var s=sr(e);if(i={lane:s,action:i,hasEagerState:!1,eagerState:null,next:null},Yd(e))Gd(n,i);else if(i=kd(e,n,i,s),i!==null){var c=St();Jt(i,e,s,c),Xd(i,n,s)}}function wy(e,n,i){var s=sr(e),c={lane:s,action:i,hasEagerState:!1,eagerState:null,next:null};if(Yd(e))Gd(n,c);else{var h=e.alternate;if(e.lanes===0&&(h===null||h.lanes===0)&&(h=n.lastRenderedReducer,h!==null))try{var w=n.lastRenderedState,I=h(w,i);if(c.hasEagerState=!0,c.eagerState=I,Xt(I,w)){var $=n.interleaved;$===null?(c.next=c,Ma(n)):(c.next=$.next,$.next=c),n.interleaved=c;return}}catch{}finally{}i=kd(e,n,c,s),i!==null&&(c=St(),Jt(i,e,s,c),Xd(i,n,s))}}function Yd(e){var n=e.alternate;return e===qe||n!==null&&n===qe}function Gd(e,n){uo=Es=!0;var i=e.pending;i===null?n.next=n:(n.next=i.next,i.next=n),e.pending=n}function Xd(e,n,i){if((i&4194240)!==0){var s=n.lanes;s&=e.pendingLanes,i|=s,n.lanes=i,si(e,i)}}var bs={readContext:Vt,useCallback:vt,useContext:vt,useEffect:vt,useImperativeHandle:vt,useInsertionEffect:vt,useLayoutEffect:vt,useMemo:vt,useReducer:vt,useRef:vt,useState:vt,useDebugValue:vt,useDeferredValue:vt,useTransition:vt,useMutableSource:vt,useSyncExternalStore:vt,useId:vt,unstable_isNewReconciler:!1},_y={readContext:Vt,useCallback:function(e,n){return mn().memoizedState=[e,n===void 0?null:n],e},useContext:Vt,useEffect:Dd,useImperativeHandle:function(e,n,i){return i=i!=null?i.concat([e]):null,Ns(4194308,4,Fd.bind(null,n,e),i)},useLayoutEffect:function(e,n){return Ns(4194308,4,e,n)},useInsertionEffect:function(e,n){return Ns(4,2,e,n)},useMemo:function(e,n){var i=mn();return n=n===void 0?null:n,e=e(),i.memoizedState=[e,n],e},useReducer:function(e,n,i){var s=mn();return n=i!==void 0?i(n):n,s.memoizedState=s.baseState=n,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:n},s.queue=e,e=e.dispatch=xy.bind(null,qe,e),[s.memoizedState,e]},useRef:function(e){var n=mn();return e={current:e},n.memoizedState=e},useState:Ad,useDebugValue:Ba,useDeferredValue:function(e){return mn().memoizedState=e},useTransition:function(){var e=Ad(!1),n=e[0];return e=vy.bind(null,e[1]),mn().memoizedState=e,[n,e]},useMutableSource:function(){},useSyncExternalStore:function(e,n,i){var s=qe,c=mn();if(Ge){if(i===void 0)throw Error(o(407));i=i()}else{if(i=n(),ct===null)throw Error(o(349));(Pr&30)!==0||Pd(s,n,i)}c.memoizedState=i;var h={value:i,getSnapshot:n};return c.queue=h,Dd(Td.bind(null,s,h,e),[e]),s.flags|=2048,ho(9,Id.bind(null,s,h,i,n),void 0,null),i},useId:function(){var e=mn(),n=ct.identifierPrefix;if(Ge){var i=Cn,s=bn;i=(s&~(1<<32-Lt(s)-1)).toString(32)+i,n=":"+n+"R"+i,i=co++,0<\/script>",e=e.removeChild(e.firstChild)):typeof s.is=="string"?e=w.createElement(i,{is:s.is}):(e=w.createElement(i),i==="select"&&(w=e,s.multiple?w.multiple=!0:s.size&&(w.size=s.size))):e=w.createElementNS(e,i),e[pn]=n,e[ro]=s,gf(e,n,!1,!1),n.stateNode=e;e:{switch(w=Dn(i,s),i){case"dialog":Ve("cancel",e),Ve("close",e),c=s;break;case"iframe":case"object":case"embed":Ve("load",e),c=s;break;case"video":case"audio":for(c=0;cbi&&(n.flags|=128,s=!0,po(h,!1),n.lanes=4194304)}else{if(!s)if(e=Ss(w),e!==null){if(n.flags|=128,s=!0,i=e.updateQueue,i!==null&&(n.updateQueue=i,n.flags|=4),po(h,!0),h.tail===null&&h.tailMode==="hidden"&&!w.alternate&&!Ge)return xt(n),null}else 2*Fe()-h.renderingStartTime>bi&&i!==1073741824&&(n.flags|=128,s=!0,po(h,!1),n.lanes=4194304);h.isBackwards?(w.sibling=n.child,n.child=w):(i=h.last,i!==null?i.sibling=w:n.child=w,h.last=w)}return h.tail!==null?(n=h.tail,h.rendering=n,h.tail=n.sibling,h.renderingStartTime=Fe(),n.sibling=null,i=Xe.current,Be(Xe,s?i&1|2:i&1),n):(xt(n),null);case 22:case 23:return fu(),s=n.memoizedState!==null,e!==null&&e.memoizedState!==null!==s&&(n.flags|=8192),s&&(n.mode&1)!==0?($t&1073741824)!==0&&(xt(n),n.subtreeFlags&6&&(n.flags|=8192)):xt(n),null;case 24:return null;case 25:return null}throw Error(o(156,n.tag))}function My(e,n){switch(_a(n),n.tag){case 1:return Et(n.type)&&ds(),e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 3:return ki(),We(kt),We(yt),La(),e=n.flags,(e&65536)!==0&&(e&128)===0?(n.flags=e&-65537|128,n):null;case 5:return Ta(n),null;case 13:if(We(Xe),e=n.memoizedState,e!==null&&e.dehydrated!==null){if(n.alternate===null)throw Error(o(340));xi()}return e=n.flags,e&65536?(n.flags=e&-65537|128,n):null;case 19:return We(Xe),null;case 4:return ki(),null;case 10:return ba(n.type._context),null;case 22:case 23:return fu(),null;case 24:return null;default:return null}}var Is=!1,wt=!1,Py=typeof WeakSet=="function"?WeakSet:Set,ge=null;function Ni(e,n){var i=e.ref;if(i!==null)if(typeof i=="function")try{i(null)}catch(s){Ze(e,n,s)}else i.current=null}function eu(e,n,i){try{i()}catch(s){Ze(e,n,s)}}var vf=!1;function Iy(e,n){if(fa=Ko,e=qc(),ia(e)){if("selectionStart"in e)var i={start:e.selectionStart,end:e.selectionEnd};else e:{i=(i=e.ownerDocument)&&i.defaultView||window;var s=i.getSelection&&i.getSelection();if(s&&s.rangeCount!==0){i=s.anchorNode;var c=s.anchorOffset,h=s.focusNode;s=s.focusOffset;try{i.nodeType,h.nodeType}catch{i=null;break e}var w=0,I=-1,$=-1,Z=0,se=0,ae=e,oe=null;t:for(;;){for(var he;ae!==i||c!==0&&ae.nodeType!==3||(I=w+c),ae!==h||s!==0&&ae.nodeType!==3||($=w+s),ae.nodeType===3&&(w+=ae.nodeValue.length),(he=ae.firstChild)!==null;)oe=ae,ae=he;for(;;){if(ae===e)break t;if(oe===i&&++Z===c&&(I=w),oe===h&&++se===s&&($=w),(he=ae.nextSibling)!==null)break;ae=oe,oe=ae.parentNode}ae=he}i=I===-1||$===-1?null:{start:I,end:$}}else i=null}i=i||{start:0,end:0}}else i=null;for(ha={focusedElem:e,selectionRange:i},Ko=!1,ge=n;ge!==null;)if(n=ge,e=n.child,(n.subtreeFlags&1028)!==0&&e!==null)e.return=n,ge=e;else for(;ge!==null;){n=ge;try{var ye=n.alternate;if((n.flags&1024)!==0)switch(n.tag){case 0:case 11:case 15:break;case 1:if(ye!==null){var ve=ye.memoizedProps,et=ye.memoizedState,X=n.stateNode,H=X.getSnapshotBeforeUpdate(n.elementType===n.type?ve:Qt(n.type,ve),et);X.__reactInternalSnapshotBeforeUpdate=H}break;case 3:var Q=n.stateNode.containerInfo;Q.nodeType===1?Q.textContent="":Q.nodeType===9&&Q.documentElement&&Q.removeChild(Q.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(o(163))}}catch(ue){Ze(n,n.return,ue)}if(e=n.sibling,e!==null){e.return=n.return,ge=e;break}ge=n.return}return ye=vf,vf=!1,ye}function go(e,n,i){var s=n.updateQueue;if(s=s!==null?s.lastEffect:null,s!==null){var c=s=s.next;do{if((c.tag&e)===e){var h=c.destroy;c.destroy=void 0,h!==void 0&&eu(n,i,h)}c=c.next}while(c!==s)}}function Ts(e,n){if(n=n.updateQueue,n=n!==null?n.lastEffect:null,n!==null){var i=n=n.next;do{if((i.tag&e)===e){var s=i.create;i.destroy=s()}i=i.next}while(i!==n)}}function tu(e){var n=e.ref;if(n!==null){var i=e.stateNode;switch(e.tag){case 5:e=i;break;default:e=i}typeof n=="function"?n(e):n.current=e}}function xf(e){var n=e.alternate;n!==null&&(e.alternate=null,xf(n)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(n=e.stateNode,n!==null&&(delete n[pn],delete n[ro],delete n[ya],delete n[hy],delete n[py])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function wf(e){return e.tag===5||e.tag===3||e.tag===4}function _f(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||wf(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function nu(e,n,i){var s=e.tag;if(s===5||s===6)e=e.stateNode,n?i.nodeType===8?i.parentNode.insertBefore(e,n):i.insertBefore(e,n):(i.nodeType===8?(n=i.parentNode,n.insertBefore(e,i)):(n=i,n.appendChild(e)),i=i._reactRootContainer,i!=null||n.onclick!==null||(n.onclick=us));else if(s!==4&&(e=e.child,e!==null))for(nu(e,n,i),e=e.sibling;e!==null;)nu(e,n,i),e=e.sibling}function ru(e,n,i){var s=e.tag;if(s===5||s===6)e=e.stateNode,n?i.insertBefore(e,n):i.appendChild(e);else if(s!==4&&(e=e.child,e!==null))for(ru(e,n,i),e=e.sibling;e!==null;)ru(e,n,i),e=e.sibling}var pt=null,Kt=!1;function rr(e,n,i){for(i=i.child;i!==null;)Sf(e,n,i),i=i.sibling}function Sf(e,n,i){if(Rt&&typeof Rt.onCommitFiberUnmount=="function")try{Rt.onCommitFiberUnmount(ri,i)}catch{}switch(i.tag){case 5:wt||Ni(i,n);case 6:var s=pt,c=Kt;pt=null,rr(e,n,i),pt=s,Kt=c,pt!==null&&(Kt?(e=pt,i=i.stateNode,e.nodeType===8?e.parentNode.removeChild(i):e.removeChild(i)):pt.removeChild(i.stateNode));break;case 18:pt!==null&&(Kt?(e=pt,i=i.stateNode,e.nodeType===8?ma(e.parentNode,i):e.nodeType===1&&ma(e,i),Yi(e)):ma(pt,i.stateNode));break;case 4:s=pt,c=Kt,pt=i.stateNode.containerInfo,Kt=!0,rr(e,n,i),pt=s,Kt=c;break;case 0:case 11:case 14:case 15:if(!wt&&(s=i.updateQueue,s!==null&&(s=s.lastEffect,s!==null))){c=s=s.next;do{var h=c,w=h.destroy;h=h.tag,w!==void 0&&((h&2)!==0||(h&4)!==0)&&eu(i,n,w),c=c.next}while(c!==s)}rr(e,n,i);break;case 1:if(!wt&&(Ni(i,n),s=i.stateNode,typeof s.componentWillUnmount=="function"))try{s.props=i.memoizedProps,s.state=i.memoizedState,s.componentWillUnmount()}catch(I){Ze(i,n,I)}rr(e,n,i);break;case 21:rr(e,n,i);break;case 22:i.mode&1?(wt=(s=wt)||i.memoizedState!==null,rr(e,n,i),wt=s):rr(e,n,i);break;default:rr(e,n,i)}}function kf(e){var n=e.updateQueue;if(n!==null){e.updateQueue=null;var i=e.stateNode;i===null&&(i=e.stateNode=new Py),n.forEach(function(s){var c=Fy.bind(null,e,s);i.has(s)||(i.add(s),s.then(c,c))})}}function Zt(e,n){var i=n.deletions;if(i!==null)for(var s=0;sc&&(c=w),s&=~h}if(s=c,s=Fe()-s,s=(120>s?120:480>s?480:1080>s?1080:1920>s?1920:3e3>s?3e3:4320>s?4320:1960*Ry(s/1960))-s,10e?16:e,or===null)var s=!1;else{if(e=or,or=null,Ds=0,(Ae&6)!==0)throw Error(o(331));var c=Ae;for(Ae|=4,ge=e.current;ge!==null;){var h=ge,w=h.child;if((ge.flags&16)!==0){var I=h.deletions;if(I!==null){for(var $=0;$Fe()-su?Rr(e,0):ou|=i),bt(e,n)}function zf(e,n){n===0&&((e.mode&1)===0?n=1:(n=oi,oi<<=1,(oi&130023424)===0&&(oi=4194304)));var i=St();e=Mn(e,n),e!==null&&(kr(e,n,i),bt(e,i))}function Oy(e){var n=e.memoizedState,i=0;n!==null&&(i=n.retryLane),zf(e,i)}function Fy(e,n){var i=0;switch(e.tag){case 13:var s=e.stateNode,c=e.memoizedState;c!==null&&(i=c.retryLane);break;case 19:s=e.stateNode;break;default:throw Error(o(314))}s!==null&&s.delete(n),zf(e,i)}var Df;Df=function(e,n,i){if(e!==null)if(e.memoizedProps!==n.pendingProps||kt.current)Nt=!0;else{if((e.lanes&i)===0&&(n.flags&128)===0)return Nt=!1,by(e,n,i);Nt=(e.flags&131072)!==0}else Nt=!1,Ge&&(n.flags&1048576)!==0&&gd(n,gs,n.index);switch(n.lanes=0,n.tag){case 2:var s=n.type;Ps(e,n),e=n.pendingProps;var c=mi(n,yt.current);Si(n,i),c=Da(null,n,s,e,c,i);var h=$a();return n.flags|=1,typeof c=="object"&&c!==null&&typeof c.render=="function"&&c.$$typeof===void 0?(n.tag=1,n.memoizedState=null,n.updateQueue=null,Et(s)?(h=!0,fs(n)):h=!1,n.memoizedState=c.state!==null&&c.state!==void 0?c.state:null,Pa(n),c.updater=Cs,n.stateNode=c,c._reactInternals=n,Wa(n,s,e,i),n=Xa(null,n,s,!0,h,i)):(n.tag=0,Ge&&h&&wa(n),_t(null,n,c,i),n=n.child),n;case 16:s=n.elementType;e:{switch(Ps(e,n),e=n.pendingProps,c=s._init,s=c(s._payload),n.type=s,c=n.tag=By(s),e=Qt(s,e),c){case 0:n=Ga(null,n,s,e,i);break e;case 1:n=uf(null,n,s,e,i);break e;case 11:n=rf(null,n,s,e,i);break e;case 14:n=of(null,n,s,Qt(s.type,e),i);break e}throw Error(o(306,s,""))}return n;case 0:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:Qt(s,c),Ga(e,n,s,c,i);case 1:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:Qt(s,c),uf(e,n,s,c,i);case 3:e:{if(cf(n),e===null)throw Error(o(387));s=n.pendingProps,h=n.memoizedState,c=h.element,Ed(e,n),_s(n,s,null,i);var w=n.memoizedState;if(s=w.element,h.isDehydrated)if(h={element:s,isDehydrated:!1,cache:w.cache,pendingSuspenseBoundaries:w.pendingSuspenseBoundaries,transitions:w.transitions},n.updateQueue.baseState=h,n.memoizedState=h,n.flags&256){c=Ei(Error(o(423)),n),n=df(e,n,s,i,c);break e}else if(s!==c){c=Ei(Error(o(424)),n),n=df(e,n,s,i,c);break e}else for(Dt=Kn(n.stateNode.containerInfo.firstChild),zt=n,Ge=!0,qt=null,i=Sd(n,null,s,i),n.child=i;i;)i.flags=i.flags&-3|4096,i=i.sibling;else{if(xi(),s===c){n=In(e,n,i);break e}_t(e,n,s,i)}n=n.child}return n;case 5:return bd(n),e===null&&ka(n),s=n.type,c=n.pendingProps,h=e!==null?e.memoizedProps:null,w=c.children,pa(s,c)?w=null:h!==null&&pa(s,h)&&(n.flags|=32),af(e,n),_t(e,n,w,i),n.child;case 6:return e===null&&ka(n),null;case 13:return ff(e,n,i);case 4:return Ia(n,n.stateNode.containerInfo),s=n.pendingProps,e===null?n.child=wi(n,null,s,i):_t(e,n,s,i),n.child;case 11:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:Qt(s,c),rf(e,n,s,c,i);case 7:return _t(e,n,n.pendingProps,i),n.child;case 8:return _t(e,n,n.pendingProps.children,i),n.child;case 12:return _t(e,n,n.pendingProps.children,i),n.child;case 10:e:{if(s=n.type._context,c=n.pendingProps,h=n.memoizedProps,w=c.value,Be(vs,s._currentValue),s._currentValue=w,h!==null)if(Xt(h.value,w)){if(h.children===c.children&&!kt.current){n=In(e,n,i);break e}}else for(h=n.child,h!==null&&(h.return=n);h!==null;){var I=h.dependencies;if(I!==null){w=h.child;for(var $=I.firstContext;$!==null;){if($.context===s){if(h.tag===1){$=Pn(-1,i&-i),$.tag=2;var Z=h.updateQueue;if(Z!==null){Z=Z.shared;var se=Z.pending;se===null?$.next=$:($.next=se.next,se.next=$),Z.pending=$}}h.lanes|=i,$=h.alternate,$!==null&&($.lanes|=i),Ca(h.return,i,n),I.lanes|=i;break}$=$.next}}else if(h.tag===10)w=h.type===n.type?null:h.child;else if(h.tag===18){if(w=h.return,w===null)throw Error(o(341));w.lanes|=i,I=w.alternate,I!==null&&(I.lanes|=i),Ca(w,i,n),w=h.sibling}else w=h.child;if(w!==null)w.return=h;else for(w=h;w!==null;){if(w===n){w=null;break}if(h=w.sibling,h!==null){h.return=w.return,w=h;break}w=w.return}h=w}_t(e,n,c.children,i),n=n.child}return n;case 9:return c=n.type,s=n.pendingProps.children,Si(n,i),c=Vt(c),s=s(c),n.flags|=1,_t(e,n,s,i),n.child;case 14:return s=n.type,c=Qt(s,n.pendingProps),c=Qt(s.type,c),of(e,n,s,c,i);case 15:return sf(e,n,n.type,n.pendingProps,i);case 17:return s=n.type,c=n.pendingProps,c=n.elementType===s?c:Qt(s,c),Ps(e,n),n.tag=1,Et(s)?(e=!0,fs(n)):e=!1,Si(n,i),Qd(n,s,c),Wa(n,s,c,i),Xa(null,n,s,!0,e,i);case 19:return pf(e,n,i);case 22:return lf(e,n,i)}throw Error(o(156,n.tag))};function $f(e,n){return te(e,n)}function Hy(e,n,i,s){this.tag=e,this.key=i,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=n,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=s,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Yt(e,n,i,s){return new Hy(e,n,i,s)}function pu(e){return e=e.prototype,!(!e||!e.isReactComponent)}function By(e){if(typeof e=="function")return pu(e)?1:0;if(e!=null){if(e=e.$$typeof,e===ne)return 11;if(e===W)return 14}return 2}function ar(e,n){var i=e.alternate;return i===null?(i=Yt(e.tag,n,e.key,e.mode),i.elementType=e.elementType,i.type=e.type,i.stateNode=e.stateNode,i.alternate=e,e.alternate=i):(i.pendingProps=n,i.type=e.type,i.flags=0,i.subtreeFlags=0,i.deletions=null),i.flags=e.flags&14680064,i.childLanes=e.childLanes,i.lanes=e.lanes,i.child=e.child,i.memoizedProps=e.memoizedProps,i.memoizedState=e.memoizedState,i.updateQueue=e.updateQueue,n=e.dependencies,i.dependencies=n===null?null:{lanes:n.lanes,firstContext:n.firstContext},i.sibling=e.sibling,i.index=e.index,i.ref=e.ref,i}function Hs(e,n,i,s,c,h){var w=2;if(s=e,typeof e=="function")pu(e)&&(w=1);else if(typeof e=="string")w=5;else e:switch(e){case V:return Ar(i.children,c,h,n);case z:w=8,c|=8;break;case G:return e=Yt(12,i,n,c|2),e.elementType=G,e.lanes=h,e;case q:return e=Yt(13,i,n,c),e.elementType=q,e.lanes=h,e;case C:return e=Yt(19,i,n,c),e.elementType=C,e.lanes=h,e;case Y:return Bs(i,c,h,n);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ee:w=10;break e;case J:w=9;break e;case ne:w=11;break e;case W:w=14;break e;case U:w=16,s=null;break e}throw Error(o(130,e==null?e:typeof e,""))}return n=Yt(w,i,n,c),n.elementType=e,n.type=s,n.lanes=h,n}function Ar(e,n,i,s){return e=Yt(7,e,s,n),e.lanes=i,e}function Bs(e,n,i,s){return e=Yt(22,e,s,n),e.elementType=Y,e.lanes=i,e.stateNode={isHidden:!1},e}function gu(e,n,i){return e=Yt(6,e,null,n),e.lanes=i,e}function mu(e,n,i){return n=Yt(4,e.children!==null?e.children:[],e.key,n),n.lanes=i,n.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},n}function Vy(e,n,i,s,c){this.tag=n,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Sr(0),this.expirationTimes=Sr(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Sr(0),this.identifierPrefix=s,this.onRecoverableError=c,this.mutableSourceEagerHydrationData=null}function yu(e,n,i,s,c,h,w,I,$){return e=new Vy(e,n,i,I,$),n===1?(n=1,h===!0&&(n|=8)):n=0,h=Yt(3,null,null,n),e.current=h,h.stateNode=e,h.memoizedState={element:s,isDehydrated:i,cache:null,transitions:null,pendingSuspenseBoundaries:null},Pa(h),e}function Wy(e,n,i){var s=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(t)}catch(r){console.error(r)}}return t(),ku.exports=n0(),ku.exports}var Zf;function r0(){if(Zf)return qs;Zf=1;var t=wp();return qs.createRoot=t.createRoot,qs.hydrateRoot=t.hydrateRoot,qs}var i0=r0();function o0(t,r="Request failed"){const o=(t||"").trim();if(!o)return r;try{const a=JSON.parse(o).detail;if(typeof a=="string"&&a.trim())return a;if(Array.isArray(a)){const u=a.map(d=>typeof d=="string"?d:d&&typeof d=="object"&&"msg"in d?String(d.msg):"").filter(Boolean);if(u.length)return u.join("; ")}}catch{}return o}async function Ue(t,r){const o=await fetch(t,{...r,headers:{"Content-Type":"application/json",...(r==null?void 0:r.headers)||{}}});if(!o.ok){const l=await o.text();throw new Error(o0(l,o.statusText||"Request failed"))}return o.json()}const s0=["github_token","gitlab_token","gitlab_oauth_client_secret","bitbucket_token","bitbucket_oauth_client_secret","ai_api_key","ai_model","ai_base_url"],$e={health:()=>Ue("/api/health"),settings:()=>Ue("/api/settings"),saveSettings:t=>{const r={...t};for(const o of s0)r[o]===""&&delete r[o];return Ue("/api/settings",{method:"PUT",body:JSON.stringify(r)})},repos:()=>Ue("/api/repos"),browse:t=>Ue(`/api/fs${t?`?path=${encodeURIComponent(t)}`:""}`),gitRefs:(t,r=50)=>Ue(`/api/git/refs?repo_path=${encodeURIComponent(t)}&limit=${r}`),index:(t,r=!0)=>Ue("/api/index",{method:"POST",body:JSON.stringify({repo_path:t,incremental:r})}),indexStatus:t=>Ue(`/api/index?repo_path=${encodeURIComponent(t)}`),indexProgress:t=>Ue(`/api/index/progress?repo_path=${encodeURIComponent(t)}`),architecture:t=>Ue(`/api/architecture?repo_path=${encodeURIComponent(t)}`),review:(t,r,o,l=!0,a=!1)=>Ue("/api/review",{method:"POST",body:JSON.stringify({repo_path:t,base:r,head:o||null,reindex:l,incremental:!0,three_dot:!0,dirty:a})}),whatIf:(t,r)=>Ue("/api/whatif",{method:"POST",body:JSON.stringify({repo_path:t,node_id:r})}),reviewPr:(t,r,o,l)=>Ue("/api/prs/review",{method:"POST",body:JSON.stringify({provider:t,repo:r,number:o,repo_path:l||null})}),init:(t,r=!1)=>Ue("/api/init",{method:"POST",body:JSON.stringify({repo_path:t,overwrite:r})}),postComment:(t,r,o,l)=>Ue("/api/prs/comment",{method:"POST",body:JSON.stringify({provider:t,repo:r,number:o,markdown:l})}),graph:(t,r="full")=>Ue(`/api/graph?repo_path=${encodeURIComponent(t)}&scope=${r}`),prs:(t,r,o="open")=>Ue("/api/prs",{method:"POST",body:JSON.stringify({provider:t,repo:r,state:o})}),scmRepos:t=>Ue(`/api/scm/repos?provider=${encodeURIComponent(t)}`),oauthStatus:()=>Ue("/api/oauth/status"),githubOAuthStart:()=>Ue("/api/oauth/github/start",{method:"POST",body:"{}"}),githubOAuthPoll:t=>Ue("/api/oauth/github/poll",{method:"POST",body:JSON.stringify({flow_id:t})}),bitbucketOAuthStart:()=>Ue("/api/oauth/bitbucket/start"),gitlabOAuthStart:()=>Ue("/api/oauth/gitlab/start"),oauthDisconnect:t=>Ue("/api/oauth/disconnect",{method:"POST",body:JSON.stringify({provider:t})}),residual:t=>Ue("/api/ai/residual",{method:"POST",body:JSON.stringify({review:t})})};function bo(t){return t.replaceAll("_"," ")}function l0(t){return t.replaceAll("_"," ")}function Sl(t){return t.split(".").pop()||t}function _p(t){if(!t)return"";const r=new Date(t);return Number.isNaN(r.getTime())?t:r.toLocaleString()}function a0(t){return t.split(/[\\/]/).filter(Boolean).pop()||t}function Dr(t){return t.replace(/([/\\._:@-])/g,"$1​")}function Yr({className:t,children:r}){return p.jsx("svg",{className:t,width:"16",height:"16",viewBox:"0 0 16 16",fill:"none",stroke:"currentColor",strokeWidth:"1.5",strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",children:r})}function u0({className:t}){return p.jsxs(Yr,{className:t,children:[p.jsx("path",{d:"M3 3.5h6.5L13 7v5.5H3z"}),p.jsx("path",{d:"M9.5 3.5V7H13"}),p.jsx("path",{d:"M5.5 9.5h5M5.5 11.5h3.5"})]})}function c0({className:t}){return p.jsxs(Yr,{className:t,children:[p.jsx("rect",{x:"2.5",y:"2.5",width:"4.5",height:"4.5",rx:"0.8"}),p.jsx("rect",{x:"9",y:"2.5",width:"4.5",height:"4.5",rx:"0.8"}),p.jsx("rect",{x:"2.5",y:"9",width:"4.5",height:"4.5",rx:"0.8"}),p.jsx("rect",{x:"9",y:"9",width:"4.5",height:"4.5",rx:"0.8"})]})}function d0({className:t}){return p.jsxs(Yr,{className:t,children:[p.jsx("circle",{cx:"4",cy:"8",r:"1.6"}),p.jsx("circle",{cx:"12",cy:"4",r:"1.6"}),p.jsx("circle",{cx:"12",cy:"12",r:"1.6"}),p.jsx("path",{d:"M5.5 7.2 10.4 4.8M5.5 8.8 10.4 11.2"})]})}function f0({className:t}){return p.jsxs(Yr,{className:t,children:[p.jsx("circle",{cx:"4.5",cy:"4",r:"1.4"}),p.jsx("circle",{cx:"4.5",cy:"12",r:"1.4"}),p.jsx("circle",{cx:"11.5",cy:"12",r:"1.4"}),p.jsx("path",{d:"M4.5 5.5v5M4.5 8h4.2a3 3 0 0 1 3 3"})]})}function h0({className:t}){return p.jsxs(Yr,{className:t,children:[p.jsx("circle",{cx:"8",cy:"8",r:"2.1"}),p.jsx("path",{d:"M8 2.5v1.6M8 11.9v1.6M2.5 8h1.6M11.9 8h1.6M4.1 4.1l1.1 1.1M10.8 10.8l1.1 1.1M11.9 4.1l-1.1 1.1M5.2 10.8l-1.1 1.1"})]})}function Sp({className:t}){return p.jsx(Yr,{className:t,children:p.jsx("path",{d:"M2.5 4.5h4L8 6h5.5v6.5h-11z"})})}function p0({className:t}){return p.jsx(Yr,{className:t,children:p.jsx("path",{d:"M4 6.5 8 10.5 12 6.5"})})}const g0="modulepreload",m0=function(t,r){return new URL(t,r).href},Jf={},y0=function(r,o,l){let a=Promise.resolve();if(o&&o.length>0){let d=function(y){return Promise.all(y.map(x=>Promise.resolve(x).then(v=>({status:"fulfilled",value:v}),v=>({status:"rejected",reason:v}))))};const f=document.getElementsByTagName("link"),g=document.querySelector("meta[property=csp-nonce]"),m=(g==null?void 0:g.nonce)||(g==null?void 0:g.getAttribute("nonce"));a=d(o.map(y=>{if(y=m0(y,l),y in Jf)return;Jf[y]=!0;const x=y.endsWith(".css"),v=x?'[rel="stylesheet"]':"";if(!!l)for(let j=f.length-1;j>=0;j--){const b=f[j];if(b.href===y&&(!x||b.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${y}"]${v}`))return;const k=document.createElement("link");if(k.rel=x?"stylesheet":g0,x||(k.as="script"),k.crossOrigin="",k.href=y,m&&k.setAttribute("nonce",m),document.head.appendChild(k),x)return new Promise((j,b)=>{k.addEventListener("load",j),k.addEventListener("error",()=>b(new Error(`Unable to preload CSS for ${y}`)))})}))}function u(d){const f=new Event("vite:preloadError",{cancelable:!0});if(f.payload=d,window.dispatchEvent(f),!f.defaultPrevented)throw d}return a.then(d=>{for(const f of d||[])f.status==="rejected"&&u(f.reason);return r().catch(u)})};function it(t){if(typeof t=="string"||typeof t=="number")return""+t;let r="";if(Array.isArray(t))for(let o=0,l;o{}};function kl(){for(var t=0,r=arguments.length,o={},l;t=0&&(l=o.slice(a+1),o=o.slice(0,a)),o&&!r.hasOwnProperty(o))throw new Error("unknown type: "+o);return{type:o,name:l}})}sl.prototype=kl.prototype={constructor:sl,on:function(t,r){var o=this._,l=x0(t+"",o),a,u=-1,d=l.length;if(arguments.length<2){for(;++u0)for(var o=new Array(a),l=0,a,u;l=0&&(r=t.slice(0,o))!=="xmlns"&&(t=t.slice(o+1)),th.hasOwnProperty(r)?{space:th[r],local:t}:t}function _0(t){return function(){var r=this.ownerDocument,o=this.namespaceURI;return o===Hu&&r.documentElement.namespaceURI===Hu?r.createElement(t):r.createElementNS(o,t)}}function S0(t){return function(){return this.ownerDocument.createElementNS(t.space,t.local)}}function kp(t){var r=El(t);return(r.local?S0:_0)(r)}function k0(){}function rc(t){return t==null?k0:function(){return this.querySelector(t)}}function E0(t){typeof t!="function"&&(t=rc(t));for(var r=this._groups,o=r.length,l=new Array(o),a=0;a=E&&(E=P+1);!(L=b[E])&&++E=0;)(d=l[a])&&(u&&d.compareDocumentPosition(u)^4&&u.parentNode.insertBefore(d,u),u=d);return this}function q0(t){t||(t=Q0);function r(x,v){return x&&v?t(x.__data__,v.__data__):!x-!v}for(var o=this._groups,l=o.length,a=new Array(l),u=0;ur?1:t>=r?0:NaN}function K0(){var t=arguments[0];return arguments[0]=this,t.apply(null,arguments),this}function Z0(){return Array.from(this)}function J0(){for(var t=this._groups,r=0,o=t.length;r1?this.each((r==null?cv:typeof r=="function"?fv:dv)(t,r,o??"")):Ri(this.node(),t)}function Ri(t,r){return t.style.getPropertyValue(r)||Cp(t).getComputedStyle(t,null).getPropertyValue(r)}function pv(t){return function(){delete this[t]}}function gv(t,r){return function(){this[t]=r}}function mv(t,r){return function(){var o=r.apply(this,arguments);o==null?delete this[t]:this[t]=o}}function yv(t,r){return arguments.length>1?this.each((r==null?pv:typeof r=="function"?mv:gv)(t,r)):this.node()[t]}function Mp(t){return t.trim().split(/^|\s+/)}function ic(t){return t.classList||new Pp(t)}function Pp(t){this._node=t,this._names=Mp(t.getAttribute("class")||"")}Pp.prototype={add:function(t){var r=this._names.indexOf(t);r<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var r=this._names.indexOf(t);r>=0&&(this._names.splice(r,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};function Ip(t,r){for(var o=ic(t),l=-1,a=r.length;++l=0&&(o=r.slice(l+1),r=r.slice(0,l)),{type:r,name:o}})}function Uv(t){return function(){var r=this.__on;if(r){for(var o=0,l=-1,a=r.length,u;o()=>t;function Bu(t,{sourceEvent:r,subject:o,target:l,identifier:a,active:u,x:d,y:f,dx:g,dy:m,dispatch:y}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:r,enumerable:!0,configurable:!0},subject:{value:o,enumerable:!0,configurable:!0},target:{value:l,enumerable:!0,configurable:!0},identifier:{value:a,enumerable:!0,configurable:!0},active:{value:u,enumerable:!0,configurable:!0},x:{value:d,enumerable:!0,configurable:!0},y:{value:f,enumerable:!0,configurable:!0},dx:{value:g,enumerable:!0,configurable:!0},dy:{value:m,enumerable:!0,configurable:!0},_:{value:y}})}Bu.prototype.on=function(){var t=this._.on.apply(this._,arguments);return t===this._?this:t};function tx(t){return!t.ctrlKey&&!t.button}function nx(){return this.parentNode}function rx(t,r){return r??{x:t.x,y:t.y}}function ix(){return navigator.maxTouchPoints||"ontouchstart"in this}function Dp(){var t=tx,r=nx,o=rx,l=ix,a={},u=kl("start","drag","end"),d=0,f,g,m,y,x=0;function v(N){N.on("mousedown.drag",S).filter(l).on("touchstart.drag",b).on("touchmove.drag",_,ex).on("touchend.drag touchcancel.drag",P).style("touch-action","none").style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}function S(N,L){if(!(y||!t.call(this,N,L))){var A=E(this,r.call(this,N,L),N,L,"mouse");A&&(Ot(N.view).on("mousemove.drag",k,Co).on("mouseup.drag",j,Co),Ap(N.view),ju(N),m=!1,f=N.clientX,g=N.clientY,A("start",N))}}function k(N){if(Ii(N),!m){var L=N.clientX-f,A=N.clientY-g;m=L*L+A*A>x}a.mouse("drag",N)}function j(N){Ot(N.view).on("mousemove.drag mouseup.drag",null),zp(N.view,m),Ii(N),a.mouse("end",N)}function b(N,L){if(t.call(this,N,L)){var A=N.changedTouches,V=r.call(this,N,L),z=A.length,G,ee;for(G=0;G>8&15|r>>4&240,r>>4&15|r&240,(r&15)<<4|r&15,1):o===8?Ks(r>>24&255,r>>16&255,r>>8&255,(r&255)/255):o===4?Ks(r>>12&15|r>>8&240,r>>8&15|r>>4&240,r>>4&15|r&240,((r&15)<<4|r&15)/255):null):(r=sx.exec(t))?new Mt(r[1],r[2],r[3],1):(r=lx.exec(t))?new Mt(r[1]*255/100,r[2]*255/100,r[3]*255/100,1):(r=ax.exec(t))?Ks(r[1],r[2],r[3],r[4]):(r=ux.exec(t))?Ks(r[1]*255/100,r[2]*255/100,r[3]*255/100,r[4]):(r=cx.exec(t))?ah(r[1],r[2]/100,r[3]/100,1):(r=dx.exec(t))?ah(r[1],r[2]/100,r[3]/100,r[4]):nh.hasOwnProperty(t)?oh(nh[t]):t==="transparent"?new Mt(NaN,NaN,NaN,0):null}function oh(t){return new Mt(t>>16&255,t>>8&255,t&255,1)}function Ks(t,r,o,l){return l<=0&&(t=r=o=NaN),new Mt(t,r,o,l)}function px(t){return t instanceof Fo||(t=Hr(t)),t?(t=t.rgb(),new Mt(t.r,t.g,t.b,t.opacity)):new Mt}function Vu(t,r,o,l){return arguments.length===1?px(t):new Mt(t,r,o,l??1)}function Mt(t,r,o,l){this.r=+t,this.g=+r,this.b=+o,this.opacity=+l}oc(Mt,Vu,$p(Fo,{brighter(t){return t=t==null?fl:Math.pow(fl,t),new Mt(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=t==null?Mo:Math.pow(Mo,t),new Mt(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new Mt(Or(this.r),Or(this.g),Or(this.b),hl(this.opacity))},displayable(){return-.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:sh,formatHex:sh,formatHex8:gx,formatRgb:lh,toString:lh}));function sh(){return`#${$r(this.r)}${$r(this.g)}${$r(this.b)}`}function gx(){return`#${$r(this.r)}${$r(this.g)}${$r(this.b)}${$r((isNaN(this.opacity)?1:this.opacity)*255)}`}function lh(){const t=hl(this.opacity);return`${t===1?"rgb(":"rgba("}${Or(this.r)}, ${Or(this.g)}, ${Or(this.b)}${t===1?")":`, ${t})`}`}function hl(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function Or(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function $r(t){return t=Or(t),(t<16?"0":"")+t.toString(16)}function ah(t,r,o,l){return l<=0?t=r=o=NaN:o<=0||o>=1?t=r=NaN:r<=0&&(t=NaN),new tn(t,r,o,l)}function Op(t){if(t instanceof tn)return new tn(t.h,t.s,t.l,t.opacity);if(t instanceof Fo||(t=Hr(t)),!t)return new tn;if(t instanceof tn)return t;t=t.rgb();var r=t.r/255,o=t.g/255,l=t.b/255,a=Math.min(r,o,l),u=Math.max(r,o,l),d=NaN,f=u-a,g=(u+a)/2;return f?(r===u?d=(o-l)/f+(o0&&g<1?0:d,new tn(d,f,g,t.opacity)}function mx(t,r,o,l){return arguments.length===1?Op(t):new tn(t,r,o,l??1)}function tn(t,r,o,l){this.h=+t,this.s=+r,this.l=+o,this.opacity=+l}oc(tn,mx,$p(Fo,{brighter(t){return t=t==null?fl:Math.pow(fl,t),new tn(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=t==null?Mo:Math.pow(Mo,t),new tn(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,r=isNaN(t)||isNaN(this.s)?0:this.s,o=this.l,l=o+(o<.5?o:1-o)*r,a=2*o-l;return new Mt(bu(t>=240?t-240:t+120,a,l),bu(t,a,l),bu(t<120?t+240:t-120,a,l),this.opacity)},clamp(){return new tn(uh(this.h),Zs(this.s),Zs(this.l),hl(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){const t=hl(this.opacity);return`${t===1?"hsl(":"hsla("}${uh(this.h)}, ${Zs(this.s)*100}%, ${Zs(this.l)*100}%${t===1?")":`, ${t})`}`}}));function uh(t){return t=(t||0)%360,t<0?t+360:t}function Zs(t){return Math.max(0,Math.min(1,t||0))}function bu(t,r,o){return(t<60?r+(o-r)*t/60:t<180?o:t<240?r+(o-r)*(240-t)/60:r)*255}const sc=t=>()=>t;function yx(t,r){return function(o){return t+o*r}}function vx(t,r,o){return t=Math.pow(t,o),r=Math.pow(r,o)-t,o=1/o,function(l){return Math.pow(t+l*r,o)}}function xx(t){return(t=+t)==1?Fp:function(r,o){return o-r?vx(r,o,t):sc(isNaN(r)?o:r)}}function Fp(t,r){var o=r-t;return o?yx(t,o):sc(isNaN(t)?r:t)}const pl=(function t(r){var o=xx(r);function l(a,u){var d=o((a=Vu(a)).r,(u=Vu(u)).r),f=o(a.g,u.g),g=o(a.b,u.b),m=Fp(a.opacity,u.opacity);return function(y){return a.r=d(y),a.g=f(y),a.b=g(y),a.opacity=m(y),a+""}}return l.gamma=t,l})(1);function wx(t,r){r||(r=[]);var o=t?Math.min(r.length,t.length):0,l=r.slice(),a;return function(u){for(a=0;ao&&(u=r.slice(o,u),f[d]?f[d]+=u:f[++d]=u),(l=l[0])===(a=a[0])?f[d]?f[d]+=a:f[++d]=a:(f[++d]=null,g.push({i:d,x:vn(l,a)})),o=Cu.lastIndex;return o180?y+=360:y-m>180&&(m+=360),v.push({i:x.push(a(x)+"rotate(",null,l)-2,x:vn(m,y)})):y&&x.push(a(x)+"rotate("+y+l)}function f(m,y,x,v){m!==y?v.push({i:x.push(a(x)+"skewX(",null,l)-2,x:vn(m,y)}):y&&x.push(a(x)+"skewX("+y+l)}function g(m,y,x,v,S,k){if(m!==x||y!==v){var j=S.push(a(S)+"scale(",null,",",null,")");k.push({i:j-4,x:vn(m,x)},{i:j-2,x:vn(y,v)})}else(x!==1||v!==1)&&S.push(a(S)+"scale("+x+","+v+")")}return function(m,y){var x=[],v=[];return m=t(m),y=t(y),u(m.translateX,m.translateY,y.translateX,y.translateY,x,v),d(m.rotate,y.rotate,x,v),f(m.skewX,y.skewX,x,v),g(m.scaleX,m.scaleY,y.scaleX,y.scaleY,x,v),m=y=null,function(S){for(var k=-1,j=v.length,b;++k=0&&t._call.call(void 0,r),t=t._next;--Li}function fh(){Br=(ml=Io.now())+Nl,Li=ko=0;try{Ax()}finally{Li=0,Dx(),Br=0}}function zx(){var t=Io.now(),r=t-ml;r>Wp&&(Nl-=r,ml=t)}function Dx(){for(var t,r=gl,o,l=1/0;r;)r._call?(l>r._time&&(l=r._time),t=r,r=r._next):(o=r._next,r._next=null,r=t?t._next=o:gl=o);Eo=t,Yu(l)}function Yu(t){if(!Li){ko&&(ko=clearTimeout(ko));var r=t-Br;r>24?(t<1/0&&(ko=setTimeout(fh,t-Io.now()-Nl)),_o&&(_o=clearInterval(_o))):(_o||(ml=Io.now(),_o=setInterval(zx,Wp)),Li=1,Up(fh))}}function hh(t,r,o){var l=new yl;return r=r==null?0:+r,l.restart(a=>{l.stop(),t(a+r)},r,o),l}var $x=kl("start","end","cancel","interrupt"),Ox=[],Gp=0,ph=1,Gu=2,al=3,gh=4,Xu=5,ul=6;function jl(t,r,o,l,a,u){var d=t.__transition;if(!d)t.__transition={};else if(o in d)return;Fx(t,o,{name:r,index:l,group:a,on:$x,tween:Ox,time:u.time,delay:u.delay,duration:u.duration,ease:u.ease,timer:null,state:Gp})}function ac(t,r){var o=sn(t,r);if(o.state>Gp)throw new Error("too late; already scheduled");return o}function wn(t,r){var o=sn(t,r);if(o.state>al)throw new Error("too late; already running");return o}function sn(t,r){var o=t.__transition;if(!o||!(o=o[r]))throw new Error("transition not found");return o}function Fx(t,r,o){var l=t.__transition,a;l[r]=o,o.timer=Yp(u,0,o.time);function u(m){o.state=ph,o.timer.restart(d,o.delay,o.time),o.delay<=m&&d(m-o.delay)}function d(m){var y,x,v,S;if(o.state!==ph)return g();for(y in l)if(S=l[y],S.name===o.name){if(S.state===al)return hh(d);S.state===gh?(S.state=ul,S.timer.stop(),S.on.call("interrupt",t,t.__data__,S.index,S.group),delete l[y]):+yGu&&l.state=0&&(r=r.slice(0,o)),!r||r==="start"})}function mw(t,r,o){var l,a,u=gw(r)?ac:wn;return function(){var d=u(this,t),f=d.on;f!==l&&(a=(l=f).copy()).on(r,o),d.on=a}}function yw(t,r){var o=this._id;return arguments.length<2?sn(this.node(),o).on.on(t):this.each(mw(o,t,r))}function vw(t){return function(){var r=this.parentNode;for(var o in this.__transition)if(+o!==t)return;r&&r.removeChild(this)}}function xw(){return this.on("end.remove",vw(this._id))}function ww(t){var r=this._name,o=this._id;typeof t!="function"&&(t=rc(t));for(var l=this._groups,a=l.length,u=new Array(a),d=0;d()=>t;function Uw(t,{sourceEvent:r,target:o,transform:l,dispatch:a}){Object.defineProperties(this,{type:{value:t,enumerable:!0,configurable:!0},sourceEvent:{value:r,enumerable:!0,configurable:!0},target:{value:o,enumerable:!0,configurable:!0},transform:{value:l,enumerable:!0,configurable:!0},_:{value:a}})}function Ln(t,r,o){this.k=t,this.x=r,this.y=o}Ln.prototype={constructor:Ln,scale:function(t){return t===1?this:new Ln(this.k*t,this.x,this.y)},translate:function(t,r){return t===0&r===0?this:new Ln(this.k,this.x+this.k*t,this.y+this.k*r)},apply:function(t){return[t[0]*this.k+this.x,t[1]*this.k+this.y]},applyX:function(t){return t*this.k+this.x},applyY:function(t){return t*this.k+this.y},invert:function(t){return[(t[0]-this.x)/this.k,(t[1]-this.y)/this.k]},invertX:function(t){return(t-this.x)/this.k},invertY:function(t){return(t-this.y)/this.k},rescaleX:function(t){return t.copy().domain(t.range().map(this.invertX,this).map(t.invert,t))},rescaleY:function(t){return t.copy().domain(t.range().map(this.invertY,this).map(t.invert,t))},toString:function(){return"translate("+this.x+","+this.y+") scale("+this.k+")"}};var bl=new Ln(1,0,0);Kp.prototype=Ln.prototype;function Kp(t){for(;!t.__zoom;)if(!(t=t.parentNode))return bl;return t.__zoom}function Mu(t){t.stopImmediatePropagation()}function So(t){t.preventDefault(),t.stopImmediatePropagation()}function Yw(t){return(!t.ctrlKey||t.type==="wheel")&&!t.button}function Gw(){var t=this;return t instanceof SVGElement?(t=t.ownerSVGElement||t,t.hasAttribute("viewBox")?(t=t.viewBox.baseVal,[[t.x,t.y],[t.x+t.width,t.y+t.height]]):[[0,0],[t.width.baseVal.value,t.height.baseVal.value]]):[[0,0],[t.clientWidth,t.clientHeight]]}function mh(){return this.__zoom||bl}function Xw(t){return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*(t.ctrlKey?10:1)}function qw(){return navigator.maxTouchPoints||"ontouchstart"in this}function Qw(t,r,o){var l=t.invertX(r[0][0])-o[0][0],a=t.invertX(r[1][0])-o[1][0],u=t.invertY(r[0][1])-o[0][1],d=t.invertY(r[1][1])-o[1][1];return t.translate(a>l?(l+a)/2:Math.min(0,l)||Math.max(0,a),d>u?(u+d)/2:Math.min(0,u)||Math.max(0,d))}function Zp(){var t=Yw,r=Gw,o=Qw,l=Xw,a=qw,u=[0,1/0],d=[[-1/0,-1/0],[1/0,1/0]],f=250,g=ll,m=kl("start","zoom","end"),y,x,v,S=500,k=150,j=0,b=10;function _(C){C.property("__zoom",mh).on("wheel.zoom",z,{passive:!1}).on("mousedown.zoom",G).on("dblclick.zoom",ee).filter(a).on("touchstart.zoom",J).on("touchmove.zoom",ne).on("touchend.zoom touchcancel.zoom",q).style("-webkit-tap-highlight-color","rgba(0,0,0,0)")}_.transform=function(C,W,U,Y){var T=C.selection?C.selection():C;T.property("__zoom",mh),C!==T?L(C,W,U,Y):T.interrupt().each(function(){A(this,arguments).event(Y).start().zoom(null,typeof W=="function"?W.apply(this,arguments):W).end()})},_.scaleBy=function(C,W,U,Y){_.scaleTo(C,function(){var T=this.__zoom.k,F=typeof W=="function"?W.apply(this,arguments):W;return T*F},U,Y)},_.scaleTo=function(C,W,U,Y){_.transform(C,function(){var T=r.apply(this,arguments),F=this.__zoom,B=U==null?N(T):typeof U=="function"?U.apply(this,arguments):U,M=F.invert(B),R=typeof W=="function"?W.apply(this,arguments):W;return o(E(P(F,R),B,M),T,d)},U,Y)},_.translateBy=function(C,W,U,Y){_.transform(C,function(){return o(this.__zoom.translate(typeof W=="function"?W.apply(this,arguments):W,typeof U=="function"?U.apply(this,arguments):U),r.apply(this,arguments),d)},null,Y)},_.translateTo=function(C,W,U,Y,T){_.transform(C,function(){var F=r.apply(this,arguments),B=this.__zoom,M=Y==null?N(F):typeof Y=="function"?Y.apply(this,arguments):Y;return o(bl.translate(M[0],M[1]).scale(B.k).translate(typeof W=="function"?-W.apply(this,arguments):-W,typeof U=="function"?-U.apply(this,arguments):-U),F,d)},Y,T)};function P(C,W){return W=Math.max(u[0],Math.min(u[1],W)),W===C.k?C:new Ln(W,C.x,C.y)}function E(C,W,U){var Y=W[0]-U[0]*C.k,T=W[1]-U[1]*C.k;return Y===C.x&&T===C.y?C:new Ln(C.k,Y,T)}function N(C){return[(+C[0][0]+ +C[1][0])/2,(+C[0][1]+ +C[1][1])/2]}function L(C,W,U,Y){C.on("start.zoom",function(){A(this,arguments).event(Y).start()}).on("interrupt.zoom end.zoom",function(){A(this,arguments).event(Y).end()}).tween("zoom",function(){var T=this,F=arguments,B=A(T,F).event(Y),M=r.apply(T,F),R=U==null?N(M):typeof U=="function"?U.apply(T,F):U,re=Math.max(M[1][0]-M[0][0],M[1][1]-M[0][1]),ie=T.__zoom,ce=typeof W=="function"?W.apply(T,F):W,fe=g(ie.invert(R).concat(re/ie.k),ce.invert(R).concat(re/ce.k));return function(de){if(de===1)de=ce;else{var K=fe(de),le=re/K[2];de=new Ln(le,R[0]-K[0]*le,R[1]-K[1]*le)}B.zoom(null,de)}})}function A(C,W,U){return!U&&C.__zooming||new V(C,W)}function V(C,W){this.that=C,this.args=W,this.active=0,this.sourceEvent=null,this.extent=r.apply(C,W),this.taps=0}V.prototype={event:function(C){return C&&(this.sourceEvent=C),this},start:function(){return++this.active===1&&(this.that.__zooming=this,this.emit("start")),this},zoom:function(C,W){return this.mouse&&C!=="mouse"&&(this.mouse[1]=W.invert(this.mouse[0])),this.touch0&&C!=="touch"&&(this.touch0[1]=W.invert(this.touch0[0])),this.touch1&&C!=="touch"&&(this.touch1[1]=W.invert(this.touch1[0])),this.that.__zoom=W,this.emit("zoom"),this},end:function(){return--this.active===0&&(delete this.that.__zooming,this.emit("end")),this},emit:function(C){var W=Ot(this.that).datum();m.call(C,this.that,new Uw(C,{sourceEvent:this.sourceEvent,target:_,transform:this.that.__zoom,dispatch:m}),W)}};function z(C,...W){if(!t.apply(this,arguments))return;var U=A(this,W).event(C),Y=this.__zoom,T=Math.max(u[0],Math.min(u[1],Y.k*Math.pow(2,l.apply(this,arguments)))),F=en(C);if(U.wheel)(U.mouse[0][0]!==F[0]||U.mouse[0][1]!==F[1])&&(U.mouse[1]=Y.invert(U.mouse[0]=F)),clearTimeout(U.wheel);else{if(Y.k===T)return;U.mouse=[F,Y.invert(F)],cl(this),U.start()}So(C),U.wheel=setTimeout(B,k),U.zoom("mouse",o(E(P(Y,T),U.mouse[0],U.mouse[1]),U.extent,d));function B(){U.wheel=null,U.end()}}function G(C,...W){if(v||!t.apply(this,arguments))return;var U=C.currentTarget,Y=A(this,W,!0).event(C),T=Ot(C.view).on("mousemove.zoom",R,!0).on("mouseup.zoom",re,!0),F=en(C,U),B=C.clientX,M=C.clientY;Ap(C.view),Mu(C),Y.mouse=[F,this.__zoom.invert(F)],cl(this),Y.start();function R(ie){if(So(ie),!Y.moved){var ce=ie.clientX-B,fe=ie.clientY-M;Y.moved=ce*ce+fe*fe>j}Y.event(ie).zoom("mouse",o(E(Y.that.__zoom,Y.mouse[0]=en(ie,U),Y.mouse[1]),Y.extent,d))}function re(ie){T.on("mousemove.zoom mouseup.zoom",null),zp(ie.view,Y.moved),So(ie),Y.event(ie).end()}}function ee(C,...W){if(t.apply(this,arguments)){var U=this.__zoom,Y=en(C.changedTouches?C.changedTouches[0]:C,this),T=U.invert(Y),F=U.k*(C.shiftKey?.5:2),B=o(E(P(U,F),Y,T),r.apply(this,W),d);So(C),f>0?Ot(this).transition().duration(f).call(L,B,Y,C):Ot(this).call(_.transform,B,Y,C)}}function J(C,...W){if(t.apply(this,arguments)){var U=C.touches,Y=U.length,T=A(this,W,C.changedTouches.length===Y).event(C),F,B,M,R;for(Mu(C),B=0;B`Seems like you have not used ${t==="svelte"?"SvelteFlowProvider":"ReactFlowProvider"} as an ancestor. Help: https://${t}flow.dev/error#001`,error002:()=>"It looks like you've created a new nodeTypes or edgeTypes object. If this wasn't on purpose please define the nodeTypes/edgeTypes outside of the component or memoize them.",error003:t=>`Node type "${t}" not found. Using fallback type "default".`,error004:()=>"The parent container needs a width and a height to render the graph.",error005:()=>"Only child nodes can use a parent extent.",error006:()=>"Can't create edge. An edge needs a source and a target.",error007:t=>`The old edge with id=${t} does not exist.`,error009:t=>`Marker type "${t}" doesn't exist.`,error008:(t,{id:r,sourceHandle:o,targetHandle:l})=>`Couldn't create edge for ${t} handle id: "${t==="source"?o:l}", edge id: ${r}.`,error010:()=>"Handle: No node id found. Make sure to only use a Handle inside a custom Node.",error011:t=>`Edge type "${t}" not found. Using fallback type "default".`,error012:t=>`Node with id "${t}" does not exist, it may have been removed. This can happen when a node is deleted before the "onNodeClick" handler is called.`,error013:(t="react")=>`It seems that you haven't loaded the styles. Please import '@xyflow/${t}/dist/style.css' or base.css to make sure everything is working properly.`,error014:()=>"useNodeConnections: No node ID found. Call useNodeConnections inside a custom Node or provide a node ID.",error015:()=>"It seems that you are trying to drag a node that is not initialized. Please use onNodesChange as explained in the docs.",error016:t=>`Edge with id "${t}" does not exist, it may have been removed. This can happen when an edge is deleted before the "onEdgeClick" handler is called.`},To=[[Number.NEGATIVE_INFINITY,Number.NEGATIVE_INFINITY],[Number.POSITIVE_INFINITY,Number.POSITIVE_INFINITY]],Jp=["Enter"," ","Escape"],eg={"node.a11yDescription.default":"Press enter or space to select a node. Press delete to remove it and escape to cancel.","node.a11yDescription.keyboardDisabled":"Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.","node.a11yDescription.ariaLiveMessage":({direction:t,x:r,y:o})=>`Moved selected node ${t}. New position, x: ${r}, y: ${o}`,"edge.a11yDescription.default":"Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.","controls.ariaLabel":"Control Panel","controls.zoomIn.ariaLabel":"Zoom In","controls.zoomOut.ariaLabel":"Zoom Out","controls.fitView.ariaLabel":"Fit View","controls.interactive.ariaLabel":"Toggle Interactivity","minimap.ariaLabel":"Mini Map","handle.ariaLabel":"Handle"};var Ai;(function(t){t.Strict="strict",t.Loose="loose"})(Ai||(Ai={}));var Fr;(function(t){t.Free="free",t.Vertical="vertical",t.Horizontal="horizontal"})(Fr||(Fr={}));var Ro;(function(t){t.Partial="partial",t.Full="full"})(Ro||(Ro={}));const tg={inProgress:!1,isValid:null,from:null,fromHandle:null,fromPosition:null,fromNode:null,to:null,toHandle:null,toPosition:null,toNode:null,pointer:null};var hr;(function(t){t.Bezier="default",t.Straight="straight",t.Step="step",t.SmoothStep="smoothstep",t.SimpleBezier="simplebezier"})(hr||(hr={}));var Lo;(function(t){t.Arrow="arrow",t.ArrowClosed="arrowclosed"})(Lo||(Lo={}));var Se;(function(t){t.Left="left",t.Top="top",t.Right="right",t.Bottom="bottom"})(Se||(Se={}));const yh={[Se.Left]:Se.Right,[Se.Right]:Se.Left,[Se.Top]:Se.Bottom,[Se.Bottom]:Se.Top};function ng(t){return t===null?null:t?"valid":"invalid"}const rg=t=>!!t&&typeof t=="object"&&"id"in t&&"source"in t&&"target"in t,Kw=t=>!!t&&typeof t=="object"&&"id"in t&&"position"in t&&!("source"in t)&&!("target"in t),cc=t=>!!t&&typeof t=="object"&&"id"in t&&"internals"in t&&!("source"in t)&&!("target"in t),Ho=(t,r=[0,0])=>{const{width:o,height:l}=ln(t),a=t.origin??r,u=o*a[0],d=l*a[1];return{x:t.position.x-u,y:t.position.y-d}},Zw=(t,r={nodeOrigin:[0,0]})=>{if(t.length===0)return{x:0,y:0,width:0,height:0};let o=!1;const l=t.reduce((a,u)=>{const d=typeof u=="string";let f=!r.nodeLookup&&!d?u:void 0;return r.nodeLookup&&(f=d?r.nodeLookup.get(u):cc(u)?u:r.nodeLookup.get(u.id)),f?(o=!0,Cl(a,vl(f,r.nodeOrigin))):a},{x:1/0,y:1/0,x2:-1/0,y2:-1/0});return o?Ml(l):{x:0,y:0,width:0,height:0}},Bo=(t,r={})=>{let o={x:1/0,y:1/0,x2:-1/0,y2:-1/0},l=!1;return t.forEach(a=>{(r.filter===void 0||r.filter(a))&&(o=Cl(o,vl(a)),l=!0)}),l?Ml(o):{x:0,y:0,width:0,height:0}},dc=(t,r,[o,l,a]=[0,0,1],u=!1,d=!1)=>{const f=(r.x-o)/a,g=(r.y-l)/a,m=r.width/a,y=r.height/a,x=[];for(const v of t.values()){const{measured:S,selectable:k=!0,hidden:j=!1}=v;if(d&&!k||j)continue;const b=S.width??v.width??v.initialWidth??0,_=S.height??v.height??v.initialHeight??0,{x:P,y:E}=v.internals.positionAbsolute,N=lg(f,g,m,y,P,E,b,_),L=b*_,A=u&&N>0;(!v.internals.handleBounds||A||N>=L||v.dragging)&&x.push(v)}return x},Jw=(t,r)=>{const o=new Set;return t.forEach(l=>{o.add(l.id)}),r.filter(l=>o.has(l.source)||o.has(l.target))};function e1(t,r){const o=new Map,l=r!=null&&r.nodes?new Set(r.nodes.map(a=>a.id)):null;return t.forEach(a=>{let u;if(r!=null&&r.includeHiddenNodes){const{width:d,height:f}=ln(a);u=d>0&&f>0}else u=!!(a.measured.width&&a.measured.height&&!a.hidden);u&&(!l||l.has(a.id))&&o.set(a.id,a)}),o}async function t1({nodes:t,width:r,height:o,panZoom:l,minZoom:a,maxZoom:u},d){if(t.size===0)return!0;const f=e1(t,d),g=Bo(f),m=hc(g,r,o,(d==null?void 0:d.minZoom)??a,(d==null?void 0:d.maxZoom)??u,(d==null?void 0:d.padding)??.1);return await l.setViewport(m,{duration:d==null?void 0:d.duration,ease:d==null?void 0:d.ease,interpolate:d==null?void 0:d.interpolate}),!0}function ig({nodeId:t,nextPosition:r,nodeLookup:o,nodeOrigin:l=[0,0],nodeExtent:a,onError:u}){const d=o.get(t),f=d.parentId?o.get(d.parentId):void 0,{x:g,y:m}=f?f.internals.positionAbsolute:{x:0,y:0},y=d.origin??l;let x=d.extent||a;if(d.extent==="parent"&&!d.expandParent)if(!f)u==null||u("005",on.error005());else{const{width:S,height:k}=ln(f);S&&k&&(x=[[g,m],[g+S,m+k]])}else f&&Wr(d.extent)&&(x=[[d.extent[0][0]+g,d.extent[0][1]+m],[d.extent[1][0]+g,d.extent[1][1]+m]]);const v=Wr(x)?Vr(r,x,d.measured):r;return(d.measured.width===void 0||d.measured.height===void 0)&&(u==null||u("015",on.error015())),{position:{x:v.x-g+(d.measured.width??0)*y[0],y:v.y-m+(d.measured.height??0)*y[1]},positionAbsolute:v}}async function n1({nodesToRemove:t=[],edgesToRemove:r=[],nodes:o,edges:l,onBeforeDelete:a}){const u=new Set(t.map(v=>v.id)),d=[];for(const v of o){if(v.deletable===!1)continue;const S=u.has(v.id),k=!S&&v.parentId&&d.find(j=>j.id===v.parentId);(S||k)&&d.push(v)}const f=new Set(r.map(v=>v.id)),g=l.filter(v=>v.deletable!==!1),y=Jw(d,g);for(const v of g)f.has(v.id)&&!y.find(k=>k.id===v.id)&&y.push(v);if(!a)return{edges:y,nodes:d};const x=await a({nodes:d,edges:y});return typeof x=="boolean"?x?{edges:y,nodes:d}:{edges:[],nodes:[]}:x}const zi=(t,r=0,o=1)=>Math.min(Math.max(t,r),o),Vr=(t={x:0,y:0},r,o)=>({x:zi(t.x,r[0][0],r[1][0]-((o==null?void 0:o.width)??0)),y:zi(t.y,r[0][1],r[1][1]-((o==null?void 0:o.height)??0))});function og(t,r,o){const{width:l,height:a}=ln(o),{x:u,y:d}=o.internals.positionAbsolute;return Vr(t,[[u,d],[u+l,d+a]],r)}const vh=(t,r,o)=>to?-zi(Math.abs(t-o),1,r)/r:0,fc=(t,r,o=15,l=40)=>{const a=vh(t.x,l,r.width-l)*o,u=vh(t.y,l,r.height-l)*o;return[a,u]},Cl=(t,r)=>({x:Math.min(t.x,r.x),y:Math.min(t.y,r.y),x2:Math.max(t.x2,r.x2),y2:Math.max(t.y2,r.y2)}),qu=({x:t,y:r,width:o,height:l})=>({x:t,y:r,x2:t+o,y2:r+l}),Ml=({x:t,y:r,x2:o,y2:l})=>({x:t,y:r,width:o-t,height:l-r}),Ao=(t,r=[0,0])=>{var a,u;const{x:o,y:l}=cc(t)?t.internals.positionAbsolute:Ho(t,r);return{x:o,y:l,width:((a=t.measured)==null?void 0:a.width)??t.width??t.initialWidth??0,height:((u=t.measured)==null?void 0:u.height)??t.height??t.initialHeight??0}},vl=(t,r=[0,0])=>{var a,u;const{x:o,y:l}=cc(t)?t.internals.positionAbsolute:Ho(t,r);return{x:o,y:l,x2:o+(((a=t.measured)==null?void 0:a.width)??t.width??t.initialWidth??0),y2:l+(((u=t.measured)==null?void 0:u.height)??t.height??t.initialHeight??0)}},sg=(t,r)=>Ml(Cl(qu(t),qu(r))),lg=(t,r,o,l,a,u,d,f)=>{const g=Math.max(0,Math.min(t+o,a+d)-Math.max(t,a)),m=Math.max(0,Math.min(r+l,u+f)-Math.max(r,u));return Math.ceil(g*m)},xl=(t,r)=>lg(t.x,t.y,t.width,t.height,r.x,r.y,r.width,r.height),xh=t=>nn(t.width)&&nn(t.height)&&nn(t.x)&&nn(t.y),nn=t=>!isNaN(t)&&isFinite(t),ag=(t,r)=>(o,l)=>{},Vo=(t,r=[1,1])=>({x:r[0]*Math.round(t.x/r[0]),y:r[1]*Math.round(t.y/r[1])}),Wo=({x:t,y:r},[o,l,a],u=!1,d=[1,1])=>{const f={x:(t-o)/a,y:(r-l)/a};return u?Vo(f,d):f},Di=({x:t,y:r},[o,l,a])=>({x:t*a+o,y:r*a+l});function Mi(t,r){if(typeof t=="number")return Math.floor((r-r/(1+t))*.5);if(typeof t=="string"&&t.endsWith("px")){const o=parseFloat(t);if(!Number.isNaN(o))return Math.floor(o)}if(typeof t=="string"&&t.endsWith("%")){const o=parseFloat(t);if(!Number.isNaN(o))return Math.floor(r*o*.01)}return console.error(`The padding value "${t}" is invalid. Please provide a number or a string with a valid unit (px or %).`),0}function r1(t,r,o){if(typeof t=="string"||typeof t=="number"){const l=Mi(t,o),a=Mi(t,r);return{top:l,right:a,bottom:l,left:a,x:a*2,y:l*2}}if(typeof t=="object"){const l=Mi(t.top??t.y??0,o),a=Mi(t.bottom??t.y??0,o),u=Mi(t.left??t.x??0,r),d=Mi(t.right??t.x??0,r);return{top:l,right:d,bottom:a,left:u,x:u+d,y:l+a}}return{top:0,right:0,bottom:0,left:0,x:0,y:0}}function i1(t,r,o,l,a,u){const{x:d,y:f}=Di(t,[r,o,l]),{x:g,y:m}=Di({x:t.x+t.width,y:t.y+t.height},[r,o,l]),y=a-g,x=u-m;return{left:Math.floor(d),top:Math.floor(f),right:Math.floor(y),bottom:Math.floor(x)}}const hc=(t,r,o,l,a,u)=>{const d=r1(u,r,o),f=(r-d.x)/t.width,g=(o-d.y)/t.height,m=Math.min(f,g),y=zi(m,l,a),x=t.x+t.width/2,v=t.y+t.height/2,S=r/2-x*y,k=o/2-v*y,j=i1(t,S,k,y,r,o),b={left:Math.min(j.left-d.left,0),top:Math.min(j.top-d.top,0),right:Math.min(j.right-d.right,0),bottom:Math.min(j.bottom-d.bottom,0)};return{x:S-b.left+b.right,y:k-b.top+b.bottom,zoom:y}},zo=()=>{var t;return typeof navigator<"u"&&((t=navigator==null?void 0:navigator.userAgent)==null?void 0:t.indexOf("Mac"))>=0};function Wr(t){return t!=null&&t!=="parent"}function ln(t){var r,o;return{width:((r=t.measured)==null?void 0:r.width)??t.width??t.initialWidth??0,height:((o=t.measured)==null?void 0:o.height)??t.height??t.initialHeight??0}}function ug(t){var r,o;return(((r=t.measured)==null?void 0:r.width)??t.width??t.initialWidth)!==void 0&&(((o=t.measured)==null?void 0:o.height)??t.height??t.initialHeight)!==void 0}function cg(t,r={width:0,height:0},o,l,a){const u={...t},d=l.get(o);if(d){const f=d.origin||a;u.x+=d.internals.positionAbsolute.x-(r.width??0)*f[0],u.y+=d.internals.positionAbsolute.y-(r.height??0)*f[1]}return u}function wh(t,r){if(t.size!==r.size)return!1;for(const o of t)if(!r.has(o))return!1;return!0}function o1(){let t,r;return{promise:new Promise((l,a)=>{t=l,r=a}),resolve:t,reject:r}}function s1(t){return{...eg,...t||{}}}function jo(t,{snapGrid:r=[0,0],snapToGrid:o=!1,transform:l,containerBounds:a}){const{x:u,y:d}=rn(t),f=Wo({x:u-((a==null?void 0:a.left)??0),y:d-((a==null?void 0:a.top)??0)},l),{x:g,y:m}=o?Vo(f,r):f;return{xSnapped:g,ySnapped:m,...f}}const pc=t=>({width:t.offsetWidth,height:t.offsetHeight}),dg=t=>{var r;return((r=t==null?void 0:t.getRootNode)==null?void 0:r.call(t))||(window==null?void 0:window.document)},l1=["INPUT","SELECT","TEXTAREA"];function fg(t){var l,a;const r=((a=(l=t.composedPath)==null?void 0:l.call(t))==null?void 0:a[0])||t.target;return(r==null?void 0:r.nodeType)!==1?!1:l1.includes(r.nodeName)||r.hasAttribute("contenteditable")||!!r.closest(".nokey")}const hg=t=>"clientX"in t,rn=(t,r)=>{var u,d;const o=hg(t),l=o?t.clientX:(u=t.touches)==null?void 0:u[0].clientX,a=o?t.clientY:(d=t.touches)==null?void 0:d[0].clientY;return{x:l-((r==null?void 0:r.left)??0),y:a-((r==null?void 0:r.top)??0)}},_h=(t,r,o,l,a)=>{const u=r.querySelectorAll(`.${t}`);return!u||!u.length?null:Array.from(u).map(d=>{const f=d.getBoundingClientRect();return{id:d.getAttribute("data-handleid"),type:t,nodeId:a,position:d.getAttribute("data-handlepos"),x:(f.left-o.left)/l,y:(f.top-o.top)/l,...pc(d)}})};function pg({sourceX:t,sourceY:r,targetX:o,targetY:l,sourceControlX:a,sourceControlY:u,targetControlX:d,targetControlY:f}){const g=t*.125+a*.375+d*.375+o*.125,m=r*.125+u*.375+f*.375+l*.125,y=Math.abs(g-t),x=Math.abs(m-r);return[g,m,y,x]}function tl(t,r){return t>=0?.5*t:r*25*Math.sqrt(-t)}function Sh({pos:t,x1:r,y1:o,x2:l,y2:a,c:u}){switch(t){case Se.Left:return[r-tl(r-l,u),o];case Se.Right:return[r+tl(l-r,u),o];case Se.Top:return[r,o-tl(o-a,u)];case Se.Bottom:return[r,o+tl(a-o,u)]}}function gg({sourceX:t,sourceY:r,sourcePosition:o=Se.Bottom,targetX:l,targetY:a,targetPosition:u=Se.Top,curvature:d=.25}){const[f,g]=Sh({pos:o,x1:t,y1:r,x2:l,y2:a,c:d}),[m,y]=Sh({pos:u,x1:l,y1:a,x2:t,y2:r,c:d}),[x,v,S,k]=pg({sourceX:t,sourceY:r,targetX:l,targetY:a,sourceControlX:f,sourceControlY:g,targetControlX:m,targetControlY:y});return[`M${t},${r} C${f},${g} ${m},${y} ${l},${a}`,x,v,S,k]}function mg({sourceX:t,sourceY:r,targetX:o,targetY:l}){const a=Math.abs(o-t)/2,u=o0}const c1=({source:t,sourceHandle:r,target:o,targetHandle:l})=>`xy-edge__${t}${r||""}-${o}${l||""}`,d1=(t,r)=>r.some(o=>o.source===t.source&&o.target===t.target&&(o.sourceHandle===t.sourceHandle||!o.sourceHandle&&!t.sourceHandle)&&(o.targetHandle===t.targetHandle||!o.targetHandle&&!t.targetHandle)),f1=(t,r,o={})=>{var u;if(!t.source||!t.target)return(u=o.onError)==null||u.call(o,"006",on.error006()),r;const l=o.getEdgeId||c1;let a;return rg(t)?a={...t}:a={...t,id:l(t)},d1(a,r)?r:(a.sourceHandle===null&&delete a.sourceHandle,a.targetHandle===null&&delete a.targetHandle,r.concat(a))};function yg({sourceX:t,sourceY:r,targetX:o,targetY:l}){const[a,u,d,f]=mg({sourceX:t,sourceY:r,targetX:o,targetY:l});return[`M ${t},${r}L ${o},${l}`,a,u,d,f]}const kh={[Se.Left]:{x:-1,y:0},[Se.Right]:{x:1,y:0},[Se.Top]:{x:0,y:-1},[Se.Bottom]:{x:0,y:1}},h1=({source:t,sourcePosition:r=Se.Bottom,target:o})=>r===Se.Left||r===Se.Right?t.xMath.sqrt(Math.pow(r.x-t.x,2)+Math.pow(r.y-t.y,2));function p1({source:t,sourcePosition:r=Se.Bottom,target:o,targetPosition:l=Se.Top,center:a,offset:u,stepPosition:d}){const f=kh[r],g=kh[l],m={x:t.x+f.x*u,y:t.y+f.y*u},y={x:o.x+g.x*u,y:o.y+g.y*u},x=h1({source:m,sourcePosition:r,target:y}),v=x.x!==0?"x":"y",S=x[v];let k=[],j,b;const _={x:0,y:0},P={x:0,y:0},[,,E,N]=mg({sourceX:t.x,sourceY:t.y,targetX:o.x,targetY:o.y});if(f[v]*g[v]===-1){v==="x"?(j=a.x??m.x+(y.x-m.x)*d,b=a.y??(m.y+y.y)/2):(j=a.x??(m.x+y.x)/2,b=a.y??m.y+(y.y-m.y)*d);const z=[{x:j,y:m.y},{x:j,y:y.y}],G=[{x:m.x,y:b},{x:y.x,y:b}];f[v]===S?k=v==="x"?z:G:k=v==="x"?G:z}else{const z=[{x:m.x,y:y.y}],G=[{x:y.x,y:m.y}];if(v==="x"?k=f.x===S?G:z:k=f.y===S?z:G,r===l){const C=Math.abs(t[v]-o[v]);if(C<=u){const W=Math.min(u-1,u-C);f[v]===S?_[v]=(m[v]>t[v]?-1:1)*W:P[v]=(y[v]>o[v]?-1:1)*W}}if(r!==l){const C=v==="x"?"y":"x",W=f[v]===g[C],U=m[C]>y[C],Y=m[C]=q?(j=(ee.x+J.x)/2,b=k[0].y):(j=k[0].x,b=(ee.y+J.y)/2)}const L={x:m.x+_.x,y:m.y+_.y},A={x:y.x+P.x,y:y.y+P.y};return[[t,...L.x!==k[0].x||L.y!==k[0].y?[L]:[],...k,...A.x!==k[k.length-1].x||A.y!==k[k.length-1].y?[A]:[],o],j,b,E,N]}function g1(t,r,o,l){const a=Math.min(Eh(t,r)/2,Eh(r,o)/2,l),{x:u,y:d}=r;if(t.x===u&&u===o.x||t.y===d&&d===o.y)return`L${u} ${d}`;if(t.y===d){const m=t.xo.id===r):t[0])||null}function Ku(t,r){return t?typeof t=="string"?t:`${r?`${r}__`:""}${Object.keys(t).sort().map(l=>`${l}=${t[l]}`).join("&")}`:""}function y1(t,{id:r,defaultColor:o,defaultMarkerStart:l,defaultMarkerEnd:a}){const u=new Set;return t.reduce((d,f)=>([f.markerStart||l,f.markerEnd||a].forEach(g=>{if(g&&typeof g=="object"){const m=Ku(g,r);u.has(m)||(d.push({id:m,color:g.color||o,...g}),u.add(m))}}),d),[]).sort((d,f)=>d.id.localeCompare(f.id))}const vg=1e3,v1=10,gc={nodeOrigin:[0,0],nodeExtent:To,elevateNodesOnSelect:!0,zIndexMode:"basic",defaults:{}},x1={...gc,checkEquality:!0};function mc(t,r){const o={...t};for(const l in r)r[l]!==void 0&&(o[l]=r[l]);return o}function w1(t,r,o){const l=mc(gc,o);for(const a of t.values())if(a.parentId)vc(a,t,r,l);else{const u=Ho(a,l.nodeOrigin),d=Wr(a.extent)?a.extent:l.nodeExtent,f=Vr(u,d,ln(a));a.internals.positionAbsolute=f}}function _1(t,r){if(!t.handles)return t.measured?r==null?void 0:r.internals.handleBounds:void 0;const o=[],l=[];for(const a of t.handles){const u={id:a.id,width:a.width??1,height:a.height??1,nodeId:t.id,x:a.x,y:a.y,position:a.position,type:a.type};a.type==="source"?o.push(u):a.type==="target"&&l.push(u)}return{source:o,target:l}}function yc(t){return t==="manual"}function Zu(t,r,o,l={}){var y,x;const a=mc(x1,l),u={i:0},d=new Map(r),f=a!=null&&a.elevateNodesOnSelect&&!yc(a.zIndexMode)?vg:0;let g=t.length>0,m=!1;r.clear(),o.clear();for(const v of t){let S=d.get(v.id);if(a.checkEquality&&v===(S==null?void 0:S.internals.userNode))r.set(v.id,S);else{const k=Ho(v,a.nodeOrigin),j=Wr(v.extent)?v.extent:a.nodeExtent,b=Vr(k,j,ln(v));S={...a.defaults,...v,measured:{width:(y=v.measured)==null?void 0:y.width,height:(x=v.measured)==null?void 0:x.height},internals:{positionAbsolute:b,handleBounds:_1(v,S),z:xg(v,f,a.zIndexMode),userNode:v}},r.set(v.id,S)}(S.measured===void 0||S.measured.width===void 0||S.measured.height===void 0)&&!S.hidden&&(g=!1),v.parentId&&vc(S,r,o,l,u),m||(m=v.selected??!1)}return{nodesInitialized:g,hasSelectedNodes:m}}function S1(t,r){if(!t.parentId)return;const o=r.get(t.parentId);o?o.set(t.id,t):r.set(t.parentId,new Map([[t.id,t]]))}function vc(t,r,o,l,a){const{elevateNodesOnSelect:u,nodeOrigin:d,nodeExtent:f,zIndexMode:g}=mc(gc,l),m=t.parentId,y=r.get(m);if(!y){console.warn(`Parent node ${m} not found. Please make sure that parent nodes are in front of their child nodes in the nodes array.`);return}S1(t,o),a&&!y.parentId&&y.internals.rootParentIndex===void 0&&g==="auto"&&(y.internals.rootParentIndex=++a.i,y.internals.z=y.internals.z+a.i*v1),a&&y.internals.rootParentIndex!==void 0&&(a.i=y.internals.rootParentIndex);const x=u&&!yc(g)?vg:0,{x:v,y:S,z:k}=k1(t,y,d,f,x,g),{positionAbsolute:j}=t.internals,b=v!==j.x||S!==j.y;(b||k!==t.internals.z)&&r.set(t.id,{...t,internals:{...t.internals,positionAbsolute:b?{x:v,y:S}:j,z:k}})}function xg(t,r,o){const l=nn(t.zIndex)?t.zIndex:0;return yc(o)?l:l+(t.selected?r:0)}function k1(t,r,o,l,a,u){const{x:d,y:f}=r.internals.positionAbsolute,g=ln(t),m=Ho(t,o),y=Wr(t.extent)?Vr(m,t.extent,g):m;let x=Vr({x:d+y.x,y:f+y.y},l,g);t.extent==="parent"&&(x=og(x,g,r));const v=xg(t,a,u),S=r.internals.z??0;return{x:x.x,y:x.y,z:S>=v?S+1:v}}function xc(t,r,o,l=[0,0]){var d;const a=[],u=new Map;for(const f of t){const g=r.get(f.parentId);if(!g)continue;const m=((d=u.get(f.parentId))==null?void 0:d.expandedRect)??Ao(g),y=sg(m,f.rect);u.set(f.parentId,{expandedRect:y,parent:g})}return u.size>0&&u.forEach(({expandedRect:f,parent:g},m)=>{var E;const y=g.internals.positionAbsolute,x=ln(g),v=g.origin??l,S=f.x0||k>0||_||P)&&(a.push({id:m,type:"position",position:{x:g.position.x-S+_,y:g.position.y-k+P}}),(E=o.get(m))==null||E.forEach(N=>{t.some(L=>L.id===N.id)||a.push({id:N.id,type:"position",position:{x:N.position.x+S,y:N.position.y+k}})})),(x.width0){const S=xc(v,r,o,a);m.push(...S)}return{changes:m,updatedInternals:g}}async function N1({delta:t,panZoom:r,transform:o,translateExtent:l,width:a,height:u}){if(!r||!t.x&&!t.y)return!1;const d=await r.setViewportConstrained({x:o[0]+t.x,y:o[1]+t.y,zoom:o[2]},[[0,0],[a,u]],l);return!!d&&(d.x!==o[0]||d.y!==o[1]||d.k!==o[2])}function Ch(t,r,o,l,a,u){let d=a;const f=l.get(d)||new Map;l.set(d,f.set(o,r)),d=`${a}-${t}`;const g=l.get(d)||new Map;if(l.set(d,g.set(o,r)),u){d=`${a}-${t}-${u}`;const m=l.get(d)||new Map;l.set(d,m.set(o,r))}}function wg(t,r,o){t.clear(),r.clear();for(const l of o){const{source:a,target:u,sourceHandle:d=null,targetHandle:f=null}=l,g={edgeId:l.id,source:a,target:u,sourceHandle:d,targetHandle:f},m=`${a}-${d}--${u}-${f}`,y=`${u}-${f}--${a}-${d}`;Ch("source",g,y,t,a,d),Ch("target",g,m,t,u,f),r.set(l.id,l)}}function _g(t,r){if(!t.parentId)return!1;const o=r.get(t.parentId);return o?o.selected?!0:_g(o,r):!1}function Mh(t,r,o){var a;let l=t;do{if((a=l==null?void 0:l.matches)!=null&&a.call(l,r))return!0;if(l===o)return!1;l=l==null?void 0:l.parentElement}while(l);return!1}function j1(t,r,o,l){const a=new Map;for(const[u,d]of t)if((d.selected||d.id===l)&&(!d.parentId||!_g(d,t))&&(d.draggable||r&&typeof d.draggable>"u")){const f=t.get(u);f&&a.set(u,{id:u,position:f.position||{x:0,y:0},distance:{x:o.x-f.internals.positionAbsolute.x,y:o.y-f.internals.positionAbsolute.y},extent:f.extent,parentId:f.parentId,origin:f.origin,expandParent:f.expandParent,internals:{positionAbsolute:f.internals.positionAbsolute||{x:0,y:0}},measured:{width:f.measured.width??0,height:f.measured.height??0}})}return a}function Pu({nodeId:t,dragItems:r,nodeLookup:o,dragging:l=!0}){var d,f,g;const a=[];for(const[m,y]of r){const x=(d=o.get(m))==null?void 0:d.internals.userNode;x&&a.push({...x,position:y.position,dragging:l})}if(!t)return[a[0],a];const u=(f=o.get(t))==null?void 0:f.internals.userNode;return[u?{...u,position:((g=r.get(t))==null?void 0:g.position)||u.position,dragging:l}:a[0],a]}function b1({dragItems:t,snapGrid:r,x:o,y:l}){const a=t.values().next().value;if(!a)return null;const u={x:o-a.distance.x,y:l-a.distance.y},d=Vo(u,r);return{x:d.x-u.x,y:d.y-u.y}}function C1({onNodeMouseDown:t,getStoreItems:r,onDragStart:o,onDrag:l,onDragStop:a}){let u={x:null,y:null},d=0,f=new Map,g=!1,m={x:0,y:0},y=null,x=!1,v=null,S=!1,k=!1,j=null;function b({noDragClassName:P,handleSelector:E,domNode:N,isSelectable:L,nodeId:A,nodeClickDistance:V=0}){v=Ot(N);function z({x:ne,y:q}){const{nodeLookup:C,nodeExtent:W,snapGrid:U,snapToGrid:Y,nodeOrigin:T,onNodeDrag:F,onSelectionDrag:B,onError:M,updateNodePositions:R}=r();u={x:ne,y:q};let re=!1;const ie=f.size>1,ce=ie&&W?qu(Bo(f)):null,fe=ie&&Y?b1({dragItems:f,snapGrid:U,x:ne,y:q}):null;for(const[de,K]of f){if(!C.has(de))continue;let le={x:ne-K.distance.x,y:q-K.distance.y};Y&&(le=fe?{x:Math.round(le.x+fe.x),y:Math.round(le.y+fe.y)}:Vo(le,U));let me=null;if(ie&&W&&!K.extent&&ce){const{positionAbsolute:pe}=K.internals,be=pe.x-ce.x+W[0][0],Pe=pe.x+K.measured.width-ce.x2+W[1][0],Ce=pe.y-ce.y+W[0][1],Re=pe.y+K.measured.height-ce.y2+W[1][1];me=[[be,Ce],[Pe,Re]]}const{position:ke,positionAbsolute:xe}=ig({nodeId:de,nextPosition:le,nodeLookup:C,nodeExtent:me||W,nodeOrigin:T,onError:M});re=re||K.position.x!==ke.x||K.position.y!==ke.y,K.position=ke,K.internals.positionAbsolute=xe}if(k=k||re,!!re&&(R(f,!0),j&&(l||F||!A&&B))){const[de,K]=Pu({nodeId:A,dragItems:f,nodeLookup:C});l==null||l(j,f,de,K),F==null||F(j,de,K),A||B==null||B(j,K)}}async function G(){if(!y)return;const{transform:ne,panBy:q,autoPanSpeed:C,autoPanOnNodeDrag:W}=r();if(!W){g=!1,cancelAnimationFrame(d);return}const[U,Y]=fc(m,y,C);(U!==0||Y!==0)&&(u.x=(u.x??0)-U/ne[2],u.y=(u.y??0)-Y/ne[2],await q({x:U,y:Y})&&z(u)),d=requestAnimationFrame(G)}function ee(ne){var ie;const{nodeLookup:q,multiSelectionActive:C,nodesDraggable:W,transform:U,snapGrid:Y,snapToGrid:T,selectNodesOnDrag:F,onNodeDragStart:B,onSelectionDragStart:M,unselectNodesAndEdges:R}=r();x=!0,(!F||!L)&&!C&&A&&((ie=q.get(A))!=null&&ie.selected||R()),L&&F&&A&&(t==null||t(A));const re=jo(ne.sourceEvent,{transform:U,snapGrid:Y,snapToGrid:T,containerBounds:y});if(u=re,f=j1(q,W,re,A),f.size>0&&(o||B||!A&&M)){const[ce,fe]=Pu({nodeId:A,dragItems:f,nodeLookup:q});o==null||o(ne.sourceEvent,f,ce,fe),B==null||B(ne.sourceEvent,ce,fe),A||M==null||M(ne.sourceEvent,fe)}}const J=Dp().clickDistance(V).on("start",ne=>{const{domNode:q,nodeDragThreshold:C,transform:W,snapGrid:U,snapToGrid:Y}=r();y=(q==null?void 0:q.getBoundingClientRect())||null,S=!1,k=!1,j=ne.sourceEvent,C===0&&ee(ne),u=jo(ne.sourceEvent,{transform:W,snapGrid:U,snapToGrid:Y,containerBounds:y}),m=rn(ne.sourceEvent,y)}).on("drag",ne=>{const{autoPanOnNodeDrag:q,transform:C,snapGrid:W,snapToGrid:U,nodeDragThreshold:Y,nodeLookup:T}=r(),F=jo(ne.sourceEvent,{transform:C,snapGrid:W,snapToGrid:U,containerBounds:y});if(j=ne.sourceEvent,(ne.sourceEvent.type==="touchmove"&&ne.sourceEvent.touches.length>1||A&&!T.has(A))&&(S=!0),!S){if(!g&&q&&x&&(g=!0,G()),!x){const B=rn(ne.sourceEvent,y),M=B.x-m.x,R=B.y-m.y;Math.sqrt(M*M+R*R)>Y&&ee(ne)}(u.x!==F.xSnapped||u.y!==F.ySnapped)&&f&&x&&(m=rn(ne.sourceEvent,y),z(F))}}).on("end",ne=>{if(!x||S){S&&f.size>0&&r().updateNodePositions(f,!1);return}if(g=!1,x=!1,cancelAnimationFrame(d),f.size>0){const{nodeLookup:q,updateNodePositions:C,onNodeDragStop:W,onSelectionDragStop:U}=r();if(k&&(C(f,!1),k=!1),a||W||!A&&U){const[Y,T]=Pu({nodeId:A,dragItems:f,nodeLookup:q,dragging:!1});a==null||a(ne.sourceEvent,f,Y,T),W==null||W(ne.sourceEvent,Y,T),A||U==null||U(ne.sourceEvent,T)}}}).filter(ne=>{const q=ne.target;return!ne.button&&(!P||!Mh(q,`.${P}`,N))&&(!E||Mh(q,E,N))});v.call(J)}function _(){v==null||v.on(".drag",null)}return{update:b,destroy:_}}function M1(t,r,o){const l=[],a={x:t.x-o,y:t.y-o,width:o*2,height:o*2};for(const u of r.values())xl(a,Ao(u))>0&&l.push(u);return l}const P1=250;function I1(t,r,o,l){var f,g;let a=[],u=1/0;const d=M1(t,o,r+P1);for(const m of d){const y=[...((f=m.internals.handleBounds)==null?void 0:f.source)??[],...((g=m.internals.handleBounds)==null?void 0:g.target)??[]];for(const x of y){if(l.nodeId===x.nodeId&&l.type===x.type&&l.id===x.id)continue;const{x:v,y:S}=Ur(m,x,x.position,!0),k=Math.sqrt(Math.pow(v-t.x,2)+Math.pow(S-t.y,2));k>r||(k1){const m=l.type==="source"?"target":"source";return a.find(y=>y.type===m)??a[0]}return a[0]}function Sg(t,r,o,l,a,u=!1){var m,y,x;const d=l.get(t);if(!d)return null;const f=a==="strict"?(m=d.internals.handleBounds)==null?void 0:m[r]:[...((y=d.internals.handleBounds)==null?void 0:y.source)??[],...((x=d.internals.handleBounds)==null?void 0:x.target)??[]],g=(o?f==null?void 0:f.find(v=>v.id===o):f==null?void 0:f[0])??null;return g&&u?{...g,...Ur(d,g,g.position,!0)}:g}function kg(t,r){return t||(r!=null&&r.classList.contains("target")?"target":r!=null&&r.classList.contains("source")?"source":null)}function T1(t,r){let o=null;return r?o=!0:t&&!r&&(o=!1),o}const Eg=()=>!0;function R1(t,{connectionMode:r,connectionRadius:o,handleId:l,nodeId:a,edgeUpdaterType:u,isTarget:d,domNode:f,nodeLookup:g,lib:m,autoPanOnConnect:y,flowId:x,panBy:v,cancelConnection:S,onConnectStart:k,onConnect:j,onConnectEnd:b,isValidConnection:_=Eg,onReconnectEnd:P,updateConnection:E,getTransform:N,getFromHandle:L,autoPanSpeed:A,dragThreshold:V=1,handleDomNode:z}){const G=dg(t.target);let ee=0,J;const{x:ne,y:q}=rn(t),C=kg(u,z),W=f==null?void 0:f.getBoundingClientRect();let U=!1;if(!W||!C)return;const Y=Sg(a,C,l,g,r);if(!Y)return;let T=rn(t,W),F=!1,B=null,M=!1,R=null;function re(){if(!y||!W)return;const[ke,xe]=fc(T,W,A);v({x:ke,y:xe}),ee=requestAnimationFrame(re)}const ie={...Y,nodeId:a,type:C,position:Y.position},ce=g.get(a);let de={inProgress:!0,isValid:null,from:Ur(ce,ie,Se.Left,!0),fromHandle:ie,fromPosition:ie.position,fromNode:ce,to:T,toHandle:null,toPosition:yh[ie.position],toNode:null,pointer:T};function K(){U=!0,E(de),k==null||k(t,{nodeId:a,handleId:l,handleType:C})}V===0&&K();function le(ke){if(!U){const{x:Re,y:tt}=rn(ke),nt=Re-ne,Je=tt-q;if(!(nt*nt+Je*Je>V*V))return;K()}if(!L()||!ie){me(ke);return}const xe=N();T=rn(ke,W),J=I1(Wo(T,xe,!1,[1,1]),o,g,ie),F||(re(),F=!0);const pe=Ng(ke,{handle:J,connectionMode:r,fromNodeId:a,fromHandleId:l,fromType:d?"target":"source",isValidConnection:_,doc:G,lib:m,flowId:x,nodeLookup:g});R=pe.handleDomNode,B=pe.connection,M=T1(!!J,pe.isValid);const be=g.get(a),Pe=be?Ur(be,ie,Se.Left,!0):de.from,Ce={...de,from:Pe,isValid:M,to:pe.toHandle&&M?Di({x:pe.toHandle.x,y:pe.toHandle.y},xe):T,toHandle:pe.toHandle,toPosition:M&&pe.toHandle?pe.toHandle.position:yh[ie.position],toNode:pe.toHandle?g.get(pe.toHandle.nodeId):null,pointer:T};E(Ce),de=Ce}function me(ke){if(!("touches"in ke&&ke.touches.length>0)){if(U){(J||R)&&B&&M&&(j==null||j(B));const{inProgress:xe,...pe}=de,be={...pe,toPosition:de.toHandle?de.toPosition:null};b==null||b(ke,be),u&&(P==null||P(ke,be))}S(),cancelAnimationFrame(ee),F=!1,M=!1,B=null,R=null,G.removeEventListener("mousemove",le),G.removeEventListener("mouseup",me),G.removeEventListener("touchmove",le),G.removeEventListener("touchend",me)}}G.addEventListener("mousemove",le),G.addEventListener("mouseup",me),G.addEventListener("touchmove",le),G.addEventListener("touchend",me)}function Ng(t,{handle:r,connectionMode:o,fromNodeId:l,fromHandleId:a,fromType:u,doc:d,lib:f,flowId:g,isValidConnection:m=Eg,nodeLookup:y}){const x=u==="target",v=r?d.querySelector(`.${f}-flow__handle[data-id="${g}-${r==null?void 0:r.nodeId}-${r==null?void 0:r.id}-${r==null?void 0:r.type}"]`):null,{x:S,y:k}=rn(t),j=d.elementFromPoint(S,k),b=j!=null&&j.classList.contains(`${f}-flow__handle`)?j:v,_={handleDomNode:b,isValid:!1,connection:null,toHandle:null};if(b){const P=kg(void 0,b),E=b.getAttribute("data-nodeid"),N=b.getAttribute("data-handleid"),L=b.classList.contains("connectable"),A=b.classList.contains("connectableend");if(!E||!P)return _;const V={source:x?E:l,sourceHandle:x?N:a,target:x?l:E,targetHandle:x?a:N};_.connection=V;const G=L&&A&&(o===Ai.Strict?x&&P==="source"||!x&&P==="target":E!==l||N!==a);_.isValid=G&&m(V),_.toHandle=Sg(E,P,N,y,o,!0)}return _}const Ju={onPointerDown:R1,isValid:Ng};function L1({domNode:t,panZoom:r,getTransform:o,getViewScale:l}){const a=Ot(t);function u({translateExtent:f,width:g,height:m,zoomStep:y=1,pannable:x=!0,zoomable:v=!0,inversePan:S=!1}){const k=E=>{if(E.sourceEvent.type!=="wheel"||!r)return;const N=o(),L=E.sourceEvent.ctrlKey&&zo()?10:1,A=-E.sourceEvent.deltaY*(E.sourceEvent.deltaMode===1?.05:E.sourceEvent.deltaMode?1:.002)*y,V=N[2]*Math.pow(2,A*L);r.scaleTo(V)};let j=[0,0];const b=E=>{(E.sourceEvent.type==="mousedown"||E.sourceEvent.type==="touchstart")&&(j=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY])},_=E=>{const N=o();if(E.sourceEvent.type!=="mousemove"&&E.sourceEvent.type!=="touchmove"||!r)return;const L=[E.sourceEvent.clientX??E.sourceEvent.touches[0].clientX,E.sourceEvent.clientY??E.sourceEvent.touches[0].clientY],A=[L[0]-j[0],L[1]-j[1]];j=L;const V=l()*Math.max(N[2],Math.log(N[2]))*(S?-1:1),z={x:N[0]-A[0]*V,y:N[1]-A[1]*V},G=[[0,0],[g,m]];r.setViewportConstrained({x:z.x,y:z.y,zoom:N[2]},G,f)},P=Zp().on("start",b).on("zoom",x?_:null).on("zoom.wheel",v?k:null);a.call(P,{})}function d(){a.on("zoom",null)}return{update:u,destroy:d,pointer:en}}const Pl=t=>({x:t.x,y:t.y,zoom:t.k}),Iu=({x:t,y:r,zoom:o})=>bl.translate(t,r).scale(o),fr=(t,r)=>t.target.closest(`.${r}`),jg=(t,r)=>r===2&&Array.isArray(t)&&t.includes(2),A1=t=>((t*=2)<=1?t*t*t:(t-=2)*t*t+2)/2,Tu=(t,r=0,o=A1,l=()=>{})=>{const a=typeof r=="number"&&r>0;return a||l(),a?t.transition().duration(r).ease(o).on("end",l):t},bg=t=>{const r=t.ctrlKey&&zo()?10:1;return-t.deltaY*(t.deltaMode===1?.05:t.deltaMode?1:.002)*r};function z1({zoomPanValues:t,noWheelClassName:r,d3Selection:o,d3Zoom:l,panOnScrollMode:a,panOnScrollSpeed:u,zoomOnPinch:d,onPanZoomStart:f,onPanZoom:g,onPanZoomEnd:m}){return y=>{if(fr(y,r))return y.ctrlKey&&y.preventDefault(),!1;y.preventDefault(),y.stopImmediatePropagation();const x=o.property("__zoom").k||1;if(y.ctrlKey&&d){const b=en(y),_=bg(y),P=x*Math.pow(2,_);l.scaleTo(o,P,b,y);return}const v=y.deltaMode===1?20:1;let S=a===Fr.Vertical?0:y.deltaX*v,k=a===Fr.Horizontal?0:y.deltaY*v;!zo()&&y.shiftKey&&a!==Fr.Vertical&&(S=y.deltaY*v,k=0),l.translateBy(o,-(S/x)*u,-(k/x)*u,{internal:!0});const j=Pl(o.property("__zoom"));clearTimeout(t.panScrollTimeout),t.isPanScrolling?g==null||g(y,j):(t.isPanScrolling=!0,f==null||f(y,j)),t.panScrollTimeout=setTimeout(()=>{m==null||m(y,j),t.isPanScrolling=!1},150)}}function D1({noWheelClassName:t,preventScrolling:r,d3ZoomHandler:o}){return function(l,a){const u=l.type==="wheel",d=!r&&u&&!l.ctrlKey,f=fr(l,t);if(l.ctrlKey&&u&&f&&l.preventDefault(),d||f)return null;l.preventDefault(),o.call(this,l,a)}}function $1({zoomPanValues:t,onDraggingChange:r,onPanZoomStart:o}){return l=>{var u,d,f;if((u=l.sourceEvent)!=null&&u.internal)return;const a=Pl(l.transform);t.mouseButton=((d=l.sourceEvent)==null?void 0:d.button)||0,t.isZoomingOrPanning=!0,t.prevViewport=a,((f=l.sourceEvent)==null?void 0:f.type)==="mousedown"&&r(!0),o&&(o==null||o(l.sourceEvent,a))}}function O1({zoomPanValues:t,panOnDrag:r,onPaneContextMenu:o,onTransformChange:l,onPanZoom:a}){return u=>{var d,f;t.usedRightMouseButton=!!(o&&jg(r,t.mouseButton??0)),(d=u.sourceEvent)!=null&&d.sync||l([u.transform.x,u.transform.y,u.transform.k]),a&&!((f=u.sourceEvent)!=null&&f.internal)&&(a==null||a(u.sourceEvent,Pl(u.transform)))}}function F1({zoomPanValues:t,panOnDrag:r,panOnScroll:o,onDraggingChange:l,onPanZoomEnd:a,onPaneContextMenu:u}){return d=>{var f;if(!((f=d.sourceEvent)!=null&&f.internal)&&(t.isZoomingOrPanning=!1,u&&jg(r,t.mouseButton??0)&&!t.usedRightMouseButton&&d.sourceEvent&&u(d.sourceEvent),t.usedRightMouseButton=!1,l(!1),a)){const g=Pl(d.transform);t.prevViewport=g,clearTimeout(t.timerId),t.timerId=setTimeout(()=>{a==null||a(d.sourceEvent,g)},o?150:0)}}}function H1({panActivationKeyPressed:t,zoomActivationKeyPressed:r,zoomOnScroll:o,zoomOnPinch:l,panOnDrag:a,panOnScroll:u,zoomOnDoubleClick:d,userSelectionActive:f,noWheelClassName:g,noPanClassName:m,lib:y,connectionInProgress:x}){return v=>{var _;const S=r||o,k=l&&v.ctrlKey,j=v.type==="wheel";if(v.button===1&&v.type==="mousedown"&&(fr(v,`${y}-flow__node`)||fr(v,`${y}-flow__edge`)||fr(v,`${y}-flow__selection`)||fr(v,`${y}-flow__nodesselection`)))return!0;if(!a&&!S&&!u&&!d&&!l||f||x&&!j||fr(v,g)&&j||fr(v,m)&&(!j||u&&j&&!r)||!l&&v.ctrlKey&&j)return!1;if(!l&&v.type==="touchstart"&&((_=v.touches)==null?void 0:_.length)>1)return v.preventDefault(),!1;if(!S&&!u&&!k&&j||!a&&(v.type==="mousedown"||v.type==="touchstart")||Array.isArray(a)&&!a.includes(v.button)&&v.type==="mousedown")return!1;const b=Array.isArray(a)&&a.includes(v.button)||!v.button||v.button<=1;return(!v.ctrlKey||j||t)&&b}}function B1({domNode:t,minZoom:r,maxZoom:o,translateExtent:l,viewport:a,onPanZoom:u,onPanZoomStart:d,onPanZoomEnd:f,onDraggingChange:g}){const m={isZoomingOrPanning:!1,usedRightMouseButton:!1,prevViewport:{},mouseButton:0,timerId:void 0,panScrollTimeout:void 0,isPanScrolling:!1},y=t.getBoundingClientRect();let x=[[0,0],[y.width,y.height]];const v=typeof ResizeObserver<"u"?new ResizeObserver(q=>{const C=q[0];C&&(x=[[0,0],[C.contentRect.width,C.contentRect.height]])}):null;v==null||v.observe(t);const S=Zp().extent(()=>x).scaleExtent([r,o]).translateExtent(l),k=Ot(t).call(S);N({x:a.x,y:a.y,zoom:zi(a.zoom,r,o)},[[0,0],[y.width,y.height]],l);const j=k.on("wheel.zoom"),b=k.on("dblclick.zoom");S.wheelDelta(bg);async function _(q,C){return k?new Promise(W=>{S==null||S.interpolate((C==null?void 0:C.interpolate)==="linear"?No:ll).transform(Tu(k,C==null?void 0:C.duration,C==null?void 0:C.ease,()=>W(!0)),q)}):!1}function P({noWheelClassName:q,noPanClassName:C,onPaneContextMenu:W,userSelectionActive:U,panOnScroll:Y,panOnDrag:T,panOnScrollMode:F,panOnScrollSpeed:B,preventScrolling:M,zoomOnPinch:R,zoomOnScroll:re,zoomOnDoubleClick:ie,panActivationKeyPressed:ce=!1,zoomActivationKeyPressed:fe,lib:de,onTransformChange:K,connectionInProgress:le,paneClickDistance:me,selectionOnDrag:ke}){U&&!m.isZoomingOrPanning&&E();const xe=Y&&!fe&&!U;S.clickDistance(ke?1/0:!nn(me)||me<0?0:me);const pe=xe?z1({zoomPanValues:m,noWheelClassName:q,d3Selection:k,d3Zoom:S,panOnScrollMode:F,panOnScrollSpeed:B,zoomOnPinch:R,onPanZoomStart:d,onPanZoom:u,onPanZoomEnd:f}):D1({noWheelClassName:q,preventScrolling:M,d3ZoomHandler:j});k.on("wheel.zoom",pe,{passive:!1});const be=$1({zoomPanValues:m,onDraggingChange:g,onPanZoomStart:d});S.on("start",be);const Pe=O1({zoomPanValues:m,panOnDrag:T,onPaneContextMenu:!!W,onPanZoom:u,onTransformChange:K});S.on("zoom",Pe);const Ce=F1({zoomPanValues:m,panOnDrag:T,panOnScroll:Y,onPaneContextMenu:W,onPanZoomEnd:f,onDraggingChange:g});S.on("end",Ce);const Re=H1({panActivationKeyPressed:ce,zoomActivationKeyPressed:fe,panOnDrag:T,zoomOnScroll:re,panOnScroll:Y,zoomOnDoubleClick:ie,zoomOnPinch:R,userSelectionActive:U,noPanClassName:C,noWheelClassName:q,lib:de,connectionInProgress:le});S.filter(Re),ie?k.on("dblclick.zoom",b):k.on("dblclick.zoom",null)}function E(){S.on("zoom",null)}async function N(q,C,W){const U=Iu(q),Y=S==null?void 0:S.constrain()(U,C,W);return Y&&await _(Y),Y}async function L(q,C){const W=Iu(q);return await _(W,C),W}function A(q){if(k){const C=Iu(q),W=k.property("__zoom");(W.k!==q.zoom||W.x!==q.x||W.y!==q.y)&&(S==null||S.transform(k,C,null,{sync:!0}))}}function V(){const q=k?Kp(k.node()):{x:0,y:0,k:1};return{x:q.x,y:q.y,zoom:q.k}}async function z(q,C){return k?new Promise(W=>{S==null||S.interpolate((C==null?void 0:C.interpolate)==="linear"?No:ll).scaleTo(Tu(k,C==null?void 0:C.duration,C==null?void 0:C.ease,()=>W(!0)),q)}):!1}async function G(q,C){return k?new Promise(W=>{S==null||S.interpolate((C==null?void 0:C.interpolate)==="linear"?No:ll).scaleBy(Tu(k,C==null?void 0:C.duration,C==null?void 0:C.ease,()=>W(!0)),q)}):!1}function ee(q){S==null||S.scaleExtent(q)}function J(q){S==null||S.translateExtent(q)}function ne(q){const C=!nn(q)||q<0?0:q;S==null||S.clickDistance(C)}return{update:P,destroy:E,setViewport:L,setViewportConstrained:N,getViewport:V,scaleTo:z,scaleBy:G,setScaleExtent:ee,setTranslateExtent:J,syncViewport:A,setClickDistance:ne}}var $i;(function(t){t.Line="line",t.Handle="handle"})($i||($i={}));function V1({width:t,prevWidth:r,height:o,prevHeight:l,affectsX:a,affectsY:u}){const d=t-r,f=o-l,g=[d>0?1:d<0?-1:0,f>0?1:f<0?-1:0];return d&&a&&(g[0]=g[0]*-1),f&&u&&(g[1]=g[1]*-1),g}function Ph(t){const r=t.includes("right")||t.includes("left"),o=t.includes("bottom")||t.includes("top"),l=t.includes("left"),a=t.includes("top");return{isHorizontal:r,isVertical:o,affectsX:l,affectsY:a}}function cr(t,r){return Math.max(0,r-t)}function dr(t,r){return Math.max(0,t-r)}function nl(t,r,o){return Math.max(0,r-t,t-o)}function Ih(t,r){return t?!r:r}function W1(t,r,o,l,a,u,d,f){let{affectsX:g,affectsY:m}=r;const{isHorizontal:y,isVertical:x}=r,v=y&&x,{xSnapped:S,ySnapped:k}=o,{minWidth:j,maxWidth:b,minHeight:_,maxHeight:P}=l,{x:E,y:N,width:L,height:A,aspectRatio:V}=t;let z=Math.floor(y?S-t.pointerX:0),G=Math.floor(x?k-t.pointerY:0);const ee=L+(g?-z:z),J=A+(m?-G:G),ne=-u[0]*L,q=-u[1]*A;let C=nl(ee,j,b),W=nl(J,_,P);if(d){let T=0,F=0;g&&z<0?T=cr(E+z+ne,d[0][0]):!g&&z>0&&(T=dr(E+ee+ne,d[1][0])),m&&G<0?F=cr(N+G+q,d[0][1]):!m&&G>0&&(F=dr(N+J+q,d[1][1])),C=Math.max(C,T),W=Math.max(W,F)}if(f){let T=0,F=0;g&&z>0?T=dr(E+z,f[0][0]):!g&&z<0&&(T=cr(E+ee,f[1][0])),m&&G>0?F=dr(N+G,f[0][1]):!m&&G<0&&(F=cr(N+J,f[1][1])),C=Math.max(C,T),W=Math.max(W,F)}if(a){if(y){const T=nl(ee/V,_,P)*V;if(C=Math.max(C,T),d){let F=0;!g&&!m||g&&!m&&v?F=dr(N+q+ee/V,d[1][1])*V:F=cr(N+q+(g?z:-z)/V,d[0][1])*V,C=Math.max(C,F)}if(f){let F=0;!g&&!m||g&&!m&&v?F=cr(N+ee/V,f[1][1])*V:F=dr(N+(g?z:-z)/V,f[0][1])*V,C=Math.max(C,F)}}if(x){const T=nl(J*V,j,b)/V;if(W=Math.max(W,T),d){let F=0;!g&&!m||m&&!g&&v?F=dr(E+J*V+ne,d[1][0])/V:F=cr(E+(m?G:-G)*V+ne,d[0][0])/V,W=Math.max(W,F)}if(f){let F=0;!g&&!m||m&&!g&&v?F=cr(E+J*V,f[1][0])/V:F=dr(E+(m?G:-G)*V,f[0][0])/V,W=Math.max(W,F)}}}G=G+(G<0?W:-W),z=z+(z<0?C:-C),a&&(v?ee>J*V?G=(Ih(g,m)?-z:z)/V:z=(Ih(g,m)?-G:G)*V:y?(G=z/V,m=g):(z=G*V,g=m));const U=g?E+z:E,Y=m?N+G:N;return{width:L+(g?-z:z),height:A+(m?-G:G),x:u[0]*z*(g?-1:1)+U,y:u[1]*G*(m?-1:1)+Y}}const Cg={width:0,height:0,x:0,y:0},U1={...Cg,pointerX:0,pointerY:0,aspectRatio:1};function Y1(t,r,o){const l=r.position.x+t.position.x,a=r.position.y+t.position.y,u=t.measured.width??0,d=t.measured.height??0,f=o[0]*u,g=o[1]*d;return[[l-f,a-g],[l+u-f,a+d-g]]}function G1({domNode:t,nodeId:r,getStoreItems:o,onChange:l,onEnd:a}){const u=Ot(t);let d={controlDirection:Ph("bottom-right"),boundaries:{minWidth:0,minHeight:0,maxWidth:Number.MAX_VALUE,maxHeight:Number.MAX_VALUE},resizeDirection:void 0,keepAspectRatio:!1};function f({controlPosition:m,boundaries:y,keepAspectRatio:x,resizeDirection:v,onResizeStart:S,onResize:k,onResizeEnd:j,shouldResize:b}){let _={...Cg},P={...U1};d={boundaries:y,resizeDirection:v,keepAspectRatio:x,controlDirection:Ph(m)};let E,N=null,L=[],A,V,z,G=!1;const ee=Dp().on("start",J=>{const{nodeLookup:ne,transform:q,snapGrid:C,snapToGrid:W,nodeOrigin:U,paneDomNode:Y}=o();if(E=ne.get(r),!E)return;N=(Y==null?void 0:Y.getBoundingClientRect())??null;const{xSnapped:T,ySnapped:F}=jo(J.sourceEvent,{transform:q,snapGrid:C,snapToGrid:W,containerBounds:N});_={width:E.measured.width??0,height:E.measured.height??0,x:E.position.x??0,y:E.position.y??0},P={..._,pointerX:T,pointerY:F,aspectRatio:_.width/_.height},A=void 0,V=Wr(E.extent)?E.extent:void 0,E.parentId&&(E.extent==="parent"||E.expandParent)&&(A=ne.get(E.parentId)),A&&E.extent==="parent"&&(V=[[0,0],[A.measured.width,A.measured.height]]),L=[],z=void 0;for(const[B,M]of ne)if(M.parentId===r&&(L.push({id:B,position:{...M.position},extent:M.extent}),M.extent==="parent"||M.expandParent)){const R=Y1(M,E,M.origin??U);z?z=[[Math.min(R[0][0],z[0][0]),Math.min(R[0][1],z[0][1])],[Math.max(R[1][0],z[1][0]),Math.max(R[1][1],z[1][1])]]:z=R}S==null||S(J,{..._})}).on("drag",J=>{const{transform:ne,snapGrid:q,snapToGrid:C,nodeOrigin:W}=o(),U=jo(J.sourceEvent,{transform:ne,snapGrid:q,snapToGrid:C,containerBounds:N}),Y=[];if(!E)return;const{x:T,y:F,width:B,height:M}=_,R={},re=E.origin??W,{width:ie,height:ce,x:fe,y:de}=W1(P,d.controlDirection,U,d.boundaries,d.keepAspectRatio,re,V,z),K=ie!==B,le=ce!==M,me=fe!==T&&K,ke=de!==F&≤if(!me&&!ke&&!K&&!le)return;if((me||ke||re[0]===1||re[1]===1)&&(R.x=me?fe:_.x,R.y=ke?de:_.y,_.x=R.x,_.y=R.y,L.length>0)){const Pe=fe-T,Ce=de-F;for(const Re of L)Re.position={x:Re.position.x-Pe+re[0]*(ie-B),y:Re.position.y-Ce+re[1]*(ce-M)},Y.push(Re)}if((K||le)&&(R.width=K&&(!d.resizeDirection||d.resizeDirection==="horizontal")?ie:_.width,R.height=le&&(!d.resizeDirection||d.resizeDirection==="vertical")?ce:_.height,_.width=R.width,_.height=R.height),A&&E.expandParent){const Pe=re[0]*(R.width??0);R.x&&R.x{G&&(j==null||j(J,{..._}),a==null||a({..._}),G=!1)});u.call(ee)}function g(){u.on(".drag",null)}return{update:f,destroy:g}}var Ru={exports:{}},Lu={},Au={exports:{}},zu={};/** + * @license React + * use-sync-external-store-shim.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Th;function X1(){if(Th)return zu;Th=1;var t=$o();function r(x,v){return x===v&&(x!==0||1/x===1/v)||x!==x&&v!==v}var o=typeof Object.is=="function"?Object.is:r,l=t.useState,a=t.useEffect,u=t.useLayoutEffect,d=t.useDebugValue;function f(x,v){var S=v(),k=l({inst:{value:S,getSnapshot:v}}),j=k[0].inst,b=k[1];return u(function(){j.value=S,j.getSnapshot=v,g(j)&&b({inst:j})},[x,S,v]),a(function(){return g(j)&&b({inst:j}),x(function(){g(j)&&b({inst:j})})},[x]),d(S),S}function g(x){var v=x.getSnapshot;x=x.value;try{var S=v();return!o(x,S)}catch{return!0}}function m(x,v){return v()}var y=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?m:f;return zu.useSyncExternalStore=t.useSyncExternalStore!==void 0?t.useSyncExternalStore:y,zu}var Rh;function q1(){return Rh||(Rh=1,Au.exports=X1()),Au.exports}/** + * @license React + * use-sync-external-store-shim/with-selector.production.js + * + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */var Lh;function Q1(){if(Lh)return Lu;Lh=1;var t=$o(),r=q1();function o(m,y){return m===y&&(m!==0||1/m===1/y)||m!==m&&y!==y}var l=typeof Object.is=="function"?Object.is:o,a=r.useSyncExternalStore,u=t.useRef,d=t.useEffect,f=t.useMemo,g=t.useDebugValue;return Lu.useSyncExternalStoreWithSelector=function(m,y,x,v,S){var k=u(null);if(k.current===null){var j={hasValue:!1,value:null};k.current=j}else j=k.current;k=f(function(){function _(A){if(!P){if(P=!0,E=A,A=v(A),S!==void 0&&j.hasValue){var V=j.value;if(S(V,A))return N=V}return N=A}if(V=N,l(E,A))return V;var z=v(A);return S!==void 0&&S(V,z)?(E=A,V):(E=A,N=z)}var P=!1,E,N,L=x===void 0?null:x;return[function(){return _(y())},L===null?void 0:function(){return _(L())}]},[y,x,v,S]);var b=a(m,k[0],k[1]);return d(function(){j.hasValue=!0,j.value=b},[b]),g(b),b},Lu}var Ah;function K1(){return Ah||(Ah=1,Ru.exports=Q1()),Ru.exports}var Z1=K1();const J1=xp(Z1),e_={},zh=t=>{let r;const o=new Set,l=(y,x)=>{const v=typeof y=="function"?y(r):y;if(!Object.is(v,r)){const S=r;r=x??(typeof v!="object"||v===null)?v:Object.assign({},r,v),o.forEach(k=>k(r,S))}},a=()=>r,g={setState:l,getState:a,getInitialState:()=>m,subscribe:y=>(o.add(y),()=>o.delete(y)),destroy:()=>{(e_?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),o.clear()}},m=r=t(l,a,g);return g},t_=t=>t?zh(t):zh,{useDebugValue:n_}=Jy,{useSyncExternalStoreWithSelector:r_}=J1,i_=t=>t;function Mg(t,r=i_,o){const l=r_(t.subscribe,t.getState,t.getServerState||t.getInitialState,r,o);return n_(l),l}const Dh=(t,r)=>{const o=t_(t),l=(a,u=r)=>Mg(o,a,u);return Object.assign(l,o),l},o_=(t,r)=>t?Dh(t,r):Dh;function Qe(t,r){if(Object.is(t,r))return!0;if(typeof t!="object"||t===null||typeof r!="object"||r===null)return!1;if(t instanceof Map&&r instanceof Map){if(t.size!==r.size)return!1;for(const[l,a]of t)if(!Object.is(a,r.get(l)))return!1;return!0}if(t instanceof Set&&r instanceof Set){if(t.size!==r.size)return!1;for(const l of t)if(!r.has(l))return!1;return!0}const o=Object.keys(t);if(o.length!==Object.keys(r).length)return!1;for(const l of o)if(!Object.prototype.hasOwnProperty.call(r,l)||!Object.is(t[l],r[l]))return!1;return!0}wp();const Il=O.createContext(null),s_=Il.Provider,Pg=on.error001("react");function ze(t,r){const o=O.useContext(Il);if(o===null)throw new Error(Pg);return Mg(o,t,r)}function Ye(){const t=O.useContext(Il);if(t===null)throw new Error(Pg);return O.useMemo(()=>({getState:t.getState,setState:t.setState,subscribe:t.subscribe}),[t])}const $h={display:"none"},l_={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},Ig="react-flow__node-desc",Tg="react-flow__edge-desc",a_="react-flow__aria-live",u_=t=>t.ariaLiveMessage,c_=t=>t.ariaLabelConfig;function d_({rfId:t}){const r=ze(u_);return p.jsx("div",{id:`${a_}-${t}`,"aria-live":"assertive","aria-atomic":"true",style:l_,children:r})}function f_({rfId:t,disableKeyboardA11y:r}){const o=ze(c_);return p.jsxs(p.Fragment,{children:[p.jsx("div",{id:`${Ig}-${t}`,style:$h,children:r?o["node.a11yDescription.default"]:o["node.a11yDescription.keyboardDisabled"]}),p.jsx("div",{id:`${Tg}-${t}`,style:$h,children:o["edge.a11yDescription.default"]}),!r&&p.jsx(d_,{rfId:t})]})}const Tl=O.forwardRef(({position:t="top-left",children:r,className:o,style:l,...a},u)=>{const d=`${t}`.split("-");return p.jsx("div",{className:it(["react-flow__panel",o,...d]),style:l,ref:u,...a,children:r})});Tl.displayName="Panel";const Oh="https://reactflow.dev?utm_source=attribution";function h_({proOptions:t,position:r="bottom-right"}){return t!=null&&t.hideAttribution?null:p.jsx(Tl,{position:r,className:"react-flow__attribution","data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: ${Oh}`,children:p.jsx("a",{href:Oh,target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const p_=t=>{const r=[],o=[];for(const[,l]of t.nodeLookup)l.selected&&r.push(l.internals.userNode);for(const[,l]of t.edgeLookup)l.selected&&o.push(l);return{selectedNodes:r,selectedEdges:o}},rl=t=>t.id;function g_(t,r){return Qe(t.selectedNodes.map(rl),r.selectedNodes.map(rl))&&Qe(t.selectedEdges.map(rl),r.selectedEdges.map(rl))}function m_({onSelectionChange:t}){const r=Ye(),{selectedNodes:o,selectedEdges:l}=ze(p_,g_);return O.useEffect(()=>{const a={nodes:o,edges:l};t==null||t(a),r.getState().onSelectionChangeHandlers.forEach(u=>u(a))},[o,l,t]),null}const y_=t=>!!t.onSelectionChangeHandlers;function v_({onSelectionChange:t}){const r=ze(y_);return t||r?p.jsx(m_,{onSelectionChange:t}):null}const Rg=[0,0],x_={x:0,y:0,zoom:1},w_=["nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraggable","autoPanOnNodeFocus","nodesConnectable","nodesFocusable","edgesFocusable","edgesReconnectable","elevateNodesOnSelect","elevateEdgesOnSelect","minZoom","maxZoom","nodeExtent","onNodesChange","onEdgesChange","elementsSelectable","connectionMode","snapGrid","snapToGrid","translateExtent","connectOnClick","defaultEdgeOptions","fitView","fitViewOptions","onNodesDelete","onEdgesDelete","onDelete","onNodeDrag","onNodeDragStart","onNodeDragStop","onSelectionDrag","onSelectionDragStart","onSelectionDragStop","onMoveStart","onMove","onMoveEnd","noPanClassName","nodeOrigin","autoPanOnConnect","autoPanOnNodeDrag","onError","connectionRadius","isValidConnection","selectNodesOnDrag","nodeDragThreshold","connectionDragThreshold","onBeforeDelete","debug","autoPanSpeed","ariaLabelConfig","zIndexMode"],Fh=[...w_,"rfId"],__=t=>({setNodes:t.setNodes,setEdges:t.setEdges,setMinZoom:t.setMinZoom,setMaxZoom:t.setMaxZoom,setTranslateExtent:t.setTranslateExtent,setNodeExtent:t.setNodeExtent,reset:t.reset,setDefaultNodesAndEdges:t.setDefaultNodesAndEdges}),Hh={translateExtent:To,nodeOrigin:Rg,minZoom:.5,maxZoom:2,elementsSelectable:!0,noPanClassName:"nopan",rfId:"1"};function S_(t){const{setNodes:r,setEdges:o,setMinZoom:l,setMaxZoom:a,setTranslateExtent:u,setNodeExtent:d,reset:f,setDefaultNodesAndEdges:g}=ze(__,Qe),m=Ye();O.useEffect(()=>(g(t.defaultNodes,t.defaultEdges),()=>{y.current=Hh,f()}),[]);const y=O.useRef(Hh);return O.useEffect(()=>{for(const x of Fh){const v=t[x],S=y.current[x];v!==S&&(typeof t[x]>"u"||(x==="nodes"?r(v):x==="edges"?o(v):x==="minZoom"?l(v):x==="maxZoom"?a(v):x==="translateExtent"?u(v):x==="nodeExtent"?d(v):x==="ariaLabelConfig"?m.setState({ariaLabelConfig:s1(v)}):x==="fitView"?m.setState({fitViewQueued:v}):x==="fitViewOptions"?m.setState({fitViewOptions:v}):m.setState({[x]:v})))}y.current=t},Fh.map(x=>t[x])),null}function Bh(){return typeof window>"u"||!window.matchMedia?null:window.matchMedia("(prefers-color-scheme: dark)")}function k_(t){var l;const[r,o]=O.useState(t==="system"?null:t);return O.useEffect(()=>{if(t!=="system"){o(t);return}const a=Bh(),u=()=>o(a!=null&&a.matches?"dark":"light");return u(),a==null||a.addEventListener("change",u),()=>{a==null||a.removeEventListener("change",u)}},[t]),r!==null?r:(l=Bh())!=null&&l.matches?"dark":"light"}const Vh=typeof document<"u"?document:null;function Do(t=null,r={target:Vh,actInsideInputWithModifier:!0}){const[o,l]=O.useState(!1),a=O.useRef(!1),u=O.useRef(new Set([])),[d,f]=O.useMemo(()=>{if(t!==null){const m=(Array.isArray(t)?t:[t]).filter(x=>typeof x=="string").map(x=>x.replace(/\+/g,` +`).replace(` + +`,` ++`).split(` +`)),y=m.reduce((x,v)=>x.concat(...v),[]);return[m,y]}return[[],[]]},[t]);return O.useEffect(()=>{const g=(r==null?void 0:r.target)??Vh,m=(r==null?void 0:r.actInsideInputWithModifier)??!0;if(t!==null){const y=S=>{var b,_;if(a.current=S.ctrlKey||S.metaKey||S.shiftKey||S.altKey,(!a.current||a.current&&!m)&&fg(S))return!1;const j=Uh(S.code,f);if(u.current.add(S[j]),Wh(d,u.current,!1)){const P=((_=(b=S.composedPath)==null?void 0:b.call(S))==null?void 0:_[0])||S.target,E=(P==null?void 0:P.nodeName)==="BUTTON"||(P==null?void 0:P.nodeName)==="A";r.preventDefault!==!1&&(a.current||!E)&&S.preventDefault(),l(!0)}},x=S=>{const k=Uh(S.code,f);Wh(d,u.current,!0)?(l(!1),u.current.clear()):u.current.delete(S[k]),S.key==="Meta"&&u.current.clear(),a.current=!1},v=()=>{u.current.clear(),l(!1)};return g==null||g.addEventListener("keydown",y),g==null||g.addEventListener("keyup",x),window.addEventListener("blur",v),window.addEventListener("contextmenu",v),()=>{g==null||g.removeEventListener("keydown",y),g==null||g.removeEventListener("keyup",x),window.removeEventListener("blur",v),window.removeEventListener("contextmenu",v)}}},[t,l]),o}function Wh(t,r,o){return t.filter(l=>o||l.length===r.size).some(l=>l.every(a=>r.has(a)))}function Uh(t,r){return r.includes(t)?"code":"key"}const E_=()=>{const t=Ye();return O.useMemo(()=>({zoomIn:async r=>{const{panZoom:o}=t.getState();return o?o.scaleBy(1.2,r):!1},zoomOut:async r=>{const{panZoom:o}=t.getState();return o?o.scaleBy(1/1.2,r):!1},zoomTo:async(r,o)=>{const{panZoom:l}=t.getState();return l?l.scaleTo(r,o):!1},getZoom:()=>t.getState().transform[2],setViewport:async(r,o)=>{const{transform:[l,a,u],panZoom:d}=t.getState();return d?(await d.setViewport({x:r.x??l,y:r.y??a,zoom:r.zoom??u},o),!0):!1},getViewport:()=>{const[r,o,l]=t.getState().transform;return{x:r,y:o,zoom:l}},setCenter:async(r,o,l)=>t.getState().setCenter(r,o,l),fitBounds:async(r,o)=>{const{width:l,height:a,minZoom:u,maxZoom:d,panZoom:f}=t.getState(),g=hc(r,l,a,u,d,(o==null?void 0:o.padding)??.1);return f?(await f.setViewport(g,{duration:o==null?void 0:o.duration,ease:o==null?void 0:o.ease,interpolate:o==null?void 0:o.interpolate}),!0):!1},screenToFlowPosition:(r,o={})=>{const{transform:l,snapGrid:a,snapToGrid:u,domNode:d}=t.getState();if(!d)return r;const{x:f,y:g}=d.getBoundingClientRect(),m={x:r.x-f,y:r.y-g},y=o.snapGrid??a,x=o.snapToGrid??u;return Wo(m,l,x,y)},flowToScreenPosition:r=>{const{transform:o,domNode:l}=t.getState();if(!l)return r;const{x:a,y:u}=l.getBoundingClientRect(),d=Di(r,o);return{x:d.x+a,y:d.y+u}}}),[])};function Lg(t,r){const o=[],l=new Map,a=[];for(const u of t)if(u.type==="add"){a.push(u);continue}else if(u.type==="remove"||u.type==="replace")l.set(u.id,[u]);else{const d=l.get(u.id);d?d.push(u):l.set(u.id,[u])}for(const u of r){const d=l.get(u.id);if(!d){o.push(u);continue}if(d[0].type==="remove")continue;if(d[0].type==="replace"){o.push({...d[0].item});continue}const f={...u};for(const g of d)N_(g,f);o.push(f)}return a.length&&a.forEach(u=>{u.index!==void 0?o.splice(u.index,0,{...u.item}):o.push({...u.item})}),o}function N_(t,r){switch(t.type){case"select":{r.selected=t.selected;break}case"position":{typeof t.position<"u"&&(r.position=t.position),typeof t.dragging<"u"&&(r.dragging=t.dragging);break}case"dimensions":{typeof t.dimensions<"u"&&(r.measured={...t.dimensions},t.setAttributes&&((t.setAttributes===!0||t.setAttributes==="width")&&(r.width=t.dimensions.width),(t.setAttributes===!0||t.setAttributes==="height")&&(r.height=t.dimensions.height))),typeof t.resizing=="boolean"&&(r.resizing=t.resizing);break}}}function j_(t,r){return Lg(t,r)}function b_(t,r){return Lg(t,r)}function zr(t,r){return{id:t,type:"select",selected:r}}function Pi(t,r=new Set,o=!1){const l=[];for(const[a,u]of t){const d=r.has(a);!(u.selected===void 0&&!d)&&u.selected!==d&&(o&&(u.selected=d),l.push(zr(u.id,d)))}return l}function Yh({items:t=[],lookup:r}){var a;const o=[],l=new Map(t.map(u=>[u.id,u]));for(const[u,d]of t.entries()){const f=r.get(d.id),g=((a=f==null?void 0:f.internals)==null?void 0:a.userNode)??f;g!==void 0&&g!==d&&o.push({id:d.id,item:d,type:"replace"}),g===void 0&&o.push({item:d,type:"add",index:u})}for(const[u]of r)l.get(u)===void 0&&o.push({id:u,type:"remove"});return o}function Gh(t){return{id:t.id,type:"remove"}}const C_=ag();function M_(t,r,o={}){return f1(t,r,{...o,onError:o.onError??C_})}const Xh=t=>Kw(t),P_=t=>rg(t);function Ag(t){return O.forwardRef(t)}const zg=typeof window<"u"?O.useLayoutEffect:O.useEffect;function qh(t){const[r,o]=O.useState(BigInt(0)),[l]=O.useState(()=>I_(()=>o(a=>a+BigInt(1))));return zg(()=>{const a=l.get();a.length&&(t(a),l.reset())},[r]),l}function I_(t){let r=[];return{get:()=>r,reset:()=>{r=[]},push:o=>{r.push(o),t()}}}const Dg=O.createContext(null);function T_({children:t}){const r=Ye(),o=O.useCallback(f=>{const{nodes:g=[],setNodes:m,hasDefaultNodes:y,onNodesChange:x,nodeLookup:v,fitViewQueued:S,onNodesChangeMiddlewareMap:k}=r.getState();let j=g;for(const _ of f)j=typeof _=="function"?_(j):_;let b=Yh({items:j,lookup:v});for(const _ of k.values())b=_(b);y&&m(j),b.length>0?x==null||x(b):S&&window.requestAnimationFrame(()=>{const{fitViewQueued:_,nodes:P,setNodes:E}=r.getState();_&&E(P)})},[]),l=qh(o),a=O.useCallback(f=>{const{edges:g=[],setEdges:m,hasDefaultEdges:y,onEdgesChange:x,edgeLookup:v}=r.getState();let S=g;for(const k of f)S=typeof k=="function"?k(S):k;y?m(S):x&&x(Yh({items:S,lookup:v}))},[]),u=qh(a),d=O.useMemo(()=>({nodeQueue:l,edgeQueue:u}),[]);return p.jsx(Dg.Provider,{value:d,children:t})}function R_(){const t=O.useContext(Dg);if(!t)throw new Error("useBatchContext must be used within a BatchProvider");return t}const L_=t=>!!t.panZoom;function Rl(){const t=E_(),r=Ye(),o=R_(),l=ze(L_),a=O.useMemo(()=>{const u=x=>r.getState().nodeLookup.get(x),d=x=>{o.nodeQueue.push(x)},f=x=>{o.edgeQueue.push(x)},g=x=>{var _,P;const{nodeLookup:v,nodeOrigin:S}=r.getState(),k=Xh(x)?x:v.get(x.id),j=k.parentId?cg(k.position,k.measured,k.parentId,v,S):k.position,b={...k,position:j,width:((_=k.measured)==null?void 0:_.width)??k.width,height:((P=k.measured)==null?void 0:P.height)??k.height};return Ao(b)},m=(x,v,S={replace:!1})=>{d(k=>k.map(j=>{if(j.id===x){const b=typeof v=="function"?v(j):v;return S.replace&&Xh(b)?b:{...j,...b}}return j}))},y=(x,v,S={replace:!1})=>{f(k=>k.map(j=>{if(j.id===x){const b=typeof v=="function"?v(j):v;return S.replace&&P_(b)?b:{...j,...b}}return j}))};return{getNodes:()=>r.getState().nodes.map(x=>({...x})),getNode:x=>{var v;return(v=u(x))==null?void 0:v.internals.userNode},getInternalNode:u,getEdges:()=>{const{edges:x=[]}=r.getState();return x.map(v=>({...v}))},getEdge:x=>r.getState().edgeLookup.get(x),setNodes:d,setEdges:f,addNodes:x=>{const v=Array.isArray(x)?x:[x];o.nodeQueue.push(S=>[...S,...v])},addEdges:x=>{const v=Array.isArray(x)?x:[x];o.edgeQueue.push(S=>[...S,...v])},toObject:()=>{const{nodes:x=[],edges:v=[],transform:S}=r.getState(),[k,j,b]=S;return{nodes:x.map(_=>({..._})),edges:v.map(_=>({..._})),viewport:{x:k,y:j,zoom:b}}},deleteElements:async({nodes:x=[],edges:v=[]})=>{const{nodes:S,edges:k,onNodesDelete:j,onEdgesDelete:b,triggerNodeChanges:_,triggerEdgeChanges:P,onDelete:E,onBeforeDelete:N}=r.getState(),{nodes:L,edges:A}=await n1({nodesToRemove:x,edgesToRemove:v,nodes:S,edges:k,onBeforeDelete:N}),V=A.length>0,z=L.length>0;if(V){const G=A.map(Gh);b==null||b(A),P(G)}if(z){const G=L.map(Gh);j==null||j(L),_(G)}return(z||V)&&(E==null||E({nodes:L,edges:A})),{deletedNodes:L,deletedEdges:A}},getIntersectingNodes:(x,v=!0,S)=>{const k=xh(x),j=k?x:g(x),b=S!==void 0;return j?(S||r.getState().nodes).filter(_=>{const P=r.getState().nodeLookup.get(_.id);if(P&&!k&&(_.id===x.id||!P.internals.positionAbsolute))return!1;const E=Ao(b?_:P),N=xl(E,j);return v&&N>0||N>=E.width*E.height||N>=j.width*j.height}):[]},isNodeIntersecting:(x,v,S=!0)=>{const j=xh(x)?x:g(x);if(!j)return!1;const b=xl(j,v);return S&&b>0||b>=v.width*v.height||b>=j.width*j.height},updateNode:m,updateNodeData:(x,v,S={replace:!1})=>{m(x,k=>{const j=typeof v=="function"?v(k):v;return S.replace?{...k,data:j}:{...k,data:{...k.data,...j}}},S)},updateEdge:y,updateEdgeData:(x,v,S={replace:!1})=>{y(x,k=>{const j=typeof v=="function"?v(k):v;return S.replace?{...k,data:j}:{...k,data:{...k.data,...j}}},S)},getNodesBounds:x=>{const{nodeLookup:v,nodeOrigin:S}=r.getState();return Zw(x,{nodeLookup:v,nodeOrigin:S})},getHandleConnections:({type:x,id:v,nodeId:S})=>{var k;return Array.from(((k=r.getState().connectionLookup.get(`${S}-${x}${v?`-${v}`:""}`))==null?void 0:k.values())??[])},getNodeConnections:({type:x,handleId:v,nodeId:S})=>{var k;return Array.from(((k=r.getState().connectionLookup.get(`${S}${x?v?`-${x}-${v}`:`-${x}`:""}`))==null?void 0:k.values())??[])},fitView:async x=>{const v=r.getState().fitViewResolver??o1();return r.setState({fitViewQueued:!0,fitViewOptions:x,fitViewResolver:v}),o.nodeQueue.push(S=>[...S]),v.promise}}},[]);return O.useMemo(()=>({...a,...t,viewportInitialized:l}),[l])}const Qh=t=>t.selected,A_=typeof window<"u"?window:void 0;function z_({deleteKeyCode:t,multiSelectionKeyCode:r}){const o=Ye(),{deleteElements:l}=Rl(),a=Do(t,{actInsideInputWithModifier:!1}),u=Do(r,{target:A_});O.useEffect(()=>{if(a){const{edges:d,nodes:f}=o.getState();l({nodes:f.filter(Qh),edges:d.filter(Qh)}),o.setState({nodesSelectionActive:!1})}},[a]),O.useEffect(()=>{o.setState({multiSelectionActive:u})},[u])}function D_(t){const r=Ye();O.useEffect(()=>{const o=()=>{var a,u,d,f;if(!t.current||!(((u=(a=t.current).checkVisibility)==null?void 0:u.call(a))??!0))return!1;const l=pc(t.current);(l.height===0||l.width===0)&&((f=(d=r.getState()).onError)==null||f.call(d,"004",on.error004())),r.setState({width:l.width||500,height:l.height||500})};if(t.current){o(),window.addEventListener("resize",o);const l=new ResizeObserver(()=>o());return l.observe(t.current),()=>{window.removeEventListener("resize",o),l&&t.current&&l.unobserve(t.current)}}},[])}const Ll={position:"absolute",width:"100%",height:"100%",top:0,left:0},$_=t=>({userSelectionActive:t.userSelectionActive,lib:t.lib,connectionInProgress:t.connection.inProgress});function O_({onPaneContextMenu:t,zoomOnScroll:r=!0,zoomOnPinch:o=!0,panOnScroll:l=!1,panActivationKeyPressed:a,panOnScrollSpeed:u=.5,panOnScrollMode:d=Fr.Free,zoomOnDoubleClick:f=!0,panOnDrag:g=!0,defaultViewport:m,translateExtent:y,minZoom:x,maxZoom:v,zoomActivationKeyCode:S,preventScrolling:k=!0,children:j,noWheelClassName:b,noPanClassName:_,onViewportChange:P,isControlledViewport:E,paneClickDistance:N,selectionOnDrag:L}){const A=Ye(),V=O.useRef(null),{userSelectionActive:z,lib:G,connectionInProgress:ee}=ze($_,Qe),J=Do(S),ne=O.useRef();D_(V);const q=O.useCallback(C=>{P==null||P({x:C[0],y:C[1],zoom:C[2]}),E||A.setState({transform:C})},[P,E]);return O.useEffect(()=>{if(V.current){ne.current=B1({domNode:V.current,minZoom:x,maxZoom:v,translateExtent:y,viewport:m,onDraggingChange:Y=>A.setState(T=>T.paneDragging===Y?T:{paneDragging:Y}),onPanZoomStart:(Y,T)=>{const{onViewportChangeStart:F,onMoveStart:B}=A.getState();B==null||B(Y,T),F==null||F(T)},onPanZoom:(Y,T)=>{const{onViewportChange:F,onMove:B}=A.getState();B==null||B(Y,T),F==null||F(T)},onPanZoomEnd:(Y,T)=>{const{onViewportChangeEnd:F,onMoveEnd:B}=A.getState();B==null||B(Y,T),F==null||F(T)}});const{x:C,y:W,zoom:U}=ne.current.getViewport();return A.setState({panZoom:ne.current,transform:[C,W,U],domNode:V.current.closest(".react-flow")}),()=>{var Y;(Y=ne.current)==null||Y.destroy()}}},[]),O.useEffect(()=>{var C;(C=ne.current)==null||C.update({onPaneContextMenu:t,zoomOnScroll:r,zoomOnPinch:o,panOnScroll:l,panActivationKeyPressed:a,panOnScrollSpeed:u,panOnScrollMode:d,zoomOnDoubleClick:f,panOnDrag:g,zoomActivationKeyPressed:J,preventScrolling:k,noPanClassName:_,userSelectionActive:z,noWheelClassName:b,lib:G,onTransformChange:q,connectionInProgress:ee,selectionOnDrag:L,paneClickDistance:N})},[t,r,o,l,a,u,d,f,g,J,k,_,z,b,G,q,ee,L,N]),p.jsx("div",{className:"react-flow__renderer",ref:V,style:Ll,children:j})}const F_=t=>({userSelectionActive:t.userSelectionActive,userSelectionRect:t.userSelectionRect});function H_(){const{userSelectionActive:t,userSelectionRect:r}=ze(F_,Qe);return t&&r?p.jsx("div",{className:"react-flow__selection react-flow__container",style:{width:r.width,height:r.height,transform:`translate(${r.x}px, ${r.y}px)`}}):null}const Du=(t,r)=>o=>{o.target===r.current&&(t==null||t(o))},B_=t=>({userSelectionActive:t.userSelectionActive,elementsSelectable:t.elementsSelectable,dragging:t.paneDragging,panBy:t.panBy,autoPanSpeed:t.autoPanSpeed});function V_({isSelecting:t,selectionKeyPressed:r,selectionMode:o=Ro.Full,panOnDrag:l,autoPanOnSelection:a,paneClickDistance:u,selectionOnDrag:d,onSelectionStart:f,onSelectionEnd:g,onPaneClick:m,onPaneContextMenu:y,onPaneScroll:x,onPaneMouseEnter:v,onPaneMouseMove:S,onPaneMouseLeave:k,children:j}){const b=O.useRef(0),_=Ye(),{userSelectionActive:P,elementsSelectable:E,dragging:N,panBy:L,autoPanSpeed:A}=ze(B_,Qe),V=E&&(t||P),z=O.useRef(null),G=O.useRef(),ee=O.useRef(new Set),J=O.useRef(new Set),ne=O.useRef(!1),q=O.useRef(!1),C=O.useRef({x:0,y:0}),W=O.useRef(!1),U=K=>{if(q.current||ne.current||_.getState().connection.inProgress){q.current=!1,ne.current=!1;return}m==null||m(K),_.getState().resetSelectedElements(),_.setState({nodesSelectionActive:!1})},Y=K=>{if(Array.isArray(l)&&(l!=null&&l.includes(2))){K.preventDefault();return}y==null||y(K)},T=x?K=>x(K):void 0,F=K=>{q.current&&(K.stopPropagation(),q.current=!1)},B=K=>{var Re,tt;if(K.pointerType==="touch"&&l!==!1&&!r)return;const{domNode:le,transform:me}=_.getState();if(G.current=le==null?void 0:le.getBoundingClientRect(),!G.current)return;const ke=K.target===z.current;if(!ke&&!!K.target.closest(".nokey")||!t||!(d&&ke||r)||K.button!==0||!K.isPrimary)return;(tt=(Re=K.target)==null?void 0:Re.setPointerCapture)==null||tt.call(Re,K.pointerId),q.current=!1;const{x:be,y:Pe}=rn(K.nativeEvent,G.current),Ce=Wo({x:be,y:Pe},me);_.setState({userSelectionRect:{width:0,height:0,startX:Ce.x,startY:Ce.y,x:be,y:Pe}}),ke||(K.stopPropagation(),K.preventDefault())};function M(K,le){const{userSelectionRect:me}=_.getState();if(!me)return;const{transform:ke,nodeLookup:xe,edgeLookup:pe,connectionLookup:be,triggerNodeChanges:Pe,triggerEdgeChanges:Ce,defaultEdgeOptions:Re}=_.getState(),tt={x:me.startX,y:me.startY},{x:nt,y:Je}=Di(tt,ke),Ke={startX:tt.x,startY:tt.y,x:Kft.id)),J.current=new Set;const st=(Re==null?void 0:Re.selectable)??!0;for(const ft of ee.current){const He=be.get(ft);if(He)for(const{edgeId:Le}of He.values()){const mt=pe.get(Le);mt&&(mt.selectable??st)&&J.current.add(Le)}}if(!wh(ot,ee.current)){const ft=Pi(xe,ee.current,!0);Pe(ft)}if(!wh(Pt,J.current)){const ft=Pi(pe,J.current);Ce(ft)}_.setState({userSelectionRect:Ke,userSelectionActive:!0,nodesSelectionActive:!1})}function R(){if(!a||!G.current)return;const[K,le]=fc(C.current,G.current,A);L({x:K,y:le}).then(me=>{if(!q.current||!me){b.current=requestAnimationFrame(R);return}const{x:ke,y:xe}=C.current;M(ke,xe),b.current=requestAnimationFrame(R)})}const re=()=>{cancelAnimationFrame(b.current),b.current=0,W.current=!1};O.useEffect(()=>()=>re(),[]);const ie=K=>{const{userSelectionRect:le,transform:me,resetSelectedElements:ke}=_.getState();if(!G.current||!le)return;const{x:xe,y:pe}=rn(K.nativeEvent,G.current);C.current={x:xe,y:pe};const be=Di({x:le.startX,y:le.startY},me);if(!q.current){const Pe=r?0:u;if(Math.hypot(xe-be.x,pe-be.y)<=Pe)return;ke(),f==null||f(K)}q.current=!0,W.current||(R(),W.current=!0),M(xe,pe)},ce=K=>{var le,me;if(!V){K.target===z.current&&_.getState().connection.inProgress&&(ne.current=!0);return}K.button===0&&((me=(le=K.target)==null?void 0:le.releasePointerCapture)==null||me.call(le,K.pointerId),!P&&K.target===z.current&&_.getState().userSelectionRect&&(U==null||U(K)),_.setState({userSelectionActive:!1,userSelectionRect:null}),q.current&&(g==null||g(K),_.setState({nodesSelectionActive:ee.current.size>0})),re())},fe=K=>{var le,me;(me=(le=K.target)==null?void 0:le.releasePointerCapture)==null||me.call(le,K.pointerId),re()},de=l===!0||Array.isArray(l)&&l.includes(0);return p.jsxs("div",{className:it(["react-flow__pane",{draggable:de,dragging:N,selection:t}]),onClick:V?void 0:Du(U,z),onContextMenu:Du(Y,z),onWheel:Du(T,z),onPointerEnter:V?void 0:v,onPointerMove:V?ie:S,onPointerUp:ce,onPointerCancel:V?fe:void 0,onPointerDownCapture:V?B:void 0,onClickCapture:V?F:void 0,onPointerLeave:k,ref:z,style:Ll,children:[j,p.jsx(H_,{})]})}function ec({id:t,store:r,unselect:o=!1,nodeRef:l}){const{addSelectedNodes:a,unselectNodesAndEdges:u,multiSelectionActive:d,nodeLookup:f,onError:g}=r.getState(),m=f.get(t);if(!m){g==null||g("012",on.error012(t));return}r.setState({nodesSelectionActive:!1}),m.selected?(o||m.selected&&d)&&(u({nodes:[m],edges:[]}),requestAnimationFrame(()=>{var y;return(y=l==null?void 0:l.current)==null?void 0:y.blur()})):a([t])}function $g({nodeRef:t,disabled:r=!1,noDragClassName:o,handleSelector:l,nodeId:a,isSelectable:u,nodeClickDistance:d}){const f=Ye(),[g,m]=O.useState(!1),y=O.useRef();return O.useEffect(()=>{if(!r)return y.current=C1({getStoreItems:()=>f.getState(),onNodeMouseDown:x=>{ec({id:x,store:f,nodeRef:t})},onDragStart:()=>{m(!0)},onDragStop:()=>{m(!1)}}),()=>{var x;(x=y.current)==null||x.destroy(),y.current=void 0}},[r,f,t]),O.useEffect(()=>{r||!t.current||!y.current||y.current.update({noDragClassName:o,handleSelector:l,domNode:t.current,isSelectable:u,nodeId:a,nodeClickDistance:d})},[o,l,r,u,t,a,d]),g}const W_=t=>r=>r.selected&&(r.draggable||t&&typeof r.draggable>"u");function Og(){const t=Ye();return O.useCallback(o=>{const{nodeExtent:l,snapToGrid:a,snapGrid:u,nodesDraggable:d,onError:f,updateNodePositions:g,nodeLookup:m,nodeOrigin:y}=t.getState(),x=new Map,v=W_(d),S=a?u[0]:5,k=a?u[1]:5,j=o.direction.x*S*o.factor,b=o.direction.y*k*o.factor;for(const[,_]of m){if(!v(_))continue;let P={x:_.internals.positionAbsolute.x+j,y:_.internals.positionAbsolute.y+b};a&&(P=Vo(P,u));const{position:E,positionAbsolute:N}=ig({nodeId:_.id,nextPosition:P,nodeLookup:m,nodeExtent:l,nodeOrigin:y,onError:f});_.position=E,_.internals.positionAbsolute=N,x.set(_.id,_)}g(x)},[])}const wc=O.createContext(null),U_=wc.Provider;wc.Consumer;const Fg=()=>O.useContext(wc),Y_=t=>({connectOnClick:t.connectOnClick,noPanClassName:t.noPanClassName,rfId:t.rfId}),Hg=O.createContext(null);function G_({children:t}){const r=ze(Y_,Qe);return p.jsx(Hg.Provider,{value:r,children:t})}function X_(){const t=O.useContext(Hg);if(!t)throw new Error("useHandleConfig must be used within a HandleConfigProvider");return t}const q_={connectingFrom:!1,connectingTo:!1,clickConnecting:!1,isPossibleEndHandle:!0,connectionInProcess:!1,clickConnectionInProcess:!1,valid:!1},Q_=(t,r,o)=>l=>{const{connectionClickStartHandle:a,connectionMode:u,connection:d}=l,{fromHandle:f,toHandle:g,isValid:m}=d;if(!f&&!a)return q_;const y=(g==null?void 0:g.nodeId)===t&&(g==null?void 0:g.id)===r&&(g==null?void 0:g.type)===o;return{connectingFrom:(f==null?void 0:f.nodeId)===t&&(f==null?void 0:f.id)===r&&(f==null?void 0:f.type)===o,connectingTo:y,clickConnecting:(a==null?void 0:a.nodeId)===t&&(a==null?void 0:a.id)===r&&(a==null?void 0:a.type)===o,isPossibleEndHandle:u===Ai.Strict?(f==null?void 0:f.type)!==o:t!==(f==null?void 0:f.nodeId)||r!==(f==null?void 0:f.id),connectionInProcess:!!f,clickConnectionInProcess:!!a,valid:y&&m}};function K_({type:t="source",position:r=Se.Top,isValidConnection:o,isConnectable:l=!0,isConnectableStart:a=!0,isConnectableEnd:u=!0,id:d,onConnect:f,children:g,className:m,onMouseDown:y,onTouchStart:x,...v},S){var W,U;const k=d||null,j=t==="target",b=Ye(),_=Fg(),{connectOnClick:P,noPanClassName:E,rfId:N}=X_(),{connectingFrom:L,connectingTo:A,clickConnecting:V,isPossibleEndHandle:z,connectionInProcess:G,clickConnectionInProcess:ee,valid:J}=ze(Q_(_,k,t),Qe);_||(U=(W=b.getState()).onError)==null||U.call(W,"010",on.error010());const ne=Y=>{const{defaultEdgeOptions:T,onConnect:F,hasDefaultEdges:B}=b.getState(),M={...T,...Y};if(B){const{edges:R,setEdges:re,onError:ie}=b.getState();re(M_(M,R,{onError:ie}))}F==null||F(M),f==null||f(M)},q=Y=>{if(!_)return;const T=hg(Y.nativeEvent);if(a&&(T&&Y.button===0||!T)){const F=b.getState();Ju.onPointerDown(Y.nativeEvent,{handleDomNode:Y.currentTarget,autoPanOnConnect:F.autoPanOnConnect,connectionMode:F.connectionMode,connectionRadius:F.connectionRadius,domNode:F.domNode,nodeLookup:F.nodeLookup,lib:F.lib,isTarget:j,handleId:k,nodeId:_,flowId:F.rfId,panBy:F.panBy,cancelConnection:F.cancelConnection,onConnectStart:F.onConnectStart,onConnectEnd:(...B)=>{var M,R;return(R=(M=b.getState()).onConnectEnd)==null?void 0:R.call(M,...B)},updateConnection:F.updateConnection,onConnect:ne,isValidConnection:o||((...B)=>{var M,R;return((R=(M=b.getState()).isValidConnection)==null?void 0:R.call(M,...B))??!0}),getTransform:()=>b.getState().transform,getFromHandle:()=>b.getState().connection.fromHandle,autoPanSpeed:F.autoPanSpeed,dragThreshold:F.connectionDragThreshold})}T?y==null||y(Y):x==null||x(Y)},C=Y=>{const{onClickConnectStart:T,onClickConnectEnd:F,connectionClickStartHandle:B,connectionMode:M,isValidConnection:R,lib:re,rfId:ie,nodeLookup:ce,connection:fe}=b.getState();if(!_||!B&&!a)return;if(!B){T==null||T(Y.nativeEvent,{nodeId:_,handleId:k,handleType:t}),b.setState({connectionClickStartHandle:{nodeId:_,type:t,id:k}});return}const de=dg(Y.target),K=o||R,{connection:le,isValid:me}=Ju.isValid(Y.nativeEvent,{handle:{nodeId:_,id:k,type:t},connectionMode:M,fromNodeId:B.nodeId,fromHandleId:B.id||null,fromType:B.type,isValidConnection:K,flowId:ie,doc:de,lib:re,nodeLookup:ce});me&&le&&ne(le);const ke=structuredClone(fe);delete ke.inProgress,ke.toPosition=ke.toHandle?ke.toHandle.position:null,F==null||F(Y,ke),b.setState({connectionClickStartHandle:null})};return p.jsx("div",{"data-handleid":k,"data-nodeid":_,"data-handlepos":r,"data-id":`${N}-${_}-${k}-${t}`,className:it(["react-flow__handle",`react-flow__handle-${r}`,"nodrag",E,m,{source:!j,target:j,connectable:l,connectablestart:a,connectableend:u,clickconnecting:V,connectingfrom:L,connectingto:A,valid:J,connectionindicator:l&&(!G||z)&&(G||ee?u:a)}]),onMouseDown:q,onTouchStart:q,onClick:P?C:void 0,ref:S,...v,children:g})}const Oi=O.memo(Ag(K_));function Z_({data:t,isConnectable:r,sourcePosition:o=Se.Bottom}){return p.jsxs(p.Fragment,{children:[t==null?void 0:t.label,p.jsx(Oi,{type:"source",position:o,isConnectable:r})]})}function J_({data:t,isConnectable:r,targetPosition:o=Se.Top,sourcePosition:l=Se.Bottom}){return p.jsxs(p.Fragment,{children:[p.jsx(Oi,{type:"target",position:o,isConnectable:r}),t==null?void 0:t.label,p.jsx(Oi,{type:"source",position:l,isConnectable:r})]})}function eS(){return null}function tS({data:t,isConnectable:r,targetPosition:o=Se.Top}){return p.jsxs(p.Fragment,{children:[p.jsx(Oi,{type:"target",position:o,isConnectable:r}),t==null?void 0:t.label]})}const wl={ArrowUp:{x:0,y:-1},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0},ArrowRight:{x:1,y:0}},Kh={input:Z_,default:J_,output:tS,group:eS};function nS(t){var r,o,l,a;return t.internals.handleBounds===void 0?{width:t.width??t.initialWidth??((r=t.style)==null?void 0:r.width),height:t.height??t.initialHeight??((o=t.style)==null?void 0:o.height)}:{width:t.width??((l=t.style)==null?void 0:l.width),height:t.height??((a=t.style)==null?void 0:a.height)}}const rS=t=>{const{width:r,height:o,x:l,y:a}=Bo(t.nodeLookup,{filter:u=>!!u.selected});return{width:nn(r)?r:null,height:nn(o)?o:null,userSelectionActive:t.userSelectionActive,transformString:`translate(${t.transform[0]}px,${t.transform[1]}px) scale(${t.transform[2]}) translate(${l}px,${a}px)`}};function iS({onSelectionContextMenu:t,noPanClassName:r,disableKeyboardA11y:o}){const l=Ye(),{width:a,height:u,transformString:d,userSelectionActive:f}=ze(rS,Qe),g=Og(),m=O.useRef(null);O.useEffect(()=>{var S;o||(S=m.current)==null||S.focus({preventScroll:!0})},[o]);const y=!f&&a!==null&&u!==null;if($g({nodeRef:m,disabled:!y}),!y)return null;const x=t?S=>{const k=l.getState().nodes.filter(j=>j.selected);t(S,k)}:void 0,v=S=>{Object.prototype.hasOwnProperty.call(wl,S.key)&&(S.preventDefault(),g({direction:wl[S.key],factor:S.shiftKey?4:1}))};return p.jsx("div",{className:it(["react-flow__nodesselection","react-flow__container",r]),style:{transform:d},children:p.jsx("div",{ref:m,className:"react-flow__nodesselection-rect",onContextMenu:x,tabIndex:o?void 0:-1,onKeyDown:o?void 0:v,style:{width:a,height:u}})})}const Zh=typeof window<"u"?window:void 0,oS=t=>({nodesSelectionActive:t.nodesSelectionActive,userSelectionActive:t.userSelectionActive});function Bg({children:t,onPaneClick:r,onPaneMouseEnter:o,onPaneMouseMove:l,onPaneMouseLeave:a,onPaneContextMenu:u,onPaneScroll:d,paneClickDistance:f,deleteKeyCode:g,selectionKeyCode:m,selectionOnDrag:y,selectionMode:x,onSelectionStart:v,onSelectionEnd:S,multiSelectionKeyCode:k,panActivationKeyCode:j,zoomActivationKeyCode:b,elementsSelectable:_,zoomOnScroll:P,zoomOnPinch:E,panOnScroll:N,panOnScrollSpeed:L,panOnScrollMode:A,zoomOnDoubleClick:V,panOnDrag:z,autoPanOnSelection:G,defaultViewport:ee,translateExtent:J,minZoom:ne,maxZoom:q,preventScrolling:C,onSelectionContextMenu:W,noWheelClassName:U,noPanClassName:Y,disableKeyboardA11y:T,onViewportChange:F,isControlledViewport:B}){const{nodesSelectionActive:M,userSelectionActive:R}=ze(oS,Qe),re=Do(m,{target:Zh}),ie=Do(j,{target:Zh}),ce=ie||z,fe=ie||N,de=y&&ce!==!0,K=re||R||de;return z_({deleteKeyCode:g,multiSelectionKeyCode:k}),p.jsx(O_,{onPaneContextMenu:u,elementsSelectable:_,zoomOnScroll:P,zoomOnPinch:E,panOnScroll:fe,panActivationKeyPressed:ie,panOnScrollSpeed:L,panOnScrollMode:A,zoomOnDoubleClick:V,panOnDrag:!re&&ce,defaultViewport:ee,translateExtent:J,minZoom:ne,maxZoom:q,zoomActivationKeyCode:b,preventScrolling:C,noWheelClassName:U,noPanClassName:Y,onViewportChange:F,isControlledViewport:B,paneClickDistance:f,selectionOnDrag:de,children:p.jsxs(V_,{onSelectionStart:v,onSelectionEnd:S,onPaneClick:r,onPaneMouseEnter:o,onPaneMouseMove:l,onPaneMouseLeave:a,onPaneContextMenu:u,onPaneScroll:d,panOnDrag:ce,autoPanOnSelection:G,isSelecting:!!K,selectionMode:x,selectionKeyPressed:re,paneClickDistance:f,selectionOnDrag:de,children:[t,M&&p.jsx(iS,{onSelectionContextMenu:W,noPanClassName:Y,disableKeyboardA11y:T})]})})}Bg.displayName="FlowRenderer";const sS=O.memo(Bg),lS=t=>r=>t?dc(r.nodeLookup,{x:0,y:0,width:r.width,height:r.height},r.transform,!0).map(o=>o.id):Array.from(r.nodeLookup.keys());function aS(t){return ze(O.useCallback(lS(t),[t]),Qe)}const uS=t=>t.updateNodeInternals;function cS(){const t=ze(uS),[r]=O.useState(()=>typeof ResizeObserver>"u"?null:new ResizeObserver(o=>{const l=new Map;o.forEach(a=>{const u=a.target.getAttribute("data-id");l.set(u,{id:u,nodeElement:a.target,force:!0})}),t(l)}));return O.useEffect(()=>()=>{r==null||r.disconnect()},[r]),r}function dS({node:t,nodeType:r,hasDimensions:o,resizeObserver:l}){const a=Ye(),u=O.useRef(null),d=O.useRef(null),f=O.useRef(t.sourcePosition),g=O.useRef(t.targetPosition),m=O.useRef(r),y=o&&!!t.internals.handleBounds;return O.useEffect(()=>{u.current&&!t.hidden&&(!y||d.current!==u.current)&&(d.current&&(l==null||l.unobserve(d.current)),l==null||l.observe(u.current),d.current=u.current)},[y,t.hidden]),O.useEffect(()=>()=>{d.current&&(l==null||l.unobserve(d.current),d.current=null)},[]),O.useEffect(()=>{if(u.current){const x=m.current!==r,v=f.current!==t.sourcePosition,S=g.current!==t.targetPosition;(x||v||S)&&(m.current=r,f.current=t.sourcePosition,g.current=t.targetPosition,a.getState().updateNodeInternals(new Map([[t.id,{id:t.id,nodeElement:u.current,force:!0}]])))}},[t.id,r,t.sourcePosition,t.targetPosition]),u}function fS({id:t,onClick:r,onMouseEnter:o,onMouseMove:l,onMouseLeave:a,onContextMenu:u,onDoubleClick:d,nodesDraggable:f,elementsSelectable:g,nodesConnectable:m,nodesFocusable:y,resizeObserver:x,noDragClassName:v,noPanClassName:S,disableKeyboardA11y:k,rfId:j,nodeTypes:b,nodeClickDistance:_,onError:P}){const{node:E,internals:N,isParent:L}=ze(K=>{const le=K.nodeLookup.get(t),me=K.parentLookup.has(t);return{node:le,internals:le.internals,isParent:me}},Qe);let A=E.type||"default",V=(b==null?void 0:b[A])||Kh[A];V===void 0&&(P==null||P("003",on.error003(A)),A="default",V=(b==null?void 0:b.default)||Kh.default);const z=!!(E.draggable||f&&typeof E.draggable>"u"),G=!!(E.selectable||g&&typeof E.selectable>"u"),ee=!!(E.connectable||m&&typeof E.connectable>"u"),J=!!(E.focusable||y&&typeof E.focusable>"u"),ne=Ye(),q=ug(E),C=dS({node:E,nodeType:A,hasDimensions:q,resizeObserver:x}),W=$g({nodeRef:C,disabled:E.hidden||!z,noDragClassName:v,handleSelector:E.dragHandle,nodeId:t,isSelectable:G,nodeClickDistance:_}),U=Og();if(E.hidden)return null;const Y=ln(E),T=nS(E),F=G||z||r||o||l||a,B=o?K=>o(K,{...N.userNode}):void 0,M=l?K=>l(K,{...N.userNode}):void 0,R=a?K=>a(K,{...N.userNode}):void 0,re=u?K=>u(K,{...N.userNode}):void 0,ie=d?K=>d(K,{...N.userNode}):void 0,ce=K=>{const{selectNodesOnDrag:le,nodeDragThreshold:me}=ne.getState();G&&(!le||!z||me>0)&&ec({id:t,store:ne,nodeRef:C}),r&&r(K,{...N.userNode})},fe=K=>{if(!(fg(K.nativeEvent)||k)){if(Jp.includes(K.key)&&G){const le=K.key==="Escape";ec({id:t,store:ne,unselect:le,nodeRef:C})}else if(z&&E.selected&&Object.prototype.hasOwnProperty.call(wl,K.key)){K.preventDefault();const{ariaLabelConfig:le}=ne.getState();ne.setState({ariaLiveMessage:le["node.a11yDescription.ariaLiveMessage"]({direction:K.key.replace("Arrow","").toLowerCase(),x:~~N.positionAbsolute.x,y:~~N.positionAbsolute.y})}),U({direction:wl[K.key],factor:K.shiftKey?4:1})}}},de=()=>{var be;if(k||!((be=C.current)!=null&&be.matches(":focus-visible")))return;const{transform:K,width:le,height:me,autoPanOnNodeFocus:ke,setCenter:xe}=ne.getState();if(!ke)return;dc(new Map([[t,E]]),{x:0,y:0,width:le,height:me},K,!0).length>0||xe(E.position.x+Y.width/2,E.position.y+Y.height/2,{zoom:K[2]})};return p.jsx("div",{className:it(["react-flow__node",`react-flow__node-${A}`,{[S]:z},E.className,{selected:E.selected,selectable:G,parent:L,draggable:z,dragging:W}]),ref:C,style:{zIndex:N.z,transform:`translate(${N.positionAbsolute.x}px,${N.positionAbsolute.y}px)`,pointerEvents:F?"all":"none",visibility:q?"visible":"hidden",...E.style,...T},"data-id":t,"data-testid":`rf__node-${t}`,onMouseEnter:B,onMouseMove:M,onMouseLeave:R,onContextMenu:re,onClick:ce,onDoubleClick:ie,onKeyDown:J?fe:void 0,tabIndex:J?0:void 0,onFocus:J?de:void 0,role:E.ariaRole??(J?"group":void 0),"aria-roledescription":"node","aria-describedby":k?void 0:`${Ig}-${j}`,"aria-label":E.ariaLabel,...E.domAttributes,children:p.jsx(U_,{value:t,children:p.jsx(V,{id:t,data:E.data,type:A,positionAbsoluteX:N.positionAbsolute.x,positionAbsoluteY:N.positionAbsolute.y,selected:E.selected??!1,selectable:G,draggable:z,deletable:E.deletable??!0,isConnectable:ee,sourcePosition:E.sourcePosition,targetPosition:E.targetPosition,dragging:W,dragHandle:E.dragHandle,zIndex:N.z,parentId:E.parentId,...Y})})})}var hS=O.memo(fS);const pS=t=>({nodesConnectable:t.nodesConnectable,nodesFocusable:t.nodesFocusable,elementsSelectable:t.elementsSelectable,onError:t.onError});function Vg(t){const{nodesConnectable:r,nodesFocusable:o,elementsSelectable:l,onError:a}=ze(pS,Qe),u=aS(t.onlyRenderVisibleElements),d=cS();return p.jsx("div",{className:"react-flow__nodes",style:Ll,children:u.map(f=>p.jsx(hS,{id:f,nodeTypes:t.nodeTypes,nodeExtent:t.nodeExtent,onClick:t.onNodeClick,onMouseEnter:t.onNodeMouseEnter,onMouseMove:t.onNodeMouseMove,onMouseLeave:t.onNodeMouseLeave,onContextMenu:t.onNodeContextMenu,onDoubleClick:t.onNodeDoubleClick,noDragClassName:t.noDragClassName,noPanClassName:t.noPanClassName,rfId:t.rfId,disableKeyboardA11y:t.disableKeyboardA11y,resizeObserver:d,nodesDraggable:t.nodesDraggable??!0,nodesConnectable:r,nodesFocusable:o,elementsSelectable:l,nodeClickDistance:t.nodeClickDistance,onError:a},f))})}Vg.displayName="NodeRenderer";const gS=O.memo(Vg);function mS(t){return ze(O.useCallback(o=>{if(!t)return o.edges.map(a=>a.id);const l=[];if(o.width&&o.height)for(const a of o.edges){const u=o.nodeLookup.get(a.source),d=o.nodeLookup.get(a.target);u&&d&&u1({sourceNode:u,targetNode:d,width:o.width,height:o.height,transform:o.transform})&&l.push(a.id)}return l},[t]),Qe)}const yS=({color:t="none",strokeWidth:r=1})=>{const o={strokeWidth:r,...t&&{stroke:t}};return p.jsx("polyline",{className:"arrow",style:o,strokeLinecap:"round",fill:"none",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4"})},vS=({color:t="none",strokeWidth:r=1})=>{const o={strokeWidth:r,...t&&{stroke:t,fill:t}};return p.jsx("polyline",{className:"arrowclosed",style:o,strokeLinecap:"round",strokeLinejoin:"round",points:"-5,-4 0,0 -5,4 -5,-4"})},Jh={[Lo.Arrow]:yS,[Lo.ArrowClosed]:vS};function xS(t){const r=Ye();return O.useMemo(()=>{var a,u;return Object.prototype.hasOwnProperty.call(Jh,t)?Jh[t]:((u=(a=r.getState()).onError)==null||u.call(a,"009",on.error009(t)),null)},[t])}const wS=({id:t,type:r,color:o,width:l=12.5,height:a=12.5,markerUnits:u="strokeWidth",strokeWidth:d,orient:f="auto-start-reverse"})=>{const g=xS(r);return g?p.jsx("marker",{className:"react-flow__arrowhead",id:t,markerWidth:`${l}`,markerHeight:`${a}`,viewBox:"-10 -10 20 20",markerUnits:u,orient:f,refX:"0",refY:"0",children:p.jsx(g,{color:o,strokeWidth:d})}):null},Wg=({defaultColor:t,rfId:r})=>{const o=ze(u=>u.edges),l=ze(u=>u.defaultEdgeOptions),a=O.useMemo(()=>y1(o,{id:r,defaultColor:t,defaultMarkerStart:l==null?void 0:l.markerStart,defaultMarkerEnd:l==null?void 0:l.markerEnd}),[o,l,r,t]);return a.length?p.jsx("svg",{className:"react-flow__marker","aria-hidden":"true",children:p.jsx("defs",{children:a.map(u=>p.jsx(wS,{id:u.id,type:u.type,color:u.color,width:u.width,height:u.height,markerUnits:u.markerUnits,strokeWidth:u.strokeWidth,orient:u.orient},u.id))})}):null};Wg.displayName="MarkerDefinitions";var _S=O.memo(Wg);function Ug({x:t,y:r,label:o,labelStyle:l,labelShowBg:a=!0,labelBgStyle:u,labelBgPadding:d=[2,4],labelBgBorderRadius:f=2,children:g,className:m,...y}){const[x,v]=O.useState({x:1,y:0,width:0,height:0}),S=it(["react-flow__edge-textwrapper",m]),k=O.useRef(null);return O.useEffect(()=>{if(k.current){const j=k.current.getBBox();v({x:j.x,y:j.y,width:j.width,height:j.height})}},[o]),o?p.jsxs("g",{transform:`translate(${t-x.width/2} ${r-x.height/2})`,className:S,visibility:x.width?"visible":"hidden",...y,children:[a&&p.jsx("rect",{width:x.width+2*d[0],x:-d[0],y:-d[1],height:x.height+2*d[1],className:"react-flow__edge-textbg",style:u,rx:f,ry:f}),p.jsx("text",{className:"react-flow__edge-text",y:x.height/2,dy:"0.3em",ref:k,style:l,children:o}),g]}):null}Ug.displayName="EdgeText";const SS=O.memo(Ug);function Al({path:t,labelX:r,labelY:o,label:l,labelStyle:a,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:g,interactionWidth:m=20,...y}){return p.jsxs(p.Fragment,{children:[p.jsx("path",{...y,d:t,fill:"none",className:it(["react-flow__edge-path",y.className])}),m?p.jsx("path",{d:t,fill:"none",strokeOpacity:0,strokeWidth:m,className:"react-flow__edge-interaction"}):null,l&&nn(r)&&nn(o)?p.jsx(SS,{x:r,y:o,label:l,labelStyle:a,labelShowBg:u,labelBgStyle:d,labelBgPadding:f,labelBgBorderRadius:g}):null]})}function ep({pos:t,x1:r,y1:o,x2:l,y2:a}){return t===Se.Left||t===Se.Right?[.5*(r+l),o]:[r,.5*(o+a)]}function Yg({sourceX:t,sourceY:r,sourcePosition:o=Se.Bottom,targetX:l,targetY:a,targetPosition:u=Se.Top}){const[d,f]=ep({pos:o,x1:t,y1:r,x2:l,y2:a}),[g,m]=ep({pos:u,x1:l,y1:a,x2:t,y2:r}),[y,x,v,S]=pg({sourceX:t,sourceY:r,targetX:l,targetY:a,sourceControlX:d,sourceControlY:f,targetControlX:g,targetControlY:m});return[`M${t},${r} C${d},${f} ${g},${m} ${l},${a}`,y,x,v,S]}function Gg(t){return O.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,sourcePosition:d,targetPosition:f,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:S,style:k,markerEnd:j,markerStart:b,interactionWidth:_})=>{const[P,E,N]=Yg({sourceX:o,sourceY:l,sourcePosition:d,targetX:a,targetY:u,targetPosition:f}),L=t.isInternal?void 0:r;return p.jsx(Al,{id:L,path:P,labelX:E,labelY:N,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:S,style:k,markerEnd:j,markerStart:b,interactionWidth:_})})}const kS=Gg({isInternal:!1}),Xg=Gg({isInternal:!0});kS.displayName="SimpleBezierEdge";Xg.displayName="SimpleBezierEdgeInternal";function qg(t){return O.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,sourcePosition:S=Se.Bottom,targetPosition:k=Se.Top,markerEnd:j,markerStart:b,pathOptions:_,interactionWidth:P})=>{const[E,N,L]=Qu({sourceX:o,sourceY:l,sourcePosition:S,targetX:a,targetY:u,targetPosition:k,borderRadius:_==null?void 0:_.borderRadius,offset:_==null?void 0:_.offset,stepPosition:_==null?void 0:_.stepPosition}),A=t.isInternal?void 0:r;return p.jsx(Al,{id:A,path:E,labelX:N,labelY:L,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,markerEnd:j,markerStart:b,interactionWidth:P})})}const Qg=qg({isInternal:!1}),Kg=qg({isInternal:!0});Qg.displayName="SmoothStepEdge";Kg.displayName="SmoothStepEdgeInternal";function Zg(t){return O.memo(({id:r,...o})=>{var a;const l=t.isInternal?void 0:r;return p.jsx(Qg,{...o,id:l,pathOptions:O.useMemo(()=>{var u;return{borderRadius:0,offset:(u=o.pathOptions)==null?void 0:u.offset}},[(a=o.pathOptions)==null?void 0:a.offset])})})}const ES=Zg({isInternal:!1}),Jg=Zg({isInternal:!0});ES.displayName="StepEdge";Jg.displayName="StepEdgeInternal";function em(t){return O.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,markerEnd:S,markerStart:k,interactionWidth:j})=>{const[b,_,P]=yg({sourceX:o,sourceY:l,targetX:a,targetY:u}),E=t.isInternal?void 0:r;return p.jsx(Al,{id:E,path:b,labelX:_,labelY:P,label:d,labelStyle:f,labelShowBg:g,labelBgStyle:m,labelBgPadding:y,labelBgBorderRadius:x,style:v,markerEnd:S,markerStart:k,interactionWidth:j})})}const NS=em({isInternal:!1}),tm=em({isInternal:!0});NS.displayName="StraightEdge";tm.displayName="StraightEdgeInternal";function nm(t){return O.memo(({id:r,sourceX:o,sourceY:l,targetX:a,targetY:u,sourcePosition:d=Se.Bottom,targetPosition:f=Se.Top,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:S,style:k,markerEnd:j,markerStart:b,pathOptions:_,interactionWidth:P})=>{const[E,N,L]=gg({sourceX:o,sourceY:l,sourcePosition:d,targetX:a,targetY:u,targetPosition:f,curvature:_==null?void 0:_.curvature}),A=t.isInternal?void 0:r;return p.jsx(Al,{id:A,path:E,labelX:N,labelY:L,label:g,labelStyle:m,labelShowBg:y,labelBgStyle:x,labelBgPadding:v,labelBgBorderRadius:S,style:k,markerEnd:j,markerStart:b,interactionWidth:P})})}const jS=nm({isInternal:!1}),rm=nm({isInternal:!0});jS.displayName="BezierEdge";rm.displayName="BezierEdgeInternal";const tp={default:rm,straight:tm,step:Jg,smoothstep:Kg,simplebezier:Xg},np={sourceX:null,sourceY:null,targetX:null,targetY:null,sourcePosition:null,targetPosition:null,zIndex:void 0},bS=(t,r,o)=>o===Se.Left?t-r:o===Se.Right?t+r:t,CS=(t,r,o)=>o===Se.Top?t-r:o===Se.Bottom?t+r:t,rp="react-flow__edgeupdater";function ip({position:t,centerX:r,centerY:o,radius:l=10,onMouseDown:a,onMouseEnter:u,onMouseOut:d,type:f}){return p.jsx("circle",{onMouseDown:a,onMouseEnter:u,onMouseOut:d,className:it([rp,`${rp}-${f}`]),cx:bS(r,l,t),cy:CS(o,l,t),r:l,stroke:"transparent",fill:"transparent"})}function MS({isReconnectable:t,reconnectRadius:r,edge:o,sourceX:l,sourceY:a,targetX:u,targetY:d,sourcePosition:f,targetPosition:g,onReconnect:m,onReconnectStart:y,onReconnectEnd:x,setReconnecting:v,setUpdateHover:S}){const k=Ye(),j=(N,L)=>{if(N.button!==0)return;const{autoPanOnConnect:A,domNode:V,connectionMode:z,connectionRadius:G,lib:ee,onConnectStart:J,cancelConnection:ne,nodeLookup:q,rfId:C,panBy:W,updateConnection:U}=k.getState(),Y=L.type==="target",T=(M,R)=>{v(!1),x==null||x(M,o,L.type,R)},F=M=>m==null?void 0:m(o,M),B=(M,R)=>{v(!0),y==null||y(N,o,L.type),J==null||J(M,R)};Ju.onPointerDown(N.nativeEvent,{autoPanOnConnect:A,connectionMode:z,connectionRadius:G,domNode:V,handleId:L.id,nodeId:L.nodeId,nodeLookup:q,isTarget:Y,edgeUpdaterType:L.type,lib:ee,flowId:C,cancelConnection:ne,panBy:W,isValidConnection:(...M)=>{var R,re;return((re=(R=k.getState()).isValidConnection)==null?void 0:re.call(R,...M))??!0},onConnect:F,onConnectStart:B,onConnectEnd:(...M)=>{var R,re;return(re=(R=k.getState()).onConnectEnd)==null?void 0:re.call(R,...M)},onReconnectEnd:T,updateConnection:U,getTransform:()=>k.getState().transform,getFromHandle:()=>k.getState().connection.fromHandle,dragThreshold:k.getState().connectionDragThreshold,handleDomNode:N.currentTarget})},b=N=>j(N,{nodeId:o.target,id:o.targetHandle??null,type:"target"}),_=N=>j(N,{nodeId:o.source,id:o.sourceHandle??null,type:"source"}),P=()=>S(!0),E=()=>S(!1);return p.jsxs(p.Fragment,{children:[(t===!0||t==="source")&&p.jsx(ip,{position:f,centerX:l,centerY:a,radius:r,onMouseDown:b,onMouseEnter:P,onMouseOut:E,type:"source"}),(t===!0||t==="target")&&p.jsx(ip,{position:g,centerX:u,centerY:d,radius:r,onMouseDown:_,onMouseEnter:P,onMouseOut:E,type:"target"})]})}function PS({id:t,edgesFocusable:r,edgesReconnectable:o,elementsSelectable:l,onClick:a,onDoubleClick:u,onContextMenu:d,onMouseEnter:f,onMouseMove:g,onMouseLeave:m,reconnectRadius:y,onReconnect:x,onReconnectStart:v,onReconnectEnd:S,rfId:k,edgeTypes:j,noPanClassName:b,onError:_,disableKeyboardA11y:P}){let E=ze(xe=>xe.edgeLookup.get(t));const N=ze(xe=>xe.defaultEdgeOptions);E=N?{...N,...E}:E;let L=E.type||"default",A=(j==null?void 0:j[L])||tp[L];A===void 0&&(_==null||_("011",on.error011(L)),L="default",A=(j==null?void 0:j.default)||tp.default);const V=!!(E.focusable||r&&typeof E.focusable>"u"),z=typeof x<"u"&&(E.reconnectable||o&&typeof E.reconnectable>"u"),G=!!(E.selectable||l&&typeof E.selectable>"u"),ee=O.useRef(null),[J,ne]=O.useState(!1),[q,C]=O.useState(!1),W=Ye(),{zIndex:U=E.zIndex,sourceX:Y,sourceY:T,targetX:F,targetY:B,sourcePosition:M,targetPosition:R}=ze(O.useCallback(xe=>{const pe=xe.nodeLookup.get(E.source),be=xe.nodeLookup.get(E.target);if(!pe||!be)return np;const Pe=m1({id:t,sourceNode:pe,targetNode:be,sourceHandle:E.sourceHandle||null,targetHandle:E.targetHandle||null,connectionMode:xe.connectionMode,onError:_}),Ce=a1({selected:E.selected,zIndex:E.zIndex,sourceNode:pe,targetNode:be,elevateOnSelect:xe.elevateEdgesOnSelect,zIndexMode:xe.zIndexMode});return{...Pe||np,zIndex:Ce}},[E.source,E.target,E.sourceHandle,E.targetHandle,E.selected,E.zIndex,_]),Qe),re=O.useMemo(()=>E.markerStart?`url('#${Ku(E.markerStart,k)}')`:void 0,[E.markerStart,k]),ie=O.useMemo(()=>E.markerEnd?`url('#${Ku(E.markerEnd,k)}')`:void 0,[E.markerEnd,k]);if(E.hidden||Y===null||T===null||F===null||B===null)return null;const ce=xe=>{var Ce;const{addSelectedEdges:pe,unselectNodesAndEdges:be,multiSelectionActive:Pe}=W.getState();G&&(W.setState({nodesSelectionActive:!1}),E.selected&&Pe?(be({nodes:[],edges:[E]}),(Ce=ee.current)==null||Ce.blur()):pe([t])),a&&a(xe,E)},fe=u?xe=>{u(xe,{...E})}:void 0,de=d?xe=>{d(xe,{...E})}:void 0,K=f?xe=>{f(xe,{...E})}:void 0,le=g?xe=>{g(xe,{...E})}:void 0,me=m?xe=>{m(xe,{...E})}:void 0,ke=xe=>{var pe;if(!P&&Jp.includes(xe.key)&&G){const{unselectNodesAndEdges:be,addSelectedEdges:Pe}=W.getState();xe.key==="Escape"?((pe=ee.current)==null||pe.blur(),be({edges:[E]})):Pe([t])}};return p.jsx("svg",{style:{zIndex:U},children:p.jsxs("g",{className:it(["react-flow__edge",`react-flow__edge-${L}`,E.className,b,{selected:E.selected,animated:E.animated,inactive:!G&&!a,updating:J,selectable:G}]),onClick:ce,onDoubleClick:fe,onContextMenu:de,onMouseEnter:K,onMouseMove:le,onMouseLeave:me,onKeyDown:V?ke:void 0,tabIndex:V?0:void 0,role:E.ariaRole??(V?"group":"img"),"aria-roledescription":"edge","data-id":t,"data-testid":`rf__edge-${t}`,"aria-label":E.ariaLabel===null?void 0:E.ariaLabel||`Edge from ${E.source} to ${E.target}`,"aria-describedby":V?`${Tg}-${k}`:void 0,ref:ee,...E.domAttributes,children:[!q&&p.jsx(A,{id:t,source:E.source,target:E.target,type:E.type,selected:E.selected,animated:E.animated,selectable:G,deletable:E.deletable??!0,label:E.label,labelStyle:E.labelStyle,labelShowBg:E.labelShowBg,labelBgStyle:E.labelBgStyle,labelBgPadding:E.labelBgPadding,labelBgBorderRadius:E.labelBgBorderRadius,sourceX:Y,sourceY:T,targetX:F,targetY:B,sourcePosition:M,targetPosition:R,data:E.data,style:E.style,sourceHandleId:E.sourceHandle,targetHandleId:E.targetHandle,markerStart:re,markerEnd:ie,pathOptions:"pathOptions"in E?E.pathOptions:void 0,interactionWidth:E.interactionWidth}),z&&p.jsx(MS,{edge:E,isReconnectable:z,reconnectRadius:y,onReconnect:x,onReconnectStart:v,onReconnectEnd:S,sourceX:Y,sourceY:T,targetX:F,targetY:B,sourcePosition:M,targetPosition:R,setUpdateHover:ne,setReconnecting:C})]})})}var IS=O.memo(PS);const TS=t=>({edgesFocusable:t.edgesFocusable,edgesReconnectable:t.edgesReconnectable,elementsSelectable:t.elementsSelectable,connectionMode:t.connectionMode,onError:t.onError});function im({defaultMarkerColor:t,onlyRenderVisibleElements:r,rfId:o,edgeTypes:l,noPanClassName:a,onReconnect:u,onEdgeContextMenu:d,onEdgeMouseEnter:f,onEdgeMouseMove:g,onEdgeMouseLeave:m,onEdgeClick:y,reconnectRadius:x,onEdgeDoubleClick:v,onReconnectStart:S,onReconnectEnd:k,disableKeyboardA11y:j}){const{edgesFocusable:b,edgesReconnectable:_,elementsSelectable:P,onError:E}=ze(TS,Qe),N=mS(r);return p.jsxs("div",{className:"react-flow__edges",children:[p.jsx(_S,{defaultColor:t,rfId:o}),N.map(L=>p.jsx(IS,{id:L,edgesFocusable:b,edgesReconnectable:_,elementsSelectable:P,noPanClassName:a,onReconnect:u,onContextMenu:d,onMouseEnter:f,onMouseMove:g,onMouseLeave:m,onClick:y,reconnectRadius:x,onDoubleClick:v,onReconnectStart:S,onReconnectEnd:k,rfId:o,onError:E,edgeTypes:l,disableKeyboardA11y:j},L))]})}im.displayName="EdgeRenderer";const RS=O.memo(im),op=t=>`translate(${t[0]}px,${t[1]}px) scale(${t[2]})`;function LS({children:t}){const r=Ye(),o=O.useRef(null),[l]=O.useState(()=>r.getState().transform);return zg(()=>{let a=null;const u=()=>{const d=r.getState().transform;a&&d[0]===a[0]&&d[1]===a[1]&&d[2]===a[2]||(a=d,o.current&&(o.current.style.transform=op(d)))};return u(),r.subscribe(u)},[r]),p.jsx("div",{ref:o,className:"react-flow__viewport xyflow__viewport react-flow__container",style:{transform:op(l)},children:t})}function AS(t){const r=Rl(),o=O.useRef(!1);O.useEffect(()=>{!o.current&&r.viewportInitialized&&t&&(setTimeout(()=>t(r),1),o.current=!0)},[t,r.viewportInitialized])}const zS=t=>{var r;return(r=t.panZoom)==null?void 0:r.syncViewport};function DS(t){const r=ze(zS),o=Ye();return O.useEffect(()=>{t&&(r==null||r(t),o.setState({transform:[t.x,t.y,t.zoom]}))},[t,r]),null}function $S(t){return t.connection.inProgress?{...t.connection,to:Wo(t.connection.to,t.transform)}:{...t.connection}}function OS(t){return $S}function FS(t){const r=OS();return ze(r,Qe)}const HS=t=>({nodesConnectable:t.nodesConnectable,isValid:t.connection.isValid,inProgress:t.connection.inProgress,width:t.width,height:t.height});function BS({containerStyle:t,style:r,type:o,component:l}){const{nodesConnectable:a,width:u,height:d,isValid:f,inProgress:g}=ze(HS,Qe);return!(u&&a&&g)?null:p.jsx("svg",{style:t,width:u,height:d,className:"react-flow__connectionline react-flow__container",children:p.jsx("g",{className:it(["react-flow__connection",ng(f)]),children:p.jsx(om,{style:r,type:o,CustomComponent:l,isValid:f})})})}const om=({style:t,type:r=hr.Bezier,CustomComponent:o,isValid:l})=>{const{inProgress:a,from:u,fromNode:d,fromHandle:f,fromPosition:g,to:m,toNode:y,toHandle:x,toPosition:v,pointer:S}=FS();if(!a)return;if(o)return p.jsx(o,{connectionLineType:r,connectionLineStyle:t,fromNode:d,fromHandle:f,fromX:u.x,fromY:u.y,toX:m.x,toY:m.y,fromPosition:g,toPosition:v,connectionStatus:ng(l),toNode:y,toHandle:x,pointer:S});let k="";const j={sourceX:u.x,sourceY:u.y,sourcePosition:g,targetX:m.x,targetY:m.y,targetPosition:v};switch(r){case hr.Bezier:[k]=gg(j);break;case hr.SimpleBezier:[k]=Yg(j);break;case hr.Step:[k]=Qu({...j,borderRadius:0});break;case hr.SmoothStep:[k]=Qu(j);break;default:[k]=yg(j)}return p.jsx("path",{d:k,fill:"none",className:"react-flow__connection-path",style:t})};om.displayName="ConnectionLine";const VS={};function sp(t=VS){O.useRef(t),Ye(),O.useEffect(()=>{},[t])}function WS(){Ye(),O.useRef(!1),O.useEffect(()=>{},[])}function sm({nodeTypes:t,edgeTypes:r,onInit:o,onNodeClick:l,onEdgeClick:a,onNodeDoubleClick:u,onEdgeDoubleClick:d,onNodeMouseEnter:f,onNodeMouseMove:g,onNodeMouseLeave:m,onNodeContextMenu:y,onSelectionContextMenu:x,onSelectionStart:v,onSelectionEnd:S,connectionLineType:k,connectionLineStyle:j,connectionLineComponent:b,connectionLineContainerStyle:_,selectionKeyCode:P,selectionOnDrag:E,selectionMode:N,multiSelectionKeyCode:L,panActivationKeyCode:A,zoomActivationKeyCode:V,deleteKeyCode:z,onlyRenderVisibleElements:G,elementsSelectable:ee,defaultViewport:J,translateExtent:ne,minZoom:q,maxZoom:C,preventScrolling:W,defaultMarkerColor:U,zoomOnScroll:Y,zoomOnPinch:T,panOnScroll:F,panOnScrollSpeed:B,panOnScrollMode:M,zoomOnDoubleClick:R,panOnDrag:re,autoPanOnSelection:ie,onPaneClick:ce,onPaneMouseEnter:fe,onPaneMouseMove:de,onPaneMouseLeave:K,onPaneScroll:le,onPaneContextMenu:me,paneClickDistance:ke,nodeClickDistance:xe,onEdgeContextMenu:pe,onEdgeMouseEnter:be,onEdgeMouseMove:Pe,onEdgeMouseLeave:Ce,reconnectRadius:Re,onReconnect:tt,onReconnectStart:nt,onReconnectEnd:Je,noDragClassName:Ke,noWheelClassName:ot,noPanClassName:Pt,disableKeyboardA11y:st,nodeExtent:ft,rfId:He,viewport:Le,onViewportChange:mt,nodesDraggable:an}){return sp(t),sp(r),WS(),AS(o),DS(Le),p.jsx(sS,{onPaneClick:ce,onPaneMouseEnter:fe,onPaneMouseMove:de,onPaneMouseLeave:K,onPaneContextMenu:me,onPaneScroll:le,paneClickDistance:ke,deleteKeyCode:z,selectionKeyCode:P,selectionOnDrag:E,selectionMode:N,onSelectionStart:v,onSelectionEnd:S,multiSelectionKeyCode:L,panActivationKeyCode:A,zoomActivationKeyCode:V,elementsSelectable:ee,zoomOnScroll:Y,zoomOnPinch:T,zoomOnDoubleClick:R,panOnScroll:F,panOnScrollSpeed:B,panOnScrollMode:M,panOnDrag:re,autoPanOnSelection:ie,defaultViewport:J,translateExtent:ne,minZoom:q,maxZoom:C,onSelectionContextMenu:x,preventScrolling:W,noDragClassName:Ke,noWheelClassName:ot,noPanClassName:Pt,disableKeyboardA11y:st,onViewportChange:mt,isControlledViewport:!!Le,children:p.jsxs(LS,{children:[p.jsx(RS,{edgeTypes:r,onEdgeClick:a,onEdgeDoubleClick:d,onReconnect:tt,onReconnectStart:nt,onReconnectEnd:Je,onlyRenderVisibleElements:G,onEdgeContextMenu:pe,onEdgeMouseEnter:be,onEdgeMouseMove:Pe,onEdgeMouseLeave:Ce,reconnectRadius:Re,defaultMarkerColor:U,noPanClassName:Pt,disableKeyboardA11y:st,rfId:He}),p.jsx(BS,{style:j,type:k,component:b,containerStyle:_}),p.jsx("div",{className:"react-flow__edgelabel-renderer"}),p.jsx(gS,{nodeTypes:t,onNodeClick:l,onNodeDoubleClick:u,onNodeMouseEnter:f,onNodeMouseMove:g,onNodeMouseLeave:m,onNodeContextMenu:y,nodeClickDistance:xe,onlyRenderVisibleElements:G,noPanClassName:Pt,noDragClassName:Ke,disableKeyboardA11y:st,nodeExtent:ft,rfId:He,nodesDraggable:an}),p.jsx("div",{className:"react-flow__viewport-portal"})]})})}sm.displayName="GraphView";const US=O.memo(sm),YS=ag(),lp=({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,width:a,height:u,fitView:d,fitViewOptions:f,minZoom:g=.5,maxZoom:m=2,nodeOrigin:y,nodeExtent:x,zIndexMode:v="basic"}={})=>{const S=new Map,k=new Map,j=new Map,b=new Map,_=l??r??[],P=o??t??[],E=y??[0,0],N=x??To;wg(j,b,_);const{nodesInitialized:L}=Zu(P,S,k,{nodeOrigin:E,nodeExtent:N,zIndexMode:v});let A=[0,0,1];if(d&&a&&u){const V=Bo(S,{filter:J=>!!((J.width||J.initialWidth)&&(J.height||J.initialHeight))}),{x:z,y:G,zoom:ee}=hc(V,a,u,g,m,(f==null?void 0:f.padding)??.1);A=[z,G,ee]}return{rfId:"1",width:a??0,height:u??0,transform:A,nodes:P,nodesInitialized:L,nodeLookup:S,parentLookup:k,edges:_,edgeLookup:b,connectionLookup:j,onNodesChange:null,onEdgesChange:null,hasDefaultNodes:o!==void 0,hasDefaultEdges:l!==void 0,panZoom:null,minZoom:g,maxZoom:m,translateExtent:To,nodeExtent:N,nodesSelectionActive:!1,userSelectionActive:!1,userSelectionRect:null,connectionMode:Ai.Strict,domNode:null,paneDragging:!1,noPanClassName:"nopan",nodeOrigin:E,nodeDragThreshold:1,connectionDragThreshold:1,snapGrid:[15,15],snapToGrid:!1,nodesDraggable:!0,nodesConnectable:!0,nodesFocusable:!0,edgesFocusable:!0,edgesReconnectable:!0,elementsSelectable:!0,elevateNodesOnSelect:!0,elevateEdgesOnSelect:!0,selectNodesOnDrag:!0,multiSelectionActive:!1,fitViewQueued:d??!1,fitViewOptions:f,fitViewResolver:null,connection:{...tg},connectionClickStartHandle:null,connectOnClick:!0,ariaLiveMessage:"",autoPanOnConnect:!0,autoPanOnNodeDrag:!0,autoPanOnNodeFocus:!0,autoPanSpeed:15,connectionRadius:20,onError:YS,isValidConnection:void 0,onSelectionChangeHandlers:[],lib:"react",debug:!1,ariaLabelConfig:eg,zIndexMode:v,onNodesChangeMiddlewareMap:new Map,onEdgesChangeMiddlewareMap:new Map}},GS=({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,width:a,height:u,fitView:d,fitViewOptions:f,minZoom:g,maxZoom:m,nodeOrigin:y,nodeExtent:x,zIndexMode:v})=>o_((S,k)=>{async function j(){const{nodeLookup:b,panZoom:_,fitViewOptions:P,fitViewResolver:E,width:N,height:L,minZoom:A,maxZoom:V}=k();_&&(await t1({nodes:b,width:N,height:L,panZoom:_,minZoom:A,maxZoom:V},P),E==null||E.resolve(!0),S({fitViewResolver:null}))}return{...lp({nodes:t,edges:r,width:a,height:u,fitView:d,fitViewOptions:f,minZoom:g,maxZoom:m,nodeOrigin:y,nodeExtent:x,defaultNodes:o,defaultEdges:l,zIndexMode:v}),setNodes:b=>{const{nodeLookup:_,parentLookup:P,nodeOrigin:E,nodeExtent:N,elevateNodesOnSelect:L,fitViewQueued:A,zIndexMode:V,nodesSelectionActive:z}=k(),{nodesInitialized:G,hasSelectedNodes:ee}=Zu(b,_,P,{nodeOrigin:E,nodeExtent:N,elevateNodesOnSelect:L,checkEquality:!0,zIndexMode:V}),J=z&ⅇA&&G?(j(),S({nodes:b,nodesInitialized:G,fitViewQueued:!1,fitViewOptions:void 0,nodesSelectionActive:J})):S({nodes:b,nodesInitialized:G,nodesSelectionActive:J})},setEdges:b=>{const{connectionLookup:_,edgeLookup:P}=k();wg(_,P,b),S({edges:b})},setDefaultNodesAndEdges:(b,_)=>{if(b){const{setNodes:P}=k();P(b),S({hasDefaultNodes:!0})}if(_){const{setEdges:P}=k();P(_),S({hasDefaultEdges:!0})}},updateNodeInternals:b=>{const{triggerNodeChanges:_,nodeLookup:P,parentLookup:E,domNode:N,nodeOrigin:L,nodeExtent:A,debug:V,fitViewQueued:z,zIndexMode:G}=k(),{changes:ee,updatedInternals:J}=E1(b,P,E,N,L,A,G);J&&(w1(P,E,{nodeOrigin:L,nodeExtent:A,zIndexMode:G}),z?(j(),S({fitViewQueued:!1,fitViewOptions:void 0})):S({}),(ee==null?void 0:ee.length)>0&&(V&&console.log("React Flow: trigger node changes",ee),_==null||_(ee)))},updateNodePositions:(b,_=!1)=>{const P=[];let E=[];const{nodeLookup:N,triggerNodeChanges:L,connection:A,updateConnection:V,onNodesChangeMiddlewareMap:z}=k();for(const[G,ee]of b){const J=N.get(G),ne=!!(J!=null&&J.expandParent&&(J!=null&&J.parentId)&&(ee!=null&&ee.position)),q={id:G,type:"position",position:ne?{x:Math.max(0,ee.position.x),y:Math.max(0,ee.position.y)}:ee.position,dragging:_};if(J&&A.inProgress&&A.fromNode.id===J.id){const C=Ur(J,A.fromHandle,Se.Left,!0);V({...A,from:C})}ne&&J.parentId&&P.push({id:G,parentId:J.parentId,rect:{...ee.internals.positionAbsolute,width:ee.measured.width??0,height:ee.measured.height??0}}),E.push(q)}if(P.length>0){const{parentLookup:G,nodeOrigin:ee}=k(),J=xc(P,N,G,ee);E.push(...J)}for(const G of z.values())E=G(E);L(E)},triggerNodeChanges:b=>{const{onNodesChange:_,setNodes:P,nodes:E,hasDefaultNodes:N,debug:L}=k();if(b!=null&&b.length){if(N){const A=j_(b,E);P(A)}L&&console.log("React Flow: trigger node changes",b),_==null||_(b)}},triggerEdgeChanges:b=>{const{onEdgesChange:_,setEdges:P,edges:E,hasDefaultEdges:N,debug:L}=k();if(b!=null&&b.length){if(N){const A=b_(b,E);P(A)}L&&console.log("React Flow: trigger edge changes",b),_==null||_(b)}},addSelectedNodes:b=>{const{multiSelectionActive:_,edgeLookup:P,nodeLookup:E,triggerNodeChanges:N,triggerEdgeChanges:L}=k();if(_){const A=b.map(V=>zr(V,!0));N(A);return}N(Pi(E,new Set([...b]),!0)),L(Pi(P))},addSelectedEdges:b=>{const{multiSelectionActive:_,edgeLookup:P,nodeLookup:E,triggerNodeChanges:N,triggerEdgeChanges:L}=k();if(_){const A=b.map(V=>zr(V,!0));L(A);return}L(Pi(P,new Set([...b]))),N(Pi(E,new Set,!0))},unselectNodesAndEdges:({nodes:b,edges:_}={})=>{const{edges:P,nodes:E,nodeLookup:N,triggerNodeChanges:L,triggerEdgeChanges:A}=k(),V=b||E,z=_||P,G=[];for(const J of V){if(!J.selected)continue;const ne=N.get(J.id);ne&&(ne.selected=!1),G.push(zr(J.id,!1))}const ee=[];for(const J of z)J.selected&&ee.push(zr(J.id,!1));L(G),A(ee)},setMinZoom:b=>{const{panZoom:_,maxZoom:P}=k();_==null||_.setScaleExtent([b,P]),S({minZoom:b})},setMaxZoom:b=>{const{panZoom:_,minZoom:P}=k();_==null||_.setScaleExtent([P,b]),S({maxZoom:b})},setTranslateExtent:b=>{var _;(_=k().panZoom)==null||_.setTranslateExtent(b),S({translateExtent:b})},resetSelectedElements:()=>{const{edges:b,nodes:_,triggerNodeChanges:P,triggerEdgeChanges:E,elementsSelectable:N}=k();if(!N)return;const L=_.reduce((V,z)=>z.selected?[...V,zr(z.id,!1)]:V,[]),A=b.reduce((V,z)=>z.selected?[...V,zr(z.id,!1)]:V,[]);P(L),E(A)},setNodeExtent:b=>{const{nodes:_,nodeLookup:P,parentLookup:E,nodeOrigin:N,elevateNodesOnSelect:L,nodeExtent:A,zIndexMode:V}=k();b[0][0]===A[0][0]&&b[0][1]===A[0][1]&&b[1][0]===A[1][0]&&b[1][1]===A[1][1]||(Zu(_,P,E,{nodeOrigin:N,nodeExtent:b,elevateNodesOnSelect:L,checkEquality:!1,zIndexMode:V}),S({nodeExtent:b}))},panBy:b=>{const{transform:_,width:P,height:E,panZoom:N,translateExtent:L}=k();return N1({delta:b,panZoom:N,transform:_,translateExtent:L,width:P,height:E})},setCenter:async(b,_,P)=>{const{width:E,height:N,maxZoom:L,panZoom:A}=k();if(!A)return!1;const V=typeof(P==null?void 0:P.zoom)<"u"?P.zoom:L;return await A.setViewport({x:E/2-b*V,y:N/2-_*V,zoom:V},{duration:P==null?void 0:P.duration,ease:P==null?void 0:P.ease,interpolate:P==null?void 0:P.interpolate}),!0},cancelConnection:()=>{S({connection:{...tg}})},updateConnection:b=>{S({connection:b})},reset:()=>S({...lp()})}},Object.is);function lm({initialNodes:t,initialEdges:r,defaultNodes:o,defaultEdges:l,initialWidth:a,initialHeight:u,initialMinZoom:d,initialMaxZoom:f,initialFitViewOptions:g,fitView:m,nodeOrigin:y,nodeExtent:x,zIndexMode:v,children:S}){const[k]=O.useState(()=>GS({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,width:a,height:u,fitView:m,minZoom:d,maxZoom:f,fitViewOptions:g,nodeOrigin:y,nodeExtent:x,zIndexMode:v}));return p.jsx(s_,{value:k,children:p.jsx(T_,{children:p.jsx(G_,{children:S})})})}function XS({children:t,nodes:r,edges:o,defaultNodes:l,defaultEdges:a,width:u,height:d,fitView:f,fitViewOptions:g,minZoom:m,maxZoom:y,nodeOrigin:x,nodeExtent:v,zIndexMode:S}){return O.useContext(Il)?p.jsx(p.Fragment,{children:t}):p.jsx(lm,{initialNodes:r,initialEdges:o,defaultNodes:l,defaultEdges:a,initialWidth:u,initialHeight:d,fitView:f,initialFitViewOptions:g,initialMinZoom:m,initialMaxZoom:y,nodeOrigin:x,nodeExtent:v,zIndexMode:S,children:t})}const qS={width:"100%",height:"100%",overflow:"hidden",position:"relative",zIndex:0};function QS({nodes:t,edges:r,defaultNodes:o,defaultEdges:l,className:a,nodeTypes:u,edgeTypes:d,onNodeClick:f,onEdgeClick:g,onInit:m,onMove:y,onMoveStart:x,onMoveEnd:v,onConnect:S,onConnectStart:k,onConnectEnd:j,onClickConnectStart:b,onClickConnectEnd:_,onNodeMouseEnter:P,onNodeMouseMove:E,onNodeMouseLeave:N,onNodeContextMenu:L,onNodeDoubleClick:A,onNodeDragStart:V,onNodeDrag:z,onNodeDragStop:G,onNodesDelete:ee,onEdgesDelete:J,onDelete:ne,onSelectionChange:q,onSelectionDragStart:C,onSelectionDrag:W,onSelectionDragStop:U,onSelectionContextMenu:Y,onSelectionStart:T,onSelectionEnd:F,onBeforeDelete:B,connectionMode:M,connectionLineType:R=hr.Bezier,connectionLineStyle:re,connectionLineComponent:ie,connectionLineContainerStyle:ce,deleteKeyCode:fe="Backspace",selectionKeyCode:de="Shift",selectionOnDrag:K=!1,selectionMode:le=Ro.Full,panActivationKeyCode:me="Space",multiSelectionKeyCode:ke=zo()?"Meta":"Control",zoomActivationKeyCode:xe=zo()?"Meta":"Control",snapToGrid:pe,snapGrid:be,onlyRenderVisibleElements:Pe=!1,selectNodesOnDrag:Ce,nodesDraggable:Re,autoPanOnNodeFocus:tt,nodesConnectable:nt,nodesFocusable:Je,nodeOrigin:Ke=Rg,edgesFocusable:ot,edgesReconnectable:Pt,elementsSelectable:st=!0,defaultViewport:ft=x_,minZoom:He=.5,maxZoom:Le=2,translateExtent:mt=To,preventScrolling:an=!0,nodeExtent:ht,defaultMarkerColor:Gt="#b1b1b7",zoomOnScroll:_n=!0,zoomOnPinch:zn=!0,panOnScroll:Gr=!1,panOnScrollSpeed:It=.5,panOnScrollMode:Sn=Fr.Free,zoomOnDoubleClick:Dn=!0,panOnDrag:un=!0,onPaneClick:$n,onPaneMouseEnter:gr,onPaneMouseMove:cn,onPaneMouseLeave:dn,onPaneScroll:Xr,onPaneContextMenu:qr,paneClickDistance:Qr=1,nodeClickDistance:Kr=0,children:On,onReconnect:mr,onReconnectStart:Fn,onReconnectEnd:kn,onEdgeContextMenu:yr,onEdgeDoubleClick:fn,onEdgeMouseEnter:vr,onEdgeMouseMove:hn,onEdgeMouseLeave:Hn,reconnectRadius:Bn=10,onNodesChange:xr,onEdgesChange:Zr,noDragClassName:Jr="nodrag",noWheelClassName:ei="nowheel",noPanClassName:Tt="nopan",fitView:Vn,fitViewOptions:Wn,connectOnClick:ti,attributionPosition:wr,proOptions:D,defaultEdgeOptions:te,elevateNodesOnSelect:_e=!0,elevateEdgesOnSelect:Ie=!1,disableKeyboardA11y:Me=!1,autoPanOnConnect:Fe,autoPanOnNodeDrag:Dl,autoPanOnSelection:Fi=!0,autoPanSpeed:Uo,connectionRadius:ni,isValidConnection:$l,onError:Yo,style:ri,id:Rt,nodeDragThreshold:Ol,connectionDragThreshold:Lt,viewport:Fl,onViewportChange:Hl,width:Bl,height:ii,colorMode:oi="light",debug:_r,onScroll:En,ariaLabelConfig:Vl,zIndexMode:Go="basic",...Hi},Xo){const Sr=Rt||"1",kr=k_(oi),Wl=O.useCallback(si=>{si.currentTarget.scrollTo({top:0,left:0,behavior:"instant"}),En==null||En(si)},[En]);return p.jsx("div",{"data-testid":"rf__wrapper",...Hi,onScroll:Wl,style:{...ri,...qS},ref:Xo,className:it(["react-flow",a,kr]),id:Rt,role:"application",children:p.jsxs(XS,{nodes:t,edges:r,width:Bl,height:ii,fitView:Vn,fitViewOptions:Wn,minZoom:He,maxZoom:Le,nodeOrigin:Ke,nodeExtent:ht,zIndexMode:Go,children:[p.jsx(S_,{nodes:t,edges:r,defaultNodes:o,defaultEdges:l,onConnect:S,onConnectStart:k,onConnectEnd:j,onClickConnectStart:b,onClickConnectEnd:_,nodesDraggable:Re,autoPanOnNodeFocus:tt,nodesConnectable:nt,nodesFocusable:Je,edgesFocusable:ot,edgesReconnectable:Pt,elementsSelectable:st,elevateNodesOnSelect:_e,elevateEdgesOnSelect:Ie,minZoom:He,maxZoom:Le,nodeExtent:ht,onNodesChange:xr,onEdgesChange:Zr,snapToGrid:pe,snapGrid:be,connectionMode:M,translateExtent:mt,connectOnClick:ti,defaultEdgeOptions:te,fitView:Vn,fitViewOptions:Wn,onNodesDelete:ee,onEdgesDelete:J,onDelete:ne,onNodeDragStart:V,onNodeDrag:z,onNodeDragStop:G,onSelectionDrag:W,onSelectionDragStart:C,onSelectionDragStop:U,onMove:y,onMoveStart:x,onMoveEnd:v,noPanClassName:Tt,nodeOrigin:Ke,rfId:Sr,autoPanOnConnect:Fe,autoPanOnNodeDrag:Dl,autoPanSpeed:Uo,onError:Yo,connectionRadius:ni,isValidConnection:$l,selectNodesOnDrag:Ce,nodeDragThreshold:Ol,connectionDragThreshold:Lt,onBeforeDelete:B,debug:_r,ariaLabelConfig:Vl,zIndexMode:Go}),p.jsx(US,{onInit:m,onNodeClick:f,onEdgeClick:g,onNodeMouseEnter:P,onNodeMouseMove:E,onNodeMouseLeave:N,onNodeContextMenu:L,onNodeDoubleClick:A,nodeTypes:u,edgeTypes:d,connectionLineType:R,connectionLineStyle:re,connectionLineComponent:ie,connectionLineContainerStyle:ce,selectionKeyCode:de,selectionOnDrag:K,selectionMode:le,deleteKeyCode:fe,multiSelectionKeyCode:ke,panActivationKeyCode:me,zoomActivationKeyCode:xe,onlyRenderVisibleElements:Pe,defaultViewport:ft,translateExtent:mt,minZoom:He,maxZoom:Le,preventScrolling:an,zoomOnScroll:_n,zoomOnPinch:zn,zoomOnDoubleClick:Dn,panOnScroll:Gr,panOnScrollSpeed:It,panOnScrollMode:Sn,panOnDrag:un,autoPanOnSelection:Fi,onPaneClick:$n,onPaneMouseEnter:gr,onPaneMouseMove:cn,onPaneMouseLeave:dn,onPaneScroll:Xr,onPaneContextMenu:qr,paneClickDistance:Qr,nodeClickDistance:Kr,onSelectionContextMenu:Y,onSelectionStart:T,onSelectionEnd:F,onReconnect:mr,onReconnectStart:Fn,onReconnectEnd:kn,onEdgeContextMenu:yr,onEdgeDoubleClick:fn,onEdgeMouseEnter:vr,onEdgeMouseMove:hn,onEdgeMouseLeave:Hn,reconnectRadius:Bn,defaultMarkerColor:Gt,noDragClassName:Jr,noWheelClassName:ei,noPanClassName:Tt,rfId:Sr,disableKeyboardA11y:Me,nodeExtent:ht,viewport:Fl,onViewportChange:Hl,nodesDraggable:Re}),p.jsx(v_,{onSelectionChange:q}),On,p.jsx(h_,{proOptions:D,position:wr}),p.jsx(f_,{rfId:Sr,disableKeyboardA11y:Me})]})})}var KS=Ag(QS);function ZS({dimensions:t,lineWidth:r,variant:o,className:l}){return p.jsx("path",{strokeWidth:r,d:`M${t[0]/2} 0 V${t[1]} M0 ${t[1]/2} H${t[0]}`,className:it(["react-flow__background-pattern",o,l])})}function JS({radius:t,className:r}){return p.jsx("circle",{cx:t,cy:t,r:t,className:it(["react-flow__background-pattern","dots",r])})}var pr;(function(t){t.Lines="lines",t.Dots="dots",t.Cross="cross"})(pr||(pr={}));const ek={[pr.Dots]:1,[pr.Lines]:1,[pr.Cross]:6},tk=t=>({transform:t.transform,patternId:`pattern-${t.rfId}`});function am({id:t,variant:r=pr.Dots,gap:o=20,size:l,lineWidth:a=1,offset:u=0,color:d,bgColor:f,style:g,className:m,patternClassName:y}){const x=O.useRef(null),{transform:v,patternId:S}=ze(tk,Qe),k=l||ek[r],j=r===pr.Dots,b=r===pr.Cross,_=Array.isArray(o)?o:[o,o],P=[_[0]*v[2]||1,_[1]*v[2]||1],E=k*v[2],N=Array.isArray(u)?u:[u,u],L=b?[E,E]:P,A=[N[0]*v[2]+L[0]/2,N[1]*v[2]+L[1]/2],V=`${S}${t||""}`;return p.jsxs("svg",{className:it(["react-flow__background",m]),style:{...g,...Ll,"--xy-background-color-props":f,"--xy-background-pattern-color-props":d},ref:x,"data-testid":"rf__background",children:[p.jsx("pattern",{id:V,x:v[0]%P[0],y:v[1]%P[1],width:P[0],height:P[1],patternUnits:"userSpaceOnUse",patternTransform:`translate(-${A[0]},-${A[1]})`,children:j?p.jsx(JS,{radius:E/2,className:y}):p.jsx(ZS,{dimensions:L,lineWidth:a,variant:r,className:y})}),p.jsx("rect",{x:"0",y:"0",width:"100%",height:"100%",fill:`url(#${V})`})]})}am.displayName="Background";const nk=O.memo(am);function rk(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 32",children:p.jsx("path",{d:"M32 18.133H18.133V32h-4.266V18.133H0v-4.266h13.867V0h4.266v13.867H32z"})})}function ik(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 5",children:p.jsx("path",{d:"M0 0h32v4.2H0z"})})}function ok(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 32 30",children:p.jsx("path",{d:"M3.692 4.63c0-.53.4-.938.939-.938h5.215V0H4.708C2.13 0 0 2.054 0 4.63v5.216h3.692V4.631zM27.354 0h-5.2v3.692h5.17c.53 0 .984.4.984.939v5.215H32V4.631A4.624 4.624 0 0027.354 0zm.954 24.83c0 .532-.4.94-.939.94h-5.215v3.768h5.215c2.577 0 4.631-2.13 4.631-4.707v-5.139h-3.692v5.139zm-23.677.94c-.531 0-.939-.4-.939-.94v-5.138H0v5.139c0 2.577 2.13 4.707 4.708 4.707h5.138V25.77H4.631z"})})}function sk(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:p.jsx("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0 8 0 4.571 3.429 4.571 7.619v3.048H3.048A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047zm4.724-13.866H7.467V7.619c0-2.59 2.133-4.724 4.723-4.724 2.591 0 4.724 2.133 4.724 4.724v3.048z"})})}function lk(){return p.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 25 32",children:p.jsx("path",{d:"M21.333 10.667H19.81V7.619C19.81 3.429 16.38 0 12.19 0c-4.114 1.828-1.37 2.133.305 2.438 1.676.305 4.42 2.59 4.42 5.181v3.048H3.047A3.056 3.056 0 000 13.714v15.238A3.056 3.056 0 003.048 32h18.285a3.056 3.056 0 003.048-3.048V13.714a3.056 3.056 0 00-3.048-3.047zM12.19 24.533a3.056 3.056 0 01-3.047-3.047 3.056 3.056 0 013.047-3.048 3.056 3.056 0 013.048 3.048 3.056 3.056 0 01-3.048 3.047z"})})}function il({children:t,className:r,...o}){return p.jsx("button",{type:"button",className:it(["react-flow__controls-button",r]),...o,children:t})}const ak=t=>({isInteractive:t.nodesDraggable||t.nodesConnectable||t.elementsSelectable,minZoomReached:t.transform[2]<=t.minZoom,maxZoomReached:t.transform[2]>=t.maxZoom,ariaLabelConfig:t.ariaLabelConfig});function um({style:t,showZoom:r=!0,showFitView:o=!0,showInteractive:l=!0,fitViewOptions:a,onZoomIn:u,onZoomOut:d,onFitView:f,onInteractiveChange:g,className:m,children:y,position:x="bottom-left",orientation:v="vertical","aria-label":S}){const k=Ye(),{isInteractive:j,minZoomReached:b,maxZoomReached:_,ariaLabelConfig:P}=ze(ak,Qe),{zoomIn:E,zoomOut:N,fitView:L}=Rl(),A=()=>{E(),u==null||u()},V=()=>{N(),d==null||d()},z=()=>{L(a),f==null||f()},G=()=>{k.setState({nodesDraggable:!j,nodesConnectable:!j,elementsSelectable:!j}),g==null||g(!j)},ee=v==="horizontal"?"horizontal":"vertical";return p.jsxs(Tl,{className:it(["react-flow__controls",ee,m]),position:x,style:t,"data-testid":"rf__controls","aria-label":S??P["controls.ariaLabel"],children:[r&&p.jsxs(p.Fragment,{children:[p.jsx(il,{onClick:A,className:"react-flow__controls-zoomin",title:P["controls.zoomIn.ariaLabel"],"aria-label":P["controls.zoomIn.ariaLabel"],disabled:_,children:p.jsx(rk,{})}),p.jsx(il,{onClick:V,className:"react-flow__controls-zoomout",title:P["controls.zoomOut.ariaLabel"],"aria-label":P["controls.zoomOut.ariaLabel"],disabled:b,children:p.jsx(ik,{})})]}),o&&p.jsx(il,{className:"react-flow__controls-fitview",onClick:z,title:P["controls.fitView.ariaLabel"],"aria-label":P["controls.fitView.ariaLabel"],children:p.jsx(ok,{})}),l&&p.jsx(il,{className:"react-flow__controls-interactive",onClick:G,title:P["controls.interactive.ariaLabel"],"aria-label":P["controls.interactive.ariaLabel"],children:j?p.jsx(lk,{}):p.jsx(sk,{})}),y]})}um.displayName="Controls";const uk=O.memo(um);function ck({id:t,x:r,y:o,width:l,height:a,style:u,color:d,strokeColor:f,strokeWidth:g,className:m,borderRadius:y,shapeRendering:x,selected:v,onClick:S}){const{background:k,backgroundColor:j}=u||{},b=d||k||j;return p.jsx("rect",{className:it(["react-flow__minimap-node",{selected:v},m]),x:r,y:o,rx:y,ry:y,width:l,height:a,style:{fill:b,stroke:f,strokeWidth:g},shapeRendering:x,onClick:S?_=>S(_,t):void 0})}const dk=O.memo(ck),fk=t=>t.nodes.map(r=>r.id),$u=t=>t instanceof Function?t:()=>t;function hk({nodeStrokeColor:t,nodeColor:r,nodeClassName:o="",nodeBorderRadius:l=5,nodeStrokeWidth:a,nodeComponent:u=dk,onClick:d}){const f=ze(fk,Qe),g=$u(r),m=$u(t),y=$u(o),x=typeof window>"u"||window.chrome?"crispEdges":"geometricPrecision";return p.jsx(p.Fragment,{children:f.map(v=>p.jsx(gk,{id:v,nodeColorFunc:g,nodeStrokeColorFunc:m,nodeClassNameFunc:y,nodeBorderRadius:l,nodeStrokeWidth:a,NodeComponent:u,onClick:d,shapeRendering:x},v))})}function pk({id:t,nodeColorFunc:r,nodeStrokeColorFunc:o,nodeClassNameFunc:l,nodeBorderRadius:a,nodeStrokeWidth:u,shapeRendering:d,NodeComponent:f,onClick:g}){const{node:m,x:y,y:x,width:v,height:S}=ze(k=>{const j=k.nodeLookup.get(t);if(!j)return{node:void 0,x:0,y:0,width:0,height:0};const b=j.internals.userNode,{x:_,y:P}=j.internals.positionAbsolute,{width:E,height:N}=ln(b);return{node:b,x:_,y:P,width:E,height:N}},Qe);return!m||m.hidden||!ug(m)?null:p.jsx(f,{x:y,y:x,width:v,height:S,style:m.style,selected:!!m.selected,className:l(m),color:r(m),borderRadius:a,strokeColor:o(m),strokeWidth:u,shapeRendering:d,onClick:g,id:m.id})}const gk=O.memo(pk);var mk=O.memo(hk);const yk=200,vk=150,xk=t=>!t.hidden,wk=t=>{const r={x:-t.transform[0]/t.transform[2],y:-t.transform[1]/t.transform[2],width:t.width/t.transform[2],height:t.height/t.transform[2]};return{viewBB:r,boundingRect:t.nodeLookup.size>0?sg(Bo(t.nodeLookup,{filter:xk}),r):r,rfId:t.rfId,panZoom:t.panZoom,translateExtent:t.translateExtent,flowWidth:t.width,flowHeight:t.height,ariaLabelConfig:t.ariaLabelConfig}},ap=(t,r)=>t.x===r.x&&t.y===r.y&&t.width===r.width&&t.height===r.height,_k=(t,r)=>ap(t.viewBB,r.viewBB)&&ap(t.boundingRect,r.boundingRect)&&t.rfId===r.rfId&&t.panZoom===r.panZoom&&t.translateExtent===r.translateExtent&&t.flowWidth===r.flowWidth&&t.flowHeight===r.flowHeight&&t.ariaLabelConfig===r.ariaLabelConfig,Sk="react-flow__minimap-desc";function cm({style:t,className:r,nodeStrokeColor:o,nodeColor:l,nodeClassName:a="",nodeBorderRadius:u=5,nodeStrokeWidth:d,nodeComponent:f,bgColor:g,maskColor:m,maskStrokeColor:y,maskStrokeWidth:x,position:v="bottom-right",onClick:S,onNodeClick:k,pannable:j=!1,zoomable:b=!1,ariaLabel:_,inversePan:P,zoomStep:E=1,offsetScale:N=5}){const L=Ye(),A=O.useRef(null),{boundingRect:V,viewBB:z,rfId:G,panZoom:ee,translateExtent:J,flowWidth:ne,flowHeight:q,ariaLabelConfig:C}=ze(wk,_k),W=(t==null?void 0:t.width)??yk,U=(t==null?void 0:t.height)??vk,Y=V.width/W,T=V.height/U,F=Math.max(Y,T),B=F*W,M=F*U,R=N*F,re=V.x-(B-V.width)/2-R,ie=V.y-(M-V.height)/2-R,ce=B+R*2,fe=M+R*2,de=`${Sk}-${G}`,K=O.useRef(0),le=O.useRef();K.current=F,O.useEffect(()=>{if(A.current&&ee)return le.current=L1({domNode:A.current,panZoom:ee,getTransform:()=>L.getState().transform,getViewScale:()=>K.current}),()=>{var pe;(pe=le.current)==null||pe.destroy()}},[ee]),O.useEffect(()=>{var pe;(pe=le.current)==null||pe.update({translateExtent:J,width:ne,height:q,inversePan:P,pannable:j,zoomStep:E,zoomable:b})},[j,b,P,E,J,ne,q]);const me=S?pe=>{var Ce;const[be,Pe]=((Ce=le.current)==null?void 0:Ce.pointer(pe))||[0,0];S(pe,{x:be,y:Pe})}:void 0,ke=k?O.useCallback((pe,be)=>{const Pe=L.getState().nodeLookup.get(be).internals.userNode;k(pe,Pe)},[]):void 0,xe=_??C["minimap.ariaLabel"];return p.jsx(Tl,{position:v,style:{...t,"--xy-minimap-background-color-props":typeof g=="string"?g:void 0,"--xy-minimap-mask-background-color-props":typeof m=="string"?m:void 0,"--xy-minimap-mask-stroke-color-props":typeof y=="string"?y:void 0,"--xy-minimap-mask-stroke-width-props":typeof x=="number"?x*F:void 0,"--xy-minimap-node-background-color-props":typeof l=="string"?l:void 0,"--xy-minimap-node-stroke-color-props":typeof o=="string"?o:void 0,"--xy-minimap-node-stroke-width-props":typeof d=="number"?d:void 0},className:it(["react-flow__minimap",r]),"data-testid":"rf__minimap",children:p.jsxs("svg",{width:W,height:U,viewBox:`${re} ${ie} ${ce} ${fe}`,className:"react-flow__minimap-svg",role:"img","aria-labelledby":de,ref:A,onClick:me,children:[xe&&p.jsx("title",{id:de,children:xe}),p.jsx(mk,{onClick:ke,nodeColor:l,nodeStrokeColor:o,nodeBorderRadius:u,nodeClassName:a,nodeStrokeWidth:d,nodeComponent:f}),p.jsx("path",{className:"react-flow__minimap-mask",d:`M${re-R},${ie-R}h${ce+R*2}v${fe+R*2}h${-ce-R*2}z + M${z.x},${z.y}h${z.width}v${z.height}h${-z.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}cm.displayName="MiniMap";const kk=O.memo(cm),Ek=t=>r=>t?`${Math.max(1/r.transform[2],1)}`:void 0,Nk={[$i.Line]:"right",[$i.Handle]:"bottom-right"};function jk({nodeId:t,position:r,variant:o=$i.Handle,className:l,style:a=void 0,children:u,color:d,minWidth:f=10,minHeight:g=10,maxWidth:m=Number.MAX_VALUE,maxHeight:y=Number.MAX_VALUE,keepAspectRatio:x=!1,resizeDirection:v,autoScale:S=!0,shouldResize:k,onResizeStart:j,onResize:b,onResizeEnd:_}){const P=Fg(),E=typeof t=="string"?t:P,N=Ye(),L=O.useRef(null),A=o===$i.Handle,V=ze(O.useCallback(Ek(A&&S),[A,S]),Qe),z=O.useRef(null),G=r??Nk[o];O.useEffect(()=>{if(!(!L.current||!E))return z.current||(z.current=G1({domNode:L.current,nodeId:E,getStoreItems:()=>{const{nodeLookup:J,transform:ne,snapGrid:q,snapToGrid:C,nodeOrigin:W,domNode:U}=N.getState();return{nodeLookup:J,transform:ne,snapGrid:q,snapToGrid:C,nodeOrigin:W,paneDomNode:U}},onChange:(J,ne)=>{const{triggerNodeChanges:q,nodeLookup:C,parentLookup:W,nodeOrigin:U}=N.getState(),Y=[],T={x:J.x,y:J.y},F=C.get(E);if(F&&F.expandParent&&F.parentId){const B=F.origin??U,M=J.width??F.measured.width??0,R=J.height??F.measured.height??0,re={id:F.id,parentId:F.parentId,rect:{width:M,height:R,...cg({x:J.x??F.position.x,y:J.y??F.position.y},{width:M,height:R},F.parentId,C,B)}},ie=xc([re],C,W,U);Y.push(...ie),T.x=J.x?Math.max(B[0]*M,J.x):void 0,T.y=J.y?Math.max(B[1]*R,J.y):void 0}if(T.x!==void 0&&T.y!==void 0){const B={id:E,type:"position",position:{...T}};Y.push(B)}if(J.width!==void 0&&J.height!==void 0){const M={id:E,type:"dimensions",resizing:!0,setAttributes:v?v==="horizontal"?"width":"height":!0,dimensions:{width:J.width,height:J.height}};Y.push(M)}for(const B of ne){const M={...B,type:"position"};Y.push(M)}q(Y)},onEnd:({width:J,height:ne})=>{const q={id:E,type:"dimensions",resizing:!1,dimensions:{width:J,height:ne}};N.getState().triggerNodeChanges([q])}})),z.current.update({controlPosition:G,boundaries:{minWidth:f,minHeight:g,maxWidth:m,maxHeight:y},keepAspectRatio:x,resizeDirection:v,onResizeStart:j,onResize:b,onResizeEnd:_,shouldResize:k}),()=>{var J;(J=z.current)==null||J.destroy()}},[G,f,g,m,y,x,j,b,_,k]);const ee=G.split("-");return p.jsx("div",{className:it(["react-flow__resize-control","nodrag",...ee,o,l]),ref:L,style:{...a,scale:V,...d&&{[A?"backgroundColor":"borderColor"]:d}},children:u})}O.memo(jk);const bk={"arch.context":0,"django.app":0,"django.route":1,"django.websocket_route":1,"fastapi.route":1,"django.url_name":2,"django.view":3,"django.viewset_action":3,"django.permission":3,"django.throttle":3,"django.serializer":4,"django.form":4,"graphql.type":4,"fastapi.model":4,"django.serializer_field":5,"django.service":5,"graphql.field":5,"django.model":6,"django.field":7,"django.relation":7,"django.task":8,"django.receiver":8,"django.signal":8,"django.test":8,"django.migration_op":8,"django.admin":8,"django.management_command":8,"django.consumer":8,"django.cache_key":8,"django.feature_flag":8,"django.side_effect":8,"openapi.path":9,"graphql.operation":9,"react.api_client":10,"django.htmx":10,"react.query_key":11,"react.hook":11,"react.feature":11,"react.route":12,"react.page":12,"django.template":12,"react.component":13,"react.context":13,"react.form_schema":14,"react.test":14};function zl(t){return bk[t]??8}const tc=208,nc=64,Ck=88,Mk=28,Pk=8;function Ik(t){if(!t.length)return Number.NaN;const r=[...t].sort((l,a)=>l-a),o=Math.floor(r.length/2);return r.length%2?r[o]:(r[o-1]+r[o])/2}function Tk(t,r=[]){const o=new Map;if(!t.length)return o;const l=new Map;for(const _ of t){const P=zl(_.type),E=l.get(P)??[];E.push(_),l.set(P,E)}const u=[...l.keys()].sort((_,P)=>_-P).map(_=>[...l.get(_)??[]].sort((P,E)=>P.name.localeCompare(E.name)||P.id.localeCompare(E.id))),d=new Set(t.map(_=>_.id)),f=new Map,g=new Map;for(const _ of t)f.set(_.id,[]),g.set(_.id,[]);for(const _ of r)!d.has(_.src)||!d.has(_.dst)||_.src===_.dst||(g.get(_.src).push(_.dst),f.get(_.dst).push(_.src));const m=new Map;u.forEach((_,P)=>{for(const E of _)m.set(E.id,P)});const y=new Map,x=()=>{for(const _ of u)_.forEach((P,E)=>y.set(P.id,E))};x();const v=(_,P)=>{const E=_.map((N,L)=>{const A=P(N.id).map(z=>y.get(z)).filter(z=>z!==void 0),V=Ik(A);return{n:N,bary:Number.isNaN(V)?L:V,name:N.name,id:N.id}});return E.sort((N,L)=>N.bary-L.bary||N.name.localeCompare(L.name)||N.id.localeCompare(L.id)),E.map(N=>N.n)},S=_=>P=>m.get(P)===_;for(let _=0;_(f.get(E)??[]).filter(S(P-1))),x();for(let P=u.length-2;P>=0;P--)u[P]=v(u[P],E=>(g.get(E)??[]).filter(S(P+1))),x()}const k=tc+Ck,j=nc+Mk,b=Math.max(...u.map(_=>_.length),1);return u.forEach((_,P)=>{const E=(b-_.length)*j/2;_.forEach((N,L)=>{o.set(N.id,{x:P*k,y:E+L*j})})}),o}const dm=90,Rk=new Set(["django.field","django.serializer_field","django.relation","django.test","react.test","graphql.field","django.url_name","django.throttle"]),up={"arch.context":"#edf2f4","django.app":"#8d99ae","django.route":"#4cc9f0","django.url_name":"#4cc9f0","django.view":"#4895ef","django.viewset_action":"#4361ee","django.permission":"#7b8cde","django.serializer":"#f4a261","django.form":"#e9c46a","django.serializer_field":"#e9c46a","django.service":"#90be6d","django.model":"#2a9d8f","django.field":"#8ac926","django.task":"#e76f51","django.receiver":"#e85d04","django.signal":"#f4a261","django.test":"#6c757d","django.admin":"#adb5bd","django.migration_op":"#9d4edd","django.consumer":"#e76f51","django.websocket_route":"#4cc9f0","django.template":"#c77dff","django.htmx":"#ff6b6b","django.cache_key":"#6c757d","django.feature_flag":"#f4a261","django.side_effect":"#e85d04","graphql.type":"#00bbf9","graphql.operation":"#00bbf9","fastapi.route":"#4cc9f0","fastapi.model":"#f4a261","openapi.path":"#00bbf9","react.api_client":"#ff6b6b","react.query_key":"#adb5bd","react.hook":"#7b2cbf","react.feature":"#9d4edd","react.route":"#c77dff","react.page":"#c77dff","react.component":"#9d4edd","react.form_schema":"#ffd166","react.test":"#6c757d"},Lk=Math.PI*(3-Math.sqrt(5)),fm=220,Ak=26,zk={0:"context",1:"routes",2:"url names",3:"views",4:"serializers",5:"services",6:"models",7:"fields",8:"jobs / signals",9:"openapi",10:"api client",11:"hooks",12:"pages",13:"components",14:"forms / tests"};function hm(t){return t.startsWith("react.")?"react":t.startsWith("openapi.")||t.startsWith("graphql.")||t.startsWith("fastapi.")?"stitch":t.startsWith("arch.")?"arch":"django"}function vE(t){return up[t]?up[t]:t.startsWith("react.")?"#9d4edd":t.startsWith("openapi.")?"#00bbf9":"#4a5568"}function Dk(t){return t>=dm?"3d":"2d"}function $k(t){return t>=dm?"overview":"full"}function Ok(t,r,o=1){const l=new Set([t]);let a=new Set([t]);for(let u=0;uo.families.has(hm(f.type)));o.detail==="overview"&&(l=l.filter(f=>!Rk.has(f.type)));const a=new Set(l.map(f=>f.id)),u=r.filter(f=>a.has(f.src)&&a.has(f.dst)),d=o.focusId?Ok(o.focusId,u,1):new Set;if(o.neighborhoodOnly&&o.focusId&&d.size){l=l.filter(g=>d.has(g.id));const f=new Set(l.map(g=>g.id));return{nodes:l,edges:u.filter(g=>f.has(g.src)&&f.has(g.dst)),neighborIds:d}}return{nodes:l,edges:u,neighborIds:d}}function xE(t){const r=new Map;for(const l of t){const a=zl(l.type),u=r.get(a)??[];u.push(l),r.set(a,u)}const o=new Map;for(const[l,a]of r){a.sort((d,f)=>d.name.localeCompare(f.name));const u=l*fm;a.forEach((d,f)=>{if(a.length===1){o.set(d.id,{x:u,y:0,z:0});return}const g=Ak*Math.sqrt(f+1),m=f*Lk;o.set(d.id,{x:u,y:g*Math.cos(m),z:g*Math.sin(m)})})}return o}function wE(t){const r=new Map;for(const o of t){const l=zl(o.type);r.set(l,(r.get(l)||0)+1)}return[...r.entries()].sort((o,l)=>o[0]-l[0]).map(([o,l])=>({layer:o,x:o*fm,count:l}))}const ol=16,Hk=12,Bk=new Set(["django.route","react.route","react.page","django.task","django.migration_op","django.permission","django.throttle","django.admin","django.management_command","openapi.path","django.consumer","django.websocket_route","django.template","django.cache_key","django.feature_flag","django.side_effect","graphql.operation","fastapi.route"]),Vk=new Set(["django.serializer","django.serializer_field","django.form","openapi.path","react.form_schema","django.route","graphql.type","graphql.field","graphql.operation","fastapi.model","fastapi.route"]),cp={"arch.context":"Ownership boundary from loadpath.yml — the context this code belongs to.","django.app":"Django app package that owns models, views, and jobs.","django.route":"HTTP URL that publishes a view. A sink: this is where a change becomes a public request.","django.url_name":"Named URL used by reverse() / {% url %} lookups.","django.view":"Request handler (class-based view, function view, or ViewSet).","django.viewset_action":"One ViewSet action (list, create, retrieve, update, destroy).","django.permission":"Auth gate on a view — who is allowed to hit this path.","django.throttle":"Rate-limit class attached to a view.","django.serializer":"Request/response contract: which fields go in and come out.","django.form":"Django form or django-filter FilterSet — the typed input contract.","django.serializer_field":"One field on a serializer or form — the typed slot on the contract.","django.service":"Internal service or use-case. Work that is not itself an HTTP sink.","django.model":"ORM model. Schema and relations live here.","django.field":"Model column. Type, indexes, and relations are the contract of the table.","django.relation":"Model-to-model relation (FK / M2M / O2O).","django.task":"Celery or Dramatiq job. Once enqueued, this is a sink.","django.receiver":"Signal handler that runs after a model event.","django.signal":"Django signal that receivers subscribe to.","django.test":"Backend test that mentions symbols on this path.","django.admin":"Django admin class for a model.","django.migration_op":"Schema migration operation (CreateModel, AlterField, …).","django.management_command":"manage.py command — an operational sink.","django.consumer":"Django Channels WebSocket/HTTP consumer. A sink once a client connects.","django.websocket_route":"ASGI WebSocket URL. A sink: this is where a change becomes a live connection.","django.template":"Django template. HTML (and HTMX) the server renders.","django.htmx":"HTMX call from a template to a URL — another published seam.","django.cache_key":"Cache get/set key. Invalidation is part of the load path.","django.feature_flag":"Feature flag checked on this path. The change may be dark-launched.","django.side_effect":"transaction.on_commit (or similar) side effect that runs after the request commits.","graphql.type":"GraphQL object/input type — a published contract.","graphql.field":"One field on a GraphQL type.","graphql.operation":"GraphQL query, mutation, or subscription. A published contract and a sink.","fastapi.route":"FastAPI path operation sitting next to Django in this repo.","fastapi.model":"Pydantic response/request model — the FastAPI contract.","openapi.path":"Generated OpenAPI operation. The typed HTTP contract between stacks.","react.api_client":"Frontend fetch or generated client call to an API path.","react.query_key":"React Query cache key. Invalidation and reads share this name.","react.hook":"Data hook wrapping query or mutation calls.","react.feature":"Frontend feature module (folder).","react.route":"Client-side route. A sink: this is a URL the user can open.","react.page":"Page or screen component rendered by a route.","react.component":"UI component.","react.form_schema":"Zod (or similar) schema — typed form inputs on the client.","react.test":"Frontend test covering a page, hook, or component.","react.context":"React context provider."},Wk={field_type:"Type",fields:"Fields",form_fields:"Form fields",permissions:"Permissions",throttles:"Throttles",authentication:"Authentication",pagination:"Pagination",filterset:"Filterset",bases:"Extends",on_delete:"on_delete",related_name:"related_name",unique:"Unique",db_index:"Indexed",relation:"Relation field",looks_idempotent_on_pk:"Idempotent on pk",broker:"Broker",route:"Route",url_name:"URL name",view:"View",include:"Includes",mounted_at:"Mounted at",full_path:"Full path",method:"Method",path:"Path",operation_id:"Operation",raw:"URL",kind:"Schema",exclude:"Excludes",queryset_in_serializer:"Queryset in serializer",get_queryset:"Custom get_queryset",get_serializer_class:"Dynamic serializer",dynamic:"Dynamic",fbv:"Function view",ninja:"Django Ninja",django_form:"Django form",mutation:"Mutation",has_error_boundary:"Error boundary",invalidation:"Cache invalidation",inferred:"Inferred stitch",generated:"Generated",shared:"Shared module",element:"Renders",model_name:"Model",field_name:"Field",op:"Operation",app:"App",feature:"Feature",from_view:"From view",mentions:"Mentions",nodeid:"Test id",task:"Task",to:"Related to"},dp=["field_type","method","path","operation_id","raw","route","mounted_at","full_path","url_name","view","element","fields","form_fields","exclude","kind","bases","permissions","authentication","throttles","pagination","filterset","on_delete","related_name","to","unique","db_index","relation","looks_idempotent_on_pk","broker","task","model_name","field_name","op","app","feature","from_view","include","fbv","ninja","django_form","mutation","has_error_boundary","invalidation","inferred","generated","shared","queryset_in_serializer","get_queryset","get_serializer_class","dynamic","mentions","nodeid"],fp=new Set(["referenced","placeholder","booted","line","call","from","import","local","source","file","plain_handler","string_ref","pagination_sink","match","via","generated_client","django","react","superseded_by_generated","foreign_app","imported"]),Uk=new Set(["looks_idempotent_on_pk"]),Yk=new Set(["inferred","generated","mutation","fbv","ninja","filterset"]);function Gk(t){return cp[t]?cp[t]:t.startsWith("react.")?"A React node on the load path.":t.startsWith("django.")?"A Django node on the load path.":t.startsWith("openapi.")?"A stitch node between Django and React.":"A node on the architecture graph."}function Xk(t,r,o){const l=new Map(r.map(x=>[x.id,x])),a=[];Bk.has(t.type)&&a.push("sink"),Vk.has(t.type)&&a.push("contract");const u=t.extra??{};u.inferred&&a.push("inferred"),u.generated&&a.push("generated"),u.mutation&&a.push("mutation"),u.fbv&&a.push("function view"),u.ninja&&a.push("ninja"),u.filterset===!0&&a.push("filterset");const d=o.filter(x=>x.dst===t.id),f=o.filter(x=>x.src===t.id),g=d.slice(0,ol).map(x=>hp(x,l,x.src)),m=f.slice(0,ol).map(x=>hp(x,l,x.dst)),y=t.file_path?`${t.file_path}${t.start_line?`:${t.start_line}`:""}`:void 0;return{type:t.type,typeLabel:bo(Sl(t.type)),layer:zk[zl(t.type)]??"other",purpose:Gk(t.type),name:t.name,qualifiedName:t.qualified_name,file:y,context:t.context,roles:a,facts:qk(u).filter(x=>!(x.key==="app"&&x.value===t.context)),inputs:g,outputs:m,extraInputs:Math.max(0,d.length-ol),extraOutputs:Math.max(0,f.length-ol)}}function hp(t,r,o){const l=r.get(o),a=o.includes(":")?o.slice(o.indexOf(":")+1):o;return{id:o,name:(l==null?void 0:l.name)||a,type:(l==null?void 0:l.type)||"",typeLabel:l?bo(Sl(l.type)):"",edgeType:t.type,edgeLabel:bo(t.type),inferred:t.confidence<.8}}function qk(t){const r=[...dp.filter(a=>a in t),...Object.keys(t).filter(a=>!dp.includes(a)&&!fp.has(a))],o=[],l=new Set;for(const a of r){if(l.has(a)||fp.has(a)||Yk.has(a))continue;l.add(a);const u=Qk(a,t[a]);u!=null&&o.push({key:a,label:Wk[a]??bo(a),value:u})}return o}function Qk(t,r){if(r==null)return null;if(typeof r=="boolean")return!r&&!Uk.has(t)?null:r?"yes":"no";if(typeof r=="number")return String(r);if(typeof r=="string")return r.trim()||null;if(Array.isArray(r)){const o=r.map(u=>typeof u=="string"||typeof u=="number"?String(u):"").filter(Boolean);if(!o.length)return null;const l=o.slice(0,Hk),a=o.length-l.length;return a>0?`${l.join(", ")} +${a} more`:l.join(", ")}return null}const Kk=new Set,Zk=O.lazy(()=>y0(()=>import("./LayeredGraph3D-DLi5mNhS.js"),[],import.meta.url).then(t=>({default:t.LayeredGraph3D}))),Jk={cheap:"var(--edge-cheap)",expensive:"var(--edge-expensive)",critical:"var(--edge-critical)"};function eE({data:t,selected:r}){return p.jsxs("div",{className:r?"lp-node selected":"lp-node",children:[p.jsx(Oi,{type:"target",position:Se.Left,isConnectable:!1}),p.jsx("div",{className:"t",children:Sl(t.type)}),p.jsx("div",{className:"n",title:t.name,children:Dr(t.name)}),p.jsx(Oi,{type:"source",position:Se.Right,isConnectable:!1})]})}const tE={load:eE},nE=new Set(["django","react","stitch","arch"]);function rE({topologyKey:t}){const{fitView:r}=Rl();return O.useEffect(()=>{let o=0;const l=requestAnimationFrame(()=>{o=requestAnimationFrame(()=>{r({padding:.2,maxZoom:1.15})})});return()=>{cancelAnimationFrame(l),cancelAnimationFrame(o)}},[r,t]),null}function iE(t,r,o=null){const l=new Map(t.map(f=>[f.id,f])),a=Tk(t,r),u=t.map(f=>({id:f.id,type:"load",position:a.get(f.id)??{x:0,y:0},data:{name:f.name,type:f.type,file:f.file_path},selected:o===f.id,sourcePosition:Se.Right,targetPosition:Se.Left,width:tc,height:nc,style:{width:tc,height:nc}})),d=r.filter(f=>l.has(f.src)&&l.has(f.dst)).map(f=>{const g=Jk[f.weight]||"var(--edge-cheap)",m=!!(o&&(f.src===o||f.dst===o));return{id:f.id,source:f.src,target:f.dst,type:"smoothstep",animated:f.weight==="critical",style:{stroke:g,strokeWidth:f.weight==="critical"?2.4:1.2,strokeDasharray:f.confidence<.8?"6 4":void 0},markerEnd:{type:Lo.ArrowClosed,width:14,height:14,color:g},label:m?f.type.replaceAll("_"," "):void 0,labelStyle:m?{fill:"var(--ink)",fontSize:10,fontWeight:600}:void 0,labelBgStyle:m?{fill:"var(--graph-bg)",fillOpacity:.92}:void 0,labelBgPadding:m?[3,5]:void 0,labelBgBorderRadius:m?4:void 0}});return{rfNodes:u,rfEdges:d}}function pp({node:t,nodes:r,edges:o,onClose:l,onWhatIf:a}){const u=Xk(t,r,o);return O.useEffect(()=>{const d=f=>{f.key==="Escape"&&l()};return window.addEventListener("keydown",d),()=>window.removeEventListener("keydown",d)},[l]),p.jsxs("aside",{className:"inspector","data-testid":"graph-inspector",children:[p.jsxs("div",{className:"inspector-head",children:[p.jsx("div",{className:"t",children:u.typeLabel}),p.jsx("div",{className:"inspector-roles",children:u.roles.map(d=>p.jsx("span",{className:"inspector-chip",children:d},d))}),p.jsx("button",{type:"button",className:"inspector-close","data-testid":"graph-inspector-close","aria-label":"Close inspector",onClick:l,children:"×"})]}),p.jsx("div",{className:"n",children:Dr(u.name)}),p.jsx("p",{className:"inspector-purpose","data-testid":"graph-inspector-purpose",children:u.purpose}),u.context?p.jsx("div",{className:"muted",children:Dr(u.context)}):null,u.file?p.jsx("div",{className:"file",children:Dr(u.file)}):null,p.jsx("div",{className:"muted",children:Dr(u.qualifiedName)}),p.jsxs("div",{className:"muted inspector-layer",children:["layer · ",u.layer]}),u.facts.length?p.jsx("dl",{className:"inspector-facts","data-testid":"graph-inspector-facts",children:u.facts.map(d=>p.jsxs("div",{className:"inspector-fact",children:[p.jsx("dt",{children:d.label}),p.jsx("dd",{children:Dr(d.value)})]},d.key))}):null,p.jsx(gp,{title:"Inputs",testId:"graph-inspector-inputs",links:u.inputs,extra:u.extraInputs,empty:"Nothing in this graph points here."}),p.jsx(gp,{title:"Outputs",testId:"graph-inspector-outputs",links:u.outputs,extra:u.extraOutputs,empty:"This node does not point at anything in this graph."}),a?p.jsx("button",{type:"button",className:"btn","data-testid":"btn-whatif",onClick:()=>a(t.id),children:"What if this changes"}):null]})}function gp({title:t,testId:r,links:o,extra:l,empty:a}){return p.jsxs("section",{className:"inspector-section","data-testid":r,children:[p.jsxs("h3",{children:[t,p.jsx("span",{className:"count",children:o.length+l})]}),o.length?p.jsx("ul",{children:o.map((u,d)=>p.jsxs("li",{children:[p.jsx("span",{className:"inspector-link-name",title:u.name,children:Dr(u.name)}),p.jsxs("span",{className:"inspector-link-meta",children:[u.typeLabel?`${u.typeLabel} · `:"",u.edgeLabel,u.inferred?" · inferred":""]})]},`${u.edgeType}:${u.id}:${d}`))}):p.jsx("p",{className:"muted",children:a}),l?p.jsxs("p",{className:"muted",children:["+",l," more"]}):null]})}function Ou({nodes:t,edges:r,onWhatIf:o,focusPath:l}){const[a,u]=O.useState(null),[d,f]=O.useState(null),[g,m]=O.useState(null),[y,x]=O.useState(new Set(nE)),[v,S]=O.useState(!1),k=typeof window<"u"&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,j=d??Dk(t.length),b=g??$k(t.length),_=v&&j==="3d"?a:null,P=O.useMemo(()=>Fk(t,r,{detail:b,families:y,focusId:_,neighborhoodOnly:!!_}),[t,r,b,y,_]),E=O.useMemo(()=>`${P.nodes.map(q=>q.id).join("\0")}|${P.edges.map(q=>q.id).join("\0")}`,[P.nodes,P.edges]),N=O.useMemo(()=>new Map(P.nodes.map(q=>[q.id,q])),[P.nodes]),L=a?N.get(a)??null:null,{rfNodes:A,rfEdges:V}=O.useMemo(()=>{const q=iE(P.nodes,P.edges,a);return k&&(q.rfEdges=q.rfEdges.map(C=>({...C,animated:!1}))),q},[P.nodes,P.edges,a,k]);O.useEffect(()=>{a&&!N.has(a)&&u(null)},[N,a]),O.useEffect(()=>{if(!l)return;const q=t.find(C=>C.file_path===l);q&&u(q.id)},[l,t]);const z=(q,C)=>{u(C.id)},G=()=>{u(null),S(!1)},ee=q=>{x(C=>{const W=new Set(C);if(W.has(q)){if(W.size===1)return C;W.delete(q)}else W.add(q);return W})},J=O.useMemo(()=>{const q=new Set;for(const C of t)q.add(hm(C.type));return q},[t]),ne=t.length-P.nodes.length;return p.jsxs("div",{className:"impact-graph",style:{flex:1,minHeight:0,position:"relative",display:"flex",flexDirection:"column"},children:[p.jsxs("div",{className:"graph-toolbar","data-testid":"graph-toolbar",children:[p.jsxs("div",{className:"seg","aria-label":"Graph projection",children:[p.jsx("button",{type:"button","data-testid":"graph-view-2d",className:j==="2d"?"active":"","aria-pressed":j==="2d",onClick:()=>f("2d"),children:"2D map"}),p.jsx("button",{type:"button","data-testid":"graph-view-3d",className:j==="3d"?"active":"","aria-pressed":j==="3d",onClick:()=>f("3d"),children:"3D layers"})]}),p.jsxs("div",{className:"seg","aria-label":"Graph detail",children:[p.jsx("button",{type:"button","data-testid":"graph-detail-overview",className:b==="overview"?"active":"","aria-pressed":b==="overview",onClick:()=>m("overview"),children:"Overview"}),p.jsx("button",{type:"button","data-testid":"graph-detail-full",className:b==="full"?"active":"","aria-pressed":b==="full",onClick:()=>m("full"),children:"Full"})]}),p.jsx("div",{className:"seg","aria-label":"Graph families",children:["django","stitch","react"].filter(q=>J.has(q)).map(q=>p.jsx("button",{type:"button","data-testid":`graph-family-${q}`,className:y.has(q)?"active":"","aria-pressed":y.has(q),onClick:()=>ee(q),children:q},q))}),j==="3d"?p.jsx("button",{type:"button",className:v?"chip-btn active":"chip-btn","data-testid":"graph-neighborhood",disabled:!a,onClick:()=>S(q=>!q),children:v?"Neighborhood":"Focus neighbors"}):null,p.jsxs("span",{className:"muted graph-count",children:[P.nodes.length," nodes · ",P.edges.length," edges",ne?` · ${ne} hidden`:""]})]}),p.jsx("div",{className:"graph-stage",children:j==="3d"?p.jsxs("div",{className:"graph-3d","data-testid":"graph-3d",children:[p.jsx("p",{className:"graph-3d-hint",children:"Architecture layers are stacked in depth (Django → stitch → React). Drag to orbit, scroll to zoom, click a node to inspect it."}),p.jsx(O.Suspense,{fallback:p.jsx("p",{className:"muted graph-3d-hint",children:"Loading 3D layers…"}),children:p.jsx(Zk,{nodes:P.nodes,edges:P.edges,selectedId:a,neighborIds:_?P.neighborIds:Kk,onSelect:q=>{u(q),q||S(!1)}})}),L?p.jsx(pp,{node:L,nodes:t,edges:r,onClose:G,onWhatIf:o}):null]}):p.jsxs(lm,{children:[p.jsxs(KS,{nodes:A,edges:V,nodeTypes:tE,fitView:!1,minZoom:.25,nodesDraggable:!1,nodesConnectable:!1,elementsSelectable:!0,deleteKeyCode:null,onNodeClick:z,onPaneClick:G,proOptions:{hideAttribution:!1},"data-testid":"impact-graph",children:[p.jsx(rE,{topologyKey:E}),p.jsx(nk,{}),p.jsx(kk,{pannable:!0,zoomable:!0,ariaLabel:"Impact graph overview",nodeColor:"var(--muted)",nodeStrokeColor:"transparent",nodeStrokeWidth:0,maskColor:"rgba(0, 0, 0, 0.45)",maskStrokeColor:"var(--accent)",maskStrokeWidth:1.4,bgColor:"var(--graph-bg)",style:{width:184,height:128}}),p.jsx(uk,{})]}),L?p.jsx(pp,{node:L,nodes:t,edges:r,onClose:G,onWhatIf:o}):null]})})]})}const mp=[{value:"HEAD",label:"HEAD",group:"preset"},{value:"HEAD~1",label:"HEAD~1",group:"preset"}],oE=["preset","branch","tag","commit"];function sE(t){var a;if(!(t!=null&&t.git))return[...mp];const r=((a=t.presets)!=null&&a.length?t.presets:mp.map(u=>u.value)).map(u=>({value:u,label:u,group:"preset"})),o=new Set(r.map(u=>u.value)),l=[...r];for(const u of t.branches||[])o.has(u.name)||(o.add(u.name),l.push({value:u.name,label:u.current?`${u.name} (current)`:u.name,detail:u.subject,group:"branch"}));for(const u of t.tags||[])o.has(u.name)||(o.add(u.name),l.push({value:u.name,label:u.name,detail:u.subject,group:"tag"}));for(const u of t.commits||[])o.has(u.sha)||(o.add(u.sha),l.push({value:u.sha,label:u.short,detail:u.subject,group:"commit"}));return l}function lE(t,r){const o=r.trim().toLowerCase();return o?t.filter(l=>l.value.toLowerCase().includes(o)||l.label.toLowerCase().includes(o)||(l.detail||"").toLowerCase().includes(o)):t}function aE(t){return oE.map(r=>({group:r,items:t.filter(o=>o.group===r)})).filter(r=>r.items.length>0)}function uE(t){return t==="preset"?"Common":t==="branch"?"Branches":t==="tag"?"Tags":"Recent commits"}function yp({value:t,onChange:r,placeholder:o,testId:l,menuTestId:a,refs:u,onNeedRefs:d}){const f=O.useId(),g=O.useRef(null),[m,y]=O.useState(!1),[x,v]=O.useState(null),[S,k]=O.useState(0),j=O.useMemo(()=>{const N=sE(u);return x===null?N:lE(N,x)},[u,x]),b=O.useMemo(()=>aE(j),[j]);O.useEffect(()=>{m&&d()},[m,d]),O.useEffect(()=>{k(0)},[x,m]);const _=()=>{y(!1),v(null)},P=N=>{r(N.value),_()},E=N=>{if(N.key==="ArrowDown"){if(N.preventDefault(),!m){y(!0);return}k(L=>Math.min(L+1,Math.max(j.length-1,0)))}else if(N.key==="ArrowUp"){if(N.preventDefault(),!m)return;k(L=>Math.max(L-1,0))}else if(N.key==="Enter"&&m){N.preventDefault();const L=j[S];L&&P(L)}else N.key==="Escape"&&m&&(N.preventDefault(),_())};return p.jsxs("div",{className:"combo",ref:g,onBlur:N=>{N.currentTarget.contains(N.relatedTarget)||_()},children:[p.jsxs("div",{className:"combo-row",children:[p.jsx("input",{"data-testid":l,value:t,placeholder:o,spellCheck:!1,role:"combobox","aria-expanded":m,"aria-controls":f,"aria-autocomplete":"list",onChange:N=>{r(N.target.value),m&&v(N.target.value)},onKeyDown:E}),p.jsx("button",{type:"button",className:"icon-btn combo-toggle","data-testid":`${l}-toggle`,"aria-label":"Show recent refs","aria-expanded":m,onMouseDown:N=>N.preventDefault(),onClick:()=>m?_():y(!0),children:p.jsx(p0,{})})]}),m?p.jsx("div",{className:"combo-menu",id:f,role:"listbox","data-testid":a,children:b.length===0?p.jsx("div",{className:"combo-empty muted",children:"No matching refs — the typed value is kept"}):b.map(N=>p.jsxs("div",{className:"combo-group",children:[p.jsx("div",{className:"combo-heading",children:uE(N.group)}),N.items.map(L=>{const A=j.indexOf(L);return p.jsxs("button",{type:"button",role:"option","aria-selected":A===S,className:A===S?"combo-option active":"combo-option","data-testid":`ref-option-${L.group}`,onMouseDown:V=>V.preventDefault(),onMouseEnter:()=>k(A),onClick:()=>P(L),children:[p.jsx("span",{className:"combo-label",children:L.label}),L.detail?p.jsx("span",{className:"combo-detail",children:L.detail}):null]},`${L.group}:${L.value}`)})]},N.group))}):null]})}function cE({initialPath:t,onSelect:r,onClose:o}){const[l,a]=O.useState(null),[u,d]=O.useState(t),[f,g]=O.useState(null),[m,y]=O.useState(""),[x,v]=O.useState(!1),S=O.useRef(null),k=O.useRef(0),j=async E=>{const N=k.current+1;k.current=N,v(!0);try{const L=await $e.browse(E);if(k.current!==N)return;a(L),d(L.path),g(L.is_git?L.path:null),y("")}catch(L){if(k.current!==N)return;y(L instanceof Error?L.message:String(L))}finally{k.current===N&&v(!1)}};O.useEffect(()=>{var E,N;j(t),(E=S.current)==null||E.focus(),(N=S.current)==null||N.select()},[t]);const b=f||(l==null?void 0:l.path)||u,_=f&&f!==(l==null?void 0:l.path)?f.split(/[\\/]/).filter(Boolean).pop():l!=null&&l.is_git?"this repository":"this folder",P=E=>{E.key==="Escape"&&(E.preventDefault(),o())};return p.jsx("div",{className:"modal-backdrop","data-testid":"repo-explorer","data-overlay":"true",onClick:o,onKeyDown:P,children:p.jsxs("div",{className:"modal",role:"dialog","aria-modal":"true","aria-labelledby":"explorer-title",onClick:E=>E.stopPropagation(),children:[p.jsxs("div",{className:"modal-head",children:[p.jsxs("div",{children:[p.jsx("h2",{id:"explorer-title",children:"Select repository"}),p.jsx("p",{className:"muted",children:"Browse to a git root, or paste the full path."})]}),p.jsx("button",{type:"button",className:"btn ghost","data-testid":"explorer-cancel",onClick:o,children:"Cancel"})]}),p.jsxs("form",{className:"explorer-path",onSubmit:E=>{E.preventDefault(),j(u)},children:[p.jsx("input",{ref:S,"data-testid":"explorer-path",value:u,onChange:E=>d(E.target.value),spellCheck:!1,"aria-label":"Directory path"}),p.jsx("button",{type:"button",className:"btn",disabled:!(l!=null&&l.parent),onClick:()=>(l==null?void 0:l.parent)&&void j(l.parent),children:"Up"}),p.jsx("button",{type:"button",className:"btn",onClick:()=>l&&void j(l.home),children:"Home"}),p.jsx("button",{type:"submit",className:"btn",children:"Go"})]}),m?p.jsx("div",{className:"error",role:"alert",children:m}):null,p.jsx("div",{className:"explorer-list",role:"listbox","aria-label":"Folders","aria-busy":x,children:l!=null&&l.entries.length?l.entries.map(E=>{const N=f===E.path;return p.jsxs("button",{type:"button",role:"option","aria-selected":N,className:N?"explorer-row active":"explorer-row","data-testid":"explorer-entry","data-path":E.path,onClick:()=>g(E.path),onDoubleClick:()=>void j(E.path),children:[p.jsx(Sp,{}),p.jsx("span",{className:"explorer-name",children:E.name}),E.is_git?p.jsx("span",{className:"chip git-badge",children:"git"}):null]},E.path)}):p.jsx("div",{className:"muted explorer-empty",children:x?"Loading…":"No folders here"})}),p.jsxs("div",{className:"modal-foot",children:[p.jsx("span",{className:"muted explorer-current",title:b,children:b}),p.jsxs("button",{type:"button",className:"btn primary","data-testid":"explorer-use",disabled:!b,onClick:()=>b&&r(b),children:["Use ",_]})]})]})})}const _l=[{id:"obsidian",label:"Obsidian",group:"dark"},{id:"nord",label:"Nord",group:"dark"},{id:"solarized-dark",label:"Solarized Dark",group:"dark"},{id:"forest",label:"Forest",group:"dark"},{id:"rose",label:"Rose Pine",group:"dark"},{id:"amber",label:"Midnight Amber",group:"dark"},{id:"volcano",label:"Volcano",group:"dark"},{id:"lavender",label:"Lavender",group:"dark"},{id:"neon-noir",label:"Neon Noir",group:"dark"},{id:"synthwave",label:"Synthwave",group:"dark"},{id:"phosphor",label:"Phosphor",group:"dark"},{id:"aurora",label:"Aurora",group:"dark"},{id:"biolume",label:"Biolume",group:"dark"},{id:"carbon",label:"Carbon",group:"dark"},{id:"paper",label:"Paper",group:"light"},{id:"solarized-light",label:"Solarized Light",group:"light"},{id:"seafoam",label:"Seafoam",group:"light"},{id:"high-contrast",label:"High Contrast",group:"light"},{id:"sakura",label:"Sakura",group:"light"},{id:"citrus",label:"Citrus",group:"light"},{id:"peach",label:"Peach Fuzz",group:"light"},{id:"candy",label:"Cotton Candy",group:"light"},{id:"sky",label:"Clear Sky",group:"light"},{id:"coral",label:"Coral Reef",group:"light"}],dE="obsidian",pm="loadpath.theme";function fE(t){return _l.some(r=>r.id===t)}function gm(){try{const t=localStorage.getItem(pm)||"";if(fE(t))return t}catch{}return dE}function hE(t){var r;return((r=_l.find(o=>o.id===t))==null?void 0:r.group)==="light"?"light":"dark"}function mm(t){document.documentElement.dataset.theme=t,document.documentElement.style.colorScheme=hE(t);try{localStorage.setItem(pm,t)}catch{}}const vp=[{id:"review",label:"Review",testId:"tab-review",shortcut:"1",icon:u0},{id:"architecture",label:"Architecture",testId:"tab-architecture",shortcut:"2",icon:c0},{id:"graph",label:"Impact graph",testId:"tab-graph",shortcut:"3",icon:d0},{id:"prs",label:"Pull requests",testId:"tab-prs",shortcut:"4",icon:f0},{id:"settings",label:"Settings",testId:"tab-settings",shortcut:"5",icon:h0}];function pE(t){const r=t.total||0;return r<=0?null:Math.min(100,Math.round(100*(t.done||0)/r))}function Fu(t,r,o){let l;try{l=new URL(t)}catch{return}if(l.protocol!=="https:"||l.username||l.password)return;const a=l.hostname.toLowerCase();a!==r&&!a.endsWith(`.${r}`)||l.pathname.startsWith(o)&&window.open(l.toString(),"_blank","noopener,noreferrer")}function gE(){var Zr,Jr,ei,Tt,Vn,Wn,ti,wr;const[t,r]=O.useState("review"),[o,l]=O.useState(localStorage.getItem("loadpath.repo")||""),[a,u]=O.useState(localStorage.getItem("loadpath.base")||"HEAD~1"),[d,f]=O.useState(localStorage.getItem("loadpath.head")||"HEAD"),[g,m]=O.useState(null),[y,x]=O.useState(null),[v,S]=O.useState([]),[k,j]=O.useState("review"),[b,_]=O.useState(""),[P,E]=O.useState(""),[N,L]=O.useState(null),[A,V]=O.useState(!1),[z,G]=O.useState(""),[ee,J]=O.useState({}),[ne,q]=O.useState([]),[C,W]=O.useState([]),[U,Y]=O.useState(localStorage.getItem("loadpath.scmRepo")||""),[T,F]=O.useState(localStorage.getItem("loadpath.provider")||"github"),[B,M]=O.useState(localStorage.getItem("loadpath.prNumber")||""),[R,re]=O.useState(localStorage.getItem("loadpath.dirty")==="1"),[ie,ce]=O.useState(0),[fe,de]=O.useState(""),[K,le]=O.useState(gm),[me,ke]=O.useState(!1),[xe,pe]=O.useState(!1),[be,Pe]=O.useState(null),[Ce,Re]=O.useState(null),[tt,nt]=O.useState(!1),[Je,Ke]=O.useState(!1),ot=O.useRef(o);ot.current=o;const Pt=O.useRef(!1);Pt.current=xe;const st=O.useRef(""),ft=D=>{le(D),mm(D)},He=O.useRef(""),Le=D=>{He.current=D,E(D)},mt=D=>{const te=()=>{$e.indexProgress(D).then(Ie=>{He.current&&(Ie.phase&&Ie.phase!=="idle"&&Ie.message&&Le(Ie.message),L(Ie.phase&&Ie.phase!=="idle"?pE(Ie):null))}).catch(()=>{})};te();const _e=window.setInterval(te,250);return()=>{window.clearInterval(_e),L(null)}};O.useEffect(()=>{$e.settings().then(J).catch(()=>{}).finally(()=>ke(!0)),$e.repos().then(D=>S(D.repos)).catch(()=>{})},[]);const an=()=>o.trim()?!0:(_("Point at a local repository path first."),!1);O.useEffect(()=>{if(t!=="architecture"||!o.trim())return;const D=o;let te=!1;return $e.architecture(D).then(_e=>{!te&&ot.current===D&&x(_e)}).catch(()=>{}),()=>{te=!0}},[t,o]);const ht=D=>{ot.current=D,l(D),localStorage.setItem("loadpath.repo",D),D.trim()!==st.current&&(st.current="",Pe(null))},Gt=O.useCallback(D=>{const te=(D??ot.current).trim();return!te||st.current===te?Promise.resolve():(st.current=te,$e.gitRefs(te).then(_e=>{ot.current.trim()===te&&Pe(_e)}).catch(()=>{st.current===te&&(st.current="",Pe(null))}))},[]),_n=(D,te)=>{u(D),f(te),localStorage.setItem("loadpath.base",D),localStorage.setItem("loadpath.head",te)},zn=(D,te,_e)=>{F(D),Y(te),localStorage.setItem("loadpath.provider",D),localStorage.setItem("loadpath.scmRepo",te),_e!==void 0&&(M(_e),localStorage.setItem("loadpath.prNumber",_e))},Gr=D=>D==="github"?!!ee.github_token_set:D==="gitlab"?!!ee.gitlab_token_set:!!ee.bitbucket_token_set,It=O.useCallback(async(D=T)=>{var te;try{const _e=await $e.scmRepos(D);W(_e.repos),(te=_e.user)!=null&&te.login&&J(Ie=>({...Ie,...D==="github"?{github_user:_e.user.login}:D==="gitlab"?{gitlab_user:_e.user.login}:{bitbucket_user:_e.user.login}}))}catch{W([])}},[T]);O.useEffect(()=>{if(t!=="prs")return;let D=!1;return It(T).catch(()=>{D||W([])}),()=>{D=!0}},[t,T,It]),O.useEffect(()=>{if(!Ce)return;let D=!1,te=0;const _e=async()=>{try{const Ie=await $e.githubOAuthPoll(Ce.flow_id);if(D)return;if(Ie.status==="complete"){Re(null);const Me=await $e.settings();J(Me),G(Ie.user?`Signed in to GitHub as ${Ie.user}`:"Signed in to GitHub"),It("github");return}if(Ie.status==="pending"||Ie.status==="slow_down"){te=window.setTimeout(_e,Math.max(Ie.interval||Ce.interval,5)*1e3);return}Re(null),_(Ie.status==="denied"?"GitHub sign-in was denied.":"GitHub sign-in expired. Try again.")}catch(Ie){if(D)return;Re(null),_(Ie instanceof Error?Ie.message:String(Ie))}};return te=window.setTimeout(_e,Math.max(Ce.interval,5)*1e3),()=>{D=!0,window.clearTimeout(te)}},[Ce,It]),O.useEffect(()=>{if(!tt)return;let D=!1,te=0;const _e=Date.now(),Ie=async()=>{try{const Me=await $e.oauthStatus();if(D)return;if(Me.bitbucket.connected){nt(!1);const Fe=await $e.settings();J(Fe),G(Me.bitbucket.user?`Signed in to Bitbucket as ${Me.bitbucket.user}`:"Signed in to Bitbucket"),It("bitbucket");return}if(Date.now()-_e>18e4){nt(!1),_("Bitbucket sign-in timed out. Finish in the browser, or try again.");return}te=window.setTimeout(Ie,1500)}catch(Me){if(D)return;nt(!1),_(Me instanceof Error?Me.message:String(Me))}};return te=window.setTimeout(Ie,1500),()=>{D=!0,window.clearTimeout(te)}},[tt,It]),O.useEffect(()=>{if(!Je)return;let D=!1,te=0;const _e=Date.now(),Ie=async()=>{try{const Me=await $e.oauthStatus();if(D)return;if(Me.gitlab.connected){Ke(!1);const Fe=await $e.settings();J(Fe),G(Me.gitlab.user?`Signed in to GitLab as ${Me.gitlab.user}`:"Signed in to GitLab"),It("gitlab");return}if(Date.now()-_e>18e4){Ke(!1),_("GitLab sign-in timed out. Finish in the browser, or try again.");return}te=window.setTimeout(Ie,1500)}catch(Me){if(D)return;Ke(!1),_(Me instanceof Error?Me.message:String(Me))}};return te=window.setTimeout(Ie,1500),()=>{D=!0,window.clearTimeout(te)}},[Je,It]);const Sn=async(D=o)=>{if(!D.trim())return null;const te=await $e.architecture(D);return ot.current===D&&x(te),te},Dn=async D=>{const te=D.trim();if(!(!te||te===ot.current)){if(He.current){_("Wait for the current job to finish before switching workspace.");return}_(""),G(""),m(null),x(null),j("architecture"),ht(te),V(!0),Le(`Loading ${a0(te)}…`);try{await Promise.all([Sn(te),Gt(te)])}catch(_e){ot.current===te&&_(_e instanceof Error?_e.message:String(_e))}finally{ot.current===te&&(Le(""),V(!1))}}},un=async()=>{if(He.current||!an())return;_(""),G(""),Le("Tracing load path…"),ht(o),_n(a,d);const D=mt(o);try{const te=await $e.review(o,a,d,!0,R);m(te),ce(0),j("review"),r("review"),await $e.repos().then(_e=>S(_e.repos)).catch(()=>{}),await Sn(o)}catch(te){_(te instanceof Error?te.message:String(te))}finally{D(),Le("")}},$n=async(D=!0)=>{if(He.current||!an())return;_(""),G(""),Le(D?"Indexing…":"Full reindex…"),ht(o);const te=mt(o);try{await $e.index(o,D);const _e=await Sn(o);await $e.repos().then(Ie=>S(Ie.repos)).catch(()=>{}),_e!=null&&_e.indexed&&(j("architecture"),r("architecture"))}catch(_e){_(_e instanceof Error?_e.message:String(_e))}finally{te(),Le("")}},gr=async()=>{if(!He.current&&an()){_(""),G(""),Le("Detecting layout…"),ht(o);try{const D=await $e.init(o);G(D.message),await $e.repos().then(te=>S(te.repos)).catch(()=>{})}catch(D){_(D instanceof Error?D.message:String(D))}finally{Le("")}}},cn=async()=>{if(g!=null&&g.markdown)try{await navigator.clipboard.writeText(g.markdown),G("Copied markdown brief")}catch(D){_(D instanceof Error?D.message:String(D))}},dn=async()=>{if(!He.current){if(!(g!=null&&g.markdown)||!U||!B){_("Pick a pull request first (Pull requests tab), then post the brief.");return}Le("Posting Loadpath brief…");try{const D=await $e.postComment(T,U,Number(B),g.markdown);G(D.updated?"Updated the Loadpath PR comment":"Posted the Loadpath PR comment")}catch(D){_(D instanceof Error?D.message:String(D))}finally{Le("")}}},Xr=async()=>{if(!He.current){_(""),Le("Fetching pull requests…");try{const D=await $e.prs(T,U);q(D.pull_requests);const te=C.find(_e=>_e.slug.toLowerCase()===U.trim().toLowerCase());te!=null&&te.local_path&&ht(te.local_path)}catch(D){_(D instanceof Error?D.message:String(D))}finally{Le("")}}},qr=async()=>{_("");try{const D=await $e.githubOAuthStart();Re(D),Fu(D.verification_uri_complete,"github.com","/login/device")}catch(D){_(D instanceof Error?D.message:String(D))}},Qr=async()=>{_("");try{const D=await $e.bitbucketOAuthStart();nt(!0),Fu(D.authorize_url,"bitbucket.org","/site/oauth2/authorize")}catch(D){nt(!1),_(D instanceof Error?D.message:String(D))}},Kr=async()=>{_("");try{const D=await $e.gitlabOAuthStart();Ke(!0),Fu(D.authorize_url,new URL(D.authorize_url).hostname,"/oauth/authorize")}catch(D){Ke(!1),_(D instanceof Error?D.message:String(D))}},On=async D=>{if(!(He.current||!o.trim())){_(""),Le("Walking what-if path…");try{const te=await $e.whatIf(o,D);G(`${te.title} — ${te.confidence.level} · ${(te.sinks||[]).length} sinks`),m({...te,markdown:te.markdown||"",index:te.index||(g==null?void 0:g.index),workspace:te.workspace||(g==null?void 0:g.workspace)}),ce(0),j("review"),r("review")}catch(te){_(te instanceof Error?te.message:String(te))}finally{Le("")}}},mr=async D=>{if(He.current)return;zn(D.provider,D.repo,String(D.number));const te=C.find(Me=>Me.slug.toLowerCase()===D.repo.toLowerCase());te!=null&&te.local_path&&ht(te.local_path),_(""),Le(`Fetching ${D.provider} #${D.number}…`);const _e=(te==null?void 0:te.local_path)||o,Ie=_e?mt(_e):()=>{};try{const Me=await $e.reviewPr(D.provider,D.repo,D.number,(te==null?void 0:te.local_path)||o||void 0);m(Me),ce(0),Me.pull_request&&typeof Me.pull_request.repo_path=="string"&&ht(Me.pull_request.repo_path),_n(String(Me.base||D.target_branch),String(Me.head||D.source_branch)),j("review"),r("review")}catch(Me){_n(D.base_sha||D.target_branch,D.head_sha||D.source_branch),r("review"),_(Me instanceof Error?Me.message:String(Me))}finally{Ie(),Le("")}},Fn=async D=>{_("");try{J(await $e.oauthDisconnect(D)),T===D&&W([]),G(`Disconnected ${D}`)}catch(te){_(te instanceof Error?te.message:String(te))}},kn=async D=>{D.preventDefault();const te=new FormData(D.currentTarget),_e={github_token:String(te.get("github_token")||""),github_oauth_client_id:String(te.get("github_oauth_client_id")||""),github_host:String(te.get("github_host")||""),gitlab_token:String(te.get("gitlab_token")||""),gitlab_host:String(te.get("gitlab_host")||""),gitlab_oauth_client_id:String(te.get("gitlab_oauth_client_id")||""),gitlab_oauth_client_secret:String(te.get("gitlab_oauth_client_secret")||""),bitbucket_token:String(te.get("bitbucket_token")||""),bitbucket_username:String(te.get("bitbucket_username")||""),bitbucket_oauth_client_id:String(te.get("bitbucket_oauth_client_id")||""),bitbucket_oauth_client_secret:String(te.get("bitbucket_oauth_client_secret")||""),ai_provider:String(te.get("ai_provider")||"none"),ai_api_key:String(te.get("ai_api_key")||""),ai_model:String(te.get("ai_model")||""),ai_base_url:String(te.get("ai_base_url")||"")},Ie=v.length?{..._e,workspaces:v.map(Me=>({path:Me.path,name:Me.name}))}:_e;try{J(await $e.saveSettings(Ie)),G("Settings saved on this machine")}catch(Me){_(Me instanceof Error?Me.message:String(Me))}},yr=async()=>{if(!(!g||He.current)){Le("Residual analysis…");try{const D=await $e.residual(g);de(D.note)}catch(D){_(D instanceof Error?D.message:String(D))}finally{Le("")}}},fn=O.useRef(un);fn.current=un;const vr=O.useRef(t);vr.current=t,O.useEffect(()=>{const D=te=>{if(Pt.current){te.key==="Escape"&&(te.preventDefault(),pe(!1));return}const _e=te.target;if(_e&&(_e.tagName==="INPUT"||_e.tagName==="TEXTAREA"||_e.tagName==="SELECT"||_e.isContentEditable)){te.key==="Escape"&&_e.blur();return}if(te.key==="Escape"){_(""),G("");return}const Ie=vp.find(Me=>Me.shortcut===te.key);if(Ie&&!te.metaKey&&!te.ctrlKey&&!te.altKey&&r(Ie.id),(te.metaKey||te.ctrlKey)&&te.key==="Enter"){if(vr.current==="settings"||vr.current==="prs"||He.current)return;te.preventDefault(),fn.current()}};return window.addEventListener("keydown",D),()=>window.removeEventListener("keydown",D)},[]);const hn=O.useMemo(()=>k==="architecture"?(y==null?void 0:y.nodes)??[]:(g==null?void 0:g.nodes)??[],[k,y,g]),Hn=O.useMemo(()=>k==="architecture"?(y==null?void 0:y.edges)??[]:(g==null?void 0:g.edges)??[],[k,y,g]),Bn=g!=null&&g.index?`${g.index.counts.nodes} nodes · ${g.index.counts.edges} edges`:y!=null&&y.indexed?`${y.counts.nodes} nodes · ${y.counts.edges} edges`:"Not indexed",xr=((g==null?void 0:g.findings)||[]).filter(D=>!D.waived);return p.jsxs("div",{className:"app",children:[p.jsx("a",{className:"skip",href:"#main",children:"Skip to content"}),p.jsxs("nav",{className:"rail","data-testid":"rail","aria-label":"Primary",children:[p.jsxs("div",{className:"brand",children:[p.jsx("div",{className:"brand-mark",children:"Loadpath"}),p.jsx("div",{className:"brand-sub",children:"Load-path review"})]}),vp.map(D=>{const te=D.icon,_e=t===D.id;return p.jsxs("button",{type:"button","data-testid":D.testId,className:_e?"nav-item active":"nav-item","aria-current":_e?"page":void 0,"aria-label":D.label,onClick:()=>r(D.id),children:[p.jsx(te,{}),p.jsx("span",{children:D.label})]},D.id)}),p.jsxs("div",{className:"theme-pick",children:[p.jsx("label",{htmlFor:"theme-select",children:"Theme"}),p.jsx("select",{id:"theme-select","data-testid":"theme-select",value:K,onChange:D=>ft(D.target.value),children:["dark","light"].map(D=>p.jsx("optgroup",{label:D==="dark"?"Dark":"Light",children:_l.filter(te=>te.group===D).map(te=>p.jsx("option",{value:te.id,children:te.label},te.id))},D))})]}),p.jsxs("div",{className:"rail-foot",children:[p.jsx("div",{className:"muted",role:"status",children:P||Bn}),p.jsxs("div",{className:"kbd-hint",children:[p.jsx("kbd",{children:"1"}),"–",p.jsx("kbd",{children:"5"})," tabs · ",p.jsx("kbd",{children:"Ctrl"}),"+",p.jsx("kbd",{children:"Enter"})," review"]})]})]}),p.jsxs("div",{className:"main",id:"main",children:[P?p.jsxs("div",{className:N!=null?"progress determinate":"progress",role:"status","aria-live":"polite","aria-busy":"true","data-testid":"progress",children:[p.jsx("i",{style:N!=null?{width:`${N}%`}:void 0}),p.jsx("span",{className:"sr-only",children:P})]}):null,p.jsxs("header",{className:"topbar","data-testid":"topbar",children:[v.length>0?p.jsxs("label",{className:"field workspace",children:[p.jsx("span",{children:"Workspace"}),p.jsxs("select",{"data-testid":"workspace-select",value:v.some(D=>D.path===o)?o:"",disabled:!!P,"aria-busy":A,onChange:D=>{D.target.value&&Dn(D.target.value)},children:[p.jsx("option",{value:"",children:"Indexed repos…"}),v.map(D=>p.jsxs("option",{value:D.path,children:[D.name,D.indexed?` (${D.counts.nodes})`:""]},D.path))]})]}):null,p.jsxs("label",{className:"field path",children:[p.jsx("span",{children:"Repository"}),p.jsxs("div",{className:"path-row",children:[p.jsx("input",{"data-testid":"repo-path",placeholder:"Local monorepo path",value:o,onChange:D=>{const te=D.target.value;l(te),te.trim()!==st.current&&(st.current="",Pe(null))},spellCheck:!1}),p.jsx("button",{type:"button",className:"icon-btn","data-testid":"btn-browse-repo","aria-label":"Browse for a local repository",onClick:()=>pe(!0),children:p.jsx(Sp,{})})]})]}),p.jsxs("label",{className:"field ref",children:[p.jsx("span",{children:"Base"}),p.jsx(yp,{testId:"base-ref",menuTestId:"base-ref-menu",value:a,onChange:D=>_n(D,d),placeholder:"base",refs:be,onNeedRefs:Gt})]}),p.jsxs("label",{className:"field ref",children:[p.jsx("span",{children:"Head"}),p.jsx(yp,{testId:"head-ref",menuTestId:"head-ref-menu",value:d,onChange:D=>_n(a,D),placeholder:"head",refs:be,onNeedRefs:Gt})]}),p.jsxs("label",{className:"field dirty",children:[p.jsx("span",{children:"Working tree"}),p.jsx("button",{type:"button",className:R?"chip-btn active":"chip-btn","data-testid":"btn-dirty","aria-pressed":R,onClick:()=>{const D=!R;re(D),localStorage.setItem("loadpath.dirty",D?"1":"0")},children:R?"Include uncommitted":"Committed range"})]}),p.jsxs("div",{className:"topbar-actions",children:[p.jsx("button",{type:"button","data-testid":"btn-init",disabled:!!P,onClick:gr,children:"Draft config"}),p.jsx("button",{type:"button","data-testid":"btn-index",disabled:!!P,onClick:()=>$n(!0),children:"Index"}),p.jsx("button",{type:"button","data-testid":"btn-review",className:"btn primary",disabled:!!P,onClick:un,children:"Review"})]})]}),p.jsxs("div",{className:"alerts",children:[b?p.jsxs("div",{className:"error","data-testid":"error",role:"alert",children:[p.jsx("span",{children:b}),p.jsx("button",{type:"button",className:"dismiss",onClick:()=>_(""),"aria-label":"Dismiss error",children:"×"})]}):null,z?p.jsxs("div",{className:"banner","data-testid":"status-note",children:[p.jsx("span",{children:z}),p.jsx("button",{type:"button",className:"dismiss",onClick:()=>G(""),"aria-label":"Dismiss",children:"×"})]}):null,((Zr=g==null?void 0:g.index)!=null&&Zr.stale||y!=null&&y.stale)&&(t==="review"||t==="architecture")?p.jsx("div",{className:"banner stale","data-testid":"index-stale",children:"Index is stale — files changed since the last extract. Index again before trusting this walk."}):null,((Jr=g==null?void 0:g.index)==null?void 0:Jr.django_boot)==="failed"||(y==null?void 0:y.django_boot)==="failed"?p.jsx("div",{className:"banner warn","data-testid":"django-boot-failed",children:((ei=g==null?void 0:g.index)==null?void 0:ei.django_boot_detail)||(y==null?void 0:y.django_boot_detail)||"django.setup() failed"}):null,(Tt=g==null?void 0:g.workspace)!=null&&Tt.dirty_overlaps_review&&t==="review"?p.jsxs("div",{className:"banner warn","data-testid":"dirty-tree",children:["Uncommitted files overlap this review: ",(g.workspace.dirty_overlap||[]).slice(0,6).join(", ")]}):null]}),p.jsxs("div",{className:"stage","aria-busy":A,children:[A?p.jsxs("div",{className:"empty workspace-loading","data-testid":"workspace-loading",children:[p.jsx("h2",{children:P}),p.jsx("p",{children:"Fetching the indexed graph for this repository."})]}):null,!A&&t==="review"&&p.jsxs("div",{className:"content","data-testid":"review-layout",children:[p.jsx("aside",{className:"brief","data-testid":"brief",children:g?p.jsx(mE,{review:g,findings:xr,aiNote:fe,busy:!!P,tourIndex:ie,onTour:ce,onAskAi:yr,onCopy:cn,onPost:dn}):p.jsxs("div",{className:"empty","data-testid":"review-empty",children:[p.jsx("h2",{children:"Trace the force of this diff"}),p.jsx("p",{children:"The graph is the architecture. The brief is where this change travels — not a hunk list."}),p.jsxs("ol",{children:[p.jsx("li",{children:"Point at a Django + React monorepo, or pick an indexed workspace."}),p.jsxs("li",{children:["Index it. Missing ",p.jsx("code",{children:"loadpath.yml"})," is drafted from ",p.jsx("code",{children:"manage.py"})," and"," ",p.jsx("code",{children:"src/features"}),"."]}),p.jsx("li",{children:"Review a git range, or open a pull request so base/head become a three-dot merge-base."})]})]})}),p.jsx("div",{className:"graph-wrap","data-testid":"review-graph",children:g?p.jsx(Ou,{nodes:g.nodes,edges:g.edges,onWhatIf:On,focusPath:(Vn=g.read_order[ie])==null?void 0:Vn.path}):null})]}),!A&&t==="architecture"&&p.jsxs("div",{className:"content","data-testid":"architecture-panel",children:[p.jsx("aside",{className:"brief","data-testid":"architecture-brief",children:y!=null&&y.indexed?p.jsx(yE,{architecture:y,busy:!!P,onReindex:()=>$n(!1),onReview:un}):p.jsx("p",{className:"muted","data-testid":"architecture-empty",children:"Index this repo to build the architecture graph. Review then walks that same graph for a git range — it does not start from a hunk list."})}),p.jsx("div",{className:"graph-wrap","data-testid":"architecture-graph",children:y!=null&&y.indexed?p.jsx(Ou,{nodes:y.nodes,edges:y.edges,onWhatIf:On}):null})]}),!A&&t==="graph"&&p.jsxs("div",{className:"graph-wrap","data-testid":"graph-full",style:{height:"100%"},children:[p.jsxs("div",{className:"graph-modes",children:[p.jsxs("div",{className:"seg","aria-label":"Graph scope",children:[p.jsx("button",{type:"button","aria-pressed":k==="review","data-testid":"graph-mode-review",className:k==="review"?"active":"",onClick:()=>j("review"),children:"This review"}),p.jsx("button",{type:"button","aria-pressed":k==="architecture","data-testid":"graph-mode-architecture",className:k==="architecture"?"active":"",onClick:()=>j("architecture"),children:"Indexed architecture"})]}),p.jsxs("div",{className:"legend","aria-hidden":"true",children:[p.jsxs("span",{children:[p.jsx("i",{})," cheap"]}),p.jsxs("span",{children:[p.jsx("i",{className:"exp"})," expensive"]}),p.jsxs("span",{children:[p.jsx("i",{className:"crit"})," critical"]}),p.jsxs("span",{children:[p.jsx("i",{className:"dash"})," inferred"]})]})]}),hn.length?p.jsx(Ou,{nodes:hn,edges:Hn,onWhatIf:On}):p.jsx("p",{className:"empty","data-testid":"graph-empty",children:"Index the repo or run a review first. Click a node to inspect it."})]}),!A&&t==="prs"&&p.jsxs("div",{className:"pr-list","data-testid":"pr-list",children:[p.jsxs("div",{className:"pr-toolbar",children:[p.jsxs("label",{className:"field provider",children:[p.jsx("span",{children:"Provider"}),p.jsxs("select",{"data-testid":"pr-provider",value:T,onChange:D=>zn(D.target.value,U,B),children:[p.jsx("option",{value:"github",children:"GitHub"}),p.jsx("option",{value:"gitlab",children:"GitLab"}),p.jsx("option",{value:"bitbucket",children:"Bitbucket"})]})]}),p.jsxs("label",{className:"field",children:[p.jsx("span",{children:"Repository"}),p.jsx("input",{"data-testid":"pr-repo",placeholder:C.length?"Search your repos":"owner/repo",value:U,onChange:D=>zn(T,D.target.value,B),list:"scm-repos",spellCheck:!1}),p.jsx("datalist",{id:"scm-repos",children:C.map(D=>p.jsxs("option",{value:D.slug,children:[D.private?"private":"public",D.local_path?" · local":""]},D.slug))})]}),p.jsx("button",{type:"button","data-testid":"btn-refresh-repos",className:"btn",disabled:!!P||!Gr(T),onClick:()=>{It(T)},children:"My repos"}),p.jsx("button",{type:"button","data-testid":"btn-list-prs",className:"btn",disabled:!!P,onClick:Xr,children:"List PRs"})]}),C.length>0?p.jsxs("p",{className:"muted scm-count","data-testid":"scm-repo-count",children:[C.length," ",T," repositor",C.length===1?"y":"ies",T==="github"&&ee.github_user?` · @${String(ee.github_user)}`:"",T==="gitlab"&&ee.gitlab_user?` · @${String(ee.gitlab_user)}`:"",T==="bitbucket"&&ee.bitbucket_user?` · ${String(ee.bitbucket_user)}`:""]}):null,ne.length===0?p.jsxs("div",{className:"empty","data-testid":"pr-empty",children:[p.jsx("h2",{children:"No pull requests loaded"}),p.jsx("p",{children:"Sign in under Settings (or paste a token), load your repositories, then list open PRs. Reviewing a PR fills base and head from its SHAs."})]}):ne.map(D=>p.jsxs("article",{className:"pr","data-testid":`pr-${D.number}`,children:[p.jsxs("h3",{children:["#",D.number," ",D.title]}),p.jsxs("div",{className:"pr-meta muted",children:[p.jsx("span",{className:`chip ${D.draft?"":"open"}`,children:D.draft?"draft":D.state}),p.jsx("span",{children:D.author}),p.jsxs("span",{children:[D.source_branch," → ",D.target_branch]})]}),p.jsxs("div",{className:"pr-actions",children:[p.jsxs("a",{href:D.url,target:"_blank",rel:"noreferrer",children:["Open on ",D.provider]}),p.jsx("button",{type:"button",className:"btn primary","data-testid":`pr-review-${D.number}`,onClick:()=>void mr(D),children:"Review this PR"})]})]},`${D.provider}-${D.number}`))]}),!A&&t==="settings"&&me&&p.jsxs("form",{className:"settings","data-testid":"settings-form",onSubmit:kn,children:[p.jsxs("div",{children:[p.jsx("h1",{children:"Settings"}),p.jsx("p",{className:"muted",children:"Tokens stay on this machine in ~/.loadpath/settings.json. AI runs only on residual uncertainty the graph could not close."})]}),p.jsxs("section",{className:"settings-card",children:[p.jsx("h2",{children:"Appearance"}),p.jsx("p",{className:"muted",children:"Local to this browser. High contrast is a first-class theme, not an afterthought."}),p.jsx("div",{className:"theme-grid","data-testid":"theme-grid",children:_l.map(D=>p.jsxs("button",{type:"button","data-theme":D.id,className:K===D.id?"theme-swatch active":"theme-swatch","data-testid":`theme-${D.id}`,onClick:()=>ft(D.id),children:[p.jsx("div",{className:"swatch-bar","aria-hidden":"true"}),p.jsx("div",{className:"name",children:D.label}),p.jsx("div",{className:"group",children:D.group})]},D.id))})]}),p.jsxs("section",{className:"settings-card",children:[p.jsx("h2",{children:"Source control"}),p.jsx("p",{className:"muted",children:"Sign in with OAuth to list every repository the account can access. Tokens stay in ~/.loadpath/settings.json. A classic PAT still works if you prefer not to register an OAuth app."}),p.jsxs("div",{className:"scm-login","data-testid":"scm-github",children:[p.jsxs("div",{children:[p.jsx("strong",{children:"GitHub"}),p.jsx("p",{className:"muted",children:ee.github_token_set?ee.github_user?`Signed in as @${String(ee.github_user)}`:"Token saved on this machine":"Not connected"})]}),p.jsx("div",{className:"btn-row",children:ee.github_token_set?p.jsx("button",{type:"button",className:"btn","data-testid":"btn-github-disconnect",onClick:()=>void Fn("github"),children:"Disconnect"}):p.jsx("button",{type:"button",className:"btn primary","data-testid":"btn-github-login",disabled:!!Ce||!ee.github_oauth_ready,onClick:()=>void qr(),children:Ce?"Waiting for GitHub…":"Sign in with GitHub"})})]}),Ce?p.jsxs("p",{className:"oauth-code","data-testid":"github-user-code",children:["Enter ",p.jsx("code",{children:Ce.user_code})," at GitHub if the browser did not fill it in."]}):null,ee.github_oauth_ready?null:p.jsx("p",{className:"muted",children:"Sign-in needs a GitHub OAuth App with Device Flow enabled. Set LOADPATH_GITHUB_CLIENT_ID or paste the client ID below."}),p.jsx("label",{htmlFor:"github_oauth_client_id",children:"GitHub OAuth client ID"}),p.jsx("input",{id:"github_oauth_client_id",name:"github_oauth_client_id","data-testid":"github-oauth-client-id",placeholder:"Ov23…",defaultValue:String(ee.github_oauth_client_id||""),autoComplete:"off"}),p.jsx("label",{htmlFor:"github_token",children:"GitHub token (optional PAT)"}),p.jsx("input",{id:"github_token",name:"github_token",type:"password",placeholder:"ghp_…",autoComplete:"off"}),p.jsx("label",{htmlFor:"github_host",children:"GitHub host (Enterprise)"}),p.jsx("input",{id:"github_host",name:"github_host","data-testid":"github-host",placeholder:"github.com",defaultValue:String(ee.github_host||""),autoComplete:"off"}),p.jsxs("div",{className:"scm-login","data-testid":"scm-gitlab",children:[p.jsxs("div",{children:[p.jsx("strong",{children:"GitLab"}),p.jsx("p",{className:"muted",children:ee.gitlab_token_set?ee.gitlab_user?`Signed in as @${String(ee.gitlab_user)}`:"Token saved on this machine":"Not connected"})]}),p.jsx("div",{className:"btn-row",children:ee.gitlab_token_set?p.jsx("button",{type:"button",className:"btn","data-testid":"btn-gitlab-disconnect",onClick:()=>void Fn("gitlab"),children:"Disconnect"}):p.jsx("button",{type:"button",className:"btn primary","data-testid":"btn-gitlab-login",disabled:Je||!ee.gitlab_oauth_ready,onClick:()=>void Kr(),children:Je?"Waiting for GitLab…":"Sign in with GitLab"})})]}),p.jsx("label",{htmlFor:"gitlab_host",children:"GitLab host"}),p.jsx("input",{id:"gitlab_host",name:"gitlab_host","data-testid":"gitlab-host",placeholder:"gitlab.com",defaultValue:String(ee.gitlab_host||""),autoComplete:"off"}),p.jsx("label",{htmlFor:"gitlab_oauth_client_id",children:"GitLab OAuth application ID"}),p.jsx("input",{id:"gitlab_oauth_client_id",name:"gitlab_oauth_client_id","data-testid":"gitlab-oauth-client-id",defaultValue:String(ee.gitlab_oauth_client_id||""),autoComplete:"off"}),p.jsx("label",{htmlFor:"gitlab_oauth_client_secret",children:"GitLab OAuth secret"}),p.jsx("input",{id:"gitlab_oauth_client_secret",name:"gitlab_oauth_client_secret",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"gitlab_token",children:"GitLab token (optional PAT)"}),p.jsx("input",{id:"gitlab_token",name:"gitlab_token",type:"password",placeholder:"glpat-…",autoComplete:"off"}),p.jsxs("div",{className:"scm-login","data-testid":"scm-bitbucket",children:[p.jsxs("div",{children:[p.jsx("strong",{children:"Bitbucket"}),p.jsx("p",{className:"muted",children:ee.bitbucket_token_set?ee.bitbucket_user?`Signed in as ${String(ee.bitbucket_user)}`:"Token saved on this machine":"Not connected"})]}),p.jsx("div",{className:"btn-row",children:ee.bitbucket_token_set?p.jsx("button",{type:"button",className:"btn","data-testid":"btn-bitbucket-disconnect",onClick:()=>void Fn("bitbucket"),children:"Disconnect"}):p.jsx("button",{type:"button",className:"btn primary","data-testid":"btn-bitbucket-login",disabled:tt||!ee.bitbucket_oauth_ready,onClick:()=>void Qr(),children:tt?"Waiting for Bitbucket…":"Sign in with Bitbucket"})})]}),ee.bitbucket_oauth_ready?null:p.jsxs("p",{className:"muted",children:["Sign-in needs a Bitbucket OAuth consumer (key + secret). Callback URL:"," ",p.jsx("code",{children:"/api/oauth/bitbucket/callback"})," on this app origin."]}),p.jsx("label",{htmlFor:"bitbucket_oauth_client_id",children:"Bitbucket OAuth key"}),p.jsx("input",{id:"bitbucket_oauth_client_id",name:"bitbucket_oauth_client_id","data-testid":"bitbucket-oauth-client-id",defaultValue:String(ee.bitbucket_oauth_client_id||""),autoComplete:"off"}),p.jsx("label",{htmlFor:"bitbucket_oauth_client_secret",children:"Bitbucket OAuth secret"}),p.jsx("input",{id:"bitbucket_oauth_client_secret",name:"bitbucket_oauth_client_secret",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"bitbucket_token",children:"Bitbucket token (optional app password)"}),p.jsx("input",{id:"bitbucket_token",name:"bitbucket_token",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"bitbucket_username",children:"Bitbucket username (app passwords)"}),p.jsx("input",{id:"bitbucket_username",name:"bitbucket_username",defaultValue:String(ee.bitbucket_username||"")})]}),p.jsxs("section",{className:"settings-card",children:[p.jsx("h2",{children:"Residual AI"}),p.jsx("label",{htmlFor:"ai_provider",children:"Provider"}),p.jsxs("select",{id:"ai_provider",name:"ai_provider",defaultValue:String(((Wn=ee.ai)==null?void 0:Wn.provider)||"none"),children:[p.jsx("option",{value:"none",children:"none (graph only)"}),p.jsx("option",{value:"anthropic",children:"Anthropic"}),p.jsx("option",{value:"openai",children:"OpenAI"}),p.jsx("option",{value:"grok",children:"Grok / xAI"}),p.jsx("option",{value:"deepseek",children:"DeepSeek"}),p.jsx("option",{value:"cursor",children:"Cursor-compatible (OpenAI protocol)"}),p.jsx("option",{value:"ollama",children:"Ollama local"})]}),p.jsx("label",{htmlFor:"ai_api_key",children:"API key"}),p.jsx("input",{id:"ai_api_key",name:"ai_api_key",type:"password",autoComplete:"off"}),p.jsx("label",{htmlFor:"ai_model",children:"Model"}),p.jsx("input",{id:"ai_model",name:"ai_model","data-testid":"ai-model",placeholder:"optional override",defaultValue:String(((ti=ee.ai)==null?void 0:ti.model)||"")}),p.jsx("label",{htmlFor:"ai_base_url",children:"Base URL"}),p.jsx("input",{id:"ai_base_url",name:"ai_base_url","data-testid":"ai-base-url",placeholder:"optional, OpenAI-compatible",defaultValue:String(((wr=ee.ai)==null?void 0:wr.base_url)||"")}),p.jsx("button",{className:"btn primary",type:"submit","data-testid":"btn-save-settings",children:"Save"})]})]})]})]}),xe?p.jsx(cE,{initialPath:o,onClose:()=>pe(!1),onSelect:D=>{if(He.current){_("Wait for the current job to finish before switching workspace.");return}pe(!1),Dn(D)}}):null]})}function mE({review:t,findings:r,aiNote:o,busy:l,tourIndex:a,onTour:u,onAskAi:d,onCopy:f,onPost:g}){var y,x,v,S,k,j,b,_,P,E,N,L,A,V;const m=[...new Set(t.confidence.reasons||[])];return p.jsxs(p.Fragment,{children:[p.jsxs("div",{className:`merge-box ${t.confidence.level}`,children:[p.jsxs("div",{className:`level ${t.confidence.level}`,children:[t.confidence.level.toUpperCase()," — ",t.title]}),m.length?p.jsx("ul",{className:"reasons",children:m.map(z=>p.jsx("li",{children:z},z))}):null,t.low_risk?p.jsx("span",{className:"chip",children:"low-risk"}):null,t.change_kinds.map(z=>p.jsx("span",{className:"chip",children:bo(z)},z)),(y=t.contract_break)!=null&&y.kind&&t.contract_break.kind!=="none"?p.jsxs("span",{className:`chip ${t.contract_break.kind==="breaking"?"blocker":""}`,"data-testid":"contract-kind",children:["contract ",t.contract_break.kind]}):null]}),p.jsxs("div",{className:"metrics",children:[p.jsxs("div",{className:"metric",children:[p.jsxs("div",{className:"n",children:[t.confidence.covered_sinks,"/",t.confidence.sinks]}),p.jsx("div",{className:"l",children:"Sinks tested"})]}),p.jsxs("div",{className:"metric",children:[p.jsx("div",{className:"n",children:r.length}),p.jsx("div",{className:"l",children:"Findings"})]}),p.jsxs("div",{className:"metric",children:[p.jsx("div",{className:"n",children:t.residuals.length}),p.jsx("div",{className:"l",children:"Residuals"})]})]}),p.jsx("pre",{className:"headline",children:t.headline}),t.index?p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Index ",p.jsx("span",{className:"count",children:t.index.counts.nodes})]}),p.jsxs("div",{className:"muted",children:["Walked ",t.index.counts.nodes," nodes / ",t.index.counts.edges," edges",t.index.reindex_skipped?" from an unchanged index":t.index.reindexed?" after an incremental refresh":" from the existing index",t.index.django_boot&&t.index.django_boot!=="off"?` · Django boot ${t.index.django_boot}`:"",(x=t.workspace)!=null&&x.three_dot?" · three-dot range":""]})]}):null,p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Read this ",p.jsx("span",{className:"count",children:t.read_order.length})]}),t.read_order.map((z,G)=>p.jsxs("div",{className:G===a?"read-item tour-current":"read-item",children:[p.jsxs("span",{className:"file",children:[G+1,". ",z.path]}),p.jsx("div",{className:"why",children:z.why})]},z.path)),t.read_order.length>0?p.jsxs("div",{className:"btn-row tour-row",children:[p.jsx("button",{type:"button",className:"btn","data-testid":"btn-tour-prev",disabled:a<=0,onClick:()=>u(Math.max(0,a-1)),children:"Previous"}),p.jsx("button",{type:"button",className:"btn primary","data-testid":"btn-tour-next",disabled:a>=t.read_order.length-1,onClick:()=>u(Math.min(t.read_order.length-1,a+1)),children:"Next in read order"}),p.jsxs("span",{className:"muted",children:[a+1,"/",t.read_order.length]})]}):null]}),p.jsxs("details",{className:"section",children:[p.jsxs("summary",{children:["Clusters ",p.jsx("span",{className:"count",children:t.clusters.length})]}),t.clusters.map(z=>p.jsxs("div",{className:"muted",children:[p.jsx("strong",{children:z.title})," — ",z.files.join(", ")]},z.id))]}),p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Architecture ",p.jsx("span",{className:"count",children:r.length})]}),r.length===0?p.jsx("div",{className:"muted",children:t.architecture_note}):r.map(z=>p.jsxs("div",{className:"finding",children:[p.jsx("span",{className:`chip ${z.severity}`,children:z.severity}),z.message]},z.rule+z.message))]}),p.jsx(ym,{cards:t.deepening}),(S=(v=t.contract_break)==null?void 0:v.reasons)!=null&&S.length?p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Contract ",p.jsx("span",{className:"count",children:t.contract_break.kind})]}),t.contract_break.reasons.map(z=>p.jsx("div",{className:"muted",children:z},z))]}):null,(k=t.auth)!=null&&k.note?p.jsxs("details",{className:"section",open:!0,children:[p.jsx("summary",{children:"Auth"}),p.jsx("div",{className:"muted",children:t.auth.note}),(t.auth.missing_permissions||[]).map(z=>p.jsxs("div",{className:"finding",children:[p.jsx("span",{className:"chip warning",children:"missing"}),z.name]},z.id))]}):null,(t.suggested_tests||[]).length?p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Suggested tests ",p.jsx("span",{className:"count",children:(j=t.suggested_tests)==null?void 0:j.length})]}),(t.suggested_tests||[]).map(z=>p.jsxs("div",{className:"residual",children:[p.jsx("strong",{children:z.title}),p.jsx("pre",{className:"headline",children:z.body})]},z.title))]}):null,(b=t.trend)!=null&&b.note?p.jsxs("details",{className:"section",children:[p.jsx("summary",{children:"Confidence trend"}),p.jsx("div",{className:"muted",children:t.trend.note}),(t.trend.points||[]).slice(0,6).map(z=>p.jsxs("div",{className:"muted",children:[z.level," · ",_p(z.created_at),z.sinks!=null?` · ${z.sinks} sinks`:""]},z.id))]}):null,p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Residual ",p.jsx("span",{className:"count",children:t.residuals.length})]}),p.jsx("p",{className:"muted",children:"AI is only used here, on what the graph could not close."}),t.residuals.map(z=>p.jsx("div",{className:"residual muted",children:z},z))]}),(P=(_=t.evolution)==null?void 0:_.notes)!=null&&P.length||(N=(E=t.evolution)==null?void 0:E.hotspots)!=null&&N.some(z=>z.commits)?p.jsxs("details",{className:"section",children:[p.jsx("summary",{children:"Churn & coupling"}),(((L=t.evolution)==null?void 0:L.notes)||[]).map(z=>p.jsx("div",{className:"muted",children:z},z)),(((A=t.evolution)==null?void 0:A.hotspots)||[]).filter(z=>z.commits).slice(0,6).map(z=>p.jsxs("div",{className:"muted",children:[p.jsx("span",{className:"file",children:z.path})," — ",z.commits," commits, bus factor ",z.bus_factor]},z.path))]}):null,p.jsxs("div",{className:"btn-row",children:[p.jsx("button",{type:"button",className:"btn",disabled:l,onClick:d,children:"Ask configured model"}),p.jsx("button",{type:"button",className:"btn","data-testid":"btn-copy-markdown",onClick:f,children:"Copy markdown"}),p.jsx("button",{type:"button",className:"btn","data-testid":"btn-post-comment",onClick:g,children:"Post to PR"})]}),o?p.jsx("pre",{className:"headline",children:o}):null,p.jsx("div",{className:"kicker",children:"Reviewers"}),p.jsx("div",{className:"muted",children:t.suggested_reviewers.join(", ")||"—"}),(V=t.knowledge_owners)!=null&&V.length?p.jsxs("div",{className:"muted",children:["Knowledge: ",t.knowledge_owners.join(", ")]}):null]})}function yE({architecture:t,busy:r,onReindex:o,onReview:l}){const a=t.findings.filter(u=>!u.waived);return p.jsxs(p.Fragment,{children:[p.jsxs("div",{className:"merge-box high",children:[p.jsxs("div",{className:"level high",children:["INDEXED — ",t.counts.nodes," nodes"]}),p.jsxs("div",{className:"muted",style:{marginTop:8},children:[t.indexed_at?`Last index ${_p(t.indexed_at)}`:"Indexed",t.incremental?" · incremental":" · full",t.stale?" · stale":"",t.django_boot&&t.django_boot!=="off"?` · Django boot ${t.django_boot}`:""]}),p.jsxs("span",{className:"chip",children:[t.counts.edges," edges"]}),t.has_config?p.jsx("span",{className:"chip",children:"loadpath.yml"}):null]}),p.jsxs("details",{className:"section",open:!0,children:[p.jsx("summary",{children:"Bounded contexts"}),Object.values(t.contexts).map(u=>p.jsxs("div",{className:"muted",children:[p.jsx("strong",{children:u.name})," — ",(u.django_apps||[]).join(", ")||"no apps"," ·"," ",(u.owners||[]).join(", ")||"unowned"]},u.name))]}),p.jsxs("details",{className:"section",children:[p.jsxs("summary",{children:["Rules ",p.jsx("span",{className:"count",children:(t.rules||[]).length})]}),(t.rules||[]).map(u=>p.jsx("div",{className:"muted",children:u},u))]}),p.jsxs("details",{className:"section",open:!0,children:[p.jsxs("summary",{children:["Findings ",p.jsx("span",{className:"count",children:a.length})]}),a.length===0?p.jsx("div",{className:"muted",children:"No architecture rule hits on the full graph."}):a.map(u=>p.jsxs("div",{className:"finding",children:[p.jsx("span",{className:`chip ${u.severity}`,children:u.severity}),u.message]},u.rule+u.message))]}),p.jsx(ym,{cards:t.deepening}),p.jsxs("details",{className:"section",open:!0,children:[p.jsx("summary",{children:"Types"}),p.jsx("table",{className:"type-table",children:p.jsx("tbody",{children:Object.entries(t.type_counts||{}).sort((u,d)=>d[1]-u[1]).slice(0,12).map(([u,d])=>p.jsxs("tr",{children:[p.jsx("td",{children:Sl(u)}),p.jsx("td",{children:d})]},u))})})]}),p.jsxs("div",{className:"btn-row",children:[p.jsx("button",{type:"button",className:"btn",disabled:r,onClick:o,"data-testid":"btn-full-reindex",children:"Full reindex"}),p.jsx("button",{type:"button",className:"btn primary",disabled:r,onClick:l,children:"Review against this index"})]})]})}function ym({cards:t}){const r=t||[];return r.length?p.jsxs("details",{className:"section",open:!0,"data-testid":"deepening-list",children:[p.jsxs("summary",{children:["Depth ",p.jsx("span",{className:"count",children:r.length})]}),p.jsx("p",{className:"muted",children:"Deepening opportunities: more behaviour behind a smaller interface, at a real seam."}),r.map(o=>p.jsxs("div",{className:"finding","data-testid":"deepening-card",children:[p.jsx("span",{className:`chip ${o.strength}`,children:l0(o.strength)}),o.top?p.jsx("span",{className:"chip",children:"top"}):null,p.jsx("strong",{children:o.title}),p.jsx("div",{className:"why",children:o.message}),o.deletion_test?p.jsxs("div",{className:"muted",children:["Deletion test: ",o.deletion_test]}):null,o.before&&o.after?p.jsxs("div",{className:"muted",children:[o.before," → ",o.after]}):null]},o.rule+o.title))]}):null}mm(gm());i0.createRoot(document.getElementById("root")).render(p.jsx(O.StrictMode,{children:p.jsx(gE,{})}));export{zk as L,wE as a,vE as c,p as j,xE as l,O as r,Sl as t}; diff --git a/src/loadpath/static/index.html b/src/loadpath/static/index.html index c49583e..0791858 100644 --- a/src/loadpath/static/index.html +++ b/src/loadpath/static/index.html @@ -17,8 +17,8 @@ - - + +
diff --git a/src/loadpath/stitch/openapi.py b/src/loadpath/stitch/openapi.py index a640082..eca60c7 100644 --- a/src/loadpath/stitch/openapi.py +++ b/src/loadpath/stitch/openapi.py @@ -175,7 +175,7 @@ def stitch(store: GraphStore, config: LoadpathConfig, repo_root: Path) -> list[s ) ) - routes = [n for n in store.nodes([NodeType.ROUTE])] + routes = [n for n in store.nodes([NodeType.ROUTE, NodeType.FASTAPI_ROUTE, NodeType.WEBSOCKET_ROUTE])] clients = [n for n in store.nodes([NodeType.API_CLIENT])] serializers = [n for n in store.nodes([NodeType.SERIALIZER])] ser_fields = [n for n in store.nodes([NodeType.SERIALIZER_FIELD])] @@ -331,10 +331,84 @@ def stitch(store: GraphStore, config: LoadpathConfig, repo_root: Path) -> list[s ) ) + residuals.extend(_stitch_graphql(store)) + residuals.extend(_stitch_htmx(store)) store.conn.commit() return residuals +def _stitch_graphql(store: GraphStore) -> list[str]: + residuals: list[str] = [] + ops = store.nodes([NodeType.GRAPHQL_OPERATION]) + server = [n for n in ops if not (n.get("extra") or {}).get("client")] + clients = [n for n in ops if (n.get("extra") or {}).get("client")] + by_name = {n["name"].lower(): n for n in server} + + def match_server(name: str) -> dict | None: + return by_name.get((name or "").lower()) + + for client in clients: + extra = client.get("extra") or {} + match = match_server(client["name"]) + if not match: + for sel in extra.get("selections") or []: + match = match_server(str(sel)) + if match: + break + if not match: + residuals.append( + f"GraphQL client operation {client['name']} has no matching server field " + f"({client.get('file_path')})" + ) + continue + store.upsert_edge( + Edge( + src=match["id"], + dst=client["id"], + type=EdgeType.CONSUMED_BY_CLIENT, + confidence=0.92, + extra={"via": "graphql", "operation": client["name"], "server": match["name"]}, + ) + ) + return residuals + + +def _stitch_htmx(store: GraphStore) -> list[str]: + residuals: list[str] = [] + calls = store.nodes([NodeType.HTMX_CALL]) + if not calls: + return residuals + routes = [ + n + for n in store.nodes([NodeType.ROUTE, NodeType.FASTAPI_ROUTE]) + if not (n.get("extra") or {}).get("include") + ] + for call in calls: + url = str((call.get("extra") or {}).get("url") or "") + if not url or url.startswith("{%"): + continue + tmpl = django_route_to_template(url) + matched = False + for route in routes: + rtmpl = django_route_to_template(str(published_route(route))) + if not _paths_match(tmpl, rtmpl) and not (tmpl.endswith(rtmpl) or rtmpl.endswith(tmpl)): + continue + store.upsert_edge( + Edge( + src=route["id"], + dst=call["id"], + type=EdgeType.CONSUMED_BY_CLIENT, + confidence=0.8, + extra={"via": "htmx", "htmx": True, "django": rtmpl, "htmx_url": tmpl}, + ) + ) + matched = True + break + if not matched: + residuals.append(f"HTMX {tmpl} has no matching Django route ({call.get('file_path')})") + return residuals + + def _paths_match(a: str, b: str) -> bool: a = normalize_url_template(a) b = django_route_to_template(b) diff --git a/src/loadpath/types.py b/src/loadpath/types.py index 4aa148c..1eb7dcc 100644 --- a/src/loadpath/types.py +++ b/src/loadpath/types.py @@ -38,6 +38,18 @@ class NodeType(StrEnum): ADMIN = "django.admin" MANAGEMENT_COMMAND = "django.management_command" TEST = "django.test" + CONSUMER = "django.consumer" + WEBSOCKET_ROUTE = "django.websocket_route" + TEMPLATE = "django.template" + HTMX_CALL = "django.htmx" + CACHE_KEY = "django.cache_key" + FEATURE_FLAG = "django.feature_flag" + SIDE_EFFECT = "django.side_effect" + GRAPHQL_TYPE = "graphql.type" + GRAPHQL_FIELD = "graphql.field" + GRAPHQL_OPERATION = "graphql.operation" + FASTAPI_ROUTE = "fastapi.route" + PYDANTIC_MODEL = "fastapi.model" # React REACT_ROUTE = "react.route" PAGE = "react.page" @@ -76,6 +88,12 @@ class EdgeType(StrEnum): HAS_PERMISSION = "has_permission" SERVES = "serves" RELATES_TO = "relates_to" + SERVES_TEMPLATE = "serves_template" + HTMX_CALLS = "htmx_calls" + INVALIDATES_CACHE = "invalidates_cache" + CHECKS_FLAG = "checks_flag" + ON_COMMIT = "on_commit" + PUBLISHES_GRAPHQL = "publishes_graphql" class EdgeWeight(StrEnum): @@ -93,6 +111,8 @@ class EdgeWeight(StrEnum): EdgeType.SERVES, EdgeType.USES_QUERY_KEY, EdgeType.RELATES_TO, + EdgeType.SERVES_TEMPLATE, + EdgeType.CHECKS_FLAG, } EXPENSIVE_EDGES = { EdgeType.SERIALIZES, @@ -104,6 +124,10 @@ class EdgeWeight(StrEnum): EdgeType.HAS_PERMISSION, EdgeType.TESTED_BY, EdgeType.MATCHES_SCHEMA, + EdgeType.HTMX_CALLS, + EdgeType.INVALIDATES_CACHE, + EdgeType.ON_COMMIT, + EdgeType.PUBLISHES_GRAPHQL, } CRITICAL_EDGES = { EdgeType.PUBLISHES_ROUTE, @@ -124,6 +148,14 @@ class EdgeWeight(StrEnum): NodeType.ADMIN, NodeType.MANAGEMENT_COMMAND, NodeType.OPENAPI_PATH, + NodeType.CONSUMER, + NodeType.WEBSOCKET_ROUTE, + NodeType.TEMPLATE, + NodeType.CACHE_KEY, + NodeType.FEATURE_FLAG, + NodeType.SIDE_EFFECT, + NodeType.GRAPHQL_OPERATION, + NodeType.FASTAPI_ROUTE, } CONTRACT_TYPES = { @@ -133,6 +165,11 @@ class EdgeWeight(StrEnum): NodeType.OPENAPI_PATH, NodeType.FORM_SCHEMA, NodeType.ROUTE, + NodeType.GRAPHQL_TYPE, + NodeType.GRAPHQL_FIELD, + NodeType.GRAPHQL_OPERATION, + NodeType.PYDANTIC_MODEL, + NodeType.FASTAPI_ROUTE, } GENERATED_PATH_MARKERS = ( @@ -158,6 +195,13 @@ class ChangeKind(StrEnum): MIXED = "mixed" +class ContractBreakKind(StrEnum): + NONE = "none" + ADDITIVE = "additive" + BREAKING = "breaking" + DRIFT = "drift" + + class ConfidenceLevel(StrEnum): HIGH = "high" MEDIUM = "medium" diff --git a/tests/e2e/test_api_flow.py b/tests/e2e/test_api_flow.py index f743cea..416dbf7 100644 --- a/tests/e2e/test_api_flow.py +++ b/tests/e2e/test_api_flow.py @@ -174,6 +174,63 @@ def test_api_lists_github_and_bitbucket_prs(tmp_path, monkeypatch): assert bb.json()["pull_requests"][0]["provider"] == "bitbucket" +def test_api_whatif_and_gitlab_mrs(tmp_path, monkeypatch): + monkeypatch.setenv("HOME", str(tmp_path / "home")) + (tmp_path / "home").mkdir() + repo = prepare_review_repo(tmp_path) + client = TestClient(create_app()) + indexed = client.post("/api/index", json={"repo_path": str(repo), "incremental": False}) + assert indexed.status_code == 200, indexed.text + walked = client.post( + "/api/whatif", + json={"repo_path": str(repo), "node_id": "django.field:billing.Invoice.total"}, + ) + assert walked.status_code == 200, walked.text + body = walked.json() + assert body["what_if"] is True + assert body["nodes"] + assert body["sinks"] + dirty = client.post( + "/api/review", + json={"repo_path": str(repo), "base": "HEAD~1", "head": "HEAD", "reindex": False, "dirty": True}, + ) + assert dirty.status_code == 200, dirty.text + assert dirty.json()["workspace"]["dirty_included"] is True + assert dirty.json()["contract_break"]["kind"] in {"breaking", "additive", "drift", "none"} + + +@respx.mock +def test_api_lists_gitlab_merge_requests(tmp_path, monkeypatch): + monkeypatch.setenv("HOME", str(tmp_path / "home")) + (tmp_path / "home").mkdir() + client = TestClient(create_app()) + client.put("/api/settings", json={"gitlab_token": "glpat_test", "gitlab_host": "gitlab.com"}) + respx.get("https://gitlab.com/api/v4/projects/acme%2Fdemo/merge_requests").mock( + return_value=httpx.Response( + 200, + json=[ + { + "id": 9, + "iid": 4, + "title": "Invoice total", + "web_url": "https://gitlab.com/acme/demo/-/merge_requests/4", + "author": {"username": "ada"}, + "source_branch": "feat/total", + "target_branch": "main", + "state": "opened", + "updated_at": "2026-08-14T00:00:00Z", + "draft": False, + "sha": "abc", + } + ], + ) + ) + gl = client.post("/api/prs", json={"provider": "gitlab", "repo": "acme/demo"}) + assert gl.status_code == 200, gl.text + assert gl.json()["pull_requests"][0]["number"] == 4 + assert gl.json()["pull_requests"][0]["provider"] == "gitlab" + + def test_blank_and_missing_repo_path_rejected(tmp_path, monkeypatch): monkeypatch.setenv("HOME", str(tmp_path / "home")) (tmp_path / "home").mkdir() diff --git a/tests/e2e/test_cli_review.py b/tests/e2e/test_cli_review.py index c021e23..b7d058f 100644 --- a/tests/e2e/test_cli_review.py +++ b/tests/e2e/test_cli_review.py @@ -72,6 +72,7 @@ def test_cli_help(): assert "architecture" in result.output assert "init" in result.output assert "mcp" in result.output + assert "whatif" in result.output def test_cli_serve_help(): @@ -103,3 +104,46 @@ def test_cli_mcp_help(): result = runner.invoke(app, ["mcp", "--help"]) assert result.exit_code == 0 assert "stdio" in result.output.lower() + + +def test_cli_whatif_dirty_and_gate(tmp_path, monkeypatch): + monkeypatch.setenv("HOME", str(tmp_path / "home")) + (tmp_path / "home").mkdir() + repo = prepare_review_repo(tmp_path) + indexed = runner.invoke(app, ["index", str(repo)]) + assert indexed.exit_code == 0, indexed.output + walked = runner.invoke(app, ["whatif", str(repo), "django.field:billing.Invoice.total"]) + assert walked.exit_code == 0, walked.output + assert "What if" in walked.output + gh_out = tmp_path / "github_output" + gated = runner.invoke( + app, + [ + "review", + str(repo), + "--base", + "HEAD~1", + "--head", + "HEAD", + "--no-reindex", + "--fail-on", + "never", + "--github-output", + str(gh_out), + "--format", + "json", + "--out", + str(tmp_path / "review.json"), + ], + ) + assert gated.exit_code == 0, gated.output + assert "level=" in gh_out.read_text() + (repo / "backend/billing/serializers.py").write_text( + (repo / "backend/billing/serializers.py").read_text() + "\n# uncommitted\n" + ) + dirty = runner.invoke( + app, + ["review", str(repo), "--dirty", "--no-reindex", "--format", "json", "--out", str(tmp_path / "dirty.json")], + ) + assert dirty.exit_code == 0, dirty.output + assert "dirty_included" in (tmp_path / "dirty.json").read_text() diff --git a/tests/e2e/test_ui_flows.py b/tests/e2e/test_ui_flows.py index 7bffc3e..74536f5 100644 --- a/tests/e2e/test_ui_flows.py +++ b/tests/e2e/test_ui_flows.py @@ -159,6 +159,51 @@ def test_ui_index_review_graph_copy_and_workspace(live_app, browser_page): assert "pull request" in post_err.lower() +@pytest.mark.playwright +def test_ui_index_polls_progress_endpoint(live_app, browser_page): + import time + + base_url, repo = live_app + page = browser_page + page.goto(base_url, wait_until="networkidle") + _wait_fonts(page) + page.get_by_test_id("repo-path").fill(str(repo)) + + progress_hits: list[str] = [] + + def on_route(route): + req = route.request + url = req.url + if "/api/index/progress" in url: + progress_hits.append(url) + route.continue_() + return + if req.method == "POST" and url.rstrip("/").endswith("/api/index"): + time.sleep(1.2) + route.continue_() + return + route.continue_() + + page.route("**/api/**", on_route) + with page.expect_response( + lambda r: r.url.rstrip("/").endswith("/api/index") and r.request.method == "POST", + timeout=60_000, + ): + page.get_by_test_id("btn-index").click() + page.locator(".progress").wait_for(timeout=5_000) + page.wait_for_function( + """() => { + const t = document.querySelector('.rail-foot .muted')?.textContent || ''; + return /Extract|Scan|Stitch|Indexed|Boot|Hashed/.test(t); + }""", + timeout=10_000, + ) + page.screenshot(path="/opt/cursor/artifacts/index_progress_bar.png") + page.get_by_test_id("architecture-brief").locator(".level").wait_for(timeout=15_000) + assert progress_hits, "UI should poll GET /api/index/progress while indexing" + page.screenshot(path="/opt/cursor/artifacts/index_complete_architecture.png") + + @pytest.mark.playwright def test_ui_settings_preserve_model_and_theme(live_app, browser_page): base_url, _repo = live_app diff --git a/tests/unit/test_index_progress.py b/tests/unit/test_index_progress.py new file mode 100644 index 0000000..3071483 --- /dev/null +++ b/tests/unit/test_index_progress.py @@ -0,0 +1,100 @@ +from __future__ import annotations + +from pathlib import Path + +from fastapi.testclient import TestClient + +from loadpath.index import index_repo +from loadpath.server.app import create_app +from tests.conftest import FIXTURE_ROOT as FIXTURE, prepare_review_repo + + +def test_index_emits_granular_progress(tmp_path: Path): + events: list[dict] = [] + store = index_repo( + FIXTURE, + db_path=tmp_path / "g.sqlite3", + incremental=False, + workers=1, + progress=events.append, + ) + phases = [e["phase"] for e in events] + assert phases[0] == "scan" + assert "extract" in phases + assert "stitch" in phases + assert phases[-1] == "done" + extracts = [e for e in events if e["phase"] == "extract" and e.get("current")] + assert extracts, "expected per-file extract events" + last_extract = [e for e in events if e["phase"] == "extract"][-1] + assert last_extract["total"] >= last_extract["done"] > 0 + assert any("/" in (e.get("current") or "") for e in extracts) + assert store.get_meta("index_workers") == "1" + assert int(store.get_meta("index_elapsed_ms") or "0") >= 0 + store.close() + + +def test_parallel_extract_matches_sequential_graph(tmp_path: Path): + one = index_repo(FIXTURE, db_path=tmp_path / "one.sqlite3", incremental=False, workers=1) + two = index_repo(FIXTURE, db_path=tmp_path / "two.sqlite3", incremental=False, workers=2) + assert two.get_meta("index_workers") == "2" + assert {n["id"] for n in one.nodes()} == {n["id"] for n in two.nodes()} + assert {e["id"] for e in one.edges()} == {e["id"] for e in two.edges()} + one.close() + two.close() + + +def test_incremental_skip_emits_skipped_progress(tmp_path: Path): + db = tmp_path / "g.sqlite3" + first = index_repo(FIXTURE, db_path=db, incremental=False, workers=1) + first.close() + events: list[dict] = [] + store = index_repo(FIXTURE, db_path=db, incremental=True, workers=1, progress=events.append) + assert store.get_meta("reindex_skipped") == "1" + assert store.get_meta("files_extracted") == "0" + assert events[-1]["phase"] == "skipped" + store.close() + + +def test_extract_error_becomes_residual(tmp_path: Path, monkeypatch): + from loadpath import index as index_mod + + def boom(rel, source, config): + raise RuntimeError("extractor exploded") + + monkeypatch.setattr(index_mod, "extract_django_file", boom) + root = tmp_path / "repo" + root.mkdir() + (root / "backend").mkdir() + (root / "backend" / "views.py").write_text("def hello():\n return 1\n") + store = index_repo(root, db_path=tmp_path / "g.sqlite3", incremental=False, workers=1) + residuals = store.get_meta("residuals") or "" + assert "Failed to extract" in residuals + assert "extractor exploded" in residuals + store.close() + + +def test_api_index_progress_idle_then_done(tmp_path, monkeypatch): + monkeypatch.setenv("HOME", str(tmp_path / "home")) + (tmp_path / "home").mkdir() + repo = prepare_review_repo(tmp_path) + client = TestClient(create_app()) + idle = client.get("/api/index/progress", params={"repo_path": str(repo)}) + assert idle.status_code == 200 + assert idle.json()["phase"] == "idle" + indexed = client.post("/api/index", json={"repo_path": str(repo), "incremental": False, "jobs": 1}) + assert indexed.status_code == 200, indexed.text + done = client.get("/api/index/progress", params={"repo_path": str(repo)}) + assert done.status_code == 200 + body = done.json() + assert body["phase"] == "done" + assert body["message"] + assert "elapsed_ms" in body + + +def test_default_workers_stays_sequential_on_small_trees(monkeypatch): + from loadpath.index import default_workers + + monkeypatch.delenv("LOADPATH_INDEX_JOBS", raising=False) + assert default_workers(42) == 1 + monkeypatch.setenv("LOADPATH_INDEX_JOBS", "3") + assert default_workers(42) == 3 diff --git a/tests/unit/test_overlays.py b/tests/unit/test_overlays.py new file mode 100644 index 0000000..7638d57 --- /dev/null +++ b/tests/unit/test_overlays.py @@ -0,0 +1,130 @@ +from __future__ import annotations + +from loadpath.config import load_config +from loadpath.extractors.django import extract_django_file +from loadpath.extractors.react import extract_react_file +from loadpath.extractors.templates import extract_template_file +from loadpath.index import index_repo +from loadpath.types import EdgeType, NodeType + +from tests.conftest import FIXTURE_ROOT as FIXTURE + + +def _cfg(): + return load_config(FIXTURE) + + +def test_extracts_strawberry_and_graphene(): + source = (FIXTURE / "backend/billing/schema.py").read_text() + g = extract_django_file("backend/billing/schema.py", source, _cfg()) + types = {n.name: n for n in g.nodes if n.type is NodeType.GRAPHQL_TYPE} + assert "InvoiceType" in types + assert "Query" in types + assert "CreditInvoice" in types + ops = {n.name for n in g.nodes if n.type is NodeType.GRAPHQL_OPERATION} + assert "invoice" in ops + assert "credit_invoice" in ops + assert any(n.type is NodeType.GRAPHQL_FIELD and n.name == "total" for n in g.nodes) + assert any(e.type is EdgeType.PUBLISHES_GRAPHQL for e in g.edges) + assert any(e.type is EdgeType.QUERIES_MODEL and "Invoice" in e.dst for e in g.edges) + + +def test_extracts_channels_consumer_and_websocket_route(): + consumer = extract_django_file( + "backend/billing/consumers.py", + (FIXTURE / "backend/billing/consumers.py").read_text(), + _cfg(), + ) + assert any(n.type is NodeType.CONSUMER and n.name == "InvoiceConsumer" for n in consumer.nodes) + routing = extract_django_file( + "backend/billing/routing.py", + (FIXTURE / "backend/billing/routing.py").read_text(), + _cfg(), + ) + ws = [n for n in routing.nodes if n.type is NodeType.WEBSOCKET_ROUTE] + assert ws + assert any("ws/invoices" in n.name for n in ws) + assert any(e.type is EdgeType.PUBLISHES_ROUTE and "InvoiceConsumer" in e.dst for e in routing.edges) + + +def test_extracts_fastapi_next_to_django_not_ninja(): + gateway = extract_django_file( + "backend/billing/gateway.py", + (FIXTURE / "backend/billing/gateway.py").read_text(), + _cfg(), + ) + assert any(n.type is NodeType.FASTAPI_ROUTE and "/internal/invoices" in n.name for n in gateway.nodes) + assert any(n.type is NodeType.PYDANTIC_MODEL and n.name == "InvoiceOut" for n in gateway.nodes) + assert any(e.type is EdgeType.USES_SERIALIZER for e in gateway.edges) + ninja = extract_django_file( + "backend/billing/api.py", + (FIXTURE / "backend/billing/api.py").read_text(), + _cfg(), + ) + assert not any(n.type is NodeType.FASTAPI_ROUTE for n in ninja.nodes) + assert any(n.type is NodeType.ROUTE and n.extra.get("ninja") for n in ninja.nodes) + mixed = extract_django_file( + "backend/billing/api.py", + "from fastapi import Depends\nfrom ninja import Router\napi = Router()\n@api.get('/ledger')\ndef ledger():\n return {}\n", + _cfg(), + ) + assert not any(n.type is NodeType.FASTAPI_ROUTE for n in mixed.nodes) + assert any(n.extra.get("ninja") for n in mixed.nodes) + + +def test_extracts_templates_htmx_and_url_tags(): + rel = "backend/billing/templates/billing/invoice_board.html" + g = extract_template_file(rel, (FIXTURE / rel).read_text(), _cfg()) + templates = [n for n in g.nodes if n.type is NodeType.TEMPLATE] + assert templates[0].qualified_name == "billing/invoice_board.html" + assert any(n.type is NodeType.HTMX_CALL and "/api/invoices/totals/" in n.name for n in g.nodes) + assert any(e.type is EdgeType.HTMX_CALLS for e in g.edges) + assert any(e.extra.get("url_name") == "invoice-board" for e in g.edges) + + +def test_view_serves_template_and_cache_flag_on_commit(): + source = (FIXTURE / "backend/billing/views.py").read_text() + g = extract_django_file("backend/billing/views.py", source, _cfg()) + assert any(n.type is NodeType.TEMPLATE and n.qualified_name == "billing/invoice_board.html" for n in g.nodes) + assert any(e.type is EdgeType.SERVES_TEMPLATE for e in g.edges) + assert any(n.type is NodeType.CACHE_KEY and n.name == "invoice:list" for n in g.nodes) + assert any(n.type is NodeType.FEATURE_FLAG and n.name == "async_ledger" for n in g.nodes) + assert any(n.type is NodeType.SIDE_EFFECT for n in g.nodes) + assert any(e.type is EdgeType.ON_COMMIT for e in g.edges) + assert any(e.type is EdgeType.INVALIDATES_CACHE for e in g.edges) + assert any(e.type is EdgeType.CHECKS_FLAG for e in g.edges) + + +def test_react_gql_document_is_a_client_operation(): + source = (FIXTURE / "frontend/src/features/billing/InvoicePage.tsx").read_text() + g = extract_react_file("frontend/src/features/billing/InvoicePage.tsx", source, _cfg()) + ops = [n for n in g.nodes if n.type is NodeType.GRAPHQL_OPERATION] + assert any(n.name == "Invoice" and n.extra.get("client") for n in ops) + assert any("invoice" in (n.extra.get("selections") or []) for n in ops) + + +def test_index_stitches_graphql_htmx_and_fastapi(tmp_path): + store = index_repo(FIXTURE, db_path=tmp_path / "g.sqlite3", incremental=False) + types = store.type_counts() + assert types.get("graphql.operation", 0) >= 2 + assert types.get("fastapi.route", 0) >= 1 + assert types.get("django.template", 0) >= 1 + assert types.get("django.consumer", 0) >= 1 + gql_edges = [ + e + for e in store.edges() + if e["type"] == "consumed_by_client" and (e.get("extra") or {}).get("via") == "graphql" + ] + assert gql_edges, "client Invoice query should stitch to server invoice field" + htmx_edges = [ + e for e in store.edges() if e["type"] == "consumed_by_client" and (e.get("extra") or {}).get("htmx") + ] + assert htmx_edges, "HTMX totals call should stitch to the Django totals route" + fastapi = next(n for n in store.nodes() if n["type"] == "fastapi.route") + openapi = [ + e + for e in store.edges() + if e["src"] == fastapi["id"] and e["type"] == "publishes_route" and "openapi" in (e.get("extra") or {}).get("via", "") + ] + assert openapi, "FastAPI /internal/invoices/{id} should stitch to OpenAPI" + store.close() diff --git a/tests/unit/test_providers_and_api.py b/tests/unit/test_providers_and_api.py index 4900c8a..d641712 100644 --- a/tests/unit/test_providers_and_api.py +++ b/tests/unit/test_providers_and_api.py @@ -4,7 +4,7 @@ from fastapi.testclient import TestClient -from loadpath.providers.scm import BitbucketProvider, GitHubProvider +from loadpath.providers.scm import BitbucketProvider, GitHubProvider, GitLabProvider from loadpath.server.app import create_app from loadpath.settings import AppSettings @@ -102,9 +102,17 @@ def handler(request: httpx.Request) -> httpx.Response: def test_github_rejects_unsafe_repo_slug(): import pytest + from loadpath.providers.scm import parse_remote_url, require_repo_slug + gh = GitHubProvider("tok") with pytest.raises(ValueError): gh.list_pull_requests("../etc/passwd") + with pytest.raises(ValueError): + require_repo_slug("acme/../passwd") + with pytest.raises(ValueError): + require_repo_slug("acme/./demo") + assert require_repo_slug("acme/billing/demo") == "acme/billing/demo" + assert parse_remote_url("https://github.com/../etc/passwd") is None def test_parse_remote_url_github_and_bitbucket(): @@ -115,7 +123,11 @@ def test_parse_remote_url_github_and_bitbucket(): assert parse_remote_url("https://x-access-token:tok@github.com/acme/demo.git") == ("github", "acme/demo") assert parse_remote_url("https://bitbucket.org/acme/demo.git") == ("bitbucket", "acme/demo") assert parse_remote_url("git@bitbucket.org:acme/demo.git") == ("bitbucket", "acme/demo") - assert parse_remote_url("https://gitlab.com/acme/demo.git") is None + assert parse_remote_url("https://gitlab.com/acme/demo.git") == ("gitlab", "acme/demo") + assert parse_remote_url("git@gitlab.com:acme/demo.git") == ("gitlab", "acme/demo") + assert parse_remote_url("https://gitlab.example.com/acme/demo.git") == ("gitlab", "acme/demo") + assert parse_remote_url("https://gitlab.com/acme/billing/demo.git") == ("gitlab", "acme/billing/demo") + assert parse_remote_url("git@gitlab.com:acme/billing/demo.git") == ("gitlab", "acme/billing/demo") def test_github_lists_repositories_across_pages(): @@ -218,3 +230,93 @@ def handler(request: httpx.Request) -> httpx.Response: assert bb.current_user()["login"] == "bob" repos = bb.list_repositories() assert [r.slug for r in repos] == ["acme/demo", "acme/ledger"] + + +def test_github_enterprise_uses_api_v3(): + gh = GitHubProvider("tok", host="github.example.com") + assert gh.base == "https://github.example.com/api/v3" + cloud = GitHubProvider("tok", host="github.com") + assert cloud.base == "https://api.github.com" + + +def test_gitlab_lists_merge_requests(): + import httpx + + def handler(request: httpx.Request) -> httpx.Response: + if request.url.path.endswith("/user"): + return httpx.Response(200, json={"username": "ada", "name": "Ada", "web_url": "https://gitlab.com/ada"}) + return httpx.Response( + 200, + json=[ + { + "id": 90, + "iid": 4, + "title": "Invoice total", + "web_url": "https://gitlab.com/acme/demo/-/merge_requests/4", + "author": {"username": "ada"}, + "source_branch": "feat/total", + "target_branch": "main", + "state": "opened", + "updated_at": "2026-08-14T00:00:00Z", + "draft": False, + "sha": "abc", + "diff_refs": {"base_sha": "def", "head_sha": "abc"}, + } + ], + ) + + client = httpx.Client(transport=httpx.MockTransport(handler)) + gl = GitLabProvider("tok", client=client) + assert gl.current_user()["login"] == "ada" + mrs = gl.list_pull_requests("acme/demo") + assert mrs[0].number == 4 + assert mrs[0].provider == "gitlab" + assert mrs[0].source_branch == "feat/total" + + +def test_clone_url_and_fetch_spec(): + from loadpath.providers.pr_fetch import clone_url, fetch_spec, local_pr_ref + + gh = clone_url("github", "acme/demo", "tok") + assert "x-access-token:tok@" in gh + assert gh.endswith("github.com/acme/demo.git") + gl = clone_url("gitlab", "acme/demo", "glpat", "gitlab.example.com") + assert "oauth2:glpat@" in gl + assert "gitlab.example.com/acme/demo.git" in gl + assert fetch_spec("github", 12) == "pull/12/head" + assert fetch_spec("gitlab", 4) == "merge-requests/4/head" + assert fetch_spec("bitbucket", 3) == "pull-requests/3/from" + assert local_pr_ref(12) == "refs/loadpath/pr-12" + + +def test_redact_secrets_strips_tokens(): + from loadpath.providers.pr_fetch import redact_secrets + + leaked = "fatal: https://oauth2:glpat-secret@gitlab.com/acme/demo.git" + assert "glpat-secret" not in redact_secrets(leaked, "glpat-secret") + assert "***" in redact_secrets(leaked, "glpat-secret") + + +def test_checkout_review_tree_indexes_pr_head(tmp_path, monkeypatch): + from loadpath.providers import pr_fetch + + monkeypatch.setattr(pr_fetch, "clones_root", lambda: tmp_path / "clones") + repo = tmp_path / "repo" + repo.mkdir() + subprocess = __import__("subprocess") + subprocess.check_call(["git", "init", "-b", "main"], cwd=repo, stdout=subprocess.DEVNULL) + subprocess.check_call(["git", "config", "user.email", "lp@test"], cwd=repo) + subprocess.check_call(["git", "config", "user.name", "lp"], cwd=repo) + (repo / "on-main.txt").write_text("main\n") + subprocess.check_call(["git", "add", "on-main.txt"], cwd=repo) + subprocess.check_call(["git", "commit", "-m", "main"], cwd=repo, stdout=subprocess.DEVNULL) + subprocess.check_call(["git", "checkout", "-b", "pr"], cwd=repo, stdout=subprocess.DEVNULL) + (repo / "on-pr.txt").write_text("pr\n") + subprocess.check_call(["git", "add", "on-pr.txt"], cwd=repo) + subprocess.check_call(["git", "commit", "-m", "pr"], cwd=repo, stdout=subprocess.DEVNULL) + pr_sha = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip() + subprocess.check_call(["git", "update-ref", "refs/loadpath/pr-9", pr_sha], cwd=repo) + subprocess.check_call(["git", "checkout", "main"], cwd=repo, stdout=subprocess.DEVNULL) + tree = pr_fetch.checkout_review_tree(repo, "refs/loadpath/pr-9", 9) + assert (tree / "on-pr.txt").is_file() + assert not (repo / "on-pr.txt").exists() diff --git a/tests/unit/test_review_features.py b/tests/unit/test_review_features.py new file mode 100644 index 0000000..dd16283 --- /dev/null +++ b/tests/unit/test_review_features.py @@ -0,0 +1,218 @@ +from __future__ import annotations + +from loadpath.review.auth import auth_path +from loadpath.review.contract import classify_contract_break +from loadpath.review.diff import DiffSet, FileDiff, git_diff +from loadpath.review.engine import run_review +from loadpath.review.gate import FAIL_ON_CHOICES, gate_result, write_github_output +from loadpath.review.suggested_tests import suggested_tests +from loadpath.review.trend import confidence_trend +from loadpath.review.whatif import simulate_node +from loadpath.types import ContractBreakKind + +from tests.conftest import copy_fixture, git_init_with_main, prepare_review_repo + + +def test_contract_break_required_field_is_breaking(): + diff = DiffSet( + files=[ + FileDiff( + path="backend/billing/serializers.py", + status="M", + patch='+ extra_kwargs = {"total": {"required": True}}\n', + ) + ] + ) + nodes = [ + { + "type": "django.serializer", + "name": "InvoiceSerializer", + "file_path": "backend/billing/serializers.py", + } + ] + result = classify_contract_break(nodes, diff) + assert result["kind"] == ContractBreakKind.BREAKING.value + assert "total" in result["fields"] + + +def test_contract_break_ignores_removed_required_true(): + diff = DiffSet( + files=[ + FileDiff( + path="backend/billing/serializers.py", + status="M", + patch='- extra_kwargs = {"total": {"required": True}}\n', + ) + ] + ) + nodes = [ + { + "type": "django.serializer", + "name": "InvoiceSerializer", + "file_path": "backend/billing/serializers.py", + } + ] + result = classify_contract_break(nodes, diff) + assert result["kind"] == ContractBreakKind.ADDITIVE.value + assert "Required contract field" not in " ".join(result["reasons"]) + + +def test_contract_break_ignores_untouched_graphql(): + diff = DiffSet( + files=[ + FileDiff( + path="backend/billing/serializers.py", + status="M", + patch="+ extra_kwargs = {\"total\": {\"required\": True}}\n", + ) + ] + ) + nodes = [ + { + "type": "django.serializer", + "name": "InvoiceSerializer", + "file_path": "backend/billing/serializers.py", + }, + { + "type": "graphql.operation", + "name": "invoice", + "file_path": "backend/billing/schema.py", + }, + ] + result = classify_contract_break(nodes, diff) + assert result["kind"] == ContractBreakKind.BREAKING.value + assert not any("GraphQL" in r for r in result["reasons"]) + + +def test_auth_path_flags_missing_permissions(): + views = [ + { + "id": "django.view:billing.OpenView", + "type": "django.view", + "name": "OpenView", + "extra": {}, + }, + { + "id": "django.view:billing.InvoiceViewSet", + "type": "django.view", + "name": "InvoiceViewSet", + "extra": {"permissions": ["IsAuthenticated"], "get_queryset": True}, + }, + ] + routes = [ + { + "id": "django.websocket_route:billing:ws/invoices/", + "type": "django.websocket_route", + "name": "ws/invoices/", + "extra": {"websocket": True}, + } + ] + class _Store: + def nodes(self, types=None): + return [] + + result = auth_path(_Store(), views + routes, []) + names = {m["name"] for m in result["missing_permissions"]} + assert "OpenView" in names + assert "ws/invoices/" in names + assert result["object_scope"] + + +def test_suggested_tests_cover_new_sinks(): + sketches = suggested_tests( + [ + {"id": "fastapi.route:x", "name": "GET /internal/invoices/{id}", "type": "fastapi.route"}, + {"id": "graphql.operation:invoice", "name": "invoice", "type": "graphql.operation"}, + {"id": "django.consumer:c", "name": "InvoiceConsumer", "type": "django.consumer"}, + {"id": "django.template:t", "name": "invoice_board.html", "type": "django.template"}, + ], + [ + { + "id": "fastapi.route:x", + "name": "GET /internal/invoices/{id}", + "type": "fastapi.route", + "extra": {"route": "/internal/invoices/{id}", "method": "GET"}, + }, + {"id": "graphql.operation:invoice", "name": "invoice", "type": "graphql.operation", "extra": {"kind": "query"}}, + {"id": "django.consumer:c", "name": "InvoiceConsumer", "type": "django.consumer", "extra": {}}, + {"id": "django.template:t", "name": "invoice_board.html", "type": "django.template", "extra": {}}, + ], + ) + kinds = {s["kind"] for s in sketches} + assert {"pytest", "graphql", "channels", "django"} <= kinds + + +def test_gate_fail_on_blocker_and_breaking_contract(tmp_path): + assert "blocker" in FAIL_ON_CHOICES + review = { + "title": "Invoice.total", + "confidence": {"level": "medium", "sinks": 4}, + "findings": [{"severity": "blocker", "waived": False}], + "contract_break": {"kind": "breaking"}, + } + blocked = gate_result(review, "blocker") + assert blocked["passed"] is False + assert blocked["exit_code"] == 2 + never = gate_result(review, "never") + assert never["passed"] is True + medium = gate_result( + { + "title": "x", + "confidence": {"level": "medium", "sinks": 1}, + "findings": [], + "contract_break": {"kind": "breaking"}, + }, + "medium", + ) + assert medium["exit_code"] == 4 + out = tmp_path / "github_output" + write_github_output(str(out), blocked, review) + text = out.read_text() + assert "passed=false" in text + assert "contract_break=breaking" in text + + +def test_dirty_diff_includes_uncommitted_and_untracked(tmp_path): + repo = copy_fixture(tmp_path) + git_init_with_main(repo) + (repo / "backend/billing/serializers.py").write_text( + (repo / "backend/billing/serializers.py").read_text() + "\n# dirty\n" + ) + (repo / "backend/billing/scratch.py").write_text("VALUE = 1\n") + committed = git_diff(repo, "HEAD", "HEAD", dirty=False) + dirty = git_diff(repo, "HEAD", "HEAD", dirty=True) + assert "backend/billing/serializers.py" not in committed.paths or committed.paths == [] + assert "backend/billing/serializers.py" in dirty.paths + assert "backend/billing/scratch.py" in dirty.paths + review = run_review(repo, base="HEAD", head="HEAD", dirty=True) + assert review["workspace"]["dirty_included"] is True + assert review["workspace"]["dirty_count"] >= 1 + + +def test_whatif_walks_from_invoice_total(tmp_path): + repo = prepare_review_repo(tmp_path) + run_review(repo, base="HEAD~1", head="HEAD") + payload = simulate_node(repo, "django.field:billing.Invoice.total") + assert payload["ok"] is True + assert payload["what_if"] is True + names = {n["name"] for n in payload["nodes"]} + assert "InvoiceSerializer" in names or "total" in names + assert payload["sinks"] + assert payload["read_order"] + assert payload["auth"]["note"] + + +def test_confidence_trend_compares_same_range(tmp_path): + repo = prepare_review_repo(tmp_path) + first = run_review(repo, base="HEAD~1", head="HEAD") + second = run_review(repo, base="HEAD~1", head="HEAD", reindex=False) + trend = second["trend"] + assert trend["points"] + assert "Confidence" in trend["note"] or "First review" in trend["note"] + from loadpath.graph.store import GraphStore + from loadpath.index import default_db_path + + store = GraphStore(default_db_path(repo)) + note = confidence_trend(store, base=first["base"] if "base" in first else None, head=None) + store.close() + assert note["note"] diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 3a44393..ce613c4 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -6,7 +6,7 @@ import { ImpactGraph } from "./ImpactGraph"; import { RefCombobox } from "./RefCombobox"; import { RepoExplorer } from "./RepoExplorer"; import { THEMES, applyTheme, readTheme, type ThemeId } from "./themes"; -import type { ArchitectureReport, DeepeningCandidate, GitRefs, IndexedRepo, PullRequest, RemoteRepo, Review } from "./types"; +import type { ArchitectureReport, DeepeningCandidate, GitRefs, IndexedRepo, IndexProgress, PullRequest, RemoteRepo, Review } from "./types"; type Tab = "review" | "architecture" | "graph" | "prs" | "settings"; type GraphMode = "review" | "architecture"; @@ -19,6 +19,12 @@ const TABS: { id: Tab; label: string; testId: string; shortcut: string; icon: ty { id: "settings", label: "Settings", testId: "tab-settings", shortcut: "5", icon: IconSettings }, ]; +function progressPercent(p: IndexProgress): number | null { + const total = p.total || 0; + if (total <= 0) return null; + return Math.min(100, Math.round((100 * (p.done || 0)) / total)); +} + function openOAuthUrl(url: string, host: string, pathPrefix: string) { let parsed: URL; try { @@ -44,6 +50,7 @@ export function App() { const [graphMode, setGraphMode] = useState("review"); const [error, setError] = useState(""); const [busy, setBusy] = useState(""); + const [indexPct, setIndexPct] = useState(null); const [workspaceLoading, setWorkspaceLoading] = useState(false); const [copied, setCopied] = useState(""); const [settings, setSettings] = useState>({}); @@ -52,6 +59,8 @@ export function App() { const [scmRepo, setScmRepo] = useState(localStorage.getItem("loadpath.scmRepo") || ""); const [provider, setProvider] = useState(localStorage.getItem("loadpath.provider") || "github"); const [prNumber, setPrNumber] = useState(localStorage.getItem("loadpath.prNumber") || ""); + const [dirty, setDirty] = useState(localStorage.getItem("loadpath.dirty") === "1"); + const [tourIndex, setTourIndex] = useState(0); const [aiNote, setAiNote] = useState(""); const [theme, setTheme] = useState(readTheme); const [settingsReady, setSettingsReady] = useState(false); @@ -64,6 +73,7 @@ export function App() { interval: number; } | null>(null); const [bitbucketWaiting, setBitbucketWaiting] = useState(false); + const [gitlabWaiting, setGitlabWaiting] = useState(false); const repoRef = useRef(repo); repoRef.current = repo; const explorerOpenRef = useRef(false); @@ -80,6 +90,24 @@ export function App() { busyRef.current = msg; setBusy(msg); }; + const watchIndex = (repoPath: string) => { + const tick = () => { + api + .indexProgress(repoPath) + .then((p) => { + if (!busyRef.current) return; + if (p.phase && p.phase !== "idle" && p.message) markBusy(p.message); + setIndexPct(p.phase && p.phase !== "idle" ? progressPercent(p) : null); + }) + .catch(() => undefined); + }; + tick(); + const id = window.setInterval(tick, 250); + return () => { + window.clearInterval(id); + setIndexPct(null); + }; + }; useEffect(() => { api @@ -159,7 +187,11 @@ export function App() { }; const tokenSetFor = (name: string) => - name === "github" ? Boolean(settings.github_token_set) : Boolean(settings.bitbucket_token_set); + name === "github" + ? Boolean(settings.github_token_set) + : name === "gitlab" + ? Boolean(settings.gitlab_token_set) + : Boolean(settings.bitbucket_token_set); const loadRemoteRepos = useCallback(async (name = provider) => { try { @@ -168,7 +200,11 @@ export function App() { if (listed.user?.login) { setSettings((current) => ({ ...current, - ...(name === "github" ? { github_user: listed.user.login } : { bitbucket_user: listed.user.login }), + ...(name === "github" + ? { github_user: listed.user.login } + : name === "gitlab" + ? { gitlab_user: listed.user.login } + : { bitbucket_user: listed.user.login }), })); } } catch { @@ -260,6 +296,42 @@ export function App() { }; }, [bitbucketWaiting, loadRemoteRepos]); + useEffect(() => { + if (!gitlabWaiting) return; + let cancelled = false; + let timer = 0; + const started = Date.now(); + const tick = async () => { + try { + const status = await api.oauthStatus(); + if (cancelled) return; + if (status.gitlab.connected) { + setGitlabWaiting(false); + const next = await api.settings(); + setSettings(next); + setCopied(status.gitlab.user ? `Signed in to GitLab as ${status.gitlab.user}` : "Signed in to GitLab"); + void loadRemoteRepos("gitlab"); + return; + } + if (Date.now() - started > 180_000) { + setGitlabWaiting(false); + setError("GitLab sign-in timed out. Finish in the browser, or try again."); + return; + } + timer = window.setTimeout(tick, 1500); + } catch (e) { + if (cancelled) return; + setGitlabWaiting(false); + setError(e instanceof Error ? e.message : String(e)); + } + }; + timer = window.setTimeout(tick, 1500); + return () => { + cancelled = true; + window.clearTimeout(timer); + }; + }, [gitlabWaiting, loadRemoteRepos]); + const loadArchitecture = async (path = repo) => { if (!path.trim()) return null; const report = await api.architecture(path); @@ -302,9 +374,11 @@ export function App() { markBusy("Tracing load path…"); persistRepo(repo); persistRefs(base, head); + const stopWatch = watchIndex(repo); try { - const r = await api.review(repo, base, head, true); + const r = await api.review(repo, base, head, true, dirty); setReview(r); + setTourIndex(0); setGraphMode("review"); setTab("review"); await api.repos().then((x) => setRepos(x.repos)).catch(() => undefined); @@ -312,6 +386,7 @@ export function App() { } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { + stopWatch(); markBusy(""); } }; @@ -323,6 +398,7 @@ export function App() { setCopied(""); markBusy(incremental ? "Indexing…" : "Full reindex…"); persistRepo(repo); + const stopWatch = watchIndex(repo); try { await api.index(repo, incremental); const report = await loadArchitecture(repo); @@ -334,6 +410,7 @@ export function App() { } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { + stopWatch(); markBusy(""); } }; @@ -422,6 +499,68 @@ export function App() { } }; + const startGitlabLogin = async () => { + setError(""); + try { + const flow = await api.gitlabOAuthStart(); + setGitlabWaiting(true); + openOAuthUrl(flow.authorize_url, new URL(flow.authorize_url).hostname, "/oauth/authorize"); + } catch (e) { + setGitlabWaiting(false); + setError(e instanceof Error ? e.message : String(e)); + } + }; + + const runWhatIf = async (nodeId: string) => { + if (busyRef.current || !repo.trim()) return; + setError(""); + markBusy("Walking what-if path…"); + try { + const result = await api.whatIf(repo, nodeId); + setCopied(`${result.title} — ${result.confidence.level} · ${(result.sinks || []).length} sinks`); + setReview({ + ...result, + markdown: result.markdown || "", + index: result.index || review?.index, + workspace: result.workspace || review?.workspace, + }); + setTourIndex(0); + setGraphMode("review"); + setTab("review"); + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + markBusy(""); + } + }; + + const reviewRemotePr = async (item: PullRequest) => { + if (busyRef.current) return; + persistPr(item.provider, item.repo, String(item.number)); + const match = remoteRepos.find((r) => r.slug.toLowerCase() === item.repo.toLowerCase()); + if (match?.local_path) persistRepo(match.local_path); + setError(""); + markBusy(`Fetching ${item.provider} #${item.number}…`); + const watchPath = match?.local_path || repo; + const stopWatch = watchPath ? watchIndex(watchPath) : () => undefined; + try { + const r = await api.reviewPr(item.provider, item.repo, item.number, match?.local_path || repo || undefined); + setReview(r); + setTourIndex(0); + if (r.pull_request && typeof r.pull_request.repo_path === "string") persistRepo(r.pull_request.repo_path); + persistRefs(String(r.base || item.target_branch), String(r.head || item.source_branch)); + setGraphMode("review"); + setTab("review"); + } catch (e) { + persistRefs(item.base_sha || item.target_branch, item.head_sha || item.source_branch); + setTab("review"); + setError(e instanceof Error ? e.message : String(e)); + } finally { + stopWatch(); + markBusy(""); + } + }; + const disconnectProvider = async (name: string) => { setError(""); try { @@ -439,6 +578,11 @@ export function App() { const body = { github_token: String(fd.get("github_token") || ""), github_oauth_client_id: String(fd.get("github_oauth_client_id") || ""), + github_host: String(fd.get("github_host") || ""), + gitlab_token: String(fd.get("gitlab_token") || ""), + gitlab_host: String(fd.get("gitlab_host") || ""), + gitlab_oauth_client_id: String(fd.get("gitlab_oauth_client_id") || ""), + gitlab_oauth_client_secret: String(fd.get("gitlab_oauth_client_secret") || ""), bitbucket_token: String(fd.get("bitbucket_token") || ""), bitbucket_username: String(fd.get("bitbucket_username") || ""), bitbucket_oauth_client_id: String(fd.get("bitbucket_oauth_client_id") || ""), @@ -584,8 +728,14 @@ export function App() {
{busy ? ( -
- +
+ {busy}
) : null} @@ -664,6 +814,22 @@ export function App() { onNeedRefs={loadGitRefs} /> +
)} @@ -771,7 +946,7 @@ export function App() {
{architecture?.indexed ? ( - + ) : null}
@@ -816,7 +991,7 @@ export function App() {
{graphNodes.length ? ( - + ) : (

Index the repo or run a review first. Click a node to inspect it. @@ -836,6 +1011,7 @@ export function App() { onChange={(e) => persistPr(e.target.value, scmRepo, prNumber)} > + @@ -877,6 +1053,7 @@ export function App() {

{remoteRepos.length} {provider} repositor{remoteRepos.length === 1 ? "y" : "ies"} {provider === "github" && settings.github_user ? ` · @${String(settings.github_user)}` : ""} + {provider === "gitlab" && settings.gitlab_user ? ` · @${String(settings.gitlab_user)}` : ""} {provider === "bitbucket" && settings.bitbucket_user ? ` · ${String(settings.bitbucket_user)}` : ""}

) : null} @@ -909,15 +1086,9 @@ export function App() { type="button" className="btn primary" data-testid={`pr-review-${p.number}`} - onClick={() => { - persistRefs(p.base_sha || p.target_branch, p.head_sha || p.source_branch); - persistPr(p.provider, p.repo, String(p.number)); - const match = remoteRepos.find((item) => item.slug.toLowerCase() === p.repo.toLowerCase()); - if (match?.local_path) persistRepo(match.local_path); - setTab("review"); - }} + onClick={() => void reviewRemotePr(p)} > - Review this range + Review this PR @@ -1012,6 +1183,65 @@ export function App() { /> + + +
+
+ GitLab +

+ {settings.gitlab_token_set + ? settings.gitlab_user + ? `Signed in as @${String(settings.gitlab_user)}` + : "Token saved on this machine" + : "Not connected"} +

+
+
+ {settings.gitlab_token_set ? ( + + ) : ( + + )} +
+
+ + + + + + + +
Bitbucket @@ -1141,6 +1371,8 @@ function ReviewBrief({ findings, aiNote, busy, + tourIndex, + onTour, onAskAi, onCopy, onPost, @@ -1149,6 +1381,8 @@ function ReviewBrief({ findings: Review["findings"]; aiNote: string; busy: boolean; + tourIndex: number; + onTour: (index: number) => void; onAskAi: () => void; onCopy: () => void; onPost: () => void; @@ -1173,6 +1407,11 @@ function ReviewBrief({ {kindLabel(k)} ))} + {review.contract_break?.kind && review.contract_break.kind !== "none" ? ( + + contract {review.contract_break.kind} + + ) : null}
@@ -1215,13 +1454,38 @@ function ReviewBrief({ Read this {review.read_order.length} {review.read_order.map((f, i) => ( -
+
{i + 1}. {f.path}
{f.why}
))} + {review.read_order.length > 0 ? ( +
+ + + + {tourIndex + 1}/{review.read_order.length} + +
+ ) : null}
@@ -1249,6 +1513,55 @@ function ReviewBrief({ )}
+ {review.contract_break?.reasons?.length ? ( +
+ + Contract {review.contract_break.kind} + + {review.contract_break.reasons.map((reason) => ( +
+ {reason} +
+ ))} +
+ ) : null} + {review.auth?.note ? ( +
+ Auth +
{review.auth.note}
+ {(review.auth.missing_permissions || []).map((item) => ( +
+ missing + {item.name} +
+ ))} +
+ ) : null} + {(review.suggested_tests || []).length ? ( +
+ + Suggested tests {review.suggested_tests?.length} + + {(review.suggested_tests || []).map((sketch) => ( +
+ {sketch.title} +
{sketch.body}
+
+ ))} +
+ ) : null} + {review.trend?.note ? ( +
+ Confidence trend +
{review.trend.note}
+ {(review.trend.points || []).slice(0, 6).map((point) => ( +
+ {point.level} · {formatWhen(point.created_at)} + {point.sinks != null ? ` · ${point.sinks} sinks` : ""} +
+ ))} +
+ ) : null}
Residual {review.residuals.length} diff --git a/ui/src/ImpactGraph.tsx b/ui/src/ImpactGraph.tsx index c94cd46..4e07811 100644 --- a/ui/src/ImpactGraph.tsx +++ b/ui/src/ImpactGraph.tsx @@ -134,11 +134,13 @@ function GraphInspector({ nodes, edges, onClose, + onWhatIf, }: { node: GraphNode; nodes: GraphNode[]; edges: GraphEdge[]; onClose: () => void; + onWhatIf?: (id: string) => void; }) { const info = inspectNode(node, nodes, edges); useEffect(() => { @@ -201,6 +203,11 @@ function GraphInspector({ extra={info.extraOutputs} empty="This node does not point at anything in this graph." /> + {onWhatIf ? ( + + ) : null} ); } @@ -247,7 +254,17 @@ function InspectorLinks({ ); } -export function ImpactGraph({ nodes, edges }: { nodes: GraphNode[]; edges: GraphEdge[] }) { +export function ImpactGraph({ + nodes, + edges, + onWhatIf, + focusPath, +}: { + nodes: GraphNode[]; + edges: GraphEdge[]; + onWhatIf?: (id: string) => void; + focusPath?: string; +}) { const [selectedId, setSelectedId] = useState(null); const [projection, setProjection] = useState(null); const [detail, setDetail] = useState(null); @@ -287,6 +304,12 @@ export function ImpactGraph({ nodes, edges }: { nodes: GraphNode[]; edges: Graph if (selectedId && !byId.has(selectedId)) setSelectedId(null); }, [byId, selectedId]); + useEffect(() => { + if (!focusPath) return; + const match = nodes.find((n) => n.file_path === focusPath); + if (match) setSelectedId(match.id); + }, [focusPath, nodes]); + const onNodeClick: NodeMouseHandler = (_evt, node) => { setSelectedId(node.id); }; @@ -412,7 +435,7 @@ export function ImpactGraph({ nodes, edges }: { nodes: GraphNode[]; edges: Graph /> {selected ? ( - + ) : null}
) : ( @@ -450,7 +473,7 @@ export function ImpactGraph({ nodes, edges }: { nodes: GraphNode[]; edges: Graph {selected ? ( - + ) : null} )} diff --git a/ui/src/api.ts b/ui/src/api.ts index 3ca16fc..eb7fd88 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -1,4 +1,4 @@ -import type { ArchitectureReport, FsListing, GitRefs, IndexedRepo, PullRequest, RemoteRepo, Review } from "./types"; +import type { ArchitectureReport, FsListing, GitRefs, GraphNode, IndexedRepo, IndexProgress, PullRequest, RemoteRepo, Review } from "./types"; export function formatApiError(text: string, fallback = "Request failed"): string { const trimmed = (text || "").trim(); @@ -39,6 +39,8 @@ async function req(path: string, init?: RequestInit): Promise { const BLANK_SETTINGS_KEYS = [ "github_token", + "gitlab_token", + "gitlab_oauth_client_secret", "bitbucket_token", "bitbucket_oauth_client_secret", "ai_api_key", @@ -68,12 +70,35 @@ export const api = { }), indexStatus: (repo_path: string) => req(`/api/index?repo_path=${encodeURIComponent(repo_path)}`), + indexProgress: (repo_path: string) => + req(`/api/index/progress?repo_path=${encodeURIComponent(repo_path)}`), architecture: (repo_path: string) => req(`/api/architecture?repo_path=${encodeURIComponent(repo_path)}`), - review: (repo_path: string, base: string, head?: string, reindex = true) => + review: (repo_path: string, base: string, head?: string, reindex = true, dirty = false) => req("/api/review", { method: "POST", - body: JSON.stringify({ repo_path, base, head: head || null, reindex, incremental: true, three_dot: true }), + body: JSON.stringify({ + repo_path, + base, + head: head || null, + reindex, + incremental: true, + three_dot: true, + dirty, + }), + }), + whatIf: (repo_path: string, node_id: string) => + req< + Review & { + ok: boolean; + node: GraphNode; + what_if?: boolean; + } + >("/api/whatif", { method: "POST", body: JSON.stringify({ repo_path, node_id }) }), + reviewPr: (provider: string, repo: string, number: number, repo_path?: string) => + req }>("/api/prs/review", { + method: "POST", + body: JSON.stringify({ provider, repo, number, repo_path: repo_path || null }), }), init: (repo_path: string, overwrite = false) => req<{ wrote: boolean; message: string; django_root: string; react_root: string; has_config: boolean }>( @@ -100,7 +125,8 @@ export const api = { ), oauthStatus: () => req<{ - github: { connected: boolean; user: string; token_set: boolean; oauth_ready: boolean }; + github: { connected: boolean; user: string; token_set: boolean; oauth_ready: boolean; host?: string }; + gitlab: { connected: boolean; user: string; token_set: boolean; oauth_ready: boolean; host?: string }; bitbucket: { connected: boolean; user: string; token_set: boolean; oauth_ready: boolean }; }>("/api/oauth/status"), githubOAuthStart: () => @@ -119,6 +145,8 @@ export const api = { }), bitbucketOAuthStart: () => req<{ flow_id: string; authorize_url: string }>("/api/oauth/bitbucket/start"), + gitlabOAuthStart: () => + req<{ flow_id: string; authorize_url: string }>("/api/oauth/gitlab/start"), oauthDisconnect: (provider: string) => req>("/api/oauth/disconnect", { method: "POST", diff --git a/ui/src/graphView.test.ts b/ui/src/graphView.test.ts index cf219e1..613cac2 100644 --- a/ui/src/graphView.test.ts +++ b/ui/src/graphView.test.ts @@ -5,6 +5,7 @@ import { LAYER_LABELS, defaultDetail, defaultProjection, + familyFor, layoutNodes3d, neighborIds, visibleGraph, @@ -89,6 +90,12 @@ describe("defaults", () => { expect(defaultDetail(LARGE_GRAPH)).toBe("overview"); }); + it("places GraphQL and FastAPI with the stitch family", () => { + expect(familyFor("graphql.operation")).toBe("stitch"); + expect(familyFor("fastapi.route")).toBe("stitch"); + expect(familyFor("django.template")).toBe("django"); + }); + it("names every architecture layer used in 2d layout", () => { for (const layer of new Set(Object.values(LAYER_ORDER))) { expect(LAYER_LABELS[layer]).toBeTruthy(); diff --git a/ui/src/graphView.ts b/ui/src/graphView.ts index 59dfc31..d638fb2 100644 --- a/ui/src/graphView.ts +++ b/ui/src/graphView.ts @@ -13,6 +13,7 @@ export const OVERVIEW_HIDDEN_TYPES = new Set([ "django.relation", "django.test", "react.test", + "graphql.field", "django.url_name", "django.throttle", ]); @@ -21,6 +22,7 @@ export const TYPE_COLOR: Record = { "arch.context": "#edf2f4", "django.app": "#8d99ae", "django.route": "#4cc9f0", + "django.url_name": "#4cc9f0", "django.view": "#4895ef", "django.viewset_action": "#4361ee", "django.permission": "#7b8cde", @@ -36,6 +38,17 @@ export const TYPE_COLOR: Record = { "django.test": "#6c757d", "django.admin": "#adb5bd", "django.migration_op": "#9d4edd", + "django.consumer": "#e76f51", + "django.websocket_route": "#4cc9f0", + "django.template": "#c77dff", + "django.htmx": "#ff6b6b", + "django.cache_key": "#6c757d", + "django.feature_flag": "#f4a261", + "django.side_effect": "#e85d04", + "graphql.type": "#00bbf9", + "graphql.operation": "#00bbf9", + "fastapi.route": "#4cc9f0", + "fastapi.model": "#f4a261", "openapi.path": "#00bbf9", "react.api_client": "#ff6b6b", "react.query_key": "#adb5bd", @@ -72,7 +85,7 @@ export const LAYER_LABELS: Record = { export function familyFor(type: string): GraphFamily { if (type.startsWith("react.")) return "react"; - if (type.startsWith("openapi.")) return "stitch"; + if (type.startsWith("openapi.") || type.startsWith("graphql.") || type.startsWith("fastapi.")) return "stitch"; if (type.startsWith("arch.")) return "arch"; return "django"; } diff --git a/ui/src/nodeInspector.test.ts b/ui/src/nodeInspector.test.ts index d58c046..43cab84 100644 --- a/ui/src/nodeInspector.test.ts +++ b/ui/src/nodeInspector.test.ts @@ -13,6 +13,9 @@ describe("typePurpose", () => { it("explains known types and falls back by family", () => { expect(typePurpose("django.field")).toMatch(/column/i); expect(typePurpose("react.form_schema")).toMatch(/typed/i); + expect(typePurpose("graphql.operation")).toMatch(/GraphQL/i); + expect(typePurpose("fastapi.route")).toMatch(/FastAPI/i); + expect(typePurpose("django.template")).toMatch(/template/i); expect(typePurpose("django.mystery")).toMatch(/Django/); }); }); diff --git a/ui/src/nodeInspector.ts b/ui/src/nodeInspector.ts index 6d24af7..d296152 100644 --- a/ui/src/nodeInspector.ts +++ b/ui/src/nodeInspector.ts @@ -16,6 +16,14 @@ export const SINK_TYPES = new Set([ "django.admin", "django.management_command", "openapi.path", + "django.consumer", + "django.websocket_route", + "django.template", + "django.cache_key", + "django.feature_flag", + "django.side_effect", + "graphql.operation", + "fastapi.route", ]); export const CONTRACT_TYPES = new Set([ @@ -25,6 +33,11 @@ export const CONTRACT_TYPES = new Set([ "openapi.path", "react.form_schema", "django.route", + "graphql.type", + "graphql.field", + "graphql.operation", + "fastapi.model", + "fastapi.route", ]); const TYPE_PURPOSE: Record = { @@ -50,6 +63,18 @@ const TYPE_PURPOSE: Record = { "django.admin": "Django admin class for a model.", "django.migration_op": "Schema migration operation (CreateModel, AlterField, …).", "django.management_command": "manage.py command — an operational sink.", + "django.consumer": "Django Channels WebSocket/HTTP consumer. A sink once a client connects.", + "django.websocket_route": "ASGI WebSocket URL. A sink: this is where a change becomes a live connection.", + "django.template": "Django template. HTML (and HTMX) the server renders.", + "django.htmx": "HTMX call from a template to a URL — another published seam.", + "django.cache_key": "Cache get/set key. Invalidation is part of the load path.", + "django.feature_flag": "Feature flag checked on this path. The change may be dark-launched.", + "django.side_effect": "transaction.on_commit (or similar) side effect that runs after the request commits.", + "graphql.type": "GraphQL object/input type — a published contract.", + "graphql.field": "One field on a GraphQL type.", + "graphql.operation": "GraphQL query, mutation, or subscription. A published contract and a sink.", + "fastapi.route": "FastAPI path operation sitting next to Django in this repo.", + "fastapi.model": "Pydantic response/request model — the FastAPI contract.", "openapi.path": "Generated OpenAPI operation. The typed HTTP contract between stacks.", "react.api_client": "Frontend fetch or generated client call to an API path.", "react.query_key": "React Query cache key. Invalidation and reads share this name.", diff --git a/ui/src/styles.css b/ui/src/styles.css index 4a586f0..f6f969b 100644 --- a/ui/src/styles.css +++ b/ui/src/styles.css @@ -874,6 +874,12 @@ kbd { background: var(--accent); animation: indeterminate 1.1s ease-in-out infinite; } +.progress.determinate i { + width: 0; + animation: none; + transform: none; + transition: width 0.2s ease-out; +} @keyframes indeterminate { 0% { transform: translateX(-120%); } 100% { transform: translateX(400%); } @@ -1333,7 +1339,12 @@ h2 { font-size: 13px; margin: 0; font-weight: 600; } border-bottom: 1px solid color-mix(in srgb, var(--line) 70%, transparent); } .read-item:last-child, .finding:last-child { border-bottom: 0; } -.read-item .why { color: var(--muted); font-size: 12px; margin-top: 2px; } +.read-item.tour-current { + background: color-mix(in srgb, var(--accent) 12%, var(--surface)); + border-color: var(--accent); +} +.tour-row { margin-top: 8px; align-items: center; } +.field.dirty .chip-btn { height: 32px; } .muted { color: var(--muted); font-size: 13px; line-height: 1.45; } .error { color: var(--low); @@ -1703,6 +1714,7 @@ h2 { font-size: 13px; margin: 0; font-weight: 600; } } @media (prefers-reduced-motion: reduce) { .progress i { animation: none; width: 100%; } + .progress.determinate i { width: var(--progress, 100%); } * { scroll-behavior: auto !important; } } @media (max-width: 960px) { diff --git a/ui/src/types.ts b/ui/src/types.ts index ff770a7..843f59e 100644 --- a/ui/src/types.ts +++ b/ui/src/types.ts @@ -48,6 +48,8 @@ export type Review = { id: string; title: string; headline: string; + base?: string; + head?: string; change_kinds: string[]; confidence: { level: "high" | "medium" | "low"; @@ -70,6 +72,15 @@ export type Review = { architecture_note: string; depth_note?: string; deepening?: DeepeningCandidate[]; + contract_break?: { kind: string; reasons: string[]; fields: string[] }; + auth?: { + note: string; + sinks: { id: string; name: string; permissions: string[] }[]; + missing_permissions: { id: string; name: string }[]; + }; + suggested_tests?: { sink: string; type: string; kind: string; title: string; body: string }[]; + trend?: { note: string; points: { id: string; created_at: string; level: string; sinks?: number }[] }; + what_if?: boolean; evolution?: { hotspots: { path: string; commits: number; bus_factor: number; complexity?: number }[]; change_coupling: { a: string; b: string; together: number; cross_context?: boolean }[]; @@ -96,6 +107,7 @@ export type Review = { dirty_count: number; dirty_overlaps_review: boolean; dirty_overlap: string[]; + dirty_included?: boolean; merge_base?: string | null; three_dot?: boolean; base_sha?: string | null; @@ -103,6 +115,19 @@ export type Review = { }; }; +export type IndexProgress = { + phase: string; + done?: number; + total?: number; + current?: string; + workers?: number; + skipped?: number; + elapsed_ms?: number; + errors?: number; + message?: string; + repo_path?: string; +}; + export type ArchitectureReport = { ok: boolean; indexed: boolean; @@ -223,6 +248,8 @@ export const LAYER_ORDER: Record = { "arch.context": 0, "django.app": 0, "django.route": 1, + "django.websocket_route": 1, + "fastapi.route": 1, "django.url_name": 2, "django.view": 3, "django.viewset_action": 3, @@ -230,8 +257,11 @@ export const LAYER_ORDER: Record = { "django.throttle": 3, "django.serializer": 4, "django.form": 4, + "graphql.type": 4, + "fastapi.model": 4, "django.serializer_field": 5, "django.service": 5, + "graphql.field": 5, "django.model": 6, "django.field": 7, "django.relation": 7, @@ -242,17 +272,24 @@ export const LAYER_ORDER: Record = { "django.migration_op": 8, "django.admin": 8, "django.management_command": 8, + "django.consumer": 8, + "django.cache_key": 8, + "django.feature_flag": 8, + "django.side_effect": 8, "openapi.path": 9, + "graphql.operation": 9, "react.api_client": 10, + "django.htmx": 10, "react.query_key": 11, "react.hook": 11, "react.feature": 11, "react.route": 12, "react.page": 12, + "django.template": 12, "react.component": 13, + "react.context": 13, "react.form_schema": 14, "react.test": 14, - "react.context": 13, }; export function layerFor(type: string): number {