# pen-core Pure document tree operations, layout engine, variables, normalization, boolean ops, and merge utilities. ## Structure - `src/tree-utils.ts` — Tree CRUD: `findNodeInTree`, `findParentInTree`, `removeNodeFromTree`, `updateNodeInTree`, `flattenNodes`, `insertNodeInTree`, `isDescendantOf`, `getNodeBounds`, `findClearX`, `scaleChildrenInPlace`, `rotateChildrenInPlace`, `nodeTreeToSummary`; page helpers: `createEmptyDocument`, `getActivePage`, `getActivePageChildren`, `setActivePageChildren`, `getAllChildren`, `migrateToPages`, `ensureDocumentNodeIds`; clone utilities: `deepCloneNode`, `cloneNodeWithNewIds`, `cloneNodesWithNewIds`; constants: `DEFAULT_FRAME_ID`, `DEFAULT_PAGE_ID` - `src/normalize.ts` — `normalizePenDocument`: format-only normalization (fill type "color" to "solid", gradient stop position to offset, sizing strings, padding arrays). Preserves `$variable` refs - `src/boolean-ops.ts` — `canBooleanOp`, `executeBooleanOp` (union/subtract/intersect via Paper.js headless) - `src/sync-lock.ts` — `isFabricSyncLocked`, `setFabricSyncLock`: prevents circular document-store to canvas sync - `src/arc-path.ts` — `buildEllipseArcPath`, `isArcEllipse`: SVG arc path generation for partial ellipses - `src/path-anchors.ts` — `anchorsToPathData`, `pathDataToAnchors`, `getPathBoundsFromAnchors`, `inferPathAnchorPointType` - `src/font-utils.ts` — `cssFontFamily`: CSS font-family string builder - `src/node-helpers.ts` — `isOverlayNode` (detects `role: 'overlay'`), `sanitizeName` (PascalCase conversion) - `src/design-md-parser.ts` — `parseDesignMd`, `generateDesignMd`, `designMdColorsToVariables`, `extractDesignMdFromDocument` - `src/constants.ts` — Canvas rendering constants (zoom limits, colors, snap thresholds, pen tool sizes, guide styling) - `src/id.ts` — `generateId` (nanoid wrapper) - `src/layout/engine.ts` — Auto-layout computation: `resolvePadding`, `getNodeWidth`, `getNodeHeight`, `computeLayoutPositions`, `inferLayout`, `fitContentWidth`, `fitContentHeight`, `isNodeVisible`, `setRootChildrenProvider`, `getRootFillWidthFallback` - `src/layout/text-measure.ts` — Text measurement: `estimateTextWidth`, `estimateTextWidthPrecise`, `estimateTextHeight`, `resolveTextContent`, `parseSizing`, `defaultLineHeight`, `hasCjkText`, `isCjkCodePoint`, `countWrappedLinesFallback`, `setWrappedLineCounter` - `src/layout/normalize-tree.ts` — `normalizeTreeLayout`: infers missing layout mode on frames, strips child x/y in layout containers - `src/layout/unwrap-fake-phone-mockup.ts` — `unwrapFakePhoneMockups`: repairs AI-generated fake phone mockup frames - `src/layout/strip-redundant-section-fills.ts` — `stripRedundantSectionFills`: removes redundant dark fills from section containers - `src/normalize/normalize-stroke-fill-schema.ts` — `normalizeStrokeFillSchema`: repairs AI-generated stroke/fill schema violations - `src/variables/resolve.ts` — `isVariableRef`, `getDefaultTheme`, `resolveVariableRef`, `resolveColorRef`, `resolveNumericRef`, `resolveNodeForCanvas` - `src/variables/replace-refs.ts` — `replaceVariableRefsInTree`: recursively rename/delete `$variable` refs in node trees - `src/merge/node-diff.ts` — `diffDocuments`: one-direction diff producing `NodePatch[]` (add/remove/modify/move) - `src/merge/node-merge.ts` — `mergeDocuments`: pure 3-way merge of PenDocument trees, returns `MergeResult` with conflicts - `src/merge/merge-helpers.ts` — Shared helpers for diff/merge (node indexing, field comparison) ## Key patterns - All tree operations are pure functions returning new references (structural sharing) - `$variable` refs are preserved in the document; resolution happens at render time via `resolveNodeForCanvas()` - Layout engine resolves `fill_container`/`fit_content` sizing and computes absolute positions ## Testing ```bash bun --bun vitest run packages/pen-core/src/__tests__/ ```