/** * src/book/components/RunLocked.tsx * * Read-only code cell with a live preview. * An "Edit" button forks the cell into a full editable Run instance. * * Usage in MDX: * * {`repeat 4 [ fd 15 rt 90 ]`} * */ import { useState, useCallback, useEffect, type ReactNode } from 'react'; import type { RunResult, DesignStats } from '../../lib/core/types.ts'; import { useCompiler } from '../../hooks/useCompiler.ts'; import BookCanvas from './BookCanvas.tsx'; import Run from './Run.tsx'; interface Props { children: ReactNode; canvasHeight?: number; } function extractCode(children: ReactNode): string { if (typeof children === 'string') return children.trim(); 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(); } export default function RunLocked({ children, canvasHeight = 280 }: Props) { const [forked, setForked] = useState(false); const code = extractCode(children); const [result, setResult] = useState(null); const [stats, setStats] = useState(null); const { compile } = useCompiler(); const run = useCallback(async () => { const response = await compile(code); if (response === null || !response.ok) return; setResult(response.result); setStats(response.stats); }, [compile, code]); // Auto-run once on mount — deferred to avoid setState directly in effect body. useEffect(() => { queueMicrotask(run); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // After forking, delegate fully to Run which has its own worker if (forked) { return {code}; } return (
{/* Toolbar */}
NeedleScript
{/* Source displayed as highlighted pre */}
        {code}
      
{/* Canvas */}
); }