diff --git a/.github/workflows/_rust-binary.yml b/.github/workflows/_rust-binary.yml index d760b379..20d97d77 100644 --- a/.github/workflows/_rust-binary.yml +++ b/.github/workflows/_rust-binary.yml @@ -167,10 +167,8 @@ jobs: working-directory: ${{ steps.dirs.outputs.worker }}/web run: pnpm install --frozen-lockfile - - name: pnpm build (VITE_PLAYGROUND='') + - name: pnpm build working-directory: ${{ steps.dirs.outputs.worker }}/web - env: - VITE_PLAYGROUND: '' run: pnpm build - name: Upload web bundle diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efea2a87..7c1cc036 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -148,8 +148,6 @@ jobs: - name: Pre-build SPA bundle if: hashFiles(format('{0}/web/package.json', matrix.worker)) != '' working-directory: ${{ matrix.worker }}/web - env: - VITE_PLAYGROUND: '' run: | pnpm install --frozen-lockfile pnpm build diff --git a/.gitignore b/.gitignore index 72e1974e..9722dd42 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,6 @@ engine.pid # Local runtime composition (root) — per-worker config.yaml files are tracked individually /config.yaml -playground*/ # harness/config.yaml is generated by `make engine` via `iii worker add .` harness/config.yaml diff --git a/DOCUMENTATION_GUIDELINES.md b/DOCUMENTATION_GUIDELINES.md index 70085611..b4b8e093 100644 --- a/DOCUMENTATION_GUIDELINES.md +++ b/DOCUMENTATION_GUIDELINES.md @@ -1,356 +1,217 @@ # Documentation guidelines -This guide describes how to author a worker's skill bundle so the docs stay -consistent across workers. When in doubt, mirror the structure of an existing -bundle in your project alongside this guide. +This guide describes how to author a worker's skill doc so agents know **when** +and **why** to use a worker without duplicating what the engine already exposes +as structured API reference. -A skill bundle is a folder of markdown files that explains, from the agent's -point of view, **when** and **why** to call each function a worker exposes. -The structure has three load-bearing parts: - -1. A predictable folder layout (so an agent can resolve `iii://...` links). -2. YAML frontmatter on every file (so the directory reader can extract title, - type, description without parsing the body). -3. A fixed section order in each how-to (so an agent always finds inputs, - outputs, and related calls in the same place). - -## 1. Folder layout - -One top-level folder per worker, named after the worker. Inside it: +Each worker ships one file: ```text -skills/ - / - index.md <- worker overview, type: index - skills/ - / - / - .md <- one how-to per function - .md <- OR one file for a whole namespace (see 4.) +engine/src/workers//skills/SKILL.md ``` -Rules: - -- The path under `skills//skills/` mirrors the function namespace. - `::::::` lives at - `skills//skills///.md` — each `::` becomes a `/`. -- File and folder segments must match `[a-z0-9_-]{1,64}`. No spaces, no - uppercase, no dots other than the trailing `.md`. -- Closely paired siblings (typically `list` + `get`, or `list` + `info`) - MAY collapse into a single file named after the namespace, e.g. one - `skills//skills//.md` can cover both - `::::::list` and `::::::get`. - See section 5 for the layout that file uses. +The file is lean on purpose. Inputs, outputs, JSON schemas, and worked examples +live in the API reference — agents fetch them with `iii list functions` and +`iii get function info` (or the equivalent SDK calls). The skill doc explains +intent, lists what's available, and covers trigger binding when the worker emits +events. -## 2. `index.md` — worker overview +## SKILL.md structure -Every worker folder has exactly one `index.md` at its root. This is the file -the directory reader's index function extracts into the per-worker bootstrap -snippet. +Every worker `SKILL.md` has YAML frontmatter and four body sections (the last +one only when the worker exposes a trigger type). -Frontmatter: +### Frontmatter ```yaml --- -type: index -title: +name: +description: >- + One sentence: what problem this worker solves and when an agent should reach + for it. --- ``` -Body shape: - -- `# ` H1. -- 1-3 paragraph overview. The first paragraph is what the directory reader's - index function will surface in agent bootstraps, so keep it self-contained - and prose-only (no bullet lists in the first paragraph). -- A bulleted list naming each sub-namespace (e.g. `my-worker::foo::*`, - `my-worker::bar::*`) with one sentence of context. -- `## How-tos` heading. -- One `### ::*` subsection per sub-namespace, each containing a - bullet list of `[function::id](iii:///)` links with a - one-line rationale per function. - -Copy-paste template: - -````markdown ---- -type: index -title: my-worker ---- - -# my-worker - -One-paragraph overview that explains what this worker does, ending with -links to its sub-namespaces: +- `name` must match the worker name exactly (same as the directory). +- `description` is what discovery surfaces in bootstraps — keep it self-contained. -- **Foo** (`my-worker::foo::*`) — short description. -- **Bar** (`my-worker::bar::*`) — short description. +### `# ` — overview -## How-tos +1–3 paragraphs in prose (no bullet lists in the opening paragraph): -### `my-worker::foo::*` +- What problem the worker solves. +- When and why to use it instead of alternatives. +- Any prerequisites (enabled in `config.yaml`, adapter choice, etc.). -- [`my-worker::foo::list`](iii://my-worker/foo/list) — one-line rationale. -- [`my-worker::foo::get`](iii://my-worker/foo/get) — one-line rationale. +This is the only narrative the agent reads before deciding whether the worker +is relevant. -### `my-worker::bar::*` +### `## When to Use` -- [`my-worker::bar::run`](iii://my-worker/bar/run) — one-line rationale. -```` +Bullet list of concrete situations that should trigger reading this skill. +Mirror the tone of other iii skills — specific triggers, not generic filler. -## 3. Per-function how-to +### `## Boundaries` -The default shape. One file per function. +Bullet list of what this worker does **not** do, common misuses, and pointers +to sibling workers when the task belongs elsewhere. -### Frontmatter +### `## Functions` -```yaml ---- -type: how-to -function_id: -title: ---- -``` +A brief catalogue of every `worker::namespace::function` the worker exposes. +One line per function — **what it is for**, not how to call it. -- `type: how-to` is what the directory reader's `functions::info` call looks - for when it picks the `how_guide` field. -- `function_id` must be the exact dotted id. The directory reader uses this - (or a body-level `iii://fn/` link) to associate the file with - a function. -- `title` should describe the action, not the function name. Good: `"List - trigger types registered with the engine"`. Bad: `"trigger-list"`. +Do **not** include: -### Required sections (in this exact order) +- `# Inputs` / `# Outputs` sections +- JSON schema blocks +- Worked examples +- `# Related` cross-links between functions -1. `# When to use` -2. `# Inputs` -3. `# Outputs` -4. `# Worked example` -5. `# Related` +Those details are in the API reference. If an agent needs the payload shape, +it should call `get function info` for the function id. -Optional sections (see section 4) slot in between `# Outputs` and -`# Worked example`. - -#### `# When to use` - -Opening paragraph in narrative voice describing the scenario that motivates -the call. Followed (optionally) by a `Reach for it when:` or `Common -situations:` bullet list of 2-4 concrete triggers. When a sibling function -overlaps, add a short "Use `` instead when ..." pointer so the -agent doesn't pick the wrong one. When two functions answer different -questions on the same namespace, use a decision table: +Format: ```markdown -| Question | Use this | -|---------------------------------------|------------------------------------| -| What is wired into my local instance? | `my-worker::local::items::info` | -| What is published in the registry? | `my-worker::registry::items::info` | +## Functions + +- `configuration::register` — declare an id with name, description, JSON Schema, and optional initial value. +- `configuration::set` — replace the value for a registered id; validates against the schema. +- `configuration::get` — read one entry by id. +- `configuration::list` — enumerate every registered id. +- `configuration::schema` — read schema/name/description without returning the value. ``` -Use this shape when two surfaces (e.g. local vs. registry) answer different -questions on the same namespace. +When two functions are tightly paired (`list` + `get`), you may group them in +one bullet if a single line covers both intents — but still name each function +id. -#### `# Inputs` +Optional: a one-sentence note on cross-cutting behaviour that isn't obvious +from function names alone (e.g. "reads expand `${VAR:default}` placeholders +against the live process env"). -One JSON code block showing the input shape. Use inline `// ...` comments -to document each field — flag required/optional and capture semantics that -aren't obvious from the field name. Follow the block with a short paragraph -stating which fields are required and listing any cross-field constraints -(`mutually exclusive`, `default when omitted`, ...). +### `## Reactive triggers` (when applicable) -#### `# Outputs` +Include this section when the worker registers a custom trigger type. Skip it +entirely for workers that only expose functions. -One JSON code block showing the response shape, again with inline -`// ...` comments per field. Follow it with a bullet list that documents: +This section is **not** deferrable to the API reference — agents need to +understand *why* to bind a trigger and *how* the two-step registration works +(handler function + trigger registration). -- Null/empty handling (`null when ...`, `omitted when ...`). -- Sort order (rows are always lex-sorted by ``). -- Resolution precedence (e.g. `title resolves in this order: frontmatter - title, then body H1, then bare id`). -- Truncation / cap rules. +Cover: -#### `# Worked example` +1. **Why bind** — what reactive behaviour you get vs. polling or imperative + follow-up calls after a write. +2. **When to bind** — concrete scenarios; when *not* to bind (e.g. the writer + already has the new value in its return payload). +3. **How to bind** — the two-step pattern: + - Register a handler with `registerFunction`. + - Register the trigger with `registerTrigger({ type, function_id, config })`. +4. **Config knobs that matter** — filters, conditions, TTL interactions, or + adapter-specific behaviour (file watch, bridge forwarding, etc.) described + in prose or a small table. Do not paste full event JSON — point to + `get function info` on the trigger type or handler for the payload schema. +5. **One minimal binding example** — enough to show the registration shape, + not a full end-to-end scenario. -One or more concrete sample calls, each preceded by a one-line prose -framing. Show the request payload as a JSON block; only show the response -when it adds information beyond what `# Outputs` already documented. +Do **not** duplicate the event payload field-by-field if the trigger type is +registered with `call_request_format` / `trigger_request_format` in the engine. +Say what the event represents and let the API reference supply the schema. -#### `# Related` +## What to leave out -Bullet list of sibling functions a caller is likely to need next. Format -each bullet as `` `function::id` `` followed by an en-dash and a short -rationale ending with a period: +| Removed from skill docs | Where it lives instead | +|-------------------------|------------------------| +| Per-function markdown files | — (deleted) | +| `index.md` + `skills/` tree | Single `SKILL.md` | +| `# Inputs`, `# Outputs` | `get function info` → request/response JSON Schema | +| `# Worked example` | Agent composes calls from schemas | +| `# Related` | Function list + agent reasoning | +| `# Side effects`, `# Caching`, `# Rendering rules` | API reference + trigger/event schemas where relevant | -```markdown -- `my-worker::foo::list` — find the id you want to inspect. -- `my-worker::bar::info` — group by a different dimension. -``` +## Style checklist + +- Function ids always in inline backticks: `` `configuration::set` ``. +- Never link to workspace file paths — agents read `SKILL.md` from the worker bundle. +- No emojis. No filler ("This document explains …"). +- Open every section with substance. +- Keep the whole file short enough to read in one pass — typically under 120 lines. -### Copy-paste template +## Copy-paste template ````markdown --- -type: how-to -function_id: my-worker::foo::get -title: Read one foo by id +name: my-worker +description: >- + Short description of the problem this worker solves and when an agent should + use it. --- -# When to use - -Call `my-worker::foo::get` when you already have a foo id (from -`my-worker::foo::list` or elsewhere) and you need the full body. - -Reach for it when: - -- You hit an `iii://...` link inside another skill and want it inlined. -- A picker UI surfaced an id that the user selected. - -Use [`my-worker::foo::list`](iii://my-worker/foo/list) instead when you -need to enumerate without already knowing an id. - -# Inputs - -```json -{ "id": "foo-123" } -``` - -`id` is required and must match `[a-z0-9_-]{1,64}`. - -# Outputs - -```json -{ - "id": "foo-123", - "title": "Example foo", - "body": "# Example foo\n\n...", - "modified_at": "2026-05-01T12:34:56+00:00" -} -``` - -- `title` resolves in this order: frontmatter `title`, then first body H1, - then bare `id` as a final fallback. -- `modified_at` is the file mtime as RFC 3339 (empty when the FS does not - expose it). - -# Worked example - -Inline a linked foo: +# my-worker -```json -{ "id": "foo-123" } -``` +One paragraph overview: what this worker is, the problem it solves, and why +you would reach for it. Mention prerequisites here if any. -# Related +Second paragraph optional — adapter notes, persistence model, or how it fits +next to other workers. -- `my-worker::foo::list` — discover the id first. -- `my-worker::foo::download` — populate the store so there's something to - read. -```` +## When to Use -## 4. Optional sections +- Concrete situation that should trigger using this worker. +- Another concrete situation. -Insert these between `# Outputs` and `# Worked example` when they apply. -Each has a fixed contract. +## Boundaries -### `# Side effects` +- What this worker does not do. +- When to use a sibling worker instead. -**Required** for any function that writes to disk, fires a trigger, or -mutates external state. Document each event/file write with its payload -shape. Subscribers read this to know what to forward. +## Functions -Use this in any how-to whose function writes to disk or emits an event. +- `my-worker::foo::register` — one-line purpose. +- `my-worker::foo::get` — one-line purpose. +- `my-worker::foo::list` — one-line purpose. -### `# Caching` +## Reactive triggers -**Required** for any function backed by a network or in-process cache. -State the cache key (which inputs partition the cache), the TTL (with the -config knob that controls it), and how to bust the cache. +Register a `my-worker` trigger when a function should run automatically every +time … — without polling `my-worker::get`. -Use this in any how-to whose function reads through a network or in-process -cache. +Reach for it when: -### `# Rendering rules` +- … +- … -**Required** when the function returns a rendered markdown document (a -`body` field whose contents the agent is expected to paste verbatim into a -prompt or message). Spell out: +If you only need the new value inside the same function that wrote it, call +`my-worker::set` and use its return value — register a trigger only when a +*different* worker should react. -- Which inputs flow into which output sections. -- Which inputs are filtered out (e.g. "only skills with `type: index` - appear in the body"). -- The exact heading levels and template snippets used. +### How to bind -Use this in any how-to whose function returns a `body` field meant to be -pasted verbatim into a prompt or message. +1. Register a handler: `registerFunction('stream::on-change', handler)`. +2. Register the trigger: -## 5. Multi-function variant +```typescript +iii.registerTrigger({ + type: 'my-worker', + function_id: 'stream::on-change', + config: { + // optional filters — see get function info on the trigger type + }, +}) +``` -When two or more siblings on the same namespace are tightly coupled -(`list` + `get`, or `list` + `info` + `download`), one file MAY cover the -whole namespace. Use this when the functions share enough context that -splitting them creates duplication. +Mutations that fire triggers: … Reads do **not** fire triggers. -Frontmatter switches `function_id` for a `functions:` array: +For the event payload shape, call `get function info` on the trigger type or +handler function id. +```` -```yaml ---- -type: how-to -functions: [my-worker::foo::list, my-worker::foo::get] -title: List and read foos ---- -``` +## Before you ship -Body shape: - -- Single `# When to use` covering the whole namespace, ideally with a - `| Question | Use this |` decision table mapping intents to function ids. -- One `# ` H1 per function, each containing `## Inputs` and - `## Outputs` H2 subsections (NOT H1 — only the function names use H1). -- A single `# Worked example` at the end covering the realistic - list-then-get flow. -- A single `# Related` at the end. -- Any applicable optional sections (`# Side effects`, `# Caching`, - `# Rendering rules`) come after `# Worked example` and before - `# Related`, same as the single-function variant. - -## 6. Style checklist - -- Function ids always go in inline backticks: `` `my-worker::foo::get` ``. - Never bare, never bolded. -- Cross-skill links use the `iii:///` URI scheme: - `[my-worker::foo::get](iii://my-worker/foo/get)`. Don't link to a - workspace path. -- Every JSON example is **valid JSON shape** (real keys, plausible - values), not pseudocode like `{ ... }`. Use `"..."` for placeholder - strings. -- Annotate JSON fields with inline `// ...` comments. Column-align the - comments when the block has many fields. -- `# Related` bullets are en-dash separated, end with a period: - `` - `fn::id` — short rationale. `` -- When two surfaces share a shape (engine vs registry, list vs info), - name the shared core fields explicitly in both files and link to the - counterpart from `# Related`. This lets a caller write one parser - against both surfaces. -- Don't repeat the function id as the file title. The `title:` field is - human-readable, action-oriented prose. -- Don't add emojis. Don't add filler ("This document explains ...", - "In this section we will ..."). Open every section with the substance. - -## 7. Mirror an existing peer - -Before writing a new how-to, find the closest match among the existing -bundles in your project and follow its structure: - -- Writing a worker `index.md`? Read another worker's overview. -- Writing a standard single-function how-to? Read any file with the five - required sections and no optional ones. -- Writing a how-to with a decision table? Read a how-to whose - `# When to use` opens with a `| Question | Use this |` table. -- Writing a write-path function? Read a how-to with a `# Side effects` - section. -- Writing a network-backed function? Read a how-to with a `# Caching` - section. -- Writing a function that returns rendered markdown? Read a how-to with a - `# Rendering rules` section. -- Writing one file for multiple sibling functions? Read a multi-function - file (its frontmatter has a `functions:` array instead of `function_id:`). +1. Read a peer worker's `SKILL.md` (if one exists) and match its tone. +2. List every function id — cross-check against `iii list functions` for the worker. +3. If the worker has a trigger type, verify the binding example matches the + registered `trigger_request_format`. +4. Confirm nothing duplicates JSON schemas already returned by `get function info`. diff --git a/console/build.rs b/console/build.rs index e98ecfed..6834e7f9 100644 --- a/console/build.rs +++ b/console/build.rs @@ -4,9 +4,8 @@ //! (used by `manifest.rs` for the registry `supported_targets` field). //! 2. Ensures the embedded SPA bundle exists. `rust-embed` reads //! `web/dist/` at compile time; if it's missing or stale we run -//! `pnpm install --frozen-lockfile && pnpm build` (with -//! `VITE_PLAYGROUND=`) inside `web/` before the rest of the crate -//! compiles. +//! `pnpm install --frozen-lockfile && pnpm build` inside `web/` +//! before the rest of the crate compiles. use std::path::{Path, PathBuf}; use std::process::Command; @@ -71,7 +70,6 @@ fn main() { let status = Command::new(&pnpm) .args(["build"]) - .env("VITE_PLAYGROUND", "") .current_dir(&web_dir) .status() .unwrap_or_else(|e| panic!("failed to spawn `pnpm build` in {}: {e}", web_dir.display())); diff --git a/console/web/.gitignore b/console/web/.gitignore index d1b04267..50667e11 100644 --- a/console/web/.gitignore +++ b/console/web/.gitignore @@ -1,6 +1,7 @@ node_modules dist dist-ssr +storybook-static .DS_Store *.log .vite diff --git a/console/web/.storybook/main.ts b/console/web/.storybook/main.ts new file mode 100644 index 00000000..2ad5a58c --- /dev/null +++ b/console/web/.storybook/main.ts @@ -0,0 +1,26 @@ +import { fileURLToPath, URL } from 'node:url' +import type { StorybookConfig } from '@storybook/react-vite' +import { mergeConfig } from 'vite' + +const config: StorybookConfig = { + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(ts|tsx)'], + addons: ['@storybook/addon-docs', '@storybook/addon-a11y'], + framework: { + name: '@storybook/react-vite', + options: {}, + }, + // Mirror the `@` -> `src` alias from vite.config.ts. The Tailwind v4 plugin + // and the React plugin are inherited from the project's vite config, so the + // iii Schematic styles (index.css) resolve exactly as they do in the app. + viteFinal(viteConfig) { + return mergeConfig(viteConfig, { + resolve: { + alias: { + '@': fileURLToPath(new URL('../src', import.meta.url)), + }, + }, + }) + }, +} + +export default config diff --git a/console/web/.storybook/preview-head.html b/console/web/.storybook/preview-head.html new file mode 100644 index 00000000..37aad7f3 --- /dev/null +++ b/console/web/.storybook/preview-head.html @@ -0,0 +1,15 @@ + + + + diff --git a/console/web/.storybook/preview.tsx b/console/web/.storybook/preview.tsx new file mode 100644 index 00000000..d9ac2d27 --- /dev/null +++ b/console/web/.storybook/preview.tsx @@ -0,0 +1,68 @@ +import type { Decorator, Preview } from '@storybook/react-vite' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { useEffect } from 'react' +import { TooltipProvider } from '@/components/ui/Tooltip' +import '../src/index.css' + +/* Mirror main.tsx so every story sees the same data-layer + tooltip context + the real app provides. A single client is fine: stories are read-mostly and + `staleTime` keeps refetch noise down. */ +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 1, + staleTime: 1_000, + }, + }, +}) + +const withProviders: Decorator = (Story) => ( + + + + + +) + +/* Drive the iii Schematic theme from the toolbar. index.html sets + `data-theme` pre-paint in the real app; here the toolbar global stands in + for that, and we stamp the body utility classes the app applies in + index.html so the canvas background + base ink match the running console. */ +const withTheme: Decorator = (Story, context) => { + const theme = context.globals.theme === 'dark' ? 'dark' : 'light' + useEffect(() => { + document.documentElement.dataset.theme = theme + document.body.classList.add('bg-bg', 'text-ink', 'font-sans') + }, [theme]) + return +} + +const preview: Preview = { + parameters: { + controls: { + matchers: { color: /(background|color)$/i, date: /Date$/i }, + }, + // The iii Schematic surface owns its own background via the `bg-bg` + // token stamped on the body; the addon's color swatches would fight it. + backgrounds: { disable: true }, + }, + initialGlobals: { theme: 'light' }, + globalTypes: { + theme: { + description: 'iii Schematic theme', + toolbar: { + title: 'Theme', + icon: 'mirror', + items: [ + { value: 'light', title: 'Light' }, + { value: 'dark', title: 'Dark' }, + ], + dynamicTitle: true, + }, + }, + }, + decorators: [withTheme, withProviders], +} + +export default preview diff --git a/console/web/DESIGN.md b/console/web/DESIGN.md index 237b2bca..890d1232 100644 --- a/console/web/DESIGN.md +++ b/console/web/DESIGN.md @@ -21,8 +21,8 @@ colors: bg-dark: "#111110" panel-dark: "#1a1916" ink-dark: "#f2f0ed" - ink-faint-dark: "#9c9893" - ink-ghost-dark: "#5d5a55" + ink-faint-dark: "#a8a49e" + ink-ghost-dark: "#8a8782" rule-dark: "#2a2926" rule-2-dark: "#1f1e1c" alert: "#c43e1c" @@ -344,8 +344,8 @@ The full design-token stylesheet — drop this in as your global CSS entrypoint: --color-panel: #1a1916; --color-paper-2: #1f1e1c; --color-ink: #f2f0ed; - --color-ink-faint: #9c9893; - --color-ink-ghost: #5d5a55; + --color-ink-faint: #a8a49e; + --color-ink-ghost: #8a8782; --color-rule: #2a2926; --color-rule-2: #1f1e1c; --color-accent: #3ea8ff; @@ -574,8 +574,8 @@ color + a small icon/stripe**, not full-color backgrounds. ### Dark theme Override the same tokens inside a `[data-theme="dark"]` block (see §0). The -ramp inverts (`#111110 → #1a1916 → #1f1e1c` for paper; `#f2f0ed → #9c9893 → -#5d5a55` for ink) and the accent swaps to electric blue (`#3ea8ff`). Same +ramp inverts (`#111110 → #1a1916 → #1f1e1c` for paper; `#f2f0ed → #a8a49e → +#8a8782` for ink) and the accent swaps to electric blue (`#3ea8ff`). Same structural logic — never collapses into pure black. To follow the OS, set the attribute on load: diff --git a/console/web/PLAYGROUND.md b/console/web/PLAYGROUND.md index 4649a4e4..0467f261 100644 --- a/console/web/PLAYGROUND.md +++ b/console/web/PLAYGROUND.md @@ -5,8 +5,9 @@ This document is the source of truth for two things: 1. **The streaming contract** every `ChatBackend` honors. The chat surface only knows about this contract, never about a specific provider — so the internals can churn freely as long as the contract holds. -2. **The Playground page** that exercises the contract through a catalog of - scenarios (slow streams, errors, multi-function runs, markdown stress, etc). +2. **The Playground stories** in Storybook that exercise the contract through + a catalog of scenarios (slow streams, errors, multi-function runs, markdown + stress, etc). If you're swapping the mock for a real backend, this is the file to read first. If a scenario fails after your refactor, the contract has drifted — @@ -14,17 +15,56 @@ either fix the backend or update both the scenario and this doc together. ## Quickstart -The Playground (and the Examples spec sheet) ship behind a build-time flag. +The Playground and the component spec sheet live in Storybook. ```bash -# dev: flag is on by default (set in .env.development) -npm run dev -# open #/playground +cd console/web +pnpm storybook +# open http://localhost:6006 → Playground ``` -In dev, the header has a `chat / playground / examples` toggle. Pick a -scenario from the left rail, send any message, and watch the right-hand -event log mirror every `StreamEvent` the backend yields. +Pick a scenario story from the sidebar, send any message, and watch the +right-hand event log mirror every `StreamEvent` the backend yields. The same +mock contract powers the in-app chat dock in dev (`pnpm dev`). + +## Component stories + +Storybook is a static gallery of every UI primitive and surface, used to +sanity-check visual changes in isolation. Stories are co-located next to their +component (e.g. [`Select.stories.tsx`](src/components/ui/Select.stories.tsx)); +the sidebar groups them under `UI`, `Chat`, `Workers`, `Design`, and +`Playground`. + +Alongside the chat primitives (composer, messages, loading, primitives, +typography, color) it covers the worker-configuration surfaces: + +| group | file | what it shows | +|----------------------|---------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| `UI/Select` | [`Select.stories.tsx`](src/components/ui/Select.stories.tsx) | the shared `Select`: flat / grouped / disabled, plus the empty-value and ellipsis fixes. | +| `Workers/SchemaForm` | [`SchemaForm.stories.tsx`](src/pages/Configuration/tabs/WorkersTab/schema-form/SchemaForm.stories.tsx) | one live `SchemaForm` per field variation (string/env, number, enum, oneOf, nullable, array, object, dictionary, $ref, errors). | +| `Workers/WorkersTab` | [`WorkersTab.stories.tsx`](src/pages/Configuration/tabs/WorkersTab/WorkersTab.stories.tsx) | the full master-detail editor over mock fixtures: select / edit / dirty / reset / save, including the inline error path. | + +The mock schemas and configs live in +[`worker-fixtures.ts`](src/stories/fixtures/worker-fixtures.ts). +Stories that render env-template string inputs are wrapped in a `.workers-tab` +container (the `WorkersTabDecorator` in +[`decorators.tsx`](src/stories/decorators.tsx)) so the Lexical pill styling +(scoped to that class in `index.css`) applies. The worker-config harness +simulates `configuration::set` with `mockValidate` — saving `telemetry` with +`sample_rate > 1`, or clearing the `database` url, drives the inline +validation-error path. + +### Select fixes documented here + +`UI/Select` is the regression surface for two +[`Select`](src/components/ui/Select.tsx) fixes: + +- **Empty / unmatched values render the placeholder**, not the raw token. A + value of `undefined` (or any id absent from the options) no longer prints + "undefined". `allowEmpty` adds a leading clear option that fires `onClear`; + the schema-form `EnumField` uses it for optional enums (clearing to `null`). +- **Long labels ellipsis** instead of stretching the trigger and pushing the + chevron off-screen. ## The streaming contract @@ -101,7 +141,7 @@ graph TD Backend["ChatBackend interface"] Mock["mockBackend (lib/backend/mock.ts)"] Real["realBackend (lib/backend/real.ts) - stub today"] - Scenarios["scenarioBackend (pages/Playground/scenarios)"] + Scenarios["scenarioBackend (stories/playground/scenarios)"] ChatView -->|consumes| Backend Backend -.implements.- Mock @@ -114,12 +154,12 @@ The seam is `chat-app/src/lib/backend/`: - [`types.ts`](src/lib/backend/types.ts) — the contract types: `StreamEvent`, `ChatStreamOptions`, `ChatBackend`. - [`mock.ts`](src/lib/backend/mock.ts) — three canned bodies, jittered token - delays, abort-aware sleeps. Imported only when `VITE_PLAYGROUND` is on. + delays, abort-aware sleeps. Used in dev; tree-shaken from prod builds. - [`real.ts`](src/lib/backend/real.ts) — stub that throws `'backend not configured'`. Replace its body with your provider; preserve the `ChatBackend` shape and you're done. -- [`index.ts`](src/lib/backend/index.ts) — `getDefaultBackend()` picks one or - the other based on the build-time flag. +- [`index.ts`](src/lib/backend/index.ts) — `getDefaultBackend()` returns the + mock when `import.meta.env.DEV`, otherwise the real backend. The chat page imports `getDefaultBackend()` once at module load and passes it to `ChatView` as a prop. Nothing else in the app depends on the choice. @@ -127,9 +167,10 @@ it to `ChatView` as a prop. Nothing else in the app depends on the choice. ## Scenarios Each scenario is a `ChatBackend` exported from -[`pages/Playground/scenarios/`](src/pages/Playground/scenarios/). The -registry in [`scenarios/index.ts`](src/pages/Playground/scenarios/index.ts) -groups them and exposes them to the picker. +[`stories/playground/scenarios/`](src/stories/playground/scenarios/). The +registry in [`scenarios/index.ts`](src/stories/playground/scenarios/index.ts) +groups them; each group maps to a `Playground/*.stories.tsx` file, so every +scenario shows up as its own story in the Storybook sidebar. | id | group | what it asserts | |---------------------|---------------|------------------------------------------------------------------------------| @@ -382,49 +423,43 @@ change, and leaves the bigger redesigns documented and unblocked. **Proposal B is the strongest match for the "side by side, both equally important" direction** and is the most likely follow-up. -## Flag plumbing +## Dev vs prod backend -A single env var, `VITE_PLAYGROUND`, controls visibility: +There's no build-time flag anymore. The Playground and the component spec +sheet are Storybook-only — their stories, the scenarios, the `EventLog`, and +the fixtures all live under [`src/stories/`](src/stories) and are never part +of the app's module graph, so they can't leak into the production bundle. -| file | value | effect | -|---------------------|-----------|-----------------------------------------------------| -| `.env.development` | `1` | dev defaults: Playground + Examples + mock shipped. | -| `.env.production` | empty | prod defaults: pages and mock tree-shaken. | +The one runtime choice left is which backend the **in-app chat dock** uses: -The flag is consumed in three places: +| build | `getDefaultBackend()` | effect | +|---------------------|-----------------------|--------------------------------------------| +| dev (`pnpm dev`) | `mockBackend` | canned streaming, no API keys. | +| prod (`pnpm build`) | `realBackend` | the stub you replace with a real provider. | -1. [`src/App.tsx`](src/App.tsx) — `lazy()`-wraps the Playground and Examples - pages and only registers the routes when the flag is truthy. -2. [`src/hooks/use-hash-route.ts`](src/hooks/use-hash-route.ts) — `#/playground` - and `#/examples` resolve to `chat` when the flag is off, so old deep links - degrade gracefully. -3. [`src/lib/backend/index.ts`](src/lib/backend/index.ts) — `getDefaultBackend()` - returns the mock when the flag is on, otherwise the real backend stub. - -Vite/Rolldown inlines `import.meta.env.VITE_PLAYGROUND` as a literal at -build time. The dead branch (and every transitive import) is then dropped -by tree-shaking. +[`src/lib/backend/index.ts`](src/lib/backend/index.ts) keys off +`import.meta.env.DEV`, which Vite/Rolldown inlines as a literal at build time, +so `mockBackend` (and its transitive imports) is dropped by tree-shaking in +production. ### Verifying a prod build is clean ```bash -npm run build -# expect: a single index-*.js, no Playground-*.js or Examples-*.js chunks - -# none of these strings should appear in dist/assets/*.js: +pnpm build +# the scenario ids live in Storybook only, so none should appear in the app +# bundle: grep -E '"happy-(plan|ask|agent)"|"abort-mid-thought"|"long-markdown"' dist/assets/*.js && echo "LEAK" || echo "clean" ``` -A flag-on build (`VITE_PLAYGROUND=1 npm run build`) emits separate -`Playground-*.js` and `Examples-*.js` chunks — that's the expected dev/staging -layout, not the production layout. +Storybook builds separately: `pnpm build-storybook` emits `storybook-static/`, +which is git-ignored and never embedded into the `console` binary. ## Adding a new scenario -Three steps. Average size is 30–60 lines. +Four steps. Average size is 30–60 lines. -1. Create `src/pages/Playground/scenarios/.ts`. Use the helpers from - [`scenarios/helpers.ts`](src/pages/Playground/scenarios/helpers.ts): +1. Create `src/stories/playground/scenarios/.ts`. Use the helpers from + [`scenarios/helpers.ts`](src/stories/playground/scenarios/helpers.ts): ```ts import { makeBackend, streamAssistant, streamThought } from './helpers' @@ -439,7 +474,7 @@ Three steps. Average size is 30–60 lines. ``` 2. Register it in - [`scenarios/index.ts`](src/pages/Playground/scenarios/index.ts): + [`scenarios/index.ts`](src/stories/playground/scenarios/index.ts): ```ts import { myScenario } from './my-scenario' @@ -457,7 +492,15 @@ Three steps. Average size is 30–60 lines. ] ``` -3. Add a row to the table in [the Scenarios section](#scenarios) of this +3. Surface it as a story. Add an export to the group's + `Playground/*.stories.tsx` file (e.g. `HappyPaths.stories.tsx`), or create a + new group file if you added a new `ScenarioGroup`: + + ```ts + export const MyScenario: Story = scenarioStory('my-scenario') + ``` + +4. Add a row to the table in [the Scenarios section](#scenarios) of this doc. The table is the regression contract — keep it in sync. ## Out of scope diff --git a/console/web/README.md b/console/web/README.md index 30d75f12..ff5c5fe6 100644 --- a/console/web/README.md +++ b/console/web/README.md @@ -5,12 +5,12 @@ Tailwind v4 and styled to the iii Schematic design system (see [`../DESIGN.md`](../DESIGN.md) for the full spec). It runs entirely client-side with mocked streaming, so there are no API keys -to configure. The mock — and an interactive Playground that exercises every -streaming edge case (errors, aborts, multi-function runs, long markdown, …) — -ships behind the `VITE_PLAYGROUND` flag, on by default in dev and off in -prod. Drop a real provider in by replacing one file +to configure. The mock backend ships in dev (`pnpm dev`) and is tree-shaken +out of production builds. Every component variant and every streaming edge +case (errors, aborts, multi-function runs, long markdown, …) lives in +Storybook (`pnpm storybook`). Drop a real provider in by replacing one file (see [Swapping in a real backend](#swapping-in-a-real-backend) below) and -[`PLAYGROUND.md`](./PLAYGROUND.md) is the contract you have to honor. +[`PLAYGROUND.md`](./PLAYGROUND.md) is the streaming contract you have to honor. ## Quickstart @@ -30,6 +30,8 @@ Then open the printed `Local:` URL (Vite picks the first free port from | `npm run build` | Type-check, then build a static bundle. | | `npm run preview` | Serve the built bundle locally. | | `npm run typecheck`| Type-check without emitting. | +| `npm run storybook`| Component + scenario stories on `:6006`. | +| `npm run build-storybook` | Build the static Storybook site. | ## What's in the box @@ -46,10 +48,12 @@ Then open the printed `Local:` URL (Vite picks the first free port from Three canned bodies — one per mode — exercise headings, lists, fenced code, blockquotes, and inline code on the first run. See [`PLAYGROUND.md`](./PLAYGROUND.md) for the full contract. -- **Playground** at `#/playground` (dev only) — a chat surface driven by a - catalog of scenarios (errors, aborts, multi-function runs, slow/fast streams, - long markdown) that stress every corner of the streaming contract. Useful - before swapping in a real backend. +- **Storybook** (`pnpm storybook`) — the component spec sheet (composer, + messages, function views, primitives, schema form, worker config, type, + color) plus the streaming **playground**: a chat surface driven by a catalog + of scenarios (errors, aborts, multi-function runs, slow/fast streams, long + markdown) that stress every corner of the streaming contract. Useful before + swapping in a real backend. - **Model picker** and **mode picker** (`plan` / `ask` / `agent`) wired into the canned response so you can see the values flow through. - **File attachments** via a hidden file input. Previewable text/image @@ -66,7 +70,7 @@ Then open the printed `Local:` URL (Vite picks the first free port from ``` src/ main.tsx - App.tsx # routing + flag-guarded lazy() for dev pages + App.tsx # routing (traces + configuration) + always-on chat dock index.css # Tailwind v4 + iii Schematic tokens + utilities lib/ utils.ts # cn = twMerge(clsx(...)) @@ -76,20 +80,24 @@ src/ types.ts # StreamEvent, ChatBackend, ChatStreamOptions mock.ts # dev-only mock; tree-shaken in prod real.ts # ← swap this stub for your provider - index.ts # getDefaultBackend() picks one based on flag + index.ts # getDefaultBackend() = mock in dev, real in prod types/chat.ts # Conversation, Message, Mode, ModelId, Attachment hooks/ use-conversations.ts # state + persistence - use-hash-route.ts # #/ #/playground #/examples + use-hash-route.ts # #/traces #/configuration use-theme.ts # theme + persistence components/ - ui/ # iii Schematic primitives + ui/ # iii Schematic primitives (+ co-located *.stories.tsx) sidebar/ # ConversationSidebar + ConversationRow - chat/ # ChatView, Composer, LexicalShell, Message, etc. + chat/ # ChatView, Composer, Message, … (+ *.stories.tsx) pages/ - Chat.tsx # the production chat surface - Examples/ # spec sheet of component variants (dev only) - Playground/ # interactive scenario sandbox (dev only) + Configuration/ # console + workers config surfaces + Traces/ # trace explorer + stories/ # Storybook-only assets (never in the app bundle) + decorators.tsx # shared decorators (.workers-tab scope, padding) + fixtures/ # mock data for the component stories + design/ # typography / color / loading token sheets + playground/ # ChatView harness + EventLog + scenario stories scenarios/ # one ChatBackend per file ``` @@ -132,9 +140,8 @@ export const realBackend: ChatBackend = { ``` To verify your implementation against the same edge cases the mock survives, -flip the flag on (`VITE_PLAYGROUND=1 npm run dev`), open `#/playground`, and -walk every scenario in the picker. If they all render correctly, your -backend is contract-clean. +open Storybook (`pnpm storybook`) and walk every story under **Playground**. +If they all render correctly, your backend is contract-clean. ## Design system diff --git a/console/web/biome.json b/console/web/biome.json index 0ea45222..79932a5c 100644 --- a/console/web/biome.json +++ b/console/web/biome.json @@ -6,7 +6,7 @@ "useIgnoreFile": true }, "files": { - "includes": ["**", "!!**/dist"] + "includes": ["**", "!!**/dist", "!!**/storybook-static"] }, "formatter": { "enabled": true, diff --git a/console/web/docs/custom-function-components.md b/console/web/docs/custom-function-components.md index 1a7c9ca1..ebbb5bde 100644 --- a/console/web/docs/custom-function-components.md +++ b/console/web/docs/custom-function-components.md @@ -245,50 +245,51 @@ const terminal = Rename tab labels if "terminal" is wrong for your UX (`custom` / `preview` / keep generic **preview** + **raw json**). -### 5. Console playground (required) +### 5. Storybook stories (required) -Ship **two** dev-only surfaces. Both are gated by `VITE_PLAYGROUND` (on in `.env.development`). Run `pnpm dev` in `console/web` and use the header toggle **chat / playground / examples**. +Every component variant and streaming scenario lives in Storybook. Run +`pnpm storybook` in `console/web`. A new function renderer needs two kinds of +story: -| Surface | Route | Purpose | -|---------|-------|---------| -| **Examples** | `#/examples` | Static spec sheet — every variant visible at once, no send button. Best for pixel-polishing a single card (pending, running, done, errors). | -| **Playground** | `#/playground` | Live chat driven by a `ChatBackend` scenario — exercises the streaming contract (`fcall-start` → `fcall-end`) and the event log rail. Best for lifecycle and regression before a real backend. | +| Kind | Where | Purpose | +|------|-------|---------| +| **Fixture story** | `src/components/chat/FunctionCallMessage.stories.tsx` | Static spec sheet — every variant visible at once, no send button. Best for pixel-polishing a single card (pending, running, done, errors). | +| **Playground story** | `src/stories/playground/*.stories.tsx` | Live chat driven by a `ChatBackend` scenario — exercises the streaming contract (`fcall-start` → `fcall-end`) and the event-log rail. Best for lifecycle and regression before a real backend. | See [`PLAYGROUND.md`](../PLAYGROUND.md) for the `StreamEvent` contract. -#### 5a. Examples — one card per tool (required) +#### 5a. Fixtures — one card per tool (required) -Create `src/pages/Examples/sections/myfeature-fixtures.ts` with a `base()` factory (copy `sandbox-fixtures.ts`). Export: +Create `src/stories/fixtures/myfeature-fixtures.ts` with a `base()` factory (copy `sandbox-fixtures.ts`). Export: - One **done** fixture per `function_id` (mix envelope-wrapped and raw payloads). - Extra fixtures for states your renderer cares about: **pending** (with `pendingApproval: true`), **running**, **error** / gate denial, edge cases (empty output, truncated grep, etc.). -Register in `src/pages/Examples/sections/message-variants.tsx`: +Add a family gallery story in `src/components/chat/FunctionCallMessage.stories.tsx`: ```tsx -import { myfeatureFixtures } from './myfeature-fixtures' +import { myfeatureFixtures } from '@/stories/fixtures/myfeature-fixtures' -{myfeatureFixtures.map((fixture) => ( - - - -))} +export const MyFeatureFamily: Story = { + name: 'myfeature family', + render: () => , +} ``` -Open `#/examples` and confirm the **terminal** tab (default) and **raw json** tab for each card. +Open Storybook → **Chat / FunctionCallMessage / myfeature family** and confirm the **terminal** tab (default) and **raw json** tab for each card. -**Sandbox reference:** `sandbox-fixtures.ts` + the `sandboxFixtures.map(...)` block at the bottom of `message-variants.tsx`. +**Sandbox reference:** `src/stories/fixtures/sandbox-fixtures.ts` + the `SandboxFamily` story in `FunctionCallMessage.stories.tsx`. #### 5b. Playground — at least one scenario (required) -Add an interactive scenario under `src/pages/Playground/scenarios/`. Every new function family needs **at least one** scenario registered in `scenarios/index.ts` so `#/playground` can exercise it end-to-end. +Add an interactive scenario under `src/stories/playground/scenarios/`. Every new function family needs **at least one** scenario registered in `scenarios/index.ts` so the **Playground** stories exercise it end-to-end. 1. **Create** `myfeature-hero.ts` (name as you like) using `makeBackend` + `streamFcall` from `scenarios/helpers.ts`: ```ts import { makeBackend, streamAssistant, streamFcall, streamThought } from './helpers' -// Reuse wire payloads from Examples when possible: -import { sandboxExecDone } from '@/pages/Examples/sections/sandbox-fixtures' +// Reuse wire payloads from the fixtures when possible: +import { sandboxExecDone } from '@/stories/fixtures/sandbox-fixtures' export const myfeatureHero = makeBackend( 'myfeature-hero', @@ -307,7 +308,7 @@ export const myfeatureHero = makeBackend( ) ``` -2. **Register** in `scenarios/index.ts`: +2. **Register** in `scenarios/index.ts` (the `group` decides which `Playground/*.stories.tsx` file surfaces it; add an export there if you introduce a new group): ```ts import { myfeatureHero } from './myfeature-hero' @@ -323,20 +324,20 @@ import { myfeatureHero } from './myfeature-hero' }, ``` -3. **Verify:** `pnpm dev` → `#/playground` → pick your scenario → send any message → confirm the custom card renders (not JSON-only) and the right-hand event log shows `fcall-start` / `fcall-end`. +3. **Verify:** `pnpm storybook` → open your scenario under **Playground** → send any message → confirm the custom card renders (not JSON-only) and the right-hand event log shows `fcall-start` / `fcall-end`. **Coverage guidance:** -| Renderer feature | Examples fixture | Playground scenario | -|------------------|------------------|---------------------| +| Renderer feature | Fixture story | Playground scenario | +|------------------|---------------|---------------------| | Success / done | per `function_id` | at least one `streamFcall` with success output | | Pending approval | `pendingApproval: true` | `streamFcall({ pendingApproval: true, approvalWaitMs: … })` — see `pending-approval.ts` | | Running shimmer | `running: true` | shorten `waitMs` and watch mid-flight (or dedicated scenario) | -| Error / gate | error fixture on Examples | `output: { error: … }` — see `error-on-fcall.ts` or `sandboxFsWriteGateError` payloads | +| Error / gate | error fixture | `output: { error: … }` — see `error-on-fcall.ts` or `sandboxFsWriteGateError` payloads | -When you add a **new** `function_id` to an existing family, add an Examples `VariantCard` **and** extend a Playground scenario (or add a focused scenario) so the picker still covers it. +When you add a **new** `function_id` to an existing family, add a fixture (and a gallery story if it's a new family) **and** extend a Playground scenario (or add a focused scenario) so the stories still cover it. -**Sandbox gap:** Examples already list all 15 tools; Playground does not yet have a dedicated `sandbox::*` scenario — add `sandbox-exec.ts` (or similar) when touching sandbox as a template for others. +**Sandbox gap:** the fixtures already list all 15 tools; the playground does not yet have a dedicated `sandbox::*` scenario — add `sandbox-exec.ts` (or similar) when touching sandbox as a template for others. ### 6. Tests @@ -353,19 +354,20 @@ pnpm build pnpm exec biome check --write \ src/components/chat/myfeature \ src/components/chat/FunctionCallMessage.tsx \ - src/pages/Examples/sections/myfeature-fixtures.ts \ - src/pages/Playground/scenarios/myfeature-hero.ts \ - src/pages/Playground/scenarios/index.ts + src/components/chat/FunctionCallMessage.stories.tsx \ + src/stories/fixtures/myfeature-fixtures.ts \ + src/stories/playground/scenarios/myfeature-hero.ts \ + src/stories/playground/scenarios/index.ts ``` ### 8. Pre-merge smoke (required) ```bash -cd console/web && pnpm dev +cd console/web && pnpm storybook ``` -- `#/examples` — scroll your new `VariantCard`s; toggle **terminal** / **raw json**. -- `#/playground` — run your new scenario; approve a pending call if applicable. +- **Chat / FunctionCallMessage** — scroll your new family gallery; toggle **terminal** / **raw json**. +- **Playground** — run your new scenario; approve a pending call if applicable. --- @@ -477,11 +479,11 @@ Consider extracting `unwrapEnvelope` and a generic `parseFunctionErrorDisplay` t | `sandbox/ErrorView.tsx` | `SandboxErrorView` / invocation errors | | `sandbox/*View.tsx` | Per-tool UI | | `sandbox/__tests__/parsers.test.ts` | Unit tests | -| `pages/Examples/sections/sandbox-fixtures.ts` | Examples fixtures (required) | -| `pages/Examples/sections/message-variants.tsx` | Registers Examples cards | -| `pages/Playground/scenarios/*.ts` | Playground `ChatBackend` scenarios (required) | -| `pages/Playground/scenarios/index.ts` | Scenario picker registry | -| `pages/Playground/scenarios/helpers.ts` | `streamFcall`, `streamThought`, `makeBackend` | +| `stories/fixtures/sandbox-fixtures.ts` | Fixture data (required) | +| `components/chat/FunctionCallMessage.stories.tsx` | Registers fixture gallery stories | +| `stories/playground/scenarios/*.ts` | Playground `ChatBackend` scenarios (required) | +| `stories/playground/scenarios/index.ts` | Scenario registry | +| `stories/playground/scenarios/helpers.ts` | `streamFcall`, `streamThought`, `makeBackend` | | `PLAYGROUND.md` | Streaming contract for scenarios | | `FunctionCallMessage.tsx` | Host integration | diff --git a/console/web/package.json b/console/web/package.json index 5bcdfd2a..978797a9 100644 --- a/console/web/package.json +++ b/console/web/package.json @@ -6,14 +6,15 @@ "packageManager": "pnpm@10.18.2", "scripts": { "dev": "vite", - "dev:playground": "VITE_PLAYGROUND=1 vite", "build": "tsc -b && vite build", "preview": "vite preview", "typecheck": "tsc -b --noEmit", "lint": "biome check .", "lint:fix": "biome check --write .", "test": "vitest run", - "test:watch": "vitest" + "test:watch": "vitest", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "dependencies": { "@lexical/react": "^0.44.0", @@ -41,6 +42,9 @@ }, "devDependencies": { "@biomejs/biome": "^2.4.15", + "@storybook/addon-a11y": "^10.4.1", + "@storybook/addon-docs": "^10.4.1", + "@storybook/react-vite": "^10.4.1", "@tailwindcss/vite": "^4.3.0", "@types/dagre": "^0.7.54", "@types/hast": "^3.0.4", @@ -49,6 +53,7 @@ "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.2", "@vitest/coverage-v8": "^4.1.6", + "storybook": "^10.4.1", "tailwindcss": "^4.3.0", "typescript": "^6.0.3", "vite": "^8.0.13", diff --git a/console/web/pnpm-lock.yaml b/console/web/pnpm-lock.yaml index 933a6f36..aee42b26 100644 --- a/console/web/pnpm-lock.yaml +++ b/console/web/pnpm-lock.yaml @@ -78,9 +78,18 @@ importers: '@biomejs/biome': specifier: ^2.4.15 version: 2.4.15 + '@storybook/addon-a11y': + specifier: ^10.4.1 + version: 10.4.1(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + '@storybook/addon-docs': + specifier: ^10.4.1 + version: 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/react-vite': + specifier: ^10.4.1 + version: 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(esbuild@0.27.7)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) '@tailwindcss/vite': specifier: ^4.3.0 - version: 4.3.0(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0)) + version: 4.3.0(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) '@types/dagre': specifier: ^0.7.54 version: 0.7.54 @@ -98,10 +107,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: ^6.0.2 - version: 6.0.2(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0)) + version: 6.0.2(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) '@vitest/coverage-v8': specifier: ^4.1.6 version: 4.1.6(vitest@4.1.6) + storybook: + specifier: ^10.4.1 + version: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) tailwindcss: specifier: ^4.3.0 version: 4.3.0 @@ -110,30 +122,104 @@ importers: version: 6.0.3 vite: specifier: ^8.0.13 - version: 8.0.13(@types/node@25.8.0)(jiti@2.7.0) + version: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) vitest: specifier: ^4.1.6 - version: 4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0)) + version: 4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.29.3': resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -194,12 +280,174 @@ packages: '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.10.0': resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + '@emnapi/wasi-threads@1.2.1': resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@floating-ui/core@1.7.5': resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} @@ -221,6 +469,15 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -312,15 +569,238 @@ packages: peerDependencies: yjs: '>=13.5.22' + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + '@napi-rs/wasm-runtime@1.1.4': resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.130.0': resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} + '@oxc-resolver/binding-android-arm-eabi@11.20.0': + resolution: {integrity: sha512-IjfWOXRgJFNdORDl+Uf1aibNgZY2guOD3zmOhx1BGVb/MIiqlFTdmjpQNplSN58lhWehnX4UNqC3QwpUo8pjJg==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.20.0': + resolution: {integrity: sha512-QqslZAuFQG8Q9xm7JuIn8JUbvywhSBMVhuQHtYW+auirZJloS41oxUUaBXk7uUhZJgp44c5zQLeVvmFaDQB+2Q==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.20.0': + resolution: {integrity: sha512-MUcavykj2ewlR+kc5arpg4tC2RvzJkUxWtNv74pf7lcNk00GpIpN43vXMj+j6r4eMmfZhlb8hueKoIb8e9kAGQ==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.20.0': + resolution: {integrity: sha512-BGB16nRUK5Etiv//ihPyzj8Lj1px0mhh4YIfe0FDf045ywknfSm0GEbiRESpr6Q4K82AvnyaRIhhluHByvS4bg==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.20.0': + resolution: {integrity: sha512-JZgtePaqj3qmD5XFHJaSLWzHRxQu0LaPkdoM1KJXYADvAaa83ijXHclV3ej3CueeW0wxfIAbGCZVP45J0CA7uQ==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0': + resolution: {integrity: sha512-hOQ/p3ry3v3SchUBXicrrnszaI/UmYzM4wtS4RGfwgVUX7a+HbyQSzJ5aOzu+o6XZkFkS3ZXN4PZAzhOb77OSg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.20.0': + resolution: {integrity: sha512-2ArPksaw0AqeuGBfoS715VF+JvJQAhD2niWgjE5hVO+L+nAfikVQopvngCMX9x4BD8itWoQ3dnikrQyl5Ho5Jg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.20.0': + resolution: {integrity: sha512-0bJnmYFp62JdZ4nVMDUZ/C58BCZOCcqgKtnUlp7L9Ojf/czIN+3j72YlLPeWLkzlr6SlYvIQA4SGV/HyO0d+qg==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-musl@11.20.0': + resolution: {integrity: sha512-wKHHzPKZo7Ufhv/Bt6yxT7FOgnIgW4gwXcJUipkShGp68W3wGVqvr1Sr0fY65lN0Oy6y41+g2kIDvkgZaMMUkw==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': + resolution: {integrity: sha512-RN8goF7Ie0B79L4i4G6OeBocTgSC56vJbQ65VJje+oXnldVpLnOU7j/AQ/dP94TcCS+Yh6WG8u3Qt4ETteXFNQ==} + cpu: [ppc64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': + resolution: {integrity: sha512-5l1yU6/xQEqLZRzxqmMxJfWPslpwCmBsdDGaBvABPehxquCXDC7dd7oraNdKSJUMDXSM7VvVj8H2D2FTjU7oWw==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': + resolution: {integrity: sha512-xHEvkbgz6UC+A3JOyDQy76LkUaxsNSfIr3/GV8slwZsnuooJiIB34gzJfsyvR4JdCYNUUPsRJc/w/oWkODu+hg==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': + resolution: {integrity: sha512-aWPDUUmSeyHvlW+SoEUd+JIJsQhVhu6a5tBpDRMu058naPAchTgAVGCFy35zjbnFlt0i8hLWziff6HX0D3LU4g==} + cpu: [s390x] + os: [linux] + + '@oxc-resolver/binding-linux-x64-gnu@11.20.0': + resolution: {integrity: sha512-x2YeSimvhJjKLVD8KSu8f/rqU1potcdEMkApIPJqjZWN7c2Fpt4g2X32WDg1p+XDAmyT7nuQGe0vnhvXeLbH+g==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-musl@11.20.0': + resolution: {integrity: sha512-kcRLEIxpZefeYfLChjpgFf3ilBzRDZ+yobMrpRsQlSrxuFGtm3U6PMU7AaEpMqo3NfDGVyJJseAjnRLzMFHjwQ==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-openharmony-arm64@11.20.0': + resolution: {integrity: sha512-HHcfnApSZGtKhTiHqe8OZruOZe5XuFQH5/E0Yhj3u8fnFvzkM4/k6WjacUf4SvA0SPEAbfbgYmVPuo0VX/fIBQ==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.20.0': + resolution: {integrity: sha512-Tn0y1XOFYHNfK1wp1Z5QK8Rcld/bsOwRISQXfqAZ5IBpv8Gz1IvV39fUWNprqNdRizgcvFhOzWwFun2zkJsyBg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.20.0': + resolution: {integrity: sha512-qPi25YNPe4YenS8MgsQU2+bIFHxxpLx1LVna2444cEHqNPhNjvWf9zqj4aWE43H9LpAsTmkkAlA3eL5ElBU3mA==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.20.0': + resolution: {integrity: sha512-Wb14jWEW8huH6It9F6sXd9vrYmIS7pMrgkU6sxpLxkP+9z+wRgs71hUEhRpcn8FOXAFa27FVWfY2tRpbfTzfLw==} + cpu: [x64] + os: [win32] + '@preact/signals-core@1.14.2': resolution: {integrity: sha512-RZHdBj9ZF4n40Rp4jS052EHHjBWf96P9oNdXPfhQTovCuWY9iQn3Gq+gOTJSgBO9A/JBuPfMOWsSX/lIU9Pc/A==} @@ -742,9 +1222,104 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@storybook/addon-a11y@10.4.1': + resolution: {integrity: sha512-MGft/IXjJ20a9KbaSVG9bHTAAoanbucKrgEiJJRNqpim8DsXA01+XTdSk17LmiOCB203Rrq9mWgdQ6+79cc8iA==} + peerDependencies: + storybook: ^10.4.1 + + '@storybook/addon-docs@10.4.1': + resolution: {integrity: sha512-IYqUdjoZe4VO2LFZlKL/gwy7DsQSWCq6hX+zc1MBmZo04yycDASk1tte57n9pdlW3ajw9yYMF/+lVBi+xQjyvw==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.4.1 + peerDependenciesMeta: + '@types/react': + optional: true + + '@storybook/builder-vite@10.4.1': + resolution: {integrity: sha512-/oyQrXoNOqN8SW5hNnYP+I1uvgFxKxWXj/EP6NXYzc5SQwImofgru+D2+6gDhL0+Q//+Hx05DJoQO2omvUJ8bQ==} + peerDependencies: + storybook: ^10.4.1 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/csf-plugin@10.4.1': + resolution: {integrity: sha512-WdPepGBxDGOUDjYd8KxMtcf+us/2PAcnBczl77XtrnxxHNs0jWesxKkiJ9yiuGrge4BPhDeAj6rxjbBoaHxLBA==} + peerDependencies: + esbuild: '*' + rollup: '*' + storybook: ^10.4.1 + vite: '*' + webpack: '*' + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.0.2': + resolution: {integrity: sha512-KZBCpXsshAIjczYNXR/rlxEtCUX/eAbpFNwKi8bcOomrLA4t/SyPz5RF+lVPO2oZBUE4sAkt43mfJUevQDSEEw==} + peerDependencies: + 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 + + '@storybook/react-dom-shim@10.4.1': + resolution: {integrity: sha512-6QFqfDNH4DMrt7yHKRfpqRopsVUc/Az+sXIdJ39IetYnHUxL3nW4NVaPc6uy/8Qi8urzUyEXL/nn7cpSIP2aPQ==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + 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 + storybook: ^10.4.1 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.4.1': + resolution: {integrity: sha512-zY6OzaXvXqBIUyc5ySE55/LAPQiF+o9ZyhQI978WMu4mY/fL7FpQ+ZVHRUCCgz/wTXtqE9jJwd/N10HI1kD0/Q==} + peerDependencies: + 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 + storybook: ^10.4.1 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/react@10.4.1': + resolution: {integrity: sha512-WuYz4NaUk4gmFAMliSpCbV8w6jP5OY9juBfw1huwzu2S/k5FhnVXwmrUaL0fmf3Bq/7NgkzmBBbZr6I6LuHayQ==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + 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 + storybook: ^10.4.1 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + '@tailwindcss/node@4.3.0': resolution: {integrity: sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==} @@ -852,9 +1427,38 @@ packages: '@tanstack/virtual-core@3.14.0': resolution: {integrity: sha512-JLANqGy/D6k4Ujmh8Tr25lGimuOXNiaVyXaCAZS0W+1390sADdGnyUdSWNIfd49gebtIxGMij4IktRVzrdr12Q==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -885,6 +1489,9 @@ packages: '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -897,6 +1504,9 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -914,6 +1524,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} @@ -948,6 +1561,9 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.6': resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} @@ -962,6 +1578,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.6': resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} @@ -971,12 +1590,21 @@ packages: '@vitest/snapshot@4.1.6': resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.6': resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.6': resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + '@xyflow/react@12.10.2': resolution: {integrity: sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==} peerDependencies: @@ -986,23 +1614,80 @@ packages: '@xyflow/system@0.0.76': resolution: {integrity: sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + ast-v8-to-istanbul@1.0.0: resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + axe-core@4.11.4: + resolution: {integrity: sha512-KunSNx+TVpkAw/6ULfhnx+HWRecjqZGTOyquAoWHYLRSdK1tB5Ihce1ZW+UY3fj33bYAFWPu7W/GRSmmrCGuxA==} + engines: {node: '>=4'} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.32: + resolution: {integrity: sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==} + engines: {node: '>=6.0.0'} + hasBin: true + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + + caniuse-lite@1.0.30001793: + resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -1019,6 +1704,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -1035,6 +1724,9 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1091,6 +1783,22 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} @@ -1105,23 +1813,65 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + electron-to-chromium@1.5.364: + resolution: {integrity: sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==} + + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + enhanced-resolve@5.21.3: resolution: {integrity: sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==} engines: {node: '>=10.13.0'} + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + es-module-lexer@2.1.0: resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@5.0.0: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} @@ -1143,10 +1893,21 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-nonce@1.0.1: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -1157,6 +1918,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} @@ -1172,6 +1937,10 @@ packages: iii-browser-sdk@0.12.0: resolution: {integrity: sha512-Z8L/YEjBMtqBNKCxt12xn28K9BkWiKMXTJxF1N9tJ2+y7qDMMncDTyOfsymNfRO5usKmIXldhzbk3dicJ1tT2w==} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} @@ -1181,16 +1950,34 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} + engines: {node: '>=16'} + isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} @@ -1213,6 +2000,19 @@ packages: js-tokens@10.0.0: resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + lexical@0.44.0: resolution: {integrity: sha512-ReDUjRlFgkGoPWzvdjr7s16PUVpHATN+2NH2NiZs+PLlISTaIFFgKil2P467oP3Vg+XgmpDsUgmWZsFJTztYjg==} @@ -1297,11 +2097,25 @@ packages: longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@11.5.1: + resolution: {integrity: sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lucide-react@1.16.0: resolution: {integrity: sha512-dYwyPzb4MEKpGUmNYk3WKWPnMrHs3FKM+q94kAnJrcDIqqn1hq2xY8scaS2ovsOCM5D51ey2gaRG3PBb1vgoYQ==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1444,6 +2258,21 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1452,15 +2281,41 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-releases@2.0.46: + resolution: {integrity: sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==} + engines: {node: '>=18'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.20.0: + resolution: {integrity: sha512-CblytBiV/a/ZXY34dsVU2NxhIOxMXst8CvDCtyBelVITgd7PLrKzbEbA6oKLdPjvDKDzCiW48qzmzZ+mYaqn+g==} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1472,6 +2327,10 @@ packages: resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} engines: {node: ^10 || ^12 || >=14} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + prism-react-renderer@2.4.1: resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==} peerDependencies: @@ -1480,6 +2339,15 @@ packages: property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} + engines: {node: ^20.9.0 || >=22} + react-dom@19.2.6: resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: @@ -1490,6 +2358,9 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: @@ -1530,6 +2401,14 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} @@ -1542,14 +2421,27 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + rolldown@1.0.1: resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + semver@7.8.0: resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} engines: {node: '>=10'} @@ -1562,6 +2454,10 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} @@ -1571,9 +2467,36 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + storybook@10.4.1: + resolution: {integrity: sha512-V1Zd2e+gBFufqAQVZ1JR8KLqALsEZ3JYSBnWwQbKa6zCfWWanR6AFMyuOkLt2gZOgGp3h2Riuz88pGNVTQSG0A==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} @@ -1584,6 +2507,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + tabbable@6.4.0: resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} @@ -1597,6 +2524,9 @@ packages: resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1608,16 +2538,32 @@ packages: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -1647,6 +2593,16 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + use-callback-ref@1.3.3: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} @@ -1762,11 +2718,33 @@ packages: jsdom: optional: true + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yjs@13.6.30: resolution: {integrity: sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==} engines: {node: '>=16.0.0', npm: '>=8.0.0'} @@ -1794,19 +2772,123 @@ packages: snapshots: + '@adobe/css-tools@4.5.0': {} + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@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/core@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.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.2 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/runtime@7.29.7': {} + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@bcoe/v8-coverage@1.0.2': {} '@biomejs/biome@2.4.15': @@ -1850,16 +2932,105 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + '@floating-ui/core@1.7.5': dependencies: '@floating-ui/utils': 0.2.11 @@ -1885,6 +3056,14 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@6.0.3)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@6.0.3) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) + optionalDependencies: + typescript: 6.0.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2025,48 +3204,188 @@ snapshots: optionalDependencies: yjs: 13.6.30 - '@lexical/rich-text@0.44.0': - dependencies: - '@lexical/clipboard': 0.44.0 - '@lexical/dragon': 0.44.0 - '@lexical/selection': 0.44.0 - '@lexical/utils': 0.44.0 - lexical: 0.44.0 + '@lexical/rich-text@0.44.0': + dependencies: + '@lexical/clipboard': 0.44.0 + '@lexical/dragon': 0.44.0 + '@lexical/selection': 0.44.0 + '@lexical/utils': 0.44.0 + lexical: 0.44.0 + + '@lexical/selection@0.44.0': + dependencies: + lexical: 0.44.0 + + '@lexical/table@0.44.0': + dependencies: + '@lexical/clipboard': 0.44.0 + '@lexical/extension': 0.44.0 + '@lexical/utils': 0.44.0 + lexical: 0.44.0 + + '@lexical/text@0.44.0': + dependencies: + lexical: 0.44.0 + + '@lexical/utils@0.44.0': + dependencies: + '@lexical/selection': 0.44.0 + lexical: 0.44.0 + + '@lexical/yjs@0.44.0(yjs@13.6.30)': + dependencies: + '@lexical/selection': 0.44.0 + lexical: 0.44.0 + yjs: 13.6.30 + + '@mdx-js/react@3.1.1(@types/react@19.2.14)(react@19.2.6)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.2.14 + react: 19.2.6 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + + '@oxc-project/types@0.130.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.20.0': + optional: true + + '@oxc-resolver/binding-android-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.20.0': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-gnu@11.20.0': + optional: true + + '@oxc-resolver/binding-linux-arm64-musl@11.20.0': + optional: true - '@lexical/selection@0.44.0': - dependencies: - lexical: 0.44.0 + '@oxc-resolver/binding-linux-ppc64-gnu@11.20.0': + optional: true - '@lexical/table@0.44.0': - dependencies: - '@lexical/clipboard': 0.44.0 - '@lexical/extension': 0.44.0 - '@lexical/utils': 0.44.0 - lexical: 0.44.0 + '@oxc-resolver/binding-linux-riscv64-gnu@11.20.0': + optional: true - '@lexical/text@0.44.0': - dependencies: - lexical: 0.44.0 + '@oxc-resolver/binding-linux-riscv64-musl@11.20.0': + optional: true - '@lexical/utils@0.44.0': - dependencies: - '@lexical/selection': 0.44.0 - lexical: 0.44.0 + '@oxc-resolver/binding-linux-s390x-gnu@11.20.0': + optional: true - '@lexical/yjs@0.44.0(yjs@13.6.30)': - dependencies: - '@lexical/selection': 0.44.0 - lexical: 0.44.0 - yjs: 13.6.30 + '@oxc-resolver/binding-linux-x64-gnu@11.20.0': + optional: true - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@oxc-resolver/binding-linux-x64-musl@11.20.0': + optional: true + + '@oxc-resolver/binding-openharmony-arm64@11.20.0': + optional: true + + '@oxc-resolver/binding-wasm32-wasi@11.20.0': dependencies: '@emnapi/core': 1.10.0 '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@oxc-project/types@0.130.0': {} + '@oxc-resolver/binding-win32-arm64-msvc@11.20.0': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.20.0': + optional: true '@preact/signals-core@1.14.2': {} @@ -2431,8 +3750,114 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@rollup/pluginutils@5.4.0': + dependencies: + '@types/estree': 1.0.9 + estree-walker: 2.0.2 + picomatch: 4.0.4 + '@standard-schema/spec@1.1.0': {} + '@storybook/addon-a11y@10.4.1(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + dependencies: + '@storybook/global': 5.0.0 + axe-core: 4.11.4 + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + + '@storybook/addon-docs@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@mdx-js/react': 3.1.1(@types/react@19.2.14)(react@19.2.6) + '@storybook/csf-plugin': 10.4.1(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/icons': 2.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@storybook/react-dom-shim': 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + ts-dedent: 2.2.0 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - '@types/react-dom' + - esbuild + - rollup + - vite + - webpack + + '@storybook/builder-vite@10.4.1(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@storybook/csf-plugin': 10.4.1(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + ts-dedent: 2.2.0 + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) + transitivePeerDependencies: + - esbuild + - rollup + - webpack + + '@storybook/csf-plugin@10.4.1(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.27.7 + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + + '@storybook/react-dom-shim@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))': + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@storybook/react-vite@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(esbuild@0.27.7)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@6.0.3)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) + '@rollup/pluginutils': 5.4.0 + '@storybook/builder-vite': 10.4.1(esbuild@0.27.7)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) + '@storybook/react': 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3) + empathic: 2.0.1 + magic-string: 0.30.21 + react: 19.2.6 + react-docgen: 8.0.3 + react-dom: 19.2.6(react@19.2.6) + resolve: 1.22.12 + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + tsconfig-paths: 4.2.0 + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - esbuild + - rollup + - supports-color + - typescript + - webpack + + '@storybook/react@10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(typescript@6.0.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.4.1(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) + react: 19.2.6 + react-docgen: 8.0.3 + react-docgen-typescript: 2.4.0(typescript@6.0.3) + react-dom: 19.2.6(react@19.2.6) + storybook: 10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + '@tailwindcss/node@4.3.0': dependencies: '@jridgewell/remapping': 2.3.5 @@ -2494,12 +3919,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.0 '@tailwindcss/oxide-win32-x64-msvc': 4.3.0 - '@tailwindcss/vite@4.3.0(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0))': + '@tailwindcss/vite@4.3.0(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@tailwindcss/node': 4.3.0 '@tailwindcss/oxide': 4.3.0 tailwindcss: 4.3.0 - vite: 8.0.13(@types/node@25.8.0)(jiti@2.7.0) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) '@tanstack/query-core@5.100.10': {} @@ -2516,11 +3941,58 @@ snapshots: '@tanstack/virtual-core@3.14.0': {} + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/runtime': 7.29.7 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 optional: true + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -2555,6 +4027,8 @@ snapshots: '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.9 @@ -2569,6 +4043,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/mdx@2.0.13': {} + '@types/ms@2.1.0': {} '@types/node@25.8.0': @@ -2585,6 +4061,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/resolve@1.20.6': {} + '@types/trusted-types@2.0.7': {} '@types/unist@2.0.11': {} @@ -2593,10 +4071,10 @@ snapshots: '@ungap/structured-clone@1.3.1': {} - '@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0))': + '@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: 8.0.13(@types/node@25.8.0)(jiti@2.7.0) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) '@vitest/coverage-v8@4.1.6(vitest@4.1.6)': dependencies: @@ -2610,7 +4088,15 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0)) + vitest: 4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 '@vitest/expect@4.1.6': dependencies: @@ -2621,13 +4107,17 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.6(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0))': + '@vitest/mocker@4.1.6(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0))': dependencies: '@vitest/spy': 4.1.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.13(@types/node@25.8.0)(jiti@2.7.0) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 '@vitest/pretty-format@4.1.6': dependencies: @@ -2645,14 +4135,26 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@4.1.6': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.1.6': dependencies: '@vitest/pretty-format': 4.1.6 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@webcontainer/env@1.1.1': {} + '@xyflow/react@12.10.2(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@xyflow/system': 0.0.76 @@ -2676,22 +4178,70 @@ snapshots: d3-selection: 3.0.0 d3-zoom: 3.0.0 + acorn@8.16.0: {} + + ansi-regex@5.0.1: {} + + ansi-styles@5.2.0: {} + aria-hidden@1.2.6: dependencies: tslib: 2.8.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + assertion-error@2.0.1: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + ast-v8-to-istanbul@1.0.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 js-tokens: 10.0.0 + axe-core@4.11.4: {} + bail@2.0.2: {} + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.32: {} + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.32 + caniuse-lite: 1.0.30001793 + electron-to-chromium: 1.5.364 + node-releases: 2.0.46 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + + caniuse-lite@1.0.30001793: {} + ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} character-entities-html4@2.1.0: {} @@ -2702,6 +4252,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -2714,6 +4266,8 @@ snapshots: convert-source-map@2.0.0: {} + css.escape@1.5.1: {} + csstype@3.2.3: {} d3-color@3.1.0: {} @@ -2765,6 +4319,17 @@ snapshots: dependencies: character-entities: 2.0.2 + deep-eql@5.0.2: {} + + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + + define-lazy-prop@3.0.0: {} + dequal@2.0.3: {} detect-libc@2.1.2: {} @@ -2775,21 +4340,72 @@ snapshots: dependencies: dequal: 2.0.3 + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + electron-to-chromium@1.5.364: {} + + empathic@2.0.1: {} + enhanced-resolve@5.21.3: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 + es-errors@1.3.0: {} + es-module-lexer@2.1.0: {} + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + escape-string-regexp@5.0.0: {} + esprima@4.0.1: {} + estree-util-is-identifier-name@3.0.0: {} + estree-walker@2.0.2: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + expect-type@1.3.0: {} extend@3.0.2: {} @@ -2801,8 +4417,18 @@ snapshots: fsevents@2.3.3: optional: true + function-bind@1.1.2: {} + + gensync@1.0.0-beta.2: {} + get-nonce@1.0.1: {} + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + graceful-fs@4.2.11: {} graphlib@2.1.8: @@ -2811,6 +4437,10 @@ snapshots: has-flag@4.0.0: {} + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.9 @@ -2841,6 +4471,8 @@ snapshots: iii-browser-sdk@0.12.0: {} + indent-string@4.0.0: {} + inline-style-parser@0.2.7: {} is-alphabetical@2.0.1: {} @@ -2850,12 +4482,26 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + is-decimal@2.0.1: {} + is-docker@3.0.0: {} + is-hexadecimal@2.0.1: {} + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + is-plain-obj@4.1.0: {} + is-wsl@3.1.1: + dependencies: + is-inside-container: 1.0.0 + isomorphic.js@0.2.5: {} istanbul-lib-coverage@3.2.2: {} @@ -2875,6 +4521,12 @@ snapshots: js-tokens@10.0.0: {} + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + lexical@0.44.0: {} lib0@0.2.117: @@ -2934,10 +4586,20 @@ snapshots: longest-streak@3.1.0: {} + loupe@3.2.1: {} + + lru-cache@11.5.1: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lucide-react@1.16.0(react@19.2.6): dependencies: react: 19.2.6 + lz-string@1.5.0: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3298,12 +4960,78 @@ snapshots: transitivePeerDependencies: - supports-color + min-indent@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimist@1.2.8: {} + + minipass@7.1.3: {} + ms@2.1.3: {} nanoid@3.3.12: {} + node-releases@2.0.46: {} + obug@2.1.1: {} + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.20.0: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.20.0 + '@oxc-resolver/binding-android-arm64': 11.20.0 + '@oxc-resolver/binding-darwin-arm64': 11.20.0 + '@oxc-resolver/binding-darwin-x64': 11.20.0 + '@oxc-resolver/binding-freebsd-x64': 11.20.0 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.20.0 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.20.0 + '@oxc-resolver/binding-linux-arm64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-arm64-musl': 11.20.0 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-riscv64-musl': 11.20.0 + '@oxc-resolver/binding-linux-s390x-gnu': 11.20.0 + '@oxc-resolver/binding-linux-x64-gnu': 11.20.0 + '@oxc-resolver/binding-linux-x64-musl': 11.20.0 + '@oxc-resolver/binding-openharmony-arm64': 11.20.0 + '@oxc-resolver/binding-wasm32-wasi': 11.20.0 + '@oxc-resolver/binding-win32-arm64-msvc': 11.20.0 + '@oxc-resolver/binding-win32-x64-msvc': 11.20.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -3314,8 +5042,17 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + path-parse@1.0.7: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.1 + minipass: 7.1.3 + pathe@2.0.3: {} + pathval@2.0.1: {} + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -3326,6 +5063,12 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + prism-react-renderer@2.4.1(react@19.2.6): dependencies: '@types/prismjs': 1.26.6 @@ -3334,6 +5077,25 @@ snapshots: property-information@7.1.0: {} + react-docgen-typescript@2.4.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + react-docgen@8.0.3: + dependencies: + '@babel/core': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.12 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@19.2.6(react@19.2.6): dependencies: react: 19.2.6 @@ -3343,6 +5105,8 @@ snapshots: dependencies: react: 19.2.6 + react-is@17.0.2: {} + react-markdown@10.1.0(@types/react@19.2.14)(react@19.2.6): dependencies: '@types/hast': 3.0.4 @@ -3390,6 +5154,19 @@ snapshots: react@19.2.6: {} + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + remark-gfm@4.0.1: dependencies: '@types/mdast': 4.0.4 @@ -3424,6 +5201,13 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + rolldown@1.0.1: dependencies: '@oxc-project/types': 0.130.0 @@ -3445,25 +5229,65 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.1 '@rolldown/binding-win32-x64-msvc': 1.0.1 + run-applescript@7.1.0: {} + scheduler@0.27.0: {} + semver@6.3.1: {} + semver@7.8.0: {} siginfo@2.0.0: {} source-map-js@1.2.1: {} + source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} stackback@0.0.2: {} std-env@4.1.0: {} + storybook@10.4.1(@testing-library/dom@10.4.1)(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.0.2(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.27.7 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.20.0 + recast: 0.23.11 + semver: 7.8.0 + use-sync-external-store: 1.6.0(react@19.2.6) + ws: 8.21.0 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - '@testing-library/dom' + - bufferutil + - react + - react-dom + - utf-8-validate + stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-bom@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-indent@4.1.1: {} + style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -3476,6 +5300,8 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} + tabbable@6.4.0: {} tailwind-merge@3.6.0: {} @@ -3484,6 +5310,8 @@ snapshots: tapable@2.3.3: {} + tiny-invariant@1.3.3: {} + tinybench@2.9.0: {} tinyexec@1.1.2: {} @@ -3493,12 +5321,24 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@2.0.0: {} + tinyrainbow@3.1.0: {} + tinyspy@4.0.4: {} + trim-lines@3.0.1: {} trough@2.2.0: {} + ts-dedent@2.2.0: {} + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} typescript@6.0.3: {} @@ -3538,6 +5378,19 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.16.0 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.6): dependencies: react: 19.2.6 @@ -3567,7 +5420,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0): + vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -3576,13 +5429,14 @@ snapshots: tinyglobby: 0.2.16 optionalDependencies: '@types/node': 25.8.0 + esbuild: 0.27.7 fsevents: 2.3.3 jiti: 2.7.0 - vitest@4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0)): + vitest@4.1.6(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)): dependencies: '@vitest/expect': 4.1.6 - '@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@25.8.0)(jiti@2.7.0)) + '@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0)) '@vitest/pretty-format': 4.1.6 '@vitest/runner': 4.1.6 '@vitest/snapshot': 4.1.6 @@ -3599,7 +5453,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.13(@types/node@25.8.0)(jiti@2.7.0) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(jiti@2.7.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.8.0 @@ -3607,11 +5461,21 @@ snapshots: transitivePeerDependencies: - msw + webpack-virtual-modules@0.6.2: {} + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 + ws@8.21.0: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + + yallist@3.1.1: {} + yjs@13.6.30: dependencies: lib0: 0.2.117 diff --git a/console/web/src/App.tsx b/console/web/src/App.tsx index cb884aaa..92df27aa 100644 --- a/console/web/src/App.tsx +++ b/console/web/src/App.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useRef, useState } from 'react' import { ChatDock } from '@/components/chat/ChatDock' import { Dialog, @@ -21,30 +21,12 @@ import { cn } from '@/lib/utils' import { Configuration } from '@/pages/Configuration' import { Traces } from '@/pages/Traces' -const PLAYGROUND_ENABLED = !!import.meta.env.VITE_PLAYGROUND - -/* Lazy + flag-guarded: when VITE_PLAYGROUND is empty at build time, both - constants fold to null and Rolldown drops the dynamic-import targets along - with every transitive module (mock backend, scenarios, examples sections). */ -const Examples = PLAYGROUND_ENABLED - ? lazy(() => - import('@/pages/Examples').then((m) => ({ default: m.Examples })), - ) - : null - -const Playground = PLAYGROUND_ENABLED - ? lazy(() => - import('@/pages/Playground').then((m) => ({ default: m.Playground })), - ) - : null - -const VIEW_OPTIONS: { value: View; label: string }[] = PLAYGROUND_ENABLED - ? [ - { value: 'traces', label: 'traces' }, - { value: 'playground', label: 'playground' }, - { value: 'examples', label: 'examples' }, - ] - : [{ value: 'traces', label: 'traces' }] +/* The component spec sheet and the streaming-scenario playground now live in + Storybook (`pnpm storybook`), not as in-app routes. The header keeps a + single `traces` entry alongside the configuration gear. */ +const VIEW_OPTIONS: { value: View; label: string }[] = [ + { value: 'traces', label: 'traces' }, +] export function App() { const [theme, setTheme] = useTheme() @@ -109,17 +91,11 @@ export function App() { onCollapse={collapseDock} />
- }> - {view === 'configuration' ? ( - - ) : view === 'examples' && Examples ? ( - - ) : view === 'playground' && Playground ? ( - - ) : ( - - )} - + {view === 'configuration' ? ( + + ) : ( + + )}
@@ -331,13 +307,3 @@ function ShortcutsDialog({ open, onOpenChange }: ShortcutsDialogProps) { ) } - -function RouteFallback() { - return ( -
-
- loading… -
-
- ) -} diff --git a/console/web/src/components/chat/ChatDock.tsx b/console/web/src/components/chat/ChatDock.tsx index cd4b3e5b..315f7dad 100644 --- a/console/web/src/components/chat/ChatDock.tsx +++ b/console/web/src/components/chat/ChatDock.tsx @@ -102,7 +102,6 @@ export function ChatDock({ return ( <> - {/* biome-ignore lint/a11y/noStaticElementInteractions: focus-only Esc shortcut for keyboard users; chrome remains operable by mouse */} + ) +} + +interface ConfigurationRowProps { + entry: ConfigurationSchemaView + selected: boolean + onSelect: () => void +} + +function ConfigurationRow({ + entry, + selected, + onSelect, +}: ConfigurationRowProps) { + return ( + + ) +} + +function LoadingRows() { + return ( +
    + {[0, 1, 2].map((i) => ( +
  • + + +
  • + ))} +
+ ) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/WorkersTab.stories.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/WorkersTab.stories.tsx new file mode 100644 index 00000000..a67ef658 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/WorkersTab.stories.tsx @@ -0,0 +1,193 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { useEffect, useRef, useState } from 'react' +import { cn } from '@/lib/utils' +import { + mockValidate, + WORKER_CONFIG_FIXTURES, +} from '@/stories/fixtures/worker-fixtures' +import type { JsonValue } from './api' +import { isDirty } from './dirty' +import { EditorEmptyState } from './EmptyState' +import { SaveBar, type SaveStatus } from './SaveBar' +import { SchemaForm } from './schema-form/SchemaForm' +import { wt } from './typography' +import { WorkersList } from './WorkersList' + +const VIEWS = WORKER_CONFIG_FIXTURES.map((f) => f.view) +const INITIAL_VALUES: Record = Object.fromEntries( + WORKER_CONFIG_FIXTURES.map((f) => [f.view.id, f.value]), +) + +/** Mock `configuration::set` latency before the server "responds". */ +const SAVE_LATENCY_MS = 600 + +/** + * Self-contained reproduction of the workers tab's master-detail editor. + * Composes the real presentational pieces — `WorkersList`, `SchemaForm`, + * `SaveBar`, `EditorEmptyState` — over local state and the + * `WORKER_CONFIG_FIXTURES`, so the full edit / dirty / reset / save / error + * flow is exercisable in Storybook without the live `configuration` worker. + * + * Save is simulated: after a short delay we run `mockValidate`; a rejection + * maps a JSON Pointer onto the offending leaf (try saving `telemetry` with + * `sample_rate > 1`, or clearing the `database` url), and a success commits + * the draft to the per-id baseline so the dirty bar clears. + */ +function WorkerConfigHarness() { + const [selectedId, setSelectedId] = useState( + VIEWS[0]?.id ?? null, + ) + const [baselines, setBaselines] = + useState>(INITIAL_VALUES) + const [drafts, setDrafts] = + useState>(INITIAL_VALUES) + const [status, setStatus] = useState({ kind: 'idle' }) + const [errors, setErrors] = useState>(new Map()) + + // Track the live selection so an in-flight mock save doesn't splash its + // status/error onto a worker the operator switched to mid-flight. + const selectedRef = useRef(selectedId) + selectedRef.current = selectedId + + const selectedView = selectedId + ? (VIEWS.find((v) => v.id === selectedId) ?? null) + : null + const draft: JsonValue = selectedId ? (drafts[selectedId] ?? null) : null + const baseline: JsonValue = selectedId + ? (baselines[selectedId] ?? null) + : null + const dirty = selectedView ? isDirty(baseline, draft) : false + + function handleSelect(id: string | null) { + setSelectedId(id) + setStatus({ kind: 'idle' }) + setErrors(new Map()) + } + + function handleChange(next: JsonValue) { + if (!selectedId) return + setDrafts((cur) => ({ ...cur, [selectedId]: next })) + if (errors.size > 0) setErrors(new Map()) + setStatus((cur) => + cur.kind === 'error' || cur.kind === 'saved' ? { kind: 'idle' } : cur, + ) + } + + function handleReset() { + if (!selectedId) return + setDrafts((cur) => ({ ...cur, [selectedId]: baselines[selectedId] })) + setErrors(new Map()) + setStatus({ kind: 'idle' }) + } + + function handleSave() { + if (!selectedId) return + const id = selectedId + const value = drafts[id] + setStatus({ kind: 'saving' }) + setErrors(new Map()) + window.setTimeout(() => { + const invalid = mockValidate(id, value) + if (invalid) { + if (selectedRef.current === id) { + setStatus({ kind: 'error', message: invalid.message }) + setErrors(new Map([[invalid.pointer, invalid.message]])) + } + return + } + setBaselines((cur) => ({ ...cur, [id]: value })) + if (selectedRef.current === id) { + setStatus({ kind: 'saved', savedAtMs: Date.now() }) + } + }, SAVE_LATENCY_MS) + } + + useEffect(() => { + // we need to disable the overflow on the html element so the scrollbar is not shown + document.querySelector('html')?.style.setProperty('overflow', 'hidden') + + return () => { + document.querySelector('html')?.style.setProperty('overflow', 'auto') + } + }, []) + + return ( +
+
+ master-detail · mock fixtures +
+
+ + {selectedView ? ( +
+
+
+

+ {selectedView.name || selectedView.id} +

+ + {selectedView.id} + +
+ {selectedView.description ? ( +

+ {selectedView.description} +

+ ) : null} +
+
+
+ +
+ +
+
+ ) : ( + + )} +
+
+ ) +} + +const meta = { + title: 'Workers/WorkersTab', + component: WorkerConfigHarness, + parameters: { layout: 'fullscreen' }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const MasterDetail: Story = { + name: 'master-detail editor', + render: () => , +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/api.ts b/console/web/src/pages/Configuration/tabs/WorkersTab/api.ts new file mode 100644 index 00000000..56ec7ed4 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/api.ts @@ -0,0 +1,117 @@ +/** + * Typed wrappers over the `configuration` worker bus functions. + * Follows the same single-call-site pattern as `lib/providers.ts`: + * every external touchpoint funnels through `getIiiClient()` here so + * components and hooks never see the raw transport. + * + * The editor always reads with `raw: true` so `${VAR:default}` templates + * are returned verbatim and can be saved back unchanged. The expanded + * value is a separate read concern (preview hover, etc.) and is fetched + * via `getConfiguration(id, { raw: false })` on demand. + */ + +import { getIiiClient } from '@/lib/iii-client' + +/* ------------------------------------------------------------------ */ +/* Shared types */ +/* ------------------------------------------------------------------ */ + +/** + * A draft-07 JSON Schema object as exposed by the `configuration` worker. + * Kept as `Record` because we do not want to commit to a + * full schema typing — the form is structural and inspects keys at runtime. + */ +export type JsonSchema = Record + +/** + * Any JSON value that a configuration can hold. Mirrors the wire format — + * we never narrow it client-side. + */ +export type JsonValue = + | null + | string + | number + | boolean + | JsonValue[] + | { [key: string]: JsonValue } + +/** + * Schema-side view of a configuration entry as returned by + * `configuration::list` and `configuration::schema`. Crucially does NOT + * include the value — operators see the schema first, then load the value + * separately to keep secrets out of the list response. + */ +export interface ConfigurationSchemaView { + id: string + name: string + description: string + schema: JsonSchema + metadata?: JsonValue +} + +interface ListResponse { + configurations: ConfigurationSchemaView[] +} + +interface GetResponse { + id: string + value: JsonValue +} + +export interface SetResponse { + new_value: JsonValue + old_value: JsonValue | null +} + +export interface GetOptions { + /** + * When true (the default for the editor), `${VAR:default}` templates are + * returned verbatim so saves round-trip cleanly. Pass `false` to fetch + * the env-expanded preview. + */ + raw?: boolean +} + +/* ------------------------------------------------------------------ */ +/* Bus calls */ +/* ------------------------------------------------------------------ */ + +export async function listConfigurations(): Promise { + const client = await getIiiClient() + const response = await client.call('configuration::list', {}) + return response.configurations ?? [] +} + +export async function getConfigurationSchema( + id: string, +): Promise { + const client = await getIiiClient() + return client.call('configuration::schema', { id }) +} + +export async function getConfiguration( + id: string, + options: GetOptions = {}, +): Promise { + const client = await getIiiClient() + const response = await client.call('configuration::get', { + id, + raw: options.raw ?? true, + }) + return response.value +} + +export interface SetConfigurationPayload { + id: string + value: JsonValue +} + +export async function setConfiguration( + payload: SetConfigurationPayload, +): Promise { + const client = await getIiiClient() + return client.call( + 'configuration::set', + payload as unknown as Record, + ) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/dirty.test.ts b/console/web/src/pages/Configuration/tabs/WorkersTab/dirty.test.ts new file mode 100644 index 00000000..683a38e2 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/dirty.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it } from 'vitest' +import { isDirty, jsonEqual } from './dirty' + +describe('jsonEqual', () => { + it('treats primitive equals as equal', () => { + expect(jsonEqual(1, 1)).toBe(true) + expect(jsonEqual('a', 'a')).toBe(true) + expect(jsonEqual(true, true)).toBe(true) + expect(jsonEqual(null, null)).toBe(true) + }) + + it('treats different primitives as not equal', () => { + expect(jsonEqual(1, 2)).toBe(false) + expect(jsonEqual('a', 'b')).toBe(false) + expect(jsonEqual(0, '0')).toBe(false) + expect(jsonEqual(false, 0)).toBe(false) + }) + + it('treats null, undefined, and missing keys interchangeably', () => { + expect(jsonEqual(null, undefined)).toBe(true) + expect(jsonEqual({ a: null }, {})).toBe(true) + expect(jsonEqual({}, { a: null })).toBe(true) + expect(jsonEqual({ a: undefined as unknown as null }, {})).toBe(true) + }) + + it('ignores object key order', () => { + expect(jsonEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true) + }) + + it('recurses through nested objects', () => { + expect(jsonEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 1 } } })).toBe(true) + expect(jsonEqual({ a: { b: { c: 1 } } }, { a: { b: { c: 2 } } })).toBe( + false, + ) + }) + + it('compares arrays positionally', () => { + expect(jsonEqual([1, 2, 3], [1, 2, 3])).toBe(true) + expect(jsonEqual([1, 2, 3], [3, 2, 1])).toBe(false) + expect(jsonEqual([1, 2], [1, 2, 3])).toBe(false) + }) + + it('does not confuse arrays with objects', () => { + expect(jsonEqual([], {})).toBe(false) + }) + + it('returns true for dictionary key renames followed by reverts', () => { + const loaded = { databases: { primary: { url: 'sqlite:./db' } } } + const draft = { databases: { primary: { url: 'sqlite:./db' } } } + expect(jsonEqual(loaded, draft)).toBe(true) + }) + + it('returns false when a deeply nested value diverges', () => { + const loaded = { + databases: { primary: { pool: { max: 10 }, url: 'sqlite:./db' } }, + } + const draft = { + databases: { primary: { pool: { max: 20 }, url: 'sqlite:./db' } }, + } + expect(jsonEqual(loaded, draft)).toBe(false) + }) + + it('treats reorderings inside dictionaries as equal', () => { + const a = { primary: { x: 1 }, secondary: { x: 2 } } + const b = { secondary: { x: 2 }, primary: { x: 1 } } + expect(jsonEqual(a, b)).toBe(true) + }) +}) + +describe('isDirty', () => { + it('is false when loaded and draft match', () => { + expect(isDirty({ a: 1 }, { a: 1 })).toBe(false) + }) + + it('is true when draft adds a new key', () => { + expect(isDirty({ a: 1 }, { a: 1, b: 2 })).toBe(true) + }) + + it('is true when draft removes a key (and value was not null)', () => { + expect(isDirty({ a: 1, b: 2 }, { a: 1 })).toBe(true) + }) + + it('is false when the removed key was null in the loaded value', () => { + expect(isDirty({ a: 1, b: null }, { a: 1 })).toBe(false) + }) + + it('is true when an array element changes', () => { + expect(isDirty({ items: [1, 2, 3] }, { items: [1, 9, 3] })).toBe(true) + }) + + it('is false when neither side has a meaningful value (both null-ish)', () => { + expect(isDirty(null, undefined)).toBe(false) + }) +}) diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/dirty.ts b/console/web/src/pages/Configuration/tabs/WorkersTab/dirty.ts new file mode 100644 index 00000000..9e5ce32e --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/dirty.ts @@ -0,0 +1,84 @@ +/** + * Deep structural comparison for `JsonValue` trees plus a small helper + * that the editor uses to decide whether to show the save bar. + * + * Why a custom comparator instead of `JSON.stringify`-then-compare: + * + * - Object key order shouldn't affect dirtiness. Forms reorder keys all + * the time (e.g. spreading `{ ...obj, k: v }`), and we don't want + * that noise to flip the dirty bit. + * - `null` and `undefined` and missing keys should compare equal, because + * the schema form coerces unset → `null` while the loaded value may + * omit the key entirely. A naive comparison would mark every loaded + * value as dirty the first time the operator touched any field. + * + * The function never throws and always terminates: it never recurses on + * the same identical pair twice (short-circuit on `Object.is`), and the + * structural recursion is bounded by tree depth. + */ + +import type { JsonValue } from './api' + +/** + * Treat `null`, `undefined`, and missing-key as the same. Returns true + * if neither value carries meaningful content for the configuration. + */ +function isAbsent(value: JsonValue | undefined): boolean { + return value === null || value === undefined +} + +/** + * Structural deep-equal for JSON-shaped values. Key order is ignored + * for objects; null / undefined / missing keys collapse together. + */ +export function jsonEqual( + a: JsonValue | undefined, + b: JsonValue | undefined, +): boolean { + if (Object.is(a, b)) return true + if (isAbsent(a) && isAbsent(b)) return true + if (isAbsent(a) || isAbsent(b)) return false + + const aIsArray = Array.isArray(a) + const bIsArray = Array.isArray(b) + if (aIsArray !== bIsArray) return false + + if (aIsArray && bIsArray) { + if (a.length !== b.length) return false + for (let i = 0; i < a.length; i++) { + if (!jsonEqual(a[i], b[i])) return false + } + return true + } + + if (typeof a === 'object' && typeof b === 'object') { + const aObj = a as { [key: string]: JsonValue } + const bObj = b as { [key: string]: JsonValue } + // Union of keys so we catch "key present in one, missing in the + // other" — which still equals when both sides are absent-ish. + const seen = new Set() + for (const key of Object.keys(aObj)) { + seen.add(key) + if (!jsonEqual(aObj[key], bObj[key])) return false + } + for (const key of Object.keys(bObj)) { + if (seen.has(key)) continue + if (!jsonEqual(aObj[key], bObj[key])) return false + } + return true + } + + return a === b +} + +/** + * Returns true when the working draft differs structurally from the + * loaded baseline. Thin wrapper for naming — `isDirty(loaded, draft)` + * reads more clearly at call sites than `!jsonEqual(loaded, draft)`. + */ +export function isDirty( + loaded: JsonValue | undefined, + draft: JsonValue | undefined, +): boolean { + return !jsonEqual(loaded, draft) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/errors.ts b/console/web/src/pages/Configuration/tabs/WorkersTab/errors.ts new file mode 100644 index 00000000..9920f1e1 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/errors.ts @@ -0,0 +1,102 @@ +/** + * Parser for `SCHEMA_INVALID` (and adjacent) errors returned by + * `configuration::set`. The configuration worker delegates validation + * to the Rust `jsonschema` crate, whose `Display` impl varies by error + * kind — some carry an `instance_path`, some quote the offending key, + * some give only a type narrative. + * + * We can't recover a JSON Pointer from every error, so the parser is + * defensive: it always returns a human-readable `message`, and adds + * `pointer` only when extraction succeeds. The `SchemaForm` keys its + * per-field error display off `pointer`; the `SaveBar` always shows the + * `message` so the operator never loses sight of why their save failed. + * + * The pattern matchers below are intentionally conservative — over- + * matching would attach errors to the wrong field, which is worse + * than not attaching them at all. + */ + +export interface ParsedSetError { + message: string + /** JSON Pointer of the offending field, when extractable. */ + pointer?: string + /** Original error `code` (e.g. `SCHEMA_INVALID`) when surfaced. */ + code?: string +} + +/** + * Match `at /a/b/c` or `at "/a/b/c"` at the end of a sentence — the + * shape the engine and the `jsonschema` crate use for instance paths. + */ +const TRAILING_POINTER_RE = /\s+at\s+"?(\/[^\s"]*)"?\s*$/ + +/** + * Match `Failed to validate '/a/b/c'` and friends — alternative phrasing + * some validation paths use. + */ +const LEADING_PATH_RE = /^(?:.+?\s+)?at\s+"?(\/[^\s"]*)"?:\s*/ + +/** + * Match `"key" is a required property` — emit a pointer to that key at + * the root level when there's no other path information. This is best- + * effort: the key could belong to a nested object, but the engine's + * default formatting for `required` violations doesn't always include + * the parent path. Better to surface nothing than a wrong path; we + * only emit when no other pointer matched. + */ +const REQUIRED_PROPERTY_RE = /"([^"]+)" is a required property/ + +export function parseSetError(err: unknown): ParsedSetError { + if (!err) return { message: 'unknown error' } + + let code: string | undefined + let raw: string + if (err instanceof Error) { + raw = err.message + } else if (typeof err === 'object') { + const o = err as { + code?: unknown + message?: unknown + error?: unknown + } + if (typeof o.code === 'string') code = o.code + if (typeof o.message === 'string') raw = o.message + else if (typeof o.error === 'string') raw = o.error + else raw = String(err) + } else { + raw = String(err) + } + + // Strip noise the engine prepends to every validation error. + let message = raw + .replace(/^Error:\s*/i, '') + .replace(/^schema validation failed:\s*/i, '') + .trim() + + let pointer: string | undefined + + const trailing = message.match(TRAILING_POINTER_RE) + if (trailing) { + pointer = trailing[1] + message = message.replace(TRAILING_POINTER_RE, '').trim() + } else { + const leading = message.match(LEADING_PATH_RE) + if (leading) { + pointer = leading[1] + message = message.replace(LEADING_PATH_RE, '').trim() + } + } + + if (!pointer) { + const required = message.match(REQUIRED_PROPERTY_RE) + if (required) { + pointer = `/${required[1].replace(/~/g, '~0').replace(/\//g, '~1')}` + } + } + + return { + message: message.length > 0 ? message : raw, + pointer, + code, + } +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/hooks.ts b/console/web/src/pages/Configuration/tabs/WorkersTab/hooks.ts new file mode 100644 index 00000000..df4b8419 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/hooks.ts @@ -0,0 +1,87 @@ +/** + * TanStack Query hooks for the worker configuration registry. Shares a + * single key namespace (`['configuration', …]`) so mutations can target + * everything that depends on a given id with one `invalidateQueries` call. + */ + +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { + type ConfigurationSchemaView, + getConfiguration, + getConfigurationSchema, + type JsonValue, + listConfigurations, + type SetConfigurationPayload, + type SetResponse, + setConfiguration, +} from './api' + +/* ------------------------------------------------------------------ */ +/* Query keys */ +/* ------------------------------------------------------------------ */ + +const KEY_ROOT = ['configuration'] as const + +export const configurationKeys = { + all: KEY_ROOT, + list: () => [...KEY_ROOT, 'list'] as const, + schema: (id: string) => [...KEY_ROOT, 'schema', id] as const, + /** + * Raw value (env templates preserved). Used by the editor so saves are + * lossless. Cached separately from the env-expanded preview below. + */ + rawValue: (id: string) => [...KEY_ROOT, 'value', id, 'raw'] as const, + /** Env-expanded value. Used by preview tooltips. */ + expandedValue: (id: string) => + [...KEY_ROOT, 'value', id, 'expanded'] as const, +} + +/* ------------------------------------------------------------------ */ +/* Queries */ +/* ------------------------------------------------------------------ */ + +export function useConfigurationsList() { + return useQuery({ + queryKey: configurationKeys.list(), + queryFn: () => listConfigurations(), + }) +} + +export function useConfigurationSchema(id: string | null | undefined) { + return useQuery({ + queryKey: configurationKeys.schema(id ?? ''), + queryFn: () => getConfigurationSchema(id as string), + enabled: !!id, + }) +} + +export function useConfigurationValue(id: string | null | undefined) { + return useQuery({ + queryKey: configurationKeys.rawValue(id ?? ''), + queryFn: () => getConfiguration(id as string, { raw: true }), + enabled: !!id, + }) +} + +/* ------------------------------------------------------------------ */ +/* Mutations */ +/* ------------------------------------------------------------------ */ + +export function useSetConfiguration(id: string | null | undefined) { + const qc = useQueryClient() + return useMutation({ + mutationFn: (payload) => setConfiguration(payload), + onSuccess: (_data, variables) => { + const targetId = variables.id ?? id ?? '' + if (targetId) { + qc.invalidateQueries({ + queryKey: configurationKeys.rawValue(targetId), + }) + qc.invalidateQueries({ + queryKey: configurationKeys.expandedValue(targetId), + }) + } + qc.invalidateQueries({ queryKey: configurationKeys.list() }) + }, + }) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/index.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/index.tsx new file mode 100644 index 00000000..3725ac9b --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/index.tsx @@ -0,0 +1,76 @@ +import { useEffect } from 'react' +import { useConfigurationsList } from './hooks' +import { WorkerEditor, WorkerEditorEmptySelection } from './WorkerEditor' +import { WorkersList } from './WorkersList' + +interface WorkersTabProps { + selectedId: string | null + onSelect: (workerId: string | null) => void + /** + * Called whenever the editor's dirty state changes. Lifted up to the + * Configuration page so the tab strip can intercept tab switches with + * the same unsaved-changes confirm we use for worker selection. + */ + onDirtyChange: (dirty: boolean) => void +} + +/** + * Master-detail layout for worker configurations. The left rail lists + * every entry from `configuration::list`; the right pane shows the + * editor for the currently-selected id (URL-driven, see `useConfigurationRoute`). + * + * When the operator lands on `#/configuration/workers` with no id, we + * auto-select the first entry once the list resolves so the editor surface + * isn't blank by default. The auto-select is a one-shot — clearing the + * selection later (e.g. by editing the hash) is preserved. + * + * Dirty-state propagation: `onDirtyChange` flows the editor's dirty + * flag up to the Configuration shell, which uses it to gate tab + URL + * navigation. The editor itself owns its draft state and only emits + * dirty/clean transitions, not the draft value. + */ +export function WorkersTab({ + selectedId, + onSelect, + onDirtyChange, +}: WorkersTabProps) { + const listQuery = useConfigurationsList() + const entries = listQuery.data ?? [] + + // Land on the first entry once the list resolves so the editor isn't + // empty on the first visit. Subsequent renders only re-run when the + // selection or the available ids change, which keeps a deliberate + // `null` selection sticky after the operator clears it. + useEffect(() => { + if (selectedId) return + if (entries.length === 0) return + onSelect(entries[0].id) + }, [selectedId, entries, onSelect]) + + const selectedEntry = + selectedId != null + ? (entries.find((e) => e.id === selectedId) ?? null) + : null + + return ( +
+ + {selectedEntry ? ( + + ) : ( + 0} /> + )} +
+ ) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/ArrayField.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/ArrayField.tsx new file mode 100644 index 00000000..d3e6be27 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/ArrayField.tsx @@ -0,0 +1,206 @@ +import { useEffect, useMemo, useState } from 'react' +import { cn } from '@/lib/utils' +import type { JsonSchema, JsonValue } from '../api' +import { wt } from '../typography' +import { FieldDispatch, type FieldProps } from './FieldDispatch' +import { errorForField, FieldShell } from './FieldShell' +import { DeleteButton, DragHandle } from './RowChrome' +import { isInlineLeaf, schemaDefault } from './ref-resolver' +import { moveItem } from './reorder' +import { useDragReorder } from './useDragReorder' + +let _itemIdCounter = 0 +function makeItemId(): string { + _itemIdCounter += 1 + return `item-${_itemIdCounter}` +} + +/** + * `type: array` editor. + * + * Primitive items (string / number / boolean / enum) render as compact + * single-control rows — a drag handle, the value control, and a trash + * button — the same env-file vocabulary as `DictionaryField`. Complex + * items (objects, arrays, oneOf) keep a bordered box with a `#N` header so + * their nested structure stays legible. + * + * Rows carry a stable per-item id (not the array index) as their React + * key, so dragging a row moves its actual DOM — including any live Lexical + * editor — to the new slot instead of leaving it in place and re-seeding. + * The ids track add / delete / reorder in lockstep and re-sync when the + * array length changes externally (reset, mutation). + * + * `minItems` disables the per-row delete when removing would drop below + * the schema minimum. `maxItems` is not enforced inline — the server + * validates on save and a hard cap would be more annoying than the rare + * reject it prevents. + */ +export function ArrayField(props: FieldProps) { + const { label, schema, value, onChange, rootSchema, required, path, errors } = + props + const description = + typeof schema.description === 'string' ? schema.description : undefined + const minItems = typeof schema.minItems === 'number' ? schema.minItems : 0 + + const items = Array.isArray(value) ? value : [] + + const itemsSchema = useMemo(() => { + if ( + schema.items && + typeof schema.items === 'object' && + !Array.isArray(schema.items) + ) { + return schema.items as JsonSchema + } + return {} + }, [schema.items]) + const inlineItem = useMemo( + () => isInlineLeaf(itemsSchema, rootSchema), + [itemsSchema, rootSchema], + ) + + // Stable React keys for each row. Internal handlers keep them aligned; + // a length mismatch means the value was replaced from outside (reset / + // cache re-seed), so we regenerate. + const itemCount = items.length + const [ids, setIds] = useState(() => + Array.from({ length: itemCount }, makeItemId), + ) + useEffect(() => { + setIds((cur) => + cur.length === itemCount + ? cur + : Array.from({ length: itemCount }, makeItemId), + ) + }, [itemCount]) + + function handleItemChange(idx: number, next: JsonValue) { + const copy = items.slice() + copy[idx] = next + onChange(copy) + } + + function handleAdd() { + onChange([...items, schemaDefault(itemsSchema)]) + setIds((cur) => [...cur, makeItemId()]) + } + + function handleDelete(idx: number) { + onChange(items.filter((_, i) => i !== idx)) + setIds((cur) => cur.filter((_, i) => i !== idx)) + } + + function handleReorder(from: number, to: number) { + onChange(moveItem(items, from, to)) + setIds((cur) => moveItem(cur, from, to)) + } + + const canDelete = items.length > minItems + const dnd = useDragReorder(items.length, handleReorder) + + return ( +
+ + {() => ( +
+ {items.length === 0 + ? 'empty · add an item below' + : `${items.length} item${items.length === 1 ? '' : 's'}`} + {minItems > 0 ? ` · min ${minItems}` : null} +
+ )} +
+ +
    + {items.map((item, idx) => { + const isDragging = dnd.dragIndex === idx + const isOver = dnd.overIndex === idx && dnd.dragIndex !== idx + const itemField = ( + handleItemChange(idx, next)} + rootSchema={rootSchema} + path={[...path, idx]} + errors={errors} + hideLabel={inlineItem} + /> + ) + return ( +
  • + {inlineItem ? ( + <> + +
    {itemField}
    + handleDelete(idx)} + disabled={!canDelete} + /> + + ) : ( + <> +
    +
    + + + #{idx + 1} + +
    + handleDelete(idx)} + disabled={!canDelete} + /> +
    +
    {itemField}
    + + )} +
  • + ) + })} +
