From 50e04525cd358b7524fb8c536987e9932f7d66c5 Mon Sep 17 00:00:00 2001 From: Kevin Date: Fri, 14 Aug 2026 16:27:42 -0700 Subject: [PATCH] feat: give the provider plugin a real Workloads support view Replaces the data-only ui/provider plugin with an actual staff support view: a Workloads list for a project and a per-workload detail page, covering the health/config/placements/instances/conditions surface a support engineer needs to answer "why isn't this workload starting." Built against staff-portal's new portal.page/project extension kind, fetching through staff-portal's own /api/internal proxy under the viewing staff member's session (no new credential). Uses datum-ui components throughout (Tabs, Table, EmptyContent, CodeEditor) rather than hand-rolled ones, documented in the new ui/CLAUDE.md alongside the "no Tailwind build of its own" constraint this plugin runs under. --- ui/CLAUDE.md | 41 ++ ui/provider/README.md | 82 ++-- ui/provider/bun.lock | 376 +++++++++++++++++- ui/provider/index.html | 14 +- ui/provider/package.json | 27 +- ui/provider/public/plugin-manifest.json | 23 +- ui/provider/src/adapter.ts | 294 ++++++++++++++ .../src/components/conditions-table.tsx | 62 +++ ui/provider/src/components/detail-list.tsx | 75 ++++ ui/provider/src/components/stat-strip.tsx | 30 ++ ui/provider/src/components/states.tsx | 81 ++++ ui/provider/src/lib/api.ts | 154 +++++++ ui/provider/src/main.tsx | 44 ++ ui/provider/src/pages/workload-detail.tsx | 361 +++++++++++++++++ ui/provider/src/pages/workload-list.tsx | 95 +++++ ui/provider/src/schema.ts | 128 ++++++ ui/provider/tsconfig.json | 9 +- ui/provider/vite.config.ts | 48 ++- 18 files changed, 1865 insertions(+), 79 deletions(-) create mode 100644 ui/CLAUDE.md create mode 100644 ui/provider/src/adapter.ts create mode 100644 ui/provider/src/components/conditions-table.tsx create mode 100644 ui/provider/src/components/detail-list.tsx create mode 100644 ui/provider/src/components/stat-strip.tsx create mode 100644 ui/provider/src/components/states.tsx create mode 100644 ui/provider/src/lib/api.ts create mode 100644 ui/provider/src/main.tsx create mode 100644 ui/provider/src/pages/workload-detail.tsx create mode 100644 ui/provider/src/pages/workload-list.tsx create mode 100644 ui/provider/src/schema.ts diff --git a/ui/CLAUDE.md b/ui/CLAUDE.md new file mode 100644 index 00000000..31b59287 --- /dev/null +++ b/ui/CLAUDE.md @@ -0,0 +1,41 @@ +# ui/consumer and ui/provider — portal plugin conventions + +Both plugins here (`ui/consumer` for cloud-portal, `ui/provider` for +staff-portal) are Module Federation remotes rendered directly into a host +portal's DOM at runtime. Two things follow from that which aren't obvious +from reading either plugin's own source in isolation: + +## Prefer datum-ui components over hand-rolled ones + +Before writing a custom component (tabs, tables, empty states, code/YAML +viewers, stat rows, etc.), check `datum-ui/packages/datum-ui/src/components/` +(base/ and features/) for an existing one — `tabs`, `table`, +`empty-content` (the real "coming soon"/empty-state component), and +`code-editor` (read-only YAML/JSON display) all already exist there and +should be used instead of reimplementing the same thing with plain divs. + +Only import `@datum-cloud/datum-ui/` names that the target host's +`federation-host.ts` actually lists in its shared config — cloud-portal and +staff-portal each curate their own subset, and it differs between them. +Anything outside that list still works, it just bundles its own copy in the +plugin's remote instead of sharing the host's singleton instance. + +## Neither plugin has a Tailwind build of its own + +There's no `tailwind.config`/`postcss.config`/`tailwindcss` package in either +`ui/consumer` or `ui/provider`, and no CSS file of their own — a plugin's +JSX renders into the *host's* DOM, so a Tailwind class only takes visual +effect if that exact class string already happens to exist in the host's own +compiled CSS. Common/simple utilities (`flex`, `grid`, `gap-3`, `grid-cols-2`, +etc.) usually coincidentally exist there because most real apps use them +somewhere. **Arbitrary-value classes almost never do** — +`grid-cols-[minmax(0,1fr)_90px]`, `min-w-[200px]`, `tracking-[0.03em]`, +`max-h-[600px]`, etc. — and they fail *silently*: no error, the class is just +absent from the host's stylesheet, so the layout collapses or the style is +simply missing. + +**Use datum-ui components first** (their own classes ship in datum-ui's +compiled CSS, so they're guaranteed to work). If a genuinely custom layout is +unavoidable, use an inline `style={{...}}` for anything beyond a handful of +extremely common utility classes — don't reach for a bracketed arbitrary +value. diff --git a/ui/provider/README.md b/ui/provider/README.md index 34e4fd67..be19bc1a 100644 --- a/ui/provider/README.md +++ b/ui/provider/README.md @@ -2,40 +2,54 @@ Compute-authored UI meant for a host other than cloud-portal — as opposed to [`ui/consumer`](../consumer), which holds compute's own cloud-portal-facing -plugin(s). This directory *is* the plugin (no further nesting): it declares a -`portal.resource/platform` extension so `staff-portal`'s -`/customers/resources` page can list compute Workloads (`compute.datumapis.com`) -across every project as a Type filter option, alongside its native AI -Edge/DNS/Domain types — shipped as a **Module Federation remote** loaded by -`staff-portal`'s plugin host (`app/modules/plugins/` there, ported from -cloud-portal's -[Portal Plugin System](https://github.com/datum-cloud/cloud-portal/blob/main/docs/enhancements/portal-plugin-system.md)). - -## No page, no component — manifest only - -Unlike a typical portal plugin (and unlike this repo's own -`compute/ui/consumer`, the per-project consumer-facing version of -this same data), this plugin exposes nothing at all. `portal.resource/platform` -is a data-only extension: it declares a label, an icon name, and the -`search.miloapis.com` target GVK (`{group, version, kind}`), and staff-portal -runs the search itself — with the *viewing staff user's own* credentials — -and renders the rows in its own trusted table. See -`staff-portal/app/modules/plugins/types.ts`'s `ResourcePlatformExtension` for -the full design and its trust-boundary reasoning, and -`staff-portal/app/routes/customer/resource/index.tsx` for where it's consumed. - -`public/plugin-manifest.json` is the entire plugin. `exposedModules` is `{}` -and there's no `src/` — nothing here executes at runtime. `vite.config.ts` -still runs a full Module Federation build (a valid, empty remote) since the -host's plugin registry pipeline expects a working `remoteEntry.js` to exist, -even though it's never actually fetched unless this plugin grows a page. +plugin(s). This directory *is* the plugin (no further nesting): a Module +Federation remote loaded by `staff-portal`'s plugin host +(`app/modules/plugins/` there, ported from cloud-portal's +[Portal Plugin System](https://github.com/datum-cloud/cloud-portal/blob/main/docs/enhancements/portal-plugin-system.md)), +declaring three extensions: + +- **`portal.resource/platform`** — a data-only extension: label, icon, and the + `search.miloapis.com` target GVK for compute Workloads + (`compute.datumapis.com`). staff-portal runs the search itself — with the + *viewing staff user's own* credentials — and lists Workloads as a Type + filter option on `/customers/resources`, across every project. See + `staff-portal/app/modules/plugins/types.ts`'s `ResourcePlatformExtension` + for the full design and its trust-boundary reasoning. +- **`portal.page/project`** (`WorkloadList`, `src/pages/workload-list.tsx`, + path `""` — the mount's index) — every Workload in one project, linking + into `WorkloadDetail` below. Reached from staff-portal's own project detail + nav (a native "Compute › Workloads" tab pointing at the plugin mount). +- **`portal.page/project`** (`WorkloadDetail`, `src/pages/workload-detail.tsx`, + path `:workloadName`) — the actual support view for a single Workload, + reached either from `WorkloadList` or by clicking a Workload row on + `/customers/resources`. + +Both pages are mounted under +`/customers/projects/:projectName/plugins//…` by staff-portal's +project-scoped plugin mount — `projectName` reaches them via `useParams()` +resolving the ancestor route match (shared react-router singleton, no extra +plumbing), and `:workloadName` (on the detail page only) from that +extension's own declared `path`. + +## The support view + +Built for a staff member fielding "why isn't my workload starting" / "what's +wrong with this workload" from a customer, not for general browsing — +Overview and Instances surface raw conditions (type/status/reason/message), +placements, network assignments, and scheduling gates, not just a coarse +health enum. Events/Logs/Metrics are honest "Coming Soon" placeholders (no +data source wired up for any of the three yet); YAML dumps the raw resource +(minus `metadata.managedFields`) as an escape hatch. All data is read client-side, polled via +`refetchInterval` (`src/lib/api.ts`) through staff-portal's own same-origin +proxy — no new credential, no plugin-owned backend. ## Local dev ``` bun install bun run build -bun run preview # built dist/ served at :5199 — see below for why not `dev` +bun run preview # built dist/ served at :5199 +bun run dev # standalone preview harness at :5199, direct (no proxy) ``` ### Serve `dev` or `preview`? @@ -43,10 +57,10 @@ bun run preview # built dist/ served at :5199 — see below for why not `dev` staff-portal loads plugin assets through its **same-origin asset proxy** (`/api/plugins/workloads/…`), never directly from `:5199`. `dev` (Vite, HMR) emits a remote entry with host-absolute chunk URLs that 404 once proxied; -`preview` (built) is proxy-relative and safe. Since this plugin has no page to -preview standalone anyway, always use `build && preview` — same rule as the -sibling `compute/ui/consumer` plugin, see its README for the full -explanation. +`preview` (built) is proxy-relative and safe. Use `dev` only for the +standalone harness at `http://localhost:5199/` (direct, no proxy — data +calls 404 there since there's no staff-portal proxy to reach); use +`build && preview` whenever staff-portal will load the plugin. To register it with a local staff-portal: @@ -59,5 +73,5 @@ bun run build && bun run preview PORTAL_PLUGINS=workloads=http://localhost:5199 ``` -then load `/customers/resources` in staff-portal and check "Workload" appears -in the Type filter. +then load `/customers/resources` in staff-portal, check "Workload" appears in +the Type filter, and click a Workload row to reach the support view. diff --git a/ui/provider/bun.lock b/ui/provider/bun.lock index 69d58caa..da5a5227 100644 --- a/ui/provider/bun.lock +++ b/ui/provider/bun.lock @@ -4,13 +4,91 @@ "workspaces": { "": { "name": "@datum-cloud/workloads-provider-plugin", + "dependencies": { + "@datum-cloud/datum-ui": "^1.7.1", + "@monaco-editor/react": "^4.7.0", + "@tanstack/react-query": "5.101.0", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "js-yaml": "^5.2.2", + "lucide-react": "^1.21.0", + "monaco-editor": "^0.55.0", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-router": "7.18.0", + "sonner": "^2.0.7", + "zod": "^4.4.3", + }, "devDependencies": { "@module-federation/vite": "^1.16.16", + "@types/js-yaml": "^4.0.9", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.2.0", + "typescript": "^6.0.3", "vite": "^7.3.5", }, }, }, "packages": { + "@asamuzakjp/css-color": ["@asamuzakjp/css-color@6.0.7", "", { "dependencies": { "@csstools/css-calc": "^3.3.0", "@csstools/css-color-parser": "^4.1.10", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "lru-cache": "^11.5.2" } }, "sha512-vC/bk1Lz7Tn/EfU9/apOTBk80/8dyGyWMowPoV1tJ52muDGsDqt2HPT2klrFUiY60MQmQv9q8yIht15JnBgDGw=="], + + "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@8.3.2", "", { "dependencies": { "bidi-js": "^1.0.3", "css-tree": "^3.2.1", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.5.2" } }, "sha512-93Z1N+BQNXysodoicpOIyNh2drHfz/CTf9nnT0FEx72GJcIiwgydD7tGAr78j41LsYn3hlRn+LdGPuBLn1Bl8Q=="], + + "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="], + + "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + + "@babel/generator": ["@babel/generator@7.29.8", "", { "dependencies": { "@babel/parser": "^7.29.8", "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="], + + "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="], + + "@babel/parser": ["@babel/parser@7.29.8", "", { "dependencies": { "@babel/types": "^7.29.8" }, "bin": "./bin/babel-parser.js" }, "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q=="], + + "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="], + + "@babel/traverse": ["@babel/traverse@7.29.8", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.8", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.8", "@babel/template": "^7.29.7", "@babel/types": "^7.29.8", "debug": "^4.3.1" } }, "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg=="], + + "@babel/types": ["@babel/types@7.29.8", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg=="], + + "@bramus/specificity": ["@bramus/specificity@2.4.2", "", { "dependencies": { "css-tree": "^3.0.0" }, "bin": { "specificity": "bin/cli.js" } }, "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw=="], + + "@csstools/color-helpers": ["@csstools/color-helpers@6.1.0", "", {}, "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg=="], + + "@csstools/css-calc": ["@csstools/css-calc@3.3.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ=="], + + "@csstools/css-color-parser": ["@csstools/css-color-parser@4.1.10", "", { "dependencies": { "@csstools/color-helpers": "^6.1.0", "@csstools/css-calc": "^3.3.0" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], + + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.1.7", "", { "peerDependencies": { "css-tree": "^3.2.1" }, "optionalPeers": ["css-tree"] }, "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig=="], + + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + + "@datum-cloud/datum-ui": ["@datum-cloud/datum-ui@1.7.1", "", { "dependencies": { "@radix-ui/react-avatar": "^1.2.6", "@radix-ui/react-checkbox": "^1.3.11", "@radix-ui/react-collapsible": "^1.1.20", "@radix-ui/react-dialog": "^1.1.23", "@radix-ui/react-dropdown-menu": "^2.1.24", "@radix-ui/react-hover-card": "^1.1.23", "@radix-ui/react-label": "^2.1.15", "@radix-ui/react-popover": "^1.1.23", "@radix-ui/react-radio-group": "^1.4.7", "@radix-ui/react-select": "^2.3.7", "@radix-ui/react-separator": "^1.1.15", "@radix-ui/react-slot": "^1.3.3", "@radix-ui/react-switch": "^1.3.7", "@radix-ui/react-tabs": "^1.1.21", "@radix-ui/react-tooltip": "^1.2.16", "@radix-ui/react-visually-hidden": "^1.2.11", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "isomorphic-dompurify": "^3.21.0", "tailwind-merge": "^3.6.0", "tw-animate-css": "^1.4.0" }, "peerDependencies": { "@conform-to/react": ">=1.19.4 <2", "@conform-to/zod": ">=1.19.4 <2", "@dnd-kit/core": ">=6.3.1 <7", "@dnd-kit/sortable": ">=10 <11", "@hookform/resolvers": ">=5.4.0 <6", "@monaco-editor/react": ">=4.7.0 <5", "@stepperize/react": ">=7 <8", "@streamdown/code": ">=1 <2", "@tanstack/react-table": ">=8.21.3 <9", "@tanstack/react-virtual": ">=3.14.3 <4", "@tiptap/extension-character-count": ">=3.27.1 <4", "@tiptap/extension-link": ">=3.27.1 <4", "@tiptap/extension-placeholder": ">=3.27.1 <4", "@tiptap/extension-underline": ">=3.27.1 <4", "@tiptap/react": ">=3.27.1 <4", "@tiptap/starter-kit": ">=3.27.1 <4", "ai": ">=7 <8", "date-fns": ">=4.1.0 <5", "date-fns-tz": ">=3 <4", "js-yaml": ">=5.2.2 <6", "leaflet": ">=1.9 <2", "leaflet-draw": ">=1 <2", "leaflet.fullscreen": ">=5 <6", "leaflet.markercluster": ">=1.5.3 <2", "lucide-react": ">=1 <2", "monaco-editor": ">=0.44.0 <1", "motion": ">=12 <13", "nprogress": ">=0.2 <1", "nuqs": ">=2 <3", "react": ">=19.2.7 <20", "react-day-picker": ">=10 <11", "react-dom": ">=19.2.7 <20", "react-dropzone": ">=15 <21", "react-hook-form": ">=7.80.0 <8", "react-leaflet": ">=5 <6", "react-leaflet-markercluster": ">=5.0.0-rc.0 <6", "react-number-format": ">=5.4.5 <6", "recharts": ">=3 <4", "sonner": ">=2.0.7 <3", "streamdown": ">=2 <3", "zod": ">=4 <5" }, "optionalPeers": ["@conform-to/react", "@conform-to/zod", "@dnd-kit/core", "@dnd-kit/sortable", "@hookform/resolvers", "@monaco-editor/react", "@stepperize/react", "@streamdown/code", "@tanstack/react-table", "@tanstack/react-virtual", "@tiptap/extension-character-count", "@tiptap/extension-link", "@tiptap/extension-placeholder", "@tiptap/extension-underline", "@tiptap/react", "@tiptap/starter-kit", "ai", "date-fns", "date-fns-tz", "js-yaml", "leaflet", "leaflet-draw", "leaflet.fullscreen", "leaflet.markercluster", "monaco-editor", "motion", "nprogress", "nuqs", "react-day-picker", "react-dropzone", "react-hook-form", "react-leaflet", "react-leaflet-markercluster", "react-number-format", "recharts", "sonner", "streamdown", "zod"] }, "sha512-D2e1tIc+6qzrqumRtwbmvfzP56b6o71XfNPykx77/i1gqbrmpm0wK4m9rvjNqzHY8B00VHfakvmtmmGd3Aua7g=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], @@ -63,6 +141,26 @@ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], + "@exodus/bytes": ["@exodus/bytes@1.15.1", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q=="], + + "@floating-ui/core": ["@floating-ui/core@1.8.0", "", { "dependencies": { "@floating-ui/utils": "^0.2.12" } }, "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ=="], + + "@floating-ui/dom": ["@floating-ui/dom@1.8.0", "", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], + + "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.9", "", { "dependencies": { "@floating-ui/dom": "^1.8.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg=="], + + "@floating-ui/utils": ["@floating-ui/utils@0.2.12", "", {}, "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@module-federation/dts-plugin": ["@module-federation/dts-plugin@2.8.1", "", { "dependencies": { "@module-federation/error-codes": "2.8.1", "@module-federation/managers": "2.8.1", "@module-federation/sdk": "2.8.1", "@module-federation/third-party-dts-extractor": "2.8.1", "adm-zip": "0.6.0", "isomorphic-ws": "5.0.0", "undici": "7.28.0", "ws": "8.21.0" }, "peerDependencies": { "typescript": "^4.9.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", "vue-tsc": ">=1.0.24" }, "optionalPeers": ["vue-tsc"] }, "sha512-JM8g76KzhhH44kHvM2JPJ5FIlQDwUNzw0vvq5EDSo/znNUmUEuSrfTssukCKg1nqnBGWLohjIV0LOOhLdCknoQ=="], "@module-federation/error-codes": ["@module-federation/error-codes@2.8.1", "", {}, "sha512-0mQ+bWt1LRCZyURx3g2b8G+aAlvk8iXIgrp3Jit/75blrlVda/eVqnHz1L+YOxwkP3xSrdbUb4423AoWti31ZQ=="], @@ -79,8 +177,98 @@ "@module-federation/vite": ["@module-federation/vite@1.20.2", "", { "dependencies": { "@module-federation/dts-plugin": "2.8.1", "@module-federation/runtime": "2.8.1", "@module-federation/sdk": "2.8.1" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-Io7nk2pcgTRdrGivJ0/xbfCYSNSVlh2uZmfZdBQjc5/E0EJ9XwMVEOipSGRMgwNrp7gPoGz+NR7AR9lmg0vXqg=="], + "@monaco-editor/loader": ["@monaco-editor/loader@1.7.0", "", { "dependencies": { "state-local": "^1.0.6" } }, "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA=="], + + "@monaco-editor/react": ["@monaco-editor/react@4.7.0", "", { "dependencies": { "@monaco-editor/loader": "^1.5.0" }, "peerDependencies": { "monaco-editor": ">= 0.25.0 < 1", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA=="], + "@napi-rs/lzma-linux-x64-gnu": ["@napi-rs/lzma-linux-x64-gnu@1.5.1", "", { "os": "linux", "cpu": "x64" }, "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ=="], + "@radix-ui/number": ["@radix-ui/number@1.1.3", "", {}, "sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA=="], + + "@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="], + + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA=="], + + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA=="], + + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ=="], + + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.20", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q=="], + + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.15", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA=="], + + "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.5", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA=="], + + "@radix-ui/react-context": ["@radix-ui/react-context@1.2.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA=="], + + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA=="], + + "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg=="], + + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-effect-event": "0.0.5" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w=="], + + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-menu": "2.1.24", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g=="], + + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.6", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ=="], + + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.16", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ=="], + + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ=="], + + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.4", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA=="], + + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g=="], + + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.24", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA=="], + + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.23", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ=="], + + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.3.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-rect": "1.1.4", "@radix-ui/react-use-size": "1.1.4", "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg=="], + + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.17", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ=="], + + "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.10", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw=="], + + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.10", "", { "dependencies": { "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg=="], + + "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.4.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ=="], + + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ=="], + + "@radix-ui/react-select": ["@radix-ui/react-select@2.3.7", "", { "dependencies": { "@radix-ui/number": "1.1.3", "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-focus-guards": "1.1.6", "@radix-ui/react-focus-scope": "1.1.16", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-use-previous": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.7.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg=="], + + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.15", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw=="], + + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q=="], + + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.3.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-size": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw=="], + + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.21", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-roving-focus": "1.1.19", "@radix-ui/react-use-controllable-state": "1.2.6" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog=="], + + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-dismissable-layer": "1.1.19", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-popper": "1.3.7", "@radix-ui/react-portal": "1.1.17", "@radix-ui/react-presence": "1.1.10", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-slot": "1.3.3", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-layout-effect": "1.1.4", "@radix-ui/react-visually-hidden": "1.2.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg=="], + + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ=="], + + "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-use-effect-event": "0.0.5", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ=="], + + "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.5", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg=="], + + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw=="], + + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw=="], + + "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.4", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg=="], + + "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.4", "", { "dependencies": { "@radix-ui/rect": "1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ=="], + + "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.4", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw=="], + + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.11", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ=="], + + "@radix-ui/rect": ["@radix-ui/rect@1.1.3", "", {}, "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.4", "", { "os": "android", "cpu": "arm" }, "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.4", "", { "os": "android", "cpu": "arm64" }, "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A=="], @@ -131,78 +319,222 @@ "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.62.4", "", { "os": "win32", "cpu": "x64" }, "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q=="], + "@tanstack/query-core": ["@tanstack/query-core@5.101.0", "", {}, "sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow=="], + + "@tanstack/react-query": ["@tanstack/react-query@5.101.0", "", { "dependencies": { "@tanstack/query-core": "5.101.0" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-rLlJXSpkqfizLWgkR5+eLeIk0MvTx/meEIR7LRjxic+qxiQP8zVjq7BqQkiCMNLQBlLfuOLqqr6KO5GtrDlmSg=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - "@typescript/typescript-aix-ppc64": ["@typescript/typescript-aix-ppc64@7.0.2", "", { "os": "aix", "cpu": "ppc64" }, "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ=="], + "@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="], - "@typescript/typescript-darwin-arm64": ["@typescript/typescript-darwin-arm64@7.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA=="], + "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], - "@typescript/typescript-darwin-x64": ["@typescript/typescript-darwin-x64@7.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA=="], + "@types/react-dom": ["@types/react-dom@19.2.4", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-Bsc+QHgp+P/F02XDzNCY9jnZNCUuLki36KT7VKrTXXLdHf+vHMNZnW1rVu5DNW/rCK+fya3DATySbLM4yhtKUw=="], - "@typescript/typescript-freebsd-arm64": ["@typescript/typescript-freebsd-arm64@7.0.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ=="], + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], - "@typescript/typescript-freebsd-x64": ["@typescript/typescript-freebsd-x64@7.0.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], - "@typescript/typescript-linux-arm": ["@typescript/typescript-linux-arm@7.0.2", "", { "os": "linux", "cpu": "arm" }, "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ=="], + "adm-zip": ["adm-zip@0.6.0", "", {}, "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg=="], - "@typescript/typescript-linux-arm64": ["@typescript/typescript-linux-arm64@7.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], - "@typescript/typescript-linux-loong64": ["@typescript/typescript-linux-loong64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ=="], + "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="], - "@typescript/typescript-linux-mips64el": ["@typescript/typescript-linux-mips64el@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.13", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ=="], - "@typescript/typescript-linux-ppc64": ["@typescript/typescript-linux-ppc64@7.0.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA=="], + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], - "@typescript/typescript-linux-riscv64": ["@typescript/typescript-linux-riscv64@7.0.2", "", { "os": "linux", "cpu": "none" }, "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ=="], + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], - "@typescript/typescript-linux-s390x": ["@typescript/typescript-linux-s390x@7.0.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001809", "", {}, "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ=="], - "@typescript/typescript-linux-x64": ["@typescript/typescript-linux-x64@7.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A=="], + "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="], - "@typescript/typescript-netbsd-arm64": ["@typescript/typescript-netbsd-arm64@7.0.2", "", { "os": "none", "cpu": "arm64" }, "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], - "@typescript/typescript-netbsd-x64": ["@typescript/typescript-netbsd-x64@7.0.2", "", { "os": "none", "cpu": "x64" }, "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA=="], + "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], - "@typescript/typescript-openbsd-arm64": ["@typescript/typescript-openbsd-arm64@7.0.2", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ=="], + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], - "@typescript/typescript-openbsd-x64": ["@typescript/typescript-openbsd-x64@7.0.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg=="], + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], - "@typescript/typescript-sunos-x64": ["@typescript/typescript-sunos-x64@7.0.2", "", { "os": "sunos", "cpu": "x64" }, "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g=="], + "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], - "@typescript/typescript-win32-arm64": ["@typescript/typescript-win32-arm64@7.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "@typescript/typescript-win32-x64": ["@typescript/typescript-win32-x64@7.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g=="], + "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="], - "adm-zip": ["adm-zip@0.6.0", "", {}, "sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg=="], + "date-fns": ["date-fns@4.4.0", "", {}, "sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w=="], + + "date-fns-tz": ["date-fns-tz@3.2.0", "", { "peerDependencies": { "date-fns": "^3.0.0 || ^4.0.0" } }, "sha512-sg8HqoTEulcbbbVXeg84u5UnlsQa8GS5QXMqjjYIhS4abEVVKIUwe0/l/UhrZdKaL/W5eWZNlbTeEIiOXTcsBQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + + "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], + + "dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.405", "", {}, "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew=="], + + "entities": ["entities@8.0.0", "", {}, "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA=="], "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], + + "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + + "isomorphic-dompurify": ["isomorphic-dompurify@3.22.0", "", { "dependencies": { "dompurify": "^3.4.12", "jsdom": "^30.0.0" } }, "sha512-cz/BkSODnQir4IV2quqbuEHhAGz4MAYGvhoemsxuwMpY/oZbRHlNjPLPyIQ0fdULiwDPUE+OHPwWb6XTEiNk1A=="], + "isomorphic-ws": ["isomorphic-ws@5.0.0", "", { "peerDependencies": { "ws": "*" } }, "sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "js-yaml": ["js-yaml@5.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.mjs" } }, "sha512-muutsYr+e2+d3rTgUGslq5rxbBlUy3cJ61IsHag2QNDQV+7zXWjkUpmALIajhrlLlrgRUiymj6U3zUr/TMK84Q=="], + + "jsdom": ["jsdom@30.0.1", "", { "dependencies": { "@asamuzakjp/css-color": "^6.0.5", "@asamuzakjp/dom-selector": "^8.3.0", "@bramus/specificity": "^2.4.2", "@csstools/css-syntax-patches-for-csstree": "^1.1.7", "@exodus/bytes": "^1.15.1", "css-tree": "^3.2.1", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.5.2", "parse5": "^8.0.1", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.2", "undici": "^8.9.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^17.1.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.2.3" }, "optionalPeers": ["canvas"] }, "sha512-52v7mUVUfNQVYYqE1lcdaymWL0njO7lTLUog6ZvW2U5KsbiLk/GnZlVJ+qx0xfNJZ6Gn+KSpPNE52vurbxZwrA=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@1.31.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg=="], + + "marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], + + "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + + "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "nanoid": ["nanoid@3.3.17", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g=="], + "node-releases": ["node-releases@2.0.53", "", {}, "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ=="], + + "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="], "postcss": ["postcss@8.5.25", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "react": ["react@19.2.3", "", {}, "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA=="], + + "react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], + + "react-router": ["react-router@7.18.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ=="], + + "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "rollup": ["rollup@4.62.4", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.4", "@rollup/rollup-android-arm64": "4.62.4", "@rollup/rollup-darwin-arm64": "4.62.4", "@rollup/rollup-darwin-x64": "4.62.4", "@rollup/rollup-freebsd-arm64": "4.62.4", "@rollup/rollup-freebsd-x64": "4.62.4", "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", "@rollup/rollup-linux-arm-musleabihf": "4.62.4", "@rollup/rollup-linux-arm64-gnu": "4.62.4", "@rollup/rollup-linux-arm64-musl": "4.62.4", "@rollup/rollup-linux-loong64-gnu": "4.62.4", "@rollup/rollup-linux-loong64-musl": "4.62.4", "@rollup/rollup-linux-ppc64-gnu": "4.62.4", "@rollup/rollup-linux-ppc64-musl": "4.62.4", "@rollup/rollup-linux-riscv64-gnu": "4.62.4", "@rollup/rollup-linux-riscv64-musl": "4.62.4", "@rollup/rollup-linux-s390x-gnu": "4.62.4", "@rollup/rollup-linux-x64-gnu": "4.62.4", "@rollup/rollup-linux-x64-musl": "4.62.4", "@rollup/rollup-openbsd-x64": "4.62.4", "@rollup/rollup-openharmony-arm64": "4.62.4", "@rollup/rollup-win32-arm64-msvc": "4.62.4", "@rollup/rollup-win32-ia32-msvc": "4.62.4", "@rollup/rollup-win32-x64-gnu": "4.62.4", "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg=="], + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "sonner": ["sonner@2.0.8", "", { "peerDependencies": { "@types/react": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UM/ByIoFra8yzV75n1o0Puu0bw5U/9UNnDacrJNspekBewIfsQ3D6ez1nvlWpt7aTsO6rujQtifBpycwIivqlg=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "state-local": ["state-local@1.0.7", "", {}, "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w=="], + + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + + "tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], - "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], + "tldts": ["tldts@7.4.10", "", { "dependencies": { "tldts-core": "^7.4.10" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog=="], + + "tldts-core": ["tldts-core@7.4.10", "", {}, "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw=="], + + "tough-cookie": ["tough-cookie@6.0.2", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA=="], + + "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="], + "update-browserslist-db": ["update-browserslist-db@1.3.1", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ=="], + + "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], + + "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], + "vite": ["vite@7.3.6", "", { "dependencies": { "esbuild": "^0.27.0 || ^0.28.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg=="], + "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + + "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], + + "whatwg-url": ["whatwg-url@17.1.0", "", { "dependencies": { "@exodus/bytes": "^1.15.1", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw=="], + "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], + + "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], + + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], + + "@asamuzakjp/css-color/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + + "@asamuzakjp/dom-selector/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + + "data-urls/whatwg-url": ["whatwg-url@16.0.1", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw=="], + + "isomorphic-dompurify/dompurify": ["dompurify@3.4.13", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ=="], + + "jsdom/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + + "jsdom/undici": ["undici@8.10.0", "", {}, "sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ=="], } } diff --git a/ui/provider/index.html b/ui/provider/index.html index 2d76a570..8098a5ee 100644 --- a/ui/provider/index.html +++ b/ui/provider/index.html @@ -2,14 +2,18 @@ - Workloads resource-type plugin + + Workloads provider plugin — standalone preview +
+ diff --git a/ui/provider/package.json b/ui/provider/package.json index 38827830..318fe128 100644 --- a/ui/provider/package.json +++ b/ui/provider/package.json @@ -1,16 +1,37 @@ { "name": "@datum-cloud/workloads-provider-plugin", "private": true, - "version": "0.2.0", + "version": "0.3.0", "type": "module", - "description": "Workloads resource-type plugin: declares a portal.resource/platform extension so staff-portal's /customers/resources page can list compute Workloads across every project via Milo's search index. No page/nav/component — the host does the query and rendering itself.", + "description": "Workloads resource-type plugin: declares a portal.resource/platform extension so staff-portal's /customers/resources page can list compute Workloads across every project via Milo's search index, and a portal.page/project extension (WorkloadDetail) — a staff support view of a single workload's health, configuration, placements, and instances.", "scripts": { "dev": "vite", "preview": "vite preview", - "build": "vite build" + "build": "vite build", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@datum-cloud/datum-ui": "^1.7.1", + "@monaco-editor/react": "^4.7.0", + "@tanstack/react-query": "5.101.0", + "lucide-react": "^1.21.0", + "monaco-editor": "^0.55.0", + "react": "19.2.3", + "react-dom": "19.2.3", + "date-fns": "^4.1.0", + "date-fns-tz": "^3.2.0", + "js-yaml": "^5.2.2", + "react-router": "7.18.0", + "sonner": "^2.0.7", + "zod": "^4.4.3" }, "devDependencies": { "@module-federation/vite": "^1.16.16", + "@types/js-yaml": "^4.0.9", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.2.0", + "typescript": "^6.0.3", "vite": "^7.3.5" } } diff --git a/ui/provider/public/plugin-manifest.json b/ui/provider/public/plugin-manifest.json index dd95e9fe..901d9d8f 100644 --- a/ui/provider/public/plugin-manifest.json +++ b/ui/provider/public/plugin-manifest.json @@ -1,12 +1,15 @@ { "name": "workloads.staff-portal.datumapis.com", - "version": "0.2.0", + "version": "0.3.0", "sdk": { "name": "@datum-cloud/portal-plugin-sdk", "range": "^1.0.0" }, "remoteEntry": "remoteEntry.js", - "exposedModules": {}, + "exposedModules": { + "WorkloadList": "./src/pages/workload-list.tsx", + "WorkloadDetail": "./src/pages/workload-detail.tsx" + }, "extensions": [ { "type": "portal.resource/platform", @@ -21,6 +24,22 @@ "kind": "Workload" } } + }, + { + "type": "portal.page/project", + "properties": { + "id": "compute-workload-list", + "path": "", + "component": { "$codeRef": "WorkloadList" } + } + }, + { + "type": "portal.page/project", + "properties": { + "id": "compute-workload-detail", + "path": ":workloadName", + "component": { "$codeRef": "WorkloadDetail" } + } } ] } diff --git a/ui/provider/src/adapter.ts b/ui/provider/src/adapter.ts new file mode 100644 index 00000000..a591dfbc --- /dev/null +++ b/ui/provider/src/adapter.ts @@ -0,0 +1,294 @@ +/** + * Raw K8s resource → schema mappers — adapted from `ui/consumer/src/adapter.ts` + * (same hand-written `Raw*` shapes against `api/v1alpha/workload_types.go` / + * `instance_types.go`, no generated-SDK dependency), extended with + * `spec.controller.schedulingGates` and `status.suspended` for Instance — + * both real "why isn't this starting" signals the consumer dashboard doesn't + * surface, but this support view is built around. + */ +import type { Condition, Instance, Workload, WorkloadHealth, WorkloadPlacement } from './schema'; + +// ── Shared ─────────────────────────────────────────────────────────────── + +interface RawCondition { + type?: string; + status?: 'True' | 'False' | 'Unknown'; + reason?: string; + message?: string; + lastTransitionTime?: string; + observedGeneration?: number; +} + +interface RawObjectMeta { + uid?: string; + name?: string; + namespace?: string; + creationTimestamp?: string; + labels?: Record; +} + +interface RawSandboxContainer { + name?: string; + image?: string; +} + +interface RawRuntime { + resources?: { + instanceType?: string; + requests?: Record; + }; + sandbox?: { containers?: RawSandboxContainer[] }; + virtualMachine?: unknown; +} + +function toConditions(conditions: RawCondition[]): Condition[] { + return conditions.map((c) => ({ + type: c.type ?? '', + status: c.status ?? 'Unknown', + reason: c.reason, + message: c.message, + lastTransitionTime: c.lastTransitionTime, + observedGeneration: c.observedGeneration, + })); +} + +function deriveHealth(conditions: RawCondition[]): WorkloadHealth { + if (!conditions || conditions.length === 0) return 'Unknown'; + + const available = conditions.find((c) => c.type === 'Available'); + const progressing = conditions.find((c) => c.type === 'Progressing'); + + if (!available) return 'Unknown'; + if (available.status === 'True') return 'Available'; + if (available.status === 'False' && progressing?.status === 'True') return 'Degraded'; + if (available.status === 'False') return 'Unavailable'; + return 'Unknown'; +} + +// ── Workload ───────────────────────────────────────────────────────────── + +/** Well-known labels stamped onto instances by the compute controllers. */ +export const INSTANCE_LABELS = { + workloadName: 'compute.datumapis.com/workload-name', +} as const; + +interface RawWorkloadPlacement { + name: string; + cityCodes?: string[]; + scaleSettings?: { minReplicas?: number; maxReplicas?: number }; +} + +interface RawWorkloadPlacementStatus { + name?: string; + conditions?: RawCondition[]; + replicas?: number; + currentReplicas?: number; + updatedReplicas?: number; + desiredReplicas?: number; + readyReplicas?: number; +} + +export interface RawWorkload { + metadata?: RawObjectMeta; + spec?: { + template?: { spec?: { runtime?: RawRuntime } }; + placements?: RawWorkloadPlacement[]; + }; + status?: { + conditions?: RawCondition[]; + replicas?: number; + currentReplicas?: number; + updatedReplicas?: number; + desiredReplicas?: number; + readyReplicas?: number; + placements?: RawWorkloadPlacementStatus[]; + }; +} + +export interface RawWorkloadList { + items?: RawWorkload[]; +} + +function deriveResources(runtime?: RawRuntime): string | undefined { + const res = runtime?.resources; + if (!res) return undefined; + + const parts: string[] = []; + if (res.instanceType) parts.push(res.instanceType); + + const requests = res.requests ?? {}; + if (requests.cpu !== undefined) parts.push(`${requests.cpu} vCPU`); + if (requests.memory !== undefined) parts.push(String(requests.memory)); + + return parts.length > 0 ? parts.join(' · ') : undefined; +} + +function deriveReplicasPerRegion(placements: RawWorkloadPlacement[]): number | undefined { + if (!placements || placements.length === 0) return undefined; + + const mins = placements.map((p) => p.scaleSettings?.minReplicas); + const first = mins[0]; + if (first === undefined) return undefined; + + return mins.every((m) => m === first) ? first : undefined; +} + +function toPlacements( + placements: RawWorkloadPlacement[], + statusPlacements: RawWorkloadPlacementStatus[] +): WorkloadPlacement[] { + const statusByName = new Map( + statusPlacements + .filter((s): s is RawWorkloadPlacementStatus & { name: string } => !!s.name) + .map((s) => [s.name, s]) + ); + + return placements.map((p) => { + const status = statusByName.get(p.name); + const conditions = status?.conditions ?? []; + const desired = status?.desiredReplicas ?? p.scaleSettings?.minReplicas ?? 0; + const ready = status?.readyReplicas ?? 0; + const current = status?.currentReplicas ?? 0; + const fromConditions = deriveHealth(conditions); + const health = + fromConditions !== 'Unknown' + ? fromConditions + : desired > 0 && ready >= desired + ? 'Available' + : ready > 0 + ? 'Degraded' + : desired > 0 + ? 'Unavailable' + : 'Unknown'; + + return { + name: p.name, + cityCodes: p.cityCodes ?? [], + readyReplicas: ready, + desiredReplicas: desired, + currentReplicas: current, + health, + conditions: toConditions(conditions), + }; + }); +} + +export function toWorkload(raw: RawWorkload): Workload { + const conditions = raw.status?.conditions ?? []; + const placements = raw.spec?.placements ?? []; + const runtime = raw.spec?.template?.spec?.runtime; + + return { + uid: raw.metadata?.uid ?? '', + name: raw.metadata?.name ?? '', + namespace: raw.metadata?.namespace, + createdAt: raw.metadata?.creationTimestamp + ? new Date(raw.metadata.creationTimestamp) + : new Date(), + image: runtime?.sandbox?.containers?.[0]?.image, + health: deriveHealth(conditions), + currentReplicas: raw.status?.currentReplicas ?? 0, + updatedReplicas: raw.status?.updatedReplicas ?? 0, + readyReplicas: raw.status?.readyReplicas ?? 0, + desiredReplicas: raw.status?.desiredReplicas ?? 0, + placements: toPlacements(placements, raw.status?.placements ?? []), + conditions: toConditions(conditions), + runtimeType: runtime ? (runtime.sandbox ? 'Container sandbox' : 'Virtual machine') : undefined, + regions: Array.from(new Set(placements.flatMap((p) => p.cityCodes ?? []))), + resources: deriveResources(runtime), + replicasPerRegion: deriveReplicasPerRegion(placements), + }; +} + +export function toWorkloadList(items: RawWorkload[]): Workload[] { + return items.map(toWorkload); +} + +// ── Instance ───────────────────────────────────────────────────────────── + +export interface RawInstance { + metadata?: RawObjectMeta; + spec?: { + runtime?: RawRuntime; + controller?: { schedulingGates?: string[] }; + }; + status?: { + conditions?: RawCondition[]; + networkInterfaces?: { + assignments?: { networkIP?: string; externalIP?: string }; + }[]; + suspended?: boolean; + }; +} + +export interface RawInstanceList { + items?: RawInstance[]; +} + +// No explicit "Failed" status field on the API — inferred from the Available +// condition's reason/message text, same heuristic as ui/consumer's adapter. +function deriveInstanceStatus(conditions: RawCondition[]): Instance['status'] { + if (!conditions || conditions.length === 0) return 'Unknown'; + + const available = conditions.find((c) => c.type === 'Available'); + if (!available) return 'Unknown'; + if (available.status === 'True') return 'Available'; + + const text = `${available.reason ?? ''} ${available.message ?? ''}`; + if (/fail|error/i.test(text)) return 'Failed'; + return 'Pending'; +} + +/** Mirrors `instanceTypeCatalog` in `internal/controller/instance_controller.go`. */ +const INSTANCE_TYPE_CATALOG: Record = { + 'datumcloud/d1-standard-2': { cpu: '1', memory: '2Gi' }, + 'd1-standard-2': { cpu: '1', memory: '2Gi' }, +}; + +function resolveInstanceResources(runtime?: RawRuntime): { cpu?: string; memory?: string } { + const requests = runtime?.resources?.requests ?? {}; + const cpu = requests.cpu; + const memory = requests.memory; + if (cpu !== undefined && memory !== undefined) return { cpu, memory }; + + const instanceType = runtime?.resources?.instanceType; + if (instanceType && INSTANCE_TYPE_CATALOG[instanceType]) { + const catalog = INSTANCE_TYPE_CATALOG[instanceType]; + return { cpu: cpu ?? catalog.cpu, memory: memory ?? catalog.memory }; + } + + return { cpu, memory }; +} + +export function toInstance(raw: RawInstance): Instance { + const labels = raw.metadata?.labels ?? {}; + const assignments = raw.status?.networkInterfaces?.[0]?.assignments; + const container = raw.spec?.runtime?.sandbox?.containers?.[0]; + const conditions = raw.status?.conditions ?? []; + const { cpu, memory } = resolveInstanceResources(raw.spec?.runtime); + + return { + uid: raw.metadata?.uid ?? '', + name: raw.metadata?.name ?? '', + namespace: raw.metadata?.namespace, + createdAt: raw.metadata?.creationTimestamp + ? new Date(raw.metadata.creationTimestamp) + : new Date(), + city: labels['compute.datumapis.com/city-code'], + placement: labels['compute.datumapis.com/placement-name'], + instanceType: raw.spec?.runtime?.resources?.instanceType, + cpu, + memory, + image: container?.image, + status: deriveInstanceStatus(conditions), + externalIP: assignments?.externalIP, + internalIP: assignments?.networkIP, + conditions: toConditions(conditions), + schedulingGates: raw.spec?.controller?.schedulingGates ?? [], + suspended: raw.status?.suspended ?? false, + }; +} + +export function toInstanceList(items: RawInstance[]): Instance[] { + return items.map(toInstance); +} diff --git a/ui/provider/src/components/conditions-table.tsx b/ui/provider/src/components/conditions-table.tsx new file mode 100644 index 00000000..0a8ed211 --- /dev/null +++ b/ui/provider/src/components/conditions-table.tsx @@ -0,0 +1,62 @@ +/** + * Raw condition table (Type/Status/Reason/Message/Last Transition) — the + * "why is this broken" surface this support view is built around. Shared by + * the Workload conditions card, per-placement conditions, and the Instances + * tab's per-row condition detail. + */ +import { StatusBadge } from './detail-list'; +import type { Condition } from '../schema'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@datum-cloud/datum-ui/table'; +import { formatDistanceToNowStrict } from 'date-fns'; + +function conditionBadgeType(status: Condition['status']): 'success' | 'danger' | 'muted' { + if (status === 'True') return 'success'; + if (status === 'False') return 'danger'; + return 'muted'; +} + +export function ConditionsTable({ conditions }: { conditions: Condition[] }) { + if (conditions.length === 0) { + return

