diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..94822bb --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,75 @@ +name: Deploy Docs to GitHub Pages + +on: + push: + branches: [main] + paths: + - 'docs/**' + - 'packages/**' + - '.github/workflows/docs.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: 'pages' + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Setup pnpm cache + uses: actions/cache@v4 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build docs + run: pnpm build:docs + + - name: Setup Pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./docs/out + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + diff --git a/README.md b/README.md index 3250201..298c28b 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,33 @@ experiences.register('welcome', { ... }); const decision = experiences.evaluate(); ``` +### Event-Driven Architecture + +Listen to events to integrate with analytics, tracking, and custom business logic: + +```typescript +// Track impressions +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + analytics.track('Experience Shown', { id: experience.id }); + } +}); + +// Track button clicks +experiences.on('experiences:action', ({ experienceId, action, url }) => { + analytics.track('Experience Action', { experienceId, action }); +}); + +// Track dismissals +experiences.on('experiences:dismissed', ({ experienceId }) => { + analytics.track('Experience Dismissed', { experienceId }); +}); +``` + +**Multiple listeners can react to the same event** (jstag3, GA, Segment, custom code). + +See the [Events Reference](https://your-docs-url/api/events) for comprehensive documentation. + ## Documentation See [notes/IMPLEMENTATION_PLAN.md](notes/IMPLEMENTATION_PLAN.md) for detailed implementation guide. diff --git a/biome.json b/biome.json index c4010bd..90a9d45 100644 --- a/biome.json +++ b/biome.json @@ -32,12 +32,17 @@ "packages/core/src/types.ts", "packages/core/src/runtime.ts", "packages/plugins/src/types.ts", - "specs/**/contracts/types.ts" + "specs/**/contracts/types.ts", + "docs/**/*.tsx" ], "linter": { "rules": { "suspicious": { - "noExplicitAny": "off" + "noExplicitAny": "off", + "noArrayIndexKey": "off" + }, + "correctness": { + "useExhaustiveDependencies": "off" } } } diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..94ad4ed --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,21 @@ +# Next.js +.next/ +out/ +build/ + +# Dependencies +node_modules/ + +# Build outputs +public/sdk/ + +# Local env +.env*.local + +# Debug +npm-debug.log* +.pnpm-debug.log* + +# Vercel +.vercel + diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7fece45 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,108 @@ +# Experience SDK Documentation + +This is the documentation site for the Experience SDK, built with [Nextra](https://nextra.site/). + +## Development + +```bash +# From root of monorepo +pnpm docs:dev +``` + +Visit http://localhost:3000 + +## Building + +### Build everything (SDK + docs) +```bash +# From root of monorepo +pnpm build:docs +``` + +### Build only docs (faster if SDK already built) +```bash +# From root of monorepo +pnpm --filter docs build +``` + +The site will be built to `docs/out/` directory (static export). + +## Deployment + +### GitHub Pages (Automatic) + +The docs are automatically deployed to GitHub Pages on every push to `main` that affects: +- `docs/**` +- `packages/**` (SDK changes) +- `.github/workflows/docs.yml` + +**Setup:** +1. Go to repository Settings → Pages +2. Set Source to "GitHub Actions" +3. Push to main → site deploys automatically + +**URL:** `https://prosdevlab.github.io/experience-sdk/` + +### Vercel (Manual) + +1. Install Vercel CLI: `npm i -g vercel` +2. From the `docs/` directory, run: `vercel` +3. Follow the prompts +4. The site will be deployed automatically + +The `vercel.json` config handles the monorepo build process. + +### Cloudflare Pages + +1. Connect your GitHub repository +2. Set build command: `pnpm install && pnpm build:docs` +3. Set output directory: `docs/out` +4. Deploy + +## Manual Build & Deploy + +```bash +# Build docs +pnpm build:docs + +# Output is in docs/out/ +# Serve it with any static file server: +npx serve docs/out +``` + +## Structure + +``` +docs/ +├── pages/ # MDX pages +│ ├── index.mdx # Homepage +│ ├── getting-started.mdx +│ ├── demo/ # Interactive demos +│ │ ├── index.mdx # Demo overview +│ │ └── banner.mdx # Banner demo +│ └── api/ # API reference +├── components/ # React components +│ ├── ExperienceDemo.tsx (old) +│ └── BannerDemo.tsx # Banner demo component +├── public/ # Static files +│ └── sdk/ # SDK bundle (auto-generated) +├── out/ # Build output (gitignored) +├── theme.config.tsx # Nextra theme config +└── next.config.js # Next.js config +``` + +## SDK Bundle + +The SDK is automatically copied to `public/sdk/` during the build process via the `prebuild` script in `package.json`. This allows: + +1. Script tag demos to load the SDK from `/sdk/index.global.js` +2. Both npm and CDN usage examples +3. Interactive demos to work without external dependencies + +The prebuild script runs: +```bash +pnpm --filter @prosdevlab/experience-sdk build && \ + mkdir -p public/sdk && \ + cp -r ../packages/core/dist/* public/sdk/ +``` + diff --git a/docs/components/ExperienceDemo.tsx b/docs/components/ExperienceDemo.tsx new file mode 100644 index 0000000..6075f93 --- /dev/null +++ b/docs/components/ExperienceDemo.tsx @@ -0,0 +1,251 @@ +'use client'; + +import { useEffect, useState } from 'react'; + +// Use global experiences API (loaded from IIFE script) +declare global { + interface Window { + experiences: any; + } +} + +interface Decision { + show: boolean; + experienceId?: string; + reasons: string[]; + trace: Array<{ + step: string; + timestamp: number; + duration: number; + input?: any; + output?: any; + passed: boolean; + }>; + context: Record; + metadata: { + evaluatedAt: number; + totalDuration: number; + experienceCount: number; + }; +} + +export function ExperienceDemo() { + const [decision, setDecision] = useState(null); + const [impressionCount, setImpressionCount] = useState(0); + const [isInitialized, setIsInitialized] = useState(false); + const [error, setError] = useState(null); + + // Initialize SDK + useEffect(() => { + // Load SDK script dynamically + const script = document.createElement('script'); + script.src = '/sdk/index.global.js'; + script.async = true; + script.onload = () => { + if (window.experiences) { + window.experiences.init({ debug: true }).then(() => { + // Register a sample experience + window.experiences.register('welcome-banner', { + type: 'banner', + targeting: { + url: { contains: '/' }, + }, + content: { + title: 'Welcome to Experience SDK!', + message: 'This is a live demo showing explainability in action.', + }, + frequency: { + max: 3, + per: 'session', + }, + }); + + setIsInitialized(true); + + // Initial evaluation + const initialDecision = window.experiences.evaluate({ url: window.location.href }); + setDecision(initialDecision); + }); + } else { + setError('Failed to load Experience SDK'); + } + }; + script.onerror = () => { + setError('Failed to load SDK script'); + }; + + document.head.appendChild(script); + + return () => { + if (window.experiences) { + window.experiences.destroy(); + } + document.head.removeChild(script); + }; + }, []); + + const handleEvaluate = () => { + if (!window.experiences) return; + const newDecision = window.experiences.evaluate({ url: window.location.href }); + setDecision(newDecision); + setImpressionCount((prev) => prev + 1); + }; + + const handleReset = () => { + if (!window.experiences) return; + window.experiences.destroy().then(() => { + window.experiences.init({ debug: true }).then(() => { + window.experiences.register('welcome-banner', { + type: 'banner', + targeting: { + url: { contains: '/' }, + }, + content: { + title: 'Welcome to Experience SDK!', + message: 'This is a live demo showing explainability in action.', + }, + frequency: { + max: 3, + per: 'session', + }, + }); + + const newDecision = window.experiences.evaluate({ url: window.location.href }); + setDecision(newDecision); + setImpressionCount(0); + }); + }); + }; + + if (error) { + return
{error}
; + } + + if (!isInitialized) { + return
Loading SDK...
; + } + + return ( +
+ {/* Banner Display */} + {decision?.show && ( +
+
+
+

Welcome to Experience SDK!

+

This is a live demo showing explainability in action.

+
+ +
+
+ )} + + {/* Controls */} +
+ + +
+ + {/* Decision Output */} + {decision && ( +
+ {/* Decision Summary */} +
+
+ {decision.show ? '✅' : '❌'} +

{decision.show ? 'SHOW' : 'HIDE'}

+
+ {decision.experienceId && ( +

+ Experience:{' '} + {decision.experienceId} +

+ )} +

+ Evaluated in {decision.metadata.totalDuration.toFixed(2)}ms +

+
+ + {/* Reasons */} +
+

Reasons

+
    + {decision.reasons.map((reason, idx) => ( +
  • + {reason.startsWith('✅') || reason.startsWith('❌') ? '' : '•'} + {reason} +
  • + ))} +
+
+ + {/* Trace Steps */} +
+

Trace Steps

+
+ {decision.trace.map((step, idx) => ( +
+
+ {idx + 1}. + {step.step} + + {step.passed ? '✓' : '✗'} + +
+ {step.output && ( +
Output: {JSON.stringify(step.output)}
+ )} +
+ ))} +
+
+ + {/* Context */} +
+ Context +
+              {JSON.stringify(decision.context, null, 2)}
+            
+
+ + {/* Full Decision JSON */} +
+ Full Decision Object +
+              {JSON.stringify(decision, null, 2)}
+            
+
+
+ )} + + {/* Stats */} +
+

Session Stats

+

Total Evaluations: {impressionCount}

+

Frequency Cap: 3 per session

+
+
+ ); +} diff --git a/docs/next-env.d.ts b/docs/next-env.d.ts new file mode 100644 index 0000000..a4a7b3f --- /dev/null +++ b/docs/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information. diff --git a/docs/next.config.js b/docs/next.config.js new file mode 100644 index 0000000..7d36f8e --- /dev/null +++ b/docs/next.config.js @@ -0,0 +1,16 @@ +/** @type {import('next').NextConfig} */ +const withNextra = require('nextra')({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.tsx', +}); + +const isProduction = process.env.NODE_ENV === 'production'; +const basePath = isProduction ? '/experience-sdk' : ''; + +module.exports = withNextra({ + output: 'export', + basePath, + images: { + unoptimized: true, + }, +}); diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..130ae3d --- /dev/null +++ b/docs/package.json @@ -0,0 +1,25 @@ +{ + "name": "docs", + "version": "0.1.0", + "description": "Experience SDK Documentation", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "export": "next export", + "prebuild": "pnpm --filter @prosdevlab/experience-sdk build && mkdir -p public/sdk && cp -r ../packages/core/dist/* public/sdk/" + }, + "dependencies": { + "next": "^14.2.0", + "nextra": "^2.13.0", + "nextra-theme-docs": "^2.13.0", + "react": "^18.3.0", + "react-dom": "^18.3.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/react": "^18.3.0", + "typescript": "^5.9.3" + } +} diff --git a/docs/pages/_app.tsx b/docs/pages/_app.tsx new file mode 100644 index 0000000..da826ed --- /dev/null +++ b/docs/pages/_app.tsx @@ -0,0 +1,5 @@ +import type { AppProps } from 'next/app'; + +export default function App({ Component, pageProps }: AppProps) { + return ; +} diff --git a/docs/pages/_meta.json b/docs/pages/_meta.json new file mode 100644 index 0000000..5e2ff93 --- /dev/null +++ b/docs/pages/_meta.json @@ -0,0 +1,7 @@ +{ + "index": "Introduction", + "getting-started": "Getting Started", + "demo": "Examples", + "reference": "API Reference" +} + diff --git a/docs/pages/demo/_meta.json b/docs/pages/demo/_meta.json new file mode 100644 index 0000000..fe8fee5 --- /dev/null +++ b/docs/pages/demo/_meta.json @@ -0,0 +1,5 @@ +{ + "index": "Overview", + "banner": "Banner Experience" +} + diff --git a/docs/pages/demo/banner.mdx b/docs/pages/demo/banner.mdx new file mode 100644 index 0000000..b3ed2fa --- /dev/null +++ b/docs/pages/demo/banner.mdx @@ -0,0 +1,192 @@ +# Banner Examples + +The Experience SDK supports banner-style experiences with targeting, frequency capping, and full explainability. + +## Basic Example + +```typescript +import { createInstance } from '@prosdevlab/experience-sdk'; + +// Create SDK instance with banner plugin +const experiences = createInstance({ debug: true }); + +// Register a welcome banner +experiences.register('welcome-banner', { + type: 'banner', + targeting: { + url: { contains: '/' } + }, + content: { + title: '👋 Welcome!', + message: 'Thanks for visiting our site', + buttons: [ + { + text: 'Get Started', + url: '/getting-started', + variant: 'primary' + } + ], + dismissable: true + }, + frequency: { + max: 3, + per: 'session' + } +}); + +// Evaluate and show +const decision = experiences.evaluate(); +console.log(decision.reasons); // See why it was shown or hidden +``` + +## Multiple Buttons + +Banners can have multiple buttons with different visual variants: + +```typescript +experiences.register('cookie-consent', { + type: 'banner', + content: { + message: 'We use cookies to improve your experience', + buttons: [ + { + text: 'Accept all', + action: 'accept-all', + variant: 'primary' + }, + { + text: 'Reject', + action: 'reject', + variant: 'secondary' + }, + { + text: 'Preferences', + action: 'preferences', + variant: 'link' + } + ], + position: 'bottom' + } +}); +``` + +Button variants: +- **primary** - Prominent blue button for main actions +- **secondary** - Subtle gray button for secondary actions +- **link** - Text link style for tertiary actions + +## Multiple Banners + +You can display multiple banners at different positions: + +```typescript +// Top banner +experiences.register('welcome', { + type: 'banner', + content: { + message: 'Welcome!', + position: 'top' + }, + priority: 50 +}); + +// Bottom banner +experiences.register('cookie-notice', { + type: 'banner', + content: { + message: 'We use cookies', + position: 'bottom' + }, + priority: 100 +}); + +// Evaluate all experiences +const decisions = experiences.evaluateAll(); +``` + +## Banner Configuration + +### Content Options + +| Property | Type | Description | +|----------|------|-------------| +| `title` | `string` | Optional banner title | +| `message` | `string` | Banner message (required) | +| `buttons` | `array` | Array of button configurations | +| `dismissable` | `boolean` | Can user dismiss? (default: `true`) | +| `position` | `'top' \| 'bottom'` | Banner position (default: `'top'`) | + +### Button Options + +```typescript +buttons: [{ + text: string; // Button label (required) + action?: string; // Custom action name + url?: string; // Navigate on click + variant?: 'primary' | 'secondary' | 'link'; // Visual style (default: 'primary') + metadata?: Record; // Custom metadata +}] +``` + +### Responsive Layout + +Buttons automatically adapt to screen size: +- **Desktop**: Buttons displayed inline horizontally +- **Mobile**: Buttons stack vertically for better touch interaction + +## Frequency Capping + +Control how often banners appear: + +```typescript +frequency: { + max: 3, // Maximum impressions + per: 'session' // 'session' | 'day' | 'week' +} +``` + +## Targeting + +Show banners based on URL patterns: + +```typescript +targeting: { + url: { + contains: '/products', // URL contains string + equals: '/about', // Exact match + matches: /^\/blog/ // Regex pattern + } +} +``` + +## Events + +Listen to banner interactions: + +```typescript +// Banner shown +experiences.on('experiences:shown', ({ experienceId }) => { + console.log(`Banner shown: ${experienceId}`); +}); + +// Banner dismissed +experiences.on('experiences:dismissed', ({ experienceId }) => { + console.log(`Banner dismissed: ${experienceId}`); +}); + +// Button clicked +experiences.on('experiences:action', ({ experienceId, action, variant, metadata }) => { + console.log(`Action: ${action}, Variant: ${variant}`); + + // Access custom metadata if provided + if (metadata) { + console.log('Metadata:', metadata); + } +}); +``` + +## Next Steps + +- [API Reference](/reference) - Complete API documentation +- [Events](/reference/events) - All available events +- [Getting Started](/getting-started) - Installation guide diff --git a/docs/pages/demo/index.mdx b/docs/pages/demo/index.mdx new file mode 100644 index 0000000..3600a41 --- /dev/null +++ b/docs/pages/demo/index.mdx @@ -0,0 +1,103 @@ +# Interactive Demo + +Experience the SDK in action with a live, interactive demo that showcases the **explainability-first** decision engine. + +## 🎯 [Banner Demo](/demo/banner) + +See how the Experience SDK evaluates and renders a banner experience in real-time. + +**Features showcased:** +- ✅ Real-time decision evaluation +- ✅ Frequency capping (3 per session) +- ✅ Explainable reasons (why show/hide?) +- ✅ Step-by-step execution trace +- ✅ Full context visibility + +[Try Live Demo →](/demo/banner) + +--- + +## What You'll Learn + +The demo demonstrates the **core value proposition**: understanding **when** and **why** an experience shows or hides. + +### 1. **Decision Engine** + - Click "Simulate Page View" to trigger evaluation + - Watch frequency cap in action (0/3 → 3/3) + - See banner hide after cap is reached + +### 2. **Explainability Output** + - **Reasons**: Human-readable explanations + - "✅ URL matches targeting rule" + - "Frequency cap not reached (2/3 this session)" + - **Trace Steps**: Machine-readable execution log + - Timing per step (milliseconds) + - Input/output for each evaluation + - **Context**: Full snapshot of evaluation state + +### 3. **Realistic Behavior** + - Banner persists across page refreshes (sessionStorage) + - Reset button clears frequency cap for retesting + - Accurate impression counting + +--- + +## More Configuration Examples + +Want to see different banner types? Check out the **Examples** section in the documentation: + +- **Cookie Banners** - Once per day, GDPR-compliant +- **Announcements** - 5 times per session +- **Flash Sales** - Limited-time urgency (2x/session) +- **Custom Targeting** - URL patterns, user segments, custom rules + +Each example shows the **configuration code** needed to implement that experience type. + +[Browse Examples →](/getting-started) + +--- + +## Coming Soon + +### 🪟 Modal Experience +*(Phase 1)* + +Interactive modal demos with: +- Center overlays +- Lightbox style +- Multi-step forms + +### 💬 Tooltip Experience +*(Phase 1)* + +Contextual tooltip demos with: +- Element targeting +- Position options (top, bottom, left, right) +- Hover vs click triggers + +--- + +## Why This Matters + +Unlike traditional personalization tools, **Experience SDK** focuses on: + +1. **Transparency** - Every decision has structured reasons +2. **Debuggability** - Trace execution step-by-step +3. **Testability** - Simulate scenarios without deployment +4. **Developer Experience** - Built for engineers who need to understand "why" + +This is the **decision layer** between: +- **User context** (provided by analytics/CDP like jstag3) ← WHO they are +- **Experience rendering** (your UI components) ← WHAT to show + +**You control WHEN** with explainable logic. + +--- + +## Next Steps + +- [Try Live Demo](/demo/banner) - See it in action +- [Getting Started](/getting-started) - Install and setup +- [API Reference](/api) - Complete documentation + + diff --git a/docs/pages/getting-started.mdx b/docs/pages/getting-started.mdx new file mode 100644 index 0000000..de5d3f7 --- /dev/null +++ b/docs/pages/getting-started.mdx @@ -0,0 +1,220 @@ +# Getting Started + +Install the Experience SDK and start building explainable experiences. + +## Installation + +### Via npm + +```bash +npm install @prosdevlab/experience-sdk +``` + +### Via pnpm + +```bash +pnpm add @prosdevlab/experience-sdk +``` + +### Via Script Tag + +```html + +``` + +## Quick Start + +### For npm/bundler users + +```typescript +import { createInstance } from '@prosdevlab/experience-sdk'; + +// Create an instance +const experiences = createInstance({ debug: true }); + +// Initialize +await experiences.init(); + +// Register an experience +experiences.register('welcome-banner', { + type: 'banner', + targeting: { + url: { contains: '/' } + }, + content: { + title: 'Welcome!', + message: 'Thanks for visiting our site' + }, + frequency: { + max: 3, + per: 'session' + } +}); + +// Evaluate +const decision = experiences.evaluate(); + +if (decision.show) { + console.log('Show experience:', decision.experienceId); + console.log('Reasons:', decision.reasons); +} +``` + +## Listening to Events + +Experience SDK uses an event-driven architecture. Listen to events to integrate with analytics, tracking, and custom business logic: + +```typescript +// Listen to evaluation events +experiences.on('experiences:evaluated', ({ decision, experience }) => { + console.log('Decision:', decision.show ? 'SHOW' : 'HIDE'); + console.log('Reasons:', decision.reasons); + + if (decision.show && experience) { + console.log('Experience:', experience.id); + } +}); + +// Listen to button clicks +experiences.on('experiences:action', ({ experienceId, action, url }) => { + console.log('User clicked:', action); + console.log('Experience:', experienceId); + + // URL navigation happens automatically if provided + if (url) { + console.log('Navigating to:', url); + } +}); + +// Listen to dismissals +experiences.on('experiences:dismissed', ({ experienceId }) => { + console.log('User dismissed:', experienceId); +}); +``` + +### Tracking with Analytics + +Integrate with any analytics platform by listening to SDK events: + +```typescript +// Google Analytics 4 +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + gtag('event', 'experience_shown', { + experience_id: experience.id, + experience_type: experience.type + }); + } +}); + +experiences.on('experiences:action', ({ experienceId, action }) => { + gtag('event', 'experience_action', { + experience_id: experienceId, + action: action + }); +}); + +// Segment +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + analytics.track('Experience Shown', { + experienceId: experience.id, + experienceType: experience.type + }); + } +}); +``` + +### Multiple Consumers + +The event-driven architecture allows multiple listeners to react to the same event: + +```typescript +// Listener 1: jstag3 tracking +experiences.on('experiences:action', ({ experienceId, action }) => { + window.jstag.send('experience_action', { + experience_id: experienceId, + action: action + }); +}); + +// Listener 2: Google Analytics (separate listener) +experiences.on('experiences:action', ({ experienceId, action }) => { + gtag('event', 'experience_action', { + experience_id: experienceId, + action: action + }); +}); + +// Listener 3: Custom business logic (separate listener) +experiences.on('experiences:action', ({ action }) => { + if (action === 'accept-cookies') { + document.cookie = 'cookies_accepted=true'; + initializeAnalytics(); + } +}); +``` + +### For script tag users + +```html + + + + + + + + + +``` + +## Understanding Decisions + +Every evaluation returns a `Decision` object with explainability built-in: + +```typescript +{ + show: true, + experienceId: 'welcome-banner', + reasons: [ + "URL matches '/'", + "Frequency cap not reached (1/3 this session)", + "Experience is active" + ], + trace: [...], // Detailed trace steps + context: {...}, // Evaluation context + metadata: { + evaluatedAt: 1234567890, + experienceCount: 1, + evaluationTimeMs: 0.5 + } +} +``` + +## Next Steps + +- [Interactive Demo](/demo) - Try it in your browser +- [API Reference](/reference) - Complete API documentation +- [Events Reference](/reference/events) - Learn about event-driven patterns +- [Plugins](/reference/plugins) - Learn about plugins + diff --git a/docs/pages/index.mdx b/docs/pages/index.mdx new file mode 100644 index 0000000..2963eee --- /dev/null +++ b/docs/pages/index.mdx @@ -0,0 +1,57 @@ +# Experience SDK + +A lightweight, explainable, plugin-based client-side experience runtime built on [@lytics/sdk-kit](https://github.com/Lytics/sdk-kit). + +## Key Features + +- **Explainability-First** - Every decision returns structured reasons +- **Plugin-Based** - Extensible architecture using sdk-kit patterns +- **Script Tag Ready** - Works without build tools (IIFE bundle) +- **Type-Safe** - Full TypeScript support +- **Developer-Focused** - Built for debugging and inspection + +## Quick Start + +```bash +npm install @prosdevlab/experience-sdk +``` + +```typescript +import { createInstance } from '@prosdevlab/experience-sdk'; + +const experiences = createInstance({ debug: true }); + +experiences.register('welcome-banner', { + type: 'banner', + targeting: { url: { contains: '/' } }, + content: { title: 'Welcome!', message: 'Thanks for visiting' } +}); + +const decision = experiences.evaluate(); +console.log(decision.reasons); // ['URL matches', 'Frequency cap not reached'] +``` + +## What Makes It Different + +Every decision includes human-readable reasons explaining **why** an experience was shown or hidden: + +```json +{ + "show": true, + "experienceId": "welcome-banner", + "reasons": [ + "URL matches '/'", + "Frequency cap not reached (1/3 this session)", + "User targeting passed" + ], + "trace": [...], + "metadata": {...} +} +``` + +## Next Steps + +- [Getting Started](/getting-started) - Installation and setup +- [Interactive Demo](/demo) - Try it in your browser +- [API Reference](/reference) - Complete API documentation + diff --git a/docs/pages/reference/_meta.json b/docs/pages/reference/_meta.json new file mode 100644 index 0000000..70a5262 --- /dev/null +++ b/docs/pages/reference/_meta.json @@ -0,0 +1,6 @@ +{ + "index": "API Reference", + "events": "Events", + "plugins": "Plugins" +} + diff --git a/docs/pages/reference/events.mdx b/docs/pages/reference/events.mdx new file mode 100644 index 0000000..42e8670 --- /dev/null +++ b/docs/pages/reference/events.mdx @@ -0,0 +1,571 @@ +# Events Reference + +Experience SDK uses an event-driven architecture. All events are emitted via the SDK's event emitter and can have multiple listeners. + +## Why Events? + +Unlike callback-based approaches, events provide: + +- **Multiple Consumers** - Many listeners can react to one event +- **Decoupled Architecture** - Banner plugin doesn't know about tracking +- **Serializable Config** - No function references in config (JSON-safe) +- **Testable** - Easy to mock and verify +- **Composable** - Add/remove listeners dynamically + +## Core Events + +### `experiences:evaluated` + +Emitted after evaluating all registered experiences. + +**When**: After calling `experiences.evaluate()` + +**Payload**: +```typescript +{ + decision: Decision; // The evaluation result + experience?: Experience; // The matched experience (if any) +} +``` + +**Example**: +```typescript +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show) { + console.log(`Show ${experience?.id}`); + console.log('Reasons:', decision.reasons); + } else { + console.log('Hide all experiences'); + console.log('Reasons:', decision.reasons); + } +}); +``` + +**Use Cases**: +- Track impressions +- Custom rendering logic +- Debug logging +- A/B test tracking + +--- + +### `experiences:action` + +Emitted when a user interacts with an experience (e.g., clicks a CTA button). + +**When**: User clicks a button configured with `button.action` or `button.url` + +**Payload**: +```typescript +{ + experienceId: string; // ID of the experience + action: string; // Action identifier + url?: string; // Navigation URL (if provided) + timestamp: number; // Event timestamp +} +``` + +**Example**: +```typescript +experiences.on('experiences:action', ({ experienceId, action, url }) => { + // Track click + analytics.track('Experience Action', { experienceId, action }); + + // Custom logic for specific actions + if (action === 'subscribe') { + showSubscriptionForm(); + } + + // URL navigation happens automatically if provided + console.log('Navigating to:', url); +}); +``` + +**Use Cases**: +- Track button clicks +- Custom navigation logic +- Trigger side effects (modals, forms, etc.) +- A/B test goal tracking + +**Note**: If `button.url` is provided, the SDK automatically navigates after emitting the event. + +--- + +### `experiences:dismissed` + +Emitted when a user dismisses an experience (e.g., closes a banner). + +**When**: User clicks the dismiss/close button + +**Payload**: +```typescript +{ + experienceId: string; // ID of the dismissed experience + timestamp: number; // Event timestamp +} +``` + +**Example**: +```typescript +experiences.on('experiences:dismissed', ({ experienceId }) => { + // Track dismissal + analytics.track('Experience Dismissed', { experienceId }); + + // Optionally suppress for longer period + suppressExperience(experienceId, '7d'); +}); +``` + +**Use Cases**: +- Track dismissal rates +- Custom suppression logic +- User preference tracking +- Engagement metrics + +**Note**: Dismissals do NOT count as impressions for frequency capping. + +--- + +### `sdk:ready` + +Emitted when SDK initialization is complete. + +**When**: After `experiences.init()` finishes + +**Payload**: None + +**Example**: +```typescript +experiences.on('sdk:ready', () => { + console.log('SDK initialized'); + // Safe to register experiences now +}); +``` + +--- + +### `sdk:destroy` + +Emitted before SDK is destroyed. + +**When**: Before `experiences.destroy()` completes + +**Payload**: None + +**Example**: +```typescript +experiences.on('sdk:destroy', () => { + console.log('Cleaning up...'); + // Unsubscribe from external services +}); +``` + +--- + +## Event Patterns + +### Multiple Listeners + +Events can have multiple listeners (unlike callbacks): + +```typescript +// Listener 1: jstag3 tracking +experiences.on('experiences:action', ({ experienceId, action }) => { + window.jstag.send('experience_action', { experienceId, action }); +}); + +// Listener 2: Google Analytics +experiences.on('experiences:action', ({ experienceId, action }) => { + gtag('event', 'experience_action', { experienceId, action }); +}); + +// Listener 3: Custom business logic +experiences.on('experiences:action', ({ action }) => { + if (action === 'accept-cookies') { + setCookieConsent(true); + } +}); +``` + +### Unsubscribing + +Store the unsubscribe function to stop listening: + +```typescript +const unsubscribe = experiences.on('experiences:action', handler); + +// Later... +unsubscribe(); // Stop listening +``` + +### Conditional Logic + +Use event payloads to implement conditional logic: + +```typescript +experiences.on('experiences:action', ({ experienceId, action }) => { + // Only track certain experiences + if (experienceId.startsWith('promo-')) { + trackPromoClick(action); + } + + // Different behavior per action + switch (action) { + case 'subscribe': + showSubscriptionModal(); + break; + case 'accept-cookies': + setCookieConsent(true); + break; + case 'dismiss': + // Custom dismissal logic + break; + } +}); +``` + +### Async Handlers + +Event handlers can be async: + +```typescript +experiences.on('experiences:action', async ({ experienceId, action }) => { + // Make API call + const response = await fetch('/api/track', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ experienceId, action, timestamp: Date.now() }) + }); + + if (response.ok) { + console.log('Tracked successfully'); + } +}); +``` + +--- + +## Integration Examples + +### jstag3 (Lytics) + +Track all experience events to Lytics: + +```typescript +// Track impressions +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + window.jstag.send('experience_shown', { + experience_id: experience.id, + experience_type: experience.type, + targeting: experience.targeting, + frequency: experience.frequency, + reasons: decision.reasons + }); + } +}); + +// Track actions +experiences.on('experiences:action', ({ experienceId, action, url }) => { + window.jstag.send('experience_action', { + experience_id: experienceId, + action: action, + destination_url: url + }); +}); + +// Track dismissals +experiences.on('experiences:dismissed', ({ experienceId }) => { + window.jstag.send('experience_dismissed', { + experience_id: experienceId + }); +}); +``` + +You can also create a reusable jstag3 plugin: + +```typescript +// jstag3Plugin.ts +export function initJstag3Tracking(experiences) { + if (!window.jstag) { + console.warn('jstag not available'); + return; + } + + experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + window.jstag.send('experience_shown', { + experience_id: experience.id, + experience_type: experience.type + }); + } + }); + + experiences.on('experiences:action', ({ experienceId, action, url }) => { + window.jstag.send('experience_action', { + experience_id: experienceId, + action: action, + destination_url: url + }); + }); + + experiences.on('experiences:dismissed', ({ experienceId }) => { + window.jstag.send('experience_dismissed', { + experience_id: experienceId + }); + }); +} + +// Usage +initJstag3Tracking(experiences); +``` + +--- + +### Google Analytics 4 + +Track experience events to GA4: + +```typescript +// Track impressions +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + gtag('event', 'experience_impression', { + experience_id: experience.id, + experience_type: experience.type + }); + } +}); + +// Track actions +experiences.on('experiences:action', ({ experienceId, action }) => { + gtag('event', 'experience_click', { + experience_id: experienceId, + action_type: action + }); +}); + +// Track dismissals +experiences.on('experiences:dismissed', ({ experienceId }) => { + gtag('event', 'experience_dismissed', { + experience_id: experienceId + }); +}); +``` + +--- + +### Segment + +Track experience events to Segment: + +```typescript +// Track impressions +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + analytics.track('Experience Shown', { + experienceId: experience.id, + experienceType: experience.type, + reasons: decision.reasons + }); + } +}); + +// Track actions +experiences.on('experiences:action', ({ experienceId, action, url }) => { + analytics.track('Experience Action', { + experienceId, + action, + url + }); +}); + +// Track dismissals +experiences.on('experiences:dismissed', ({ experienceId }) => { + analytics.track('Experience Dismissed', { + experienceId + }); +}); +``` + +--- + +### Custom API + +Send events to your own API: + +```typescript +const trackEvent = async (eventName, payload) => { + await fetch('/api/analytics/track', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + event: eventName, + payload, + timestamp: Date.now() + }) + }); +}; + +// Track all events +experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + trackEvent('experience_shown', { + experienceId: experience.id, + experienceType: experience.type + }); + } +}); + +experiences.on('experiences:action', ({ experienceId, action, url }) => { + trackEvent('experience_action', { experienceId, action, url }); +}); + +experiences.on('experiences:dismissed', ({ experienceId }) => { + trackEvent('experience_dismissed', { experienceId }); +}); +``` + +--- + +## Comparison: Callbacks vs. Events + +### Pathfora (Callbacks - Monolithic) + +```javascript +// ❌ Not serializable, single consumer, tightly coupled +{ + okMessage: 'Shop Now', + confirmAction: { + name: 'shop_cta', + callback: function(event, payload) { + // MUST define all logic here + analytics.track('Shop CTA', payload); + jstag.send('shop_cta', payload); + window.location = '/products'; + } + } +} +``` + +**Problems:** +- Can't serialize config to JSON (has function) +- Can't save to CMS/database +- Tight coupling (widget executes callback) +- Single handler only +- Hard to test + +--- + +### Our SDK (Events - Modular) + +```typescript +// ✅ Config is pure data (serializable) +{ + button: { + text: 'Shop Now', + url: '/products', + action: 'shop-cta' + } +} + +// App code (decoupled) +experiences.on('experiences:action', ({ action, url }) => { + // Custom analytics + analytics.track('Banner Action', { action, url }); +}); + +// jstag3 plugin (separate concern) +experiences.on('experiences:action', ({ action }) => { + jstag.send('experience_action', { action }); +}); + +// URL navigation happens automatically +``` + +**Benefits:** +- ✅ Config is pure data (save to CMS!) +- ✅ Multiple consumers +- ✅ Decoupled (banner doesn't know about tracking) +- ✅ Easy to test +- ✅ Composable + +--- + +## Best Practices + +### 1. Keep Event Handlers Lightweight + +```typescript +// ✅ Good: Quick, non-blocking +experiences.on('experiences:action', ({ experienceId, action }) => { + gtag('event', 'experience_action', { experienceId, action }); +}); + +// ❌ Avoid: Heavy computation or long-running tasks +experiences.on('experiences:action', async ({ experienceId }) => { + // This could block for seconds + const analytics = await heavyAnalyticsInit(); + await analytics.track('event'); +}); +``` + +### 2. Unsubscribe When Done + +```typescript +// Store unsubscribe function +const unsubscribe = experiences.on('experiences:action', handler); + +// Clean up when component unmounts +useEffect(() => { + return () => { + unsubscribe(); + }; +}, []); +``` + +### 3. Use TypeScript for Type Safety + +```typescript +import type { Decision, Experience } from '@prosdevlab/experience-sdk'; + +experiences.on('experiences:evaluated', ({ decision, experience }: { + decision: Decision; + experience?: Experience; +}) => { + // TypeScript will help you + if (experience) { + console.log(experience.id); // Autocomplete works! + } +}); +``` + +### 4. Create Reusable Integration Functions + +```typescript +// integrations/analytics.ts +export function setupAnalyticsTracking(experiences) { + experiences.on('experiences:evaluated', ({ decision, experience }) => { + if (decision.show && experience) { + analytics.track('Experience Shown', { experienceId: experience.id }); + } + }); + + experiences.on('experiences:action', ({ experienceId, action }) => { + analytics.track('Experience Action', { experienceId, action }); + }); +} + +// main.ts +import { setupAnalyticsTracking } from './integrations/analytics'; +setupAnalyticsTracking(experiences); +``` + +--- + +## Next Steps + +- [Getting Started](/getting-started) - Basic event examples +- [Banner Demo](/demo/banner) - See events in action +- [API Reference](/reference) - Complete API docs + diff --git a/docs/pages/reference/index.mdx b/docs/pages/reference/index.mdx new file mode 100644 index 0000000..56e0b90 --- /dev/null +++ b/docs/pages/reference/index.mdx @@ -0,0 +1,273 @@ +# API Reference + +Complete API documentation for the Experience SDK. + +## Core API + +### `createInstance(config?)` + +Creates a new instance of the Experience SDK. + +```typescript +import { createInstance } from '@prosdevlab/experience-sdk'; + +const experiences = createInstance({ + debug: true, + storage: 'session' +}); +``` + +**Parameters:** +- `config` (optional): Configuration object + - `debug?: boolean` - Enable debug mode + - `storage?: 'session' | 'local' | 'memory'` - Storage backend + +**Returns:** `ExperienceRuntime` instance + +--- + +### `init(config?)` + +Initializes the SDK. Call this before registering experiences. + +```typescript +await experiences.init({ debug: true }); +``` + +**Parameters:** +- `config` (optional): Configuration object (same as createInstance) + +**Returns:** `Promise` + +--- + +### `register(id, experience)` + +Registers an experience for evaluation. + +```typescript +experiences.register('my-banner', { + type: 'banner', + targeting: { + url: { contains: '/products' } + }, + content: { + title: 'Special Offer', + message: 'Get 20% off today!' + }, + frequency: { + max: 3, + per: 'session' + } +}); +``` + +**Parameters:** +- `id: string` - Unique identifier for the experience +- `experience: Omit` - Experience configuration + +**Returns:** `void` + +--- + +### `evaluate(context?)` + +Evaluates registered experiences and returns a decision. + +```typescript +const decision = experiences.evaluate({ + url: window.location.href, + user: { id: 'user-123' } +}); +``` + +**Parameters:** +- `context` (optional): Evaluation context + - `url?: string` - Current URL + - `user?: UserContext` - User information + - `timestamp?: number` - Evaluation timestamp + - `custom?: Record` - Custom context + +**Returns:** `Decision` object + +--- + +### `explain(experienceId)` + +Explains why a specific experience would/wouldn't show. + +```typescript +const explanation = experiences.explain('my-banner'); +console.log(explanation?.reasons); +``` + +**Parameters:** +- `experienceId: string` - Experience ID to explain + +**Returns:** `Decision | null` + +--- + +### `getState()` + +Gets current runtime state. + +```typescript +const state = experiences.getState(); +console.log('Registered experiences:', state.experiences); +console.log('Decision history:', state.decisions); +``` + +**Returns:** `RuntimeState` object + +--- + +### `on(event, handler)` + +Subscribes to SDK events. Returns an unsubscribe function. + +```typescript +const unsubscribe = experiences.on('experiences:evaluated', ({ decision, experience }) => { + console.log('Decision made:', decision); +}); + +// Later: stop listening +unsubscribe(); +``` + +**Parameters:** +- `event: string` - Event name (see [Events Reference](/reference/events)) +- `handler: Function` - Event handler function + +**Returns:** `() => void` - Unsubscribe function + +#### Available Events + +##### `experiences:evaluated` + +Emitted after evaluating all registered experiences. + +```typescript +experiences.on('experiences:evaluated', ({ decision, experience }) => { + console.log('Show?', decision.show); + console.log('Experience:', experience?.id); + console.log('Reasons:', decision.reasons); +}); +``` + +**Payload:** +- `decision: Decision` - The evaluation result +- `experience?: Experience` - The matched experience (if any) + +##### `experiences:action` + +Emitted when a user clicks a CTA button. + +```typescript +experiences.on('experiences:action', ({ experienceId, action, url, timestamp }) => { + console.log('Action:', action); + console.log('URL:', url); // Will navigate automatically if present + + // Track in analytics + gtag('event', 'experience_action', { experienceId, action }); +}); +``` + +**Payload:** +- `experienceId: string` - ID of the experience +- `action: string` - Action identifier (e.g., 'cta', 'accept-cookies') +- `url?: string` - Navigation URL (if provided) +- `timestamp: number` - Event timestamp + +**Note:** If `button.url` is provided, navigation happens automatically after the event is emitted. + +##### `experiences:dismissed` + +Emitted when a user dismisses an experience (e.g., closes a banner). + +```typescript +experiences.on('experiences:dismissed', ({ experienceId, timestamp }) => { + console.log('User dismissed:', experienceId); + + // Track dismissal + analytics.track('Experience Dismissed', { experienceId }); +}); +``` + +**Payload:** +- `experienceId: string` - ID of the dismissed experience +- `timestamp: number` - Event timestamp + +**Note:** Dismissals do NOT count as impressions for frequency capping. + +##### `sdk:ready` + +Emitted when SDK initialization is complete. + +```typescript +experiences.on('sdk:ready', () => { + console.log('SDK ready!'); +}); +``` + +##### `sdk:destroy` + +Emitted before SDK is destroyed. + +```typescript +experiences.on('sdk:destroy', () => { + console.log('SDK shutting down'); +}); +``` + +#### Multiple Listeners + +Events can have multiple listeners (unlike callbacks): + +```typescript +// Listener 1: jstag3 tracking +experiences.on('experiences:action', ({ experienceId, action }) => { + window.jstag.send('experience_action', { experienceId, action }); +}); + +// Listener 2: Google Analytics +experiences.on('experiences:action', ({ experienceId, action }) => { + gtag('event', 'experience_action', { experienceId, action }); +}); + +// Listener 3: Custom logic +experiences.on('experiences:action', ({ action }) => { + if (action === 'accept-cookies') { + setCookieConsent(true); + } +}); +``` + +See the [Events Reference](/reference/events) for comprehensive event documentation. + +--- + +### `destroy()` + +Destroys the SDK instance and cleans up resources. + +```typescript +await experiences.destroy(); +``` + +**Returns:** `Promise` + +--- + +## Types + +See the [TypeScript Types](/reference/types) page for complete type definitions. + +## Events + +See the [Events Reference](/reference/events) page for comprehensive event documentation and integration examples. + +## Plugins + +See the [Plugins](/reference/plugins) page for plugin documentation. + diff --git a/docs/pages/reference/plugins.mdx b/docs/pages/reference/plugins.mdx new file mode 100644 index 0000000..268dcce --- /dev/null +++ b/docs/pages/reference/plugins.mdx @@ -0,0 +1,355 @@ +# Plugins + +The Experience SDK uses a plugin architecture powered by [@lytics/sdk-kit](https://github.com/Lytics/sdk-kit). Plugins extend the runtime with additional capabilities. + +## Official Plugins + +### Banner Plugin + +Renders banner experiences in the DOM with automatic positioning, theming, and responsive layout. + +#### Configuration + +```typescript +import { createInstance } from '@prosdevlab/experience-sdk'; + +const experiences = createInstance({ + banner: { + position: 'top', // 'top' | 'bottom' (default: 'top') + zIndex: 10000, // Custom z-index (default: 10000) + dismissable: true // Allow dismissal (default: true) + } +}); +``` + +#### API Methods + +##### `banner.show(experience)` + +Manually show a banner experience. + +```typescript +experiences.banner.show({ + id: 'welcome', + type: 'banner', + targeting: {}, + content: { + title: 'Welcome!', + message: 'Thanks for visiting.', + buttons: [ + { text: 'Get Started', url: '/start', variant: 'primary' } + ] + } +}); +``` + +##### `banner.remove(experienceId)` + +Remove a specific banner. + +```typescript +experiences.banner.remove('welcome'); +``` + +##### `banner.isShowing(experienceId?)` + +Check if a banner is currently showing. + +```typescript +// Check specific banner +if (experiences.banner.isShowing('welcome')) { + console.log('Welcome banner is visible'); +} + +// Check if any banner is showing +if (experiences.banner.isShowing()) { + console.log('A banner is visible'); +} +``` + +#### Button Variants + +Banners support multiple buttons with different visual styles: + +```typescript +buttons: [ + { + text: 'Accept all', + action: 'accept', + variant: 'primary', // Blue, prominent + metadata: { consent: ['all'] } + }, + { + text: 'Reject', + action: 'reject', + variant: 'secondary', // Gray, outlined + metadata: { consent: [] } + }, + { + text: 'Preferences', + action: 'preferences', + variant: 'link', // Text link style + url: '/preferences' + } +] +``` + +**Variants:** +- `primary` - Blue button for main actions (default) +- `secondary` - Gray outlined button for secondary actions +- `link` - Text link style for tertiary actions + +#### Responsive Layout + +Banners automatically adapt to screen size: +- **Desktop** (>640px): Buttons display inline horizontally +- **Mobile** (≤640px): Buttons stack vertically for better touch interaction + +#### Events + +The banner plugin emits the following events: + +**`experiences:shown`** + +```typescript +experiences.on('experiences:shown', ({ experienceId, type }) => { + console.log(`${type} shown:`, experienceId); +}); +``` + +**`experiences:action`** + +```typescript +experiences.on('experiences:action', ({ experienceId, action, variant, metadata, url }) => { + console.log('Button clicked:', action, 'variant:', variant); + if (metadata) { + console.log('Metadata:', metadata); + } +}); +``` + +**`experiences:dismissed`** + +```typescript +experiences.on('experiences:dismissed', ({ experienceId }) => { + console.log('Banner dismissed:', experienceId); +}); +``` + +See [Banner Examples](/demo/banner) for complete usage examples. + +--- + +### Frequency Plugin + +Manages impression tracking and frequency capping using persistent storage. + +#### Configuration + +```typescript +const experiences = createInstance({ + frequency: { + enabled: true, // Enable frequency tracking (default: true) + storage: 'local' // 'local' | 'session' | 'memory' (default: 'local') + } +}); +``` + +#### Experience-Level Frequency + +Define frequency caps per experience: + +```typescript +experiences.register('welcome', { + type: 'banner', + content: { message: 'Welcome!' }, + frequency: { + max: 3, // Maximum impressions + per: 'session' // 'session' | 'day' | 'week' + } +}); +``` + +#### API Methods + +##### `frequency.getImpressionCount(experienceId, period?)` + +Get impression count for an experience. + +```typescript +const count = experiences.frequency.getImpressionCount('welcome', 'session'); +console.log(`Shown ${count} times this session`); +``` + +**Parameters:** +- `experienceId: string` - Experience to check +- `period?: 'session' | 'day' | 'week'` - Time period (optional) + +**Returns:** `number` + +##### `frequency.recordImpression(experienceId)` + +Manually record an impression. + +```typescript +experiences.frequency.recordImpression('welcome'); +``` + +##### `frequency.reset(experienceId?)` + +Reset impression counts. + +```typescript +// Reset specific experience +experiences.frequency.reset('welcome'); + +// Reset all experiences +experiences.frequency.reset(); +``` + +#### How It Works + +1. **Impressions** are recorded when `experiences:shown` event is emitted +2. **Storage** persists counts in localStorage (or sessionStorage/memory) +3. **Evaluation** checks counts before showing experiences +4. **Dismissals** do NOT count as impressions + +#### Storage Keys + +Frequency data is stored with namespaced keys: + +``` +experiences.frequency.session:welcome = 3 +experiences.frequency.day:2024-12-25:welcome = 1 +experiences.frequency.week:2024-W52:welcome = 5 +``` + +--- + +### Debug Plugin + +Provides console logging and window event emission for debugging and Chrome extension integration. + +#### Configuration + +```typescript +const experiences = createInstance({ + debug: { + enabled: true, // Enable debug output (default: false) + windowEvents: true, // Emit to window (default: true when enabled) + prefix: 'experiences' // Log prefix (default: 'experiences') + } +}); +``` + +Or use the shorthand: + +```typescript +const experiences = createInstance({ debug: true }); +``` + +#### API Methods + +##### `debug.log(message, data?)` + +Log a message with optional data. + +```typescript +experiences.debug.log('User clicked button', { action: 'cta' }); +``` + +**Console output:** +``` +[experiences] User clicked button { action: 'cta' } +``` + +**Window event:** +```javascript +window.addEventListener('experiences:debug', (event) => { + console.log(event.detail); + // { message: 'User clicked button', data: { action: 'cta' }, timestamp: ... } +}); +``` + +#### Automatic Logging + +When debug mode is enabled, the plugin automatically logs: + +- SDK initialization +- Experience registration +- Evaluation results +- Decision reasons +- Event emissions + +```typescript +const experiences = createInstance({ debug: true }); + +experiences.register('welcome', { ... }); +// [experiences] Experience registered: welcome + +const decision = experiences.evaluate(); +// [experiences] Evaluating 1 experience(s) +// [experiences] Decision: show=true, experience=welcome +// [experiences] Reasons: ["✅ URL matches", "✅ Frequency: 0/3 this session"] +``` + +#### Chrome Extension Integration + +The debug plugin emits events to `window` that can be captured by Chrome extensions: + +```javascript +// In Chrome extension content script +window.addEventListener('experiences:debug', (event) => { + chrome.runtime.sendMessage({ + type: 'EXPERIENCE_DEBUG', + payload: event.detail + }); +}); +``` + +This enables building DevTools panels for inspecting experience decisions in real-time. + +--- + +## Plugin Development + +The SDK uses [@lytics/sdk-kit](https://github.com/Lytics/sdk-kit)'s plugin system. Custom plugins can be created using the `PluginFunction` interface: + +```typescript +import type { PluginFunction } from '@lytics/sdk-kit'; + +const myPlugin: PluginFunction = (plugin, instance, config) => { + // Namespace + plugin.ns('my.plugin'); + + // Default config + plugin.defaults({ myPlugin: { enabled: true } }); + + // Expose API + plugin.expose({ + myMethod() { + console.log('Hello from plugin!'); + } + }); + + // Listen to events + instance.on('experiences:evaluated', (decision) => { + // React to evaluations + }); +}; + +// Use the plugin +const experiences = createInstance(); +experiences.use(myPlugin); +``` + +See the [sdk-kit documentation](https://github.com/Lytics/sdk-kit) for complete plugin development guide. + +--- + +## Next Steps + +- [Banner Examples](/demo/banner) - Complete banner usage examples +- [Events Reference](/reference/events) - All SDK events +- [API Reference](/reference) - Core API documentation + diff --git a/docs/theme.config.tsx b/docs/theme.config.tsx new file mode 100644 index 0000000..d31b678 --- /dev/null +++ b/docs/theme.config.tsx @@ -0,0 +1,60 @@ +import type { DocsThemeConfig } from 'nextra-theme-docs'; + +const config: DocsThemeConfig = { + logo: Experience SDK, + project: { + link: 'https://github.com/prosdevlab/experience-sdk', + }, + docsRepositoryBase: 'https://github.com/prosdevlab/experience-sdk/tree/main/docs', + footer: { + text: ( + + MIT {new Date().getFullYear()} ©{' '} + + prosdevlab + + + ), + }, + useNextSeoProps() { + return { + titleTemplate: '%s – Experience SDK', + }; + }, + head: ( + <> + + + + + + ), +}; + +export default config; diff --git a/docs/tsconfig.json b/docs/tsconfig.json new file mode 100644 index 0000000..2461717 --- /dev/null +++ b/docs/tsconfig.json @@ -0,0 +1,41 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "preserve", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "isolatedModules": true, + "incremental": true, + "noEmit": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./*" + ] + }, + "allowJs": true + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/docs/vercel.json b/docs/vercel.json new file mode 100644 index 0000000..6f94401 --- /dev/null +++ b/docs/vercel.json @@ -0,0 +1,7 @@ +{ + "buildCommand": "cd .. && pnpm build && cd docs", + "outputDirectory": ".next", + "installCommand": "cd .. && pnpm install", + "framework": "nextjs" +} + diff --git a/package.json b/package.json index 4217880..5eddd55 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,14 @@ "author": "prosdevlab", "license": "MIT", "workspaces": [ - "packages/*" + "packages/*", + "docs" ], "scripts": { - "build": "turbo build", - "dev": "turbo dev", + "build": "turbo build --filter=!docs", + "build:sdk": "pnpm --filter @prosdevlab/experience-sdk-plugins build && pnpm --filter @prosdevlab/experience-sdk build", + "build:docs": "pnpm build:sdk && pnpm --filter docs build", + "dev": "turbo dev --filter=!docs", "lint": "turbo lint", "test": "vitest run", "test:watch": "vitest", @@ -25,7 +28,11 @@ "lint-staged": "biome check --write --no-errors-on-unmatched", "changeset": "changeset", "version": "changeset version", - "release": "pnpm build && changeset publish" + "release": "pnpm build && changeset publish", + "docs:dev": "pnpm --filter docs dev", + "docs:build": "pnpm build:docs", + "docs:start": "pnpm --filter docs start", + "docs:export": "pnpm build:docs && pnpm --filter docs export" }, "devDependencies": { "@biomejs/biome": "2.3.10", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 1ad57ff..c562411 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -21,6 +21,7 @@ export { createInstance, destroy, evaluate, + evaluateAll, explain, getState, init, diff --git a/packages/core/src/runtime.test.ts b/packages/core/src/runtime.test.ts index 87fd6fe..395a32f 100644 --- a/packages/core/src/runtime.test.ts +++ b/packages/core/src/runtime.test.ts @@ -175,13 +175,34 @@ describe('ExperienceRuntime', () => { }); }); - it('should emit evaluated event', () => { + it('should emit evaluated event with decision and experience', async () => { + await runtime.init(); + + // Register an experience so we have something to evaluate + runtime.register('test-banner', { + type: 'banner', + targeting: { url: { contains: '/' } }, + content: { + title: 'Test', + message: 'Message', + }, + }); + const evaluatedHandler = vi.fn(); runtime.on('experiences:evaluated', evaluatedHandler); runtime.evaluate(); expect(evaluatedHandler).toHaveBeenCalledOnce(); + const emittedData = evaluatedHandler.mock.calls[0][0]; + expect(emittedData).toHaveProperty('decision'); + expect(emittedData).toHaveProperty('experience'); + expect(emittedData.decision).toHaveProperty('show', true); + expect(emittedData.decision).toHaveProperty('reasons'); + // Experience exists because we registered one above + expect(emittedData.experience).toBeDefined(); + expect(emittedData.experience).toHaveProperty('id', 'test-banner'); + expect(emittedData.experience).toHaveProperty('type', 'banner'); }); it('should store decision history', () => { @@ -427,7 +448,9 @@ describe('ExperienceRuntime', () => { }); describe('frequency targeting', () => { - it('should track frequency rule in trace', () => { + it('should track frequency rule in trace', async () => { + await runtime.init(); + runtime.register('limited', { type: 'banner', targeting: {}, @@ -440,8 +463,8 @@ describe('ExperienceRuntime', () => { const decision = runtime.evaluate(); - expect(decision.trace.some((step) => step.step === 'check-frequency-rule')).toBe(true); - expect(decision.reasons.some((r) => r.includes('Frequency rule'))).toBe(true); + expect(decision.trace.some((step) => step.step === 'check-frequency-cap')).toBe(true); + expect(decision.reasons.some((r) => r.includes('Frequency cap'))).toBe(true); }); }); @@ -460,4 +483,206 @@ describe('ExperienceRuntime', () => { expect(decision.show).toBe(true); }); }); + + describe('evaluateAll()', () => { + beforeEach(async () => { + await runtime.init(); + }); + + it('should return decisions for all matching experiences', () => { + runtime.register('banner1', { + type: 'banner', + targeting: { url: { contains: '/' } }, + content: { title: 'Banner 1', message: 'Message 1' }, + }); + + runtime.register('banner2', { + type: 'banner', + targeting: { url: { contains: '/' } }, + content: { title: 'Banner 2', message: 'Message 2' }, + }); + + runtime.register('banner3', { + type: 'banner', + targeting: { url: { contains: '/nonexistent' } }, + content: { title: 'Banner 3', message: 'Message 3' }, + }); + + const decisions = runtime.evaluateAll({ url: 'https://example.com/' }); + + expect(decisions).toHaveLength(3); + expect(decisions.filter((d) => d.show)).toHaveLength(2); + expect(decisions[0].experienceId).toBe('banner1'); + expect(decisions[1].experienceId).toBe('banner2'); + expect(decisions[2].experienceId).toBe('banner3'); + expect(decisions[2].show).toBe(false); + }); + + it('should sort experiences by priority (descending)', () => { + runtime.register('low', { + type: 'banner', + targeting: {}, + content: { title: 'Low', message: 'Low priority' }, + priority: 10, + }); + + runtime.register('high', { + type: 'banner', + targeting: {}, + content: { title: 'High', message: 'High priority' }, + priority: 100, + }); + + runtime.register('medium', { + type: 'banner', + targeting: {}, + content: { title: 'Medium', message: 'Medium priority' }, + priority: 50, + }); + + const decisions = runtime.evaluateAll(); + + expect(decisions[0].experienceId).toBe('high'); // 100 + expect(decisions[1].experienceId).toBe('medium'); // 50 + expect(decisions[2].experienceId).toBe('low'); // 10 + }); + + it('should maintain registration order for tied priorities', () => { + runtime.register('first', { + type: 'banner', + targeting: {}, + content: { title: 'First', message: 'First registered' }, + priority: 50, + }); + + runtime.register('second', { + type: 'banner', + targeting: {}, + content: { title: 'Second', message: 'Second registered' }, + priority: 50, + }); + + runtime.register('third', { + type: 'banner', + targeting: {}, + content: { title: 'Third', message: 'Third registered' }, + priority: 50, + }); + + const decisions = runtime.evaluateAll(); + + expect(decisions[0].experienceId).toBe('first'); + expect(decisions[1].experienceId).toBe('second'); + expect(decisions[2].experienceId).toBe('third'); + }); + + it('should default to priority 0 when not specified', () => { + runtime.register('no-priority', { + type: 'banner', + targeting: {}, + content: { title: 'No Priority', message: 'No priority set' }, + }); + + runtime.register('with-priority', { + type: 'banner', + targeting: {}, + content: { title: 'With Priority', message: 'Priority set' }, + priority: 1, + }); + + const decisions = runtime.evaluateAll(); + + // with-priority (1) should come before no-priority (0) + expect(decisions[0].experienceId).toBe('with-priority'); + expect(decisions[1].experienceId).toBe('no-priority'); + }); + + it('should check frequency caps for each experience', () => { + runtime.register('capped', { + type: 'banner', + targeting: {}, + content: { title: 'Capped', message: 'Has frequency cap' }, + frequency: { max: 1, per: 'session' }, + }); + + runtime.register('unlimited', { + type: 'banner', + targeting: {}, + content: { title: 'Unlimited', message: 'No frequency cap' }, + }); + + // First evaluation: both show + const decisions1 = runtime.evaluateAll(); + expect(decisions1.filter((d) => d.show)).toHaveLength(2); + + // Second evaluation: only unlimited shows (capped reached limit) + const decisions2 = runtime.evaluateAll(); + expect(decisions2.filter((d) => d.show)).toHaveLength(1); + expect(decisions2.find((d) => d.experienceId === 'unlimited')?.show).toBe(true); + expect(decisions2.find((d) => d.experienceId === 'capped')?.show).toBe(false); + }); + + it('should emit experiences:evaluated event with array', () => { + const handler = vi.fn(); + runtime.on('experiences:evaluated', handler); + + runtime.register('banner1', { + type: 'banner', + targeting: {}, + content: { title: 'Banner 1', message: 'Message 1' }, + }); + + runtime.register('banner2', { + type: 'banner', + targeting: {}, + content: { title: 'Banner 2', message: 'Message 2' }, + }); + + runtime.evaluateAll(); + + expect(handler).toHaveBeenCalledOnce(); + expect(handler).toHaveBeenCalledWith( + expect.arrayContaining([ + expect.objectContaining({ + decision: expect.objectContaining({ experienceId: 'banner1' }), + experience: expect.objectContaining({ id: 'banner1' }), + }), + expect.objectContaining({ + decision: expect.objectContaining({ experienceId: 'banner2' }), + experience: expect.objectContaining({ id: 'banner2' }), + }), + ]) + ); + }); + + it('should only emit matched experiences in event', () => { + const handler = vi.fn(); + runtime.on('experiences:evaluated', handler); + + runtime.register('match', { + type: 'banner', + targeting: { url: { contains: '/' } }, + content: { title: 'Match', message: 'Matches' }, + }); + + runtime.register('no-match', { + type: 'banner', + targeting: { url: { contains: '/nonexistent' } }, + content: { title: 'No Match', message: 'Does not match' }, + }); + + runtime.evaluateAll({ url: 'https://example.com/' }); + + expect(handler).toHaveBeenCalledOnce(); + const emittedDecisions = handler.mock.calls[0][0]; + expect(emittedDecisions).toHaveLength(1); + expect(emittedDecisions[0].decision.experienceId).toBe('match'); + }); + + it('should return empty array when no experiences registered', () => { + const decisions = runtime.evaluateAll(); + + expect(decisions).toEqual([]); + }); + }); }); diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts index e69ea17..498d0bf 100644 --- a/packages/core/src/runtime.ts +++ b/packages/core/src/runtime.ts @@ -27,6 +27,7 @@ export class ExperienceRuntime { private experiences: Map = new Map(); private decisions: Decision[] = []; private initialized = false; + private destroyed = false; constructor(config: ExperienceConfig = {}) { // Create SDK instance @@ -51,6 +52,22 @@ export class ExperienceRuntime { return; } + // Recreate SDK if it was destroyed + if (this.destroyed) { + this.sdk = new SDK({ + name: 'experience-sdk', + ...config, + }); + + // Re-register core plugins + this.sdk.use(storagePlugin); + this.sdk.use(debugPlugin); + this.sdk.use(frequencyPlugin); + this.sdk.use(bannerPlugin); + + this.destroyed = false; + } + if (config) { // Merge config if provided Object.entries(config).forEach(([key, value]) => { @@ -74,12 +91,18 @@ export class ExperienceRuntime { const exp: Experience = { id, ...experience }; this.experiences.set(id, exp); + // Register frequency config with frequency plugin if it exists + if (exp.frequency && (this.sdk as any).frequency?._registerExperience) { + (this.sdk as any).frequency._registerExperience(id, exp.frequency.per); + } + this.sdk.emit('experiences:registered', { id, experience: exp }); } /** * Evaluate experiences against context * Returns decision with explainability + * First match wins (use evaluateAll() for multiple experiences) */ evaluate(context?: Partial): Decision { const startTime = Date.now(); @@ -97,6 +120,44 @@ export class ExperienceRuntime { allTrace.push(...result.trace); if (result.matched) { + // Check frequency cap if experience has frequency rules + if (experience.frequency && (this.sdk as any).frequency) { + const freqStart = Date.now(); + const hasReached = (this.sdk as any).frequency.hasReachedCap( + experience.id, + experience.frequency.max, + experience.frequency.per + ); + + allTrace.push({ + step: 'check-frequency-cap', + timestamp: freqStart, + duration: Date.now() - freqStart, + input: experience.frequency, + output: hasReached, + passed: !hasReached, + }); + + if (hasReached) { + const count = (this.sdk as any).frequency.getImpressionCount( + experience.id, + experience.frequency.per + ); + allReasons.push( + `Frequency cap reached (${count}/${experience.frequency.max} this ${experience.frequency.per})` + ); + continue; // Skip this experience, check next + } + + const count = (this.sdk as any).frequency.getImpressionCount( + experience.id, + experience.frequency.per + ); + allReasons.push( + `Frequency cap not reached (${count}/${experience.frequency.max} this ${experience.frequency.per})` + ); + } + matchedExperience = experience; break; // First match wins } @@ -118,12 +179,115 @@ export class ExperienceRuntime { // Store decision for inspection this.decisions.push(decision); - // Emit for plugins to react - this.sdk.emit('experiences:evaluated', decision); + // Emit for plugins to react (include matched experience for rendering) + this.sdk.emit('experiences:evaluated', { + decision, + experience: matchedExperience, + }); return decision; } + /** + * Evaluate all experiences against context + * Returns multiple decisions (sorted by priority) + * All matching experiences will be shown + */ + evaluateAll(context?: Partial): Decision[] { + const evalContext = buildContext(context); + + // Sort experiences by priority (higher = more important) + // Ties maintain registration order (Map preserves insertion order) + const sortedExperiences = Array.from(this.experiences.values()).sort((a, b) => { + const priorityA = a.priority ?? 0; + const priorityB = b.priority ?? 0; + return priorityB - priorityA; // Descending order + }); + + const decisions: Decision[] = []; + + // Evaluate each experience + for (const experience of sortedExperiences) { + const expStartTime = Date.now(); + const result = evaluateExperience(experience, evalContext); + + let show = result.matched; + const reasons = [...result.reasons]; + const trace = [...result.trace]; + + // Check frequency cap if experience has frequency rules + if (show && experience.frequency && (this.sdk as any).frequency) { + const freqStart = Date.now(); + const hasReached = (this.sdk as any).frequency.hasReachedCap( + experience.id, + experience.frequency.max, + experience.frequency.per + ); + + trace.push({ + step: 'check-frequency-cap', + timestamp: freqStart, + duration: Date.now() - freqStart, + input: experience.frequency, + output: hasReached, + passed: !hasReached, + }); + + if (hasReached) { + const count = (this.sdk as any).frequency.getImpressionCount( + experience.id, + experience.frequency.per + ); + reasons.push( + `Frequency cap reached (${count}/${experience.frequency.max} this ${experience.frequency.per})` + ); + show = false; + } else { + const count = (this.sdk as any).frequency.getImpressionCount( + experience.id, + experience.frequency.per + ); + reasons.push( + `Frequency cap not reached (${count}/${experience.frequency.max} this ${experience.frequency.per})` + ); + } + } + + const decision: Decision = { + show, + experienceId: experience.id, + reasons, + trace, + context: evalContext, + metadata: { + evaluatedAt: Date.now(), + totalDuration: Date.now() - expStartTime, + experiencesEvaluated: 1, + }, + }; + + decisions.push(decision); + this.decisions.push(decision); + } + + // Emit single event with all decisions (array) + // Plugins can filter to their relevant experiences + const matchedDecisions = decisions.filter((d) => d.show); + const matchedExperiences = matchedDecisions + .map((d) => d.experienceId && this.experiences.get(d.experienceId)) + .filter((exp): exp is Experience => exp !== undefined); + + this.sdk.emit( + 'experiences:evaluated', + matchedDecisions.map((decision, index) => ({ + decision, + experience: matchedExperiences[index], + })) + ); + + return decisions; + } + /** * Explain a specific experience */ @@ -158,7 +322,7 @@ export class ExperienceRuntime { initialized: this.initialized, experiences: new Map(this.experiences), decisions: [...this.decisions], - config: this.sdk.getAll(), + config: this.sdk ? this.sdk.getAll() : {}, }; } @@ -173,7 +337,10 @@ export class ExperienceRuntime { * Destroy runtime */ async destroy(): Promise { - await this.sdk.destroy(); + if (this.sdk) { + await this.sdk.destroy(); + } + this.destroyed = true; this.experiences.clear(); this.decisions = []; this.initialized = false; @@ -229,22 +396,6 @@ export function evaluateExperience( } } - // Evaluate frequency rule (will be checked by frequency plugin) - if (experience.frequency) { - const freqStart = Date.now(); - // Note: Actual frequency checking is done by the frequency plugin - // This just records that a frequency rule exists - trace.push({ - step: 'check-frequency-rule', - timestamp: freqStart, - duration: Date.now() - freqStart, - input: experience.frequency, - output: true, - passed: true, - }); - reasons.push('Frequency rule will be checked by frequency plugin'); - } - return { matched, reasons, trace }; } diff --git a/packages/core/src/singleton.ts b/packages/core/src/singleton.ts index f6420d7..f004802 100644 --- a/packages/core/src/singleton.ts +++ b/packages/core/src/singleton.ts @@ -67,6 +67,7 @@ export function register(id: string, experience: Omit): void { /** * Evaluate experiences against current context + * First match wins (use evaluateAll() for multiple experiences) * * @example * ```typescript @@ -83,6 +84,28 @@ export function evaluate(context?: Partial): Decision { return defaultInstance.evaluate(context); } +/** + * Evaluate all experiences against current context + * Returns array of decisions sorted by priority (higher = more important) + * All matching experiences will be shown + * + * @example + * ```typescript + * import { evaluateAll } from '@prosdevlab/experience-sdk'; + * + * const decisions = evaluateAll({ url: window.location.href }); + * decisions.forEach(decision => { + * if (decision.show) { + * console.log('Show:', decision.experienceId); + * console.log('Reasons:', decision.reasons); + * } + * }); + * ``` + */ +export function evaluateAll(context?: Partial): Decision[] { + return defaultInstance.evaluateAll(context); +} + /** * Explain why a specific experience would/wouldn't show * @@ -160,6 +183,7 @@ export const experiences = { init, register, evaluate, + evaluateAll, explain, getState, on, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index e2fb2c1..0bff975 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -22,6 +22,8 @@ export interface Experience { content: ExperienceContent; /** Optional frequency capping configuration */ frequency?: FrequencyConfig; + /** Priority for ordering (higher = more important, default: 0) */ + priority?: number; } /** diff --git a/packages/core/tsup.config.ts b/packages/core/tsup.config.ts index 0744fd7..c12bcd0 100644 --- a/packages/core/tsup.config.ts +++ b/packages/core/tsup.config.ts @@ -1,16 +1,22 @@ import { defineConfig } from 'tsup'; -export default defineConfig({ +export default defineConfig((options) => ({ entry: ['src/index.ts'], - format: ['esm', 'iife'], + // Only build ESM in watch mode (faster, no bundling issues) + // Build both ESM and IIFE in production + format: options.watch ? ['esm'] : ['esm', 'iife'], dts: true, splitting: false, sourcemap: true, clean: true, treeshake: true, - minify: true, + minify: !options.watch, // Don't minify in watch mode for faster builds outDir: 'dist', globalName: 'experiences', - // Bundle dependencies for IIFE (script tag) - noExternal: ['@lytics/sdk-kit', '@lytics/sdk-kit-plugins', '@prosdevlab/experience-sdk-plugins'], -}); + // Bundle all dependencies for IIFE (script tag) in production + // In watch mode (ESM only), external workspace packages to avoid resolution issues + noExternal: options.watch + ? ['@lytics/sdk-kit', '@lytics/sdk-kit-plugins'] + : ['@lytics/sdk-kit', '@lytics/sdk-kit-plugins', '@prosdevlab/experience-sdk-plugins'], + external: options.watch ? ['@prosdevlab/experience-sdk-plugins'] : [], +})); diff --git a/packages/plugins/src/banner/banner.test.ts b/packages/plugins/src/banner/banner.test.ts index f55804b..a35ff3b 100644 --- a/packages/plugins/src/banner/banner.test.ts +++ b/packages/plugins/src/banner/banner.test.ts @@ -262,7 +262,7 @@ describe('Banner Plugin', () => { expect(sdk.banner.isShowing()).toBe(true); }); - it('should remove existing banner when showing new one', () => { + it('should support multiple banners simultaneously', () => { const experience1: Experience = { id: 'banner-1', type: 'banner', @@ -287,7 +287,8 @@ describe('Banner Plugin', () => { expect(document.querySelector('[data-experience-id="banner-1"]')).toBeTruthy(); sdk.banner.show(experience2); - expect(document.querySelector('[data-experience-id="banner-1"]')).toBeNull(); + // Both banners should be present + expect(document.querySelector('[data-experience-id="banner-1"]')).toBeTruthy(); expect(document.querySelector('[data-experience-id="banner-2"]')).toBeTruthy(); }); @@ -340,6 +341,98 @@ describe('Banner Plugin', () => { }); }); + it('should auto-render banner on experiences:evaluated event', () => { + const experience: Experience = { + id: 'auto-banner', + type: 'banner', + targeting: {}, + content: { + title: 'Auto Banner', + message: 'Auto message', + }, + }; + + // Emit the event that runtime would emit + sdk.emit('experiences:evaluated', { + decision: { + show: true, + experienceId: 'auto-banner', + reasons: ['test'], + trace: [], + context: {}, + metadata: { evaluatedAt: Date.now(), totalDuration: 0, experiencesEvaluated: 1 }, + }, + experience, + }); + + // Banner should be automatically rendered + const banner = document.querySelector('[data-experience-id="auto-banner"]'); + expect(banner).toBeTruthy(); + expect(banner?.textContent).toContain('Auto Banner'); + }); + + it('should auto-hide banner when decision is false', () => { + const experience: Experience = { + id: 'hide-banner', + type: 'banner', + targeting: {}, + content: { + title: 'Hide Test', + message: 'Will hide', + }, + }; + + // First show the banner + sdk.banner.show(experience); + expect(sdk.banner.isShowing()).toBe(true); + + // Emit event with show: false + sdk.emit('experiences:evaluated', { + decision: { + show: false, + experienceId: 'hide-banner', + reasons: ['Frequency cap reached'], + trace: [], + context: {}, + metadata: { evaluatedAt: Date.now(), totalDuration: 0, experiencesEvaluated: 1 }, + }, + experience, + }); + + // Banner should be removed + expect(sdk.banner.isShowing()).toBe(false); + expect(document.querySelector('[data-experience-id="hide-banner"]')).toBeNull(); + }); + + it('should only handle banner-type experiences', () => { + const modalExperience = { + id: 'modal', + type: 'modal' as const, + targeting: {}, + content: { + title: 'Modal', + body: 'Modal content', + }, + }; + + // Emit event with modal type + sdk.emit('experiences:evaluated', { + decision: { + show: true, + experienceId: 'modal', + reasons: ['test'], + trace: [], + context: {}, + metadata: { evaluatedAt: Date.now(), totalDuration: 0, experiencesEvaluated: 1 }, + }, + experience: modalExperience, + }); + + // Banner should NOT be rendered + expect(document.querySelector('[data-experience-id="modal"]')).toBeNull(); + expect(sdk.banner.isShowing()).toBe(false); + }); + it('should remove banner on destroy event', async () => { const experience: Experience = { id: 'test-banner', @@ -362,6 +455,203 @@ describe('Banner Plugin', () => { }); }); + describe('CTA Button', () => { + it('should render multiple buttons from buttons array', () => { + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Cookie consent', + buttons: [ + { text: 'Accept all', action: 'accept', variant: 'primary' }, + { text: 'Reject', action: 'reject', variant: 'secondary' }, + { text: 'Preferences', action: 'preferences', variant: 'link' }, + ], + }, + }; + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + const buttons = banner?.querySelectorAll('button'); + + // Should have 3 action buttons + 1 dismiss button (default) + expect(buttons?.length).toBe(4); + expect(buttons?.[0].textContent).toBe('Accept all'); + expect(buttons?.[1].textContent).toBe('Reject'); + expect(buttons?.[2].textContent).toBe('Preferences'); + }); + + it('should use default primary variant when not specified', () => { + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Test', + buttons: [{ text: 'Click Me', action: 'test' }], + }, + }; + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + const button = banner?.querySelector('button') as HTMLElement; + + expect(button).toBeTruthy(); + // Primary variant should have white text + expect(button.style.color).toContain('255'); // rgb(255, 255, 255) + }); + + it('should emit action event with variant and metadata', () => { + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Cookie consent', + buttons: [ + { + text: 'Accept', + action: 'accept', + variant: 'primary', + metadata: { consent_categories: ['all'] }, + }, + ], + }, + }; + + let emittedEvent: any; + sdk.on('experiences:action', (event: any) => { + emittedEvent = event; + }); + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + const button = banner?.querySelector('button') as HTMLElement; + button.click(); + + expect(emittedEvent).toBeTruthy(); + expect(emittedEvent.action).toBe('accept'); + expect(emittedEvent.variant).toBe('primary'); + expect(emittedEvent.metadata).toEqual({ consent_categories: ['all'] }); + }); + + it('should not render CTA button when not provided', () => { + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Test message', + }, + }; + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + // Should only have dismiss button (×), not a CTA button + const buttons = banner?.querySelectorAll('button'); + expect(buttons?.length).toBe(1); + expect(buttons?.[0].textContent).toBe('×'); + }); + + it('should emit experiences:action event when button clicked', () => { + const handler = vi.fn(); + sdk.on('experiences:action', handler); + + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Test message', + buttons: [ + { + text: 'Click Me', + action: 'test-action', + url: '/test', + }, + ], + }, + }; + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + const ctaButton = Array.from(banner?.querySelectorAll('button') || []).find( + (btn) => btn.textContent === 'Click Me' + ) as HTMLElement; + + ctaButton.click(); + + expect(handler).toHaveBeenCalledWith({ + experienceId: 'test-banner', + type: 'banner', + action: 'test-action', + url: '/test', + variant: 'primary', + metadata: undefined, + timestamp: expect.any(Number), + }); + }); + + it('should respect dismissable: false config', () => { + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Test message', + buttons: [ + { + text: 'Click Me', + }, + ], + dismissable: false, + }, + }; + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + const buttons = banner?.querySelectorAll('button'); + + // Should only have action button, no dismiss button + expect(buttons?.length).toBe(1); + expect(buttons?.[0].textContent).toBe('Click Me'); + }); + + it('should show both action and dismiss button when both provided', () => { + const experience: Experience = { + id: 'test-banner', + type: 'banner', + targeting: {}, + content: { + message: 'Test message', + buttons: [ + { + text: 'Learn More', + }, + ], + dismissable: true, + }, + }; + + sdk.banner.show(experience); + + const banner = document.querySelector('[data-experience-id="test-banner"]'); + const buttons = banner?.querySelectorAll('button'); + + // Should have both buttons + expect(buttons?.length).toBe(2); + expect(buttons?.[0].textContent).toBe('Learn More'); + expect(buttons?.[1].textContent).toBe('×'); + }); + }); + describe('Styling', () => { it('should apply correct z-index', () => { const experience: Experience = { diff --git a/packages/plugins/src/banner/banner.ts b/packages/plugins/src/banner/banner.ts index ee8883b..1125855 100644 --- a/packages/plugins/src/banner/banner.ts +++ b/packages/plugins/src/banner/banner.ts @@ -48,29 +48,65 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { }, }); - let activeBanner: HTMLElement | null = null; + // Track multiple active banners by experience ID + const activeBanners = new Map(); /** * Create banner DOM element */ function createBannerElement(experience: Experience): HTMLElement { const content = experience.content as BannerContent; - const position = config.get('banner.position') ?? 'top'; - const dismissable = config.get('banner.dismissable') ?? true; + // Allow per-experience position override, fall back to global config + const position = content.position ?? config.get('banner.position') ?? 'top'; + const dismissable = content.dismissable ?? config.get('banner.dismissable') ?? true; const zIndex = config.get('banner.zIndex') ?? 10000; + // Detect dark mode + const isDarkMode = document.documentElement.classList.contains('dark'); + + // Theme-aware colors - professional subtle style + const bgColor = isDarkMode ? '#1f2937' : '#f9fafb'; + const textColor = isDarkMode ? '#f3f4f6' : '#111827'; + const borderColor = isDarkMode ? '#374151' : '#e5e7eb'; + const shadowColor = isDarkMode ? 'rgba(0, 0, 0, 0.3)' : 'rgba(0, 0, 0, 0.05)'; + // Create banner container const banner = document.createElement('div'); banner.setAttribute('data-experience-id', experience.id); + + // Add responsive media query styles + const styleId = `banner-responsive-${experience.id}`; + if (!document.getElementById(styleId)) { + const style = document.createElement('style'); + style.id = styleId; + style.textContent = ` + @media (max-width: 640px) { + [data-experience-id="${experience.id}"] { + flex-direction: column !important; + align-items: flex-start !important; + } + [data-experience-id="${experience.id}"] > div:last-child { + width: 100%; + flex-direction: column !important; + } + [data-experience-id="${experience.id}"] button { + width: 100%; + } + } + `; + document.head.appendChild(style); + } + banner.style.cssText = ` position: fixed; ${position}: 0; left: 0; right: 0; - background: #007bff; - color: #ffffff; + background: ${bgColor}; + color: ${textColor}; padding: 16px 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + border-${position === 'top' ? 'bottom' : 'top'}: 1px solid ${borderColor}; + box-shadow: 0 ${position === 'top' ? '1' : '-1'}px 3px 0 ${shadowColor}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; line-height: 1.5; @@ -100,21 +136,117 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { banner.appendChild(contentDiv); + // Create button container for actions and/or dismiss + const buttonContainer = document.createElement('div'); + buttonContainer.style.cssText = ` + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; + `; + + // Helper function to create button with variant styling + function createButton(buttonConfig: { + text: string; + action?: string; + url?: string; + variant?: 'primary' | 'secondary' | 'link'; + metadata?: Record; + }): HTMLButtonElement { + const button = document.createElement('button'); + button.textContent = buttonConfig.text; + + const variant = buttonConfig.variant || 'primary'; + + // Variant-based styling + let bg: string, hoverBg: string, textColor: string, border: string; + + if (variant === 'primary') { + bg = isDarkMode ? '#3b82f6' : '#2563eb'; + hoverBg = isDarkMode ? '#2563eb' : '#1d4ed8'; + textColor = '#ffffff'; + border = 'none'; + } else if (variant === 'secondary') { + bg = isDarkMode ? '#374151' : '#ffffff'; + hoverBg = isDarkMode ? '#4b5563' : '#f9fafb'; + textColor = isDarkMode ? '#f3f4f6' : '#374151'; + border = isDarkMode ? '1px solid #4b5563' : '1px solid #d1d5db'; + } else { + // 'link' + bg = 'transparent'; + hoverBg = isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)'; + textColor = isDarkMode ? '#93c5fd' : '#2563eb'; + border = 'none'; + } + + button.style.cssText = ` + background: ${bg}; + border: ${border}; + color: ${textColor}; + padding: ${variant === 'link' ? '4px 8px' : '8px 16px'}; + font-size: 14px; + font-weight: ${variant === 'link' ? '400' : '500'}; + border-radius: 6px; + cursor: pointer; + transition: all 0.2s; + text-decoration: ${variant === 'link' ? 'underline' : 'none'}; + `; + + button.addEventListener('mouseenter', () => { + button.style.background = hoverBg; + }); + + button.addEventListener('mouseleave', () => { + button.style.background = bg; + }); + + button.addEventListener('click', () => { + // Emit action event + instance.emit('experiences:action', { + experienceId: experience.id, + type: 'banner', + action: buttonConfig.action, + url: buttonConfig.url, + metadata: buttonConfig.metadata, + variant: variant, + timestamp: Date.now(), + }); + + // Navigate if URL provided + if (buttonConfig.url) { + window.location.href = buttonConfig.url; + } + }); + + return button; + } + + // Add buttons from buttons array + if (content.buttons && content.buttons.length > 0) { + content.buttons.forEach((buttonConfig) => { + const button = createButton(buttonConfig); + buttonContainer.appendChild(button); + }); + } + // Add dismiss button if dismissable if (dismissable) { const closeButton = document.createElement('button'); closeButton.innerHTML = '×'; closeButton.setAttribute('aria-label', 'Close banner'); + + const closeColor = isDarkMode ? '#9ca3af' : '#6b7280'; + closeButton.style.cssText = ` background: transparent; border: none; - color: inherit; - font-size: 28px; + color: ${closeColor}; + font-size: 24px; line-height: 1; cursor: pointer; padding: 0; margin: 0; - opacity: 0.8; + opacity: 0.7; transition: opacity 0.2s; `; @@ -123,20 +255,22 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { }); closeButton.addEventListener('mouseleave', () => { - closeButton.style.opacity = '0.8'; + closeButton.style.opacity = '0.7'; }); closeButton.addEventListener('click', () => { - remove(); + remove(experience.id); instance.emit('experiences:dismissed', { experienceId: experience.id, type: 'banner', }); }); - banner.appendChild(closeButton); + buttonContainer.appendChild(closeButton); } + banner.appendChild(buttonContainer); + return banner; } @@ -144,9 +278,9 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { * Show a banner experience */ function show(experience: Experience): void { - // Remove any existing banner first - if (activeBanner) { - remove(); + // If banner already showing for this experience, skip + if (activeBanners.has(experience.id)) { + return; } // Only show if we're in a browser environment @@ -156,7 +290,7 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { const banner = createBannerElement(experience); document.body.appendChild(banner); - activeBanner = banner; + activeBanners.set(experience.id, banner); instance.emit('experiences:shown', { experienceId: experience.id, @@ -166,20 +300,32 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { } /** - * Remove the active banner + * Remove a banner by experience ID (or all if no ID provided) */ - function remove(): void { - if (activeBanner?.parentNode) { - activeBanner.parentNode.removeChild(activeBanner); - activeBanner = null; + function remove(experienceId?: string): void { + if (experienceId) { + // Remove specific banner + const banner = activeBanners.get(experienceId); + if (banner?.parentNode) { + banner.parentNode.removeChild(banner); + } + activeBanners.delete(experienceId); + } else { + // Remove all banners + for (const [id, banner] of activeBanners.entries()) { + if (banner?.parentNode) { + banner.parentNode.removeChild(banner); + } + activeBanners.delete(id); + } } } /** - * Check if a banner is currently showing + * Check if any banner is currently showing */ function isShowing(): boolean { - return activeBanner !== null; + return activeBanners.size > 0; } // Expose banner API @@ -192,17 +338,27 @@ export const bannerPlugin: PluginFunction = (plugin, instance, config) => { }); // Auto-show banner on experiences:evaluated event - instance.on('experiences:evaluated', (decision: Decision) => { - // Only show if: - // 1. Decision says to show - // 2. Experience ID exists - // 3. Experience type is 'banner' - if (decision.show && decision.experienceId) { - // We need to find the experience to check its type - // For now, we'll assume if a banner-type experience was evaluated and should show, - // we'll receive it through another mechanism or the decision will include more info - // This is a simplification - in a real implementation, the runtime would need to - // pass the full experience object or we'd need to query it + instance.on('experiences:evaluated', (payload: unknown) => { + // Handle both single decision and array of decisions + // evaluate() emits: { decision, experience } + // evaluateAll() emits: [{ decision, experience }, ...] + const items = Array.isArray(payload) ? payload : [payload]; + + for (const item of items) { + // Item is { decision, experience } + const typedItem = item as { decision?: Decision; experience?: Experience }; + const decision = typedItem.decision; + const experience = typedItem.experience; + + // Only handle banner-type experiences + if (experience?.type === 'banner') { + if (decision?.show) { + show(experience); + } else if (experience.id && activeBanners.has(experience.id)) { + // Hide specific banner if decision says don't show + remove(experience.id); + } + } } }); diff --git a/packages/plugins/src/frequency/frequency.test.ts b/packages/plugins/src/frequency/frequency.test.ts index 6f9c45b..074fda9 100644 --- a/packages/plugins/src/frequency/frequency.test.ts +++ b/packages/plugins/src/frequency/frequency.test.ts @@ -10,6 +10,14 @@ describe('Frequency Plugin', () => { let sdk: SDKWithFrequency; beforeEach(() => { + // Clear any existing storage to avoid pollution between tests + if (typeof sessionStorage !== 'undefined') { + sessionStorage.clear(); + } + if (typeof localStorage !== 'undefined') { + localStorage.clear(); + } + // Use memory storage for tests sdk = new SDK({ frequency: { enabled: true }, @@ -131,11 +139,11 @@ describe('Frequency Plugin', () => { // Mock Date.now() for first impression (25 hours ago - outside window) vi.spyOn(Date, 'now').mockReturnValue(now - 25 * 60 * 60 * 1000); - sdk.frequency.recordImpression('welcome-banner'); + sdk.frequency.recordImpression('welcome-banner', 'day'); // Mock Date.now() for second impression (now - inside window) vi.spyOn(Date, 'now').mockReturnValue(now); - sdk.frequency.recordImpression('welcome-banner'); + sdk.frequency.recordImpression('welcome-banner', 'day'); // Only 1 impression within last 24 hours expect(sdk.frequency.hasReachedCap('welcome-banner', 2, 'day')).toBe(false); @@ -149,12 +157,12 @@ describe('Frequency Plugin', () => { // Record 2 impressions 8 days ago (outside week window) vi.spyOn(Date, 'now').mockReturnValue(now - 8 * 24 * 60 * 60 * 1000); - sdk.frequency.recordImpression('welcome-banner'); - sdk.frequency.recordImpression('welcome-banner'); + sdk.frequency.recordImpression('welcome-banner', 'week'); + sdk.frequency.recordImpression('welcome-banner', 'week'); // Record 1 impression 3 days ago (inside week window) vi.spyOn(Date, 'now').mockReturnValue(now - 3 * 24 * 60 * 60 * 1000); - sdk.frequency.recordImpression('welcome-banner'); + sdk.frequency.recordImpression('welcome-banner', 'week'); // Current time vi.spyOn(Date, 'now').mockReturnValue(now); @@ -172,7 +180,7 @@ describe('Frequency Plugin', () => { // Record 3 impressions within last day for (let i = 0; i < 3; i++) { vi.spyOn(Date, 'now').mockReturnValue(now - i * 60 * 60 * 1000); // Each hour - sdk.frequency.recordImpression('welcome-banner'); + sdk.frequency.recordImpression('welcome-banner', 'day'); } vi.spyOn(Date, 'now').mockReturnValue(now); @@ -202,7 +210,7 @@ describe('Frequency Plugin', () => { }, }; - sdk.emit('experiences:evaluated', decision); + sdk.emit('experiences:evaluated', { decision }); expect(sdk.frequency.getImpressionCount('welcome-banner')).toBe(1); }); @@ -223,7 +231,7 @@ describe('Frequency Plugin', () => { }, }; - sdk.emit('experiences:evaluated', decision); + sdk.emit('experiences:evaluated', { decision }); expect(sdk.frequency.getImpressionCount('welcome-banner')).toBe(0); }); @@ -244,7 +252,7 @@ describe('Frequency Plugin', () => { }, }; - sdk.emit('experiences:evaluated', decision); + sdk.emit('experiences:evaluated', { decision }); // Should not throw or record expect(sdk.frequency.getImpressionCount('any-experience')).toBe(0); @@ -269,15 +277,15 @@ describe('Frequency Plugin', () => { }, }; - sdk.emit('experiences:evaluated', decision); + sdk.emit('experiences:evaluated', { decision }); expect(sdk.frequency.getImpressionCount('welcome-banner')).toBe(0); }); }); describe('Storage Integration', () => { - it('should persist impressions across SDK instances', () => { - // Record impression in first instance + it('should persist impressions across SDK instances (session storage)', () => { + // Record impression in first instance (uses sessionStorage) sdk.frequency.recordImpression('welcome-banner'); expect(sdk.frequency.getImpressionCount('welcome-banner')).toBe(1); @@ -289,17 +297,18 @@ describe('Frequency Plugin', () => { sdk2.use(storagePlugin); sdk2.use(frequencyPlugin); - // Impressions should NOT persist (memory backend is per-instance) - expect(sdk2.frequency.getImpressionCount('welcome-banner')).toBe(0); + // Impressions SHOULD persist (sessionStorage is shared) + expect(sdk2.frequency.getImpressionCount('welcome-banner')).toBe(1); }); it('should use namespaced storage keys', () => { sdk.frequency.recordImpression('welcome-banner'); - // Check storage directly - const storageData = sdk.storage.get('experiences:frequency:welcome-banner'); + // Check sessionStorage directly (frequency plugin uses sessionStorage for 'session' per) + const storageKey = 'experiences:frequency:welcome-banner'; + const storageData = JSON.parse(sessionStorage.getItem(storageKey) || '{}'); expect(storageData).toBeDefined(); - expect((storageData as { count: number }).count).toBe(1); + expect(storageData.count).toBe(1); }); }); @@ -339,7 +348,8 @@ describe('Frequency Plugin', () => { expect(sdk.frequency.getImpressionCount('welcome-banner')).toBe(5); // Check storage for cleaned impressions array - const storageData = sdk.storage.get('experiences:frequency:welcome-banner') as { + const storageKey = 'experiences:frequency:welcome-banner'; + const storageData = JSON.parse(sessionStorage.getItem(storageKey) || '{}') as { impressions: number[]; }; // Should only keep impressions from last 7 days (2 recent ones) diff --git a/packages/plugins/src/frequency/frequency.ts b/packages/plugins/src/frequency/frequency.ts index 61aaf3b..8ac5f5f 100644 --- a/packages/plugins/src/frequency/frequency.ts +++ b/packages/plugins/src/frequency/frequency.ts @@ -7,7 +7,7 @@ import type { PluginFunction, SDK } from '@lytics/sdk-kit'; import { type StoragePlugin, storagePlugin } from '@lytics/sdk-kit-plugins'; -import type { Decision } from '../types'; +import type { Decision, TraceStep } from '../types'; export interface FrequencyPluginConfig { frequency?: { @@ -26,6 +26,7 @@ interface ImpressionData { count: number; lastImpression: number; impressions: number[]; + per?: 'session' | 'day' | 'week'; // Track which storage type this uses } /** @@ -54,6 +55,9 @@ export const frequencyPlugin: PluginFunction = (plugin, instance, config) => { }, }); + // Track experience frequency configs + const experienceFrequencyMap = new Map(); + // Auto-load storage plugin if not already loaded if (!(instance as SDK & { storage?: StoragePlugin }).storage) { instance.use(storagePlugin); @@ -62,31 +66,52 @@ export const frequencyPlugin: PluginFunction = (plugin, instance, config) => { const isEnabled = (): boolean => config.get('frequency.enabled') ?? true; const getNamespace = (): string => config.get('frequency.namespace') ?? 'experiences:frequency'; + // Helper to get the right storage backend based on frequency type + const getStorageBackend = (per: 'session' | 'day' | 'week'): Storage => { + return per === 'session' ? sessionStorage : localStorage; + }; + // Helper to get storage key const getStorageKey = (experienceId: string): string => { return `${getNamespace()}:${experienceId}`; }; // Helper to get impression data - const getImpressionData = (experienceId: string): ImpressionData => { - const storage = (instance as SDK & { storage: StoragePlugin }).storage; - const data = storage.get(getStorageKey(experienceId)) as ImpressionData | null; + const getImpressionData = ( + experienceId: string, + per: 'session' | 'day' | 'week' + ): ImpressionData => { + const storage = getStorageBackend(per); + const key = getStorageKey(experienceId); + const raw = storage.getItem(key); - if (!data) { + if (!raw) { return { count: 0, lastImpression: 0, impressions: [], + per, }; } - return data; + try { + return JSON.parse(raw) as ImpressionData; + } catch { + return { + count: 0, + lastImpression: 0, + impressions: [], + per, + }; + } }; // Helper to save impression data const saveImpressionData = (experienceId: string, data: ImpressionData): void => { - const storage = (instance as SDK & { storage: StoragePlugin }).storage; - storage.set(getStorageKey(experienceId), data); + const per = data.per || 'session'; // Default to session if not specified + const storage = getStorageBackend(per); + const key = getStorageKey(experienceId); + storage.setItem(key, JSON.stringify(data)); }; // Get time window in milliseconds @@ -104,9 +129,12 @@ export const frequencyPlugin: PluginFunction = (plugin, instance, config) => { /** * Get impression count for an experience */ - const getImpressionCount = (experienceId: string): number => { + const getImpressionCount = ( + experienceId: string, + per: 'session' | 'day' | 'week' = 'session' + ): number => { if (!isEnabled()) return 0; - const data = getImpressionData(experienceId); + const data = getImpressionData(experienceId, per); return data.count; }; @@ -120,7 +148,7 @@ export const frequencyPlugin: PluginFunction = (plugin, instance, config) => { ): boolean => { if (!isEnabled()) return false; - const data = getImpressionData(experienceId); + const data = getImpressionData(experienceId, per); const timeWindow = getTimeWindow(per); const now = Date.now(); @@ -138,16 +166,20 @@ export const frequencyPlugin: PluginFunction = (plugin, instance, config) => { /** * Record an impression for an experience */ - const recordImpression = (experienceId: string): void => { + const recordImpression = ( + experienceId: string, + per: 'session' | 'day' | 'week' = 'session' + ): void => { if (!isEnabled()) return; - const data = getImpressionData(experienceId); + const data = getImpressionData(experienceId, per); const now = Date.now(); // Update count and add timestamp data.count += 1; data.lastImpression = now; data.impressions.push(now); + data.per = per; // Store the frequency type // Keep only recent impressions (last 7 days) const sevenDaysAgo = now - 7 * 24 * 60 * 60 * 1000; @@ -170,15 +202,45 @@ export const frequencyPlugin: PluginFunction = (plugin, instance, config) => { getImpressionCount, hasReachedCap, recordImpression, + // Internal method to register experience frequency config + _registerExperience: (experienceId: string, per: 'session' | 'day' | 'week') => { + experienceFrequencyMap.set(experienceId, per); + }, }, }); // Listen to evaluation events and record impressions if (isEnabled()) { - instance.on('experiences:evaluated', (decision: Decision) => { - // Only record if experience was shown - if (decision.show && decision.experienceId) { - recordImpression(decision.experienceId); + instance.on('experiences:evaluated', (payload: unknown) => { + // Handle both single decision and array of decisions + // evaluate() emits: { decision, experience } + // evaluateAll() emits: [{ decision, experience }, ...] + const items = Array.isArray(payload) ? payload : [payload]; + + for (const item of items) { + // Item is { decision, experience } + const decision = (item as { decision?: Decision }).decision; + + // Only record if experience was shown + if (decision?.show && decision.experienceId) { + // Try to get the 'per' value from our map, fall back to checking the input in trace + let per: 'session' | 'day' | 'week' = + experienceFrequencyMap.get(decision.experienceId) || 'session'; + + // If not in map, try to infer from the decision trace + if (!experienceFrequencyMap.has(decision.experienceId)) { + const freqStep = decision.trace.find( + (t: TraceStep) => t.step === 'check-frequency-cap' + ); + if (freqStep?.input && typeof freqStep.input === 'object' && 'per' in freqStep.input) { + per = (freqStep.input as { per: 'session' | 'day' | 'week' }).per; + // Cache it for next time + experienceFrequencyMap.set(decision.experienceId, per); + } + } + + recordImpression(decision.experienceId, per); + } } }); } diff --git a/packages/plugins/src/types.ts b/packages/plugins/src/types.ts index 3b34939..4f50d8e 100644 --- a/packages/plugins/src/types.ts +++ b/packages/plugins/src/types.ts @@ -14,6 +14,15 @@ export type ExperienceContent = BannerContent | ModalContent | TooltipContent; export interface BannerContent { title?: string; message: string; + buttons?: Array<{ + text: string; + action?: string; + url?: string; + variant?: 'primary' | 'secondary' | 'link'; + metadata?: Record; + }>; + dismissable?: boolean; + position?: 'top' | 'bottom'; } /** @@ -46,6 +55,7 @@ export interface Experience { max: number; per: 'session' | 'day' | 'week'; }; + priority?: number; } /** @@ -65,9 +75,11 @@ export interface Decision { */ export interface TraceStep { step: string; - matched: boolean; - rule?: string; - value?: any; + timestamp: number; + duration: number; + input?: any; + output?: any; + passed: boolean; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ac13a23..b3219a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -49,6 +49,34 @@ importers: specifier: ^4.0.16 version: 4.0.16(@types/node@24.10.4)(jiti@2.6.1)(jsdom@27.3.0) + docs: + dependencies: + next: + specifier: ^14.2.0 + version: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: + specifier: ^2.13.0 + version: 2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra-theme-docs: + specifier: ^2.13.0 + version: 2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.0 + version: 18.3.1 + react-dom: + specifier: ^18.3.0 + version: 18.3.1(react@18.3.1) + devDependencies: + '@types/node': + specifier: ^22.0.0 + version: 22.19.3 + '@types/react': + specifier: ^18.3.0 + version: 18.3.27 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + packages/core: dependencies: '@lytics/sdk-kit': @@ -189,6 +217,9 @@ packages: cpu: [x64] os: [win32] + '@braintree/sanitize-url@6.0.4': + resolution: {integrity: sha512-s3jaWicZd0pkP0jf5ysyHUI/RE7MHos6qlToFcGWXVp+ykHOy77OUMrfbgJ9it2C5bow7OIQwYYaHjk9XlBQ2A==} + '@changesets/apply-release-plan@7.0.14': resolution: {integrity: sha512-ddBvf9PHdy2YY0OUiEl3TV78mH9sckndJR14QAt87KLEbIov81XO0q0QAmvooBxXlqRRP8I9B7XOzZwQG7JkWA==} @@ -501,6 +532,13 @@ packages: cpu: [x64] os: [win32] + '@headlessui/react@1.7.19': + resolution: {integrity: sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==} + engines: {node: '>=10'} + peerDependencies: + react: ^16 || ^17 || ^18 + react-dom: ^16 || ^17 || ^18 + '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} engines: {node: '>=18'} @@ -542,6 +580,165 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@mdx-js/mdx@2.3.0': + resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + + '@mdx-js/react@2.3.0': + resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==} + peerDependencies: + react: '>=16' + + '@napi-rs/simple-git-android-arm-eabi@0.1.22': + resolution: {integrity: sha512-JQZdnDNm8o43A5GOzwN/0Tz3CDBQtBUNqzVwEopm32uayjdjxev1Csp1JeaqF3v9djLDIvsSE39ecsN2LhCKKQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/simple-git-android-arm64@0.1.22': + resolution: {integrity: sha512-46OZ0SkhnvM+fapWjzg/eqbJvClxynUpWYyYBn4jAj7GQs1/Yyc8431spzDmkA8mL0M7Xo8SmbkzTDE7WwYAfg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/simple-git-darwin-arm64@0.1.22': + resolution: {integrity: sha512-zH3h0C8Mkn9//MajPI6kHnttywjsBmZ37fhLX/Fiw5XKu84eHA6dRyVtMzoZxj6s+bjNTgaMgMUucxPn9ktxTQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/simple-git-darwin-x64@0.1.22': + resolution: {integrity: sha512-GZN7lRAkGKB6PJxWsoyeYJhh85oOOjVNyl+/uipNX8bR+mFDCqRsCE3rRCFGV9WrZUHXkcuRL2laIRn7lLi3ag==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/simple-git-freebsd-x64@0.1.22': + resolution: {integrity: sha512-xyqX1C5I0WBrUgZONxHjZH5a4LqQ9oki3SKFAVpercVYAcx3pq6BkZy1YUOP4qx78WxU1CCNfHBN7V+XO7D99A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/simple-git-linux-arm-gnueabihf@0.1.22': + resolution: {integrity: sha512-4LOtbp9ll93B9fxRvXiUJd1/RM3uafMJE7dGBZGKWBMGM76+BAcCEUv2BY85EfsU/IgopXI6n09TycRfPWOjxA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/simple-git-linux-arm64-gnu@0.1.22': + resolution: {integrity: sha512-GVOjP/JjCzbQ0kSqao7ctC/1sodVtv5VF57rW9BFpo2y6tEYPCqHnkQkTpieuwMNe+TVOhBUC1+wH0d9/knIHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/simple-git-linux-arm64-musl@0.1.22': + resolution: {integrity: sha512-MOs7fPyJiU/wqOpKzAOmOpxJ/TZfP4JwmvPad/cXTOWYwwyppMlXFRms3i98EU3HOazI/wMU2Ksfda3+TBluWA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@napi-rs/simple-git-linux-ppc64-gnu@0.1.22': + resolution: {integrity: sha512-L59dR30VBShRUIZ5/cQHU25upNgKS0AMQ7537J6LCIUEFwwXrKORZKJ8ceR+s3Sr/4jempWVvMdjEpFDE4HYww==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + + '@napi-rs/simple-git-linux-s390x-gnu@0.1.22': + resolution: {integrity: sha512-4FHkPlCSIZUGC6HiADffbe6NVoTBMd65pIwcd40IDbtFKOgFMBA+pWRqKiQ21FERGH16Zed7XHJJoY3jpOqtmQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + + '@napi-rs/simple-git-linux-x64-gnu@0.1.22': + resolution: {integrity: sha512-Ei1tM5Ho/dwknF3pOzqkNW9Iv8oFzRxE8uOhrITcdlpxRxVrBVptUF6/0WPdvd7R9747D/q61QG/AVyWsWLFKw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/simple-git-linux-x64-musl@0.1.22': + resolution: {integrity: sha512-zRYxg7it0p3rLyEJYoCoL2PQJNgArVLyNavHW03TFUAYkYi5bxQ/UFNVpgxMaXohr5yu7qCBqeo9j4DWeysalg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@napi-rs/simple-git-win32-arm64-msvc@0.1.22': + resolution: {integrity: sha512-XGFR1fj+Y9cWACcovV2Ey/R2xQOZKs8t+7KHPerYdJ4PtjVzGznI4c2EBHXtdOIYvkw7tL5rZ7FN1HJKdD5Quw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/simple-git-win32-ia32-msvc@0.1.22': + resolution: {integrity: sha512-Gqr9Y0gs6hcNBA1IXBpoqTFnnIoHuZGhrYqaZzEvGMLrTrpbXrXVEtX3DAAD2RLc1b87CPcJ49a7sre3PU3Rfw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/simple-git-win32-x64-msvc@0.1.22': + resolution: {integrity: sha512-hQjcreHmUcpw4UrtkOron1/TQObfe484lxiXFLLUj7aWnnnOVs1mnXq5/Bo9+3NYZldFpFRJPdPBeHCisXkKJg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/simple-git@0.1.22': + resolution: {integrity: sha512-bMVoAKhpjTOPHkW/lprDPwv5aD4R4C3Irt8vn+SKA9wudLe9COLxOhurrKRsxmZccUbWXRF7vukNeGUAj5P8kA==} + engines: {node: '>= 10'} + + '@next/env@14.2.35': + resolution: {integrity: sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==} + + '@next/swc-darwin-arm64@14.2.33': + resolution: {integrity: sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@14.2.33': + resolution: {integrity: sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@14.2.33': + resolution: {integrity: sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@14.2.33': + resolution: {integrity: sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@14.2.33': + resolution: {integrity: sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@14.2.33': + resolution: {integrity: sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@14.2.33': + resolution: {integrity: sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-ia32-msvc@14.2.33': + resolution: {integrity: sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@14.2.33': + resolution: {integrity: sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -554,6 +751,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@popperjs/core@2.11.8': + resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} + '@rollup/rollup-android-arm-eabi@4.54.0': resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} cpu: [arm] @@ -667,27 +867,113 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.5': + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + + '@tanstack/react-virtual@3.13.13': + resolution: {integrity: sha512-4o6oPMDvQv+9gMi8rE6gWmsOjtUZUYIJHv7EB+GblyYdi8U6OqLl8rhHWIUZSL1dUU2dPwTdTgybCKf9EjIrQg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/virtual-core@3.13.13': + resolution: {integrity: sha512-uQFoSdKKf5S8k51W5t7b2qpfkyIbdHMzAn+AMQvHPxKUPeo1SsGaA4JRISQT87jm28b7z8OEqPcg1IOZagQHcA==} + + '@theguild/remark-mermaid@0.0.5': + resolution: {integrity: sha512-e+ZIyJkEv9jabI4m7q29wZtZv+2iwPGsXJ2d46Zi7e+QcFudiyuqhLhHG/3gX3ZEB+hxTch+fpItyMS8jwbIcw==} + peerDependencies: + react: ^18.2.0 + + '@theguild/remark-npm2yarn@0.2.1': + resolution: {integrity: sha512-jUTFWwDxtLEFtGZh/TW/w30ySaDJ8atKWH8dq2/IiQF61dPrGfETpl0WxD0VdBfuLOeU14/kop466oBSRO/5CA==} + '@tsconfig/node-lts@24.0.0': resolution: {integrity: sha512-8mSTqWwCd6aQpvxSrpQlMoA9RiUZSs7bYhL5qsLXIIaN9HQaINeoydrRu/Y7/fws4bvfuyhs0BRnW9/NI8tySg==} + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} '@types/conventional-commits-parser@5.0.2': resolution: {integrity: sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==} + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@2.3.10': + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@22.19.3': + resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==} + '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitest/coverage-v8@4.0.16': resolution: {integrity: sha512-2rNdjEIsPRzsdu6/9Eq0AYAzYdpP6Bx9cje9tL3FE5XzXRQF1fNU9pe/1yE8fCrS0HD+fBtt6gLPh6LI57tX7A==} peerDependencies: @@ -730,6 +1016,11 @@ packages: resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} hasBin: true + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} @@ -750,6 +1041,13 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-sequence-parser@1.1.3: + resolution: {integrity: sha512-+fksAx9eG3Ab6LDnLs3ZqZa8KVJ/jYnX+D4Qe1azX+LFGFAXqynCQLOdLpNYN/l9e7l6hMWwZbrnctqr6eSQSw==} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -757,6 +1055,12 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + arch@2.2.0: + resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} + + arg@1.0.0: + resolution: {integrity: sha512-Wk7TEzl1KqvTGs/uyhmHO/3XLd3t1UeU4IstvPXVzGPM522cTjqjNZ99esCkcL52sjqjo8e8CTBcWhkxvGzoAw==} + argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -777,6 +1081,13 @@ packages: ast-v8-to-istanbul@0.3.9: resolution: {integrity: sha512-dSC6tJeOJxbZrPzPbv5mMd6CMiQ1ugaVXXPRad2fXUSsy1kstFn9XQWemV9VW7Y7kpxgQ/4WMoZfwdH8XSU48w==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + better-path-resolve@1.0.0: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} @@ -794,6 +1105,10 @@ packages: peerDependencies: esbuild: '>=0.18' + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -802,14 +1117,36 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + caniuse-lite@1.0.30001761: + resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@2.3.0: + resolution: {integrity: sha512-Az5zJR2CBujap2rqXGaJKaPHyJ0IrUimvYNX+ncCy8PJP4ltOGTrHUIo097ZaL2zMeKYpiCdqDvS6zdrTFok3Q==} + engines: {node: '>=4'} + chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} @@ -821,24 +1158,55 @@ packages: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clipboardy@1.2.2: + resolution: {integrity: sha512-16KrBOV7bHmHdxcQiCvfUFYVFyEah4FI8vYT1Fr7CGSA4G+xBWMEfUEQJS1hxeHGtI9ju1Bzs9uXSbj5HZKArw==} + engines: {node: '>=4'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} + compute-scroll-into-view@3.1.1: + resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} + confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -859,6 +1227,9 @@ packages: engines: {node: '>=16'} hasBin: true + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + cosmiconfig-typescript-loader@6.2.0: resolution: {integrity: sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==} engines: {node: '>=v18'} @@ -876,6 +1247,9 @@ packages: typescript: optional: true + cross-spawn@5.1.0: + resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -888,6 +1262,160 @@ packages: resolution: {integrity: sha512-GlsEptulso7Jg0VaOZ8BXQi3AkYM5BOJKEO/rjMidSCq70FkIC5y0eawrCXeYzxgt3OCf4Ls+eoxN+/05vN0Ag==} engines: {node: '>=20'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + dargs@8.1.0: resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} engines: {node: '>=12'} @@ -896,6 +1424,9 @@ packages: resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} engines: {node: '>=20'} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -908,18 +1439,41 @@ packages: decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} + dompurify@3.3.1: + resolution: {integrity: sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==} + dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} + elkjs@0.9.3: + resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -950,18 +1504,55 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true + estree-util-attach-comments@2.1.1: + resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} + + estree-util-build-jsx@2.2.2: + resolution: {integrity: sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==} + + estree-util-is-identifier-name@2.1.0: + resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==} + + estree-util-to-js@1.2.0: + resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==} + + estree-util-value-to-estree@3.5.0: + resolution: {integrity: sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==} + + estree-util-visit@1.2.1: + resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + execa@0.8.0: + resolution: {integrity: sha512-zDWS+Rb1E8BlqqhALSt9kUhss8Qq4nN3iof3gsOdyINksElaPyNBtKUMTR62qhvgVWR0CqCX7sdnKe4MnUbFEA==} + engines: {node: '>=4'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} @@ -1002,6 +1593,12 @@ packages: fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flexsearch@0.7.43: + resolution: {integrity: sha512-c5o/+Um8aqCSOXGcZoqZOm+NqtVwNsvVpWv6lfmSclU954O3wvQKxxK8zj74fPaSJbXpSLTs4PRhh+wnoCXnKg==} + + focus-visible@5.2.1: + resolution: {integrity: sha512-8Bx950VD1bWTQJEH/AM6SpEk+SU55aVnp4Ujhuuxy3eMEBCRwBnTBnVXr9YAPvZL3/CNjCa8u4IWfNmEO53whA==} + fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -1019,11 +1616,24 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-stream@3.0.0: + resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==} + engines: {node: '>=4'} + git-raw-commits@4.0.0: resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} engines: {node: '>=16'} hasBin: true + git-up@7.0.0: + resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} + + git-url-parse@13.1.1: + resolution: {integrity: sha512-PCFJyeSSdtnbfhSNRw9Wk96dDCNx+sogTe4YNXeXSJxt7xz5hvXekuRn9JX7m+Mf4OscCu8h+mtAl3+h5Fo8lQ==} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1039,9 +1649,57 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + has-flag@2.0.0: + resolution: {integrity: sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==} + engines: {node: '>=0.10.0'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hash-obj@4.0.0: + resolution: {integrity: sha512-FwO1BUVWkyHasWDW4S8o0ssQXjvyghLV2rfVhnN36b2bbcj45eGiuzdn9XOvOpjV3TKQD7Gm2BWNXdE9V4KKYg==} + engines: {node: '>=12'} + + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-estree@2.3.3: + resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + + hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} @@ -1050,6 +1708,9 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -1090,9 +1751,40 @@ packages: resolution: {integrity: sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + intersection-observer@0.12.2: + resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} + deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1105,6 +1797,9 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -1113,9 +1808,27 @@ packages: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} + is-obj@3.0.0: + resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} + engines: {node: '>=12'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + is-ssh@1.4.1: + resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} + + is-stream@1.1.0: + resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} + engines: {node: '>=0.10.0'} + is-subdir@1.2.0: resolution: {integrity: sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==} engines: {node: '>=4'} @@ -1184,6 +1897,9 @@ packages: json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -1191,6 +1907,24 @@ packages: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} engines: {'0': node >= 0.2.0} + katex@0.16.27: + resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} + hasBin: true + + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -1210,9 +1944,16 @@ packages: resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} + lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.get@4.4.2: + resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} + deprecated: This package is deprecated. Use the optional chaining (?.) operator instead. + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -1237,10 +1978,20 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@11.2.4: resolution: {integrity: sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==} engines: {node: 20 || >=22} + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -1251,6 +2002,73 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + markdown-extensions@1.1.1: + resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} + engines: {node: '>=0.10.0'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + match-sorter@6.3.4: + resolution: {integrity: sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==} + + mdast-util-definitions@5.1.2: + resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} + + mdast-util-find-and-replace@2.2.2: + resolution: {integrity: sha512-MTtdFRz/eMDHXzeK6W3dO7mXUlF82Gom4y0oOgvHhh/HXZAGvIQDUvQ0SuUx+j2tv44b8xTHOm8K/9OoRFnXKw==} + + mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + + mdast-util-gfm-autolink-literal@1.0.3: + resolution: {integrity: sha512-My8KJ57FYEy2W2LyNom4n3E7hKTuQk/0SES0u16tjA9Z3oFkF4RrC/hPAPgjlSpezsOvI8ObcXcElo92wn5IGA==} + + mdast-util-gfm-footnote@1.0.2: + resolution: {integrity: sha512-56D19KOGbE00uKVj3sgIykpwKL179QsVFwx/DCW0u/0+URsryacI4MAdNJl0dh+u2PSsD9FtxPFbHCzJ78qJFQ==} + + mdast-util-gfm-strikethrough@1.0.3: + resolution: {integrity: sha512-DAPhYzTYrRcXdMjUtUjKvW9z/FNAMTdU0ORyMcbmkwYNbKocDpdk+PX1L1dQgOID/+vVs1uBQ7ElrBQfZ0cuiQ==} + + mdast-util-gfm-table@1.0.7: + resolution: {integrity: sha512-jjcpmNnQvrmN5Vx7y7lEc2iIOEytYv7rTvu+MeyAsSHTASGCCRA79Igg2uKssgOs1i1po8s3plW0sTu1wkkLGg==} + + mdast-util-gfm-task-list-item@1.0.2: + resolution: {integrity: sha512-PFTA1gzfp1B1UaiJVyhJZA1rm0+Tzn690frc/L8vNX1Jop4STZgOE6bxUhnzdVSB+vm2GU1tIsuQcA9bxTQpMQ==} + + mdast-util-gfm@2.0.2: + resolution: {integrity: sha512-qvZ608nBppZ4icQlhQQIAdc6S3Ffj9RGmzwUKUWuEICFnd1LVkN3EktF7ZHAgfcEdvZB5owU9tQgt99e2TlLjg==} + + mdast-util-math@2.0.2: + resolution: {integrity: sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==} + + mdast-util-mdx-expression@1.3.2: + resolution: {integrity: sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==} + + mdast-util-mdx-jsx@2.1.4: + resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==} + + mdast-util-mdx@2.0.1: + resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==} + + mdast-util-mdxjs-esm@1.3.1: + resolution: {integrity: sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==} + + mdast-util-phrasing@3.0.1: + resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} + + mdast-util-to-hast@12.3.0: + resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} + + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + + mdast-util-to-markdown@1.5.0: + resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} + + mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + mdn-data@2.12.2: resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} @@ -1262,6 +2080,132 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@10.9.5: + resolution: {integrity: sha512-eRlKEjzak4z1rcXeCd1OAlyawhrptClQDo8OuI8n6bSVqJ9oMfd5Lrf3Q+TdJHewi/9AIOc3UmEo8Fz+kNzzuQ==} + + micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + + micromark-extension-gfm-autolink-literal@1.0.5: + resolution: {integrity: sha512-z3wJSLrDf8kRDOh2qBtoTRD53vJ+CWIyo7uyZuxf/JAbNJjiHsOpG1y5wxk8drtv3ETAHutCu6N3thkOOgueWg==} + + micromark-extension-gfm-footnote@1.1.2: + resolution: {integrity: sha512-Yxn7z7SxgyGWRNa4wzf8AhYYWNrwl5q1Z8ii+CSTTIqVkmGZF1CElX2JI8g5yGoM3GAman9/PVCUFUSJ0kB/8Q==} + + micromark-extension-gfm-strikethrough@1.0.7: + resolution: {integrity: sha512-sX0FawVE1o3abGk3vRjOH50L5TTLr3b5XMqnP9YDRb34M0v5OoZhG+OHFz1OffZ9dlwgpTBKaT4XW/AsUVnSDw==} + + micromark-extension-gfm-table@1.0.7: + resolution: {integrity: sha512-3ZORTHtcSnMQEKtAOsBQ9/oHp9096pI/UvdPtN7ehKvrmZZ2+bbWhi0ln+I9drmwXMt5boocn6OlwQzNXeVeqw==} + + micromark-extension-gfm-tagfilter@1.0.2: + resolution: {integrity: sha512-5XWB9GbAUSHTn8VPU8/1DBXMuKYT5uOgEjJb8gN3mW0PNW5OPHpSdojoqf+iq1xo7vWzw/P8bAHY0n6ijpXF7g==} + + micromark-extension-gfm-task-list-item@1.0.5: + resolution: {integrity: sha512-RMFXl2uQ0pNQy6Lun2YBYT9g9INXtWJULgbt01D/x8/6yJ2qpKyzdZD3pi6UIkzF++Da49xAelVKUeUMqd5eIQ==} + + micromark-extension-gfm@2.0.3: + resolution: {integrity: sha512-vb9OoHqrhCmbRidQv/2+Bc6pkP0FrtlhurxZofvOEy5o8RtuuvTq+RQ1Vw5ZDNrVraQZu3HixESqbG+0iKk/MQ==} + + micromark-extension-math@2.1.2: + resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==} + + micromark-extension-mdx-expression@1.0.8: + resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==} + + micromark-extension-mdx-jsx@1.0.5: + resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==} + + micromark-extension-mdx-md@1.0.1: + resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==} + + micromark-extension-mdxjs-esm@1.0.5: + resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==} + + micromark-extension-mdxjs@1.0.1: + resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==} + + micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} + + micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} + + micromark-factory-mdx-expression@1.0.9: + resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==} + + micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + + micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} + + micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} + + micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + + micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} + + micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + + micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + + micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + + micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@1.2.3: + resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} + + micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} + + micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} + + micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} + + micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + + micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -1287,6 +2231,72 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + next-mdx-remote@4.4.1: + resolution: {integrity: sha512-1BvyXaIou6xy3XoNF4yaMZUCb6vD2GTAa5ciOa6WoO+gAUTYsb1K4rI/HSC2ogAWLrb/7VSV52skz07vOzmqIQ==} + engines: {node: '>=14', npm: '>=7'} + peerDependencies: + react: '>=16.x <=18.x' + react-dom: '>=16.x <=18.x' + + next-seo@6.8.0: + resolution: {integrity: sha512-zcxaV67PFXCSf8e6SXxbxPaOTgc8St/esxfsYXfQXMM24UESUVSXFm7f2A9HMkAwa0Gqn4s64HxYZAGfdF4Vhg==} + peerDependencies: + next: ^8.1.1-canary.54 || >=9.0.0 + react: '>=16.0.0' + react-dom: '>=16.0.0' + + next-themes@0.2.1: + resolution: {integrity: sha512-B+AKNfYNIzh0vqQQKqQItTS8evEouKD7H5Hj3kmuPERwddR2TxvDSFZuTj6T7Jfn1oyeUyJMydPl1Bkxkh0W7A==} + peerDependencies: + next: '*' + react: '*' + react-dom: '*' + + next@14.2.35: + resolution: {integrity: sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==} + engines: {node: '>=18.17.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + sass: + optional: true + + nextra-theme-docs@2.13.4: + resolution: {integrity: sha512-2XOoMfwBCTYBt8ds4ZHftt9Wyf2XsykiNo02eir/XEYB+sGeUoE77kzqfidjEOKCSzOHYbK9BDMcg2+B/2vYRw==} + peerDependencies: + next: '>=9.5.3' + nextra: 2.13.4 + react: '>=16.13.1' + react-dom: '>=16.13.1' + + nextra@2.13.4: + resolution: {integrity: sha512-7of2rSBxuUa3+lbMmZwG9cqgftcoNOVQLTT6Rxf3EhBR9t1EI7b43dted8YoqSNaigdE3j1CoyNkX8N/ZzlEpw==} + engines: {node: '>=16'} + peerDependencies: + next: '>=9.5.3' + react: '>=16.13.1' + react-dom: '>=16.13.1' + + non-layered-tidy-tree-layout@2.0.2: + resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} + + npm-run-path@2.0.2: + resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} + engines: {node: '>=4'} + + npm-to-yarn@2.2.1: + resolution: {integrity: sha512-O/j/ROyX0KGLG7O6Ieut/seQ0oiTpHF2tXAcFbpdTLQFiaNtkyTXXocM1fwpaa60dg1qpWj0nHlbNhx6qwuENQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1301,10 +2311,18 @@ packages: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=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-limit@4.0.0: resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1332,10 +2350,25 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} + parse-numeric-range@1.3.0: + resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} + + parse-path@7.1.0: + resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} + + parse-url@8.1.0: + resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} @@ -1347,6 +2380,10 @@ packages: resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + path-key@2.0.1: + resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} + engines: {node: '>=4'} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -1358,6 +2395,9 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1398,6 +2438,10 @@ packages: yaml: optional: true + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + postcss@8.5.6: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} @@ -1407,6 +2451,18 @@ packages: engines: {node: '>=10.13.0'} hasBin: true + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + protocols@2.0.2: + resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} + + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1417,6 +2473,15 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -1425,6 +2490,42 @@ packages: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} + reading-time@1.5.0: + resolution: {integrity: sha512-onYyVhBNr4CmAxFsKS7bz+uTLRakypIe4R+5A824vBSkQy/hB3fZepoVEf8OVAxzLvK+H/jm9TzpI3ETSm64Kg==} + + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + + rehype-pretty-code@0.9.11: + resolution: {integrity: sha512-Eq90eCYXQJISktfRZ8PPtwc5SUyH6fJcxS8XOMnHPUQZBtC6RYo67gGlley9X2nR8vlniPj0/7oCDEYHKQa/oA==} + engines: {node: '>=16'} + peerDependencies: + shiki: '*' + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + remark-gfm@3.0.1: + resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} + + remark-math@5.1.1: + resolution: {integrity: sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==} + + remark-mdx@2.3.0: + resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} + + remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} + + remark-reading-time@2.0.2: + resolution: {integrity: sha512-ILjIuR0dQQ8pELPgaFvz7ralcSN62rD/L1pTUJgWb4gfua3ZwYEI8mnKGxEQCbrXSUF/OvycTkcUbifGOtOn5A==} + + remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1445,6 +2546,9 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@4.54.0: resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -1453,6 +2557,13 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1460,22 +2571,46 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scroll-into-view-if-needed@3.1.0: + resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} + + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + semver@7.7.3: resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true + shebang-command@1.2.0: + resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} + engines: {node: '>=0.10.0'} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} + shebang-regex@1.0.0: + resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} + engines: {node: '>=0.10.0'} + shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@0.14.7: + resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -1484,6 +2619,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + sort-keys@5.1.0: + resolution: {integrity: sha512-aSbHV0DaBcr7u0PVHXzM6NbZNAtrr9sF6+Qfs9UUVG7Ll3jQ6hHi8F/xqIIcn2rvIVbr0v/2zyjSdwSV47AgLQ==} + engines: {node: '>=12'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1492,6 +2631,9 @@ packages: resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} engines: {node: '>= 12'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spawndamnit@3.0.1: resolution: {integrity: sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg==} @@ -1508,23 +2650,61 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} + strip-eof@1.0.0: + resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} + engines: {node: '>=0.10.0'} + + style-to-object@0.4.4: + resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + + styled-jsx@5.1.1: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + sucrase@3.35.1: resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true + supports-color@4.5.0: + resolution: {integrity: sha512-ycQR/UbvI9xIlEdQT1TQqwoXtEldExbCEAJgRo5YXlmSKjv6ThHnP9/vwGa1gr19Gfw+LkFd7KqYMhzrRC5JYw==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1568,6 +2748,14 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + title@3.5.3: + resolution: {integrity: sha512-20JyowYglSEeCvZv3EZ0nZ046vLarO37prvV0mbtQV7C8DJPGgN967r8SJkqd3XK3K3lD3/Iyfp3avjfil8Q2Q==} + hasBin: true + + titleize@1.0.0: + resolution: {integrity: sha512-TARUb7z1pGvlLxgPk++7wJ6aycXF3GJ0sNSBTAsTuJrQG5QuZlkUQP+zl+nbjAh4gMX9yDw9ZYklMd7vAfJKEw==} + engines: {node: '>=0.10.0'} + tldts-core@7.0.19: resolution: {integrity: sha512-lJX2dEWx0SGH4O6p+7FPwYmJ/bu1JbcGJ8RLaG9b7liIgZ85itUVEPbMtWRVrde/0fnDPEPHW10ZsKW3kVsE9A==} @@ -1591,9 +2779,22 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsup@8.5.1: resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} engines: {node: '>=18'} @@ -1647,6 +2848,10 @@ packages: resolution: {integrity: sha512-5JIA5aYBAJSAhrhbyag1ZuMSgUZnHtI+Sq3H8D3an4fL8PeF+L1yYvbEJg47akP1PFfATMf5ehkqFnxfkmuwZQ==} hasBin: true + type-fest@1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -1655,6 +2860,9 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -1662,10 +2870,94 @@ packages: resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} engines: {node: '>=18'} + unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-generated@2.0.1: + resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} + + unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@1.1.2: + resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} + + unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@4.0.2: + resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-remove@4.0.0: + resolution: {integrity: sha512-b4gokeGId57UVRX/eVKej5gXqGlc9+trkORhFJpu9raqZkZhU0zm8Doi05+HaiBsMEIJowL+2WtQ5ItjsngPXg==} + + unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@4.1.1: + resolution: {integrity: sha512-1xAFJXAKpnnJl8G7K5KgU7FY55y3GcLIXqkzUj5QF/QVP7biUm0K0O2oqVkYsdjzJKifYeWn9+o6piAK2hGSHw==} + + unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@3.1.0: + resolution: {integrity: sha512-Szoh+R/Ll68QWAyQyZZpQzZQm2UPbxibDvaY8Xc9SUtYgPsDzx5AWSk++UUt2hJuow8mvwR+rG+LQLw+KsuAKA==} + + unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-matter@3.0.1: + resolution: {integrity: sha512-CAAIDwnh6ZdtrqAuxdElUqQRQDQgbbIrYtDYI8gCjXS1qQ+1XdLoK8FIZWxJwn0/I+BkSSZpar3SOgjemQz4fg==} + + vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite@7.3.0: resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1740,10 +3032,22 @@ packages: jsdom: optional: true + vscode-oniguruma@1.7.0: + resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + + vscode-textmate@8.0.0: + resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + webidl-conversions@8.0.0: resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} engines: {node: '>=20'} @@ -1760,6 +3064,10 @@ packages: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -1797,6 +3105,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -1805,10 +3116,20 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + yocto-queue@1.2.2: resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} engines: {node: '>=12.20'} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@acemir/cssom@0.9.30': {} @@ -1889,6 +3210,8 @@ snapshots: '@biomejs/cli-win32-x64@2.3.10': optional: true + '@braintree/sanitize-url@6.0.4': {} + '@changesets/apply-release-plan@7.0.14': dependencies: '@changesets/config': 3.1.2 @@ -2243,6 +3566,13 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true + '@headlessui/react@1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/react-virtual': 3.13.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + client-only: 0.0.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + '@inquirer/external-editor@1.0.3(@types/node@24.10.4)': dependencies: chardet: 2.1.1 @@ -2288,6 +3618,126 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@mdx-js/mdx@2.3.0': + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/mdx': 2.0.13 + estree-util-build-jsx: 2.2.2 + estree-util-is-identifier-name: 2.1.0 + estree-util-to-js: 1.2.0 + estree-walker: 3.0.3 + hast-util-to-estree: 2.3.3 + markdown-extensions: 1.1.1 + periscopic: 3.1.0 + remark-mdx: 2.3.0 + remark-parse: 10.0.2 + remark-rehype: 10.1.0 + unified: 10.1.2 + unist-util-position-from-estree: 1.1.2 + unist-util-stringify-position: 3.0.3 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + transitivePeerDependencies: + - supports-color + + '@mdx-js/react@2.3.0(react@18.3.1)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 18.3.27 + react: 18.3.1 + + '@napi-rs/simple-git-android-arm-eabi@0.1.22': + optional: true + + '@napi-rs/simple-git-android-arm64@0.1.22': + optional: true + + '@napi-rs/simple-git-darwin-arm64@0.1.22': + optional: true + + '@napi-rs/simple-git-darwin-x64@0.1.22': + optional: true + + '@napi-rs/simple-git-freebsd-x64@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-arm-gnueabihf@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-arm64-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-arm64-musl@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-ppc64-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-s390x-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-x64-gnu@0.1.22': + optional: true + + '@napi-rs/simple-git-linux-x64-musl@0.1.22': + optional: true + + '@napi-rs/simple-git-win32-arm64-msvc@0.1.22': + optional: true + + '@napi-rs/simple-git-win32-ia32-msvc@0.1.22': + optional: true + + '@napi-rs/simple-git-win32-x64-msvc@0.1.22': + optional: true + + '@napi-rs/simple-git@0.1.22': + optionalDependencies: + '@napi-rs/simple-git-android-arm-eabi': 0.1.22 + '@napi-rs/simple-git-android-arm64': 0.1.22 + '@napi-rs/simple-git-darwin-arm64': 0.1.22 + '@napi-rs/simple-git-darwin-x64': 0.1.22 + '@napi-rs/simple-git-freebsd-x64': 0.1.22 + '@napi-rs/simple-git-linux-arm-gnueabihf': 0.1.22 + '@napi-rs/simple-git-linux-arm64-gnu': 0.1.22 + '@napi-rs/simple-git-linux-arm64-musl': 0.1.22 + '@napi-rs/simple-git-linux-ppc64-gnu': 0.1.22 + '@napi-rs/simple-git-linux-s390x-gnu': 0.1.22 + '@napi-rs/simple-git-linux-x64-gnu': 0.1.22 + '@napi-rs/simple-git-linux-x64-musl': 0.1.22 + '@napi-rs/simple-git-win32-arm64-msvc': 0.1.22 + '@napi-rs/simple-git-win32-ia32-msvc': 0.1.22 + '@napi-rs/simple-git-win32-x64-msvc': 0.1.22 + + '@next/env@14.2.35': {} + + '@next/swc-darwin-arm64@14.2.33': + optional: true + + '@next/swc-darwin-x64@14.2.33': + optional: true + + '@next/swc-linux-arm64-gnu@14.2.33': + optional: true + + '@next/swc-linux-arm64-musl@14.2.33': + optional: true + + '@next/swc-linux-x64-gnu@14.2.33': + optional: true + + '@next/swc-linux-x64-musl@14.2.33': + optional: true + + '@next/swc-win32-arm64-msvc@14.2.33': + optional: true + + '@next/swc-win32-ia32-msvc@14.2.33': + optional: true + + '@next/swc-win32-x64-msvc@14.2.33': + optional: true + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -2300,6 +3750,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@popperjs/core@2.11.8': {} + '@rollup/rollup-android-arm-eabi@4.54.0': optional: true @@ -2368,8 +3820,40 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': + dependencies: + '@swc/counter': 0.1.3 + tslib: 2.8.1 + + '@tanstack/react-virtual@3.13.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@tanstack/virtual-core': 3.13.13 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@tanstack/virtual-core@3.13.13': {} + + '@theguild/remark-mermaid@0.0.5(react@18.3.1)': + dependencies: + mermaid: 10.9.5 + react: 18.3.1 + unist-util-visit: 5.0.0 + transitivePeerDependencies: + - supports-color + + '@theguild/remark-npm2yarn@0.2.1': + dependencies: + npm-to-yarn: 2.2.1 + unist-util-visit: 5.0.0 + '@tsconfig/node-lts@24.0.0': {} + '@types/acorn@4.0.6': + dependencies: + '@types/estree': 1.0.8 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -2379,16 +3863,76 @@ snapshots: dependencies: '@types/node': 24.10.4 + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-time@3.0.4': {} + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + '@types/estree@1.0.8': {} + '@types/hast@2.3.10': + dependencies: + '@types/unist': 2.0.11 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/js-yaml@4.0.9': {} + + '@types/katex@0.16.7': {} + + '@types/mdast@3.0.15': + dependencies: + '@types/unist': 2.0.11 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + '@types/node@12.20.55': {} + '@types/node@22.19.3': + dependencies: + undici-types: 6.21.0 + '@types/node@24.10.4': dependencies: undici-types: 7.16.0 + '@types/prop-types@15.7.15': {} + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + '@vitest/coverage-v8@4.0.16(vitest@4.0.16(@types/node@24.10.4)(jiti@2.6.1)(jsdom@27.3.0))': dependencies: '@bcoe/v8-coverage': 1.0.2 @@ -2450,6 +3994,10 @@ snapshots: jsonparse: 1.3.1 through: 2.3.8 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn@8.15.0: {} agent-base@7.1.4: {} @@ -2465,12 +4013,22 @@ snapshots: ansi-regex@5.0.1: {} + ansi-sequence-parser@1.1.3: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 any-promise@1.3.0: {} + arch@2.2.0: {} + + arg@1.0.0: {} + argparse@1.0.10: dependencies: sprintf-js: 1.0.3 @@ -2489,6 +4047,10 @@ snapshots: estree-walker: 3.0.3 js-tokens: 9.0.1 + astring@1.9.0: {} + + bail@2.0.2: {} + better-path-resolve@1.0.0: dependencies: is-windows: 1.0.2 @@ -2506,14 +4068,36 @@ snapshots: esbuild: 0.27.2 load-tsconfig: 0.2.5 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + cac@6.7.14: {} callsites@3.1.0: {} + caniuse-lite@1.0.30001761: {} + + ccount@2.0.1: {} + chai@6.2.2: {} + chalk@2.3.0: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 4.5.0 + chalk@5.6.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chardet@2.1.1: {} chokidar@4.0.3: @@ -2522,25 +4106,48 @@ snapshots: ci-info@3.9.0: {} + client-only@0.0.1: {} + + clipboardy@1.2.2: + dependencies: + arch: 2.2.0 + execa: 0.8.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} + comma-separated-tokens@2.0.3: {} + commander@4.1.1: {} + commander@7.2.0: {} + + commander@8.3.0: {} + compare-func@2.0.0: dependencies: array-ify: 1.0.0 dot-prop: 5.3.0 + compute-scroll-into-view@3.1.1: {} + confbox@0.1.8: {} consola@3.4.2: {} @@ -2560,6 +4167,10 @@ snapshots: meow: 12.1.1 split2: 4.2.0 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + cosmiconfig-typescript-loader@6.2.0(@types/node@24.10.4)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): dependencies: '@types/node': 24.10.4 @@ -2576,6 +4187,12 @@ snapshots: optionalDependencies: typescript: 5.9.3 + cross-spawn@5.1.0: + dependencies: + lru-cache: 4.1.5 + shebang-command: 1.2.0 + which: 1.3.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2593,6 +4210,187 @@ snapshots: '@csstools/css-syntax-patches-for-csstree': 1.0.22 css-tree: 3.1.0 + csstype@3.2.3: {} + + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.0: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.22 + dargs@8.1.0: {} data-urls@6.0.0: @@ -2600,22 +4398,46 @@ snapshots: whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 + dayjs@1.11.19: {} + debug@4.4.3: dependencies: ms: 2.1.3 decimal.js@10.6.0: {} + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + + dequal@2.0.3: {} + detect-indent@6.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + diff@5.2.0: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 + dompurify@3.3.1: + optionalDependencies: + '@types/trusted-types': 2.0.7 + dot-prop@5.3.0: dependencies: is-obj: 2.0.0 + elkjs@0.9.3: {} + emoji-regex@8.0.0: {} enquirer@2.4.1: @@ -2664,14 +4486,61 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + + escape-string-regexp@5.0.0: {} + esprima@4.0.1: {} + estree-util-attach-comments@2.1.1: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@2.2.2: + dependencies: + '@types/estree-jsx': 1.0.5 + estree-util-is-identifier-name: 2.1.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@2.1.0: {} + + estree-util-to-js@1.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-value-to-estree@3.5.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-visit@1.2.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 2.0.11 + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 + execa@0.8.0: + dependencies: + cross-spawn: 5.1.0 + get-stream: 3.0.0 + is-stream: 1.1.0 + npm-run-path: 2.0.2 + p-finally: 1.0.0 + signal-exit: 3.0.7 + strip-eof: 1.0.0 + expect-type@1.3.0: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend@3.0.2: {} + extendable-error@0.1.7: {} fast-deep-equal@3.1.3: {} @@ -2715,6 +4584,10 @@ snapshots: mlly: 1.8.0 rollup: 4.54.0 + flexsearch@0.7.43: {} + + focus-visible@5.2.1: {} + fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -2732,17 +4605,30 @@ snapshots: get-caller-file@2.0.5: {} + get-stream@3.0.0: {} + git-raw-commits@4.0.0: dependencies: dargs: 8.1.0 meow: 12.1.1 split2: 4.2.0 - glob-parent@5.1.2: + git-up@7.0.0: dependencies: - is-glob: 4.0.3 + is-ssh: 1.4.1 + parse-url: 8.1.0 - global-directory@4.0.1: + git-url-parse@13.1.1: + dependencies: + git-up: 7.0.0 + + github-slugger@2.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -2757,14 +4643,135 @@ snapshots: graceful-fs@4.2.11: {} + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + + has-flag@2.0.0: {} + has-flag@4.0.0: {} + hash-obj@4.0.0: + dependencies: + is-obj: 3.0.0 + sort-keys: 5.1.0 + type-fest: 1.4.0 + + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@2.3.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/unist': 2.0.11 + comma-separated-tokens: 2.0.3 + estree-util-attach-comments: 2.1.1 + estree-util-is-identifier-name: 2.1.0 + hast-util-whitespace: 2.0.1 + mdast-util-mdx-expression: 1.3.2 + mdast-util-mdxjs-esm: 1.3.1 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + style-to-object: 0.4.4 + unist-util-position: 4.0.4 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + + hast-util-whitespace@2.0.1: {} + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 html-escaper@2.0.2: {} + html-void-elements@3.0.0: {} + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -2802,8 +4809,29 @@ snapshots: ini@4.1.1: {} + inline-style-parser@0.1.1: {} + + internmap@1.0.1: {} + + internmap@2.0.3: {} + + intersection-observer@0.12.2: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + is-arrayish@0.2.1: {} + is-buffer@2.0.5: {} + + is-decimal@2.0.1: {} + + is-extendable@0.1.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -2812,12 +4840,28 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-number@7.0.0: {} is-obj@2.0.0: {} + is-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + is-ssh@1.4.1: + dependencies: + protocols: 2.0.2 + + is-stream@1.1.0: {} + is-subdir@1.2.0: dependencies: better-path-resolve: 1.0.0 @@ -2899,12 +4943,26 @@ snapshots: json-schema-traverse@1.0.0: {} + jsonc-parser@3.3.1: {} + jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 jsonparse@1.3.1: {} + katex@0.16.27: + dependencies: + commander: 8.3.0 + + khroma@2.1.0: {} + + kind-of@6.0.3: {} + + kleur@4.1.5: {} + + layout-base@1.0.2: {} + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -2919,8 +4977,12 @@ snapshots: dependencies: p-locate: 6.0.0 + lodash-es@4.17.22: {} + lodash.camelcase@4.3.0: {} + lodash.get@4.4.2: {} + lodash.isplainobject@4.0.6: {} lodash.kebabcase@4.1.1: {} @@ -2937,8 +4999,19 @@ snapshots: lodash.upperfirst@4.3.1: {} + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@11.2.4: {} + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -2953,12 +5026,507 @@ snapshots: dependencies: semver: 7.7.3 + markdown-extensions@1.1.1: {} + + markdown-table@3.0.4: {} + + match-sorter@6.3.4: + dependencies: + '@babel/runtime': 7.28.4 + remove-accents: 0.5.0 + + mdast-util-definitions@5.1.2: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + unist-util-visit: 4.1.2 + + mdast-util-find-and-replace@2.2.2: + dependencies: + '@types/mdast': 3.0.15 + escape-string-regexp: 5.0.0 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + + mdast-util-from-markdown@1.3.1: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + decode-named-character-reference: 1.2.0 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@1.0.3: + dependencies: + '@types/mdast': 3.0.15 + ccount: 2.0.1 + mdast-util-find-and-replace: 2.2.2 + micromark-util-character: 1.2.0 + + mdast-util-gfm-footnote@1.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + micromark-util-normalize-identifier: 1.1.0 + + mdast-util-gfm-strikethrough@1.0.3: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + + mdast-util-gfm-table@1.0.7: + dependencies: + '@types/mdast': 3.0.15 + markdown-table: 3.0.4 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@1.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + + mdast-util-gfm@2.0.2: + dependencies: + mdast-util-from-markdown: 1.3.1 + mdast-util-gfm-autolink-literal: 1.0.3 + mdast-util-gfm-footnote: 1.0.2 + mdast-util-gfm-strikethrough: 1.0.3 + mdast-util-gfm-table: 1.0.7 + mdast-util-gfm-task-list-item: 1.0.2 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-math@2.0.2: + dependencies: + '@types/mdast': 3.0.15 + longest-streak: 3.1.0 + mdast-util-to-markdown: 1.5.0 + + mdast-util-mdx-expression@1.3.2: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@2.1.4: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + ccount: 2.0.1 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-remove-position: 4.0.2 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@2.0.1: + dependencies: + mdast-util-from-markdown: 1.3.1 + mdast-util-mdx-expression: 1.3.2 + mdast-util-mdx-jsx: 2.1.4 + mdast-util-mdxjs-esm: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@1.3.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@3.0.1: + dependencies: + '@types/mdast': 3.0.15 + unist-util-is: 5.2.1 + + mdast-util-to-hast@12.3.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-definitions: 5.1.2 + micromark-util-sanitize-uri: 1.2.0 + trim-lines: 3.0.1 + unist-util-generated: 2.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@1.5.0: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + longest-streak: 3.1.0 + mdast-util-phrasing: 3.0.1 + mdast-util-to-string: 3.2.0 + micromark-util-decode-string: 1.1.0 + unist-util-visit: 4.1.2 + zwitch: 2.0.4 + + mdast-util-to-string@3.2.0: + dependencies: + '@types/mdast': 3.0.15 + mdn-data@2.12.2: {} meow@12.1.1: {} merge2@1.4.1: {} + mermaid@10.9.5: + dependencies: + '@braintree/sanitize-url': 6.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + elkjs: 0.9.3 + katex: 0.16.27 + khroma: 2.1.0 + lodash-es: 4.17.22 + mdast-util-from-markdown: 1.3.1 + non-layered-tidy-tree-layout: 2.0.2 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 9.0.1 + web-worker: 1.5.0 + transitivePeerDependencies: + - supports-color + + micromark-core-commonmark@1.1.0: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-autolink-literal@1.0.5: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-extension-gfm-footnote@1.1.2: + dependencies: + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-strikethrough@1.0.7: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-table@1.0.7: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm-tagfilter@1.0.2: + dependencies: + micromark-util-types: 1.1.0 + + micromark-extension-gfm-task-list-item@1.0.5: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-gfm@2.0.3: + dependencies: + micromark-extension-gfm-autolink-literal: 1.0.5 + micromark-extension-gfm-footnote: 1.1.2 + micromark-extension-gfm-strikethrough: 1.0.7 + micromark-extension-gfm-table: 1.0.7 + micromark-extension-gfm-tagfilter: 1.0.2 + micromark-extension-gfm-task-list-item: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-extension-math@2.1.2: + dependencies: + '@types/katex': 0.16.7 + katex: 0.16.27 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-mdx-expression@1.0.8: + dependencies: + '@types/estree': 1.0.8 + micromark-factory-mdx-expression: 1.0.9 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-mdx-jsx@1.0.5: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.8 + estree-util-is-identifier-name: 2.1.0 + micromark-factory-mdx-expression: 1.0.9 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-extension-mdx-md@1.0.1: + dependencies: + micromark-util-types: 1.1.0 + + micromark-extension-mdxjs-esm@1.0.5: + dependencies: + '@types/estree': 1.0.8 + micromark-core-commonmark: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-position-from-estree: 1.1.2 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-extension-mdxjs@1.0.1: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 1.0.8 + micromark-extension-mdx-jsx: 1.0.5 + micromark-extension-mdx-md: 1.0.1 + micromark-extension-mdxjs-esm: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-destination@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-label@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-factory-mdx-expression@1.0.9: + dependencies: + '@types/estree': 1.0.8 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-position-from-estree: 1.1.2 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-factory-space@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 + + micromark-factory-title@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-whitespace@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-character@1.2.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-classify-character@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-combine-extensions@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-decode-numeric-character-reference@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-decode-string@1.1.0: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 + + micromark-util-encode@1.1.0: {} + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@1.2.3: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.8 + '@types/unist': 2.0.11 + estree-util-visit: 1.2.1 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-util-html-tag-name@1.2.0: {} + + micromark-util-normalize-identifier@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-resolve-all@1.1.0: + dependencies: + micromark-util-types: 1.1.0 + + micromark-util-sanitize-uri@1.2.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-util-symbol@1.1.0: {} + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@1.1.0: {} + + micromark-util-types@2.0.2: {} + + micromark@3.2.0: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -2985,6 +5553,116 @@ snapshots: nanoid@3.3.11: {} + next-mdx-remote@4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@mdx-js/mdx': 2.3.0 + '@mdx-js/react': 2.3.0(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + vfile: 5.3.7 + vfile-matter: 3.0.1 + transitivePeerDependencies: + - supports-color + + next-seo@6.8.0(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + next: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next-themes@0.2.1(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + next: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@next/env': 14.2.35 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001761 + graceful-fs: 4.2.11 + postcss: 8.4.31 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(react@18.3.1) + optionalDependencies: + '@next/swc-darwin-arm64': 14.2.33 + '@next/swc-darwin-x64': 14.2.33 + '@next/swc-linux-arm64-gnu': 14.2.33 + '@next/swc-linux-arm64-musl': 14.2.33 + '@next/swc-linux-x64-gnu': 14.2.33 + '@next/swc-linux-x64-musl': 14.2.33 + '@next/swc-win32-arm64-msvc': 14.2.33 + '@next/swc-win32-ia32-msvc': 14.2.33 + '@next/swc-win32-x64-msvc': 14.2.33 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + nextra-theme-docs@2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@popperjs/core': 2.11.8 + clsx: 2.1.1 + escape-string-regexp: 5.0.0 + flexsearch: 0.7.43 + focus-visible: 5.2.1 + git-url-parse: 13.1.1 + intersection-observer: 0.12.2 + match-sorter: 6.3.4 + next: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-seo: 6.8.0(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: 0.2.1(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: 2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + scroll-into-view-if-needed: 3.1.0 + zod: 3.25.76 + + nextra@2.13.4(next@14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 2.3.0 + '@mdx-js/react': 2.3.0(react@18.3.1) + '@napi-rs/simple-git': 0.1.22 + '@theguild/remark-mermaid': 0.0.5(react@18.3.1) + '@theguild/remark-npm2yarn': 0.2.1 + clsx: 2.1.1 + github-slugger: 2.0.0 + graceful-fs: 4.2.11 + gray-matter: 4.0.3 + katex: 0.16.27 + lodash.get: 4.4.2 + next: 14.2.35(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + p-limit: 3.1.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + rehype-katex: 7.0.1 + rehype-pretty-code: 0.9.11(shiki@0.14.7) + rehype-raw: 7.0.0 + remark-gfm: 3.0.1 + remark-math: 5.1.1 + remark-reading-time: 2.0.2 + shiki: 0.14.7 + slash: 3.0.0 + title: 3.5.3 + unist-util-remove: 4.0.0 + unist-util-visit: 5.0.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + non-layered-tidy-tree-layout@2.0.2: {} + + npm-run-path@2.0.2: + dependencies: + path-key: 2.0.1 + + npm-to-yarn@2.2.1: {} + object-assign@4.1.1: {} obug@2.1.1: {} @@ -2995,10 +5673,16 @@ snapshots: dependencies: p-map: 2.1.0 + p-finally@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-limit@4.0.0: dependencies: yocto-queue: 1.2.2 @@ -3023,6 +5707,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -3030,6 +5724,20 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 + parse-numeric-range@1.3.0: {} + + parse-path@7.1.0: + dependencies: + protocols: 2.0.2 + + parse-url@8.1.0: + dependencies: + parse-path: 7.1.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + parse5@8.0.0: dependencies: entities: 6.0.1 @@ -3038,12 +5746,20 @@ snapshots: path-exists@5.0.0: {} + path-key@2.0.1: {} + path-key@3.1.1: {} path-type@4.0.0: {} pathe@2.0.3: {} + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.8 + estree-walker: 3.0.3 + is-reference: 3.0.3 + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3067,6 +5783,12 @@ snapshots: jiti: 2.6.1 postcss: 8.5.6 + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + postcss@8.5.6: dependencies: nanoid: 3.3.11 @@ -3075,12 +5797,30 @@ snapshots: prettier@2.8.8: {} + property-information@6.5.0: {} + + property-information@7.1.0: {} + + protocols@2.0.2: {} + + pseudomap@1.0.2: {} + punycode@2.3.1: {} quansync@0.2.11: {} queue-microtask@1.2.3: {} + react-dom@18.3.1(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 + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -3090,6 +5830,78 @@ snapshots: readdirp@4.1.2: {} + reading-time@1.5.0: {} + + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.27 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + + rehype-pretty-code@0.9.11(shiki@0.14.7): + dependencies: + '@types/hast': 2.3.10 + hash-obj: 4.0.0 + parse-numeric-range: 1.3.0 + shiki: 0.14.7 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + remark-gfm@3.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-gfm: 2.0.2 + micromark-extension-gfm: 2.0.3 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-math@5.1.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-math: 2.0.2 + micromark-extension-math: 2.1.2 + unified: 10.1.2 + + remark-mdx@2.3.0: + dependencies: + mdast-util-mdx: 2.0.1 + micromark-extension-mdxjs: 1.0.1 + transitivePeerDependencies: + - supports-color + + remark-parse@10.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-reading-time@2.0.2: + dependencies: + estree-util-is-identifier-name: 2.1.0 + estree-util-value-to-estree: 3.5.0 + reading-time: 1.5.0 + unist-util-visit: 3.1.0 + + remark-rehype@10.1.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-to-hast: 12.3.0 + unified: 10.1.2 + + remove-accents@0.5.0: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -3100,6 +5912,8 @@ snapshots: reusify@1.1.0: {} + robust-predicates@3.0.2: {} + rollup@4.54.0: dependencies: '@types/estree': 1.0.8 @@ -3132,30 +5946,70 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + + sade@1.8.1: + dependencies: + mri: 1.2.0 + safer-buffer@2.1.2: {} saxes@6.0.0: dependencies: xmlchars: 2.2.0 + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scroll-into-view-if-needed@3.1.0: + dependencies: + compute-scroll-into-view: 3.1.1 + + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + semver@7.7.3: {} + shebang-command@1.2.0: + dependencies: + shebang-regex: 1.0.0 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 + shebang-regex@1.0.0: {} + shebang-regex@3.0.0: {} + shiki@0.14.7: + dependencies: + ansi-sequence-parser: 1.1.3 + jsonc-parser: 3.3.1 + vscode-oniguruma: 1.7.0 + vscode-textmate: 8.0.0 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} slash@3.0.0: {} + sort-keys@5.1.0: + dependencies: + is-plain-obj: 4.1.0 + source-map-js@1.2.1: {} source-map@0.7.6: {} + space-separated-tokens@2.0.2: {} + spawndamnit@3.0.1: dependencies: cross-spawn: 7.0.6 @@ -3169,18 +6023,40 @@ snapshots: std-env@3.10.0: {} + streamsearch@1.1.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 + strip-bom-string@1.0.0: {} + strip-bom@3.0.0: {} + strip-eof@1.0.0: {} + + style-to-object@0.4.4: + dependencies: + inline-style-parser: 0.1.1 + + styled-jsx@5.1.1(react@18.3.1): + dependencies: + client-only: 0.0.1 + react: 18.3.1 + + stylis@4.3.6: {} + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -3191,6 +6067,10 @@ snapshots: tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 + supports-color@4.5.0: + dependencies: + has-flag: 2.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3224,6 +6104,15 @@ snapshots: tinyrainbow@3.0.3: {} + title@3.5.3: + dependencies: + arg: 1.0.0 + chalk: 2.3.0 + clipboardy: 1.2.2 + titleize: 1.0.0 + + titleize@1.0.0: {} + tldts-core@7.0.19: {} tldts@7.0.19: @@ -3244,8 +6133,16 @@ snapshots: tree-kill@1.2.2: {} + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-dedent@2.2.0: {} + ts-interface-checker@0.1.13: {} + tslib@2.8.1: {} + tsup@8.5.1(jiti@2.6.1)(postcss@8.5.6)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.2) @@ -3301,16 +6198,156 @@ snapshots: turbo-windows-64: 2.7.2 turbo-windows-arm64: 2.7.2 + type-fest@1.4.0: {} + typescript@5.9.3: {} ufo@1.6.1: {} + undici-types@6.21.0: {} + undici-types@7.16.0: {} unicorn-magic@0.1.0: {} + unified@10.1.2: + dependencies: + '@types/unist': 2.0.11 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 5.3.7 + + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-generated@2.0.1: {} + + unist-util-is@5.2.1: + dependencies: + '@types/unist': 2.0.11 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@1.1.2: + dependencies: + '@types/unist': 2.0.11 + + unist-util-position@4.0.4: + dependencies: + '@types/unist': 2.0.11 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@4.0.2: + dependencies: + '@types/unist': 2.0.11 + unist-util-visit: 4.1.2 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + unist-util-remove@4.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unist-util-stringify-position@3.0.3: + dependencies: + '@types/unist': 2.0.11 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@4.1.1: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + + unist-util-visit-parents@5.1.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@3.1.0: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + unist-util-visit-parents: 4.1.1 + + unist-util-visit@4.1.2: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + universalify@0.1.2: {} + uuid@9.0.1: {} + + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.0 + kleur: 4.1.5 + sade: 1.8.1 + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-matter@3.0.1: + dependencies: + '@types/js-yaml': 4.0.9 + is-buffer: 2.0.5 + js-yaml: 4.1.1 + + vfile-message@3.1.4: + dependencies: + '@types/unist': 2.0.11 + unist-util-stringify-position: 3.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@5.3.7: + dependencies: + '@types/unist': 2.0.11 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1): dependencies: esbuild: 0.27.2 @@ -3362,10 +6399,18 @@ snapshots: - tsx - yaml + vscode-oniguruma@1.7.0: {} + + vscode-textmate@8.0.0: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + web-namespaces@2.0.1: {} + + web-worker@1.5.0: {} + webidl-conversions@8.0.0: {} whatwg-encoding@3.1.1: @@ -3379,6 +6424,10 @@ snapshots: tr46: 6.0.0 webidl-conversions: 8.0.0 + which@1.3.1: + dependencies: + isexe: 2.0.0 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -3402,6 +6451,8 @@ snapshots: y18n@5.0.8: {} + yallist@2.1.2: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -3414,4 +6465,10 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} + yocto-queue@1.2.2: {} + + zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4340350..37e8d30 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - - 'packages/*' \ No newline at end of file + - 'packages/*' + - 'docs' \ No newline at end of file diff --git a/turbo.json b/turbo.json index 2fafba6..dca1c61 100644 --- a/turbo.json +++ b/turbo.json @@ -7,6 +7,10 @@ "dependsOn": ["^build"], "outputs": ["dist/**"] }, + "docs#build": { + "dependsOn": ["^build"], + "outputs": [".next/**", "!.next/cache/**"] + }, "test": { "dependsOn": ["^build"], "inputs": ["src/**/*.ts", "test/**/*.ts"] @@ -18,6 +22,7 @@ "outputs": [] }, "dev": { + "dependsOn": ["^build"], "cache": false, "persistent": true },