Explorar o código

stripe updates (#121)

* stripe cleanup

* full screen flag

* fixes

* stripe cleanup

* refactor

* fixes

* progressive

* fix data

* fixes

* fixes actions

* fixes

* fixes
Chris Tate hai 4 meses
pai
achega
a66aef17f9
Modificáronse 64 ficheiros con 11335 adicións e 191 borrados
  1. 5 0
      .gitignore
  2. 0 7
      examples/stripe-app/.env.example
  3. 30 80
      examples/stripe-app/README.md
  4. 3 0
      examples/stripe-app/api/.env.example
  5. 26 0
      examples/stripe-app/api/README.md
  6. 50 0
      examples/stripe-app/api/app/api/generate/route.ts
  7. 15 0
      examples/stripe-app/api/app/layout.tsx
  8. 8 0
      examples/stripe-app/api/app/page.tsx
  9. 6 0
      examples/stripe-app/api/next-env.d.ts
  10. 5 0
      examples/stripe-app/api/next.config.ts
  11. 23 0
      examples/stripe-app/api/package.json
  12. 41 0
      examples/stripe-app/api/tsconfig.json
  13. 14 0
      examples/stripe-app/drawer-app/.env.example
  14. 45 0
      examples/stripe-app/drawer-app/README.md
  15. 0 0
      examples/stripe-app/drawer-app/eslint.config.js
  16. 0 0
      examples/stripe-app/drawer-app/jest.config.js
  17. 3 1
      examples/stripe-app/drawer-app/package.json
  18. 0 0
      examples/stripe-app/drawer-app/pnpm-lock.yaml
  19. 42 0
      examples/stripe-app/drawer-app/scripts/setup.mjs
  20. 1 0
      examples/stripe-app/drawer-app/src/lib/config.ts
  21. 0 0
      examples/stripe-app/drawer-app/src/lib/render/catalog.ts
  22. 1586 0
      examples/stripe-app/drawer-app/src/lib/render/catalog/actions.ts
  23. 87 22
      examples/stripe-app/drawer-app/src/lib/render/catalog/components.tsx
  24. 0 0
      examples/stripe-app/drawer-app/src/lib/render/index.ts
  25. 13 1
      examples/stripe-app/drawer-app/src/lib/render/renderer.tsx
  26. 146 0
      examples/stripe-app/drawer-app/src/lib/stream-spec.ts
  27. 0 0
      examples/stripe-app/drawer-app/src/lib/stripe.ts
  28. 0 0
      examples/stripe-app/drawer-app/src/views/CustomerDetails.test.tsx
  29. 2 1
      examples/stripe-app/drawer-app/src/views/CustomerDetails.tsx
  30. 0 0
      examples/stripe-app/drawer-app/src/views/Customers.test.tsx
  31. 2 1
      examples/stripe-app/drawer-app/src/views/Customers.tsx
  32. 0 0
      examples/stripe-app/drawer-app/src/views/Home.test.tsx
  33. 55 41
      examples/stripe-app/drawer-app/src/views/Home.tsx
  34. 2 1
      examples/stripe-app/drawer-app/src/views/Invoices.tsx
  35. 2 1
      examples/stripe-app/drawer-app/src/views/PaymentDetails.tsx
  36. 2 1
      examples/stripe-app/drawer-app/src/views/Payments.tsx
  37. 2 1
      examples/stripe-app/drawer-app/src/views/Products.tsx
  38. 2 1
      examples/stripe-app/drawer-app/src/views/Subscriptions.tsx
  39. 0 0
      examples/stripe-app/drawer-app/src/views/brand_icon.svg
  40. 4 4
      examples/stripe-app/drawer-app/stripe-app.template.json
  41. 0 0
      examples/stripe-app/drawer-app/tsconfig.json
  42. 0 0
      examples/stripe-app/drawer-app/ui-extensions.d.ts
  43. 14 0
      examples/stripe-app/fullpage-app/.env.example
  44. 49 0
      examples/stripe-app/fullpage-app/README.md
  45. 17 0
      examples/stripe-app/fullpage-app/eslint.config.js
  46. 7 0
      examples/stripe-app/fullpage-app/jest.config.js
  47. 28 0
      examples/stripe-app/fullpage-app/package.json
  48. 5270 0
      examples/stripe-app/fullpage-app/pnpm-lock.yaml
  49. 42 0
      examples/stripe-app/fullpage-app/scripts/setup.mjs
  50. 1 0
      examples/stripe-app/fullpage-app/src/lib/config.ts
  51. 1358 0
      examples/stripe-app/fullpage-app/src/lib/render/catalog.ts
  52. 14 20
      examples/stripe-app/fullpage-app/src/lib/render/catalog/actions.ts
  53. 1405 0
      examples/stripe-app/fullpage-app/src/lib/render/catalog/components.tsx
  54. 11 0
      examples/stripe-app/fullpage-app/src/lib/render/index.ts
  55. 145 0
      examples/stripe-app/fullpage-app/src/lib/render/renderer.tsx
  56. 146 0
      examples/stripe-app/fullpage-app/src/lib/stream-spec.ts
  57. 37 0
      examples/stripe-app/fullpage-app/src/lib/stripe.ts
  58. 308 0
      examples/stripe-app/fullpage-app/src/views/FullPage.tsx
  59. 4 0
      examples/stripe-app/fullpage-app/src/views/brand_icon.svg
  60. 97 0
      examples/stripe-app/fullpage-app/stripe-app.template.json
  61. 3 0
      examples/stripe-app/fullpage-app/tsconfig.json
  62. 1 0
      examples/stripe-app/fullpage-app/ui-extensions.d.ts
  63. 155 8
      pnpm-lock.yaml
  64. 1 0
      pnpm-workspace.yaml

+ 5 - 0
.gitignore

@@ -44,3 +44,8 @@ yarn-error.log*
 # opensrc - source code for packages
 opensrc/
 .env*.local
+
+# Stripe apps (generated from template + build artifacts)
+examples/stripe-app/*/stripe-app.json
+examples/stripe-app/*/.build
+examples/stripe-app/*/yarn.lock

+ 0 - 7
examples/stripe-app/.env.example

@@ -1,7 +0,0 @@
-# AI Gateway API Key
-# Get your key from Vercel AI Gateway dashboard
-AI_GATEWAY_API_KEY=
-
-# Optional: Stripe API keys if you want to use real data
-# STRIPE_SECRET_KEY=sk_test_...
-# STRIPE_PUBLISHABLE_KEY=pk_test_...

+ 30 - 80
examples/stripe-app/README.md

@@ -1,99 +1,49 @@
-# Stripe App Example
+# Stripe App Examples
 
-A [Stripe App](https://stripe.com/docs/stripe-apps) example demonstrating how to use json-render to build dynamic, AI-generated UI within the Stripe Dashboard.
+[Stripe Apps](https://stripe.com/docs/stripe-apps) examples demonstrating how to use json-render to build dynamic, AI-generated UI within the Stripe Dashboard.
 
-## Overview
+## Structure
 
-This example shows how to integrate json-render with Stripe's UI Extension SDK to create dashboard views that can be dynamically generated from prompts. The app includes:
+| Folder | Description |
+|--------|-------------|
+| [api/](./api) | Next.js server providing the `/api/generate` endpoint for AI-powered UI generation |
+| [drawer-app/](./drawer-app) | Standard Stripe App that renders in the Dashboard drawer (sidebar) |
+| [fullpage-app/](./fullpage-app) | Full-page Stripe App using `FullPageView` (requires alpha access) |
 
-- **Home** - Revenue dashboard with AI-powered UI generation
-- **Customers** - Customer list and management
-- **Customer Details** - Individual customer view
-- **Payments** - Payment list and details
-- **Subscriptions** - Subscription management
-- **Invoices** - Invoice list
-- **Products** - Product catalog
+## Quick Start
 
-## Features
-
-- Full Stripe UIXT component catalog mapped to json-render
-- Dynamic spec generation from natural language prompts
-- Real-time data binding with Stripe API
-- Action handlers for Stripe operations (refunds, subscriptions, etc.)
-
-## Getting Started
-
-### Prerequisites
-
-- [Stripe CLI](https://stripe.com/docs/stripe-cli) installed
-- A Stripe account with app development enabled
-
-### Installation
+### 1. Start the API server
 
 ```bash
-# Install dependencies
+cd api
 pnpm install
-
-# Start the Stripe App in development mode
-stripe apps start
+cp .env.example .env  # Set AI_GATEWAY_API_KEY
+pnpm dev
 ```
 
-### Development
+### 2. Start a Stripe App
 
-The app uses the Stripe UI Extension SDK which handles authentication automatically - no API keys required in your environment.
+For the standard drawer app:
 
 ```bash
-# Run linting
-pnpm lint
-
-# Run tests
-pnpm test
+cd drawer-app
+pnpm install
+pnpm setup
+stripe apps start
 ```
 
-## How It Works
+For the full-page app (requires alpha access):
 
-1. **Component Catalog**: Maps json-render component types to Stripe UIXT components (`Box`, `Button`, `Badge`, etc.)
-
-2. **Action Handlers**: Defines actions that can be triggered from the UI (e.g., `refundPayment`, `cancelSubscription`)
-
-3. **StripeRenderer**: A custom renderer that uses the Stripe component catalog to render json-render specs
-
-4. **Views**: Each view fetches data from the Stripe API and renders it using json-render specs
-
-## Example Usage
-
-```tsx
-import { StripeRenderer, stripeCatalog } from "./lib/render";
-import type { Spec } from "@json-render/react";
-
-const spec: Spec = {
-  root: "container",
-  elements: {
-    container: {
-      type: "Stack",
-      props: { direction: "vertical", gap: "medium" },
-      children: ["heading", "metric"],
-    },
-    heading: {
-      type: "Heading",
-      props: { text: "Revenue", size: "large" },
-      children: [],
-    },
-    metric: {
-      type: "Metric",
-      props: { label: "Total", value: "$12,345", format: "currency" },
-      children: [],
-    },
-  },
-};
-
-function MyView() {
-  return <StripeRenderer spec={spec} catalog={stripeCatalog} />;
-}
+```bash
+cd fullpage-app
+pnpm install
+cp .env.example .env  # Set STRIPE_APP_ID
+pnpm setup
+stripe apps start
+# Navigate to https://dashboard.stripe.com/test/app/<your-app-id>
 ```
 
-## Learn More
+## Notes
 
-- [json-render Documentation](https://json-render.com/docs)
-- [Stripe Apps Documentation](https://stripe.com/docs/stripe-apps)
-- [Stripe UI Extension SDK](https://stripe.com/docs/stripe-apps/reference/ui-extension-sdk)
+- The AI generation feature requires the API server to be running. Without it, the apps fall back to locally generated specs using real Stripe data.
+- The `fullpage-app` uses Stripe's full-page apps **private developer preview**. You need Stripe to enable the feature flag for your app and account. See [fullpage-app/README.md](./fullpage-app/README.md) for details.

+ 3 - 0
examples/stripe-app/api/.env.example

@@ -0,0 +1,3 @@
+# AI Gateway API Key (required for AI generation)
+# Get your key from Vercel AI Gateway dashboard
+AI_GATEWAY_API_KEY=

+ 26 - 0
examples/stripe-app/api/README.md

@@ -0,0 +1,26 @@
+# Stripe App API
+
+A lightweight Next.js server that provides the `/api/generate` endpoint for AI-powered UI generation in the Stripe App examples.
+
+## Setup
+
+```bash
+pnpm install
+
+cp .env.example .env
+# Set AI_GATEWAY_API_KEY
+```
+
+## Running
+
+```bash
+pnpm dev
+```
+
+Runs on port 3001. The Stripe apps (`drawer-app/` and `fullpage-app/`) expect this server to be running for AI generation features.
+
+## API
+
+### POST /api/generate
+
+Accepts `{ prompt, systemPrompt }` and returns a streamed text response with a json-render spec.

+ 50 - 0
examples/stripe-app/api/app/api/generate/route.ts

@@ -0,0 +1,50 @@
+import { streamText } from "ai";
+import { gateway } from "@ai-sdk/gateway";
+
+export const maxDuration = 60;
+
+const DEFAULT_MODEL = "anthropic/claude-haiku-4.5";
+
+const CORS_HEADERS: Record<string, string> = {
+  "Access-Control-Allow-Origin": "*",
+  "Access-Control-Allow-Methods": "POST, OPTIONS",
+  "Access-Control-Allow-Headers": "Content-Type",
+};
+
+export async function POST(req: Request) {
+  const { prompt, systemPrompt } = await req.json();
+
+  if (!prompt) {
+    return new Response(JSON.stringify({ error: "prompt is required" }), {
+      status: 400,
+      headers: { "Content-Type": "application/json", ...CORS_HEADERS },
+    });
+  }
+
+  try {
+    const result = streamText({
+      model: gateway(DEFAULT_MODEL),
+      system: systemPrompt ?? "You are a helpful UI builder.",
+      prompt,
+      temperature: 0.7,
+    });
+
+    const response = result.toTextStreamResponse();
+
+    for (const [key, value] of Object.entries(CORS_HEADERS)) {
+      response.headers.set(key, value);
+    }
+
+    return response;
+  } catch (err) {
+    const message = err instanceof Error ? err.message : "Generation failed";
+    return new Response(JSON.stringify({ error: message }), {
+      status: 500,
+      headers: { "Content-Type": "application/json", ...CORS_HEADERS },
+    });
+  }
+}
+
+export async function OPTIONS() {
+  return new Response(null, { headers: CORS_HEADERS });
+}

+ 15 - 0
examples/stripe-app/api/app/layout.tsx

@@ -0,0 +1,15 @@
+export const metadata = {
+  title: "Stripe App API",
+};
+
+export default function RootLayout({
+  children,
+}: {
+  children: React.ReactNode;
+}) {
+  return (
+    <html lang="en">
+      <body>{children}</body>
+    </html>
+  );
+}

+ 8 - 0
examples/stripe-app/api/app/page.tsx

@@ -0,0 +1,8 @@
+export default function Home() {
+  return (
+    <main style={{ padding: "2rem", fontFamily: "system-ui, sans-serif" }}>
+      <h1>Stripe App API</h1>
+      <p>POST /api/generate to generate UI specs.</p>
+    </main>
+  );
+}

+ 6 - 0
examples/stripe-app/api/next-env.d.ts

@@ -0,0 +1,6 @@
+/// <reference types="next" />
+/// <reference types="next/image-types/global" />
+import "./.next/dev/types/routes.d.ts";
+
+// NOTE: This file should not be edited
+// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

+ 5 - 0
examples/stripe-app/api/next.config.ts

@@ -0,0 +1,5 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {};
+
+export default nextConfig;

+ 23 - 0
examples/stripe-app/api/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "stripe-app-api",
+  "version": "0.1.0",
+  "private": true,
+  "type": "module",
+  "scripts": {
+    "dev": "next dev --port 3001",
+    "build": "next build",
+    "start": "next start --port 3001"
+  },
+  "dependencies": {
+    "@ai-sdk/gateway": "^3.0.50",
+    "ai": "^6.0.91",
+    "next": "^16.1.6",
+    "react": "^19.1.0",
+    "react-dom": "^19.1.0"
+  },
+  "devDependencies": {
+    "@types/node": "^22.0.0",
+    "@types/react": "^19.1.0",
+    "typescript": "^5.4.5"
+  }
+}

+ 41 - 0
examples/stripe-app/api/tsconfig.json

@@ -0,0 +1,41 @@
+{
+  "compilerOptions": {
+    "target": "ES2017",
+    "lib": [
+      "dom",
+      "dom.iterable",
+      "esnext"
+    ],
+    "allowJs": true,
+    "skipLibCheck": true,
+    "strict": true,
+    "noEmit": true,
+    "esModuleInterop": true,
+    "module": "esnext",
+    "moduleResolution": "bundler",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "jsx": "react-jsx",
+    "incremental": true,
+    "plugins": [
+      {
+        "name": "next"
+      }
+    ],
+    "paths": {
+      "@/*": [
+        "./*"
+      ]
+    }
+  },
+  "include": [
+    "next-env.d.ts",
+    "**/*.ts",
+    "**/*.tsx",
+    ".next/types/**/*.ts",
+    ".next/dev/types/**/*.ts"
+  ],
+  "exclude": [
+    "node_modules"
+  ]
+}

+ 14 - 0
examples/stripe-app/drawer-app/.env.example

@@ -0,0 +1,14 @@
+# Stripe App ID (optional - overrides the template default)
+# Get this from your Stripe Apps dashboard after uploading
+# STRIPE_APP_ID=com.example.your-app-id
+
+# Stripe App Name (optional - overrides the template default)
+# STRIPE_APP_NAME=My App Name
+
+# AI Gateway API Key
+# Get your key from Vercel AI Gateway dashboard
+AI_GATEWAY_API_KEY=
+
+# Optional: Stripe API keys if you want to use real data
+# STRIPE_SECRET_KEY=sk_test_...
+# STRIPE_PUBLISHABLE_KEY=pk_test_...

+ 45 - 0
examples/stripe-app/drawer-app/README.md

@@ -0,0 +1,45 @@
+# Stripe App (Drawer)
+
+A [Stripe App](https://stripe.com/docs/stripe-apps) example demonstrating how to use json-render to build dynamic, AI-generated UI in the Stripe Dashboard drawer.
+
+## Views
+
+- **Home** - Revenue dashboard with AI-powered UI generation
+- **Customers** - Customer list and management
+- **Customer Details** - Individual customer view
+- **Payments** - Payment list and details
+- **Subscriptions** - Subscription management
+- **Invoices** - Invoice list
+- **Products** - Product catalog
+
+## Setup
+
+```bash
+pnpm install
+
+cp .env.example .env
+# Optionally set STRIPE_APP_ID to your own app ID
+
+pnpm setup
+```
+
+## Running
+
+Start the API server first (from `../api`):
+
+```bash
+cd ../api && pnpm dev
+```
+
+Then start the Stripe app:
+
+```bash
+stripe apps start
+```
+
+## How It Works
+
+1. **Component Catalog** - Maps json-render component types to Stripe UIXT components
+2. **Action Handlers** - Stripe operations (refunds, subscriptions, etc.)
+3. **StripeRenderer** - Custom renderer connecting json-render specs to Stripe components
+4. **Views** - Each view fetches data from the Stripe API and renders it using json-render specs

+ 0 - 0
examples/stripe-app/eslint.config.js → examples/stripe-app/drawer-app/eslint.config.js


+ 0 - 0
examples/stripe-app/jest.config.js → examples/stripe-app/drawer-app/jest.config.js


+ 3 - 1
examples/stripe-app/package.json → examples/stripe-app/drawer-app/package.json

@@ -9,12 +9,14 @@
     "@json-render/core": "workspace:*",
     "@json-render/react": "workspace:*",
     "@stripe/ui-extension-sdk": "^9.1.0",
-    "stripe": "^13.11.0"
+    "stripe": "^13.11.0",
+    "zod": "^4.0.0"
   },
   "engines": {
     "node": ">=14"
   },
   "scripts": {
+    "setup": "node scripts/setup.mjs",
     "lint": "eslint src --max-warnings 0",
     "test": "jest"
   },

+ 0 - 0
examples/stripe-app/pnpm-lock.yaml → examples/stripe-app/drawer-app/pnpm-lock.yaml


+ 42 - 0
examples/stripe-app/drawer-app/scripts/setup.mjs

@@ -0,0 +1,42 @@
+import { readFileSync, writeFileSync, existsSync } from "fs";
+import { resolve, dirname } from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const root = resolve(__dirname, "..");
+
+function loadEnv() {
+  const envPath = resolve(root, ".env");
+  if (!existsSync(envPath)) return {};
+  const env = {};
+  for (const line of readFileSync(envPath, "utf-8").split("\n")) {
+    const trimmed = line.trim();
+    if (!trimmed || trimmed.startsWith("#")) continue;
+    const idx = trimmed.indexOf("=");
+    if (idx === -1) continue;
+    const key = trimmed.slice(0, idx).trim();
+    const val = trimmed.slice(idx + 1).trim();
+    env[key] = val;
+  }
+  return env;
+}
+
+const env = loadEnv();
+const templatePath = resolve(root, "stripe-app.template.json");
+const outputPath = resolve(root, "stripe-app.json");
+
+const template = JSON.parse(readFileSync(templatePath, "utf-8"));
+
+if (env.STRIPE_APP_ID) {
+  template.id = env.STRIPE_APP_ID;
+  console.log(`Using app ID from .env: ${env.STRIPE_APP_ID}`);
+} else {
+  console.log(`No STRIPE_APP_ID in .env, using template default: ${template.id}`);
+}
+
+if (env.STRIPE_APP_NAME) {
+  template.name = env.STRIPE_APP_NAME;
+}
+
+writeFileSync(outputPath, JSON.stringify(template, null, 4) + "\n");
+console.log("Generated stripe-app.json");

+ 1 - 0
examples/stripe-app/drawer-app/src/lib/config.ts

@@ -0,0 +1 @@
+export const API_GENERATE_URL = "http://localhost:3001/api/generate";

+ 0 - 0
examples/stripe-app/src/lib/render/catalog.ts → examples/stripe-app/drawer-app/src/lib/render/catalog.ts


+ 1586 - 0
examples/stripe-app/drawer-app/src/lib/render/catalog/actions.ts

@@ -0,0 +1,1586 @@
+import { stripe, formatAmount, formatDate } from "../../stripe";
+
+/**
+ * Type for setState function
+ */
+type SetState = (
+  updater: (prev: Record<string, unknown>) => Record<string, unknown>,
+) => void;
+
+/**
+ * Action handlers for Stripe operations
+ *
+ * These use the real Stripe API via the UI Extension SDK.
+ */
+export const actionHandlers: Record<
+  string,
+  (
+    params: Record<string, unknown> | undefined,
+    setState: SetState,
+    data: Record<string, unknown>,
+  ) => Promise<void>
+> = {
+  // ===========================================================================
+  // Customer Actions
+  // ===========================================================================
+  fetchCustomers: async (params, setState) => {
+    try {
+      const customers = await stripe.customers.list({
+        limit: (params?.limit as number) ?? 10,
+        email: (params?.email as string) || undefined,
+        starting_after: (params?.startingAfter as string) || undefined,
+      });
+
+      const data = customers.data.map((c) => ({
+        id: c.id,
+        name: c.name ?? c.email ?? "Unknown",
+        email: c.email ?? "",
+        phone: c.phone ?? "",
+        status: c.delinquent ? "inactive" : "active",
+        created: formatDate(c.created),
+        balance: c.balance,
+        currency: c.currency ?? "usd",
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        customers: {
+          data,
+          total: customers.data.length,
+          hasMore: customers.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchCustomers error:", error);
+    }
+  },
+
+  viewCustomer: async (params, setState) => {
+    if (params?.customerId) {
+      const id = String(params.customerId);
+      setState((prev) => ({
+        ...prev,
+        _actionResult: {
+          action: "viewCustomer",
+          message: `Opening customer ${id}`,
+          url: `https://dashboard.stripe.com/customers/${id}`,
+        },
+      }));
+      try {
+        window.open(`https://dashboard.stripe.com/customers/${id}`, "_blank");
+      } catch {
+        // blocked in iframe
+      }
+    }
+  },
+
+  createCustomer: async (params, setState) => {
+    try {
+      await stripe.customers.create({
+        email: (params?.email as string) ?? "",
+        name: (params?.name as string) ?? undefined,
+        phone: (params?.phone as string) ?? undefined,
+        description: (params?.description as string) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchCustomers({}, setState, {});
+    } catch (error) {
+      console.error("createCustomer error:", error);
+    }
+  },
+
+  updateCustomer: async (params, setState) => {
+    try {
+      if (!params?.customerId) return;
+      await stripe.customers.update(params.customerId as string, {
+        email: (params?.email as string) ?? undefined,
+        name: (params?.name as string) ?? undefined,
+        phone: (params?.phone as string) ?? undefined,
+        description: (params?.description as string) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchCustomers({}, setState, {});
+    } catch (error) {
+      console.error("updateCustomer error:", error);
+    }
+  },
+
+  deleteCustomer: async (params, setState) => {
+    try {
+      if (!params?.customerId) return;
+      await stripe.customers.del(params.customerId as string);
+      await actionHandlers.fetchCustomers({}, setState, {});
+    } catch (error) {
+      console.error("deleteCustomer error:", error);
+    }
+  },
+
+  searchCustomers: async (params, setState) => {
+    try {
+      const customers = await stripe.customers.search({
+        query: (params?.query as string) ?? "",
+        limit: (params?.limit as number) ?? 10,
+      });
+
+      const data = customers.data.map((c) => ({
+        id: c.id,
+        name: c.name ?? c.email ?? "Unknown",
+        email: c.email ?? "",
+        status: c.delinquent ? "inactive" : "active",
+        created: formatDate(c.created),
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        searchResults: { customers: data, total: customers.data.length },
+      }));
+    } catch (error) {
+      console.error("searchCustomers error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Payment Intent Actions
+  // ===========================================================================
+  fetchPayments: async (params, setState) => {
+    try {
+      const payments = await stripe.paymentIntents.list({
+        limit: (params?.limit as number) ?? 10,
+        customer: (params?.customerId as string) || undefined,
+        starting_after: (params?.startingAfter as string) || undefined,
+      });
+
+      const data = payments.data.map((p) => ({
+        id: p.id,
+        amount: p.amount,
+        currency: p.currency,
+        status: p.status,
+        description: p.description ?? `Payment ${p.id.slice(-8)}`,
+        created: formatDate(p.created),
+        formattedAmount: formatAmount(p.amount, p.currency),
+        customerId: p.customer as string,
+      }));
+
+      const succeeded = payments.data.filter((p) => p.status === "succeeded");
+      const totalVolume = succeeded.reduce((sum, p) => sum + p.amount, 0);
+      const successRate =
+        payments.data.length > 0
+          ? ((succeeded.length / payments.data.length) * 100).toFixed(1)
+          : "0";
+
+      setState((prev) => ({
+        ...prev,
+        payments: {
+          data,
+          total: payments.data.length,
+          totalVolume: formatAmount(totalVolume, "usd"),
+          successRate: `${successRate}%`,
+          hasMore: payments.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchPayments error:", error);
+    }
+  },
+
+  viewPayment: async (params, setState) => {
+    if (params?.paymentId) {
+      const id = String(params.paymentId);
+      setState((prev) => ({
+        ...prev,
+        _actionResult: {
+          action: "viewPayment",
+          message: `Opening payment ${id}`,
+          url: `https://dashboard.stripe.com/payments/${id}`,
+        },
+      }));
+      try {
+        window.open(`https://dashboard.stripe.com/payments/${id}`, "_blank");
+      } catch {
+        // blocked in iframe -- state update above still shows feedback
+      }
+    }
+  },
+
+  createPaymentIntent: async (params, setState) => {
+    try {
+      await stripe.paymentIntents.create({
+        amount: (params?.amount as number) ?? 0,
+        currency: (params?.currency as string) ?? "usd",
+        customer: (params?.customerId as string) ?? undefined,
+        description: (params?.description as string) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchPayments({}, setState, {});
+    } catch (error) {
+      console.error("createPaymentIntent error:", error);
+    }
+  },
+
+  capturePayment: async (params, setState) => {
+    try {
+      if (!params?.paymentId) return;
+      await stripe.paymentIntents.capture(params.paymentId as string, {
+        amount_to_capture: (params?.amountToCapture as number) ?? undefined,
+      });
+      await actionHandlers.fetchPayments({}, setState, {});
+    } catch (error) {
+      console.error("capturePayment error:", error);
+    }
+  },
+
+  cancelPayment: async (params, setState) => {
+    try {
+      if (!params?.paymentId) return;
+      await stripe.paymentIntents.cancel(params.paymentId as string, {
+        cancellation_reason:
+          (params?.reason as
+            | "duplicate"
+            | "fraudulent"
+            | "requested_by_customer"
+            | "abandoned") ?? undefined,
+      });
+      await actionHandlers.fetchPayments({}, setState, {});
+    } catch (error) {
+      console.error("cancelPayment error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Refund Actions
+  // ===========================================================================
+  fetchRefunds: async (params, setState) => {
+    try {
+      const refunds = await stripe.refunds.list({
+        limit: (params?.limit as number) ?? 10,
+        payment_intent: (params?.paymentIntentId as string) || undefined,
+        charge: (params?.chargeId as string) || undefined,
+      });
+
+      const data = refunds.data.map((r) => ({
+        id: r.id,
+        amount: r.amount,
+        currency: r.currency,
+        status: r.status,
+        reason: r.reason,
+        created: formatDate(r.created),
+        formattedAmount: formatAmount(r.amount, r.currency),
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        refunds: { data, total: refunds.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchRefunds error:", error);
+    }
+  },
+
+  refundPayment: async (params, setState) => {
+    try {
+      if (!params?.paymentId) return;
+      await stripe.refunds.create({
+        payment_intent: params.paymentId as string,
+        amount: (params?.amount as number) ?? undefined,
+        reason:
+          (params?.reason as
+            | "duplicate"
+            | "fraudulent"
+            | "requested_by_customer") ?? undefined,
+      });
+      await actionHandlers.fetchPayments({}, setState, {});
+      await actionHandlers.fetchRefunds({}, setState, {});
+    } catch (error) {
+      console.error("refundPayment error:", error);
+    }
+  },
+
+  updateRefund: async (params, setState) => {
+    try {
+      if (!params?.refundId) return;
+      await stripe.refunds.update(params.refundId as string, {
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchRefunds({}, setState, {});
+    } catch (error) {
+      console.error("updateRefund error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Charge Actions
+  // ===========================================================================
+  fetchCharges: async (params, setState) => {
+    try {
+      const charges = await stripe.charges.list({
+        limit: (params?.limit as number) ?? 10,
+        customer: (params?.customerId as string) || undefined,
+        payment_intent: (params?.paymentIntentId as string) || undefined,
+      });
+
+      const data = charges.data.map((c) => ({
+        id: c.id,
+        amount: c.amount,
+        currency: c.currency,
+        status: c.status,
+        captured: c.captured,
+        description: c.description,
+        created: formatDate(c.created),
+        formattedAmount: formatAmount(c.amount, c.currency),
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        charges: { data, total: charges.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchCharges error:", error);
+    }
+  },
+
+  captureCharge: async (params, setState) => {
+    try {
+      if (!params?.chargeId) return;
+      await stripe.charges.capture(params.chargeId as string, {
+        amount: (params?.amount as number) ?? undefined,
+      });
+      await actionHandlers.fetchCharges({}, setState, {});
+    } catch (error) {
+      console.error("captureCharge error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Subscription Actions
+  // ===========================================================================
+  fetchSubscriptions: async (params, setState) => {
+    try {
+      const subscriptions = await stripe.subscriptions.list({
+        limit: (params?.limit as number) ?? 10,
+        status: (params?.status as string) || undefined,
+        customer: (params?.customerId as string) || undefined,
+        price: (params?.priceId as string) || undefined,
+      });
+
+      const data = subscriptions.data.map((s) => ({
+        id: s.id,
+        planName:
+          s.items.data[0]?.price?.nickname ??
+          s.items.data[0]?.price?.id ??
+          "Plan",
+        status: s.status,
+        amount: s.items.data[0]?.price?.unit_amount ?? 0,
+        currency: s.items.data[0]?.price?.currency ?? "usd",
+        interval: s.items.data[0]?.price?.recurring?.interval ?? "month",
+        customerId: s.customer as string,
+        currentPeriodEnd: formatDate(s.current_period_end),
+        cancelAtPeriodEnd: s.cancel_at_period_end,
+      }));
+
+      const active = subscriptions.data.filter(
+        (s) => s.status === "active",
+      ).length;
+      const trialing = subscriptions.data.filter(
+        (s) => s.status === "trialing",
+      ).length;
+      const pastDue = subscriptions.data.filter(
+        (s) => s.status === "past_due",
+      ).length;
+      const canceled = subscriptions.data.filter(
+        (s) => s.status === "canceled",
+      ).length;
+
+      setState((prev) => ({
+        ...prev,
+        subscriptions: {
+          data,
+          total: subscriptions.data.length,
+          active,
+          trialing,
+          pastDue,
+          canceled,
+          hasMore: subscriptions.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchSubscriptions error:", error);
+    }
+  },
+
+  viewSubscription: async (params) => {
+    if (params?.subscriptionId) {
+      window.open(
+        `https://dashboard.stripe.com/subscriptions/${params.subscriptionId}`,
+        "_blank",
+      );
+    }
+  },
+
+  createSubscription: async (params, setState) => {
+    try {
+      if (!params?.customerId || !params?.priceId) return;
+      await stripe.subscriptions.create({
+        customer: params.customerId as string,
+        items: [
+          {
+            price: params.priceId as string,
+            quantity: (params?.quantity as number) ?? 1,
+          },
+        ],
+        trial_period_days: (params?.trialPeriodDays as number) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchSubscriptions({}, setState, {});
+    } catch (error) {
+      console.error("createSubscription error:", error);
+    }
+  },
+
+  updateSubscription: async (params, setState) => {
+    try {
+      if (!params?.subscriptionId) return;
+      const updateParams: Record<string, unknown> = {};
+      if (params.priceId) {
+        updateParams.items = [
+          {
+            price: params.priceId as string,
+            quantity: (params?.quantity as number) ?? 1,
+          },
+        ];
+      }
+      if (params.metadata) {
+        updateParams.metadata = params.metadata;
+      }
+      await stripe.subscriptions.update(
+        params.subscriptionId as string,
+        updateParams,
+      );
+      await actionHandlers.fetchSubscriptions({}, setState, {});
+    } catch (error) {
+      console.error("updateSubscription error:", error);
+    }
+  },
+
+  cancelSubscription: async (params, setState) => {
+    try {
+      if (!params?.subscriptionId) return;
+      if (params.immediately) {
+        await stripe.subscriptions.cancel(params.subscriptionId as string);
+      } else {
+        await stripe.subscriptions.update(params.subscriptionId as string, {
+          cancel_at_period_end: true,
+        });
+      }
+      await actionHandlers.fetchSubscriptions({}, setState, {});
+    } catch (error) {
+      console.error("cancelSubscription error:", error);
+    }
+  },
+
+  pauseSubscription: async (params, setState) => {
+    try {
+      if (!params?.subscriptionId) return;
+      await stripe.subscriptions.update(params.subscriptionId as string, {
+        pause_collection: {
+          behavior: "mark_uncollectible",
+          resumes_at: (params?.resumeAt as number) ?? undefined,
+        },
+      });
+      await actionHandlers.fetchSubscriptions({}, setState, {});
+    } catch (error) {
+      console.error("pauseSubscription error:", error);
+    }
+  },
+
+  resumeSubscription: async (params, setState) => {
+    try {
+      if (!params?.subscriptionId) return;
+      await stripe.subscriptions.update(params.subscriptionId as string, {
+        pause_collection: "",
+      });
+      await actionHandlers.fetchSubscriptions({}, setState, {});
+    } catch (error) {
+      console.error("resumeSubscription error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Invoice Actions
+  // ===========================================================================
+  fetchInvoices: async (params, setState) => {
+    try {
+      const invoices = await stripe.invoices.list({
+        limit: (params?.limit as number) ?? 10,
+        status: (params?.status as string) || undefined,
+        customer: (params?.customerId as string) || undefined,
+        subscription: (params?.subscriptionId as string) || undefined,
+      });
+
+      const data = invoices.data.map((inv) => ({
+        id: inv.id,
+        invoiceNumber: inv.number ?? inv.id.slice(-8),
+        amount: inv.amount_due,
+        currency: inv.currency,
+        status: inv.status ?? "draft",
+        dueDate: inv.due_date ? formatDate(inv.due_date) : null,
+        customerEmail: inv.customer_email ?? "",
+        formattedAmount: formatAmount(inv.amount_due, inv.currency),
+        hostedInvoiceUrl: inv.hosted_invoice_url,
+        pdfUrl: inv.invoice_pdf,
+      }));
+
+      const outstanding = invoices.data
+        .filter((i) => i.status === "open")
+        .reduce((sum, i) => sum + i.amount_due, 0);
+      const paid = invoices.data
+        .filter((i) => i.status === "paid")
+        .reduce((sum, i) => sum + i.amount_paid, 0);
+      const overdue = invoices.data
+        .filter(
+          (i) =>
+            i.status === "open" && i.due_date && i.due_date < Date.now() / 1000,
+        )
+        .reduce((sum, i) => sum + i.amount_due, 0);
+
+      setState((prev) => ({
+        ...prev,
+        invoices: {
+          data,
+          total: invoices.data.length,
+          outstanding: formatAmount(outstanding, "usd"),
+          paid: formatAmount(paid, "usd"),
+          overdue: formatAmount(overdue, "usd"),
+          hasMore: invoices.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchInvoices error:", error);
+    }
+  },
+
+  viewInvoice: async (params) => {
+    if (params?.invoiceId) {
+      window.open(
+        `https://dashboard.stripe.com/invoices/${params.invoiceId}`,
+        "_blank",
+      );
+    }
+  },
+
+  createInvoice: async (params, setState) => {
+    try {
+      if (!params?.customerId) return;
+      await stripe.invoices.create({
+        customer: params.customerId as string,
+        description: (params?.description as string) ?? undefined,
+        days_until_due: (params?.daysUntilDue as number) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("createInvoice error:", error);
+    }
+  },
+
+  addInvoiceItem: async (params, setState) => {
+    try {
+      if (!params?.invoiceId) return;
+      await stripe.invoiceItems.create({
+        invoice: params.invoiceId as string,
+        amount: (params?.amount as number) ?? 0,
+        currency: (params?.currency as string) ?? "usd",
+        description: (params?.description as string) ?? undefined,
+      });
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("addInvoiceItem error:", error);
+    }
+  },
+
+  finalizeInvoice: async (params, setState) => {
+    try {
+      if (!params?.invoiceId) return;
+      await stripe.invoices.finalizeInvoice(params.invoiceId as string, {
+        auto_advance: (params?.autoAdvance as boolean) ?? undefined,
+      });
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("finalizeInvoice error:", error);
+    }
+  },
+
+  sendInvoice: async (params, setState) => {
+    try {
+      if (!params?.invoiceId) return;
+      await stripe.invoices.sendInvoice(params.invoiceId as string);
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("sendInvoice error:", error);
+    }
+  },
+
+  payInvoice: async (params, setState) => {
+    try {
+      if (!params?.invoiceId) return;
+      await stripe.invoices.pay(params.invoiceId as string, {
+        payment_method: (params?.paymentMethodId as string) ?? undefined,
+      });
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("payInvoice error:", error);
+    }
+  },
+
+  voidInvoice: async (params, setState) => {
+    try {
+      if (!params?.invoiceId) return;
+      await stripe.invoices.voidInvoice(params.invoiceId as string);
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("voidInvoice error:", error);
+    }
+  },
+
+  markInvoiceUncollectible: async (params, setState) => {
+    try {
+      if (!params?.invoiceId) return;
+      await stripe.invoices.markUncollectible(params.invoiceId as string);
+      await actionHandlers.fetchInvoices({}, setState, {});
+    } catch (error) {
+      console.error("markInvoiceUncollectible error:", error);
+    }
+  },
+
+  downloadInvoicePdf: async (params, _, data) => {
+    try {
+      if (!params?.invoiceId) return;
+      const invoices =
+        (data.invoices as { data: Array<{ id: string; pdfUrl: string }> })
+          ?.data ?? [];
+      const invoice = invoices.find((i) => i.id === params.invoiceId);
+      if (invoice?.pdfUrl) {
+        window.open(invoice.pdfUrl, "_blank");
+      }
+    } catch (error) {
+      console.error("downloadInvoicePdf error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Product & Price Actions
+  // ===========================================================================
+  fetchProducts: async (params, setState) => {
+    try {
+      const products = await stripe.products.list({
+        limit: (params?.limit as number) ?? 10,
+        active: (params?.active as boolean) ?? undefined,
+      });
+
+      const data = products.data.map((p) => ({
+        id: p.id,
+        name: p.name,
+        description: p.description,
+        active: p.active,
+        created: formatDate(p.created),
+        images: p.images,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        products: {
+          data,
+          total: products.data.length,
+          hasMore: products.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchProducts error:", error);
+    }
+  },
+
+  viewProduct: async (params) => {
+    if (params?.productId) {
+      window.open(
+        `https://dashboard.stripe.com/products/${params.productId}`,
+        "_blank",
+      );
+    }
+  },
+
+  createProduct: async (params, setState) => {
+    try {
+      if (!params?.name) return;
+      await stripe.products.create({
+        name: params.name as string,
+        description: (params?.description as string) ?? undefined,
+        active: (params?.active as boolean) ?? true,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchProducts({}, setState, {});
+    } catch (error) {
+      console.error("createProduct error:", error);
+    }
+  },
+
+  updateProduct: async (params, setState) => {
+    try {
+      if (!params?.productId) return;
+      await stripe.products.update(params.productId as string, {
+        name: (params?.name as string) ?? undefined,
+        description: (params?.description as string) ?? undefined,
+        active: (params?.active as boolean) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchProducts({}, setState, {});
+    } catch (error) {
+      console.error("updateProduct error:", error);
+    }
+  },
+
+  archiveProduct: async (params, setState) => {
+    try {
+      if (!params?.productId) return;
+      await stripe.products.update(params.productId as string, {
+        active: false,
+      });
+      await actionHandlers.fetchProducts({}, setState, {});
+    } catch (error) {
+      console.error("archiveProduct error:", error);
+    }
+  },
+
+  fetchPrices: async (params, setState) => {
+    try {
+      const prices = await stripe.prices.list({
+        limit: (params?.limit as number) ?? 10,
+        product: (params?.productId as string) || undefined,
+        active: (params?.active as boolean) ?? undefined,
+        type: (params?.type as "one_time" | "recurring") || undefined,
+      });
+
+      const data = prices.data.map((p) => ({
+        id: p.id,
+        nickname: p.nickname,
+        unitAmount: p.unit_amount,
+        currency: p.currency,
+        active: p.active,
+        type: p.type,
+        recurring: p.recurring,
+        formattedAmount: formatAmount(p.unit_amount ?? 0, p.currency),
+        productId: typeof p.product === "string" ? p.product : p.product?.id,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        prices: { data, total: prices.data.length, hasMore: prices.has_more },
+      }));
+    } catch (error) {
+      console.error("fetchPrices error:", error);
+    }
+  },
+
+  createPrice: async (params, setState) => {
+    try {
+      if (!params?.productId || !params?.unitAmount) return;
+      const recurring = params?.recurring as
+        | { interval: string; intervalCount?: number }
+        | undefined;
+      await stripe.prices.create({
+        product: params.productId as string,
+        unit_amount: params.unitAmount as number,
+        currency: (params?.currency as string) ?? "usd",
+        recurring: recurring
+          ? {
+              interval: recurring.interval as "day" | "week" | "month" | "year",
+              interval_count: recurring.intervalCount ?? undefined,
+            }
+          : undefined,
+        nickname: (params?.nickname as string) ?? undefined,
+      });
+      await actionHandlers.fetchPrices({}, setState, {});
+    } catch (error) {
+      console.error("createPrice error:", error);
+    }
+  },
+
+  updatePrice: async (params, setState) => {
+    try {
+      if (!params?.priceId) return;
+      await stripe.prices.update(params.priceId as string, {
+        active: (params?.active as boolean) ?? undefined,
+        nickname: (params?.nickname as string) ?? undefined,
+        metadata: (params?.metadata as Record<string, string>) ?? undefined,
+      });
+      await actionHandlers.fetchPrices({}, setState, {});
+    } catch (error) {
+      console.error("updatePrice error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Balance & Payout Actions
+  // ===========================================================================
+  fetchBalance: async (_, setState) => {
+    try {
+      const balance = await stripe.balance.retrieve();
+
+      const available = balance.available.reduce((sum, b) => sum + b.amount, 0);
+      const pending = balance.pending.reduce((sum, b) => sum + b.amount, 0);
+
+      setState((prev) => ({
+        ...prev,
+        balance: {
+          available,
+          pending,
+          formattedAvailable: formatAmount(available, "usd"),
+          formattedPending: formatAmount(pending, "usd"),
+          currency: balance.available[0]?.currency ?? "usd",
+        },
+      }));
+    } catch (error) {
+      console.error("fetchBalance error:", error);
+    }
+  },
+
+  fetchPayouts: async (params, setState) => {
+    try {
+      const payouts = await stripe.payouts.list({
+        limit: (params?.limit as number) ?? 10,
+        status: (params?.status as string) || undefined,
+      });
+
+      const data = payouts.data.map((p) => ({
+        id: p.id,
+        amount: p.amount,
+        currency: p.currency,
+        status: p.status,
+        arrivalDate: formatDate(p.arrival_date),
+        created: formatDate(p.created),
+        formattedAmount: formatAmount(p.amount, p.currency),
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        payouts: {
+          data,
+          total: payouts.data.length,
+          hasMore: payouts.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchPayouts error:", error);
+    }
+  },
+
+  createPayout: async (params, setState) => {
+    try {
+      if (!params?.amount) return;
+      await stripe.payouts.create({
+        amount: params.amount as number,
+        currency: (params?.currency as string) ?? "usd",
+        description: (params?.description as string) ?? undefined,
+      });
+      await actionHandlers.fetchPayouts({}, setState, {});
+      await actionHandlers.fetchBalance({}, setState, {});
+    } catch (error) {
+      console.error("createPayout error:", error);
+    }
+  },
+
+  cancelPayout: async (params, setState) => {
+    try {
+      if (!params?.payoutId) return;
+      await stripe.payouts.cancel(params.payoutId as string);
+      await actionHandlers.fetchPayouts({}, setState, {});
+    } catch (error) {
+      console.error("cancelPayout error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Dispute Actions
+  // ===========================================================================
+  fetchDisputes: async (params, setState) => {
+    try {
+      const disputes = await stripe.disputes.list({
+        limit: (params?.limit as number) ?? 10,
+        charge: (params?.chargeId as string) || undefined,
+      });
+
+      const data = disputes.data.map((d) => ({
+        id: d.id,
+        amount: d.amount,
+        currency: d.currency,
+        status: d.status,
+        reason: d.reason,
+        created: formatDate(d.created),
+        formattedAmount: formatAmount(d.amount, d.currency),
+        evidenceDueBy: d.evidence_details?.due_by
+          ? formatDate(d.evidence_details.due_by)
+          : null,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        disputes: {
+          data,
+          total: disputes.data.length,
+          hasMore: disputes.has_more,
+        },
+      }));
+    } catch (error) {
+      console.error("fetchDisputes error:", error);
+    }
+  },
+
+  viewDispute: async (params) => {
+    if (params?.disputeId) {
+      window.open(
+        `https://dashboard.stripe.com/disputes/${params.disputeId}`,
+        "_blank",
+      );
+    }
+  },
+
+  updateDispute: async (params, setState) => {
+    try {
+      if (!params?.disputeId) return;
+      const evidence = params?.evidence as Record<string, string> | undefined;
+      await stripe.disputes.update(params.disputeId as string, {
+        evidence: evidence
+          ? {
+              customer_name: evidence.customerName ?? undefined,
+              customer_email_address:
+                evidence.customerEmailAddress ?? undefined,
+              product_description: evidence.productDescription ?? undefined,
+              uncategorized_text: evidence.uncategorizedText ?? undefined,
+            }
+          : undefined,
+        submit: (params?.submit as boolean) ?? undefined,
+      });
+      await actionHandlers.fetchDisputes({}, setState, {});
+    } catch (error) {
+      console.error("updateDispute error:", error);
+    }
+  },
+
+  closeDispute: async (params, setState) => {
+    try {
+      if (!params?.disputeId) return;
+      await stripe.disputes.close(params.disputeId as string);
+      await actionHandlers.fetchDisputes({}, setState, {});
+    } catch (error) {
+      console.error("closeDispute error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Payment Method Actions
+  // ===========================================================================
+  fetchPaymentMethods: async (params, setState) => {
+    try {
+      if (!params?.customerId) return;
+      const paymentMethods = await stripe.paymentMethods.list({
+        customer: params.customerId as string,
+        type: (params?.type as "card" | "us_bank_account") || "card",
+      });
+
+      const data = paymentMethods.data.map((pm) => ({
+        id: pm.id,
+        type: pm.type,
+        card: pm.card
+          ? {
+              brand: pm.card.brand,
+              last4: pm.card.last4,
+              expMonth: pm.card.exp_month,
+              expYear: pm.card.exp_year,
+            }
+          : null,
+        created: formatDate(pm.created),
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        paymentMethods: { data, total: paymentMethods.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchPaymentMethods error:", error);
+    }
+  },
+
+  attachPaymentMethod: async (params, setState) => {
+    try {
+      if (!params?.paymentMethodId || !params?.customerId) return;
+      await stripe.paymentMethods.attach(params.paymentMethodId as string, {
+        customer: params.customerId as string,
+      });
+      await actionHandlers.fetchPaymentMethods(
+        { customerId: params.customerId },
+        setState,
+        {},
+      );
+    } catch (error) {
+      console.error("attachPaymentMethod error:", error);
+    }
+  },
+
+  detachPaymentMethod: async (params, _setState) => {
+    try {
+      if (!params?.paymentMethodId) return;
+      await stripe.paymentMethods.detach(params.paymentMethodId as string);
+    } catch (error) {
+      console.error("detachPaymentMethod error:", error);
+    }
+  },
+
+  setDefaultPaymentMethod: async (params, setState) => {
+    try {
+      if (!params?.customerId || !params?.paymentMethodId) return;
+      await stripe.customers.update(params.customerId as string, {
+        invoice_settings: {
+          default_payment_method: params.paymentMethodId as string,
+        },
+      });
+      await actionHandlers.fetchCustomers({}, setState, {});
+    } catch (error) {
+      console.error("setDefaultPaymentMethod error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Coupon & Promotion Actions
+  // ===========================================================================
+  fetchCoupons: async (params, setState) => {
+    try {
+      const coupons = await stripe.coupons.list({
+        limit: (params?.limit as number) ?? 10,
+      });
+
+      const data = coupons.data.map((c) => ({
+        id: c.id,
+        name: c.name,
+        percentOff: c.percent_off,
+        amountOff: c.amount_off,
+        currency: c.currency,
+        duration: c.duration,
+        durationInMonths: c.duration_in_months,
+        maxRedemptions: c.max_redemptions,
+        timesRedeemed: c.times_redeemed,
+        valid: c.valid,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        coupons: { data, total: coupons.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchCoupons error:", error);
+    }
+  },
+
+  createCoupon: async (params, setState) => {
+    try {
+      await stripe.coupons.create({
+        percent_off: (params?.percentOff as number) ?? undefined,
+        amount_off: (params?.amountOff as number) ?? undefined,
+        currency: (params?.currency as string) ?? undefined,
+        duration:
+          (params?.duration as "forever" | "once" | "repeating") ?? "once",
+        duration_in_months: (params?.durationInMonths as number) ?? undefined,
+        name: (params?.name as string) ?? undefined,
+        max_redemptions: (params?.maxRedemptions as number) ?? undefined,
+      });
+      await actionHandlers.fetchCoupons({}, setState, {});
+    } catch (error) {
+      console.error("createCoupon error:", error);
+    }
+  },
+
+  deleteCoupon: async (params, setState) => {
+    try {
+      if (!params?.couponId) return;
+      await stripe.coupons.del(params.couponId as string);
+      await actionHandlers.fetchCoupons({}, setState, {});
+    } catch (error) {
+      console.error("deleteCoupon error:", error);
+    }
+  },
+
+  fetchPromotionCodes: async (params, setState) => {
+    try {
+      const promoCodes = await stripe.promotionCodes.list({
+        limit: (params?.limit as number) ?? 10,
+        coupon: (params?.couponId as string) || undefined,
+        active: (params?.active as boolean) ?? undefined,
+      });
+
+      const data = promoCodes.data.map((p) => ({
+        id: p.id,
+        code: p.code,
+        couponId: typeof p.coupon === "string" ? p.coupon : p.coupon?.id,
+        active: p.active,
+        maxRedemptions: p.max_redemptions,
+        timesRedeemed: p.times_redeemed,
+        expiresAt: p.expires_at ? formatDate(p.expires_at) : null,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        promotionCodes: { data, total: promoCodes.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchPromotionCodes error:", error);
+    }
+  },
+
+  createPromotionCode: async (params, setState) => {
+    try {
+      if (!params?.couponId) return;
+      await stripe.promotionCodes.create({
+        coupon: params.couponId as string,
+        code: (params?.code as string) ?? undefined,
+        max_redemptions: (params?.maxRedemptions as number) ?? undefined,
+        expires_at: (params?.expiresAt as number) ?? undefined,
+      });
+      await actionHandlers.fetchPromotionCodes({}, setState, {});
+    } catch (error) {
+      console.error("createPromotionCode error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Checkout Session Actions
+  // ===========================================================================
+  createCheckoutSession: async (params, setState) => {
+    try {
+      const lineItems =
+        (params?.lineItems as Array<{ priceId: string; quantity: number }>) ??
+        [];
+      const session = await stripe.checkout.sessions.create({
+        mode:
+          (params?.mode as "payment" | "subscription" | "setup") ?? "payment",
+        line_items: lineItems.map((li) => ({
+          price: li.priceId,
+          quantity: li.quantity,
+        })),
+        success_url: (params?.successUrl as string) ?? "",
+        cancel_url: (params?.cancelUrl as string) ?? "",
+        customer: (params?.customerId as string) ?? undefined,
+      });
+
+      setState((prev) => ({
+        ...prev,
+        checkoutSession: { id: session.id, url: session.url },
+      }));
+
+      if (session.url) {
+        window.open(session.url, "_blank");
+      }
+    } catch (error) {
+      console.error("createCheckoutSession error:", error);
+    }
+  },
+
+  fetchCheckoutSessions: async (params, setState) => {
+    try {
+      const sessions = await stripe.checkout.sessions.list({
+        limit: (params?.limit as number) ?? 10,
+        customer: (params?.customerId as string) || undefined,
+        payment_intent: (params?.paymentIntentId as string) || undefined,
+      });
+
+      const data = sessions.data.map((s) => ({
+        id: s.id,
+        mode: s.mode,
+        status: s.status,
+        amountTotal: s.amount_total,
+        currency: s.currency,
+        customerEmail: s.customer_email,
+        url: s.url,
+        created: formatDate(s.created),
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        checkoutSessions: { data, total: sessions.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchCheckoutSessions error:", error);
+    }
+  },
+
+  expireCheckoutSession: async (params, setState) => {
+    try {
+      if (!params?.sessionId) return;
+      await stripe.checkout.sessions.expire(params.sessionId as string);
+      await actionHandlers.fetchCheckoutSessions({}, setState, {});
+    } catch (error) {
+      console.error("expireCheckoutSession error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Billing Portal Actions
+  // ===========================================================================
+  createBillingPortalSession: async (params, setState) => {
+    try {
+      if (!params?.customerId) return;
+      const session = await stripe.billingPortal.sessions.create({
+        customer: params.customerId as string,
+        return_url: (params?.returnUrl as string) ?? window.location.href,
+      });
+
+      setState((prev) => ({
+        ...prev,
+        billingPortalSession: { url: session.url },
+      }));
+
+      if (session.url) {
+        window.open(session.url, "_blank");
+      }
+    } catch (error) {
+      console.error("createBillingPortalSession error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Event Actions
+  // ===========================================================================
+  fetchEvents: async (params, setState) => {
+    try {
+      const events = await stripe.events.list({
+        limit: (params?.limit as number) ?? 10,
+        type: (params?.type as string) || undefined,
+        created: {
+          gte: (params?.createdGte as number) || undefined,
+          lte: (params?.createdLte as number) || undefined,
+        },
+      });
+
+      const data = events.data.map((e) => ({
+        id: e.id,
+        type: e.type,
+        created: formatDate(e.created),
+        livemode: e.livemode,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        events: { data, total: events.data.length, hasMore: events.has_more },
+      }));
+    } catch (error) {
+      console.error("fetchEvents error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Setup Intent Actions
+  // ===========================================================================
+  createSetupIntent: async (params, setState) => {
+    try {
+      const setupIntent = await stripe.setupIntents.create({
+        customer: (params?.customerId as string) ?? undefined,
+        payment_method_types: (params?.paymentMethodTypes as string[]) ?? [
+          "card",
+        ],
+        usage: (params?.usage as "on_session" | "off_session") ?? "off_session",
+      });
+
+      setState((prev) => ({
+        ...prev,
+        setupIntent: {
+          id: setupIntent.id,
+          clientSecret: setupIntent.client_secret,
+        },
+      }));
+    } catch (error) {
+      console.error("createSetupIntent error:", error);
+    }
+  },
+
+  fetchSetupIntents: async (params, setState) => {
+    try {
+      const setupIntents = await stripe.setupIntents.list({
+        limit: (params?.limit as number) ?? 10,
+        customer: (params?.customerId as string) || undefined,
+      });
+
+      const data = setupIntents.data.map((si) => ({
+        id: si.id,
+        status: si.status,
+        usage: si.usage,
+        created: formatDate(si.created),
+        paymentMethodTypes: si.payment_method_types,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        setupIntents: { data, total: setupIntents.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchSetupIntents error:", error);
+    }
+  },
+
+  cancelSetupIntent: async (params, setState) => {
+    try {
+      if (!params?.setupIntentId) return;
+      await stripe.setupIntents.cancel(params.setupIntentId as string);
+      await actionHandlers.fetchSetupIntents({}, setState, {});
+    } catch (error) {
+      console.error("cancelSetupIntent error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Tax Rate Actions
+  // ===========================================================================
+  fetchTaxRates: async (params, setState) => {
+    try {
+      const taxRates = await stripe.taxRates.list({
+        limit: (params?.limit as number) ?? 10,
+        active: (params?.active as boolean) ?? undefined,
+        inclusive: (params?.inclusive as boolean) ?? undefined,
+      });
+
+      const data = taxRates.data.map((tr) => ({
+        id: tr.id,
+        displayName: tr.display_name,
+        percentage: tr.percentage,
+        inclusive: tr.inclusive,
+        jurisdiction: tr.jurisdiction,
+        description: tr.description,
+        active: tr.active,
+      }));
+
+      setState((prev) => ({
+        ...prev,
+        taxRates: { data, total: taxRates.data.length },
+      }));
+    } catch (error) {
+      console.error("fetchTaxRates error:", error);
+    }
+  },
+
+  createTaxRate: async (params, setState) => {
+    try {
+      if (!params?.displayName || params?.percentage === undefined) return;
+      await stripe.taxRates.create({
+        display_name: params.displayName as string,
+        percentage: params.percentage as number,
+        inclusive: (params?.inclusive as boolean) ?? false,
+        jurisdiction: (params?.jurisdiction as string) ?? undefined,
+        description: (params?.description as string) ?? undefined,
+      });
+      await actionHandlers.fetchTaxRates({}, setState, {});
+    } catch (error) {
+      console.error("createTaxRate error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Data & Refresh Actions
+  // ===========================================================================
+  refreshData: async (_, setState) => {
+    await Promise.all([
+      actionHandlers.fetchCustomers({}, setState, {}),
+      actionHandlers.fetchPayments({}, setState, {}),
+      actionHandlers.fetchSubscriptions({}, setState, {}),
+      actionHandlers.fetchInvoices({}, setState, {}),
+    ]);
+  },
+
+  refreshCustomers: async (_, setState) => {
+    await actionHandlers.fetchCustomers({}, setState, {});
+  },
+
+  refreshPayments: async (_, setState) => {
+    await actionHandlers.fetchPayments({}, setState, {});
+  },
+
+  refreshSubscriptions: async (_, setState) => {
+    await actionHandlers.fetchSubscriptions({}, setState, {});
+  },
+
+  refreshInvoices: async (_, setState) => {
+    await actionHandlers.fetchInvoices({}, setState, {});
+  },
+
+  exportData: async (params, _, data) => {
+    try {
+      const format = (params?.format as string) ?? "json";
+      const dataType = (params?.dataType as string) ?? "customers";
+      const exportData = (data[dataType] as { data: unknown[] })?.data ?? [];
+
+      if (format === "json") {
+        const blob = new Blob([JSON.stringify(exportData, null, 2)], {
+          type: "application/json",
+        });
+        const url = URL.createObjectURL(blob);
+        const a = document.createElement("a");
+        a.href = url;
+        a.download = `${dataType}.json`;
+        a.click();
+        URL.revokeObjectURL(url);
+      } else if (format === "csv") {
+        if (exportData.length === 0) return;
+        const headers = Object.keys(exportData[0] as object);
+        const csv = [
+          headers.join(","),
+          ...exportData.map((row) =>
+            headers
+              .map((h) =>
+                JSON.stringify((row as Record<string, unknown>)[h] ?? ""),
+              )
+              .join(","),
+          ),
+        ].join("\n");
+        const blob = new Blob([csv], { type: "text/csv" });
+        const url = URL.createObjectURL(blob);
+        const a = document.createElement("a");
+        a.href = url;
+        a.download = `${dataType}.csv`;
+        a.click();
+        URL.revokeObjectURL(url);
+      }
+    } catch (error) {
+      console.error("exportData error:", error);
+    }
+  },
+
+  // ===========================================================================
+  // Navigation Actions
+  // ===========================================================================
+  navigate: async (_params) => {
+    // In a real app, this would use the router
+  },
+
+  openDashboard: async (params) => {
+    const page = (params?.page as string) ?? "home";
+    const paths: Record<string, string> = {
+      home: "",
+      payments: "payments",
+      customers: "customers",
+      products: "products",
+      subscriptions: "subscriptions",
+      invoices: "invoices",
+      connect: "connect/accounts",
+      reports: "reports",
+      developers: "developers",
+    };
+    window.open(`https://dashboard.stripe.com/${paths[page] ?? ""}`, "_blank");
+  },
+
+  openExternalLink: async (params) => {
+    if (params?.url) {
+      window.open(params.url as string, "_blank");
+    }
+  },
+
+  // ===========================================================================
+  // Form Actions
+  // ===========================================================================
+  submitForm: async (_params, _, _data) => {
+    // Implementation depends on form handling logic
+  },
+
+  resetForm: async (params, setState) => {
+    const formId = params?.formId as string;
+    if (formId) {
+      setState((prev) => ({ ...prev, [formId]: {} }));
+    }
+  },
+
+  validateForm: async (_params, _, _data) => {
+    // Implementation depends on form validation logic
+  },
+
+  setFormValue: async (params, setState) => {
+    if (params?.statePath) {
+      setState((prev) => ({
+        ...prev,
+        [params.statePath as string]: params?.value,
+      }));
+    }
+  },
+
+  // ===========================================================================
+  // UI Actions
+  // ===========================================================================
+  showToast: async (_params) => {
+    // In a real app, use showToast from @stripe/ui-extension-sdk/utils
+  },
+
+  copyToClipboard: async (params) => {
+    if (params?.text) {
+      await navigator.clipboard.writeText(params.text as string);
+    }
+  },
+
+  setLoading: async (params, setState) => {
+    setState((prev) => ({
+      ...prev,
+      loading: params?.loading,
+      loadingMessage: params?.message,
+    }));
+  },
+
+  // ===========================================================================
+  // Filter & Sort Actions
+  // ===========================================================================
+  setFilter: async (params, setState) => {
+    if (params?.key) {
+      setState((prev) => ({
+        ...prev,
+        filters: {
+          ...(prev.filters as Record<string, unknown>),
+          [params.key as string]: params?.value,
+        },
+      }));
+    }
+  },
+
+  clearFilters: async (_, setState) => {
+    setState((prev) => ({ ...prev, filters: {} }));
+  },
+
+  setSort: async (params, setState) => {
+    setState((prev) => ({
+      ...prev,
+      sort: { field: params?.field, direction: params?.direction },
+    }));
+  },
+
+  setPageSize: async (params, setState) => {
+    setState((prev) => ({ ...prev, pageSize: params?.size }));
+  },
+
+  goToPage: async (params, setState) => {
+    setState((prev) => ({ ...prev, currentPage: params?.page }));
+  },
+};
+
+// =============================================================================
+// Execute Action
+// =============================================================================
+
+type SetStateFn = (
+  updater: (prev: Record<string, unknown>) => Record<string, unknown>,
+) => void;
+
+/**
+ * Execute an action by name with the given parameters.
+ */
+export async function executeAction(
+  actionName: string,
+  params: Record<string, unknown> | undefined,
+  setState: SetStateFn,
+  data: Record<string, unknown> = {},
+): Promise<void> {
+  const handler = actionHandlers[actionName];
+  if (handler) {
+    await handler(params, setState, data);
+  } else {
+    console.warn("Unknown action:", actionName);
+  }
+}

+ 87 - 22
examples/stripe-app/src/lib/render/catalog/components.tsx → examples/stripe-app/drawer-app/src/lib/render/catalog/components.tsx

@@ -62,6 +62,7 @@ type ExtendedRenderProps<P = Record<string, unknown>> =
   ComponentRenderProps<P> & {
     state?: Record<string, unknown>;
     getValue?: (path: string) => unknown;
+    onAction?: (actionName: string, params?: Record<string, unknown>) => void;
   };
 
 // NOTE: All interactive components use `emit` to fire named events.
@@ -254,23 +255,47 @@ export const Metric: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
   );
 };
 
+const BADGE_TYPES = new Set([
+  "neutral",
+  "urgent",
+  "warning",
+  "negative",
+  "positive",
+  "info",
+]);
+const BADGE_ALIAS: Record<string, string> = {
+  success: "positive",
+  error: "negative",
+  danger: "negative",
+  critical: "urgent",
+  default: "neutral",
+  primary: "info",
+};
+
+function coerceBadgeType(
+  raw: unknown,
+): "neutral" | "urgent" | "warning" | "negative" | "positive" | "info" {
+  const s = String(raw ?? "neutral");
+  if (BADGE_TYPES.has(s))
+    return s as
+      | "neutral"
+      | "urgent"
+      | "warning"
+      | "negative"
+      | "positive"
+      | "info";
+  return (BADGE_ALIAS[s] ?? "neutral") as
+    | "neutral"
+    | "urgent"
+    | "warning"
+    | "negative"
+    | "positive"
+    | "info";
+}
+
 export const Badge: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
   const { label, type = "neutral" } = element.props as Record<string, unknown>;
-  return (
-    <UIBadge
-      type={
-        type as
-          | "neutral"
-          | "urgent"
-          | "warning"
-          | "negative"
-          | "positive"
-          | "info"
-      }
-    >
-      {String(label || "")}
-    </UIBadge>
-  );
+  return <UIBadge type={coerceBadgeType(type)}>{String(label || "")}</UIBadge>;
 };
 
 export const Icon: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
@@ -309,6 +334,21 @@ export const Spinner: FunctionComponent<ExtendedRenderProps> = ({
 // =========================================================================
 // Feedback Components
 // =========================================================================
+const BANNER_TYPES = new Set(["default", "caution", "critical"]);
+const BANNER_ALIAS: Record<string, string> = {
+  info: "default",
+  warning: "caution",
+  error: "critical",
+  danger: "critical",
+  success: "default",
+};
+
+function coerceBannerType(raw: unknown): "default" | "caution" | "critical" {
+  const s = String(raw ?? "default");
+  if (BANNER_TYPES.has(s)) return s as "default" | "caution" | "critical";
+  return (BANNER_ALIAS[s] ?? "default") as "default" | "caution" | "critical";
+}
+
 export const Banner: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
   const {
     title,
@@ -319,7 +359,7 @@ export const Banner: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
     <UIBanner
       title={title ? String(title) : undefined}
       description={description ? String(description) : undefined}
-      type={type as "default" | "caution" | "critical"}
+      type={coerceBannerType(type)}
     />
   );
 };
@@ -634,11 +674,12 @@ export const DateField: FunctionComponent<ExtendedRenderProps> = ({
 export const Button: FunctionComponent<ExtendedRenderProps> = ({
   element,
   emit,
+  onAction,
 }) => {
   const {
     label,
     action,
-    actionParams: _actionParams,
+    actionParams,
     type = "primary",
     size = "medium",
     disabled,
@@ -646,6 +687,18 @@ export const Button: FunctionComponent<ExtendedRenderProps> = ({
     href,
   } = element.props as Record<string, unknown>;
 
+  const handlePress = action
+    ? () => {
+        emit("press");
+        if (onAction) {
+          onAction(
+            String(action),
+            actionParams as Record<string, unknown> | undefined,
+          );
+        }
+      }
+    : undefined;
+
   return (
     <UIButton
       type={type as "primary" | "secondary" | "destructive"}
@@ -653,7 +706,7 @@ export const Button: FunctionComponent<ExtendedRenderProps> = ({
       disabled={Boolean(disabled) || undefined}
       pending={Boolean(pending) || undefined}
       href={href ? String(href) : undefined}
-      onPress={action ? () => emit("press") : undefined}
+      onPress={handlePress}
     >
       {String(label || "")}
     </UIButton>
@@ -715,14 +768,26 @@ export const BarChart: FunctionComponent<ExtendedRenderProps> = ({
       x={String(xKey || "x")}
       y={String(yKey || "y")}
       color={colorKey ? String(colorKey) : undefined}
-      axis={showAxis as "x" | "y" | "both" | "none"}
-      grid={showGrid as "x" | "y" | "both" | "none"}
+      axis={coerceAxisGrid(showAxis, "both")}
+      grid={coerceAxisGrid(showGrid, "none")}
       legend={Boolean(showLegend) || undefined}
       tooltip={Boolean(showTooltip)}
     />
   );
 };
 
+function coerceAxisGrid(
+  val: unknown,
+  fallback: string,
+): "x" | "y" | "both" | "none" {
+  if (val === true) return "both";
+  if (val === false) return "none";
+  const s = String(val ?? fallback);
+  if (["x", "y", "both", "none"].includes(s))
+    return s as "x" | "y" | "both" | "none";
+  return fallback as "x" | "y" | "both" | "none";
+}
+
 export const LineChart: FunctionComponent<ExtendedRenderProps> = ({
   element,
 }) => {
@@ -751,8 +816,8 @@ export const LineChart: FunctionComponent<ExtendedRenderProps> = ({
       x={String(xKey || "x")}
       y={String(yKey || "y")}
       color={colorKey ? String(colorKey) : undefined}
-      axis={showAxis as "x" | "y" | "both" | "none"}
-      grid={showGrid as "x" | "y" | "both" | "none"}
+      axis={coerceAxisGrid(showAxis, "both")}
+      grid={coerceAxisGrid(showGrid, "none")}
       legend={Boolean(showLegend) || undefined}
       tooltip={Boolean(showTooltip)}
     />

+ 0 - 0
examples/stripe-app/src/lib/render/index.ts → examples/stripe-app/drawer-app/src/lib/render/index.ts


+ 13 - 1
examples/stripe-app/src/lib/render/renderer.tsx → examples/stripe-app/drawer-app/src/lib/render/renderer.tsx

@@ -9,6 +9,7 @@ import {
 } from "@json-render/react";
 
 import { components, Fallback } from "./catalog/components";
+import { executeAction } from "./catalog/actions";
 
 // =============================================================================
 // Types
@@ -72,6 +73,12 @@ function buildRegistry(
           }
           return current;
         },
+        onAction: (actionName: string, params?: Record<string, unknown>) => {
+          const setState = setDataRef.current;
+          if (setState) {
+            executeAction(actionName, params, setState, dataRef.current);
+          }
+        },
       });
   }
 
@@ -114,10 +121,15 @@ export function StripeRenderer({
     [loading],
   );
 
+  const mergedState = useMemo(
+    () => (spec?.state ? { ...data, ...spec.state } : data),
+    [data, spec?.state],
+  );
+
   if (!spec) return null;
 
   return (
-    <StateProvider initialState={data} onStateChange={onStateChange}>
+    <StateProvider initialState={mergedState} onStateChange={onStateChange}>
       <VisibilityProvider>
         <ActionProvider>
           <Renderer

+ 146 - 0
examples/stripe-app/drawer-app/src/lib/stream-spec.ts

@@ -0,0 +1,146 @@
+import type { Spec } from "@json-render/react";
+
+interface JsonPatch {
+  op: string;
+  path: string;
+  value?: unknown;
+  from?: string;
+}
+
+function setDeep(
+  obj: Record<string, unknown>,
+  segments: string[],
+  value: unknown,
+): void {
+  let current: unknown = obj;
+  for (let i = 0; i < segments.length - 1; i++) {
+    const seg = segments[i];
+    const next = (current as Record<string, unknown>)[seg];
+    if (next && typeof next === "object") {
+      current = next;
+    } else {
+      const container = /^\d+$/.test(segments[i + 1]) ? [] : {};
+      (current as Record<string, unknown>)[seg] = container;
+      current = container;
+    }
+  }
+  const last = segments[segments.length - 1];
+  if (Array.isArray(current)) {
+    (current as unknown[])[Number(last)] = value;
+  } else {
+    (current as Record<string, unknown>)[last] = value;
+  }
+}
+
+function setSpecValue(spec: Spec, path: string, value: unknown): void {
+  if (path === "/root") {
+    (spec as Record<string, unknown>).root = value as string;
+    return;
+  }
+  if (path === "/state") {
+    (spec as Record<string, unknown>).state = value;
+    return;
+  }
+  if (path.startsWith("/state/")) {
+    if (!spec.state) (spec as Record<string, unknown>).state = {};
+    const segments = path.slice("/state/".length).split("/");
+    setDeep(spec.state as Record<string, unknown>, segments, value);
+    return;
+  }
+  const elemMatch = path.match(/^\/elements\/(.+)/);
+  if (elemMatch) {
+    spec.elements[elemMatch[1]] = value as Spec["elements"][string];
+  }
+}
+
+function applyPatch(spec: Spec, patch: JsonPatch): Spec {
+  const next: Spec = {
+    ...spec,
+    elements: { ...spec.elements },
+    ...(spec.state ? { state: { ...spec.state } } : {}),
+  };
+  switch (patch.op) {
+    case "add":
+    case "replace":
+      setSpecValue(next, patch.path, patch.value);
+      break;
+    case "remove":
+      if (patch.path.startsWith("/elements/")) {
+        const key = patch.path.slice("/elements/".length);
+        delete next.elements[key];
+      }
+      break;
+  }
+  return next;
+}
+
+/**
+ * Fetch an AI generation endpoint that returns a JSONL patch stream,
+ * applying patches progressively via onPatch.
+ */
+export async function streamSpec(
+  url: string,
+  body: Record<string, unknown>,
+  onPatch: (spec: Spec) => void,
+): Promise<Spec> {
+  const response = await fetch(url, {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify(body),
+  });
+
+  if (!response.ok) {
+    let msg = `API error: ${response.status}`;
+    try {
+      const errData = await response.json();
+      if (errData.error) msg = errData.error;
+    } catch {
+      // use default
+    }
+    throw new Error(msg);
+  }
+
+  const reader = response.body?.getReader();
+  if (!reader) throw new Error("No response body");
+
+  const decoder = new TextDecoder();
+  let buffer = "";
+  let spec: Spec = { root: "", elements: {} };
+
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done) break;
+
+    buffer += decoder.decode(value, { stream: true });
+    const lines = buffer.split("\n");
+    buffer = lines.pop() ?? "";
+
+    for (const line of lines) {
+      const trimmed = line.trim();
+      if (!trimmed || trimmed.startsWith("//")) continue;
+      try {
+        const parsed = JSON.parse(trimmed) as JsonPatch;
+        if (parsed.op) {
+          spec = applyPatch(spec, parsed);
+          onPatch(spec);
+        }
+      } catch {
+        // skip non-JSON lines
+      }
+    }
+  }
+
+  if (buffer.trim()) {
+    try {
+      const parsed = JSON.parse(buffer.trim()) as JsonPatch;
+      if (parsed.op) {
+        spec = applyPatch(spec, parsed);
+        onPatch(spec);
+      }
+    } catch {
+      // skip
+    }
+  }
+
+  return spec;
+}

+ 0 - 0
examples/stripe-app/src/lib/stripe.ts → examples/stripe-app/drawer-app/src/lib/stripe.ts


+ 0 - 0
examples/stripe-app/src/views/CustomerDetails.test.tsx → examples/stripe-app/drawer-app/src/views/CustomerDetails.test.tsx


+ 2 - 1
examples/stripe-app/src/views/CustomerDetails.tsx → examples/stripe-app/drawer-app/src/views/CustomerDetails.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -373,7 +374,7 @@ const CustomerDetails = ({ environment }: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({

+ 0 - 0
examples/stripe-app/src/views/Customers.test.tsx → examples/stripe-app/drawer-app/src/views/Customers.test.tsx


+ 2 - 1
examples/stripe-app/src/views/Customers.tsx → examples/stripe-app/drawer-app/src/views/Customers.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -172,7 +173,7 @@ const Customers = (_props: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ prompt, systemPrompt }),

+ 0 - 0
examples/stripe-app/src/views/Home.test.tsx → examples/stripe-app/drawer-app/src/views/Home.test.tsx


+ 55 - 41
examples/stripe-app/src/views/Home.tsx → examples/stripe-app/drawer-app/src/views/Home.tsx

@@ -1,10 +1,12 @@
 import { useState, useCallback, useEffect } from "react";
 import type { Spec } from "@json-render/react";
 import {
+  Banner,
   Box,
   ContextView,
   Divider,
   Button,
+  Link,
   TextField,
   Tabs,
   TabList,
@@ -20,6 +22,8 @@ import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 import BrandIcon from "./brand_icon.svg";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
+import { API_GENERATE_URL } from "../lib/config";
+import { streamSpec } from "../lib/stream-spec";
 
 // =============================================================================
 // Dynamic Specs (use real data from context)
@@ -503,34 +507,23 @@ const Home = (_props: ExtensionContextValue) => {
     setError(null);
 
     try {
-      const response = await fetch("http://localhost:3000/api/generate", {
-        method: "POST",
-        headers: { "Content-Type": "application/json" },
-        body: JSON.stringify({
-          prompt,
+      const dataSnapshot = JSON.stringify(data, null, 2);
+
+      await streamSpec(
+        API_GENERATE_URL,
+        {
+          prompt: `${prompt}\n\nAVAILABLE STRIPE DATA (use this real data, do NOT invent fake numbers):\n${dataSnapshot}`,
           systemPrompt: stripeCatalog.prompt({
             system:
               "You are a Stripe dashboard widget builder. Generate UI specs for displaying Stripe data.",
+            customRules: [
+              'LAYOUT CONSTRAINT: This UI renders inside a narrow Stripe dashboard drawer (~320px wide). NEVER place more than 2 small items side-by-side horizontally. Prefer vertical (stacked) layouts. Use direction:"horizontal" sparingly and only for very compact items like a label+value pair.',
+              'DATA: The user prompt includes real Stripe data under "AVAILABLE STRIPE DATA". You MUST use these real values when setting /state. NEVER invent or hallucinate numbers. If the data you need is not available, say so in a Text element rather than making up data. The data keys are: customers (data[], total), payments (data[], total, totalVolume, successRate), subscriptions (data[], total, active, trialing, pastDue, canceled), invoices (data[], total, totalAmount, paid, open, overdue).',
+            ],
           }),
-        }),
-      });
-
-      if (!response.ok) {
-        throw new Error(`API error: ${response.status}`);
-      }
-
-      const contentType = response.headers.get("content-type") || "";
-
-      if (contentType.includes("application/json")) {
-        const responseData = await response.json();
-        if (responseData.spec) {
-          setCurrentSpec(responseData.spec);
-          return;
-        } else if (responseData.error) {
-          throw new Error(responseData.error);
-        }
-      }
-      throw new Error("Invalid response");
+        },
+        (spec) => setCurrentSpec(spec),
+      );
     } catch (err) {
       // Use dynamic specs with real data as fallback
       const lowerPrompt = prompt.toLowerCase();
@@ -625,26 +618,47 @@ const Home = (_props: ExtensionContextValue) => {
                 {currentSpec && (
                   <>
                     <Divider />
-                    <Box css={{ font: "subheading" }}>
-                      Preview (with real Stripe data)
-                    </Box>
-                    <Box
-                      css={{
-                        padding: "medium",
-                        borderRadius: "medium",
-                        keyline: "neutral",
-                      }}
-                    >
-                      <StripeRenderer
-                        spec={currentSpec}
-                        data={data}
-                        setData={handleSetState}
-                        loading={isGenerating}
-                      />
-                    </Box>
+                    <StripeRenderer
+                      spec={currentSpec}
+                      data={data}
+                      setData={handleSetState}
+                      loading={isGenerating}
+                    />
                   </>
                 )}
 
+                {(
+                  data._actionResult as
+                    | { message?: string; url?: string }
+                    | undefined
+                )?.message && (
+                  <Banner
+                    title={(data._actionResult as { message: string }).message}
+                    description={
+                      (data._actionResult as { url?: string }).url
+                        ? "Open in Stripe Dashboard"
+                        : undefined
+                    }
+                    actions={
+                      (data._actionResult as { url?: string }).url ? (
+                        <Link
+                          href={(data._actionResult as { url: string }).url}
+                          external
+                        >
+                          Open
+                        </Link>
+                      ) : undefined
+                    }
+                    onDismiss={() =>
+                      setState((prev) => {
+                        const next = { ...prev };
+                        delete next._actionResult;
+                        return next;
+                      })
+                    }
+                  />
+                )}
+
                 <Divider />
                 <Box css={{ font: "caption", color: "secondary" }}>
                   Try: &quot;Show revenue metrics&quot; &bull; &quot;Show recent

+ 2 - 1
examples/stripe-app/src/views/Invoices.tsx → examples/stripe-app/drawer-app/src/views/Invoices.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -215,7 +216,7 @@ const Invoices = (_props: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ prompt, systemPrompt }),

+ 2 - 1
examples/stripe-app/src/views/PaymentDetails.tsx → examples/stripe-app/drawer-app/src/views/PaymentDetails.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -264,7 +265,7 @@ const PaymentDetails = ({ environment }: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({

+ 2 - 1
examples/stripe-app/src/views/Payments.tsx → examples/stripe-app/drawer-app/src/views/Payments.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -181,7 +182,7 @@ const Payments = (_props: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ prompt, systemPrompt }),

+ 2 - 1
examples/stripe-app/src/views/Products.tsx → examples/stripe-app/drawer-app/src/views/Products.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -232,7 +233,7 @@ const Products = (_props: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ prompt, systemPrompt }),

+ 2 - 1
examples/stripe-app/src/views/Subscriptions.tsx → examples/stripe-app/drawer-app/src/views/Subscriptions.tsx

@@ -10,6 +10,7 @@ import {
 import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
 
 import BrandIcon from "./brand_icon.svg";
+import { API_GENERATE_URL } from "../lib/config";
 import { stripeCatalog, StripeRenderer } from "../lib/render";
 import { executeAction } from "../lib/render/catalog/actions";
 
@@ -230,7 +231,7 @@ const Subscriptions = (_props: ExtensionContextValue) => {
     const systemPrompt = stripeCatalog.prompt();
 
     try {
-      const response = await fetch("/api/generate", {
+      const response = await fetch(API_GENERATE_URL, {
         method: "POST",
         headers: { "Content-Type": "application/json" },
         body: JSON.stringify({ prompt, systemPrompt }),

+ 0 - 0
examples/stripe-app/src/views/brand_icon.svg → examples/stripe-app/drawer-app/src/views/brand_icon.svg


+ 4 - 4
examples/stripe-app/stripe-app.json → examples/stripe-app/drawer-app/stripe-app.template.json

@@ -37,8 +37,8 @@
             "purpose": "Send and manage invoices"
         },
         {
-            "permission": "refund_write",
-            "purpose": "Process refunds"
+            "permission": "charge_write",
+            "purpose": "Process charges and refunds"
         },
         {
             "permission": "product_read",
@@ -49,11 +49,11 @@
             "purpose": "Create and manage products"
         },
         {
-            "permission": "price_read",
+            "permission": "plan_read",
             "purpose": "Display pricing information"
         },
         {
-            "permission": "price_write",
+            "permission": "plan_write",
             "purpose": "Create and manage prices"
         },
         {

+ 0 - 0
examples/stripe-app/tsconfig.json → examples/stripe-app/drawer-app/tsconfig.json


+ 0 - 0
examples/stripe-app/ui-extensions.d.ts → examples/stripe-app/drawer-app/ui-extensions.d.ts


+ 14 - 0
examples/stripe-app/fullpage-app/.env.example

@@ -0,0 +1,14 @@
+# Stripe App ID (optional - overrides the template default)
+# Get this from your Stripe Apps dashboard after uploading
+# STRIPE_APP_ID=com.example.your-app-id
+
+# Stripe App Name (optional - overrides the template default)
+# STRIPE_APP_NAME=My App Name
+
+# AI Gateway API Key
+# Get your key from Vercel AI Gateway dashboard
+AI_GATEWAY_API_KEY=
+
+# Optional: Stripe API keys if you want to use real data
+# STRIPE_SECRET_KEY=sk_test_...
+# STRIPE_PUBLISHABLE_KEY=pk_test_...

+ 49 - 0
examples/stripe-app/fullpage-app/README.md

@@ -0,0 +1,49 @@
+# Full Page Stripe App (Alpha)
+
+A full-page [Stripe App](https://stripe.com/docs/stripe-apps) example using json-render. This uses the `FullPageView` component and the `stripe.dashboard.fullpage` viewport, which are part of Stripe's **private developer preview**.
+
+## Prerequisites
+
+- [Stripe CLI](https://stripe.com/docs/stripe-cli) with the apps plugin
+- A Stripe account with **full-page apps alpha access** enabled by Stripe
+- Both your `app_id` and `account_id` must be flagged in by Stripe
+
+## Setup
+
+```bash
+pnpm install
+
+cp .env.example .env
+# Set STRIPE_APP_ID to your app ID
+
+pnpm setup
+```
+
+## Running
+
+Start the API server first (from `../api`):
+
+```bash
+cd ../api && pnpm dev
+```
+
+Then start the Stripe app:
+
+```bash
+stripe apps start
+```
+
+Navigate directly to your full-page app URL (it will not appear in the left navigation):
+
+```
+https://dashboard.stripe.com/test/app/<your-app-id>
+```
+
+## Troubleshooting
+
+If you are redirected to the Dashboard home page, the feature flag is not enabled for your account. Contact your Stripe partner to have both your `app_id` and `account_id` flagged in.
+
+## Resources
+
+- [FullPageView Component](https://docs.stripe.com/stripe-apps/components/fullpageview?app-sdk-version=9)
+- [FullPageTabs Component](https://docs.stripe.com/stripe-apps/components/fullpagetabs?app-sdk-version=9)

+ 17 - 0
examples/stripe-app/fullpage-app/eslint.config.js

@@ -0,0 +1,17 @@
+import { config as reactConfig } from "@repo/eslint-config/react-internal";
+
+/** @type {import("eslint").Linter.Config[]} */
+export default [
+  ...reactConfig,
+  {
+    rules: {
+      // Disable prop-types - we use TypeScript for type checking
+      "react/prop-types": "off",
+      // Allow underscore-prefixed unused variables
+      "@typescript-eslint/no-unused-vars": [
+        "warn",
+        { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
+      ],
+    },
+  },
+];

+ 7 - 0
examples/stripe-app/fullpage-app/jest.config.js

@@ -0,0 +1,7 @@
+/* eslint-env node */
+/* eslint-disable @typescript-eslint/no-var-requires */
+const UIExtensionsConfig = require("@stripe/ui-extension-tools/jest.config.ui-extension");
+
+module.exports = {
+  ...UIExtensionsConfig,
+};

+ 28 - 0
examples/stripe-app/fullpage-app/package.json

@@ -0,0 +1,28 @@
+{
+  "name": "com.example.json-render-fullpage-demo",
+  "version": "0.0.1",
+  "description": "Full-page Stripe App example (alpha)",
+  "private": true,
+  "license": "~~proprietary~~",
+  "type": "module",
+  "dependencies": {
+    "@json-render/core": "workspace:*",
+    "@json-render/react": "workspace:*",
+    "@stripe/ui-extension-sdk": "9.2.0-alpha.0",
+    "stripe": "^13.11.0",
+    "zod": "^4.0.0"
+  },
+  "engines": {
+    "node": ">=14"
+  },
+  "scripts": {
+    "setup": "node scripts/setup.mjs",
+    "lint": "eslint src --max-warnings 0",
+    "test": "jest"
+  },
+  "devDependencies": {
+    "@repo/eslint-config": "workspace:*",
+    "@stripe/ui-extension-tools": "^0.0.1",
+    "eslint": "^9.39.0"
+  }
+}

+ 5270 - 0
examples/stripe-app/fullpage-app/pnpm-lock.yaml

@@ -0,0 +1,5270 @@
+lockfileVersion: '9.0'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
+
+overrides:
+  '@types/react': ^17.0.2
+
+importers:
+
+  .:
+    dependencies:
+      '@json-render/core':
+        specifier: ^0.4.0
+        version: 0.4.0(zod@4.3.6)
+      '@json-render/react':
+        specifier: ^0.4.0
+        version: 0.4.0(react@18.3.1)(zod@4.3.6)
+      '@stripe/ui-extension-sdk':
+        specifier: ^9.1.0
+        version: 9.1.0(stripe@13.11.0)
+      stripe:
+        specifier: ^13.11.0
+        version: 13.11.0
+    devDependencies:
+      '@stripe/ui-extension-tools':
+        specifier: ^0.0.1
+        version: 0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))
+
+packages:
+
+  '@babel/code-frame@7.29.0':
+    resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/compat-data@7.29.0':
+    resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/core@7.29.0':
+    resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/generator@7.29.1':
+    resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-compilation-targets@7.28.6':
+    resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-globals@7.28.0':
+    resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-imports@7.28.6':
+    resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-module-transforms@7.28.6':
+    resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  '@babel/helper-plugin-utils@7.28.6':
+    resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-string-parser@7.27.1':
+    resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-identifier@7.28.5':
+    resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helper-validator-option@7.27.1':
+    resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/helpers@7.28.6':
+    resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/parser@7.29.0':
+    resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+
+  '@babel/plugin-syntax-async-generators@7.8.4':
+    resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-bigint@7.8.3':
+    resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-class-properties@7.12.13':
+    resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-class-static-block@7.14.5':
+    resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-import-attributes@7.28.6':
+    resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-import-meta@7.10.4':
+    resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-json-strings@7.8.3':
+    resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-logical-assignment-operators@7.10.4':
+    resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3':
+    resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-numeric-separator@7.10.4':
+    resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-object-rest-spread@7.8.3':
+    resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-optional-catch-binding@7.8.3':
+    resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-optional-chaining@7.8.3':
+    resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-private-property-in-object@7.14.5':
+    resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-top-level-await@7.14.5':
+    resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/plugin-syntax-typescript@7.28.6':
+    resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+
+  '@babel/template@7.28.6':
+    resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/traverse@7.29.0':
+    resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+    engines: {node: '>=6.9.0'}
+
+  '@babel/types@7.29.0':
+    resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
+    engines: {node: '>=6.9.0'}
+
+  '@bcoe/v8-coverage@0.2.3':
+    resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+
+  '@eslint-community/eslint-utils@4.9.1':
+    resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+
+  '@eslint-community/regexpp@4.12.2':
+    resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
+    engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+
+  '@eslint/eslintrc@2.1.4':
+    resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  '@eslint/js@8.57.1':
+    resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  '@humanwhocodes/config-array@0.13.0':
+    resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==}
+    engines: {node: '>=10.10.0'}
+    deprecated: Use @eslint/config-array instead
+
+  '@humanwhocodes/module-importer@1.0.1':
+    resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+    engines: {node: '>=12.22'}
+
+  '@humanwhocodes/object-schema@2.0.3':
+    resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+    deprecated: Use @eslint/object-schema instead
+
+  '@istanbuljs/load-nyc-config@1.1.0':
+    resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
+    engines: {node: '>=8'}
+
+  '@istanbuljs/schema@0.1.3':
+    resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+    engines: {node: '>=8'}
+
+  '@jest/console@27.5.1':
+    resolution: {integrity: sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/core@27.5.1':
+    resolution: {integrity: sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    peerDependencies:
+      node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
+  '@jest/environment@27.5.1':
+    resolution: {integrity: sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/expect-utils@28.1.3':
+    resolution: {integrity: sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  '@jest/fake-timers@27.5.1':
+    resolution: {integrity: sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/globals@27.5.1':
+    resolution: {integrity: sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/reporters@27.5.1':
+    resolution: {integrity: sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    peerDependencies:
+      node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
+  '@jest/schemas@28.1.3':
+    resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  '@jest/source-map@27.5.1':
+    resolution: {integrity: sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/test-result@27.5.1':
+    resolution: {integrity: sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/test-sequencer@27.5.1':
+    resolution: {integrity: sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/transform@27.5.1':
+    resolution: {integrity: sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/types@26.6.2':
+    resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==}
+    engines: {node: '>= 10.14.2'}
+
+  '@jest/types@27.5.1':
+    resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  '@jest/types@28.1.3':
+    resolution: {integrity: sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  '@jridgewell/gen-mapping@0.3.13':
+    resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+  '@jridgewell/remapping@2.3.5':
+    resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+  '@jridgewell/resolve-uri@3.1.2':
+    resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+    engines: {node: '>=6.0.0'}
+
+  '@jridgewell/sourcemap-codec@1.5.5':
+    resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+  '@json-render/core@0.4.0':
+    resolution: {integrity: sha512-zcmNNetyXoqShG9qfXnipnkaGLn3XC7Kec/t3+C39mXAStg2RX29ciZwsZT+Fzo900LeRHRIGel7L/IHCdktrA==}
+    peerDependencies:
+      zod: ^4.0.0
+
+  '@json-render/react@0.4.0':
+    resolution: {integrity: sha512-OAXdWdOrAXHEFzpEF7xV+84D00JEmLMKlt5u0wc+C/P+q4q6TnpAWx2j28PBpDB2mpidkW9VnTUM+SCH9J8Lrw==}
+    peerDependencies:
+      react: ^19.0.0
+
+  '@nodelib/fs.scandir@2.1.5':
+    resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.stat@2.0.5':
+    resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+    engines: {node: '>= 8'}
+
+  '@nodelib/fs.walk@1.2.8':
+    resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+    engines: {node: '>= 8'}
+
+  '@remote-ui/async-subscription@2.1.18':
+    resolution: {integrity: sha512-O+76qOiTGN6iJZBFncELNIBnhCbHAIYrsrG0n3SjmwXE9Gy6nl4xZoa5FqIcw5VPz7EAdwKBkFsRE5uyQB7FOw==}
+
+  '@remote-ui/core@2.2.5':
+    resolution: {integrity: sha512-np+j+Bn5fWjqaOng0UQv4S48JqO/aLkKzCLR07c+PRXbxHxvMuGZeq/MuWOyTheUWhDaV2McrGDk+g/yYKQ8mA==}
+
+  '@remote-ui/react@5.0.5':
+    resolution: {integrity: sha512-pzOOL+aU7VBOTu9WpLLVe6YD786zQc7bAxvxTtmZMVlYvsmshD9PeiHsRjhJI8fCLc/RtDeTcMuV7/JjSiblwQ==}
+    peerDependencies:
+      react: '>=17.0.0 <19.0.0'
+      react-reconciler: '>=0.26.0 <0.30.0'
+    peerDependenciesMeta:
+      react-reconciler:
+        optional: true
+
+  '@remote-ui/rpc@1.4.5':
+    resolution: {integrity: sha512-Cr+06niG/vmE4A9YsmaKngRuuVSWKMY42NMwtZfy+gctRWGu6Wj9BWuMJg5CEp+JTkRBPToqT5rqnrg1G/Wvow==}
+
+  '@remote-ui/rpc@1.4.7':
+    resolution: {integrity: sha512-ORiaKsbVBSEi3Z4YWOj5Ucrp70NrkNktI1hdqqfBW7Z3o0YoxTX9MIqtLmsc6721IbjmExvLrLip5I5Y7uAbng==}
+
+  '@remote-ui/testing@1.4.3':
+    resolution: {integrity: sha512-gjR6wl3nK6Ym3+B3JKjbsrV34Oqs6TOh84vMAhwKlugRR7538dLClt958sNRMruVJK9jEXTGytmvn7wYCr5aHQ==}
+
+  '@remote-ui/types@1.1.3':
+    resolution: {integrity: sha512-P1kN1F3p0oMgnLN8Of1Ie9am3sLvJ7nhqHH1pvzkrxqjVwhhyPVZNcwOHyUNZPKp62izhDavdrcnqrdXzVJqGA==}
+
+  '@sinclair/typebox@0.24.51':
+    resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==}
+
+  '@sinonjs/commons@1.8.6':
+    resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==}
+
+  '@sinonjs/fake-timers@8.1.0':
+    resolution: {integrity: sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==}
+
+  '@stripe/ui-extension-sdk@9.1.0':
+    resolution: {integrity: sha512-NRmgAUMxksVcq4vTDuxl/1nacpb3lJSJnd1RTJgo4pk0WKpDRNNvZKJg7CMjN5VVTvnNnVEHDkAG43powUQt3A==}
+    peerDependencies:
+      stripe: '>= 8.195.0'
+
+  '@stripe/ui-extension-tools@0.0.1':
+    resolution: {integrity: sha512-0pOgQ3AuEUeypAgAhcJbyC9QxMaMW1OqzzxkCO4a+5ALDyIFEA6M4jDQ3H9KayNwqQ23qq+PQ0rlYY7dRACNgA==}
+
+  '@tootallnate/once@1.1.2':
+    resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
+    engines: {node: '>= 6'}
+
+  '@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/graceful-fs@4.1.9':
+    resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
+
+  '@types/istanbul-lib-coverage@2.0.6':
+    resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
+
+  '@types/istanbul-lib-report@3.0.3':
+    resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
+
+  '@types/istanbul-reports@3.0.4':
+    resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
+
+  '@types/jest@28.1.8':
+    resolution: {integrity: sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==}
+
+  '@types/json-schema@7.0.15':
+    resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+
+  '@types/node@25.2.1':
+    resolution: {integrity: sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==}
+
+  '@types/prettier@2.7.3':
+    resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==}
+
+  '@types/prop-types@15.7.15':
+    resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+  '@types/react-reconciler@0.28.9':
+    resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
+    peerDependencies:
+      '@types/react': ^17.0.2
+
+  '@types/react@17.0.91':
+    resolution: {integrity: sha512-xauZca6qMeCU3Moy0KxCM9jtf1vyk6qRYK39Ryf3afUqwgNUjRIGoDdS9BcGWgAMGSg1hvP4XcmlYrM66PtqeA==}
+
+  '@types/scheduler@0.16.8':
+    resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
+
+  '@types/semver@7.7.1':
+    resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==}
+
+  '@types/stack-utils@2.0.3':
+    resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
+
+  '@types/stylis@4.2.6':
+    resolution: {integrity: sha512-4nebF2ZJGzQk0ka0O6+FZUWceyFv4vWq/0dXBMmrSeAwzOuOd/GxE5Pa64d/ndeNLG73dXoBsRzvtsVsYUv6Uw==}
+
+  '@types/yargs-parser@21.0.3':
+    resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
+
+  '@types/yargs@15.0.20':
+    resolution: {integrity: sha512-KIkX+/GgfFitlASYCGoSF+T4XRXhOubJLhkLVtSfsRTe9jWMmuM2g28zQ41BtPTG7TRBb2xHW+LCNVE9QR/vsg==}
+
+  '@types/yargs@16.0.11':
+    resolution: {integrity: sha512-sbtvk8wDN+JvEdabmZExoW/HNr1cB7D/j4LT08rMiuikfA7m/JNJg7ATQcgzs34zHnoScDkY0ZRSl29Fkmk36g==}
+
+  '@types/yargs@17.0.35':
+    resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==}
+
+  '@typescript-eslint/eslint-plugin@5.62.0':
+    resolution: {integrity: sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^5.0.0
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/parser@5.62.0':
+    resolution: {integrity: sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/scope-manager@5.62.0':
+    resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  '@typescript-eslint/type-utils@5.62.0':
+    resolution: {integrity: sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/types@5.62.0':
+    resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  '@typescript-eslint/typescript-estree@5.62.0':
+    resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+
+  '@typescript-eslint/utils@5.62.0':
+    resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+  '@typescript-eslint/visitor-keys@5.62.0':
+    resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  '@ungap/structured-clone@1.3.0':
+    resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+
+  abab@2.0.6:
+    resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
+    deprecated: Use your platform's native atob() and btoa() methods instead
+
+  acorn-globals@6.0.0:
+    resolution: {integrity: sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==}
+
+  acorn-jsx@5.3.2:
+    resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+    peerDependencies:
+      acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+
+  acorn-walk@7.2.0:
+    resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==}
+    engines: {node: '>=0.4.0'}
+
+  acorn@7.4.1:
+    resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  acorn@8.15.0:
+    resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==}
+    engines: {node: '>=0.4.0'}
+    hasBin: true
+
+  agent-base@6.0.2:
+    resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
+    engines: {node: '>= 6.0.0'}
+
+  ajv@6.12.6:
+    resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+
+  ansi-escapes@4.3.2:
+    resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
+    engines: {node: '>=8'}
+
+  ansi-regex@5.0.1:
+    resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+    engines: {node: '>=8'}
+
+  ansi-styles@4.3.0:
+    resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+    engines: {node: '>=8'}
+
+  ansi-styles@5.2.0:
+    resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
+    engines: {node: '>=10'}
+
+  anymatch@3.1.3:
+    resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+    engines: {node: '>= 8'}
+
+  argparse@1.0.10:
+    resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
+
+  argparse@2.0.1:
+    resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+  array-buffer-byte-length@1.0.2:
+    resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
+    engines: {node: '>= 0.4'}
+
+  array-includes@3.1.9:
+    resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==}
+    engines: {node: '>= 0.4'}
+
+  array-union@2.1.0:
+    resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+    engines: {node: '>=8'}
+
+  array.prototype.findlast@1.2.5:
+    resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
+    engines: {node: '>= 0.4'}
+
+  array.prototype.flat@1.3.3:
+    resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
+    engines: {node: '>= 0.4'}
+
+  array.prototype.flatmap@1.3.3:
+    resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
+    engines: {node: '>= 0.4'}
+
+  array.prototype.tosorted@1.1.4:
+    resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
+    engines: {node: '>= 0.4'}
+
+  arraybuffer.prototype.slice@1.0.4:
+    resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
+    engines: {node: '>= 0.4'}
+
+  async-function@1.0.0:
+    resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
+    engines: {node: '>= 0.4'}
+
+  asynckit@0.4.0:
+    resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
+  available-typed-arrays@1.0.7:
+    resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
+    engines: {node: '>= 0.4'}
+
+  babel-jest@27.5.1:
+    resolution: {integrity: sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    peerDependencies:
+      '@babel/core': ^7.8.0
+
+  babel-plugin-istanbul@6.1.1:
+    resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==}
+    engines: {node: '>=8'}
+
+  babel-plugin-jest-hoist@27.5.1:
+    resolution: {integrity: sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  babel-preset-current-node-syntax@1.2.0:
+    resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0 || ^8.0.0-0
+
+  babel-preset-jest@27.5.1:
+    resolution: {integrity: sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+
+  balanced-match@1.0.2:
+    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+
+  baseline-browser-mapping@2.9.19:
+    resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
+    hasBin: true
+
+  brace-expansion@1.1.12:
+    resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+
+  braces@3.0.3:
+    resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
+    engines: {node: '>=8'}
+
+  browser-process-hrtime@1.0.0:
+    resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==}
+
+  browserslist@4.28.1:
+    resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+
+  bs-logger@0.2.6:
+    resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
+    engines: {node: '>= 6'}
+
+  bser@2.1.1:
+    resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==}
+
+  buffer-from@1.1.2:
+    resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
+
+  call-bind-apply-helpers@1.0.2:
+    resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
+    engines: {node: '>= 0.4'}
+
+  call-bind@1.0.8:
+    resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
+    engines: {node: '>= 0.4'}
+
+  call-bound@1.0.4:
+    resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
+    engines: {node: '>= 0.4'}
+
+  callsites@3.1.0:
+    resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+    engines: {node: '>=6'}
+
+  camelcase@5.3.1:
+    resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
+    engines: {node: '>=6'}
+
+  camelcase@6.3.0:
+    resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==}
+    engines: {node: '>=10'}
+
+  caniuse-lite@1.0.30001768:
+    resolution: {integrity: sha512-qY3aDRZC5nWPgHUgIB84WL+nySuo19wk0VJpp/XI9T34lrvkyhRvNVOFJOp2kxClQhiFBu+TaUSudf6oa3vkSA==}
+
+  chalk@4.1.2:
+    resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+    engines: {node: '>=10'}
+
+  char-regex@1.0.2:
+    resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
+    engines: {node: '>=10'}
+
+  ci-info@3.9.0:
+    resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==}
+    engines: {node: '>=8'}
+
+  cjs-module-lexer@1.4.3:
+    resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}
+
+  cliui@7.0.4:
+    resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+
+  co@4.6.0:
+    resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
+    engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
+
+  collect-v8-coverage@1.0.3:
+    resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==}
+
+  color-convert@2.0.1:
+    resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+    engines: {node: '>=7.0.0'}
+
+  color-name@1.1.4:
+    resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+
+  combined-stream@1.0.8:
+    resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+    engines: {node: '>= 0.8'}
+
+  concat-map@0.0.1:
+    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+
+  convert-source-map@1.9.0:
+    resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
+
+  convert-source-map@2.0.0:
+    resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
+  cross-spawn@7.0.6:
+    resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
+    engines: {node: '>= 8'}
+
+  cssom@0.3.8:
+    resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
+
+  cssom@0.4.4:
+    resolution: {integrity: sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==}
+
+  cssstyle@2.3.0:
+    resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==}
+    engines: {node: '>=8'}
+
+  csstype@3.2.3:
+    resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+  data-urls@2.0.0:
+    resolution: {integrity: sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==}
+    engines: {node: '>=10'}
+
+  data-view-buffer@1.0.2:
+    resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
+    engines: {node: '>= 0.4'}
+
+  data-view-byte-length@1.0.2:
+    resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
+    engines: {node: '>= 0.4'}
+
+  data-view-byte-offset@1.0.1:
+    resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
+    engines: {node: '>= 0.4'}
+
+  debug@4.4.3:
+    resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+    engines: {node: '>=6.0'}
+    peerDependencies:
+      supports-color: '*'
+    peerDependenciesMeta:
+      supports-color:
+        optional: true
+
+  decimal.js@10.6.0:
+    resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
+
+  dedent@0.7.0:
+    resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
+
+  deep-is@0.1.4:
+    resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+
+  deepmerge@4.3.1:
+    resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+    engines: {node: '>=0.10.0'}
+
+  define-data-property@1.1.4:
+    resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
+    engines: {node: '>= 0.4'}
+
+  define-properties@1.2.1:
+    resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
+    engines: {node: '>= 0.4'}
+
+  delayed-stream@1.0.0:
+    resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+    engines: {node: '>=0.4.0'}
+
+  detect-newline@3.1.0:
+    resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
+    engines: {node: '>=8'}
+
+  diff-sequences@26.6.2:
+    resolution: {integrity: sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==}
+    engines: {node: '>= 10.14.2'}
+
+  diff-sequences@27.5.1:
+    resolution: {integrity: sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  diff-sequences@28.1.1:
+    resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  dir-glob@3.0.1:
+    resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+    engines: {node: '>=8'}
+
+  doctrine@2.1.0:
+    resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
+    engines: {node: '>=0.10.0'}
+
+  doctrine@3.0.0:
+    resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
+    engines: {node: '>=6.0.0'}
+
+  domexception@2.0.1:
+    resolution: {integrity: sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==}
+    engines: {node: '>=8'}
+    deprecated: Use your platform's native DOMException instead
+
+  dunder-proto@1.0.1:
+    resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
+    engines: {node: '>= 0.4'}
+
+  electron-to-chromium@1.5.286:
+    resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==}
+
+  emittery@0.8.1:
+    resolution: {integrity: sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==}
+    engines: {node: '>=10'}
+
+  emoji-regex@8.0.0:
+    resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
+  error-ex@1.3.4:
+    resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+  es-abstract@1.24.1:
+    resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==}
+    engines: {node: '>= 0.4'}
+
+  es-define-property@1.0.1:
+    resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
+    engines: {node: '>= 0.4'}
+
+  es-errors@1.3.0:
+    resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
+    engines: {node: '>= 0.4'}
+
+  es-iterator-helpers@1.2.2:
+    resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
+    engines: {node: '>= 0.4'}
+
+  es-object-atoms@1.1.1:
+    resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
+    engines: {node: '>= 0.4'}
+
+  es-set-tostringtag@2.1.0:
+    resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
+    engines: {node: '>= 0.4'}
+
+  es-shim-unscopables@1.1.0:
+    resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
+    engines: {node: '>= 0.4'}
+
+  es-to-primitive@1.3.0:
+    resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
+    engines: {node: '>= 0.4'}
+
+  escalade@3.2.0:
+    resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+    engines: {node: '>=6'}
+
+  escape-string-regexp@2.0.0:
+    resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==}
+    engines: {node: '>=8'}
+
+  escape-string-regexp@4.0.0:
+    resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+    engines: {node: '>=10'}
+
+  escodegen@2.1.0:
+    resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
+    engines: {node: '>=6.0'}
+    hasBin: true
+
+  eslint-plugin-react-hooks@4.6.2:
+    resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+
+  eslint-plugin-react@7.37.5:
+    resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+
+  eslint-scope@5.1.1:
+    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+    engines: {node: '>=8.0.0'}
+
+  eslint-scope@7.2.2:
+    resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  eslint-visitor-keys@3.4.3:
+    resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  eslint@8.57.1:
+    resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options.
+    hasBin: true
+
+  espree@9.6.1:
+    resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+
+  esprima@4.0.1:
+    resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+    engines: {node: '>=4'}
+    hasBin: true
+
+  esquery@1.7.0:
+    resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
+    engines: {node: '>=0.10'}
+
+  esrecurse@4.3.0:
+    resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+    engines: {node: '>=4.0'}
+
+  estraverse@4.3.0:
+    resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==}
+    engines: {node: '>=4.0'}
+
+  estraverse@5.3.0:
+    resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+    engines: {node: '>=4.0'}
+
+  esutils@2.0.3:
+    resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+    engines: {node: '>=0.10.0'}
+
+  execa@5.1.1:
+    resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+    engines: {node: '>=10'}
+
+  exit@0.1.2:
+    resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
+    engines: {node: '>= 0.8.0'}
+
+  expect@27.5.1:
+    resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  expect@28.1.3:
+    resolution: {integrity: sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  fast-deep-equal@3.1.3:
+    resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+  fast-glob@3.3.3:
+    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+    engines: {node: '>=8.6.0'}
+
+  fast-json-stable-stringify@2.1.0:
+    resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+
+  fast-levenshtein@2.0.6:
+    resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+
+  fastq@1.20.1:
+    resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
+
+  fb-watchman@2.0.2:
+    resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==}
+
+  file-entry-cache@6.0.1:
+    resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
+    engines: {node: ^10.12.0 || >=12.0.0}
+
+  fill-range@7.1.1:
+    resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
+    engines: {node: '>=8'}
+
+  find-up@4.1.0:
+    resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
+    engines: {node: '>=8'}
+
+  find-up@5.0.0:
+    resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+    engines: {node: '>=10'}
+
+  flat-cache@3.2.0:
+    resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
+    engines: {node: ^10.12.0 || >=12.0.0}
+
+  flatted@3.3.3:
+    resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+
+  for-each@0.3.5:
+    resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
+    engines: {node: '>= 0.4'}
+
+  form-data@3.0.4:
+    resolution: {integrity: sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==}
+    engines: {node: '>= 6'}
+
+  fs.realpath@1.0.0:
+    resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+
+  fsevents@2.3.3:
+    resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+    os: [darwin]
+
+  function-bind@1.1.2:
+    resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+
+  function.prototype.name@1.1.8:
+    resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
+    engines: {node: '>= 0.4'}
+
+  functions-have-names@1.2.3:
+    resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
+
+  generator-function@2.0.1:
+    resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
+    engines: {node: '>= 0.4'}
+
+  gensync@1.0.0-beta.2:
+    resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+    engines: {node: '>=6.9.0'}
+
+  get-caller-file@2.0.5:
+    resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+    engines: {node: 6.* || 8.* || >= 10.*}
+
+  get-intrinsic@1.3.0:
+    resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
+    engines: {node: '>= 0.4'}
+
+  get-package-type@0.1.0:
+    resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
+    engines: {node: '>=8.0.0'}
+
+  get-proto@1.0.1:
+    resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
+    engines: {node: '>= 0.4'}
+
+  get-stream@6.0.1:
+    resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+    engines: {node: '>=10'}
+
+  get-symbol-description@1.1.0:
+    resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
+    engines: {node: '>= 0.4'}
+
+  glob-parent@5.1.2:
+    resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+    engines: {node: '>= 6'}
+
+  glob-parent@6.0.2:
+    resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+    engines: {node: '>=10.13.0'}
+
+  glob@7.2.3:
+    resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+    deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+
+  globals@13.24.0:
+    resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+    engines: {node: '>=8'}
+
+  globalthis@1.0.4:
+    resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
+    engines: {node: '>= 0.4'}
+
+  globby@11.1.0:
+    resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+    engines: {node: '>=10'}
+
+  gopd@1.2.0:
+    resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
+    engines: {node: '>= 0.4'}
+
+  graceful-fs@4.2.11:
+    resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+  graphemer@1.4.0:
+    resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+
+  has-bigints@1.1.0:
+    resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
+    engines: {node: '>= 0.4'}
+
+  has-flag@4.0.0:
+    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+    engines: {node: '>=8'}
+
+  has-property-descriptors@1.0.2:
+    resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
+
+  has-proto@1.2.0:
+    resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
+    engines: {node: '>= 0.4'}
+
+  has-symbols@1.1.0:
+    resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
+    engines: {node: '>= 0.4'}
+
+  has-tostringtag@1.0.2:
+    resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
+    engines: {node: '>= 0.4'}
+
+  hasown@2.0.2:
+    resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
+    engines: {node: '>= 0.4'}
+
+  html-encoding-sniffer@2.0.1:
+    resolution: {integrity: sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==}
+    engines: {node: '>=10'}
+
+  html-escaper@2.0.2:
+    resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
+  http-proxy-agent@4.0.1:
+    resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
+    engines: {node: '>= 6'}
+
+  https-proxy-agent@5.0.1:
+    resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
+    engines: {node: '>= 6'}
+
+  human-signals@2.1.0:
+    resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+    engines: {node: '>=10.17.0'}
+
+  iconv-lite@0.4.24:
+    resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
+    engines: {node: '>=0.10.0'}
+
+  ignore@5.3.2:
+    resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
+    engines: {node: '>= 4'}
+
+  import-fresh@3.3.1:
+    resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
+    engines: {node: '>=6'}
+
+  import-local@3.2.0:
+    resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==}
+    engines: {node: '>=8'}
+    hasBin: true
+
+  imurmurhash@0.1.4:
+    resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+    engines: {node: '>=0.8.19'}
+
+  inflight@1.0.6:
+    resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+    deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
+
+  inherits@2.0.4:
+    resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+  internal-slot@1.1.0:
+    resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
+    engines: {node: '>= 0.4'}
+
+  invariant@2.2.4:
+    resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
+
+  is-array-buffer@3.0.5:
+    resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
+    engines: {node: '>= 0.4'}
+
+  is-arrayish@0.2.1:
+    resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+  is-async-function@2.1.1:
+    resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
+    engines: {node: '>= 0.4'}
+
+  is-bigint@1.1.0:
+    resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
+    engines: {node: '>= 0.4'}
+
+  is-boolean-object@1.2.2:
+    resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
+    engines: {node: '>= 0.4'}
+
+  is-callable@1.2.7:
+    resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
+    engines: {node: '>= 0.4'}
+
+  is-core-module@2.16.1:
+    resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
+    engines: {node: '>= 0.4'}
+
+  is-data-view@1.0.2:
+    resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
+    engines: {node: '>= 0.4'}
+
+  is-date-object@1.1.0:
+    resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
+    engines: {node: '>= 0.4'}
+
+  is-extglob@2.1.1:
+    resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+    engines: {node: '>=0.10.0'}
+
+  is-finalizationregistry@1.1.1:
+    resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
+    engines: {node: '>= 0.4'}
+
+  is-fullwidth-code-point@3.0.0:
+    resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+    engines: {node: '>=8'}
+
+  is-generator-fn@2.1.0:
+    resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
+    engines: {node: '>=6'}
+
+  is-generator-function@1.1.2:
+    resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
+    engines: {node: '>= 0.4'}
+
+  is-glob@4.0.3:
+    resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+    engines: {node: '>=0.10.0'}
+
+  is-map@2.0.3:
+    resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
+    engines: {node: '>= 0.4'}
+
+  is-negative-zero@2.0.3:
+    resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
+    engines: {node: '>= 0.4'}
+
+  is-number-object@1.1.1:
+    resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
+    engines: {node: '>= 0.4'}
+
+  is-number@7.0.0:
+    resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+    engines: {node: '>=0.12.0'}
+
+  is-path-inside@3.0.3:
+    resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+    engines: {node: '>=8'}
+
+  is-potential-custom-element-name@1.0.1:
+    resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
+  is-regex@1.2.1:
+    resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
+    engines: {node: '>= 0.4'}
+
+  is-set@2.0.3:
+    resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
+    engines: {node: '>= 0.4'}
+
+  is-shared-array-buffer@1.0.4:
+    resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
+    engines: {node: '>= 0.4'}
+
+  is-stream@2.0.1:
+    resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+    engines: {node: '>=8'}
+
+  is-string@1.1.1:
+    resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
+    engines: {node: '>= 0.4'}
+
+  is-symbol@1.1.1:
+    resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
+    engines: {node: '>= 0.4'}
+
+  is-typed-array@1.1.15:
+    resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
+    engines: {node: '>= 0.4'}
+
+  is-typedarray@1.0.0:
+    resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
+
+  is-weakmap@2.0.2:
+    resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
+    engines: {node: '>= 0.4'}
+
+  is-weakref@1.1.1:
+    resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
+    engines: {node: '>= 0.4'}
+
+  is-weakset@2.0.4:
+    resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
+    engines: {node: '>= 0.4'}
+
+  isarray@2.0.5:
+    resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
+
+  isexe@2.0.0:
+    resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+
+  istanbul-lib-coverage@3.2.2:
+    resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+    engines: {node: '>=8'}
+
+  istanbul-lib-instrument@5.2.1:
+    resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
+    engines: {node: '>=8'}
+
+  istanbul-lib-report@3.0.1:
+    resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+    engines: {node: '>=10'}
+
+  istanbul-lib-source-maps@4.0.1:
+    resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==}
+    engines: {node: '>=10'}
+
+  istanbul-reports@3.2.0:
+    resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==}
+    engines: {node: '>=8'}
+
+  iterator.prototype@1.1.5:
+    resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
+    engines: {node: '>= 0.4'}
+
+  jest-changed-files@27.5.1:
+    resolution: {integrity: sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-circus@27.5.1:
+    resolution: {integrity: sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-cli@27.5.1:
+    resolution: {integrity: sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    hasBin: true
+    peerDependencies:
+      node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
+  jest-config@27.5.1:
+    resolution: {integrity: sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    peerDependencies:
+      ts-node: '>=9.0.0'
+    peerDependenciesMeta:
+      ts-node:
+        optional: true
+
+  jest-diff@26.6.2:
+    resolution: {integrity: sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==}
+    engines: {node: '>= 10.14.2'}
+
+  jest-diff@27.5.1:
+    resolution: {integrity: sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-diff@28.1.3:
+    resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  jest-docblock@27.5.1:
+    resolution: {integrity: sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-each@27.5.1:
+    resolution: {integrity: sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-environment-jsdom@27.5.1:
+    resolution: {integrity: sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-environment-node@27.5.1:
+    resolution: {integrity: sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-get-type@26.3.0:
+    resolution: {integrity: sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==}
+    engines: {node: '>= 10.14.2'}
+
+  jest-get-type@27.5.1:
+    resolution: {integrity: sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-get-type@28.0.2:
+    resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  jest-haste-map@27.5.1:
+    resolution: {integrity: sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-jasmine2@27.5.1:
+    resolution: {integrity: sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-leak-detector@27.5.1:
+    resolution: {integrity: sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-matcher-utils@26.6.2:
+    resolution: {integrity: sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==}
+    engines: {node: '>= 10.14.2'}
+
+  jest-matcher-utils@27.5.1:
+    resolution: {integrity: sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-matcher-utils@28.1.3:
+    resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  jest-message-util@27.5.1:
+    resolution: {integrity: sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-message-util@28.1.3:
+    resolution: {integrity: sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  jest-mock@27.5.1:
+    resolution: {integrity: sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-pnp-resolver@1.2.3:
+    resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==}
+    engines: {node: '>=6'}
+    peerDependencies:
+      jest-resolve: '*'
+    peerDependenciesMeta:
+      jest-resolve:
+        optional: true
+
+  jest-regex-util@27.5.1:
+    resolution: {integrity: sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-resolve-dependencies@27.5.1:
+    resolution: {integrity: sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-resolve@27.5.1:
+    resolution: {integrity: sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-runner@27.5.1:
+    resolution: {integrity: sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-runtime@27.5.1:
+    resolution: {integrity: sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-serializer@27.5.1:
+    resolution: {integrity: sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-snapshot@27.5.1:
+    resolution: {integrity: sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-transform-stub@2.0.0:
+    resolution: {integrity: sha512-lspHaCRx/mBbnm3h4uMMS3R5aZzMwyNpNIJLXj4cEsV0mIUtS4IjYJLSoyjRCtnxb6RIGJ4NL2quZzfIeNhbkg==}
+
+  jest-util@27.5.1:
+    resolution: {integrity: sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-util@28.1.3:
+    resolution: {integrity: sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  jest-validate@27.5.1:
+    resolution: {integrity: sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-watcher@27.5.1:
+    resolution: {integrity: sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+
+  jest-worker@27.5.1:
+    resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
+    engines: {node: '>= 10.13.0'}
+
+  jest@27.5.1:
+    resolution: {integrity: sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    hasBin: true
+    peerDependencies:
+      node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0
+    peerDependenciesMeta:
+      node-notifier:
+        optional: true
+
+  js-tokens@4.0.0:
+    resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+  js-yaml@3.14.2:
+    resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==}
+    hasBin: true
+
+  js-yaml@4.1.1:
+    resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+    hasBin: true
+
+  jsdom@16.7.0:
+    resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==}
+    engines: {node: '>=10'}
+    peerDependencies:
+      canvas: ^2.5.0
+    peerDependenciesMeta:
+      canvas:
+        optional: true
+
+  jsesc@3.1.0:
+    resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  json-buffer@3.0.1:
+    resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+
+  json-parse-even-better-errors@2.3.1:
+    resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+  json-schema-traverse@0.4.1:
+    resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+
+  json-stable-stringify-without-jsonify@1.0.1:
+    resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+
+  json5@2.2.3:
+    resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+    engines: {node: '>=6'}
+    hasBin: true
+
+  jsx-ast-utils@3.3.5:
+    resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
+    engines: {node: '>=4.0'}
+
+  keyv@4.5.4:
+    resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+
+  kleur@3.0.3:
+    resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+    engines: {node: '>=6'}
+
+  leven@3.1.0:
+    resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
+    engines: {node: '>=6'}
+
+  levn@0.4.1:
+    resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+    engines: {node: '>= 0.8.0'}
+
+  lines-and-columns@1.2.4:
+    resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+  locate-path@5.0.0:
+    resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
+    engines: {node: '>=8'}
+
+  locate-path@6.0.0:
+    resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+    engines: {node: '>=10'}
+
+  lodash.memoize@4.1.2:
+    resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
+
+  lodash.merge@4.6.2:
+    resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+
+  lodash@4.17.23:
+    resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
+
+  loose-envify@1.4.0:
+    resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+    hasBin: true
+
+  lru-cache@5.1.1:
+    resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+  make-dir@4.0.0:
+    resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+    engines: {node: '>=10'}
+
+  make-error@1.3.6:
+    resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+  makeerror@1.0.12:
+    resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==}
+
+  math-intrinsics@1.1.0:
+    resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
+    engines: {node: '>= 0.4'}
+
+  merge-stream@2.0.0:
+    resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
+  merge2@1.4.1:
+    resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+    engines: {node: '>= 8'}
+
+  micromatch@4.0.8:
+    resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
+    engines: {node: '>=8.6'}
+
+  mime-db@1.52.0:
+    resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+    engines: {node: '>= 0.6'}
+
+  mime-types@2.1.35:
+    resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+    engines: {node: '>= 0.6'}
+
+  mimic-fn@2.1.0:
+    resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+    engines: {node: '>=6'}
+
+  minimatch@3.1.2:
+    resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+
+  ms@2.1.3:
+    resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+  natural-compare-lite@1.4.0:
+    resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
+
+  natural-compare@1.4.0:
+    resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+
+  node-int64@0.4.0:
+    resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
+
+  node-releases@2.0.27:
+    resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+  normalize-path@3.0.0:
+    resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+    engines: {node: '>=0.10.0'}
+
+  npm-run-path@4.0.1:
+    resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+    engines: {node: '>=8'}
+
+  nwsapi@2.2.23:
+    resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==}
+
+  object-assign@4.1.1:
+    resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+    engines: {node: '>=0.10.0'}
+
+  object-inspect@1.13.4:
+    resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
+    engines: {node: '>= 0.4'}
+
+  object-keys@1.1.1:
+    resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
+    engines: {node: '>= 0.4'}
+
+  object.assign@4.1.7:
+    resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
+    engines: {node: '>= 0.4'}
+
+  object.entries@1.1.9:
+    resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
+    engines: {node: '>= 0.4'}
+
+  object.fromentries@2.0.8:
+    resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
+    engines: {node: '>= 0.4'}
+
+  object.values@1.2.1:
+    resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
+    engines: {node: '>= 0.4'}
+
+  once@1.4.0:
+    resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+
+  onetime@5.1.2:
+    resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+    engines: {node: '>=6'}
+
+  optionator@0.9.4:
+    resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
+    engines: {node: '>= 0.8.0'}
+
+  own-keys@1.0.1:
+    resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
+    engines: {node: '>= 0.4'}
+
+  p-limit@2.3.0:
+    resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
+    engines: {node: '>=6'}
+
+  p-limit@3.1.0:
+    resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+    engines: {node: '>=10'}
+
+  p-locate@4.1.0:
+    resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
+    engines: {node: '>=8'}
+
+  p-locate@5.0.0:
+    resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+    engines: {node: '>=10'}
+
+  p-try@2.2.0:
+    resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
+    engines: {node: '>=6'}
+
+  parent-module@1.0.1:
+    resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+    engines: {node: '>=6'}
+
+  parse-json@5.2.0:
+    resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+    engines: {node: '>=8'}
+
+  parse5@6.0.1:
+    resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==}
+
+  path-exists@4.0.0:
+    resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+    engines: {node: '>=8'}
+
+  path-is-absolute@1.0.1:
+    resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+    engines: {node: '>=0.10.0'}
+
+  path-key@3.1.1:
+    resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+    engines: {node: '>=8'}
+
+  path-parse@1.0.7:
+    resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+
+  path-type@4.0.0:
+    resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+    engines: {node: '>=8'}
+
+  picocolors@1.1.1:
+    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+  picomatch@2.3.1:
+    resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+    engines: {node: '>=8.6'}
+
+  pirates@4.0.7:
+    resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
+    engines: {node: '>= 6'}
+
+  pkg-dir@4.2.0:
+    resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
+    engines: {node: '>=8'}
+
+  possible-typed-array-names@1.1.0:
+    resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
+    engines: {node: '>= 0.4'}
+
+  prelude-ls@1.2.1:
+    resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+    engines: {node: '>= 0.8.0'}
+
+  pretty-format@26.6.2:
+    resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==}
+    engines: {node: '>= 10'}
+
+  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}
+
+  pretty-format@28.1.3:
+    resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==}
+    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
+
+  prompts@2.4.2:
+    resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+    engines: {node: '>= 6'}
+
+  prop-types@15.8.1:
+    resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
+  psl@1.15.0:
+    resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==}
+
+  punycode@2.3.1:
+    resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+    engines: {node: '>=6'}
+
+  qs@6.14.1:
+    resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
+    engines: {node: '>=0.6'}
+
+  querystringify@2.2.0:
+    resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
+
+  queue-microtask@1.2.3:
+    resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+
+  react-is@16.13.1:
+    resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+  react-is@17.0.2:
+    resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
+
+  react-is@18.3.1:
+    resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
+
+  react-reconciler@0.29.0:
+    resolution: {integrity: sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==}
+    engines: {node: '>=0.10.0'}
+    peerDependencies:
+      react: ^18.2.0
+
+  react@18.3.1:
+    resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+    engines: {node: '>=0.10.0'}
+
+  reflect.getprototypeof@1.0.10:
+    resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
+    engines: {node: '>= 0.4'}
+
+  regexp.prototype.flags@1.5.4:
+    resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
+    engines: {node: '>= 0.4'}
+
+  require-directory@2.1.1:
+    resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+    engines: {node: '>=0.10.0'}
+
+  requires-port@1.0.0:
+    resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
+
+  resolve-cwd@3.0.0:
+    resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==}
+    engines: {node: '>=8'}
+
+  resolve-from@4.0.0:
+    resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+    engines: {node: '>=4'}
+
+  resolve-from@5.0.0:
+    resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
+    engines: {node: '>=8'}
+
+  resolve.exports@1.1.1:
+    resolution: {integrity: sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==}
+    engines: {node: '>=10'}
+
+  resolve@1.22.11:
+    resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
+    engines: {node: '>= 0.4'}
+    hasBin: true
+
+  resolve@2.0.0-next.5:
+    resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
+    hasBin: true
+
+  reusify@1.1.0:
+    resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
+    engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+
+  rimraf@3.0.2:
+    resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+    deprecated: Rimraf versions prior to v4 are no longer supported
+    hasBin: true
+
+  run-parallel@1.2.0:
+    resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+
+  safe-array-concat@1.1.3:
+    resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
+    engines: {node: '>=0.4'}
+
+  safe-push-apply@1.0.0:
+    resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
+    engines: {node: '>= 0.4'}
+
+  safe-regex-test@1.1.0:
+    resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
+    engines: {node: '>= 0.4'}
+
+  safer-buffer@2.1.2:
+    resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+  saxes@5.0.1:
+    resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
+    engines: {node: '>=10'}
+
+  scheduler@0.23.2:
+    resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+  semver@6.3.1:
+    resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+    hasBin: true
+
+  semver@7.7.4:
+    resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
+    engines: {node: '>=10'}
+    hasBin: true
+
+  set-function-length@1.2.2:
+    resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
+    engines: {node: '>= 0.4'}
+
+  set-function-name@2.0.2:
+    resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
+    engines: {node: '>= 0.4'}
+
+  set-proto@1.0.0:
+    resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
+    engines: {node: '>= 0.4'}
+
+  shebang-command@2.0.0:
+    resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+    engines: {node: '>=8'}
+
+  shebang-regex@3.0.0:
+    resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+    engines: {node: '>=8'}
+
+  side-channel-list@1.0.0:
+    resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
+    engines: {node: '>= 0.4'}
+
+  side-channel-map@1.0.1:
+    resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
+    engines: {node: '>= 0.4'}
+
+  side-channel-weakmap@1.0.2:
+    resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
+    engines: {node: '>= 0.4'}
+
+  side-channel@1.1.0:
+    resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
+    engines: {node: '>= 0.4'}
+
+  signal-exit@3.0.7:
+    resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+  sisteransi@1.0.5:
+    resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+  slash@3.0.0:
+    resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+    engines: {node: '>=8'}
+
+  source-map-support@0.5.21:
+    resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
+
+  source-map@0.6.1:
+    resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
+    engines: {node: '>=0.10.0'}
+
+  source-map@0.7.6:
+    resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
+    engines: {node: '>= 12'}
+
+  sprintf-js@1.0.3:
+    resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+
+  stack-utils@2.0.6:
+    resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
+    engines: {node: '>=10'}
+
+  stop-iteration-iterator@1.1.0:
+    resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
+    engines: {node: '>= 0.4'}
+
+  string-length@4.0.2:
+    resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
+    engines: {node: '>=10'}
+
+  string-width@4.2.3:
+    resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+    engines: {node: '>=8'}
+
+  string.prototype.matchall@4.0.12:
+    resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
+    engines: {node: '>= 0.4'}
+
+  string.prototype.repeat@1.0.0:
+    resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
+
+  string.prototype.trim@1.2.10:
+    resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
+    engines: {node: '>= 0.4'}
+
+  string.prototype.trimend@1.0.9:
+    resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
+    engines: {node: '>= 0.4'}
+
+  string.prototype.trimstart@1.0.8:
+    resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
+    engines: {node: '>= 0.4'}
+
+  strip-ansi@6.0.1:
+    resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+    engines: {node: '>=8'}
+
+  strip-bom@4.0.0:
+    resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+    engines: {node: '>=8'}
+
+  strip-final-newline@2.0.0:
+    resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+    engines: {node: '>=6'}
+
+  strip-json-comments@3.1.1:
+    resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+    engines: {node: '>=8'}
+
+  stripe@13.11.0:
+    resolution: {integrity: sha512-yPxVJxUzP1QHhHeFnYjJl48QwDS1+5befcL7ju7+t+i88D5r0rbsL+GkCCS6zgcU+TiV5bF9eMGcKyJfLf8BZQ==}
+    engines: {node: '>=12.*'}
+
+  supports-color@7.2.0:
+    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+    engines: {node: '>=8'}
+
+  supports-color@8.1.1:
+    resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+    engines: {node: '>=10'}
+
+  supports-hyperlinks@2.3.0:
+    resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==}
+    engines: {node: '>=8'}
+
+  supports-preserve-symlinks-flag@1.0.0:
+    resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+    engines: {node: '>= 0.4'}
+
+  symbol-tree@3.2.4:
+    resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
+  terminal-link@2.1.1:
+    resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==}
+    engines: {node: '>=8'}
+
+  test-exclude@6.0.0:
+    resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+    engines: {node: '>=8'}
+
+  text-table@0.2.0:
+    resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+
+  throat@6.0.2:
+    resolution: {integrity: sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==}
+
+  tmpl@1.0.5:
+    resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
+
+  to-regex-range@5.0.1:
+    resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+    engines: {node: '>=8.0'}
+
+  tough-cookie@4.1.4:
+    resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==}
+    engines: {node: '>=6'}
+
+  tr46@2.1.0:
+    resolution: {integrity: sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==}
+    engines: {node: '>=8'}
+
+  ts-jest@27.1.5:
+    resolution: {integrity: sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==}
+    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+    hasBin: true
+    peerDependencies:
+      '@babel/core': '>=7.0.0-beta.0 <8'
+      '@types/jest': ^27.0.0
+      babel-jest: '>=27.0.0 <28'
+      esbuild: '*'
+      jest: ^27.0.0
+      typescript: '>=3.8 <5.0'
+    peerDependenciesMeta:
+      '@babel/core':
+        optional: true
+      '@types/jest':
+        optional: true
+      babel-jest:
+        optional: true
+      esbuild:
+        optional: true
+
+  tslib@1.14.1:
+    resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+
+  tsutils@3.21.0:
+    resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+    engines: {node: '>= 6'}
+    peerDependencies:
+      typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+
+  type-check@0.4.0:
+    resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+    engines: {node: '>= 0.8.0'}
+
+  type-detect@4.0.8:
+    resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==}
+    engines: {node: '>=4'}
+
+  type-fest@0.20.2:
+    resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+    engines: {node: '>=10'}
+
+  type-fest@0.21.3:
+    resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
+    engines: {node: '>=10'}
+
+  typed-array-buffer@1.0.3:
+    resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
+    engines: {node: '>= 0.4'}
+
+  typed-array-byte-length@1.0.3:
+    resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
+    engines: {node: '>= 0.4'}
+
+  typed-array-byte-offset@1.0.4:
+    resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
+    engines: {node: '>= 0.4'}
+
+  typed-array-length@1.0.7:
+    resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
+    engines: {node: '>= 0.4'}
+
+  typedarray-to-buffer@3.1.5:
+    resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
+
+  typescript@4.9.5:
+    resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
+    engines: {node: '>=4.2.0'}
+    hasBin: true
+
+  unbox-primitive@1.1.0:
+    resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
+    engines: {node: '>= 0.4'}
+
+  undici-types@7.16.0:
+    resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+  universalify@0.2.0:
+    resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==}
+    engines: {node: '>= 4.0.0'}
+
+  update-browserslist-db@1.2.3:
+    resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+
+  uri-js@4.4.1:
+    resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+
+  url-parse@1.5.10:
+    resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
+
+  v8-to-istanbul@8.1.1:
+    resolution: {integrity: sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==}
+    engines: {node: '>=10.12.0'}
+
+  w3c-hr-time@1.0.2:
+    resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
+    deprecated: Use your platform's native performance.now() and performance.timeOrigin.
+
+  w3c-xmlserializer@2.0.0:
+    resolution: {integrity: sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==}
+    engines: {node: '>=10'}
+
+  walker@1.0.8:
+    resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==}
+
+  webidl-conversions@5.0.0:
+    resolution: {integrity: sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==}
+    engines: {node: '>=8'}
+
+  webidl-conversions@6.1.0:
+    resolution: {integrity: sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==}
+    engines: {node: '>=10.4'}
+
+  whatwg-encoding@1.0.5:
+    resolution: {integrity: sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==}
+    deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
+
+  whatwg-mimetype@2.3.0:
+    resolution: {integrity: sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==}
+
+  whatwg-url@8.7.0:
+    resolution: {integrity: sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==}
+    engines: {node: '>=10'}
+
+  which-boxed-primitive@1.1.1:
+    resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
+    engines: {node: '>= 0.4'}
+
+  which-builtin-type@1.2.1:
+    resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
+    engines: {node: '>= 0.4'}
+
+  which-collection@1.0.2:
+    resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
+    engines: {node: '>= 0.4'}
+
+  which-typed-array@1.1.20:
+    resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==}
+    engines: {node: '>= 0.4'}
+
+  which@2.0.2:
+    resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+    engines: {node: '>= 8'}
+    hasBin: true
+
+  word-wrap@1.2.5:
+    resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
+    engines: {node: '>=0.10.0'}
+
+  wrap-ansi@7.0.0:
+    resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+    engines: {node: '>=10'}
+
+  wrappy@1.0.2:
+    resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+
+  write-file-atomic@3.0.3:
+    resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
+
+  ws@7.5.10:
+    resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==}
+    engines: {node: '>=8.3.0'}
+    peerDependencies:
+      bufferutil: ^4.0.1
+      utf-8-validate: ^5.0.2
+    peerDependenciesMeta:
+      bufferutil:
+        optional: true
+      utf-8-validate:
+        optional: true
+
+  xml-name-validator@3.0.0:
+    resolution: {integrity: sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==}
+
+  xmlchars@2.2.0:
+    resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
+  y18n@5.0.8:
+    resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+    engines: {node: '>=10'}
+
+  yallist@3.1.1:
+    resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+  yargs-parser@20.2.9:
+    resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==}
+    engines: {node: '>=10'}
+
+  yargs@16.2.0:
+    resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+    engines: {node: '>=10'}
+
+  yocto-queue@0.1.0:
+    resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+    engines: {node: '>=10'}
+
+  zod@4.3.6:
+    resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+
+snapshots:
+
+  '@babel/code-frame@7.29.0':
+    dependencies:
+      '@babel/helper-validator-identifier': 7.28.5
+      js-tokens: 4.0.0
+      picocolors: 1.1.1
+
+  '@babel/compat-data@7.29.0': {}
+
+  '@babel/core@7.29.0':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/generator': 7.29.1
+      '@babel/helper-compilation-targets': 7.28.6
+      '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
+      '@babel/helpers': 7.28.6
+      '@babel/parser': 7.29.0
+      '@babel/template': 7.28.6
+      '@babel/traverse': 7.29.0
+      '@babel/types': 7.29.0
+      '@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.1':
+    dependencies:
+      '@babel/parser': 7.29.0
+      '@babel/types': 7.29.0
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+      jsesc: 3.1.0
+
+  '@babel/helper-compilation-targets@7.28.6':
+    dependencies:
+      '@babel/compat-data': 7.29.0
+      '@babel/helper-validator-option': 7.27.1
+      browserslist: 4.28.1
+      lru-cache: 5.1.1
+      semver: 6.3.1
+
+  '@babel/helper-globals@7.28.0': {}
+
+  '@babel/helper-module-imports@7.28.6':
+    dependencies:
+      '@babel/traverse': 7.29.0
+      '@babel/types': 7.29.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-module-imports': 7.28.6
+      '@babel/helper-validator-identifier': 7.28.5
+      '@babel/traverse': 7.29.0
+    transitivePeerDependencies:
+      - supports-color
+
+  '@babel/helper-plugin-utils@7.28.6': {}
+
+  '@babel/helper-string-parser@7.27.1': {}
+
+  '@babel/helper-validator-identifier@7.28.5': {}
+
+  '@babel/helper-validator-option@7.27.1': {}
+
+  '@babel/helpers@7.28.6':
+    dependencies:
+      '@babel/template': 7.28.6
+      '@babel/types': 7.29.0
+
+  '@babel/parser@7.29.0':
+    dependencies:
+      '@babel/types': 7.29.0
+
+  '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/helper-plugin-utils': 7.28.6
+
+  '@babel/template@7.28.6':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/parser': 7.29.0
+      '@babel/types': 7.29.0
+
+  '@babel/traverse@7.29.0':
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@babel/generator': 7.29.1
+      '@babel/helper-globals': 7.28.0
+      '@babel/parser': 7.29.0
+      '@babel/template': 7.28.6
+      '@babel/types': 7.29.0
+      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
+
+  '@bcoe/v8-coverage@0.2.3': {}
+
+  '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)':
+    dependencies:
+      eslint: 8.57.1
+      eslint-visitor-keys: 3.4.3
+
+  '@eslint-community/regexpp@4.12.2': {}
+
+  '@eslint/eslintrc@2.1.4':
+    dependencies:
+      ajv: 6.12.6
+      debug: 4.4.3
+      espree: 9.6.1
+      globals: 13.24.0
+      ignore: 5.3.2
+      import-fresh: 3.3.1
+      js-yaml: 4.1.1
+      minimatch: 3.1.2
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@eslint/js@8.57.1': {}
+
+  '@humanwhocodes/config-array@0.13.0':
+    dependencies:
+      '@humanwhocodes/object-schema': 2.0.3
+      debug: 4.4.3
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+
+  '@humanwhocodes/module-importer@1.0.1': {}
+
+  '@humanwhocodes/object-schema@2.0.3': {}
+
+  '@istanbuljs/load-nyc-config@1.1.0':
+    dependencies:
+      camelcase: 5.3.1
+      find-up: 4.1.0
+      get-package-type: 0.1.0
+      js-yaml: 3.14.2
+      resolve-from: 5.0.0
+
+  '@istanbuljs/schema@0.1.3': {}
+
+  '@jest/console@27.5.1':
+    dependencies:
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      jest-message-util: 27.5.1
+      jest-util: 27.5.1
+      slash: 3.0.0
+
+  '@jest/core@27.5.1':
+    dependencies:
+      '@jest/console': 27.5.1
+      '@jest/reporters': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/transform': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      ansi-escapes: 4.3.2
+      chalk: 4.1.2
+      emittery: 0.8.1
+      exit: 0.1.2
+      graceful-fs: 4.2.11
+      jest-changed-files: 27.5.1
+      jest-config: 27.5.1
+      jest-haste-map: 27.5.1
+      jest-message-util: 27.5.1
+      jest-regex-util: 27.5.1
+      jest-resolve: 27.5.1
+      jest-resolve-dependencies: 27.5.1
+      jest-runner: 27.5.1
+      jest-runtime: 27.5.1
+      jest-snapshot: 27.5.1
+      jest-util: 27.5.1
+      jest-validate: 27.5.1
+      jest-watcher: 27.5.1
+      micromatch: 4.0.8
+      rimraf: 3.0.2
+      slash: 3.0.0
+      strip-ansi: 6.0.1
+    transitivePeerDependencies:
+      - bufferutil
+      - canvas
+      - supports-color
+      - ts-node
+      - utf-8-validate
+
+  '@jest/environment@27.5.1':
+    dependencies:
+      '@jest/fake-timers': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      jest-mock: 27.5.1
+
+  '@jest/expect-utils@28.1.3':
+    dependencies:
+      jest-get-type: 28.0.2
+
+  '@jest/fake-timers@27.5.1':
+    dependencies:
+      '@jest/types': 27.5.1
+      '@sinonjs/fake-timers': 8.1.0
+      '@types/node': 25.2.1
+      jest-message-util: 27.5.1
+      jest-mock: 27.5.1
+      jest-util: 27.5.1
+
+  '@jest/globals@27.5.1':
+    dependencies:
+      '@jest/environment': 27.5.1
+      '@jest/types': 27.5.1
+      expect: 27.5.1
+
+  '@jest/reporters@27.5.1':
+    dependencies:
+      '@bcoe/v8-coverage': 0.2.3
+      '@jest/console': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/transform': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      collect-v8-coverage: 1.0.3
+      exit: 0.1.2
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      istanbul-lib-coverage: 3.2.2
+      istanbul-lib-instrument: 5.2.1
+      istanbul-lib-report: 3.0.1
+      istanbul-lib-source-maps: 4.0.1
+      istanbul-reports: 3.2.0
+      jest-haste-map: 27.5.1
+      jest-resolve: 27.5.1
+      jest-util: 27.5.1
+      jest-worker: 27.5.1
+      slash: 3.0.0
+      source-map: 0.6.1
+      string-length: 4.0.2
+      terminal-link: 2.1.1
+      v8-to-istanbul: 8.1.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@jest/schemas@28.1.3':
+    dependencies:
+      '@sinclair/typebox': 0.24.51
+
+  '@jest/source-map@27.5.1':
+    dependencies:
+      callsites: 3.1.0
+      graceful-fs: 4.2.11
+      source-map: 0.6.1
+
+  '@jest/test-result@27.5.1':
+    dependencies:
+      '@jest/console': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/istanbul-lib-coverage': 2.0.6
+      collect-v8-coverage: 1.0.3
+
+  '@jest/test-sequencer@27.5.1':
+    dependencies:
+      '@jest/test-result': 27.5.1
+      graceful-fs: 4.2.11
+      jest-haste-map: 27.5.1
+      jest-runtime: 27.5.1
+    transitivePeerDependencies:
+      - supports-color
+
+  '@jest/transform@27.5.1':
+    dependencies:
+      '@babel/core': 7.29.0
+      '@jest/types': 27.5.1
+      babel-plugin-istanbul: 6.1.1
+      chalk: 4.1.2
+      convert-source-map: 1.9.0
+      fast-json-stable-stringify: 2.1.0
+      graceful-fs: 4.2.11
+      jest-haste-map: 27.5.1
+      jest-regex-util: 27.5.1
+      jest-util: 27.5.1
+      micromatch: 4.0.8
+      pirates: 4.0.7
+      slash: 3.0.0
+      source-map: 0.6.1
+      write-file-atomic: 3.0.3
+    transitivePeerDependencies:
+      - supports-color
+
+  '@jest/types@26.6.2':
+    dependencies:
+      '@types/istanbul-lib-coverage': 2.0.6
+      '@types/istanbul-reports': 3.0.4
+      '@types/node': 25.2.1
+      '@types/yargs': 15.0.20
+      chalk: 4.1.2
+
+  '@jest/types@27.5.1':
+    dependencies:
+      '@types/istanbul-lib-coverage': 2.0.6
+      '@types/istanbul-reports': 3.0.4
+      '@types/node': 25.2.1
+      '@types/yargs': 16.0.11
+      chalk: 4.1.2
+
+  '@jest/types@28.1.3':
+    dependencies:
+      '@jest/schemas': 28.1.3
+      '@types/istanbul-lib-coverage': 2.0.6
+      '@types/istanbul-reports': 3.0.4
+      '@types/node': 25.2.1
+      '@types/yargs': 17.0.35
+      chalk: 4.1.2
+
+  '@jridgewell/gen-mapping@0.3.13':
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.5.5
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/remapping@2.3.5':
+    dependencies:
+      '@jridgewell/gen-mapping': 0.3.13
+      '@jridgewell/trace-mapping': 0.3.31
+
+  '@jridgewell/resolve-uri@3.1.2': {}
+
+  '@jridgewell/sourcemap-codec@1.5.5': {}
+
+  '@jridgewell/trace-mapping@0.3.31':
+    dependencies:
+      '@jridgewell/resolve-uri': 3.1.2
+      '@jridgewell/sourcemap-codec': 1.5.5
+
+  '@json-render/core@0.4.0(zod@4.3.6)':
+    dependencies:
+      zod: 4.3.6
+
+  '@json-render/react@0.4.0(react@18.3.1)(zod@4.3.6)':
+    dependencies:
+      '@json-render/core': 0.4.0(zod@4.3.6)
+      react: 18.3.1
+    transitivePeerDependencies:
+      - zod
+
+  '@nodelib/fs.scandir@2.1.5':
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      run-parallel: 1.2.0
+
+  '@nodelib/fs.stat@2.0.5': {}
+
+  '@nodelib/fs.walk@1.2.8':
+    dependencies:
+      '@nodelib/fs.scandir': 2.1.5
+      fastq: 1.20.1
+
+  '@remote-ui/async-subscription@2.1.18':
+    dependencies:
+      '@remote-ui/rpc': 1.4.7
+
+  '@remote-ui/core@2.2.5':
+    dependencies:
+      '@remote-ui/rpc': 1.4.5
+      '@remote-ui/types': 1.1.3
+
+  '@remote-ui/react@5.0.5(react-reconciler@0.29.0(react@18.3.1))(react@18.3.1)':
+    dependencies:
+      '@remote-ui/async-subscription': 2.1.18
+      '@remote-ui/core': 2.2.5
+      '@remote-ui/rpc': 1.4.5
+      '@types/react': 17.0.91
+      '@types/react-reconciler': 0.28.9(@types/react@17.0.91)
+      react: 18.3.1
+    optionalDependencies:
+      react-reconciler: 0.29.0(react@18.3.1)
+
+  '@remote-ui/rpc@1.4.5': {}
+
+  '@remote-ui/rpc@1.4.7': {}
+
+  '@remote-ui/testing@1.4.3':
+    dependencies:
+      '@remote-ui/core': 2.2.5
+      jest-matcher-utils: 26.6.2
+
+  '@remote-ui/types@1.1.3': {}
+
+  '@sinclair/typebox@0.24.51': {}
+
+  '@sinonjs/commons@1.8.6':
+    dependencies:
+      type-detect: 4.0.8
+
+  '@sinonjs/fake-timers@8.1.0':
+    dependencies:
+      '@sinonjs/commons': 1.8.6
+
+  '@stripe/ui-extension-sdk@9.1.0(stripe@13.11.0)':
+    dependencies:
+      '@remote-ui/core': 2.2.5
+      '@remote-ui/react': 5.0.5(react-reconciler@0.29.0(react@18.3.1))(react@18.3.1)
+      '@remote-ui/rpc': 1.4.5
+      '@remote-ui/testing': 1.4.3
+      '@types/stylis': 4.2.6
+      invariant: 2.2.4
+      react: 18.3.1
+      react-reconciler: 0.29.0(react@18.3.1)
+      stripe: 13.11.0
+
+  '@stripe/ui-extension-tools@0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))':
+    dependencies:
+      '@types/jest': 28.1.8
+      '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
+      '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+      eslint: 8.57.1
+      eslint-plugin-react: 7.37.5(eslint@8.57.1)
+      eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1)
+      jest: 27.5.1
+      jest-transform-stub: 2.0.0
+      ts-jest: 27.1.5(@babel/core@7.29.0)(@types/jest@28.1.8)(babel-jest@27.5.1(@babel/core@7.29.0))(jest@27.5.1)(typescript@4.9.5)
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - '@babel/core'
+      - babel-jest
+      - bufferutil
+      - canvas
+      - esbuild
+      - node-notifier
+      - supports-color
+      - ts-node
+      - utf-8-validate
+
+  '@tootallnate/once@1.1.2': {}
+
+  '@types/babel__core@7.20.5':
+    dependencies:
+      '@babel/parser': 7.29.0
+      '@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.0
+      '@babel/types': 7.29.0
+
+  '@types/babel__traverse@7.28.0':
+    dependencies:
+      '@babel/types': 7.29.0
+
+  '@types/graceful-fs@4.1.9':
+    dependencies:
+      '@types/node': 25.2.1
+
+  '@types/istanbul-lib-coverage@2.0.6': {}
+
+  '@types/istanbul-lib-report@3.0.3':
+    dependencies:
+      '@types/istanbul-lib-coverage': 2.0.6
+
+  '@types/istanbul-reports@3.0.4':
+    dependencies:
+      '@types/istanbul-lib-report': 3.0.3
+
+  '@types/jest@28.1.8':
+    dependencies:
+      expect: 28.1.3
+      pretty-format: 28.1.3
+
+  '@types/json-schema@7.0.15': {}
+
+  '@types/node@25.2.1':
+    dependencies:
+      undici-types: 7.16.0
+
+  '@types/prettier@2.7.3': {}
+
+  '@types/prop-types@15.7.15': {}
+
+  '@types/react-reconciler@0.28.9(@types/react@17.0.91)':
+    dependencies:
+      '@types/react': 17.0.91
+
+  '@types/react@17.0.91':
+    dependencies:
+      '@types/prop-types': 15.7.15
+      '@types/scheduler': 0.16.8
+      csstype: 3.2.3
+
+  '@types/scheduler@0.16.8': {}
+
+  '@types/semver@7.7.1': {}
+
+  '@types/stack-utils@2.0.3': {}
+
+  '@types/stylis@4.2.6': {}
+
+  '@types/yargs-parser@21.0.3': {}
+
+  '@types/yargs@15.0.20':
+    dependencies:
+      '@types/yargs-parser': 21.0.3
+
+  '@types/yargs@16.0.11':
+    dependencies:
+      '@types/yargs-parser': 21.0.3
+
+  '@types/yargs@17.0.35':
+    dependencies:
+      '@types/yargs-parser': 21.0.3
+
+  '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
+    dependencies:
+      '@eslint-community/regexpp': 4.12.2
+      '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+      '@typescript-eslint/scope-manager': 5.62.0
+      '@typescript-eslint/type-utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+      '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+      debug: 4.4.3
+      eslint: 8.57.1
+      graphemer: 1.4.0
+      ignore: 5.3.2
+      natural-compare-lite: 1.4.0
+      semver: 7.7.4
+      tsutils: 3.21.0(typescript@4.9.5)
+    optionalDependencies:
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
+    dependencies:
+      '@typescript-eslint/scope-manager': 5.62.0
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
+      debug: 4.4.3
+      eslint: 8.57.1
+    optionalDependencies:
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/scope-manager@5.62.0':
+    dependencies:
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/visitor-keys': 5.62.0
+
+  '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
+    dependencies:
+      '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
+      '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
+      debug: 4.4.3
+      eslint: 8.57.1
+      tsutils: 3.21.0(typescript@4.9.5)
+    optionalDependencies:
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/types@5.62.0': {}
+
+  '@typescript-eslint/typescript-estree@5.62.0(typescript@4.9.5)':
+    dependencies:
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/visitor-keys': 5.62.0
+      debug: 4.4.3
+      globby: 11.1.0
+      is-glob: 4.0.3
+      semver: 7.7.4
+      tsutils: 3.21.0(typescript@4.9.5)
+    optionalDependencies:
+      typescript: 4.9.5
+    transitivePeerDependencies:
+      - supports-color
+
+  '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@4.9.5)':
+    dependencies:
+      '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1)
+      '@types/json-schema': 7.0.15
+      '@types/semver': 7.7.1
+      '@typescript-eslint/scope-manager': 5.62.0
+      '@typescript-eslint/types': 5.62.0
+      '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
+      eslint: 8.57.1
+      eslint-scope: 5.1.1
+      semver: 7.7.4
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+
+  '@typescript-eslint/visitor-keys@5.62.0':
+    dependencies:
+      '@typescript-eslint/types': 5.62.0
+      eslint-visitor-keys: 3.4.3
+
+  '@ungap/structured-clone@1.3.0': {}
+
+  abab@2.0.6: {}
+
+  acorn-globals@6.0.0:
+    dependencies:
+      acorn: 7.4.1
+      acorn-walk: 7.2.0
+
+  acorn-jsx@5.3.2(acorn@8.15.0):
+    dependencies:
+      acorn: 8.15.0
+
+  acorn-walk@7.2.0: {}
+
+  acorn@7.4.1: {}
+
+  acorn@8.15.0: {}
+
+  agent-base@6.0.2:
+    dependencies:
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  ajv@6.12.6:
+    dependencies:
+      fast-deep-equal: 3.1.3
+      fast-json-stable-stringify: 2.1.0
+      json-schema-traverse: 0.4.1
+      uri-js: 4.4.1
+
+  ansi-escapes@4.3.2:
+    dependencies:
+      type-fest: 0.21.3
+
+  ansi-regex@5.0.1: {}
+
+  ansi-styles@4.3.0:
+    dependencies:
+      color-convert: 2.0.1
+
+  ansi-styles@5.2.0: {}
+
+  anymatch@3.1.3:
+    dependencies:
+      normalize-path: 3.0.0
+      picomatch: 2.3.1
+
+  argparse@1.0.10:
+    dependencies:
+      sprintf-js: 1.0.3
+
+  argparse@2.0.1: {}
+
+  array-buffer-byte-length@1.0.2:
+    dependencies:
+      call-bound: 1.0.4
+      is-array-buffer: 3.0.5
+
+  array-includes@3.1.9:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      is-string: 1.1.1
+      math-intrinsics: 1.1.0
+
+  array-union@2.1.0: {}
+
+  array.prototype.findlast@1.2.5:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      es-shim-unscopables: 1.1.0
+
+  array.prototype.flat@1.3.3:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-shim-unscopables: 1.1.0
+
+  array.prototype.flatmap@1.3.3:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-shim-unscopables: 1.1.0
+
+  array.prototype.tosorted@1.1.4:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-errors: 1.3.0
+      es-shim-unscopables: 1.1.0
+
+  arraybuffer.prototype.slice@1.0.4:
+    dependencies:
+      array-buffer-byte-length: 1.0.2
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      is-array-buffer: 3.0.5
+
+  async-function@1.0.0: {}
+
+  asynckit@0.4.0: {}
+
+  available-typed-arrays@1.0.7:
+    dependencies:
+      possible-typed-array-names: 1.1.0
+
+  babel-jest@27.5.1(@babel/core@7.29.0):
+    dependencies:
+      '@babel/core': 7.29.0
+      '@jest/transform': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/babel__core': 7.20.5
+      babel-plugin-istanbul: 6.1.1
+      babel-preset-jest: 27.5.1(@babel/core@7.29.0)
+      chalk: 4.1.2
+      graceful-fs: 4.2.11
+      slash: 3.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  babel-plugin-istanbul@6.1.1:
+    dependencies:
+      '@babel/helper-plugin-utils': 7.28.6
+      '@istanbuljs/load-nyc-config': 1.1.0
+      '@istanbuljs/schema': 0.1.3
+      istanbul-lib-instrument: 5.2.1
+      test-exclude: 6.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  babel-plugin-jest-hoist@27.5.1:
+    dependencies:
+      '@babel/template': 7.28.6
+      '@babel/types': 7.29.0
+      '@types/babel__core': 7.20.5
+      '@types/babel__traverse': 7.28.0
+
+  babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0):
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0)
+      '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0)
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0)
+      '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0)
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0)
+
+  babel-preset-jest@27.5.1(@babel/core@7.29.0):
+    dependencies:
+      '@babel/core': 7.29.0
+      babel-plugin-jest-hoist: 27.5.1
+      babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
+
+  balanced-match@1.0.2: {}
+
+  baseline-browser-mapping@2.9.19: {}
+
+  brace-expansion@1.1.12:
+    dependencies:
+      balanced-match: 1.0.2
+      concat-map: 0.0.1
+
+  braces@3.0.3:
+    dependencies:
+      fill-range: 7.1.1
+
+  browser-process-hrtime@1.0.0: {}
+
+  browserslist@4.28.1:
+    dependencies:
+      baseline-browser-mapping: 2.9.19
+      caniuse-lite: 1.0.30001768
+      electron-to-chromium: 1.5.286
+      node-releases: 2.0.27
+      update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+  bs-logger@0.2.6:
+    dependencies:
+      fast-json-stable-stringify: 2.1.0
+
+  bser@2.1.1:
+    dependencies:
+      node-int64: 0.4.0
+
+  buffer-from@1.1.2: {}
+
+  call-bind-apply-helpers@1.0.2:
+    dependencies:
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+
+  call-bind@1.0.8:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
+      get-intrinsic: 1.3.0
+      set-function-length: 1.2.2
+
+  call-bound@1.0.4:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      get-intrinsic: 1.3.0
+
+  callsites@3.1.0: {}
+
+  camelcase@5.3.1: {}
+
+  camelcase@6.3.0: {}
+
+  caniuse-lite@1.0.30001768: {}
+
+  chalk@4.1.2:
+    dependencies:
+      ansi-styles: 4.3.0
+      supports-color: 7.2.0
+
+  char-regex@1.0.2: {}
+
+  ci-info@3.9.0: {}
+
+  cjs-module-lexer@1.4.3: {}
+
+  cliui@7.0.4:
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 7.0.0
+
+  co@4.6.0: {}
+
+  collect-v8-coverage@1.0.3: {}
+
+  color-convert@2.0.1:
+    dependencies:
+      color-name: 1.1.4
+
+  color-name@1.1.4: {}
+
+  combined-stream@1.0.8:
+    dependencies:
+      delayed-stream: 1.0.0
+
+  concat-map@0.0.1: {}
+
+  convert-source-map@1.9.0: {}
+
+  convert-source-map@2.0.0: {}
+
+  cross-spawn@7.0.6:
+    dependencies:
+      path-key: 3.1.1
+      shebang-command: 2.0.0
+      which: 2.0.2
+
+  cssom@0.3.8: {}
+
+  cssom@0.4.4: {}
+
+  cssstyle@2.3.0:
+    dependencies:
+      cssom: 0.3.8
+
+  csstype@3.2.3: {}
+
+  data-urls@2.0.0:
+    dependencies:
+      abab: 2.0.6
+      whatwg-mimetype: 2.3.0
+      whatwg-url: 8.7.0
+
+  data-view-buffer@1.0.2:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      is-data-view: 1.0.2
+
+  data-view-byte-length@1.0.2:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      is-data-view: 1.0.2
+
+  data-view-byte-offset@1.0.1:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      is-data-view: 1.0.2
+
+  debug@4.4.3:
+    dependencies:
+      ms: 2.1.3
+
+  decimal.js@10.6.0: {}
+
+  dedent@0.7.0: {}
+
+  deep-is@0.1.4: {}
+
+  deepmerge@4.3.1: {}
+
+  define-data-property@1.1.4:
+    dependencies:
+      es-define-property: 1.0.1
+      es-errors: 1.3.0
+      gopd: 1.2.0
+
+  define-properties@1.2.1:
+    dependencies:
+      define-data-property: 1.1.4
+      has-property-descriptors: 1.0.2
+      object-keys: 1.1.1
+
+  delayed-stream@1.0.0: {}
+
+  detect-newline@3.1.0: {}
+
+  diff-sequences@26.6.2: {}
+
+  diff-sequences@27.5.1: {}
+
+  diff-sequences@28.1.1: {}
+
+  dir-glob@3.0.1:
+    dependencies:
+      path-type: 4.0.0
+
+  doctrine@2.1.0:
+    dependencies:
+      esutils: 2.0.3
+
+  doctrine@3.0.0:
+    dependencies:
+      esutils: 2.0.3
+
+  domexception@2.0.1:
+    dependencies:
+      webidl-conversions: 5.0.0
+
+  dunder-proto@1.0.1:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-errors: 1.3.0
+      gopd: 1.2.0
+
+  electron-to-chromium@1.5.286: {}
+
+  emittery@0.8.1: {}
+
+  emoji-regex@8.0.0: {}
+
+  error-ex@1.3.4:
+    dependencies:
+      is-arrayish: 0.2.1
+
+  es-abstract@1.24.1:
+    dependencies:
+      array-buffer-byte-length: 1.0.2
+      arraybuffer.prototype.slice: 1.0.4
+      available-typed-arrays: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      data-view-buffer: 1.0.2
+      data-view-byte-length: 1.0.2
+      data-view-byte-offset: 1.0.1
+      es-define-property: 1.0.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      es-set-tostringtag: 2.1.0
+      es-to-primitive: 1.3.0
+      function.prototype.name: 1.1.8
+      get-intrinsic: 1.3.0
+      get-proto: 1.0.1
+      get-symbol-description: 1.1.0
+      globalthis: 1.0.4
+      gopd: 1.2.0
+      has-property-descriptors: 1.0.2
+      has-proto: 1.2.0
+      has-symbols: 1.1.0
+      hasown: 2.0.2
+      internal-slot: 1.1.0
+      is-array-buffer: 3.0.5
+      is-callable: 1.2.7
+      is-data-view: 1.0.2
+      is-negative-zero: 2.0.3
+      is-regex: 1.2.1
+      is-set: 2.0.3
+      is-shared-array-buffer: 1.0.4
+      is-string: 1.1.1
+      is-typed-array: 1.1.15
+      is-weakref: 1.1.1
+      math-intrinsics: 1.1.0
+      object-inspect: 1.13.4
+      object-keys: 1.1.1
+      object.assign: 4.1.7
+      own-keys: 1.0.1
+      regexp.prototype.flags: 1.5.4
+      safe-array-concat: 1.1.3
+      safe-push-apply: 1.0.0
+      safe-regex-test: 1.1.0
+      set-proto: 1.0.0
+      stop-iteration-iterator: 1.1.0
+      string.prototype.trim: 1.2.10
+      string.prototype.trimend: 1.0.9
+      string.prototype.trimstart: 1.0.8
+      typed-array-buffer: 1.0.3
+      typed-array-byte-length: 1.0.3
+      typed-array-byte-offset: 1.0.4
+      typed-array-length: 1.0.7
+      unbox-primitive: 1.1.0
+      which-typed-array: 1.1.20
+
+  es-define-property@1.0.1: {}
+
+  es-errors@1.3.0: {}
+
+  es-iterator-helpers@1.2.2:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-errors: 1.3.0
+      es-set-tostringtag: 2.1.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.3.0
+      globalthis: 1.0.4
+      gopd: 1.2.0
+      has-property-descriptors: 1.0.2
+      has-proto: 1.2.0
+      has-symbols: 1.1.0
+      internal-slot: 1.1.0
+      iterator.prototype: 1.1.5
+      safe-array-concat: 1.1.3
+
+  es-object-atoms@1.1.1:
+    dependencies:
+      es-errors: 1.3.0
+
+  es-set-tostringtag@2.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      has-tostringtag: 1.0.2
+      hasown: 2.0.2
+
+  es-shim-unscopables@1.1.0:
+    dependencies:
+      hasown: 2.0.2
+
+  es-to-primitive@1.3.0:
+    dependencies:
+      is-callable: 1.2.7
+      is-date-object: 1.1.0
+      is-symbol: 1.1.1
+
+  escalade@3.2.0: {}
+
+  escape-string-regexp@2.0.0: {}
+
+  escape-string-regexp@4.0.0: {}
+
+  escodegen@2.1.0:
+    dependencies:
+      esprima: 4.0.1
+      estraverse: 5.3.0
+      esutils: 2.0.3
+    optionalDependencies:
+      source-map: 0.6.1
+
+  eslint-plugin-react-hooks@4.6.2(eslint@8.57.1):
+    dependencies:
+      eslint: 8.57.1
+
+  eslint-plugin-react@7.37.5(eslint@8.57.1):
+    dependencies:
+      array-includes: 3.1.9
+      array.prototype.findlast: 1.2.5
+      array.prototype.flatmap: 1.3.3
+      array.prototype.tosorted: 1.1.4
+      doctrine: 2.1.0
+      es-iterator-helpers: 1.2.2
+      eslint: 8.57.1
+      estraverse: 5.3.0
+      hasown: 2.0.2
+      jsx-ast-utils: 3.3.5
+      minimatch: 3.1.2
+      object.entries: 1.1.9
+      object.fromentries: 2.0.8
+      object.values: 1.2.1
+      prop-types: 15.8.1
+      resolve: 2.0.0-next.5
+      semver: 6.3.1
+      string.prototype.matchall: 4.0.12
+      string.prototype.repeat: 1.0.0
+
+  eslint-scope@5.1.1:
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 4.3.0
+
+  eslint-scope@7.2.2:
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+
+  eslint-visitor-keys@3.4.3: {}
+
+  eslint@8.57.1:
+    dependencies:
+      '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1)
+      '@eslint-community/regexpp': 4.12.2
+      '@eslint/eslintrc': 2.1.4
+      '@eslint/js': 8.57.1
+      '@humanwhocodes/config-array': 0.13.0
+      '@humanwhocodes/module-importer': 1.0.1
+      '@nodelib/fs.walk': 1.2.8
+      '@ungap/structured-clone': 1.3.0
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.6
+      debug: 4.4.3
+      doctrine: 3.0.0
+      escape-string-regexp: 4.0.0
+      eslint-scope: 7.2.2
+      eslint-visitor-keys: 3.4.3
+      espree: 9.6.1
+      esquery: 1.7.0
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 6.0.1
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      globals: 13.24.0
+      graphemer: 1.4.0
+      ignore: 5.3.2
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      is-path-inside: 3.0.3
+      js-yaml: 4.1.1
+      json-stable-stringify-without-jsonify: 1.0.1
+      levn: 0.4.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.4
+      strip-ansi: 6.0.1
+      text-table: 0.2.0
+    transitivePeerDependencies:
+      - supports-color
+
+  espree@9.6.1:
+    dependencies:
+      acorn: 8.15.0
+      acorn-jsx: 5.3.2(acorn@8.15.0)
+      eslint-visitor-keys: 3.4.3
+
+  esprima@4.0.1: {}
+
+  esquery@1.7.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  esrecurse@4.3.0:
+    dependencies:
+      estraverse: 5.3.0
+
+  estraverse@4.3.0: {}
+
+  estraverse@5.3.0: {}
+
+  esutils@2.0.3: {}
+
+  execa@5.1.1:
+    dependencies:
+      cross-spawn: 7.0.6
+      get-stream: 6.0.1
+      human-signals: 2.1.0
+      is-stream: 2.0.1
+      merge-stream: 2.0.0
+      npm-run-path: 4.0.1
+      onetime: 5.1.2
+      signal-exit: 3.0.7
+      strip-final-newline: 2.0.0
+
+  exit@0.1.2: {}
+
+  expect@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      jest-get-type: 27.5.1
+      jest-matcher-utils: 27.5.1
+      jest-message-util: 27.5.1
+
+  expect@28.1.3:
+    dependencies:
+      '@jest/expect-utils': 28.1.3
+      jest-get-type: 28.0.2
+      jest-matcher-utils: 28.1.3
+      jest-message-util: 28.1.3
+      jest-util: 28.1.3
+
+  fast-deep-equal@3.1.3: {}
+
+  fast-glob@3.3.3:
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      '@nodelib/fs.walk': 1.2.8
+      glob-parent: 5.1.2
+      merge2: 1.4.1
+      micromatch: 4.0.8
+
+  fast-json-stable-stringify@2.1.0: {}
+
+  fast-levenshtein@2.0.6: {}
+
+  fastq@1.20.1:
+    dependencies:
+      reusify: 1.1.0
+
+  fb-watchman@2.0.2:
+    dependencies:
+      bser: 2.1.1
+
+  file-entry-cache@6.0.1:
+    dependencies:
+      flat-cache: 3.2.0
+
+  fill-range@7.1.1:
+    dependencies:
+      to-regex-range: 5.0.1
+
+  find-up@4.1.0:
+    dependencies:
+      locate-path: 5.0.0
+      path-exists: 4.0.0
+
+  find-up@5.0.0:
+    dependencies:
+      locate-path: 6.0.0
+      path-exists: 4.0.0
+
+  flat-cache@3.2.0:
+    dependencies:
+      flatted: 3.3.3
+      keyv: 4.5.4
+      rimraf: 3.0.2
+
+  flatted@3.3.3: {}
+
+  for-each@0.3.5:
+    dependencies:
+      is-callable: 1.2.7
+
+  form-data@3.0.4:
+    dependencies:
+      asynckit: 0.4.0
+      combined-stream: 1.0.8
+      es-set-tostringtag: 2.1.0
+      hasown: 2.0.2
+      mime-types: 2.1.35
+
+  fs.realpath@1.0.0: {}
+
+  fsevents@2.3.3:
+    optional: true
+
+  function-bind@1.1.2: {}
+
+  function.prototype.name@1.1.8:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      functions-have-names: 1.2.3
+      hasown: 2.0.2
+      is-callable: 1.2.7
+
+  functions-have-names@1.2.3: {}
+
+  generator-function@2.0.1: {}
+
+  gensync@1.0.0-beta.2: {}
+
+  get-caller-file@2.0.5: {}
+
+  get-intrinsic@1.3.0:
+    dependencies:
+      call-bind-apply-helpers: 1.0.2
+      es-define-property: 1.0.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      function-bind: 1.1.2
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      has-symbols: 1.1.0
+      hasown: 2.0.2
+      math-intrinsics: 1.1.0
+
+  get-package-type@0.1.0: {}
+
+  get-proto@1.0.1:
+    dependencies:
+      dunder-proto: 1.0.1
+      es-object-atoms: 1.1.1
+
+  get-stream@6.0.1: {}
+
+  get-symbol-description@1.1.0:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+
+  glob-parent@5.1.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob-parent@6.0.2:
+    dependencies:
+      is-glob: 4.0.3
+
+  glob@7.2.3:
+    dependencies:
+      fs.realpath: 1.0.0
+      inflight: 1.0.6
+      inherits: 2.0.4
+      minimatch: 3.1.2
+      once: 1.4.0
+      path-is-absolute: 1.0.1
+
+  globals@13.24.0:
+    dependencies:
+      type-fest: 0.20.2
+
+  globalthis@1.0.4:
+    dependencies:
+      define-properties: 1.2.1
+      gopd: 1.2.0
+
+  globby@11.1.0:
+    dependencies:
+      array-union: 2.1.0
+      dir-glob: 3.0.1
+      fast-glob: 3.3.3
+      ignore: 5.3.2
+      merge2: 1.4.1
+      slash: 3.0.0
+
+  gopd@1.2.0: {}
+
+  graceful-fs@4.2.11: {}
+
+  graphemer@1.4.0: {}
+
+  has-bigints@1.1.0: {}
+
+  has-flag@4.0.0: {}
+
+  has-property-descriptors@1.0.2:
+    dependencies:
+      es-define-property: 1.0.1
+
+  has-proto@1.2.0:
+    dependencies:
+      dunder-proto: 1.0.1
+
+  has-symbols@1.1.0: {}
+
+  has-tostringtag@1.0.2:
+    dependencies:
+      has-symbols: 1.1.0
+
+  hasown@2.0.2:
+    dependencies:
+      function-bind: 1.1.2
+
+  html-encoding-sniffer@2.0.1:
+    dependencies:
+      whatwg-encoding: 1.0.5
+
+  html-escaper@2.0.2: {}
+
+  http-proxy-agent@4.0.1:
+    dependencies:
+      '@tootallnate/once': 1.1.2
+      agent-base: 6.0.2
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  https-proxy-agent@5.0.1:
+    dependencies:
+      agent-base: 6.0.2
+      debug: 4.4.3
+    transitivePeerDependencies:
+      - supports-color
+
+  human-signals@2.1.0: {}
+
+  iconv-lite@0.4.24:
+    dependencies:
+      safer-buffer: 2.1.2
+
+  ignore@5.3.2: {}
+
+  import-fresh@3.3.1:
+    dependencies:
+      parent-module: 1.0.1
+      resolve-from: 4.0.0
+
+  import-local@3.2.0:
+    dependencies:
+      pkg-dir: 4.2.0
+      resolve-cwd: 3.0.0
+
+  imurmurhash@0.1.4: {}
+
+  inflight@1.0.6:
+    dependencies:
+      once: 1.4.0
+      wrappy: 1.0.2
+
+  inherits@2.0.4: {}
+
+  internal-slot@1.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      hasown: 2.0.2
+      side-channel: 1.1.0
+
+  invariant@2.2.4:
+    dependencies:
+      loose-envify: 1.4.0
+
+  is-array-buffer@3.0.5:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
+
+  is-arrayish@0.2.1: {}
+
+  is-async-function@2.1.1:
+    dependencies:
+      async-function: 1.0.0
+      call-bound: 1.0.4
+      get-proto: 1.0.1
+      has-tostringtag: 1.0.2
+      safe-regex-test: 1.1.0
+
+  is-bigint@1.1.0:
+    dependencies:
+      has-bigints: 1.1.0
+
+  is-boolean-object@1.2.2:
+    dependencies:
+      call-bound: 1.0.4
+      has-tostringtag: 1.0.2
+
+  is-callable@1.2.7: {}
+
+  is-core-module@2.16.1:
+    dependencies:
+      hasown: 2.0.2
+
+  is-data-view@1.0.2:
+    dependencies:
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
+      is-typed-array: 1.1.15
+
+  is-date-object@1.1.0:
+    dependencies:
+      call-bound: 1.0.4
+      has-tostringtag: 1.0.2
+
+  is-extglob@2.1.1: {}
+
+  is-finalizationregistry@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
+
+  is-fullwidth-code-point@3.0.0: {}
+
+  is-generator-fn@2.1.0: {}
+
+  is-generator-function@1.1.2:
+    dependencies:
+      call-bound: 1.0.4
+      generator-function: 2.0.1
+      get-proto: 1.0.1
+      has-tostringtag: 1.0.2
+      safe-regex-test: 1.1.0
+
+  is-glob@4.0.3:
+    dependencies:
+      is-extglob: 2.1.1
+
+  is-map@2.0.3: {}
+
+  is-negative-zero@2.0.3: {}
+
+  is-number-object@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
+      has-tostringtag: 1.0.2
+
+  is-number@7.0.0: {}
+
+  is-path-inside@3.0.3: {}
+
+  is-potential-custom-element-name@1.0.1: {}
+
+  is-regex@1.2.1:
+    dependencies:
+      call-bound: 1.0.4
+      gopd: 1.2.0
+      has-tostringtag: 1.0.2
+      hasown: 2.0.2
+
+  is-set@2.0.3: {}
+
+  is-shared-array-buffer@1.0.4:
+    dependencies:
+      call-bound: 1.0.4
+
+  is-stream@2.0.1: {}
+
+  is-string@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
+      has-tostringtag: 1.0.2
+
+  is-symbol@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
+      has-symbols: 1.1.0
+      safe-regex-test: 1.1.0
+
+  is-typed-array@1.1.15:
+    dependencies:
+      which-typed-array: 1.1.20
+
+  is-typedarray@1.0.0: {}
+
+  is-weakmap@2.0.2: {}
+
+  is-weakref@1.1.1:
+    dependencies:
+      call-bound: 1.0.4
+
+  is-weakset@2.0.4:
+    dependencies:
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
+
+  isarray@2.0.5: {}
+
+  isexe@2.0.0: {}
+
+  istanbul-lib-coverage@3.2.2: {}
+
+  istanbul-lib-instrument@5.2.1:
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/parser': 7.29.0
+      '@istanbuljs/schema': 0.1.3
+      istanbul-lib-coverage: 3.2.2
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+
+  istanbul-lib-report@3.0.1:
+    dependencies:
+      istanbul-lib-coverage: 3.2.2
+      make-dir: 4.0.0
+      supports-color: 7.2.0
+
+  istanbul-lib-source-maps@4.0.1:
+    dependencies:
+      debug: 4.4.3
+      istanbul-lib-coverage: 3.2.2
+      source-map: 0.6.1
+    transitivePeerDependencies:
+      - supports-color
+
+  istanbul-reports@3.2.0:
+    dependencies:
+      html-escaper: 2.0.2
+      istanbul-lib-report: 3.0.1
+
+  iterator.prototype@1.1.5:
+    dependencies:
+      define-data-property: 1.1.4
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      get-proto: 1.0.1
+      has-symbols: 1.1.0
+      set-function-name: 2.0.2
+
+  jest-changed-files@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      execa: 5.1.1
+      throat: 6.0.2
+
+  jest-circus@27.5.1:
+    dependencies:
+      '@jest/environment': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      co: 4.6.0
+      dedent: 0.7.0
+      expect: 27.5.1
+      is-generator-fn: 2.1.0
+      jest-each: 27.5.1
+      jest-matcher-utils: 27.5.1
+      jest-message-util: 27.5.1
+      jest-runtime: 27.5.1
+      jest-snapshot: 27.5.1
+      jest-util: 27.5.1
+      pretty-format: 27.5.1
+      slash: 3.0.0
+      stack-utils: 2.0.6
+      throat: 6.0.2
+    transitivePeerDependencies:
+      - supports-color
+
+  jest-cli@27.5.1:
+    dependencies:
+      '@jest/core': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/types': 27.5.1
+      chalk: 4.1.2
+      exit: 0.1.2
+      graceful-fs: 4.2.11
+      import-local: 3.2.0
+      jest-config: 27.5.1
+      jest-util: 27.5.1
+      jest-validate: 27.5.1
+      prompts: 2.4.2
+      yargs: 16.2.0
+    transitivePeerDependencies:
+      - bufferutil
+      - canvas
+      - supports-color
+      - ts-node
+      - utf-8-validate
+
+  jest-config@27.5.1:
+    dependencies:
+      '@babel/core': 7.29.0
+      '@jest/test-sequencer': 27.5.1
+      '@jest/types': 27.5.1
+      babel-jest: 27.5.1(@babel/core@7.29.0)
+      chalk: 4.1.2
+      ci-info: 3.9.0
+      deepmerge: 4.3.1
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      jest-circus: 27.5.1
+      jest-environment-jsdom: 27.5.1
+      jest-environment-node: 27.5.1
+      jest-get-type: 27.5.1
+      jest-jasmine2: 27.5.1
+      jest-regex-util: 27.5.1
+      jest-resolve: 27.5.1
+      jest-runner: 27.5.1
+      jest-util: 27.5.1
+      jest-validate: 27.5.1
+      micromatch: 4.0.8
+      parse-json: 5.2.0
+      pretty-format: 27.5.1
+      slash: 3.0.0
+      strip-json-comments: 3.1.1
+    transitivePeerDependencies:
+      - bufferutil
+      - canvas
+      - supports-color
+      - utf-8-validate
+
+  jest-diff@26.6.2:
+    dependencies:
+      chalk: 4.1.2
+      diff-sequences: 26.6.2
+      jest-get-type: 26.3.0
+      pretty-format: 26.6.2
+
+  jest-diff@27.5.1:
+    dependencies:
+      chalk: 4.1.2
+      diff-sequences: 27.5.1
+      jest-get-type: 27.5.1
+      pretty-format: 27.5.1
+
+  jest-diff@28.1.3:
+    dependencies:
+      chalk: 4.1.2
+      diff-sequences: 28.1.1
+      jest-get-type: 28.0.2
+      pretty-format: 28.1.3
+
+  jest-docblock@27.5.1:
+    dependencies:
+      detect-newline: 3.1.0
+
+  jest-each@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      chalk: 4.1.2
+      jest-get-type: 27.5.1
+      jest-util: 27.5.1
+      pretty-format: 27.5.1
+
+  jest-environment-jsdom@27.5.1:
+    dependencies:
+      '@jest/environment': 27.5.1
+      '@jest/fake-timers': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      jest-mock: 27.5.1
+      jest-util: 27.5.1
+      jsdom: 16.7.0
+    transitivePeerDependencies:
+      - bufferutil
+      - canvas
+      - supports-color
+      - utf-8-validate
+
+  jest-environment-node@27.5.1:
+    dependencies:
+      '@jest/environment': 27.5.1
+      '@jest/fake-timers': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      jest-mock: 27.5.1
+      jest-util: 27.5.1
+
+  jest-get-type@26.3.0: {}
+
+  jest-get-type@27.5.1: {}
+
+  jest-get-type@28.0.2: {}
+
+  jest-haste-map@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      '@types/graceful-fs': 4.1.9
+      '@types/node': 25.2.1
+      anymatch: 3.1.3
+      fb-watchman: 2.0.2
+      graceful-fs: 4.2.11
+      jest-regex-util: 27.5.1
+      jest-serializer: 27.5.1
+      jest-util: 27.5.1
+      jest-worker: 27.5.1
+      micromatch: 4.0.8
+      walker: 1.0.8
+    optionalDependencies:
+      fsevents: 2.3.3
+
+  jest-jasmine2@27.5.1:
+    dependencies:
+      '@jest/environment': 27.5.1
+      '@jest/source-map': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      co: 4.6.0
+      expect: 27.5.1
+      is-generator-fn: 2.1.0
+      jest-each: 27.5.1
+      jest-matcher-utils: 27.5.1
+      jest-message-util: 27.5.1
+      jest-runtime: 27.5.1
+      jest-snapshot: 27.5.1
+      jest-util: 27.5.1
+      pretty-format: 27.5.1
+      throat: 6.0.2
+    transitivePeerDependencies:
+      - supports-color
+
+  jest-leak-detector@27.5.1:
+    dependencies:
+      jest-get-type: 27.5.1
+      pretty-format: 27.5.1
+
+  jest-matcher-utils@26.6.2:
+    dependencies:
+      chalk: 4.1.2
+      jest-diff: 26.6.2
+      jest-get-type: 26.3.0
+      pretty-format: 26.6.2
+
+  jest-matcher-utils@27.5.1:
+    dependencies:
+      chalk: 4.1.2
+      jest-diff: 27.5.1
+      jest-get-type: 27.5.1
+      pretty-format: 27.5.1
+
+  jest-matcher-utils@28.1.3:
+    dependencies:
+      chalk: 4.1.2
+      jest-diff: 28.1.3
+      jest-get-type: 28.0.2
+      pretty-format: 28.1.3
+
+  jest-message-util@27.5.1:
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@jest/types': 27.5.1
+      '@types/stack-utils': 2.0.3
+      chalk: 4.1.2
+      graceful-fs: 4.2.11
+      micromatch: 4.0.8
+      pretty-format: 27.5.1
+      slash: 3.0.0
+      stack-utils: 2.0.6
+
+  jest-message-util@28.1.3:
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      '@jest/types': 28.1.3
+      '@types/stack-utils': 2.0.3
+      chalk: 4.1.2
+      graceful-fs: 4.2.11
+      micromatch: 4.0.8
+      pretty-format: 28.1.3
+      slash: 3.0.0
+      stack-utils: 2.0.6
+
+  jest-mock@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+
+  jest-pnp-resolver@1.2.3(jest-resolve@27.5.1):
+    optionalDependencies:
+      jest-resolve: 27.5.1
+
+  jest-regex-util@27.5.1: {}
+
+  jest-resolve-dependencies@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      jest-regex-util: 27.5.1
+      jest-snapshot: 27.5.1
+    transitivePeerDependencies:
+      - supports-color
+
+  jest-resolve@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      chalk: 4.1.2
+      graceful-fs: 4.2.11
+      jest-haste-map: 27.5.1
+      jest-pnp-resolver: 1.2.3(jest-resolve@27.5.1)
+      jest-util: 27.5.1
+      jest-validate: 27.5.1
+      resolve: 1.22.11
+      resolve.exports: 1.1.1
+      slash: 3.0.0
+
+  jest-runner@27.5.1:
+    dependencies:
+      '@jest/console': 27.5.1
+      '@jest/environment': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/transform': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      emittery: 0.8.1
+      graceful-fs: 4.2.11
+      jest-docblock: 27.5.1
+      jest-environment-jsdom: 27.5.1
+      jest-environment-node: 27.5.1
+      jest-haste-map: 27.5.1
+      jest-leak-detector: 27.5.1
+      jest-message-util: 27.5.1
+      jest-resolve: 27.5.1
+      jest-runtime: 27.5.1
+      jest-util: 27.5.1
+      jest-worker: 27.5.1
+      source-map-support: 0.5.21
+      throat: 6.0.2
+    transitivePeerDependencies:
+      - bufferutil
+      - canvas
+      - supports-color
+      - utf-8-validate
+
+  jest-runtime@27.5.1:
+    dependencies:
+      '@jest/environment': 27.5.1
+      '@jest/fake-timers': 27.5.1
+      '@jest/globals': 27.5.1
+      '@jest/source-map': 27.5.1
+      '@jest/test-result': 27.5.1
+      '@jest/transform': 27.5.1
+      '@jest/types': 27.5.1
+      chalk: 4.1.2
+      cjs-module-lexer: 1.4.3
+      collect-v8-coverage: 1.0.3
+      execa: 5.1.1
+      glob: 7.2.3
+      graceful-fs: 4.2.11
+      jest-haste-map: 27.5.1
+      jest-message-util: 27.5.1
+      jest-mock: 27.5.1
+      jest-regex-util: 27.5.1
+      jest-resolve: 27.5.1
+      jest-snapshot: 27.5.1
+      jest-util: 27.5.1
+      slash: 3.0.0
+      strip-bom: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+
+  jest-serializer@27.5.1:
+    dependencies:
+      '@types/node': 25.2.1
+      graceful-fs: 4.2.11
+
+  jest-snapshot@27.5.1:
+    dependencies:
+      '@babel/core': 7.29.0
+      '@babel/generator': 7.29.1
+      '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0)
+      '@babel/traverse': 7.29.0
+      '@babel/types': 7.29.0
+      '@jest/transform': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/babel__traverse': 7.28.0
+      '@types/prettier': 2.7.3
+      babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
+      chalk: 4.1.2
+      expect: 27.5.1
+      graceful-fs: 4.2.11
+      jest-diff: 27.5.1
+      jest-get-type: 27.5.1
+      jest-haste-map: 27.5.1
+      jest-matcher-utils: 27.5.1
+      jest-message-util: 27.5.1
+      jest-util: 27.5.1
+      natural-compare: 1.4.0
+      pretty-format: 27.5.1
+      semver: 7.7.4
+    transitivePeerDependencies:
+      - supports-color
+
+  jest-transform-stub@2.0.0: {}
+
+  jest-util@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      ci-info: 3.9.0
+      graceful-fs: 4.2.11
+      picomatch: 2.3.1
+
+  jest-util@28.1.3:
+    dependencies:
+      '@jest/types': 28.1.3
+      '@types/node': 25.2.1
+      chalk: 4.1.2
+      ci-info: 3.9.0
+      graceful-fs: 4.2.11
+      picomatch: 2.3.1
+
+  jest-validate@27.5.1:
+    dependencies:
+      '@jest/types': 27.5.1
+      camelcase: 6.3.0
+      chalk: 4.1.2
+      jest-get-type: 27.5.1
+      leven: 3.1.0
+      pretty-format: 27.5.1
+
+  jest-watcher@27.5.1:
+    dependencies:
+      '@jest/test-result': 27.5.1
+      '@jest/types': 27.5.1
+      '@types/node': 25.2.1
+      ansi-escapes: 4.3.2
+      chalk: 4.1.2
+      jest-util: 27.5.1
+      string-length: 4.0.2
+
+  jest-worker@27.5.1:
+    dependencies:
+      '@types/node': 25.2.1
+      merge-stream: 2.0.0
+      supports-color: 8.1.1
+
+  jest@27.5.1:
+    dependencies:
+      '@jest/core': 27.5.1
+      import-local: 3.2.0
+      jest-cli: 27.5.1
+    transitivePeerDependencies:
+      - bufferutil
+      - canvas
+      - supports-color
+      - ts-node
+      - utf-8-validate
+
+  js-tokens@4.0.0: {}
+
+  js-yaml@3.14.2:
+    dependencies:
+      argparse: 1.0.10
+      esprima: 4.0.1
+
+  js-yaml@4.1.1:
+    dependencies:
+      argparse: 2.0.1
+
+  jsdom@16.7.0:
+    dependencies:
+      abab: 2.0.6
+      acorn: 8.15.0
+      acorn-globals: 6.0.0
+      cssom: 0.4.4
+      cssstyle: 2.3.0
+      data-urls: 2.0.0
+      decimal.js: 10.6.0
+      domexception: 2.0.1
+      escodegen: 2.1.0
+      form-data: 3.0.4
+      html-encoding-sniffer: 2.0.1
+      http-proxy-agent: 4.0.1
+      https-proxy-agent: 5.0.1
+      is-potential-custom-element-name: 1.0.1
+      nwsapi: 2.2.23
+      parse5: 6.0.1
+      saxes: 5.0.1
+      symbol-tree: 3.2.4
+      tough-cookie: 4.1.4
+      w3c-hr-time: 1.0.2
+      w3c-xmlserializer: 2.0.0
+      webidl-conversions: 6.1.0
+      whatwg-encoding: 1.0.5
+      whatwg-mimetype: 2.3.0
+      whatwg-url: 8.7.0
+      ws: 7.5.10
+      xml-name-validator: 3.0.0
+    transitivePeerDependencies:
+      - bufferutil
+      - supports-color
+      - utf-8-validate
+
+  jsesc@3.1.0: {}
+
+  json-buffer@3.0.1: {}
+
+  json-parse-even-better-errors@2.3.1: {}
+
+  json-schema-traverse@0.4.1: {}
+
+  json-stable-stringify-without-jsonify@1.0.1: {}
+
+  json5@2.2.3: {}
+
+  jsx-ast-utils@3.3.5:
+    dependencies:
+      array-includes: 3.1.9
+      array.prototype.flat: 1.3.3
+      object.assign: 4.1.7
+      object.values: 1.2.1
+
+  keyv@4.5.4:
+    dependencies:
+      json-buffer: 3.0.1
+
+  kleur@3.0.3: {}
+
+  leven@3.1.0: {}
+
+  levn@0.4.1:
+    dependencies:
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+
+  lines-and-columns@1.2.4: {}
+
+  locate-path@5.0.0:
+    dependencies:
+      p-locate: 4.1.0
+
+  locate-path@6.0.0:
+    dependencies:
+      p-locate: 5.0.0
+
+  lodash.memoize@4.1.2: {}
+
+  lodash.merge@4.6.2: {}
+
+  lodash@4.17.23: {}
+
+  loose-envify@1.4.0:
+    dependencies:
+      js-tokens: 4.0.0
+
+  lru-cache@5.1.1:
+    dependencies:
+      yallist: 3.1.1
+
+  make-dir@4.0.0:
+    dependencies:
+      semver: 7.7.4
+
+  make-error@1.3.6: {}
+
+  makeerror@1.0.12:
+    dependencies:
+      tmpl: 1.0.5
+
+  math-intrinsics@1.1.0: {}
+
+  merge-stream@2.0.0: {}
+
+  merge2@1.4.1: {}
+
+  micromatch@4.0.8:
+    dependencies:
+      braces: 3.0.3
+      picomatch: 2.3.1
+
+  mime-db@1.52.0: {}
+
+  mime-types@2.1.35:
+    dependencies:
+      mime-db: 1.52.0
+
+  mimic-fn@2.1.0: {}
+
+  minimatch@3.1.2:
+    dependencies:
+      brace-expansion: 1.1.12
+
+  ms@2.1.3: {}
+
+  natural-compare-lite@1.4.0: {}
+
+  natural-compare@1.4.0: {}
+
+  node-int64@0.4.0: {}
+
+  node-releases@2.0.27: {}
+
+  normalize-path@3.0.0: {}
+
+  npm-run-path@4.0.1:
+    dependencies:
+      path-key: 3.1.1
+
+  nwsapi@2.2.23: {}
+
+  object-assign@4.1.1: {}
+
+  object-inspect@1.13.4: {}
+
+  object-keys@1.1.1: {}
+
+  object.assign@4.1.7:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-object-atoms: 1.1.1
+      has-symbols: 1.1.0
+      object-keys: 1.1.1
+
+  object.entries@1.1.9:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-object-atoms: 1.1.1
+
+  object.fromentries@2.0.8:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-object-atoms: 1.1.1
+
+  object.values@1.2.1:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-object-atoms: 1.1.1
+
+  once@1.4.0:
+    dependencies:
+      wrappy: 1.0.2
+
+  onetime@5.1.2:
+    dependencies:
+      mimic-fn: 2.1.0
+
+  optionator@0.9.4:
+    dependencies:
+      deep-is: 0.1.4
+      fast-levenshtein: 2.0.6
+      levn: 0.4.1
+      prelude-ls: 1.2.1
+      type-check: 0.4.0
+      word-wrap: 1.2.5
+
+  own-keys@1.0.1:
+    dependencies:
+      get-intrinsic: 1.3.0
+      object-keys: 1.1.1
+      safe-push-apply: 1.0.0
+
+  p-limit@2.3.0:
+    dependencies:
+      p-try: 2.2.0
+
+  p-limit@3.1.0:
+    dependencies:
+      yocto-queue: 0.1.0
+
+  p-locate@4.1.0:
+    dependencies:
+      p-limit: 2.3.0
+
+  p-locate@5.0.0:
+    dependencies:
+      p-limit: 3.1.0
+
+  p-try@2.2.0: {}
+
+  parent-module@1.0.1:
+    dependencies:
+      callsites: 3.1.0
+
+  parse-json@5.2.0:
+    dependencies:
+      '@babel/code-frame': 7.29.0
+      error-ex: 1.3.4
+      json-parse-even-better-errors: 2.3.1
+      lines-and-columns: 1.2.4
+
+  parse5@6.0.1: {}
+
+  path-exists@4.0.0: {}
+
+  path-is-absolute@1.0.1: {}
+
+  path-key@3.1.1: {}
+
+  path-parse@1.0.7: {}
+
+  path-type@4.0.0: {}
+
+  picocolors@1.1.1: {}
+
+  picomatch@2.3.1: {}
+
+  pirates@4.0.7: {}
+
+  pkg-dir@4.2.0:
+    dependencies:
+      find-up: 4.1.0
+
+  possible-typed-array-names@1.1.0: {}
+
+  prelude-ls@1.2.1: {}
+
+  pretty-format@26.6.2:
+    dependencies:
+      '@jest/types': 26.6.2
+      ansi-regex: 5.0.1
+      ansi-styles: 4.3.0
+      react-is: 17.0.2
+
+  pretty-format@27.5.1:
+    dependencies:
+      ansi-regex: 5.0.1
+      ansi-styles: 5.2.0
+      react-is: 17.0.2
+
+  pretty-format@28.1.3:
+    dependencies:
+      '@jest/schemas': 28.1.3
+      ansi-regex: 5.0.1
+      ansi-styles: 5.2.0
+      react-is: 18.3.1
+
+  prompts@2.4.2:
+    dependencies:
+      kleur: 3.0.3
+      sisteransi: 1.0.5
+
+  prop-types@15.8.1:
+    dependencies:
+      loose-envify: 1.4.0
+      object-assign: 4.1.1
+      react-is: 16.13.1
+
+  psl@1.15.0:
+    dependencies:
+      punycode: 2.3.1
+
+  punycode@2.3.1: {}
+
+  qs@6.14.1:
+    dependencies:
+      side-channel: 1.1.0
+
+  querystringify@2.2.0: {}
+
+  queue-microtask@1.2.3: {}
+
+  react-is@16.13.1: {}
+
+  react-is@17.0.2: {}
+
+  react-is@18.3.1: {}
+
+  react-reconciler@0.29.0(react@18.3.1):
+    dependencies:
+      loose-envify: 1.4.0
+      react: 18.3.1
+      scheduler: 0.23.2
+
+  react@18.3.1:
+    dependencies:
+      loose-envify: 1.4.0
+
+  reflect.getprototypeof@1.0.10:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      get-proto: 1.0.1
+      which-builtin-type: 1.2.1
+
+  regexp.prototype.flags@1.5.4:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-errors: 1.3.0
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      set-function-name: 2.0.2
+
+  require-directory@2.1.1: {}
+
+  requires-port@1.0.0: {}
+
+  resolve-cwd@3.0.0:
+    dependencies:
+      resolve-from: 5.0.0
+
+  resolve-from@4.0.0: {}
+
+  resolve-from@5.0.0: {}
+
+  resolve.exports@1.1.1: {}
+
+  resolve@1.22.11:
+    dependencies:
+      is-core-module: 2.16.1
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  resolve@2.0.0-next.5:
+    dependencies:
+      is-core-module: 2.16.1
+      path-parse: 1.0.7
+      supports-preserve-symlinks-flag: 1.0.0
+
+  reusify@1.1.0: {}
+
+  rimraf@3.0.2:
+    dependencies:
+      glob: 7.2.3
+
+  run-parallel@1.2.0:
+    dependencies:
+      queue-microtask: 1.2.3
+
+  safe-array-concat@1.1.3:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      get-intrinsic: 1.3.0
+      has-symbols: 1.1.0
+      isarray: 2.0.5
+
+  safe-push-apply@1.0.0:
+    dependencies:
+      es-errors: 1.3.0
+      isarray: 2.0.5
+
+  safe-regex-test@1.1.0:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      is-regex: 1.2.1
+
+  safer-buffer@2.1.2: {}
+
+  saxes@5.0.1:
+    dependencies:
+      xmlchars: 2.2.0
+
+  scheduler@0.23.2:
+    dependencies:
+      loose-envify: 1.4.0
+
+  semver@6.3.1: {}
+
+  semver@7.7.4: {}
+
+  set-function-length@1.2.2:
+    dependencies:
+      define-data-property: 1.1.4
+      es-errors: 1.3.0
+      function-bind: 1.1.2
+      get-intrinsic: 1.3.0
+      gopd: 1.2.0
+      has-property-descriptors: 1.0.2
+
+  set-function-name@2.0.2:
+    dependencies:
+      define-data-property: 1.1.4
+      es-errors: 1.3.0
+      functions-have-names: 1.2.3
+      has-property-descriptors: 1.0.2
+
+  set-proto@1.0.0:
+    dependencies:
+      dunder-proto: 1.0.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+
+  shebang-command@2.0.0:
+    dependencies:
+      shebang-regex: 3.0.0
+
+  shebang-regex@3.0.0: {}
+
+  side-channel-list@1.0.0:
+    dependencies:
+      es-errors: 1.3.0
+      object-inspect: 1.13.4
+
+  side-channel-map@1.0.1:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      object-inspect: 1.13.4
+
+  side-channel-weakmap@1.0.2:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      get-intrinsic: 1.3.0
+      object-inspect: 1.13.4
+      side-channel-map: 1.0.1
+
+  side-channel@1.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      object-inspect: 1.13.4
+      side-channel-list: 1.0.0
+      side-channel-map: 1.0.1
+      side-channel-weakmap: 1.0.2
+
+  signal-exit@3.0.7: {}
+
+  sisteransi@1.0.5: {}
+
+  slash@3.0.0: {}
+
+  source-map-support@0.5.21:
+    dependencies:
+      buffer-from: 1.1.2
+      source-map: 0.6.1
+
+  source-map@0.6.1: {}
+
+  source-map@0.7.6: {}
+
+  sprintf-js@1.0.3: {}
+
+  stack-utils@2.0.6:
+    dependencies:
+      escape-string-regexp: 2.0.0
+
+  stop-iteration-iterator@1.1.0:
+    dependencies:
+      es-errors: 1.3.0
+      internal-slot: 1.1.0
+
+  string-length@4.0.2:
+    dependencies:
+      char-regex: 1.0.2
+      strip-ansi: 6.0.1
+
+  string-width@4.2.3:
+    dependencies:
+      emoji-regex: 8.0.0
+      is-fullwidth-code-point: 3.0.0
+      strip-ansi: 6.0.1
+
+  string.prototype.matchall@4.0.12:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-errors: 1.3.0
+      es-object-atoms: 1.1.1
+      get-intrinsic: 1.3.0
+      gopd: 1.2.0
+      has-symbols: 1.1.0
+      internal-slot: 1.1.0
+      regexp.prototype.flags: 1.5.4
+      set-function-name: 2.0.2
+      side-channel: 1.1.0
+
+  string.prototype.repeat@1.0.0:
+    dependencies:
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+
+  string.prototype.trim@1.2.10:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-data-property: 1.1.4
+      define-properties: 1.2.1
+      es-abstract: 1.24.1
+      es-object-atoms: 1.1.1
+      has-property-descriptors: 1.0.2
+
+  string.prototype.trimend@1.0.9:
+    dependencies:
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      define-properties: 1.2.1
+      es-object-atoms: 1.1.1
+
+  string.prototype.trimstart@1.0.8:
+    dependencies:
+      call-bind: 1.0.8
+      define-properties: 1.2.1
+      es-object-atoms: 1.1.1
+
+  strip-ansi@6.0.1:
+    dependencies:
+      ansi-regex: 5.0.1
+
+  strip-bom@4.0.0: {}
+
+  strip-final-newline@2.0.0: {}
+
+  strip-json-comments@3.1.1: {}
+
+  stripe@13.11.0:
+    dependencies:
+      '@types/node': 25.2.1
+      qs: 6.14.1
+
+  supports-color@7.2.0:
+    dependencies:
+      has-flag: 4.0.0
+
+  supports-color@8.1.1:
+    dependencies:
+      has-flag: 4.0.0
+
+  supports-hyperlinks@2.3.0:
+    dependencies:
+      has-flag: 4.0.0
+      supports-color: 7.2.0
+
+  supports-preserve-symlinks-flag@1.0.0: {}
+
+  symbol-tree@3.2.4: {}
+
+  terminal-link@2.1.1:
+    dependencies:
+      ansi-escapes: 4.3.2
+      supports-hyperlinks: 2.3.0
+
+  test-exclude@6.0.0:
+    dependencies:
+      '@istanbuljs/schema': 0.1.3
+      glob: 7.2.3
+      minimatch: 3.1.2
+
+  text-table@0.2.0: {}
+
+  throat@6.0.2: {}
+
+  tmpl@1.0.5: {}
+
+  to-regex-range@5.0.1:
+    dependencies:
+      is-number: 7.0.0
+
+  tough-cookie@4.1.4:
+    dependencies:
+      psl: 1.15.0
+      punycode: 2.3.1
+      universalify: 0.2.0
+      url-parse: 1.5.10
+
+  tr46@2.1.0:
+    dependencies:
+      punycode: 2.3.1
+
+  ts-jest@27.1.5(@babel/core@7.29.0)(@types/jest@28.1.8)(babel-jest@27.5.1(@babel/core@7.29.0))(jest@27.5.1)(typescript@4.9.5):
+    dependencies:
+      bs-logger: 0.2.6
+      fast-json-stable-stringify: 2.1.0
+      jest: 27.5.1
+      jest-util: 27.5.1
+      json5: 2.2.3
+      lodash.memoize: 4.1.2
+      make-error: 1.3.6
+      semver: 7.7.4
+      typescript: 4.9.5
+      yargs-parser: 20.2.9
+    optionalDependencies:
+      '@babel/core': 7.29.0
+      '@types/jest': 28.1.8
+      babel-jest: 27.5.1(@babel/core@7.29.0)
+
+  tslib@1.14.1: {}
+
+  tsutils@3.21.0(typescript@4.9.5):
+    dependencies:
+      tslib: 1.14.1
+      typescript: 4.9.5
+
+  type-check@0.4.0:
+    dependencies:
+      prelude-ls: 1.2.1
+
+  type-detect@4.0.8: {}
+
+  type-fest@0.20.2: {}
+
+  type-fest@0.21.3: {}
+
+  typed-array-buffer@1.0.3:
+    dependencies:
+      call-bound: 1.0.4
+      es-errors: 1.3.0
+      is-typed-array: 1.1.15
+
+  typed-array-byte-length@1.0.3:
+    dependencies:
+      call-bind: 1.0.8
+      for-each: 0.3.5
+      gopd: 1.2.0
+      has-proto: 1.2.0
+      is-typed-array: 1.1.15
+
+  typed-array-byte-offset@1.0.4:
+    dependencies:
+      available-typed-arrays: 1.0.7
+      call-bind: 1.0.8
+      for-each: 0.3.5
+      gopd: 1.2.0
+      has-proto: 1.2.0
+      is-typed-array: 1.1.15
+      reflect.getprototypeof: 1.0.10
+
+  typed-array-length@1.0.7:
+    dependencies:
+      call-bind: 1.0.8
+      for-each: 0.3.5
+      gopd: 1.2.0
+      is-typed-array: 1.1.15
+      possible-typed-array-names: 1.1.0
+      reflect.getprototypeof: 1.0.10
+
+  typedarray-to-buffer@3.1.5:
+    dependencies:
+      is-typedarray: 1.0.0
+
+  typescript@4.9.5: {}
+
+  unbox-primitive@1.1.0:
+    dependencies:
+      call-bound: 1.0.4
+      has-bigints: 1.1.0
+      has-symbols: 1.1.0
+      which-boxed-primitive: 1.1.1
+
+  undici-types@7.16.0: {}
+
+  universalify@0.2.0: {}
+
+  update-browserslist-db@1.2.3(browserslist@4.28.1):
+    dependencies:
+      browserslist: 4.28.1
+      escalade: 3.2.0
+      picocolors: 1.1.1
+
+  uri-js@4.4.1:
+    dependencies:
+      punycode: 2.3.1
+
+  url-parse@1.5.10:
+    dependencies:
+      querystringify: 2.2.0
+      requires-port: 1.0.0
+
+  v8-to-istanbul@8.1.1:
+    dependencies:
+      '@types/istanbul-lib-coverage': 2.0.6
+      convert-source-map: 1.9.0
+      source-map: 0.7.6
+
+  w3c-hr-time@1.0.2:
+    dependencies:
+      browser-process-hrtime: 1.0.0
+
+  w3c-xmlserializer@2.0.0:
+    dependencies:
+      xml-name-validator: 3.0.0
+
+  walker@1.0.8:
+    dependencies:
+      makeerror: 1.0.12
+
+  webidl-conversions@5.0.0: {}
+
+  webidl-conversions@6.1.0: {}
+
+  whatwg-encoding@1.0.5:
+    dependencies:
+      iconv-lite: 0.4.24
+
+  whatwg-mimetype@2.3.0: {}
+
+  whatwg-url@8.7.0:
+    dependencies:
+      lodash: 4.17.23
+      tr46: 2.1.0
+      webidl-conversions: 6.1.0
+
+  which-boxed-primitive@1.1.1:
+    dependencies:
+      is-bigint: 1.1.0
+      is-boolean-object: 1.2.2
+      is-number-object: 1.1.1
+      is-string: 1.1.1
+      is-symbol: 1.1.1
+
+  which-builtin-type@1.2.1:
+    dependencies:
+      call-bound: 1.0.4
+      function.prototype.name: 1.1.8
+      has-tostringtag: 1.0.2
+      is-async-function: 2.1.1
+      is-date-object: 1.1.0
+      is-finalizationregistry: 1.1.1
+      is-generator-function: 1.1.2
+      is-regex: 1.2.1
+      is-weakref: 1.1.1
+      isarray: 2.0.5
+      which-boxed-primitive: 1.1.1
+      which-collection: 1.0.2
+      which-typed-array: 1.1.20
+
+  which-collection@1.0.2:
+    dependencies:
+      is-map: 2.0.3
+      is-set: 2.0.3
+      is-weakmap: 2.0.2
+      is-weakset: 2.0.4
+
+  which-typed-array@1.1.20:
+    dependencies:
+      available-typed-arrays: 1.0.7
+      call-bind: 1.0.8
+      call-bound: 1.0.4
+      for-each: 0.3.5
+      get-proto: 1.0.1
+      gopd: 1.2.0
+      has-tostringtag: 1.0.2
+
+  which@2.0.2:
+    dependencies:
+      isexe: 2.0.0
+
+  word-wrap@1.2.5: {}
+
+  wrap-ansi@7.0.0:
+    dependencies:
+      ansi-styles: 4.3.0
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+
+  wrappy@1.0.2: {}
+
+  write-file-atomic@3.0.3:
+    dependencies:
+      imurmurhash: 0.1.4
+      is-typedarray: 1.0.0
+      signal-exit: 3.0.7
+      typedarray-to-buffer: 3.1.5
+
+  ws@7.5.10: {}
+
+  xml-name-validator@3.0.0: {}
+
+  xmlchars@2.2.0: {}
+
+  y18n@5.0.8: {}
+
+  yallist@3.1.1: {}
+
+  yargs-parser@20.2.9: {}
+
+  yargs@16.2.0:
+    dependencies:
+      cliui: 7.0.4
+      escalade: 3.2.0
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      string-width: 4.2.3
+      y18n: 5.0.8
+      yargs-parser: 20.2.9
+
+  yocto-queue@0.1.0: {}
+
+  zod@4.3.6: {}

+ 42 - 0
examples/stripe-app/fullpage-app/scripts/setup.mjs

@@ -0,0 +1,42 @@
+import { readFileSync, writeFileSync, existsSync } from "fs";
+import { resolve, dirname } from "path";
+import { fileURLToPath } from "url";
+
+const __dirname = dirname(fileURLToPath(import.meta.url));
+const root = resolve(__dirname, "..");
+
+function loadEnv() {
+  const envPath = resolve(root, ".env");
+  if (!existsSync(envPath)) return {};
+  const env = {};
+  for (const line of readFileSync(envPath, "utf-8").split("\n")) {
+    const trimmed = line.trim();
+    if (!trimmed || trimmed.startsWith("#")) continue;
+    const idx = trimmed.indexOf("=");
+    if (idx === -1) continue;
+    const key = trimmed.slice(0, idx).trim();
+    const val = trimmed.slice(idx + 1).trim();
+    env[key] = val;
+  }
+  return env;
+}
+
+const env = loadEnv();
+const templatePath = resolve(root, "stripe-app.template.json");
+const outputPath = resolve(root, "stripe-app.json");
+
+const template = JSON.parse(readFileSync(templatePath, "utf-8"));
+
+if (env.STRIPE_APP_ID) {
+  template.id = env.STRIPE_APP_ID;
+  console.log(`Using app ID from .env: ${env.STRIPE_APP_ID}`);
+} else {
+  console.log(`No STRIPE_APP_ID in .env, using template default: ${template.id}`);
+}
+
+if (env.STRIPE_APP_NAME) {
+  template.name = env.STRIPE_APP_NAME;
+}
+
+writeFileSync(outputPath, JSON.stringify(template, null, 4) + "\n");
+console.log("Generated stripe-app.json");

+ 1 - 0
examples/stripe-app/fullpage-app/src/lib/config.ts

@@ -0,0 +1 @@
+export const API_GENERATE_URL = "http://localhost:3001/api/generate";

+ 1358 - 0
examples/stripe-app/fullpage-app/src/lib/render/catalog.ts

@@ -0,0 +1,1358 @@
+import { defineCatalog } from "@json-render/core";
+import { schema } from "@json-render/react";
+import { z } from "zod";
+
+/**
+ * Stripe UIXT Catalog
+ *
+ * Comprehensive catalog mapping to Stripe UI Extension SDK components.
+ */
+export const stripeCatalog = defineCatalog(schema, {
+  components: {
+    // =========================================================================
+    // Layout Components
+    // =========================================================================
+    Stack: {
+      props: z.object({
+        direction: z.enum(["horizontal", "vertical"]).default("vertical"),
+        gap: z
+          .enum(["xsmall", "small", "medium", "large", "xlarge"])
+          .default("medium"),
+        alignX: z.enum(["start", "center", "end", "stretch"]).nullable(),
+        alignY: z
+          .enum(["top", "center", "baseline", "bottom", "stretch"])
+          .nullable(),
+        distribute: z.enum(["space-between", "packed"]).nullable(),
+      }),
+      description:
+        "Flex layout container for arranging children horizontally or vertically with configurable gap and alignment",
+      example: { direction: "vertical", gap: "medium" },
+    },
+
+    Inline: {
+      props: z.object({
+        gap: z.enum(["xsmall", "small", "medium", "large"]).default("small"),
+      }),
+      description:
+        "Inline layout for text and small elements that wrap naturally",
+    },
+
+    Divider: {
+      props: z.object({}),
+      description:
+        "Visual horizontal divider line to separate content sections",
+    },
+
+    Accordion: {
+      props: z.object({}),
+      slots: ["default"],
+      description:
+        "Collapsible accordion container for expandable content sections",
+    },
+
+    AccordionItem: {
+      props: z.object({
+        title: z.string(),
+        subtitle: z.string().nullable(),
+        defaultOpen: z.boolean().nullable(),
+      }),
+      slots: ["default"],
+      description:
+        "Individual accordion item with title, optional subtitle, and collapsible content",
+    },
+
+    // =========================================================================
+    // Typography Components
+    // =========================================================================
+    Heading: {
+      props: z.object({
+        text: z.string(),
+        size: z
+          .enum(["xsmall", "small", "medium", "large", "xlarge"])
+          .default("large"),
+      }),
+      description: "Display a heading/title text with configurable size",
+      example: { text: "Overview", size: "large" },
+    },
+
+    Text: {
+      props: z.object({
+        content: z.string(),
+        color: z
+          .enum([
+            "primary",
+            "secondary",
+            "disabled",
+            "critical",
+            "success",
+            "warning",
+            "info",
+          ])
+          .default("primary"),
+        size: z.enum(["xsmall", "small", "medium", "large"]).default("medium"),
+        weight: z
+          .enum(["regular", "medium", "semibold", "bold"])
+          .default("regular"),
+      }),
+      description:
+        "Display body text with configurable color, size, and weight",
+      example: { content: "Payment received successfully.", color: "primary" },
+    },
+
+    // =========================================================================
+    // Data Display Components
+    // =========================================================================
+    Metric: {
+      props: z.object({
+        label: z.string(),
+        value: z.string(),
+        change: z.string().nullable(),
+        changeType: z
+          .enum(["positive", "negative", "neutral"])
+          .default("neutral"),
+        format: z.enum(["currency", "number", "percent"]).nullable(),
+      }),
+      description:
+        "Display a key metric with label, value, and optional trend indicator for KPIs",
+      example: {
+        label: "Revenue",
+        value: "$12,450",
+        change: "+8.2%",
+        changeType: "positive",
+        format: "currency",
+      },
+    },
+
+    Badge: {
+      props: z.object({
+        label: z.string(),
+        type: z
+          .enum([
+            "neutral",
+            "urgent",
+            "warning",
+            "negative",
+            "positive",
+            "info",
+          ])
+          .default("neutral"),
+      }),
+      description: "Status badge indicator with configurable color type",
+      example: { label: "Active", type: "positive" },
+    },
+
+    Icon: {
+      props: z.object({
+        name: z.string(),
+        size: z.enum(["xsmall", "small", "medium", "large"]).default("medium"),
+        color: z
+          .enum([
+            "primary",
+            "secondary",
+            "disabled",
+            "critical",
+            "success",
+            "warning",
+            "info",
+          ])
+          .nullable(),
+      }),
+      description:
+        "Display an icon from Stripe's icon set (e.g., 'check', 'warning', 'customer', 'payment')",
+    },
+
+    Img: {
+      props: z.object({
+        src: z.string(),
+        alt: z.string().nullable(),
+        width: z.number().nullable(),
+        height: z.number().nullable(),
+      }),
+      description: "Display an image with configurable dimensions",
+    },
+
+    Spinner: {
+      props: z.object({
+        size: z.enum(["small", "medium", "large"]).default("medium"),
+      }),
+      description: "Loading spinner indicator",
+    },
+
+    // =========================================================================
+    // Feedback Components
+    // =========================================================================
+    Banner: {
+      props: z.object({
+        title: z.string(),
+        description: z.string().nullable(),
+        type: z.enum(["default", "caution", "critical"]).default("default"),
+        dismissible: z.boolean().nullable(),
+      }),
+      description:
+        "Alert banner for important messages with optional dismiss button",
+    },
+
+    // =========================================================================
+    // List Components
+    // =========================================================================
+    List: {
+      props: z.object({}),
+      slots: ["default"],
+      description:
+        "Container for ListItem components with optional action handling",
+    },
+
+    ListItem: {
+      props: z.object({
+        title: z.string(),
+        secondaryTitle: z.string().nullable(),
+        value: z.string().nullable(),
+        id: z.string().nullable(),
+        size: z.enum(["default", "large"]).default("default"),
+      }),
+      description:
+        "List item with title, optional secondary text and value display",
+    },
+
+    PropertyList: {
+      props: z.object({
+        orientation: z.enum(["vertical", "horizontal"]).default("vertical"),
+      }),
+      slots: ["default"],
+      description:
+        "List of label-value pairs for displaying properties/details",
+    },
+
+    PropertyListItem: {
+      props: z.object({
+        label: z.string(),
+        value: z.string(),
+      }),
+      description: "Single property item with label and value",
+    },
+
+    TaskList: {
+      props: z.object({}),
+      slots: ["default"],
+      description: "Container for task items with status indicators",
+    },
+
+    TaskListItem: {
+      props: z.object({
+        title: z.string(),
+        status: z
+          .enum(["not-started", "in-progress", "blocked", "complete"])
+          .default("not-started"),
+        action: z.string().nullable(),
+      }),
+      description: "Task item with title and status indicator",
+    },
+
+    // =========================================================================
+    // Menu Components
+    // =========================================================================
+    Menu: {
+      props: z.object({
+        triggerLabel: z.string(),
+      }),
+      slots: ["default"],
+      description: "Dropdown menu container with trigger button",
+    },
+
+    MenuItem: {
+      props: z.object({
+        label: z.string(),
+        id: z.string(),
+        disabled: z.boolean().nullable(),
+        action: z.string().nullable(),
+      }),
+      description: "Menu item with label and optional action",
+    },
+
+    MenuGroup: {
+      props: z.object({
+        title: z.string().nullable(),
+      }),
+      slots: ["default"],
+      description: "Group of menu items with optional title",
+    },
+
+    // =========================================================================
+    // Form Components
+    // =========================================================================
+    TextField: {
+      props: z.object({
+        label: z.string(),
+        placeholder: z.string().nullable(),
+        description: z.string().nullable(),
+        error: z.string().nullable(),
+        value: z.string().nullable(),
+        type: z
+          .enum(["text", "email", "password", "number", "tel", "url"])
+          .default("text"),
+        size: z.enum(["small", "medium", "large"]).default("medium"),
+        disabled: z.boolean().nullable(),
+        required: z.boolean().nullable(),
+      }),
+      description:
+        "Text input field with label, validation. Use $bindState on value for two-way binding.",
+      example: {
+        label: "Email",
+        placeholder: "customer@example.com",
+        value: { $bindState: "/form/email" },
+        type: "email",
+      },
+    },
+
+    TextArea: {
+      props: z.object({
+        label: z.string(),
+        placeholder: z.string().nullable(),
+        description: z.string().nullable(),
+        error: z.string().nullable(),
+        value: z.string().nullable(),
+        rows: z.number().default(3),
+        disabled: z.boolean().nullable(),
+        required: z.boolean().nullable(),
+      }),
+      description:
+        "Multi-line text input with configurable rows. Use $bindState on value for two-way binding.",
+    },
+
+    Select: {
+      props: z.object({
+        label: z.string(),
+        description: z.string().nullable(),
+        error: z.string().nullable(),
+        value: z.string().nullable(),
+        options: z.array(z.object({ value: z.string(), label: z.string() })),
+        size: z.enum(["small", "medium", "large"]).default("medium"),
+        disabled: z.boolean().nullable(),
+        required: z.boolean().nullable(),
+      }),
+      description:
+        "Dropdown select input with configurable options. Use $bindState on value for two-way binding.",
+    },
+
+    Checkbox: {
+      props: z.object({
+        label: z.string(),
+        description: z.string().nullable(),
+        error: z.string().nullable(),
+        checked: z.boolean().nullable(),
+        defaultChecked: z.boolean().nullable(),
+        disabled: z.boolean().nullable(),
+      }),
+      description:
+        "Checkbox input with label and description. Use $bindState on checked for two-way binding.",
+    },
+
+    Radio: {
+      props: z.object({
+        label: z.string(),
+        description: z.string().nullable(),
+        value: z.string().nullable(),
+        optionValue: z.string(),
+        name: z.string(),
+        disabled: z.boolean().nullable(),
+      }),
+      description:
+        "Radio button input for selecting one option from a group. Use $bindState on value for two-way binding.",
+    },
+
+    Switch: {
+      props: z.object({
+        label: z.string(),
+        description: z.string().nullable(),
+        checked: z.boolean().nullable(),
+        defaultChecked: z.boolean().nullable(),
+        disabled: z.boolean().nullable(),
+      }),
+      description:
+        "Toggle switch for boolean values. Use $bindState on checked for two-way binding.",
+    },
+
+    DateField: {
+      props: z.object({
+        label: z.string(),
+        description: z.string().nullable(),
+        error: z.string().nullable(),
+        value: z.string().nullable(),
+        size: z.enum(["small", "medium", "large"]).default("medium"),
+        disabled: z.boolean().nullable(),
+      }),
+      description:
+        "Date input field with date picker. Use $bindState on value for two-way binding.",
+    },
+
+    // =========================================================================
+    // Button Components
+    // =========================================================================
+    Button: {
+      props: z.object({
+        label: z.string(),
+        action: z.string(),
+        actionParams: z.record(z.string(), z.unknown()).nullable(),
+        type: z
+          .enum(["primary", "secondary", "destructive"])
+          .default("primary"),
+        size: z.enum(["small", "medium", "large"]).default("medium"),
+        disabled: z.boolean().nullable(),
+        pending: z.boolean().nullable(),
+        href: z.string().nullable(),
+      }),
+      description:
+        "Action button with configurable style, size, and action handling",
+      example: {
+        label: "View Details",
+        action: "viewCustomer",
+        type: "primary",
+      },
+    },
+
+    ButtonGroup: {
+      props: z.object({}),
+      slots: ["default"],
+      description: "Group of buttons displayed together",
+    },
+
+    Link: {
+      props: z.object({
+        label: z.string(),
+        href: z.string(),
+        type: z.enum(["primary", "secondary"]).default("primary"),
+        external: z.boolean().nullable(),
+      }),
+      description: "Text link for navigation",
+    },
+
+    // =========================================================================
+    // Chart Components
+    // =========================================================================
+    BarChart: {
+      props: z.object({
+        data: z.array(z.record(z.unknown())).nullable(),
+        xKey: z.string(),
+        yKey: z.string(),
+        colorKey: z.string().nullable(),
+        showAxis: z.enum(["x", "y", "both", "none"]).default("both"),
+        showGrid: z.enum(["x", "y", "both", "none"]).default("none"),
+        showLegend: z.boolean().nullable(),
+        showTooltip: z.boolean().default(true),
+      }),
+      description:
+        "Bar chart visualization. Use $state on data to bind to state array.",
+    },
+
+    LineChart: {
+      props: z.object({
+        data: z.array(z.record(z.unknown())).nullable(),
+        xKey: z.string(),
+        yKey: z.string(),
+        colorKey: z.string().nullable(),
+        showAxis: z.enum(["x", "y", "both", "none"]).default("both"),
+        showGrid: z.enum(["x", "y", "both", "none"]).default("none"),
+        showLegend: z.boolean().nullable(),
+        showTooltip: z.boolean().default(true),
+      }),
+      description:
+        "Line chart visualization. Use $state on data to bind to state array.",
+    },
+
+    Sparkline: {
+      props: z.object({
+        data: z.array(z.record(z.unknown())).nullable(),
+        xKey: z.string(),
+        yKey: z.string(),
+        showTooltip: z.boolean().nullable(),
+      }),
+      description:
+        "Compact sparkline chart for inline data visualization. Use $state on data to bind to state array.",
+    },
+
+    // =========================================================================
+    // Table Components
+    // =========================================================================
+    DataTable: {
+      props: z.object({
+        title: z.string().nullable(),
+        data: z.array(z.record(z.unknown())).nullable(),
+        columns: z.array(z.object({ key: z.string(), label: z.string() })),
+        emptyMessage: z.string().nullable(),
+        rowAction: z.string().nullable(),
+      }),
+      description:
+        "Data table with configurable columns and optional row actions. Use $state on data to bind to state array.",
+      example: {
+        title: "Recent Payments",
+        data: { $state: "/payments/data" },
+        columns: [
+          { key: "amount", label: "Amount" },
+          { key: "status", label: "Status" },
+        ],
+      },
+    },
+
+    // =========================================================================
+    // Stripe-Specific Card Components
+    // =========================================================================
+    CustomerCard: {
+      props: z.object({
+        name: z.string(),
+        email: z.string(),
+        status: z.enum(["active", "inactive"]).default("active"),
+        customerId: z.string().nullable(),
+      }),
+      description:
+        "Card displaying customer information with name, email, and status",
+      example: {
+        name: "Jane Smith",
+        email: "jane@example.com",
+        status: "active",
+      },
+    },
+
+    PaymentCard: {
+      props: z.object({
+        amount: z.number(),
+        currency: z.string().default("usd"),
+        status: z
+          .enum([
+            "succeeded",
+            "pending",
+            "failed",
+            "canceled",
+            "requires_action",
+          ])
+          .default("succeeded"),
+        description: z.string().nullable(),
+        paymentId: z.string().nullable(),
+      }),
+      description: "Card displaying payment information with amount and status",
+    },
+
+    SubscriptionCard: {
+      props: z.object({
+        planName: z.string(),
+        status: z
+          .enum([
+            "active",
+            "trialing",
+            "past_due",
+            "canceled",
+            "unpaid",
+            "incomplete",
+          ])
+          .default("active"),
+        amount: z.number(),
+        currency: z.string().default("usd"),
+        interval: z.enum(["day", "week", "month", "year"]).default("month"),
+        currentPeriodEnd: z.string().nullable(),
+      }),
+      description:
+        "Card displaying subscription details with plan, status, and billing info",
+    },
+
+    InvoiceCard: {
+      props: z.object({
+        invoiceNumber: z.string(),
+        amount: z.number(),
+        currency: z.string().default("usd"),
+        status: z
+          .enum(["draft", "open", "paid", "void", "uncollectible"])
+          .default("open"),
+        dueDate: z.string().nullable(),
+        customerEmail: z.string().nullable(),
+      }),
+      description:
+        "Card displaying invoice details with number, amount, and status",
+    },
+
+    RefundCard: {
+      props: z.object({
+        amount: z.number(),
+        currency: z.string().default("usd"),
+        status: z
+          .enum(["pending", "succeeded", "failed", "canceled"])
+          .default("succeeded"),
+        reason: z.string().nullable(),
+      }),
+      description: "Card displaying refund information",
+    },
+
+    DisputeCard: {
+      props: z.object({
+        amount: z.number(),
+        currency: z.string().default("usd"),
+        status: z
+          .enum([
+            "warning_needs_response",
+            "warning_under_review",
+            "warning_closed",
+            "needs_response",
+            "under_review",
+            "won",
+            "lost",
+          ])
+          .default("needs_response"),
+        reason: z.string().nullable(),
+        dueDate: z.string().nullable(),
+      }),
+      description:
+        "Card displaying dispute information with status and deadline",
+    },
+
+    BalanceCard: {
+      props: z.object({
+        available: z.number(),
+        pending: z.number(),
+        currency: z.string().default("usd"),
+      }),
+      description: "Card showing account balance breakdown",
+    },
+
+    // =========================================================================
+    // Chip Components
+    // =========================================================================
+    Chip: {
+      props: z.object({
+        label: z.string(),
+        value: z.string().nullable(),
+        removable: z.boolean().nullable(),
+        action: z.string().nullable(),
+      }),
+      description: "Chip/tag component for filters or selections",
+    },
+
+    ChipList: {
+      props: z.object({}),
+      slots: ["default"],
+      description: "Container for multiple chips",
+    },
+
+    // =========================================================================
+    // Tooltip Component
+    // =========================================================================
+    Tooltip: {
+      props: z.object({
+        content: z.string(),
+        placement: z.enum(["top", "bottom", "left", "right"]).default("top"),
+      }),
+      slots: ["default"],
+      description: "Tooltip that appears on hover over child element",
+    },
+  },
+
+  actions: {
+    // =========================================================================
+    // Customer Actions
+    // =========================================================================
+    fetchCustomers: {
+      params: z.object({
+        limit: z.number().nullable(),
+        email: z.string().nullable(),
+        startingAfter: z.string().nullable(),
+      }),
+      description:
+        "Fetch customers list with pagination. Data at '/customers/data'",
+    },
+    viewCustomer: {
+      params: z.object({ customerId: z.string() }),
+      description: "Open customer details in Stripe Dashboard",
+    },
+    createCustomer: {
+      params: z.object({
+        email: z.string(),
+        name: z.string().nullable(),
+        phone: z.string().nullable(),
+        description: z.string().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Create a new customer with optional metadata",
+    },
+    updateCustomer: {
+      params: z.object({
+        customerId: z.string(),
+        email: z.string().nullable(),
+        name: z.string().nullable(),
+        phone: z.string().nullable(),
+        description: z.string().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Update an existing customer",
+    },
+    deleteCustomer: {
+      params: z.object({ customerId: z.string() }),
+      description: "Delete a customer permanently",
+    },
+    searchCustomers: {
+      params: z.object({ query: z.string(), limit: z.number().nullable() }),
+      description: "Search customers by email, name, or metadata",
+    },
+
+    // =========================================================================
+    // Payment Intent Actions
+    // =========================================================================
+    fetchPayments: {
+      params: z.object({
+        limit: z.number().nullable(),
+        customerId: z.string().nullable(),
+        status: z.string().nullable(),
+        startingAfter: z.string().nullable(),
+      }),
+      description: "Fetch payment intents list. Data at '/payments/data'",
+    },
+    viewPayment: {
+      params: z.object({ paymentId: z.string() }),
+      description: "Open payment details in Stripe Dashboard",
+    },
+    createPaymentIntent: {
+      params: z.object({
+        amount: z.number(),
+        currency: z.string(),
+        customerId: z.string().nullable(),
+        description: z.string().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Create a new payment intent",
+    },
+    capturePayment: {
+      params: z.object({
+        paymentId: z.string(),
+        amountToCapture: z.number().nullable(),
+      }),
+      description: "Capture an authorized payment",
+    },
+    cancelPayment: {
+      params: z.object({
+        paymentId: z.string(),
+        reason: z
+          .enum([
+            "duplicate",
+            "fraudulent",
+            "requested_by_customer",
+            "abandoned",
+          ])
+          .nullable(),
+      }),
+      description: "Cancel a payment intent",
+    },
+
+    // =========================================================================
+    // Refund Actions
+    // =========================================================================
+    fetchRefunds: {
+      params: z.object({
+        limit: z.number().nullable(),
+        paymentIntentId: z.string().nullable(),
+        chargeId: z.string().nullable(),
+      }),
+      description: "Fetch refunds list. Data at '/refunds/data'",
+    },
+    refundPayment: {
+      params: z.object({
+        paymentId: z.string(),
+        amount: z.number().nullable(),
+        reason: z
+          .enum(["duplicate", "fraudulent", "requested_by_customer"])
+          .nullable(),
+      }),
+      description: "Create a refund for a payment",
+    },
+    updateRefund: {
+      params: z.object({
+        refundId: z.string(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Update refund metadata",
+    },
+
+    // =========================================================================
+    // Charge Actions
+    // =========================================================================
+    fetchCharges: {
+      params: z.object({
+        limit: z.number().nullable(),
+        customerId: z.string().nullable(),
+        paymentIntentId: z.string().nullable(),
+      }),
+      description: "Fetch charges list. Data at '/charges/data'",
+    },
+    captureCharge: {
+      params: z.object({ chargeId: z.string(), amount: z.number().nullable() }),
+      description: "Capture a previously authorized charge",
+    },
+
+    // =========================================================================
+    // Subscription Actions
+    // =========================================================================
+    fetchSubscriptions: {
+      params: z.object({
+        limit: z.number().nullable(),
+        status: z
+          .enum([
+            "active",
+            "past_due",
+            "unpaid",
+            "canceled",
+            "incomplete",
+            "incomplete_expired",
+            "trialing",
+            "paused",
+          ])
+          .nullable(),
+        customerId: z.string().nullable(),
+        priceId: z.string().nullable(),
+      }),
+      description: "Fetch subscriptions. Data at '/subscriptions/data'",
+    },
+    viewSubscription: {
+      params: z.object({ subscriptionId: z.string() }),
+      description: "Open subscription details in Stripe Dashboard",
+    },
+    createSubscription: {
+      params: z.object({
+        customerId: z.string(),
+        priceId: z.string(),
+        quantity: z.number().nullable(),
+        trialPeriodDays: z.number().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Create a new subscription for a customer",
+    },
+    updateSubscription: {
+      params: z.object({
+        subscriptionId: z.string(),
+        priceId: z.string().nullable(),
+        quantity: z.number().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Update a subscription (change price, quantity)",
+    },
+    cancelSubscription: {
+      params: z.object({
+        subscriptionId: z.string(),
+        immediately: z.boolean().nullable(),
+        cancelAtPeriodEnd: z.boolean().nullable(),
+      }),
+      description: "Cancel a subscription immediately or at period end",
+    },
+    pauseSubscription: {
+      params: z.object({
+        subscriptionId: z.string(),
+        resumeAt: z.number().nullable(),
+      }),
+      description: "Pause a subscription's payment collection",
+    },
+    resumeSubscription: {
+      params: z.object({ subscriptionId: z.string() }),
+      description: "Resume a paused subscription",
+    },
+
+    // =========================================================================
+    // Invoice Actions
+    // =========================================================================
+    fetchInvoices: {
+      params: z.object({
+        limit: z.number().nullable(),
+        status: z
+          .enum(["draft", "open", "paid", "uncollectible", "void"])
+          .nullable(),
+        customerId: z.string().nullable(),
+        subscriptionId: z.string().nullable(),
+      }),
+      description: "Fetch invoices. Data at '/invoices/data'",
+    },
+    viewInvoice: {
+      params: z.object({ invoiceId: z.string() }),
+      description: "Open invoice in Stripe Dashboard",
+    },
+    createInvoice: {
+      params: z.object({
+        customerId: z.string(),
+        description: z.string().nullable(),
+        daysUntilDue: z.number().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Create a draft invoice for a customer",
+    },
+    addInvoiceItem: {
+      params: z.object({
+        invoiceId: z.string(),
+        amount: z.number(),
+        currency: z.string(),
+        description: z.string().nullable(),
+      }),
+      description: "Add a line item to an invoice",
+    },
+    finalizeInvoice: {
+      params: z.object({
+        invoiceId: z.string(),
+        autoAdvance: z.boolean().nullable(),
+      }),
+      description: "Finalize a draft invoice",
+    },
+    sendInvoice: {
+      params: z.object({ invoiceId: z.string() }),
+      description: "Send invoice to customer via email",
+    },
+    payInvoice: {
+      params: z.object({
+        invoiceId: z.string(),
+        paymentMethodId: z.string().nullable(),
+      }),
+      description: "Pay an open invoice",
+    },
+    voidInvoice: {
+      params: z.object({ invoiceId: z.string() }),
+      description: "Void an invoice (cannot be undone)",
+    },
+    markInvoiceUncollectible: {
+      params: z.object({ invoiceId: z.string() }),
+      description: "Mark invoice as uncollectible",
+    },
+    downloadInvoicePdf: {
+      params: z.object({ invoiceId: z.string() }),
+      description: "Download invoice as PDF",
+    },
+
+    // =========================================================================
+    // Product & Price Actions
+    // =========================================================================
+    fetchProducts: {
+      params: z.object({
+        limit: z.number().nullable(),
+        active: z.boolean().nullable(),
+      }),
+      description: "Fetch products list. Data at '/products/data'",
+    },
+    viewProduct: {
+      params: z.object({ productId: z.string() }),
+      description: "Open product in Stripe Dashboard",
+    },
+    createProduct: {
+      params: z.object({
+        name: z.string(),
+        description: z.string().nullable(),
+        active: z.boolean().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Create a new product",
+    },
+    updateProduct: {
+      params: z.object({
+        productId: z.string(),
+        name: z.string().nullable(),
+        description: z.string().nullable(),
+        active: z.boolean().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Update a product",
+    },
+    archiveProduct: {
+      params: z.object({ productId: z.string() }),
+      description: "Archive a product (set active=false)",
+    },
+    fetchPrices: {
+      params: z.object({
+        limit: z.number().nullable(),
+        productId: z.string().nullable(),
+        active: z.boolean().nullable(),
+        type: z.enum(["one_time", "recurring"]).nullable(),
+      }),
+      description: "Fetch prices list. Data at '/prices/data'",
+    },
+    createPrice: {
+      params: z.object({
+        productId: z.string(),
+        unitAmount: z.number(),
+        currency: z.string(),
+        recurring: z
+          .object({
+            interval: z.enum(["day", "week", "month", "year"]),
+            intervalCount: z.number().nullable(),
+          })
+          .nullable(),
+        nickname: z.string().nullable(),
+      }),
+      description: "Create a new price for a product",
+    },
+    updatePrice: {
+      params: z.object({
+        priceId: z.string(),
+        active: z.boolean().nullable(),
+        nickname: z.string().nullable(),
+        metadata: z.record(z.string(), z.string()).nullable(),
+      }),
+      description: "Update a price",
+    },
+
+    // =========================================================================
+    // Balance & Payout Actions
+    // =========================================================================
+    fetchBalance: {
+      params: z.object({}),
+      description: "Fetch current account balance. Data at '/balance'",
+    },
+    fetchPayouts: {
+      params: z.object({
+        limit: z.number().nullable(),
+        status: z.enum(["pending", "paid", "failed", "canceled"]).nullable(),
+      }),
+      description: "Fetch payouts list. Data at '/payouts/data'",
+    },
+    createPayout: {
+      params: z.object({
+        amount: z.number(),
+        currency: z.string(),
+        description: z.string().nullable(),
+      }),
+      description: "Create a payout to your bank account",
+    },
+    cancelPayout: {
+      params: z.object({ payoutId: z.string() }),
+      description: "Cancel a pending payout",
+    },
+
+    // =========================================================================
+    // Dispute Actions
+    // =========================================================================
+    fetchDisputes: {
+      params: z.object({
+        limit: z.number().nullable(),
+        status: z
+          .enum([
+            "warning_needs_response",
+            "warning_under_review",
+            "warning_closed",
+            "needs_response",
+            "under_review",
+            "won",
+            "lost",
+          ])
+          .nullable(),
+        chargeId: z.string().nullable(),
+      }),
+      description: "Fetch disputes list. Data at '/disputes/data'",
+    },
+    viewDispute: {
+      params: z.object({ disputeId: z.string() }),
+      description: "Open dispute in Stripe Dashboard",
+    },
+    updateDispute: {
+      params: z.object({
+        disputeId: z.string(),
+        evidence: z
+          .object({
+            customerName: z.string().nullable(),
+            customerEmailAddress: z.string().nullable(),
+            productDescription: z.string().nullable(),
+            uncategorizedText: z.string().nullable(),
+          })
+          .nullable(),
+        submit: z.boolean().nullable(),
+      }),
+      description: "Update dispute evidence",
+    },
+    closeDispute: {
+      params: z.object({ disputeId: z.string() }),
+      description: "Close a dispute and accept it",
+    },
+
+    // =========================================================================
+    // Payment Method Actions
+    // =========================================================================
+    fetchPaymentMethods: {
+      params: z.object({
+        customerId: z.string(),
+        type: z
+          .enum(["card", "bank_account", "us_bank_account", "sepa_debit"])
+          .nullable(),
+      }),
+      description:
+        "Fetch customer payment methods. Data at '/paymentMethods/data'",
+    },
+    attachPaymentMethod: {
+      params: z.object({ paymentMethodId: z.string(), customerId: z.string() }),
+      description: "Attach a payment method to a customer",
+    },
+    detachPaymentMethod: {
+      params: z.object({ paymentMethodId: z.string() }),
+      description: "Detach a payment method from its customer",
+    },
+    setDefaultPaymentMethod: {
+      params: z.object({ customerId: z.string(), paymentMethodId: z.string() }),
+      description: "Set the default payment method for a customer",
+    },
+
+    // =========================================================================
+    // Coupon & Promotion Actions
+    // =========================================================================
+    fetchCoupons: {
+      params: z.object({ limit: z.number().nullable() }),
+      description: "Fetch coupons list. Data at '/coupons/data'",
+    },
+    createCoupon: {
+      params: z.object({
+        percentOff: z.number().nullable(),
+        amountOff: z.number().nullable(),
+        currency: z.string().nullable(),
+        duration: z.enum(["forever", "once", "repeating"]),
+        durationInMonths: z.number().nullable(),
+        name: z.string().nullable(),
+        maxRedemptions: z.number().nullable(),
+      }),
+      description: "Create a new coupon",
+    },
+    deleteCoupon: {
+      params: z.object({ couponId: z.string() }),
+      description: "Delete a coupon",
+    },
+    fetchPromotionCodes: {
+      params: z.object({
+        limit: z.number().nullable(),
+        couponId: z.string().nullable(),
+        active: z.boolean().nullable(),
+      }),
+      description: "Fetch promotion codes. Data at '/promotionCodes/data'",
+    },
+    createPromotionCode: {
+      params: z.object({
+        couponId: z.string(),
+        code: z.string().nullable(),
+        maxRedemptions: z.number().nullable(),
+        expiresAt: z.number().nullable(),
+      }),
+      description: "Create a promotion code for a coupon",
+    },
+
+    // =========================================================================
+    // Checkout Session Actions
+    // =========================================================================
+    createCheckoutSession: {
+      params: z.object({
+        mode: z.enum(["payment", "subscription", "setup"]),
+        lineItems: z.array(
+          z.object({ priceId: z.string(), quantity: z.number() }),
+        ),
+        successUrl: z.string(),
+        cancelUrl: z.string(),
+        customerId: z.string().nullable(),
+      }),
+      description: "Create a Checkout Session and get the URL",
+    },
+    fetchCheckoutSessions: {
+      params: z.object({
+        limit: z.number().nullable(),
+        customerId: z.string().nullable(),
+        paymentIntentId: z.string().nullable(),
+      }),
+      description: "Fetch checkout sessions. Data at '/checkoutSessions/data'",
+    },
+    expireCheckoutSession: {
+      params: z.object({ sessionId: z.string() }),
+      description: "Expire an open checkout session",
+    },
+
+    // =========================================================================
+    // Billing Portal Actions
+    // =========================================================================
+    createBillingPortalSession: {
+      params: z.object({ customerId: z.string(), returnUrl: z.string() }),
+      description: "Create a billing portal session for customer self-service",
+    },
+
+    // =========================================================================
+    // Webhook & Event Actions
+    // =========================================================================
+    fetchEvents: {
+      params: z.object({
+        limit: z.number().nullable(),
+        type: z.string().nullable(),
+        createdGte: z.number().nullable(),
+        createdLte: z.number().nullable(),
+      }),
+      description: "Fetch recent events/webhooks. Data at '/events/data'",
+    },
+
+    // =========================================================================
+    // Setup Intent Actions
+    // =========================================================================
+    createSetupIntent: {
+      params: z.object({
+        customerId: z.string().nullable(),
+        paymentMethodTypes: z.array(z.string()).nullable(),
+        usage: z.enum(["on_session", "off_session"]).nullable(),
+      }),
+      description: "Create a SetupIntent for saving payment methods",
+    },
+    fetchSetupIntents: {
+      params: z.object({
+        limit: z.number().nullable(),
+        customerId: z.string().nullable(),
+      }),
+      description: "Fetch setup intents. Data at '/setupIntents/data'",
+    },
+    cancelSetupIntent: {
+      params: z.object({ setupIntentId: z.string() }),
+      description: "Cancel a setup intent",
+    },
+
+    // =========================================================================
+    // Tax Rate Actions
+    // =========================================================================
+    fetchTaxRates: {
+      params: z.object({
+        limit: z.number().nullable(),
+        active: z.boolean().nullable(),
+        inclusive: z.boolean().nullable(),
+      }),
+      description: "Fetch tax rates. Data at '/taxRates/data'",
+    },
+    createTaxRate: {
+      params: z.object({
+        displayName: z.string(),
+        percentage: z.number(),
+        inclusive: z.boolean(),
+        jurisdiction: z.string().nullable(),
+        description: z.string().nullable(),
+      }),
+      description: "Create a new tax rate",
+    },
+
+    // =========================================================================
+    // Data & Refresh Actions
+    // =========================================================================
+    refreshData: {
+      params: z.object({}),
+      description: "Refresh all dashboard data",
+    },
+    refreshCustomers: {
+      params: z.object({}),
+      description: "Refresh customers data only",
+    },
+    refreshPayments: {
+      params: z.object({}),
+      description: "Refresh payments data only",
+    },
+    refreshSubscriptions: {
+      params: z.object({}),
+      description: "Refresh subscriptions data only",
+    },
+    refreshInvoices: {
+      params: z.object({}),
+      description: "Refresh invoices data only",
+    },
+    exportData: {
+      params: z.object({
+        format: z.enum(["csv", "json"]),
+        dataType: z.enum([
+          "customers",
+          "payments",
+          "subscriptions",
+          "invoices",
+        ]),
+      }),
+      description: "Export data as CSV or JSON",
+    },
+
+    // =========================================================================
+    // Navigation Actions
+    // =========================================================================
+    navigate: {
+      params: z.object({ path: z.string() }),
+      description: "Navigate to a Dashboard page",
+    },
+    openDashboard: {
+      params: z.object({
+        page: z
+          .enum([
+            "home",
+            "payments",
+            "customers",
+            "products",
+            "subscriptions",
+            "invoices",
+            "connect",
+            "reports",
+            "developers",
+          ])
+          .nullable(),
+      }),
+      description: "Open Stripe Dashboard page in new tab",
+    },
+    openExternalLink: {
+      params: z.object({ url: z.string() }),
+      description: "Open an external URL in new tab",
+    },
+
+    // =========================================================================
+    // Form Actions
+    // =========================================================================
+    submitForm: {
+      params: z.object({ formId: z.string().nullable() }),
+      description: "Submit form data",
+    },
+    resetForm: {
+      params: z.object({ formId: z.string().nullable() }),
+      description: "Reset form to initial values",
+    },
+    validateForm: {
+      params: z.object({ formId: z.string().nullable() }),
+      description: "Validate form without submitting",
+    },
+    setFormValue: {
+      params: z.object({ path: z.string(), value: z.unknown() }),
+      description: "Set a specific form field value",
+    },
+
+    // =========================================================================
+    // UI Actions
+    // =========================================================================
+    showToast: {
+      params: z.object({
+        message: z.string(),
+        type: z.enum(["success", "error", "warning", "info"]).nullable(),
+      }),
+      description: "Show a toast notification",
+    },
+    copyToClipboard: {
+      params: z.object({ text: z.string() }),
+      description: "Copy text to clipboard",
+    },
+    setLoading: {
+      params: z.object({
+        loading: z.boolean(),
+        message: z.string().nullable(),
+      }),
+      description: "Show/hide loading state",
+    },
+
+    // =========================================================================
+    // Filter & Sort Actions
+    // =========================================================================
+    setFilter: {
+      params: z.object({ key: z.string(), value: z.unknown() }),
+      description: "Set a filter value",
+    },
+    clearFilters: {
+      params: z.object({}),
+      description: "Clear all filters",
+    },
+    setSort: {
+      params: z.object({
+        field: z.string(),
+        direction: z.enum(["asc", "desc"]),
+      }),
+      description: "Set sort field and direction",
+    },
+    setPageSize: {
+      params: z.object({ size: z.number() }),
+      description: "Set items per page",
+    },
+    goToPage: {
+      params: z.object({ page: z.number() }),
+      description: "Navigate to a specific page",
+    },
+  },
+});

+ 14 - 20
examples/stripe-app/src/lib/render/catalog/actions.ts → examples/stripe-app/fullpage-app/src/lib/render/catalog/actions.ts

@@ -1374,16 +1374,16 @@ export const actionHandlers: Record<
     await actionHandlers.fetchCustomers({}, setState, {});
   },
 
-  refreshPayments: async (_) => {
-    await actionHandlers.fetchPayments({}, setData, {});
+  refreshPayments: async (_, setState) => {
+    await actionHandlers.fetchPayments({}, setState, {});
   },
 
-  refreshSubscriptions: async (_) => {
-    await actionHandlers.fetchSubscriptions({}, setData, {});
+  refreshSubscriptions: async (_, setState) => {
+    await actionHandlers.fetchSubscriptions({}, setState, {});
   },
 
-  refreshInvoices: async (_) => {
-    await actionHandlers.fetchInvoices({}, setData, {});
+  refreshInvoices: async (_, setState) => {
+    await actionHandlers.fetchInvoices({}, setState, {});
   },
 
   exportData: async (params, _, data) => {
@@ -1431,11 +1431,8 @@ export const actionHandlers: Record<
   // ===========================================================================
   // Navigation Actions
   // ===========================================================================
-  navigate: async (params) => {
-    if (params?.statePath) {
-      console.log("Navigate to:", params.statePath);
-      // In a real app, this would use the router
-    }
+  navigate: async (_params) => {
+    // In a real app, this would use the router
   },
 
   openDashboard: async (params) => {
@@ -1463,8 +1460,7 @@ export const actionHandlers: Record<
   // ===========================================================================
   // Form Actions
   // ===========================================================================
-  submitForm: async (params, _, data) => {
-    console.log("Submit form:", params?.formId, data);
+  submitForm: async (_params, _, _data) => {
     // Implementation depends on form handling logic
   },
 
@@ -1475,9 +1471,8 @@ export const actionHandlers: Record<
     }
   },
 
-  validateForm: async (params, _, data) => {
-    console.log("Validate form:", params?.formId, data);
-    return;
+  validateForm: async (_params, _, _data) => {
+    // Implementation depends on form validation logic
   },
 
   setFormValue: async (params, setState) => {
@@ -1492,9 +1487,8 @@ export const actionHandlers: Record<
   // ===========================================================================
   // UI Actions
   // ===========================================================================
-  showToast: async (params) => {
-    // In UIXT, you would use the showToast API from the SDK
-    console.log("Toast:", params?.type, params?.message);
+  showToast: async (_params) => {
+    // In a real app, use showToast from @stripe/ui-extension-sdk/utils
   },
 
   copyToClipboard: async (params) => {
@@ -1567,6 +1561,6 @@ export async function executeAction(
   if (handler) {
     await handler(params, setState, data);
   } else {
-    console.log("Unknown action:", actionName, params);
+    console.warn("Unknown action:", actionName);
   }
 }

+ 1405 - 0
examples/stripe-app/fullpage-app/src/lib/render/catalog/components.tsx

@@ -0,0 +1,1405 @@
+/**
+ * Stripe UIXT Component Implementations
+ *
+ * Maps json-render catalog components to Stripe UI Extension SDK components.
+ */
+import type { FunctionComponent } from "react";
+import type { ComponentRenderProps } from "@json-render/react";
+import {
+  Box,
+  Badge as UIBadge,
+  Banner as UIBanner,
+  Button as UIButton,
+  Checkbox as UICheckbox,
+  DateField as UIDateField,
+  Divider as UIDivider,
+  Icon as UIIcon,
+  Img as UIImg,
+  Inline as UIInline,
+  Link as UILink,
+  List as UIList,
+  ListItem as UIListItem,
+  Menu as UIMenu,
+  MenuGroup as UIMenuGroup,
+  MenuItem as UIMenuItem,
+  PropertyList as UIPropertyList,
+  PropertyListItem as UIPropertyListItem,
+  Radio as UIRadio,
+  Select as UISelect,
+  Spinner as UISpinner,
+  Switch as UISwitch,
+  TaskList as UITaskList,
+  TaskListItem as UITaskListItem,
+  TextArea as UITextArea,
+  TextField as UITextField,
+  Tooltip as UITooltip,
+  Accordion as UIAccordion,
+  AccordionItem as UIAccordionItem,
+  BarChart as UIBarChart,
+  LineChart as UILineChart,
+  Sparkline as UISparkline,
+  ButtonGroup as UIButtonGroup,
+  Chip as UIChip,
+  ChipList as UIChipList,
+  Table,
+  TableHead,
+  TableBody,
+  TableRow,
+  TableHeaderCell,
+  TableCell,
+} from "@stripe/ui-extension-sdk/ui";
+
+// Helper to format currency
+const formatCurrency = (amount: number, currency = "usd"): string => {
+  return new Intl.NumberFormat("en-US", {
+    style: "currency",
+    currency: currency.toUpperCase(),
+  }).format(amount / 100);
+};
+
+// Extended props type for components with data access
+type ExtendedRenderProps<P = Record<string, unknown>> =
+  ComponentRenderProps<P> & {
+    state?: Record<string, unknown>;
+    getValue?: (path: string) => unknown;
+    onAction?: (actionName: string, params?: Record<string, unknown>) => void;
+  };
+
+// NOTE: All interactive components use `emit` to fire named events.
+// The renderer resolves events to action bindings from the element's `on` field.
+
+// =========================================================================
+// Layout Components
+// =========================================================================
+export const Stack: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  children,
+}) => {
+  const {
+    direction = "vertical",
+    gap = "medium",
+    distribute,
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <Box
+      css={{
+        stack: direction === "horizontal" ? "x" : "y",
+        gap: gap as "xsmall" | "small" | "medium" | "large" | "xlarge",
+        ...(distribute === "space-between" && {
+          distribute: "space-between" as const,
+        }),
+      }}
+    >
+      {children}
+    </Box>
+  );
+};
+
+export const Inline: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+}) => {
+  return <UIInline>{children}</UIInline>;
+};
+
+export const Divider: FunctionComponent<ExtendedRenderProps> = () => {
+  return <UIDivider />;
+};
+
+export const Accordion: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+}) => {
+  return <UIAccordion>{children}</UIAccordion>;
+};
+
+export const AccordionItem: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  children,
+}) => {
+  const { title, subtitle, defaultOpen } = element.props as Record<
+    string,
+    unknown
+  >;
+  return (
+    <UIAccordionItem
+      title={String(title || "")}
+      subtitle={subtitle ? String(subtitle) : undefined}
+      defaultOpen={Boolean(defaultOpen) || undefined}
+    >
+      {children}
+    </UIAccordionItem>
+  );
+};
+
+// =========================================================================
+// Typography Components
+// =========================================================================
+export const Heading: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const { text, size = "large" } = element.props as Record<string, unknown>;
+
+  const fontMap: Record<
+    string,
+    "heading" | "title" | "subtitle" | "subheading" | "body"
+  > = {
+    xsmall: "body",
+    small: "subheading",
+    medium: "subtitle",
+    large: "title",
+    xlarge: "heading",
+  };
+
+  return (
+    <Box css={{ font: fontMap[size as string] || "title", fontWeight: "bold" }}>
+      {String(text || "")}
+    </Box>
+  );
+};
+
+export const Text: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const {
+    content,
+    color = "primary",
+    size = "medium",
+    weight = "regular",
+  } = element.props as Record<string, unknown>;
+
+  const colorMap: Record<
+    string,
+    | "primary"
+    | "secondary"
+    | "disabled"
+    | "critical"
+    | "success"
+    | "attention"
+    | "info"
+  > = {
+    primary: "primary",
+    secondary: "secondary",
+    disabled: "disabled",
+    critical: "critical",
+    success: "success",
+    warning: "attention",
+    info: "info",
+  };
+
+  const fontMap: Record<string, "caption" | "body" | "subtitle" | "title"> = {
+    xsmall: "caption",
+    small: "caption",
+    medium: "body",
+    large: "subtitle",
+  };
+
+  const weightMap: Record<string, "regular" | "semibold" | "bold"> = {
+    regular: "regular",
+    medium: "regular",
+    semibold: "semibold",
+    bold: "bold",
+  };
+
+  return (
+    <Box
+      css={{
+        font: fontMap[size as string] || "body",
+        color: colorMap[color as string] || "primary",
+        fontWeight: weightMap[weight as string] || "regular",
+      }}
+    >
+      {String(content || "")}
+    </Box>
+  );
+};
+
+// =========================================================================
+// Data Display Components
+// =========================================================================
+export const Metric: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const {
+    label,
+    value,
+    change,
+    changeType = "neutral",
+  } = element.props as Record<string, unknown>;
+
+  const changeColors: Record<string, "success" | "critical" | "secondary"> = {
+    positive: "success",
+    negative: "critical",
+    neutral: "secondary",
+  };
+
+  return (
+    <Box css={{ stack: "y", gap: "xsmall" }}>
+      <Box css={{ font: "caption", color: "secondary" }}>
+        {String(label || "")}
+      </Box>
+      <Box css={{ font: "heading", fontWeight: "bold" }}>
+        {String(value || "")}
+      </Box>
+      {change && (
+        <Box
+          css={{
+            font: "caption",
+            color: changeColors[changeType as string] || "secondary",
+          }}
+        >
+          {changeType === "positive"
+            ? "↑"
+            : changeType === "negative"
+              ? "↓"
+              : ""}{" "}
+          {String(change)}
+        </Box>
+      )}
+    </Box>
+  );
+};
+
+const BADGE_TYPES = new Set([
+  "neutral",
+  "urgent",
+  "warning",
+  "negative",
+  "positive",
+  "info",
+]);
+const BADGE_ALIAS: Record<string, string> = {
+  success: "positive",
+  error: "negative",
+  danger: "negative",
+  critical: "urgent",
+  default: "neutral",
+  primary: "info",
+};
+
+function coerceBadgeType(
+  raw: unknown,
+): "neutral" | "urgent" | "warning" | "negative" | "positive" | "info" {
+  const s = String(raw ?? "neutral");
+  if (BADGE_TYPES.has(s))
+    return s as
+      | "neutral"
+      | "urgent"
+      | "warning"
+      | "negative"
+      | "positive"
+      | "info";
+  return (BADGE_ALIAS[s] ?? "neutral") as
+    | "neutral"
+    | "urgent"
+    | "warning"
+    | "negative"
+    | "positive"
+    | "info";
+}
+
+export const Badge: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const { label, type = "neutral" } = element.props as Record<string, unknown>;
+  return <UIBadge type={coerceBadgeType(type)}>{String(label || "")}</UIBadge>;
+};
+
+export const Icon: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const { name, size = "medium" } = element.props as Record<string, unknown>;
+  const sizeMap: Record<string, "xsmall" | "small" | "medium" | "large"> = {
+    xsmall: "xsmall",
+    small: "small",
+    medium: "medium",
+    large: "large",
+  };
+  return (
+    // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Stripe UIXT Icon name type is complex
+    <UIIcon name={name as any} size={sizeMap[size as string] || "medium"} />
+  );
+};
+
+export const Img: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const { src, alt, width, height } = element.props as Record<string, unknown>;
+  return (
+    <UIImg
+      src={String(src || "")}
+      alt={String(alt || "")}
+      width={width as number | undefined}
+      height={height as number | undefined}
+    />
+  );
+};
+
+export const Spinner: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const { size = "medium" } = element.props as Record<string, unknown>;
+  return <UISpinner size={size as "small" | "medium" | "large"} />;
+};
+
+// =========================================================================
+// Feedback Components
+// =========================================================================
+const BANNER_TYPES = new Set(["default", "caution", "critical"]);
+const BANNER_ALIAS: Record<string, string> = {
+  info: "default",
+  warning: "caution",
+  error: "critical",
+  danger: "critical",
+  success: "default",
+};
+
+function coerceBannerType(raw: unknown): "default" | "caution" | "critical" {
+  const s = String(raw ?? "default");
+  if (BANNER_TYPES.has(s)) return s as "default" | "caution" | "critical";
+  return (BANNER_ALIAS[s] ?? "default") as "default" | "caution" | "critical";
+}
+
+export const Banner: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const {
+    title,
+    description,
+    type = "default",
+  } = element.props as Record<string, unknown>;
+  return (
+    <UIBanner
+      title={title ? String(title) : undefined}
+      description={description ? String(description) : undefined}
+      type={coerceBannerType(type)}
+    />
+  );
+};
+
+// =========================================================================
+// List Components
+// =========================================================================
+export const List: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+  emit,
+}) => {
+  return <UIList onAction={(_id) => emit("select")}>{children}</UIList>;
+};
+
+export const ListItem: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    title,
+    secondaryTitle,
+    value,
+    id,
+    size = "default",
+  } = element.props as Record<string, unknown>;
+  return (
+    <UIListItem
+      title={<Box>{String(title || "")}</Box>}
+      secondaryTitle={
+        secondaryTitle ? <Box>{String(secondaryTitle)}</Box> : undefined
+      }
+      value={value ? <Box>{String(value)}</Box> : undefined}
+      id={id ? String(id) : undefined}
+      size={size as "default" | "large"}
+    />
+  );
+};
+
+export const PropertyList: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  children,
+}) => {
+  const { orientation = "vertical" } = element.props as Record<string, unknown>;
+  return (
+    <UIPropertyList orientation={orientation as "vertical" | "horizontal"}>
+      {children}
+    </UIPropertyList>
+  );
+};
+
+export const PropertyListItem: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const { label, value } = element.props as Record<string, unknown>;
+  return (
+    <UIPropertyListItem
+      label={String(label || "")}
+      value={String(value || "")}
+    />
+  );
+};
+
+export const TaskList: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+}) => {
+  return <UITaskList>{children}</UITaskList>;
+};
+
+export const TaskListItem: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+}) => {
+  const {
+    title,
+    status = "not-started",
+    action,
+  } = element.props as Record<string, unknown>;
+  return (
+    <UITaskListItem
+      title={String(title || "")}
+      status={status as "not-started" | "in-progress" | "blocked" | "complete"}
+      onPress={action ? () => emit("press") : undefined}
+    />
+  );
+};
+
+// =========================================================================
+// Menu Components
+// =========================================================================
+export const Menu: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  children,
+  emit,
+}) => {
+  const { triggerLabel } = element.props as Record<string, unknown>;
+  return (
+    <UIMenu
+      trigger={
+        <UIButton type="secondary">{String(triggerLabel || "Menu")}</UIButton>
+      }
+      onAction={(_id) => emit("select")}
+    >
+      {children}
+    </UIMenu>
+  );
+};
+
+export const MenuItem: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+}) => {
+  const { label, id, disabled, action } = element.props as Record<
+    string,
+    unknown
+  >;
+  return (
+    <UIMenuItem
+      id={String(id || "")}
+      disabled={Boolean(disabled) || undefined}
+      onAction={action ? () => emit("select") : undefined}
+    >
+      {String(label || "")}
+    </UIMenuItem>
+  );
+};
+
+export const MenuGroup: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+}) => {
+  return <UIMenuGroup>{children}</UIMenuGroup>;
+};
+
+// =========================================================================
+// Form Components
+// =========================================================================
+export const TextField: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    label,
+    placeholder,
+    description,
+    error,
+    value = "",
+    size = "medium",
+    disabled,
+    required,
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <UITextField
+      label={String(label || "")}
+      placeholder={placeholder ? String(placeholder) : undefined}
+      description={description ? String(description) : undefined}
+      error={error ? String(error) : undefined}
+      value={String(value ?? "")}
+      size={size as "small" | "medium" | "large"}
+      disabled={Boolean(disabled) || undefined}
+      required={Boolean(required) || undefined}
+      onChange={() => undefined}
+    />
+  );
+};
+
+export const TextArea: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    label,
+    placeholder,
+    description,
+    error,
+    value = "",
+    rows = 3,
+    disabled,
+    required,
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <UITextArea
+      label={String(label || "")}
+      placeholder={placeholder ? String(placeholder) : undefined}
+      description={description ? String(description) : undefined}
+      error={error ? String(error) : undefined}
+      value={String(value ?? "")}
+      rows={Number(rows)}
+      disabled={Boolean(disabled) || undefined}
+      required={Boolean(required) || undefined}
+      onChange={() => undefined}
+    />
+  );
+};
+
+export const Select: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const {
+    label,
+    description,
+    error,
+    value = "",
+    options = [],
+    size = "medium",
+    disabled,
+    required,
+  } = element.props as Record<string, unknown>;
+  const opts = options as Array<{ value: string; label: string }>;
+
+  return (
+    <UISelect
+      label={String(label || "")}
+      description={description ? String(description) : undefined}
+      error={error ? String(error) : undefined}
+      value={String(value ?? "")}
+      size={size as "small" | "medium" | "large"}
+      disabled={Boolean(disabled) || undefined}
+      required={Boolean(required) || undefined}
+      onChange={() => undefined}
+    >
+      {opts.map((opt: { value: string; label: string }) => (
+        <option key={opt.value} value={opt.value}>
+          {opt.label}
+        </option>
+      ))}
+    </UISelect>
+  );
+};
+
+export const Checkbox: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const { label, description, error, checked, defaultChecked, disabled } =
+    element.props as Record<string, unknown>;
+  const isChecked = checked ?? defaultChecked;
+
+  return (
+    <UICheckbox
+      label={String(label || "")}
+      description={description ? String(description) : undefined}
+      error={error ? String(error) : undefined}
+      checked={Boolean(isChecked) || undefined}
+      disabled={Boolean(disabled) || undefined}
+      onChange={() => undefined}
+    />
+  );
+};
+
+export const Radio: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const {
+    label,
+    description,
+    value: selectedValue,
+    optionValue,
+    name,
+    disabled,
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <UIRadio
+      label={String(label || "")}
+      description={description ? String(description) : undefined}
+      value={String(optionValue ?? "")}
+      name={String(name || "")}
+      checked={String(selectedValue ?? "") === String(optionValue ?? "")}
+      disabled={Boolean(disabled) || undefined}
+      onChange={() => undefined}
+    />
+  );
+};
+
+export const Switch: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const { label, description, checked, defaultChecked, disabled } =
+    element.props as Record<string, unknown>;
+  const isChecked = checked ?? defaultChecked;
+
+  return (
+    <UISwitch
+      label={String(label || "")}
+      description={description ? String(description) : undefined}
+      checked={Boolean(isChecked) || undefined}
+      disabled={Boolean(disabled) || undefined}
+      onChange={() => undefined}
+    />
+  );
+};
+
+export const DateField: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    label,
+    description,
+    error,
+    value = "",
+    size = "medium",
+    disabled,
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <UIDateField
+      label={String(label || "")}
+      description={description ? String(description) : undefined}
+      error={error ? String(error) : undefined}
+      value={String(value ?? "")}
+      size={size as "small" | "medium" | "large"}
+      disabled={Boolean(disabled) || undefined}
+      onChange={() => undefined}
+    />
+  );
+};
+
+// =========================================================================
+// Button Components
+// =========================================================================
+export const Button: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+  onAction,
+}) => {
+  const {
+    label,
+    action,
+    actionParams,
+    type = "primary",
+    size = "medium",
+    disabled,
+    pending,
+    href,
+  } = element.props as Record<string, unknown>;
+
+  const handlePress = action
+    ? () => {
+        emit("press");
+        if (onAction) {
+          onAction(
+            String(action),
+            actionParams as Record<string, unknown> | undefined,
+          );
+        }
+      }
+    : undefined;
+
+  return (
+    <UIButton
+      type={type as "primary" | "secondary" | "destructive"}
+      size={size as "small" | "medium" | "large"}
+      disabled={Boolean(disabled) || undefined}
+      pending={Boolean(pending) || undefined}
+      href={href ? String(href) : undefined}
+      onPress={handlePress}
+    >
+      {String(label || "")}
+    </UIButton>
+  );
+};
+
+export const ButtonGroup: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+}) => {
+  return <UIButtonGroup>{children}</UIButtonGroup>;
+};
+
+export const Link: FunctionComponent<ExtendedRenderProps> = ({ element }) => {
+  const {
+    label,
+    href,
+    type = "primary",
+    external,
+  } = element.props as Record<string, unknown>;
+  return (
+    <UILink
+      href={String(href || "")}
+      type={type as "primary" | "secondary"}
+      external={Boolean(external) || undefined}
+    >
+      {String(label || "")}
+    </UILink>
+  );
+};
+
+// =========================================================================
+// Chart Components
+// =========================================================================
+export const BarChart: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    data = [],
+    xKey,
+    yKey,
+    colorKey,
+    showAxis = "both",
+    showGrid = "none",
+    showLegend,
+    showTooltip = true,
+  } = element.props as Record<string, unknown>;
+
+  const chartData = Array.isArray(data) ? data : [];
+
+  if (!chartData || !Array.isArray(chartData) || chartData.length === 0) {
+    return (
+      <Box css={{ color: "secondary", font: "caption" }}>No data available</Box>
+    );
+  }
+
+  return (
+    <UIBarChart
+      data={chartData}
+      x={String(xKey || "x")}
+      y={String(yKey || "y")}
+      color={colorKey ? String(colorKey) : undefined}
+      axis={coerceAxisGrid(showAxis, "both")}
+      grid={coerceAxisGrid(showGrid, "none")}
+      legend={Boolean(showLegend) || undefined}
+      tooltip={Boolean(showTooltip)}
+    />
+  );
+};
+
+function coerceAxisGrid(
+  val: unknown,
+  fallback: string,
+): "x" | "y" | "both" | "none" {
+  if (val === true) return "both";
+  if (val === false) return "none";
+  const s = String(val ?? fallback);
+  if (["x", "y", "both", "none"].includes(s))
+    return s as "x" | "y" | "both" | "none";
+  return fallback as "x" | "y" | "both" | "none";
+}
+
+export const LineChart: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    data = [],
+    xKey,
+    yKey,
+    colorKey,
+    showAxis = "both",
+    showGrid = "none",
+    showLegend,
+    showTooltip = true,
+  } = element.props as Record<string, unknown>;
+
+  const chartData = Array.isArray(data) ? data : [];
+
+  if (!chartData || !Array.isArray(chartData) || chartData.length === 0) {
+    return (
+      <Box css={{ color: "secondary", font: "caption" }}>No data available</Box>
+    );
+  }
+
+  return (
+    <UILineChart
+      data={chartData}
+      x={String(xKey || "x")}
+      y={String(yKey || "y")}
+      color={colorKey ? String(colorKey) : undefined}
+      axis={coerceAxisGrid(showAxis, "both")}
+      grid={coerceAxisGrid(showGrid, "none")}
+      legend={Boolean(showLegend) || undefined}
+      tooltip={Boolean(showTooltip)}
+    />
+  );
+};
+
+export const Sparkline: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    data = [],
+    xKey,
+    yKey,
+    showTooltip,
+  } = element.props as Record<string, unknown>;
+
+  const chartData = Array.isArray(data) ? data : [];
+
+  if (!chartData || !Array.isArray(chartData) || chartData.length === 0) {
+    return <Box css={{ color: "secondary", font: "caption" }}>—</Box>;
+  }
+
+  return (
+    <UISparkline
+      data={chartData}
+      x={String(xKey || "x")}
+      y={String(yKey || "y")}
+      tooltip={Boolean(showTooltip) || undefined}
+    />
+  );
+};
+
+// =========================================================================
+// Table Component
+// =========================================================================
+export const DataTable: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    title,
+    data = [],
+    columns = [],
+    emptyMessage,
+  } = element.props as Record<string, unknown>;
+
+  const tableData = Array.isArray(data) ? data : [];
+  const cols = columns as Array<{ key: string; label: string }>;
+
+  if (!tableData || tableData.length === 0) {
+    return (
+      <Box css={{ stack: "y", gap: "small" }}>
+        {title && (
+          <Box css={{ font: "subtitle", fontWeight: "semibold" }}>
+            {String(title)}
+          </Box>
+        )}
+        <Box css={{ color: "secondary", font: "caption" }}>
+          {String(emptyMessage || "No data")}
+        </Box>
+      </Box>
+    );
+  }
+
+  return (
+    <Box css={{ stack: "y", gap: "small" }}>
+      {title && (
+        <Box css={{ font: "subtitle", fontWeight: "semibold" }}>
+          {String(title)}
+        </Box>
+      )}
+      <Table>
+        <TableHead>
+          <TableRow>
+            {cols.map((col: { key: string; label: string }) => (
+              <TableHeaderCell key={col.key}>{col.label}</TableHeaderCell>
+            ))}
+          </TableRow>
+        </TableHead>
+        <TableBody>
+          {tableData.map((row, idx) => (
+            <TableRow key={idx}>
+              {cols.map((col: { key: string; label: string }) => (
+                <TableCell key={col.key}>
+                  {String(row[col.key] ?? "")}
+                </TableCell>
+              ))}
+            </TableRow>
+          ))}
+        </TableBody>
+      </Table>
+    </Box>
+  );
+};
+
+// =========================================================================
+// Stripe-Specific Card Components
+// =========================================================================
+export const CustomerCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+}) => {
+  const {
+    name,
+    email,
+    status = "active",
+    customerId,
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "neutral",
+      }}
+    >
+      <Box css={{ stack: "x", distribute: "space-between", alignY: "center" }}>
+        <Box css={{ font: "subtitle", fontWeight: "semibold" }}>
+          {String(name || "")}
+        </Box>
+        <UIBadge type={status === "active" ? "positive" : "neutral"}>
+          {String(status)}
+        </UIBadge>
+      </Box>
+      <Box css={{ color: "secondary" }}>{String(email || "")}</Box>
+      {customerId && (
+        <UIButton type="secondary" size="small" onPress={() => emit("press")}>
+          View Details
+        </UIButton>
+      )}
+    </Box>
+  );
+};
+
+export const PaymentCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+}) => {
+  const {
+    amount,
+    currency = "usd",
+    status = "succeeded",
+    description,
+    paymentId,
+  } = element.props as Record<string, unknown>;
+
+  const statusColors: Record<
+    string,
+    "positive" | "warning" | "negative" | "neutral" | "info"
+  > = {
+    succeeded: "positive",
+    pending: "warning",
+    failed: "negative",
+    canceled: "neutral",
+    requires_action: "info",
+  };
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "neutral",
+      }}
+    >
+      <Box css={{ stack: "x", distribute: "space-between", alignY: "center" }}>
+        <Box css={{ font: "title", fontWeight: "bold" }}>
+          {formatCurrency(Number(amount) || 0, String(currency))}
+        </Box>
+        <UIBadge type={statusColors[String(status)] || "neutral"}>
+          {String(status)}
+        </UIBadge>
+      </Box>
+      {description && (
+        <Box css={{ color: "secondary" }}>{String(description)}</Box>
+      )}
+      {paymentId && (
+        <Box css={{ stack: "x", gap: "small" }}>
+          <UIButton type="secondary" size="small" onPress={() => emit("press")}>
+            View
+          </UIButton>
+          <UIButton
+            type="destructive"
+            size="small"
+            onPress={() => emit("press")}
+          >
+            Refund
+          </UIButton>
+        </Box>
+      )}
+    </Box>
+  );
+};
+
+export const SubscriptionCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    planName,
+    status = "active",
+    amount,
+    currency = "usd",
+    interval = "month",
+    currentPeriodEnd,
+  } = element.props as Record<string, unknown>;
+
+  const statusColors: Record<
+    string,
+    "positive" | "warning" | "negative" | "neutral" | "info"
+  > = {
+    active: "positive",
+    trialing: "info",
+    past_due: "warning",
+    canceled: "neutral",
+    unpaid: "negative",
+    incomplete: "warning",
+  };
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "neutral",
+      }}
+    >
+      <Box css={{ stack: "x", distribute: "space-between", alignY: "center" }}>
+        <Box css={{ font: "subtitle", fontWeight: "semibold" }}>
+          {String(planName || "")}
+        </Box>
+        <UIBadge type={statusColors[String(status)] || "neutral"}>
+          {String(status)}
+        </UIBadge>
+      </Box>
+      <Box css={{ font: "title", fontWeight: "bold" }}>
+        {formatCurrency(Number(amount) || 0, String(currency))}/
+        {String(interval)}
+      </Box>
+      {currentPeriodEnd && (
+        <Box css={{ color: "secondary", font: "caption" }}>
+          Renews: {String(currentPeriodEnd)}
+        </Box>
+      )}
+    </Box>
+  );
+};
+
+export const InvoiceCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+}) => {
+  const {
+    invoiceNumber,
+    amount,
+    currency = "usd",
+    status = "open",
+    dueDate,
+    customerEmail,
+  } = element.props as Record<string, unknown>;
+
+  const statusColors: Record<
+    string,
+    "positive" | "warning" | "negative" | "neutral" | "info"
+  > = {
+    draft: "neutral",
+    open: "info",
+    paid: "positive",
+    void: "neutral",
+    uncollectible: "negative",
+  };
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "neutral",
+      }}
+    >
+      <Box css={{ stack: "x", distribute: "space-between", alignY: "center" }}>
+        <Box css={{ font: "subtitle", fontWeight: "semibold" }}>
+          {String(invoiceNumber || "")}
+        </Box>
+        <UIBadge type={statusColors[String(status)] || "neutral"}>
+          {String(status)}
+        </UIBadge>
+      </Box>
+      <Box css={{ font: "title", fontWeight: "bold" }}>
+        {formatCurrency(Number(amount) || 0, String(currency))}
+      </Box>
+      {customerEmail && (
+        <Box css={{ color: "secondary" }}>{String(customerEmail)}</Box>
+      )}
+      {dueDate && (
+        <Box css={{ color: "secondary", font: "caption" }}>
+          Due: {String(dueDate)}
+        </Box>
+      )}
+      {status === "open" && (
+        <UIButton type="primary" size="small" onPress={() => emit("press")}>
+          Send Invoice
+        </UIButton>
+      )}
+    </Box>
+  );
+};
+
+export const RefundCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    amount,
+    currency = "usd",
+    status = "succeeded",
+    reason,
+  } = element.props as Record<string, unknown>;
+
+  const statusColors: Record<
+    string,
+    "positive" | "warning" | "negative" | "neutral"
+  > = {
+    pending: "warning",
+    succeeded: "positive",
+    failed: "negative",
+    canceled: "neutral",
+  };
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "neutral",
+      }}
+    >
+      <Box css={{ stack: "x", distribute: "space-between", alignY: "center" }}>
+        <Box css={{ font: "title", fontWeight: "bold" }}>
+          {formatCurrency(Number(amount) || 0, String(currency))}
+        </Box>
+        <UIBadge type={statusColors[String(status)] || "neutral"}>
+          {String(status)}
+        </UIBadge>
+      </Box>
+      {reason && <Box css={{ color: "secondary" }}>{String(reason)}</Box>}
+    </Box>
+  );
+};
+
+export const DisputeCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    amount,
+    currency = "usd",
+    status = "needs_response",
+    reason,
+    dueDate,
+  } = element.props as Record<string, unknown>;
+
+  const statusColors: Record<
+    string,
+    "positive" | "warning" | "negative" | "neutral" | "urgent" | "info"
+  > = {
+    warning_needs_response: "urgent",
+    warning_under_review: "warning",
+    warning_closed: "neutral",
+    needs_response: "urgent",
+    under_review: "warning",
+    won: "positive",
+    lost: "negative",
+  };
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "critical",
+      }}
+    >
+      <Box css={{ stack: "x", distribute: "space-between", alignY: "center" }}>
+        <Box css={{ font: "title", fontWeight: "bold" }}>
+          {formatCurrency(Number(amount) || 0, String(currency))}
+        </Box>
+        <UIBadge type={statusColors[String(status)] || "warning"}>
+          {String(status).replace(/_/g, " ")}
+        </UIBadge>
+      </Box>
+      {reason && <Box css={{ color: "secondary" }}>{String(reason)}</Box>}
+      {dueDate && (
+        <Box css={{ color: "critical", font: "caption" }}>
+          Response due: {String(dueDate)}
+        </Box>
+      )}
+    </Box>
+  );
+};
+
+export const BalanceCard: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  const {
+    available,
+    pending,
+    currency = "usd",
+  } = element.props as Record<string, unknown>;
+
+  return (
+    <Box
+      css={{
+        stack: "y",
+        gap: "small",
+        padding: "medium",
+        borderRadius: "medium",
+        keyline: "neutral",
+      }}
+    >
+      <Box css={{ font: "subtitle", fontWeight: "semibold" }}>Balance</Box>
+      <Box css={{ stack: "x", distribute: "space-between" }}>
+        <Box>
+          <Box css={{ font: "caption", color: "secondary" }}>Available</Box>
+          <Box css={{ font: "title", fontWeight: "bold", color: "success" }}>
+            {formatCurrency(Number(available) || 0, String(currency))}
+          </Box>
+        </Box>
+        <Box>
+          <Box css={{ font: "caption", color: "secondary" }}>Pending</Box>
+          <Box css={{ font: "title", fontWeight: "bold" }}>
+            {formatCurrency(Number(pending) || 0, String(currency))}
+          </Box>
+        </Box>
+      </Box>
+    </Box>
+  );
+};
+
+// =========================================================================
+// Chip Components
+// =========================================================================
+export const Chip: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  emit,
+}) => {
+  const { label, value, removable, action } = element.props as Record<
+    string,
+    unknown
+  >;
+  return (
+    <UIChip
+      label={String(label || "")}
+      value={value ? String(value) : undefined}
+      onClose={removable && action ? () => emit("remove") : undefined}
+    />
+  );
+};
+
+export const ChipList: FunctionComponent<ExtendedRenderProps> = ({
+  children,
+}) => {
+  return <UIChipList>{children}</UIChipList>;
+};
+
+// =========================================================================
+// Tooltip Component
+// =========================================================================
+export const Tooltip: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+  children,
+}) => {
+  const { content, placement = "top" } = element.props as Record<
+    string,
+    unknown
+  >;
+
+  // Tooltip needs a trigger element - use children if available
+  if (!children) {
+    return <Box>{String(content || "")}</Box>;
+  }
+
+  return (
+    <UITooltip
+      trigger={<Box>{children}</Box>}
+      placement={placement as "top" | "bottom" | "left" | "right"}
+    >
+      <Box>{String(content || "")}</Box>
+    </UITooltip>
+  );
+};
+
+// =========================================================================
+// Fallback Component
+// =========================================================================
+export const Fallback: FunctionComponent<ExtendedRenderProps> = ({
+  element,
+}) => {
+  return (
+    <Box css={{ padding: "small", keyline: "critical", borderRadius: "small" }}>
+      <Box css={{ color: "critical", font: "caption" }}>
+        Unknown component: {element.type}
+      </Box>
+    </Box>
+  );
+};
+
+// =========================================================================
+// Component Map Export
+// =========================================================================
+export const components: Record<
+  string,
+  FunctionComponent<ExtendedRenderProps>
+> = {
+  // Layout
+  Stack,
+  Inline,
+  Divider,
+  Accordion,
+  AccordionItem,
+  // Typography
+  Heading,
+  Text,
+  // Data Display
+  Metric,
+  Badge,
+  Icon,
+  Img,
+  Spinner,
+  // Feedback
+  Banner,
+  // Lists
+  List,
+  ListItem,
+  PropertyList,
+  PropertyListItem,
+  TaskList,
+  TaskListItem,
+  // Menus
+  Menu,
+  MenuItem,
+  MenuGroup,
+  // Forms
+  TextField,
+  TextArea,
+  Select,
+  Checkbox,
+  Radio,
+  Switch,
+  DateField,
+  // Buttons
+  Button,
+  ButtonGroup,
+  Link,
+  // Charts
+  BarChart,
+  LineChart,
+  Sparkline,
+  // Tables
+  DataTable,
+  // Stripe Cards
+  CustomerCard,
+  PaymentCard,
+  SubscriptionCard,
+  InvoiceCard,
+  RefundCard,
+  DisputeCard,
+  BalanceCard,
+  // Chips
+  Chip,
+  ChipList,
+  // Tooltip
+  Tooltip,
+  // Fallback
+  Fallback,
+};

+ 11 - 0
examples/stripe-app/fullpage-app/src/lib/render/index.ts

@@ -0,0 +1,11 @@
+// Catalog
+export { stripeCatalog } from "./catalog";
+
+// Components
+export { components, Fallback } from "./catalog/components";
+
+// Actions
+export { actionHandlers, executeAction } from "./catalog/actions";
+
+// Renderer
+export { StripeRenderer, type StripeRendererProps } from "./renderer";

+ 145 - 0
examples/stripe-app/fullpage-app/src/lib/render/renderer.tsx

@@ -0,0 +1,145 @@
+import { useMemo, useRef, type ReactNode } from "react";
+import {
+  Renderer,
+  type ComponentRegistry,
+  type Spec,
+  StateProvider,
+  VisibilityProvider,
+  ActionProvider,
+} from "@json-render/react";
+
+import { components, Fallback } from "./catalog/components";
+import { executeAction } from "./catalog/actions";
+
+// =============================================================================
+// Types
+// =============================================================================
+
+type SetState = (
+  updater: (prev: Record<string, unknown>) => Record<string, unknown>,
+) => void;
+
+export interface StripeRendererProps {
+  /** The UI spec to render (from json-render) */
+  spec: Spec | null;
+  /** Data context for components */
+  data?: Record<string, unknown>;
+  /** Function to update data */
+  setData?: SetState;
+  /** Callback when data changes */
+  onStateChange?: (path: string, value: unknown) => void;
+  /** Whether the spec is currently loading/streaming */
+  loading?: boolean;
+}
+
+// =============================================================================
+// Build Registry
+// =============================================================================
+
+/**
+ * Build a component registry from our components map.
+ * Uses refs to avoid recreating on data changes.
+ */
+function buildRegistry(
+  dataRef: React.RefObject<Record<string, unknown>>,
+  setDataRef: React.RefObject<SetState | undefined>,
+  loading?: boolean,
+): ComponentRegistry {
+  const registry: ComponentRegistry = {};
+
+  for (const [name, Component] of Object.entries(components)) {
+    const noop = () => {};
+    registry[name] = (renderProps: {
+      element: { type: string; props: Record<string, unknown> };
+      children?: ReactNode;
+      emit?: (event: string) => void;
+    }) =>
+      Component({
+        element: renderProps.element,
+        children: renderProps.children,
+        emit: renderProps.emit ?? noop,
+        loading,
+        state: dataRef.current,
+        getValue: (path: string) => {
+          const data = dataRef.current;
+          const parts = path.replace(/^\//, "").split("/");
+          let current: unknown = data;
+          for (const part of parts) {
+            if (current && typeof current === "object" && part in current) {
+              current = (current as Record<string, unknown>)[part];
+            } else {
+              return undefined;
+            }
+          }
+          return current;
+        },
+        onAction: (actionName: string, params?: Record<string, unknown>) => {
+          const setState = setDataRef.current;
+          if (setState) {
+            executeAction(actionName, params, setState, dataRef.current);
+          }
+        },
+      });
+  }
+
+  return registry;
+}
+
+/**
+ * Fallback component for unknown types
+ */
+const fallbackRegistry = (renderProps: {
+  element: { type: string; props: Record<string, unknown> };
+}) => <Fallback element={renderProps.element} />;
+
+// =============================================================================
+// StripeRenderer Component
+// =============================================================================
+
+/**
+ * Main renderer component for Stripe UIXT.
+ *
+ * Wraps the json-render Renderer with all necessary providers
+ * and connects it to the Stripe component implementations.
+ */
+export function StripeRenderer({
+  spec,
+  data = {},
+  setData,
+  onStateChange,
+  loading,
+}: StripeRendererProps): ReactNode {
+  // Use refs to keep registry stable while still accessing latest data/setData
+  const dataRef = useRef(data);
+  const setDataRef = useRef(setData);
+  dataRef.current = data;
+  setDataRef.current = setData;
+
+  // Memoize registry - only changes when loading changes
+  const registry = useMemo(
+    () => buildRegistry(dataRef, setDataRef, loading),
+    [loading],
+  );
+
+  const mergedState = useMemo(
+    () => (spec?.state ? { ...data, ...spec.state } : data),
+    [data, spec?.state],
+  );
+
+  if (!spec) return null;
+
+  return (
+    <StateProvider initialState={mergedState} onStateChange={onStateChange}>
+      <VisibilityProvider>
+        <ActionProvider>
+          <Renderer
+            spec={spec}
+            registry={registry}
+            fallback={fallbackRegistry}
+            loading={loading}
+          />
+        </ActionProvider>
+      </VisibilityProvider>
+    </StateProvider>
+  );
+}

+ 146 - 0
examples/stripe-app/fullpage-app/src/lib/stream-spec.ts

@@ -0,0 +1,146 @@
+import type { Spec } from "@json-render/react";
+
+interface JsonPatch {
+  op: string;
+  path: string;
+  value?: unknown;
+  from?: string;
+}
+
+function setDeep(
+  obj: Record<string, unknown>,
+  segments: string[],
+  value: unknown,
+): void {
+  let current: unknown = obj;
+  for (let i = 0; i < segments.length - 1; i++) {
+    const seg = segments[i];
+    const next = (current as Record<string, unknown>)[seg];
+    if (next && typeof next === "object") {
+      current = next;
+    } else {
+      const container = /^\d+$/.test(segments[i + 1]) ? [] : {};
+      (current as Record<string, unknown>)[seg] = container;
+      current = container;
+    }
+  }
+  const last = segments[segments.length - 1];
+  if (Array.isArray(current)) {
+    (current as unknown[])[Number(last)] = value;
+  } else {
+    (current as Record<string, unknown>)[last] = value;
+  }
+}
+
+function setSpecValue(spec: Spec, path: string, value: unknown): void {
+  if (path === "/root") {
+    (spec as Record<string, unknown>).root = value as string;
+    return;
+  }
+  if (path === "/state") {
+    (spec as Record<string, unknown>).state = value;
+    return;
+  }
+  if (path.startsWith("/state/")) {
+    if (!spec.state) (spec as Record<string, unknown>).state = {};
+    const segments = path.slice("/state/".length).split("/");
+    setDeep(spec.state as Record<string, unknown>, segments, value);
+    return;
+  }
+  const elemMatch = path.match(/^\/elements\/(.+)/);
+  if (elemMatch) {
+    spec.elements[elemMatch[1]] = value as Spec["elements"][string];
+  }
+}
+
+function applyPatch(spec: Spec, patch: JsonPatch): Spec {
+  const next: Spec = {
+    ...spec,
+    elements: { ...spec.elements },
+    ...(spec.state ? { state: { ...spec.state } } : {}),
+  };
+  switch (patch.op) {
+    case "add":
+    case "replace":
+      setSpecValue(next, patch.path, patch.value);
+      break;
+    case "remove":
+      if (patch.path.startsWith("/elements/")) {
+        const key = patch.path.slice("/elements/".length);
+        delete next.elements[key];
+      }
+      break;
+  }
+  return next;
+}
+
+/**
+ * Fetch an AI generation endpoint that returns a JSONL patch stream,
+ * applying patches progressively via onPatch.
+ */
+export async function streamSpec(
+  url: string,
+  body: Record<string, unknown>,
+  onPatch: (spec: Spec) => void,
+): Promise<Spec> {
+  const response = await fetch(url, {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify(body),
+  });
+
+  if (!response.ok) {
+    let msg = `API error: ${response.status}`;
+    try {
+      const errData = await response.json();
+      if (errData.error) msg = errData.error;
+    } catch {
+      // use default
+    }
+    throw new Error(msg);
+  }
+
+  const reader = response.body?.getReader();
+  if (!reader) throw new Error("No response body");
+
+  const decoder = new TextDecoder();
+  let buffer = "";
+  let spec: Spec = { root: "", elements: {} };
+
+  while (true) {
+    const { done, value } = await reader.read();
+    if (done) break;
+
+    buffer += decoder.decode(value, { stream: true });
+    const lines = buffer.split("\n");
+    buffer = lines.pop() ?? "";
+
+    for (const line of lines) {
+      const trimmed = line.trim();
+      if (!trimmed || trimmed.startsWith("//")) continue;
+      try {
+        const parsed = JSON.parse(trimmed) as JsonPatch;
+        if (parsed.op) {
+          spec = applyPatch(spec, parsed);
+          onPatch(spec);
+        }
+      } catch {
+        // skip non-JSON lines
+      }
+    }
+  }
+
+  if (buffer.trim()) {
+    try {
+      const parsed = JSON.parse(buffer.trim()) as JsonPatch;
+      if (parsed.op) {
+        spec = applyPatch(spec, parsed);
+        onPatch(spec);
+      }
+    } catch {
+      // skip
+    }
+  }
+
+  return spec;
+}

+ 37 - 0
examples/stripe-app/fullpage-app/src/lib/stripe.ts

@@ -0,0 +1,37 @@
+import Stripe from "stripe";
+import {
+  createHttpClient,
+  STRIPE_API_KEY,
+} from "@stripe/ui-extension-sdk/http_client";
+
+/**
+ * Create a Stripe client for use in Stripe Apps.
+ *
+ * The Stripe UI Extension SDK handles authentication automatically
+ * through the httpClient - no real API key is needed.
+ */
+export const stripe = new Stripe(STRIPE_API_KEY, {
+  httpClient: createHttpClient(),
+  apiVersion: "2024-12-18.acacia",
+});
+
+/**
+ * Format amount for display (converts cents to dollars)
+ */
+export function formatAmount(amount: number, currency = "usd"): string {
+  return new Intl.NumberFormat("en-US", {
+    style: "currency",
+    currency: currency.toUpperCase(),
+  }).format(amount / 100);
+}
+
+/**
+ * Format date for display
+ */
+export function formatDate(timestamp: number): string {
+  return new Date(timestamp * 1000).toLocaleDateString("en-US", {
+    year: "numeric",
+    month: "short",
+    day: "numeric",
+  });
+}

+ 308 - 0
examples/stripe-app/fullpage-app/src/views/FullPage.tsx

@@ -0,0 +1,308 @@
+import { useState, useCallback, useEffect } from "react";
+import type { Spec } from "@json-render/react";
+import {
+  Box,
+  FullPageView,
+  FullPageTabs,
+  FullPageTab,
+  Button,
+  TextField,
+  Divider,
+  List,
+  ListItem,
+  Spinner,
+} from "@stripe/ui-extension-sdk/ui";
+import type { ExtensionContextValue } from "@stripe/ui-extension-sdk/context";
+
+import { stripeCatalog, StripeRenderer } from "../lib/render";
+import { executeAction } from "../lib/render/catalog/actions";
+import { API_GENERATE_URL } from "../lib/config";
+import { streamSpec } from "../lib/stream-spec";
+
+function createOverviewSpec(data: Record<string, unknown>): Spec {
+  const customers = data.customers as { total?: number } | undefined;
+  const payments = data.payments as
+    | { totalVolume?: string; successRate?: string }
+    | undefined;
+  const subscriptions = data.subscriptions as
+    | { active?: number; trialing?: number; pastDue?: number }
+    | undefined;
+
+  return {
+    root: "root",
+    elements: {
+      root: {
+        type: "Stack",
+        props: { direction: "vertical", gap: "large" },
+        children: ["metrics", "divider", "refresh"],
+      },
+      metrics: {
+        type: "Stack",
+        props: { direction: "horizontal", gap: "medium" },
+        children: ["m1", "m2", "m3"],
+      },
+      m1: {
+        type: "Metric",
+        props: {
+          label: "Payment Volume",
+          value: payments?.totalVolume ?? "$0",
+          change: payments?.successRate
+            ? `${payments.successRate} success`
+            : null,
+          changeType: "positive",
+        },
+        children: [],
+      },
+      m2: {
+        type: "Metric",
+        props: {
+          label: "Active Subscriptions",
+          value: String(subscriptions?.active ?? 0),
+          change: subscriptions?.trialing
+            ? `+${subscriptions.trialing} trialing`
+            : null,
+          changeType: "positive",
+        },
+        children: [],
+      },
+      m3: {
+        type: "Metric",
+        props: {
+          label: "Total Customers",
+          value: String(customers?.total ?? 0),
+          changeType: "neutral",
+        },
+        children: [],
+      },
+      divider: { type: "Divider", props: {}, children: [] },
+      refresh: {
+        type: "Button",
+        props: {
+          label: "Refresh Data",
+          action: "refreshData",
+          type: "secondary",
+        },
+        children: [],
+      },
+    },
+  };
+}
+
+const componentCatalogInfo = Object.entries(stripeCatalog.data.components).map(
+  ([name, def]) => ({
+    name,
+    description: def.description,
+  }),
+);
+
+const actionsCatalogInfo = Object.entries(stripeCatalog.data.actions).map(
+  ([name, def]) => ({
+    name,
+    description: def.description,
+  }),
+);
+
+const FullPage = (_props: ExtensionContextValue) => {
+  const [prompt, setPrompt] = useState("");
+  const [isGenerating, setIsGenerating] = useState(false);
+  const [isLoading, setIsLoading] = useState(true);
+  const [currentSpec, setCurrentSpec] = useState<Spec | null>(null);
+  const [data, setState] = useState<Record<string, unknown>>({});
+  const [error, setError] = useState<string | null>(null);
+
+  const handleSetState = useCallback(
+    (updater: (prev: Record<string, unknown>) => Record<string, unknown>) => {
+      setState((prev) => updater(prev));
+    },
+    [],
+  );
+
+  useEffect(() => {
+    async function loadData() {
+      setIsLoading(true);
+      try {
+        await executeAction("refreshData", {}, handleSetState, {});
+      } catch (err) {
+        console.error("Failed to load data:", err);
+        setError("Failed to load Stripe data");
+      } finally {
+        setIsLoading(false);
+      }
+    }
+    loadData();
+  }, [handleSetState]);
+
+  const handleGenerate = async () => {
+    if (!prompt.trim()) return;
+    setIsGenerating(true);
+    setError(null);
+
+    try {
+      await streamSpec(
+        API_GENERATE_URL,
+        {
+          prompt,
+          systemPrompt: stripeCatalog.prompt({
+            system:
+              "You are a Stripe dashboard builder. Generate UI specs for displaying Stripe data in a full-page layout.",
+          }),
+        },
+        (spec) => setCurrentSpec(spec),
+      );
+    } catch (err) {
+      setCurrentSpec(createOverviewSpec(data));
+      setError(
+        `Using local generation (${err instanceof Error ? err.message : "API unavailable"})`,
+      );
+    } finally {
+      setIsGenerating(false);
+    }
+  };
+
+  if (isLoading) {
+    return (
+      <FullPageView>
+        <FullPageTabs>
+          <FullPageTab
+            id="overview"
+            label="Overview"
+            content={
+              <Box
+                css={{
+                  stack: "y",
+                  gap: "medium",
+                  alignX: "center",
+                  paddingY: "xlarge",
+                }}
+              >
+                <Spinner size="large" />
+                <Box css={{ font: "body", color: "secondary" }}>
+                  Loading Stripe data...
+                </Box>
+              </Box>
+            }
+          />
+        </FullPageTabs>
+      </FullPageView>
+    );
+  }
+
+  return (
+    <FullPageView
+      pageAction={{
+        label: "Refresh Data",
+        onPress: () => executeAction("refreshData", {}, handleSetState, {}),
+      }}
+    >
+      <FullPageTabs>
+        <FullPageTab
+          id="overview"
+          label="Overview"
+          content={
+            <Box css={{ stack: "y", gap: "large", padding: "large" }}>
+              <StripeRenderer
+                spec={createOverviewSpec(data)}
+                data={data}
+                setData={handleSetState}
+                loading={false}
+              />
+            </Box>
+          }
+        />
+
+        <FullPageTab
+          id="generate"
+          label="Generate"
+          content={
+            <Box css={{ stack: "y", gap: "medium", padding: "large" }}>
+              <TextField
+                label="Describe the dashboard you want"
+                placeholder="e.g., Show MRR, payments, or subscriptions"
+                value={prompt}
+                onChange={(e) => setPrompt(e.target.value)}
+              />
+              <Button
+                type="primary"
+                onPress={handleGenerate}
+                disabled={isGenerating || !prompt.trim()}
+              >
+                {isGenerating ? "Generating..." : "Generate"}
+              </Button>
+
+              {error && (
+                <Box css={{ color: "secondary", font: "caption" }}>{error}</Box>
+              )}
+
+              {currentSpec && (
+                <>
+                  <Divider />
+                  <Box css={{ font: "subheading" }}>
+                    Preview (with real Stripe data)
+                  </Box>
+                  <Box
+                    css={{
+                      padding: "medium",
+                      borderRadius: "medium",
+                      keyline: "neutral",
+                    }}
+                  >
+                    <StripeRenderer
+                      spec={currentSpec}
+                      data={data}
+                      setData={handleSetState}
+                      loading={isGenerating}
+                    />
+                  </Box>
+                </>
+              )}
+            </Box>
+          }
+        />
+
+        <FullPageTab
+          id="components"
+          label="Components"
+          content={
+            <Box css={{ padding: "large" }}>
+              <Box css={{ font: "subheading", marginBottom: "medium" }}>
+                Available Components ({componentCatalogInfo.length})
+              </Box>
+              <List>
+                {componentCatalogInfo.map((comp) => (
+                  <ListItem
+                    key={comp.name}
+                    title={comp.name}
+                    secondaryTitle={comp.description}
+                  />
+                ))}
+              </List>
+            </Box>
+          }
+        />
+
+        <FullPageTab
+          id="actions"
+          label="Actions"
+          content={
+            <Box css={{ padding: "large" }}>
+              <Box css={{ font: "subheading", marginBottom: "medium" }}>
+                Available Actions ({actionsCatalogInfo.length})
+              </Box>
+              <List>
+                {actionsCatalogInfo.map((action) => (
+                  <ListItem
+                    key={action.name}
+                    title={action.name}
+                    secondaryTitle={action.description}
+                  />
+                ))}
+              </List>
+            </Box>
+          }
+        />
+      </FullPageTabs>
+    </FullPageView>
+  );
+};
+
+export default FullPage;

+ 4 - 0
examples/stripe-app/fullpage-app/src/views/brand_icon.svg

@@ -0,0 +1,4 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M24.218 9.10285C24.4008 8.99422 24.5577 8.84887 24.682 8.67797L17.5155 4.41809C16.5777 3.86064 15.4223 3.86064 14.4845 4.41809L7.31798 8.67797C7.44229 8.84887 7.59924 8.99422 7.78199 9.10285L15.2509 13.5425C15.7163 13.8192 16.2837 13.8192 16.7491 13.5425L24.218 9.10285Z" fill="#FFFFFF"/>
+<path d="M26.5 11.5603C26.5 11.4215 26.491 11.2841 26.4736 11.1486C26.2529 11.3479 26.0113 11.5268 25.7509 11.6817L18.282 16.1213C16.8718 16.9596 15.1282 16.9596 13.718 16.1213L6.2491 11.6817C5.98866 11.5268 5.7471 11.3479 5.52644 11.1486C5.50895 11.2841 5.5 11.4215 5.5 11.5603V20.4397C5.5 21.5546 6.07772 22.5848 7.01554 23.1423L14.4845 27.5819C15.4223 28.1394 16.5777 28.1394 17.5155 27.5819L24.9845 23.1423C25.9223 22.5848 26.5 21.5546 26.5 20.4397V11.5603Z" fill="#FFFFFF"/>
+</svg>

+ 97 - 0
examples/stripe-app/fullpage-app/stripe-app.template.json

@@ -0,0 +1,97 @@
+{
+    "id": "com.example.json-render-demo",
+    "version": "0.0.1",
+    "name": "json-render Full Page Demo",
+    "icon": "",
+    "permissions": [
+        {
+            "permission": "customer_read",
+            "purpose": "Display customer information in dashboards"
+        },
+        {
+            "permission": "customer_write",
+            "purpose": "Create and manage customers"
+        },
+        {
+            "permission": "payment_intent_read",
+            "purpose": "Display payment information in dashboards"
+        },
+        {
+            "permission": "charge_read",
+            "purpose": "Display charge information and details"
+        },
+        {
+            "permission": "subscription_read",
+            "purpose": "Display subscription information in dashboards"
+        },
+        {
+            "permission": "subscription_write",
+            "purpose": "Manage subscriptions"
+        },
+        {
+            "permission": "invoice_read",
+            "purpose": "Display invoice information in dashboards"
+        },
+        {
+            "permission": "invoice_write",
+            "purpose": "Send and manage invoices"
+        },
+        {
+            "permission": "charge_write",
+            "purpose": "Process charges and refunds"
+        },
+        {
+            "permission": "product_read",
+            "purpose": "Display product catalog"
+        },
+        {
+            "permission": "product_write",
+            "purpose": "Create and manage products"
+        },
+        {
+            "permission": "plan_read",
+            "purpose": "Display pricing information"
+        },
+        {
+            "permission": "plan_write",
+            "purpose": "Create and manage prices"
+        },
+        {
+            "permission": "balance_read",
+            "purpose": "Display account balance"
+        },
+        {
+            "permission": "payout_read",
+            "purpose": "Display payout information"
+        },
+        {
+            "permission": "event_read",
+            "purpose": "Display webhook events"
+        },
+        {
+            "permission": "dispute_read",
+            "purpose": "Display dispute information"
+        },
+        {
+            "permission": "coupon_read",
+            "purpose": "Display coupon information"
+        },
+        {
+            "permission": "coupon_write",
+            "purpose": "Create and manage coupons"
+        }
+    ],
+    "ui_extension": {
+        "views": [
+            {
+                "viewport": "stripe.dashboard.fullpage",
+                "component": "FullPage"
+            }
+        ],
+        "content_security_policy": {
+            "connect-src": null,
+            "image-src": null,
+            "purpose": "No external connections needed for core functionality"
+        }
+    }
+}

+ 3 - 0
examples/stripe-app/fullpage-app/tsconfig.json

@@ -0,0 +1,3 @@
+{
+  "extends": "@stripe/ui-extension-tools/tsconfig.ui-extension"
+}

+ 1 - 0
examples/stripe-app/fullpage-app/ui-extensions.d.ts

@@ -0,0 +1 @@
+/// <reference types="@stripe/ui-extension-tools" />

+ 155 - 8
pnpm-lock.yaml

@@ -560,24 +560,83 @@ importers:
         specifier: ^5.7.2
         version: 5.9.2
 
-  examples/stripe-app:
+  examples/stripe-app/api:
+    dependencies:
+      '@ai-sdk/gateway':
+        specifier: ^3.0.50
+        version: 3.0.50(zod@4.3.5)
+      ai:
+        specifier: ^6.0.91
+        version: 6.0.91(zod@4.3.5)
+      next:
+        specifier: ^16.1.6
+        version: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+      react:
+        specifier: ^19.1.0
+        version: 19.2.4
+      react-dom:
+        specifier: ^19.1.0
+        version: 19.2.4(react@19.2.4)
+    devDependencies:
+      '@types/node':
+        specifier: ^22.0.0
+        version: 22.19.6
+      '@types/react':
+        specifier: ^19.1.0
+        version: 19.2.3
+      typescript:
+        specifier: ^5.4.5
+        version: 5.9.2
+
+  examples/stripe-app/drawer-app:
     dependencies:
       '@json-render/core':
         specifier: workspace:*
-        version: link:../../packages/core
+        version: link:../../../packages/core
       '@json-render/react':
         specifier: workspace:*
-        version: link:../../packages/react
+        version: link:../../../packages/react
       '@stripe/ui-extension-sdk':
         specifier: ^9.1.0
         version: 9.1.0(stripe@13.11.0)
       stripe:
         specifier: ^13.11.0
         version: 13.11.0
+      zod:
+        specifier: ^4.0.0
+        version: 4.3.5
     devDependencies:
       '@repo/eslint-config':
         specifier: workspace:*
-        version: link:../../packages/eslint-config
+        version: link:../../../packages/eslint-config
+      '@stripe/ui-extension-tools':
+        specifier: ^0.0.1
+        version: 0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))
+      eslint:
+        specifier: ^9.39.0
+        version: 9.39.2(jiti@2.6.1)
+
+  examples/stripe-app/fullpage-app:
+    dependencies:
+      '@json-render/core':
+        specifier: workspace:*
+        version: link:../../../packages/core
+      '@json-render/react':
+        specifier: workspace:*
+        version: link:../../../packages/react
+      '@stripe/ui-extension-sdk':
+        specifier: 9.2.0-alpha.0
+        version: 9.2.0-alpha.0(stripe@13.11.0)
+      stripe:
+        specifier: ^13.11.0
+        version: 13.11.0
+      zod:
+        specifier: ^4.0.0
+        version: 4.3.5
+    devDependencies:
+      '@repo/eslint-config':
+        specifier: workspace:*
+        version: link:../../../packages/eslint-config
       '@stripe/ui-extension-tools':
         specifier: ^0.0.1
         version: 0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))
@@ -856,6 +915,12 @@ packages:
     peerDependencies:
       zod: ^3.25.76 || ^4.1.8
 
+  '@ai-sdk/gateway@3.0.50':
+    resolution: {integrity: sha512-Jdd1a8VgbD7l7r+COj0h5SuaYRfPvOJ/AO6l0OrmTPEcI2MUQPr3C4JttfpNkcheEN+gOdy0CtZWuG17bW2fjw==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      zod: ^3.25.76 || ^4.1.8
+
   '@ai-sdk/provider-utils@4.0.13':
     resolution: {integrity: sha512-HHG72BN4d+OWTcq2NwTxOm/2qvk1duYsnhCDtsbYwn/h/4zeqURu1S0+Cn0nY2Ysq9a9HGKvrYuMn9bgFhR2Og==}
     engines: {node: '>=18'}
@@ -868,6 +933,12 @@ packages:
     peerDependencies:
       zod: ^3.25.76 || ^4.1.8
 
+  '@ai-sdk/provider-utils@4.0.15':
+    resolution: {integrity: sha512-8XiKWbemmCbvNN0CLR9u3PQiet4gtEVIrX4zzLxnCj06AwsEDJwJVBbKrEI4t6qE8XRSIvU2irka0dcpziKW6w==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      zod: ^3.25.76 || ^4.1.8
+
   '@ai-sdk/provider-utils@4.0.5':
     resolution: {integrity: sha512-Ow/X/SEkeExTTc1x+nYLB9ZHK2WUId8+9TlkamAx7Tl9vxU+cKzWx2dwjgMHeCN6twrgwkLrrtqckQeO4mxgVA==}
     engines: {node: '>=18'}
@@ -4097,6 +4168,11 @@ packages:
     peerDependencies:
       stripe: '>= 8.195.0'
 
+  '@stripe/ui-extension-sdk@9.2.0-alpha.0':
+    resolution: {integrity: sha512-VMW2jE/y/engb/ygosJy7ZlmikX2b/wMLU+MEusj9n7SHvlVVHjQOzQA53M17n8Hq9urxhyV8BNv6S6YYsi7VA==}
+    peerDependencies:
+      stripe: '>= 8.195.0'
+
   '@stripe/ui-extension-tools@0.0.1':
     resolution: {integrity: sha512-0pOgQ3AuEUeypAgAhcJbyC9QxMaMW1OqzzxkCO4a+5ALDyIFEA6M4jDQ3H9KayNwqQ23qq+PQ0rlYY7dRACNgA==}
 
@@ -4757,6 +4833,12 @@ packages:
     peerDependencies:
       zod: ^3.25.76 || ^4.1.8
 
+  ai@6.0.91:
+    resolution: {integrity: sha512-k1/8BusZMhYVxxLZt0BUZzm9HVDCCh117nyWfWUx5xjR2+tWisJbXgysL7EBMq2lgyHwgpA1jDR3tVjWSdWZXw==}
+    engines: {node: '>=18'}
+    peerDependencies:
+      zod: ^3.25.76 || ^4.1.8
+
   ajv-formats@2.1.1:
     resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
     peerDependencies:
@@ -10037,6 +10119,13 @@ snapshots:
       '@vercel/oidc': 3.1.0
       zod: 4.3.5
 
+  '@ai-sdk/gateway@3.0.50(zod@4.3.5)':
+    dependencies:
+      '@ai-sdk/provider': 3.0.8
+      '@ai-sdk/provider-utils': 4.0.15(zod@4.3.5)
+      '@vercel/oidc': 3.1.0
+      zod: 4.3.5
+
   '@ai-sdk/provider-utils@4.0.13(zod@4.3.5)':
     dependencies:
       '@ai-sdk/provider': 3.0.7
@@ -10051,6 +10140,13 @@ snapshots:
       eventsource-parser: 3.0.6
       zod: 4.3.5
 
+  '@ai-sdk/provider-utils@4.0.15(zod@4.3.5)':
+    dependencies:
+      '@ai-sdk/provider': 3.0.8
+      '@standard-schema/spec': 1.1.0
+      eventsource-parser: 3.0.6
+      zod: 4.3.5
+
   '@ai-sdk/provider-utils@4.0.5(zod@4.3.5)':
     dependencies:
       '@ai-sdk/provider': 3.0.2
@@ -14448,14 +14544,14 @@ snapshots:
 
   '@remote-ui/core@2.2.5':
     dependencies:
-      '@remote-ui/rpc': 1.4.5
+      '@remote-ui/rpc': 1.4.7
       '@remote-ui/types': 1.1.3
 
   '@remote-ui/react@5.0.5(react-reconciler@0.29.0(react@18.3.1))(react@18.3.1)':
     dependencies:
       '@remote-ui/async-subscription': 2.1.18
       '@remote-ui/core': 2.2.5
-      '@remote-ui/rpc': 1.4.5
+      '@remote-ui/rpc': 1.4.7
       '@types/react': 18.3.28
       '@types/react-reconciler': 0.28.9(@types/react@18.3.28)
       react: 18.3.1
@@ -14757,10 +14853,22 @@ snapshots:
       react-reconciler: 0.29.0(react@18.3.1)
       stripe: 13.11.0
 
+  '@stripe/ui-extension-sdk@9.2.0-alpha.0(stripe@13.11.0)':
+    dependencies:
+      '@remote-ui/core': 2.2.5
+      '@remote-ui/react': 5.0.5(react-reconciler@0.29.0(react@18.3.1))(react@18.3.1)
+      '@remote-ui/rpc': 1.4.5
+      '@remote-ui/testing': 1.4.3
+      '@types/stylis': 4.2.6
+      invariant: 2.2.4
+      react: 18.3.1
+      react-reconciler: 0.29.0(react@18.3.1)
+      stripe: 13.11.0
+
   '@stripe/ui-extension-tools@0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))':
     dependencies:
       '@types/jest': 28.1.8
-      '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
+      '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
       '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
       eslint: 8.57.1
       eslint-plugin-react: 7.37.5(eslint@8.57.1)
@@ -15087,7 +15195,7 @@ snapshots:
       '@types/node': 22.19.6
     optional: true
 
-  '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
+  '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
     dependencies:
       '@eslint-community/regexpp': 4.12.2
       '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
@@ -15497,6 +15605,14 @@ snapshots:
       '@opentelemetry/api': 1.9.0
       zod: 4.3.5
 
+  ai@6.0.91(zod@4.3.5):
+    dependencies:
+      '@ai-sdk/gateway': 3.0.50(zod@4.3.5)
+      '@ai-sdk/provider': 3.0.8
+      '@ai-sdk/provider-utils': 4.0.15(zod@4.3.5)
+      '@opentelemetry/api': 1.9.0
+      zod: 4.3.5
+
   ajv-formats@2.1.1(ajv@8.17.1):
     optionalDependencies:
       ajv: 8.17.1
@@ -19667,6 +19783,32 @@ snapshots:
       - '@babel/core'
       - babel-plugin-macros
 
+  next@16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+    dependencies:
+      '@next/env': 16.1.6
+      '@swc/helpers': 0.5.15
+      baseline-browser-mapping: 2.9.14
+      caniuse-lite: 1.0.30001764
+      postcss: 8.4.31
+      react: 19.2.4
+      react-dom: 19.2.4(react@19.2.4)
+      styled-jsx: 5.1.6(react@19.2.4)
+    optionalDependencies:
+      '@next/swc-darwin-arm64': 16.1.6
+      '@next/swc-darwin-x64': 16.1.6
+      '@next/swc-linux-arm64-gnu': 16.1.6
+      '@next/swc-linux-arm64-musl': 16.1.6
+      '@next/swc-linux-x64-gnu': 16.1.6
+      '@next/swc-linux-x64-musl': 16.1.6
+      '@next/swc-win32-arm64-msvc': 16.1.6
+      '@next/swc-win32-x64-msvc': 16.1.6
+      '@opentelemetry/api': 1.9.0
+      babel-plugin-react-compiler: 1.0.0
+      sharp: 0.34.5
+    transitivePeerDependencies:
+      - '@babel/core'
+      - babel-plugin-macros
+
   node-abi@3.87.0:
     dependencies:
       semver: 7.7.3
@@ -21510,6 +21652,11 @@ snapshots:
       client-only: 0.0.1
       react: 19.2.3
 
+  styled-jsx@5.1.6(react@19.2.4):
+    dependencies:
+      client-only: 0.0.1
+      react: 19.2.4
+
   sucrase@3.35.1:
     dependencies:
       '@jridgewell/gen-mapping': 0.3.13

+ 1 - 0
pnpm-workspace.yaml

@@ -1,4 +1,5 @@
 packages:
   - "apps/*"
   - "examples/*"
+  - "examples/stripe-app/*"
   - "packages/*"