figma-preview-mcp 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +220 -0
  2. package/dist/ast-enricher.d.ts +80 -0
  3. package/dist/ast-enricher.d.ts.map +1 -0
  4. package/dist/ast-enricher.js +318 -0
  5. package/dist/ast-enricher.js.map +1 -0
  6. package/dist/ast-module-splitter.d.ts +106 -0
  7. package/dist/ast-module-splitter.d.ts.map +1 -0
  8. package/dist/ast-module-splitter.js +325 -0
  9. package/dist/ast-module-splitter.js.map +1 -0
  10. package/dist/bounds-cache.d.ts +48 -0
  11. package/dist/bounds-cache.d.ts.map +1 -0
  12. package/dist/bounds-cache.js +71 -0
  13. package/dist/bounds-cache.js.map +1 -0
  14. package/dist/image-downloader.d.ts +20 -0
  15. package/dist/image-downloader.d.ts.map +1 -0
  16. package/dist/image-downloader.js +248 -0
  17. package/dist/image-downloader.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +1554 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/layout-inference.d.ts +98 -0
  23. package/dist/layout-inference.d.ts.map +1 -0
  24. package/dist/layout-inference.js +486 -0
  25. package/dist/layout-inference.js.map +1 -0
  26. package/dist/layout-verifier.d.ts +91 -0
  27. package/dist/layout-verifier.d.ts.map +1 -0
  28. package/dist/layout-verifier.js +318 -0
  29. package/dist/layout-verifier.js.map +1 -0
  30. package/dist/parser.d.ts +120 -0
  31. package/dist/parser.d.ts.map +1 -0
  32. package/dist/parser.js +310 -0
  33. package/dist/parser.js.map +1 -0
  34. package/dist/renderer.d.ts +8 -0
  35. package/dist/renderer.d.ts.map +1 -0
  36. package/dist/renderer.js +639 -0
  37. package/dist/renderer.js.map +1 -0
  38. package/dist/server.d.ts +7 -0
  39. package/dist/server.d.ts.map +1 -0
  40. package/dist/server.js +93 -0
  41. package/dist/server.js.map +1 -0
  42. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # figma-preview-mcp
