diff --git a/.github/workflows/deploy-editor.yml b/.github/workflows/deploy-editor.yml new file mode 100644 index 00000000000..e60f99e2cba --- /dev/null +++ b/.github/workflows/deploy-editor.yml @@ -0,0 +1,56 @@ +name: Deploy Visual Editor to GitHub Pages + +on: + push: + branches: [main, feature/visual-workflow-editor] + paths: + - 'docs/editor-app/**' + + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deploy.outputs.page_url }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + cache-dependency-path: docs/editor-app/package-lock.json + + - name: Install dependencies + working-directory: docs/editor-app + run: npm ci + + - name: Copy WASM loader files + run: | + mkdir -p docs/editor-app/public/wasm + cp docs/public/wasm/* docs/editor-app/public/wasm/ + + - name: Build for GitHub Pages + working-directory: docs/editor-app + run: npx vite build --mode ghpages + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/editor-app/dist + + - name: Deploy to GitHub Pages + id: deploy + uses: actions/deploy-pages@v4 diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md new file mode 100644 index 00000000000..1fac772212e --- /dev/null +++ b/.specify/memory/constitution.md @@ -0,0 +1,18 @@ +# Constitution + +## Principles + +1. **Visual-First**: The canvas is the primary workspace. Key interactions should happen directly on the canvas, not in side panels. +2. **Delight**: Every interaction should feel polished, responsive, and satisfying. Animations, transitions, and micro-interactions matter. +3. **Simplicity**: Fewer clicks, fewer context switches. The fastest path to a configured workflow wins. +4. **Consistency**: Follow existing Primer design tokens, dark mode support, and component patterns. +5. **Accessibility**: All interactions must be keyboard-navigable and screen-reader friendly. +6. **Performance**: No perceptible lag. Lazy-load where possible but never at the cost of interaction speed. + +## Constraints + +- Must work within the existing React Flow + Zustand + Vite architecture +- Must maintain backward compatibility with existing localStorage state +- Must support both light and dark modes via CSS custom properties +- Must not break existing Playwright e2e tests +- Must work on mobile viewports (responsive) diff --git a/.specify/specs/001-canvas-instructions-editor/plan.md b/.specify/specs/001-canvas-instructions-editor/plan.md new file mode 100644 index 00000000000..70257e2f17c --- /dev/null +++ b/.specify/specs/001-canvas-instructions-editor/plan.md @@ -0,0 +1,81 @@ +# Implementation Plan: Canvas-Native Instructions Editor + +## Architecture Overview + +The inline instructions editor is a new React Flow node type variant that replaces the compact `InstructionsNode` when the user double-clicks it. It renders directly on the canvas as a larger, interactive node with a real textarea. + +### Key Technical Decisions + +1. **Node expansion via React Flow node resizing**: When expanded, the InstructionsNode renders a wider/taller variant. React Flow handles re-layout via Dagre. +2. **State in Zustand**: Add `instructionsExpanded: boolean` to `workflowStore` to track inline editor state. +3. **CSS transitions for animation**: Use CSS transitions on width/height/opacity for smooth expand/collapse. +4. **Textarea with `nodrag` class**: React Flow's `nodrag` class prevents node dragging when interacting with the textarea, allowing normal text selection and input. +5. **Formatting toolbar**: Simple button row that wraps selected text with markdown markers. No rich-text rendering. + +## File Changes + +### New Files +- `src/components/Nodes/InstructionsNodeExpanded.tsx` - The expanded inline editor component +- `src/styles/instructions-editor.css` - Styles for the expanded editor +- `e2e/inline-instructions.spec.ts` - Playwright tests for the new feature + +### Modified Files +- `src/components/Nodes/InstructionsNode.tsx` - Add double-click handler, conditional rendering +- `src/stores/workflowStore.ts` - Add `instructionsExpanded` state + actions +- `src/components/Canvas/WorkflowGraph.tsx` - Adjust node dimensions when expanded +- `src/styles/nodes.css` - Add expanded node styles +- `src/components/Panels/InstructionsPanel.tsx` - No changes needed (kept for backward compat) + +## Component Design + +### InstructionsNode (modified) +``` +- Handles double-click → sets instructionsExpanded = true +- When expanded: renders InstructionsNodeExpanded +- When collapsed: renders current preview (with improved preview per US-5) +``` + +### InstructionsNodeExpanded (new) +``` +┌──────────────────────────────────────────────┐ +│ 📝 Instructions ─ ✕ │ +│──────────────────────────────────────────────│ +│ [B] [I] [H] [•] [1.] formatting │ +│──────────────────────────────────────────────│ +│ │ +│ Tell the AI what to do... │ +│ │ +│ (auto-growing textarea, min 6 lines) │ +│ │ +│──────────────────────────────────────────────│ +│ [+ Be concise] [+ Review code] [+ ...] 42c │ +└──────────────────────────────────────────────┘ + Width: 480px (vs 260px collapsed) + Max height: 400px with scroll +``` + +### Data Flow +``` +Double-click InstructionsNode + → workflowStore.setInstructionsExpanded(true) + → InstructionsNode re-renders with expanded=true + → Renders InstructionsNodeExpanded inside BaseNode (wider) + → WorkflowGraph detects expanded state, uses larger node dimensions for Dagre + → Canvas auto-fits to accommodate larger node + +Type in textarea + → workflowStore.setInstructions(text) + → Auto-compile fires (existing debounce) + → Collapsed preview updates when collapsed + +Click outside / Escape + → workflowStore.setInstructionsExpanded(false) + → Node collapses with CSS transition + → Dagre re-layouts with original dimensions +``` + +## Responsive Behavior + +- **Desktop (>= 1024px)**: Full inline editor on canvas +- **Tablet (768-1023px)**: Inline editor but slightly narrower (420px) +- **Mobile (< 768px)**: Fall back to right panel (no inline editor) diff --git a/.specify/specs/001-canvas-instructions-editor/spec.md b/.specify/specs/001-canvas-instructions-editor/spec.md new file mode 100644 index 00000000000..3acf56a44ae --- /dev/null +++ b/.specify/specs/001-canvas-instructions-editor/spec.md @@ -0,0 +1,206 @@ +# Spec: Canvas-Native Instructions Editor + +## Problem Statement + +Today, writing agent instructions in the visual workflow editor requires a disjointed two-step interaction: + +1. Click the "Instructions" node on the canvas +2. A right-side panel opens with a plain textarea + +This creates several UX problems: +- **Context switching**: The user's eyes must jump from the canvas to a side panel, breaking spatial flow +- **The instructions feel secondary**: Despite being the most important part of any agent workflow (they define _what the agent does_), instructions are given the same generic panel treatment as toggle-heavy config nodes like permissions or network +- **The textarea is generic and uninspiring**: A plain monospace textarea with no formatting affordances, no structure helpers, and no visual hierarchy +- **No inline preview**: The canvas node shows only 3 truncated lines of preview text, giving no sense of the instruction's richness + +Instructions are the soul of an agentic workflow. They deserve a first-class, delightful authoring experience directly on the canvas. + +## Vision + +When a user clicks the Instructions node (or double-clicks to enter edit mode), the node expands into a beautiful, full-featured inline editor directly on the canvas. The editor feels like writing in a modern note-taking app - clean, focused, with just enough formatting support to structure agent instructions clearly. The experience is so pleasant that users _want_ to spend time crafting their instructions. + +--- + +## User Stories + +### P1 - Core Inline Editing + +#### US-1: Expand instructions node into inline editor +**As a** workflow author +**I want to** double-click the Instructions node and have it expand into an inline editor on the canvas +**So that** I can write instructions without leaving my visual context + +**Acceptance Scenarios:** + +```gherkin +Scenario: Double-click to expand + Given I am viewing the workflow canvas with an Instructions node + When I double-click the Instructions node + Then the node smoothly expands into a larger inline editor card + And the editor textarea is automatically focused + And the canvas pans/zooms to keep the editor comfortably centered + +Scenario: Single-click preserves current behavior + Given I am viewing the workflow canvas + When I single-click the Instructions node + Then the right panel opens as before (backward compatible) + And the node does NOT expand inline + +Scenario: Click outside to collapse + Given the inline instructions editor is expanded on the canvas + When I click on the canvas background or another node + Then the editor smoothly collapses back to the compact node preview + And my text changes are preserved + +Scenario: Escape to collapse + Given the inline instructions editor is expanded and focused + When I press Escape + Then the editor collapses back to the compact node + And focus returns to the canvas +``` + +#### US-2: Rich inline editing experience +**As a** workflow author +**I want** the inline editor to feel like a modern writing surface +**So that** writing instructions is pleasant and efficient + +**Acceptance Scenarios:** + +```gherkin +Scenario: Auto-growing editor + Given the inline editor is expanded + When I type multiple lines of instructions + Then the editor height grows smoothly to fit content + And the editor has a reasonable max-height with scrolling + +Scenario: Placeholder guidance + Given the inline editor is expanded and empty + Then I see helpful placeholder text guiding me on what to write + And the placeholder disappears when I start typing + +Scenario: Character count + Given I am typing in the inline editor + Then I see a subtle character count in the bottom-right corner + And it updates in real-time as I type + +Scenario: Live compilation feedback + Given I am typing in the inline editor + When I pause typing for 500ms + Then the workflow auto-compiles in the background + And any compilation errors related to instructions are shown inline +``` + +#### US-3: Quick-insert snippets toolbar +**As a** workflow author +**I want** quick-access buttons to insert common instruction patterns +**So that** I can build instructions faster without typing everything from scratch + +**Acceptance Scenarios:** + +```gherkin +Scenario: Snippets toolbar visible + Given the inline editor is expanded + Then I see a toolbar below the text area with snippet buttons + And the toolbar has common patterns like "Be concise", "Review code", "Create issue" + +Scenario: Insert snippet + Given the inline editor is expanded with some text + When I click a snippet button + Then the snippet text is appended to my instructions + And the cursor moves to the end of the inserted text + +Scenario: Toolbar doesn't obstruct + Given the inline editor is expanded + Then the snippets toolbar does not overlap the text area + And the toolbar scrolls horizontally if there are many snippets +``` + +### P2 - Enhanced Editing Features + +#### US-4: Markdown formatting shortcuts +**As a** workflow author +**I want** basic markdown formatting support in the inline editor +**So that** I can structure my instructions with headings, lists, and emphasis + +**Acceptance Scenarios:** + +```gherkin +Scenario: Formatting toolbar + Given the inline editor is expanded + Then I see a subtle formatting toolbar above the text area + And it includes buttons for: Bold, Italic, Heading, Bulleted list, Numbered list + +Scenario: Apply bold formatting + Given I have selected text in the inline editor + When I click the Bold button or press Cmd/Ctrl+B + Then the selected text is wrapped with ** markers + And the selection is preserved + +Scenario: Insert heading + Given my cursor is at the beginning of a line + When I click the Heading button + Then "## " is inserted at the start of the line +``` + +#### US-5: Improved node preview when collapsed +**As a** workflow author +**I want** the collapsed Instructions node to show a richer preview of my instructions +**So that** I can see at a glance what the agent will do + +**Acceptance Scenarios:** + +```gherkin +Scenario: Multi-line preview + Given I have written instructions with multiple sections + When the Instructions node is in collapsed state + Then I see up to 4 lines of preview text + And the text preserves visual structure (headings appear bolder) + And a "..." indicator shows if there's more content + +Scenario: Empty state with better CTA + Given I have not written any instructions + When I view the Instructions node + Then I see an inviting call-to-action: "Double-click to write instructions" + And the CTA has a subtle pen/edit icon +``` + +### P3 - Polish & Delight + +#### US-6: Smooth expand/collapse animation +**As a** workflow author +**I want** the transition between collapsed node and expanded editor to be buttery smooth +**So that** the experience feels polished and professional + +**Acceptance Scenarios:** + +```gherkin +Scenario: Expand animation + Given the Instructions node is in collapsed state + When I double-click to expand + Then the node expands with a smooth ease-out animation (~250ms) + And the content fades in slightly after the container expands + And neighboring nodes gently shift to make room + +Scenario: Collapse animation + Given the inline editor is expanded + When I click outside to collapse + Then the editor shrinks with a smooth ease-in animation (~200ms) + And the preview text fades in as the editor shrinks +``` + +--- + +## Success Criteria + +1. **Adoption**: >80% of instruction editing happens via the inline editor (not the side panel) within a session +2. **Speed**: Time from double-click to first keystroke < 300ms +3. **Stability**: Zero regressions in existing e2e tests +4. **Accessibility**: Inline editor is fully keyboard-navigable (Tab, Escape, shortcuts) +5. **Responsiveness**: Works correctly on viewports >= 768px wide (on mobile, fall back to panel) + +## Out of Scope + +- AI-assisted instruction writing (autocomplete, suggestions) - future enhancement +- Collaborative editing - single-user tool +- Full rich-text/WYSIWYG editing - we use plain text with optional markdown shortcuts +- Drag-to-reorder instruction blocks - plain text is sufficient diff --git a/.specify/specs/001-canvas-instructions-editor/tasks.md b/.specify/specs/001-canvas-instructions-editor/tasks.md new file mode 100644 index 00000000000..d98c832ec4e --- /dev/null +++ b/.specify/specs/001-canvas-instructions-editor/tasks.md @@ -0,0 +1,28 @@ +# Tasks: Canvas-Native Instructions Editor + +## Phase 1: Foundation + +- [ ] [T1] [P1] [US-1] Add `instructionsExpanded` boolean + `setInstructionsExpanded` action to workflowStore.ts +- [ ] [T2] [P1] [US-1] Create `InstructionsNodeExpanded.tsx` component with textarea, auto-focus, `nodrag`/`nowheel` classes +- [ ] [T3] [P1] [US-1] Create `instructions-editor.css` with expanded node styles, dark mode support, CSS transitions +- [ ] [T4] [P1] [US-1] Modify `InstructionsNode.tsx` to handle double-click → expand, render expanded variant conditionally + +## Phase 2: Canvas Integration + +- [ ] [T5] [P1] [US-1] Update `WorkflowGraph.tsx` to use larger node dimensions (480x400) when instructions is expanded +- [ ] [T6] [P1] [US-1] Add click-outside and Escape handlers to collapse the expanded editor +- [ ] [T7] [P1] [US-2] Implement auto-growing textarea height with max-height + scroll +- [ ] [T8] [P1] [US-3] Add snippet buttons toolbar below textarea in expanded editor + +## Phase 3: Enhanced Features + +- [ ] [T9] [P2] [US-4] Add formatting toolbar (Bold, Italic, Heading, List) with keyboard shortcuts +- [ ] [T10] [P2] [US-5] Improve collapsed node preview: 4 lines, better CTA with edit icon +- [ ] [T11] [P3] [US-6] Polish expand/collapse animations (ease-out 250ms, content fade-in stagger) + +## Phase 4: Testing + +- [ ] [T12] [P1] Create Playwright e2e test: double-click expands, typing works, click-outside collapses +- [ ] [T13] [P1] Create Playwright e2e test: snippets insert text, character count updates +- [ ] [T14] [P2] Create Playwright e2e test: formatting toolbar applies markdown markers +- [ ] [T15] [P1] Verify existing e2e tests still pass (no regressions) diff --git a/docs/editor-app/ARCHITECTURE.md b/docs/editor-app/ARCHITECTURE.md new file mode 100644 index 00000000000..4b5594613cb --- /dev/null +++ b/docs/editor-app/ARCHITECTURE.md @@ -0,0 +1,323 @@ +# Visual Workflow Editor — Architecture + +## Tech Stack + +| Layer | Technology | Purpose | +|-------|-----------|---------| +| Build | Vite 6 + React 19 + TypeScript 5.7 | Fast HMR, modern bundling | +| Visual Graph | @xyflow/react v12 (React Flow) | Node-based flow editor | +| UI Components | @primer/react v37 | GitHub-native design system | +| Accessible Primitives | @radix-ui/* | Dialog, tooltip, select, tabs, accordion | +| State Management | Zustand v5 | Lightweight, middleware-ready store | +| Drag & Drop | @dnd-kit/core + @dnd-kit/sortable | Sidebar-to-canvas DnD | +| Icons | @primer/octicons-react + lucide-react | Consistent iconography | +| Animations | framer-motion v11 | Panel transitions, node effects | +| Syntax Highlighting | Prism.js (via CDN or prism-react-renderer) | YAML output highlighting | +| Notifications | sonner | Toast notifications | +| WASM Runtime | Go WASM (existing gh-aw.wasm) | Real-time compilation | + +## Build Configuration + +``` +docs/editor-app/ ← Vite project root +├── vite.config.ts ← Output to ../public/editor/ +├── index.html ← SPA entry point +├── public/ ← Static assets (copied as-is) +└── src/ ← React source +``` + +**Vite config key points:** +- `build.outDir`: `../public/editor/` +- `base`: `/gh-aw/editor/` (GitHub Pages subpath) +- WASM files served from `/gh-aw/wasm/` (existing location) +- Dev server proxies `/wasm/` to `../public/wasm/` + +## Component Hierarchy + +``` + +├── # Primer theme (light/dark) +│ ├──
# Logo, workflow name, status, actions +│ │ ├── +│ │ ├── # Ready / Compiling / Error badge +│ │ ├── +│ │ ├── +│ │ ├── # Download .md, .yml, copy clipboard +│ │ └── +│ │ +│ ├── # Three-panel resizable layout +│ │ ├── # Left panel (240px, collapsible) +│ │ │ ├── # Drag-to-add node categories +│ │ │ │ ├── # "Triggers", "Configuration", etc. +│ │ │ │ └── # Individual draggable node type +│ │ │ ├── # Pre-built workflow templates +│ │ │ │ ├── +│ │ │ │ └── +│ │ │ └── # Switch: Visual / Markdown / YAML +│ │ │ +│ │ ├── # Center panel (flex-grow) +│ │ │ ├── +│ │ │ │ ├── # React Flow instance +│ │ │ │ │ ├── +│ │ │ │ │ ├── +│ │ │ │ │ ├── +│ │ │ │ │ ├── +│ │ │ │ │ ├── +│ │ │ │ │ ├── +│ │ │ │ │ ├── +│ │ │ │ │ └── +│ │ │ │ ├── +│ │ │ │ └── # Zoom, fit, lock +│ │ │ └── # Shown when no nodes +│ │ │ +│ │ └── # Right panel (360px, collapsible) +│ │ ├── +│ │ ├── +│ │ ├── +│ │ ├── +│ │ ├── +│ │ ├── +│ │ ├── +│ │ └── +│ │ +│ ├── # Bottom drawer / side panel +│ │ ├── # "YAML Output" | "Markdown Source" +│ │ ├── +│ │ └── +│ │ +│ ├── # First-visit welcome +│ ├── # Step-by-step tooltips +│ └── # Notification toasts +``` + +## State Model (Zustand) + +```typescript +interface WorkflowState { + // Metadata + name: string; + description: string; + + // Trigger configuration + trigger: { + event: string; // 'issues' | 'pull_request' | 'issue_comment' | ... + activityTypes: string[]; // 'opened' | 'closed' | 'labeled' | ... + branches?: string[]; + paths?: string[]; + schedule?: string; // cron expression + skipRoles?: string[]; + skipBots?: boolean; + }; + + // Permissions + permissions: Record; + // e.g. { contents: 'write', issues: 'write', pull_requests: 'read' } + + // Engine + engine: { + type: 'claude' | 'copilot' | 'codex' | 'custom'; + model?: string; + config?: Record; + }; + + // Tools + tools: string[]; + // e.g. ['github', 'playwright', 'bash', 'web-search'] + + // Instructions (markdown body) + instructions: string; + + // Safe outputs + safeOutputs: Record>; + // e.g. { 'create-issue': true, 'add-comment': true, 'add-labels': { labels: ['bug'] } } + + // Network + network: { + allowed: string[]; + blocked: string[]; + }; + + // Advanced + sandbox?: Record; + mcpServers?: Record; + steps?: unknown[]; + postSteps?: unknown[]; + imports?: string[]; + environment?: Record; + cache?: boolean; + strict?: boolean; + + // UI state + selectedNodeId: string | null; + viewMode: 'visual' | 'markdown' | 'yaml'; + compiledYaml: string; + compiledMarkdown: string; + warnings: string[]; + error: string | null; + isCompiling: boolean; + isReady: boolean; + + // Actions + setTrigger: (trigger: Partial) => void; + setPermissions: (perms: Record) => void; + setEngine: (engine: Partial) => void; + toggleTool: (tool: string) => void; + setInstructions: (text: string) => void; + toggleSafeOutput: (key: string) => void; + setNetwork: (network: Partial) => void; + selectNode: (id: string | null) => void; + setViewMode: (mode: 'visual' | 'markdown' | 'yaml') => void; + loadTemplate: (template: WorkflowTemplate) => void; + reset: () => void; +} +``` + +## Data Flow + +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ Visual UI │────▶│ Zustand Store │────▶│ Markdown Generator│ +│ (React) │ │ (state) │ │ (state → .md) │ +└──────────────┘ └──────────────┘ └──────────────────┘ + │ + ▼ +┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ +│ YAML Output │◀────│ Web Worker │◀────│ WASM Compiler │ +│ (preview) │ │ (postMessage)│ │ (gh-aw.wasm) │ +└──────────────┘ └──────────────┘ └──────────────────┘ +``` + +1. User interacts with visual UI (clicks, types, toggles) +2. React components call Zustand actions +3. Zustand middleware triggers markdown generation (debounced 400ms) +4. Generated markdown is sent to Web Worker via postMessage +5. Worker runs WASM `compileWorkflow(markdown)` +6. Result (yaml, warnings, error) posted back to main thread +7. Zustand store updates `compiledYaml`, `warnings`, `error` +8. YAML preview panel re-renders with new output + +## File Structure + +``` +docs/editor-app/ +├── index.html +├── vite.config.ts +├── tsconfig.json +├── package.json +├── ARCHITECTURE.md +├── DESIGN.md +├── USER-JOURNEYS.md +└── src/ + ├── main.tsx # React entry point + ├── App.tsx # Root component + ├── types/ + │ ├── workflow.ts # WorkflowState, WorkflowTemplate types + │ ├── nodes.ts # React Flow node type definitions + │ └── compiler.ts # WASM compiler message types + ├── stores/ + │ ├── workflowStore.ts # Main Zustand store + │ └── uiStore.ts # UI-only state (panels, theme) + ├── utils/ + │ ├── markdownGenerator.ts # State → markdown conversion + │ ├── compiler.ts # WASM compiler bridge + │ ├── templates.ts # Template definitions + │ └── fieldDescriptions.ts # Plain English field descriptions + ├── components/ + │ ├── Header/ + │ │ ├── Header.tsx + │ │ ├── CompilationStatus.tsx + │ │ └── ExportMenu.tsx + │ ├── Sidebar/ + │ │ ├── Sidebar.tsx + │ │ ├── NodePalette.tsx + │ │ └── TemplateGallery.tsx + │ ├── Canvas/ + │ │ ├── WorkflowGraph.tsx + │ │ └── EmptyState.tsx + │ ├── Nodes/ + │ │ ├── BaseNode.tsx # Shared node chrome + │ │ ├── TriggerNode.tsx + │ │ ├── PermissionsNode.tsx + │ │ ├── EngineNode.tsx + │ │ ├── ToolsNode.tsx + │ │ ├── InstructionsNode.tsx + │ │ ├── SafeOutputsNode.tsx + │ │ ├── NetworkNode.tsx + │ │ └── StepsNode.tsx + │ ├── Panels/ + │ │ ├── PanelContainer.tsx # Shared panel chrome + │ │ ├── TriggerPanel.tsx + │ │ ├── PermissionsPanel.tsx + │ │ ├── EnginePanel.tsx + │ │ ├── ToolsPanel.tsx + │ │ ├── InstructionsPanel.tsx + │ │ ├── SafeOutputsPanel.tsx + │ │ ├── NetworkPanel.tsx + │ │ └── StepsPanel.tsx + │ ├── YamlPreview/ + │ │ ├── YamlPreview.tsx + │ │ └── MarkdownSource.tsx + │ ├── Onboarding/ + │ │ ├── WelcomeModal.tsx + │ │ └── GuidedTour.tsx + │ └── shared/ + │ ├── ResizablePanel.tsx + │ ├── HelpTooltip.tsx + │ ├── FieldLabel.tsx # Label + help icon + description + │ └── StatusBadge.tsx + ├── hooks/ + │ ├── useCompiler.ts # WASM compilation hook + │ ├── useAutoCompile.ts # Debounced auto-compile + │ └── useTheme.ts # Theme detection + persistence + └── styles/ + ├── globals.css # Reset, Primer tokens + ├── nodes.css # React Flow node styles + └── panels.css # Property panel styles +``` + +## WASM Integration + +The existing WASM infrastructure is preserved: +- `docs/public/wasm/gh-aw.wasm` — 16MB Go WASM binary +- `docs/public/wasm/wasm_exec.js` — Go runtime glue +- `docs/public/wasm/compiler-loader.js` — Worker API wrapper +- `docs/public/wasm/compiler-worker.js` — Web Worker script + +The React app creates a typed bridge: + +```typescript +// src/utils/compiler.ts +import { createWorkerCompiler } from '/wasm/compiler-loader.js'; + +let compiler: WorkerCompiler | null = null; + +export async function initCompiler(): Promise { + compiler = await createWorkerCompiler('/wasm/'); +} + +export async function compile(markdown: string): Promise { + if (!compiler) throw new Error('Compiler not initialized'); + return compiler.compile(markdown); +} +``` + +The `useAutoCompile` hook subscribes to store changes and triggers compilation: + +```typescript +// src/hooks/useAutoCompile.ts +export function useAutoCompile() { + const store = useWorkflowStore(); + + useEffect(() => { + const unsubscribe = useWorkflowStore.subscribe( + (state) => generateMarkdown(state), + debounce((markdown) => { + compile(markdown).then(result => { + store.setCompilationResult(result); + }); + }, 400) + ); + return unsubscribe; + }, []); +} +``` diff --git a/docs/editor-app/DEPLOY-REQUIREMENTS.md b/docs/editor-app/DEPLOY-REQUIREMENTS.md new file mode 100644 index 00000000000..01abe099130 --- /dev/null +++ b/docs/editor-app/DEPLOY-REQUIREMENTS.md @@ -0,0 +1,297 @@ +# Deploy to GitHub — Requirements + +**Status**: Approved +**Date**: 2026-02-24 +**References**: [DEPLOY-TO-GITHUB-PLAN.md](./DEPLOY-TO-GITHUB-PLAN.md), [SECURITY-REVIEW.md](./src/SECURITY-REVIEW.md) + +--- + +## 1. User Workflows + +### W-1: First-Time User (No Token) + +1. User opens the editor, configures a workflow (or loads a template). +2. User clicks "Deploy to GitHub" in the Export menu. +3. Dialog opens at the **Token Setup** step. +4. User clicks "Create a token on GitHub" link — opens `github.com/settings/tokens/new?scopes=repo,workflow&description=gh-aw-editor-deploy` in a new tab. +5. User pastes token into the input field. +6. "Remember this token" checkbox is **unchecked by default**. +7. User clicks "Save & Continue". +8. System calls `GET /user` to validate token. On success, transitions to **Repo Selection** step. +9. User enters `owner/repo`, confirms branch name (auto-derived from workflow name), and clicks "Deploy". +10. System executes the 5-step deploy sequence (verify repo → create branch → upload .md → upload .lock.yml → create PR). +11. **Success** step shows a link to the PR. User clicks "View PR on GitHub" or "Done". + +### W-2: Returning User (Saved Token) + +1. User clicks "Deploy to GitHub". +2. System detects a saved token, calls `GET /user` to validate it. +3. If valid, dialog opens directly at the **Repo Selection** step (skips token setup). +4. If invalid/expired, dialog shows an error and falls back to the **Token Setup** step with the message "Your saved token is no longer valid. Please enter a new one." + +### W-3: Deploy to a New/Empty Repo + +1. User enters a repo slug for a repo that has no `.github/workflows/` directory yet. +2. Deploy succeeds — the GitHub Contents API creates intermediate directories automatically. +3. PR is created as normal. No special handling required. + +### W-4: Deploy a Second Workflow (Branch Conflict) + +1. User deploys workflow "issue-triage" → branch `aw/issue-triage` is created, PR opened. +2. User modifies the workflow and deploys again with the same branch name. +3. GitHub returns 422 ("Reference already exists") on `POST /repos/{owner}/{repo}/git/refs`. +4. System shows an error: "Branch `aw/issue-triage` already exists. Change the branch name or delete the existing branch on GitHub." +5. User changes branch name to `aw/issue-triage-v2` and retries successfully. + +### W-5: Invalid or Expired Token + +1. User's previously saved token is revoked or expired. +2. On deploy attempt, `GET /user` returns 401. +3. System clears the invalid token from memory (and localStorage if persisted). +4. Dialog shows **Token Setup** step with error: "Your token is invalid or expired. Please enter a new one." + +--- + +## 2. Edge Cases + +### E-1: Empty Workflow + +- **Trigger**: User clicks "Deploy to GitHub" with no template loaded and no configuration. +- **Expected**: Deploy button is **disabled** in the Export menu. Tooltip: "Configure a workflow before deploying." +- **Gate condition**: `compiledMarkdown` is empty OR `compiledYaml` is empty OR `error` is non-null in `workflowStore`. + +### E-2: Workflow Compiles with Warnings + +- **Trigger**: WASM compiler returns warnings but no error, and `compiledYaml` is non-empty. +- **Expected**: Deploy is **allowed**. Warnings are shown in the Repo Selection step as a dismissible banner: "Compilation produced {N} warning(s). The workflow may not behave as expected." + +### E-3: WASM Compiler Not Yet Loaded + +- **Trigger**: User clicks "Deploy to GitHub" before the WASM compiler has initialized (`isReady === false` in `workflowStore`). +- **Expected**: Deploy button is **disabled** with tooltip "Compiler loading..." until `isReady` becomes `true`. + +### E-4: Token Lacks Required Scope (403) + +- **Trigger**: Token is valid (`GET /user` succeeds) but lacks `repo` or `workflow` scope. File upload returns 403. +- **Expected**: System shows error: "Your token doesn't have permission to push to this repository. Ensure your token has `repo` and `workflow` scopes." Deploy stops at the failed step; prior steps keep their checkmarks. + +### E-5: Repo Doesn't Exist (404) + +- **Trigger**: User enters a non-existent repo slug. +- **Expected**: Step 1 ("Verify repository access") fails with error: "Repository `{owner}/{repo}` not found. Check the name and your token's access." + +### E-6: Branch Already Exists (422) + +- See W-4 above. Error message includes the branch name and suggests renaming. + +### E-7: Network Timeout / Failure + +- **Trigger**: Any GitHub API call fails with a network error (no HTTP status). +- **Expected**: Current step shows error icon. Error message: "Network error — check your internet connection and try again." A "Retry" button appears that retries from the failed step (not from the beginning). + +### E-8: User Closes Dialog Mid-Deploy + +- **Trigger**: User clicks X or presses Escape while deploy is in progress. +- **Expected**: A confirmation prompt appears: "Deploy is in progress. Closing will cancel the remaining steps. Already-uploaded files will remain on GitHub." Options: "Continue Deploying" | "Close Anyway". +- If closed: deploy is cancelled, no PR is created (orphaned branch may remain). + +### E-9: Malformed Repo Slug + +- **Trigger**: User enters `/`, `a/b/c`, `../..`, or special characters. +- **Expected**: Inline validation error under the repo input: "Enter a valid repository in the format `owner/repo`." Deploy button stays disabled. +- **Validation regex**: `^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$` + +### E-10: Invalid Branch Name + +- **Trigger**: User edits auto-generated branch name to include `..`, spaces, `~`, `^`, `:`, `\`, or trailing `.lock`. +- **Expected**: Inline validation error: "Invalid branch name." Deploy button stays disabled. + +### E-11: File Already Exists on Target Branch + +- **Trigger**: User deploys a workflow whose `.md` or `.lock.yml` file already exists at the path on the base branch. +- **Expected**: The Contents API `PUT` creates or **updates** the file (specify `sha` of existing file). This is fine for a new branch. If the branch is new, no conflict is possible. No special handling needed. + +### E-12: Very Large Workflow + +- **Trigger**: Workflow markdown or compiled YAML exceeds GitHub's 1MB file size limit. +- **Expected**: System shows error: "Workflow file is too large (>{size}). Reduce the instructions or configuration." This is unlikely in practice. + +--- + +## 3. Acceptance Criteria + +Each criterion is testable via Playwright e2e tests or unit tests. + +### AC-1: End-to-End Deploy (Happy Path) + +**Test**: Load "Issue Triage" template → open Deploy dialog → enter a valid token → enter `{test-owner}/{test-repo}` → click Deploy → verify PR is created. + +**Assertions**: +- [ ] PR exists at `https://github.com/{owner}/{repo}/pull/{number}` +- [ ] PR title matches `Add Agentic Workflow: {workflow-name}` +- [ ] PR base branch is `main` (or the repo's default branch) +- [ ] PR head branch matches the `branchName` field from the dialog + +### AC-2: PR Contains Valid Workflow Source (.md) + +**Test**: After AC-1, fetch the `.md` file from the PR branch. + +**Assertions**: +- [ ] File exists at `.github/workflows/{name}.md` +- [ ] File content starts with `---` (YAML frontmatter delimiter) +- [ ] File contains `name:` field in frontmatter +- [ ] File contains `on:` field in frontmatter +- [ ] File contains `engine:` field in frontmatter +- [ ] File contains an instructions section (markdown body after frontmatter) +- [ ] File content matches `workflowStore.compiledMarkdown` byte-for-byte + +### AC-3: PR Contains Valid Compiled YAML (.lock.yml) + +**Test**: After AC-1, fetch the `.lock.yml` file from the PR branch. + +**Assertions**: +- [ ] File exists at `.github/workflows/{name}.lock.yml` +- [ ] File parses as valid YAML +- [ ] YAML contains top-level `name:` key +- [ ] YAML contains top-level `on:` key +- [ ] YAML contains `jobs:` with at least one job +- [ ] File content matches `workflowStore.compiledYaml` byte-for-byte + +### AC-4: Error Messages Are Clear and Actionable + +**Test**: Trigger each error condition and verify the displayed message. + +| Condition | Expected Message (substring match) | +|-----------|-----------------------------------| +| Invalid token (401) | "invalid or expired" | +| Repo not found (404) | "not found" | +| Missing scope (403) | "permission" | +| Branch exists (422) | "already exists" | +| Network failure | "Network error" | +| Malformed repo slug | "owner/repo" (format hint) | + +**Assertions**: +- [ ] Error messages do NOT contain the token string +- [ ] Error messages include the specific resource that failed (repo name, branch name) +- [ ] Each error message suggests a corrective action + +### AC-5: Token Not Persisted by Default + +**Test**: Enter token with "Remember" unchecked → close dialog → reopen. + +**Assertions**: +- [ ] `localStorage.getItem('gh-aw-deploy')` does NOT contain a token string after dialog close (when "Remember" is unchecked) +- [ ] `rememberToken` defaults to `false` in `deployStore` initial state +- [ ] After page refresh, the token is gone and the dialog starts at the Token Setup step +- [ ] When "Remember" IS checked: `localStorage` contains the token, and re-opening skips to Repo Selection + +### AC-6: Deploy Button Disabled When Workflow Is Empty + +**Test**: Reset the editor to empty state → check Deploy button. + +**Assertions**: +- [ ] Deploy menu item is disabled (not clickable) when `compiledYaml` is empty +- [ ] Deploy menu item is disabled when `compiledMarkdown` is empty +- [ ] Deploy menu item is disabled when `error` is non-null in `workflowStore` +- [ ] Deploy menu item is disabled when `isReady` is `false` (compiler not loaded) +- [ ] Deploy menu item becomes enabled after loading a template and successful compilation + +### AC-7: Progress Steps Animate Correctly + +**Test**: Initiate a deploy and observe each step. + +**Assertions**: +- [ ] Exactly 5 steps are shown: "Verify repository access", "Create branch", "Upload workflow source", "Upload compiled YAML", "Create pull request" +- [ ] Each step starts with a pending indicator (e.g., empty circle) +- [ ] The currently executing step shows a spinner/loading indicator +- [ ] Completed steps show a checkmark +- [ ] On error, the failed step shows an error icon; subsequent steps remain pending +- [ ] Steps transition in order (no skipping) + +### AC-8: Dark Mode Compatible + +**Test**: Toggle theme to dark mode → open Deploy dialog. + +**Assertions**: +- [ ] Dialog background uses dark theme colors (not white) +- [ ] Text is readable (sufficient contrast ratio) +- [ ] Input fields have dark theme styling +- [ ] Progress indicators (checkmarks, spinners, error icons) are visible on dark background +- [ ] No hardcoded colors that break in dark mode + +--- + +## 4. Security Requirements + +These are derived from the [security review](./src/SECURITY-REVIEW.md) and must be addressed in implementation. + +### S-1: Input Validation + +- [ ] Repo slug validated with `^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$` before any API call +- [ ] Branch name validated against git ref rules (no `..`, no `\`, no trailing `.lock`, no ASCII control chars) +- [ ] All user-supplied path segments (`owner`, `repo`, `branch`, `filepath`) use `encodeURIComponent()` in API URLs + +### S-2: Token Safety + +- [ ] `rememberToken` defaults to `false` +- [ ] Token is never logged to console +- [ ] Token is never included in error messages shown to the user +- [ ] Token is transmitted only via `Authorization` header over HTTPS +- [ ] A "Clear saved token" option is accessible from the deploy dialog at all times + +### S-3: PR Body Safety + +- [ ] `${{ }}` patterns in user-authored content are escaped (rendered as code literals) before inclusion in PR body +- [ ] PR body does not include raw workflow instructions that could contain injection strings + +### S-4: Modern APIs + +- [ ] No usage of deprecated `unescape()` — use `TextEncoder` for UTF-8 base64 encoding + +### S-5: Double-Submit Protection + +- [ ] Deploy button is disabled while a deploy is in progress +- [ ] Re-clicking "Deploy" during an active deploy does not trigger a second API call sequence + +--- + +## 5. Non-Functional Requirements + +### NF-1: No Backend Required + +- All API calls go directly from the browser to `api.github.com` via `fetch()`. +- No CORS proxy, no Cloudflare Worker, no server-side component. + +### NF-2: No New Heavy Dependencies + +- Use native `fetch()` for GitHub API calls — do NOT add `@octokit/rest` or `axios`. +- The only new dependencies should be those already in `package.json` (Radix Dialog is already present). + +### NF-3: Copy CLI Command Fallback + +- The Export menu should also offer "Copy CLI command" that generates a `gh aw compile` + `gh pr create` command sequence for users who prefer not to use browser tokens. + +### NF-4: State Cleanup + +- On dialog close (after success or cancel), reset the deploy store's transient state (progress, error, prUrl) but preserve token/username if remembered. + +--- + +## 6. Files to Create + +| File | Purpose | +|------|---------| +| `src/stores/deployStore.ts` | Zustand store: token, step, repo, progress, prUrl | +| `src/utils/githubApi.ts` | `fetch()` wrapper for GitHub REST API (7 endpoints) | +| `src/utils/deploy.ts` | Orchestration: validate → branch → files → PR | +| `src/components/Deploy/DeployDialog.tsx` | Multi-step Radix Dialog container | +| `src/components/Deploy/TokenSetup.tsx` | Step 1: token input + validation | +| `src/components/Deploy/RepoSelector.tsx` | Step 2: repo/branch inputs + validation | +| `src/components/Deploy/DeployProgress.tsx` | Step 3: animated progress checklist | +| `src/components/Deploy/DeploySuccess.tsx` | Step 4: PR link + done button | + +## 7. Files to Modify + +| File | Change | +|------|--------| +| `src/components/Header/ExportMenu.tsx` | Add "Deploy to GitHub" and "Copy CLI Command" items | diff --git a/docs/editor-app/DEPLOY-TO-GITHUB-PLAN.md b/docs/editor-app/DEPLOY-TO-GITHUB-PLAN.md new file mode 100644 index 00000000000..780f6691be4 --- /dev/null +++ b/docs/editor-app/DEPLOY-TO-GITHUB-PLAN.md @@ -0,0 +1,210 @@ +# Architecture: "Deploy to Your Repo" Feature + +## Overview + +Add a "Deploy to GitHub" button that lets users push their configured workflow directly to a GitHub repo as a PR — entirely from the browser, no backend needed. + +## Approach: PAT (Personal Access Token) — Purely Frontend + +### Why PAT? + +| Option | Purely Frontend? | UX | Complexity | +|--------|-----------------|-----|------------| +| **A: PAT (recommended)** | Yes | Good (one-time setup) | Low | +| B: Device Flow OAuth | No (needs CORS proxy) | Decent | Medium | +| C: OAuth + Cloudflare Worker | No (needs Worker) | Best | Medium-High | +| D: Copy `gh` CLI command | Yes | Poor (manual) | Very Low | + +- GitHub REST API (`api.github.com`) **supports CORS** — browser `fetch()` works +- GitHub OAuth token endpoints **do NOT support CORS** — OAuth needs a proxy server +- PAT is the only fully-frontend auth option +- Fine-grained PATs can be scoped to specific repos + +## UI Flow + +### Step 1: Token Setup (one-time) + +``` +┌─────────────────────────────────────────┐ +│ Deploy to GitHub [X] │ +│ │ +│ To deploy workflows, you need a │ +│ GitHub Personal Access Token with │ +│ repo and workflow scopes. │ +│ │ +│ [Create a token on GitHub →] │ +│ │ +│ Token: [_________________________] │ +│ [ ] Remember this token │ +│ │ +│ [Cancel] [Save & Continue] │ +└─────────────────────────────────────────┘ +``` + +Link: `github.com/settings/tokens/new?scopes=repo,workflow&description=gh-aw-editor-deploy` + +### Step 2: Repository Selection + +``` +┌─────────────────────────────────────────┐ +│ Deploy to GitHub [X] │ +│ │ +│ Repository: │ +│ [owner/repo___________________] │ +│ │ +│ Branch name: │ +│ [aw/my-workflow_______________] │ +│ │ +│ Base branch: │ +│ [main_________________________] │ +│ │ +│ [Cancel] [Deploy] │ +└─────────────────────────────────────────┘ +``` + +### Step 3: Progress + +``` +┌─────────────────────────────────────────┐ +│ Deploying... [X] │ +│ │ +│ ✓ Verified repository access │ +│ ✓ Created branch aw/my-workflow │ +│ ✓ Uploaded workflow.md │ +│ ● Uploading workflow.lock.yml... │ +│ ○ Creating pull request │ +└─────────────────────────────────────────┘ +``` + +### Step 4: Success + +``` +┌─────────────────────────────────────────┐ +│ Deployed! ✓ [X] │ +│ │ +│ Pull request created successfully. │ +│ │ +│ [View PR on GitHub →] │ +│ │ +│ [Done] │ +└─────────────────────────────────────────┘ +``` + +## GitHub API Calls + +All calls to `api.github.com` with `Authorization: Bearer {token}`: + +| Step | API Call | Purpose | +|------|----------|---------| +| 1 | `GET /user` | Validate token, get username | +| 2 | `GET /repos/{owner}/{repo}` | Verify repo exists + push access | +| 3 | `GET /repos/{owner}/{repo}/git/ref/heads/{branch}` | Get default branch SHA | +| 4 | `POST /repos/{owner}/{repo}/git/refs` | Create branch `aw/{name}` | +| 5 | `PUT /repos/{owner}/{repo}/contents/.github/workflows/{name}.md` | Upload workflow source | +| 6 | `PUT /repos/{owner}/{repo}/contents/.github/workflows/{name}.lock.yml` | Upload compiled YAML | +| 7 | `POST /repos/{owner}/{repo}/pulls` | Create the PR | + +**Required token scopes**: `repo` + `workflow` + +**Important**: Do NOT include `X-GitHub-Api-Version` header — it causes CORS preflight failures. + +## Files to Create + +| File | Purpose | +|------|---------| +| `src/stores/deployStore.ts` | Zustand store: token, step, repo, progress, prUrl | +| `src/utils/githubApi.ts` | Thin `fetch()` wrapper for GitHub REST API | +| `src/utils/deploy.ts` | Orchestration: branch → files → PR | +| `src/components/Deploy/DeployDialog.tsx` | Multi-step dialog (Radix Dialog) | +| `src/components/Deploy/TokenSetup.tsx` | Step 1: token input | +| `src/components/Deploy/RepoSelector.tsx` | Step 2: repo/branch selection | +| `src/components/Deploy/DeployProgress.tsx` | Step 3: progress checklist | +| `src/components/Deploy/DeploySuccess.tsx` | Step 4: success + PR link | + +## Files to Modify + +| File | Change | +|------|--------| +| `src/components/Header/Header.tsx` | Add "Deploy to GitHub" button to Export menu | + +## Files Deployed to the Repo + +The PR creates two files: + +1. **`.github/workflows/{name}.md`** — workflow source (human-editable) +2. **`.github/workflows/{name}.lock.yml`** — compiled YAML (machine-generated) + +Both from `workflowStore.compiledMarkdown` and `workflowStore.compiledYaml`. + +## PR Body Template + +```markdown +## Add Agentic Workflow: {name} + +{description} + +### Files +- `.github/workflows/{name}.md` — Workflow source (edit this) +- `.github/workflows/{name}.lock.yml` — Compiled output (auto-generated) + +### Trigger +{trigger event description} + +### Next Steps +1. Review the workflow configuration +2. Merge this PR to activate the workflow + +--- +*Deployed from [gh-aw Visual Editor](https://mossaka.github.io/gh-aw-editor-visualizer/)* +``` + +## Security + +- Token stored in `localStorage` (same as existing Zustand stores) +- Static site with no user-generated HTML — minimal XSS surface +- Fine-grained PATs can scope to specific repos +- Token never included in committed files +- User can revoke token anytime from GitHub settings + +## Deploy Store Schema + +```typescript +interface DeployState { + token: string | null; + username: string | null; + isOpen: boolean; + step: 'auth' | 'repo' | 'deploying' | 'success' | 'error'; + repoSlug: string; // "owner/repo" + branchName: string; // "aw/workflow-name" + baseBranch: string; // "main" + progress: { id: string; label: string; status: 'pending' | 'running' | 'done' | 'error' }[]; + prUrl: string | null; + error: string | null; +} +``` + +## Fallback: Copy gh CLI Command + +For users who don't want browser tokens, add "Copy CLI command" to Export menu: + +```bash +# Save workflow files +cat > .github/workflows/{name}.md << 'WORKFLOW_EOF' +{markdown content} +WORKFLOW_EOF + +# Compile +gh aw compile .github/workflows/{name}.md + +# Create PR +gh pr create --title "Add agentic workflow: {name}" --body "..." +``` + +## Future Enhancements (V2) + +- Repo search autocomplete via `GET /user/repos` +- Token encryption with user passphrase +- Branch conflict resolution +- Update existing workflow (not just create) +- Device Flow OAuth (when GitHub adds CORS support) +- Import workflow from repo into editor diff --git a/docs/editor-app/DESIGN.md b/docs/editor-app/DESIGN.md new file mode 100644 index 00000000000..55be13fcd89 --- /dev/null +++ b/docs/editor-app/DESIGN.md @@ -0,0 +1,1585 @@ +# Visual Agentic Workflow Editor - Design Specification + +## Table of Contents + +1. [Design Philosophy](#1-design-philosophy) +2. [Layout Design](#2-layout-design) +3. [Visual Flow Graph Design](#3-visual-flow-graph-design) +4. [Property Panel Designs](#4-property-panel-designs) +5. [Color System & Theme](#5-color-system--theme) +6. [Interactions & Animations](#6-interactions--animations) +7. [Component Specifications](#7-component-specifications) +8. [Onboarding Design](#8-onboarding-design) + +--- + +## 1. Design Philosophy + +**Target Users**: Business analysts, product managers, team leads - people who understand *what* they want an AI agent to do but shouldn't need to write YAML. + +**Core Principles**: +- **Visual-first**: Every concept has a visual representation. No raw text editing required. +- **Progressive disclosure**: Show the simplest view by default; advanced options are one click away. +- **Safe defaults**: Pre-fill smart defaults so users can create working workflows with minimal input. +- **Immediate feedback**: Compile in real-time so users see results as they build. + +**Mental Model**: A workflow is a visual pipeline: *"When X happens, use Y engine with Z tools to follow these instructions, then produce these outputs."* Each concept is a card (node) on a canvas, connected by lines showing the flow. + +--- + +## 2. Layout Design + +### 2.1 Overall Layout + +``` ++------------------------------------------------------------------+ +| [Logo] Workflow Name [Status] [Theme] [Compile] [Export] | <- Header (48px) ++----------+-----------------------------------+--------------------+ +| | | | +| SIDEBAR | CANVAS | PROPERTIES | +| (240px) | (flex) | (360px) | +| | | | +| [Nodes] | +-------+ +--------+ | [Form fields for | +| [Search] | |Trigger |---->|Engine | | selected node] | +| | +-------+ +--------+ | | +| Trigger | | | | +| Engine | +--------+ | | +| Tools | | Tools | | | +| Instruct | +--------+ | | +| Outputs | | | | +| Network | +--------+ | | +| Perms | |Outputs | | | +| | +--------+ | | +| | | | ++----------+-----------------------------------+--------------------+ +| [Zoom: -/+/Fit] [Minimap] [YAML Preview Toggle] | <- Footer bar (32px) ++------------------------------------------------------------------+ +``` + +### 2.2 Header Bar + +``` ++------------------------------------------------------------------+ +| [>_] gh-aw | my-workflow.md [*] | Ready | [D] [Compile] [v] | ++------------------------------------------------------------------+ + ^ ^ ^ ^ ^ ^ ^ + Logo Name (editable) Dirty Status Theme Button Export + dot badge toggle dropdown +``` + +- **Height**: 48px +- **Background**: `--bgColor-default` (Primer) +- **Border**: 1px bottom border `--borderColor-default` +- **Logo**: Terminal icon `>_` in accent color, links back to docs. 20x20px. +- **Workflow Name**: Inline-editable text field, `font-size: 14px`, `font-weight: 600`. Click to edit, blur to save. +- **Dirty Indicator**: Small 6px circle, `--fgColor-attention` (amber), appears when unsaved changes exist. +- **Status Badge**: Primer `Label` component: + - `Label--success` + green dot: "Ready" + - `Label--accent` + pulsing dot: "Compiling..." + - `Label--danger` + red dot: "Error" +- **Theme Toggle**: `btn-octicon` with sun/moon icon. 32x32px hit target. +- **Compile Button**: Primer `btn btn-primary btn-sm`. Play icon + "Compile" text. +- **Export Dropdown**: `btn btn-sm` with caret. Options: "Download .md", "Download .lock.yml", "Copy YAML", "Open in Playground". + +### 2.3 Sidebar (Node Palette) + +``` ++-----------+ +| PALETTE | ++-----------+ +| [Search] | <- Filter nodes ++-----------+ +| | +| [*] Trigg | <- Draggable node cards +| [B] Engin | +| [W] Tools | +| [D] Instr | +| [M] Outpu | +| [G] Netwo | +| [S] Perms | +| | ++-----------+ +| TEMPLATES | <- Expandable section ++-----------+ +| PR Review | +| Issue Tri | +| Code Scan | ++-----------+ +``` + +- **Width**: 240px (collapsible to 48px icon-only rail) +- **Background**: `--bgColor-default` +- **Border**: 1px right border +- **Search**: Primer `TextInput` with search icon, `font-size: 13px`, 8px padding +- **Node Cards**: + - Dimensions: 208px wide x 56px tall + - Border-radius: 8px + - Left accent bar: 3px wide, colored by node type + - Icon: 20x20px, left-aligned + - Label: `font-size: 13px`, `font-weight: 500` + - Subtitle: `font-size: 11px`, `color: --fgColor-muted` + - Cursor: `grab` on hover + - On drag start: opacity 0.6, show ghost card following cursor +- **Collapse Button**: Chevron icon at top-right of sidebar. Sidebar collapses to 48px rail showing only icons. +- **Templates Section**: Collapsible accordion. Each template is a pre-built workflow graph that can be dragged onto the canvas. + +### 2.4 Canvas (Center) + +- **Flex**: Takes all remaining horizontal space +- **Background**: Dot grid pattern (dots spaced 20px apart) + - Light mode: dots `rgba(0,0,0,0.06)` on `#ffffff` + - Dark mode: dots `rgba(255,255,255,0.04)` on `#0d1117` +- **Pan**: Middle-click drag or Space+drag +- **Zoom**: Scroll wheel, range 25%-200%, default 100% +- **Selection**: Click node to select (blue outline), click canvas to deselect +- **Multi-select**: Shift+click or drag selection rectangle + +### 2.5 Properties Panel (Right) + +- **Width**: 360px (collapsible) +- **Background**: `--bgColor-default` +- **Border**: 1px left border +- **Header**: Selected node type icon + name, 40px height +- **Content**: Scrollable form area below header +- **Empty State**: "Select a node to edit its properties" with pointer illustration +- **Collapse**: Click the `<<` button to hide panel; a `>>` tab appears on the right edge + +### 2.6 Footer Bar + +``` ++------------------------------------------------------------------+ +| [-] 100% [+] [Fit] [Minimap] [YAML Preview: Off/On] | ++------------------------------------------------------------------+ +``` + +- **Height**: 32px +- **Background**: `--bgColor-subtle` +- **Zoom Controls**: `-` / percentage / `+` / "Fit" button. All `btn-octicon` style. +- **Minimap Toggle**: Small rectangle icon, opens a 160x120px minimap overlay in bottom-right of canvas. +- **YAML Preview Toggle**: Slide toggle. When enabled, a bottom drawer slides up showing the compiled YAML (read-only, syntax-highlighted). + +--- + +## 3. Visual Flow Graph Design + +### 3.1 Node Anatomy + +Every node follows the same base structure: + +``` ++--+---------------------------------------+ +| | [Icon] Node Title | +| | subtitle / preview text | +| +---------------------------------------+ +|AC| Node-specific content | +| | (badges, chips, preview) | ++--+---------------------------------------+ + ^ + Accent bar (3px, node-type color) +``` + +- **Base dimensions**: 260px wide, variable height (min 64px) +- **Border-radius**: 12px +- **Border**: 1px solid `--borderColor-default` +- **Shadow**: `0 1px 3px rgba(0,0,0,0.08)` (light), `0 1px 3px rgba(0,0,0,0.3)` (dark) +- **Background**: `--bgColor-default` +- **Accent bar**: 3px left border, colored by node type +- **Title**: `font-size: 13px`, `font-weight: 600` +- **Subtitle**: `font-size: 11px`, `color: --fgColor-muted`, max 1 line, ellipsis overflow + +### 3.2 Connection Lines (Edges) + +- **Style**: Bezier curves with smooth entry/exit +- **Color**: `--borderColor-default` (idle), `--fgColor-accent` (active/selected) +- **Width**: 2px +- **Arrow**: Small chevron at target end, 8px +- **Animation on creation**: Line draws from source to target over 200ms + +### 3.3 Node Type Designs + +#### 3.3.1 Trigger Node + +``` ++--+---------------------------------------+ +| | [Zap] Trigger | +| | When this happens... | +|G |---------------------------------------+ +|R | | +|E | +------------------+ | +|E | | issue_comment | [opened] | +|N | +------------------+ | +| | Schedule: every Monday 9am | ++--+---------------------------------------+ +``` + +- **Accent**: `#2da44e` (green-500) +- **Icon**: Zap/lightning bolt, 18x18px, filled green +- **Content**: + - Event type shown as a Primer `Label` badge + - Activity types shown as small gray `Label--secondary` badges + - Schedule displayed as human-readable text (converted from cron) + - If `workflow_dispatch`: shows "Manual trigger" with play button icon +- **Handles**: Single output handle on the right edge (small 8px circle) + +#### 3.3.2 Permissions Node + +``` ++--+---------------------------------------+ +| | [Shield] Permissions | +|Y | Token access levels | +|E |---------------------------------------+ +|L | | +|L | [issues: write] [contents: read] | +|O | [pull-requests: write] [metadata: R] | +|W | [models: read] | +| | | ++--+---------------------------------------+ +``` + +- **Accent**: `#d4a72c` (yellow-600) +- **Icon**: Shield with checkmark, 18x18px +- **Content**: + - Permission badges in a flex-wrap grid + - Each badge: `Label` with colored variant + - `write`: `Label--attention` (amber background) + - `read`: `Label--success` (green background) + - `none`: `Label--secondary` (gray, dimmed) + - Format: `scope: level` (e.g., "issues: write") + - If `read-all` or `write-all` shorthand: single large badge +- **Handles**: Input on left, output on right + +#### 3.3.3 Engine Node + +``` ++--+---------------------------------------+ +| | [Brain] Engine | +|B | AI processor | +|L |---------------------------------------+ +|U | | +|E | +------+ Claude | +| | | LOGO | Model: claude-sonnet-4-... | +| | +------+ Max turns: 10 | +| | | ++--+---------------------------------------+ +``` + +- **Accent**: `#0969da` (blue-500) +- **Icon**: Brain/sparkle, 18x18px +- **Content**: + - Engine logo (24x24px): Claude (purple anthropic icon), Copilot (GitHub Copilot icon), Codex (OpenAI icon) + - Engine name in bold: "Claude", "Copilot", or "Codex" + - Model shown below in muted text (if set) + - Max turns shown as a small detail + - If custom engine: shows "Custom" with gear icon +- **Handles**: Input on left, output on right + +#### 3.3.4 Tools Node + +``` ++--+---------------------------------------+ +| | [Wrench] Tools | +|P | MCP servers & capabilities | +|U |---------------------------------------+ +|R | | +|P | [GH] GitHub [PW] Playwright | +|L | [B] Bash [E] Edit | +|E | [WF] Web Fetch [S] Serena | +| | | ++--+---------------------------------------+ +``` + +- **Accent**: `#8250df` (purple-500) +- **Icon**: Wrench, 18x18px +- **Content**: + - Tool chips in a flex-wrap grid + - Each chip: rounded pill shape (border-radius: 12px) + - 16x16px icon on left (tool-specific icon) + - Tool name text, `font-size: 12px` + - Background: light purple tint `rgba(130,80,223,0.08)` + - Tool-specific icons: + - GitHub: Octocat mark + - Bash: Terminal `>_` + - Playwright: Drama mask + - Edit: Pencil + - Web Fetch: Globe with arrow + - Web Search: Magnifying glass + - Serena: Code brackets + - Cache Memory: Database + - Repo Memory: Git branch +- **Handles**: Input on left, output on right + +#### 3.3.5 Instructions Node + +``` ++--+---------------------------------------+ +| | [Doc] Instructions | +|G | Mission & context for the agent | +|R |---------------------------------------+ +|A | | +|Y | # Mission | +| | Review the pull request for | +| | security issues and suggest... | +| | ~~~ | ++--+---------------------------------------+ +``` + +- **Accent**: `#57606a` (gray-500) +- **Icon**: Document/file text, 18x18px +- **Content**: + - Rendered markdown preview (first ~4 lines) + - Monospace font, `font-size: 12px`, `line-height: 1.5` + - Overflow: fade-to-background gradient at bottom + - Click opens full markdown editor in Properties panel +- **Handles**: Input on left, output on right + +#### 3.3.6 Safe Outputs Node + +``` ++--+---------------------------------------+ +| | [Megaphone] Safe Outputs | +|G | What the agent can produce | +|R |---------------------------------------+ +|E | | +|E | [+] add-comment [+] create-issue | +|N | [+] add-labels [+] create-pr | +| | [+] submit-review | +| | | ++--+---------------------------------------+ +``` + +- **Accent**: `#1a7f37` (green-600) +- **Icon**: Megaphone/broadcast, 18x18px +- **Content**: + - Output type badges in flex-wrap grid + - Each badge: Primer `Label--success` with `+` icon prefix + - Human-readable names (hyphenated): "add-comment", "create-issue", "create-pr" + - Badge count shown in node title area: "Safe Outputs (5)" +- **Handles**: Input on left (no output - terminal node) + +#### 3.3.7 Network Node + +``` ++--+---------------------------------------+ +| | [Globe] Network | +|R | Egress control & firewall | +|E |---------------------------------------+ +|D | | +| | Allowed: 5 domains | +| | [github.com] [api.anthropic.com] | +| | [pypi.org] +2 more | +| | Firewall: AWF v0.13.5 | ++--+---------------------------------------+ +``` + +- **Accent**: `#cf222e` (red-500) +- **Icon**: Globe with shield overlay, 18x18px +- **Content**: + - "Allowed: N domains" summary line + - First 2-3 domain chips shown, `+N more` overflow indicator + - Domain chips: monospace font, `font-size: 11px`, gray background + - Firewall status line if configured + - If `network: defaults`: shows "Defaults" badge +- **Handles**: Input on left, output on right + +### 3.4 Default Flow Layout + +When creating a new workflow, nodes auto-arrange in this vertical order: + +``` + +----------+ + | Trigger | (always first - defines "when") + +----------+ + | + +----------+ + | Perms | (what access the workflow has) + +----------+ + | + +----------+ + | Engine | (which AI processes it) + +----------+ + | + +----------+ + | Tools | (what tools the AI can use) + +----------+ + | + +----------+ + | Instruct | (what the AI should do) + +----------+ + | + +----------+ + | Outputs | (what the AI can produce) + +----------+ + | + +----------+ + | Network | (network restrictions) + +----------+ +``` + +Edges connect each node to the next automatically. The user can rearrange freely, but this is the default. + +--- + +## 4. Property Panel Designs + +When a node is selected, the Properties panel shows a form specific to that node type. + +### 4.1 Common Panel Structure + +``` ++--------------------------------------------+ +| [NodeIcon] Node Type Name [X] | <- Panel header (40px) ++--------------------------------------------+ +| [Tab1] [Tab2] [Tab3] | <- Optional tabs ++--------------------------------------------+ +| | +| Section Header | +| ---------------------------------------- | +| Field Label [?] | +| [Input / Selector / Toggle] | +| | +| Field Label [?] | +| [Input / Selector / Toggle] | +| | +| > Advanced Options | <- Collapsible section +| | ++--------------------------------------------+ +``` + +- **Panel header**: 40px, node icon + type name + close button +- **Section headers**: `font-size: 12px`, `font-weight: 600`, uppercase, `letter-spacing: 0.5px`, `color: --fgColor-muted`. 1px bottom border. +- **Field labels**: `font-size: 13px`, `font-weight: 500`. Inline `[?]` tooltip icon. +- **Tooltips**: On `[?]` hover, show a Primer `Tooltip` with plain-English explanation. Max width 280px. +- **Spacing**: 16px padding, 12px gap between fields, 20px gap between sections +- **Scrolling**: Panel body scrolls independently of header + +### 4.2 Trigger Node Properties + +``` ++--------------------------------------------+ +| [Zap] Trigger [X] | ++--------------------------------------------+ +| | +| Event Type | +| ---------------------------------------- | +| Choose what triggers this workflow [?] | +| | +| +------------------+ +------------------+ | +| | [Chat] Issue | | [PR] Pull | | +| | Comment | | Request | | +| +------------------+ +------------------+ | +| +------------------+ +------------------+ | +| | [Cal] Schedule | | [Zap] Push | | +| +------------------+ +------------------+ | +| +------------------+ +------------------+ | +| | [Play] Manual | | [Tag] Release | | +| +------------------+ +------------------+ | +| +------------------+ +------------------+ | +| | [Slash] Slash | | [Bug] Issues | | +| | Command | | | | +| +------------------+ +------------------+ | +| | +| Activity Types [?] | +| (shown dynamically after event selection) | +| [x] opened [x] edited [ ] closed | +| [ ] reopened [ ] labeled | +| | +| > Conditional Execution | +| ---------------------------------------- | +| Skip Roles [?] | +| [Tag input: admin, write, ...] | +| | +| Skip Bots [?] | +| [Tag input: dependabot, ...] | +| | +| Skip If Match [?] | +| [Text input: GitHub search query] | +| | +| > Reactions & Status | +| ---------------------------------------- | +| Reaction [?] | +| [Emoji picker: +1 -1 heart rocket ...] | +| | +| Status Comment [?] | +| [Toggle: On/Off] | +| | +| > Manual Approval | +| ---------------------------------------- | +| Environment [?] | +| [Text input: environment name] | +| | ++--------------------------------------------+ +``` + +**Event Type Selector**: +- 2-column grid of selectable cards +- Each card: ~152px wide, 56px tall +- Icon (24x24) + event name +- Border: 1px solid `--borderColor-default` +- Selected: 2px solid `--fgColor-accent`, light accent background +- Hover: `--bgColor-subtle` + +**Event cards and their icons**: +| Event | Icon | Label | +|-------|------|-------| +| `issue_comment` | Chat bubble | Issue Comment | +| `pull_request` | Git merge | Pull Request | +| `schedule` | Calendar/clock | Schedule | +| `push` | Arrow up | Push | +| `workflow_dispatch` | Play circle | Manual | +| `release` | Tag | Release | +| `slash_command` | Forward slash | Slash Command | +| `issues` | Circle dot | Issues | +| `discussion` | Discussion bubble | Discussion | +| `pull_request_review` | Eye | PR Review | + +**Activity Types**: +- Checkbox grid, shown dynamically based on selected event type +- Pre-checked with common defaults (e.g., "opened" for issues) +- Maps to the event's `types:` field + +**Schedule Input** (when Schedule event selected): +- Natural language input: "every Monday at 9am" +- Shows converted cron expression below in muted monospace +- Preset buttons: "Hourly", "Daily", "Weekly", "Monthly" + +**Reaction Selector**: +- Horizontal row of clickable emoji icons +- Options: +1, -1, laugh, confused, heart, hooray, rocket, eyes, none +- Selected reaction gets accent ring +- Maps to `on.reaction` field + +### 4.3 Permissions Node Properties + +``` ++--------------------------------------------+ +| [Shield] Permissions [X] | ++--------------------------------------------+ +| | +| Quick Setup [?] | +| ---------------------------------------- | +| Choose a preset or customize individually | +| | +| ( ) Read All ( ) Write All (*) Custom | +| | +| Permission Scopes | +| ---------------------------------------- | +| | +| issues [None | Read | Write v] | +| contents [None | Read | Write v] | +| pull-requests [None | Read | Write v] | +| discussions [None | Read | Write v] | +| actions [None | Read | Write v] | +| checks [None | Read | Write v] | +| deployments [None | Read | Write v] | +| models [None | Read v] | +| packages [None | Read | Write v] | +| pages [None | Read | Write v] | +| security-events [None | Read | Write v] | +| statuses [None | Read | Write v] | +| id-token [None | Write v] | +| metadata [None | Read | Write v] | +| attestations [None | Read | Write v] | +| | +| [Auto-detect minimum permissions] | +| | ++--------------------------------------------+ +``` + +**Quick Setup**: Radio button group with three options. Selecting "Read All" or "Write All" disables the individual selectors and sets all to that level. + +**Permission Selectors**: +- Each row: scope name (left-aligned, `font-size: 13px`) + segmented control (right-aligned) +- Segmented control: 3 options ("None", "Read", "Write") as Primer `SegmentedControl` + - None: gray + - Read: green tint + - Write: amber tint +- Rows with non-default values are highlighted with subtle accent background +- Special cases: + - `id-token`: Only shows "None" and "Write" (no Read option) + - `models`: Only shows "None" and "Read" (no Write option) + +**Smart Defaults**: Based on selected engine and tools, auto-suggest permissions: +- If tools include `github` with write actions: suggest `issues: write`, `pull-requests: write`, `contents: read` +- If engine is `copilot`: suggest `models: read` +- "Auto-detect" button analyzes the full workflow and recommends the minimum set + +### 4.4 Engine Node Properties + +``` ++--------------------------------------------+ +| [Brain] Engine [X] | ++--------------------------------------------+ +| | +| AI Engine [?] | +| ---------------------------------------- | +| Which AI processes your workflow | +| | +| +-------------+ +-------------+ | +| | [A] Claude | | [GH] Copil | | +| | Anthropic | | ot (Rec.)| | +| +-------------+ +-------------+ | +| +-------------+ +-------------+ | +| | [OA] Codex | | [Gear] Cus | | +| | OpenAI | | tom | | +| +-------------+ +-------------+ | +| | +| Model (optional) [?] | +| [Dropdown: claude-sonnet-4-... v] | +| Tip: Leave blank for the latest default | +| | +| Max Turns [?] | +| [Slider: 1 ----o---------- 50] [10] | +| How many iterations the agent can take | +| | +| > Advanced | +| ---------------------------------------- | +| Version [?] | +| [Text input] | +| | +| Custom Command [?] | +| [Text input] | +| | +| Additional Args [?] | +| [Tag input: --flag, --verbose, ...] | +| | +| Environment Variables [?] | +| [Key-Value editor] | +| | +| Concurrency | +| Group [Text input] | +| Cancel in-progress [Toggle] | +| | ++--------------------------------------------+ +``` + +**Engine Selector**: +- 2-column grid of selectable cards (same pattern as Trigger events) +- Each card shows: engine logo (24x24) + engine name + company name +- "Copilot" card has "(Recommended)" tag +- Selected card: accent border + background tint + +**Engine cards**: +| Engine | Logo | Name | Subtitle | +|--------|------|------|----------| +| `claude` | Anthropic A mark (purple) | Claude | By Anthropic | +| `copilot` | GitHub Copilot icon | Copilot | By GitHub | +| `codex` | OpenAI icon | Codex | By OpenAI | +| custom | Gear icon | Custom | Bring your own | + +**Model Dropdown**: +- Shown after engine selection +- Pre-populated with known models for the selected engine: + - Claude: claude-sonnet-4-20250514, claude-opus-4-20250514, etc. + - Copilot: (default, managed by GitHub) + - Codex: gpt-4, etc. +- Free-text entry allowed for custom model strings + +**Max Turns Slider**: +- Range: 1-50, default 10 +- Slider with numeric input box beside it +- Tooltip: "Higher values let the agent take more steps but cost more" + +### 4.5 Tools Node Properties + +``` ++--------------------------------------------+ +| [Wrench] Tools [X] | ++--------------------------------------------+ +| | +| Available Tools [?] | +| ---------------------------------------- | +| Toggle the tools your agent can use | +| | +| +------------------------------------------+ +| | [GH] GitHub API [Toggle: ON ] || +| | Repository operations [Configure >]|| +| +------------------------------------------+ +| | [>_] Bash [Toggle: ON ] || +| | Shell command execution || +| +------------------------------------------+ +| | [Pencil] Edit [Toggle: OFF] || +| | File reading & writing || +| +------------------------------------------+ +| | [Drama] Playwright [Toggle: OFF] || +| | Browser automation [Configure >]|| +| +------------------------------------------+ +| | [Globe] Web Fetch [Toggle: OFF] || +| | Download web pages || +| +------------------------------------------+ +| | [Search] Web Search [Toggle: OFF] || +| | Internet search || +| +------------------------------------------+ +| | [Code] Serena [Toggle: OFF] || +| | AI code intelligence [Configure >]|| +| +------------------------------------------+ +| | [DB] Cache Memory [Toggle: OFF] || +| | Persistent storage [Configure >]|| +| +------------------------------------------+ +| | [Git] Repo Memory [Toggle: OFF] || +| | Git-based storage [Configure >]|| +| +------------------------------------------+ +| | [AW] Agentic Workflows [Toggle: OFF] || +| | Workflow introspection || +| +------------------------------------------+ +| | +| > GitHub Tool Configuration | +| (expanded when "Configure >" is clicked) | +| ---------------------------------------- | +| Mode: ( ) Local (*) Remote | +| Read-only: [Toggle: OFF] | +| Lockdown: [Toggle: OFF] | +| Allowed Functions [?] | +| [Tag input: create_issue, ...] | +| Toolsets [?] | +| [Tag input: repos, issues, ...] | +| | +| Timeouts | +| ---------------------------------------- | +| Operation Timeout [?] | +| [Number input: 300] seconds | +| Startup Timeout [?] | +| [Number input: 60] seconds | +| | ++--------------------------------------------+ +``` + +**Tool Toggle Cards**: +- Full-width list items, 52px tall +- Icon (20x20) + tool name (bold) + description (muted) on left +- Toggle switch on right +- "Configure >" link appears for tools with sub-options (GitHub, Playwright, Serena, Cache Memory, Repo Memory) +- Clicking "Configure >" expands an inline configuration section below the toggle card + +**Tool list with descriptions**: +| Tool | Icon | Description | Has Config | +|------|------|-------------|------------| +| GitHub | Octocat | Repository operations (issues, PRs, content) | Yes | +| Bash | Terminal | Shell command execution | No | +| Edit | Pencil | File reading & writing | No | +| Playwright | Drama mask | Browser automation & screenshots | Yes | +| Web Fetch | Globe+arrow | Download web pages & API responses | No | +| Web Search | Magnifier | Internet search & results | No | +| Serena | Code brackets | AI code intelligence with language services | Yes | +| Cache Memory | Database | Persistent cross-run storage via cache | Yes | +| Repo Memory | Git branch | Git-based persistent storage | Yes | +| Agentic Workflows | Workflow icon | Workflow introspection & analysis | No | + +**GitHub Sub-configuration** (expanded): +- Mode selector: Radio buttons "Local" / "Remote" +- Read-only toggle +- Lockdown toggle +- Allowed functions: Tag input with autocomplete from known GitHub API functions +- Toolsets: Tag input with suggestions (repos, issues, pull_requests, etc.) +- GitHub App: Optional section for app-id and private-key fields + +**Playwright Sub-configuration** (expanded): +- Allowed domains: Tag input +- Version: Text input + +**Cache Memory Sub-configuration** (expanded): +- Cache key: Text input +- Description: Text input +- Retention days: Number input (1-90) +- Restore only: Toggle +- Scope: Radio buttons "Workflow" / "Repo" +- Allowed extensions: Tag input + +**Repo Memory Sub-configuration** (expanded): +- Branch prefix: Text input (default: "memory") +- Target repo: Text input (default: current repo) +- File glob: Tag input +- Max file size: Number input (bytes) +- Max file count: Number input + +### 4.6 Instructions Node Properties + +``` ++--------------------------------------------+ +| [Doc] Instructions [X] | ++--------------------------------------------+ +| [Edit] [Preview] | ++--------------------------------------------+ +| | +| # Mission | +| | +| Review the pull request and check for: | +| - Security vulnerabilities | +| - Code quality issues | +| - Test coverage gaps | +| | +| ## Guidelines | +| | +| - Be constructive and specific | +| - Suggest fixes, not just problems | +| - Reference relevant documentation | +| | +| 1,247 / 20,000 characters | +| | +| > Quick Snippets | +| ---------------------------------------- | +| [Review checklist] | +| [Issue triage rules] | +| [Security guidelines] | +| [Documentation style] | +| | ++--------------------------------------------+ +``` + +**Editor Mode**: +- Full markdown editor filling the panel body +- Monospace font, `font-size: 13px`, `line-height: 1.6` +- Syntax highlighting for markdown headings, lists, bold/italic +- Tab key inserts 2 spaces +- Character count shown below: current / max + +**Preview Mode**: +- Rendered markdown preview using GitHub-flavored markdown +- Read-only + +**Tabs**: "Edit" and "Preview" toggle tabs at top (Primer `UnderlineNav`) + +**Quick Snippets**: Pre-built instruction templates the user can insert with one click. Each inserts a block of markdown at the cursor position. + +### 4.7 Safe Outputs Node Properties + +``` ++--------------------------------------------+ +| [Megaphone] Safe Outputs [X] | ++--------------------------------------------+ +| | +| Output Actions [?] | +| ---------------------------------------- | +| What your agent can create or modify | +| | +| Comments & Reviews | +| ........................................ | +| [x] Add Comment | +| [x] Create PR Review Comment | +| [x] Submit PR Review | +| [x] Reply to Review Comment | +| [ ] Resolve Review Thread | +| [ ] Hide Comment | +| | +| Issues | +| ........................................ | +| [x] Create Issue | +| [ ] Close Issue | +| [ ] Update Issue | +| [ ] Link Sub-Issue | +| | +| Pull Requests | +| ........................................ | +| [ ] Create Pull Request | +| [ ] Close Pull Request | +| [ ] Update Pull Request | +| [ ] Mark PR Ready for Review | +| [ ] Push to PR Branch | +| | +| Labels & Assignment | +| ........................................ | +| [x] Add Labels | +| [ ] Remove Labels | +| [ ] Add Reviewer | +| [ ] Assign to User | +| [ ] Unassign from User | +| [ ] Assign to Agent (@copilot) | +| [ ] Assign Milestone | +| | +| Discussions | +| ........................................ | +| [ ] Create Discussion | +| [ ] Close Discussion | +| [ ] Update Discussion | +| | +| Projects | +| ........................................ | +| [ ] Update Project | +| [ ] Create Project | +| [ ] Create Project Status Update | +| | +| Code Scanning | +| ........................................ | +| [ ] Create Code Scanning Alert | +| [ ] Autofix Code Scanning Alert | +| | +| Other | +| ........................................ | +| [ ] Dispatch Workflow | +| [ ] Upload Asset | +| [ ] Update Release | +| | +| > Global Settings | +| ---------------------------------------- | +| Staged Mode [?] [Toggle: OFF] | +| Preview without making real changes | +| | +| Allowed Domains [?] | +| [Tag input: github.com, ...] | +| | +| Max Patch Size [?] | +| [Number input: 100] KB | +| | +| Footer [?] [Toggle: ON] | +| | +| Threat Detection [?] [Toggle: OFF] | +| | ++--------------------------------------------+ +``` + +**Output Checkboxes**: +- Grouped by category with section sub-headers +- Each checkbox: `font-size: 13px` +- Categories separated by 12px gap + dotted sub-header line +- Count shown in node: "N actions enabled" + +**Complete output action list** (mapped from `safe-outputs` schema): +| Category | Action | Schema Key | +|----------|--------|------------| +| Comments & Reviews | Add Comment | `add-comment` | +| Comments & Reviews | Create PR Review Comment | `create-pull-request-review-comment` | +| Comments & Reviews | Submit PR Review | `submit-pull-request-review` | +| Comments & Reviews | Reply to Review Comment | `reply-to-pull-request-review-comment` | +| Comments & Reviews | Resolve Review Thread | `resolve-pull-request-review-thread` | +| Comments & Reviews | Hide Comment | `hide-comment` | +| Issues | Create Issue | `create-issue` | +| Issues | Close Issue | `close-issue` | +| Issues | Update Issue | `update-issue` | +| Issues | Link Sub-Issue | `link-sub-issue` | +| Pull Requests | Create Pull Request | `create-pull-request` | +| Pull Requests | Close Pull Request | `close-pull-request` | +| Pull Requests | Update Pull Request | `update-pull-request` | +| Pull Requests | Mark PR Ready | `mark-pull-request-as-ready-for-review` | +| Pull Requests | Push to PR Branch | `push-to-pull-request-branch` | +| Labels & Assignment | Add Labels | `add-labels` | +| Labels & Assignment | Remove Labels | `remove-labels` | +| Labels & Assignment | Add Reviewer | `add-reviewer` | +| Labels & Assignment | Assign to User | `assign-to-user` | +| Labels & Assignment | Unassign from User | `unassign-from-user` | +| Labels & Assignment | Assign to Agent | `assign-to-agent` | +| Labels & Assignment | Assign Milestone | `assign-milestone` | +| Discussions | Create Discussion | `create-discussion` | +| Discussions | Close Discussion | `close-discussion` | +| Discussions | Update Discussion | `update-discussion` | +| Projects | Update Project | `update-project` | +| Projects | Create Project | `create-project` | +| Projects | Project Status Update | `create-project-status-update` | +| Code Scanning | Create Alert | `create-code-scanning-alert` | +| Code Scanning | Autofix Alert | `autofix-code-scanning-alert` | +| Other | Dispatch Workflow | `dispatch-workflow` | +| Other | Upload Asset | `upload-asset` | +| Other | Update Release | `update-release` | +| Other | Create Agent Task | `create-agent-task` | +| Other | Create Agent Session | `create-agent-session` | + +### 4.8 Network Node Properties + +``` ++--------------------------------------------+ +| [Globe] Network [X] | ++--------------------------------------------+ +| | +| Network Access [?] | +| ---------------------------------------- | +| Control which domains the agent can reach | +| | +| Quick Setup | +| (*) Defaults (recommended) | +| ( ) Custom | +| ( ) Unrestricted (no firewall) | +| | +| Allowed Domains [?] | +| (shown when Custom is selected) | +| ---------------------------------------- | +| +-----------------------------------+ | +| | github.com [x] | | +| | api.anthropic.com [x] | | +| | pypi.org [x] | | +| | registry.npmjs.org [x] | | +| +-----------------------------------+ | +| [+ Add domain] | +| | +| Ecosystem Presets [?] | +| [x] Python (pypi.org, etc.) | +| [x] Node (npmjs.org, etc.) | +| [ ] Go (proxy.golang.org, etc.) | +| [ ] Rust (crates.io, etc.) | +| | +| Blocked Domains [?] | +| ---------------------------------------- | +| [Tag input: tracker.example.com, ...] | +| | +| > Firewall Settings | +| ---------------------------------------- | +| Firewall [?] | +| [Toggle: ON] AWF (Agent Workflow FW) | +| | +| Version [?] | +| [Text input: 0.13.5] | +| | +| Log Level [?] | +| [Dropdown: debug | info | warn | error] | +| | +| SSL Bump [?] | +| [Toggle: OFF] | +| | +| Allow URLs (requires SSL Bump) [?] | +| [Tag input: pattern, ...] | +| | ++--------------------------------------------+ +``` + +**Domain List**: +- Each domain in its own row with a delete `[x]` button +- `+ Add domain` button below the list +- Input validates domain format (hostname with optional wildcard) +- Supports `*.example.com` wildcard patterns + +**Ecosystem Presets**: +- Checkboxes that auto-add/remove known domains for each ecosystem +- Shows tooltip listing all domains in the preset on hover +- Maps to `network.allowed` array values like `"python"`, `"node"` + +--- + +## 5. Color System & Theme + +### 5.1 Node Accent Colors + +| Node Type | Light Mode | Dark Mode | CSS Variable | +|--------------|---------------|---------------|--------------------------| +| Trigger | `#2da44e` | `#3fb950` | `--node-trigger` | +| Permissions | `#d4a72c` | `#d29922` | `--node-permissions` | +| Engine | `#0969da` | `#58a6ff` | `--node-engine` | +| Tools | `#8250df` | `#bc8cff` | `--node-tools` | +| Instructions | `#57606a` | `#8b949e` | `--node-instructions` | +| Safe Outputs | `#1a7f37` | `#56d364` | `--node-safe-outputs` | +| Network | `#cf222e` | `#f85149` | `--node-network` | + +### 5.2 Status Colors + +| Status | Light Mode | Dark Mode | Usage | +|----------|---------------|---------------|--------------------------| +| Success | `#2da44e` | `#3fb950` | Compile success, ready | +| Warning | `#d4a72c` | `#d29922` | Compile warnings | +| Error | `#cf222e` | `#f85149` | Compile errors | +| Info | `#0969da` | `#58a6ff` | Loading, compiling | + +### 5.3 Light Theme Palette + +``` +Background (canvas): #ffffff +Background (subtle): #f6f8fa +Background (muted): #eaeef2 +Foreground (default): #1f2328 +Foreground (muted): #57606a +Foreground (subtle): #6e7781 +Border (default): #d0d7de +Border (muted): #d8dee4 +Accent (fg): #0969da +Accent (bg): #ddf4ff +``` + +### 5.4 Dark Theme Palette + +``` +Background (canvas): #0d1117 +Background (subtle): #161b22 +Background (muted): #21262d +Foreground (default): #e6edf3 +Foreground (muted): #8b949e +Foreground (subtle): #6e7681 +Border (default): #30363d +Border (muted): #21262d +Accent (fg): #58a6ff +Accent (bg): #388bfd26 +``` + +### 5.5 Canvas Grid + +- **Light mode**: `radial-gradient(circle, rgba(0,0,0,0.06) 1px, transparent 1px)` every 20px +- **Dark mode**: `radial-gradient(circle, rgba(255,255,255,0.04) 1px, transparent 1px)` every 20px + +### 5.6 Primer Design Tokens + +The editor uses GitHub Primer design tokens wherever possible: + +- Colors: `--bgColor-*`, `--fgColor-*`, `--borderColor-*` +- Typography: `--fontStack-monospace`, `--fontStack-system` +- Spacing: 4px base unit (4, 8, 12, 16, 20, 24, 32, 40, 48) +- Border radius: 6px (default), 8px (cards), 12px (nodes), 100px (pills) +- Shadows: Primer shadow utilities (`shadow-sm`, `shadow-md`) + +--- + +## 6. Interactions & Animations + +### 6.1 Node Interactions + +**Hover**: +- Border color transitions to `--borderColor-emphasis` (darker) +- Subtle shadow elevation: `0 2px 8px rgba(0,0,0,0.12)` +- Transition: `border-color 150ms ease, box-shadow 150ms ease` + +**Selected**: +- Border: 2px solid `--fgColor-accent` (blue) +- Shadow: `0 0 0 3px var(--bgColor-accent-muted)` (blue glow ring) +- Properties panel populates with node data + +**Dragging**: +- Opacity: 0.85 +- Shadow: `0 8px 24px rgba(0,0,0,0.15)` (elevated) +- Cursor: `grabbing` +- Other nodes dim slightly (opacity 0.7) + +**Delete** (when node selected): +- Press `Delete` or `Backspace` key +- Node fades out with scale-down: `opacity 0 + scale(0.95)` over 150ms +- Connected edges animate retraction + +### 6.2 Panel Transitions + +**Sidebar collapse**: +- Width animates from 240px to 48px over 200ms, `ease-out` +- Node labels fade out during first 100ms +- Icons remain visible in collapsed state + +**Properties panel collapse**: +- Width animates from 360px to 0px over 200ms, `ease-out` +- Content fades out during first 100ms +- A 32px wide "expand" tab slides in from the right edge + +**YAML Preview drawer**: +- Slides up from bottom of canvas area +- Height: 40% of canvas height +- Transition: `transform 250ms cubic-bezier(0.4, 0, 0.2, 1)` +- Drag handle at top for resizing + +### 6.3 Compilation Status Animation + +**Compile button press**: +1. Button text changes to "Compiling..." with a spinner icon (replaces play icon) +2. Status badge pulses with blue dot animation +3. On success: + - Status badge transitions to green "Ready" + - Brief green flash on the YAML preview header (200ms) + - If YAML preview is open, content updates with a subtle fade-in +4. On error: + - Status badge transitions to red "Error" + - Error banner slides down from below the header (200ms ease-out) + - Affected node(s) get a red border pulse + +### 6.4 Drag-and-Drop from Sidebar + +1. **Grab**: User mousedowns on a sidebar node card +2. **Drag**: A ghost card follows the cursor at 0.7 opacity, with a subtle shadow +3. **Over canvas**: Canvas shows a translucent placement preview (dashed border outline where the node will land) +4. **Drop**: Node materializes with a scale-up animation (`scale(0.9)` to `scale(1)` over 150ms) +5. **Auto-connect**: If dropped near an existing node's output handle, an edge auto-connects with a drawing animation + +### 6.5 Connection Drawing + +1. **Start**: User drags from a node's output handle (small circle on the edge) +2. **Drawing**: A bezier curve follows the cursor from the source handle +3. **Snap**: When cursor approaches another node's input handle, the line snaps to it with a subtle glow on the target handle +4. **Complete**: Line solidifies and the handle highlights briefly +5. **Cancel**: Releasing over empty canvas causes the line to retract back to source (150ms) + +### 6.6 Node Addition Animation + +When a new node is added (via sidebar drag or template): +- Node appears with `opacity: 0, scale: 0.9` and animates to `opacity: 1, scale: 1` over 200ms +- If auto-connecting, the edge draws in after the node appears (100ms delay) + +--- + +## 7. Component Specifications + +### 7.1 Selectable Card + +Used for: Event type selection, Engine selection + +``` ++---------------------------+ +| [Icon] Label | +| Description text | ++---------------------------+ +``` + +- **Dimensions**: Flex, min-width 140px, height 56px +- **Padding**: 12px +- **Border**: 1px solid `--borderColor-default` +- **Border-radius**: 8px +- **Background**: `--bgColor-default` +- **Icon**: 24x24px, left-aligned, `color: --fgColor-muted` +- **Label**: `font-size: 13px`, `font-weight: 600`, `color: --fgColor-default` +- **Description**: `font-size: 11px`, `color: --fgColor-muted` +- **Hover**: `background: --bgColor-subtle`, `border-color: --borderColor-emphasis` +- **Selected**: `border: 2px solid --fgColor-accent`, `background: --bgColor-accent-muted` +- **Focus**: `box-shadow: 0 0 0 3px --bgColor-accent-muted` + +### 7.2 Toggle Switch + +Used for: Tool toggles, boolean settings + +- **Track**: 34px x 20px, border-radius 10px +- **Knob**: 16px x 16px circle, white, `box-shadow: 0 1px 3px rgba(0,0,0,0.2)` +- **Off state**: Track background `--bgColor-neutral-muted` +- **On state**: Track background `--fgColor-accent` +- **Transition**: Knob `transform: translateX(14px)` over 180ms ease + +### 7.3 Segmented Control + +Used for: Permission levels (None/Read/Write) + +- **Container**: Inline flex, border-radius 6px, border 1px solid `--borderColor-default` +- **Segment**: Padding `4px 12px`, `font-size: 12px`, `font-weight: 500` +- **Selected segment**: Background fill with accent color, white text +- **Segment variants**: + - None: gray background when selected + - Read: green tint background when selected + - Write: amber tint background when selected +- **Transition**: Background color 150ms ease + +### 7.4 Tag Input + +Used for: Domains, labels, args, allowed functions + +``` ++------------------------------------------+ +| [github.com x] [pypi.org x] [type...] | ++------------------------------------------+ +``` + +- **Container**: Flex-wrap, min-height 36px, border 1px solid `--borderColor-default`, border-radius 6px, padding 4px +- **Tag**: Inline pill, `font-size: 12px`, background `--bgColor-muted`, border-radius 12px, padding `2px 8px` +- **Tag remove**: Small `x` button (14px), appears on hover +- **Input**: Borderless, `font-size: 13px`, grows to fill remaining width +- **Enter or comma**: Creates a new tag from current input +- **Autocomplete**: Dropdown appears below with matching suggestions when applicable + +### 7.5 Number Input with Slider + +Used for: Max turns, timeouts + +``` +[----o------------------] [10] +``` + +- **Slider track**: Full width minus 60px, height 4px, border-radius 2px, `--bgColor-muted` +- **Slider fill**: Height 4px, `--fgColor-accent` +- **Thumb**: 16px circle, white, `box-shadow: 0 1px 3px rgba(0,0,0,0.2)`, accent border +- **Number input**: 56px wide, border 1px solid `--borderColor-default`, border-radius 6px, `font-size: 13px`, text-align center + +### 7.6 Key-Value Editor + +Used for: Environment variables, secrets + +``` ++------------------------------------------+ +| Key | Value [x] | ++------------------------------------------+ +| API_KEY | ${{ secrets.KEY }} [x] | +| NODE_ENV | production [x] | ++------------------------------------------+ +| [+ Add variable] | ++------------------------------------------+ +``` + +- **Row height**: 36px +- **Key input**: 40% width, `font-family: monospace`, `font-size: 13px` +- **Value input**: 50% width, same styling +- **Delete button**: 10% width, `btn-octicon` with trash icon +- **Add button**: Full width, dashed border, `color: --fgColor-muted` + +### 7.7 Tooltip + +Used for: `[?]` help icons on every field + +- **Trigger**: Small `?` circle icon (16px), `color: --fgColor-muted`, `cursor: help` +- **Tooltip body**: Max-width 280px, `font-size: 12px`, `line-height: 1.5` +- **Background**: `--bgColor-emphasis` (dark in light mode, light in dark mode) +- **Text color**: `--fgColor-onEmphasis` +- **Border-radius**: 6px +- **Padding**: 8px 12px +- **Arrow**: 6px CSS triangle pointing toward the trigger +- **Show delay**: 200ms hover +- **Hide delay**: 100ms mouse leave + +### 7.8 Dropdown Select + +Used for: Model selection, log level, mode + +- **Trigger**: Button-style, `font-size: 13px`, border 1px solid `--borderColor-default`, border-radius 6px, padding `6px 12px` +- **Chevron**: Small down arrow on right +- **Dropdown**: Max-height 200px, overflow-y auto, `box-shadow: 0 4px 12px rgba(0,0,0,0.15)`, border-radius 8px +- **Item**: Padding `8px 12px`, `font-size: 13px` +- **Item hover**: `background: --bgColor-accent-muted` +- **Selected item**: Checkmark icon on left + +### 7.9 YAML Preview Panel + +``` ++------------------------------------------------------------------+ +| ===== drag handle ===== [Copy] [X] | ++------------------------------------------------------------------+ +| 1 | # Auto-generated by gh-aw visual editor | +| 2 | name: my-workflow | +| 3 | on: | +| 4 | issue_comment: | +| 5 | types: [created] | +| 6 | | ++------------------------------------------------------------------+ +``` + +- **Height**: 40% of canvas, resizable via drag handle +- **Background**: `--bgColor-subtle` +- **Font**: Monospace, `font-size: 13px`, `line-height: 1.6` +- **Line numbers**: 48px gutter, right-aligned, `color: --fgColor-muted` +- **Syntax highlighting**: YAML keywords in accent color, strings in green, comments in muted +- **Read-only**: No editing allowed (output only) +- **Copy button**: Top-right corner, copies full YAML to clipboard + +--- + +## 8. Onboarding Design + +### 8.1 Welcome Modal + +Shown on first visit (tracked in localStorage). + +``` ++----------------------------------------------------------+ +| | +| [Illustration: connected workflow nodes] | +| | +| Welcome to the Visual Workflow Editor | +| | +| Build AI-powered GitHub workflows visually. | +| No code required. | +| | +| Start from a template: | +| | +| +-------------------+ +-------------------+ | +| | [Chat] PR Review | | [Bug] Issue Triage| | +| | AI reviews pull | | Auto-label and | | +| | requests for... | | route issues... | | +| +-------------------+ +-------------------+ | +| +-------------------+ +-------------------+ | +| | [Shield] Security | | [Zap] Custom Bot | | +| | Scan code for | | Start with a | | +| | vulnerabilities...| | blank canvas... | | +| +-------------------+ +-------------------+ | +| | +| [Start from scratch] [Don't show again] | +| | ++----------------------------------------------------------+ +``` + +- **Overlay**: Semi-transparent backdrop `rgba(0,0,0,0.5)` +- **Modal**: 560px wide, border-radius 12px, `--bgColor-default` +- **Illustration**: Simple SVG showing connected nodes (120px tall) +- **Title**: `font-size: 24px`, `font-weight: 600` +- **Subtitle**: `font-size: 14px`, `color: --fgColor-muted` +- **Template cards**: 2x2 grid, same selectable card pattern (240px wide, 72px tall) +- **"Start from scratch"**: Primer `btn btn-primary` +- **"Don't show again"**: Text link, `font-size: 12px`, `color: --fgColor-muted` + +### 8.2 Template Definitions + +| Template | Trigger | Engine | Tools | Safe Outputs | Description | +|----------|---------|--------|-------|--------------|-------------| +| PR Review | `pull_request: opened, synchronize` | copilot | github, bash | add-comment, submit-review | Reviews PRs for quality | +| Issue Triage | `issues: opened, labeled` | copilot | github | add-labels, add-comment, assign-to-user | Auto-labels and routes issues | +| Security Scan | `push` + `schedule: daily` | claude | github, bash | create-issue, add-labels, create-code-scanning-alert | Scans for vulnerabilities | +| Custom Bot | `issue_comment` (slash_command) | copilot | github, bash | add-comment | Blank slash-command bot | + +Each template generates a complete node graph pre-populated with: +- Correct trigger configuration +- Appropriate engine selection +- Recommended tools enabled +- Safe outputs selected +- Network set to "defaults" +- Permissions auto-detected from tools/outputs +- Starter instructions in the Instructions node + +### 8.3 Guided Tour + +A 5-step tooltip tour that highlights key areas. Uses a spotlight effect (dims everything except the highlighted area). + +**Step 1: Sidebar** +``` ++-------------------+ +| Drag nodes from | +| here onto the | +| canvas to build | +| your workflow. | +| | +| [1/5] [Next >] | ++-------------------+ + \ + v (points to sidebar) +``` + +**Step 2: Canvas** +``` ++-------------------+ +| This is your | +| workflow canvas. | +| Nodes represent | +| each part of | +| your workflow. | +| | +| [2/5] [Next >] | ++-------------------+ + \ + v (points to canvas center) +``` + +**Step 3: Node connections** +``` ++-------------------+ +| Drag from one | +| node's handle to | +| another to | +| connect them. | +| | +| [3/5] [Next >] | ++-------------------+ + \ + v (points to an edge) +``` + +**Step 4: Properties panel** +``` ++-------------------+ +| Click any node | +| to configure it | +| here. Each node | +| type has its own | +| settings. | +| | +| [4/5] [Next >] | ++-------------------+ + \ + v (points to properties panel) +``` + +**Step 5: Compile** +``` ++-------------------+ +| When you're done, | +| hit Compile to | +| generate the | +| GitHub Actions | +| YAML file. | +| | +| [5/5] [Done!] | ++-------------------+ + \ + v (points to compile button) +``` + +**Tour tooltip styling**: +- Background: `--bgColor-emphasis` (dark) +- Text: `--fgColor-onEmphasis` (white) +- Border-radius: 8px +- Padding: 16px +- Width: 240px +- Arrow: CSS triangle pointing to highlighted element +- Progress: "N/5" counter +- "Next" button: Primer `btn btn-sm btn-primary` +- "Skip tour" link: `font-size: 12px`, underlined, `color: --fgColor-onEmphasis` +- Spotlight: Highlighted element gets `z-index: 1000` and the rest of the page is dimmed with a dark overlay + +### 8.4 Empty State Designs + +**Empty Canvas** (no nodes placed): +``` ++--------------------------------------------------+ +| | +| [Illustration: dashed rectangle | +| with arrow from sidebar] | +| | +| Drag a node from the sidebar | +| to start building your workflow | +| | +| or | +| | +| [Choose a template] | +| | ++--------------------------------------------------+ +``` + +**Empty Properties Panel** (no node selected): +``` ++--------------------------------------------+ +| | +| [Illustration: cursor clicking node] | +| | +| Select a node on the canvas | +| to edit its properties | +| | ++--------------------------------------------+ +``` + +**Empty state styling**: +- Illustration: Simple SVG line art, 80px tall, `color: --fgColor-muted` at 40% opacity +- Text: `font-size: 14px`, `color: --fgColor-muted`, text-align center +- CTA button: Primer `btn btn-sm btn-outline` + +--- + +## Appendix A: Keyboard Shortcuts + +| Shortcut | Action | +|----------|--------| +| `Ctrl/Cmd + Enter` | Compile workflow | +| `Ctrl/Cmd + S` | Save / Download .md | +| `Ctrl/Cmd + Z` | Undo | +| `Ctrl/Cmd + Shift + Z` | Redo | +| `Delete` / `Backspace` | Delete selected node | +| `Ctrl/Cmd + A` | Select all nodes | +| `Ctrl/Cmd + D` | Duplicate selected node | +| `Space + Drag` | Pan canvas | +| `Ctrl/Cmd + +/-` | Zoom in/out | +| `Ctrl/Cmd + 0` | Reset zoom to 100% | +| `Ctrl/Cmd + 1` | Fit all nodes to view | +| `Escape` | Deselect all / Close panel | +| `?` | Open keyboard shortcut help | + +## Appendix B: Responsive Behavior + +**Breakpoints**: +- Desktop: >= 1024px - full 3-panel layout +- Tablet: 768px - 1023px - sidebar collapses to icon rail, properties panel becomes overlay +- Mobile: < 768px - single panel view with bottom sheet navigation + +**Tablet mode**: +- Sidebar: 48px icon rail (always collapsed) +- Canvas: fills remaining space +- Properties: Slides in as overlay from right (320px), with backdrop dim + +**Mobile mode**: +- Canvas takes full screen +- Bottom tab bar replaces sidebar: [Palette] [Canvas] [Properties] [YAML] +- Each tab shows its panel as full-screen view +- Nodes are touch-draggable with long-press to initiate + +## Appendix C: Typography + +| Element | Font | Size | Weight | Color | +|---------|------|------|--------|-------| +| Header title | System | 14px | 600 | `--fgColor-default` | +| Node title | System | 13px | 600 | `--fgColor-default` | +| Node subtitle | System | 11px | 400 | `--fgColor-muted` | +| Node content | System | 12px | 400 | `--fgColor-default` | +| Panel section header | System | 12px | 600 | `--fgColor-muted` | +| Panel field label | System | 13px | 500 | `--fgColor-default` | +| Panel help text | System | 12px | 400 | `--fgColor-muted` | +| Code / YAML | Monospace | 13px | 400 | `--fgColor-default` | +| Badge / Label | System | 12px | 500 | varies | +| Tooltip | System | 12px | 400 | `--fgColor-onEmphasis` | + +Font stacks: +- **System**: `-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif` (Primer default) +- **Monospace**: `'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace` (Primer monospace) diff --git a/docs/editor-app/SECURITY-REVIEW.md b/docs/editor-app/SECURITY-REVIEW.md new file mode 100644 index 00000000000..b3fd62e52ce --- /dev/null +++ b/docs/editor-app/SECURITY-REVIEW.md @@ -0,0 +1,117 @@ +# Security Review — Visual Workflow Editor + +**Reviewer**: Security Review Agent +**Date**: 2026-02-19 +**Scope**: All source files in `docs/editor-app/src/` +**Overall Risk**: LOW + +--- + +## Summary + +The visual editor is a client-side-only React SPA with no backend, no authentication, and no network requests. All compilation happens via WASM in a Web Worker. The attack surface is minimal. No critical or high-severity issues found. + +--- + +## 1. XSS Risk Assessment + +**Status**: PASS + +- **No `dangerouslySetInnerHTML`** usage found anywhere in the codebase. +- All user inputs (workflow name, instructions, domain names, trigger config) are rendered as React text content, which is auto-escaped by React's JSX rendering. +- The Instructions node preview (`InstructionsNode.tsx:31`) renders text with `whiteSpace: pre-wrap` — safe, no HTML interpretation. +- YAML/Markdown preview panels use `prism-react-renderer`, which tokenizes and renders via `` elements — no raw HTML injection path. +- Template names and descriptions are hardcoded constants (`templates.ts`), not user-supplied. + +## 2. Input Sanitization + +**Status**: PASS with minor notes + +- **Domain inputs** (`NetworkPanel.tsx`): Only validates for empty/whitespace and duplicates. Does not validate domain format (e.g., rejects nothing like `">`). However, this is a **non-issue** because: + - The domain string is only used to generate markdown text output + - It's properly YAML-quoted via `yamlString()` in `markdownGenerator.ts:301-313` + - No network calls are ever made to these domains from the editor +- **YAML generation** (`markdownGenerator.ts`): The `yamlString()` function properly escapes special YAML characters (`:#{}[]|>!%@` etc.) and wraps in double quotes when needed. Backslashes and double quotes are escaped. This prevents YAML injection. +- **Workflow name**: Used as a filename in export (`Header.tsx:79`). The name is sanitized via Blob download (browser handles filename safety). No path traversal risk since it's a client-side download. + +## 3. WASM / Compiler Bridge Security + +**Status**: PASS + +- **Dynamic import path** (`compiler.ts:20-21`): Uses `/* @vite-ignore */` dynamic import with `wasmBasePath` parameter. This path is hardcoded as `'/gh-aw/wasm/'` in `App.tsx:33` — **not user-controllable**. +- **Worker communication**: The `compile()` function sends markdown strings to the Web Worker via `postMessage`. The response is typed via `CompileResult` interface. No arbitrary code execution path. +- **WASM isolation**: The Go WASM binary runs in a Web Worker, isolated from the main thread's DOM. Even if the WASM binary had a vulnerability, it cannot access the page's DOM or user data directly. + +## 4. LocalStorage Usage + +**Status**: PASS + +Two Zustand stores persist to `localStorage`: + +| Store Key | Data Stored | Sensitive? | +|-----------|-------------|------------| +| `workflow-editor-state` | Workflow config: name, trigger, engine, tools, instructions, network domains, permissions, safe outputs | No | +| `workflow-editor-ui` | UI preferences: theme, sidebar state, onboarding flag, auto-compile toggle, disclosure level | No | + +- **No secrets, tokens, API keys, or credentials** are stored. +- The `partialize` option in `workflowStore.ts:219-234` correctly excludes transient UI state (`selectedNodeId`, `compiledYaml`, `error`, etc.) from persistence. +- User-authored instruction text is stored, which could theoretically contain sensitive content, but this is user-controlled and expected behavior for a local editor. + +**Note**: No handling for `localStorage` being unavailable (e.g., private browsing mode). Zustand's `persist` middleware silently falls back, so this won't crash, but state won't persist. + +## 5. Dependency Audit + +**Status**: PASS + +``` +$ npm audit +found 0 vulnerabilities +``` + +All dependencies are well-known, actively maintained packages: +- React 19, Zustand 5, Vite 7 — current major versions +- `@xyflow/react` v12, `@primer/react` v38, `@radix-ui/*` — established UI libraries +- `prism-react-renderer` v2 — syntax highlighting (no eval/code execution) +- `framer-motion` v12, `sonner` v2, `lucide-react` — UI utilities +- `@dagrejs/dagre` v2 — graph layout algorithm (pure computation) + +No transitive vulnerabilities detected. + +## 6. CSP Compatibility + +**Status**: PASS with one note + +- **No inline scripts** in `index.html`. The entry point uses ` + + diff --git a/docs/editor-app/package-lock.json b/docs/editor-app/package-lock.json new file mode 100644 index 00000000000..33c3a0a2327 --- /dev/null +++ b/docs/editor-app/package-lock.json @@ -0,0 +1,2685 @@ +{ + "name": "editor-app", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "editor-app", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@dagrejs/dagre": "^2.0.4", + "@radix-ui/react-dialog": "^1.1.15", + "@xyflow/react": "^12.10.0", + "lucide-react": "^0.574.0", + "prism-react-renderer": "^2.4.1", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "sonner": "^2.0.7", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.4", + "typescript": "^5.9.3", + "vite": "^7.3.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dagrejs/dagre": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-2.0.4.tgz", + "integrity": "sha512-J6vCWTNpicHF4zFlZG1cS5DkGzMr9941gddYkakjrg3ZNev4bbqEgLHFTWiFrcJm7UCRu7olO3K6IRDd9gSGhA==", + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "3.0.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-3.0.4.tgz", + "integrity": "sha512-HxZ7fCvAwTLCWCO0WjDkzAFQze8LdC6iOpKbetDKHIuDfIgMlIzYzqZ4nxwLlclQX+3ZVeZ1K2OuaOE2WWcyOg==", + "license": "MIT" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/prismjs": { + "version": "1.26.6", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.6.tgz", + "integrity": "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.4.tgz", + "integrity": "sha512-VIcFLdRi/VYRU8OL/puL7QXMYafHmqOnwTZY50U1JPlCNj30PxCMx65c494b1K9be9hX83KVt0+gTEwTWLqToA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.10.0", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.0.tgz", + "integrity": "sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.74", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.74", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.74.tgz", + "integrity": "sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001770", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001770.tgz", + "integrity": "sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.574.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.574.0.tgz", + "integrity": "sha512-dJ8xb5juiZVIbdSn3HTyHsjjIwUwZ4FNwV0RtYDScOyySOeie1oXZTymST6YPJ4Qwt3Po8g4quhYl4OxtACiuQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prism-react-renderer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", + "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", + "license": "MIT", + "dependencies": { + "@types/prismjs": "^1.26.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/sonner": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", + "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", + "license": "MIT", + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/docs/editor-app/package.json b/docs/editor-app/package.json new file mode 100644 index 00000000000..a1a20a73140 --- /dev/null +++ b/docs/editor-app/package.json @@ -0,0 +1,36 @@ +{ + "name": "editor-app", + "version": "1.0.0", + "main": "index.js", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "deploy": "bash scripts/deploy.sh" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@dagrejs/dagre": "^2.0.4", + "@radix-ui/react-dialog": "^1.1.15", + "@xyflow/react": "^12.10.0", + "lucide-react": "^0.574.0", + "prism-react-renderer": "^2.4.1", + "react": "^19.2.4", + "react-dom": "^19.2.4", + "sonner": "^2.0.7", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@playwright/test": "^1.58.2", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.1.4", + "typescript": "^5.9.3", + "vite": "^7.3.1" + } +} diff --git a/docs/editor-app/playwright.config.ts b/docs/editor-app/playwright.config.ts new file mode 100644 index 00000000000..8ce0f656ca4 --- /dev/null +++ b/docs/editor-app/playwright.config.ts @@ -0,0 +1,26 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './e2e', + timeout: 30_000, + expect: { timeout: 5_000 }, + fullyParallel: true, + retries: 0, + reporter: 'list', + use: { + baseURL: 'http://localhost:5173/gh-aw/editor/', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + ], + webServer: { + command: 'npm run dev', + url: 'http://localhost:5173/gh-aw/editor/', + reuseExistingServer: !process.env.CI, + timeout: 15_000, + }, +}); diff --git a/docs/editor-app/public/wasm b/docs/editor-app/public/wasm new file mode 120000 index 00000000000..9fbe466068a --- /dev/null +++ b/docs/editor-app/public/wasm @@ -0,0 +1 @@ +/home/mossaka/developer/gh-aw-repos/gh-aw-visual-editor/docs/public/wasm \ No newline at end of file diff --git a/docs/editor-app/scripts/deploy.sh b/docs/editor-app/scripts/deploy.sh new file mode 100755 index 00000000000..cf6d7354eab --- /dev/null +++ b/docs/editor-app/scripts/deploy.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +# Navigate to editor-app root +cd "$(dirname "$0")/.." + +# Ensure WASM loader files from docs/public/wasm are available +if [ ! -f public/wasm/gh-aw.wasm ]; then + echo "Copying WASM files from docs/public/wasm..." + mkdir -p public/wasm + # Copy any files from the docs-level public/wasm that aren't already present + for f in ../../public/wasm/*; do + if [ -f "$f" ]; then + cp -n "$f" public/wasm/ 2>/dev/null || true + fi + done +fi + +echo "Building for GitHub Pages..." +npx vite build --mode ghpages + +echo "Deploying to gh-pages branch..." +cd dist +git init +git checkout -b gh-pages +git add -A +git commit -m "Deploy visual editor - $(date -u +%Y-%m-%dT%H:%M:%SZ)" + +# Deploy to the external visualizer repo +# NOTE: For cross-repo push, you must have push access to this repository. +# If using HTTPS, set GH_TOKEN or GITHUB_TOKEN env var, or use SSH. +REPO_URL="${DEPLOY_REPO_URL:-https://github.com/Mossaka/gh-aw-editor-visualizer.git}" + +if [ -n "$GH_TOKEN" ]; then + REPO_URL="https://x-access-token:${GH_TOKEN}@github.com/Mossaka/gh-aw-editor-visualizer.git" +elif [ -n "$GITHUB_TOKEN" ]; then + REPO_URL="https://x-access-token:${GITHUB_TOKEN}@github.com/Mossaka/gh-aw-editor-visualizer.git" +fi + +git remote add origin "$REPO_URL" +git push origin gh-pages --force + +echo "" +echo "Deployed successfully!" +echo "View at: https://mossaka.github.io/gh-aw-editor-visualizer/" diff --git a/docs/editor-app/src/App.tsx b/docs/editor-app/src/App.tsx new file mode 100644 index 00000000000..0df1e0389be --- /dev/null +++ b/docs/editor-app/src/App.tsx @@ -0,0 +1,236 @@ +import { useEffect, useRef, lazy, Suspense } from 'react'; +import { Header } from './components/Header/Header'; +import { ErrorPanel } from './components/ErrorPanel/ErrorPanel'; +import { MobileTabBar } from './components/shared/MobileTabBar'; +import { useUIStore } from './stores/uiStore'; +import { useWorkflowStore } from './stores/workflowStore'; +import { useAutoCompile } from './hooks/useAutoCompile'; +import { useFieldHighlight } from './hooks/useFieldHighlight'; +import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts'; +import { useHistory } from './hooks/useHistory'; +import { initCompiler } from './utils/compiler'; +import { parseHash, decodeState } from './utils/deepLink'; +import { toast } from './utils/lazyToast'; +import './styles/globals.css'; +import './styles/nodes.css'; +import './styles/panels.css'; + +// Lazy-load heavy components that are not needed for initial render +const Sidebar = lazy(() => import('./components/Sidebar/Sidebar').then(m => ({ default: m.Sidebar }))); +const PropertiesPanel = lazy(() => import('./components/Panels/PropertiesPanel').then(m => ({ default: m.PropertiesPanel }))); +const EditorView = lazy(() => import('./components/EditorView/EditorView').then(m => ({ default: m.EditorView }))); +const YamlPreview = lazy(() => import('./components/YamlPreview/YamlPreview').then(m => ({ default: m.YamlPreview }))); +const WelcomeModal = lazy(() => import('./components/Onboarding/WelcomeModal').then(m => ({ default: m.WelcomeModal }))); +const LazyToaster = lazy(() => import('sonner').then(m => ({ default: m.Toaster }))); +const ShortcutsHelp = lazy(() => import('./components/shared/ShortcutsHelp').then(m => ({ default: m.ShortcutsHelp }))); +const GuidedTour = lazy(() => import('./components/Onboarding/GuidedTour').then(m => ({ default: m.GuidedTour }))); +const DeployDialog = lazy(() => import('./components/Deploy/DeployDialog').then(m => ({ default: m.DeployDialog }))); + +// Canvas is large (~220KB with ReactFlow) -- lazy load with a loading skeleton +const CanvasWithProvider = lazy(() => import('./components/Canvas/CanvasWithProvider')); + +/** Lightweight placeholder shown while the canvas (ReactFlow ~220KB) loads */ +function CanvasPlaceholder() { + return ( +
+ Loading canvas... +
+ ); +} + +export default function App() { + const sidebarOpen = useUIStore((s) => s.sidebarOpen); + const propertiesPanelOpen = useUIStore((s) => s.propertiesPanelOpen); + const yamlPreviewOpen = useUIStore((s) => s.yamlPreviewOpen); + const hasSeenOnboarding = useUIStore((s) => s.hasSeenOnboarding); + const guidedTourStep = useUIStore((s) => s.guidedTourStep); + const theme = useUIStore((s) => s.theme); + + const selectedNodeId = useWorkflowStore((s) => s.selectedNodeId); + const viewMode = useWorkflowStore((s) => s.viewMode); + const isCompiling = useWorkflowStore((s) => s.isCompiling); + const error = useWorkflowStore((s) => s.error); + const warnings = useWorkflowStore((s) => s.warnings); + const setIsReady = useWorkflowStore((s) => s.setIsReady); + const setError = useWorkflowStore((s) => s.setError); + + // Aria-live status announcements + const statusRef = useRef(''); + const prevCompilingRef = useRef(false); + + useEffect(() => { + if (isCompiling && !prevCompilingRef.current) { + statusRef.current = 'Compiling workflow...'; + } else if (!isCompiling && prevCompilingRef.current) { + if (error) { + statusRef.current = `Compilation failed: ${error.message}`; + } else if (warnings.length > 0) { + statusRef.current = `Compilation completed with ${warnings.length} warning${warnings.length !== 1 ? 's' : ''}`; + } else { + statusRef.current = 'Compilation succeeded'; + } + } + prevCompilingRef.current = isCompiling; + }, [isCompiling, error, warnings]); + + // Initialize WASM compiler in background -- does NOT block UI render + useEffect(() => { + const wasmPath = `${import.meta.env.BASE_URL}wasm/`; + initCompiler(wasmPath) + .then(() => setIsReady(true)) + .catch((err) => { + setError({ message: `Compiler initialization failed: ${err instanceof Error ? err.message : String(err)}`, severity: 'error' }); + }); + }, [setIsReady, setError]); + + useAutoCompile(); + useFieldHighlight(); + useKeyboardShortcuts(); + useHistory(); + + // Load workflow from URL hash on mount + useEffect(() => { + const { mode, value } = parseHash(); + if (mode === 'b64' && value) { + const state = decodeState(value); + if (state) { + useWorkflowStore.getState().loadState(state); + toast.success('Workflow loaded from share link'); + } + } else if (mode === 'url' && value) { + fetch(value) + .then((r) => r.text()) + .then((md) => { + useWorkflowStore.getState().setCompiledMarkdown(md); + useWorkflowStore.getState().setViewMode('markdown'); + toast.success('Workflow loaded from URL'); + }) + .catch(() => toast.error('Failed to load workflow from URL')); + } + }, []); + + // Dark/light mode: respect uiStore theme setting + useEffect(() => { + if (theme === 'light' || theme === 'dark') { + document.documentElement.setAttribute('data-color-mode', theme); + return; + } + // 'auto' — follow system preference + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + const apply = () => { + document.documentElement.setAttribute('data-color-mode', mq.matches ? 'dark' : 'light'); + }; + apply(); + mq.addEventListener('change', apply); + return () => mq.removeEventListener('change', apply); + }, [theme]); + + const isVisualMode = viewMode === 'visual'; + const showProperties = isVisualMode && propertiesPanelOpen && selectedNodeId !== null; + + const layoutClasses = [ + 'app-layout', + !isVisualMode ? 'editor-mode' : '', + sidebarOpen && isVisualMode ? '' : 'sidebar-collapsed', + showProperties ? 'properties-open' : '', + ].filter(Boolean).join(' '); + + return ( + <> + {/* Skip navigation links */} + Skip to canvas + Skip to properties + Skip to sidebar + + {/* Aria-live region for status announcements */} +
+ {statusRef.current} +
+ +
+
+
+
+ {isVisualMode ? ( + <> + {/* Overlay backdrop for tablet/mobile sidebar and properties */} + {(sidebarOpen || showProperties) && ( +
{ + if (sidebarOpen) useUIStore.getState().toggleSidebar(); + if (showProperties) useUIStore.getState().togglePropertiesPanel(); + }} + /> + )} + +
+ }> + + +
+ {showProperties && ( + + )} + + ) : ( +
+ + + +
+ )} + {/* In editor mode, errors/warnings are shown inline in the YAML pane */} + {isVisualMode && ( +
+ +
+ )} + {isVisualMode && } +
+ {isVisualMode && yamlPreviewOpen && ( + + + + )} + {!hasSeenOnboarding && ( + + + + )} + {guidedTourStep !== null && ( + + + + )} + + + + + + + + + + + ); +} diff --git a/docs/editor-app/src/SECURITY-REVIEW-V2.md b/docs/editor-app/src/SECURITY-REVIEW-V2.md new file mode 100644 index 00000000000..69d2a2dd02e --- /dev/null +++ b/docs/editor-app/src/SECURITY-REVIEW-V2.md @@ -0,0 +1,142 @@ +# Security Review: Deploy to GitHub (v2) + +**Date:** 2026-02-25 +**Reviewer:** security-tester (automated) +**Scope:** `githubApi.ts`, `deployStore.ts`, `deploy.ts`, `components/Deploy/*.tsx` + +--- + +## Summary + +The deploy v2 implementation is well-structured with good baseline security practices: +React JSX auto-escaping prevents XSS, `encodeURIComponent` is applied to owner/repo in URLs, +`${{ }}` expression injection is escaped in PR body content, and token persistence is opt-in. + +Two medium-severity issues require code fixes. No critical or high-severity findings. + +--- + +## Findings + +### M-1: Workflow name used in file path without sanitization [MEDIUM] + +**File:** `src/utils/deploy.ts:33-34` + +```ts +const mdPath = `.github/workflows/${workflowName}.md`; +const ymlPath = `.github/workflows/${workflowName}.lock.yml`; +``` + +`workflowName` originates from user input (`workflowStore.name`) and is used to construct the +`path` parameter for the GitHub Contents API. If the name contains path traversal sequences +(e.g., `../../../README`), files could be written outside `.github/workflows/`. + +While the GitHub API may normalize paths server-side, this is a defense-in-depth gap. +The name should be sanitized to allow only safe filename characters. + +**Fix:** Sanitize `workflowName` before constructing paths. Strip path separators, `..`, +and restrict to `[a-zA-Z0-9._-]`. + +--- + +### M-2: PR URL rendered as href without origin validation [MEDIUM] + +**File:** `src/components/Deploy/SuccessStep.tsx:15` + +```tsx + +``` + +`prUrl` comes from the GitHub API response (`html_url`). If the API response were tampered +with (MITM, compromised proxy, or future API changes), a malicious `javascript:` or data URI +could be rendered as a clickable link. + +**Fix:** Validate that `prUrl` starts with `https://github.com/` before rendering as a link. + +--- + +### L-1: File path parameter not URL-encoded [LOW] + +**File:** `src/utils/githubApi.ts:104` + +```ts +`${API_BASE}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/contents/${path}` +``` + +The `path` variable is not URL-encoded. Characters like `#`, `?`, or `%` in the workflow name +would break the URL. This is partially mitigated by M-1's fix (restricting characters in the +name), but the path segments should also be encoded for correctness. + +**Fix:** Encode individual path segments of `path` while preserving `/` separators. + +--- + +### L-2: Token stored in plaintext localStorage [LOW] + +**File:** `src/stores/deployStore.ts:148-161` + +When "Remember this token" is checked, the PAT is persisted to `localStorage` under key +`gh-aw-deploy` in plaintext. Any XSS vulnerability in the page (or browser extensions with +page access) could exfiltrate the token. + +**Mitigating factors:** Opt-in only, React auto-escaping reduces XSS risk, and this is the +standard pattern for client-side token storage without a backend. + +**Recommendation:** Document the risk clearly to users. Consider adding a warning in the UI. +No code fix required. + +--- + +### L-3: Branch name validation incomplete [LOW] + +**File:** `src/components/Deploy/RepoStep.tsx:10` + +```ts +const BRANCH_INVALID = /(\.\.|[ ~^:\\]|\.lock$)/; +``` + +Missing checks: leading/trailing `/`, leading `-`, names starting with `.`, names containing +`@{`. These are rejected by git but would only fail at the GitHub API level, producing a +less clear error message. + +**Recommendation:** Add additional checks or improve the error message for API rejections. +Low priority since GitHub API enforces these rules. + +--- + +### L-4: GitHub API error messages may leak internal details [LOW] + +**File:** `src/utils/githubApi.ts:32-33` + +```ts +const msg = (body as { message?: string }).message || res.statusText; +``` + +Raw GitHub API error messages are captured. The `deploy.ts` error handler (lines 136-148) +already replaces messages for common status codes (401, 403, 404) with user-friendly text, +which is good. Remaining edge cases (422, 500, etc.) pass through the raw API message. + +**Mitigating factors:** GitHub API messages are generally safe for end users. +No code fix required. + +--- + +## Positive Findings + +| Area | Assessment | +|------|-----------| +| XSS prevention | React JSX auto-escaping used throughout. No `dangerouslySetInnerHTML`. | +| URL injection | `encodeURIComponent` on owner/repo in all API URLs. | +| Expression injection | `escapeGitHubExpressions()` neutralizes `${{ }}` in PR body. | +| Token lifecycle | 401 clears token automatically. `clearToken` resets all auth state. | +| Input validation | Repo slug regex and branch name checks prevent most bad inputs. | +| Error handling | User-friendly messages for common failures. No stack traces leaked. | +| Token input | Uses `type="password"` to prevent shoulder surfing. | + +--- + +## Recommended Fixes (ordered by priority) + +1. **M-1** — Sanitize `workflowName` in `deploy.ts` before path construction +2. **M-2** — Validate `prUrl` origin in `SuccessStep.tsx` +3. **L-1** — Encode path segments in `createOrUpdateFile` URL diff --git a/docs/editor-app/src/SECURITY-REVIEW.md b/docs/editor-app/src/SECURITY-REVIEW.md new file mode 100644 index 00000000000..b9efc0f8017 --- /dev/null +++ b/docs/editor-app/src/SECURITY-REVIEW.md @@ -0,0 +1,210 @@ +# Security Review: Deploy to GitHub Feature + +**Date**: 2026-02-24 +**Reviewer**: security-reviewer (automated audit) +**Scope**: `src/utils/githubApi.ts`, `src/stores/deployStore.ts`, `src/utils/deploy.ts`, `src/components/Deploy/*.tsx` + +--- + +## Summary + +The Deploy to GitHub feature allows users to authenticate with a GitHub Personal Access Token, select a repository, and create a pull request containing workflow files. Overall the implementation follows reasonable patterns, but there are several findings that should be addressed before production use. + +**Overall Risk**: Medium + +--- + +## Findings + +### 1. Token Persisted in localStorage by Default + +**Severity**: Medium +**File**: `src/stores/deployStore.ts:63` + +The `rememberToken` flag defaults to `true`, which means the PAT is silently persisted in `localStorage` (under key `gh-aw-deploy`) without the user explicitly opting in. On shared or public computers, this leaves the token accessible to anyone with physical access or any JavaScript running on the same origin. + +**Recommendation**: +- Change `rememberToken` default to `false` so users must explicitly opt in to persistence. +- Add a brief warning near the checkbox explaining that the token will be stored in the browser. + +--- + +### 2. Token Stored in Plain Text (XSS Exposure) + +**Severity**: Medium +**File**: `src/stores/deployStore.ts:107-116` + +When `rememberToken` is true, the raw PAT string is stored unencrypted in `localStorage`. Any XSS vulnerability on the same origin would allow an attacker to read `localStorage.getItem('gh-aw-deploy')` and extract the token. Since this is a GitHub Pages site, the attack surface includes any other code running on the `githubnext.github.io` origin. + +**Note**: Client-side encryption wouldn't provide real protection against XSS (since the decryption key must also be in JS). The primary mitigation is preventing XSS and minimizing persistence. + +**Recommendation**: +- Keep `rememberToken` off by default (see finding #1). +- Use `sessionStorage` instead of `localStorage` when `rememberToken` is false — currently the token is held only in Zustand memory, which is correct. Verify this stays the case. +- Add a "Clear saved token" option accessible from the UI at all times (not just during the auth step). +- Consider using the `partialize` function to exclude the token entirely and re-prompt each session, storing only the username for UX convenience. + +--- + +### 3. Inconsistent URL Encoding in API Client + +**Severity**: Medium +**File**: `src/utils/githubApi.ts:44-57, 88` + +`createOrUpdateFile` correctly uses `encodeURIComponent()` for `owner` and `repo` in the URL path (line 88), but `getRepo` (line 45), `getDefaultBranchSha` (line 57), `createBranch` (line 68), and `createPullRequest` (line 109) do **not** encode these parameters. If a user enters a malformed repo slug (e.g., containing `/`, `?`, `#`, or `..`), the resulting URL could resolve to an unintended API endpoint. + +**Example**: A repo slug of `owner/repo/../../orgs/secret-org` would produce an API call to an unintended endpoint. + +**Recommendation**: +- Apply `encodeURIComponent()` to all user-supplied path segments (`owner`, `repo`, `branch`) in every API function. +- Alternatively, add a single validation function for repo slug format (`^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$`) and call it before any API calls. + +--- + +### 4. No Client-Side Input Validation on Repo Slug + +**Severity**: Medium +**File**: `src/components/Deploy/RepoSelector.tsx:30`, `src/utils/deploy.ts:26` + +The deploy gate is simply `repoSlug.includes('/')` (RepoSelector.tsx:30), and the split is an unchecked `repoSlug.split('/')` (deploy.ts:26). This permits values like `/`, `a/b/c/d`, `../..`, or strings with special characters. These values propagate directly into GitHub API URLs. + +**Recommendation**: +- Validate the repo slug with a regex: `^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$` +- Show a validation error in the UI if the format doesn't match. +- Ensure `split('/')` produces exactly 2 non-empty segments before proceeding. + +--- + +### 5. Branch Name Not Validated + +**Severity**: Low +**File**: `src/components/Deploy/RepoSelector.tsx:20-27`, `src/utils/githubApi.ts:57,68` + +The auto-derived branch name sanitizes the workflow name well (line 21-25), but the user can freely edit it afterward. Branch names containing `..`, spaces, `~`, `^`, `:`, `\`, or leading dots are invalid in git but would be sent to the API, resulting in confusing error messages. More critically, special characters could alter the URL path in `getDefaultBranchSha`. + +**Recommendation**: +- Validate branch names against git ref rules (no `..`, no ASCII control chars, no `\`, no trailing `.lock`, etc.). +- Or at minimum, apply the same sanitization used for auto-generation when the user edits the field. + +--- + +### 6. PR Body Content Injection — GitHub Actions Expression Risk + +**Severity**: Medium +**File**: `src/utils/deploy.ts:87-113` + +The `buildPrBody` function extracts `description` and `trigger` from user-authored markdown via regex and interpolates them directly into the PR body. If the workflow markdown contains GitHub Actions expressions like `${{ github.event.issue.title }}` in its frontmatter fields, these strings end up verbatim in the PR body. + +While PR bodies themselves are not evaluated as Actions expressions, the pattern normalizes unsafe content in contexts that could be copy-pasted or referenced by workflows that read PR bodies (e.g., `${{ github.event.pull_request.body }}`). + +**Recommendation**: +- Escape or strip `${{ }}` patterns from extracted values before inserting into the PR body. +- A simple replacement: `value.replace(/\$\{\{/g, '`${{`')` would render them as code literals. + +--- + +### 7. Deprecated `unescape()` Usage + +**Severity**: Low +**File**: `src/utils/githubApi.ts:93` + +The expression `btoa(unescape(encodeURIComponent(content)))` uses the deprecated `unescape()` function. While this is a common pattern for UTF-8-safe base64 encoding and works correctly in all modern browsers, it may trigger linter warnings and could theoretically be removed from future browser standards. + +**Recommendation**: +- Replace with a `TextEncoder`-based approach: + ```ts + const bytes = new TextEncoder().encode(content); + const binary = Array.from(bytes, (b) => String.fromCharCode(b)).join(''); + const base64 = btoa(binary); + ``` + +--- + +### 8. Error Messages — No Token Leakage + +**Severity**: Info (Positive Finding) +**File**: `src/utils/githubApi.ts:27-30`, `src/components/Deploy/TokenSetup.tsx:36-39` + +Error handling is safe. The `GitHubApiError` only includes `body.message` (from GitHub's API response) or `res.statusText` — never the token itself. The `TokenSetup` component shows generic error messages ("Invalid token", "GitHub returned {status}"). No token fragments leak into error UI or console. + +--- + +### 9. HTTPS-Only API Communication + +**Severity**: Info (Positive Finding) +**File**: `src/utils/githubApi.ts:1` + +`API_BASE` is hardcoded to `https://api.github.com`. Token is transmitted only in the `Authorization` header, never in URL query parameters. This prevents token leakage in browser history, server logs, or referrer headers. + +--- + +### 10. Token Scope Guidance + +**Severity**: Info +**File**: `src/components/Deploy/TokenSetup.tsx:83` + +The "Create a token" link requests `repo` and `workflow` scopes on classic PATs. These are broad scopes — `repo` grants full access to all repositories. + +**Recommendation**: +- Add guidance suggesting **fine-grained PATs** as the preferred option, scoped to specific repositories with only: + - `contents: write` (for pushing files) + - `pull_requests: write` (for creating PRs) + - `metadata: read` (for repo info) +- Keep classic PAT instructions as a fallback. + +--- + +### 11. No CORS / CSRF Concerns + +**Severity**: Info (Positive Finding) + +This is a static SPA making direct `fetch()` calls to `api.github.com`. GitHub's CORS policy permits cross-origin requests with `Authorization` headers. There is no server-side session or cookie-based auth, so CSRF is not applicable. + +--- + +### 12. `deployWorkflow` Not Yet Connected + +**Severity**: Info +**File**: `src/utils/deploy.ts:16` + +The `deployWorkflow` function is exported but not imported or called by any component. The `RepoSelector` transitions to the `'deploying'` step, but nothing triggers the actual API call sequence. This appears to be work-in-progress. When wiring this up, ensure errors are caught and don't leak token info, and that the deploy cannot be double-triggered. + +--- + +### 13. Shared Origin Risk (GitHub Pages) + +**Severity**: Low + +If deployed on `githubnext.github.io`, the app shares an origin with other projects under the `githubnext` org on GitHub Pages. Any XSS in a sibling project could potentially read this app's `localStorage`. This is a platform-level concern, not a code bug. + +**Recommendation**: +- Consider deploying on a custom domain for origin isolation. +- Or accept this risk given that `githubnext` controls all projects on that subdomain. + +--- + +## Dependencies Audit + +| Dependency | Version | Risk | +|---|---|---| +| `zustand` | ^5.0.11 | Low — well-maintained state library, `persist` middleware uses `localStorage` directly | +| `@radix-ui/react-dialog` | ^1.1.15 | Low — widely used, accessible dialog primitive | +| `lucide-react` | ^0.574.0 | Low — icon library, no security surface | +| `react` / `react-dom` | ^19.2.4 | Low — React auto-escapes JSX output, strong XSS protection | +| `sonner` | ^2.0.7 | Low — toast library | +| `vite` | ^7.3.1 (dev) | Low — build tool only | + +No high-risk dependencies identified. The app has a small dependency surface. + +--- + +## Recommended Mitigations (Priority Order) + +1. **[Medium]** Validate repo slug format before API calls (finding #3, #4) +2. **[Medium]** Apply `encodeURIComponent()` consistently across all API functions (finding #3) +3. **[Medium]** Default `rememberToken` to `false` (finding #1) +4. **[Medium]** Escape `${{ }}` in PR body content (finding #6) +5. **[Low]** Validate branch names client-side (finding #5) +6. **[Low]** Replace deprecated `unescape()` (finding #7) +7. **[Info]** Add fine-grained PAT guidance (finding #10) +8. **[Info]** Wire up `deployWorkflow` with error boundary and double-trigger protection (finding #12) diff --git a/docs/editor-app/src/components/Canvas/CanvasWithProvider.tsx b/docs/editor-app/src/components/Canvas/CanvasWithProvider.tsx new file mode 100644 index 00000000000..5aa1bcd3761 --- /dev/null +++ b/docs/editor-app/src/components/Canvas/CanvasWithProvider.tsx @@ -0,0 +1,15 @@ +import { ReactFlowProvider } from '@xyflow/react'; +import { WorkflowGraph } from './WorkflowGraph'; + +/** + * Wraps WorkflowGraph with ReactFlowProvider. + * This is a separate lazy-loaded module so the entire ReactFlow library + * (~220KB) is deferred from the initial critical bundle. + */ +export default function CanvasWithProvider() { + return ( + + + + ); +} diff --git a/docs/editor-app/src/components/Canvas/EmptyState.tsx b/docs/editor-app/src/components/Canvas/EmptyState.tsx new file mode 100644 index 00000000000..c4e1315126d --- /dev/null +++ b/docs/editor-app/src/components/Canvas/EmptyState.tsx @@ -0,0 +1,30 @@ +import { Workflow, LayoutTemplate } from 'lucide-react'; +import { useUIStore } from '../../stores/uiStore'; +import '../../styles/nodes.css'; + +export function EmptyState() { + const setSidebarTab = useUIStore((s) => s.setSidebarTab); + const sidebarOpen = useUIStore((s) => s.sidebarOpen); + const toggleSidebar = useUIStore((s) => s.toggleSidebar); + + const handleBrowseTemplates = () => { + setSidebarTab('templates'); + if (!sidebarOpen) { + toggleSidebar(); + } + }; + + return ( +
+ +

Build your workflow

+

+ Get started by choosing a template from the sidebar, or add blocks to build your workflow step by step. +

+ +
+ ); +} diff --git a/docs/editor-app/src/components/Canvas/WorkflowGraph.tsx b/docs/editor-app/src/components/Canvas/WorkflowGraph.tsx new file mode 100644 index 00000000000..bfa8a41de40 --- /dev/null +++ b/docs/editor-app/src/components/Canvas/WorkflowGraph.tsx @@ -0,0 +1,269 @@ +import { useMemo, useCallback } from 'react'; +import { + ReactFlow, + MiniMap, + Controls, + Background, + BackgroundVariant, + useNodesState, + useEdgesState, + type NodeTypes, + type Node, + type Edge, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import dagre from '@dagrejs/dagre'; + +import { useUIStore } from '../../stores/uiStore'; +import { TriggerNode } from '../Nodes/TriggerNode'; +import { EngineNode } from '../Nodes/EngineNode'; +import { ToolsNode } from '../Nodes/ToolsNode'; +import { InstructionsNode } from '../Nodes/InstructionsNode'; +import { SafeOutputsNode } from '../Nodes/SafeOutputsNode'; +import { NetworkNode } from '../Nodes/NetworkNode'; +import { SettingsNode } from '../Nodes/SettingsNode'; +import { StepsNode } from '../Nodes/StepsNode'; +import { EmptyState } from './EmptyState'; +import { useWorkflowStore } from '../../stores/workflowStore'; +import type { WorkflowNodeData } from '../../types/nodes'; +import '../../styles/nodes.css'; + +const nodeTypes: NodeTypes = { + trigger: TriggerNode, + engine: EngineNode, + tools: ToolsNode, + instructions: InstructionsNode, + safeOutputs: SafeOutputsNode, + network: NetworkNode, + settings: SettingsNode, + steps: StepsNode, +}; + +const NODE_WIDTH = 260; +const NODE_HEIGHT = 120; +const EXPANDED_INSTRUCTIONS_WIDTH = 480; +const EXPANDED_INSTRUCTIONS_HEIGHT = 400; + +interface NodeDef { + id: string; + type: string; + label: string; + description: string; + isRequired: boolean; + isConfigured: (state: ReturnType) => boolean; +} + +const ALL_NODES: NodeDef[] = [ + { + id: 'trigger', + type: 'trigger', + label: 'When this happens', + description: 'Choose what starts this workflow', + isRequired: true, + isConfigured: (s) => s.trigger.event !== '', + }, + { + id: 'engine', + type: 'engine', + label: 'AI Assistant', + description: 'Choose which AI runs this', + isRequired: true, + isConfigured: (s) => s.engine.type !== '', + }, + { + id: 'tools', + type: 'tools', + label: 'Agent Tools', + description: 'What tools the agent can use', + isRequired: false, + isConfigured: (s) => s.tools.length > 0, + }, + { + id: 'instructions', + type: 'instructions', + label: 'Instructions', + description: 'Tell the agent what to do', + isRequired: true, + isConfigured: (s) => s.instructions.trim().length > 0, + }, + { + id: 'safeOutputs', + type: 'safeOutputs', + label: 'What it can do', + description: 'Actions the agent can take', + isRequired: false, + isConfigured: (s) => Object.keys(s.safeOutputs).length > 0, + }, + { + id: 'network', + type: 'network', + label: 'Network Access', + description: 'Allowed network domains', + isRequired: false, + isConfigured: (s) => + s.network.allowed.length > 0 || s.network.blocked.length > 0, + }, + { + id: 'settings', + type: 'settings', + label: 'Settings', + description: 'Concurrency, rate limits, platform', + isRequired: false, + isConfigured: (s) => + s.platform !== '' || + (s.concurrency?.group ?? '') !== '' || + (s.concurrency?.cancelInProgress ?? false) || + (s.rateLimit?.max ?? '') !== '' || + (s.rateLimit?.window ?? '') !== '', + }, + { + id: 'steps', + type: 'steps', + label: 'Custom Steps', + description: 'Additional workflow steps', + isRequired: false, + isConfigured: () => false, + }, +]; + +function getLayoutedElements(nodes: Node[], edges: Edge[], instructionsExpanded: boolean) { + const g = new dagre.graphlib.Graph(); + g.setDefaultEdgeLabel(() => ({})); + g.setGraph({ rankdir: 'TB', nodesep: 40, ranksep: 60 }); + + nodes.forEach((node) => { + const isExpandedInstructions = node.id === 'instructions' && instructionsExpanded; + const w = isExpandedInstructions ? EXPANDED_INSTRUCTIONS_WIDTH : NODE_WIDTH; + const h = isExpandedInstructions ? EXPANDED_INSTRUCTIONS_HEIGHT : NODE_HEIGHT; + g.setNode(node.id, { width: w, height: h }); + }); + + edges.forEach((edge) => { + g.setEdge(edge.source, edge.target); + }); + + dagre.layout(g); + + const layoutedNodes = nodes.map((node) => { + const pos = g.node(node.id); + const isExpandedInstructions = node.id === 'instructions' && instructionsExpanded; + const w = isExpandedInstructions ? EXPANDED_INSTRUCTIONS_WIDTH : NODE_WIDTH; + const h = isExpandedInstructions ? EXPANDED_INSTRUCTIONS_HEIGHT : NODE_HEIGHT; + return { + ...node, + position: { + x: pos.x - w / 2, + y: pos.y - h / 2, + }, + }; + }); + + return { nodes: layoutedNodes, edges }; +} + +export function WorkflowGraph() { + const state = useWorkflowStore(); + const selectNode = useWorkflowStore((s) => s.selectNode); + const instructionsExpanded = useWorkflowStore((s) => s.instructionsExpanded); + const setInstructionsExpanded = useWorkflowStore((s) => s.setInstructionsExpanded); + const theme = useUIStore((s) => s.theme); + + // Resolve effective color mode (auto → system preference) + const isDark = theme === 'dark' || (theme === 'auto' && typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches); + const colorMode = isDark ? 'dark' : 'light'; + + // Determine which nodes to show: required nodes + configured optional nodes + const visibleNodeDefs = useMemo(() => { + return ALL_NODES.filter((def) => def.isRequired || def.isConfigured(state)); + }, [state]); + + // Build React Flow nodes + const rawNodes: Node[] = useMemo(() => { + return visibleNodeDefs.map((def) => ({ + id: def.id, + type: def.type, + position: { x: 0, y: 0 }, + data: { + label: def.label, + type: def.type as WorkflowNodeData['type'], + description: def.description, + isConfigured: def.isConfigured(state), + }, + })); + }, [visibleNodeDefs, state]); + + // Build edges connecting sequential nodes + const rawEdges: Edge[] = useMemo(() => { + return visibleNodeDefs.slice(1).map((def, i) => ({ + id: `e-${visibleNodeDefs[i].id}-${def.id}`, + source: visibleNodeDefs[i].id, + target: def.id, + animated: true, + style: { stroke: 'var(--borderColor-default, #d1d9e0)' }, + })); + }, [visibleNodeDefs]); + + // Apply dagre layout + const { nodes: layoutedNodes, edges: layoutedEdges } = useMemo( + () => getLayoutedElements(rawNodes, rawEdges, instructionsExpanded), + [rawNodes, rawEdges, instructionsExpanded] + ); + + const [nodes, setNodes, onNodesChange] = useNodesState(layoutedNodes); + const [edges, , onEdgesChange] = useEdgesState(layoutedEdges); + + // Sync layout when nodes change + useMemo(() => { + setNodes(layoutedNodes); + }, [layoutedNodes, setNodes]); + + const onNodeClick = useCallback( + (_: React.MouseEvent, node: Node) => { + selectNode(node.id); + }, + [selectNode] + ); + + const onPaneClick = useCallback(() => { + selectNode(null); + setInstructionsExpanded(false); + }, [selectNode, setInstructionsExpanded]); + + if (visibleNodeDefs.length === 0) { + return ; + } + + return ( + + + + + + ); +} diff --git a/docs/editor-app/src/components/Deploy/DeployDialog.tsx b/docs/editor-app/src/components/Deploy/DeployDialog.tsx new file mode 100644 index 00000000000..c592503ee2f --- /dev/null +++ b/docs/editor-app/src/components/Deploy/DeployDialog.tsx @@ -0,0 +1,114 @@ +import * as Dialog from '@radix-ui/react-dialog'; +import { X, AlertCircle } from 'lucide-react'; +import { useDeployStore } from '../../stores/deployStore'; +import { TokenStep } from './TokenStep'; +import { RepoStep } from './RepoStep'; +import { ProgressStep } from './ProgressStep'; +import { SuccessStep } from './SuccessStep'; + +const STEP_TITLES: Record = { + auth: 'Deploy to GitHub', + repo: 'Deploy to GitHub', + deploying: 'Deploying...', + success: 'Deployed!', + error: 'Deploy Failed', +}; + +export function DeployDialog() { + const isOpen = useDeployStore((s) => s.isOpen); + const step = useDeployStore((s) => s.step); + const error = useDeployStore((s) => s.error); + const closeDialog = useDeployStore((s) => s.closeDialog); + const isDeploying = useDeployStore((s) => s.isDeploying); + const setStep = useDeployStore((s) => s.setStep); + + const handleOpenChange = (open: boolean) => { + if (!open) { + if (isDeploying) { + if (window.confirm('Deploy is in progress. Closing will cancel the remaining steps. Already-uploaded files will remain on GitHub.')) { + closeDialog(); + } + } else { + closeDialog(); + } + } + }; + + return ( + + + + + {/* Header */} +
+ + {STEP_TITLES[step] || 'Deploy to GitHub'} + + + + +
+ + {/* Step content */} + {step === 'auth' && } + {step === 'repo' && } + {step === 'deploying' && } + {step === 'success' && } + {step === 'error' && ( +
+ +
+ + + {error} + +
+
+ + +
+
+ )} +
+
+
+ ); +} + +const overlayStyle: React.CSSProperties = { + position: 'fixed', inset: 0, + background: 'rgba(0, 0, 0, 0.4)', + zIndex: 1000, +}; + +const contentStyle: React.CSSProperties = { + position: 'fixed', + top: '50%', left: '50%', + transform: 'translate(-50%, -50%)', + width: 'min(480px, 90vw)', + maxHeight: '85vh', + overflow: 'auto', + padding: 24, + borderRadius: 12, + background: 'var(--color-bg-default, #ffffff)', + border: '1px solid var(--color-border-default, #d0d7de)', + boxShadow: '0 8px 30px rgba(0, 0, 0, 0.12)', + zIndex: 1001, +}; + +const primaryBtnStyle: React.CSSProperties = { + display: 'flex', alignItems: 'center', gap: 6, + padding: '8px 16px', fontSize: 13, fontWeight: 600, + border: 'none', borderRadius: 6, cursor: 'pointer', + background: 'var(--color-btn-primary-bg, #1f883d)', + color: '#ffffff', +}; + +const secondaryBtnStyle: React.CSSProperties = { + padding: '8px 16px', fontSize: 13, fontWeight: 500, + border: '1px solid var(--color-border-default, #d0d7de)', borderRadius: 6, + background: 'var(--color-bg-default, #ffffff)', + color: 'var(--color-fg-default, #1f2328)', cursor: 'pointer', +}; diff --git a/docs/editor-app/src/components/Deploy/ProgressStep.tsx b/docs/editor-app/src/components/Deploy/ProgressStep.tsx new file mode 100644 index 00000000000..5731aae0a7a --- /dev/null +++ b/docs/editor-app/src/components/Deploy/ProgressStep.tsx @@ -0,0 +1,46 @@ +import { CheckCircle2, Circle, Loader2, XCircle } from 'lucide-react'; +import { useDeployStore } from '../../stores/deployStore'; + +export function ProgressStep() { + const progress = useDeployStore((s) => s.progress); + + return ( +
+ {progress.map((step) => ( +
+ {step.status === 'done' && ( + + )} + {step.status === 'running' && ( + + )} + {step.status === 'pending' && ( + + )} + {step.status === 'error' && ( + + )} + + {step.label} + {step.status === 'running' && '...'} + +
+ ))} +
+ ); +} diff --git a/docs/editor-app/src/components/Deploy/RepoStep.tsx b/docs/editor-app/src/components/Deploy/RepoStep.tsx new file mode 100644 index 00000000000..617aa6a2d01 --- /dev/null +++ b/docs/editor-app/src/components/Deploy/RepoStep.tsx @@ -0,0 +1,203 @@ +import { useState, useMemo } from 'react'; +import { GitBranch, Loader2 } from 'lucide-react'; +import { useDeployStore } from '../../stores/deployStore'; +import { useWorkflowStore } from '../../stores/workflowStore'; +import { generateMarkdown } from '../../utils/markdownGenerator'; +import { compile, isCompilerReady } from '../../utils/compiler'; +import { runDeploy } from '../../utils/deploy'; + +const REPO_REGEX = /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/; +const BRANCH_INVALID = /(\.\.|[ ~^:\\]|\.lock$)/; + +export function RepoStep() { + const store = useDeployStore(); + const { + username, + repoSlug, + branchName, + baseBranch, + setRepoSlug, + setBranchName, + setBaseBranch, + setStep, + closeDialog, + clearToken, + isDeploying, + setIsDeploying, + initProgress, + } = store; + + const workflowName = useWorkflowStore((s) => s.name); + const [deploying, setDeploying] = useState(false); + + // Auto-derive branch name from workflow name + const derivedBranch = useMemo(() => { + if (branchName) return branchName; + if (workflowName) return `aw/${workflowName}`; + return 'aw/workflow'; + }, [branchName, workflowName]); + + const repoValid = REPO_REGEX.test(repoSlug); + const branchValid = derivedBranch.length > 0 && !BRANCH_INVALID.test(derivedBranch); + const canDeploy = repoValid && branchValid && !deploying && !isDeploying; + + const handleDeploy = async () => { + if (!canDeploy) return; + setDeploying(true); + setIsDeploying(true); + + // Generate markdown RIGHT HERE from current workflow state + const wfState = useWorkflowStore.getState(); + const markdown = generateMarkdown(wfState); + + // Try to get compiled YAML + let yaml = wfState.compiledYaml || ''; + if (!yaml && isCompilerReady()) { + try { + const result = await Promise.race([ + compile(markdown), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Compile timeout')), 10000), + ), + ]); + yaml = result.yaml || ''; + } catch { + // Compile failed or timed out + } + } + + if (!yaml) { + useDeployStore.getState().setError('No compiled YAML available. Make sure your workflow compiles successfully first.'); + setDeploying(false); + return; + } + + if (!markdown) { + useDeployStore.getState().setError('No workflow markdown available.'); + setDeploying(false); + return; + } + + // Switch to progress view and initialize + const finalBranch = branchName || derivedBranch; + setBranchName(finalBranch); + initProgress(); + setStep('deploying'); + + // Run deploy with a fresh store reference + const currentStore = useDeployStore.getState(); + await runDeploy(currentStore, markdown, yaml, wfState.name || 'workflow'); + setDeploying(false); + }; + + return ( +
+ {/* Logged in as */} +
+ + Logged in as {username} + + +
+ + {/* Repository */} +
+ + setRepoSlug(e.target.value)} + placeholder="owner/repo" + data-testid="repo-input" + style={inputStyle} + /> + {repoSlug && !repoValid && ( +
Enter a valid repository in the format owner/repo.
+ )} +
+ + {/* Branch name */} +
+ + setBranchName(e.target.value)} + placeholder="aw/my-workflow" + data-testid="branch-input" + style={inputStyle} + /> + {(branchName || derivedBranch) && !branchValid && ( +
Invalid branch name.
+ )} +
+ + {/* Base branch */} +
+ + setBaseBranch(e.target.value)} + placeholder="main" + data-testid="base-branch-input" + style={inputStyle} + /> +
+ + {/* Actions */} +
+ + +
+
+ ); +} + +const labelStyle: React.CSSProperties = { + display: 'flex', alignItems: 'center', + fontSize: 12, fontWeight: 600, marginBottom: 4, + color: 'var(--color-fg-default, #1f2328)', +}; + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px 12px', + fontSize: 13, + border: '1px solid var(--color-border-default, #d0d7de)', + borderRadius: 6, + background: 'var(--color-bg-default, #ffffff)', + color: 'var(--color-fg-default, #1f2328)', + outline: 'none', + boxSizing: 'border-box', +}; + +const errorHintStyle: React.CSSProperties = { + fontSize: 11, color: 'var(--color-danger-fg, #cf222e)', marginTop: 4, +}; + +const primaryBtnStyle: React.CSSProperties = { + display: 'flex', alignItems: 'center', gap: 6, + padding: '8px 16px', fontSize: 13, fontWeight: 600, + border: 'none', borderRadius: 6, cursor: 'pointer', + background: 'var(--color-btn-primary-bg, #1f883d)', + color: '#ffffff', +}; + +const secondaryBtnStyle: React.CSSProperties = { + padding: '8px 16px', fontSize: 13, fontWeight: 500, + border: '1px solid var(--color-border-default, #d0d7de)', borderRadius: 6, + background: 'var(--color-bg-default, #ffffff)', + color: 'var(--color-fg-default, #1f2328)', cursor: 'pointer', +}; diff --git a/docs/editor-app/src/components/Deploy/SuccessStep.tsx b/docs/editor-app/src/components/Deploy/SuccessStep.tsx new file mode 100644 index 00000000000..bdeae2d2a1c --- /dev/null +++ b/docs/editor-app/src/components/Deploy/SuccessStep.tsx @@ -0,0 +1,43 @@ +import { CheckCircle2, ExternalLink } from 'lucide-react'; +import { useDeployStore } from '../../stores/deployStore'; + +export function SuccessStep() { + const prUrl = useDeployStore((s) => s.prUrl); + const closeDialog = useDeployStore((s) => s.closeDialog); + + return ( +
+ ); +} diff --git a/docs/editor-app/src/components/Deploy/TokenStep.tsx b/docs/editor-app/src/components/Deploy/TokenStep.tsx new file mode 100644 index 00000000000..0edc496cf2c --- /dev/null +++ b/docs/editor-app/src/components/Deploy/TokenStep.tsx @@ -0,0 +1,131 @@ +import { useState } from 'react'; +import { KeyRound, ExternalLink, Loader2 } from 'lucide-react'; +import { useDeployStore } from '../../stores/deployStore'; +import { validateToken } from '../../utils/githubApi'; + +export function TokenStep() { + const setToken = useDeployStore((s) => s.setToken); + const setStep = useDeployStore((s) => s.setStep); + const rememberToken = useDeployStore((s) => s.rememberToken); + const setRememberToken = useDeployStore((s) => s.setRememberToken); + const closeDialog = useDeployStore((s) => s.closeDialog); + const storeError = useDeployStore((s) => s.error); + + const [input, setInput] = useState(''); + const [validating, setValidating] = useState(false); + const [error, setError] = useState(storeError); + + const handleContinue = async () => { + if (!input.trim()) return; + setValidating(true); + setError(null); + try { + const user = await validateToken(input.trim()); + setToken(input.trim(), user.login); + setStep('repo'); + } catch { + setError('Invalid token. Please check and try again.'); + } finally { + setValidating(false); + } + }; + + return ( +
+
+ + + To deploy workflows, you need a GitHub Personal Access Token with{' '} + repo and{' '} + workflow scopes. + +
+ + + Create a token on GitHub + + +
+ + setInput(e.target.value)} + onKeyDown={(e) => { if (e.key === 'Enter') handleContinue(); }} + placeholder="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + data-testid="token-input" + style={inputStyle} + autoFocus + /> +
+ + {error && ( +
+ {error} +
+ )} + + + +
+ + +
+
+ ); +} + +const inputStyle: React.CSSProperties = { + width: '100%', + padding: '8px 12px', + fontSize: 13, + fontFamily: 'monospace', + border: '1px solid var(--color-border-default, #d0d7de)', + borderRadius: 6, + background: 'var(--color-bg-default, #ffffff)', + color: 'var(--color-fg-default, #1f2328)', + outline: 'none', + boxSizing: 'border-box', +}; + +const primaryBtnStyle: React.CSSProperties = { + display: 'flex', alignItems: 'center', gap: 6, + padding: '8px 16px', fontSize: 13, fontWeight: 600, + border: 'none', borderRadius: 6, cursor: 'pointer', + background: 'var(--color-btn-primary-bg, #1f883d)', + color: '#ffffff', +}; + +const secondaryBtnStyle: React.CSSProperties = { + padding: '8px 16px', fontSize: 13, fontWeight: 500, + border: '1px solid var(--color-border-default, #d0d7de)', borderRadius: 6, + background: 'var(--color-bg-default, #ffffff)', + color: 'var(--color-fg-default, #1f2328)', cursor: 'pointer', +}; diff --git a/docs/editor-app/src/components/EditorView/DocsPanel.tsx b/docs/editor-app/src/components/EditorView/DocsPanel.tsx new file mode 100644 index 00000000000..af06ffa95e1 --- /dev/null +++ b/docs/editor-app/src/components/EditorView/DocsPanel.tsx @@ -0,0 +1,333 @@ +import { useState, useRef, useEffect, useMemo } from 'react'; +import { referenceSections, type RefEntry, type RefSection } from '../../utils/referenceData'; + +interface DocsPanelProps { + /** When set, auto-scroll to this key's section. */ + activeDocKey?: string | null; + /** Called after the panel has scrolled to the active key. */ + onScrollComplete?: () => void; +} + +export function DocsPanel({ activeDocKey, onScrollComplete }: DocsPanelProps) { + const [filter, setFilter] = useState(''); + const contentRef = useRef(null); + const entryRefs = useRef>(new Map()); + + // Filter sections based on search input + const filteredSections = useMemo(() => { + if (!filter.trim()) return referenceSections; + const q = filter.toLowerCase(); + const result: RefSection[] = []; + for (const section of referenceSections) { + const matched = section.entries.filter(entry => matchesFilter(entry, q)); + if (matched.length > 0) { + result.push({ ...section, entries: matched }); + } + } + return result; + }, [filter]); + + // Auto-scroll to activeDocKey + useEffect(() => { + if (!activeDocKey) return; + const el = entryRefs.current.get(activeDocKey); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + // Brief highlight flash + el.classList.add('docs-entry--highlight'); + const timer = setTimeout(() => { + el.classList.remove('docs-entry--highlight'); + onScrollComplete?.(); + }, 1200); + return () => clearTimeout(timer); + } + }, [activeDocKey, onScrollComplete]); + + const registerRef = (key: string, el: HTMLElement | null) => { + if (el) entryRefs.current.set(key, el); + else entryRefs.current.delete(key); + }; + + return ( +
+ {/* Search */} +
+ setFilter(e.target.value)} + placeholder="Search fields..." + style={styles.searchInput} + /> + {filter && ( + + )} +
+ + {/* Table of Contents */} + {!filter && ( + + )} + + {/* Content */} +
+ {filteredSections.length === 0 && ( +
No fields match "{filter}"
+ )} + {filteredSections.map(section => ( +
+
{section.title}
+ {section.entries.map(entry => ( + + ))} +
+ ))} +
+
+ ); +} + +function EntryCard({ + entry, + registerRef, + activeKey, + depth = 0, +}: { + entry: RefEntry; + registerRef: (key: string, el: HTMLElement | null) => void; + activeKey?: string | null; + depth?: number; +}) { + const isActive = activeKey === entry.key; + + return ( + <> +
registerRef(entry.key, el)} + className={`docs-entry ${isActive ? 'docs-entry--highlight' : ''}`} + style={{ + ...styles.entry, + marginLeft: depth * 16, + borderLeftColor: isActive ? 'var(--color-accent-fg, #0969da)' : 'transparent', + }} + id={`docs-entry-${entry.key}`} + > +
+ {entry.key} + {entry.type} + {entry.required && required} +
+
{entry.title}
+
{entry.description}
+ {entry.examples.map((ex, i) => ( +
{ex}
+ ))} + {entry.link && ( + + Full docs → + + )} +
+ {entry.children?.map(child => ( + + ))} + + ); +} + +function matchesFilter(entry: RefEntry, query: string): boolean { + const hay = `${entry.key} ${entry.title} ${entry.description}`.toLowerCase(); + if (hay.includes(query)) return true; + if (entry.children?.some(c => matchesFilter(c, query))) return true; + return false; +} + +// ── Inline styles (matches editor pane aesthetic) ── + +const mono = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace'; + +const styles: Record = { + container: { + display: 'flex', + flexDirection: 'column', + height: '100%', + overflow: 'hidden', + }, + searchBox: { + position: 'relative', + padding: '8px 12px', + borderBottom: '1px solid var(--color-border-default, #d0d7de)', + flexShrink: 0, + }, + searchInput: { + width: '100%', + padding: '6px 28px 6px 8px', + fontSize: '12px', + fontFamily: 'inherit', + border: '1px solid var(--color-border-default, #d0d7de)', + borderRadius: '6px', + background: 'var(--color-bg-default, #fff)', + color: 'var(--color-fg-default, #1f2328)', + outline: 'none', + }, + clearBtn: { + position: 'absolute', + right: '18px', + top: '50%', + transform: 'translateY(-50%)', + border: 'none', + background: 'none', + cursor: 'pointer', + color: 'var(--color-fg-muted, #656d76)', + fontSize: '12px', + lineHeight: 1, + padding: '2px 4px', + }, + toc: { + padding: '8px 12px', + borderBottom: '1px solid var(--color-border-default, #d0d7de)', + flexShrink: 0, + }, + tocTitle: { + fontSize: '11px', + fontWeight: 600, + textTransform: 'uppercase' as const, + letterSpacing: '0.5px', + color: 'var(--color-fg-muted, #656d76)', + marginBottom: '4px', + }, + tocLink: { + display: 'block', + fontSize: '12px', + color: 'var(--color-accent-fg, #0969da)', + textDecoration: 'none', + padding: '2px 0', + cursor: 'pointer', + }, + content: { + flex: 1, + overflowY: 'auto', + padding: '8px 12px 24px', + }, + emptyState: { + padding: '24px', + color: 'var(--color-fg-muted, #656d76)', + fontSize: '13px', + textAlign: 'center' as const, + }, + section: { + marginBottom: '20px', + }, + sectionTitle: { + fontSize: '12px', + fontWeight: 600, + textTransform: 'uppercase' as const, + letterSpacing: '0.5px', + color: 'var(--color-fg-muted, #656d76)', + paddingBottom: '4px', + borderBottom: '1px solid var(--color-border-muted, #d8dee4)', + marginBottom: '8px', + }, + entry: { + padding: '10px 12px', + marginBottom: '8px', + borderRadius: '6px', + border: '1px solid var(--color-border-default, #d0d7de)', + borderLeft: '3px solid transparent', + background: 'var(--color-bg-default, #fff)', + transition: 'border-color 0.2s ease, background 0.2s ease', + }, + entryHeader: { + display: 'flex', + alignItems: 'center', + gap: '6px', + marginBottom: '4px', + flexWrap: 'wrap' as const, + }, + entryKey: { + fontSize: '13px', + fontWeight: 600, + fontFamily: mono, + color: 'var(--color-accent-fg, #0969da)', + }, + typeBadge: { + fontSize: '10px', + fontWeight: 500, + fontFamily: mono, + padding: '1px 6px', + borderRadius: '10px', + background: 'var(--color-bg-muted, #eaeef2)', + color: 'var(--color-fg-muted, #656d76)', + }, + requiredBadge: { + fontSize: '10px', + fontWeight: 600, + padding: '1px 6px', + borderRadius: '10px', + background: 'color-mix(in srgb, var(--color-danger-fg, #d1242f) 12%, transparent)', + color: 'var(--color-danger-fg, #d1242f)', + }, + entryTitle: { + fontSize: '13px', + fontWeight: 600, + color: 'var(--color-fg-default, #1f2328)', + marginBottom: '2px', + }, + entryDesc: { + fontSize: '12px', + color: 'var(--color-fg-muted, #656d76)', + lineHeight: 1.5, + marginBottom: '6px', + }, + exampleBlock: { + fontSize: '11px', + fontFamily: mono, + lineHeight: 1.5, + padding: '6px 8px', + borderRadius: '4px', + background: 'var(--color-bg-subtle, #f6f8fa)', + border: '1px solid var(--color-border-muted, #d8dee4)', + overflow: 'auto', + marginBottom: '4px', + whiteSpace: 'pre-wrap' as const, + }, + docsLink: { + fontSize: '11px', + color: 'var(--color-accent-fg, #0969da)', + textDecoration: 'none', + fontWeight: 500, + }, +}; diff --git a/docs/editor-app/src/components/EditorView/EditorView.tsx b/docs/editor-app/src/components/EditorView/EditorView.tsx new file mode 100644 index 00000000000..a48a8f8b86a --- /dev/null +++ b/docs/editor-app/src/components/EditorView/EditorView.tsx @@ -0,0 +1,811 @@ +import { useState, useEffect, useRef, useCallback, useMemo } from 'react'; +import { Highlight, themes } from 'prism-react-renderer'; +import { AlertTriangle } from 'lucide-react'; +import { useWorkflowStore } from '../../stores/workflowStore'; +import { useUIStore } from '../../stores/uiStore'; +import { generateMarkdown } from '../../utils/markdownGenerator'; +import { compile, isCompilerReady } from '../../utils/compiler'; +import { DocsPanel } from './DocsPanel'; +import { getRefEntry } from '../../utils/referenceData'; + +/* ── Markdown tokenizer ── + * Parses markdown text into spans with type annotations for syntax highlighting. + * Handles: frontmatter delimiters, YAML keys/values inside frontmatter, headings, + * lists, bold, italic, inline code, code fences, comments, and links. + */ +type TokenType = + | 'plain' + | 'frontmatter-delimiter' + | 'frontmatter-key' + | 'frontmatter-colon' + | 'frontmatter-value' + | 'heading-marker' + | 'heading-text' + | 'list-marker' + | 'bold' + | 'italic' + | 'inline-code' + | 'code-fence' + | 'comment' + | 'link-bracket' + | 'link-text' + | 'link-paren' + | 'link-url'; + +interface MdToken { + type: TokenType; + content: string; +} + +/** Tokenize inline markdown elements within a string (bold, italic, code, links). */ +function tokenizeInline(text: string): MdToken[] { + const tokens: MdToken[] = []; + // Regex matches: inline code, bold (**), italic (*), or markdown links [text](url) + const inlineRe = /(`[^`]+`)|(\*\*[^*]+\*\*)|(\*[^*]+\*)|(\[[^\]]*\]\([^)]*\))/g; + let lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = inlineRe.exec(text)) !== null) { + if (match.index > lastIndex) { + tokens.push({ type: 'plain', content: text.slice(lastIndex, match.index) }); + } + const m = match[0]; + if (match[1]) { + // Inline code + tokens.push({ type: 'inline-code', content: m }); + } else if (match[2]) { + // Bold + tokens.push({ type: 'bold', content: m }); + } else if (match[3]) { + // Italic + tokens.push({ type: 'italic', content: m }); + } else if (match[4]) { + // Link [text](url) + const bracketEnd = m.indexOf(']'); + tokens.push({ type: 'link-bracket', content: '[' }); + tokens.push({ type: 'link-text', content: m.slice(1, bracketEnd) }); + tokens.push({ type: 'link-bracket', content: ']' }); + tokens.push({ type: 'link-paren', content: '(' }); + tokens.push({ type: 'link-url', content: m.slice(bracketEnd + 2, m.length - 1) }); + tokens.push({ type: 'link-paren', content: ')' }); + } + lastIndex = match.index + m.length; + } + + if (lastIndex < text.length) { + tokens.push({ type: 'plain', content: text.slice(lastIndex) }); + } + return tokens; +} + +/** Full-line tokenizer: returns an array of token arrays, one per line. */ +function tokenizeMarkdown(source: string): MdToken[][] { + const lines = source.split('\n'); + const result: MdToken[][] = []; + let inFrontmatter = false; + let frontmatterCount = 0; // how many --- delimiters we've seen + let inCodeBlock = false; + + for (const line of lines) { + const trimmed = line.trimEnd(); + + // Detect frontmatter delimiters (must be exactly "---" at the start) + if (!inCodeBlock && /^---\s*$/.test(trimmed)) { + if (!inFrontmatter && frontmatterCount === 0) { + // Opening delimiter + inFrontmatter = true; + frontmatterCount = 1; + result.push([{ type: 'frontmatter-delimiter', content: line }]); + continue; + } else if (inFrontmatter && frontmatterCount === 1) { + // Closing delimiter + inFrontmatter = false; + frontmatterCount = 2; + result.push([{ type: 'frontmatter-delimiter', content: line }]); + continue; + } + } + + // Inside frontmatter: highlight YAML keys and values + if (inFrontmatter) { + const keyMatch = line.match(/^(\s*)([\w][\w.-]*)(\s*:\s*)(.*)/); + if (keyMatch) { + const tokens: MdToken[] = []; + if (keyMatch[1]) tokens.push({ type: 'plain', content: keyMatch[1] }); + tokens.push({ type: 'frontmatter-key', content: keyMatch[2] }); + tokens.push({ type: 'frontmatter-colon', content: keyMatch[3] }); + if (keyMatch[4]) tokens.push({ type: 'frontmatter-value', content: keyMatch[4] }); + result.push(tokens); + } else if (/^\s*#/.test(line)) { + result.push([{ type: 'comment', content: line }]); + } else if (/^\s*-\s/.test(line)) { + // YAML list item inside frontmatter + const dashMatch = line.match(/^(\s*-\s)(.*)/); + if (dashMatch) { + result.push([ + { type: 'list-marker', content: dashMatch[1] }, + { type: 'frontmatter-value', content: dashMatch[2] }, + ]); + } else { + result.push([{ type: 'frontmatter-value', content: line }]); + } + } else { + result.push([{ type: 'frontmatter-value', content: line }]); + } + continue; + } + + // Code fence (``` or ~~~) + if (/^(`{3,}|~{3,})/.test(trimmed)) { + inCodeBlock = !inCodeBlock; + result.push([{ type: 'code-fence', content: line }]); + continue; + } + + if (inCodeBlock) { + result.push([{ type: 'inline-code', content: line }]); + continue; + } + + // Headings + const headingMatch = line.match(/^(#{1,6}\s)(.*)/); + if (headingMatch) { + const tokens: MdToken[] = [ + { type: 'heading-marker', content: headingMatch[1] }, + ]; + tokens.push(...tokenizeInline(headingMatch[2]).map(t => + t.type === 'plain' ? { ...t, type: 'heading-text' as TokenType } : t + )); + result.push(tokens); + continue; + } + + // Unordered list items + const listMatch = line.match(/^(\s*[-*+]\s)(.*)/); + if (listMatch) { + result.push([ + { type: 'list-marker', content: listMatch[1] }, + ...tokenizeInline(listMatch[2]), + ]); + continue; + } + + // Ordered list items + const orderedMatch = line.match(/^(\s*\d+\.\s)(.*)/); + if (orderedMatch) { + result.push([ + { type: 'list-marker', content: orderedMatch[1] }, + ...tokenizeInline(orderedMatch[2]), + ]); + continue; + } + + // HTML comments + if (/^\s*/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern://i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},{pattern:/^(\s*)["']|["']$/,lookbehind:!0}]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},o.languages.markup.tag.inside["attr-value"].inside.entity=o.languages.markup.entity,o.languages.markup.doctype.inside["internal-subset"].inside=o.languages.markup,o.hooks.add("wrap",function(e){e.type==="entity"&&(e.attributes.title=e.content.replace(/&/,"&"))}),Object.defineProperty(o.languages.markup.tag,"addInlined",{value:function(e,l){var a={},a=(a["language-"+l]={pattern:/(^$)/i,lookbehind:!0,inside:o.languages[l]},a.cdata=/^$/i,{"included-cdata":{pattern://i,inside:a}}),l=(a["language-"+l]={pattern:/[\s\S]+/,inside:o.languages[l]},{});l[e]={pattern:RegExp(/(<__[^>]*>)(?:))*\]\]>|(?!)/.source.replace(/__/g,function(){return e}),"i"),lookbehind:!0,greedy:!0,inside:a},o.languages.insertBefore("markup","cdata",l)}}),Object.defineProperty(o.languages.markup.tag,"addAttribute",{value:function(e,t){o.languages.markup.tag.inside["special-attr"].push({pattern:RegExp(/(^|["'\s])/.source+"(?:"+e+")"+/\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))/.source,"i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[t,"language-"+t],inside:o.languages[t]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),o.languages.html=o.languages.markup,o.languages.mathml=o.languages.markup,o.languages.svg=o.languages.markup,o.languages.xml=o.languages.extend("markup",{}),o.languages.ssml=o.languages.xml,o.languages.atom=o.languages.xml,o.languages.rss=o.languages.xml,(function(e){var t={pattern:/\\[\\(){}[\]^$+*?|.]/,alias:"escape"},a=/\\(?:x[\da-fA-F]{2}|u[\da-fA-F]{4}|u\{[\da-fA-F]+\}|0[0-7]{0,2}|[123][0-7]{2}|c[a-zA-Z]|.)/,l="(?:[^\\\\-]|"+a.source+")",l=RegExp(l+"-"+l),p={pattern:/(<|')[^<>']+(?=[>']$)/,lookbehind:!0,alias:"variable"};e.languages.regex={"char-class":{pattern:/((?:^|[^\\])(?:\\\\)*)\[(?:[^\\\]]|\\[\s\S])*\]/,lookbehind:!0,inside:{"char-class-negation":{pattern:/(^\[)\^/,lookbehind:!0,alias:"operator"},"char-class-punctuation":{pattern:/^\[|\]$/,alias:"punctuation"},range:{pattern:l,inside:{escape:a,"range-punctuation":{pattern:/-/,alias:"operator"}}},"special-escape":t,"char-set":{pattern:/\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},escape:a}},"special-escape":t,"char-set":{pattern:/\.|\\[wsd]|\\p\{[^{}]+\}/i,alias:"class-name"},backreference:[{pattern:/\\(?![123][0-7]{2})[1-9]/,alias:"keyword"},{pattern:/\\k<[^<>']+>/,alias:"keyword",inside:{"group-name":p}}],anchor:{pattern:/[$^]|\\[ABbGZz]/,alias:"function"},escape:a,group:[{pattern:/\((?:\?(?:<[^<>']+>|'[^<>']+'|[>:]|]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/},o.languages.javascript=o.languages.extend("clike",{"class-name":[o.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:{pattern:RegExp(/(^|[^\w$])/.source+"(?:"+/NaN|Infinity/.source+"|"+/0[bB][01]+(?:_[01]+)*n?/.source+"|"+/0[oO][0-7]+(?:_[0-7]+)*n?/.source+"|"+/0[xX][\dA-Fa-f]+(?:_[\dA-Fa-f]+)*n?/.source+"|"+/\d+(?:_\d+)*n/.source+"|"+/(?:\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\.\d+(?:_\d+)*)(?:[Ee][+-]?\d+(?:_\d+)*)?/.source+")"+/(?![\w$])/.source),lookbehind:!0},operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),o.languages.javascript["class-name"][0].pattern=/(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/,o.languages.insertBefore("javascript","keyword",{regex:{pattern:RegExp(/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)/.source+/\//.source+"(?:"+/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}/.source+"|"+/(?:\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.|\[(?:[^[\]\\\r\n]|\\.)*\])*\])*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}v[dgimyus]{0,7}/.source+")"+/(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/.source),lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:o.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:o.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:o.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:o.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:o.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),o.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:o.languages.javascript}},string:/[\s\S]+/}},"string-property":{pattern:/((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m,lookbehind:!0,greedy:!0,alias:"property"}}),o.languages.insertBefore("javascript","operator",{"literal-property":{pattern:/((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m,lookbehind:!0,alias:"property"}}),o.languages.markup&&(o.languages.markup.tag.addInlined("script","javascript"),o.languages.markup.tag.addAttribute(/on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)/.source,"javascript")),o.languages.js=o.languages.javascript,o.languages.actionscript=o.languages.extend("javascript",{keyword:/\b(?:as|break|case|catch|class|const|default|delete|do|dynamic|each|else|extends|final|finally|for|function|get|if|implements|import|in|include|instanceof|interface|internal|is|namespace|native|new|null|override|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|use|var|void|while|with)\b/,operator:/\+\+|--|(?:[+\-*\/%^]|&&?|\|\|?|<>?>?|[!=]=?)=?|[~?@]/}),o.languages.actionscript["class-name"].alias="function",delete o.languages.actionscript.parameter,delete o.languages.actionscript["literal-property"],o.languages.markup&&o.languages.insertBefore("actionscript","string",{xml:{pattern:/(^|[^.])<\/?\w+(?:\s+[^\s>\/=]+=("|')(?:\\[\s\S]|(?!\2)[^\\])*\2)*\s*\/?>/,lookbehind:!0,inside:o.languages.markup}}),(function(e){var t=/#(?!\{).+/,a={pattern:/#\{[^}]+\}/,alias:"variable"};e.languages.coffeescript=e.languages.extend("javascript",{comment:t,string:[{pattern:/'(?:\\[\s\S]|[^\\'])*'/,greedy:!0},{pattern:/"(?:\\[\s\S]|[^\\"])*"/,greedy:!0,inside:{interpolation:a}}],keyword:/\b(?:and|break|by|catch|class|continue|debugger|delete|do|each|else|extend|extends|false|finally|for|if|in|instanceof|is|isnt|let|loop|namespace|new|no|not|null|of|off|on|or|own|return|super|switch|then|this|throw|true|try|typeof|undefined|unless|until|when|while|window|with|yes|yield)\b/,"class-member":{pattern:/@(?!\d)\w+/,alias:"variable"}}),e.languages.insertBefore("coffeescript","comment",{"multiline-comment":{pattern:/###[\s\S]+?###/,alias:"comment"},"block-regex":{pattern:/\/{3}[\s\S]*?\/{3}/,alias:"regex",inside:{comment:t,interpolation:a}}}),e.languages.insertBefore("coffeescript","string",{"inline-javascript":{pattern:/`(?:\\[\s\S]|[^\\`])*`/,inside:{delimiter:{pattern:/^`|`$/,alias:"punctuation"},script:{pattern:/[\s\S]+/,alias:"language-javascript",inside:e.languages.javascript}}},"multiline-string":[{pattern:/'''[\s\S]*?'''/,greedy:!0,alias:"string"},{pattern:/"""[\s\S]*?"""/,greedy:!0,alias:"string",inside:{interpolation:a}}]}),e.languages.insertBefore("coffeescript","keyword",{property:/(?!\d)\w+(?=\s*:(?!:))/}),delete e.languages.coffeescript["template-string"],e.languages.coffee=e.languages.coffeescript})(o),(function(e){var t=e.languages.javadoclike={parameter:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*@(?:arg|arguments|param)\s+)\w+/m,lookbehind:!0},keyword:{pattern:/(^[\t ]*(?:\/{3}|\*|\/\*\*)\s*|\{)@[a-z][a-zA-Z-]+\b/m,lookbehind:!0},punctuation:/[{}]/};Object.defineProperty(t,"addSupport",{value:function(a,l){(a=typeof a=="string"?[a]:a).forEach(function(p){var h=function(y){y.inside||(y.inside={}),y.inside.rest=l},c="doc-comment";if(m=e.languages[p]){var m,g=m[c];if((g=g||(m=e.languages.insertBefore(p,"comment",{"doc-comment":{pattern:/(^|[^\\])\/\*\*[^/][\s\S]*?(?:\*\/|$)/,lookbehind:!0,alias:"comment"}}))[c])instanceof RegExp&&(g=m[c]={pattern:g}),Array.isArray(g))for(var d=0,b=g.length;d|\+|~|\|\|/,punctuation:/[(),]/}},e.languages.css.atrule.inside["selector-function-argument"].inside=t,e.languages.insertBefore("css","property",{variable:{pattern:/(^|[^-\w\xA0-\uFFFF])--(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*/i,lookbehind:!0}}),{pattern:/(\b\d+)(?:%|[a-z]+(?![\w-]))/,lookbehind:!0}),a={pattern:/(^|[^\w.-])-?(?:\d+(?:\.\d+)?|\.\d+)/,lookbehind:!0};e.languages.insertBefore("css","function",{operator:{pattern:/(\s)[+\-*\/](?=\s)/,lookbehind:!0},hexcode:{pattern:/\B#[\da-f]{3,8}\b/i,alias:"color"},color:[{pattern:/(^|[^\w-])(?:AliceBlue|AntiqueWhite|Aqua|Aquamarine|Azure|Beige|Bisque|Black|BlanchedAlmond|Blue|BlueViolet|Brown|BurlyWood|CadetBlue|Chartreuse|Chocolate|Coral|CornflowerBlue|Cornsilk|Crimson|Cyan|DarkBlue|DarkCyan|DarkGoldenRod|DarkGr[ae]y|DarkGreen|DarkKhaki|DarkMagenta|DarkOliveGreen|DarkOrange|DarkOrchid|DarkRed|DarkSalmon|DarkSeaGreen|DarkSlateBlue|DarkSlateGr[ae]y|DarkTurquoise|DarkViolet|DeepPink|DeepSkyBlue|DimGr[ae]y|DodgerBlue|FireBrick|FloralWhite|ForestGreen|Fuchsia|Gainsboro|GhostWhite|Gold|GoldenRod|Gr[ae]y|Green|GreenYellow|HoneyDew|HotPink|IndianRed|Indigo|Ivory|Khaki|Lavender|LavenderBlush|LawnGreen|LemonChiffon|LightBlue|LightCoral|LightCyan|LightGoldenRodYellow|LightGr[ae]y|LightGreen|LightPink|LightSalmon|LightSeaGreen|LightSkyBlue|LightSlateGr[ae]y|LightSteelBlue|LightYellow|Lime|LimeGreen|Linen|Magenta|Maroon|MediumAquaMarine|MediumBlue|MediumOrchid|MediumPurple|MediumSeaGreen|MediumSlateBlue|MediumSpringGreen|MediumTurquoise|MediumVioletRed|MidnightBlue|MintCream|MistyRose|Moccasin|NavajoWhite|Navy|OldLace|Olive|OliveDrab|Orange|OrangeRed|Orchid|PaleGoldenRod|PaleGreen|PaleTurquoise|PaleVioletRed|PapayaWhip|PeachPuff|Peru|Pink|Plum|PowderBlue|Purple|RebeccaPurple|Red|RosyBrown|RoyalBlue|SaddleBrown|Salmon|SandyBrown|SeaGreen|SeaShell|Sienna|Silver|SkyBlue|SlateBlue|SlateGr[ae]y|Snow|SpringGreen|SteelBlue|Tan|Teal|Thistle|Tomato|Transparent|Turquoise|Violet|Wheat|White|WhiteSmoke|Yellow|YellowGreen)(?![\w-])/i,lookbehind:!0},{pattern:/\b(?:hsl|rgb)\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*\)\B|\b(?:hsl|rgb)a\(\s*\d{1,3}\s*,\s*\d{1,3}%?\s*,\s*\d{1,3}%?\s*,\s*(?:0|0?\.\d+|1)\s*\)\B/i,inside:{unit:t,number:a,function:/[\w-]+(?=\()/,punctuation:/[(),]/}}],entity:/\\[\da-f]{1,8}/i,unit:t,number:a})})(o),(function(e){var t=/[*&][^\s[\]{},]+/,a=/!(?:<[\w\-%#;/?:@&=+$,.!~*'()[\]]+>|(?:[a-zA-Z\d-]*!)?[\w\-%#;/?:@&=+$.~*'()]+)?/,l="(?:"+a.source+"(?:[ ]+"+t.source+")?|"+t.source+"(?:[ ]+"+a.source+")?)",p=/(?:[^\s\x00-\x08\x0e-\x1f!"#%&'*,\-:>?@[\]`{|}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]|[?:-])(?:[ \t]*(?:(?![#:])|:))*/.source.replace(//g,function(){return/[^\s\x00-\x08\x0e-\x1f,[\]{}\x7f-\x84\x86-\x9f\ud800-\udfff\ufffe\uffff]/.source}),h=/"(?:[^"\\\r\n]|\\.)*"|'(?:[^'\\\r\n]|\\.)*'/.source;function c(m,g){g=(g||"").replace(/m/g,"")+"m";var d=/([:\-,[{]\s*(?:\s<>[ \t]+)?)(?:<>)(?=[ \t]*(?:$|,|\]|\}|(?:[\r\n]\s*)?#))/.source.replace(/<>/g,function(){return l}).replace(/<>/g,function(){return m});return RegExp(d,g)}e.languages.yaml={scalar:{pattern:RegExp(/([\-:]\s*(?:\s<>[ \t]+)?[|>])[ \t]*(?:((?:\r?\n|\r)[ \t]+)\S[^\r\n]*(?:\2[^\r\n]+)*)/.source.replace(/<>/g,function(){return l})),lookbehind:!0,alias:"string"},comment:/#.*/,key:{pattern:RegExp(/((?:^|[:\-,[{\r\n?])[ \t]*(?:<>[ \t]+)?)<>(?=\s*:\s)/.source.replace(/<>/g,function(){return l}).replace(/<>/g,function(){return"(?:"+p+"|"+h+")"})),lookbehind:!0,greedy:!0,alias:"atrule"},directive:{pattern:/(^[ \t]*)%.+/m,lookbehind:!0,alias:"important"},datetime:{pattern:c(/\d{4}-\d\d?-\d\d?(?:[tT]|[ \t]+)\d\d?:\d{2}:\d{2}(?:\.\d*)?(?:[ \t]*(?:Z|[-+]\d\d?(?::\d{2})?))?|\d{4}-\d{2}-\d{2}|\d\d?:\d{2}(?::\d{2}(?:\.\d*)?)?/.source),lookbehind:!0,alias:"number"},boolean:{pattern:c(/false|true/.source,"i"),lookbehind:!0,alias:"important"},null:{pattern:c(/null|~/.source,"i"),lookbehind:!0,alias:"important"},string:{pattern:c(h),lookbehind:!0,greedy:!0},number:{pattern:c(/[+-]?(?:0x[\da-f]+|0o[0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?|\.inf|\.nan)/.source,"i"),lookbehind:!0},tag:a,important:t,punctuation:/---|[:[\]{}\-,|>?]|\.\.\./},e.languages.yml=e.languages.yaml})(o),(function(e){var t=/(?:\\.|[^\\\n\r]|(?:\n|\r\n?)(?![\r\n]))/.source;function a(d){return d=d.replace(//g,function(){return t}),RegExp(/((?:^|[^\\])(?:\\{2})*)/.source+"(?:"+d+")")}var l=/(?:\\.|``(?:[^`\r\n]|`(?!`))+``|`[^`\r\n]+`|[^\\|\r\n`])+/.source,p=/\|?__(?:\|__)+\|?(?:(?:\n|\r\n?)|(?![\s\S]))/.source.replace(/__/g,function(){return l}),h=/\|?[ \t]*:?-{3,}:?[ \t]*(?:\|[ \t]*:?-{3,}:?[ \t]*)+\|?(?:\n|\r\n?)/.source,c=(e.languages.markdown=e.languages.extend("markup",{}),e.languages.insertBefore("markdown","prolog",{"front-matter-block":{pattern:/(^(?:\s*[\r\n])?)---(?!.)[\s\S]*?[\r\n]---(?!.)/,lookbehind:!0,greedy:!0,inside:{punctuation:/^---|---$/,"front-matter":{pattern:/\S+(?:\s+\S+)*/,alias:["yaml","language-yaml"],inside:e.languages.yaml}}},blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},table:{pattern:RegExp("^"+p+h+"(?:"+p+")*","m"),inside:{"table-data-rows":{pattern:RegExp("^("+p+h+")(?:"+p+")*$"),lookbehind:!0,inside:{"table-data":{pattern:RegExp(l),inside:e.languages.markdown},punctuation:/\|/}},"table-line":{pattern:RegExp("^("+p+")"+h+"$"),lookbehind:!0,inside:{punctuation:/\||:?-{3,}:?/}},"table-header-row":{pattern:RegExp("^"+p+"$"),inside:{"table-header":{pattern:RegExp(l),alias:"important",inside:e.languages.markdown},punctuation:/\|/}}}},code:[{pattern:/((?:^|\n)[ \t]*\n|(?:^|\r\n?)[ \t]*\r\n?)(?: {4}|\t).+(?:(?:\n|\r\n?)(?: {4}|\t).+)*/,lookbehind:!0,alias:"keyword"},{pattern:/^```[\s\S]*?^```$/m,greedy:!0,inside:{"code-block":{pattern:/^(```.*(?:\n|\r\n?))[\s\S]+?(?=(?:\n|\r\n?)^```$)/m,lookbehind:!0},"code-language":{pattern:/^(```).+/,lookbehind:!0},punctuation:/```/}}],title:[{pattern:/\S.*(?:\n|\r\n?)(?:==+|--+)(?=[ \t]*$)/m,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])(?:[\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:a(/\b__(?:(?!_)|_(?:(?!_))+_)+__\b|\*\*(?:(?!\*)|\*(?:(?!\*))+\*)+\*\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^..)[\s\S]+(?=..$)/,lookbehind:!0,inside:{}},punctuation:/\*\*|__/}},italic:{pattern:a(/\b_(?:(?!_)|__(?:(?!_))+__)+_\b|\*(?:(?!\*)|\*\*(?:(?!\*))+\*\*)+\*/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^.)[\s\S]+(?=.$)/,lookbehind:!0,inside:{}},punctuation:/[*_]/}},strike:{pattern:a(/(~~?)(?:(?!~))+\2/.source),lookbehind:!0,greedy:!0,inside:{content:{pattern:/(^~~?)[\s\S]+(?=\1$)/,lookbehind:!0,inside:{}},punctuation:/~~?/}},"code-snippet":{pattern:/(^|[^\\`])(?:``[^`\r\n]+(?:`[^`\r\n]+)*``(?!`)|`[^`\r\n]+`(?!`))/,lookbehind:!0,greedy:!0,alias:["code","keyword"]},url:{pattern:a(/!?\[(?:(?!\]))+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)|[ \t]?\[(?:(?!\]))+\])/.source),lookbehind:!0,greedy:!0,inside:{operator:/^!/,content:{pattern:/(^\[)[^\]]+(?=\])/,lookbehind:!0,inside:{}},variable:{pattern:/(^\][ \t]?\[)[^\]]+(?=\]$)/,lookbehind:!0},url:{pattern:/(^\]\()[^\s)]+/,lookbehind:!0},string:{pattern:/(^[ \t]+)"(?:\\.|[^"\\])*"(?=\)$)/,lookbehind:!0}}}}),["url","bold","italic","strike"].forEach(function(d){["url","bold","italic","strike","code-snippet"].forEach(function(b){d!==b&&(e.languages.markdown[d].inside.content.inside[b]=e.languages.markdown[b])})}),e.hooks.add("after-tokenize",function(d){d.language!=="markdown"&&d.language!=="md"||(function b(y){if(y&&typeof y!="string")for(var v=0,E=y.length;v",quot:'"'},g=String.fromCodePoint||String.fromCharCode;e.languages.md=e.languages.markdown})(o),o.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:o.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:false|true)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/\b[A-Z]\w*Input\b/,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*\{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},o.hooks.add("after-tokenize",function(e){if(e.language==="graphql")for(var t=e.tokens.filter(function(r){return typeof r!="string"&&r.type!=="comment"&&r.type!=="scalar"}),a=0;a?|<|>)?|>[>=]?|\b(?:AND|BETWEEN|DIV|ILIKE|IN|IS|LIKE|NOT|OR|REGEXP|RLIKE|SOUNDS LIKE|XOR)\b/i,punctuation:/[;[\]()`,.]/},(function(e){var t=e.languages.javascript["template-string"],a=t.pattern.source,l=t.inside.interpolation,p=l.inside["interpolation-punctuation"],h=l.pattern.source;function c(y,v){if(e.languages[y])return{pattern:RegExp("((?:"+v+")\\s*)"+a),lookbehind:!0,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},"embedded-code":{pattern:/[\s\S]+/,alias:y}}}}function m(y,v,E){return y={code:y,grammar:v,language:E},e.hooks.run("before-tokenize",y),y.tokens=e.tokenize(y.code,y.grammar),e.hooks.run("after-tokenize",y),y.tokens}function g(y,v,E){var s=e.tokenize(y,{interpolation:{pattern:RegExp(h),lookbehind:!0}}),r=0,n={},s=m(s.map(function(i){if(typeof i=="string")return i;for(var f,k,i=i.content;y.indexOf((k=r++,f="___"+E.toUpperCase()+"_"+k+"___"))!==-1;);return n[f]=i,f}).join(""),v,E),u=Object.keys(n);return r=0,(function i(f){for(var k=0;k=u.length)return;var w,A,x,_,F,D,N,I=f[k];typeof I=="string"||typeof I.content=="string"?(w=u[r],(N=(D=typeof I=="string"?I:I.content).indexOf(w))!==-1&&(++r,A=D.substring(0,N),F=n[w],x=void 0,(_={})["interpolation-punctuation"]=p,(_=e.tokenize(F,_)).length===3&&((x=[1,1]).push.apply(x,m(_[1],e.languages.javascript,"javascript")),_.splice.apply(_,x)),x=new e.Token("interpolation",_,l.alias,F),_=D.substring(N+w.length),F=[],A&&F.push(A),F.push(x),_&&(i(D=[_]),F.push.apply(F,D)),typeof I=="string"?(f.splice.apply(f,[k,1].concat(F)),k+=F.length-1):I.content=F)):(N=I.content,Array.isArray(N)?i(N):i([N]))}})(s),new e.Token(E,s,"language-"+E,y)}e.languages.javascript["template-string"]=[c("css",/\b(?:styled(?:\([^)]*\))?(?:\s*\.\s*\w+(?:\([^)]*\))*)*|css(?:\s*\.\s*(?:global|resolve))?|createGlobalStyle|keyframes)/.source),c("html",/\bhtml|\.\s*(?:inner|outer)HTML\s*\+?=/.source),c("svg",/\bsvg/.source),c("markdown",/\b(?:markdown|md)/.source),c("graphql",/\b(?:gql|graphql(?:\s*\.\s*experimental)?)/.source),c("sql",/\bsql/.source),t].filter(Boolean);var d={javascript:!0,js:!0,typescript:!0,ts:!0,jsx:!0,tsx:!0};function b(y){return typeof y=="string"?y:Array.isArray(y)?y.map(b).join(""):b(y.content)}e.hooks.add("after-tokenize",function(y){y.language in d&&(function v(E){for(var r=0,n=E.length;r]|<(?:[^<>]|<[^<>]*>)*>)*>)?/,lookbehind:!0,greedy:!0,inside:null},builtin:/\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/}),e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/,/\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/,/\btype\b(?=\s*(?:[\{*]|$))/),delete e.languages.typescript.parameter,delete e.languages.typescript["literal-property"];var t=e.languages.extend("typescript",{});delete t["class-name"],e.languages.typescript["class-name"].inside=t,e.languages.insertBefore("typescript","function",{decorator:{pattern:/@[$\w\xA0-\uFFFF]+/,inside:{at:{pattern:/^@/,alias:"operator"},function:/^[\s\S]+/}},"generic-function":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/,greedy:!0,inside:{function:/^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:t}}}}),e.languages.ts=e.languages.typescript})(o),(function(e){var t=e.languages.javascript,a=/\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})+\}/.source,l="(@(?:arg|argument|param|property)\\s+(?:"+a+"\\s+)?)";e.languages.jsdoc=e.languages.extend("javadoclike",{parameter:{pattern:RegExp(l+/(?:(?!\s)[$\w\xA0-\uFFFF.])+(?=\s|$)/.source),lookbehind:!0,inside:{punctuation:/\./}}}),e.languages.insertBefore("jsdoc","keyword",{"optional-parameter":{pattern:RegExp(l+/\[(?:(?!\s)[$\w\xA0-\uFFFF.])+(?:=[^[\]]+)?\](?=\s|$)/.source),lookbehind:!0,inside:{parameter:{pattern:/(^\[)[$\w\xA0-\uFFFF\.]+/,lookbehind:!0,inside:{punctuation:/\./}},code:{pattern:/(=)[\s\S]*(?=\]$)/,lookbehind:!0,inside:t,alias:"language-javascript"},punctuation:/[=[\]]/}},"class-name":[{pattern:RegExp(/(@(?:augments|class|extends|interface|memberof!?|template|this|typedef)\s+(?:\s+)?)[A-Z]\w*(?:\.[A-Z]\w*)*/.source.replace(//g,function(){return a})),lookbehind:!0,inside:{punctuation:/\./}},{pattern:RegExp("(@[a-z]+\\s+)"+a),lookbehind:!0,inside:{string:t.string,number:t.number,boolean:t.boolean,keyword:e.languages.typescript.keyword,operator:/=>|\.\.\.|[&|?:*]/,punctuation:/[.,;=<>{}()[\]]/}}],example:{pattern:/(@example\s+(?!\s))(?:[^@\s]|\s+(?!\s))+?(?=\s*(?:\*\s*)?(?:@\w|\*\/))/,lookbehind:!0,inside:{code:{pattern:/^([\t ]*(?:\*\s*)?)\S.*$/m,lookbehind:!0,inside:t,alias:"language-javascript"}}}}),e.languages.javadoclike.addSupport("javascript",e.languages.jsdoc)})(o),(function(e){e.languages.flow=e.languages.extend("javascript",{}),e.languages.insertBefore("flow","keyword",{type:[{pattern:/\b(?:[Bb]oolean|Function|[Nn]umber|[Ss]tring|[Ss]ymbol|any|mixed|null|void)\b/,alias:"class-name"}]}),e.languages.flow["function-variable"].pattern=/(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=\s*(?:function\b|(?:\([^()]*\)(?:\s*:\s*\w+)?|(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/i,delete e.languages.flow.parameter,e.languages.insertBefore("flow","operator",{"flow-punctuation":{pattern:/\{\||\|\}/,alias:"punctuation"}}),Array.isArray(e.languages.flow.keyword)||(e.languages.flow.keyword=[e.languages.flow.keyword]),e.languages.flow.keyword.unshift({pattern:/(^|[^$]\b)(?:Class|declare|opaque|type)\b(?!\$)/,lookbehind:!0},{pattern:/(^|[^$]\B)\$(?:Diff|Enum|Exact|Keys|ObjMap|PropertyType|Record|Shape|Subtype|Supertype|await)\b(?!\$)/,lookbehind:!0})})(o),o.languages.n4js=o.languages.extend("javascript",{keyword:/\b(?:Array|any|boolean|break|case|catch|class|const|constructor|continue|debugger|declare|default|delete|do|else|enum|export|extends|false|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|module|new|null|number|package|private|protected|public|return|set|static|string|super|switch|this|throw|true|try|typeof|var|void|while|with|yield)\b/}),o.languages.insertBefore("n4js","constant",{annotation:{pattern:/@+\w+/,alias:"operator"}}),o.languages.n4jsd=o.languages.n4js,(function(e){function t(c,m){return RegExp(c.replace(//g,function(){return/(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/.source}),m)}e.languages.insertBefore("javascript","function-variable",{"method-variable":{pattern:RegExp("(\\.\\s*)"+e.languages.javascript["function-variable"].pattern.source),lookbehind:!0,alias:["function-variable","method","function","property-access"]}}),e.languages.insertBefore("javascript","function",{method:{pattern:RegExp("(\\.\\s*)"+e.languages.javascript.function.source),lookbehind:!0,alias:["function","property-access"]}}),e.languages.insertBefore("javascript","constant",{"known-class-name":[{pattern:/\b(?:(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)?Array|ArrayBuffer|BigInt|Boolean|DataView|Date|Error|Function|Intl|JSON|(?:Weak)?(?:Map|Set)|Math|Number|Object|Promise|Proxy|Reflect|RegExp|String|Symbol|WebAssembly)\b/,alias:"class-name"},{pattern:/\b(?:[A-Z]\w*)Error\b/,alias:"class-name"}]}),e.languages.insertBefore("javascript","keyword",{imports:{pattern:t(/(\bimport\b\s*)(?:(?:\s*,\s*(?:\*\s*as\s+|\{[^{}]*\}))?|\*\s*as\s+|\{[^{}]*\})(?=\s*\bfrom\b)/.source),lookbehind:!0,inside:e.languages.javascript},exports:{pattern:t(/(\bexport\b\s*)(?:\*(?:\s*as\s+)?(?=\s*\bfrom\b)|\{[^{}]*\})/.source),lookbehind:!0,inside:e.languages.javascript}}),e.languages.javascript.keyword.unshift({pattern:/\b(?:as|default|export|from|import)\b/,alias:"module"},{pattern:/\b(?:await|break|catch|continue|do|else|finally|for|if|return|switch|throw|try|while|yield)\b/,alias:"control-flow"},{pattern:/\bnull\b/,alias:["null","nil"]},{pattern:/\bundefined\b/,alias:"nil"}),e.languages.insertBefore("javascript","operator",{spread:{pattern:/\.{3}/,alias:"operator"},arrow:{pattern:/=>/,alias:"operator"}}),e.languages.insertBefore("javascript","punctuation",{"property-access":{pattern:t(/(\.\s*)#?/.source),lookbehind:!0},"maybe-class-name":{pattern:/(^|[^$\w\xA0-\uFFFF])[A-Z][$\w\xA0-\uFFFF]+/,lookbehind:!0},dom:{pattern:/\b(?:document|(?:local|session)Storage|location|navigator|performance|window)\b/,alias:"variable"},console:{pattern:/\bconsole(?=\s*\.)/,alias:"class-name"}});for(var a=["function","function-variable","method","method-variable","property-access"],l=0;l*\.{3}(?:[^{}]|)*\})/.source;function h(g,d){return g=g.replace(//g,function(){return a}).replace(//g,function(){return l}).replace(//g,function(){return p}),RegExp(g,d)}p=h(p).source,e.languages.jsx=e.languages.extend("markup",t),e.languages.jsx.tag.pattern=h(/<\/?(?:[\w.:-]+(?:+(?:[\w.:$-]+(?:=(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s{'"/>=]+|))?|))**\/?)?>/.source),e.languages.jsx.tag.inside.tag.pattern=/^<\/?[^\s>\/]*/,e.languages.jsx.tag.inside["attr-value"].pattern=/=(?!\{)(?:"(?:\\[\s\S]|[^\\"])*"|'(?:\\[\s\S]|[^\\'])*'|[^\s'">]+)/,e.languages.jsx.tag.inside.tag.inside["class-name"]=/^[A-Z]\w*(?:\.[A-Z]\w*)*$/,e.languages.jsx.tag.inside.comment=t.comment,e.languages.insertBefore("inside","attr-name",{spread:{pattern:h(//.source),inside:e.languages.jsx}},e.languages.jsx.tag),e.languages.insertBefore("inside","special-attr",{script:{pattern:h(/=/.source),alias:"language-javascript",inside:{"script-punctuation":{pattern:/^=(?=\{)/,alias:"punctuation"},rest:e.languages.jsx}}},e.languages.jsx.tag);function c(g){for(var d=[],b=0;b"&&d.push({tagName:m(y.content[0].content[1]),openedBraces:0}):0]=?/,punctuation:/[(),]/}},literal:{pattern:/#(?:colorLiteral|column|dsohandle|file(?:ID|Literal|Path)?|function|imageLiteral|line)\b/,alias:"constant"},"other-directive":{pattern:/#\w+\b/,alias:"property"},attribute:{pattern:/@\w+/,alias:"atrule"},"function-definition":{pattern:/(\bfunc\s+)\w+/,lookbehind:!0,alias:"function"},label:{pattern:/\b(break|continue)\s+\w+|\b[a-zA-Z_]\w*(?=\s*:\s*(?:for|repeat|while)\b)/,lookbehind:!0,alias:"important"},keyword:/\b(?:Any|Protocol|Self|Type|actor|as|assignment|associatedtype|associativity|async|await|break|case|catch|class|continue|convenience|default|defer|deinit|didSet|do|dynamic|else|enum|extension|fallthrough|fileprivate|final|for|func|get|guard|higherThan|if|import|in|indirect|infix|init|inout|internal|is|isolated|lazy|left|let|lowerThan|mutating|none|nonisolated|nonmutating|open|operator|optional|override|postfix|precedencegroup|prefix|private|protocol|public|repeat|required|rethrows|return|right|safe|self|set|some|static|struct|subscript|super|switch|throw|throws|try|typealias|unowned|unsafe|var|weak|where|while|willSet)\b/,boolean:/\b(?:false|true)\b/,nil:{pattern:/\bnil\b/,alias:"constant"},"short-argument":/\$\d+\b/,omit:{pattern:/\b_\b/,alias:"keyword"},number:/\b(?:[\d_]+(?:\.[\de_]+)?|0x[a-f0-9_]+(?:\.[a-f0-9p_]+)?|0b[01_]+|0o[0-7_]+)\b/i,"class-name":/\b[A-Z](?:[A-Z_\d]*[a-z]\w*)?\b/,function:/\b[a-z_]\w*(?=\s*\()/i,constant:/\b(?:[A-Z_]{2,}|k[A-Z][A-Za-z_]+)\b/,operator:/[-+*/%=!<>&|^~?]+|\.[.\-+*/%=!<>&|^~?]+/,punctuation:/[{}[\]();,.:\\]/},o.languages.swift["string-literal"].forEach(function(e){e.inside.interpolation.inside=o.languages.swift}),(function(e){e.languages.kotlin=e.languages.extend("clike",{keyword:{pattern:/(^|[^.])\b(?:abstract|actual|annotation|as|break|by|catch|class|companion|const|constructor|continue|crossinline|data|do|dynamic|else|enum|expect|external|final|finally|for|fun|get|if|import|in|infix|init|inline|inner|interface|internal|is|lateinit|noinline|null|object|open|operator|out|override|package|private|protected|public|reified|return|sealed|set|super|suspend|tailrec|this|throw|to|try|typealias|val|var|vararg|when|where|while)\b/,lookbehind:!0},function:[{pattern:/(?:`[^\r\n`]+`|\b\w+)(?=\s*\()/,greedy:!0},{pattern:/(\.)(?:`[^\r\n`]+`|\w+)(?=\s*\{)/,lookbehind:!0,greedy:!0}],number:/\b(?:0[xX][\da-fA-F]+(?:_[\da-fA-F]+)*|0[bB][01]+(?:_[01]+)*|\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?[fFL]?)\b/,operator:/\+[+=]?|-[-=>]?|==?=?|!(?:!|==?)?|[\/*%<>]=?|[?:]:?|\.\.|&&|\|\||\b(?:and|inv|or|shl|shr|ushr|xor)\b/}),delete e.languages.kotlin["class-name"];var t={"interpolation-punctuation":{pattern:/^\$\{?|\}$/,alias:"punctuation"},expression:{pattern:/[\s\S]+/,inside:e.languages.kotlin}};e.languages.insertBefore("kotlin","string",{"string-literal":[{pattern:/"""(?:[^$]|\$(?:(?!\{)|\{[^{}]*\}))*?"""/,alias:"multiline",inside:{interpolation:{pattern:/\$(?:[a-z_]\w*|\{[^{}]*\})/i,inside:t},string:/[\s\S]+/}},{pattern:/"(?:[^"\\\r\n$]|\\.|\$(?:(?!\{)|\{[^{}]*\}))*"/,alias:"singleline",inside:{interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$(?:[a-z_]\w*|\{[^{}]*\})/i,lookbehind:!0,inside:t},string:/[\s\S]+/}}],char:{pattern:/'(?:[^'\\\r\n]|\\(?:.|u[a-fA-F0-9]{0,4}))'/,greedy:!0}}),delete e.languages.kotlin.string,e.languages.insertBefore("kotlin","keyword",{annotation:{pattern:/\B@(?:\w+:)?(?:[A-Z]\w*|\[[^\]]+\])/,alias:"builtin"}}),e.languages.insertBefore("kotlin","function",{label:{pattern:/\b\w+@|@\w+\b/,alias:"symbol"}}),e.languages.kt=e.languages.kotlin,e.languages.kts=e.languages.kotlin})(o),o.languages.c=o.languages.extend("clike",{comment:{pattern:/\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},"class-name":{pattern:/(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/,lookbehind:!0},keyword:/\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/,function:/\b[a-z_]\w*(?=\s*\()/i,number:/(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i,operator:/>>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/}),o.languages.insertBefore("c","string",{char:{pattern:/'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/,greedy:!0}}),o.languages.insertBefore("c","string",{macro:{pattern:/(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im,lookbehind:!0,greedy:!0,alias:"property",inside:{string:[{pattern:/^(#\s*include\s*)<[^>]+>/,lookbehind:!0},o.languages.c.string],char:o.languages.c.char,comment:o.languages.c.comment,"macro-name":[{pattern:/(^#\s*define\s+)\w+\b(?!\()/i,lookbehind:!0},{pattern:/(^#\s*define\s+)\w+\b(?=\()/i,lookbehind:!0,alias:"function"}],directive:{pattern:/^(#\s*)[a-z]+/,lookbehind:!0,alias:"keyword"},"directive-hash":/^#/,punctuation:/##|\\(?=[\r\n])/,expression:{pattern:/\S[\s\S]*/,inside:o.languages.c}}}}),o.languages.insertBefore("c","function",{constant:/\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/}),delete o.languages.c.boolean,o.languages.objectivec=o.languages.extend("c",{string:{pattern:/@?"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/,greedy:!0},keyword:/\b(?:asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|in|inline|int|long|register|return|self|short|signed|sizeof|static|struct|super|switch|typedef|typeof|union|unsigned|void|volatile|while)\b|(?:@interface|@end|@implementation|@protocol|@class|@public|@protected|@private|@property|@try|@catch|@finally|@throw|@synthesize|@dynamic|@selector)\b/,operator:/-[->]?|\+\+?|!=?|<>?=?|==?|&&?|\|\|?|[~^%?*\/@]/}),delete o.languages.objectivec["class-name"],o.languages.objc=o.languages.objectivec,o.languages.reason=o.languages.extend("clike",{string:{pattern:/"(?:\\(?:\r\n|[\s\S])|[^\\\r\n"])*"/,greedy:!0},"class-name":/\b[A-Z]\w*/,keyword:/\b(?:and|as|assert|begin|class|constraint|do|done|downto|else|end|exception|external|for|fun|function|functor|if|in|include|inherit|initializer|lazy|let|method|module|mutable|new|nonrec|object|of|open|or|private|rec|sig|struct|switch|then|to|try|type|val|virtual|when|while|with)\b/,operator:/\.{3}|:[:=]|\|>|->|=(?:==?|>)?|<=?|>=?|[|^?'#!~`]|[+\-*\/]\.?|\b(?:asr|land|lor|lsl|lsr|lxor|mod)\b/}),o.languages.insertBefore("reason","class-name",{char:{pattern:/'(?:\\x[\da-f]{2}|\\o[0-3][0-7][0-7]|\\\d{3}|\\.|[^'\\\r\n])'/,greedy:!0},constructor:/\b[A-Z]\w*\b(?!\s*\.)/,label:{pattern:/\b[a-z]\w*(?=::)/,alias:"symbol"}}),delete o.languages.reason.function,(function(e){for(var t=/\/\*(?:[^*/]|\*(?!\/)|\/(?!\*)|)*\*\//.source,a=0;a<2;a++)t=t.replace(//g,function(){return t});t=t.replace(//g,function(){return/[^\s\S]/.source}),e.languages.rust={comment:[{pattern:RegExp(/(^|[^\\])/.source+t),lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/,greedy:!0},char:{pattern:/b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/,greedy:!0},attribute:{pattern:/#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/,greedy:!0,alias:"attr-name",inside:{string:null}},"closure-params":{pattern:/([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/,lookbehind:!0,greedy:!0,inside:{"closure-punctuation":{pattern:/^\||\|$/,alias:"punctuation"},rest:null}},"lifetime-annotation":{pattern:/'\w+/,alias:"symbol"},"fragment-specifier":{pattern:/(\$\w+:)[a-z]+/,lookbehind:!0,alias:"punctuation"},variable:/\$\w+/,"function-definition":{pattern:/(\bfn\s+)\w+/,lookbehind:!0,alias:"function"},"type-definition":{pattern:/(\b(?:enum|struct|trait|type|union)\s+)\w+/,lookbehind:!0,alias:"class-name"},"module-declaration":[{pattern:/(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/,lookbehind:!0,alias:"namespace"},{pattern:/(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/,lookbehind:!0,alias:"namespace",inside:{punctuation:/::/}}],keyword:[/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/,/\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/],function:/\b[a-z_]\w*(?=\s*(?:::\s*<|\())/,macro:{pattern:/\b\w+!/,alias:"property"},constant:/\b[A-Z_][A-Z_\d]+\b/,"class-name":/\b[A-Z]\w*\b/,namespace:{pattern:/(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/,inside:{punctuation:/::/}},number:/\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/,boolean:/\b(?:false|true)\b/,punctuation:/->|\.\.=|\.{1,3}|::|[{}[\];(),:]/,operator:/[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/},e.languages.rust["closure-params"].inside.rest=e.languages.rust,e.languages.rust.attribute.inside.string=e.languages.rust.string})(o),o.languages.go=o.languages.extend("clike",{string:{pattern:/(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/,lookbehind:!0,greedy:!0},keyword:/\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/,boolean:/\b(?:_|false|iota|nil|true)\b/,number:[/\b0(?:b[01_]+|o[0-7_]+)i?\b/i,/\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i,/(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i],operator:/[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./,builtin:/\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/}),o.languages.insertBefore("go","string",{char:{pattern:/'(?:\\.|[^'\\\r\n]){0,10}'/,greedy:!0}}),delete o.languages.go["class-name"],(function(e){var t=/\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/,a=/\b(?!)\w+(?:\s*\.\s*\w+)*\b/.source.replace(//g,function(){return t.source});e.languages.cpp=e.languages.extend("c",{"class-name":[{pattern:RegExp(/(\b(?:class|concept|enum|struct|typename)\s+)(?!)\w+/.source.replace(//g,function(){return t.source})),lookbehind:!0},/\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/,/\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i,/\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/],keyword:t,number:{pattern:/(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i,greedy:!0},operator:/>>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/,boolean:/\b(?:false|true)\b/}),e.languages.insertBefore("cpp","string",{module:{pattern:RegExp(/(\b(?:import|module)\s+)/.source+"(?:"+/"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|<[^<>\r\n]*>/.source+"|"+/(?:\s*:\s*)?|:\s*/.source.replace(//g,function(){return a})+")"),lookbehind:!0,greedy:!0,inside:{string:/^[<"][\s\S]+/,operator:/:/,punctuation:/\./}},"raw-string":{pattern:/R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/,alias:"string",greedy:!0}}),e.languages.insertBefore("cpp","keyword",{"generic-function":{pattern:/\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i,inside:{function:/^\w+/,generic:{pattern:/<[\s\S]+/,alias:"class-name",inside:e.languages.cpp}}}}),e.languages.insertBefore("cpp","operator",{"double-colon":{pattern:/::/,alias:"punctuation"}}),e.languages.insertBefore("cpp","class-name",{"base-clause":{pattern:/(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/,lookbehind:!0,greedy:!0,inside:e.languages.extend("cpp",{})}}),e.languages.insertBefore("inside","double-colon",{"class-name":/\b[a-z_]\w*\b(?!\s*::)/i},e.languages.cpp["base-clause"])})(o),o.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0,greedy:!0},"string-interpolation":{pattern:/(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/m,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:False|None|True)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i,operator:/[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},o.languages.python["string-interpolation"].inside.interpolation.inside.rest=o.languages.python,o.languages.py=o.languages.python,o.languages.json={property:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?=\s*:)/,lookbehind:!0,greedy:!0},string:{pattern:/(^|[^\\])"(?:\\.|[^\\"\r\n])*"(?!\s*:)/,lookbehind:!0,greedy:!0},comment:{pattern:/\/\/.*|\/\*[\s\S]*?(?:\*\/|$)/,greedy:!0},number:/-?\b\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,punctuation:/[{}[\],]/,operator:/:/,boolean:/\b(?:false|true)\b/,null:{pattern:/\bnull\b/,alias:"keyword"}},o.languages.webmanifest=o.languages.json;var ke={};be(ke,{dracula:()=>Ee,duotoneDark:()=>Ae,duotoneLight:()=>_e,github:()=>Te,gruvboxMaterialDark:()=>nt,gruvboxMaterialLight:()=>ot,jettwaveDark:()=>Ke,jettwaveLight:()=>Xe,nightOwl:()=>Ie,nightOwlLight:()=>Le,oceanicNext:()=>De,okaidia:()=>Be,oneDark:()=>Je,oneLight:()=>tt,palenight:()=>Pe,shadesOfPurple:()=>ze,synthwave84:()=>Ue,ultramin:()=>Ze,vsDark:()=>le,vsLight:()=>He});var we={plain:{color:"#F8F8F2",backgroundColor:"#282A36"},styles:[{types:["prolog","constant","builtin"],style:{color:"rgb(189, 147, 249)"}},{types:["inserted","function"],style:{color:"rgb(80, 250, 123)"}},{types:["deleted"],style:{color:"rgb(255, 85, 85)"}},{types:["changed"],style:{color:"rgb(255, 184, 108)"}},{types:["punctuation","symbol"],style:{color:"rgb(248, 248, 242)"}},{types:["string","char","tag","selector"],style:{color:"rgb(255, 121, 198)"}},{types:["keyword","variable"],style:{color:"rgb(189, 147, 249)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(98, 114, 164)"}},{types:["attr-name"],style:{color:"rgb(241, 250, 140)"}}]},Ee=we,xe={plain:{backgroundColor:"#2a2734",color:"#9a86fd"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#6c6783"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#e09142"}},{types:["property","function"],style:{color:"#9a86fd"}},{types:["tag-id","selector","atrule-id"],style:{color:"#eeebff"}},{types:["attr-name"],style:{color:"#c4b9fe"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule","placeholder","variable"],style:{color:"#ffcc99"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#c4b9fe"}}]},Ae=xe,Se={plain:{backgroundColor:"#faf8f5",color:"#728fcb"},styles:[{types:["comment","prolog","doctype","cdata","punctuation"],style:{color:"#b6ad9a"}},{types:["namespace"],style:{opacity:.7}},{types:["tag","operator","number"],style:{color:"#063289"}},{types:["property","function"],style:{color:"#b29762"}},{types:["tag-id","selector","atrule-id"],style:{color:"#2d2006"}},{types:["attr-name"],style:{color:"#896724"}},{types:["boolean","string","entity","url","attr-value","keyword","control","directive","unit","statement","regex","atrule"],style:{color:"#728fcb"}},{types:["placeholder","variable"],style:{color:"#93abdc"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"#896724"}}]},_e=Se,Fe={plain:{color:"#393A34",backgroundColor:"#f6f8fa"},styles:[{types:["comment","prolog","doctype","cdata"],style:{color:"#999988",fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}},{types:["string","attr-value"],style:{color:"#e3116c"}},{types:["punctuation","operator"],style:{color:"#393A34"}},{types:["entity","url","symbol","number","boolean","variable","constant","property","regex","inserted"],style:{color:"#36acaa"}},{types:["atrule","keyword","attr-name","selector"],style:{color:"#00a4db"}},{types:["function","deleted","tag"],style:{color:"#d73a49"}},{types:["function-variable"],style:{color:"#6f42c1"}},{types:["tag","selector","keyword"],style:{color:"#00009f"}}]},Te=Fe,Re={plain:{color:"#d6deeb",backgroundColor:"#011627"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(99, 119, 119)",fontStyle:"italic"}},{types:["string","url"],style:{color:"rgb(173, 219, 103)"}},{types:["variable"],style:{color:"rgb(214, 222, 235)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation"],style:{color:"rgb(199, 146, 234)"}},{types:["selector","doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(255, 203, 139)"}},{types:["tag","operator","keyword"],style:{color:"rgb(127, 219, 202)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["property"],style:{color:"rgb(128, 203, 196)"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}}]},Ie=Re,Oe={plain:{color:"#403f53",backgroundColor:"#FBFBFB"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)",fontStyle:"italic"}},{types:["inserted","attr-name"],style:{color:"rgb(72, 118, 214)",fontStyle:"italic"}},{types:["comment"],style:{color:"rgb(152, 159, 177)",fontStyle:"italic"}},{types:["string","builtin","char","constant","url"],style:{color:"rgb(72, 118, 214)"}},{types:["variable"],style:{color:"rgb(201, 103, 101)"}},{types:["number"],style:{color:"rgb(170, 9, 130)"}},{types:["punctuation"],style:{color:"rgb(153, 76, 195)"}},{types:["function","selector","doctype"],style:{color:"rgb(153, 76, 195)",fontStyle:"italic"}},{types:["class-name"],style:{color:"rgb(17, 17, 17)"}},{types:["tag"],style:{color:"rgb(153, 76, 195)"}},{types:["operator","property","keyword","namespace"],style:{color:"rgb(12, 150, 155)"}},{types:["boolean"],style:{color:"rgb(188, 84, 84)"}}]},Le=Oe,T={char:"#D8DEE9",comment:"#999999",keyword:"#c5a5c5",primitive:"#5a9bcf",string:"#8dc891",variable:"#d7deea",boolean:"#ff8b50",tag:"#fc929e",function:"#79b6f2",className:"#FAC863"},Ne={plain:{backgroundColor:"#282c34",color:"#ffffff"},styles:[{types:["attr-name"],style:{color:T.keyword}},{types:["attr-value"],style:{color:T.string}},{types:["comment","block-comment","prolog","doctype","cdata","shebang"],style:{color:T.comment}},{types:["property","number","function-name","constant","symbol","deleted"],style:{color:T.primitive}},{types:["boolean"],style:{color:T.boolean}},{types:["tag"],style:{color:T.tag}},{types:["string"],style:{color:T.string}},{types:["punctuation"],style:{color:T.string}},{types:["selector","char","builtin","inserted"],style:{color:T.char}},{types:["function"],style:{color:T.function}},{types:["operator","entity","url","variable"],style:{color:T.variable}},{types:["keyword"],style:{color:T.keyword}},{types:["atrule","class-name"],style:{color:T.className}},{types:["important"],style:{fontWeight:"400"}},{types:["bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["namespace"],style:{opacity:.7}}]},De=Ne,Ce={plain:{color:"#f8f8f2",backgroundColor:"#272822"},styles:[{types:["changed"],style:{color:"rgb(162, 191, 252)",fontStyle:"italic"}},{types:["deleted"],style:{color:"#f92672",fontStyle:"italic"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)",fontStyle:"italic"}},{types:["comment"],style:{color:"#8292a2",fontStyle:"italic"}},{types:["string","url"],style:{color:"#a6e22e"}},{types:["variable"],style:{color:"#f8f8f2"}},{types:["number"],style:{color:"#ae81ff"}},{types:["builtin","char","constant","function","class-name"],style:{color:"#e6db74"}},{types:["punctuation"],style:{color:"#f8f8f2"}},{types:["selector","doctype"],style:{color:"#a6e22e",fontStyle:"italic"}},{types:["tag","operator","keyword"],style:{color:"#66d9ef"}},{types:["boolean"],style:{color:"#ae81ff"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)",opacity:.7}},{types:["tag","property"],style:{color:"#f92672"}},{types:["attr-name"],style:{color:"#a6e22e !important"}},{types:["doctype"],style:{color:"#8292a2"}},{types:["rule"],style:{color:"#e6db74"}}]},Be=Ce,$e={plain:{color:"#bfc7d5",backgroundColor:"#292d3e"},styles:[{types:["comment"],style:{color:"rgb(105, 112, 152)",fontStyle:"italic"}},{types:["string","inserted"],style:{color:"rgb(195, 232, 141)"}},{types:["number"],style:{color:"rgb(247, 140, 108)"}},{types:["builtin","char","constant","function"],style:{color:"rgb(130, 170, 255)"}},{types:["punctuation","selector"],style:{color:"rgb(199, 146, 234)"}},{types:["variable"],style:{color:"rgb(191, 199, 213)"}},{types:["class-name","attr-name"],style:{color:"rgb(255, 203, 107)"}},{types:["tag","deleted"],style:{color:"rgb(255, 85, 114)"}},{types:["operator"],style:{color:"rgb(137, 221, 255)"}},{types:["boolean"],style:{color:"rgb(255, 88, 116)"}},{types:["keyword"],style:{fontStyle:"italic"}},{types:["doctype"],style:{color:"rgb(199, 146, 234)",fontStyle:"italic"}},{types:["namespace"],style:{color:"rgb(178, 204, 214)"}},{types:["url"],style:{color:"rgb(221, 221, 221)"}}]},Pe=$e,je={plain:{color:"#9EFEFF",backgroundColor:"#2D2A55"},styles:[{types:["changed"],style:{color:"rgb(255, 238, 128)"}},{types:["deleted"],style:{color:"rgba(239, 83, 80, 0.56)"}},{types:["inserted"],style:{color:"rgb(173, 219, 103)"}},{types:["comment"],style:{color:"rgb(179, 98, 255)",fontStyle:"italic"}},{types:["punctuation"],style:{color:"rgb(255, 255, 255)"}},{types:["constant"],style:{color:"rgb(255, 98, 140)"}},{types:["string","url"],style:{color:"rgb(165, 255, 144)"}},{types:["variable"],style:{color:"rgb(255, 238, 128)"}},{types:["number","boolean"],style:{color:"rgb(255, 98, 140)"}},{types:["attr-name"],style:{color:"rgb(255, 180, 84)"}},{types:["keyword","operator","property","namespace","tag","selector","doctype"],style:{color:"rgb(255, 157, 0)"}},{types:["builtin","char","constant","function","class-name"],style:{color:"rgb(250, 208, 0)"}}]},ze=je,Me={plain:{backgroundColor:"linear-gradient(to bottom, #2a2139 75%, #34294f)",backgroundImage:"#34294f",color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"},styles:[{types:["comment","block-comment","prolog","doctype","cdata"],style:{color:"#495495",fontStyle:"italic"}},{types:["punctuation"],style:{color:"#ccc"}},{types:["tag","attr-name","namespace","number","unit","hexcode","deleted"],style:{color:"#e2777a"}},{types:["property","selector"],style:{color:"#72f1b8",textShadow:"0 0 2px #100c0f, 0 0 10px #257c5575, 0 0 35px #21272475"}},{types:["function-name"],style:{color:"#6196cc"}},{types:["boolean","selector-id","function"],style:{color:"#fdfdfd",textShadow:"0 0 2px #001716, 0 0 3px #03edf975, 0 0 5px #03edf975, 0 0 8px #03edf975"}},{types:["class-name","maybe-class-name","builtin"],style:{color:"#fff5f6",textShadow:"0 0 2px #000, 0 0 10px #fc1f2c75, 0 0 5px #fc1f2c75, 0 0 25px #fc1f2c75"}},{types:["constant","symbol"],style:{color:"#f92aad",textShadow:"0 0 2px #100c0f, 0 0 5px #dc078e33, 0 0 10px #fff3"}},{types:["important","atrule","keyword","selector-class"],style:{color:"#f4eee4",textShadow:"0 0 2px #393a33, 0 0 8px #f39f0575, 0 0 2px #f39f0575"}},{types:["string","char","attr-value","regex","variable"],style:{color:"#f87c32"}},{types:["parameter"],style:{fontStyle:"italic"}},{types:["entity","url"],style:{color:"#67cdcc"}},{types:["operator"],style:{color:"ffffffee"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["entity"],style:{cursor:"help"}},{types:["inserted"],style:{color:"green"}}]},Ue=Me,Ge={plain:{color:"#282a2e",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(197, 200, 198)"}},{types:["string","number","builtin","variable"],style:{color:"rgb(150, 152, 150)"}},{types:["class-name","function","tag","attr-name"],style:{color:"rgb(40, 42, 46)"}}]},Ze=Ge,qe={plain:{color:"#9CDCFE",backgroundColor:"#1E1E1E"},styles:[{types:["prolog"],style:{color:"rgb(0, 0, 128)"}},{types:["comment"],style:{color:"rgb(106, 153, 85)"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"rgb(86, 156, 214)"}},{types:["number","inserted"],style:{color:"rgb(181, 206, 168)"}},{types:["constant"],style:{color:"rgb(100, 102, 149)"}},{types:["attr-name","variable"],style:{color:"rgb(156, 220, 254)"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"rgb(206, 145, 120)"}},{types:["selector"],style:{color:"rgb(215, 186, 125)"}},{types:["tag"],style:{color:"rgb(78, 201, 176)"}},{types:["tag"],languages:["markup"],style:{color:"rgb(86, 156, 214)"}},{types:["punctuation","operator"],style:{color:"rgb(212, 212, 212)"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"rgb(220, 220, 170)"}},{types:["class-name"],style:{color:"rgb(78, 201, 176)"}},{types:["char"],style:{color:"rgb(209, 105, 105)"}}]},le=qe,We={plain:{color:"#000000",backgroundColor:"#ffffff"},styles:[{types:["comment"],style:{color:"rgb(0, 128, 0)"}},{types:["builtin"],style:{color:"rgb(0, 112, 193)"}},{types:["number","variable","inserted"],style:{color:"rgb(9, 134, 88)"}},{types:["operator"],style:{color:"rgb(0, 0, 0)"}},{types:["constant","char"],style:{color:"rgb(129, 31, 63)"}},{types:["tag"],style:{color:"rgb(128, 0, 0)"}},{types:["attr-name"],style:{color:"rgb(255, 0, 0)"}},{types:["deleted","string"],style:{color:"rgb(163, 21, 21)"}},{types:["changed","punctuation"],style:{color:"rgb(4, 81, 165)"}},{types:["function","keyword"],style:{color:"rgb(0, 0, 255)"}},{types:["class-name"],style:{color:"rgb(38, 127, 153)"}}]},He=We,Ye={plain:{color:"#f8fafc",backgroundColor:"#011627"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#569CD6"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#f8fafc"}},{types:["attr-name","variable"],style:{color:"#9CDCFE"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#cbd5e1"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#D4D4D4"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#7dd3fc"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},Ke=Ye,Ve={plain:{color:"#0f172a",backgroundColor:"#f1f5f9"},styles:[{types:["prolog"],style:{color:"#000080"}},{types:["comment"],style:{color:"#6A9955"}},{types:["builtin","changed","keyword","interpolation-punctuation"],style:{color:"#0c4a6e"}},{types:["number","inserted"],style:{color:"#B5CEA8"}},{types:["constant"],style:{color:"#0f172a"}},{types:["attr-name","variable"],style:{color:"#0c4a6e"}},{types:["deleted","string","attr-value","template-punctuation"],style:{color:"#64748b"}},{types:["selector"],style:{color:"#D7BA7D"}},{types:["tag"],style:{color:"#0ea5e9"}},{types:["tag"],languages:["markup"],style:{color:"#0ea5e9"}},{types:["punctuation","operator"],style:{color:"#475569"}},{types:["punctuation"],languages:["markup"],style:{color:"#808080"}},{types:["function"],style:{color:"#0e7490"}},{types:["class-name"],style:{color:"#0ea5e9"}},{types:["char"],style:{color:"#D16969"}}]},Xe=Ve,Qe={plain:{backgroundColor:"hsl(220, 13%, 18%)",color:"hsl(220, 14%, 71%)",textShadow:"0 1px rgba(0, 0, 0, 0.3)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(220, 10%, 40%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(220, 14%, 71%)"}},{types:["attr-name","class-name","maybe-class-name","boolean","constant","number","atrule"],style:{color:"hsl(29, 54%, 61%)"}},{types:["keyword"],style:{color:"hsl(286, 60%, 67%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(355, 65%, 65%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value"],style:{color:"hsl(95, 38%, 62%)"}},{types:["variable","operator","function"],style:{color:"hsl(207, 82%, 66%)"}},{types:["url"],style:{color:"hsl(187, 47%, 55%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(220, 14%, 71%)"}}]},Je=Qe,et={plain:{backgroundColor:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)"},styles:[{types:["comment","prolog","cdata"],style:{color:"hsl(230, 4%, 64%)"}},{types:["doctype","punctuation","entity"],style:{color:"hsl(230, 8%, 24%)"}},{types:["attr-name","class-name","boolean","constant","number","atrule"],style:{color:"hsl(35, 99%, 36%)"}},{types:["keyword"],style:{color:"hsl(301, 63%, 40%)"}},{types:["property","tag","symbol","deleted","important"],style:{color:"hsl(5, 74%, 59%)"}},{types:["selector","string","char","builtin","inserted","regex","attr-value","punctuation"],style:{color:"hsl(119, 34%, 47%)"}},{types:["variable","operator","function"],style:{color:"hsl(221, 87%, 60%)"}},{types:["url"],style:{color:"hsl(198, 99%, 37%)"}},{types:["deleted"],style:{textDecorationLine:"line-through"}},{types:["inserted"],style:{textDecorationLine:"underline"}},{types:["italic"],style:{fontStyle:"italic"}},{types:["important","bold"],style:{fontWeight:"bold"}},{types:["important"],style:{color:"hsl(230, 8%, 24%)"}}]},tt=et,at={plain:{color:"#ebdbb2",backgroundColor:"#292828"},styles:[{types:["imports","class-name","maybe-class-name","constant","doctype","builtin","function"],style:{color:"#d8a657"}},{types:["property-access"],style:{color:"#7daea3"}},{types:["tag"],style:{color:"#e78a4e"}},{types:["attr-name","char","url","regex"],style:{color:"#a9b665"}},{types:["attr-value","string"],style:{color:"#89b482"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#ea6962"}},{types:["entity","number","symbol"],style:{color:"#d3869b"}}]},nt=at,rt={plain:{color:"#654735",backgroundColor:"#f9f5d7"},styles:[{types:["delimiter","boolean","keyword","selector","important","atrule","property","variable","deleted"],style:{color:"#af2528"}},{types:["imports","class-name","maybe-class-name","constant","doctype","builtin"],style:{color:"#b4730e"}},{types:["string","attr-value"],style:{color:"#477a5b"}},{types:["property-access"],style:{color:"#266b79"}},{types:["function","attr-name","char","url"],style:{color:"#72761e"}},{types:["tag"],style:{color:"#b94c07"}},{types:["comment","prolog","cdata","operator","inserted"],style:{color:"#a89984"}},{types:["entity","number","symbol"],style:{color:"#924f79"}}]},ot=rt,st=e=>P.useCallback(t=>{var a=t,{className:l,style:p,line:h}=a,c=se(a,["className","style","line"]);const m=q(L({},c),{className:ne("token-line",l)});return typeof e=="object"&&"plain"in e&&(m.style=e.plain),typeof p=="object"&&(m.style=L(L({},m.style||{}),p)),m},[e]),lt=e=>{const t=P.useCallback(({types:a,empty:l})=>{if(e!=null){{if(a.length===1&&a[0]==="plain")return l!=null?{display:"inline-block"}:void 0;if(a.length===1&&l!=null)return e[a[0]]}return Object.assign(l!=null?{display:"inline-block"}:{},...a.map(p=>e[p]))}},[e]);return P.useCallback(a=>{var l=a,{token:p,className:h,style:c}=l,m=se(l,["token","className","style"]);const g=q(L({},m),{className:ne("token",...p.types,h),children:p.content,style:t(p)});return c!=null&&(g.style=L(L({},g.style||{}),c)),g},[t])},it=/\r\n|\r|\n/,J=e=>{e.length===0?e.push({types:["plain"],content:` +`,empty:!0}):e.length===1&&e[0].content===""&&(e[0].content=` +`,e[0].empty=!0)},ee=(e,t)=>{const a=e.length;return a>0&&e[a-1]===t?e:e.concat(t)},ut=e=>{const t=[[]],a=[e],l=[0],p=[e.length];let h=0,c=0,m=[];const g=[m];for(;c>-1;){for(;(h=l[c]++)0?b:["plain"],d=v):(b=ee(b,v.type),v.alias&&(b=ee(b,v.alias)),d=v.content),typeof d!="string"){c++,t.push(b),a.push(d),l.push(0),p.push(d.length);continue}const E=d.split(it),r=E.length;m.push({types:b,content:E[0]});for(let n=1;nP.useMemo(()=>{if(a==null)return te([t]);const p={code:t,grammar:a,language:l,tokens:[]};return e.hooks.run("before-tokenize",p),p.tokens=e.tokenize(t,a),e.hooks.run("after-tokenize",p),te(p.tokens)},[t,a,l,e]),pt=(e,t)=>{const{plain:a}=e,l=e.styles.reduce((p,h)=>{const{languages:c,style:m}=h;return c&&!c.includes(t)||h.types.forEach(g=>{const d=L(L({},p[g]),m);p[g]=d}),p},{});return l.root=a,l.plain=q(L({},a),{backgroundColor:void 0}),l},gt=pt,dt=({children:e,language:t,code:a,theme:l,prism:p})=>{const h=t.toLowerCase(),c=gt(l,h),m=st(c),g=lt(c),d=p.languages[h],b=ct({prism:p,language:h,code:a,grammar:d});return e({tokens:b,className:`prism-code language-${h}`,style:c!=null?c.root:{},getLineProps:m,getTokenProps:g})},ft=e=>P.createElement(dt,q(L({},e),{prism:e.prism||o,theme:e.theme||le,code:e.code,language:e.language}));export{ft as H,ke as t}; diff --git a/docs/public/editor/assets/radix-vendor-B-4BPIkP.js b/docs/public/editor/assets/radix-vendor-B-4BPIkP.js new file mode 100644 index 00000000000..d4618046f86 --- /dev/null +++ b/docs/public/editor/assets/radix-vendor-B-4BPIkP.js @@ -0,0 +1,45 @@ +import{r as i,j as g,R as Ne,a as it,b as st}from"./react-vendor-D4h_eraO.js";function A(e,t,{checkForDefaultPrevented:n=!0}={}){return function(o){if(e?.(o),n===!1||!o.defaultPrevented)return t?.(o)}}function me(e,t){if(typeof e=="function")return e(t);e!=null&&(e.current=t)}function Oe(...e){return t=>{let n=!1;const r=e.map(o=>{const a=me(o,t);return!n&&typeof a=="function"&&(n=!0),a});if(n)return()=>{for(let o=0;o{const{children:c,...s}=a,f=i.useMemo(()=>s,Object.values(s));return g.jsx(n.Provider,{value:f,children:c})};r.displayName=e+"Provider";function o(a){const c=i.useContext(n);if(c)return c;if(t!==void 0)return t;throw new Error(`\`${a}\` must be used within \`${e}\``)}return[r,o]}function ut(e,t=[]){let n=[];function r(a,c){const s=i.createContext(c),f=n.length;n=[...n,c];const l=v=>{const{scope:m,children:h,...w}=v,u=m?.[e]?.[f]||s,p=i.useMemo(()=>w,Object.values(w));return g.jsx(u.Provider,{value:p,children:h})};l.displayName=a+"Provider";function d(v,m){const h=m?.[e]?.[f]||s,w=i.useContext(h);if(w)return w;if(c!==void 0)return c;throw new Error(`\`${v}\` must be used within \`${a}\``)}return[l,d]}const o=()=>{const a=n.map(c=>i.createContext(c));return function(s){const f=s?.[e]||a;return i.useMemo(()=>({[`__scope${e}`]:{...s,[e]:f}}),[s,f])}};return o.scopeName=e,[r,lt(o,...t)]}function lt(...e){const t=e[0];if(e.length===1)return t;const n=()=>{const r=e.map(o=>({useScope:o(),scopeName:o.scopeName}));return function(a){const c=r.reduce((s,{useScope:f,scopeName:l})=>{const v=f(a)[`__scope${l}`];return{...s,...v}},{});return i.useMemo(()=>({[`__scope${t.scopeName}`]:c}),[c])}};return n.scopeName=t.scopeName,n}var B=globalThis?.document?i.useLayoutEffect:()=>{},ft=Ne[" useId ".trim().toString()]||(()=>{}),dt=0;function ee(e){const[t,n]=i.useState(ft());return B(()=>{n(r=>r??String(dt++))},[e]),e||(t?`radix-${t}`:"")}var vt=Ne[" useInsertionEffect ".trim().toString()]||B;function mt({prop:e,defaultProp:t,onChange:n=()=>{},caller:r}){const[o,a,c]=pt({defaultProp:t,onChange:n}),s=e!==void 0,f=s?e:o;{const d=i.useRef(e!==void 0);i.useEffect(()=>{const v=d.current;v!==s&&console.warn(`${r} is changing from ${v?"controlled":"uncontrolled"} to ${s?"controlled":"uncontrolled"}. Components should not switch from controlled to uncontrolled (or vice versa). Decide between using a controlled or uncontrolled value for the lifetime of the component.`),d.current=s},[s,r])}const l=i.useCallback(d=>{if(s){const v=ht(d)?d(e):d;v!==e&&c.current?.(v)}else a(d)},[s,e,a,c]);return[f,l]}function pt({defaultProp:e,onChange:t}){const[n,r]=i.useState(e),o=i.useRef(n),a=i.useRef(t);return vt(()=>{a.current=t},[t]),i.useEffect(()=>{o.current!==n&&(a.current?.(n),o.current=n)},[n,o]),[n,r,a]}function ht(e){return typeof e=="function"}function xe(e){const t=gt(e),n=i.forwardRef((r,o)=>{const{children:a,...c}=r,s=i.Children.toArray(a),f=s.find(Et);if(f){const l=f.props.children,d=s.map(v=>v===f?i.Children.count(l)>1?i.Children.only(null):i.isValidElement(l)?l.props.children:null:v);return g.jsx(t,{...c,ref:o,children:i.isValidElement(l)?i.cloneElement(l,void 0,d):null})}return g.jsx(t,{...c,ref:o,children:a})});return n.displayName=`${e}.Slot`,n}function gt(e){const t=i.forwardRef((n,r)=>{const{children:o,...a}=n;if(i.isValidElement(o)){const c=Ct(o),s=bt(a,o.props);return o.type!==i.Fragment&&(s.ref=r?Oe(r,c):c),i.cloneElement(o,s)}return i.Children.count(o)>1?i.Children.only(null):null});return t.displayName=`${e}.SlotClone`,t}var yt=Symbol("radix.slottable");function Et(e){return i.isValidElement(e)&&typeof e.type=="function"&&"__radixId"in e.type&&e.type.__radixId===yt}function bt(e,t){const n={...t};for(const r in t){const o=e[r],a=t[r];/^on[A-Z]/.test(r)?o&&a?n[r]=(...s)=>{const f=a(...s);return o(...s),f}:o&&(n[r]=o):r==="style"?n[r]={...o,...a}:r==="className"&&(n[r]=[o,a].filter(Boolean).join(" "))}return{...e,...n}}function Ct(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var St=["a","button","div","form","h2","h3","img","input","label","li","nav","ol","p","select","span","svg","ul"],O=St.reduce((e,t)=>{const n=xe(`Primitive.${t}`),r=i.forwardRef((o,a)=>{const{asChild:c,...s}=o,f=c?n:t;return typeof window<"u"&&(window[Symbol.for("radix-ui")]=!0),g.jsx(f,{...s,ref:a})});return r.displayName=`Primitive.${t}`,{...e,[t]:r}},{});function wt(e,t){e&&it.flushSync(()=>e.dispatchEvent(t))}function U(e){const t=i.useRef(e);return i.useEffect(()=>{t.current=e}),i.useMemo(()=>(...n)=>t.current?.(...n),[])}function Rt(e,t=globalThis?.document){const n=U(e);i.useEffect(()=>{const r=o=>{o.key==="Escape"&&n(o)};return t.addEventListener("keydown",r,{capture:!0}),()=>t.removeEventListener("keydown",r,{capture:!0})},[n,t])}var Pt="DismissableLayer",ce="dismissableLayer.update",Dt="dismissableLayer.pointerDownOutside",Nt="dismissableLayer.focusOutside",pe,Ae=i.createContext({layers:new Set,layersWithOutsidePointerEventsDisabled:new Set,branches:new Set}),Te=i.forwardRef((e,t)=>{const{disableOutsidePointerEvents:n=!1,onEscapeKeyDown:r,onPointerDownOutside:o,onFocusOutside:a,onInteractOutside:c,onDismiss:s,...f}=e,l=i.useContext(Ae),[d,v]=i.useState(null),m=d?.ownerDocument??globalThis?.document,[,h]=i.useState({}),w=M(t,E=>v(E)),u=Array.from(l.layers),[p]=[...l.layersWithOutsidePointerEventsDisabled].slice(-1),y=u.indexOf(p),b=d?u.indexOf(d):-1,C=l.layersWithOutsidePointerEventsDisabled.size>0,S=b>=y,R=At(E=>{const T=E.target,L=[...l.branches].some(j=>j.contains(T));!S||L||(o?.(E),c?.(E),E.defaultPrevented||s?.())},m),P=Tt(E=>{const T=E.target;[...l.branches].some(j=>j.contains(T))||(a?.(E),c?.(E),E.defaultPrevented||s?.())},m);return Rt(E=>{b===l.layers.size-1&&(r?.(E),!E.defaultPrevented&&s&&(E.preventDefault(),s()))},m),i.useEffect(()=>{if(d)return n&&(l.layersWithOutsidePointerEventsDisabled.size===0&&(pe=m.body.style.pointerEvents,m.body.style.pointerEvents="none"),l.layersWithOutsidePointerEventsDisabled.add(d)),l.layers.add(d),he(),()=>{n&&l.layersWithOutsidePointerEventsDisabled.size===1&&(m.body.style.pointerEvents=pe)}},[d,m,n,l]),i.useEffect(()=>()=>{d&&(l.layers.delete(d),l.layersWithOutsidePointerEventsDisabled.delete(d),he())},[d,l]),i.useEffect(()=>{const E=()=>h({});return document.addEventListener(ce,E),()=>document.removeEventListener(ce,E)},[]),g.jsx(O.div,{...f,ref:w,style:{pointerEvents:C?S?"auto":"none":void 0,...e.style},onFocusCapture:A(e.onFocusCapture,P.onFocusCapture),onBlurCapture:A(e.onBlurCapture,P.onBlurCapture),onPointerDownCapture:A(e.onPointerDownCapture,R.onPointerDownCapture)})});Te.displayName=Pt;var Ot="DismissableLayerBranch",xt=i.forwardRef((e,t)=>{const n=i.useContext(Ae),r=i.useRef(null),o=M(t,r);return i.useEffect(()=>{const a=r.current;if(a)return n.branches.add(a),()=>{n.branches.delete(a)}},[n.branches]),g.jsx(O.div,{...e,ref:o})});xt.displayName=Ot;function At(e,t=globalThis?.document){const n=U(e),r=i.useRef(!1),o=i.useRef(()=>{});return i.useEffect(()=>{const a=s=>{if(s.target&&!r.current){let f=function(){Ie(Dt,n,l,{discrete:!0})};const l={originalEvent:s};s.pointerType==="touch"?(t.removeEventListener("click",o.current),o.current=f,t.addEventListener("click",o.current,{once:!0})):f()}else t.removeEventListener("click",o.current);r.current=!1},c=window.setTimeout(()=>{t.addEventListener("pointerdown",a)},0);return()=>{window.clearTimeout(c),t.removeEventListener("pointerdown",a),t.removeEventListener("click",o.current)}},[t,n]),{onPointerDownCapture:()=>r.current=!0}}function Tt(e,t=globalThis?.document){const n=U(e),r=i.useRef(!1);return i.useEffect(()=>{const o=a=>{a.target&&!r.current&&Ie(Nt,n,{originalEvent:a},{discrete:!1})};return t.addEventListener("focusin",o),()=>t.removeEventListener("focusin",o)},[t,n]),{onFocusCapture:()=>r.current=!0,onBlurCapture:()=>r.current=!1}}function he(){const e=new CustomEvent(ce);document.dispatchEvent(e)}function Ie(e,t,n,{discrete:r}){const o=n.originalEvent.target,a=new CustomEvent(e,{bubbles:!1,cancelable:!0,detail:n});t&&o.addEventListener(e,t,{once:!0}),r?wt(o,a):o.dispatchEvent(a)}var te="focusScope.autoFocusOnMount",ne="focusScope.autoFocusOnUnmount",ge={bubbles:!1,cancelable:!0},It="FocusScope",Me=i.forwardRef((e,t)=>{const{loop:n=!1,trapped:r=!1,onMountAutoFocus:o,onUnmountAutoFocus:a,...c}=e,[s,f]=i.useState(null),l=U(o),d=U(a),v=i.useRef(null),m=M(t,u=>f(u)),h=i.useRef({paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}}).current;i.useEffect(()=>{if(r){let u=function(C){if(h.paused||!s)return;const S=C.target;s.contains(S)?v.current=S:x(v.current,{select:!0})},p=function(C){if(h.paused||!s)return;const S=C.relatedTarget;S!==null&&(s.contains(S)||x(v.current,{select:!0}))},y=function(C){if(document.activeElement===document.body)for(const R of C)R.removedNodes.length>0&&x(s)};document.addEventListener("focusin",u),document.addEventListener("focusout",p);const b=new MutationObserver(y);return s&&b.observe(s,{childList:!0,subtree:!0}),()=>{document.removeEventListener("focusin",u),document.removeEventListener("focusout",p),b.disconnect()}}},[r,s,h.paused]),i.useEffect(()=>{if(s){Ee.add(h);const u=document.activeElement;if(!s.contains(u)){const y=new CustomEvent(te,ge);s.addEventListener(te,l),s.dispatchEvent(y),y.defaultPrevented||(Mt(Wt(Le(s)),{select:!0}),document.activeElement===u&&x(s))}return()=>{s.removeEventListener(te,l),setTimeout(()=>{const y=new CustomEvent(ne,ge);s.addEventListener(ne,d),s.dispatchEvent(y),y.defaultPrevented||x(u??document.body,{select:!0}),s.removeEventListener(ne,d),Ee.remove(h)},0)}}},[s,l,d,h]);const w=i.useCallback(u=>{if(!n&&!r||h.paused)return;const p=u.key==="Tab"&&!u.altKey&&!u.ctrlKey&&!u.metaKey,y=document.activeElement;if(p&&y){const b=u.currentTarget,[C,S]=Lt(b);C&&S?!u.shiftKey&&y===S?(u.preventDefault(),n&&x(C,{select:!0})):u.shiftKey&&y===C&&(u.preventDefault(),n&&x(S,{select:!0})):y===b&&u.preventDefault()}},[n,r,h.paused]);return g.jsx(O.div,{tabIndex:-1,...c,ref:m,onKeyDown:w})});Me.displayName=It;function Mt(e,{select:t=!1}={}){const n=document.activeElement;for(const r of e)if(x(r,{select:t}),document.activeElement!==n)return}function Lt(e){const t=Le(e),n=ye(t,e),r=ye(t.reverse(),e);return[n,r]}function Le(e){const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:r=>{const o=r.tagName==="INPUT"&&r.type==="hidden";return r.disabled||r.hidden||o?NodeFilter.FILTER_SKIP:r.tabIndex>=0?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t}function ye(e,t){for(const n of e)if(!_t(n,{upTo:t}))return n}function _t(e,{upTo:t}){if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t!==void 0&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1}function Ft(e){return e instanceof HTMLInputElement&&"select"in e}function x(e,{select:t=!1}={}){if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),e!==n&&Ft(e)&&t&&e.select()}}var Ee=kt();function kt(){let e=[];return{add(t){const n=e[0];t!==n&&n?.pause(),e=be(e,t),e.unshift(t)},remove(t){e=be(e,t),e[0]?.resume()}}}function be(e,t){const n=[...e],r=n.indexOf(t);return r!==-1&&n.splice(r,1),n}function Wt(e){return e.filter(t=>t.tagName!=="A")}var jt="Portal",_e=i.forwardRef((e,t)=>{const{container:n,...r}=e,[o,a]=i.useState(!1);B(()=>a(!0),[]);const c=n||o&&globalThis?.document?.body;return c?st.createPortal(g.jsx(O.div,{...r,ref:t}),c):null});_e.displayName=jt;function Bt(e,t){return i.useReducer((n,r)=>t[n][r]??n,e)}var q=e=>{const{present:t,children:n}=e,r=Ut(t),o=typeof n=="function"?n({present:r.isPresent}):i.Children.only(n),a=M(r.ref,$t(o));return typeof n=="function"||r.isPresent?i.cloneElement(o,{ref:a}):null};q.displayName="Presence";function Ut(e){const[t,n]=i.useState(),r=i.useRef(null),o=i.useRef(e),a=i.useRef("none"),c=e?"mounted":"unmounted",[s,f]=Bt(c,{mounted:{UNMOUNT:"unmounted",ANIMATION_OUT:"unmountSuspended"},unmountSuspended:{MOUNT:"mounted",ANIMATION_END:"unmounted"},unmounted:{MOUNT:"mounted"}});return i.useEffect(()=>{const l=V(r.current);a.current=s==="mounted"?l:"none"},[s]),B(()=>{const l=r.current,d=o.current;if(d!==e){const m=a.current,h=V(l);e?f("MOUNT"):h==="none"||l?.display==="none"?f("UNMOUNT"):f(d&&m!==h?"ANIMATION_OUT":"UNMOUNT"),o.current=e}},[e,f]),B(()=>{if(t){let l;const d=t.ownerDocument.defaultView??window,v=h=>{const u=V(r.current).includes(CSS.escape(h.animationName));if(h.target===t&&u&&(f("ANIMATION_END"),!o.current)){const p=t.style.animationFillMode;t.style.animationFillMode="forwards",l=d.setTimeout(()=>{t.style.animationFillMode==="forwards"&&(t.style.animationFillMode=p)})}},m=h=>{h.target===t&&(a.current=V(r.current))};return t.addEventListener("animationstart",m),t.addEventListener("animationcancel",v),t.addEventListener("animationend",v),()=>{d.clearTimeout(l),t.removeEventListener("animationstart",m),t.removeEventListener("animationcancel",v),t.removeEventListener("animationend",v)}}else f("ANIMATION_END")},[t,f]),{isPresent:["mounted","unmountSuspended"].includes(s),ref:i.useCallback(l=>{r.current=l?getComputedStyle(l):null,n(l)},[])}}function V(e){return e?.animationName||"none"}function $t(e){let t=Object.getOwnPropertyDescriptor(e.props,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning;return n?e.ref:(t=Object.getOwnPropertyDescriptor(e,"ref")?.get,n=t&&"isReactWarning"in t&&t.isReactWarning,n?e.props.ref:e.props.ref||e.ref)}var re=0;function Vt(){i.useEffect(()=>{const e=document.querySelectorAll("[data-radix-focus-guard]");return document.body.insertAdjacentElement("afterbegin",e[0]??Ce()),document.body.insertAdjacentElement("beforeend",e[1]??Ce()),re++,()=>{re===1&&document.querySelectorAll("[data-radix-focus-guard]").forEach(t=>t.remove()),re--}},[])}function Ce(){const e=document.createElement("span");return e.setAttribute("data-radix-focus-guard",""),e.tabIndex=0,e.style.outline="none",e.style.opacity="0",e.style.position="fixed",e.style.pointerEvents="none",e}var N=function(){return N=Object.assign||function(t){for(var n,r=1,o=arguments.length;r"u")return sn;var t=cn(e),n=document.documentElement.clientWidth,r=window.innerWidth;return{left:t[0],top:t[1],right:t[2],gap:Math.max(0,r-n+t[2]-t[0])}},ln=je(),W="data-scroll-locked",fn=function(e,t,n,r){var o=e.left,a=e.top,c=e.right,s=e.gap;return n===void 0&&(n="margin"),` + .`.concat(Kt,` { + overflow: hidden `).concat(r,`; + padding-right: `).concat(s,"px ").concat(r,`; + } + body[`).concat(W,`] { + overflow: hidden `).concat(r,`; + overscroll-behavior: contain; + `).concat([t&&"position: relative ".concat(r,";"),n==="margin"&&` + padding-left: `.concat(o,`px; + padding-top: `).concat(a,`px; + padding-right: `).concat(c,`px; + margin-left:0; + margin-top:0; + margin-right: `).concat(s,"px ").concat(r,`; + `),n==="padding"&&"padding-right: ".concat(s,"px ").concat(r,";")].filter(Boolean).join(""),` + } + + .`).concat(X,` { + right: `).concat(s,"px ").concat(r,`; + } + + .`).concat(z,` { + margin-right: `).concat(s,"px ").concat(r,`; + } + + .`).concat(X," .").concat(X,` { + right: 0 `).concat(r,`; + } + + .`).concat(z," .").concat(z,` { + margin-right: 0 `).concat(r,`; + } + + body[`).concat(W,`] { + `).concat(Gt,": ").concat(s,`px; + } +`)},we=function(){var e=parseInt(document.body.getAttribute(W)||"0",10);return isFinite(e)?e:0},dn=function(){i.useEffect(function(){return document.body.setAttribute(W,(we()+1).toString()),function(){var e=we()-1;e<=0?document.body.removeAttribute(W):document.body.setAttribute(W,e.toString())}},[])},vn=function(e){var t=e.noRelative,n=e.noImportant,r=e.gapMode,o=r===void 0?"margin":r;dn();var a=i.useMemo(function(){return un(o)},[o]);return i.createElement(ln,{styles:fn(a,!t,o,n?"":"!important")})},ue=!1;if(typeof window<"u")try{var H=Object.defineProperty({},"passive",{get:function(){return ue=!0,!0}});window.addEventListener("test",H,H),window.removeEventListener("test",H,H)}catch{ue=!1}var _=ue?{passive:!1}:!1,mn=function(e){return e.tagName==="TEXTAREA"},Be=function(e,t){if(!(e instanceof Element))return!1;var n=window.getComputedStyle(e);return n[t]!=="hidden"&&!(n.overflowY===n.overflowX&&!mn(e)&&n[t]==="visible")},pn=function(e){return Be(e,"overflowY")},hn=function(e){return Be(e,"overflowX")},Re=function(e,t){var n=t.ownerDocument,r=t;do{typeof ShadowRoot<"u"&&r instanceof ShadowRoot&&(r=r.host);var o=Ue(e,r);if(o){var a=$e(e,r),c=a[1],s=a[2];if(c>s)return!0}r=r.parentNode}while(r&&r!==n.body);return!1},gn=function(e){var t=e.scrollTop,n=e.scrollHeight,r=e.clientHeight;return[t,n,r]},yn=function(e){var t=e.scrollLeft,n=e.scrollWidth,r=e.clientWidth;return[t,n,r]},Ue=function(e,t){return e==="v"?pn(t):hn(t)},$e=function(e,t){return e==="v"?gn(t):yn(t)},En=function(e,t){return e==="h"&&t==="rtl"?-1:1},bn=function(e,t,n,r,o){var a=En(e,window.getComputedStyle(t).direction),c=a*r,s=n.target,f=t.contains(s),l=!1,d=c>0,v=0,m=0;do{if(!s)break;var h=$e(e,s),w=h[0],u=h[1],p=h[2],y=u-p-a*w;(w||y)&&Ue(e,s)&&(v+=y,m+=w);var b=s.parentNode;s=b&&b.nodeType===Node.DOCUMENT_FRAGMENT_NODE?b.host:b}while(!f&&s!==document.body||f&&(t.contains(s)||t===s));return(d&&Math.abs(v)<1||!d&&Math.abs(m)<1)&&(l=!0),l},K=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},Pe=function(e){return[e.deltaX,e.deltaY]},De=function(e){return e&&"current"in e?e.current:e},Cn=function(e,t){return e[0]===t[0]&&e[1]===t[1]},Sn=function(e){return` + .block-interactivity-`.concat(e,` {pointer-events: none;} + .allow-interactivity-`).concat(e,` {pointer-events: all;} +`)},wn=0,F=[];function Rn(e){var t=i.useRef([]),n=i.useRef([0,0]),r=i.useRef(),o=i.useState(wn++)[0],a=i.useState(je)[0],c=i.useRef(e);i.useEffect(function(){c.current=e},[e]),i.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var u=Ht([e.lockRef.current],(e.shards||[]).map(De),!0).filter(Boolean);return u.forEach(function(p){return p.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),u.forEach(function(p){return p.classList.remove("allow-interactivity-".concat(o))})}}},[e.inert,e.lockRef.current,e.shards]);var s=i.useCallback(function(u,p){if("touches"in u&&u.touches.length===2||u.type==="wheel"&&u.ctrlKey)return!c.current.allowPinchZoom;var y=K(u),b=n.current,C="deltaX"in u?u.deltaX:b[0]-y[0],S="deltaY"in u?u.deltaY:b[1]-y[1],R,P=u.target,E=Math.abs(C)>Math.abs(S)?"h":"v";if("touches"in u&&E==="h"&&P.type==="range")return!1;var T=window.getSelection(),L=T&&T.anchorNode,j=L?L===P||L.contains(P):!1;if(j)return!1;var $=Re(E,P);if(!$)return!0;if($?R=E:(R=E==="v"?"h":"v",$=Re(E,P)),!$)return!1;if(!r.current&&"changedTouches"in u&&(C||S)&&(r.current=R),!R)return!0;var ve=r.current||R;return bn(ve,p,u,ve==="h"?C:S)},[]),f=i.useCallback(function(u){var p=u;if(!(!F.length||F[F.length-1]!==a)){var y="deltaY"in p?Pe(p):K(p),b=t.current.filter(function(R){return R.name===p.type&&(R.target===p.target||p.target===R.shadowParent)&&Cn(R.delta,y)})[0];if(b&&b.should){p.cancelable&&p.preventDefault();return}if(!b){var C=(c.current.shards||[]).map(De).filter(Boolean).filter(function(R){return R.contains(p.target)}),S=C.length>0?s(p,C[0]):!c.current.noIsolation;S&&p.cancelable&&p.preventDefault()}}},[]),l=i.useCallback(function(u,p,y,b){var C={name:u,delta:p,target:y,should:b,shadowParent:Pn(y)};t.current.push(C),setTimeout(function(){t.current=t.current.filter(function(S){return S!==C})},1)},[]),d=i.useCallback(function(u){n.current=K(u),r.current=void 0},[]),v=i.useCallback(function(u){l(u.type,Pe(u),u.target,s(u,e.lockRef.current))},[]),m=i.useCallback(function(u){l(u.type,K(u),u.target,s(u,e.lockRef.current))},[]);i.useEffect(function(){return F.push(a),e.setCallbacks({onScrollCapture:v,onWheelCapture:v,onTouchMoveCapture:m}),document.addEventListener("wheel",f,_),document.addEventListener("touchmove",f,_),document.addEventListener("touchstart",d,_),function(){F=F.filter(function(u){return u!==a}),document.removeEventListener("wheel",f,_),document.removeEventListener("touchmove",f,_),document.removeEventListener("touchstart",d,_)}},[]);var h=e.removeScrollBar,w=e.inert;return i.createElement(i.Fragment,null,w?i.createElement(a,{styles:Sn(o)}):null,h?i.createElement(vn,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function Pn(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const Dn=Jt(We,Rn);var Ve=i.forwardRef(function(e,t){return i.createElement(Q,N({},e,{ref:t,sideCar:Dn}))});Ve.classNames=Q.classNames;var Nn=function(e){if(typeof document>"u")return null;var t=Array.isArray(e)?e[0]:e;return t.ownerDocument.body},k=new WeakMap,G=new WeakMap,Y={},se=0,He=function(e){return e&&(e.host||He(e.parentNode))},On=function(e,t){return t.map(function(n){if(e.contains(n))return n;var r=He(n);return r&&e.contains(r)?r:(console.error("aria-hidden",n,"in not contained inside",e,". Doing nothing"),null)}).filter(function(n){return!!n})},xn=function(e,t,n,r){var o=On(t,Array.isArray(e)?e:[e]);Y[n]||(Y[n]=new WeakMap);var a=Y[n],c=[],s=new Set,f=new Set(o),l=function(v){!v||s.has(v)||(s.add(v),l(v.parentNode))};o.forEach(l);var d=function(v){!v||f.has(v)||Array.prototype.forEach.call(v.children,function(m){if(s.has(m))d(m);else try{var h=m.getAttribute(r),w=h!==null&&h!=="false",u=(k.get(m)||0)+1,p=(a.get(m)||0)+1;k.set(m,u),a.set(m,p),c.push(m),u===1&&w&&G.set(m,!0),p===1&&m.setAttribute(n,"true"),w||m.setAttribute(r,"true")}catch(y){console.error("aria-hidden: cannot operate on ",m,y)}})};return d(t),s.clear(),se++,function(){c.forEach(function(v){var m=k.get(v)-1,h=a.get(v)-1;k.set(v,m),a.set(v,h),m||(G.has(v)||v.removeAttribute(r),G.delete(v)),h||v.removeAttribute(n)}),se--,se||(k=new WeakMap,k=new WeakMap,G=new WeakMap,Y={})}},An=function(e,t,n){n===void 0&&(n="data-aria-hidden");var r=Array.from(Array.isArray(e)?e:[e]),o=Nn(e);return o?(r.push.apply(r,Array.from(o.querySelectorAll("[aria-live], script"))),xn(r,o,n,"aria-hidden")):function(){return null}},J="Dialog",[Ke]=ut(J),[Tn,D]=Ke(J),Ge=e=>{const{__scopeDialog:t,children:n,open:r,defaultOpen:o,onOpenChange:a,modal:c=!0}=e,s=i.useRef(null),f=i.useRef(null),[l,d]=mt({prop:r,defaultProp:o??!1,onChange:a,caller:J});return g.jsx(Tn,{scope:t,triggerRef:s,contentRef:f,contentId:ee(),titleId:ee(),descriptionId:ee(),open:l,onOpenChange:d,onOpenToggle:i.useCallback(()=>d(v=>!v),[d]),modal:c,children:n})};Ge.displayName=J;var Ye="DialogTrigger",In=i.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=D(Ye,n),a=M(t,o.triggerRef);return g.jsx(O.button,{type:"button","aria-haspopup":"dialog","aria-expanded":o.open,"aria-controls":o.contentId,"data-state":de(o.open),...r,ref:a,onClick:A(e.onClick,o.onOpenToggle)})});In.displayName=Ye;var le="DialogPortal",[Mn,Xe]=Ke(le,{forceMount:void 0}),ze=e=>{const{__scopeDialog:t,forceMount:n,children:r,container:o}=e,a=D(le,t);return g.jsx(Mn,{scope:t,forceMount:n,children:i.Children.map(r,c=>g.jsx(q,{present:n||a.open,children:g.jsx(_e,{asChild:!0,container:o,children:c})}))})};ze.displayName=le;var Z="DialogOverlay",Ze=i.forwardRef((e,t)=>{const n=Xe(Z,e.__scopeDialog),{forceMount:r=n.forceMount,...o}=e,a=D(Z,e.__scopeDialog);return a.modal?g.jsx(q,{present:r||a.open,children:g.jsx(_n,{...o,ref:t})}):null});Ze.displayName=Z;var Ln=xe("DialogOverlay.RemoveScroll"),_n=i.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=D(Z,n);return g.jsx(Ve,{as:Ln,allowPinchZoom:!0,shards:[o.contentRef],children:g.jsx(O.div,{"data-state":de(o.open),...r,ref:t,style:{pointerEvents:"auto",...r.style}})})}),I="DialogContent",qe=i.forwardRef((e,t)=>{const n=Xe(I,e.__scopeDialog),{forceMount:r=n.forceMount,...o}=e,a=D(I,e.__scopeDialog);return g.jsx(q,{present:r||a.open,children:a.modal?g.jsx(Fn,{...o,ref:t}):g.jsx(kn,{...o,ref:t})})});qe.displayName=I;var Fn=i.forwardRef((e,t)=>{const n=D(I,e.__scopeDialog),r=i.useRef(null),o=M(t,n.contentRef,r);return i.useEffect(()=>{const a=r.current;if(a)return An(a)},[]),g.jsx(Qe,{...e,ref:o,trapFocus:n.open,disableOutsidePointerEvents:!0,onCloseAutoFocus:A(e.onCloseAutoFocus,a=>{a.preventDefault(),n.triggerRef.current?.focus()}),onPointerDownOutside:A(e.onPointerDownOutside,a=>{const c=a.detail.originalEvent,s=c.button===0&&c.ctrlKey===!0;(c.button===2||s)&&a.preventDefault()}),onFocusOutside:A(e.onFocusOutside,a=>a.preventDefault())})}),kn=i.forwardRef((e,t)=>{const n=D(I,e.__scopeDialog),r=i.useRef(!1),o=i.useRef(!1);return g.jsx(Qe,{...e,ref:t,trapFocus:!1,disableOutsidePointerEvents:!1,onCloseAutoFocus:a=>{e.onCloseAutoFocus?.(a),a.defaultPrevented||(r.current||n.triggerRef.current?.focus(),a.preventDefault()),r.current=!1,o.current=!1},onInteractOutside:a=>{e.onInteractOutside?.(a),a.defaultPrevented||(r.current=!0,a.detail.originalEvent.type==="pointerdown"&&(o.current=!0));const c=a.target;n.triggerRef.current?.contains(c)&&a.preventDefault(),a.detail.originalEvent.type==="focusin"&&o.current&&a.preventDefault()}})}),Qe=i.forwardRef((e,t)=>{const{__scopeDialog:n,trapFocus:r,onOpenAutoFocus:o,onCloseAutoFocus:a,...c}=e,s=D(I,n),f=i.useRef(null),l=M(t,f);return Vt(),g.jsxs(g.Fragment,{children:[g.jsx(Me,{asChild:!0,loop:!0,trapped:r,onMountAutoFocus:o,onUnmountAutoFocus:a,children:g.jsx(Te,{role:"dialog",id:s.contentId,"aria-describedby":s.descriptionId,"aria-labelledby":s.titleId,"data-state":de(s.open),...c,ref:l,onDismiss:()=>s.onOpenChange(!1)})}),g.jsxs(g.Fragment,{children:[g.jsx(Wn,{titleId:s.titleId}),g.jsx(Bn,{contentRef:f,descriptionId:s.descriptionId})]})]})}),fe="DialogTitle",Je=i.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=D(fe,n);return g.jsx(O.h2,{id:o.titleId,...r,ref:t})});Je.displayName=fe;var et="DialogDescription",tt=i.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=D(et,n);return g.jsx(O.p,{id:o.descriptionId,...r,ref:t})});tt.displayName=et;var nt="DialogClose",rt=i.forwardRef((e,t)=>{const{__scopeDialog:n,...r}=e,o=D(nt,n);return g.jsx(O.button,{type:"button",...r,ref:t,onClick:A(e.onClick,()=>o.onOpenChange(!1))})});rt.displayName=nt;function de(e){return e?"open":"closed"}var ot="DialogTitleWarning",[$n,at]=ct(ot,{contentName:I,titleName:fe,docsSlug:"dialog"}),Wn=({titleId:e})=>{const t=at(ot),n=`\`${t.contentName}\` requires a \`${t.titleName}\` for the component to be accessible for screen reader users. + +If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component. + +For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return i.useEffect(()=>{e&&(document.getElementById(e)||console.error(n))},[n,e]),null},jn="DialogDescriptionWarning",Bn=({contentRef:e,descriptionId:t})=>{const r=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${at(jn).contentName}}.`;return i.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(r))},[r,e,t]),null},Vn=Ge,Hn=ze,Kn=Ze,Gn=qe,Yn=Je,Xn=tt,zn=rt;export{Gn as C,Xn as D,Kn as O,Hn as P,Vn as R,Yn as T,zn as a}; diff --git a/docs/public/editor/assets/react-vendor-D4h_eraO.js b/docs/public/editor/assets/react-vendor-D4h_eraO.js new file mode 100644 index 00000000000..e57c6534b5f --- /dev/null +++ b/docs/public/editor/assets/react-vendor-D4h_eraO.js @@ -0,0 +1,9 @@ +function ws(M,Sl){for(var W=0;Wo[al]})}}}return Object.freeze(Object.defineProperty(M,Symbol.toStringTag,{value:"Module"}))}function Em(M){return M&&M.__esModule&&Object.prototype.hasOwnProperty.call(M,"default")?M.default:M}var ai={exports:{}},ge={};var mm;function Ws(){if(mm)return ge;mm=1;var M=Symbol.for("react.transitional.element"),Sl=Symbol.for("react.fragment");function W(o,al,vl){var Ol=null;if(vl!==void 0&&(Ol=""+vl),al.key!==void 0&&(Ol=""+al.key),"key"in al){vl={};for(var Rl in al)Rl!=="key"&&(vl[Rl]=al[Rl])}else vl=al;return al=vl.ref,{$$typeof:M,type:o,key:Ol,ref:al!==void 0?al:null,props:vl}}return ge.Fragment=Sl,ge.jsx=W,ge.jsxs=W,ge}var sm;function $s(){return sm||(sm=1,ai.exports=Ws()),ai.exports}var ed=$s(),ei={exports:{}},C={};var dm;function Fs(){if(dm)return C;dm=1;var M=Symbol.for("react.transitional.element"),Sl=Symbol.for("react.portal"),W=Symbol.for("react.fragment"),o=Symbol.for("react.strict_mode"),al=Symbol.for("react.profiler"),vl=Symbol.for("react.consumer"),Ol=Symbol.for("react.context"),Rl=Symbol.for("react.forward_ref"),N=Symbol.for("react.suspense"),A=Symbol.for("react.memo"),$=Symbol.for("react.lazy"),R=Symbol.for("react.activity"),il=Symbol.iterator;function wl(v){return v===null||typeof v!="object"?null:(v=il&&v[il]||v["@@iterator"],typeof v=="function"?v:null)}var Bl={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},ql=Object.assign,Ut={};function Wl(v,E,O){this.props=v,this.context=E,this.refs=Ut,this.updater=O||Bl}Wl.prototype.isReactComponent={},Wl.prototype.setState=function(v,E){if(typeof v!="object"&&typeof v!="function"&&v!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,v,E,"setState")},Wl.prototype.forceUpdate=function(v){this.updater.enqueueForceUpdate(this,v,"forceUpdate")};function wt(){}wt.prototype=Wl.prototype;function Nl(v,E,O){this.props=v,this.context=E,this.refs=Ut,this.updater=O||Bl}var nt=Nl.prototype=new wt;nt.constructor=Nl,ql(nt,Wl.prototype),nt.isPureReactComponent=!0;var Tt=Array.isArray;function Gl(){}var x={H:null,A:null,T:null,S:null},Xl=Object.prototype.hasOwnProperty;function Et(v,E,O){var U=O.ref;return{$$typeof:M,type:v,key:E,ref:U!==void 0?U:null,props:O}}function Qu(v,E){return Et(v.type,E,v.props)}function At(v){return typeof v=="object"&&v!==null&&v.$$typeof===M}function Ql(v){var E={"=":"=0",":":"=2"};return"$"+v.replace(/[=:]/g,function(O){return E[O]})}var zu=/\/+/g;function rt(v,E){return typeof v=="object"&&v!==null&&v.key!=null?Ql(""+v.key):E.toString(36)}function St(v){switch(v.status){case"fulfilled":return v.value;case"rejected":throw v.reason;default:switch(typeof v.status=="string"?v.then(Gl,Gl):(v.status="pending",v.then(function(E){v.status==="pending"&&(v.status="fulfilled",v.value=E)},function(E){v.status==="pending"&&(v.status="rejected",v.reason=E)})),v.status){case"fulfilled":return v.value;case"rejected":throw v.reason}}throw v}function b(v,E,O,U,Y){var X=typeof v;(X==="undefined"||X==="boolean")&&(v=null);var F=!1;if(v===null)F=!0;else switch(X){case"bigint":case"string":case"number":F=!0;break;case"object":switch(v.$$typeof){case M:case Sl:F=!0;break;case $:return F=v._init,b(F(v._payload),E,O,U,Y)}}if(F)return Y=Y(v),F=U===""?"."+rt(v,0):U,Tt(Y)?(O="",F!=null&&(O=F.replace(zu,"$&/")+"/"),b(Y,E,O,"",function(Oa){return Oa})):Y!=null&&(At(Y)&&(Y=Qu(Y,O+(Y.key==null||v&&v.key===Y.key?"":(""+Y.key).replace(zu,"$&/")+"/")+F)),E.push(Y)),1;F=0;var Cl=U===""?".":U+":";if(Tt(v))for(var dl=0;dl>>1,el=b[P];if(0>>1;Pal(O,q))Ual(Y,O)?(b[P]=Y,b[U]=q,P=U):(b[P]=O,b[E]=q,P=E);else if(Ual(Y,q))b[P]=Y,b[U]=q,P=U;else break l}}return _}function al(b,_){var q=b.sortIndex-_.sortIndex;return q!==0?q:b.id-_.id}if(M.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var vl=performance;M.unstable_now=function(){return vl.now()}}else{var Ol=Date,Rl=Ol.now();M.unstable_now=function(){return Ol.now()-Rl}}var N=[],A=[],$=1,R=null,il=3,wl=!1,Bl=!1,ql=!1,Ut=!1,Wl=typeof setTimeout=="function"?setTimeout:null,wt=typeof clearTimeout=="function"?clearTimeout:null,Nl=typeof setImmediate<"u"?setImmediate:null;function nt(b){for(var _=W(A);_!==null;){if(_.callback===null)o(A);else if(_.startTime<=b)o(A),_.sortIndex=_.expirationTime,Sl(N,_);else break;_=W(A)}}function Tt(b){if(ql=!1,nt(b),!Bl)if(W(N)!==null)Bl=!0,Gl||(Gl=!0,Ql());else{var _=W(A);_!==null&&St(Tt,_.startTime-b)}}var Gl=!1,x=-1,Xl=5,Et=-1;function Qu(){return Ut?!0:!(M.unstable_now()-Etb&&Qu());){var P=R.callback;if(typeof P=="function"){R.callback=null,il=R.priorityLevel;var el=P(R.expirationTime<=b);if(b=M.unstable_now(),typeof el=="function"){R.callback=el,nt(b),_=!0;break t}R===W(N)&&o(N),nt(b)}else o(N);R=W(N)}if(R!==null)_=!0;else{var v=W(A);v!==null&&St(Tt,v.startTime-b),_=!1}}break l}finally{R=null,il=q,wl=!1}_=void 0}}finally{_?Ql():Gl=!1}}}var Ql;if(typeof Nl=="function")Ql=function(){Nl(At)};else if(typeof MessageChannel<"u"){var zu=new MessageChannel,rt=zu.port2;zu.port1.onmessage=At,Ql=function(){rt.postMessage(null)}}else Ql=function(){Wl(At,0)};function St(b,_){x=Wl(function(){b(M.unstable_now())},_)}M.unstable_IdlePriority=5,M.unstable_ImmediatePriority=1,M.unstable_LowPriority=4,M.unstable_NormalPriority=3,M.unstable_Profiling=null,M.unstable_UserBlockingPriority=2,M.unstable_cancelCallback=function(b){b.callback=null},M.unstable_forceFrameRate=function(b){0>b||125P?(b.sortIndex=q,Sl(A,b),W(N)===null&&b===W(A)&&(ql?(wt(x),x=-1):ql=!0,St(Tt,q-P))):(b.sortIndex=el,Sl(N,b),Bl||wl||(Bl=!0,Gl||(Gl=!0,Ql()))),b},M.unstable_shouldYield=Qu,M.unstable_wrapCallback=function(b){var _=il;return function(){var q=il;il=_;try{return b.apply(this,arguments)}finally{il=q}}}})(ci)),ci}var Sm;function Ps(){return Sm||(Sm=1,fi.exports=Is()),fi.exports}var ii={exports:{}},pl={};var gm;function ld(){if(gm)return pl;gm=1;var M=yi();function Sl(N){var A="https://react.dev/errors/"+N;if(1"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(M)}catch(Sl){console.error(Sl)}}return M(),ii.exports=ld(),ii.exports}var zm;function td(){if(zm)return be;zm=1;var M=Ps(),Sl=yi(),W=_m();function o(l){var t="https://react.dev/errors/"+l;if(1el||(l.current=P[el],P[el]=null,el--)}function O(l,t){el++,P[el]=l.current,l.current=t}var U=v(null),Y=v(null),X=v(null),F=v(null);function Cl(l,t){switch(O(X,t),O(Y,l),O(U,null),t.nodeType){case 9:case 11:l=(l=t.documentElement)&&(l=l.namespaceURI)?Cv(l):0;break;default:if(l=t.tagName,t=t.namespaceURI)t=Cv(t),l=Yv(t,l);else switch(l){case"svg":l=1;break;case"math":l=2;break;default:l=0}}E(U),O(U,l)}function dl(){E(U),E(Y),E(X)}function Oa(l){l.memoizedState!==null&&O(F,l);var t=U.current,u=Yv(t,l.type);t!==u&&(O(Y,l),O(U,u))}function ze(l){Y.current===l&&(E(U),E(Y)),F.current===l&&(E(F),de._currentValue=q)}var Xn,vi;function Tu(l){if(Xn===void 0)try{throw Error()}catch(u){var t=u.stack.trim().match(/\n( *(at )?)/);Xn=t&&t[1]||"",vi=-1)":-1e||i[a]!==d[e]){var g=` +`+i[a].replace(" at new "," at ");return l.displayName&&g.includes("")&&(g=g.replace("",l.displayName)),g}while(1<=a&&0<=e);break}}}finally{Qn=!1,Error.prepareStackTrace=u}return(u=l?l.displayName||l.name:"")?Tu(u):""}function Om(l,t){switch(l.tag){case 26:case 27:case 5:return Tu(l.type);case 16:return Tu("Lazy");case 13:return l.child!==t&&t!==null?Tu("Suspense Fallback"):Tu("Suspense");case 19:return Tu("SuspenseList");case 0:case 15:return jn(l.type,!1);case 11:return jn(l.type.render,!1);case 1:return jn(l.type,!0);case 31:return Tu("Activity");default:return""}}function mi(l){try{var t="",u=null;do t+=Om(l,u),u=l,l=l.return;while(l);return t}catch(a){return` +Error generating stack: `+a.message+` +`+a.stack}}var Zn=Object.prototype.hasOwnProperty,Vn=M.unstable_scheduleCallback,xn=M.unstable_cancelCallback,Mm=M.unstable_shouldYield,Dm=M.unstable_requestPaint,$l=M.unstable_now,Um=M.unstable_getCurrentPriorityLevel,si=M.unstable_ImmediatePriority,di=M.unstable_UserBlockingPriority,Te=M.unstable_NormalPriority,rm=M.unstable_LowPriority,hi=M.unstable_IdlePriority,Hm=M.log,Nm=M.unstable_setDisableYieldValue,Ma=null,Fl=null;function Wt(l){if(typeof Hm=="function"&&Nm(l),Fl&&typeof Fl.setStrictMode=="function")try{Fl.setStrictMode(Ma,l)}catch{}}var kl=Math.clz32?Math.clz32:qm,pm=Math.log,Rm=Math.LN2;function qm(l){return l>>>=0,l===0?32:31-(pm(l)/Rm|0)|0}var Ee=256,Ae=262144,_e=4194304;function Eu(l){var t=l&42;if(t!==0)return t;switch(l&-l){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return l&261888;case 262144:case 524288:case 1048576:case 2097152:return l&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return l&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return l}}function Oe(l,t,u){var a=l.pendingLanes;if(a===0)return 0;var e=0,n=l.suspendedLanes,f=l.pingedLanes;l=l.warmLanes;var c=a&134217727;return c!==0?(a=c&~n,a!==0?e=Eu(a):(f&=c,f!==0?e=Eu(f):u||(u=c&~l,u!==0&&(e=Eu(u))))):(c=a&~n,c!==0?e=Eu(c):f!==0?e=Eu(f):u||(u=a&~l,u!==0&&(e=Eu(u)))),e===0?0:t!==0&&t!==e&&(t&n)===0&&(n=e&-e,u=t&-t,n>=u||n===32&&(u&4194048)!==0)?t:e}function Da(l,t){return(l.pendingLanes&~(l.suspendedLanes&~l.pingedLanes)&t)===0}function Cm(l,t){switch(l){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function oi(){var l=_e;return _e<<=1,(_e&62914560)===0&&(_e=4194304),l}function Ln(l){for(var t=[],u=0;31>u;u++)t.push(l);return t}function Ua(l,t){l.pendingLanes|=t,t!==268435456&&(l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0)}function Ym(l,t,u,a,e,n){var f=l.pendingLanes;l.pendingLanes=u,l.suspendedLanes=0,l.pingedLanes=0,l.warmLanes=0,l.expiredLanes&=u,l.entangledLanes&=u,l.errorRecoveryDisabledLanes&=u,l.shellSuspendCounter=0;var c=l.entanglements,i=l.expirationTimes,d=l.hiddenUpdates;for(u=f&~u;0"u")return null;try{return l.activeElement||l.body}catch{return l.body}}var Zm=/[\n"\\]/g;function ct(l){return l.replace(Zm,function(t){return"\\"+t.charCodeAt(0).toString(16)+" "})}function Fn(l,t,u,a,e,n,f,c){l.name="",f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"?l.type=f:l.removeAttribute("type"),t!=null?f==="number"?(t===0&&l.value===""||l.value!=t)&&(l.value=""+ft(t)):l.value!==""+ft(t)&&(l.value=""+ft(t)):f!=="submit"&&f!=="reset"||l.removeAttribute("value"),t!=null?kn(l,f,ft(t)):u!=null?kn(l,f,ft(u)):a!=null&&l.removeAttribute("value"),e==null&&n!=null&&(l.defaultChecked=!!n),e!=null&&(l.checked=e&&typeof e!="function"&&typeof e!="symbol"),c!=null&&typeof c!="function"&&typeof c!="symbol"&&typeof c!="boolean"?l.name=""+ft(c):l.removeAttribute("name")}function ri(l,t,u,a,e,n,f,c){if(n!=null&&typeof n!="function"&&typeof n!="symbol"&&typeof n!="boolean"&&(l.type=n),t!=null||u!=null){if(!(n!=="submit"&&n!=="reset"||t!=null)){$n(l);return}u=u!=null?""+ft(u):"",t=t!=null?""+ft(t):u,c||t===l.value||(l.value=t),l.defaultValue=t}a=a??e,a=typeof a!="function"&&typeof a!="symbol"&&!!a,l.checked=c?l.checked:!!a,l.defaultChecked=!!a,f!=null&&typeof f!="function"&&typeof f!="symbol"&&typeof f!="boolean"&&(l.name=f),$n(l)}function kn(l,t,u){t==="number"&&Ue(l.ownerDocument)===l||l.defaultValue===""+u||(l.defaultValue=""+u)}function Ku(l,t,u,a){if(l=l.options,t){t={};for(var e=0;e"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),uf=!1;if(pt)try{var pa={};Object.defineProperty(pa,"passive",{get:function(){uf=!0}}),window.addEventListener("test",pa,pa),window.removeEventListener("test",pa,pa)}catch{uf=!1}var Ft=null,af=null,He=null;function Yi(){if(He)return He;var l,t=af,u=t.length,a,e="value"in Ft?Ft.value:Ft.textContent,n=e.length;for(l=0;l=Ca),Zi=" ",Vi=!1;function xi(l,t){switch(l){case"keyup":return o1.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Li(l){return l=l.detail,typeof l=="object"&&"data"in l?l.data:null}var $u=!1;function g1(l,t){switch(l){case"compositionend":return Li(t);case"keypress":return t.which!==32?null:(Vi=!0,Zi);case"textInput":return l=t.data,l===Zi&&Vi?null:l;default:return null}}function b1(l,t){if($u)return l==="compositionend"||!yf&&xi(l,t)?(l=Yi(),He=af=Ft=null,$u=!1,l):null;switch(l){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:u,offset:t-l};l=a}l:{for(;u;){if(u.nextSibling){u=u.nextSibling;break l}u=u.parentNode}u=void 0}u=Ii(u)}}function l0(l,t){return l&&t?l===t?!0:l&&l.nodeType===3?!1:t&&t.nodeType===3?l0(l,t.parentNode):"contains"in l?l.contains(t):l.compareDocumentPosition?!!(l.compareDocumentPosition(t)&16):!1:!1}function t0(l){l=l!=null&&l.ownerDocument!=null&&l.ownerDocument.defaultView!=null?l.ownerDocument.defaultView:window;for(var t=Ue(l.document);t instanceof l.HTMLIFrameElement;){try{var u=typeof t.contentWindow.location.href=="string"}catch{u=!1}if(u)l=t.contentWindow;else break;t=Ue(l.document)}return t}function sf(l){var t=l&&l.nodeName&&l.nodeName.toLowerCase();return t&&(t==="input"&&(l.type==="text"||l.type==="search"||l.type==="tel"||l.type==="url"||l.type==="password")||t==="textarea"||l.contentEditable==="true")}var D1=pt&&"documentMode"in document&&11>=document.documentMode,Fu=null,df=null,Xa=null,hf=!1;function u0(l,t,u){var a=u.window===u?u.document:u.nodeType===9?u:u.ownerDocument;hf||Fu==null||Fu!==Ue(a)||(a=Fu,"selectionStart"in a&&sf(a)?a={start:a.selectionStart,end:a.selectionEnd}:(a=(a.ownerDocument&&a.ownerDocument.defaultView||window).getSelection(),a={anchorNode:a.anchorNode,anchorOffset:a.anchorOffset,focusNode:a.focusNode,focusOffset:a.focusOffset}),Xa&&Ga(Xa,a)||(Xa=a,a=_n(df,"onSelect"),0>=f,e-=f,_t=1<<32-kl(t)+e|u<G?(V=r,r=null):V=r.sibling;var J=h(m,r,s[G],z);if(J===null){r===null&&(r=V);break}l&&r&&J.alternate===null&&t(m,r),y=n(J,y,G),K===null?H=J:K.sibling=J,K=J,r=V}if(G===s.length)return u(m,r),L&&qt(m,G),H;if(r===null){for(;GG?(V=r,r=null):V=r.sibling;var bu=h(m,r,J.value,z);if(bu===null){r===null&&(r=V);break}l&&r&&bu.alternate===null&&t(m,r),y=n(bu,y,G),K===null?H=bu:K.sibling=bu,K=bu,r=V}if(J.done)return u(m,r),L&&qt(m,G),H;if(r===null){for(;!J.done;G++,J=s.next())J=T(m,J.value,z),J!==null&&(y=n(J,y,G),K===null?H=J:K.sibling=J,K=J);return L&&qt(m,G),H}for(r=a(r);!J.done;G++,J=s.next())J=S(r,m,G,J.value,z),J!==null&&(l&&J.alternate!==null&&r.delete(J.key===null?G:J.key),y=n(J,y,G),K===null?H=J:K.sibling=J,K=J);return l&&r.forEach(function(Js){return t(m,Js)}),L&&qt(m,G),H}function ul(m,y,s,z){if(typeof s=="object"&&s!==null&&s.type===ql&&s.key===null&&(s=s.props.children),typeof s=="object"&&s!==null){switch(s.$$typeof){case wl:l:{for(var H=s.key;y!==null;){if(y.key===H){if(H=s.type,H===ql){if(y.tag===7){u(m,y.sibling),z=e(y,s.props.children),z.return=m,m=z;break l}}else if(y.elementType===H||typeof H=="object"&&H!==null&&H.$$typeof===Xl&&Ru(H)===y.type){u(m,y.sibling),z=e(y,s.props),La(z,s),z.return=m,m=z;break l}u(m,y);break}else t(m,y);y=y.sibling}s.type===ql?(z=Uu(s.props.children,m.mode,z,s.key),z.return=m,m=z):(z=Qe(s.type,s.key,s.props,null,m.mode,z),La(z,s),z.return=m,m=z)}return f(m);case Bl:l:{for(H=s.key;y!==null;){if(y.key===H)if(y.tag===4&&y.stateNode.containerInfo===s.containerInfo&&y.stateNode.implementation===s.implementation){u(m,y.sibling),z=e(y,s.children||[]),z.return=m,m=z;break l}else{u(m,y);break}else t(m,y);y=y.sibling}z=Ef(s,m.mode,z),z.return=m,m=z}return f(m);case Xl:return s=Ru(s),ul(m,y,s,z)}if(St(s))return D(m,y,s,z);if(Ql(s)){if(H=Ql(s),typeof H!="function")throw Error(o(150));return s=H.call(s),p(m,y,s,z)}if(typeof s.then=="function")return ul(m,y,Je(s),z);if(s.$$typeof===Nl)return ul(m,y,Ve(m,s),z);we(m,s)}return typeof s=="string"&&s!==""||typeof s=="number"||typeof s=="bigint"?(s=""+s,y!==null&&y.tag===6?(u(m,y.sibling),z=e(y,s),z.return=m,m=z):(u(m,y),z=Tf(s,m.mode,z),z.return=m,m=z),f(m)):u(m,y)}return function(m,y,s,z){try{xa=0;var H=ul(m,y,s,z);return ca=null,H}catch(r){if(r===fa||r===Le)throw r;var K=Pl(29,r,null,m.mode);return K.lanes=z,K.return=m,K}}}var Cu=M0(!0),D0=M0(!1),tu=!1;function qf(l){l.updateQueue={baseState:l.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,lanes:0,hiddenCallbacks:null},callbacks:null}}function Cf(l,t){l=l.updateQueue,t.updateQueue===l&&(t.updateQueue={baseState:l.baseState,firstBaseUpdate:l.firstBaseUpdate,lastBaseUpdate:l.lastBaseUpdate,shared:l.shared,callbacks:null})}function uu(l){return{lane:l,tag:0,payload:null,callback:null,next:null}}function au(l,t,u){var a=l.updateQueue;if(a===null)return null;if(a=a.shared,(w&2)!==0){var e=a.pending;return e===null?t.next=t:(t.next=e.next,e.next=t),a.pending=t,t=Xe(l),y0(l,null,u),t}return Ge(l,a,t,u),Xe(l)}function Ka(l,t,u){if(t=t.updateQueue,t!==null&&(t=t.shared,(u&4194048)!==0)){var a=t.lanes;a&=l.pendingLanes,u|=a,t.lanes=u,gi(l,u)}}function Yf(l,t){var u=l.updateQueue,a=l.alternate;if(a!==null&&(a=a.updateQueue,u===a)){var e=null,n=null;if(u=u.firstBaseUpdate,u!==null){do{var f={lane:u.lane,tag:u.tag,payload:u.payload,callback:null,next:null};n===null?e=n=f:n=n.next=f,u=u.next}while(u!==null);n===null?e=n=t:n=n.next=t}else e=n=t;u={baseState:a.baseState,firstBaseUpdate:e,lastBaseUpdate:n,shared:a.shared,callbacks:a.callbacks},l.updateQueue=u;return}l=u.lastBaseUpdate,l===null?u.firstBaseUpdate=t:l.next=t,u.lastBaseUpdate=t}var Bf=!1;function Ja(){if(Bf){var l=na;if(l!==null)throw l}}function wa(l,t,u,a){Bf=!1;var e=l.updateQueue;tu=!1;var n=e.firstBaseUpdate,f=e.lastBaseUpdate,c=e.shared.pending;if(c!==null){e.shared.pending=null;var i=c,d=i.next;i.next=null,f===null?n=d:f.next=d,f=i;var g=l.alternate;g!==null&&(g=g.updateQueue,c=g.lastBaseUpdate,c!==f&&(c===null?g.firstBaseUpdate=d:c.next=d,g.lastBaseUpdate=i))}if(n!==null){var T=e.baseState;f=0,g=d=i=null,c=n;do{var h=c.lane&-536870913,S=h!==c.lane;if(S?(Z&h)===h:(a&h)===h){h!==0&&h===ea&&(Bf=!0),g!==null&&(g=g.next={lane:0,tag:c.tag,payload:c.payload,callback:null,next:null});l:{var D=l,p=c;h=t;var ul=u;switch(p.tag){case 1:if(D=p.payload,typeof D=="function"){T=D.call(ul,T,h);break l}T=D;break l;case 3:D.flags=D.flags&-65537|128;case 0:if(D=p.payload,h=typeof D=="function"?D.call(ul,T,h):D,h==null)break l;T=R({},T,h);break l;case 2:tu=!0}}h=c.callback,h!==null&&(l.flags|=64,S&&(l.flags|=8192),S=e.callbacks,S===null?e.callbacks=[h]:S.push(h))}else S={lane:h,tag:c.tag,payload:c.payload,callback:c.callback,next:null},g===null?(d=g=S,i=T):g=g.next=S,f|=h;if(c=c.next,c===null){if(c=e.shared.pending,c===null)break;S=c,c=S.next,S.next=null,e.lastBaseUpdate=S,e.shared.pending=null}}while(!0);g===null&&(i=T),e.baseState=i,e.firstBaseUpdate=d,e.lastBaseUpdate=g,n===null&&(e.shared.lanes=0),iu|=f,l.lanes=f,l.memoizedState=T}}function U0(l,t){if(typeof l!="function")throw Error(o(191,l));l.call(t)}function r0(l,t){var u=l.callbacks;if(u!==null)for(l.callbacks=null,l=0;ln?n:8;var f=b.T,c={};b.T=c,tc(l,!1,t,u);try{var i=e(),d=b.S;if(d!==null&&d(c,i),i!==null&&typeof i=="object"&&typeof i.then=="function"){var g=Y1(i,a);Fa(l,t,g,et(l))}else Fa(l,t,a,et(l))}catch(T){Fa(l,t,{then:function(){},status:"rejected",reason:T},et())}finally{_.p=n,f!==null&&c.types!==null&&(f.types=c.types),b.T=f}}function Z1(){}function Pf(l,t,u,a){if(l.tag!==5)throw Error(o(476));var e=fy(l).queue;ny(l,e,t,q,u===null?Z1:function(){return cy(l),u(a)})}function fy(l){var t=l.memoizedState;if(t!==null)return t;t={memoizedState:q,baseState:q,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Gt,lastRenderedState:q},next:null};var u={};return t.next={memoizedState:u,baseState:u,baseQueue:null,queue:{pending:null,lanes:0,dispatch:null,lastRenderedReducer:Gt,lastRenderedState:u},next:null},l.memoizedState=t,l=l.alternate,l!==null&&(l.memoizedState=t),t}function cy(l){var t=fy(l);t.next===null&&(t=l.alternate.memoizedState),Fa(l,t.next.queue,{},et())}function lc(){return Ul(de)}function iy(){return ol().memoizedState}function yy(){return ol().memoizedState}function V1(l){for(var t=l.return;t!==null;){switch(t.tag){case 24:case 3:var u=et();l=uu(u);var a=au(t,l,u);a!==null&&(Jl(a,t,u),Ka(a,t,u)),t={cache:Hf()},l.payload=t;return}t=t.return}}function x1(l,t,u){var a=et();u={lane:a,revertLane:0,gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null},an(l)?my(t,u):(u=bf(l,t,u,a),u!==null&&(Jl(u,l,a),sy(u,t,a)))}function vy(l,t,u){var a=et();Fa(l,t,u,a)}function Fa(l,t,u,a){var e={lane:a,revertLane:0,gesture:null,action:u,hasEagerState:!1,eagerState:null,next:null};if(an(l))my(t,e);else{var n=l.alternate;if(l.lanes===0&&(n===null||n.lanes===0)&&(n=t.lastRenderedReducer,n!==null))try{var f=t.lastRenderedState,c=n(f,u);if(e.hasEagerState=!0,e.eagerState=c,Il(c,f))return Ge(l,t,e,0),nl===null&&Be(),!1}catch{}if(u=bf(l,t,e,a),u!==null)return Jl(u,l,a),sy(u,t,a),!0}return!1}function tc(l,t,u,a){if(a={lane:2,revertLane:qc(),gesture:null,action:a,hasEagerState:!1,eagerState:null,next:null},an(l)){if(t)throw Error(o(479))}else t=bf(l,u,a,2),t!==null&&Jl(t,l,2)}function an(l){var t=l.alternate;return l===B||t!==null&&t===B}function my(l,t){ya=Fe=!0;var u=l.pending;u===null?t.next=t:(t.next=u.next,u.next=t),l.pending=t}function sy(l,t,u){if((u&4194048)!==0){var a=t.lanes;a&=l.pendingLanes,u|=a,t.lanes=u,gi(l,u)}}var ka={readContext:Ul,use:Pe,useCallback:ml,useContext:ml,useEffect:ml,useImperativeHandle:ml,useLayoutEffect:ml,useInsertionEffect:ml,useMemo:ml,useReducer:ml,useRef:ml,useState:ml,useDebugValue:ml,useDeferredValue:ml,useTransition:ml,useSyncExternalStore:ml,useId:ml,useHostTransitionStatus:ml,useFormState:ml,useActionState:ml,useOptimistic:ml,useMemoCache:ml,useCacheRefresh:ml};ka.useEffectEvent=ml;var dy={readContext:Ul,use:Pe,useCallback:function(l,t){return Yl().memoizedState=[l,t===void 0?null:t],l},useContext:Ul,useEffect:F0,useImperativeHandle:function(l,t,u){u=u!=null?u.concat([l]):null,tn(4194308,4,ly.bind(null,t,l),u)},useLayoutEffect:function(l,t){return tn(4194308,4,l,t)},useInsertionEffect:function(l,t){tn(4,2,l,t)},useMemo:function(l,t){var u=Yl();t=t===void 0?null:t;var a=l();if(Yu){Wt(!0);try{l()}finally{Wt(!1)}}return u.memoizedState=[a,t],a},useReducer:function(l,t,u){var a=Yl();if(u!==void 0){var e=u(t);if(Yu){Wt(!0);try{u(t)}finally{Wt(!1)}}}else e=t;return a.memoizedState=a.baseState=e,l={pending:null,lanes:0,dispatch:null,lastRenderedReducer:l,lastRenderedState:e},a.queue=l,l=l.dispatch=x1.bind(null,B,l),[a.memoizedState,l]},useRef:function(l){var t=Yl();return l={current:l},t.memoizedState=l},useState:function(l){l=Wf(l);var t=l.queue,u=vy.bind(null,B,t);return t.dispatch=u,[l.memoizedState,u]},useDebugValue:kf,useDeferredValue:function(l,t){var u=Yl();return If(u,l,t)},useTransition:function(){var l=Wf(!1);return l=ny.bind(null,B,l.queue,!0,!1),Yl().memoizedState=l,[!1,l]},useSyncExternalStore:function(l,t,u){var a=B,e=Yl();if(L){if(u===void 0)throw Error(o(407));u=u()}else{if(u=t(),nl===null)throw Error(o(349));(Z&127)!==0||C0(a,t,u)}e.memoizedState=u;var n={value:u,getSnapshot:t};return e.queue=n,F0(B0.bind(null,a,n,l),[l]),a.flags|=2048,ma(9,{destroy:void 0},Y0.bind(null,a,n,u,t),null),u},useId:function(){var l=Yl(),t=nl.identifierPrefix;if(L){var u=Ot,a=_t;u=(a&~(1<<32-kl(a)-1)).toString(32)+u,t="_"+t+"R_"+u,u=ke++,0<\/script>",n=n.removeChild(n.firstChild);break;case"select":n=typeof a.is=="string"?f.createElement("select",{is:a.is}):f.createElement("select"),a.multiple?n.multiple=!0:a.size&&(n.size=a.size);break;default:n=typeof a.is=="string"?f.createElement(e,{is:a.is}):f.createElement(e)}}n[Ml]=t,n[jl]=a;l:for(f=t.child;f!==null;){if(f.tag===5||f.tag===6)n.appendChild(f.stateNode);else if(f.tag!==4&&f.tag!==27&&f.child!==null){f.child.return=f,f=f.child;continue}if(f===t)break l;for(;f.sibling===null;){if(f.return===null||f.return===t)break l;f=f.return}f.sibling.return=f.return,f=f.sibling}t.stateNode=n;l:switch(Hl(n,e,a),e){case"button":case"input":case"select":case"textarea":a=!!a.autoFocus;break l;case"img":a=!0;break l;default:a=!1}a&&Qt(t)}}return cl(t),oc(t,t.type,l===null?null:l.memoizedProps,t.pendingProps,u),null;case 6:if(l&&t.stateNode!=null)l.memoizedProps!==a&&Qt(t);else{if(typeof a!="string"&&t.stateNode===null)throw Error(o(166));if(l=X.current,ua(t)){if(l=t.stateNode,u=t.memoizedProps,a=null,e=Dl,e!==null)switch(e.tag){case 27:case 5:a=e.memoizedProps}l[Ml]=t,l=!!(l.nodeValue===u||a!==null&&a.suppressHydrationWarning===!0||Rv(l.nodeValue,u)),l||Pt(t,!0)}else l=On(l).createTextNode(a),l[Ml]=t,t.stateNode=l}return cl(t),null;case 31:if(u=t.memoizedState,l===null||l.memoizedState!==null){if(a=ua(t),u!==null){if(l===null){if(!a)throw Error(o(318));if(l=t.memoizedState,l=l!==null?l.dehydrated:null,!l)throw Error(o(557));l[Ml]=t}else ru(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;cl(t),l=!1}else u=Mf(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=u),l=!0;if(!l)return t.flags&256?(tt(t),t):(tt(t),null);if((t.flags&128)!==0)throw Error(o(558))}return cl(t),null;case 13:if(a=t.memoizedState,l===null||l.memoizedState!==null&&l.memoizedState.dehydrated!==null){if(e=ua(t),a!==null&&a.dehydrated!==null){if(l===null){if(!e)throw Error(o(318));if(e=t.memoizedState,e=e!==null?e.dehydrated:null,!e)throw Error(o(317));e[Ml]=t}else ru(),(t.flags&128)===0&&(t.memoizedState=null),t.flags|=4;cl(t),e=!1}else e=Mf(),l!==null&&l.memoizedState!==null&&(l.memoizedState.hydrationErrors=e),e=!0;if(!e)return t.flags&256?(tt(t),t):(tt(t),null)}return tt(t),(t.flags&128)!==0?(t.lanes=u,t):(u=a!==null,l=l!==null&&l.memoizedState!==null,u&&(a=t.child,e=null,a.alternate!==null&&a.alternate.memoizedState!==null&&a.alternate.memoizedState.cachePool!==null&&(e=a.alternate.memoizedState.cachePool.pool),n=null,a.memoizedState!==null&&a.memoizedState.cachePool!==null&&(n=a.memoizedState.cachePool.pool),n!==e&&(a.flags|=2048)),u!==l&&u&&(t.child.flags|=8192),yn(t,t.updateQueue),cl(t),null);case 4:return dl(),l===null&&Gc(t.stateNode.containerInfo),cl(t),null;case 10:return Yt(t.type),cl(t),null;case 19:if(E(hl),a=t.memoizedState,a===null)return cl(t),null;if(e=(t.flags&128)!==0,n=a.rendering,n===null)if(e)Pa(a,!1);else{if(sl!==0||l!==null&&(l.flags&128)!==0)for(l=t.child;l!==null;){if(n=$e(l),n!==null){for(t.flags|=128,Pa(a,!1),l=n.updateQueue,t.updateQueue=l,yn(t,l),t.subtreeFlags=0,l=u,u=t.child;u!==null;)v0(u,l),u=u.sibling;return O(hl,hl.current&1|2),L&&qt(t,a.treeForkCount),t.child}l=l.sibling}a.tail!==null&&$l()>hn&&(t.flags|=128,e=!0,Pa(a,!1),t.lanes=4194304)}else{if(!e)if(l=$e(n),l!==null){if(t.flags|=128,e=!0,l=l.updateQueue,t.updateQueue=l,yn(t,l),Pa(a,!0),a.tail===null&&a.tailMode==="hidden"&&!n.alternate&&!L)return cl(t),null}else 2*$l()-a.renderingStartTime>hn&&u!==536870912&&(t.flags|=128,e=!0,Pa(a,!1),t.lanes=4194304);a.isBackwards?(n.sibling=t.child,t.child=n):(l=a.last,l!==null?l.sibling=n:t.child=n,a.last=n)}return a.tail!==null?(l=a.tail,a.rendering=l,a.tail=l.sibling,a.renderingStartTime=$l(),l.sibling=null,u=hl.current,O(hl,e?u&1|2:u&1),L&&qt(t,a.treeForkCount),l):(cl(t),null);case 22:case 23:return tt(t),Xf(),a=t.memoizedState!==null,l!==null?l.memoizedState!==null!==a&&(t.flags|=8192):a&&(t.flags|=8192),a?(u&536870912)!==0&&(t.flags&128)===0&&(cl(t),t.subtreeFlags&6&&(t.flags|=8192)):cl(t),u=t.updateQueue,u!==null&&yn(t,u.retryQueue),u=null,l!==null&&l.memoizedState!==null&&l.memoizedState.cachePool!==null&&(u=l.memoizedState.cachePool.pool),a=null,t.memoizedState!==null&&t.memoizedState.cachePool!==null&&(a=t.memoizedState.cachePool.pool),a!==u&&(t.flags|=2048),l!==null&&E(pu),null;case 24:return u=null,l!==null&&(u=l.memoizedState.cache),t.memoizedState.cache!==u&&(t.flags|=2048),Yt(gl),cl(t),null;case 25:return null;case 30:return null}throw Error(o(156,t.tag))}function W1(l,t){switch(_f(t),t.tag){case 1:return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 3:return Yt(gl),dl(),l=t.flags,(l&65536)!==0&&(l&128)===0?(t.flags=l&-65537|128,t):null;case 26:case 27:case 5:return ze(t),null;case 31:if(t.memoizedState!==null){if(tt(t),t.alternate===null)throw Error(o(340));ru()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 13:if(tt(t),l=t.memoizedState,l!==null&&l.dehydrated!==null){if(t.alternate===null)throw Error(o(340));ru()}return l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 19:return E(hl),null;case 4:return dl(),null;case 10:return Yt(t.type),null;case 22:case 23:return tt(t),Xf(),l!==null&&E(pu),l=t.flags,l&65536?(t.flags=l&-65537|128,t):null;case 24:return Yt(gl),null;case 25:return null;default:return null}}function Gy(l,t){switch(_f(t),t.tag){case 3:Yt(gl),dl();break;case 26:case 27:case 5:ze(t);break;case 4:dl();break;case 31:t.memoizedState!==null&&tt(t);break;case 13:tt(t);break;case 19:E(hl);break;case 10:Yt(t.type);break;case 22:case 23:tt(t),Xf(),l!==null&&E(pu);break;case 24:Yt(gl)}}function le(l,t){try{var u=t.updateQueue,a=u!==null?u.lastEffect:null;if(a!==null){var e=a.next;u=e;do{if((u.tag&l)===l){a=void 0;var n=u.create,f=u.inst;a=n(),f.destroy=a}u=u.next}while(u!==e)}}catch(c){I(t,t.return,c)}}function fu(l,t,u){try{var a=t.updateQueue,e=a!==null?a.lastEffect:null;if(e!==null){var n=e.next;a=n;do{if((a.tag&l)===l){var f=a.inst,c=f.destroy;if(c!==void 0){f.destroy=void 0,e=t;var i=u,d=c;try{d()}catch(g){I(e,i,g)}}}a=a.next}while(a!==n)}}catch(g){I(t,t.return,g)}}function Xy(l){var t=l.updateQueue;if(t!==null){var u=l.stateNode;try{r0(t,u)}catch(a){I(l,l.return,a)}}}function Qy(l,t,u){u.props=Bu(l.type,l.memoizedProps),u.state=l.memoizedState;try{u.componentWillUnmount()}catch(a){I(l,t,a)}}function te(l,t){try{var u=l.ref;if(u!==null){switch(l.tag){case 26:case 27:case 5:var a=l.stateNode;break;case 30:a=l.stateNode;break;default:a=l.stateNode}typeof u=="function"?l.refCleanup=u(a):u.current=a}}catch(e){I(l,t,e)}}function Mt(l,t){var u=l.ref,a=l.refCleanup;if(u!==null)if(typeof a=="function")try{a()}catch(e){I(l,t,e)}finally{l.refCleanup=null,l=l.alternate,l!=null&&(l.refCleanup=null)}else if(typeof u=="function")try{u(null)}catch(e){I(l,t,e)}else u.current=null}function jy(l){var t=l.type,u=l.memoizedProps,a=l.stateNode;try{l:switch(t){case"button":case"input":case"select":case"textarea":u.autoFocus&&a.focus();break l;case"img":u.src?a.src=u.src:u.srcSet&&(a.srcset=u.srcSet)}}catch(e){I(l,l.return,e)}}function Sc(l,t,u){try{var a=l.stateNode;Ss(a,l.type,u,t),a[jl]=t}catch(e){I(l,l.return,e)}}function Zy(l){return l.tag===5||l.tag===3||l.tag===26||l.tag===27&&du(l.type)||l.tag===4}function gc(l){l:for(;;){for(;l.sibling===null;){if(l.return===null||Zy(l.return))return null;l=l.return}for(l.sibling.return=l.return,l=l.sibling;l.tag!==5&&l.tag!==6&&l.tag!==18;){if(l.tag===27&&du(l.type)||l.flags&2||l.child===null||l.tag===4)continue l;l.child.return=l,l=l.child}if(!(l.flags&2))return l.stateNode}}function bc(l,t,u){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?(u.nodeType===9?u.body:u.nodeName==="HTML"?u.ownerDocument.body:u).insertBefore(l,t):(t=u.nodeType===9?u.body:u.nodeName==="HTML"?u.ownerDocument.body:u,t.appendChild(l),u=u._reactRootContainer,u!=null||t.onclick!==null||(t.onclick=Nt));else if(a!==4&&(a===27&&du(l.type)&&(u=l.stateNode,t=null),l=l.child,l!==null))for(bc(l,t,u),l=l.sibling;l!==null;)bc(l,t,u),l=l.sibling}function vn(l,t,u){var a=l.tag;if(a===5||a===6)l=l.stateNode,t?u.insertBefore(l,t):u.appendChild(l);else if(a!==4&&(a===27&&du(l.type)&&(u=l.stateNode),l=l.child,l!==null))for(vn(l,t,u),l=l.sibling;l!==null;)vn(l,t,u),l=l.sibling}function Vy(l){var t=l.stateNode,u=l.memoizedProps;try{for(var a=l.type,e=t.attributes;e.length;)t.removeAttributeNode(e[0]);Hl(t,a,u),t[Ml]=l,t[jl]=u}catch(n){I(l,l.return,n)}}var jt=!1,Tl=!1,zc=!1,xy=typeof WeakSet=="function"?WeakSet:Set,_l=null;function $1(l,t){if(l=l.containerInfo,jc=pn,l=t0(l),sf(l)){if("selectionStart"in l)var u={start:l.selectionStart,end:l.selectionEnd};else l:{u=(u=l.ownerDocument)&&u.defaultView||window;var a=u.getSelection&&u.getSelection();if(a&&a.rangeCount!==0){u=a.anchorNode;var e=a.anchorOffset,n=a.focusNode;a=a.focusOffset;try{u.nodeType,n.nodeType}catch{u=null;break l}var f=0,c=-1,i=-1,d=0,g=0,T=l,h=null;t:for(;;){for(var S;T!==u||e!==0&&T.nodeType!==3||(c=f+e),T!==n||a!==0&&T.nodeType!==3||(i=f+a),T.nodeType===3&&(f+=T.nodeValue.length),(S=T.firstChild)!==null;)h=T,T=S;for(;;){if(T===l)break t;if(h===u&&++d===e&&(c=f),h===n&&++g===a&&(i=f),(S=T.nextSibling)!==null)break;T=h,h=T.parentNode}T=S}u=c===-1||i===-1?null:{start:c,end:i}}else u=null}u=u||{start:0,end:0}}else u=null;for(Zc={focusedElem:l,selectionRange:u},pn=!1,_l=t;_l!==null;)if(t=_l,l=t.child,(t.subtreeFlags&1028)!==0&&l!==null)l.return=t,_l=l;else for(;_l!==null;){switch(t=_l,n=t.alternate,l=t.flags,t.tag){case 0:if((l&4)!==0&&(l=t.updateQueue,l=l!==null?l.events:null,l!==null))for(u=0;u title"))),Hl(n,a,u),n[Ml]=l,Al(n),a=n;break l;case"link":var f=$v("link","href",e).get(a+(u.href||""));if(f){for(var c=0;cul&&(f=ul,ul=p,p=f);var m=Pi(c,p),y=Pi(c,ul);if(m&&y&&(S.rangeCount!==1||S.anchorNode!==m.node||S.anchorOffset!==m.offset||S.focusNode!==y.node||S.focusOffset!==y.offset)){var s=T.createRange();s.setStart(m.node,m.offset),S.removeAllRanges(),p>ul?(S.addRange(s),S.extend(y.node,y.offset)):(s.setEnd(y.node,y.offset),S.addRange(s))}}}}for(T=[],S=c;S=S.parentNode;)S.nodeType===1&&T.push({element:S,left:S.scrollLeft,top:S.scrollTop});for(typeof c.focus=="function"&&c.focus(),c=0;cu?32:u,b.T=null,u=Dc,Dc=null;var n=vu,f=Kt;if(El=0,Sa=vu=null,Kt=0,(w&6)!==0)throw Error(o(331));var c=w;if(w|=4,lv(n.current),ky(n,n.current,f,u),w=c,ce(0,!1),Fl&&typeof Fl.onPostCommitFiberRoot=="function")try{Fl.onPostCommitFiberRoot(Ma,n)}catch{}return!0}finally{_.p=e,b.T=a,bv(l,t)}}function Tv(l,t,u){t=yt(u,t),t=nc(l.stateNode,t,2),l=au(l,t,2),l!==null&&(Ua(l,2),Dt(l))}function I(l,t,u){if(l.tag===3)Tv(l,l,u);else for(;t!==null;){if(t.tag===3){Tv(t,l,u);break}else if(t.tag===1){var a=t.stateNode;if(typeof t.type.getDerivedStateFromError=="function"||typeof a.componentDidCatch=="function"&&(yu===null||!yu.has(a))){l=yt(u,l),u=Ey(2),a=au(t,u,2),a!==null&&(Ay(u,a,t,l),Ua(a,2),Dt(a));break}}t=t.return}}function Nc(l,t,u){var a=l.pingCache;if(a===null){a=l.pingCache=new I1;var e=new Set;a.set(t,e)}else e=a.get(t),e===void 0&&(e=new Set,a.set(t,e));e.has(u)||(Ac=!0,e.add(u),l=as.bind(null,l,t,u),t.then(l,l))}function as(l,t,u){var a=l.pingCache;a!==null&&a.delete(t),l.pingedLanes|=l.suspendedLanes&u,l.warmLanes&=~u,nl===l&&(Z&u)===u&&(sl===4||sl===3&&(Z&62914560)===Z&&300>$l()-dn?(w&2)===0&&ga(l,0):_c|=u,oa===Z&&(oa=0)),Dt(l)}function Ev(l,t){t===0&&(t=oi()),l=Du(l,t),l!==null&&(Ua(l,t),Dt(l))}function es(l){var t=l.memoizedState,u=0;t!==null&&(u=t.retryLane),Ev(l,u)}function ns(l,t){var u=0;switch(l.tag){case 31:case 13:var a=l.stateNode,e=l.memoizedState;e!==null&&(u=e.retryLane);break;case 19:a=l.stateNode;break;case 22:a=l.stateNode._retryCache;break;default:throw Error(o(314))}a!==null&&a.delete(t),Ev(l,u)}function fs(l,t){return Vn(l,t)}var Tn=null,za=null,pc=!1,En=!1,Rc=!1,su=0;function Dt(l){l!==za&&l.next===null&&(za===null?Tn=za=l:za=za.next=l),En=!0,pc||(pc=!0,is())}function ce(l,t){if(!Rc&&En){Rc=!0;do for(var u=!1,a=Tn;a!==null;){if(l!==0){var e=a.pendingLanes;if(e===0)var n=0;else{var f=a.suspendedLanes,c=a.pingedLanes;n=(1<<31-kl(42|l)+1)-1,n&=e&~(f&~c),n=n&201326741?n&201326741|1:n?n|2:0}n!==0&&(u=!0,Mv(a,n))}else n=Z,n=Oe(a,a===nl?n:0,a.cancelPendingCommit!==null||a.timeoutHandle!==-1),(n&3)===0||Da(a,n)||(u=!0,Mv(a,n));a=a.next}while(u);Rc=!1}}function cs(){Av()}function Av(){En=pc=!1;var l=0;su!==0&&bs()&&(l=su);for(var t=$l(),u=null,a=Tn;a!==null;){var e=a.next,n=_v(a,t);n===0?(a.next=null,u===null?Tn=e:u.next=e,e===null&&(za=u)):(u=a,(l!==0||(n&3)!==0)&&(En=!0)),a=e}El!==0&&El!==5||ce(l),su!==0&&(su=0)}function _v(l,t){for(var u=l.suspendedLanes,a=l.pingedLanes,e=l.expirationTimes,n=l.pendingLanes&-62914561;0c)break;var g=i.transferSize,T=i.initiatorType;g&&qv(T)&&(i=i.responseEnd,f+=g*(i"u"?null:document;function Kv(l,t,u){var a=Ta;if(a&&typeof t=="string"&&t){var e=ct(t);e='link[rel="'+l+'"][href="'+e+'"]',typeof u=="string"&&(e+='[crossorigin="'+u+'"]'),Lv.has(e)||(Lv.add(e),l={rel:l,crossOrigin:u,href:t},a.querySelector(e)===null&&(t=a.createElement("link"),Hl(t,"link",l),Al(t),a.head.appendChild(t)))}}function Us(l){Jt.D(l),Kv("dns-prefetch",l,null)}function rs(l,t){Jt.C(l,t),Kv("preconnect",l,t)}function Hs(l,t,u){Jt.L(l,t,u);var a=Ta;if(a&&l&&t){var e='link[rel="preload"][as="'+ct(t)+'"]';t==="image"&&u&&u.imageSrcSet?(e+='[imagesrcset="'+ct(u.imageSrcSet)+'"]',typeof u.imageSizes=="string"&&(e+='[imagesizes="'+ct(u.imageSizes)+'"]')):e+='[href="'+ct(l)+'"]';var n=e;switch(t){case"style":n=Ea(l);break;case"script":n=Aa(l)}ot.has(n)||(l=R({rel:"preload",href:t==="image"&&u&&u.imageSrcSet?void 0:l,as:t},u),ot.set(n,l),a.querySelector(e)!==null||t==="style"&&a.querySelector(me(n))||t==="script"&&a.querySelector(se(n))||(t=a.createElement("link"),Hl(t,"link",l),Al(t),a.head.appendChild(t)))}}function Ns(l,t){Jt.m(l,t);var u=Ta;if(u&&l){var a=t&&typeof t.as=="string"?t.as:"script",e='link[rel="modulepreload"][as="'+ct(a)+'"][href="'+ct(l)+'"]',n=e;switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":n=Aa(l)}if(!ot.has(n)&&(l=R({rel:"modulepreload",href:l},t),ot.set(n,l),u.querySelector(e)===null)){switch(a){case"audioworklet":case"paintworklet":case"serviceworker":case"sharedworker":case"worker":case"script":if(u.querySelector(se(n)))return}a=u.createElement("link"),Hl(a,"link",l),Al(a),u.head.appendChild(a)}}}function ps(l,t,u){Jt.S(l,t,u);var a=Ta;if(a&&l){var e=xu(a).hoistableStyles,n=Ea(l);t=t||"default";var f=e.get(n);if(!f){var c={loading:0,preload:null};if(f=a.querySelector(me(n)))c.loading=5;else{l=R({rel:"stylesheet",href:l,"data-precedence":t},u),(u=ot.get(n))&&Wc(l,u);var i=f=a.createElement("link");Al(i),Hl(i,"link",l),i._p=new Promise(function(d,g){i.onload=d,i.onerror=g}),i.addEventListener("load",function(){c.loading|=1}),i.addEventListener("error",function(){c.loading|=2}),c.loading|=4,Dn(f,t,a)}f={type:"stylesheet",instance:f,count:1,state:c},e.set(n,f)}}}function Rs(l,t){Jt.X(l,t);var u=Ta;if(u&&l){var a=xu(u).hoistableScripts,e=Aa(l),n=a.get(e);n||(n=u.querySelector(se(e)),n||(l=R({src:l,async:!0},t),(t=ot.get(e))&&$c(l,t),n=u.createElement("script"),Al(n),Hl(n,"link",l),u.head.appendChild(n)),n={type:"script",instance:n,count:1,state:null},a.set(e,n))}}function qs(l,t){Jt.M(l,t);var u=Ta;if(u&&l){var a=xu(u).hoistableScripts,e=Aa(l),n=a.get(e);n||(n=u.querySelector(se(e)),n||(l=R({src:l,async:!0,type:"module"},t),(t=ot.get(e))&&$c(l,t),n=u.createElement("script"),Al(n),Hl(n,"link",l),u.head.appendChild(n)),n={type:"script",instance:n,count:1,state:null},a.set(e,n))}}function Jv(l,t,u,a){var e=(e=X.current)?Mn(e):null;if(!e)throw Error(o(446));switch(l){case"meta":case"title":return null;case"style":return typeof u.precedence=="string"&&typeof u.href=="string"?(t=Ea(u.href),u=xu(e).hoistableStyles,a=u.get(t),a||(a={type:"style",instance:null,count:0,state:null},u.set(t,a)),a):{type:"void",instance:null,count:0,state:null};case"link":if(u.rel==="stylesheet"&&typeof u.href=="string"&&typeof u.precedence=="string"){l=Ea(u.href);var n=xu(e).hoistableStyles,f=n.get(l);if(f||(e=e.ownerDocument||e,f={type:"stylesheet",instance:null,count:0,state:{loading:0,preload:null}},n.set(l,f),(n=e.querySelector(me(l)))&&!n._p&&(f.instance=n,f.state.loading=5),ot.has(l)||(u={rel:"preload",as:"style",href:u.href,crossOrigin:u.crossOrigin,integrity:u.integrity,media:u.media,hrefLang:u.hrefLang,referrerPolicy:u.referrerPolicy},ot.set(l,u),n||Cs(e,l,u,f.state))),t&&a===null)throw Error(o(528,""));return f}if(t&&a!==null)throw Error(o(529,""));return null;case"script":return t=u.async,u=u.src,typeof u=="string"&&t&&typeof t!="function"&&typeof t!="symbol"?(t=Aa(u),u=xu(e).hoistableScripts,a=u.get(t),a||(a={type:"script",instance:null,count:0,state:null},u.set(t,a)),a):{type:"void",instance:null,count:0,state:null};default:throw Error(o(444,l))}}function Ea(l){return'href="'+ct(l)+'"'}function me(l){return'link[rel="stylesheet"]['+l+"]"}function wv(l){return R({},l,{"data-precedence":l.precedence,precedence:null})}function Cs(l,t,u,a){l.querySelector('link[rel="preload"][as="style"]['+t+"]")?a.loading=1:(t=l.createElement("link"),a.preload=t,t.addEventListener("load",function(){return a.loading|=1}),t.addEventListener("error",function(){return a.loading|=2}),Hl(t,"link",u),Al(t),l.head.appendChild(t))}function Aa(l){return'[src="'+ct(l)+'"]'}function se(l){return"script[async]"+l}function Wv(l,t,u){if(t.count++,t.instance===null)switch(t.type){case"style":var a=l.querySelector('style[data-href~="'+ct(u.href)+'"]');if(a)return t.instance=a,Al(a),a;var e=R({},u,{"data-href":u.href,"data-precedence":u.precedence,href:null,precedence:null});return a=(l.ownerDocument||l).createElement("style"),Al(a),Hl(a,"style",e),Dn(a,u.precedence,l),t.instance=a;case"stylesheet":e=Ea(u.href);var n=l.querySelector(me(e));if(n)return t.state.loading|=4,t.instance=n,Al(n),n;a=wv(u),(e=ot.get(e))&&Wc(a,e),n=(l.ownerDocument||l).createElement("link"),Al(n);var f=n;return f._p=new Promise(function(c,i){f.onload=c,f.onerror=i}),Hl(n,"link",a),t.state.loading|=4,Dn(n,u.precedence,l),t.instance=n;case"script":return n=Aa(u.src),(e=l.querySelector(se(n)))?(t.instance=e,Al(e),e):(a=u,(e=ot.get(n))&&(a=R({},u),$c(a,e)),l=l.ownerDocument||l,e=l.createElement("script"),Al(e),Hl(e,"link",a),l.head.appendChild(e),t.instance=e);case"void":return null;default:throw Error(o(443,t.type))}else t.type==="stylesheet"&&(t.state.loading&4)===0&&(a=t.instance,t.state.loading|=4,Dn(a,u.precedence,l));return t.instance}function Dn(l,t,u){for(var a=u.querySelectorAll('link[rel="stylesheet"][data-precedence],style[data-precedence]'),e=a.length?a[a.length-1]:null,n=e,f=0;f title"):null)}function Ys(l,t,u){if(u===1||t.itemProp!=null)return!1;switch(l){case"meta":case"title":return!0;case"style":if(typeof t.precedence!="string"||typeof t.href!="string"||t.href==="")break;return!0;case"link":if(typeof t.rel!="string"||typeof t.href!="string"||t.href===""||t.onLoad||t.onError)break;return t.rel==="stylesheet"?(l=t.disabled,typeof t.precedence=="string"&&l==null):!0;case"script":if(t.async&&typeof t.async!="function"&&typeof t.async!="symbol"&&!t.onLoad&&!t.onError&&t.src&&typeof t.src=="string")return!0}return!1}function kv(l){return!(l.type==="stylesheet"&&(l.state.loading&3)===0)}function Bs(l,t,u,a){if(u.type==="stylesheet"&&(typeof a.media!="string"||matchMedia(a.media).matches!==!1)&&(u.state.loading&4)===0){if(u.instance===null){var e=Ea(a.href),n=t.querySelector(me(e));if(n){t=n._p,t!==null&&typeof t=="object"&&typeof t.then=="function"&&(l.count++,l=rn.bind(l),t.then(l,l)),u.state.loading|=4,u.instance=n,Al(n);return}n=t.ownerDocument||t,a=wv(a),(e=ot.get(e))&&Wc(a,e),n=n.createElement("link"),Al(n);var f=n;f._p=new Promise(function(c,i){f.onload=c,f.onerror=i}),Hl(n,"link",a),u.instance=n}l.stylesheets===null&&(l.stylesheets=new Map),l.stylesheets.set(u,t),(t=u.state.preload)&&(u.state.loading&3)===0&&(l.count++,u=rn.bind(l),t.addEventListener("load",u),t.addEventListener("error",u))}}var Fc=0;function Gs(l,t){return l.stylesheets&&l.count===0&&Nn(l,l.stylesheets),0Fc?50:800)+t);return l.unsuspend=u,function(){l.unsuspend=null,clearTimeout(a),clearTimeout(e)}}:null}function rn(){if(this.count--,this.count===0&&(this.imgCount===0||!this.waitingForImages)){if(this.stylesheets)Nn(this,this.stylesheets);else if(this.unsuspend){var l=this.unsuspend;this.unsuspend=null,l()}}}var Hn=null;function Nn(l,t){l.stylesheets=null,l.unsuspend!==null&&(l.count++,Hn=new Map,t.forEach(Xs,l),Hn=null,rn.call(l))}function Xs(l,t){if(!(t.state.loading&4)){var u=Hn.get(l);if(u)var a=u.get(null);else{u=new Map,Hn.set(l,u);for(var e=l.querySelectorAll("link[data-precedence],style[data-precedence]"),n=0;n"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(M)}catch(Sl){console.error(Sl)}}return M(),ni.exports=td(),ni.exports}var fd=ud(),ad=_m();const cd=Em(ad);export{nd as R,ad as a,cd as b,yi as c,ks as d,fd as e,Em as g,ed as j,Am as r}; diff --git a/docs/public/editor/assets/wrench-DBKN94Hu.js b/docs/public/editor/assets/wrench-DBKN94Hu.js new file mode 100644 index 00000000000..7e3376de16a --- /dev/null +++ b/docs/public/editor/assets/wrench-DBKN94Hu.js @@ -0,0 +1 @@ +import{c as a}from"./index-XYwUYdkL.js";const e=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0",key:"vwvbt9"}],["path",{d:"M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326",key:"11g9vi"}]],p=a("bell",e);const t=[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]],y=a("bot",t);const c=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],k=a("check",c);const o=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],l=a("globe",o);const h=[["path",{d:"M3 5h.01",key:"18ugdj"}],["path",{d:"M3 12h.01",key:"nlz23k"}],["path",{d:"M3 19h.01",key:"noohij"}],["path",{d:"M8 5h13",key:"1pao27"}],["path",{d:"M8 12h13",key:"1za7za"}],["path",{d:"M8 19h13",key:"m83p4d"}]],M=a("list",h);const d=[["path",{d:"M14.536 21.686a.5.5 0 0 0 .937-.024l6.5-19a.496.496 0 0 0-.635-.635l-19 6.5a.5.5 0 0 0-.024.937l7.93 3.18a2 2 0 0 1 1.112 1.11z",key:"1ffxy3"}],["path",{d:"m21.854 2.147-10.94 10.939",key:"12cjpa"}]],i=a("send",d);const n=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]],r=a("wrench",n);export{p as B,k as C,l as G,M as L,i as S,r as W,y as a}; diff --git a/docs/public/editor/editor.js b/docs/public/editor/editor.js deleted file mode 100644 index 7727a7995e3..00000000000 --- a/docs/public/editor/editor.js +++ /dev/null @@ -1,434 +0,0 @@ -// ================================================================ -// gh-aw Playground - Application Logic -// ================================================================ - -import { createWorkerCompiler } from '/gh-aw/wasm/compiler-loader.js'; - -// --------------------------------------------------------------- -// Default workflow content -// --------------------------------------------------------------- -const DEFAULT_CONTENT = `--- -name: hello-world -description: A simple hello world workflow -on: - workflow_dispatch: -engine: copilot ---- - -# Mission - -Say hello to the world! Check the current date and time, and greet the user warmly. -`; - -// --------------------------------------------------------------- -// DOM Elements -// --------------------------------------------------------------- -const $ = (id) => document.getElementById(id); - -const editor = $('editor'); -const outputPre = $('outputPre'); -const outputPlaceholder = $('outputPlaceholder'); -const compileBtn = $('compileBtn'); -const copyBtn = $('copyBtn'); -const statusBadge = $('statusBadge'); -const statusText = $('statusText'); -const loadingOverlay = $('loadingOverlay'); -const errorBanner = $('errorBanner'); -const errorText = $('errorText'); -const warningBanner = $('warningBanner'); -const warningText = $('warningText'); -const lineNumbers = $('lineNumbers'); -const lineNumbersInner = $('lineNumbersInner'); -const themeToggle = $('themeToggle'); -const toggleTrack = $('toggleTrack'); -const divider = $('divider'); -const panelEditor = $('panelEditor'); -const panelOutput = $('panelOutput'); -const panels = $('panels'); -const tabBar = $('tabBar'); -const tabAdd = $('tabAdd'); - -// --------------------------------------------------------------- -// State -// --------------------------------------------------------------- -let compiler = null; -let isReady = false; -let isCompiling = false; -let autoCompile = true; -let compileTimer = null; -let currentYaml = ''; - -// File tabs state: ordered list of { name, content } -const MAIN_FILE = 'workflow.md'; -let files = [{ name: MAIN_FILE, content: DEFAULT_CONTENT }]; -let activeTab = MAIN_FILE; - -// --------------------------------------------------------------- -// Theme -// --------------------------------------------------------------- -function getPreferredTheme() { - const saved = localStorage.getItem('gh-aw-playground-theme'); - if (saved) return saved; - return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; -} - -function setTheme(theme) { - document.documentElement.setAttribute('data-theme', theme); - localStorage.setItem('gh-aw-playground-theme', theme); - const sunIcon = themeToggle.querySelector('.icon-sun'); - const moonIcon = themeToggle.querySelector('.icon-moon'); - sunIcon.style.display = theme === 'dark' ? 'block' : 'none'; - moonIcon.style.display = theme === 'dark' ? 'none' : 'block'; -} - -setTheme(getPreferredTheme()); - -themeToggle.addEventListener('click', () => { - const current = document.documentElement.getAttribute('data-theme'); - setTheme(current === 'dark' ? 'light' : 'dark'); -}); - -window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { - if (!localStorage.getItem('gh-aw-playground-theme')) { - setTheme(e.matches ? 'dark' : 'light'); - } -}); - -// --------------------------------------------------------------- -// Keyboard shortcut hint (Mac vs other) -// --------------------------------------------------------------- -const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; -document.querySelectorAll('.kbd-hint-mac').forEach(el => el.style.display = isMac ? 'inline' : 'none'); -document.querySelectorAll('.kbd-hint-other').forEach(el => el.style.display = isMac ? 'none' : 'inline'); - -// --------------------------------------------------------------- -// Status -// --------------------------------------------------------------- -function setStatus(status, text) { - statusBadge.setAttribute('data-status', status); - statusText.textContent = text; -} - -// --------------------------------------------------------------- -// Line numbers -// --------------------------------------------------------------- -function updateLineNumbers() { - const lines = editor.value.split('\n').length; - let html = ''; - for (let i = 1; i <= lines; i++) html += '
' + i + '
'; - lineNumbersInner.innerHTML = html; -} - -function syncLineNumberScroll() { - lineNumbers.scrollTop = editor.scrollTop; -} - -// --------------------------------------------------------------- -// File tabs -// --------------------------------------------------------------- -function getFile(name) { - return files.find(f => f.name === name); -} - -function renderTabs() { - tabBar.querySelectorAll('.tab').forEach(el => el.remove()); - - for (const file of files) { - const tab = document.createElement('div'); - tab.className = 'tab' + (file.name === activeTab ? ' active' : ''); - tab.dataset.name = file.name; - - const label = document.createElement('span'); - label.textContent = file.name; - tab.appendChild(label); - - if (file.name !== MAIN_FILE) { - const close = document.createElement('button'); - close.className = 'tab-close'; - close.title = 'Remove file'; - close.innerHTML = ''; - close.addEventListener('click', (e) => { - e.stopPropagation(); - removeTab(file.name); - }); - tab.appendChild(close); - } - - tab.addEventListener('click', () => switchTab(file.name)); - tabBar.insertBefore(tab, tabAdd); - } -} - -function switchTab(name) { - const current = getFile(activeTab); - if (current) current.content = editor.value; - - activeTab = name; - const file = getFile(name); - if (file) { - editor.value = file.content; - updateLineNumbers(); - } - renderTabs(); -} - -function addTab() { - const name = prompt('File path (e.g. shared/my-tools.md):'); - if (!name || !name.trim()) return; - - const trimmed = name.trim(); - if (getFile(trimmed)) { switchTab(trimmed); return; } - - const defaultImportContent = `--- -# Shared workflow component -# This file can define: tools, steps, engine, mcp-servers, etc. -tools: - - name: example_tool - description: An example tool ---- - -# Instructions - -Add your shared workflow instructions here. -`; - - files.push({ name: trimmed, content: defaultImportContent }); - switchTab(trimmed); -} - -function removeTab(name) { - if (name === MAIN_FILE) return; - files = files.filter(f => f.name !== name); - if (activeTab === name) { - switchTab(MAIN_FILE); - } else { - renderTabs(); - } - if (autoCompile && isReady) scheduleCompile(); -} - -tabAdd.addEventListener('click', addTab); - -// --------------------------------------------------------------- -// Editor setup -// --------------------------------------------------------------- -editor.value = DEFAULT_CONTENT; -updateLineNumbers(); -renderTabs(); - -editor.addEventListener('input', () => { - updateLineNumbers(); - const file = getFile(activeTab); - if (file) file.content = editor.value; - if (autoCompile && isReady) scheduleCompile(); -}); - -editor.addEventListener('scroll', syncLineNumberScroll); - -editor.addEventListener('keydown', (e) => { - if (e.key === 'Tab') { - e.preventDefault(); - const start = editor.selectionStart; - const end = editor.selectionEnd; - editor.value = editor.value.substring(0, start) + ' ' + editor.value.substring(end); - editor.selectionStart = editor.selectionEnd = start + 2; - editor.dispatchEvent(new Event('input')); - } - if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) { - e.preventDefault(); - doCompile(); - } -}); - -// --------------------------------------------------------------- -// Auto-compile toggle -// --------------------------------------------------------------- -$('autoCompileToggle').addEventListener('click', () => { - autoCompile = !autoCompile; - toggleTrack.classList.toggle('active', autoCompile); -}); - -// --------------------------------------------------------------- -// Compile -// --------------------------------------------------------------- -function scheduleCompile() { - if (compileTimer) clearTimeout(compileTimer); - compileTimer = setTimeout(doCompile, 400); -} - -function getImportFiles() { - const importFiles = {}; - for (const file of files) { - if (file.name !== MAIN_FILE) importFiles[file.name] = file.content; - } - return Object.keys(importFiles).length > 0 ? importFiles : undefined; -} - -async function doCompile() { - if (!isReady || isCompiling) return; - if (compileTimer) { clearTimeout(compileTimer); compileTimer = null; } - - // Save current editor content - const currentFile = getFile(activeTab); - if (currentFile) currentFile.content = editor.value; - - // Get the main workflow content - const mainFile = getFile(MAIN_FILE); - const md = mainFile ? mainFile.content : ''; - if (!md.trim()) { - outputPre.style.display = 'none'; - outputPlaceholder.style.display = 'flex'; - outputPlaceholder.textContent = 'Compiled YAML will appear here'; - currentYaml = ''; - copyBtn.disabled = true; - return; - } - - isCompiling = true; - setStatus('compiling', 'Compiling...'); - compileBtn.disabled = true; - errorBanner.classList.remove('visible'); - warningBanner.classList.remove('visible'); - - try { - const importFiles = getImportFiles(); - const result = await compiler.compile(md, importFiles); - - if (result.error) { - setStatus('error', 'Error'); - errorText.textContent = result.error; - errorBanner.classList.add('visible'); - } else { - setStatus('ready', 'Ready'); - currentYaml = result.yaml; - outputPre.textContent = result.yaml; - outputPre.style.display = 'block'; - outputPlaceholder.style.display = 'none'; - copyBtn.disabled = false; - - if (result.warnings && result.warnings.length > 0) { - warningText.textContent = result.warnings.join('\n'); - warningBanner.classList.add('visible'); - } - } - } catch (err) { - setStatus('error', 'Error'); - errorText.textContent = err.message || String(err); - errorBanner.classList.add('visible'); - } finally { - isCompiling = false; - compileBtn.disabled = !isReady; - } -} - -compileBtn.addEventListener('click', doCompile); - -// --------------------------------------------------------------- -// Copy YAML -// --------------------------------------------------------------- -function showCopyFeedback() { - const feedback = $('copyFeedback'); - feedback.classList.add('show'); - setTimeout(() => feedback.classList.remove('show'), 2000); -} - -copyBtn.addEventListener('click', async () => { - if (!currentYaml) return; - try { - await navigator.clipboard.writeText(currentYaml); - } catch { - const ta = document.createElement('textarea'); - ta.value = currentYaml; - document.body.appendChild(ta); - ta.select(); - document.execCommand('copy'); - document.body.removeChild(ta); - } - showCopyFeedback(); -}); - -// --------------------------------------------------------------- -// Banner close -// --------------------------------------------------------------- -$('errorClose').addEventListener('click', () => errorBanner.classList.remove('visible')); -$('warningClose').addEventListener('click', () => warningBanner.classList.remove('visible')); - -// --------------------------------------------------------------- -// Draggable divider -// --------------------------------------------------------------- -let isDragging = false; - -function resizePanels(clientX, clientY) { - const rect = panels.getBoundingClientRect(); - const isMobile = window.innerWidth < 768; - const pos = isMobile ? clientY - rect.top : clientX - rect.left; - const size = isMobile ? rect.height : rect.width; - const clamped = Math.max(0.2, Math.min(0.8, pos / size)); - panelEditor.style.flex = `0 0 ${clamped * 100}%`; - panelOutput.style.flex = `0 0 ${(1 - clamped) * 100}%`; -} - -divider.addEventListener('mousedown', (e) => { - isDragging = true; - divider.classList.add('dragging'); - const isMobile = window.innerWidth < 768; - document.body.style.cursor = isMobile ? 'row-resize' : 'col-resize'; - document.body.style.userSelect = 'none'; - e.preventDefault(); -}); - -document.addEventListener('mousemove', (e) => { - if (isDragging) resizePanels(e.clientX, e.clientY); -}); - -document.addEventListener('mouseup', () => { - if (isDragging) { - isDragging = false; - divider.classList.remove('dragging'); - document.body.style.cursor = ''; - document.body.style.userSelect = ''; - } -}); - -divider.addEventListener('touchstart', (e) => { - isDragging = true; - divider.classList.add('dragging'); - e.preventDefault(); -}); - -document.addEventListener('touchmove', (e) => { - if (isDragging) resizePanels(e.touches[0].clientX, e.touches[0].clientY); -}); - -document.addEventListener('touchend', () => { - if (isDragging) { - isDragging = false; - divider.classList.remove('dragging'); - } -}); - -// --------------------------------------------------------------- -// Initialize compiler -// --------------------------------------------------------------- -async function init() { - try { - compiler = createWorkerCompiler({ - workerUrl: '/gh-aw/wasm/compiler-worker.js' - }); - - await compiler.ready; - isReady = true; - setStatus('ready', 'Ready'); - compileBtn.disabled = false; - loadingOverlay.classList.add('hidden'); - - if (autoCompile) doCompile(); - } catch (err) { - setStatus('error', 'Failed to load'); - loadingOverlay.querySelector('.loading-text').textContent = 'Failed to load compiler'; - loadingOverlay.querySelector('.loading-subtext').textContent = err.message; - loadingOverlay.querySelector('.loading-spinner').style.display = 'none'; - } -} - -init(); diff --git a/docs/public/editor/index.html b/docs/public/editor/index.html index da6cd99ff3a..20d8e15991a 100644 --- a/docs/public/editor/index.html +++ b/docs/public/editor/index.html @@ -1,705 +1,15 @@ - + - - -gh-aw Playground - - - + + + Agentic Workflow Builder + + + + - -
- -
-
-
Loading gh-aw compiler...
-
Downloading WebAssembly module (~17 MB)
-
- - -
- - - - - - gh-aw Playground - - -
- - - - Loading... - - -
- - - - - -
-
- - -
- - -
- - -
- - -
- - -
- -
-
- - - - - Workflow (.md) - - - - Ctrl+Enter - -
-
-
-
-
- -
-
- -
- - -
-
- - - - - Compiled Output (.lock.yml) - -
-
-
- Compiled YAML will appear here -
-

-      
-
-
- - - -
- - + +
diff --git a/docs/public/editor/wasm/compiler-loader.js b/docs/public/editor/wasm/compiler-loader.js new file mode 100644 index 00000000000..7117fa06e5f --- /dev/null +++ b/docs/public/editor/wasm/compiler-loader.js @@ -0,0 +1,196 @@ +/** + * compiler-loader.js -- ES module that spawns compiler-worker.js and + * provides a clean async API for the Astro docs site. + * + * Usage: + * import { createWorkerCompiler } from '/wasm/compiler-loader.js'; + * + * const compiler = createWorkerCompiler(); + * await compiler.ready; + * const { yaml, warnings, error } = await compiler.compile(markdownString); + * const { errors, warnings } = await compiler.validate(markdownString); + * // With imports: + * const { yaml } = await compiler.compile(markdown, { 'shared/tools.md': '...' }); + * compiler.terminate(); + */ + +/** + * Create a worker-backed compiler instance. + * + * @param {Object} [options] + * @param {string} [options.workerUrl] - URL to compiler-worker.js + * (default: resolves relative to this module) + * @returns {{ compile: (markdown: string, files?: Record) => Promise<{yaml: string, warnings: string[], error: string|null}>, + * ready: Promise, + * terminate: () => void }} + */ +export function createWorkerCompiler(options = {}) { + const moduleDir = new URL('.', import.meta.url).href; + const workerUrl = options.workerUrl || new URL('compiler-worker.js', moduleDir).href; + + const worker = new Worker(workerUrl); + + // Monotonically increasing message ID for correlating requests/responses. + let nextId = 1; + + // Map of pending compile requests: id -> { resolve, reject } + const pending = new Map(); + + // Ready promise -- resolves when the worker sends { type: 'ready' }. + let readyResolve; + let readyReject; + const ready = new Promise((resolve, reject) => { + readyResolve = resolve; + readyReject = reject; + }); + + let isReady = false; + let isTerminated = false; + + /** + * Handle messages from the worker. + */ + worker.onmessage = function (event) { + const msg = event.data; + + switch (msg.type) { + case 'ready': + isReady = true; + readyResolve(); + break; + + case 'result': { + const entry = pending.get(msg.id); + if (entry) { + pending.delete(msg.id); + entry.resolve({ + yaml: msg.yaml, + warnings: msg.warnings || [], + error: msg.error || null, + }); + } + break; + } + + case 'validate_result': { + const entry = pending.get(msg.id); + if (entry) { + pending.delete(msg.id); + entry.resolve({ + errors: msg.errors || [], + warnings: msg.warnings || [], + }); + } + break; + } + + case 'error': { + // An error with id === null means init failure. + if (msg.id === null || msg.id === undefined) { + readyReject(new Error(msg.error)); + break; + } + + const entry = pending.get(msg.id); + if (entry) { + pending.delete(msg.id); + // Wrap raw error strings into a structured CompilerError object + entry.resolve({ + yaml: '', + warnings: [], + error: { message: msg.error, severity: 'error' }, + }); + } + break; + } + } + }; + + /** + * Handle worker-level errors (e.g. script load failure). + */ + worker.onerror = function (event) { + const errorMsg = event.message || 'Unknown worker error'; + + if (!isReady) { + readyReject(new Error(errorMsg)); + } + + // Reject all pending requests. + for (const [id, entry] of pending) { + entry.reject(new Error(errorMsg)); + } + pending.clear(); + }; + + /** + * Compile a markdown workflow string to GitHub Actions YAML. + * + * @param {string} markdown + * @param {Record} [files] - Optional map of file paths to content + * for import resolution (e.g. {"shared/tools.md": "---\ntools:..."}) + * @returns {Promise<{yaml: string, warnings: string[], error: string|null}>} + */ + function compile(markdown, files) { + if (isTerminated) { + return Promise.reject(new Error('Compiler worker has been terminated.')); + } + + const id = nextId++; + + return new Promise((resolve, reject) => { + pending.set(id, { resolve, reject }); + const msg = { type: 'compile', id, markdown }; + if (files && Object.keys(files).length > 0) { + msg.files = files; + } + worker.postMessage(msg); + }); + } + + /** + * Validate a markdown workflow string with full schema validation enabled. + * + * @param {string} markdown + * @param {Record} [files] - Optional map of file paths to content + * @returns {Promise<{errors: Array<{field: string, message: string, severity: string}>, warnings: string[]}>} + */ + function validate(markdown, files) { + if (isTerminated) { + return Promise.reject(new Error('Compiler worker has been terminated.')); + } + + const id = nextId++; + + return new Promise((resolve, reject) => { + pending.set(id, { resolve, reject }); + const msg = { type: 'validate', id, markdown }; + if (files && Object.keys(files).length > 0) { + msg.files = files; + } + worker.postMessage(msg); + }); + } + + /** + * Terminate the worker. After this, compile() will reject. + */ + function terminate() { + if (isTerminated) return; + isTerminated = true; + worker.terminate(); + + // Reject anything still pending. + for (const [id, entry] of pending) { + entry.reject(new Error('Compiler worker was terminated.')); + } + pending.clear(); + } + + return { + compile, + validate, + ready, + terminate, + }; +} diff --git a/docs/public/editor/wasm/compiler-worker.js b/docs/public/editor/wasm/compiler-worker.js new file mode 100644 index 00000000000..682befd4cd3 --- /dev/null +++ b/docs/public/editor/wasm/compiler-worker.js @@ -0,0 +1,233 @@ +/** + * compiler-worker.js -- Web Worker that loads gh-aw.wasm and exposes + * the compileWorkflow and validateWorkflow functions via postMessage. + * + * Message protocol (inbound): + * { type: 'compile', id: , markdown: , files?: } + * { type: 'validate', id: , markdown: , files?: } + * + * Message protocol (outbound): + * { type: 'ready' } + * { type: 'result', id, yaml: , warnings: , error: null } + * { type: 'validate_result', id, errors: [{field, message, severity}], warnings: } + * { type: 'error', id, error: } + * + * This file is a classic script (not an ES module) because Web Workers + * need importScripts() to load wasm_exec.js synchronously. + */ + +/* global importScripts, Go, compileWorkflow, validateWorkflow, WebAssembly */ + +'use strict'; + +(function () { + // 1. Load Go's wasm_exec.js (provides the global `Go` class) + importScripts('./wasm_exec.js'); + + var ready = false; + + /** + * Initialize the Go WebAssembly runtime. + */ + async function init() { + try { + var go = new Go(); + + // Try streaming instantiation first; fall back to array buffer + // for servers that don't serve .wasm with application/wasm MIME type. + var result; + try { + result = await WebAssembly.instantiateStreaming( + fetch('./gh-aw.wasm'), + go.importObject, + ); + } catch (streamErr) { + var resp = await fetch('./gh-aw.wasm'); + var buf = await resp.arrayBuffer(); + result = await WebAssembly.instantiate(buf, go.importObject); + } + + // Start the Go program. go.run() never resolves because main() + // does `select{}`, so we intentionally do NOT await it. + go.run(result.instance); + + // Poll until the Go code has registered compileWorkflow on globalThis. + await waitForGlobal('compileWorkflow', 5000); + + ready = true; + self.postMessage({ type: 'ready' }); + } catch (err) { + self.postMessage({ + type: 'error', + id: null, + error: 'Worker initialization failed: ' + err.message, + }); + } + } + + /** + * Poll for a global property to appear. + */ + function waitForGlobal(name, timeoutMs) { + return new Promise(function (resolve, reject) { + var start = Date.now(); + (function check() { + if (typeof self[name] !== 'undefined') { + resolve(); + } else if (Date.now() - start > timeoutMs) { + reject(new Error('Timed out waiting for globalThis.' + name)); + } else { + setTimeout(check, 10); + } + })(); + }); + } + + /** + * Handle incoming messages from the main thread. + */ + self.onmessage = async function (event) { + var msg = event.data; + + if (msg.type === 'compile') { + await handleCompile(msg); + } else if (msg.type === 'validate') { + await handleValidate(msg); + } + }; + + /** + * Handle a compile request. + */ + async function handleCompile(msg) { + var id = msg.id; + + if (!ready) { + self.postMessage({ + type: 'error', + id: id, + error: 'Compiler is not ready yet.', + }); + return; + } + + if (typeof msg.markdown !== 'string') { + self.postMessage({ + type: 'error', + id: id, + error: 'markdown must be a string.', + }); + return; + } + + try { + // compileWorkflow returns a Promise (Go side). + // Pass optional files object for import resolution. + var files = msg.files || null; + var result = await compileWorkflow(msg.markdown, files); + + // The Go function returns { yaml: string, warnings: Array, error: null|object } + var warnings = []; + if (result.warnings) { + for (var i = 0; i < result.warnings.length; i++) { + warnings.push(result.warnings[i]); + } + } + + // error is now a structured object (or null) from Go, not a string + var errorObj = null; + if (result.error && typeof result.error === 'object') { + errorObj = { + message: result.error.message || '', + field: result.error.field || null, + line: result.error.line || null, + column: result.error.column || null, + severity: result.error.severity || 'error', + suggestion: result.error.suggestion || null, + docsUrl: result.error.docsUrl || null, + }; + } + + self.postMessage({ + type: 'result', + id: id, + yaml: result.yaml || '', + warnings: warnings, + error: errorObj, + }); + } catch (err) { + // Promise rejection = unexpected error, wrap in structured format + self.postMessage({ + type: 'error', + id: id, + error: err.message || String(err), + }); + } + } + + /** + * Handle a validate request. + */ + async function handleValidate(msg) { + var id = msg.id; + + if (!ready) { + self.postMessage({ + type: 'error', + id: id, + error: 'Compiler is not ready yet.', + }); + return; + } + + if (typeof msg.markdown !== 'string') { + self.postMessage({ + type: 'error', + id: id, + error: 'markdown must be a string.', + }); + return; + } + + try { + var files = msg.files || null; + var result = await validateWorkflow(msg.markdown, files); + + // The Go function returns { errors: [{field, message, severity}], warnings: [] } + var errors = []; + if (result.errors) { + for (var i = 0; i < result.errors.length; i++) { + var e = result.errors[i]; + errors.push({ + field: e.field || '', + message: e.message || '', + severity: e.severity || 'error', + }); + } + } + + var warnings = []; + if (result.warnings) { + for (var j = 0; j < result.warnings.length; j++) { + warnings.push(result.warnings[j]); + } + } + + self.postMessage({ + type: 'validate_result', + id: id, + errors: errors, + warnings: warnings, + }); + } catch (err) { + self.postMessage({ + type: 'error', + id: id, + error: err.message || String(err), + }); + } + } + + // Start initialization immediately. + init(); +})(); diff --git a/docs/public/editor/wasm/wasm_exec.js b/docs/public/editor/wasm/wasm_exec.js new file mode 100644 index 00000000000..d71af9e97e8 --- /dev/null +++ b/docs/public/editor/wasm/wasm_exec.js @@ -0,0 +1,575 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +"use strict"; + +(() => { + const enosys = () => { + const err = new Error("not implemented"); + err.code = "ENOSYS"; + return err; + }; + + if (!globalThis.fs) { + let outputBuf = ""; + globalThis.fs = { + constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused + writeSync(fd, buf) { + outputBuf += decoder.decode(buf); + const nl = outputBuf.lastIndexOf("\n"); + if (nl != -1) { + console.log(outputBuf.substring(0, nl)); + outputBuf = outputBuf.substring(nl + 1); + } + return buf.length; + }, + write(fd, buf, offset, length, position, callback) { + if (offset !== 0 || length !== buf.length || position !== null) { + callback(enosys()); + return; + } + const n = this.writeSync(fd, buf); + callback(null, n); + }, + chmod(path, mode, callback) { callback(enosys()); }, + chown(path, uid, gid, callback) { callback(enosys()); }, + close(fd, callback) { callback(enosys()); }, + fchmod(fd, mode, callback) { callback(enosys()); }, + fchown(fd, uid, gid, callback) { callback(enosys()); }, + fstat(fd, callback) { callback(enosys()); }, + fsync(fd, callback) { callback(null); }, + ftruncate(fd, length, callback) { callback(enosys()); }, + lchown(path, uid, gid, callback) { callback(enosys()); }, + link(path, link, callback) { callback(enosys()); }, + lstat(path, callback) { callback(enosys()); }, + mkdir(path, perm, callback) { callback(enosys()); }, + open(path, flags, mode, callback) { callback(enosys()); }, + read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, + readdir(path, callback) { callback(enosys()); }, + readlink(path, callback) { callback(enosys()); }, + rename(from, to, callback) { callback(enosys()); }, + rmdir(path, callback) { callback(enosys()); }, + stat(path, callback) { callback(enosys()); }, + symlink(path, link, callback) { callback(enosys()); }, + truncate(path, length, callback) { callback(enosys()); }, + unlink(path, callback) { callback(enosys()); }, + utimes(path, atime, mtime, callback) { callback(enosys()); }, + }; + } + + if (!globalThis.process) { + globalThis.process = { + getuid() { return -1; }, + getgid() { return -1; }, + geteuid() { return -1; }, + getegid() { return -1; }, + getgroups() { throw enosys(); }, + pid: -1, + ppid: -1, + umask() { throw enosys(); }, + cwd() { throw enosys(); }, + chdir() { throw enosys(); }, + } + } + + if (!globalThis.path) { + globalThis.path = { + resolve(...pathSegments) { + return pathSegments.join("/"); + } + } + } + + if (!globalThis.crypto) { + throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)"); + } + + if (!globalThis.performance) { + throw new Error("globalThis.performance is not available, polyfill required (performance.now only)"); + } + + if (!globalThis.TextEncoder) { + throw new Error("globalThis.TextEncoder is not available, polyfill required"); + } + + if (!globalThis.TextDecoder) { + throw new Error("globalThis.TextDecoder is not available, polyfill required"); + } + + const encoder = new TextEncoder("utf-8"); + const decoder = new TextDecoder("utf-8"); + + globalThis.Go = class { + constructor() { + this.argv = ["js"]; + this.env = {}; + this.exit = (code) => { + if (code !== 0) { + console.warn("exit code:", code); + } + }; + this._exitPromise = new Promise((resolve) => { + this._resolveExitPromise = resolve; + }); + this._pendingEvent = null; + this._scheduledTimeouts = new Map(); + this._nextCallbackTimeoutID = 1; + + const setInt64 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); + } + + const setInt32 = (addr, v) => { + this.mem.setUint32(addr + 0, v, true); + } + + const getInt64 = (addr) => { + const low = this.mem.getUint32(addr + 0, true); + const high = this.mem.getInt32(addr + 4, true); + return low + high * 4294967296; + } + + const loadValue = (addr) => { + const f = this.mem.getFloat64(addr, true); + if (f === 0) { + return undefined; + } + if (!isNaN(f)) { + return f; + } + + const id = this.mem.getUint32(addr, true); + return this._values[id]; + } + + const storeValue = (addr, v) => { + const nanHead = 0x7FF80000; + + if (typeof v === "number" && v !== 0) { + if (isNaN(v)) { + this.mem.setUint32(addr + 4, nanHead, true); + this.mem.setUint32(addr, 0, true); + return; + } + this.mem.setFloat64(addr, v, true); + return; + } + + if (v === undefined) { + this.mem.setFloat64(addr, 0, true); + return; + } + + let id = this._ids.get(v); + if (id === undefined) { + id = this._idPool.pop(); + if (id === undefined) { + id = this._values.length; + } + this._values[id] = v; + this._goRefCounts[id] = 0; + this._ids.set(v, id); + } + this._goRefCounts[id]++; + let typeFlag = 0; + switch (typeof v) { + case "object": + if (v !== null) { + typeFlag = 1; + } + break; + case "string": + typeFlag = 2; + break; + case "symbol": + typeFlag = 3; + break; + case "function": + typeFlag = 4; + break; + } + this.mem.setUint32(addr + 4, nanHead | typeFlag, true); + this.mem.setUint32(addr, id, true); + } + + const loadSlice = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + return new Uint8Array(this._inst.exports.mem.buffer, array, len); + } + + const loadSliceOfValues = (addr) => { + const array = getInt64(addr + 0); + const len = getInt64(addr + 8); + const a = new Array(len); + for (let i = 0; i < len; i++) { + a[i] = loadValue(array + i * 8); + } + return a; + } + + const loadString = (addr) => { + const saddr = getInt64(addr + 0); + const len = getInt64(addr + 8); + return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); + } + + const testCallExport = (a, b) => { + this._inst.exports.testExport0(); + return this._inst.exports.testExport(a, b); + } + + const timeOrigin = Date.now() - performance.now(); + this.importObject = { + _gotest: { + add: (a, b) => a + b, + callExport: testCallExport, + }, + gojs: { + // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) + // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported + // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). + // This changes the SP, thus we have to update the SP used by the imported function. + + // func wasmExit(code int32) + "runtime.wasmExit": (sp) => { + sp >>>= 0; + const code = this.mem.getInt32(sp + 8, true); + this.exited = true; + delete this._inst; + delete this._values; + delete this._goRefCounts; + delete this._ids; + delete this._idPool; + this.exit(code); + }, + + // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) + "runtime.wasmWrite": (sp) => { + sp >>>= 0; + const fd = getInt64(sp + 8); + const p = getInt64(sp + 16); + const n = this.mem.getInt32(sp + 24, true); + fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); + }, + + // func resetMemoryDataView() + "runtime.resetMemoryDataView": (sp) => { + sp >>>= 0; + this.mem = new DataView(this._inst.exports.mem.buffer); + }, + + // func nanotime1() int64 + "runtime.nanotime1": (sp) => { + sp >>>= 0; + setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); + }, + + // func walltime() (sec int64, nsec int32) + "runtime.walltime": (sp) => { + sp >>>= 0; + const msec = (new Date).getTime(); + setInt64(sp + 8, msec / 1000); + this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); + }, + + // func scheduleTimeoutEvent(delay int64) int32 + "runtime.scheduleTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this._nextCallbackTimeoutID; + this._nextCallbackTimeoutID++; + this._scheduledTimeouts.set(id, setTimeout( + () => { + this._resume(); + while (this._scheduledTimeouts.has(id)) { + // for some reason Go failed to register the timeout event, log and try again + // (temporary workaround for https://github.com/golang/go/issues/28975) + console.warn("scheduleTimeoutEvent: missed timeout event"); + this._resume(); + } + }, + getInt64(sp + 8), + )); + this.mem.setInt32(sp + 16, id, true); + }, + + // func clearTimeoutEvent(id int32) + "runtime.clearTimeoutEvent": (sp) => { + sp >>>= 0; + const id = this.mem.getInt32(sp + 8, true); + clearTimeout(this._scheduledTimeouts.get(id)); + this._scheduledTimeouts.delete(id); + }, + + // func getRandomData(r []byte) + "runtime.getRandomData": (sp) => { + sp >>>= 0; + crypto.getRandomValues(loadSlice(sp + 8)); + }, + + // func finalizeRef(v ref) + "syscall/js.finalizeRef": (sp) => { + sp >>>= 0; + const id = this.mem.getUint32(sp + 8, true); + this._goRefCounts[id]--; + if (this._goRefCounts[id] === 0) { + const v = this._values[id]; + this._values[id] = null; + this._ids.delete(v); + this._idPool.push(id); + } + }, + + // func stringVal(value string) ref + "syscall/js.stringVal": (sp) => { + sp >>>= 0; + storeValue(sp + 24, loadString(sp + 8)); + }, + + // func valueGet(v ref, p string) ref + "syscall/js.valueGet": (sp) => { + sp >>>= 0; + const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 32, result); + }, + + // func valueSet(v ref, p string, x ref) + "syscall/js.valueSet": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); + }, + + // func valueDelete(v ref, p string) + "syscall/js.valueDelete": (sp) => { + sp >>>= 0; + Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); + }, + + // func valueIndex(v ref, i int) ref + "syscall/js.valueIndex": (sp) => { + sp >>>= 0; + storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); + }, + + // valueSetIndex(v ref, i int, x ref) + "syscall/js.valueSetIndex": (sp) => { + sp >>>= 0; + Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); + }, + + // func valueCall(v ref, m string, args []ref) (ref, bool) + "syscall/js.valueCall": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const m = Reflect.get(v, loadString(sp + 16)); + const args = loadSliceOfValues(sp + 32); + const result = Reflect.apply(m, v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, result); + this.mem.setUint8(sp + 64, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 56, err); + this.mem.setUint8(sp + 64, 0); + } + }, + + // func valueInvoke(v ref, args []ref) (ref, bool) + "syscall/js.valueInvoke": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.apply(v, undefined, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueNew(v ref, args []ref) (ref, bool) + "syscall/js.valueNew": (sp) => { + sp >>>= 0; + try { + const v = loadValue(sp + 8); + const args = loadSliceOfValues(sp + 16); + const result = Reflect.construct(v, args); + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, result); + this.mem.setUint8(sp + 48, 1); + } catch (err) { + sp = this._inst.exports.getsp() >>> 0; // see comment above + storeValue(sp + 40, err); + this.mem.setUint8(sp + 48, 0); + } + }, + + // func valueLength(v ref) int + "syscall/js.valueLength": (sp) => { + sp >>>= 0; + setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); + }, + + // valuePrepareString(v ref) (ref, int) + "syscall/js.valuePrepareString": (sp) => { + sp >>>= 0; + const str = encoder.encode(String(loadValue(sp + 8))); + storeValue(sp + 16, str); + setInt64(sp + 24, str.length); + }, + + // valueLoadString(v ref, b []byte) + "syscall/js.valueLoadString": (sp) => { + sp >>>= 0; + const str = loadValue(sp + 8); + loadSlice(sp + 16).set(str); + }, + + // func valueInstanceOf(v ref, t ref) bool + "syscall/js.valueInstanceOf": (sp) => { + sp >>>= 0; + this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); + }, + + // func copyBytesToGo(dst []byte, src ref) (int, bool) + "syscall/js.copyBytesToGo": (sp) => { + sp >>>= 0; + const dst = loadSlice(sp + 8); + const src = loadValue(sp + 32); + if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + // func copyBytesToJS(dst ref, src []byte) (int, bool) + "syscall/js.copyBytesToJS": (sp) => { + sp >>>= 0; + const dst = loadValue(sp + 8); + const src = loadSlice(sp + 16); + if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { + this.mem.setUint8(sp + 48, 0); + return; + } + const toCopy = src.subarray(0, dst.length); + dst.set(toCopy); + setInt64(sp + 40, toCopy.length); + this.mem.setUint8(sp + 48, 1); + }, + + "debug": (value) => { + console.log(value); + }, + } + }; + } + + async run(instance) { + if (!(instance instanceof WebAssembly.Instance)) { + throw new Error("Go.run: WebAssembly.Instance expected"); + } + this._inst = instance; + this.mem = new DataView(this._inst.exports.mem.buffer); + this._values = [ // JS values that Go currently has references to, indexed by reference id + NaN, + 0, + null, + true, + false, + globalThis, + this, + ]; + this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id + this._ids = new Map([ // mapping from JS values to reference ids + [0, 1], + [null, 2], + [true, 3], + [false, 4], + [globalThis, 5], + [this, 6], + ]); + this._idPool = []; // unused ids that have been garbage collected + this.exited = false; // whether the Go program has exited + + // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. + let offset = 4096; + + const strPtr = (str) => { + const ptr = offset; + const bytes = encoder.encode(str + "\0"); + new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); + offset += bytes.length; + if (offset % 8 !== 0) { + offset += 8 - (offset % 8); + } + return ptr; + }; + + const argc = this.argv.length; + + const argvPtrs = []; + this.argv.forEach((arg) => { + argvPtrs.push(strPtr(arg)); + }); + argvPtrs.push(0); + + const keys = Object.keys(this.env).sort(); + keys.forEach((key) => { + argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); + }); + argvPtrs.push(0); + + const argv = offset; + argvPtrs.forEach((ptr) => { + this.mem.setUint32(offset, ptr, true); + this.mem.setUint32(offset + 4, 0, true); + offset += 8; + }); + + // The linker guarantees global data starts from at least wasmMinDataAddr. + // Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr. + const wasmMinDataAddr = 4096 + 8192; + if (offset >= wasmMinDataAddr) { + throw new Error("total length of command line and environment variables exceeds limit"); + } + + this._inst.exports.run(argc, argv); + if (this.exited) { + this._resolveExitPromise(); + } + await this._exitPromise; + } + + _resume() { + if (this.exited) { + throw new Error("Go program has already exited"); + } + this._inst.exports.resume(); + if (this.exited) { + this._resolveExitPromise(); + } + } + + _makeFuncWrapper(id) { + const go = this; + return function () { + const event = { id: id, this: this, args: arguments }; + go._pendingEvent = event; + go._resume(); + return event.result; + }; + } + } +})(); diff --git a/docs/public/wasm/compiler-loader.js b/docs/public/wasm/compiler-loader.js index 5cb87783565..7117fa06e5f 100644 --- a/docs/public/wasm/compiler-loader.js +++ b/docs/public/wasm/compiler-loader.js @@ -8,6 +8,7 @@ * const compiler = createWorkerCompiler(); * await compiler.ready; * const { yaml, warnings, error } = await compiler.compile(markdownString); + * const { errors, warnings } = await compiler.validate(markdownString); * // With imports: * const { yaml } = await compiler.compile(markdown, { 'shared/tools.md': '...' }); * compiler.terminate(); @@ -65,7 +66,19 @@ export function createWorkerCompiler(options = {}) { entry.resolve({ yaml: msg.yaml, warnings: msg.warnings || [], - error: null, + error: msg.error || null, + }); + } + break; + } + + case 'validate_result': { + const entry = pending.get(msg.id); + if (entry) { + pending.delete(msg.id); + entry.resolve({ + errors: msg.errors || [], + warnings: msg.warnings || [], }); } break; @@ -81,10 +94,11 @@ export function createWorkerCompiler(options = {}) { const entry = pending.get(msg.id); if (entry) { pending.delete(msg.id); + // Wrap raw error strings into a structured CompilerError object entry.resolve({ yaml: '', warnings: [], - error: msg.error, + error: { message: msg.error, severity: 'error' }, }); } break; @@ -134,6 +148,30 @@ export function createWorkerCompiler(options = {}) { }); } + /** + * Validate a markdown workflow string with full schema validation enabled. + * + * @param {string} markdown + * @param {Record} [files] - Optional map of file paths to content + * @returns {Promise<{errors: Array<{field: string, message: string, severity: string}>, warnings: string[]}>} + */ + function validate(markdown, files) { + if (isTerminated) { + return Promise.reject(new Error('Compiler worker has been terminated.')); + } + + const id = nextId++; + + return new Promise((resolve, reject) => { + pending.set(id, { resolve, reject }); + const msg = { type: 'validate', id, markdown }; + if (files && Object.keys(files).length > 0) { + msg.files = files; + } + worker.postMessage(msg); + }); + } + /** * Terminate the worker. After this, compile() will reject. */ @@ -151,6 +189,7 @@ export function createWorkerCompiler(options = {}) { return { compile, + validate, ready, terminate, }; diff --git a/docs/public/wasm/compiler-worker.js b/docs/public/wasm/compiler-worker.js index 5d9349a151c..682befd4cd3 100644 --- a/docs/public/wasm/compiler-worker.js +++ b/docs/public/wasm/compiler-worker.js @@ -1,20 +1,22 @@ /** * compiler-worker.js -- Web Worker that loads gh-aw.wasm and exposes - * the compileWorkflow function via postMessage. + * the compileWorkflow and validateWorkflow functions via postMessage. * * Message protocol (inbound): - * { type: 'compile', id: , markdown: , files?: } + * { type: 'compile', id: , markdown: , files?: } + * { type: 'validate', id: , markdown: , files?: } * * Message protocol (outbound): * { type: 'ready' } - * { type: 'result', id: , yaml: , warnings: , error: null } - * { type: 'error', id: , error: } + * { type: 'result', id, yaml: , warnings: , error: null } + * { type: 'validate_result', id, errors: [{field, message, severity}], warnings: } + * { type: 'error', id, error: } * * This file is a classic script (not an ES module) because Web Workers * need importScripts() to load wasm_exec.js synchronously. */ -/* global importScripts, Go, compileWorkflow, WebAssembly */ +/* global importScripts, Go, compileWorkflow, validateWorkflow, WebAssembly */ 'use strict'; @@ -87,10 +89,17 @@ self.onmessage = async function (event) { var msg = event.data; - if (msg.type !== 'compile') { - return; + if (msg.type === 'compile') { + await handleCompile(msg); + } else if (msg.type === 'validate') { + await handleValidate(msg); } + }; + /** + * Handle a compile request. + */ + async function handleCompile(msg) { var id = msg.id; if (!ready) { @@ -117,7 +126,7 @@ var files = msg.files || null; var result = await compileWorkflow(msg.markdown, files); - // The Go function returns { yaml: string, warnings: Array, error: null|string } + // The Go function returns { yaml: string, warnings: Array, error: null|object } var warnings = []; if (result.warnings) { for (var i = 0; i < result.warnings.length; i++) { @@ -125,29 +134,99 @@ } } - if (result.error) { - self.postMessage({ - type: 'error', - id: id, - error: String(result.error), - }); - } else { - self.postMessage({ - type: 'result', - id: id, - yaml: result.yaml || '', - warnings: warnings, - error: null, - }); + // error is now a structured object (or null) from Go, not a string + var errorObj = null; + if (result.error && typeof result.error === 'object') { + errorObj = { + message: result.error.message || '', + field: result.error.field || null, + line: result.error.line || null, + column: result.error.column || null, + severity: result.error.severity || 'error', + suggestion: result.error.suggestion || null, + docsUrl: result.error.docsUrl || null, + }; } + + self.postMessage({ + type: 'result', + id: id, + yaml: result.yaml || '', + warnings: warnings, + error: errorObj, + }); } catch (err) { + // Promise rejection = unexpected error, wrap in structured format self.postMessage({ type: 'error', id: id, error: err.message || String(err), }); } - }; + } + + /** + * Handle a validate request. + */ + async function handleValidate(msg) { + var id = msg.id; + + if (!ready) { + self.postMessage({ + type: 'error', + id: id, + error: 'Compiler is not ready yet.', + }); + return; + } + + if (typeof msg.markdown !== 'string') { + self.postMessage({ + type: 'error', + id: id, + error: 'markdown must be a string.', + }); + return; + } + + try { + var files = msg.files || null; + var result = await validateWorkflow(msg.markdown, files); + + // The Go function returns { errors: [{field, message, severity}], warnings: [] } + var errors = []; + if (result.errors) { + for (var i = 0; i < result.errors.length; i++) { + var e = result.errors[i]; + errors.push({ + field: e.field || '', + message: e.message || '', + severity: e.severity || 'error', + }); + } + } + + var warnings = []; + if (result.warnings) { + for (var j = 0; j < result.warnings.length; j++) { + warnings.push(result.warnings[j]); + } + } + + self.postMessage({ + type: 'validate_result', + id: id, + errors: errors, + warnings: warnings, + }); + } catch (err) { + self.postMessage({ + type: 'error', + id: id, + error: err.message || String(err), + }); + } + } // Start initialization immediately. init();