/** * src/book/components/Run.tsx * * Editable NeedleScript code cell with a live hoop preview. * The book's primary teaching component. * * Usage in MDX: * * {`repeat 6 [ fd 20 rt 60 ]`} * */ import { useState, useCallback, useEffect, useLayoutEffect, useRef, type ReactNode } from 'react'; import Editor from '@monaco-editor/react'; import type { BeforeMount, OnMount } from '@monaco-editor/react'; import type { editor } from 'monaco-editor'; import type { RunResult, DesignStats } from '../../lib/core/types.ts'; import { useCompiler } from '../../hooks/useCompiler.ts'; import { registerNeedlescript, scheduleNeedlescriptProviders } from '../../lib/editor/monaco.ts'; import BookCanvas from './BookCanvas.tsx'; import { useBookTheme } from '../lib/useBookTheme.ts'; interface Props { children: ReactNode; /** Canvas height in pixels. Default 280. */ canvasHeight?: number; /** Auto-run on mount without requiring a manual Run click. Default true. */ autoRun?: boolean; } function extractCode(children: ReactNode): string { // MDX passes code content as a string child (possibly nested in a React element). // Trim surrounding newlines that MDX often adds. if (typeof children === 'string') return children.trim(); // React element wrapping (e.g. from remark-code processing) — extract text const el = children as React.ReactElement<{ children: ReactNode }>; if (el && typeof el === 'object' && 'props' in el) { return extractCode(el.props.children); } return String(children ?? '').trim(); } /** Minimal book-themed stats row */ function StatsRow({ stats, error }: { stats: DesignStats | null; error: string | null }) { if (error) { return (