openpencil/packages/pen-core/CLAUDE.md
Fini 1c0cb8c7ef fix(canvas): require explicit role:'overlay' for layout-flow escape hatch
isBadgeOverlayNode matched role:'badge'|'pill'|'tag' and pulled those
children out of their parent's auto-layout, rendering them at (0,0) of
the parent and stacking them on top of siblings. But in this repo
badge/pill/tag are inline-component roles (see role-resolver NAME_EXACT_MAP
and strip-redundant-section-fills PROTECTED_ROLES) — they're meant to
flow in layout like any other child.

Rename to isOverlayNode and narrow to role:'overlay'. Add matching
"Layout-escape roles" guidance in role-definitions.md so generation
prompts can reach the new opt-in. Inline roles now flow correctly;
true floating decorations (notification dots, corner ribbons) still
have a dedicated marker.
2026-04-16 21:07:40 +08:00

3.8 KiB

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.tsnormalizePenDocument: format-only normalization (fill type "color" to "solid", gradient stop position to offset, sizing strings, padding arrays). Preserves $variable refs
  • src/boolean-ops.tscanBooleanOp, executeBooleanOp (union/subtract/intersect via Paper.js headless)
  • src/sync-lock.tsisFabricSyncLocked, setFabricSyncLock: prevents circular document-store to canvas sync
  • src/arc-path.tsbuildEllipseArcPath, isArcEllipse: SVG arc path generation for partial ellipses
  • src/path-anchors.tsanchorsToPathData, pathDataToAnchors, getPathBoundsFromAnchors, inferPathAnchorPointType
  • src/font-utils.tscssFontFamily: CSS font-family string builder
  • src/node-helpers.tsisOverlayNode (detects role: 'overlay'), sanitizeName (PascalCase conversion)
  • src/design-md-parser.tsparseDesignMd, generateDesignMd, designMdColorsToVariables, extractDesignMdFromDocument
  • src/constants.ts — Canvas rendering constants (zoom limits, colors, snap thresholds, pen tool sizes, guide styling)
  • src/id.tsgenerateId (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.tsnormalizeTreeLayout: infers missing layout mode on frames, strips child x/y in layout containers
  • src/layout/unwrap-fake-phone-mockup.tsunwrapFakePhoneMockups: repairs AI-generated fake phone mockup frames
  • src/layout/strip-redundant-section-fills.tsstripRedundantSectionFills: removes redundant dark fills from section containers
  • src/normalize/normalize-stroke-fill-schema.tsnormalizeStrokeFillSchema: repairs AI-generated stroke/fill schema violations
  • src/variables/resolve.tsisVariableRef, getDefaultTheme, resolveVariableRef, resolveColorRef, resolveNumericRef, resolveNodeForCanvas
  • src/variables/replace-refs.tsreplaceVariableRefsInTree: recursively rename/delete $variable refs in node trees
  • src/merge/node-diff.tsdiffDocuments: one-direction diff producing NodePatch[] (add/remove/modify/move)
  • src/merge/node-merge.tsmergeDocuments: 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

bun --bun vitest run packages/pen-core/src/__tests__/