From 5ebd3008f6e743316da78727143634caee953801 Mon Sep 17 00:00:00 2001 From: Thomas Hallock Date: Sun, 18 Jan 2026 09:24:28 -0600 Subject: [PATCH] Add visual debug mode and documentation for flowchart system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add DebugMermaidDiagram component to visualize flowchart with current node highlighted in debug mode - Add DebugStepTimeline for undo/redo navigation through flowchart steps - Add rawMermaid field to ExecutableFlowchart schema for debug rendering - Add comprehensive README.md documenting the flowchart walker system - Add JSDoc comments to parser.ts, loader.ts, definitions/index.ts - Add flowchart section to .claude/CLAUDE.md - Add PageWithNav hamburger menu to flowchart page - Fix MathDisplay to show implicit coefficient of 1 (1x → x) - Install mermaid dependency for flowchart visualization Co-Authored-By: Claude Opus 4.5 --- apps/web/.claude/CLAUDE.md | 42 + apps/web/package.json | 1 + .../src/app/flowchart/[flowchartId]/page.tsx | 53 +- .../flowchart/AnimatedMathDisplay.tsx | 6 +- .../flowchart/DebugMermaidDiagram.tsx | 145 ++++ .../flowchart/DebugStepTimeline.tsx | 320 +++++++ .../components/flowchart/FlowchartWalker.tsx | 292 ++++++- .../src/components/flowchart/MathDisplay.tsx | 7 +- apps/web/src/lib/flowcharts/README.md | 231 +++++ .../src/lib/flowcharts/definitions/index.ts | 61 +- apps/web/src/lib/flowcharts/loader.ts | 65 +- apps/web/src/lib/flowcharts/parser.ts | 121 ++- apps/web/src/lib/flowcharts/schema.ts | 51 +- .../__tests__/memoizationBenchmark.test.ts | 3 +- .../sessionGenerationBenchmark.test.ts | 4 +- .../__tests__/skillCostMemoization.test.ts | 28 +- pnpm-lock.yaml | 816 +++++++++++++++++- 17 files changed, 2143 insertions(+), 103 deletions(-) create mode 100644 apps/web/src/components/flowchart/DebugMermaidDiagram.tsx create mode 100644 apps/web/src/components/flowchart/DebugStepTimeline.tsx create mode 100644 apps/web/src/lib/flowcharts/README.md diff --git a/apps/web/.claude/CLAUDE.md b/apps/web/.claude/CLAUDE.md index cd939ebf..6f63ae34 100644 --- a/apps/web/.claude/CLAUDE.md +++ b/apps/web/.claude/CLAUDE.md @@ -1382,6 +1382,48 @@ If you find yourself: 3. [ ] Am I using `mutation.isPending` instead of manual loading state? 4. [ ] Am I NOT using `router.refresh()` for cache updates? +## Flowchart Walker System + +When working on the interactive flowchart walker system, refer to: + +- **[`src/lib/flowcharts/README.md`](../src/lib/flowcharts/README.md)** - Complete system documentation + - Architecture overview (JSON definitions + Mermaid content) + - Where to find files for each flowchart + - Node types and their behavior + - Data flow and key functions + - Adding new flowcharts + +**CRITICAL: Finding Mermaid Content** + +Mermaid content is **NOT always in separate `.mmd` files!** Many flowcharts embed their mermaid content directly in `definitions/index.ts`. + +| Flowchart ID | JSON Definition | Mermaid Content | +|--------------|-----------------|-----------------| +| `subtraction-regrouping` | `definitions/subtraction-regrouping.flow.json` | `definitions/subtraction-regrouping-flowchart.mmd` | +| `fraction-add-sub` | `definitions/fraction-add-sub.flow.json` | **EMBEDDED** in `definitions/index.ts` as `FRACTION_MERMAID` | +| `linear-equations` | `definitions/linear-equations.flow.json` | **EMBEDDED** in `definitions/index.ts` as `LINEAR_EQUATIONS_MERMAID` | + +**To find node content for a flowchart:** +1. **First check `definitions/index.ts`** - search for the node ID (e.g., `READY1`) +2. If not embedded, check the `.mmd` file referenced in the JSON's `mermaidFile` field + +**Key Files:** + +- `src/lib/flowcharts/definitions/index.ts` - **Registry + EMBEDDED MERMAID CONTENT** +- `src/lib/flowcharts/definitions/*.flow.json` - JSON behavior definitions +- `src/lib/flowcharts/loader.ts` - Merges JSON + Mermaid into ExecutableFlowchart +- `src/lib/flowcharts/parser.ts` - Parses Mermaid content into nodes/edges/phases +- `src/lib/flowcharts/evaluator.ts` - Expression evaluation engine +- `src/components/flowchart/FlowchartWalker.tsx` - Main UI component + +**Two-File Architecture:** + +Each flowchart has two parts: +1. **JSON definition** (`.flow.json`): Node types, validation logic, variables, constraints +2. **Mermaid content** (`.mmd` or embedded): Visual presentation, node text, phases + +The loader merges these into an `ExecutableFlowchart` at runtime. + ## Daily Practice System When working on the curriculum-based daily practice system, refer to: diff --git a/apps/web/package.json b/apps/web/package.json index 81df9756..ed838b60 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -93,6 +93,7 @@ "lib0": "^0.2.114", "lucide-react": "^0.294.0", "make-plural": "^7.4.0", + "mermaid": "^11.12.2", "nanoid": "^5.1.6", "next": "^14.2.32", "next-auth": "5.0.0-beta.29", diff --git a/apps/web/src/app/flowchart/[flowchartId]/page.tsx b/apps/web/src/app/flowchart/[flowchartId]/page.tsx index b03557da..ae63b198 100644 --- a/apps/web/src/app/flowchart/[flowchartId]/page.tsx +++ b/apps/web/src/app/flowchart/[flowchartId]/page.tsx @@ -7,8 +7,9 @@ import type { ExecutableFlowchart, ProblemValue } from '@/lib/flowcharts/schema' import { loadFlowchart } from '@/lib/flowcharts/loader' import { getFlowchart } from '@/lib/flowcharts/definitions' import { FlowchartWalker, FlowchartProblemInput } from '@/components/flowchart' +import { PageWithNav } from '@/components/PageWithNav' import { css } from '../../../../styled-system/css' -import { vstack, hstack } from '../../../../styled-system/patterns' +import { vstack } from '../../../../styled-system/patterns' type PageState = | { type: 'loading' } @@ -101,33 +102,34 @@ export default function FlowchartPage() { router.push('/flowchart') }, [router]) + // Nav slot content - Back to flowcharts link + const navSlot = ( + + ← Back to flowcharts + + ) + // Render based on state return ( -
- {/* Header */} -
- +
+ {/* Main content */} +
- ← Back to flowcharts - -
- - {/* Main content */} -
{state.type === 'loading' && (
)} -
-
+ + + ) } diff --git a/apps/web/src/components/flowchart/AnimatedMathDisplay.tsx b/apps/web/src/components/flowchart/AnimatedMathDisplay.tsx index 733a75c4..2e708b88 100644 --- a/apps/web/src/components/flowchart/AnimatedMathDisplay.tsx +++ b/apps/web/src/components/flowchart/AnimatedMathDisplay.tsx @@ -27,9 +27,9 @@ export function AnimatedMathDisplay({ }: AnimatedMathDisplayProps) { const prevExpressionRef = useRef(expression) - const [layers, setLayers] = useState< - Array<{ expression: string; opacity: number; id: number }> - >([{ expression, opacity: 1, id: 0 }]) + const [layers, setLayers] = useState>([ + { expression, opacity: 1, id: 0 }, + ]) const idCounter = useRef(1) diff --git a/apps/web/src/components/flowchart/DebugMermaidDiagram.tsx b/apps/web/src/components/flowchart/DebugMermaidDiagram.tsx new file mode 100644 index 00000000..ac5b75a0 --- /dev/null +++ b/apps/web/src/components/flowchart/DebugMermaidDiagram.tsx @@ -0,0 +1,145 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' +import { css } from '../../../styled-system/css' + +interface DebugMermaidDiagramProps { + /** Raw mermaid content */ + mermaidContent: string + /** Current node ID to highlight */ + currentNodeId: string +} + +/** + * DebugMermaidDiagram - Renders a mermaid flowchart with the current node highlighted. + * + * Only rendered when visual debug mode is enabled. + * Uses mermaid.js to render the flowchart SVG with custom styling for the current node. + */ +export function DebugMermaidDiagram({ mermaidContent, currentNodeId }: DebugMermaidDiagramProps) { + const containerRef = useRef(null) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + let mounted = true + + async function renderDiagram() { + if (!containerRef.current) return + + setIsLoading(true) + setError(null) + + try { + // Dynamic import to avoid SSR issues + const mermaid = (await import('mermaid')).default + + // Initialize mermaid with custom config + mermaid.initialize({ + startOnLoad: false, + theme: 'default', + securityLevel: 'loose', + flowchart: { + useMaxWidth: true, + htmlLabels: true, + curve: 'basis', + }, + }) + + // Add style definition to highlight the current node + // We append this to the mermaid content + const highlightStyle = ` + style ${currentNodeId} fill:#fbbf24,stroke:#d97706,stroke-width:4px,color:#000 +` + // Insert the highlight style before the last closing style or at the end + const contentWithHighlight = mermaidContent + '\n' + highlightStyle + + // Generate unique ID for this render + const id = `mermaid-debug-${Date.now()}` + + // Render the diagram + const { svg } = await mermaid.render(id, contentWithHighlight) + + if (mounted && containerRef.current) { + containerRef.current.innerHTML = svg + + // Make the SVG responsive + const svgElement = containerRef.current.querySelector('svg') + if (svgElement) { + svgElement.style.maxWidth = '100%' + svgElement.style.height = 'auto' + } + } + } catch (err) { + console.error('Mermaid render error:', err) + if (mounted) { + setError(err instanceof Error ? err.message : 'Failed to render diagram') + } + } finally { + if (mounted) { + setIsLoading(false) + } + } + } + + renderDiagram() + + return () => { + mounted = false + } + }, [mermaidContent, currentNodeId]) + + if (error) { + return ( +
+ Failed to render flowchart: {error} +
+ ) + } + + return ( +
+ {isLoading && ( +
+ Loading flowchart... +
+ )} +
+
+ ) +} diff --git a/apps/web/src/components/flowchart/DebugStepTimeline.tsx b/apps/web/src/components/flowchart/DebugStepTimeline.tsx new file mode 100644 index 00000000..74e8d8ab --- /dev/null +++ b/apps/web/src/components/flowchart/DebugStepTimeline.tsx @@ -0,0 +1,320 @@ +'use client' + +import { css } from '../../../styled-system/css' +import { hstack } from '../../../styled-system/patterns' +import type { FlowchartState } from '@/lib/flowcharts/schema' + +interface TimelineStep { + state: FlowchartState + nodeTitle: string +} + +interface DebugStepTimelineProps { + /** All states in the timeline (history + current + redo) */ + steps: TimelineStep[] + /** Index of current step */ + currentIndex: number + /** Navigate to specific step */ + onNavigate: (index: number) => void + /** Can go back */ + canGoBack: boolean + /** Can go forward (redo stack has items) */ + canGoForward: boolean + /** Can skip (not at terminal) */ + canSkip: boolean + onBack: () => void + onForward: () => void + /** Skip/auto-advance through current node */ + onSkip: () => void + /** Whether auto-advance is paused */ + autoAdvancePaused: boolean + /** Toggle auto-advance pause */ + onToggleAutoAdvance: () => void +} + +/** + * DebugStepTimeline - A horizontal timeline showing all steps when visual debug is enabled. + * + * Shows: + * - Past steps (green) - from stateHistory + * - Current step (blue, highlighted) + * - Future steps (gray) - from redoStack + * + * Users can click any step to jump directly to it. + */ +export function DebugStepTimeline({ + steps, + currentIndex, + onNavigate, + canGoBack, + canGoForward, + canSkip, + onBack, + onForward, + onSkip, + autoAdvancePaused, + onToggleAutoAdvance, +}: DebugStepTimelineProps) { + if (steps.length === 0) return null + + return ( +
+ {/* Header with nav buttons */} +
+ {/* Back button */} + + + {/* Title and auto-advance toggle */} +
+ + Debug ({currentIndex + 1}/{steps.length}) + + + {/* Auto-advance toggle */} + +
+ + {/* Navigation buttons */} +
+ {/* Forward button (redo) */} + + + {/* Skip button (auto-advance) */} + +
+
+ + {/* Step timeline */} +
+ {steps.map((step, idx) => { + const isPast = idx < currentIndex + const isCurrent = idx === currentIndex + const isFuture = idx > currentIndex + + return ( + + ) + })} +
+
+ ) +} diff --git a/apps/web/src/components/flowchart/FlowchartWalker.tsx b/apps/web/src/components/flowchart/FlowchartWalker.tsx index 24c22ed9..9f9b95a2 100644 --- a/apps/web/src/components/flowchart/FlowchartWalker.tsx +++ b/apps/web/src/components/flowchart/FlowchartWalker.tsx @@ -8,6 +8,7 @@ import type { DecisionNode, CheckpointNode, } from '@/lib/flowcharts/schema' +import { useVisualDebugSafe } from '@/contexts/VisualDebugContext' import { initializeState, getNextNode, @@ -28,6 +29,8 @@ import { FlowchartDecision } from './FlowchartDecision' import { FlowchartCheckpoint } from './FlowchartCheckpoint' import { FlowchartPhaseRail } from './FlowchartPhaseRail' import { MathDisplay } from './MathDisplay' +import { DebugStepTimeline } from './DebugStepTimeline' +import { DebugMermaidDiagram } from './DebugMermaidDiagram' // ============================================================================= // Types @@ -86,13 +89,20 @@ export function FlowchartWalker({ const [wrongDecision, setWrongDecision] = useState(null) // History stack for back navigation (stores full state snapshots) const [stateHistory, setStateHistory] = useState([]) + // Redo stack for forward navigation (when user goes back) + const [redoStack, setRedoStack] = useState([]) // Track checked checklist items for the current node const [checkedItems, setCheckedItems] = useState>(new Set()) + // Debug mode: pause auto-advance to inspect nodes + const [autoAdvancePaused, setAutoAdvancePaused] = useState(false) // Track browser history depth for this walker session const historyDepthRef = useRef(0) // Flag to prevent double-handling when we programmatically go back const isNavigatingBackRef = useRef(false) + // Visual debug mode + const { isVisualDebugEnabled } = useVisualDebugSafe() + // Current node const currentNode = useMemo( () => flowchart.nodes[state.currentNode], @@ -106,6 +116,39 @@ export function FlowchartWalker({ currentChecklist && currentChecklist.length > 0 + // Build timeline steps for visual debug mode + const timelineSteps = useMemo(() => { + if (!isVisualDebugEnabled) return [] + + const steps: Array<{ state: FlowchartState; nodeTitle: string }> = [] + + // Add states from history (past steps) + for (const s of stateHistory) { + const node = flowchart.nodes[s.currentNode] + steps.push({ + state: s, + nodeTitle: node?.content?.title || s.currentNode, + }) + } + + // Add current state + steps.push({ + state, + nodeTitle: currentNode?.content?.title || state.currentNode, + }) + + // Add states from redo stack (future steps) - reversed so oldest redo is first + for (const s of redoStack.slice().reverse()) { + const node = flowchart.nodes[s.currentNode] + steps.push({ + state: s, + nodeTitle: node?.content?.title || s.currentNode, + }) + } + + return steps + }, [isVisualDebugEnabled, stateHistory, state, redoStack, flowchart.nodes, currentNode]) + // Reset checked items when node changes useEffect(() => { setCheckedItems(new Set()) @@ -119,6 +162,12 @@ export function FlowchartWalker({ historyDepthRef.current-- isNavigatingBackRef.current = true + // Push current state to redo stack before going back + setState((currentState) => { + setRedoStack((prev) => [...prev, currentState]) + return currentState + }) + const previousState = stateHistory[stateHistory.length - 1] setStateHistory((prev) => prev.slice(0, -1)) setState(previousState) @@ -171,6 +220,9 @@ export function FlowchartWalker({ // Save current state to history before advancing setStateHistory((prev) => [...prev, state]) + // Clear redo stack - user has made a new choice, branching invalidates redo + setRedoStack([]) + // Push browser history entry so back button works historyDepthRef.current++ window.history.pushState({ flowchartStep: historyDepthRef.current }, '') @@ -194,13 +246,16 @@ export function FlowchartWalker({ // Check if new node is terminal if (isTerminal(flowchart, nextNodeId)) { - setTimeout(() => { - setPhase({ type: 'complete' }) - onComplete?.(newState) - }, 500) + // If auto-advance is paused, don't auto-complete - let user inspect the terminal node + if (!autoAdvancePaused) { + setTimeout(() => { + setPhase({ type: 'complete' }) + onComplete?.(newState) + }, 500) + } } }, - [flowchart, state, currentNode, onComplete] + [flowchart, state, currentNode, onComplete, autoAdvancePaused] ) // Go back to the previous step (uses browser history so back button stays in sync) @@ -215,6 +270,140 @@ export function FlowchartWalker({ window.history.back() }, [stateHistory, onChangeProblem]) + // Go forward to the next step (from redo stack) - for visual debug mode + const goForward = useCallback(() => { + if (redoStack.length === 0) return + + // Push current state to history + setStateHistory((prev) => [...prev, state]) + + // Pop next state from redo stack + const nextState = redoStack[redoStack.length - 1] + setRedoStack((prev) => prev.slice(0, -1)) + setState(nextState) + setPhase({ type: 'showingNode' }) + setWrongAttempts(0) + setWrongDecision(null) + setCheckedItems(new Set()) + + historyDepthRef.current++ + window.history.pushState({ flowchartStep: historyDepthRef.current }, '') + }, [redoStack, state]) + + // Navigate to a specific step in the full timeline (for visual debug mode) + // The timeline is: stateHistory + [current] + redoStack (reversed) + const navigateToHistoryStep = useCallback( + (targetIndex: number) => { + // Build full timeline: stateHistory + [current] + redoStack (reversed) + const fullTimeline = [...stateHistory, state, ...redoStack.slice().reverse()] + const currentIndex = stateHistory.length + + if (targetIndex === currentIndex) return // Already there + if (targetIndex < 0 || targetIndex >= fullTimeline.length) return + + const targetState = fullTimeline[targetIndex] + if (!targetState) return + + // Calculate new stateHistory (everything before target) + const newHistory = fullTimeline.slice(0, targetIndex) + // Calculate new redoStack (everything after target, reversed back) + const newRedo = fullTimeline.slice(targetIndex + 1).reverse() + + setStateHistory(newHistory) + setRedoStack(newRedo) + setState(targetState) + setPhase({ type: 'showingNode' }) + setWrongAttempts(0) + setWrongDecision(null) + setCheckedItems(new Set()) + + // Update browser history depth to match + historyDepthRef.current = targetIndex + window.history.replaceState({ flowchartStep: historyDepthRef.current }, '') + }, + [stateHistory, state, redoStack] + ) + + // Debug skip - auto-advance through the current node (for visual debug mode) + // For decision nodes: picks the first option + // For checkpoint nodes: computes and submits the correct answer + // For instruction nodes: just advances + const debugSkipStep = useCallback(() => { + if (!currentNode) return + + const def = currentNode.definition + + switch (def.type) { + case 'instruction': + case 'milestone': + // Just advance + advanceToNext() + break + + case 'decision': { + // Pick the first option + const decisionDef = def as DecisionNode + if (decisionDef.options.length > 0) { + const firstOption = decisionDef.options[0] + advanceToNext(firstOption.value, firstOption.value, true) + } + break + } + + case 'checkpoint': { + // Compute the correct answer and submit it + const checkpointDef = def as CheckpointNode + try { + const context = createContextFromState(state) + + if (checkpointDef.inputType === 'two-numbers' && Array.isArray(checkpointDef.expected)) { + // Two-number checkpoint + const twoNumberAnswer: [number, number] = [ + evaluate(checkpointDef.expected[0], context) as number, + evaluate(checkpointDef.expected[1], context) as number, + ] + const newState = applyStateUpdate( + state, + state.currentNode, + flowchart, + twoNumberAnswer as unknown as ProblemValue + ) + setState(newState) + advanceToNext(undefined, twoNumberAnswer as unknown as ProblemValue, true) + } else if (typeof checkpointDef.expected === 'string') { + // Single value checkpoint - expected is a string expression + const correctAnswer = evaluate(checkpointDef.expected, context) as number + const newState = applyStateUpdate(state, state.currentNode, flowchart, correctAnswer) + setState(newState) + advanceToNext(undefined, correctAnswer, true) + } else { + // Shouldn't reach here, but just advance if we do + advanceToNext() + } + } catch { + // If we can't compute the answer, just try to advance + advanceToNext() + } + break + } + + case 'terminal': + // Complete + setPhase({ type: 'complete' }) + onComplete?.(state) + break + + default: + advanceToNext() + } + }, [currentNode, state, flowchart, advanceToNext, onComplete]) + + // Check if we can skip (not at terminal) + const canDebugSkip = useMemo(() => { + if (!currentNode) return false + return !isTerminal(flowchart, state.currentNode) + }, [currentNode, flowchart, state.currentNode]) + // Navigate to a specific step in the working problem history // Clicking on ledger entry i takes you to the state right after that entry was created const navigateToStep = useCallback( @@ -322,10 +511,12 @@ export function FlowchartWalker({ expected: result?.expected ?? value, userAnswer: value, }) - // Auto-advance after short delay - setTimeout(() => { - advanceToNext(undefined, value as ProblemValue, true) - }, 1000) + // Auto-advance after short delay (unless auto-advance is paused) + if (!autoAdvancePaused) { + setTimeout(() => { + advanceToNext(undefined, value as ProblemValue, true) + }, 1000) + } } else { // Wrong answer setWrongAttempts((prev) => prev + 1) @@ -338,7 +529,7 @@ export function FlowchartWalker({ }) } }, - [flowchart, state, advanceToNext] + [flowchart, state, advanceToNext, autoAdvancePaused] ) const handleChecklistToggle = useCallback( @@ -351,9 +542,9 @@ export function FlowchartWalker({ next.add(index) } - // Check if all items are now checked - if so, auto-advance + // Check if all items are now checked - if so, auto-advance (unless auto-advance is paused) const totalItems = currentChecklist?.length ?? 0 - if (next.size === totalItems && totalItems > 0) { + if (next.size === totalItems && totalItems > 0 && !autoAdvancePaused) { // Small delay so the user sees the final checkbox check setTimeout(() => { advanceToNext() @@ -363,7 +554,7 @@ export function FlowchartWalker({ return next }) }, - [currentChecklist, advanceToNext] + [currentChecklist, advanceToNext, autoAdvancePaused] ) // ============================================================================= @@ -557,8 +748,10 @@ export function FlowchartWalker({ } case 'milestone': - // Auto-advance milestones - setTimeout(() => advanceToNext(), 500) + // Auto-advance milestones (unless auto-advance is paused) + if (!autoAdvancePaused) { + setTimeout(() => advanceToNext(), 500) + } return (
+ {/* Debug step timeline - only visible when visual debug mode is enabled */} + {isVisualDebugEnabled && timelineSteps.length > 0 && ( + 0} + canGoForward={redoStack.length > 0} + canSkip={canDebugSkip} + onBack={goBack} + onForward={goForward} + onSkip={debugSkipStep} + autoAdvancePaused={autoAdvancePaused} + onToggleAutoAdvance={() => setAutoAdvancePaused((prev) => !prev)} + /> + )} + + {/* Debug mermaid diagram - shows flowchart with current node highlighted */} + {isVisualDebugEnabled && flowchart.rawMermaid && ( + + )} + {/* Phase rail with flowchart navigation */} @@ -883,27 +1101,29 @@ export function FlowchartWalker({ }, })} > - {/* Node content */} -
- {currentNode && ( - - )} -
+ {/* Node content - skip for milestone nodes (they show emoji in interaction area) */} + {currentNode?.definition.type !== 'milestone' && ( +
+ {currentNode && ( + + )} +
+ )} {/* Interaction area */}
_MERMAID = ...` +2. If not embedded, check the `mermaidFile` field in the `.flow.json` file +3. The actual `.mmd` file will be in `definitions/` directory + +## Architecture Overview + +Each flowchart consists of **two parts**: + +### 1. JSON Definition (`.flow.json`) +Defines **behavior and interactivity**: +- Node types (instruction, decision, checkpoint, milestone, terminal) +- Problem input schema (what values users can enter) +- Variables and computed values +- Validation rules and expected answers +- Working problem transformations + +### 2. Mermaid Content (`.mmd` or embedded in `index.ts`) +Defines **visual presentation**: +- Node content (title, body text, examples, warnings, checklists) +- Visual layout and styling +- Phase/subgraph organization +- Edge labels and styling + +### Why Two Files? +- **Separation of concerns**: Content authors can edit mermaid without touching logic +- **Mermaid compatibility**: Flowcharts render in standard Mermaid viewers +- **Reusability**: Same definition structure across different topics + +## Directory Structure + +``` +lib/flowcharts/ +├── README.md # This file +├── schema.ts # TypeScript types for all structures +├── parser.ts # Mermaid file parsing (extracts nodes, edges, phases) +├── loader.ts # Combines JSON + Mermaid into ExecutableFlowchart +├── evaluator.ts # Expression evaluation engine +├── constraint-parser.ts # Parses generation constraints +├── example-generator-client.ts # Client-side problem generation +├── example-generator.worker.ts # Web worker for generation +├── index.ts # Public exports +├── benchmark.ts # Performance benchmarks +├── __tests__/ # Tests +└── definitions/ # Flowchart definitions + ├── index.ts # Registry + EMBEDDED MERMAID CONTENT + ├── *.flow.json # JSON behavior definitions + └── *.mmd # Standalone mermaid files (if not embedded) +``` + +## Key Concepts + +### Node Types + +| Type | Purpose | User Interaction | +|------|---------|------------------| +| `instruction` | Show content | Tap to continue | +| `decision` | Ask yes/no or multiple choice | Tap choice button | +| `checkpoint` | Validate user answer | Enter value, app validates | +| `milestone` | Success marker | Auto-advances (shows emoji briefly) | +| `terminal` | End state | Shows completion screen | + +### ExecutableFlowchart + +The final merged structure used at runtime: + +```typescript +interface ExecutableFlowchart { + definition: FlowchartDefinition // From .flow.json + mermaid: ParsedMermaid // Parsed from mermaid content + nodes: Record // Merged nodes +} + +interface ExecutableNode { + id: string + definition: FlowchartNode // Behavior from JSON + content: ParsedNodeContent // Display content from mermaid +} +``` + +### Node Content Parsing + +Mermaid nodes use special formatting parsed by `parser.ts`: + +```mermaid +NODE["Title Here
───────
Body text line 1
Body text line 2
📝 Example text
⚠️ Warning text"] +``` + +Parsed into: +```typescript +{ + title: "Title Here", + body: ["Body text line 1", "Body text line 2"], + example: "Example text", + warning: "Warning text", + checklist: undefined, + raw: "..." +} +``` + +## Data Flow + +``` +┌─────────────────────┐ ┌─────────────────────┐ +│ .flow.json │ │ Mermaid content │ +│ (behavior) │ │ (.mmd or embedded) │ +└─────────┬───────────┘ └─────────┬───────────┘ + │ │ + │ loadFlowchart() │ + └───────────┬───────────────┘ + │ + ▼ + ┌───────────────────────┐ + │ ExecutableFlowchart │ + │ (merged structure) │ + └───────────┬───────────┘ + │ + │ initializeState() + ▼ + ┌───────────────────────┐ + │ FlowchartState │ + │ (runtime state) │ + └───────────┬───────────┘ + │ + │ FlowchartWalker component + ▼ + ┌───────────────────────┐ + │ User Interface │ + └───────────────────────┘ +``` + +## Key Functions + +### `definitions/index.ts` +- `getFlowchart(id)` - Get a flowchart by ID (returns definition + mermaid) +- `getFlowchartList()` - Get metadata for all flowcharts +- `FLOWCHARTS` - Registry mapping IDs to definitions + +### `loader.ts` +- `loadFlowchart(definition, mermaid)` - Merge JSON and mermaid into ExecutableFlowchart +- `initializeState(flowchart, problemInput)` - Create initial runtime state +- `advanceState(state, nextNode, ...)` - Move to next node +- `validateCheckpoint(flowchart, node, state, input)` - Check user answer +- `isDecisionCorrect(flowchart, node, state, choice)` - Check decision choice +- `formatProblemDisplay(flowchart, problem)` - Format problem for display + +### `parser.ts` +- `parseMermaidFile(content)` - Parse mermaid into nodes, edges, phases +- `parseNodeContent(raw)` - Parse node label into structured content +- `getNextNodes(mermaid, nodeId)` - Get successor nodes +- `findNodePhase(mermaid, nodeId)` - Find which phase a node belongs to + +### `evaluator.ts` +- `evaluate(expression, context)` - Evaluate math/logic expressions +- Supports: arithmetic, comparisons, boolean logic, ternary, functions (gcd, lcm, floor, etc.) + +## Adding a New Flowchart + +1. **Create the JSON definition** (`definitions/my-flowchart.flow.json`): + ```json + { + "id": "my-flowchart", + "title": "My Flowchart", + "mermaidFile": "my-flowchart.mmd", + "problemInput": { ... }, + "variables": { ... }, + "entryNode": "START", + "nodes": { ... } + } + ``` + +2. **Create the mermaid content** - either: + - Standalone file: `definitions/my-flowchart.mmd` + - OR embed in `definitions/index.ts` as `const MY_FLOWCHART_MERMAID = \`...\`` + +3. **Register in `definitions/index.ts`**: + ```typescript + import myDefinition from './my-flowchart.flow.json' + + export const FLOWCHARTS = { + 'my-flowchart': { + definition: myDefinition as FlowchartDefinition, + mermaid: MY_FLOWCHART_MERMAID, // or read from .mmd file + meta: { id: 'my-flowchart', title: '...', ... } + }, + // ... + } + ``` + +## Debugging Tips + +### Finding why a node looks wrong + +1. **Find the node ID** - Look at `data-current-node` attribute in browser DevTools +2. **Check the mermaid content** - Look in `definitions/index.ts` for embedded mermaid +3. **Check the JSON definition** - Look in `.flow.json` for node type and behavior +4. **Check the parsed content** - Log `currentNode.content` in FlowchartWalker + +### Common issues + +| Issue | Likely Cause | +|-------|--------------| +| Node shows only emoji | Mermaid node has no `` title, just emoji like `(("👍"))` | +| Content shows twice | Node content rendered by both container and interaction area | +| Wrong answer marked correct | Check `expected` expression in checkpoint definition | +| Node not advancing | Check `next` field or `edges` in JSON definition | + +## Related Components + +- `components/flowchart/FlowchartWalker.tsx` - Main walker UI component +- `components/flowchart/FlowchartNodeContent.tsx` - Renders parsed node content +- `components/flowchart/FlowchartDecision.tsx` - Decision button UI +- `components/flowchart/FlowchartCheckpoint.tsx` - Checkpoint input UI +- `components/flowchart/FlowchartPhaseRail.tsx` - Phase progress indicator +- `components/flowchart/DebugStepTimeline.tsx` - Visual debug timeline +- `app/flowchart/page.tsx` - Flowchart picker page +- `app/flowchart/[flowchartId]/page.tsx` - Flowchart walker page diff --git a/apps/web/src/lib/flowcharts/definitions/index.ts b/apps/web/src/lib/flowcharts/definitions/index.ts index 092a57da..53c9171e 100644 --- a/apps/web/src/lib/flowcharts/definitions/index.ts +++ b/apps/web/src/lib/flowcharts/definitions/index.ts @@ -1,7 +1,36 @@ /** - * Flowchart Definitions Index + * Flowchart Definitions Registry * - * Exports all available flowchart definitions with their Mermaid content. + * **THIS FILE CONTAINS EMBEDDED MERMAID CONTENT!** + * + * Each flowchart has two parts: + * 1. **JSON definition** (`.flow.json`): Behavior, validation, variables + * 2. **Mermaid content**: Visual presentation, node text, phases + * + * ## Where is the Mermaid content? + * + * | Flowchart | Mermaid Location | + * |-----------|------------------| + * | subtraction-regrouping | Embedded below as `SUBTRACTION_MERMAID` | + * | fraction-add-sub | Embedded below as `FRACTION_MERMAID` | + * | linear-equations | Embedded below as `LINEAR_EQUATIONS_MERMAID` | + * + * **To find node content**: Search this file for the node ID (e.g., `READY1`) in + * the appropriate `*_MERMAID` constant. + * + * ## Why Embed Mermaid? + * + * Next.js doesn't support `?raw` imports for loading text files. + * Embedding the mermaid content as template strings is the simplest solution. + * + * ## Adding a New Flowchart + * + * 1. Create `my-flowchart.flow.json` in this directory + * 2. Add `const MY_FLOWCHART_MERMAID = \`...\`` below + * 3. Import the JSON and add to `FLOWCHARTS` registry + * + * @see {@link ../README.md} for complete system documentation + * @module flowcharts/definitions */ import type { FlowchartDefinition } from '../schema' @@ -9,7 +38,17 @@ import subtractionDefinition from './subtraction-regrouping.flow.json' import fractionDefinition from './fraction-add-sub.flow.json' import linearEquationsDefinition from './linear-equations.flow.json' -// Mermaid content embedded as strings (since Next.js doesn't support ?raw imports) +// ============================================================================= +// EMBEDDED MERMAID CONTENT +// ============================================================================= +// These constants contain the visual content for each flowchart. +// Search for node IDs (e.g., "READY1", "STEP0") to find their content. + +/** + * Mermaid content for subtraction-regrouping flowchart. + * Nodes: START, COMPARE, HAPPY, SAD, CHECK1, CHECK1B, NEEDIT, SKIP, TENS, + * TAKEONE, BREAK, ADDTEN, CHECK2, DOONES, DOTENS, DONE + */ const SUBTRACTION_MERMAID = `%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '18px', 'primaryColor': '#e3f2fd', 'primaryTextColor': '#1a1a1a', 'primaryBorderColor': '#90caf9', 'lineColor': '#444444'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 30, 'rankSpacing': 50, 'padding': 20}}}%% flowchart TB subgraph PHASE1["1. 👀 LOOK"] @@ -68,6 +107,16 @@ flowchart TB style DONE fill:#66bb6a,stroke:#2e7d32,stroke-width:2px ` +/** + * Mermaid content for fraction-add-sub flowchart. + * Nodes: STEP0, STEP1, READY1, READY2, READY3, STEP2, CONV1A, CONV1B, CONV1C, + * STEP3, STEP3B, CHECK1, REMIND, ADDSUB, GOSTEP4, GOSTEP4B, GOSTEP4C, + * BORROWCHECK, BORROW, CHECK2, STEP4, SIMPLIFY_Q, SIMPLIFY_HOW, + * IMPROPER_Q, MIXED_HOW, CHECK3, DONE + * + * NOTE: Milestone nodes (READY1, READY2, READY3, GOSTEP4, etc.) only contain + * emoji like (("👍")) - they display briefly before auto-advancing. + */ const FRACTION_MERMAID = `%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#e3f2fd', 'primaryTextColor': '#1a1a1a', 'primaryBorderColor': '#90caf9', 'lineColor': '#444444'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 25, 'rankSpacing': 40, 'padding': 15}}}%% flowchart TB subgraph PHASE1["1. 🔍 MAKE THE BOTTOMS MATCH"] @@ -122,6 +171,12 @@ flowchart TB style PHASE3 fill:#e8f5e9,stroke:#388e3c,stroke-width:3px ` +/** + * Mermaid content for linear-equations flowchart. + * Nodes: INTRO, BALANCE, FIND_OP, STUCK_ADD, STUCK_MUL, CHECK1, GOAL, + * HOWSTUCK, ZERO, ONE, MAKEZ, MAKEONE, EX_ADD, EX_MUL, REMIND, + * CHECK2, PLUG, MATCH, DONE, RETRY + */ const LINEAR_EQUATIONS_MERMAID = `%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '14px', 'primaryColor': '#e3f2fd', 'primaryTextColor': '#1a1a1a', 'primaryBorderColor': '#90caf9', 'lineColor': '#444444'}, 'flowchart': {'curve': 'basis', 'nodeSpacing': 25, 'rankSpacing': 40, 'padding': 15}}}%% flowchart TB subgraph PHASE1["1. 🔍 UNDERSTAND THE EQUATION"] diff --git a/apps/web/src/lib/flowcharts/loader.ts b/apps/web/src/lib/flowcharts/loader.ts index 713430f2..52cf5e39 100644 --- a/apps/web/src/lib/flowcharts/loader.ts +++ b/apps/web/src/lib/flowcharts/loader.ts @@ -1,7 +1,41 @@ /** * Flowchart Loader * - * Loads and merges .mmd and .flow.json files into an executable flowchart. + * Combines JSON definitions (`.flow.json`) with Mermaid content (`.mmd` or embedded) + * to create executable flowcharts, and manages runtime state as users walk through them. + * + * ## Key Functions + * + * - {@link loadFlowchart} - Merge JSON definition + Mermaid into ExecutableFlowchart + * - {@link initializeState} - Create initial runtime state from problem input + * - {@link advanceState} - Move to next node in the flowchart + * - {@link validateCheckpoint} - Check if user's answer is correct + * - {@link isDecisionCorrect} - Check if user chose the correct option + * - {@link formatProblemDisplay} - Format problem values for display + * + * ## Data Flow + * + * ``` + * FlowchartDefinition + Mermaid content + * ↓ + * loadFlowchart() + * ↓ + * ExecutableFlowchart + * ↓ + * initializeState(flowchart, problemInput) + * ↓ + * FlowchartState + * ↓ + * advanceState(), validateCheckpoint(), etc. + * ``` + * + * ## Where to Find Mermaid Content + * + * **IMPORTANT**: Mermaid content is NOT always in separate `.mmd` files! + * Check `definitions/index.ts` first - many flowcharts embed their mermaid as constants. + * + * @see {@link ./README.md} for complete system documentation + * @module flowcharts/loader */ import type { @@ -32,7 +66,33 @@ import { // ============================================================================= /** - * Load and merge a flowchart definition with its Mermaid content + * Load and merge a flowchart definition with its Mermaid content. + * + * This is the main entry point for creating an executable flowchart. + * It combines: + * - **JSON definition** (`.flow.json`): Node types, validation logic, variables + * - **Mermaid content**: Node display content, phases, visual structure + * + * ## Node Merging + * + * For each node ID: + * 1. If in JSON definition: uses that node type/behavior + * 2. If only in Mermaid: creates default `instruction` node + * 3. Content always comes from Mermaid (parsed via `parseNodeContent`) + * + * ## Common Usage + * + * ```typescript + * import { getFlowchart } from './definitions' + * import { loadFlowchart } from './loader' + * + * const data = getFlowchart('fraction-add-sub') + * const flowchart = await loadFlowchart(data.definition, data.mermaid) + * ``` + * + * @param definition - The JSON definition from `.flow.json` + * @param mermaidContent - The Mermaid content (from `.mmd` file or embedded string) + * @returns Promise resolving to executable flowchart ready for FlowchartWalker */ export async function loadFlowchart( definition: FlowchartDefinition, @@ -78,6 +138,7 @@ export async function loadFlowchart( return { definition, mermaid, + rawMermaid: mermaidContent, nodes, } } diff --git a/apps/web/src/lib/flowcharts/parser.ts b/apps/web/src/lib/flowcharts/parser.ts index b18c5b12..28a04d95 100644 --- a/apps/web/src/lib/flowcharts/parser.ts +++ b/apps/web/src/lib/flowcharts/parser.ts @@ -1,10 +1,36 @@ /** * Mermaid Flowchart Parser * - * Extracts structure from .mmd files: - * - Node IDs and content - * - Edge connections - * - Subgraph (phase) definitions + * Extracts structure from Mermaid flowchart content (from .mmd files or embedded strings). + * + * ## Key Concepts + * + * - **Node content** is stored in Mermaid node labels using special formatting + * - **Phases** are Mermaid subgraphs that group related nodes + * - **Edges** connect nodes and may have labels + * + * ## Content Formatting + * + * Mermaid node labels use HTML-like formatting: + * - `...` - Title (extracted separately) + * - `
` - Line breaks + * - `...` - Italic (used for examples) + * - `───────` - Dividers (ignored) + * - `📝` - Example marker + * - `⚠️` - Warning marker + * - `☐` / `☑` - Checklist items + * + * @example + * ```typescript + * import { parseMermaidFile, parseNodeContent } from './parser' + * + * const mermaid = parseMermaidFile(mermaidContent) + * const nodeContent = mermaid.nodes['START'] + * const parsed = parseNodeContent(nodeContent) + * console.log(parsed.title, parsed.body) + * ``` + * + * @module flowcharts/parser */ import type { ParsedMermaid, ParsedNodeContent, ParsedEdge } from './schema' @@ -14,14 +40,49 @@ import type { ParsedMermaid, ParsedNodeContent, ParsedEdge } from './schema' // ============================================================================= /** - * Parse the raw content string from a Mermaid node label. + * Parse the raw content string from a Mermaid node label into structured content. * - * Mermaid node content uses: - * - ... for bold (title) - * -
for line breaks - * - ... for italic (examples) - * - ─────── for dividers - * - Emojis throughout + * ## Input Format + * + * Mermaid node content uses HTML-like formatting: + * - `...` - Bold text becomes the **title** + * - `
` - Line breaks separate content + * - `...` - Italic text (treated as examples) + * - `───────` - Divider lines (ignored) + * - `📝` - Marks example text + * - `⚠️` - Marks warning text + * - `☐` / `☑` - Checklist items + * + * ## Output Structure + * + * ```typescript + * { + * title: "The title text", // From ... or first line + * body: ["Line 1", "Line 2"], // Main content lines + * example: "Example text", // Lines after 📝 or + * warning: "Warning text", // Lines with ⚠️ + * checklist: ["☐ Item 1"], // Lines with checkboxes + * raw: "original content" // Original for fallback + * } + * ``` + * + * ## Edge Cases + * + * - **Emoji-only nodes** (like milestone `(("👍"))`): Title becomes the emoji, body is empty + * - **No `` tags**: First line becomes the title + * - **Multi-line titles**: `Line 1
Line 2
` becomes a single-line title + * + * @param raw - The raw content string from a Mermaid node label + * @returns Parsed and structured node content + * + * @example + * ```typescript + * const content = parseNodeContent('Step 1
Do this thing
📝 Example: 3 + 4 = 7') + * // { title: "Step 1", body: ["Do this thing"], example: "Example: 3 + 4 = 7", ... } + * + * const emoji = parseNodeContent('👍') + * // { title: "👍", body: [], ... } + * ``` */ export function parseNodeContent(raw: string): ParsedNodeContent { // Decode HTML entities that might be in the content @@ -131,7 +192,43 @@ function stripHtml(str: string): string { // ============================================================================= /** - * Parse a complete Mermaid flowchart file + * Parse a complete Mermaid flowchart into nodes, edges, and phases. + * + * ## What It Extracts + * + * - **Nodes**: ID → raw content mapping (content is NOT parsed here, use `parseNodeContent`) + * - **Edges**: From → To connections with optional labels + * - **Phases**: Subgraph groupings with title and contained node IDs + * + * ## Node Shapes Supported + * + * - `ID["content"]` - Rectangle + * - `ID{"content"}` - Diamond (decision) + * - `ID(["content"])` - Stadium (rounded rectangle) + * - `ID(("content"))` - Circle (milestones, often emoji-only) + * + * ## Edge Format + * + * - `A --> B` - Simple edge + * - `A -->|"label"| B` - Edge with label + * + * @param content - The complete Mermaid flowchart content (from .mmd file or embedded string) + * @returns Parsed structure with nodes, edges, and phases + * + * @example + * ```typescript + * const mermaid = parseMermaidFile(` + * flowchart TB + * subgraph PHASE1["Step 1"] + * START["Begin"] --> DECISION{"Continue?"} + * DECISION -->|"Yes"| DONE(("👍")) + * end + * `) + * + * mermaid.nodes['START'] // 'Begin' + * mermaid.edges[0] // { from: 'START', to: 'DECISION' } + * mermaid.phases[0] // { id: 'PHASE1', title: 'Step 1', nodes: ['START', 'DECISION', 'DONE'] } + * ``` */ export function parseMermaidFile(content: string): ParsedMermaid { const nodes: Record = {} diff --git a/apps/web/src/lib/flowcharts/schema.ts b/apps/web/src/lib/flowcharts/schema.ts index 8cfa5849..3668fd2d 100644 --- a/apps/web/src/lib/flowcharts/schema.ts +++ b/apps/web/src/lib/flowcharts/schema.ts @@ -1,8 +1,53 @@ /** * Flowchart Walker Schema Types * - * Defines the structure of .flow.json companion files that add - * interactivity metadata to .mmd Mermaid flowcharts. + * This module defines all TypeScript types for the flowchart walker system. + * These types correspond to the structure of `.flow.json` files and the + * runtime state used during flowchart execution. + * + * ## Architecture Overview + * + * ``` + * FlowchartDefinition (from .flow.json) + * ├── problemInput: ProblemInputSchema # User input form definition + * ├── variables: Record # Computed values + * ├── nodes: Record # Node behavior definitions + * ├── workingProblem?: WorkingProblemConfig # Evolving problem display + * └── constraints?: GenerationConstraints # Problem generation rules + * + * ParsedMermaid (from .mmd or embedded) + * ├── nodes: Record # Raw node content + * ├── edges: ParsedEdge[] # Connections between nodes + * └── phases: Phase[] # Subgraph groupings + * + * ExecutableFlowchart = FlowchartDefinition + ParsedMermaid merged + * └── nodes: Record # Ready for display + * + * FlowchartState (runtime) + * ├── problem: user input values + * ├── computed: calculated variables + * ├── currentNode: where we are + * └── history: actions taken + * ``` + * + * ## Node Types + * + * | Type | Purpose | User Action | + * |------|---------|-------------| + * | `instruction` | Show content | Tap to continue | + * | `decision` | Yes/No or multiple choice | Tap option | + * | `checkpoint` | Validate user answer | Enter value | + * | `milestone` | Success marker | Auto-advances | + * | `terminal` | End state | Shows completion | + * + * ## File Locations + * + * - JSON definitions: `lib/flowcharts/definitions/*.flow.json` + * - Mermaid content: `lib/flowcharts/definitions/index.ts` (embedded) or `*.mmd` + * - This file: Type definitions only, no runtime logic + * + * @see {@link ../README.md} for complete system documentation + * @module flowcharts/schema */ // ============================================================================= @@ -434,5 +479,7 @@ export interface ExecutableNode { export interface ExecutableFlowchart { definition: FlowchartDefinition mermaid: ParsedMermaid + /** Raw mermaid content string (for debug rendering) */ + rawMermaid: string nodes: Record } diff --git a/apps/web/src/utils/__tests__/memoizationBenchmark.test.ts b/apps/web/src/utils/__tests__/memoizationBenchmark.test.ts index 4a756466..b2424ae8 100644 --- a/apps/web/src/utils/__tests__/memoizationBenchmark.test.ts +++ b/apps/web/src/utils/__tests__/memoizationBenchmark.test.ts @@ -84,8 +84,7 @@ describe('A/B Performance Benchmark', () => { console.log(` Run ${run + 1}: ${elapsed.toFixed(1)}ms`) } - const nonMemoizedMean = - nonMemoizedTimes.reduce((a, b) => a + b, 0) / nonMemoizedTimes.length + const nonMemoizedMean = nonMemoizedTimes.reduce((a, b) => a + b, 0) / nonMemoizedTimes.length // ============= MEMOIZED ============= console.log(`\n🚀 Testing MEMOIZED...`) diff --git a/apps/web/src/utils/__tests__/sessionGenerationBenchmark.test.ts b/apps/web/src/utils/__tests__/sessionGenerationBenchmark.test.ts index cf308157..be7802fd 100644 --- a/apps/web/src/utils/__tests__/sessionGenerationBenchmark.test.ts +++ b/apps/web/src/utils/__tests__/sessionGenerationBenchmark.test.ts @@ -166,7 +166,9 @@ describe('Session Generation Benchmark', () => { console.log(` Per problem: ${(finalTime / 60).toFixed(1)}ms`) console.log(` Cache entries: ${finalStats.size}`) console.log(` Cache hits: ${finalStats.hits.toLocaleString()}`) - console.log(` Cache hit rate: ${((finalStats.hits / (finalStats.hits + finalStats.misses)) * 100).toFixed(1)}%`) + console.log( + ` Cache hit rate: ${((finalStats.hits / (finalStats.hits + finalStats.misses)) * 100).toFixed(1)}%` + ) // Phase 2 recommendation console.log(`\n🤔 PHASE 2 RECOMMENDATION:`) diff --git a/apps/web/src/utils/__tests__/skillCostMemoization.test.ts b/apps/web/src/utils/__tests__/skillCostMemoization.test.ts index 908bc4e0..1724e461 100644 --- a/apps/web/src/utils/__tests__/skillCostMemoization.test.ts +++ b/apps/web/src/utils/__tests__/skillCostMemoization.test.ts @@ -72,21 +72,21 @@ describe('analyzeStepSkills memoization', () => { { currentValue: 100, term: -1, description: '100-1=99 borrow from hundreds' }, ] - it.each(testCases)( - '$description produces identical results memoized vs non-memoized', - ({ currentValue, term }) => { - const newValue = currentValue + term + it.each(testCases)('$description produces identical results memoized vs non-memoized', ({ + currentValue, + term, + }) => { + const newValue = currentValue + term - // First call (cache miss) - should compute fresh - const memoizedResult = analyzeStepSkillsMemoized(currentValue, term, newValue) + // First call (cache miss) - should compute fresh + const memoizedResult = analyzeStepSkillsMemoized(currentValue, term, newValue) - // Non-memoized version for comparison - const directResult = analyzeStepSkills(currentValue, term, newValue) + // Non-memoized version for comparison + const directResult = analyzeStepSkills(currentValue, term, newValue) - // Results must be identical - expect(memoizedResult).toEqual(directResult) - } - ) + // Results must be identical + expect(memoizedResult).toEqual(directResult) + }) it('repeated calls return identical arrays', () => { // Call multiple times with same inputs @@ -273,7 +273,9 @@ describe('performance benchmark', () => { expect(stats.hits).toBeGreaterThanOrEqual(operations.length * 2) // Log for manual verification - console.log(`Memoized: ${memoizedTime.toFixed(2)}ms for ${repeatedOperations.length} operations`) + console.log( + `Memoized: ${memoizedTime.toFixed(2)}ms for ${repeatedOperations.length} operations` + ) console.log(`Cache stats: ${stats.size} entries, ${stats.hits} hits, ${stats.misses} misses`) console.log(`Hit rate: ${((stats.hits / (stats.hits + stats.misses)) * 100).toFixed(1)}%`) }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 26e15fed..2a4cc355 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -255,6 +255,9 @@ importers: make-plural: specifier: ^7.4.0 version: 7.4.0 + mermaid: + specifier: ^11.12.2 + version: 11.12.2 nanoid: specifier: ^5.1.6 version: 5.1.6 @@ -664,6 +667,9 @@ packages: '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@asamuzakjp/css-color@3.2.0': resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} @@ -1311,6 +1317,24 @@ packages: '@base2/pretty-print-object@1.0.1': resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==} + '@braintree/sanitize-url@7.1.1': + resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} + + '@chevrotain/cst-dts-gen@11.0.3': + resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} + + '@chevrotain/gast@11.0.3': + resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} + + '@chevrotain/regexp-to-ast@11.0.3': + resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} + + '@chevrotain/types@11.0.3': + resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} + + '@chevrotain/utils@11.0.3': + resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} + '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} @@ -2282,6 +2306,12 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.0': + resolution: {integrity: sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==} + '@img/colour@1.0.0': resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} engines: {node: '>=18'} @@ -2569,6 +2599,9 @@ packages: '@mediapipe/tasks-vision@0.10.17': resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} + '@mermaid-js/parser@0.6.3': + resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} + '@modelcontextprotocol/sdk@1.25.2': resolution: {integrity: sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww==} engines: {node: '>=18'} @@ -4602,9 +4635,99 @@ packages: '@types/cross-spawn@6.0.6': resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + '@types/d3-force@3.0.10': resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -4659,6 +4782,9 @@ packages: '@types/fluent-ffmpeg@2.1.28': resolution: {integrity: sha512-5ovxsDwBcPfJ+eYs1I/ZpcYCnkce7pvH9AHSvrZllAp1ZPpTRDZAFjF3TRFbukxSgIYTTNYePbS0rKUmaxVbXw==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -5872,6 +5998,14 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} + chevrotain-allstar@0.3.1: + resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} + peerDependencies: + chevrotain: ^11.0.0 + + chevrotain@11.0.3: + resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -6144,6 +6278,12 @@ packages: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cosmiconfig@7.1.0: resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} engines: {node: '>=10'} @@ -6265,22 +6405,162 @@ packages: typescript: optional: true + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.1: + resolution: {integrity: sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + d3-dispatch@3.0.1: resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} engines: {node: '>=12'} + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + d3-force@3.0.0: resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} engines: {node: '>=12'} + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + d3-quadtree@3.0.1: resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} engines: {node: '>=12'} + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + d3-timer@3.0.1: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.13: + resolution: {integrity: sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==} + damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -6308,6 +6588,9 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} + debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -6418,6 +6701,9 @@ packages: resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} engines: {node: '>=10'} + delaunator@5.0.1: + resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -7508,6 +7794,9 @@ packages: resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==} engines: {node: '>=10'} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + handlebars@4.7.8: resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} engines: {node: '>=0.4.7'} @@ -7813,6 +8102,13 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + intl-messageformat@10.7.18: resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==} @@ -8306,9 +8602,16 @@ packages: jzz@1.9.6: resolution: {integrity: sha512-J7ENLhXwfm2BNDKRUrL8eKtPhUS/CtMBpiafxQHDBcOWSocLhearDKEdh+ylnZFcr5OXWTed0gj6l/txeQA9vg==} + katex@0.16.27: + resolution: {integrity: sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} @@ -8321,6 +8624,10 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + langium@3.3.1: + resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} + engines: {node: '>=16.0.0'} + language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -8328,6 +8635,12 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + lazy-universal-dotenv@4.0.0: resolution: {integrity: sha512-aXpZJRnTkpK6gQ/z4nk+ZBLd/Qdp118cvPruLSIQzQNRhKwEcdXCOzXuF55VDqIiuAaY3UGZ10DJtvZzDcvsxg==} engines: {node: '>=14.0.0'} @@ -8621,6 +8934,11 @@ packages: peerDependencies: marked: '>=1 <12' + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + marked@9.1.6: resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} engines: {node: '>= 16'} @@ -8720,6 +9038,9 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + mermaid@11.12.2: + resolution: {integrity: sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==} + meshline@3.3.1: resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} peerDependencies: @@ -9473,6 +9794,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} @@ -9622,6 +9946,12 @@ packages: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + polished@4.3.1: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} @@ -10292,6 +10622,9 @@ packages: resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} engines: {node: '>= 0.8'} + robust-predicates@3.0.2: + resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup@3.29.5: resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} @@ -10302,6 +10635,9 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -10312,6 +10648,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} @@ -10837,6 +11176,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.6: + resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -11013,6 +11355,10 @@ packages: tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -11519,6 +11865,10 @@ packages: utrie@1.0.2: resolution: {integrity: sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true @@ -11631,6 +11981,26 @@ packages: vm-browserify@1.1.2: resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-uri@3.0.8: + resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} + vue-component-type-helpers@3.2.2: resolution: {integrity: sha512-x8C2nx5XlUNM0WirgfTkHjJGO/ABBxlANZDtHw2HclHtQnn+RFPTnbjMJn8jHZW4TlUam0asHcA14lf1C6Jb+A==} @@ -12037,6 +12407,11 @@ snapshots: '@adobe/css-tools@4.4.4': {} + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.0.2 + '@asamuzakjp/css-color@3.2.0': dependencies: '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) @@ -12874,6 +13249,25 @@ snapshots: '@base2/pretty-print-object@1.0.1': {} + '@braintree/sanitize-url@7.1.1': {} + + '@chevrotain/cst-dts-gen@11.0.3': + dependencies: + '@chevrotain/gast': 11.0.3 + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/gast@11.0.3': + dependencies: + '@chevrotain/types': 11.0.3 + lodash-es: 4.17.21 + + '@chevrotain/regexp-to-ast@11.0.3': {} + + '@chevrotain/types@11.0.3': {} + + '@chevrotain/utils@11.0.3': {} + '@clack/core@0.5.0': dependencies: picocolors: 1.1.1 @@ -13564,6 +13958,14 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.0': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.0 + '@img/colour@1.0.0': {} '@img/sharp-darwin-arm64@0.34.5': @@ -13880,6 +14282,10 @@ snapshots: '@mediapipe/tasks-vision@0.10.17': {} + '@mermaid-js/parser@0.6.3': + dependencies: + langium: 3.3.1 + '@modelcontextprotocol/sdk@1.25.2(hono@4.11.4)(zod@4.1.12)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.4) @@ -16987,8 +17393,123 @@ snapshots: dependencies: '@types/node': 20.19.19 + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + '@types/d3-force@3.0.10': {} + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -17047,6 +17568,8 @@ snapshots: dependencies: '@types/node': 20.19.19 + '@types/geojson@7946.0.16': {} + '@types/glob@7.2.0': dependencies: '@types/minimatch': 6.0.0 @@ -18408,6 +18931,20 @@ snapshots: check-error@2.1.1: {} + chevrotain-allstar@0.3.1(chevrotain@11.0.3): + dependencies: + chevrotain: 11.0.3 + lodash-es: 4.17.21 + + chevrotain@11.0.3: + dependencies: + '@chevrotain/cst-dts-gen': 11.0.3 + '@chevrotain/gast': 11.0.3 + '@chevrotain/regexp-to-ast': 11.0.3 + '@chevrotain/types': 11.0.3 + '@chevrotain/utils': 11.0.3 + lodash-es: 4.17.21 + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -18669,6 +19206,14 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cosmiconfig@7.1.0: dependencies: '@types/parse-json': 4.0.2 @@ -18818,18 +19363,190 @@ snapshots: optionalDependencies: typescript: 5.9.3 + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.1): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.1 + + cytoscape-fcose@2.2.0(cytoscape@3.33.1): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.1 + + cytoscape@3.33.1: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.0.1 + d3-dispatch@3.0.1: {} + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + d3-force@3.0.0: dependencies: d3-dispatch: 3.0.1 d3-quadtree: 3.0.1 d3-timer: 3.0.1 + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + d3-quadtree@3.0.1: {} + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + d3-timer@3.0.1: {} + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.13: + dependencies: + d3: 7.9.0 + lodash-es: 4.17.21 + damerau-levenshtein@1.0.8: {} data-urls@5.0.0: @@ -18864,6 +19581,8 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 + dayjs@1.11.19: {} + debounce@1.2.1: {} debug@2.6.9: @@ -18971,6 +19690,10 @@ snapshots: rimraf: 3.0.2 slash: 3.0.0 + delaunator@5.0.1: + dependencies: + robust-predicates: 3.0.2 + delayed-stream@1.0.0: {} denque@2.1.0: {} @@ -19066,7 +19789,6 @@ snapshots: dompurify@3.3.1: optionalDependencies: '@types/trusted-types': 2.0.7 - optional: true domutils@2.8.0: dependencies: @@ -20363,6 +21085,8 @@ snapshots: dependencies: duplexer: 0.1.2 + hachure-fill@0.5.2: {} + handlebars@4.7.8: dependencies: minimist: 1.2.8 @@ -20762,6 +21486,10 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + internmap@1.0.1: {} + + internmap@2.0.3: {} + intl-messageformat@10.7.18: dependencies: '@formatjs/ecma402-abstract': 2.3.6 @@ -21330,22 +22058,40 @@ snapshots: jazz-midi: 1.7.9 optional: true + katex@0.16.27: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + kind-of@6.0.3: {} kleur@3.0.3: {} kleur@4.1.5: {} + langium@3.3.1: + dependencies: + chevrotain: 11.0.3 + chevrotain-allstar: 0.3.1(chevrotain@11.0.3) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.0.8 + language-subtag-registry@0.3.23: {} language-tags@1.0.9: dependencies: language-subtag-registry: 0.3.23 + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + lazy-universal-dotenv@4.0.0: dependencies: app-root-dir: 1.0.2 @@ -21598,6 +22344,8 @@ snapshots: node-emoji: 2.2.0 supports-hyperlinks: 3.2.0 + marked@16.4.2: {} + marked@9.1.6: {} math-intrinsics@1.1.0: {} @@ -21791,6 +22539,29 @@ snapshots: merge2@1.4.1: {} + mermaid@11.12.2: + dependencies: + '@braintree/sanitize-url': 7.1.1 + '@iconify/utils': 3.1.0 + '@mermaid-js/parser': 0.6.3 + '@types/d3': 7.4.3 + cytoscape: 3.33.1 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.1) + cytoscape-fcose: 2.2.0(cytoscape@3.33.1) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.13 + dayjs: 1.11.19 + dompurify: 3.3.1 + katex: 0.16.27 + khroma: 2.1.0 + lodash-es: 4.17.21 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.3.6 + ts-dedent: 2.2.0 + uuid: 11.1.0 + meshline@3.3.1(three@0.169.0): dependencies: three: 0.169.0 @@ -22585,6 +23356,8 @@ snapshots: path-browserify@1.0.1: {} + path-data-parser@0.1.0: {} + path-exists@3.0.0: {} path-exists@4.0.0: {} @@ -22705,6 +23478,13 @@ snapshots: pngjs@5.0.0: {} + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + polished@4.3.1: dependencies: '@babel/runtime': 7.28.4 @@ -23519,6 +24299,8 @@ snapshots: hash-base: 3.1.2 inherits: 2.0.4 + robust-predicates@3.0.2: {} + rollup@3.29.5: optionalDependencies: fsevents: 2.3.3 @@ -23551,6 +24333,13 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.52.4 fsevents: 2.3.3 + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + router@2.2.0: dependencies: debug: 4.4.3(supports-color@8.1.1) @@ -23567,6 +24356,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rw@1.3.3: {} + rxjs@7.8.2: dependencies: tslib: 2.8.1 @@ -24226,6 +25017,8 @@ snapshots: optionalDependencies: '@babel/core': 7.28.4 + stylis@4.3.6: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.13 @@ -24415,6 +25208,8 @@ snapshots: tinyexec@0.3.2: {} + tinyexec@1.0.2: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -24927,6 +25722,8 @@ snapshots: base64-arraybuffer: 1.0.2 optional: true + uuid@11.1.0: {} + uuid@9.0.1: {} validate-npm-package-license@3.0.4: @@ -25095,6 +25892,23 @@ snapshots: vm-browserify@1.1.2: {} + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-uri@3.0.8: {} + vue-component-type-helpers@3.2.2: {} vue-demi@0.14.10(vue@3.5.26(typescript@5.9.3)):