diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..05b5b42 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,141 @@ +# AGENTS.md — briefing for agents working on `feat/external-bridge` + +Minimal operational notes for picking up the `@graphistry/client-api-context` prototype. The "why" and architecture are in `graphistry/ai_code_notes/architecture/external_bridge.md` and `client_context.md` — read those first if you're doing design work. This file is for getting productive quickly. + +## What this prototype is + +A React provider + hooks surface over the Graphistry iframe's falcor model. Lets host apps mount ``, hide the built-in chrome, and build custom inspector / filter bar / etc. with `useSelection()`, `useFilters()`, `useGraphistry()` — no rxjs, no falcor knowledge required. + +Spans two repos, both on branch `feat/external-bridge`: +- `graphistry/` — iframe side (`apps/core/viz/src/client/falcor/LocalDataSink.js` — Protocol v2) +- `graphistry-js/` — host side (this repo) + +Packages involved in this repo (`projects/`): +- `client-api/` — existing package; we added `rpc.js`, `externalStore.js`, `subscriptions.js`, `snapshots.js`, `errors.js`. Framework-agnostic. +- `client-api-context/` — **new**. React-only. Provider, Scene, hooks. +- `client-api-context-example/` — **new**. Vite + React 19 + Tailwind 4 smoke test. + +## Getting started + +**Prereq:** Alex's dev Graphistry on `:8491` must be up and running `feat/external-bridge` branch (has Protocol v2). If it's down, the iframe won't load and you'll see no data. Alex owns that stack — ask, don't try to bring it up yourself. + +```bash +cd projects/client-api-context-example +npm install --no-workspaces --legacy-peer-deps # only if deps weren't restored +npm run dev # Vite on 127.0.0.1:5174 +``` + +Vite is reached on Alex's Mac via the Tailscale proxy at `https://exrhizome.tailc68bf0.ts.net:8493/`. Port mapping and proxy are his setup — don't worry about it. + +### How packages link + +**No npm workspace linking, no build step.** `vite.config.ts` uses `resolve.alias`: + +```ts +'@graphistry/client-api-context': resolve(__dirname, '../client-api-context/src/index.ts'), +'@graphistry/client-api': resolve(__dirname, '../client-api/src/index.js'), +``` + +Editing any file under either package's `src/` hot-reloads live in the running example. The example's `package.json` lists them as `file:…` deps just to make `npm install` happy; the alias is what actually resolves at runtime. + +## Gotchas — things that burned time + +1. **React 19 ref callback identity.** If a ref callback's `useCallback` depends on the whole context object, every Provider value update regenerates the callback, React treats it as needing re-attach, old(null)→new(el) cycles, and `setRpc(null)` inside the null branch re-triggers the loop → `Maximum update depth exceeded`. Always destructure *stable* pieces (`registerIframe`, `sceneSrc`) out of ctx and depend on just those. See `context.tsx` `GraphistryScene` for the working pattern. + +2. **Fast Refresh bails on mixed exports.** A file exporting both a component (`GraphistryProvider`) and a hook or non-component (`useGraphistry`) forces full reload on every edit. That's why this package is split across `context.tsx` (components only), `hooks.ts` (hooks only), `internal.ts` (Ctx + types), `errors.ts` (error classes). + +3. **`vite-plugin-console-forward` peer dep ≤ Vite 6** — plugin predates Vite 7/8. Install with `--legacy-peer-deps`; runtime is fine because it only uses the stable HMR `send`/`on` API. It forwards browser `console.*` and unhandled errors to the Vite dev-server stdout prefixed `[browser:…]`, which is how an agent editing on the server sees runtime behavior without devtools on the Mac. + +4. **HMR WS over Tailscale** needs `hmr: { clientPort: 8493, protocol: 'wss' }` in `vite.config.ts`. If the WS can't upgrade (proxy dropping `Connection: Upgrade` headers), console-forward also stops working — they ride the same socket. Symptom: `send was called before connect` spam from `@vite/client`. Check devtools Network → WS for a `101 Switching Protocols` on the vite endpoint. + +5. **Chrome-disable URL params** (passed through `GraphistryProvider params={...}`): + - `menu=false` — hides toolbar (`view.js:104` collapses toolbarHeight to 0) + - `info=false` — hides session info bar + - `splashAfter=false` — kills splash (`server/splash.js:13`) + - `type=arrow` — required by this dataset format + - Note: `play={number}` is *layout duration*, NOT a splash toggle. Easy to confuse. + +6. **`hub.graphistry.com` runs old viz code.** It does not support Protocol v2 (no RPC envelope, no generic `pathSets` subscribe, no `graphistry-sub-error`). For end-to-end testing you MUST point at Alex's dev Graphistry. Default HOST in the example is `${location.hostname}:8491` exactly for this reason. + +7. **Protocol v2 generic subscribe uses `this.model`**, not the whitelisted router. The raw falcor Model is passed to `LocalDataSink` via live.js so we can hook its `_source.emitter.on('falcor-update', …)` for push. Consequence: a malicious client could subscribe to any pathSet in the full schema, not just `withClientAPIRoutes`. Acceptable for trusted-embed, TODO for untrusted — wrap with a whitelist check. + +## Protocol v2 quick reference + +Wire messages (all have `agent: 'graphistryjs'`): + +| Message | Who sends | Shape | +|---|---|---| +| `ready` | host → iframe | `{subscriptionAPIVersion: 2}` | +| `init` | iframe → host | `{cache, subscriptionAPIVersion}` | +| `graphistry-init-ack` | host → iframe | `{subscriptionAPIVersion: 2}` | +| `graphistry-subscribe` | host → iframe | `{path, pathSets?, options?}` | +| `graphistry-unsubscribe` | host → iframe | `{path}` | +| `graphistry-sub-update` | iframe → host | `{path, data}` — iframe walks static prefix of first pathSet before posting | +| `graphistry-sub-error` | iframe → host | `{path, error: {kind, path, flag?, message}}` | +| `graphistry-rpc-request` | host → iframe | `{id, op: 'call'\|'get'\|'set', path?/paths?/json?, args?}` | +| `graphistry-rpc-response` | iframe → host | `{id, result?, error?: {kind, op, message}}` | + +### RPC is generic — no op whitelist + +`op` is only `call`/`get`/`set`; the iframe proxies to `getDataSource()` (the `withClientAPIRoutes`-filtered router). The whitelist is `ClientAPIRoutes.js`. Client-side convenience (`g.addFilter(expr)`) lives in `client-api-context`; the iframe stays dumb. + +### Subscribe is two-mode + +- **v1 hand-enriched** (`.labels`, `.selection.labels`): no `pathSets`, iframe uses a custom fragment + viz-side React container that calls `publishedPathUpdatedSubject.next({path, data})`. Kept for back-compat. +- **v2 generic** (everything else): client sends `pathSets`, iframe opens `model.get(...).subscribe(...)` and relays on any server `falcor-update`. No viz-side container needed. Fragments canonicalized in `client-api/src/snapshots.js` (e.g. `FRAGMENT_FILTERS`). + +## Key files + +### graphistry-js + +- `projects/client-api/src/rpc.js` — `createRpcClient` + `GraphistryRpcError` +- `projects/client-api/src/externalStore.js` — shallow-eq + refcount store +- `projects/client-api/src/subscriptions.js` — `SubscriptionManager`, postMessage dispatch +- `projects/client-api/src/snapshots.js` — projections + `FRAGMENT_FILTERS` +- `projects/client-api-context/src/context.tsx` — `GraphistryProvider`, `GraphistryScene` +- `projects/client-api-context/src/hooks.ts` — all hooks +- `projects/client-api-context/src/internal.ts` — `Ctx` + shared types +- `projects/client-api-context/src/client-api.d.ts` — ambient types for the untyped JS package +- `projects/client-api-context-example/src/App.tsx` — the demo +- `projects/client-api-context-example/vite.config.ts` — Tailwind + console-forward + HMR config + +### graphistry + +- `apps/core/viz/src/client/falcor/LocalDataSink.js` — Protocol v2 handlers +- `apps/core/viz/src/client/live.js` — where LocalDataSink gets constructed with the raw model +- `apps/core/viz/src/client/falcor/ClientAPIRoutes.js` — the RPC whitelist (Falcor QL) +- `ai_code_notes/architecture/external_bridge.md` — design overview +- `ai_code_notes/architecture/client_context.md` — MVP plan + Protocol v2 wire shapes +- `ai_code_notes/architecture/fragments.md` — falcor path inventory (pre-existing, curated) + +## Current state + +- Branches `feat/external-bridge` on both repos, not pushed. +- Example renders with Tailwind dashboard theme, floating panels over a full-bleed viz. +- Dev graphistry was being updated as of session handoff — end-to-end connection not yet verified. +- RPC envelope + generic subscribe implemented and type-check. Runtime untested against the matching graphistry build. + +## Next goals + +1. **Debug connection between example and dev Graphistry.** Watch `[browser:…]` output from vite-plugin-console-forward. Expected behaviors when handshake succeeds: init postMessage arrives, `useGraphistry().ready` goes true, `useGraphistry().subscriptionAPIVersion` shows `2`. When `useSelection()` is read, a `graphistry-subscribe` for `.selection.labels` fires; expect either a sub-update (data) or a sub-error (likely permission, if `flag_unsafe_jsapi_export_row` is off on Alex's dev box). For `useFilters()`, expect a sub-update carrying the filters pseudo-array once any filter exists on the view. + +2. **Style polish.** Current theme is dashboard-dark + brand teal/purple/green. Good targets: cleaner typography for the label lists, empty/loading states, subtle entry animations on panels, better error affordances (a dismissable toast would be nicer than the inline red block). + +## Branch etiquette + +- Commits should stay focused (one concern each). Architecture decisions go in the architecture docs, not in commit messages. +- Don't rebase or force-push `feat/external-bridge`; Alex pulls from it. +- No pushes to remote unless asked. + +## Watching runtime behavior + +```bash +# From the exrhizome server, after `npm run dev` backgrounded: +tail -f +``` + +`[browser:debug]` / `[browser:log]` / `[browser:warn]` / `[browser:error]` prefixed lines are browser-side. `[vite]` lines are HMR. If neither stream updates when you edit a file, HMR WS is broken — see gotcha #4. + +## Environment + +This is Alex's `exrhizome` server, not your machine. Run and edit freely. For infrastructure questions (how graphistry is deployed, tailscale routing, mode switching) ask Alex — `deploy.md` in this repo has the gory details but you almost certainly don't need them. diff --git a/Dockerfile b/Dockerfile index 37e0b6c..a1829ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.13.0-slim as base +FROM node:16-bullseye-slim as base WORKDIR /opt/graphistry-js RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ diff --git a/deploy.md b/deploy.md new file mode 100644 index 0000000..be18a0d --- /dev/null +++ b/deploy.md @@ -0,0 +1,87 @@ +# graphistry-js embedded dev — implementor handoff + +You edit the viz (`graphistry/apps/core/viz/src/`) and the JS client stack (`graphistry-js/projects/client-api/src/`, `projects/client-api-context/src/`). + +## Who does what + +- **Alex** reviews in a Mac browser over Tailscale. He doesn't run anything. +- **You** run the example Vite dev server and read its logs. You own that loop. +- **Operator** owns the Graphistry docker-compose dev stack + Tailscale routes. + +## Hot-reload — no build step in the hot path + +- `apps/core/viz/src/` → nodemon + webpack-dev-server HMR push updates into Alex's iframe. +- `graphistry-js/projects/*/src/` → Vite HMR via `resolve.alias` in `projects/client-api-context-example/vite.config.ts`. + +## Run the example yourself + +Exact command (keeps stderr in the same stream so you can grep the task-output for it): + +```bash +cd /data/projects/graphistry/graphistry-js/projects/client-api-context-example && npm run dev 2>&1 +``` + +Binds `0.0.0.0:5174`; Tailscale exposes `:8493`. + +Browser `console.*` and uncaught errors stream to your Vite terminal via `server.forwardConsole`. That's your debugging channel. + +## Agent debug loop + +Vite's stdout lands at `/proc//fd/1` → a Claude task-output file; tail it to read every `→iframe get` / `←iframe ok` envelope. Loop: edit → `ScheduleWakeup` ~60–90s for HMR to land and hooks to re-fire → grep the log for the latest matching RPC id → judge response shape → iterate. Ask Alex for restart sequence 1 if the viz looks stuck. + +## Iframe HMR route (gotcha) + +``` +browser wss://…:8495/sockjs-node → tailscale → 127.0.0.1:3002 → container:3000 (WDS) +``` + +`WDS_SOCKET_PORT=8495` only tells the browser where to reconnect; WDS is hard-coded at 3000 in `apps/core/viz/scripts/start.js` — don't try to change the listen port. + +## Builds (not hot path) + +Only needed before hand-off, on `package.json` changes, or if hot-reload gets confused. + +| Package | `npm run build` produces | +|---|---| +| `client-api` | `dist/index.{cjs,esm,iife}.min.js` | +| `client-api-context` | `dist/index.{js,cjs}` + `.d.ts` | +| `client-api-context-example` | `dist/` (also the fastest TS typecheck) | + +Deps drift → `npm install --no-workspaces` in the affected package (no workspaces field at root). + +## Restart sequences (operator, flag if needed) + +**1. Viz-side code restart (most common)** — source changes that nodemon/WDS missed, or you want a clean slate: + +```bash +docker restart compose-streamgl-viz-1 compose-nginx-1 +``` + +Always pair `nginx` with `streamgl-viz` (and `streamgl-gpu`, `forge-etl-python`): on recreate, containers get new IPs; nginx's `upstream { keepalive }` caches the old IP until restart, causing 502s that surface as HTML error pages / RxJS "undefined stream" errors. + +**2. Compose config change** (ports, env, mounts) — `docker restart` isn't enough; need recreate: + +```bash +docker rm -f compose-streamgl-viz-1 +cd /data/projects/graphistry/graphistry && CUDA_SHORT_VERSION=13 ./dc.dev up -d --force-recreate streamgl-viz +docker restart compose-nginx-1 +``` + +**3. New npm dep in viz** — image rebuild: + +```bash +cd /data/projects/graphistry/graphistry +CUDA_SHORT_VERSION=13 ./dc.dev build streamgl-viz +CUDA_SHORT_VERSION=13 ./dc.dev up -d --force-recreate streamgl-viz +docker restart compose-nginx-1 +``` + +## Summary + +| Change | File(s) | Action | +|---|---|---| +| Client API / context / example | `graphistry-js/projects/*/src/**` | nothing — Vite HMR | +| Viz source | `apps/core/viz/src/**` | nothing — nodemon + WDS | +| Viz stuck / stale bundle | — | sequence 1 | +| Compose ports/env changed | `compose/development.yml` | sequence 2 | +| Viz dep added | `apps/core/viz/package.json` | sequence 3 | diff --git a/lerna.json b/lerna.json index ee5616d..d956581 100644 --- a/lerna.json +++ b/lerna.json @@ -2,6 +2,8 @@ "version": "5.1.6", "packages": [ "projects/client-api", + "projects/client-api-context", + "projects/client-api-context-example", "projects/client-api-react", "projects/cra-test", "projects/cra-test-18", diff --git a/projects/client-api-context-example/.gitignore b/projects/client-api-context-example/.gitignore new file mode 100644 index 0000000..b431156 --- /dev/null +++ b/projects/client-api-context-example/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.vite diff --git a/projects/client-api-context-example/index.html b/projects/client-api-context-example/index.html new file mode 100644 index 0000000..cc3a336 --- /dev/null +++ b/projects/client-api-context-example/index.html @@ -0,0 +1,12 @@ + + + + + + client-api-context-example + + +
+ + + diff --git a/projects/client-api-context-example/package-lock.json b/projects/client-api-context-example/package-lock.json new file mode 100644 index 0000000..0297b77 --- /dev/null +++ b/projects/client-api-context-example/package-lock.json @@ -0,0 +1,1443 @@ +{ + "name": "client-api-context-example", + "version": "5.1.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "client-api-context-example", + "version": "5.1.6", + "dependencies": { + "@graphistry/client-api-context": "file:../client-api-context", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^6.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.3.0", + "vite": "^8.0.0", + "vite-plugin-console-forward": "^0.1.0" + } + }, + "../client-api-context": { + "name": "@graphistry/client-api-context", + "version": "5.1.6", + "license": "Apache-2.0", + "dependencies": { + "@graphistry/client-api": "^5.1.6" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "tsup": "^8.0.0", + "typescript": "^5.3.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@graphistry/client-api-context": { + "resolved": "../client-api-context", + "link": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", + "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", + "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.19.0", + "jiti": "^2.6.1", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz", + "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-arm64": "4.2.2", + "@tailwindcss/oxide-darwin-x64": "4.2.2", + "@tailwindcss/oxide-freebsd-x64": "4.2.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", + "@tailwindcss/oxide-linux-x64-musl": "4.2.2", + "@tailwindcss/oxide-wasm32-wasi": "4.2.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz", + "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz", + "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz", + "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz", + "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz", + "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz", + "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz", + "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz", + "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz", + "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz", + "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.8.1", + "@emnapi/runtime": "^1.8.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.1", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz", + "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz", + "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.2.tgz", + "integrity": "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.2.2", + "@tailwindcss/oxide": "4.2.2", + "tailwindcss": "4.2.2" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.7" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/rolldown": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" + } + }, + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tailwindcss": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", + "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.15", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-console-forward": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/vite-plugin-console-forward/-/vite-plugin-console-forward-0.1.0.tgz", + "integrity": "sha512-y2FdnKpheUXr714xJ4+/A/ho7nl9l5YnCLrk4ww5h6NnONmGRMXB7zgJj2d0oX/4wiH4ntOrpIuV6u2cMEusKg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "vite": "^4.0.0 || ^5.0.0 || ^6.0.0" + } + } + } +} diff --git a/projects/client-api-context-example/package.json b/projects/client-api-context-example/package.json new file mode 100644 index 0000000..ac8a88b --- /dev/null +++ b/projects/client-api-context-example/package.json @@ -0,0 +1,26 @@ +{ + "name": "client-api-context-example", + "version": "5.1.6", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@graphistry/client-api-context": "file:../client-api-context", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.0.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^6.0.0", + "tailwindcss": "^4.0.0", + "typescript": "^5.3.0", + "vite": "^8.0.0", + "vite-plugin-console-forward": "^0.1.0" + } +} diff --git a/projects/client-api-context-example/src/App.tsx b/projects/client-api-context-example/src/App.tsx new file mode 100644 index 0000000..1f9dda2 --- /dev/null +++ b/projects/client-api-context-example/src/App.tsx @@ -0,0 +1,23 @@ +// End-to-end smoke test for @graphistry/client-api-context — and the +// advertisement for how it's meant to be used. Entire integration is a +// provider, the iframe, and self-positioning panels. Business logic lives +// in the package hooks; UI primitives in ./ui; each panel under ./panels. + +import { GraphistryProvider, GraphistryScene } from '@graphistry/client-api-context'; +import { DATASET, HOST, SCENE_BG_HEX, SCENE_PARAMS } from './config'; +import { SettingsDrawer } from './panels/SettingsDrawer'; +import { SelectionInspector } from './panels/SelectionInspector'; +import { FilterBar } from './panels/FilterBar'; + +export function App() { + return ( + +
+ + + + +
+
+ ); +} diff --git a/projects/client-api-context-example/src/components/PaletteSelect.tsx b/projects/client-api-context-example/src/components/PaletteSelect.tsx new file mode 100644 index 0000000..c73cc07 --- /dev/null +++ b/projects/client-api-context-example/src/components/PaletteSelect.tsx @@ -0,0 +1,133 @@ +// Popover dropdown over a list of Graphistry color palettes. The trigger +// shows the selected palette's swatch + label; the menu renders each option +// with an inline swatch preview — something the native { + setNhHighlight(v); + write([...PATH_RENDERER, 'points', 'neighborhoodHighlight'], v); + }} + /> + + + Labels + + { + setLabelFg(v); + write([...PATH_LABELS_SCENE, 'foreground', 'color'], v); + }} + /> + + + { + setLabelBg(v); + write([...PATH_LABELS_SCENE, 'background', 'color'], v); + }} + /> + + + { + setLabelsEnabled(v); + write([...PATH_LABELS_SCENE, 'enabled'], v); + }} + /> + + + { + setLabelsShorten(v); + write([...PATH_LABELS_SCENE, 'shortenLabels'], v); + }} + /> + + + { + setLabelsOnHover(v); + write([...PATH_LABELS_SCENE, 'highlightEnabled'], v); + }} + /> + + + ); +} diff --git a/projects/client-api-context-example/src/panels/Arrange.tsx b/projects/client-api-context-example/src/panels/Arrange.tsx new file mode 100644 index 0000000..4b1614a --- /dev/null +++ b/projects/client-api-context-example/src/panels/Arrange.tsx @@ -0,0 +1,85 @@ +// Full-width "Arrange" toggle — runs or pauses the force-directed layout. +// Named for what the user sees ("Arrange my graph") rather than physics +// ("Simulate" / "Solve"). Writes `scene.simulating` AND mirrors into +// `scene.controls[0].selected` so the viz's own toolbar stays in sync. +// Local state only — no readback; a subscription-driven version would +// need `simulating` as a readOnly leaf in ClientAPIRoutes. + +import { useState } from 'react'; +import { + useGraphistry, + GraphistryRpcError, +} from '@graphistry/client-api-context'; + +const PATH_VIEW = ['workbooks', 'open', 'views', 'current'] as const; + +function PlayGlyph({ size = 12 }: { size?: number }) { + return ( + + + + ); +} + +function PauseGlyph({ size = 12 }: { size?: number }) { + return ( + + + + + ); +} + +export function ArrangeButton() { + const { rpc, ready } = useGraphistry(); + const [running, setRunning] = useState(false); + + const toggle = async () => { + if (!rpc) return; + const next = !running; + setRunning(next); + try { + await rpc.set({ + paths: [ + [...PATH_VIEW, 'scene', 'simulating'], + [...PATH_VIEW, 'scene', 'controls', 0, 'selected'], + ], + jsonGraph: { + workbooks: { + open: { + views: { + current: { + scene: { simulating: next, controls: { 0: { selected: next } } }, + }, + }, + }, + }, + }, + }); + } catch (err) { + if (err instanceof GraphistryRpcError && (err as { kind?: string }).kind === 'disposed') return; + console.warn('[Arrange] toggle failed', err); + setRunning(!next); // revert on error + } + }; + + // Both states carry a 2px border so toggling running doesn't nudge the + // surrounding layout by a pixel. Idle paints a plain border-strong + // stroke; running swaps in the animated conic-gradient ring from + // `.arrange-active` (index.css). + return ( + + ); +} diff --git a/projects/client-api-context-example/src/panels/Encodings.tsx b/projects/client-api-context-example/src/panels/Encodings.tsx new file mode 100644 index 0000000..3668150 --- /dev/null +++ b/projects/client-api-context-example/src/panels/Encodings.tsx @@ -0,0 +1,204 @@ +// Attribute-driven encoding builder for point color, point size, and edge +// color. All wire-protocol concerns (ref-walking, ghost-entry filtering, +// attribute prefix stripping, palette-colors spread, atom-wrapping) live +// inside `@graphistry/client-api-context` — this panel just wires the hooks +// into the three row layouts. + +import { useMemo, useState } from 'react'; +import { + useGraphistry, + useColumns, + usePalettes, + useEncoding, + columnsForEncoding, + GraphistryRpcError, +} from '@graphistry/client-api-context'; +import type { + ColumnMeta, + Palette, + EncodingKind, +} from '@graphistry/client-api-context'; +import { Button, SectionHeader, Select } from '../ui'; +import { ChevronGlyph, PaletteSelect, PaletteSwatch } from '../components/PaletteSelect'; + +/** Strip the `componentType:` prefix from an attribute for display. The + * bridge takes the bare name across the wire, but ColumnMeta.attribute + * keeps the prefix so callers can distinguish point vs edge variants of + * the same column name. */ +function bareAttr(attr: string): string { + return attr.replace(/^(point|edge):/, ''); +} + +interface AppliedState { + attribute: string; + paletteLabel?: string; + paletteColors?: string[]; +} + +/** One encoding row: compact summary + chevron, click to reveal the editor + * in-place (attribute select, palette select, swatch preview, apply/reset). + * Local UI state only — `useEncoding(kind)` owns the imperative write. */ +function EncodingRow({ + kind, + columns, + palettes, +}: { + kind: EncodingKind; + columns: ColumnMeta[]; + palettes: Palette[]; +}) { + const { ready } = useGraphistry(); + const encoding = useEncoding(kind); + const meta = encoding.meta; + const [expanded, setExpanded] = useState(false); + const [attribute, setAttribute] = useState(''); + const [paletteName, setPaletteName] = useState(''); + const [applied, setApplied] = useState(null); + const [status, setStatus] = useState(null); + + const availableColumns = useMemo( + () => columnsForEncoding(columns, kind), + [columns, kind] + ); + const selectedPalette = palettes.find((p) => p.name === paletteName); + + const run = async (fn: () => Promise, okMsg: string, onOk?: () => void) => { + setStatus(null); + try { + await fn(); + setStatus(okMsg); + onOk?.(); + } catch (err) { + if (err instanceof GraphistryRpcError && (err as { kind?: string }).kind === 'disposed') return; + setStatus(`⚠ ${err instanceof Error ? err.message : String(err)}`); + } + }; + + const apply = () => { + if (!attribute) return; + run( + () => encoding.apply({ attribute, palette: selectedPalette }), + 'Applied', + () => + setApplied({ + attribute, + paletteLabel: selectedPalette?.label ?? selectedPalette?.name, + paletteColors: selectedPalette?.colors, + }) + ); + }; + + const reset = () => { + run(() => encoding.reset(), 'Cleared', () => setApplied(null)); + }; + + const busy = encoding.writing; + const applyDisabled = busy || !ready || !attribute || (meta.needsPalette && !paletteName); + const summaryText = applied + ? meta.needsPalette && applied.paletteLabel + ? `${bareAttr(applied.attribute)} · ${applied.paletteLabel}` + : bareAttr(applied.attribute) + : '—'; + + return ( +
+ + {expanded && ( +
+ {/* Stack label above control so long attribute / palette names + don't overflow a label column. `
+
+ {meta.needsPalette && ( +
+ + +
+ )} +
+ {status && ( + + {status} + + )} +
+ {applied && ( + + )} + +
+
+ + )} + + ); +} + +export function EncodingsPanel() { + const { columns } = useColumns(); + const { palettes: pointPalettes } = usePalettes('point'); + const { palettes: edgePalettes } = usePalettes('edge'); + + return ( + <> + Encodings + + + + + ); +} diff --git a/projects/client-api-context-example/src/panels/FilterBar.tsx b/projects/client-api-context-example/src/panels/FilterBar.tsx new file mode 100644 index 0000000..84ce0f9 --- /dev/null +++ b/projects/client-api-context-example/src/panels/FilterBar.tsx @@ -0,0 +1,149 @@ +// Right-rail collapsible panel for managing filters. Reads the current list +// (plus the default LIMIT filter) via `useFilters()`, which streams updates +// through the Falcor Bridge's filter subscription. Mutations (add/remove/ +// reset) go through the hook's imperative handlers; this panel just +// surfaces busy/error state and renders the AST of each filter compactly. + +import { useState } from 'react'; +import { + useFilters, + GraphistryControlledError, + GraphistryRpcError, +} from '@graphistry/client-api-context'; +import { Button, Chip, ErrorBox, Input, Panel } from '../ui'; + +type AstNode = { type: string; [k: string]: unknown }; + +function renderAst(node: unknown): string { + if (node === null || node === undefined) return ''; + if (typeof node !== 'object') return String(node); + const n = node as AstNode; + switch (n.type) { + case 'Literal': + return n.dataType === 'string' ? JSON.stringify(n.value) : String(n.value); + case 'Identifier': + return String((n.name as string) ?? (n.value as string) ?? '?'); + case 'MemberExpression': + return `${renderAst(n.object)}.${typeof n.property === 'string' ? n.property : renderAst(n.property)}`; + case 'LimitExpression': + return `LIMIT ${renderAst(n.value)}`; + case 'BinaryExpression': + case 'BinaryPredicate': + return `${renderAst(n.left)} ${(n.operator as string) ?? (n.op as string) ?? '?'} ${renderAst(n.right)}`; + case 'NotExpression': + return `NOT ${renderAst(n.value)}`; + case 'FunctionCall': { + const name = (n.name as string) ?? (n.callee ? renderAst(n.callee) : 'fn'); + const args = ((n.arguments as AstNode[]) ?? []).map(renderAst).join(', '); + return `${name}(${args})`; + } + default: + try { + return JSON.stringify(node); + } catch { + return String(n.type); + } + } +} + +function filterDisplay(f: Record, i: number): string { + const q = f.query as { ast?: unknown; error?: unknown } | string | undefined; + if (typeof q === 'string') return q; + if (q && typeof q === 'object') { + if (typeof q.error === 'string' && q.error) return `⚠ ${q.error}`; + if (q.ast !== undefined) return renderAst(q.ast); + } + if (typeof f.name === 'string') return f.name; + return `filter ${i}`; +} + +export function FilterBar() { + const filters = useFilters(); + const [expr, setExpr] = useState('point:degree > 1'); + const [busy, setBusy] = useState(false); + const [lastError, setLastError] = useState(null); + + const run = async (action: () => Promise) => { + setBusy(true); + setLastError(null); + try { + await action(); + } catch (err) { + if (err instanceof GraphistryControlledError) { + setLastError(`controlled: ${err.message}`); + } else if (err instanceof GraphistryRpcError) { + setLastError(`rpc.${err.kind}: ${err.message}`); + } else { + setLastError(String(err)); + } + } finally { + setBusy(false); + } + }; + + const count = filters.filters.length; + + const titleNode = ( + +

+ Filters +

+ 0 ? 'text-accent' : 'text-text-faint'}`} + > + {count} active + +
+ ); + + return ( +
+ +
+ run(() => filters.add(expr))} + placeholder="filter expression" + /> + + +
+ + {count > 0 && ( +
    + {filters.filters.map((f, i) => ( + f.id && run(() => filters.remove(f.id!))} + title={f.id ? 'Remove filter' : 'Filter has no id; cannot remove'} + ariaLabel="Remove filter" + > + × + + } + > + {filterDisplay(f, i)} + + ))} +
+ )} + + {lastError && ( +
+ {lastError} +
+ )} +
+
+ ); +} diff --git a/projects/client-api-context-example/src/panels/Layout.tsx b/projects/client-api-context-example/src/panels/Layout.tsx new file mode 100644 index 0000000..4ac5184 --- /dev/null +++ b/projects/client-api-context-example/src/panels/Layout.tsx @@ -0,0 +1,92 @@ +// ForceAtlas-2 tuning sliders. The parameter catalog is hardcoded because +// the server's `apps/core/viz/src/models/layout.js` declares these as +// literal constants that never change at runtime; round-tripping them +// through the Falcor Bridge for metadata would be dead weight. Writes go +// to `layout.options.forceatlas2barnes[i].value`, whitelisted write-only +// in ClientAPIRoutes.js. Prior attempts to read bounds via RPC triggered a +// stack overflow in the falcor router's route-matching when the nested +// `props.{min,max,step,scale}` branch was added to the whitelist (see +// feat/external-bridge); the fix is to not extend the whitelist at all. + +import { useState } from 'react'; +import { + useSceneSetter, + GraphistryRpcError, +} from '@graphistry/client-api-context'; +import { PanelRow, SectionHeader, Slider, Toggle } from '../ui'; + +const PATH_FA2 = ['layout', 'options', 'forceatlas2barnes'] as const; + +type Fa2ParamMeta = + | { + kind: 'number'; + name: string; + default: number; + min: number; + max: number; + step: number; + } + | { + kind: 'bool'; + name: string; + default: boolean; + }; + +const FA2_PARAMS: Fa2ParamMeta[] = [ + { kind: 'number', name: 'Precision vs. Speed', default: -0.1, min: -5, max: 5, step: 0.1 }, + { kind: 'number', name: 'Center Magnet', default: 0.10471285480508996, min: 0, max: 100, step: 1 }, + { kind: 'number', name: 'Expansion Ratio', default: 0.10471285480508996, min: 0, max: 100, step: 1 }, + { kind: 'number', name: 'Edge Influence', default: 0, min: 0, max: 100, step: 1 }, + { kind: 'bool', name: 'Compact Layout', default: false }, + { kind: 'bool', name: 'Dissuade Hubs', default: false }, + { kind: 'bool', name: 'Strong Separation (LinLog)', default: false }, + { kind: 'bool', name: 'Locked X coordinates', default: false }, + { kind: 'bool', name: 'Locked Y coordinates', default: false }, + { kind: 'bool', name: 'Locked radius', default: false }, +]; + +export function LayoutPanel() { + const set = useSceneSetter(); + + const [values, setValues] = useState<(number | boolean)[]>(() => + FA2_PARAMS.map((p) => p.default) + ); + + const writeValue = (idx: number, v: number | boolean) => { + setValues((cur) => { + const copy = cur.slice(); + copy[idx] = v; + return copy; + }); + set([...PATH_FA2, idx, 'value'], v).catch((err) => { + if (err instanceof GraphistryRpcError && (err as { kind?: string }).kind === 'disposed') return; + console.warn('[Layout] set failed', idx, err); + }); + }; + + return ( + <> + Layout + {FA2_PARAMS.map((p, i) => { + if (p.kind === 'bool') { + return ( + + writeValue(i, v)} /> + + ); + } + return ( + + writeValue(i, v)} + /> + + ); + })} + + ); +} diff --git a/projects/client-api-context-example/src/panels/SelectionInspector.tsx b/projects/client-api-context-example/src/panels/SelectionInspector.tsx new file mode 100644 index 0000000..1a59f28 --- /dev/null +++ b/projects/client-api-context-example/src/panels/SelectionInspector.tsx @@ -0,0 +1,161 @@ +// Right-rail panel that summarises the current selection. +// - Empty selection: renders nothing. +// - Single point / single edge: shows the focused label, its glyph, and +// a compact key-value list of that entity's columns. +// - Multiple: shows a collapsed list of the first 30 items per kind. +// The raw selection snapshot comes from `useSelection()`, which streams +// the viz's post-message selection updates through the Falcor Bridge. + +import { useSelection } from '@graphistry/client-api-context'; +import { + EdgeGlyph, + ErrorBox, + KeyValueRow, + Panel, + PointGlyph, + ScrollArea, + StatusDot, +} from '../ui'; + +function rowLabel(l: { title?: string; label?: string; value?: unknown }): string { + return ( + (typeof l.title === 'string' && l.title) || + (typeof l.label === 'string' && l.label) || + (l.value != null ? String(l.value) : '') + ); +} + +type ColumnEntry = { key?: string; value?: unknown; dataType?: string }; + +function normalizeColumns(raw: unknown): ColumnEntry[] { + if (!raw || typeof raw !== 'object') return []; + const out: ColumnEntry[] = []; + const keys = Object.keys(raw) + .filter((k) => /^\d+$/.test(k)) + .map(Number) + .sort((a, b) => a - b); + for (const k of keys) { + const entry = (raw as Record)[String(k)]; + if (entry && typeof entry === 'object') out.push(entry); + } + if (out.length === 0) { + for (const [k, v] of Object.entries(raw)) out.push({ key: k, value: v }); + } + return out; +} + +function formatColumnValue(v: unknown): string { + if (v === null || v === undefined) return '—'; + if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return String(v); + try { + return JSON.stringify(v); + } catch { + return String(v); + } +} + +/** Top-right rail slot. The panel self-positions so App.tsx stays clean. */ +function Rail({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +export function SelectionInspector() { + const sel = useSelection(); + + if (sel.error) { + return ( + + }> + {sel.error.message} + + + ); + } + if (!sel.ready || sel.points.length + sel.edges.length === 0) return null; + + const onlyPoint = sel.points.length === 1 && sel.edges.length === 0 ? sel.pointLabels[0] : null; + const onlyEdge = sel.edges.length === 1 && sel.points.length === 0 ? sel.edgeLabels[0] : null; + const focused = onlyPoint ?? onlyEdge; + + if (focused) { + const glyph = onlyPoint ? : ; + const label = rowLabel(focused); + const columns = normalizeColumns(focused.columns); + return ( + + + {glyph} + {label || ( + + #{focused.globalIndex ?? focused.index ?? '?'} + + )} + + } + bodyPad={false} + > + {columns.length === 0 ? ( +
No fields.
+ ) : ( + +
+ {columns.map((c, i) => ( + + ))} +
+
+ )} +
+
+ ); + } + + // Multi-selection: compact list. + const total = sel.points.length + sel.edges.length; + return ( + + + {total} selected + + } + bodyPad={false} + > + +
    + {sel.pointLabels.slice(0, 30).map((l, i) => ( +
  • + + + {rowLabel(l) || `#${l.globalIndex ?? l.index ?? '?'}`} + +
  • + ))} + {sel.edgeLabels.slice(0, 30).map((l, i) => ( +
  • + + + {rowLabel(l) || `→${l.globalIndex ?? l.index ?? '?'}`} + +
  • + ))} +
+
+
+
+ ); +} diff --git a/projects/client-api-context-example/src/panels/SettingsDrawer.tsx b/projects/client-api-context-example/src/panels/SettingsDrawer.tsx new file mode 100644 index 0000000..13299e4 --- /dev/null +++ b/projects/client-api-context-example/src/panels/SettingsDrawer.tsx @@ -0,0 +1,51 @@ +// Left-slide settings drawer — hamburger trigger, sticky Arrange toolbar, +// and the three stacked settings panels. Owns its own open/close state +// (persisted across HMR so iterating on any panel doesn't lose the open +// drawer every reload). + +import { useEffect, useState } from 'react'; +import { Drawer, HamburgerGlyph, IconButton } from '../ui'; +import { ArrangeButton } from './Arrange'; +import { LayoutPanel } from './Layout'; +import { EncodingsPanel } from './Encodings'; +import { AppearancePanel } from './Appearance'; +import { SCENE_BG_HEX } from '../config'; + +const STORAGE_KEY = 'drawerOpen'; + +function usePersistedBool(key: string, initial: boolean): [boolean, (v: boolean) => void] { + const [v, setV] = useState(() => { + if (typeof window === 'undefined') return initial; + return window.localStorage.getItem(key) === '1'; + }); + useEffect(() => { + if (typeof window !== 'undefined') { + window.localStorage.setItem(key, v ? '1' : '0'); + } + }, [key, v]); + return [v, setV]; +} + +export function SettingsDrawer() { + const [open, setOpen] = usePersistedBool(STORAGE_KEY, false); + + return ( + <> +
+ setOpen(true)}> + + +
+ setOpen(false)} + title="Settings" + toolbar={} + > + + + + + + ); +} diff --git a/projects/client-api-context-example/src/ui.tsx b/projects/client-api-context-example/src/ui.tsx new file mode 100644 index 0000000..c742714 --- /dev/null +++ b/projects/client-api-context-example/src/ui.tsx @@ -0,0 +1,576 @@ +// UI primitives for the client-api-context example. +// +// Tokens live in index.css (@theme). Keep this file component-only — no +// business logic, no data fetching — so composition in App.tsx reads as +// "panels + rows filled with data from hooks". + +import { useEffect, useRef, useState, type ReactNode } from 'react'; + +// ---------- Glyphs ---------- + +export function PointGlyph() { + return ( + + ); +} + +export function EdgeGlyph() { + return ( + + + + + ); +} + +export function StatusDot({ tone }: { tone: 'ready' | 'pending' | 'error' }) { + const cls = { + ready: 'bg-ok shadow-[0_0_8px_rgba(126,212,163,0.45)]', + pending: 'bg-warn animate-pulse', + error: 'bg-err', + }[tone]; + return ; +} + +// ---------- Panel ---------- + +export interface PanelProps { + title?: string; + titleNode?: ReactNode; + right?: ReactNode; + children: ReactNode; + /** Whether to wrap body content in `p-3`. Set false when children own + * their own padding (typically a `ScrollArea` that paints to the edges). */ + bodyPad?: boolean; + /** When true, the header becomes a toggle and body is collapsed by default + * (or per `defaultOpen`). `right` is rendered next to the chevron. */ + collapsible?: boolean; + defaultOpen?: boolean; +} + +export function Panel({ + title, + titleNode, + right, + children, + bodyPad = true, + collapsible = false, + defaultOpen = true, +}: PanelProps) { + const [open, setOpen] = useState(defaultOpen); + const isOpen = !collapsible || open; + const hasHeader = + title !== undefined || titleNode !== undefined || right !== undefined || collapsible; + + const header = ( + <> + {titleNode ?? + (title ? ( +