No conditions reported.

; + } + + return ( +
+ + + + Type + Status + Reason + Message + Last Transition + + + + {conditions.map((c, index) => ( + + {c.type} + + {c.status} + + {c.reason ?? '—'} + {c.message ?? '—'} + + {c.lastTransitionTime + ? formatDistanceToNowStrict(new Date(c.lastTransitionTime), { addSuffix: true }) + : '—'} + + + ))} + +
+
+ ); +} diff --git a/ui/provider/src/components/detail-list.tsx b/ui/provider/src/components/detail-list.tsx new file mode 100644 index 00000000..4eb788a4 --- /dev/null +++ b/ui/provider/src/components/detail-list.tsx @@ -0,0 +1,75 @@ +/** + * Field list — ported verbatim from `ui/consumer/src/components/detail-list.tsx` + * (matches cloud-portal's `app/components/list/list.tsx` styling; staff-portal + * has no equivalent exported to plugins, so this bundles its own copy, same + * convention as the consumer plugin). + */ +import { Badge } from '@datum-cloud/datum-ui/badge'; +import { cn } from '@datum-cloud/datum-ui/utils'; + +export interface DetailListItem { + label: React.ReactNode; + content: React.ReactNode; + hidden?: boolean; + className?: string; +} + +export function DetailList({ + items, + className, + itemClassName, + labelClassName, +}: { + items: DetailListItem[]; + className?: string; + itemClassName?: string; + labelClassName?: string; +}) { + return ( +
+ {items + .filter((item) => !item.hidden) + .map((item, index) => ( +
+
+ {item.label} +
+
+ {item.content} +
+
+ ))} +
+ ); +} + +/** Compact status badge matching portal `BadgeStatus` sizing. */ +export function StatusBadge({ + type, + children, +}: { + type: 'success' | 'warning' | 'danger' | 'info' | 'secondary' | 'muted' | 'primary'; + children: React.ReactNode; +}) { + return ( +
+ + {children} + +
+ ); +} diff --git a/ui/provider/src/components/stat-strip.tsx b/ui/provider/src/components/stat-strip.tsx new file mode 100644 index 00000000..597a1609 --- /dev/null +++ b/ui/provider/src/components/stat-strip.tsx @@ -0,0 +1,30 @@ +/** + * Shared horizontal stat strip — ported verbatim from + * `ui/consumer/src/components/stat-strip.tsx`. + */ +import { cn } from '@datum-cloud/datum-ui/utils'; + +export interface Stat { + label: string; + value: string; + className?: string; +} + +export function StatStrip({ stats, testId }: { stats: Stat[]; testId?: string }) { + return ( +
+
+ {stats.map((s) => ( +
+ + {s.label} + + {s.value} +
+ ))} +
+
+ ); +} diff --git a/ui/provider/src/components/states.tsx b/ui/provider/src/components/states.tsx new file mode 100644 index 00000000..e6f5a1eb --- /dev/null +++ b/ui/provider/src/components/states.tsx @@ -0,0 +1,81 @@ +/** + * Shared loading / error / restricted-access states — ported verbatim from + * `ui/consumer/src/components/states.tsx` (no server loader here either, so + * this is written fresh against the plugin's own `ApiError`). + */ +import { ApiError } from '../lib/api'; +import { Card, CardContent } from '@datum-cloud/datum-ui/card'; +import { Skeleton } from '@datum-cloud/datum-ui/skeleton'; +import { LockIcon, ServerCrashIcon } from 'lucide-react'; + +/** Content-area placeholder only. No page chrome. */ +export function LoadingSkeleton() { + return ( +
+ +
+ + +
+
+ ); +} + +export function RestrictedState({ message }: { message: string }) { + return ( +
+ + + +
+

Access restricted

+

{message}

+
+
+
+
+ ); +} + +export function ErrorState({ error, onRetry }: { error: unknown; onRetry: () => void }) { + const message = error instanceof Error ? error.message : 'An unknown error occurred'; + + return ( +
+ + + +
+

Failed to load

+

{message}

+
+ +
+
+
+ ); +} + +/** + * Renders the restricted state for a 403 `ApiError`, otherwise the generic + * error state. Call once `error` is truthy — keep page chrome outside. + */ +export function ErrorOrRestrictedState({ + error, + restrictedMessage, + onRetry, +}: { + error: unknown; + restrictedMessage: string; + onRetry: () => void; +}) { + if (error instanceof ApiError && error.status === 403) { + return ; + } + return ; +} diff --git a/ui/provider/src/lib/api.ts b/ui/provider/src/lib/api.ts new file mode 100644 index 00000000..4c5d73bb --- /dev/null +++ b/ui/provider/src/lib/api.ts @@ -0,0 +1,154 @@ +/** + * Data-fetching for the provider plugin's Workload detail view. + * + * Every call goes through staff-portal's same-origin proxy at + * `/api/internal/…` (see staff-portal's `app/server/routes/api.ts`), exactly + * like `ui/consumer/src/lib/api.ts` does against cloud-portal's own proxy — + * plain `fetch()` against the compute aggregated apiserver, no plugin-owned + * backend, no new credential (runs under the viewing staff member's own + * session). + * + * `projectName` is resolved via `useParams()` from the host's shared + * react-router singleton — the mount route + * (`/customers/projects/:projectName/plugins/workloads/:workloadName`) puts + * it in scope as an ancestor route param even though this plugin's own + * declared page path only adds `:workloadName`. + * + * `@tanstack/react-query` is a host-shared singleton (see vite.config.ts); + * this plugin must NOT create its own QueryClient. + */ +import { toInstanceList, toWorkload, toWorkloadList, INSTANCE_LABELS } from '../adapter'; +import type { RawInstanceList, RawWorkload, RawWorkloadList } from '../adapter'; +import type { Instance, Workload } from '../schema'; +import { useQuery, type UseQueryResult } from '@tanstack/react-query'; + +export const PLUGIN_ID = 'workloads.staff-portal.datumapis.com'; + +/** Live-ish polling interval — no watch stream in v1. */ +const REFETCH_INTERVAL_MS = 10_000; + +export class ApiError extends Error { + status: number; + + constructor(status: number, message: string) { + super(message); + this.name = 'ApiError'; + this.status = status; + } +} + +function getProjectScopedBase(projectName: string): string { + return `/api/internal/apis/resourcemanager.miloapis.com/v1alpha1/projects/${projectName}/control-plane`; +} + +// v1alpha, NOT v1alpha1 — verified against api/v1alpha/groupversion_info.go. +const WORKLOADS_PATH = '/apis/compute.datumapis.com/v1alpha/namespaces/default/workloads'; +const INSTANCES_PATH = '/apis/compute.datumapis.com/v1alpha/namespaces/default/instances'; + +/** + * Every `/api/internal/*` response is wrapped by staff-portal's own proxy + * (`createSuccessResponse` in `app/server/response.ts`): `{ requestId, code, + * data, path }` — the upstream K8s object lives at `.data`, not the body + * root. Unlike `ui/consumer`'s `/api/proxy/...` (cloud-portal), which passes + * the upstream response through unwrapped. + */ +interface ProxyEnvelope { + data: T; +} + +async function proxyFetch(projectName: string, path: string): Promise { + const url = `${getProjectScopedBase(projectName)}${path}`; + const res = await fetch(url, { headers: { Accept: 'application/json' } }); + if (!res.ok) { + throw new ApiError(res.status, `Request failed (${res.status}): ${path}`); + } + const envelope = (await res.json()) as ProxyEnvelope; + return envelope.data; +} + +async function fetchWorkloads(projectName: string): Promise { + const body = await proxyFetch(projectName, `${WORKLOADS_PATH}?limit=100`); + return toWorkloadList(body.items ?? []); +} + +export function useWorkloads(projectName: string | undefined): UseQueryResult { + return useQuery({ + queryKey: [PLUGIN_ID, 'workloads', projectName], + enabled: !!projectName, + queryFn: () => fetchWorkloads(projectName as string), + refetchInterval: REFETCH_INTERVAL_MS, + retry: false, + }); +} + +/** + * `useWorkload` and `useWorkloadRaw` share this exact query key so they share + * one underlying fetch/cache entry (react-query dedupes by key) — the YAML + * tab needs the unadapted resource and Overview needs the adapted one, but + * there's no reason to hit the same endpoint twice or for one to resolve + * before the other. + */ +function workloadQueryKey(projectName: string | undefined, name: string | undefined) { + return [PLUGIN_ID, 'workload-raw', projectName, name] as const; +} + +async function fetchRawWorkload(projectName: string, name: string): Promise { + return proxyFetch(projectName, `${WORKLOADS_PATH}/${name}`); +} + +export function useWorkload( + projectName: string | undefined, + name: string | undefined +): UseQueryResult { + return useQuery({ + queryKey: workloadQueryKey(projectName, name), + enabled: !!projectName && !!name, + queryFn: () => fetchRawWorkload(projectName as string, name as string), + select: toWorkload, + refetchInterval: REFETCH_INTERVAL_MS, + retry: false, + }); +} + +/** Raw resource, for the YAML tab — same query as {@link useWorkload}, unadapted. */ +export function useWorkloadRaw( + projectName: string | undefined, + name: string | undefined +): UseQueryResult { + return useQuery({ + queryKey: workloadQueryKey(projectName, name), + enabled: !!projectName && !!name, + queryFn: () => fetchRawWorkload(projectName as string, name as string), + refetchInterval: REFETCH_INTERVAL_MS, + retry: false, + }); +} + +function workloadInstancesSelector(workloadName: string): string { + return `${INSTANCE_LABELS.workloadName}=${workloadName}`; +} + +async function fetchWorkloadInstances( + projectName: string, + workloadName: string +): Promise { + const query = new URLSearchParams({ labelSelector: workloadInstancesSelector(workloadName) }); + const body = await proxyFetch( + projectName, + `${INSTANCES_PATH}?${query.toString()}` + ); + return toInstanceList(body.items ?? []); +} + +export function useWorkloadInstances( + projectName: string | undefined, + workloadName: string | undefined +): UseQueryResult { + return useQuery({ + queryKey: [PLUGIN_ID, 'workload-instances', projectName, workloadName], + enabled: !!projectName && !!workloadName, + queryFn: () => fetchWorkloadInstances(projectName as string, workloadName as string), + refetchInterval: REFETCH_INTERVAL_MS, + retry: false, + }); +} diff --git a/ui/provider/src/main.tsx b/ui/provider/src/main.tsx new file mode 100644 index 00000000..0136a30c --- /dev/null +++ b/ui/provider/src/main.tsx @@ -0,0 +1,44 @@ +import WorkloadDetail from './pages/workload-detail'; +import WorkloadList from './pages/workload-list'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { Link, MemoryRouter, Route, Routes } from 'react-router'; + +// Standalone preview only. Wraps the page in a MemoryRouter so `useParams()` +// resolves the same params the host's project-scoped plugin mount would +// supply, and a QueryClientProvider so the data hooks run — exactly what the +// host provides in production. Data calls hit /api/internal/... which 404s +// standalone (no staff-portal proxy), so the page shows its error state; +// that's expected here. Run the full staff-portal to see live data. +const queryClient = new QueryClient(); + +const base = '/customers/projects/:projectName/plugins/workloads'; + +createRoot(document.getElementById('root')!).render( + + +
+

