-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy paththroughline-discover.js
More file actions
122 lines (104 loc) · 3.93 KB
/
Copy paththroughline-discover.js
File metadata and controls
122 lines (104 loc) · 3.93 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/**
* throughline-discover.js
* ───────────────────────
* Phase 1: Map the current page structure.
*
* Works on ANY file — single long page, multi-frame canvas, nested sections.
* Does NOT require frames to be broken into top-level units.
* Automatically excludes hidden nodes — hidden shot cards are ignored.
*
* Returns:
* - pageMap: full map of visible top-level nodes, children up to 2 levels deep
* - sectionList: flat list of named reviewable sections
* - placementX: where to place the critique frame
* - mode: "selection" | "page"
*
* AGENT INSTRUCTIONS AFTER RUNNING THIS:
* 1. Present the section list to the designer in a readable format
* 2. Ask which sections to review
* 3. Call get_screenshot + get_design_context on chosen section node IDs
* 4. Proceed to Phase 2 analysis
*/
const page = figma.currentPage
const selection = page.selection
// ── Helper: recursively map a node up to `depth` levels ──────────────────
// Hidden nodes are excluded at every level
function mapNode(node, depth = 0, maxDepth = 2) {
const base = {
id: node.id,
name: node.name,
type: node.type,
visible: node.visible !== false,
width: Math.round(node.width),
height: Math.round(node.height),
x: Math.round(node.x),
y: Math.round(node.y),
}
const hasChildren = 'children' in node && node.children && node.children.length > 0
if (!hasChildren || depth >= maxDepth) return base
if (node.type === 'INSTANCE' && depth > 0) return base
base.children = node.children
.filter(child => child.visible !== false) // exclude hidden nodes
.slice(0, 30)
.map(child => mapNode(child, depth + 1, maxDepth))
return base
}
// ── Build page map ─────────────────────────────────────────────────────────
let mode = 'page'
let targetNodes = page.children.filter(n => n.visible !== false)
if (selection.length > 0) {
mode = 'selection'
targetNodes = selection.filter(n => n.visible !== false)
}
const nodeMap = targetNodes.map(n => mapNode(n, 0, 2))
// ── Find placement position ────────────────────────────────────────────────
let maxX = 0
for (const child of page.children) {
if (child.visible === false) continue
const right = child.x + child.width
if (right > maxX) maxX = right
}
// ── Build flat section list — visible nodes only ───────────────────────────
const sectionList = []
for (const topNode of page.children) {
if (topNode.visible === false) continue
if (!('children' in topNode) || !topNode.children) continue
sectionList.push({
id: topNode.id,
name: topNode.name,
type: topNode.type,
level: 'page',
width: Math.round(topNode.width),
height: Math.round(topNode.height),
})
for (const child of topNode.children) {
if (child.visible === false) continue // skip hidden children
const isNamed = child.name &&
!child.name.match(/^(Frame|Group|Rectangle|Vector)\s*\d*$/)
if (isNamed) {
sectionList.push({
id: child.id,
name: child.name,
type: child.type,
level: 'section',
parentName: topNode.name,
width: Math.round(child.width),
height: Math.round(child.height),
visible: child.visible !== false,
})
}
}
}
return {
pageId: page.id,
pageName: page.name,
mode,
selectionCount: selection.length,
nodeCount: page.children.filter(n => n.visible !== false).length,
hiddenNodeCount: page.children.filter(n => n.visible === false).length,
nodeMap,
sectionList,
placementX: maxX + 100,
placementY: 0,
note: "Hidden nodes excluded. Hidden shot cards in storyboard rows are not included in sectionList."
}