+ {title} +

+ ) : null)} + {collapsible ? ( + + {right} + + + + + ) : ( + right + )} + + ); + + return ( +
+ {hasHeader && + (collapsible ? ( + + ) : ( +
+ {header} +
+ ))} + {isOpen &&
{children}
} +
+ ); +} + +// ---------- ScrollArea ---------- + +/** Vertically scrollable region with soft mask-image fades at top/bottom so + * overflow is visually hinted without adding DOM. Browsers force + * `overflow-x: hidden` when `-y` is `auto`; that's acceptable here because + * rows inside are width-bounded. */ +export function ScrollArea({ + className, + children, +}: { + className?: string; + children: ReactNode; +}) { + const mask = + 'linear-gradient(to bottom, transparent 0, black 14px, black calc(100% - 14px), transparent 100%)'; + return ( +
+ {children} +
+ ); +} + +// ---------- Button ---------- + +type ButtonVariant = 'primary' | 'ghost' | 'iconGhost'; + +export function Button({ + variant = 'primary', + disabled, + onClick, + children, + title, + ariaLabel, + className, +}: { + variant?: ButtonVariant; + disabled?: boolean; + onClick?: () => void; + children: ReactNode; + title?: string; + ariaLabel?: string; + className?: string; +}) { + const base = + 'rounded-sm transition-colors disabled:opacity-40 disabled:cursor-not-allowed'; + const styles: Record = { + primary: + 'bg-accent hover:bg-accent/90 text-ink font-semibold px-3 py-1.5 text-xs', + ghost: + 'border border-border text-text-dim hover:bg-elevated px-3 py-1.5 text-xs', + iconGhost: + 'text-text-faint hover:text-err opacity-50 hover:opacity-100 text-sm leading-none px-1', + }; + return ( + + ); +} + +// ---------- Input ---------- + +export function Input({ + value, + onChange, + onSubmit, + placeholder, + className, +}: { + value: string; + onChange: (v: string) => void; + onSubmit?: () => void; + placeholder?: string; + className?: string; +}) { + return ( + onChange(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && onSubmit) onSubmit(); + }} + className={`flex-1 min-w-0 rounded-sm bg-ink/60 border border-border px-3 py-1.5 text-xs font-mono text-text placeholder-text-faint focus:outline-none focus:ring-1 focus:ring-accent focus:border-accent transition-colors ${className ?? ''}`} + placeholder={placeholder} + spellCheck={false} + /> + ); +} + +// ---------- KeyValueRow ---------- + +/** Inline when the combined length fits on one line; otherwise stack key + * above value so long values (big integers, JSON blobs, URLs) don't + * overflow or crowd the key label. Caller passes `altIndex` to drive + * subtle stripe alternation. */ +export function KeyValueRow({ + keyText, + value, + altIndex, + inlineThreshold = 34, +}: { + keyText: string; + value: string; + altIndex?: number; + inlineThreshold?: number; +}) { + const inline = keyText.length + value.length <= inlineThreshold && !value.includes('\n'); + const stripe = altIndex !== undefined && altIndex % 2 === 1 ? 'bg-white/[0.025]' : ''; + const base = `rounded-sm px-2 py-1 min-w-0 ${stripe}`; + if (inline) { + return ( +
+
{keyText}
+
{value}
+
+ ); + } + return ( +
+
{keyText}
+
{value}
+
+ ); +} + +// ---------- ErrorBox ---------- + +export function ErrorBox({ children }: { children: ReactNode }) { + return ( +
+ {children} +
+ ); +} + +// ---------- Chip (generic pill row, used by filters + anywhere else) ---------- + +export function Chip({ + tone = 'active', + children, + right, +}: { + tone?: 'active' | 'muted'; + children: ReactNode; + right?: ReactNode; +}) { + const dot = tone === 'active' ? 'bg-ok' : 'bg-border-strong'; + return ( +
  • + + {children} + {right} +
  • + ); +} + +// ---------- Drawer + Hamburger ---------- + +/** 14px line-group hamburger glyph. Parent controls color via + * `text-text-{dim,faint}`; the button variant sets hover. */ +export function HamburgerGlyph({ size = 16 }: { size?: number }) { + return ( + + + + ); +} + +/** Minimal icon button, no background — used for the hamburger. Keeps the + * viz canvas visually unbroken at rest. */ +export function IconButton({ + onClick, + ariaLabel, + children, + className, +}: { + onClick?: () => void; + ariaLabel: string; + children: ReactNode; + className?: string; +}) { + return ( + + ); +} + +/** Left-slide drawer. Caller owns `open`/`onClose` (so the trigger can live + * anywhere). Renders a backdrop with click-to-close and an escape-key + * listener; body scrolls independently. */ +export function Drawer({ + open, + onClose, + title, + width = 320, + toolbar, + children, +}: { + open: boolean; + onClose: () => void; + title?: string; + width?: number; + /** Sticky content below the title and above the scroll area. Use for + * always-visible actions (eg the Arrange button) that shouldn't scroll + * out of view with the panel list. */ + toolbar?: ReactNode; + children: ReactNode; +}) { + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === 'Escape') onClose(); + }; + window.addEventListener('keydown', onKey); + return () => window.removeEventListener('keydown', onKey); + }, [open, onClose]); + + // No backdrop — the viz stays fully interactive while the drawer is open. + // The drawer's own surface catches clicks; outside clicks pass through to + // the canvas. Close via the × button or Esc. + return ( + <> + + + ); +} + +// ---------- Panel primitives (rows, sections) ---------- + +/** Section separator inside a Drawer or Panel. All-caps micro-label; sits + * tight to the row below. */ +export function SectionHeader({ children }: { children: ReactNode }) { + return ( +
    + {children} +
    + ); +} + +/** One labelled control row. Label sits left (flex-1 by default so short + * toggles/swatches end-align cleanly); control area sits right. For sliders + * pass `wideLabel={false}` so the slider takes most of the row. */ +export function PanelRow({ + label, + labelFill = false, + children, +}: { + label: string; + labelFill?: boolean; + children: ReactNode; +}) { + return ( +
    + + {label} + +
    {children}
    +
    + ); +} + +// ---------- Toggle ---------- + +export function Toggle({ + checked, + onChange, +}: { + checked: boolean; + onChange?: (v: boolean) => void; +}) { + return ( + + ); +} + +// ---------- Slider ---------- + +/** Range input styled to the theme. Native element for accessibility; the + * track + thumb are painted with inline gradients so no global CSS is + * required. Shows numeric value on the right. */ +export function Slider({ + value, + min = 0, + max = 1, + step, + onChange, +}: { + value: number; + min?: number; + max?: number; + step?: number; + onChange?: (v: number) => void; +}) { + const pct = max === min ? 0 : ((value - min) / (max - min)) * 100; + const fmt = Number.isInteger(value) ? String(value) : value.toFixed(2); + const trackBg = `linear-gradient(to right, var(--color-accent) 0%, var(--color-accent) ${pct}%, var(--color-elevated) ${pct}%, var(--color-elevated) 100%)`; + return ( +
    + onChange?.(+e.target.value)} + className="ui-slider flex-1 min-w-0 h-1 appearance-none rounded-sm cursor-pointer accent-accent" + style={{ background: trackBg }} + /> + + {fmt} + +
    + ); +} + +// ---------- Select ---------- + +export function Select({ + value, + options, + onChange, +}: { + value: string; + options: Array<{ value: string; label?: string }>; + onChange?: (v: string) => void; +}) { + return ( + + ); +} + +// ---------- ColorSwatch ---------- + +/** Square color swatch that opens a native color picker on click, with a + * hex input alongside. Prototype-simple — no HSV wheel. */ +export function ColorSwatch({ + value, + onChange, +}: { + value: string; + onChange?: (v: string) => void; +}) { + const [hex, setHex] = useState(value); + const inputRef = useRef(null); + // Re-sync when external value changes (e.g. initial load from viz state). + useEffect(() => setHex(value), [value]); + const commit = (v: string) => { + if (/^#[0-9a-fA-F]{6}$/.test(v)) onChange?.(v); + }; + return ( +
    + { + setHex(e.target.value); + onChange?.(e.target.value); + }} + className="size-5 rounded-sm border border-border cursor-pointer bg-transparent appearance-none overflow-hidden" + aria-label="Pick color" + /> + setHex(e.target.value)} + onBlur={() => commit(hex)} + onKeyDown={(e) => { + if (e.key === 'Enter') commit(hex); + }} + spellCheck={false} + className="w-20 rounded-sm bg-ink/60 border border-border px-2 py-1 text-[10px] font-mono text-text placeholder-text-faint focus:outline-none focus:ring-1 focus:ring-accent" + /> +
    + ); +} diff --git a/projects/client-api-context-example/tsconfig.json b/projects/client-api-context-example/tsconfig.json new file mode 100644 index 0000000..4663921 --- /dev/null +++ b/projects/client-api-context-example/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "@graphistry/client-api-context": ["../client-api-context/src/index.ts"], + "@graphistry/client-api": ["../client-api/src/index.js"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/projects/client-api-context-example/tsconfig.node.json b/projects/client-api-context-example/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/projects/client-api-context-example/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/projects/client-api-context-example/vite.config.ts b/projects/client-api-context-example/vite.config.ts new file mode 100644 index 0000000..0044068 --- /dev/null +++ b/projects/client-api-context-example/vite.config.ts @@ -0,0 +1,40 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; +import consoleForward from 'vite-plugin-console-forward'; +import { fileURLToPath } from 'node:url'; +import { dirname, resolve } from 'node:path'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +export default defineConfig({ + plugins: [ + react(), + tailwindcss(), + // Forwards browser console.* + onerror + unhandledrejection to this + // dev-server's stdout, so coding agents editing on the server can see + // runtime behavior without needing browser devtools on the Mac. + consoleForward({ + levels: ['error', 'warn', 'info', 'log', 'debug'], + captureErrors: true, + captureRejections: true, + prefix: 'browser', + }), + ], + resolve: { + alias: { + '@graphistry/client-api-context': resolve(__dirname, '../client-api-context/src/index.ts'), + '@graphistry/client-api': resolve(__dirname, '../client-api/src/index.js'), + }, + }, + server: { + host: '0.0.0.0', + port: 5174, + strictPort: true, + allowedHosts: ['.ts.net', 'localhost', '127.0.0.1'], + hmr: { + clientPort: 8493, + protocol: 'wss', + }, + }, +}); diff --git a/projects/client-api-context/README.md b/projects/client-api-context/README.md new file mode 100644 index 0000000..69769b6 --- /dev/null +++ b/projects/client-api-context/README.md @@ -0,0 +1,3 @@ +# @graphistry/client-api-context + +React provider + hooks over `@graphistry/client-api`. See `ai_code_notes/architecture/external_bridge.md` and `client_context.md` in the graphistry repo for design context. diff --git a/projects/client-api-context/package-lock.json b/projects/client-api-context/package-lock.json new file mode 100644 index 0000000..735275b --- /dev/null +++ b/projects/client-api-context/package-lock.json @@ -0,0 +1,1738 @@ +{ + "name": "@graphistry/client-api-context", + "version": "5.1.6", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@graphistry/client-api-context", + "version": "5.1.6", + "license": "Apache-2.0", + "dependencies": { + "@graphistry/client-api": "^5.1.6" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "tsup": "^8.0.0", + "typescript": "^5.3.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@graphistry/client-api": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@graphistry/client-api/-/client-api-5.1.8.tgz", + "integrity": "sha512-ar+i5hfoFxbsPGcD0YobTPWCYIDsTZ7ww8I4zd+n9qay14KyvL15jQHXY+/VqHHiUQX+dtMUCtqF+PVuI9Semg==", + "license": "Apache-2.0", + "dependencies": { + "@graphistry/falcor-json-graph": "^2.9.10", + "@graphistry/falcor-model-rxjs": "2.11.0", + "@graphistry/falcor-socket-datasource": "2.11.3", + "@graphistry/js-upload-api": "^5.1.8", + "shallowequal": "1.1.0" + }, + "peerDependencies": { + "rxjs": ">=7.2.0" + } + }, + "node_modules/@graphistry/falcor": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@graphistry/falcor/-/falcor-2.11.0.tgz", + "integrity": "sha512-YWxGtdB32memyZm7xrNfY+7DhtStS2+EEFNWyc9alCE4zZAmDTAqQO9GrwIMPSzaf3BaWAAlgAAyMU9LLJVukQ==", + "license": "Apache-2.0", + "dependencies": { + "@graphistry/falcor-path-utils": "^2.9.10", + "symbol-observable": "1.0.4" + } + }, + "node_modules/@graphistry/falcor-json-graph": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/@graphistry/falcor-json-graph/-/falcor-json-graph-2.9.10.tgz", + "integrity": "sha512-81SxqwoIeTGu0T1C1tErVP8hYOzUjHSnc6zj7mLnAyOKhC+PTm/F8OPMfj1M5CVHMiWOX+5J+P0jJq+DcO1zPA==", + "license": "Apache 2.0", + "dependencies": { + "@graphistry/falcor-path-syntax": "^2.9.10" + } + }, + "node_modules/@graphistry/falcor-model-rxjs": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/@graphistry/falcor-model-rxjs/-/falcor-model-rxjs-2.11.0.tgz", + "integrity": "sha512-ZG6hxuWIUQ/fY+w1WWWs51RLi9l/3EVG8673kyDoFYEqh4KA5+Ee+gM6dgszPEA9hIVLyQivwrawt2z07Zwi8w==", + "license": "ISC", + "dependencies": { + "@graphistry/falcor": "^2.11.0", + "@graphistry/falcor-path-syntax": "^2.9.10", + "babel-runtime": "^6.26.0", + "rxjs": "~5.4.2" + } + }, + "node_modules/@graphistry/falcor-model-rxjs/node_modules/rxjs": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.4.3.tgz", + "integrity": "sha512-fSNi+y+P9ss+EZuV0GcIIqPUK07DEaMRUtLJvdcvMyFjc9dizuDjere+A4V7JrLGnm9iCc+nagV/4QdMTkqC4A==", + "license": "Apache-2.0", + "dependencies": { + "symbol-observable": "^1.0.1" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@graphistry/falcor-path-syntax": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/@graphistry/falcor-path-syntax/-/falcor-path-syntax-2.9.10.tgz", + "integrity": "sha512-KgWAMrkrJmjsxEEkJKA5wwedXSkA5wxQD1ia3CLby9B20dIcRgGIahZss5qdhwQkuoZdVw+aJNhXjSFlnj/qSw==", + "license": "ALV2" + }, + "node_modules/@graphistry/falcor-path-utils": { + "version": "2.9.10", + "resolved": "https://registry.npmjs.org/@graphistry/falcor-path-utils/-/falcor-path-utils-2.9.10.tgz", + "integrity": "sha512-DSpApTNtIaonsj/g3vyePJsiPnnO1XsCa3C6aBcuQx3fTjmslU1E3qTjl/tVnwPCLoLFSvKvmVvvMcivFxq7fA==", + "license": "ALV2" + }, + "node_modules/@graphistry/falcor-socket-datasource": { + "version": "2.11.3", + "resolved": "https://registry.npmjs.org/@graphistry/falcor-socket-datasource/-/falcor-socket-datasource-2.11.3.tgz", + "integrity": "sha512-Wee/dpUuVfvxpZWbHsmhgY7nuFF5Iu4emZnghLmMGTmCsGqlUo3nZBwPtUVeAsSy4bBEEyJ1vURipJERCgcAmQ==", + "license": "MIT", + "dependencies": { + "babel-runtime": "^6.26.0", + "uuid": "9.0.0" + } + }, + "node_modules/@graphistry/js-upload-api": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/@graphistry/js-upload-api/-/js-upload-api-5.1.8.tgz", + "integrity": "sha512-uevpBr0SlOmdEQ23M06OOqQNacVc/IypadT522BXnSN/bE5jI0LvxJfjjfjBNr06O5TknP07/c1pb/zlWcTEYw==", + "license": "Apache-2.0" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "license": "MIT", + "dependencies": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "node_modules/bundle-require": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.1.0.tgz", + "integrity": "sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.18" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/core-js": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fix-dts-default-cjs-exports": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fix-dts-default-cjs-exports/-/fix-dts-default-cjs-exports-1.0.1.tgz", + "integrity": "sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "rollup": "^4.34.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/react": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.5" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "peer": true + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/symbol-observable": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz", + "integrity": "sha512-+zhuFJT/l/6NEDL3gkjQ1flyil069glapYzByUjrUdPcn9Z3uFDHbz9TBrPhMz5xFEPyrI3NmDRlJ/lKxnIkIA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/tsup": { + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.5.1.tgz", + "integrity": "sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^5.1.0", + "cac": "^6.7.14", + "chokidar": "^4.0.3", + "consola": "^3.4.0", + "debug": "^4.4.0", + "esbuild": "^0.27.0", + "fix-dts-default-cjs-exports": "^1.0.0", + "joycon": "^3.1.1", + "picocolors": "^1.1.1", + "postcss-load-config": "^6.0.1", + "resolve-from": "^5.0.0", + "rollup": "^4.34.8", + "source-map": "^0.7.6", + "sucrase": "^3.35.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.11", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@microsoft/api-extractor": "^7.36.0", + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.5.0" + }, + "peerDependenciesMeta": { + "@microsoft/api-extractor": { + "optional": true + }, + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/projects/client-api-context/package.json b/projects/client-api-context/package.json new file mode 100644 index 0000000..ddd2f60 --- /dev/null +++ b/projects/client-api-context/package.json @@ -0,0 +1,45 @@ +{ + "name": "@graphistry/client-api-context", + "version": "5.1.6", + "description": "React provider + hooks over @graphistry/client-api", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "clean": "rm -rf dist node_modules", + "lint": "echo 'lint not configured yet'" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/graphistry/graphistry-js.git" + }, + "homepage": "https://github.com/graphistry/graphistry-js/projects/client-api-context#readme", + "author": "Graphistry, Inc ", + "license": "Apache-2.0", + "dependencies": { + "@graphistry/client-api": "^5.1.6" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "tsup": "^8.0.0", + "typescript": "^5.3.0" + } +} diff --git a/projects/client-api-context/src/client-api.d.ts b/projects/client-api-context/src/client-api.d.ts new file mode 100644 index 0000000..e53cb3c --- /dev/null +++ b/projects/client-api-context/src/client-api.d.ts @@ -0,0 +1,136 @@ +// Ambient types for the bits of @graphistry/client-api we consume. +// The upstream package ships untyped JS — these declarations give the +// React wrapper a type-safe boundary against the raw JS module. + +declare module '@graphistry/client-api' { + // ---------- RPC ---------- + export type GraphistryRpcErrorKind = + | 'invalid_op' + | 'internal' + | 'timeout' + | 'disposed' + | (string & {}); + + export interface GraphistryRpcErrorShape { + kind?: GraphistryRpcErrorKind; + op?: string; + message?: string; + [extra: string]: unknown; + } + + export class GraphistryRpcError extends Error { + readonly kind: GraphistryRpcErrorKind; + readonly op: string; + constructor(shape: GraphistryRpcErrorShape); + } + + export interface RpcClient { + call(path: readonly (string | number)[], args?: readonly unknown[]): Promise; + get(...paths: ReadonlyArray): Promise; + set(json: unknown): Promise; + dispose(): void; + } + + export function createRpcClient( + iframe: HTMLIFrameElement, + options?: { timeoutMs?: number; window?: Window } + ): RpcClient; + + // ---------- ExternalStore ---------- + export interface ExternalStoreOptions { + initial: T; + arrayKeys?: ReadonlyArray; + onFirstListener?: () => void; + onLastListener?: () => void; + } + + export class ExternalStore { + constructor(options: ExternalStoreOptions); + subscribe(listener: () => void): () => void; + getSnapshot(): T; + update(incoming: T): void; + setError(error: unknown): void; + } + + // ---------- Subscription manager ---------- + export type FalcorKey = string | number; + export type FalcorRange = { from?: number; to?: number; length?: number }; + export type FalcorPathEl = FalcorKey | readonly FalcorKey[] | FalcorRange; + export type FalcorPathSet = readonly FalcorPathEl[]; + + export class SubscriptionManager { + iframe: HTMLIFrameElement | null; + register( + path: string, + store: ExternalStore, + project: (raw: unknown) => T, + pathSets?: ReadonlyArray + ): void; + acquire(path: string): void; + release(path: string): void; + attachIframe(iframe: HTMLIFrameElement): void; + detachIframe(): void; + } + + // ---------- Errors ---------- + export class GraphistryPermissionError extends Error { + readonly path: string; + readonly flag?: string; + constructor(opts: { path: string; flag?: string; message?: string }); + } + + // ---------- Snapshots ---------- + export interface LabelEntry { + index: number; + title: string; + globalIndex: number; + label?: string; + value?: unknown; + columns?: Record; + } + + export interface SelectionSnapshot { + points: number[]; + edges: number[]; + pointLabels: LabelEntry[]; + edgeLabels: LabelEntry[]; + ready: boolean; + error: GraphistryPermissionError | null; + } + + export interface LabelsSnapshot { + labels: LabelEntry[]; + ready: boolean; + error: GraphistryPermissionError | null; + } + + export interface FilterEntry { + id?: string; + query?: string; + enabled?: boolean; + name?: string; + dataType?: string; + level?: string; + } + + export interface FiltersSnapshot { + filters: FilterEntry[]; + ready: boolean; + error: GraphistryPermissionError | null; + } + + export const initialSelection: SelectionSnapshot; + export const initialLabels: LabelsSnapshot; + export const initialFilters: FiltersSnapshot; + export function projectSelection(raw: unknown): SelectionSnapshot; + export function projectLabels(raw: unknown): LabelsSnapshot; + export function projectFilters(raw: unknown): FiltersSnapshot; + + // ---------- Canonical pathSets for generic subscribe ---------- + export const FRAGMENT_FILTERS: ReadonlyArray; + + // ---------- Path constants ---------- + export const PATH_SELECTION_LABELS: '.selection.labels'; + export const PATH_LABELS: '.labels'; + export const PATH_FILTERS: '.filters'; +} diff --git a/projects/client-api-context/src/context.tsx b/projects/client-api-context/src/context.tsx new file mode 100644 index 0000000..7e1de4a --- /dev/null +++ b/projects/client-api-context/src/context.tsx @@ -0,0 +1,322 @@ +// Graphistry React provider + scene component. +// +// Components only — hooks live in hooks.ts, shared types + Context live in +// internal.ts. This split keeps Fast Refresh working when editing either. + +import { + useCallback, + useEffect, + useMemo, + useRef, + useState, + type ReactNode, +} from 'react'; +import { + createRpcClient, + ExternalStore, + SubscriptionManager, + PATH_SELECTION_LABELS, + PATH_LABELS, + PATH_FILTERS, + initialSelection, + initialLabels, + initialFilters, + projectSelection, + projectLabels, + projectFilters, + FRAGMENT_FILTERS, + type RpcClient, + type SelectionSnapshot, + type LabelsSnapshot, + type FiltersSnapshot, +} from '@graphistry/client-api'; +import { Ctx, type ControlledDomain, type GraphistryHandle, type InternalContext } from './internal.js'; +import { GraphistryControlledError, GraphistryRpcError } from './errors.js'; + +const SUBSCRIPTION_API_VERSION = 2; + +const PATH_VIEW = ['workbooks', 'open', 'views', 'current'] as const; +const PATH_FILTERS_ADD = [...PATH_VIEW, 'filters', 'add'] as const; +const PATH_FILTERS_REMOVE = [...PATH_VIEW, 'filters', 'remove'] as const; +const PATH_FILTERS_RESET = [...PATH_VIEW, 'filters', 'reset'] as const; +const PATH_SELECTION_SET_EXTERNAL = [...PATH_VIEW, 'selection', 'setExternal'] as const; + +export interface GraphistryProviderProps { + children?: ReactNode; + /** Graphistry server host, e.g. "hub.graphistry.com". */ + host: string; + /** Dataset id / slug. */ + dataset: string; + /** Escape hatch — full iframe src URL. Overrides host+dataset. */ + src?: string; + /** Extra query-string params appended to the iframe URL. */ + params?: Record; + /** Scene background color (hex, e.g. `#0B0C14`). Applied over RPC after the + * iframe handshake completes (targets falcor path `scene.bg.color`). */ + bg?: string; + /** RPC timeout in ms. Default 30000. */ + rpcTimeoutMs?: number; + /** When set, the filters domain is controlled — hook mutators throw. */ + filters?: readonly string[]; + /** When set, the exclusions domain is controlled. */ + exclusions?: readonly string[]; +} + +function buildSceneSrc(props: GraphistryProviderProps): string { + if (props.src) return props.src; + const base = `https://${props.host.replace(/\/$/, '')}/graph/graph.html`; + const qs = new URLSearchParams(); + qs.set('dataset', props.dataset); + for (const [k, v] of Object.entries(props.params ?? {})) { + if (v !== undefined && v !== null) qs.set(k, String(v)); + } + return `${base}?${qs.toString()}`; +} + +function deriveControlled(props: GraphistryProviderProps): Set { + const out = new Set(); + if (props.filters !== undefined) out.add('filters'); + if (props.exclusions !== undefined) out.add('exclusions'); + return out; +} + +export function GraphistryProvider(props: GraphistryProviderProps) { + const { children, rpcTimeoutMs } = props; + + const subs = useRef(null); + const storesRef = useRef(null); + + if (subs.current === null) { + const manager = new SubscriptionManager(); + const selection = new ExternalStore({ + initial: initialSelection, + arrayKeys: ['points', 'edges', 'pointLabels', 'edgeLabels'], + onFirstListener: () => manager.acquire(PATH_SELECTION_LABELS), + onLastListener: () => manager.release(PATH_SELECTION_LABELS), + }); + const labels = new ExternalStore({ + initial: initialLabels, + arrayKeys: ['labels'], + onFirstListener: () => manager.acquire(PATH_LABELS), + onLastListener: () => manager.release(PATH_LABELS), + }); + const filters = new ExternalStore({ + initial: initialFilters, + arrayKeys: ['filters'], + onFirstListener: () => manager.acquire(PATH_FILTERS), + onLastListener: () => manager.release(PATH_FILTERS), + }); + // v1 hand-enriched paths: no pathSets — iframe uses its custom fragment + publishedPathUpdatedSubject. + // `withColumns: true` asks the iframe-side selection container to expand + // the label fragment to include the row's full column values, so panels + // can render per-node fields (see LocalDataSink.getSelectionLabelFragment). + manager.register(PATH_SELECTION_LABELS, selection, projectSelection, undefined, { withColumns: true }); + manager.register(PATH_LABELS, labels, projectLabels); + // v2 generic path: pathSets sent to iframe, which opens model.get(...) internally. + manager.register(PATH_FILTERS, filters, projectFilters, FRAGMENT_FILTERS); + subs.current = manager; + storesRef.current = { selection, labels, filters }; + } + const stores = storesRef.current!; + + const [rpc, setRpc] = useState(null); + const [subscriptionAPIVersion, setSubscriptionAPIVersion] = useState(null); + const iframeRef = useRef(null); + + const controlled = useMemo( + () => deriveControlled(props), + // eslint-disable-next-line react-hooks/exhaustive-deps + [props.filters, props.exclusions] + ); + + const registerIframe = useCallback((iframe: HTMLIFrameElement | null) => { + if (iframeRef.current && iframeRef.current !== iframe) { + const prev = iframeRef.current as HTMLIFrameElement & { __graphistryCleanup?: () => void }; + prev.__graphistryCleanup?.(); + subs.current?.detachIframe(); + setRpc((r) => { + r?.dispose(); + return null; + }); + setSubscriptionAPIVersion(null); + } + iframeRef.current = iframe; + if (!iframe) return; + + subs.current!.attachIframe(iframe); + setRpc(createRpcClient(iframe, { timeoutMs: rpcTimeoutMs })); + + const onMessage = (e: MessageEvent) => { + if (e.source !== iframe.contentWindow) return; + const data = e.data as { type?: string; agent?: string; subscriptionAPIVersion?: number } | undefined; + if (!data || data.agent !== 'graphistryjs' || data.type !== 'init') return; + console.debug('[ctx] ←iframe init; subscriptionAPIVersion=', data.subscriptionAPIVersion); + if (typeof data.subscriptionAPIVersion === 'number') { + setSubscriptionAPIVersion(data.subscriptionAPIVersion); + } + console.debug('[ctx] →iframe graphistry-init-ack'); + iframe.contentWindow?.postMessage( + { type: 'graphistry-init-ack', agent: 'graphistryjs', subscriptionAPIVersion: SUBSCRIPTION_API_VERSION }, + '*' + ); + }; + window.addEventListener('message', onMessage); + const sendReady = (why: string) => { + console.debug('[ctx] →iframe ready (' + why + ')'); + iframe.contentWindow?.postMessage( + { type: 'ready', agent: 'graphistryjs', subscriptionAPIVersion: SUBSCRIPTION_API_VERSION }, + '*' + ); + }; + sendReady('register'); + const onLoad = () => sendReady('iframe-load'); + iframe.addEventListener('load', onLoad); + (iframe as HTMLIFrameElement & { __graphistryCleanup?: () => void }).__graphistryCleanup = () => { + window.removeEventListener('message', onMessage); + iframe.removeEventListener('load', onLoad); + }; + }, [rpcTimeoutMs]); + + // After RPC mutations, the iframe's generic-subscribe push pipeline does + // not always observe the resulting falcor model change (RPC routes through + // the whitelisted dataSource model; `falcor-update` events fire on the raw + // model). Force a re-fetch on the relevant subscription so panels reflect + // the new state. + // + // For filters specifically, the server's `filters.add` call returns + // `filters[N] = $ref(expressionsById[id])` but NOT the expression data + // itself. A bare `refresh()` re-subscribes on stale cache that still has + // the unresolved ref — the push arrives with `filters[N] = undefined`, + // projectFilters drops it, and the new filter never renders. Priming + // via an rpc.get (which invalidates the outer-model cache in + // LocalDataSink before dispatching the request) pulls the expression + // data through the server first, so the follow-up refresh's push has + // complete data. + const refreshAfter = useCallback((p: Promise, path: string): Promise => { + return p.then(async (v) => { + if (path === PATH_FILTERS && rpc) { + try { + await rpc.get([ + ...PATH_VIEW, 'filters', + { from: 0, to: 30 }, + ['id', 'query', 'enabled', 'name', 'dataType', 'level'], + ]); + } catch (_) { /* best-effort prime — swallow */ } + } + subs.current?.refresh(path); + return v; + }); + }, [rpc]); + + const handle = useMemo(() => ({ + rpc, + ready: rpc !== null, + subscriptionAPIVersion, + addFilter: (expr) => { + if (controlled.has('filters')) throw new GraphistryControlledError('filters', 'filters'); + if (!rpc) return Promise.reject(new Error('Graphistry iframe not yet mounted; wait for handle.ready.')); + return refreshAfter(rpc.call(PATH_FILTERS_ADD, [expr]), PATH_FILTERS); + }, + removeFilter: (id) => { + if (controlled.has('filters')) throw new GraphistryControlledError('filters', 'filters'); + if (!rpc) return Promise.reject(new Error('Graphistry iframe not yet mounted; wait for handle.ready.')); + return refreshAfter(rpc.call(PATH_FILTERS_REMOVE, [id]), PATH_FILTERS); + }, + resetFilters: () => { + if (controlled.has('filters')) throw new GraphistryControlledError('filters', 'filters'); + if (!rpc) return Promise.reject(new Error('Graphistry iframe not yet mounted; wait for handle.ready.')); + return refreshAfter(rpc.call(PATH_FILTERS_RESET, []), PATH_FILTERS); + }, + setSelectionExternal: (points, edges, darken) => { + if (controlled.has('selection')) throw new GraphistryControlledError('selection', 'selection'); + if (!rpc) return Promise.reject(new Error('Graphistry iframe not yet mounted; wait for handle.ready.')); + return refreshAfter(rpc.call(PATH_SELECTION_SET_EXTERNAL, [Array.from(points), Array.from(edges), !!darken]), PATH_SELECTION_LABELS); + }, + }), [rpc, subscriptionAPIVersion, controlled, refreshAfter]); + + const sceneSrc = useMemo( + () => buildSceneSrc(props), + // eslint-disable-next-line react-hooks/exhaustive-deps + [props.host, props.dataset, props.src, props.params] + ); + + // Scene-level RPC props get pushed into the falcor model once the iframe + // handshake completes. The live-reactive path is `scene.renderer.background.color` + // (see apps/core/viz/src/models/scene/scene.js — `scene.bg.color` is a $ref + // to `scene.renderer.background`, not the color leaf, so writing there + // does not propagate to the rendering component). + // + // Gated on subscriptionAPIVersion rather than just `rpc` because `rpc` is + // non-null the moment registerIframe runs (before iframe.contentWindow has + // loaded its LocalDataSink listener). Firing too early drops the message + // into a pre-handshake void — same failure mode as early subscribes. + const { bg } = props; + useEffect(() => { + if (!rpc || subscriptionAPIVersion === null || bg === undefined) return; + console.debug('[GraphistryProvider] →iframe set scene.renderer.background.color =', bg); + rpc.set({ + paths: [[...PATH_VIEW, 'scene', 'renderer', 'background', 'color']], + jsonGraph: { workbooks: { open: { views: { current: { scene: { renderer: { background: { color: bg } } } } } } } }, + }).catch((err) => { + // StrictMode disposes the first rpc before its response lands; second + // mount carries a fresh rpc and resolves normally. + if (err instanceof GraphistryRpcError && err.kind === 'disposed') return; + console.warn('[GraphistryProvider] bg set failed:', err); + }); + }, [rpc, subscriptionAPIVersion, bg]); + + const value = useMemo(() => ({ + stores, + handle, + controlled, + registerIframe, + sceneSrc, + }), [stores, handle, controlled, registerIframe, sceneSrc]); + + return {children}; +} + +export interface GraphistrySceneProps extends React.IframeHTMLAttributes { + src?: string; +} + +export function GraphistryScene({ src, style, ...rest }: GraphistrySceneProps) { + // Only pull the stable bits out of context. `registerIframe` is a stable + // useCallback inside the Provider; `sceneSrc` only changes when the + // Provider's host/dataset props change. Depending on the whole `ctx` + // object would regenerate this component's ref callback on every + // Provider re-render, which in React 19 triggers re-attach cycles and + // an infinite setState loop through registerIframe → setRpc. + const ctx = useContextOrThrow(); + const { registerIframe, sceneSrc: defaultSrc } = ctx; + const ref = useRef(null); + + const setRef = useCallback((el: HTMLIFrameElement | null) => { + if (ref.current && ref.current !== el) { + (ref.current as HTMLIFrameElement & { __graphistryCleanup?: () => void }).__graphistryCleanup?.(); + registerIframe(null); + } + ref.current = el; + if (el) registerIframe(el); + }, [registerIframe]); + + return ( +