+ Standalone preview — staff-portal loads this plugin via + /plugin-manifest.json and /remoteEntry.js, not this page. + The data tabs show their error state here (no staff-portal proxy). Run the full + staff-portal to see live data. +

+ + + + } /> + } /> + + +
+
+
+); diff --git a/ui/provider/src/pages/workload-detail.tsx b/ui/provider/src/pages/workload-detail.tsx new file mode 100644 index 00000000..f275165e --- /dev/null +++ b/ui/provider/src/pages/workload-detail.tsx @@ -0,0 +1,361 @@ +/** + * `portal.page/project` extension at `:workloadName`, exposed as + * `WorkloadDetail` — the staff-portal support view for a single Workload. + * + * Built for a staff member fielding "why isn't my workload starting" / + * "what's wrong with this workload" from a customer: Overview surfaces raw + * conditions (not just a coarse health enum), Instances is the direct + * per-instance drill-down (image pull failures, crash loops, scheduling + * gates, quota), Events/Logs/Metrics are honest "Coming Soon" placeholders + * since none of those have a data source wired up yet, and YAML gives an + * escape hatch to the raw resource for anything the tabs don't surface. + * + * `projectName` comes from `useParams()` resolving the ancestor route param + * from staff-portal's project-scoped plugin mount + * (`/customers/projects/:projectName/plugins/workloads/:workloadName`) — + * see `../lib/api.ts`'s header comment for why this works with no extra + * prop/context plumbing. + */ +import type { RawWorkload } from '../adapter'; +import { ConditionsTable } from '../components/conditions-table'; +import { DetailList, StatusBadge } from '../components/detail-list'; +import { StatStrip, type Stat } from '../components/stat-strip'; +import { ErrorOrRestrictedState, LoadingSkeleton } from '../components/states'; +import { useWorkload, useWorkloadInstances, useWorkloadRaw } from '../lib/api'; +import { + healthToBadgeType, + instanceStatusToBadgeType, + type Instance, + type Workload, +} from '../schema'; +import { Card, CardContent } from '@datum-cloud/datum-ui/card'; +import { CodeEditor } from '@datum-cloud/datum-ui/code-editor'; +import { EmptyContent } from '@datum-cloud/datum-ui/empty-content'; +import { PageTitle } from '@datum-cloud/datum-ui/page-title'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@datum-cloud/datum-ui/table'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@datum-cloud/datum-ui/tabs'; +import { formatDistanceToNowStrict } from 'date-fns'; +import { dump } from 'js-yaml'; +import { BoxIcon, MapPinIcon, Settings2Icon } from 'lucide-react'; +import { useMemo, useState } from 'react'; +import { useParams } from 'react-router'; + +const TABS = ['Overview', 'Instances', 'Events', 'Logs', 'Metrics', 'YAML'] as const; +type Tab = (typeof TABS)[number]; + +function GeneralCard({ workload }: { workload: Workload }) { + return ( + + +
+ + General +
+ + {workload.health} + + ), + }, + { + label: 'Resource Name', + content: {workload.name}, + }, + { + label: 'Ready', + content: `${workload.readyReplicas}/${workload.desiredReplicas}`, + }, + { + label: 'Updated', + content: `${workload.updatedReplicas}/${workload.desiredReplicas}`, + }, + { + label: 'Created', + content: formatDistanceToNowStrict(workload.createdAt, { addSuffix: true }), + }, + ]} + /> +
+
+ ); +} + +function ConfigurationCard({ workload }: { workload: Workload }) { + return ( + + +
+ + Configuration +
+ + {workload.image} + + ) : ( + '—' + ), + }, + { label: 'Resources', content: workload.resources ?? '—' }, + { + label: 'Replicas', + content: + workload.replicasPerRegion !== undefined + ? `${workload.replicasPerRegion}/region · ${workload.desiredReplicas} total` + : `${workload.desiredReplicas} total`, + }, + { label: 'Regions', content: workload.regions.join(', ') || '—' }, + ]} + /> +
+
+ ); +} + +function PlacementsCard({ workload }: { workload: Workload }) { + if (workload.placements.length === 0) return null; + + return ( + + +
+ + Placements +
+
+ {workload.placements.map((p) => ( +
+
+
+ {p.name} + + {p.cityCodes.join(', ') || 'no city codes'} + +
+
+ + {p.readyReplicas}/{p.desiredReplicas} ready + + {p.health} +
+
+ {p.conditions.length > 0 && } +
+ ))} +
+
+
+ ); +} + +function ConditionsCard({ workload }: { workload: Workload }) { + return ( + + + Conditions + + + + ); +} + +function OverviewTab({ workload }: { workload: Workload }) { + const stats: Stat[] = [ + { label: 'Ready', value: `${workload.readyReplicas}/${workload.desiredReplicas}` }, + { label: 'Current', value: `${workload.currentReplicas}/${workload.desiredReplicas}` }, + { label: 'Updated', value: `${workload.updatedReplicas}/${workload.desiredReplicas}` }, + { label: 'Regions', value: String(workload.regions.length) }, + ]; + + return ( +
+ +
+ + +
+ + +
+ ); +} + +function InstanceRow({ instance }: { instance: Instance }) { + const [expanded, setExpanded] = useState(false); + + return ( + <> + setExpanded((v) => !v)}> + +
+ {instance.name} + + {instance.status} + + {instance.suspended && Suspended} + {instance.schedulingGates.length > 0 && ( + + Gated: {instance.schedulingGates.join(', ')} + + )} +
+
+ {instance.city ?? '—'} + + {instance.internalIP ?? '—'} + + + {instance.externalIP ?? '—'} + + + {formatDistanceToNowStrict(instance.createdAt, { addSuffix: true })} + +
+ {expanded && ( + + + + + + )} + + ); +} + +function InstancesTab({ instances }: { instances: Instance[] }) { + if (instances.length === 0) { + return ( + + ); + } + + return ( +
+ + + + Instance + Region + Internal IP + External IP + Created + + + + {instances.map((instance) => ( + + ))} + +
+
+ ); +} + +/** Strips the noisy, rarely-useful `metadata.managedFields` before display. */ +function withoutManagedFields(raw: unknown): unknown { + if (!raw || typeof raw !== 'object') return raw; + const { metadata, ...rest } = raw as { metadata?: Record }; + if (!metadata) return raw; + const { managedFields, ...restMetadata } = metadata; + return { ...rest, metadata: restMetadata }; +} + +/** Same `dump` convention as staff-portal's own `edge-yaml-card.tsx`. */ +function YamlTab({ raw }: { raw: RawWorkload | undefined }) { + const yaml = useMemo( + () => (raw ? dump(withoutManagedFields(raw), { indent: 2, lineWidth: -1, noRefs: true }) : ''), + [raw] + ); + + if (!raw) return ; + return ; +} + +export default function WorkloadDetail() { + const { projectName, workloadName } = useParams<{ + projectName: string; + workloadName: string; + }>(); + const [tab, setTab] = useState('Overview'); + + const { data: workload, isLoading, error, refetch } = useWorkload(projectName, workloadName); + const { data: instances = [] } = useWorkloadInstances(projectName, workloadName); + const { data: raw } = useWorkloadRaw(projectName, workloadName); + + const titleName = workload?.name ?? workloadName ?? 'Workload'; + + return ( +
+ + + {isLoading && } + + {!isLoading && (error || !workload) && ( + void refetch()} + /> + )} + + {!isLoading && !error && workload && ( + setTab(v as Tab)}> + + {TABS.map((t) => ( + + {t} + + ))} + + + + + + + + + + + + + + + + + + + + + + )} +
+ ); +} diff --git a/ui/provider/src/pages/workload-list.tsx b/ui/provider/src/pages/workload-list.tsx new file mode 100644 index 00000000..34d0592a --- /dev/null +++ b/ui/provider/src/pages/workload-list.tsx @@ -0,0 +1,95 @@ +/** + * `portal.page/project` extension at `""` (the plugin mount's index), + * exposed as `WorkloadList` — every Workload in this project, linking into + * the existing `WorkloadDetail` support view (`:workloadName`, a sibling + * route under the same mount). + * + * `projectName` comes from `useParams()` the same way `WorkloadDetail` gets + * it — see `../lib/api.ts`'s header comment. + */ +import { StatusBadge } from '../components/detail-list'; +import { ErrorOrRestrictedState, LoadingSkeleton } from '../components/states'; +import { useWorkloads } from '../lib/api'; +import { healthToBadgeType, type Workload } from '../schema'; +import { EmptyContent } from '@datum-cloud/datum-ui/empty-content'; +import { PageTitle } from '@datum-cloud/datum-ui/page-title'; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@datum-cloud/datum-ui/table'; +import { formatDistanceToNowStrict } from 'date-fns'; +import { Link, useParams } from 'react-router'; + +function WorkloadRow({ workload }: { workload: Workload }) { + return ( + + + + {workload.name} + + + + {workload.health} + + + {workload.readyReplicas}/{workload.desiredReplicas} + + + {workload.regions.join(', ') || '—'} + + + {formatDistanceToNowStrict(workload.createdAt, { addSuffix: true })} + + + ); +} + +export default function WorkloadList() { + const { projectName } = useParams<{ projectName: string }>(); + const { data: workloads, isLoading, error, refetch } = useWorkloads(projectName); + + return ( +
+ + + {isLoading && } + + {!isLoading && error && ( + void refetch()} + /> + )} + + {!isLoading && !error && workloads && workloads.length === 0 && ( + + )} + + {!isLoading && !error && workloads && workloads.length > 0 && ( +
+ + + + Name + Health + Ready + Regions + Created + + + + {workloads.map((workload) => ( + + ))} + +
+
+ )} +
+ ); +} diff --git a/ui/provider/src/schema.ts b/ui/provider/src/schema.ts new file mode 100644 index 00000000..60a4ebc9 --- /dev/null +++ b/ui/provider/src/schema.ts @@ -0,0 +1,128 @@ +/** + * Zod schemas for the provider plugin's Workload detail view — adapted from + * `ui/consumer/src/schema.ts` (same underlying compute API), extended with a + * couple of fields the consumer dashboard doesn't need but a support view + * does: per-instance scheduling gates and the suspended flag (both are on + * `api/v1alpha/instance_types.go`'s `InstanceSpec.Controller` / + * `InstanceStatus.Suspended`). + */ +import { z } from 'zod'; + +// ── Workload ───────────────────────────────────────────────────────────── + +export type WorkloadHealth = 'Available' | 'Degraded' | 'Unavailable' | 'Unknown'; + +export const conditionSchema = z.object({ + type: z.string(), + status: z.enum(['True', 'False', 'Unknown']), + reason: z.string().optional(), + message: z.string().optional(), + lastTransitionTime: z.string().optional(), + observedGeneration: z.number().optional(), +}); + +export type Condition = z.infer; + +export const workloadPlacementSchema = z.object({ + name: z.string(), + cityCodes: z.array(z.string()).default([]), + readyReplicas: z.number(), + desiredReplicas: z.number(), + currentReplicas: z.number(), + health: z.enum(['Available', 'Degraded', 'Unavailable', 'Unknown']), + conditions: z.array(conditionSchema).default([]), +}); + +export type WorkloadPlacement = z.infer; + +export const workloadResourceSchema = z.object({ + uid: z.string(), + name: z.string(), + namespace: z.string().optional(), + createdAt: z.coerce.date(), + image: z.string().optional(), + health: z.enum(['Available', 'Degraded', 'Unavailable', 'Unknown']), + currentReplicas: z.number(), + updatedReplicas: z.number(), + readyReplicas: z.number(), + desiredReplicas: z.number(), + placements: z.array(workloadPlacementSchema).default([]), + conditions: z.array(conditionSchema).default([]), + runtimeType: z.string().optional(), + regions: z.array(z.string()).default([]), + resources: z.string().optional(), + replicasPerRegion: z.number().optional(), +}); + +export type Workload = z.infer; + +export const workloadListSchema = z.object({ + items: z.array(workloadResourceSchema), +}); + +export type WorkloadList = z.infer; + +/** Maps a health/condition-status value to a `Badge` `type` prop. */ +export function healthToBadgeType( + health: WorkloadHealth | 'True' | 'False' +): 'success' | 'warning' | 'danger' | 'muted' { + switch (health) { + case 'Available': + case 'True': + return 'success'; + case 'Degraded': + return 'warning'; + case 'Unavailable': + case 'False': + return 'danger'; + default: + return 'muted'; + } +} + +// ── Instance ───────────────────────────────────────────────────────────── + +export type InstanceStatusValue = 'Available' | 'Pending' | 'Failed' | 'Unknown'; + +export const instanceResourceSchema = z.object({ + uid: z.string(), + name: z.string(), + namespace: z.string().optional(), + createdAt: z.coerce.date(), + city: z.string().optional(), + placement: z.string().optional(), + instanceType: z.string().optional(), + cpu: z.string().optional(), + memory: z.string().optional(), + image: z.string().optional(), + status: z.enum(['Available', 'Pending', 'Failed', 'Unknown']), + externalIP: z.string().optional(), + internalIP: z.string().optional(), + conditions: z.array(conditionSchema).default([]), + /** Present while the instance is gated from scheduling (e.g. awaiting quota). */ + schedulingGates: z.array(z.string()).default([]), + suspended: z.boolean().default(false), +}); + +export type Instance = z.infer; + +export const instanceListSchema = z.object({ + items: z.array(instanceResourceSchema), +}); + +export type InstanceList = z.infer; + +export function instanceStatusToBadgeType( + status: InstanceStatusValue +): 'success' | 'warning' | 'danger' | 'muted' { + switch (status) { + case 'Available': + return 'success'; + case 'Pending': + return 'warning'; + case 'Failed': + return 'danger'; + default: + return 'muted'; + } +} diff --git a/ui/provider/tsconfig.json b/ui/provider/tsconfig.json index a3bf17ca..2af885b1 100644 --- a/ui/provider/tsconfig.json +++ b/ui/provider/tsconfig.json @@ -1,13 +1,18 @@ { "compilerOptions": { "target": "ESNext", - "lib": ["ESNext"], + "useDefineForClassFields": true, + "lib": ["ESNext", "DOM", "DOM.Iterable"], "module": "ESNext", "moduleResolution": "bundler", + "jsx": "react-jsx", "strict": true, "noEmit": true, + "esModuleInterop": true, "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, "types": ["vite/client"] }, - "include": ["vite.config.ts"] + "include": ["src", "vite.config.ts"] } diff --git a/ui/provider/vite.config.ts b/ui/provider/vite.config.ts index 27cad9a2..e779b1e1 100644 --- a/ui/provider/vite.config.ts +++ b/ui/provider/vite.config.ts @@ -1,23 +1,22 @@ import { federation } from '@module-federation/vite'; +import react from '@vitejs/plugin-react'; import { defineConfig } from 'vite'; // Workloads resource-type plugin — a Module Federation remote loaded by the // staff-portal host at runtime (see staff-portal's app/modules/plugins/, // ported from cloud-portal's plugin-host system). // -// Unlike a typical portal plugin, this one exposes no page/nav/component at -// all — it exists purely to declare a `portal.resource/platform` extension in -// public/plugin-manifest.json, which lets staff-portal's -// `/customers/resources` page query and render Workload rows *itself* (see -// that manifest's comment and app/modules/plugins/types.ts's -// ResourcePlatformExtension in staff-portal for the full design). No plugin -// code executes to produce those rows, so there's nothing to build here -// beyond a valid (empty) remote — `exposes` stays `{}` until this plugin -// grows an actual page. +// Declares three extensions: `portal.resource/platform` (data-only, lets +// staff-portal's /customers/resources page query and render Workload rows +// itself — no plugin code executes for that) and two `portal.page/project` +// pages — `WorkloadList` (the mount's index) and `WorkloadDetail` +// (`:workloadName`) — the actual support views. // // Assets are fetched server-side by staff-portal's asset proxy and served // under /api/plugins//…, so plain http://localhost during dev is fine -// and the browser never contacts this origin directly. +// and the browser never contacts this origin directly. `shared` mirrors +// staff-portal's `federation-host.ts` DATUM_UI_SHARED set exactly — those are +// the only `@datum-cloud/datum-ui` subpaths the host actually provides. export default defineConfig({ server: { port: 5199, @@ -34,6 +33,7 @@ export default defineConfig({ minify: false, }, plugins: [ + react(), federation({ // MUST equal the manifest `name` — the host keys the remote by this id. name: 'workloads.staff-portal.datumapis.com', @@ -41,7 +41,33 @@ export default defineConfig({ // requested through the asset proxy as /api/plugins/workloads/remoteEntry.js. filename: 'remoteEntry.js', manifest: true, - exposes: {}, + // Exposed keys map 1:1 to the manifest's `exposedModules` keys / $codeRefs. + exposes: { + './WorkloadList': './src/pages/workload-list.tsx', + './WorkloadDetail': './src/pages/workload-detail.tsx', + }, + shared: { + react: { singleton: true, requiredVersion: '^19.0.0' }, + 'react-dom': { singleton: true, requiredVersion: '^19.0.0' }, + // `react-dom/client` is only ever imported by this plugin's + // standalone preview harness (main.tsx, never loaded by the real + // host) — but Vite/MF still discovers it in the build graph and, left + // unconfigured, auto-shares it with a strict version check that hard + // -fails on any host/plugin react-dom patch drift (confirmed: staff- + // portal runs 19.2.3, this plugin's own installed react-dom is + // 19.2.7 — "Failed to bridge external shared module" at container + // load, before any exposed component even runs). requiredVersion: + // false — same rule as the datum-ui entries below — makes this a + // no-op version check instead of a crash. + 'react-dom/client': { singleton: true, requiredVersion: false }, + 'react-router': { singleton: true, requiredVersion: '^7.0.0' }, + '@tanstack/react-query': { singleton: true, requiredVersion: '^5.0.0' }, + '@datum-cloud/datum-ui/badge': { singleton: true, requiredVersion: false }, + '@datum-cloud/datum-ui/button': { singleton: true, requiredVersion: false }, + '@datum-cloud/datum-ui/card': { singleton: true, requiredVersion: false }, + '@datum-cloud/datum-ui/icons': { singleton: true, requiredVersion: false }, + '@datum-cloud/datum-ui/skeleton': { singleton: true, requiredVersion: false }, + }, }), ], });