+ + +
+ ) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/BooleanField.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/BooleanField.tsx new file mode 100644 index 00000000..93f19757 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/BooleanField.tsx @@ -0,0 +1,45 @@ +import { ModeToggle } from '@/components/ui/ModeToggle' +import { wt } from '../typography' +import type { FieldProps } from './FieldDispatch' +import { errorForField, FieldShell } from './FieldShell' + +type BoolKey = 'true' | 'false' + +const BOOL_OPTIONS: { value: BoolKey; label: string }[] = [ + { value: 'true', label: 'true' }, + { value: 'false', label: 'false' }, +] + +/** + * Two-state segmented toggle for `type: boolean`. We reuse `ModeToggle` + * in `radio` mode so the visual matches the theme picker on the console + * settings tab — boolean preferences read consistently across the whole + * page instead of inventing a one-off toggle widget. + */ +export function BooleanField(props: FieldProps) { + const { label, schema, value, onChange, required, hideLabel } = props + const description = + typeof schema.description === 'string' ? schema.description : undefined + const current: BoolKey = value === true ? 'true' : 'false' + + return ( + + {() => ( + + value={current} + onChange={(next) => onChange(next === 'true')} + options={BOOL_OPTIONS} + variant="radio" + aria-label={label} + className={wt.toggle} + /> + )} + + ) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/DictionaryField.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/DictionaryField.tsx new file mode 100644 index 00000000..3d1d9c08 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/DictionaryField.tsx @@ -0,0 +1,274 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { Input } from '@/components/ui/Input' +import { cn } from '@/lib/utils' +import type { JsonSchema, JsonValue } from '../api' +import { wt } from '../typography' +import { FieldDispatch, type FieldProps } from './FieldDispatch' +import { errorForField, FieldShell } from './FieldShell' +import { DeleteButton, DragHandle } from './RowChrome' +import { isInlineLeaf, schemaDefault } from './ref-resolver' +import { moveItem } from './reorder' +import { useDragReorder } from './useDragReorder' + +interface DictionaryRow { + /** Stable identity across renames + reorders. Used as the React key so the + * row's DOM survives key edits without remounting the input (would lose + * focus). */ + id: string + /** Currently-typed key. Lives in row state because validation (uniqueness) + * happens before we commit to the canonical value object. */ + key: string +} + +let _rowIdCounter = 0 +function makeRowId(): string { + _rowIdCounter += 1 + return `row-${_rowIdCounter}` +} + +/** + * Env-file style key/value editor for `type: object` schemas that only + * declare `additionalProperties` (no concrete `properties`). Each entry is + * a row: a drag handle, the key input, the value control (inline when the + * value is a primitive — the common `${VAR}`-style env case — or stacked + * below for object/array values), and a trash button. An "+ add entry" + * button appends a fresh row. + * + * Order is operator-controlled: rows can be dragged (or arrow-keyed) to + * reorder, and the committed value object is rebuilt in row order. Object + * key insertion order is preserved through the save round-trip. + * + * Uniqueness is enforced visually (a warn tint + inline marker on the + * offending row) and structurally — duplicate or empty keys never commit + * upward to the value object, so a typo doesn't drop data. `minProperties` + * disables delete when removing would drop below the schema minimum. + */ +export function DictionaryField(props: FieldProps) { + const { label, schema, value, onChange, rootSchema, required, path, errors } = + props + const description = + typeof schema.description === 'string' ? schema.description : undefined + const minProperties = + typeof schema.minProperties === 'number' ? schema.minProperties : 0 + + const obj = isPlainObject(value) ? value : {} + const valueSchema = useMemo(() => { + if ( + schema.additionalProperties && + typeof schema.additionalProperties === 'object' && + !Array.isArray(schema.additionalProperties) + ) { + return schema.additionalProperties as JsonSchema + } + return {} + }, [schema.additionalProperties]) + const inlineValue = useMemo( + () => isInlineLeaf(valueSchema, rootSchema), + [valueSchema, rootSchema], + ) + + const [rows, setRows] = useState(() => + Object.keys(obj).map((key) => ({ id: makeRowId(), key })), + ) + const [lastAddedId, setLastAddedId] = useState(null) + + const lastSyncedKeysRef = useRef( + rows + .map((r) => r.key) + .sort() + .join('\u0000'), + ) + useEffect(() => { + const objKeys = Object.keys(obj).sort().join('\u0000') + const ourValidKeys = rows + .map((r) => r.key) + .filter((k) => k.length > 0) + .sort() + .join('\u0000') + if (objKeys !== lastSyncedKeysRef.current && objKeys !== ourValidKeys) { + setRows(Object.keys(obj).map((key) => ({ id: makeRowId(), key }))) + lastSyncedKeysRef.current = objKeys + } + }, [obj, rows]) + + const keyCounts = new Map() + for (const r of rows) { + if (r.key.length === 0) continue + keyCounts.set(r.key, (keyCounts.get(r.key) ?? 0) + 1) + } + + function commitRows(nextRows: DictionaryRow[]) { + setRows(nextRows) + const counts = new Map() + for (const r of nextRows) { + if (r.key.length === 0) continue + counts.set(r.key, (counts.get(r.key) ?? 0) + 1) + } + const allUnique = [...counts.values()].every((c) => c === 1) + const allNonEmpty = nextRows.every((r) => r.key.length > 0) + if (allUnique && allNonEmpty) { + const next: { [key: string]: JsonValue } = {} + for (const r of nextRows) { + next[r.key] = obj[r.key] ?? schemaDefault(valueSchema) + } + onChange(next) + // Order-independent: reordering keeps the same key set, so external + // sync won't fight a drag. + lastSyncedKeysRef.current = nextRows + .map((r) => r.key) + .sort() + .join('\u0000') + } + } + + function handleKeyChange(rowId: string, nextKey: string) { + commitRows(rows.map((r) => (r.id === rowId ? { ...r, key: nextKey } : r))) + } + + function handleValueChange(row: DictionaryRow, nextValue: JsonValue) { + if (row.key.length === 0) return + if ((keyCounts.get(row.key) ?? 0) > 1) return + onChange({ ...obj, [row.key]: nextValue }) + } + + function handleAdd() { + const existing = new Set(rows.map((r) => r.key)) + const base = 'new_entry' + let candidate = base + let i = 1 + while (existing.has(candidate)) { + candidate = `${base}_${i++}` + } + const id = makeRowId() + setLastAddedId(id) + commitRows([...rows, { id, key: candidate }]) + } + + function handleDelete(rowId: string) { + commitRows(rows.filter((r) => r.id !== rowId)) + } + + function handleReorder(from: number, to: number) { + commitRows(moveItem(rows, from, to)) + } + + const canDelete = rows.length > minProperties + const dnd = useDragReorder(rows.length, handleReorder) + + return ( +
+ + {() => ( +
+ {rows.length === 0 + ? 'no entries · add one below' + : `${rows.length} entr${rows.length === 1 ? 'y' : 'ies'}`} + {minProperties > 0 ? ` · min ${minProperties}` : null} +
+ )} +
+ + {rows.length > 0 ? ( +
    + {rows.map((row, idx) => { + const duplicate = (keyCounts.get(row.key) ?? 0) > 1 + const empty = row.key.length === 0 + const flagged = duplicate || empty + const isDragging = dnd.dragIndex === idx + const isOver = dnd.overIndex === idx && dnd.dragIndex !== idx + const valueField = ( + handleValueChange(row, next)} + rootSchema={rootSchema} + path={[...path, row.key]} + errors={errors} + hideLabel + /> + ) + return ( +
  • +
    + +
    + handleKeyChange(row.id, next)} + preserveCase + autoFocus={row.id === lastAddedId} + placeholder="entry_name" + aria-label="entry key" + className={cn(flagged && 'border-warn', wt.control)} + /> +
    + {inlineValue ? ( +
    {valueField}
    + ) : null} + handleDelete(row.id)} + disabled={!canDelete} + /> +
    + {flagged ? ( +

    + {empty ? 'key cannot be empty' : 'duplicate key'} +

    + ) : null} + {!inlineValue ? ( +
    + {valueField} +
    + ) : null} +
  • + ) + })} +
+ ) : null} + + +
+ ) +} + +function isPlainObject( + value: JsonValue | undefined, +): value is { [key: string]: JsonValue } { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/EnumField.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/EnumField.tsx new file mode 100644 index 00000000..559fe2b3 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/EnumField.tsx @@ -0,0 +1,84 @@ +import { Select } from '@/components/ui/Select' +import { cn } from '@/lib/utils' +import type { JsonValue } from '../api' +import { wt } from '../typography' +import type { FieldProps } from './FieldDispatch' +import { errorForField, FieldShell } from './FieldShell' + +/** + * Enum field for schemas with an `enum` keyword. Options come straight + * from the schema; we stringify each enum value so the Radix-based + * `Select` (which is string-typed) can render it, and re-hydrate the + * original primitive value on change so booleans and numbers round-trip. + * + * The schema's `enumNames` (a common extension) is honored when present + * so worker authors can give operators human-friendly labels without + * changing the underlying value. + */ +export function EnumField(props: FieldProps) { + const { label, schema, value, onChange, required, hideLabel } = props + const description = + typeof schema.description === 'string' ? schema.description : undefined + + const rawEnum = Array.isArray(schema.enum) ? (schema.enum as JsonValue[]) : [] + const enumNames = + Array.isArray(schema.enumNames) && + (schema.enumNames as unknown[]).every((n) => typeof n === 'string') + ? (schema.enumNames as string[]) + : null + + const options = rawEnum.map((raw, idx) => ({ + value: encodeEnumValue(raw), + label: enumNames?.[idx] ?? defaultLabel(raw), + })) + + // Leave the key `undefined` when the value is unset so the trigger shows + // the placeholder instead of stringifying `undefined` into `j:undefined`. + const currentKey = + value === undefined ? undefined : encodeEnumValue(value as JsonValue) + + return ( + + {() => ( + + value={currentKey} + onChange={(nextKey) => { + const match = rawEnum[options.findIndex((o) => o.value === nextKey)] + if (match !== undefined) onChange(match) + }} + options={options} + // Optional enums can be cleared back to "unset"; `null` is the + // form's canonical empty value (matches NumberField / NullableField). + allowEmpty={!required} + emptyLabel="unset" + onClear={() => onChange(null)} + aria-label={label} + placeholder="select…" + className={cn('w-full', wt.control)} + /> + )} + + ) +} + +/** + * Stable string encoding for an arbitrary enum value. We can't use the + * raw JSON because Radix Select requires string keys; the encoded form + * is opaque (`json:`) so it never collides with a real + * string enum that happens to equal another value's stringification. + */ +function encodeEnumValue(value: JsonValue): string { + if (typeof value === 'string') return `s:${value}` + return `j:${JSON.stringify(value)}` +} + +function defaultLabel(value: JsonValue): string { + if (typeof value === 'string') return value + return JSON.stringify(value) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/FieldDispatch.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/FieldDispatch.tsx new file mode 100644 index 00000000..16efe155 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/FieldDispatch.tsx @@ -0,0 +1,138 @@ +/** + * Single dispatch point that picks the right field component for a + * (schema, value) pair. Every recursive call from `ObjectSection`, + * `DictionaryField`, `ArrayField`, etc. funnels through here so the + * type → component mapping lives in exactly one place. + * + * Dispatch order is deliberately precedence-ordered, not type-driven: + * an `enum` always wins regardless of `type`, a `oneOf` wins next, and + * a nullable union is handled before the underlying type is rendered. + * Otherwise the standard JSON Schema type keyword decides. + */ + +import { cn } from '@/lib/utils' +import type { JsonSchema, JsonValue } from '../api' +import { wt } from '../typography' +import { ArrayField } from './ArrayField' +import { BooleanField } from './BooleanField' +import { DictionaryField } from './DictionaryField' +import { EnumField } from './EnumField' +import { NullableField } from './NullableField' +import { NumberField } from './NumberField' +import { ObjectSection } from './ObjectSection' +import { OneOfField } from './OneOfField' +import { isNullable, resolveSchema, schemaTypes } from './ref-resolver' +import { StringField } from './StringField' + +export interface FieldProps { + /** + * Display label for the field. The dispatcher itself does not render + * the label — every concrete field component is responsible for its + * own labelling because the label placement differs (inline for + * primitives, header for sections, row for dictionary entries). + */ + label: string + schema: JsonSchema + value: JsonValue | undefined + onChange: (next: JsonValue) => void + /** + * Full root schema, threaded through every recursive call so `$ref` + * lookups can resolve back to `#/definitions/…`. + */ + rootSchema: JsonSchema + /** + * True when the field is required by its parent object. Surfaces as a + * small `[required]` badge so operators know the entry can't be unset. + */ + required?: boolean + /** + * Field path from the root value. Threaded through so leaf fields can + * look up their own server-side error message in the `errors` map and + * so dictionary / array entries can build stable React keys. + */ + path: ReadonlyArray + /** + * Map of JSON-Pointer → human-readable error from the last + * `configuration::set` attempt. Passed through every level so the + * leaf that owns a given path can render the matching message. + */ + errors?: ReadonlyMap + /** + * Suppress the field's own label/header chrome when the parent already + * provides identity (e.g. a dictionary row whose key input IS the label, + * or an array row whose position is the label). Honored by + * `ObjectSection` and `FieldShell` (and forwarded by the primitive + * fields); primitives without a parent-supplied label fall back to their + * default rendering. + */ + hideLabel?: boolean +} + +export function FieldDispatch(props: FieldProps) { + const resolved = resolveSchema(props.schema, { root: props.rootSchema }) + const next: FieldProps = { ...props, schema: resolved } + + if (Array.isArray(resolved.enum)) { + return + } + if (Array.isArray(resolved.oneOf) || Array.isArray(resolved.anyOf)) { + return + } + if (isNullable(resolved)) { + return + } + + const types = schemaTypes(resolved) + const primary = types[0] + + if (primary === 'object') { + if (resolved.properties && typeof resolved.properties === 'object') { + return + } + if ( + resolved.additionalProperties && + resolved.additionalProperties !== false + ) { + return + } + return + } + if (primary === 'array') return + if (primary === 'string') return + if (primary === 'integer' || primary === 'number') { + return + } + if (primary === 'boolean') return + + // Unknown / unsupported — render a read-only JSON preview so the + // operator at least sees the stored value. + return +} + +/** + * Inline last-resort renderer for schemas the dispatcher can't classify + * (e.g. `{ type: "object" }` with no `properties` or `additionalProperties`, + * or a schema with no `type` and no `enum`/`oneOf`). Shown as a + * read-only JSON block with a short note so the operator knows why + * editing is disabled. + */ +function UnknownField({ label, value }: FieldProps) { + return ( +
+
+ {label} + + [unsupported schema · read-only] + +
+
+        {JSON.stringify(value, null, 2)}
+      
+
+ ) +} diff --git a/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/FieldShell.tsx b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/FieldShell.tsx new file mode 100644 index 00000000..d40ba258 --- /dev/null +++ b/console/web/src/pages/Configuration/tabs/WorkersTab/schema-form/FieldShell.tsx @@ -0,0 +1,114 @@ +/** + * Common label + description + error chrome for primitive (leaf) field + * components. Object / dictionary / array fields render their own headers + * because the spacing and emphasis differ, so this shell intentionally + * targets only the single-control case. + * + * The label is rendered as a small uppercased eyebrow label so it stays + * visually distinct from the schema's `title`, which the section header + * uses for object groups. Description (when present) lives directly under + * the control as small ink-faint helper text so the operator can scan + * down the form without bouncing back up between the label and the input. + */ + +import type { ReactNode } from 'react' +import { useId } from 'react' +import { cn } from '@/lib/utils' +import { wt } from '../typography' + +interface FieldShellProps { + label: string + description?: string + required?: boolean + errorMessage?: string | null + /** + * Render-prop for the control itself. Receives the generated DOM id + * so the control's `id` matches the rendered `