/** * @file figma/from-codegen.ts — Figma Dev Mode codegen → one Maude artboard. * @scope apps/studio/figma/from-codegen.ts * @purpose Convert the React + Tailwind MODULE the local Dev Mode server * returns into a `DCArtboard` a Maude canvas renders and a human * edits — for ONE frame, on explicit invocation (`--explode`). * * @rationale Two earlier routes were built and both measured broken on the same * live file (DDR-219 § Context). Route 1 derived layout from the node * tree, which means reimplementing auto-layout, constraints, * clipping, blend modes and vector networks in CSS — the bug surface * grows with the fidelity of the source. Route 2 rendered each frame * to an image, which is faithful and inert. This route asks Figma for * the layout ALREADY RESOLVED and converts the result. The editability * bar is met, not abandoned — but it is met by taking Figma's DOM * rather than by deriving one. * * @invariant THE MODULE STAYS REACT. The first spike flattened to HTML by * stripping with regexes, and on the first real screen it rendered * `type IconsProps = …` as visible body text — exactly the defect * class the "named parser, never regex" rule exists to prevent. * A real response is a MODULE: asset constants, a type alias, a * parameterized helper component, then the default export. Maude * canvases ARE React, so keeping the helper is both natively * renderable and strictly MORE editable than inlining it fourteen * times. * * @invariant EVERY IDENTIFIER IS REGENERATED (DDR-216 D6 via DDR-219 D4). * D6 calls the identifier space "airtight — there is no Figma string * in it at all", and that holds only because every name comes from * `identifierFromNodeId`. Codegen returns React source carrying its * OWN names, derived from layer names * (`Component231320F78B2B43C7B5A04A6Ff8B6244C45005C` was measured). * So: component names, parameter names and local names are ALL * discarded and regenerated. Only string VALUES survive, escaped. * * @invariant ELEMENT AND ATTRIBUTE ALLOWLISTS, NEVER DENYLISTS (D5 rule 3). A * denylist has to remember `