2
+
3
+ 一个 Model Context Protocol (MCP) 服务器,可将 Figma 设计稿渲染为**高保真交互式预览**——带有节点检查器,可直接点击复制节点信息到 AI 对话中。
4
+
5
+ ---
6
+
7
+ ## ✨ 功能特性
8
+
9
+ - 📸 **高保真预览** — 在浏览器中预览 Figma 设计稿,100% 还原视觉效果
10
+ - 🖱️ **节点检查器** — 悬停查看节点名称,点击复制节点信息到剪贴板
11
+ - 🔍 **节点详情查询** — 获取完整节点数据,包括被压缩的内联样式
12
+ - 📷 **截图验证** — 截取预览页面图片,在 AI 对话中展示
13
+ - 🔬 **像素级 Diff 对比** — 使用 pixelmatch 进行程序化像素级对比,生成差异高亮图 + 差异百分比统计
14
+
15
+ ---
16
+
17
+ ## 🚀 快速开始
18
+
19
+ ### 1. 获取 Figma Access Token
20
+
21
+ 1. 打开 [Figma](https://www.figma.com),进入 **Account Settings**
22
+ 2. 滚动到 **Personal access tokens**
23
+ 3. 点击 **Generate new token**,命名并复制 token
24
+
25
+ ### 2. 安装配置
26
+
27
+ 在 MCP 配置文件中添加(如 `mcp.json` 或 Claude Desktop 的 `claude_desktop_config.json`):
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "figma-preview-mcp": {
33
+ "command": "npx",
34
+ "args": ["-y", "figma-preview-mcp"],
35
+ "env": {
36
+ "FIGMA_TOKEN": "figd_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
37
+ "PIXELMATCH_THRESHOLD": "0.2"
38
+ }
39
+ }
40
+ }
41
+ }
42
+ ```
43
+
44
+ > **注意:** 将 `FIGMA_TOKEN` 替换为你的 Figma 个人访问令牌。
45
+
46
+ ---
47
+
48
+ ## 🎮 使用方法
49
+
50
+ 配置完成后,在 AI 对话中粘贴 Figma 链接:
51
+
52
+ ```
53
+ 预览 https://www.figma.com/design/YOUR_FILE_KEY/MyApp?node-id=123-456
54
+ ```
55
+
56
+ AI 会自动:
57
+ 1. 解析 URL 中的 `fileKey` 和 `nodeId`
58
+ 2. 直接从 Figma API 获取完整节点树(包含所有子节点)
59
+ 3. 下载目标节点的高清截图
60
+ 4. 在 **http://localhost:3456** 打开交互式预览
61
+
62
+ ### 节点检查器
63
+
64
+ | 操作 | 效果 |
65
+ |------|------|
66
+ | **悬停** 任意元素 | 蓝色高亮边框 + 节点名称提示 |
67
+ | **点击** 任意元素 | 橙色选中边框 + 节点信息复制到剪贴板 |
68
+ | **再次点击** | 取消选中 |
69
+
70
+ 复制的文本格式:
71
+
72
+ ```
73
+ 节点路径: MyPage / header / navigation-bar / back-button
74
+ 节点ID: 978:14642
75
+ 类型: INSTANCE
76
+ fileKey: YOUR_FILE_KEY
77
+ ```
78
+
79
+ 直接粘贴到 AI 对话中即可引用该节点。
80
+
81
+ ### 获取完整节点详情
82
+
83
+ 当需要完整节点数据(如修复内联文字样式)时,AI 会调用 `get_node_details`:
84
+
85
+ ```
86
+ 修复这个节点的样式
87
+ 节点路径: MyPage / title
88
+ 节点ID: 981:18766
89
+ 类型: TEXT
90
+ fileKey: YOUR_FILE_KEY
91
+ ```
92
+
93
+ AI 会自动获取完整数据,包括:
94
+ - `characterStyleOverrides` — 逐字符样式索引
95
+ - `styleOverrideTable` — 内联样式定义
96
+ - `fills`、`strokes`、`effects` — 完整视觉属性
97
+ - 更多...
98
+
99
+ ### 截图与对比工具
100
+
101
+ #### 截取预览页面
102
+
103
+ ```
104
+ 截取当前预览页面的图片
105
+ ```
106
+
107
+ #### 像素级 Diff 对比
108
+
109
+ 使用 **pixelmatch** 进行程序化像素级对比。工作流程:
110
+
111
+ 1. **渲染设计稿预览** — 调用 `render_preview` 获取 Figma 设计稿截图(自动返回 Base64 截图)
112
+ 2. **截取开发环境截图** — 使用 Chrome DevTools MCP 的 `take_screenshot` 保存开发页面为 PNG
113
+ 3. **执行像素级对比** — 调用 `compare_design_implementation` 对比两张截图
114
+
115
+ ```
116
+ # Step 1: 渲染设计稿(figma-preview-mcp)
117
+ 调用 render_preview:
118
+ fileKey: <fileKey>
119
+ nodeId: <nodeId>
120
+ # 返回结果包含 screenshot (Base64) 和 url,直接使用 screenshot 即可
121
+
122
+ # Step 2: 截取开发页面(Chrome DevTools MCP)
123
+ 调用 resize_page: { width: 375, height: 812 }
124
+ 调用 take_screenshot: { filePath: /tmp/dev.png }
125
+
126
+ # Step 3: 像素级对比(figma-preview-mcp)
127
+ 调用 compare_design_implementation:
128
+ designImagePath: /tmp/design.png # 从 render_preview 的 screenshot 保存而来
129
+ devImagePath: /tmp/dev.png
130
+ ```
131
+
132
+ AI 会返回:
133
+
134
+ 1. **差异高亮图** — 红色区域表示像素不匹配
135
+ 2. **差异统计** — 不匹配像素数、差异百分比
136
+
137
+ 示例输出:
138
+ ```
139
+ ## ❌ 像素级对比结果
140
+
141
+ **差异统计:**
142
+ - 不匹配像素数:**12,345** / 608,400
143
+ - 差异率:**2.03%**
144
+ - 对比尺寸:750 × 812px
145
+
146
+ **结论:** 发现明显差异,请查看红色高亮区域。
147
+ ```
148
+
149
+ **可选参数:**
150
+ - `threshold` — 颜色差异灵敏度(0-1,默认 0.2,越小越敏感)
151
+
152
+ **如何解读 Diff 图:**
153
+ - 🔴 **红色区域** = 像素颜色不匹配(位置偏移、颜色错误、元素缺失)
154
+ - 🟠 **橙色区域** = 抗锯齿差异(通常可忽略)
155
+ - ⬛ **暗色区域** = 完全匹配
156
+
157
+ ---
158
+
159
+ ## 🔧 工作原理
160
+
161
+ ```
162
+ Figma URL
163
+
164
+ └─► figma-preview-mcp
165
+
166
+ ├─► Figma REST API → 完整节点树(depth=100)
167
+ ├─► 计算相对坐标 → 每个节点的 _absX / _absY
168
+ ├─► Figma Export API → 高清 PNG 截图(2 倍分辨率)
169
+
170
+ └─► 本地 HTTP 服务器(端口 3456)
171
+ ├── PNG 截图作为背景
172
+ └── 透明覆盖层(每个节点一个 div)
173
+ └── 悬停 / 点击 → 节点信息
174
+ ```
175
+
176
+ ### 为什么用截图 + 覆盖层,而不是 CSS 重建?
177
+
178
+ 用 CSS 像素级还原 Figma 布局极其复杂(渐变、蒙版、混合模式、自动布局变体、组件覆盖...)。我们直接用 Figma 自身的渲染作为真实来源,在上面叠加一个透明交互层——100% 视觉保真度,零布局 bug。
179
+
180
+ ---
181
+
182
+ ## ⚙️ 配置项
183
+
184
+ | 环境变量 | 必需 | 说明 |
185
+ |---------|------|------|
186
+ | `FIGMA_TOKEN` | ✅ 是 | Figma 个人访问令牌 |
187
+ | `PORT` | 否 | HTTP 服务器端口(默认 `3456`) |
188
+ | `PIXELMATCH_THRESHOLD` | 否 | 像素对比灵敏度阈值 0-1(默认 `0.2`,越小越敏感) |
189
+
190
+ ---
191
+
192
+ ## 🛠️ 本地开发
193
+
194
+ ```bash
195
+ # 克隆仓库
196
+ cd figma-preview-mcp
197
+
198
+ # 安装依赖
199
+ npm install
200
+
201
+ # 构建
202
+ npm run build
203
+
204
+ # 在 MCP 配置中使用本地构建
205
+ # 在 mcp.json 中:
206
+ # "args": ["/absolute/path/to/figma-preview-mcp/dist/index.js"]
207
+ ```
208
+
209
+ ---
210
+
211
+ ## 📋 系统要求
212
+
213
+ - Node.js ≥ 18.0.0
214
+ - 有效的 Figma 访问令牌
215
+
216
+ ---
217
+
218
+ ## 📄 许可证
219
+
220
+ MIT
@@ -0,0 +1,80 @@
1
+ import type { ParsedFigmaData } from './parser.js';
2
+ import { type InferredLayout } from './layout-inference.js';
3
+ export interface EnrichedASTNode {
4
+ nodeId: string;
5
+ name: string;
6
+ type: string;
7
+ bounds: {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ layout: InferredLayout;
14
+ style?: {
15
+ backgroundColor?: string;
16
+ borderRadius?: number | string;
17
+ opacity?: number;
18
+ overflow?: string;
19
+ boxShadow?: string;
20
+ border?: string;
21
+ };
22
+ text?: {
23
+ content: string;
24
+ fontFamily?: string;
25
+ fontSize?: number;
26
+ fontWeight?: number | string;
27
+ color?: string;
28
+ lineHeight?: number | string;
29
+ letterSpacing?: number | string;
30
+ textAlign?: string;
31
+ /** Rich text segments with mixed styles (e.g. highlighted words) */
32
+ styledSegments?: Array<{
33
+ text: string;
34
+ color?: string;
35
+ fontWeight?: number | string;
36
+ fontFamily?: string;
37
+ fontSize?: number;
38
+ fontStyle?: string;
39
+ textDecoration?: string;
40
+ }>;
41
+ };
42
+ image?: {
43
+ type: 'raster' | 'vector' | 'svg';
44
+ imageRef?: string;
45
+ suggestedFormat: 'png' | 'svg' | 'jpg';
46
+ };
47
+ flags?: {
48
+ isGraphicSubtree?: boolean;
49
+ needsDetailFetch?: boolean;
50
+ isInvisible?: boolean;
51
+ hasComplexFills?: boolean;
52
+ /** This node uses position:absolute to overlay on top of sibling (negative-gap pattern) */
53
+ isAbsoluteOverlay?: boolean;
54
+ };
55
+ children?: EnrichedASTNode[];
56
+ }
57
+ export interface EnrichedASTStats {
58
+ totalNodes: number;
59
+ figmaLayoutNodes: number;
60
+ inferredLayoutNodes: number;
61
+ absoluteNodes: number;
62
+ needsDetailFetch: number;
63
+ }
64
+ export interface EnrichedAST {
65
+ version: '1.0';
66
+ fileKey: string;
67
+ rootNodeId: string;
68
+ viewport: {
69
+ width: number;
70
+ height: number;
71
+ };
72
+ stats: EnrichedASTStats;
73
+ root: EnrichedASTNode;
74
+ }
75
+ /**
76
+ * Build an EnrichedAST from a ParsedFigmaData node tree.
77
+ * The nodeTree should already have _absX/_absY injected (from injectBounds).
78
+ */
79
+ export declare function enrichAST(nodeTree: ParsedFigmaData, fileKey: string, rootNodeId: string): EnrichedAST;
80
+ //# sourceMappingURL=ast-enricher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-enricher.d.ts","sourceRoot":"","sources":["../src/ast-enricher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAa,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9D,OAAO,EAAwC,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIlG,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IAEb,MAAM,EAAE;QACN,CAAC,EAAE,MAAM,CAAC;QACV,CAAC,EAAE,MAAM,CAAC;QACV,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IAEF,MAAM,EAAE,cAAc,CAAC;IAEvB,KAAK,CAAC,EAAE;QACN,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,IAAI,CAAC,EAAE;QACL,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QAChC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,oEAAoE;QACpE,cAAc,CAAC,EAAE,KAAK,CAAC;YACrB,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;YAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,cAAc,CAAC,EAAE,MAAM,CAAC;SACzB,CAAC,CAAC;KACJ,CAAC;IAEF,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,KAAK,CAAC;QAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,eAAe,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC;KACxC,CAAC;IAEF,KAAK,CAAC,EAAE;QACN,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;QACtB,eAAe,CAAC,EAAE,OAAO,CAAC;QAC1B,2FAA2F;QAC3F,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IAEF,QAAQ,CAAC,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE;QACR,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,KAAK,EAAE,gBAAgB,CAAC;IACxB,IAAI,EAAE,eAAe,CAAC;CACvB;AA4SD;;;GAGG;AACH,wBAAgB,SAAS,CACvB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,GACjB,WAAW,CA+Bb"}
@@ -0,0 +1,318 @@
1
+ import { inferLayoutForNode } from './layout-inference.js';
2
+ // ─── Constants ──────────────────────────────────────────────────────────────
3
+ const VECTOR_LEAF_TYPES = new Set([
4
+ 'VECTOR', 'ELLIPSE', 'RECTANGLE', 'LINE', 'REGULAR_POLYGON', 'STAR',
5
+ 'BOOLEAN_OPERATION', 'IMAGE-SVG', 'SVG', 'IMAGE',
6
+ ]);
7
+ const IMAGE_TYPES = new Set(['IMAGE-SVG', 'SVG', 'IMAGE', 'VECTOR']);
8
+ // ─── Node Classification Helpers ────────────────────────────────────────────
9
+ function isNodeInvisible(node) {
10
+ if (node._visible === false)
11
+ return true;
12
+ if (node.style?.opacity === 0)
13
+ return true;
14
+ const w = node.layout?.width ?? 0;
15
+ const h = node.layout?.height ?? 0;
16
+ if (w === 0 && h === 0)
17
+ return true;
18
+ return false;
19
+ }
20
+ function isGraphicOnlySubtree(node) {
21
+ if (VECTOR_LEAF_TYPES.has(node.type))
22
+ return true;
23
+ if (node.imageRef != null)
24
+ return true;
25
+ if (node.type === 'TEXT')
26
+ return false;
27
+ if (node.type === 'FRAME') {
28
+ const mode = node.layout?.mode;
29
+ if (mode === 'row' || mode === 'column')
30
+ return false;
31
+ }
32
+ if (!node.children || node.children.length === 0)
33
+ return true;
34
+ return node.children.every(child => isGraphicOnlySubtree(child));
35
+ }
36
+ function isImageNode(node) {
37
+ return IMAGE_TYPES.has(node.type) || node.imageRef != null;
38
+ }
39
+ /**
40
+ * Detect if a node needs a get_node_details call for full style data.
41
+ */
42
+ function detectNeedsDetailFetch(node) {
43
+ let needsDetailFetch = false;
44
+ let hasComplexFills = false;
45
+ // Check for gradient/complex fills
46
+ if (node.style?.backgroundColor) {
47
+ const bg = node.style.backgroundColor;
48
+ if (bg.includes('gradient')) {
49
+ // Gradient string may be incomplete from initial API
50
+ hasComplexFills = true;
51
+ needsDetailFetch = true;
52
+ }
53
+ }
54
+ // Check fills reference for multiple fills
55
+ if (Array.isArray(node.fills) && node.fills.length > 1) {
56
+ hasComplexFills = true;
57
+ needsDetailFetch = true;
58
+ }
59
+ // TEXT node without textStyle (missing font data)
60
+ if (node.type === 'TEXT' && !node.textStyle?.fontFamily && !node.textStyle?.fontSize) {
61
+ needsDetailFetch = true;
62
+ }
63
+ return { needsDetailFetch, hasComplexFills };
64
+ }
65
+ // ─── Node Enrichment ────────────────────────────────────────────────────────
66
+ function enrichNode(node, parentBounds) {
67
+ // Skip invisible nodes
68
+ if (isNodeInvisible(node))
69
+ return null;
70
+ const bounds = {
71
+ x: node._absX ?? node.layout?.x ?? 0,
72
+ y: node._absY ?? node.layout?.y ?? 0,
73
+ width: node.layout?.width ?? 0,
74
+ height: node.layout?.height ?? 0,
75
+ };
76
+ const isText = node.type === 'TEXT';
77
+ const isImage = isImageNode(node);
78
+ const isGraphic = isGraphicOnlySubtree(node);
79
+ // Build child bounds for layout inference (only for non-leaf, non-graphic nodes)
80
+ // Use the same coordinate source as bounds (i.e. _absX ?? layout.x) for consistency.
81
+ const visibleChildren = (node.children ?? []).filter(c => !isNodeInvisible(c));
82
+ // Separate absolutely-positioned children from flex flow children.
83
+ // Figma `layoutPositioning: "ABSOLUTE"` means the child is positioned absolutely
84
+ // within an Auto Layout parent — it doesn't participate in flex flow.
85
+ const absoluteChildren = visibleChildren.filter(c => c.layout?.layoutPositioning === 'ABSOLUTE');
86
+ const flowChildren = visibleChildren.filter(c => c.layout?.layoutPositioning !== 'ABSOLUTE');
87
+ const childBoundsList = flowChildren.map(c => ({
88
+ nodeId: c.id,
89
+ x: c._absX ?? c.layout?.x ?? 0,
90
+ y: c._absY ?? c.layout?.y ?? 0,
91
+ width: c.layout?.width ?? 0,
92
+ height: c.layout?.height ?? 0,
93
+ }));
94
+ // Infer layout (use same bounds as used for this node's output bounds)
95
+ // Only pass flow children (not absolute ones) to the inference engine
96
+ const layout = (!isText && !isImage && !isGraphic)
97
+ ? inferLayoutForNode(node, childBoundsList, bounds)
98
+ : {
99
+ source: 'figma',
100
+ display: 'block',
101
+ selfSizing: {
102
+ horizontal: (node.layout?.horizontalSizing ?? 'fixed'),
103
+ vertical: (node.layout?.verticalSizing ?? 'fixed'),
104
+ ...((node.layout?.horizontalSizing === 'fill' || node.layout?.verticalSizing === 'fill') ? { flex: 1 } : {}),
105
+ },
106
+ };
107
+ // Build enriched node
108
+ const enriched = {
109
+ nodeId: node.id,
110
+ name: node.name,
111
+ type: node.type,
112
+ bounds,
113
+ layout,
114
+ };
115
+ // Style — for TEXT nodes, remove backgroundColor (it's the text fill color, not a background)
116
+ if (node.style && Object.keys(node.style).length > 0) {
117
+ if (isText) {
118
+ const { backgroundColor, ...restStyle } = node.style;
119
+ if (Object.keys(restStyle).length > 0) {
120
+ enriched.style = restStyle;
121
+ }
122
+ }
123
+ else {
124
+ enriched.style = { ...node.style };
125
+ }
126
+ }
127
+ // Text
128
+ if (isText) {
129
+ enriched.text = {
130
+ content: node.text ?? node.name,
131
+ fontFamily: node.textStyle?.fontFamily,
132
+ fontSize: node.textStyle?.fontSize,
133
+ fontWeight: node.textStyle?.fontWeight,
134
+ color: node.textStyle?.color,
135
+ lineHeight: node.textStyle?.lineHeight,
136
+ letterSpacing: node.textStyle?.letterSpacing,
137
+ textAlign: node.textStyle?.textAlign,
138
+ };
139
+ // Rich text: include styled segments if present
140
+ if (node.styledSegments && node.styledSegments.length > 0) {
141
+ enriched.text.styledSegments = node.styledSegments;
142
+ }
143
+ }
144
+ // Image
145
+ if (isImage || isGraphic) {
146
+ const hasImageRef = node.imageRef != null;
147
+ enriched.image = {
148
+ type: hasImageRef ? 'raster' : 'vector',
149
+ imageRef: node.imageRef,
150
+ suggestedFormat: hasImageRef ? 'png' : 'svg',
151
+ };
152
+ }
153
+ // Flags
154
+ const detailCheck = detectNeedsDetailFetch(node);
155
+ const flags = {};
156
+ if (isGraphic)
157
+ flags.isGraphicSubtree = true;
158
+ if (detailCheck.needsDetailFetch)
159
+ flags.needsDetailFetch = true;
160
+ if (detailCheck.hasComplexFills)
161
+ flags.hasComplexFills = true;
162
+ if (Object.keys(flags).length > 0) {
163
+ enriched.flags = flags;
164
+ }
165
+ // Recurse children (skip for graphic subtrees and leaf nodes)
166
+ if (!isText && !isImage && !isGraphic && visibleChildren.length > 0) {
167
+ const enrichedChildren = [];
168
+ // Check if any children are "overlapping" (negative-gap pattern)
169
+ const overlappingIds = new Set(layout.overlappingChildIds ?? []);
170
+ // Build set of Figma-absolute children (layoutPositioning: ABSOLUTE)
171
+ const figmaAbsoluteIds = new Set(absoluteChildren.map(c => c.id));
172
+ for (const child of visibleChildren) {
173
+ const enrichedChild = enrichNode(child, bounds);
174
+ if (!enrichedChild)
175
+ continue;
176
+ // Handle Figma absolute-positioned children (layoutPositioning: "ABSOLUTE")
177
+ if (figmaAbsoluteIds.has(child.id)) {
178
+ const childBounds = enrichedChild.bounds;
179
+ const relLeft = childBounds.x - bounds.x;
180
+ const relTop = childBounds.y - bounds.y;
181
+ const relRight = bounds.width - (relLeft + childBounds.width);
182
+ const relBottom = bounds.height - (relTop + childBounds.height);
183
+ // Use Figma constraints to decide which edges to pin to
184
+ const hConstraint = child.layout?.constraintHorizontal ?? 'LEFT';
185
+ const vConstraint = child.layout?.constraintVertical ?? 'TOP';
186
+ const posProps = {};
187
+ if (hConstraint === 'RIGHT') {
188
+ posProps.right = relRight;
189
+ }
190
+ else if (hConstraint === 'CENTER') {
191
+ posProps.left = relLeft;
192
+ }
193
+ else if (hConstraint === 'LEFT_RIGHT') {
194
+ posProps.left = relLeft;
195
+ posProps.right = relRight;
196
+ }
197
+ else {
198
+ posProps.left = relLeft;
199
+ } // LEFT or default
200
+ if (vConstraint === 'BOTTOM') {
201
+ posProps.bottom = relBottom;
202
+ }
203
+ else if (vConstraint === 'CENTER') {
204
+ posProps.top = relTop;
205
+ }
206
+ else if (vConstraint === 'TOP_BOTTOM') {
207
+ posProps.top = relTop;
208
+ posProps.bottom = relBottom;
209
+ }
210
+ else {
211
+ posProps.top = relTop;
212
+ } // TOP or default
213
+ enrichedChild.layout = {
214
+ ...enrichedChild.layout,
215
+ source: 'figma',
216
+ display: enrichedChild.layout.display,
217
+ position: 'absolute',
218
+ ...posProps,
219
+ };
220
+ if (!enrichedChild.flags)
221
+ enrichedChild.flags = {};
222
+ enrichedChild.flags.isAbsoluteOverlay = true;
223
+ enrichedChildren.push(enrichedChild);
224
+ continue;
225
+ }
226
+ // Override layout for overlapping children (negative-gap heuristic, backup)
227
+ if (overlappingIds.has(child.id)) {
228
+ const childBounds = enrichedChild.bounds;
229
+ // Compute position relative to parent's top-left corner
230
+ const relLeft = childBounds.x - bounds.x;
231
+ const relTop = childBounds.y - bounds.y;
232
+ const relRight = bounds.width - (relLeft + childBounds.width);
233
+ const relBottom = bounds.height - (relTop + childBounds.height);
234
+ // Determine which edge to pin to (whichever is closer)
235
+ const pinRight = relRight <= relLeft;
236
+ const pinBottom = relBottom <= relTop;
237
+ enrichedChild.layout = {
238
+ ...enrichedChild.layout,
239
+ source: 'figma',
240
+ display: 'block',
241
+ position: 'absolute',
242
+ top: relTop,
243
+ ...(pinRight ? { right: relRight } : { left: relLeft }),
244
+ ...(pinBottom ? { bottom: relBottom } : {}),
245
+ };
246
+ // Add a hint flag
247
+ if (!enrichedChild.flags)
248
+ enrichedChild.flags = {};
249
+ enrichedChild.flags.isAbsoluteOverlay = true;
250
+ }
251
+ enrichedChildren.push(enrichedChild);
252
+ }
253
+ if (enrichedChildren.length > 0) {
254
+ enriched.children = enrichedChildren;
255
+ }
256
+ }
257
+ return enriched;
258
+ }
259
+ // ─── Stats ──────────────────────────────────────────────────────────────────
260
+ function buildStats(root) {
261
+ const stats = {
262
+ totalNodes: 0,
263
+ figmaLayoutNodes: 0,
264
+ inferredLayoutNodes: 0,
265
+ absoluteNodes: 0,
266
+ needsDetailFetch: 0,
267
+ };
268
+ function walk(node) {
269
+ stats.totalNodes++;
270
+ if (node.layout.source === 'figma')
271
+ stats.figmaLayoutNodes++;
272
+ else if (node.layout.source === 'inferred')
273
+ stats.inferredLayoutNodes++;
274
+ else if (node.layout.source === 'absolute')
275
+ stats.absoluteNodes++;
276
+ if (node.flags?.needsDetailFetch)
277
+ stats.needsDetailFetch++;
278
+ if (node.children) {
279
+ node.children.forEach(walk);
280
+ }
281
+ }
282
+ walk(root);
283
+ return stats;
284
+ }
285
+ // ─── Main Entry Point ───────────────────────────────────────────────────────
286
+ /**
287
+ * Build an EnrichedAST from a ParsedFigmaData node tree.
288
+ * The nodeTree should already have _absX/_absY injected (from injectBounds).
289
+ */
290
+ export function enrichAST(nodeTree, fileKey, rootNodeId) {
291
+ const rootNode = nodeTree.nodes[0];
292
+ if (!rootNode) {
293
+ throw new Error('No root node found in nodeTree');
294
+ }
295
+ const rootBounds = {
296
+ x: 0,
297
+ y: 0,
298
+ width: rootNode.layout?.width ?? 0,
299
+ height: rootNode.layout?.height ?? 0,
300
+ };
301
+ const enrichedRoot = enrichNode(rootNode, rootBounds);
302
+ if (!enrichedRoot) {
303
+ throw new Error('Root node was filtered out (invisible?)');
304
+ }
305
+ const stats = buildStats(enrichedRoot);
306
+ return {
307
+ version: '1.0',
308
+ fileKey,
309
+ rootNodeId: rootNodeId.replace(/-/g, ':'),
310
+ viewport: {
311
+ width: rootBounds.width,
312
+ height: rootBounds.height,
313
+ },
314
+ stats,
315
+ root: enrichedRoot,
316
+ };
317
+ }
318
+ //# sourceMappingURL=ast-enricher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast-enricher.js","sourceRoot":"","sources":["../src/ast-enricher.ts"],"names":[],"mappings":"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"}