screenhand 0.1.1 → 0.3.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.
- package/README.md +193 -109
- package/bin/darwin-arm64/macos-bridge +0 -0
- package/dist/mcp-desktop.js +5876 -0
- package/dist/scripts/codex-monitor-daemon.js +335 -0
- package/dist/scripts/export-help-center.js +112 -0
- package/dist/scripts/marketing-loop.js +117 -0
- package/dist/scripts/observer-daemon.js +288 -0
- package/dist/scripts/orchestrator-daemon.js +399 -0
- package/dist/scripts/supervisor-daemon.js +272 -0
- package/dist/scripts/threads-campaign.js +208 -0
- package/dist/scripts/worker-daemon.js +228 -0
- package/dist/src/agent/cli.js +82 -0
- package/dist/src/agent/loop.js +274 -0
- package/dist/src/community/fetcher.js +109 -0
- package/dist/src/community/index.js +6 -0
- package/dist/src/community/publisher.js +191 -0
- package/dist/src/community/remote-api.js +121 -0
- package/dist/src/community/types.js +3 -0
- package/dist/src/community/validator.js +95 -0
- package/{src/config.ts → dist/src/config.js} +5 -10
- package/dist/src/context-tracker.js +489 -0
- package/{src/index.ts → dist/src/index.js} +32 -52
- package/dist/src/ingestion/coverage-auditor.js +233 -0
- package/dist/src/ingestion/doc-parser.js +164 -0
- package/dist/src/ingestion/index.js +8 -0
- package/dist/src/ingestion/menu-scanner.js +152 -0
- package/dist/src/ingestion/reference-merger.js +186 -0
- package/dist/src/ingestion/shortcut-extractor.js +180 -0
- package/dist/src/ingestion/tutorial-extractor.js +170 -0
- package/dist/src/ingestion/types.js +3 -0
- package/dist/src/jobs/manager.js +305 -0
- package/dist/src/jobs/runner.js +806 -0
- package/dist/src/jobs/store.js +102 -0
- package/dist/src/jobs/types.js +30 -0
- package/dist/src/jobs/worker.js +97 -0
- package/dist/src/learning/engine.js +356 -0
- package/dist/src/learning/index.js +9 -0
- package/dist/src/learning/locator-policy.js +120 -0
- package/dist/src/learning/pattern-policy.js +89 -0
- package/dist/src/learning/recovery-policy.js +116 -0
- package/dist/src/learning/sensor-policy.js +115 -0
- package/dist/src/learning/timing-model.js +204 -0
- package/dist/src/learning/topology-policy.js +90 -0
- package/dist/src/learning/types.js +9 -0
- package/dist/src/logging/timeline-logger.js +48 -0
- package/dist/src/mcp/mcp-stdio-server.js +464 -0
- package/dist/src/mcp/server.js +363 -0
- package/dist/src/mcp-entry.js +60 -0
- package/dist/src/memory/playbook-seeds.js +200 -0
- package/dist/src/memory/recall.js +222 -0
- package/dist/src/memory/research.js +104 -0
- package/dist/src/memory/seeds.js +101 -0
- package/dist/src/memory/service.js +446 -0
- package/dist/src/memory/session.js +169 -0
- package/dist/src/memory/store.js +451 -0
- package/{src/runtime/locator-cache.ts → dist/src/memory/types.js} +1 -17
- package/dist/src/monitor/codex-monitor.js +382 -0
- package/dist/src/monitor/task-queue.js +97 -0
- package/dist/src/monitor/types.js +62 -0
- package/dist/src/native/bridge-client.js +412 -0
- package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
- package/dist/src/observer/state.js +199 -0
- package/dist/src/observer/types.js +43 -0
- package/dist/src/orchestrator/state.js +68 -0
- package/dist/src/orchestrator/types.js +22 -0
- package/dist/src/perception/ax-source.js +162 -0
- package/dist/src/perception/cdp-source.js +162 -0
- package/dist/src/perception/coordinator.js +771 -0
- package/dist/src/perception/frame-differ.js +287 -0
- package/dist/src/perception/index.js +22 -0
- package/dist/src/perception/manager.js +199 -0
- package/dist/src/perception/types.js +47 -0
- package/dist/src/perception/vision-source.js +399 -0
- package/dist/src/planner/deterministic.js +298 -0
- package/dist/src/planner/executor.js +870 -0
- package/dist/src/planner/goal-store.js +92 -0
- package/dist/src/planner/index.js +21 -0
- package/dist/src/planner/planner.js +520 -0
- package/dist/src/planner/tool-registry.js +71 -0
- package/dist/src/planner/types.js +22 -0
- package/dist/src/platform/explorer.js +213 -0
- package/dist/src/platform/help-center-markdown.js +527 -0
- package/dist/src/platform/learner.js +257 -0
- package/dist/src/playbook/engine.js +486 -0
- package/dist/src/playbook/index.js +20 -0
- package/dist/src/playbook/mcp-recorder.js +204 -0
- package/dist/src/playbook/recorder.js +536 -0
- package/dist/src/playbook/runner.js +408 -0
- package/dist/src/playbook/store.js +312 -0
- package/dist/src/playbook/types.js +17 -0
- package/dist/src/recovery/detectors.js +156 -0
- package/dist/src/recovery/engine.js +327 -0
- package/dist/src/recovery/index.js +20 -0
- package/dist/src/recovery/strategies.js +274 -0
- package/dist/src/recovery/types.js +20 -0
- package/dist/src/runtime/accessibility-adapter.js +430 -0
- package/dist/src/runtime/app-adapter.js +64 -0
- package/dist/src/runtime/applescript-adapter.js +305 -0
- package/dist/src/runtime/ax-role-map.js +96 -0
- package/dist/src/runtime/browser-adapter.js +52 -0
- package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
- package/dist/src/runtime/composite-adapter.js +221 -0
- package/dist/src/runtime/execution-contract.js +159 -0
- package/dist/src/runtime/executor.js +286 -0
- package/dist/src/runtime/locator-cache.js +50 -0
- package/dist/src/runtime/planning-loop.js +63 -0
- package/dist/src/runtime/service.js +432 -0
- package/dist/src/runtime/session-manager.js +63 -0
- package/dist/src/runtime/state-observer.js +121 -0
- package/dist/src/runtime/vision-adapter.js +225 -0
- package/dist/src/state/app-map-types.js +72 -0
- package/dist/src/state/app-map.js +1974 -0
- package/dist/src/state/entity-tracker.js +108 -0
- package/dist/src/state/fusion.js +96 -0
- package/dist/src/state/index.js +21 -0
- package/dist/src/state/ladder-generator.js +236 -0
- package/dist/src/state/persistence.js +156 -0
- package/dist/src/state/types.js +17 -0
- package/dist/src/state/world-model.js +1456 -0
- package/dist/src/supervisor/locks.js +186 -0
- package/dist/src/supervisor/supervisor.js +403 -0
- package/dist/src/supervisor/types.js +30 -0
- package/dist/src/test-mcp-protocol.js +154 -0
- package/dist/src/types.js +17 -0
- package/dist/src/util/atomic-write.js +133 -0
- package/dist/src/util/sanitize.js +146 -0
- package/dist-app-maps/com.figma.Desktop.json +959 -0
- package/dist-app-maps/com.hnc.Discord.json +1146 -0
- package/dist-app-maps/notion.id.json +2831 -0
- package/dist-playbooks/canva-screenhand-carousel.json +445 -0
- package/dist-playbooks/codex-desktop.json +76 -0
- package/dist-playbooks/competitor-research-stack.json +122 -0
- package/dist-playbooks/davinci-color-grade.json +153 -0
- package/dist-playbooks/davinci-edit-timeline.json +162 -0
- package/dist-playbooks/davinci-render.json +114 -0
- package/dist-playbooks/devto.json +52 -0
- package/dist-playbooks/discord.json +41 -0
- package/dist-playbooks/google-flow-create-project.json +59 -0
- package/dist-playbooks/google-flow-edit-image.json +90 -0
- package/dist-playbooks/google-flow-edit-video.json +90 -0
- package/dist-playbooks/google-flow-generate-image.json +68 -0
- package/dist-playbooks/google-flow-generate-video.json +191 -0
- package/dist-playbooks/google-flow-open-project.json +48 -0
- package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
- package/dist-playbooks/google-flow-search-assets.json +64 -0
- package/dist-playbooks/instagram.json +57 -0
- package/dist-playbooks/linkedin.json +52 -0
- package/dist-playbooks/n8n.json +43 -0
- package/dist-playbooks/reddit.json +52 -0
- package/dist-playbooks/threads.json +59 -0
- package/dist-playbooks/x-twitter.json +59 -0
- package/dist-playbooks/youtube.json +59 -0
- package/dist-references/canva.json +646 -0
- package/dist-references/codex-desktop.json +305 -0
- package/dist-references/davinci-resolve-keyboard.json +594 -0
- package/dist-references/davinci-resolve-menu-map.json +1139 -0
- package/dist-references/davinci-resolve-menus-batch1.json +116 -0
- package/dist-references/davinci-resolve-menus-batch2.json +372 -0
- package/dist-references/davinci-resolve-menus-batch3.json +330 -0
- package/dist-references/davinci-resolve-menus-batch4.json +297 -0
- package/dist-references/davinci-resolve-shortcuts.json +333 -0
- package/dist-references/devto.json +317 -0
- package/dist-references/discord.json +549 -0
- package/dist-references/figma.json +1186 -0
- package/dist-references/finder.json +146 -0
- package/dist-references/google-ads-transparency.json +95 -0
- package/dist-references/google-flow.json +649 -0
- package/dist-references/instagram.json +341 -0
- package/dist-references/linkedin.json +324 -0
- package/dist-references/meta-ad-library.json +86 -0
- package/dist-references/n8n.json +387 -0
- package/dist-references/notes.json +27 -0
- package/dist-references/notion.json +163 -0
- package/dist-references/reddit.json +341 -0
- package/dist-references/threads.json +337 -0
- package/dist-references/x-twitter.json +403 -0
- package/dist-references/youtube.json +373 -0
- package/native/macos-bridge/Package.swift +1 -0
- package/native/macos-bridge/Sources/AccessibilityBridge.swift +257 -36
- package/native/macos-bridge/Sources/AppManagement.swift +212 -2
- package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +348 -53
- package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
- package/native/macos-bridge/Sources/VisionBridge.swift +165 -7
- package/native/macos-bridge/Sources/main.swift +169 -16
- package/native/windows-bridge/Program.cs +5 -0
- package/native/windows-bridge/ScreenCapture.cs +124 -0
- package/package.json +29 -4
- package/scripts/postinstall.cjs +127 -0
- package/.claude/commands/automate.md +0 -28
- package/.claude/commands/debug-ui.md +0 -19
- package/.claude/commands/screenshot.md +0 -15
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.mcp.json +0 -8
- package/DESKTOP_MCP_GUIDE.md +0 -92
- package/SECURITY.md +0 -44
- package/docs/architecture.md +0 -47
- package/install-skills.sh +0 -19
- package/mcp-bridge.ts +0 -271
- package/mcp-desktop.ts +0 -1221
- package/playbooks/instagram.json +0 -41
- package/playbooks/instagram_v2.json +0 -201
- package/playbooks/x_v1.json +0 -211
- package/scripts/devpost-live-loop.mjs +0 -421
- package/src/logging/timeline-logger.ts +0 -55
- package/src/mcp/server.ts +0 -449
- package/src/memory/recall.ts +0 -191
- package/src/memory/research.ts +0 -146
- package/src/memory/seeds.ts +0 -123
- package/src/memory/session.ts +0 -201
- package/src/memory/store.ts +0 -434
- package/src/memory/types.ts +0 -69
- package/src/native/bridge-client.ts +0 -239
- package/src/runtime/accessibility-adapter.ts +0 -487
- package/src/runtime/app-adapter.ts +0 -169
- package/src/runtime/applescript-adapter.ts +0 -376
- package/src/runtime/ax-role-map.ts +0 -102
- package/src/runtime/browser-adapter.ts +0 -129
- package/src/runtime/cdp-chrome-adapter.ts +0 -676
- package/src/runtime/composite-adapter.ts +0 -274
- package/src/runtime/executor.ts +0 -396
- package/src/runtime/planning-loop.ts +0 -81
- package/src/runtime/service.ts +0 -448
- package/src/runtime/session-manager.ts +0 -50
- package/src/runtime/state-observer.ts +0 -136
- package/src/runtime/vision-adapter.ts +0 -297
- package/src/types.ts +0 -297
- package/tests/bridge-client.test.ts +0 -176
- package/tests/browser-stealth.test.ts +0 -210
- package/tests/composite-adapter.test.ts +0 -64
- package/tests/mcp-server.test.ts +0 -151
- package/tests/memory-recall.test.ts +0 -339
- package/tests/memory-research.test.ts +0 -159
- package/tests/memory-seeds.test.ts +0 -120
- package/tests/memory-store.test.ts +0 -392
- package/tests/types.test.ts +0 -92
- package/tsconfig.check.json +0 -17
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
- /package/{playbooks → dist-references}/devpost.json +0 -0
|
@@ -0,0 +1,1186 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "figma",
|
|
3
|
+
"name": "Figma Design Tool Automation",
|
|
4
|
+
"description": "Battle-tested playbook for Figma browser automation via CDP. Covers file browser navigation, editor tools (shapes, text, frames), layer management, properties panel, share dialog, design/prototype tabs, export, and programmatic design via Figma Plugin API. Figma uses WebGL canvas — DOM mouse events don't work on canvas, but CDP Input.dispatchMouseEvent (browser_human_click) does. For complex UI creation, use the Figma Plugin API (figma.createFrame/Text/Rectangle) via browser_js — far more reliable than manual canvas clicks.",
|
|
5
|
+
"platform": "figma",
|
|
6
|
+
"bundleId": "com.figma.Desktop",
|
|
7
|
+
"version": "3.4.0",
|
|
8
|
+
"urlPatterns": ["*figma.com/files/*", "*figma.com/design/*", "*figma.com/board/*", "*figma.com/community/*"],
|
|
9
|
+
"tags": ["figma", "design", "browser", "cdp", "webgl", "canvas", "animation", "prototyping", "plugins"],
|
|
10
|
+
"successCount": 131,
|
|
11
|
+
"failCount": 21,
|
|
12
|
+
|
|
13
|
+
"urls": {
|
|
14
|
+
"home": "https://www.figma.com/files/recent",
|
|
15
|
+
"team_files": "https://www.figma.com/files/team/{teamId}/recents-and-sharing",
|
|
16
|
+
"design_file": "https://www.figma.com/design/{fileId}/{fileName}",
|
|
17
|
+
"board_file": "https://www.figma.com/board/{fileId}/{fileName}",
|
|
18
|
+
"community": "https://www.figma.com/community",
|
|
19
|
+
"community_plugin": "https://www.figma.com/community/plugin/{pluginId}/{pluginSlug}",
|
|
20
|
+
"settings": "https://www.figma.com/settings"
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
"selectors": {
|
|
24
|
+
"file_browser": {
|
|
25
|
+
"search_input": "[data-testid='facetedSearchInput']",
|
|
26
|
+
"profile_button": "[data-testid='ProfileButton']",
|
|
27
|
+
"desktop_header": "[data-testid='file-browser-desktop-header']",
|
|
28
|
+
"new_design_file": "[data-testid='new-design-file-button']",
|
|
29
|
+
"new_figjam_file": "[data-testid='new-whiteboard-file-button']",
|
|
30
|
+
"new_slides_file": "[data-testid='new-slides-file-button']",
|
|
31
|
+
"new_buzz_file": "[data-testid='new-cooper-file-button']",
|
|
32
|
+
"new_site_file": "[data-testid='new-sites-file-button']",
|
|
33
|
+
"new_make_file": "[data-testid='new-figmake-file-button']",
|
|
34
|
+
"file_import": "[data-testid='file-import-button']",
|
|
35
|
+
"thumbnail_container": "[data-testid='thumbnail-container']",
|
|
36
|
+
"favorite_star": "[data-testid='favorite-star-button']",
|
|
37
|
+
"ai_prompt_box": "[data-testid='prompt-box']",
|
|
38
|
+
"ai_submit": "[data-testid='prompt-box-submit-button']",
|
|
39
|
+
"help_widget": "[data-testid='help-widget-button']"
|
|
40
|
+
},
|
|
41
|
+
"editor_header": {
|
|
42
|
+
"filename": "[data-testid='filename']",
|
|
43
|
+
"filename_input": "input[aria-label='Figma Design']",
|
|
44
|
+
"_filename_note": "Click [data-testid='filename'] to enter edit mode. The input that appears has aria-label='Figma Design' (not the file name). Use browser_fill_form on this input, then Enter to commit.",
|
|
45
|
+
"folder_link": "[data-testid='folder-name-link']",
|
|
46
|
+
"main_menu": "button[aria-label='Main menu']",
|
|
47
|
+
"missing_fonts": "button[aria-label='Missing fonts']",
|
|
48
|
+
"minimize_ui": "button[aria-label='Minimize UI']",
|
|
49
|
+
"edit_file_menu": "button[aria-label='Edit file menu']",
|
|
50
|
+
"multiplayer_tools": "button[aria-label='Multiplayer tools']",
|
|
51
|
+
"present": "[data-testid='present-as-prototype']",
|
|
52
|
+
"prototype_view": "button[aria-label='Prototype view']",
|
|
53
|
+
"share": "[data-testid='multiplayer-toolbar-share-button']",
|
|
54
|
+
"image_avatar": "[data-testid='image-avatar']"
|
|
55
|
+
},
|
|
56
|
+
"left_panel": {
|
|
57
|
+
"body": "[data-testid='left-panel-body']",
|
|
58
|
+
"file_layers_tab": "[data-testid='FILE_LAYERS_TAB_TEST_ID']",
|
|
59
|
+
"new_page_button": "[data-testid='new-page-button']",
|
|
60
|
+
"pages_row": "[data-testid='PagesRowWrapper']",
|
|
61
|
+
"objects_panel": "[data-testid='objects-panel']",
|
|
62
|
+
"layer_row": "[data-testid='layer-row']",
|
|
63
|
+
"layer_row_with_children": "[data-testid='layer-row-with-children']",
|
|
64
|
+
"layer_panel_row": "[data-testid$='-layers-panel-row']",
|
|
65
|
+
"locked_icon": "[data-testid='locked-icon']",
|
|
66
|
+
"visible_icon": "[data-testid='visible-icon']",
|
|
67
|
+
"toggle_visibility": "[data-testid='object-row-toggle-visibility']",
|
|
68
|
+
"resize_handle": "[data-testid='leftPanelContainer.resizablePanel']",
|
|
69
|
+
"find_button": "button[aria-label='Find']"
|
|
70
|
+
},
|
|
71
|
+
"toolbar": {
|
|
72
|
+
"move_tool": "[data-testid='Move-tool']",
|
|
73
|
+
"frame_tool": "[data-testid='Frame-tool']",
|
|
74
|
+
"rectangle_tool": "[data-testid='Rectangle-tool']",
|
|
75
|
+
"pen_tool": "[data-testid='Pen-tool']",
|
|
76
|
+
"text_tool": "[data-testid='Text-tool']",
|
|
77
|
+
"comment_tool": "[data-testid='Comment-tool']",
|
|
78
|
+
"actions_tool": "[data-testid='Actions-tool']",
|
|
79
|
+
"copy_colors_tool": "[data-testid='Copy colors-tool']",
|
|
80
|
+
"select_tools_chevron": "[data-testid='SelectTools-chevron']",
|
|
81
|
+
"frame_tools_chevron": "[data-testid='FrameTools-chevron']",
|
|
82
|
+
"shape_tools_chevron": "[data-testid='ShapeTools-chevron']",
|
|
83
|
+
"pen_tools_chevron": "[data-testid='PenTools-chevron']",
|
|
84
|
+
"toolbelt_wrapper": "[data-testid='design-toolbelt-wrapper']",
|
|
85
|
+
"mode_segmented_control": "[data-testid='toolbelt-mode-segmented-control']",
|
|
86
|
+
"mode_draw": "[data-testid='toolbelt-mode-option-illustration']",
|
|
87
|
+
"mode_design": "[data-testid='toolbelt-mode-option-design']",
|
|
88
|
+
"mode_dev": "[data-testid='toolbelt-mode-option-handoff']"
|
|
89
|
+
},
|
|
90
|
+
"properties_panel": {
|
|
91
|
+
"panel": "[data-testid='properties-panel']",
|
|
92
|
+
"tab_header": "[data-testid='property-panel-tab-header']",
|
|
93
|
+
"resize_handle": "[data-testid='propertiesPanelContainer.resizablePanel']",
|
|
94
|
+
"x_position": "input[aria-label='X-position']",
|
|
95
|
+
"y_position": "input[aria-label='Y-position']",
|
|
96
|
+
"rotation": "input[aria-label='Rotation']",
|
|
97
|
+
"width": "input[aria-label='transform-width']",
|
|
98
|
+
"height": "input[aria-label='transform-height']",
|
|
99
|
+
"_width_height_note": "Width/height inputs may not have aria-labels in some Figma versions. Fallback: find inputs with value '100' (default size) inside properties panel after the X/Y/Rotation inputs. Use JS select() + execCommand('insertText') + Tab/Enter to set values.",
|
|
100
|
+
"aspect_ratio_lock": "input[aria-label='aspect-ratio-lock-toggle']",
|
|
101
|
+
"corner_radius": "input[aria-label='transform-corner-radius']",
|
|
102
|
+
"color": "input[aria-label='Color']",
|
|
103
|
+
"opacity": "input[aria-label*='opacity']",
|
|
104
|
+
"font_search": "input[aria-label='font-picker-search-input']",
|
|
105
|
+
"font_size": "input[aria-label^='Font size']",
|
|
106
|
+
"create_component": "button[aria-label='Create component']",
|
|
107
|
+
"align_left": "button[aria-label='Align left']",
|
|
108
|
+
"align_center_h": "button[aria-label='Align horizontal centers']",
|
|
109
|
+
"align_right": "button[aria-label='Align right']",
|
|
110
|
+
"align_top": "button[aria-label='Align top']",
|
|
111
|
+
"align_center_v": "button[aria-label='Align vertical centers']",
|
|
112
|
+
"align_bottom": "button[aria-label='Align bottom']",
|
|
113
|
+
"rotate_90": "button[aria-label='Rotate 90˚ right']",
|
|
114
|
+
"flip_horizontal": "button[aria-label='Flip horizontal']",
|
|
115
|
+
"flip_vertical": "button[aria-label='Flip vertical']",
|
|
116
|
+
"resize_to_fit": "button[aria-label='Resize to fit']",
|
|
117
|
+
"blend_mode": "button[aria-label='Apply blend mode']",
|
|
118
|
+
"individual_corners": "button[aria-label='Individual corners']",
|
|
119
|
+
"fill_styles": "button[aria-label='Fill, Apply styles and variables']",
|
|
120
|
+
"add_fill": "button[aria-label='Add fill']",
|
|
121
|
+
"fill_color_swatch": "button[aria-label^='Solid color hex']",
|
|
122
|
+
"add_stroke": "button[aria-label='Add stroke']",
|
|
123
|
+
"stroke_styles": "button[aria-label='Stroke, Apply styles and variables']",
|
|
124
|
+
"add_effect": "button[aria-label='Add effect']",
|
|
125
|
+
"add_layout_guide": "button[aria-label='Add layout guide']",
|
|
126
|
+
"add_export": "button[aria-label='Add export settings']",
|
|
127
|
+
"open_variables": "button[aria-label='Open variables']",
|
|
128
|
+
"create_style": "button[aria-label='Create style']"
|
|
129
|
+
},
|
|
130
|
+
"share_dialog": {
|
|
131
|
+
"email_input": "input[aria-label='Add comma separated emails to invite']",
|
|
132
|
+
"invite_button": "button:has-text('Invite')",
|
|
133
|
+
"copy_link": "button:has-text('Copy link')",
|
|
134
|
+
"copy_dev_mode_link": "button:has-text('Copy Dev Mode link')",
|
|
135
|
+
"copy_prototype_link": "button:has-text('Copy prototype link')",
|
|
136
|
+
"publish_community": "button:has-text('Publish to Community')",
|
|
137
|
+
"get_embed_code": "button:has-text('Get embed code')"
|
|
138
|
+
},
|
|
139
|
+
"actions_panel": {
|
|
140
|
+
"actions_button": "button[aria-label='Actions']",
|
|
141
|
+
"_actions_note": "Two Actions buttons may exist in DOM (one per toolbar row). Use the visible one (getBoundingClientRect().width > 0). Located in the bottom toolbar.",
|
|
142
|
+
"quick_actions_window": ".quick_actions--qaV2Window--EtP7F",
|
|
143
|
+
"quick_actions_search": ".search--searchInput--k55la",
|
|
144
|
+
"plugins_widgets_tab": "text content 'Plugins & widgets' inside the quick actions window"
|
|
145
|
+
},
|
|
146
|
+
"plugin_ui": {
|
|
147
|
+
"plugin_iframe": "iframe[src*='plugin-sandbox']",
|
|
148
|
+
"_plugin_iframe_note": "Plugin UI runs in sandboxed iframe. Content NOT accessible via parent JS. Use screenshot/OCR + coordinates for interaction.",
|
|
149
|
+
"plugin_container": ".jsvm_ui--pluginUI--YQm-H",
|
|
150
|
+
"plugin_modal_window": "[data-testid='pluginModalWindow']"
|
|
151
|
+
},
|
|
152
|
+
"other": {
|
|
153
|
+
"canvas": "canvas",
|
|
154
|
+
"bottom_right_tools": "[data-testid='bottom-right-tools-container']",
|
|
155
|
+
"help_zoom_menu": "[data-testid='buzz-help-and-zoom-menu']",
|
|
156
|
+
"help_button": "[data-testid='help-widget-button']",
|
|
157
|
+
"visual_bell": "[data-testid='visual-bell-root']",
|
|
158
|
+
"plugin_modal": "[data-testid='pluginModalWindow']"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
"keyboard_shortcuts": {
|
|
163
|
+
"move_tool": "V",
|
|
164
|
+
"frame_tool": "F",
|
|
165
|
+
"rectangle_tool": "R",
|
|
166
|
+
"pen_tool": "P",
|
|
167
|
+
"text_tool": "T",
|
|
168
|
+
"comment_tool": "C",
|
|
169
|
+
"copy_colors": "Ctrl+C",
|
|
170
|
+
"actions_palette": "Cmd+/ or Cmd+K",
|
|
171
|
+
"rerun_last_plugin": "Cmd+Option+P",
|
|
172
|
+
"notes": "Keyboard shortcuts only work when Figma canvas has focus. JS keydown events do NOT trigger Figma shortcuts — they must go through CDP Input.dispatchKeyEvent or native OS key events."
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
"detection": {
|
|
176
|
+
"is_file_browser": "window.location.href.includes('/files/')",
|
|
177
|
+
"is_editor": "window.location.href.includes('/design/') || window.location.href.includes('/board/')",
|
|
178
|
+
"has_canvas": "!!document.querySelector('canvas')",
|
|
179
|
+
"is_logged_in": "!!document.querySelector('[data-testid=\"ProfileButton\"]') || !!document.querySelector('[data-testid=\"filename\"]')",
|
|
180
|
+
"has_selection": "!!document.querySelector('input[aria-label=\"X-position\"]')",
|
|
181
|
+
"tool_active": "tool => document.querySelector(`[data-testid=\"${tool}-tool\"]`)?.getAttribute('aria-pressed') === 'true'",
|
|
182
|
+
"share_dialog_open": "!!Array.from(document.querySelectorAll('[role=\"dialog\"]')).find(d => d.textContent?.includes('Share'))",
|
|
183
|
+
"menu_open": "document.querySelectorAll('[role=\"menuitem\"]').length > 0"
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
"flows": {
|
|
187
|
+
"open_file_from_browser": {
|
|
188
|
+
"description": "Open a design file from the file browser home page",
|
|
189
|
+
"steps": [
|
|
190
|
+
"Find thumbnail: [data-testid='thumbnail-container']",
|
|
191
|
+
"Get parent container with file name text to identify correct file",
|
|
192
|
+
"Double-click via JS: full mousedown+mouseup+click sequence with detail:1 then detail:2 + dblclick event",
|
|
193
|
+
"Wait for URL to change to /design/ or /board/",
|
|
194
|
+
"Wait for canvas element to appear"
|
|
195
|
+
],
|
|
196
|
+
"guards": ["Must be on file browser page", "Must be logged in"],
|
|
197
|
+
"why": "Single click selects file (shows context menu). Double-click opens it. Figma's SPA uses custom event handlers — need full dblclick event sequence with proper detail counts. browser_human_click does single click only."
|
|
198
|
+
},
|
|
199
|
+
"create_new_file": {
|
|
200
|
+
"description": "Create a new design/FigJam/Slides file",
|
|
201
|
+
"steps": [
|
|
202
|
+
"Find new file button: [data-testid='new-design-file-button'] (or whiteboard/slides/etc)",
|
|
203
|
+
"Use JS click: document.querySelector('[data-testid=\"new-design-file-button\"]').click()",
|
|
204
|
+
"Wait for URL to change to /design/: browser_wait with window.location.href.includes('/design/')",
|
|
205
|
+
"Wait for canvas: browser_wait with !!document.querySelector('canvas')"
|
|
206
|
+
],
|
|
207
|
+
"guards": ["Must be on file browser page"],
|
|
208
|
+
"why": "browser_human_click on the new file button does NOT work — it clicks at button coordinates but Figma's SPA doesn't respond to CDP mouse events on this button. JS .click() works reliably. The new file opens in the same tab (not a new tab)."
|
|
209
|
+
},
|
|
210
|
+
"rename_file": {
|
|
211
|
+
"description": "Rename the current design file",
|
|
212
|
+
"steps": [
|
|
213
|
+
"Click filename: browser_human_click on [data-testid='filename']",
|
|
214
|
+
"Input appears with aria-label='Figma Design' (always this label, not the current filename)",
|
|
215
|
+
"Type new name: browser_fill_form on input[aria-label='Figma Design'] with clear:true",
|
|
216
|
+
"Commit: JS dispatch keydown Enter on the input, or Tab away",
|
|
217
|
+
"Verify: re-read [data-testid='filename'] textContent"
|
|
218
|
+
],
|
|
219
|
+
"guards": ["Must be in editor"],
|
|
220
|
+
"why": "The filename input always has aria-label='Figma Design' regardless of current name. browser_fill_form with clear:true works for typing the new name."
|
|
221
|
+
},
|
|
222
|
+
"programmatic_design": {
|
|
223
|
+
"description": "Create complex UI designs programmatically using Figma Plugin API via browser_js",
|
|
224
|
+
"steps": [
|
|
225
|
+
"The Figma Plugin API (figma.*) is available in browser_js — no plugin installation needed",
|
|
226
|
+
"Load fonts first: await figma.loadFontAsync({ family: 'Inter', style: 'Regular' })",
|
|
227
|
+
"Create frames: const frame = figma.createFrame(); frame.resize(390, 844); frame.fills = [{type:'SOLID', color:{r:0,g:0,b:0}}]",
|
|
228
|
+
"Create text: const t = figma.createText(); t.characters = 'Hello'; t.fontSize = 24; t.fontName = {family:'Inter', style:'Bold'}",
|
|
229
|
+
"Create shapes: figma.createRectangle(), figma.createEllipse()",
|
|
230
|
+
"Parent elements: frame.appendChild(child)",
|
|
231
|
+
"Set position: element.x = 20; element.y = 100",
|
|
232
|
+
"Set colors: element.fills = [{type:'SOLID', color:{r:1,g:1,b:1}}]",
|
|
233
|
+
"Strokes: element.strokes = [{type:'SOLID', color:{r:0,g:1,b:0}}]; element.strokeWeight = 2",
|
|
234
|
+
"Corner radius: element.cornerRadius = 16",
|
|
235
|
+
"Zoom to fit: figma.viewport.scrollAndZoomIntoView(figma.currentPage.children)",
|
|
236
|
+
"Wrap in async IIFE: (async () => { await figma.loadFontAsync(...); ... return 'done'; })()"
|
|
237
|
+
],
|
|
238
|
+
"guards": ["Must be in editor with canvas loaded"],
|
|
239
|
+
"why": "RECOMMENDED for complex designs. The Figma Plugin API is far more reliable than clicking canvas elements one by one. It avoids all issues with canvas coordinates, focus stealing, and timing. You can create entire multi-screen app designs in a single browser_js call. Available fonts: Inter (Regular, Medium, Semi Bold, Bold, Extra Bold), Roboto, and other Google Fonts loaded in the file."
|
|
240
|
+
},
|
|
241
|
+
"single_scrollable_website": {
|
|
242
|
+
"description": "Create a full scrollable website in one frame (not separate screens) for realistic prototype testing",
|
|
243
|
+
"steps": [
|
|
244
|
+
"Create ONE tall frame: const P = figma.createFrame(); P.resize(1440, 7200); P.clipsContent = true;",
|
|
245
|
+
"Stack sections vertically inside: Hero (y:0-900), Features (y:900-1700), etc.",
|
|
246
|
+
"For alternating dark/light sections: add full-width Rectangle fills at section Y offsets",
|
|
247
|
+
"Set prototype starting point: figma.currentPage.flowStartingPoints = [{nodeId: P.id, name: 'Website'}]",
|
|
248
|
+
"In prototype mode, the frame scrolls vertically like a real webpage",
|
|
249
|
+
"No click-to-navigate needed — just scroll. Much more realistic than separate screen frames."
|
|
250
|
+
],
|
|
251
|
+
"guards": ["Must be in editor"],
|
|
252
|
+
"why": "Separate frames per section break the scrolling experience in prototype mode. A single tall frame with clipsContent=true behaves like a real scrollable website. Users can test the full UX flow by scrolling, not clicking. Height of 7200px fits ~8 desktop sections at 900px each."
|
|
253
|
+
},
|
|
254
|
+
"prototype_connections": {
|
|
255
|
+
"description": "Set up prototype flows and transitions between frames",
|
|
256
|
+
"steps": [
|
|
257
|
+
"Set starting point: page.flowStartingPoints = [{nodeId: frameId, name: 'Flow Name'}]",
|
|
258
|
+
"Add click navigation: frame.reactions = [{trigger:{type:'ON_CLICK'}, actions:[{type:'NODE', destinationId:targetId, navigation:'NAVIGATE', transition:{type:'SMART_ANIMATE', easing:{type:'EASE_IN_AND_OUT'}, duration:0.5}, preserveScrollPosition:false}]}]",
|
|
259
|
+
"Transition types: SMART_ANIMATE, DISSOLVE, SLIDE_IN, SLIDE_OUT, PUSH, MOVE_IN, MOVE_OUT",
|
|
260
|
+
"Trigger types: ON_CLICK, ON_HOVER, ON_PRESS, ON_DRAG, AFTER_TIMEOUT, MOUSE_ENTER, MOUSE_LEAVE",
|
|
261
|
+
"For multi-screen flows: loop through frames setting each to navigate to next"
|
|
262
|
+
],
|
|
263
|
+
"guards": ["Must have frames on the page"],
|
|
264
|
+
"why": "Prototype connections make designs interactive in Figma's prototype mode. SMART_ANIMATE smoothly transitions matching layers between frames."
|
|
265
|
+
},
|
|
266
|
+
"effects_and_glassmorphism": {
|
|
267
|
+
"description": "Apply shadows, blurs, and glassmorphic effects via Plugin API",
|
|
268
|
+
"steps": [
|
|
269
|
+
"Drop shadow glow: element.effects = [{type:'DROP_SHADOW', color:{r,g,b, a:0.2}, offset:{x:0,y:0}, radius:50, spread:0, visible:true, blendMode:'NORMAL'}]",
|
|
270
|
+
"Layer blur (ambient orbs): element.effects = [{type:'LAYER_BLUR', radius:150, visible:true}]",
|
|
271
|
+
"BACKGROUND_BLUR (real glassmorphism): element.effects = [{type:'BACKGROUND_BLUR', radius:40, visible:true}] — requires semi-transparent fill on the element",
|
|
272
|
+
"Glass card recipe: fill white at 10% opacity + BACKGROUND_BLUR radius 24-40 + white stroke at 20% + DROP_SHADOW",
|
|
273
|
+
"Dark glass: fill black at 30% + BACKGROUND_BLUR + INNER_SHADOW (top edge reflection)",
|
|
274
|
+
"Tinted glass: fill brand-color at 15% + BACKGROUND_BLUR + colored border",
|
|
275
|
+
"Multiple effects stack: element.effects = [backgroundBlur, dropShadow, innerShadow]",
|
|
276
|
+
"Behind the glass: place colorful blurred ellipses (LAYER_BLUR) behind the glass card for visible frosted effect"
|
|
277
|
+
],
|
|
278
|
+
"guards": ["Must be in editor"],
|
|
279
|
+
"why": "BACKGROUND_BLUR is the real glassmorphism (equivalent to CSS backdrop-filter:blur). Must have content behind the element for the blur to be visible. Combine with semi-transparent fill + border + inner shadow for convincing glass. Three variants tested: light glass, dark glass, tinted glass."
|
|
280
|
+
},
|
|
281
|
+
"auto_layout": {
|
|
282
|
+
"description": "Create responsive flexbox-like layouts via Plugin API",
|
|
283
|
+
"steps": [
|
|
284
|
+
"Horizontal row: frame.layoutMode = 'HORIZONTAL'; frame.itemSpacing = 16;",
|
|
285
|
+
"Vertical stack: frame.layoutMode = 'VERTICAL'; frame.itemSpacing = 12;",
|
|
286
|
+
"Sizing: frame.primaryAxisSizingMode = 'AUTO'; frame.counterAxisSizingMode = 'AUTO'; (or 'FIXED')",
|
|
287
|
+
"Padding: frame.paddingLeft = 24; frame.paddingRight = 24; frame.paddingTop = 20; frame.paddingBottom = 20;",
|
|
288
|
+
"Alignment: frame.primaryAxisAlignItems = 'CENTER'|'MIN'|'MAX'|'SPACE_BETWEEN'; frame.counterAxisAlignItems = 'CENTER'|'MIN'|'MAX';",
|
|
289
|
+
"Child fill: child must be appended FIRST, then set child.layoutSizingHorizontal = 'FILL'; (ERRORS if set before appending to auto-layout parent)",
|
|
290
|
+
"Wrap layout: frame.layoutWrap = 'WRAP'; frame.counterAxisSpacing = 10; (tag clouds, chip grids)",
|
|
291
|
+
"Nested: auto-layout frames inside auto-layout frames — e.g. horizontal row of vertical cards"
|
|
292
|
+
],
|
|
293
|
+
"guards": ["Must be in editor"],
|
|
294
|
+
"why": "Auto Layout is THE most important feature for real UI design. Without it, everything is absolute-positioned and breaks. CRITICAL GOTCHA: layoutSizingHorizontal='FILL' MUST be set AFTER appending the child to an auto-layout parent, not before — Figma throws 'FILL can only be set on children of auto-layout frames' error."
|
|
295
|
+
},
|
|
296
|
+
"image_fills": {
|
|
297
|
+
"description": "Load real images from URLs into Figma designs",
|
|
298
|
+
"steps": [
|
|
299
|
+
"Load image: const image = await figma.createImageAsync('https://images.unsplash.com/photo-xxx?w=400&h=300&fit=crop');",
|
|
300
|
+
"Apply to any shape: node.fills = [{type:'IMAGE', imageHash:image.hash, scaleMode:'FILL'}];",
|
|
301
|
+
"Scale modes: 'FILL' (cover), 'FIT' (contain), 'CROP' (manual crop), 'TILE' (repeat)",
|
|
302
|
+
"Circular avatar: const avatar = figma.createEllipse(); avatar.fills = [{type:'IMAGE', imageHash:img.hash, scaleMode:'FILL'}];",
|
|
303
|
+
"Avatar with border: avatar.strokes = [{type:'SOLID', color:{r:0.4,g:0.3,b:1}}]; avatar.strokeWeight = 3;",
|
|
304
|
+
"Card with image: create Frame with auto-layout, add image Frame as child, add text area below",
|
|
305
|
+
"Unsplash URL pattern: add ?w=WIDTH&h=HEIGHT&fit=crop for optimized loading"
|
|
306
|
+
],
|
|
307
|
+
"guards": ["Must be in editor", "URL must be accessible (CORS-free)"],
|
|
308
|
+
"why": "createImageAsync loads real photos from URLs — replaces colored rectangles as image placeholders. Works with Unsplash, any public image URL. The hash can be reused across multiple fills. Tested on rectangles, ellipses, and frames."
|
|
309
|
+
},
|
|
310
|
+
"svg_import": {
|
|
311
|
+
"description": "Import real SVG icons and shapes into Figma",
|
|
312
|
+
"steps": [
|
|
313
|
+
"Import SVG: const node = figma.createNodeFromSvg('<svg xmlns=\"http://www.w3.org/2000/svg\" ...>...</svg>');",
|
|
314
|
+
"Resize: node.resize(28, 28);",
|
|
315
|
+
"Recolor SVG: traverse children recursively, set fills and strokes on each path/shape",
|
|
316
|
+
"Recolor function: function recolorSvg(node, color) { if ('fills' in node) node.fills = node.fills.map(f => ({...f, color})); if ('strokes' in node) node.strokes = node.strokes.map(s => ({...s, color})); if ('children' in node) for (const c of node.children) recolorSvg(c, color); }",
|
|
317
|
+
"Lucide icons: use SVG strings from Lucide icon library (stroke-based, 24x24 viewBox)",
|
|
318
|
+
"Complex shapes: hexagons, circuit boards, abstract patterns — all work as SVG strings",
|
|
319
|
+
"MUST include xmlns='http://www.w3.org/2000/svg' in the SVG string"
|
|
320
|
+
],
|
|
321
|
+
"guards": ["Must be in editor", "SVG must have xmlns attribute"],
|
|
322
|
+
"why": "createNodeFromSvg replaces emoji icons completely. Import any SVG string — Lucide, Heroicons, custom shapes. Returns a FrameNode containing vector children that can be recolored programmatically. Tested with 8 Lucide icons + 3 complex multi-element SVGs."
|
|
323
|
+
},
|
|
324
|
+
"gradient_types": {
|
|
325
|
+
"description": "Create radial, angular, and diamond gradients for advanced visual effects",
|
|
326
|
+
"steps": [
|
|
327
|
+
"Radial glow: fills=[{type:'GRADIENT_RADIAL', gradientTransform:[[0.5,0,0.25],[0,0.5,0.25]], gradientStops:[{position:0,color:{r:0.6,g:0.2,b:1,a:0.8}},{position:1,color:{r:0,g:0,b:0,a:0}}]}]",
|
|
328
|
+
"Angular/Conic: type:'GRADIENT_ANGULAR' — creates rainbow sweep or conic gradient",
|
|
329
|
+
"Diamond: type:'GRADIENT_DIAMOND' — creates diamond-shaped radial falloff",
|
|
330
|
+
"Multi-layer fills: node.fills = [solidBg, radialGlow1, radialGlow2, radialGlow3] — stack unlimited",
|
|
331
|
+
"GradientTransform: [[scaleX, 0, translateX], [0, scaleY, translateY]] — values 0-1 relative to node bounds",
|
|
332
|
+
"Centering: [[0.5, 0, 0.25], [0, 0.5, 0.25]] centers a gradient with 50% scale"
|
|
333
|
+
],
|
|
334
|
+
"guards": ["Must be in editor"],
|
|
335
|
+
"why": "Radial gradients replace LAYER_BLUR hacks for glow effects. Multi-layer stacked radials create beautiful nebula/aurora backgrounds. Angular gradients create conic sweeps. Diamond creates unique jewel-like falloffs. gradientTransform is a 2x3 affine matrix controlling position and scale."
|
|
336
|
+
},
|
|
337
|
+
"components_and_variants": {
|
|
338
|
+
"description": "Create reusable components with state variants and interactive prototyping",
|
|
339
|
+
"steps": [
|
|
340
|
+
"Create component: const comp = figma.createComponent(); comp.name = 'State=Default';",
|
|
341
|
+
"Create variants: make multiple components with 'PropertyName=Value' naming convention",
|
|
342
|
+
"Combine: const set = figma.combineAsVariants([default, hover, active, disabled], figma.currentPage);",
|
|
343
|
+
"Variant set name: set.name = 'Button'; — Figma auto-adds padding and layout",
|
|
344
|
+
"Instance: const inst = defaultComp.createInstance(); parentFrame.appendChild(inst);",
|
|
345
|
+
"Interactive: comp.reactions = [{trigger:{type:'ON_HOVER'}, actions:[{type:'NODE', destinationId:hoverComp.id, navigation:'CHANGE_TO', transition:{type:'SMART_ANIMATE', easing:{type:'EASE_OUT'}, duration:0.15}}]}];",
|
|
346
|
+
"Variant naming: 'State=Default', 'Size=Large', 'Type=Primary' — Figma parses Property=Value format"
|
|
347
|
+
],
|
|
348
|
+
"guards": ["Must be in editor"],
|
|
349
|
+
"why": "Components make designs reusable. combineAsVariants groups them into a variant set. ON_HOVER + CHANGE_TO + SMART_ANIMATE creates interactive hover states in prototype mode. Instances inherit changes from the main component. Note: variant set is placed on the PAGE, not inside the test frame — it's a top-level node."
|
|
350
|
+
},
|
|
351
|
+
"blend_modes": {
|
|
352
|
+
"description": "Apply blend modes for visual depth and atmosphere",
|
|
353
|
+
"steps": [
|
|
354
|
+
"On node: node.blendMode = 'SCREEN'; — applies to entire layer",
|
|
355
|
+
"Per fill: node.fills = [{type:'SOLID', color:{...}, blendMode:'MULTIPLY'}];",
|
|
356
|
+
"Key modes for UI: SCREEN (glow/light), MULTIPLY (shadows/tint), OVERLAY (contrast boost), SOFT_LIGHT (subtle contrast)",
|
|
357
|
+
"Creative modes: DIFFERENCE (psychedelic), EXCLUSION (muted), COLOR_DODGE (intense brighten), LUMINOSITY (brightness only)",
|
|
358
|
+
"Best practice: layer a colored rectangle with blend mode OVER a photo for color grading effects",
|
|
359
|
+
"19 modes total: PASS_THROUGH, NORMAL, DARKEN, MULTIPLY, LINEAR_BURN, COLOR_BURN, LIGHTEN, SCREEN, LINEAR_DODGE, COLOR_DODGE, OVERLAY, SOFT_LIGHT, HARD_LIGHT, DIFFERENCE, EXCLUSION, HUE, SATURATION, COLOR, LUMINOSITY"
|
|
360
|
+
],
|
|
361
|
+
"guards": ["Must be in editor"],
|
|
362
|
+
"why": "Blend modes add visual depth that's impossible with plain fills. SCREEN mode over dark backgrounds creates glowing light effects. MULTIPLY tints photos. Tested 8 modes over a real photo background — all work correctly."
|
|
363
|
+
},
|
|
364
|
+
"boolean_operations": {
|
|
365
|
+
"description": "Combine shapes into custom forms via boolean operations",
|
|
366
|
+
"steps": [
|
|
367
|
+
"Union: figma.union([shape1, shape2], parentFrame); — combines shapes",
|
|
368
|
+
"Subtract: figma.subtract([base, cutout], parentFrame); — removes cutout from base",
|
|
369
|
+
"Intersect: figma.intersect([shape1, shape2], parentFrame); — keeps only overlap",
|
|
370
|
+
"Exclude: figma.exclude([shape1, shape2], parentFrame); — keeps non-overlapping areas",
|
|
371
|
+
"Result is a BooleanOperationNode — set fills on the result, not the original shapes",
|
|
372
|
+
"Badge cutout pattern: subtract a circle from a rect to create a notification badge notch",
|
|
373
|
+
"Shapes must be siblings in the same parent before boolean operation"
|
|
374
|
+
],
|
|
375
|
+
"guards": ["Must be in editor"],
|
|
376
|
+
"why": "Boolean operations create custom shapes from primitives. The badge cutout pattern (subtract circle from rect) is a common UI pattern for notification indicators. All 4 operations tested and working."
|
|
377
|
+
},
|
|
378
|
+
"masks": {
|
|
379
|
+
"description": "Clip images and content into custom shapes using masks",
|
|
380
|
+
"steps": [
|
|
381
|
+
"Create mask shape: const mask = figma.createStar(); mask.resize(180,180); mask.isMask = true;",
|
|
382
|
+
"Add to parent: parentFrame.appendChild(mask);",
|
|
383
|
+
"Add content AFTER mask: parentFrame.appendChild(imageNode); — subsequent siblings get masked",
|
|
384
|
+
"Shape options: createStar(), createPolygon() (set .pointCount=6 for hex), createRectangle() (set .cornerRadius=40 for squircle), createEllipse()",
|
|
385
|
+
"Parent must have clipsContent = true for clean edges",
|
|
386
|
+
"Order matters: mask FIRST, then content. Content must be a sibling (same parent level)"
|
|
387
|
+
],
|
|
388
|
+
"guards": ["Must be in editor"],
|
|
389
|
+
"why": "Masks clip images into any shape — star, hexagon, squircle, custom vector. Tested with real Unsplash images inside star, hex, and squircle masks. Key: isMask=true on the shape, then image as next sibling."
|
|
390
|
+
},
|
|
391
|
+
"vector_paths": {
|
|
392
|
+
"description": "Create custom shapes from SVG path data",
|
|
393
|
+
"steps": [
|
|
394
|
+
"Create: const vec = figma.createVector();",
|
|
395
|
+
"Set path: vec.vectorPaths = [{windingRule:'NONZERO', data:'M 0 0 L 100 0 L 100 100 Z'}];",
|
|
396
|
+
"Path format: standard SVG path commands (M, L, C, Q, Z) with SPACE-separated coordinates",
|
|
397
|
+
"CRITICAL: Use spaces between coordinates, NOT commas — 'M 50 0 L 20 55' NOT 'M 50,0 L 20,55'",
|
|
398
|
+
"Fills: vec.fills = [{type:'SOLID', color:{r:1,g:0.8,b:0}}];",
|
|
399
|
+
"Resize: vec.resize(100, 100); — scales the vector",
|
|
400
|
+
"Fallback: if vectorPaths fails, use figma.createNodeFromSvg() with the path in an SVG wrapper"
|
|
401
|
+
],
|
|
402
|
+
"guards": ["Must be in editor"],
|
|
403
|
+
"why": "Vector paths create arbitrary shapes from path data. Figma's path parser is STRICT — no commas between coordinates (unlike browser SVG). If path parsing fails, wrap in <svg> and use createNodeFromSvg as reliable fallback. Tested: lightning, arrow, chevron, cross, ribbon shapes."
|
|
404
|
+
},
|
|
405
|
+
"text_range_styles": {
|
|
406
|
+
"description": "Apply mixed typography within a single text node",
|
|
407
|
+
"steps": [
|
|
408
|
+
"Mixed sizes: text.setRangeFontSize(startIndex, endIndex, 48);",
|
|
409
|
+
"Mixed weights: text.setRangeFontName(0, 10, {family:'Inter', style:'Extra Bold'});",
|
|
410
|
+
"Mixed colors: text.setRangeFills(0, 10, [{type:'SOLID', color:{r:0.5,g:0.3,b:1}}]);",
|
|
411
|
+
"Underline: text.setRangeTextDecoration(0, 15, 'UNDERLINE');",
|
|
412
|
+
"Strikethrough: text.setRangeTextDecoration(20, 33, 'STRIKETHROUGH');",
|
|
413
|
+
"Text case: text.setRangeTextCase(0, 9, 'UPPER'); — also 'LOWER', 'TITLE', 'ORIGINAL'",
|
|
414
|
+
"Letter spacing: text.setRangeLetterSpacing(0, 5, {value:-1, unit:'PIXELS'});",
|
|
415
|
+
"All range methods take (startIndex, endIndex) — endIndex is exclusive"
|
|
416
|
+
],
|
|
417
|
+
"guards": ["Must be in editor", "Font must be loaded for each style used"],
|
|
418
|
+
"why": "Range styles allow rich text within one node — highlighted keywords, mixed weights, colored segments. All setRange* methods use character indices (0-based, end exclusive). Must load all font weights used across ranges."
|
|
419
|
+
},
|
|
420
|
+
"design_tokens": {
|
|
421
|
+
"description": "Create reusable paint and text styles (design tokens)",
|
|
422
|
+
"steps": [
|
|
423
|
+
"Paint style: const style = figma.createPaintStyle(); style.name = 'Brand/Primary'; style.paints = [{type:'SOLID', color:{r:0.4,g:0.3,b:1}}];",
|
|
424
|
+
"Apply: node.fillStyleId = style.id;",
|
|
425
|
+
"Text style: const ts = figma.createTextStyle(); ts.name = 'Typography/Heading'; ts.fontSize = 32; ts.fontName = {family:'Inter', style:'Bold'};",
|
|
426
|
+
"Apply: textNode.textStyleId = ts.id;",
|
|
427
|
+
"Naming convention: use 'Category/Name' format — e.g. 'Brand/Primary', 'Typography/Display'",
|
|
428
|
+
"Additional text style properties: ts.letterSpacing, ts.lineHeight, ts.paragraphSpacing",
|
|
429
|
+
"Styles appear in Figma's style panel and can be reused across the file"
|
|
430
|
+
],
|
|
431
|
+
"guards": ["Must be in editor"],
|
|
432
|
+
"why": "Design tokens make designs consistent and maintainable. Paint styles define reusable colors. Text styles define reusable typography. Both applied via styleId. Tested: 5 paint styles (Brand/*) + 5 text styles (Typography/*)."
|
|
433
|
+
},
|
|
434
|
+
"constraints": {
|
|
435
|
+
"description": "Set responsive constraints for elements within frames",
|
|
436
|
+
"steps": [
|
|
437
|
+
"Set: node.constraints = {horizontal:'CENTER', vertical:'MIN'};",
|
|
438
|
+
"Horizontal: 'MIN' (left), 'CENTER', 'MAX' (right), 'STRETCH' (full width), 'SCALE' (proportional)",
|
|
439
|
+
"Vertical: 'MIN' (top), 'CENTER', 'MAX' (bottom), 'STRETCH' (full height), 'SCALE'",
|
|
440
|
+
"STRETCH makes element fill parent width/height when parent resizes",
|
|
441
|
+
"SCALE makes element resize proportionally with parent",
|
|
442
|
+
"Only works on children of non-auto-layout frames (auto-layout uses layoutSizing instead)"
|
|
443
|
+
],
|
|
444
|
+
"guards": ["Must be in editor", "Parent must not be auto-layout"],
|
|
445
|
+
"why": "Constraints define how elements respond when parent frame resizes. Essential for responsive design. Note: auto-layout frames use layoutSizingHorizontal/Vertical instead of constraints."
|
|
446
|
+
},
|
|
447
|
+
"export": {
|
|
448
|
+
"description": "Export a node as PNG, JPG, SVG, or PDF",
|
|
449
|
+
"steps": [
|
|
450
|
+
"Get bytes: const bytes = await node.exportAsync({format:'PNG', constraint:{type:'SCALE',value:2}});",
|
|
451
|
+
"Supported formats: 'PNG', 'JPG', 'SVG', 'PDF'",
|
|
452
|
+
"Returns Uint8Array — check bytes.length for size validation",
|
|
453
|
+
"Use constraint {type:'SCALE',value:2} for 2x resolution export"
|
|
454
|
+
],
|
|
455
|
+
"guards": ["Must be in editor", "Node must exist"],
|
|
456
|
+
"why": "Export API returns raw bytes for any node in any format. Use for asset export, QA snapshots, or downstream processing."
|
|
457
|
+
},
|
|
458
|
+
"shared_plugin_data": {
|
|
459
|
+
"description": "Store and retrieve metadata on nodes via SharedPluginData",
|
|
460
|
+
"steps": [
|
|
461
|
+
"Write: node.setSharedPluginData('my_namespace', 'key', 'value');",
|
|
462
|
+
"Read: node.getSharedPluginData('my_namespace', 'key');",
|
|
463
|
+
"List keys: node.getSharedPluginDataKeys('my_namespace');",
|
|
464
|
+
"GOTCHA: namespace must be alphanumeric, _ or . only — NO hyphens"
|
|
465
|
+
],
|
|
466
|
+
"guards": ["Must be in editor", "Namespace must be valid (alphanumeric/_ only)"],
|
|
467
|
+
"why": "SharedPluginData works without plugin ID. Private setPluginData requires plugin context."
|
|
468
|
+
},
|
|
469
|
+
"layout_grids": {
|
|
470
|
+
"description": "Add column, row, or pixel grids to frames",
|
|
471
|
+
"steps": [
|
|
472
|
+
"Column grid: {pattern:'COLUMNS', alignment:'STRETCH', count:12, gutterSize:20, offset:40, visible:true, color:{r:1,g:0,b:0,a:0.1}}",
|
|
473
|
+
"Row grid: {pattern:'ROWS', alignment:'STRETCH', count:6, gutterSize:16, offset:20}",
|
|
474
|
+
"Pixel grid: {pattern:'GRID', sectionSize:8, visible:true}",
|
|
475
|
+
"Stack multiple: frame.layoutGrids = [columnsGrid, rowsGrid, pixelGrid];",
|
|
476
|
+
"GOTCHA: sectionSize is ONLY for pattern:'GRID'. COLUMNS/ROWS use count, gutterSize, offset"
|
|
477
|
+
],
|
|
478
|
+
"guards": ["Must be in editor", "Target must be a frame"],
|
|
479
|
+
"why": "Layout grids provide visual spacing guides. Multiple grids can be stacked for comprehensive grid systems."
|
|
480
|
+
},
|
|
481
|
+
"overlay_interactions": {
|
|
482
|
+
"description": "Create modal and tooltip overlays via reactions API",
|
|
483
|
+
"steps": [
|
|
484
|
+
"Create overlay content (modal/tooltip) as a separate frame or component",
|
|
485
|
+
"Set reaction: node.reactions = [{trigger:{type:'ON_CLICK'}, actions:[{type:'NODE', destinationId:overlayId, navigation:'OVERLAY', transition:{type:'DISSOLVE', easing:{type:'EASE_OUT'}, duration:0.3}, resetVideoPosition:false}]}];",
|
|
486
|
+
"Use ON_HOVER trigger for tooltips, ON_CLICK for modals",
|
|
487
|
+
"GOTCHA: use resetVideoPosition NOT preserveScrollPosition"
|
|
488
|
+
],
|
|
489
|
+
"guards": ["Must be in editor", "Overlay content must exist as separate node"],
|
|
490
|
+
"why": "OVERLAY navigation shows content on top of current screen without navigating away. Essential for modals, tooltips, drawers."
|
|
491
|
+
},
|
|
492
|
+
"component_properties": {
|
|
493
|
+
"description": "Add text and boolean properties to components",
|
|
494
|
+
"steps": [
|
|
495
|
+
"Add property: comp.addComponentProperty('title', 'TEXT', 'Default Value');",
|
|
496
|
+
"Get full key: const defs = comp.componentPropertyDefinitions; const key = Object.keys(defs).find(k => k.startsWith('title#'));",
|
|
497
|
+
"Link to text node: textNode.componentPropertyReferences = {characters: key};",
|
|
498
|
+
"Set on instance: instance.setProperties({[key]: 'Custom Value'});",
|
|
499
|
+
"GOTCHA: property keys include #id suffix — must read from componentPropertyDefinitions"
|
|
500
|
+
],
|
|
501
|
+
"guards": ["Must be in editor", "Node must be a component"],
|
|
502
|
+
"why": "Component properties make components configurable. TEXT props drive text content, BOOLEAN props show/hide elements."
|
|
503
|
+
},
|
|
504
|
+
"variables": {
|
|
505
|
+
"description": "Create variable collections and bind variables to nodes",
|
|
506
|
+
"steps": [
|
|
507
|
+
"Create collection: const col = figma.variables.createVariableCollection('Tokens');",
|
|
508
|
+
"Rename mode: col.renameMode(col.modes[0].modeId, 'Light');",
|
|
509
|
+
"Create variable: const v = figma.variables.createVariable('primary', col, 'COLOR');",
|
|
510
|
+
"Set value: v.setValueForMode(col.modes[0].modeId, {r:0.4,g:0.2,b:1,a:1});",
|
|
511
|
+
"Bind to node: node.setBoundVariable('fills', 0, v);",
|
|
512
|
+
"Types: COLOR, FLOAT, STRING, BOOLEAN",
|
|
513
|
+
"LIMITATION: addMode fails on free plan ('Limited to 1 modes only')"
|
|
514
|
+
],
|
|
515
|
+
"guards": ["Must be in editor"],
|
|
516
|
+
"why": "Variables are the foundation for design tokens. Bind colors, spacing, and text to variables for systematic design."
|
|
517
|
+
},
|
|
518
|
+
"select_tool": {
|
|
519
|
+
"description": "Activate a toolbar tool (Move, Frame, Rectangle, Pen, Text, Comment)",
|
|
520
|
+
"steps": [
|
|
521
|
+
"Click tool button: [data-testid='{ToolName}-tool'] via browser_human_click",
|
|
522
|
+
"Verify: aria-pressed='true' on the clicked tool button"
|
|
523
|
+
],
|
|
524
|
+
"guards": ["Must be in editor"],
|
|
525
|
+
"why": "browser_human_click reliably activates toolbar tools. The tool stays active until used or another tool is selected."
|
|
526
|
+
},
|
|
527
|
+
"create_shape": {
|
|
528
|
+
"description": "Create a rectangle, frame, or other shape on canvas",
|
|
529
|
+
"steps": [
|
|
530
|
+
"Activate tool: browser_human_click on [data-testid='Rectangle-tool'] (or Frame-tool, etc)",
|
|
531
|
+
"Verify tool is active: aria-pressed='true'",
|
|
532
|
+
"Click on canvas: browser_human_click on 'canvas' selector",
|
|
533
|
+
"This creates a default 100x100 element at the click position",
|
|
534
|
+
"Tool auto-deactivates (switches back to Move)",
|
|
535
|
+
"New layer appears in layers panel: [data-testid$='-layers-panel-row']"
|
|
536
|
+
],
|
|
537
|
+
"guards": ["Must be in editor"],
|
|
538
|
+
"why": "CDP Input.dispatchMouseEvent (via browser_human_click) works on Figma's WebGL canvas. DOM-level mouse events (dispatchEvent MouseEvent) do NOT work. Single click creates default-sized shape; drag would create custom size but requires native drag or CDP mouse event sequences."
|
|
539
|
+
},
|
|
540
|
+
"select_layer": {
|
|
541
|
+
"description": "Select a layer from the layers panel",
|
|
542
|
+
"steps": [
|
|
543
|
+
"Find layer: [data-testid$='-layers-panel-row'] with matching text content",
|
|
544
|
+
"Use browser_human_click on the layer row",
|
|
545
|
+
"Verify: properties panel shows X-position, Y-position inputs with values",
|
|
546
|
+
"Selected layer's properties become editable in the right panel"
|
|
547
|
+
],
|
|
548
|
+
"guards": ["Must be in editor"]
|
|
549
|
+
},
|
|
550
|
+
"modify_properties": {
|
|
551
|
+
"description": "Change element position, size, color, or other properties",
|
|
552
|
+
"steps": [
|
|
553
|
+
"Select the target layer first (see select_layer flow)",
|
|
554
|
+
"Find the input: e.g. input[aria-label='X-position']",
|
|
555
|
+
"PREFERRED METHOD: Use JS select() + execCommand('insertText') — this reliably replaces the value:",
|
|
556
|
+
" input.focus(); input.select(); document.execCommand('insertText', false, 'newValue');",
|
|
557
|
+
" input.dispatchEvent(new KeyboardEvent('keydown', {key:'Enter', code:'Enter', keyCode:13, bubbles:true}));",
|
|
558
|
+
"ALTERNATIVE: browser_fill_form with clear:true — but WARNING: clear:true sometimes doesn't clear, causing value appending (e.g. '100' + '390' = '100390')",
|
|
559
|
+
"For Color input: same select()+execCommand pattern. Color values are 6-char hex without #",
|
|
560
|
+
"For Font size: same pattern. Use Tab instead of Enter to commit",
|
|
561
|
+
"Verify: re-read input value matches new value"
|
|
562
|
+
],
|
|
563
|
+
"guards": ["Must have a selection"],
|
|
564
|
+
"why": "browser_fill_form clear:true is unreliable on some Figma inputs — it appends instead of replacing. The JS select()+execCommand('insertText') pattern always works because it explicitly selects all text first. Tab key works more reliably than Enter for committing some property values."
|
|
565
|
+
},
|
|
566
|
+
"open_share_dialog": {
|
|
567
|
+
"description": "Open the Share dialog",
|
|
568
|
+
"steps": [
|
|
569
|
+
"Click Share button: browser_human_click on [data-testid='multiplayer-toolbar-share-button']",
|
|
570
|
+
"Dialog appears with role='dialog' containing 'Share' text",
|
|
571
|
+
"Available actions: invite via email, copy link, copy prototype link, publish to community"
|
|
572
|
+
],
|
|
573
|
+
"guards": ["Must be in editor"],
|
|
574
|
+
"why": "Clicking Share button again closes the dialog (toggle behavior). Canvas click does NOT close it."
|
|
575
|
+
},
|
|
576
|
+
"close_share_dialog": {
|
|
577
|
+
"description": "Close the Share dialog",
|
|
578
|
+
"steps": [
|
|
579
|
+
"Click Share button again: browser_human_click on [data-testid='multiplayer-toolbar-share-button']",
|
|
580
|
+
"Dialog closes"
|
|
581
|
+
],
|
|
582
|
+
"why": "Share button toggles the dialog. Escape key via JS doesn't work. Canvas click doesn't close it either."
|
|
583
|
+
},
|
|
584
|
+
"open_main_menu": {
|
|
585
|
+
"description": "Open the hamburger/main menu",
|
|
586
|
+
"steps": [
|
|
587
|
+
"Click: browser_human_click on button[aria-label='Main menu']",
|
|
588
|
+
"Menu items appear with role='menuitem'",
|
|
589
|
+
"Items: Back to files, Actions, File, Edit, View, Object, Text, Arrange, Vector, Plugins, Widgets, Preferences, Libraries, Help and account"
|
|
590
|
+
],
|
|
591
|
+
"guards": ["Must be in editor"],
|
|
592
|
+
"why": "Menu auto-closes very quickly — you must read items immediately in the same tool call sequence. The menu is a toggle (click again to close)."
|
|
593
|
+
},
|
|
594
|
+
"switch_design_prototype_tab": {
|
|
595
|
+
"description": "Switch between Design and Prototype tabs in properties panel",
|
|
596
|
+
"steps": [
|
|
597
|
+
"Find tab header: [data-testid='property-panel-tab-header']",
|
|
598
|
+
"Click Design or Prototype button within it",
|
|
599
|
+
"Verify: aria-selected='true' on the clicked tab"
|
|
600
|
+
]
|
|
601
|
+
},
|
|
602
|
+
"navigate_back_to_files": {
|
|
603
|
+
"description": "Go back to file browser from editor",
|
|
604
|
+
"steps": [
|
|
605
|
+
"Click folder link: [data-testid='folder-name-link']",
|
|
606
|
+
"Wait for URL to change back to /files/"
|
|
607
|
+
]
|
|
608
|
+
},
|
|
609
|
+
"present_prototype": {
|
|
610
|
+
"description": "Start prototype presentation",
|
|
611
|
+
"steps": [
|
|
612
|
+
"Click Present button: browser_human_click on [data-testid='present-as-prototype']",
|
|
613
|
+
"Prototype opens in new tab or overlay"
|
|
614
|
+
]
|
|
615
|
+
},
|
|
616
|
+
"plugin_install_and_run": {
|
|
617
|
+
"description": "Find, save (install), and run Figma plugins from within the editor using the Actions panel",
|
|
618
|
+
"steps": [
|
|
619
|
+
"STEP 1 — Open Actions panel: click button[aria-label='Actions'] in the BOTTOM TOOLBAR (the '/' icon). Two may exist in DOM — pick the visible one (getBoundingClientRect().width > 0). Panel class: .quick_actions--qaV2Window--EtP7F",
|
|
620
|
+
"STEP 2 — Switch to Plugins tab: click the 'Plugins & widgets' tab inside the quick actions panel",
|
|
621
|
+
"STEP 3 — Search for plugin: type plugin name into .search--searchInput--k55la using browser_fill_form. Results update live from Figma Community",
|
|
622
|
+
"STEP 4 — If results show 'Search plugins & widgets for X': click that row to search Figma Community from within the editor",
|
|
623
|
+
"STEP 5 — Click plugin result row: opens detail view with plugin description, user count, and two buttons: 'Save ⌘⏎' and 'Run ⏎'",
|
|
624
|
+
"STEP 6 — Save plugin: click 'Save' to permanently add to your account. It returns to results list (save was successful). Plugin now appears in right-click > Plugins > Saved plugins",
|
|
625
|
+
"STEP 7 — Run plugin: click plugin result again → click 'Run ⏎' button. Quick actions panel closes and plugin iframe appears (src='/plugin-sandbox', typically 348×670px)",
|
|
626
|
+
"RE-RUN: Cmd+Option+P (Mac) / Ctrl+Alt+P (Windows) re-runs last plugin",
|
|
627
|
+
"CONTEXT MENU: right-click canvas → Plugins → saved plugins list → click plugin name to run",
|
|
628
|
+
"COMMUNITY PAGE: 'Open in' button → select file from 'Recent files' dropdown → opens file in new tab. UNRELIABLE for auto-launching plugin — use Actions method instead"
|
|
629
|
+
],
|
|
630
|
+
"guards": ["Must be in editor with canvas loaded", "Must be logged into Figma account"],
|
|
631
|
+
"why": "Plugin management in Figma UI3 (2025+) moved from old 'Resources' header to 'Actions' bottom toolbar button. The Actions > Plugins & widgets > Search > Save/Run flow is the most reliable. Community page 'Open in' opens the file but may not auto-run the plugin. Plugin UIs run inside sandboxed /plugin-sandbox iframes — parent page JS CANNOT access iframe content. Use screenshot or OCR to read plugin UI."
|
|
632
|
+
},
|
|
633
|
+
"plugin_ui_interaction": {
|
|
634
|
+
"description": "Interact with a running Figma plugin's UI (which runs in a sandboxed iframe)",
|
|
635
|
+
"steps": [
|
|
636
|
+
"Plugin iframe: src='https://www.figma.com/plugin-sandbox', visible when running (width > 50, height > 50)",
|
|
637
|
+
"CANNOT read iframe content via parent page JS (cross-origin sandbox restriction)",
|
|
638
|
+
"To see plugin UI: use screenshot or OCR tool",
|
|
639
|
+
"To click inside plugin: use browser_human_click with coordinates from screenshot/OCR (clicks pass through to iframe via CDP)",
|
|
640
|
+
"To type in plugin inputs: click the input first via coordinates, then use type_text or browser_fill_form",
|
|
641
|
+
"Plugin communicates with Figma via figma.ui.postMessage / figma.ui.onmessage — these are internal and not accessible",
|
|
642
|
+
"To close plugin: press Escape (via key tool) or click X in plugin window header",
|
|
643
|
+
"Plugin toast: while running, Figma shows a toast notification at bottom of editor"
|
|
644
|
+
],
|
|
645
|
+
"guards": ["Plugin must be running (visible iframe)"],
|
|
646
|
+
"why": "Plugin UIs are fully sandboxed — no DOM access from parent. All interaction must go through visual methods (screenshot + coordinates). CDP mouse events DO pass through to the iframe correctly."
|
|
647
|
+
},
|
|
648
|
+
"parallax_effect": {
|
|
649
|
+
"description": "Create parallax-like motion with Smart Animate between matched layers at different offsets",
|
|
650
|
+
"steps": [
|
|
651
|
+
"Create Frame A and Frame B (same dimensions)",
|
|
652
|
+
"Add 3 layers with SAME names in both frames: 'bg-layer' (slow), 'mid-layer' (medium), 'fg-text' (fast)",
|
|
653
|
+
"In Frame B, offset each layer differently: bg moves 20px, mid moves 100px, fg moves 200px",
|
|
654
|
+
"Connect: frameA.reactions = [{trigger:{type:'ON_CLICK'}, actions:[{type:'NODE', destinationId:frameB.id, navigation:'NAVIGATE', transition:{type:'SMART_ANIMATE', easing:{type:'EASE_IN_AND_OUT'}, duration:0.8}}]}]",
|
|
655
|
+
"Smart Animate interpolates each matched layer independently — creates parallax depth effect"
|
|
656
|
+
],
|
|
657
|
+
"guards": ["Must be in editor"],
|
|
658
|
+
"why": "Parallax is a visual illusion where background moves slower than foreground. Smart Animate auto-detects matching layer names and interpolates position/opacity/size/color independently. Duration 0.6-1.0s works best for parallax feel."
|
|
659
|
+
},
|
|
660
|
+
"scroll_overflow": {
|
|
661
|
+
"description": "Create scrollable frames with horizontal, vertical, or 2D panning",
|
|
662
|
+
"steps": [
|
|
663
|
+
"Vertical scroll: frame.clipsContent = true; frame.overflowDirection = 'VERTICAL'; add content taller than frame",
|
|
664
|
+
"Horizontal scroll: frame.overflowDirection = 'HORIZONTAL'; add wide auto-layout row with cards inside",
|
|
665
|
+
"2D pan (map-like): frame.overflowDirection = 'BOTH'; add large content frame (800x800) inside smaller viewport (390x300)",
|
|
666
|
+
"Sticky nav: set parent to layoutMode='VERTICAL', then child.layoutPositioning = 'ABSOLUTE' + constraints={horizontal:'STRETCH', vertical:'MIN'}",
|
|
667
|
+
"overflowDirection options: 'NONE' | 'HORIZONTAL' | 'VERTICAL' | 'BOTH'"
|
|
668
|
+
],
|
|
669
|
+
"guards": ["Must be in editor"],
|
|
670
|
+
"why": "Scroll behavior is set per-frame. clipsContent=true clips overflow. overflowDirection controls which axes scroll in prototype mode. For sticky elements, parent MUST have layoutMode set before child can use layoutPositioning='ABSOLUTE'."
|
|
671
|
+
},
|
|
672
|
+
"animation_triggers_reference": {
|
|
673
|
+
"description": "Complete reference of all prototype trigger types tested via Plugin API",
|
|
674
|
+
"steps": [
|
|
675
|
+
"WORKING TRIGGERS (11): ON_CLICK, ON_HOVER, ON_PRESS, ON_DRAG, ON_MEDIA_END, AFTER_TIMEOUT({timeout:ms}), MOUSE_ENTER({delay:0}), MOUSE_LEAVE({delay:0}), MOUSE_UP({delay:0}), MOUSE_DOWN({delay:0}), ON_KEY_DOWN({device:'KEYBOARD',keyCodes:[32]}), ON_MEDIA_HIT({mediaHitTime:seconds})",
|
|
676
|
+
"WORKING TRANSITIONS (2): DISSOLVE, SMART_ANIMATE",
|
|
677
|
+
"WORKING NAVIGATIONS (5): NAVIGATE, SWAP, OVERLAY, CHANGE_TO (variants only), SCROLL_TO (null transition only)",
|
|
678
|
+
"WORKING EASINGS (12): EASE_IN, EASE_OUT, EASE_IN_AND_OUT, LINEAR, EASE_IN_BACK, EASE_OUT_BACK, EASE_IN_AND_OUT_BACK, GENTLE, QUICK, BOUNCY, SLOW, CUSTOM_SPRING",
|
|
679
|
+
"WORKING ACTIONS (3): NODE (navigate/overlay/swap/change), BACK, URL({url:'https://...'})",
|
|
680
|
+
"PAID PLAN ONLY: SET_VARIABLE, multiple actions per trigger, conditionals",
|
|
681
|
+
"NOT WORKING transitions: SCROLL_ANIMATE, MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT",
|
|
682
|
+
"NOT WORKING easings: CUSTOM_BEZIER (all formats), SPRING, GENTLE_SPRING",
|
|
683
|
+
"ON_KEY_DOWN format: {type:'ON_KEY_DOWN', device:'KEYBOARD', keyCodes:[32]} — keyCode 32=space. NOT 'KEY_DOWN'"
|
|
684
|
+
],
|
|
685
|
+
"guards": ["Must be in editor"],
|
|
686
|
+
"why": "Comprehensive tested reference. Previous sessions only tested ON_CLICK/ON_HOVER/ON_PRESS/AFTER_TIMEOUT. This session tested ALL trigger types. MOUSE_ENTER/LEAVE now confirmed WORKING (previous session said broken — they need delay:0 property). ON_KEY_DOWN needs device+keyCodes (was trying wrong type name before)."
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
|
|
690
|
+
"tool_preferences": {
|
|
691
|
+
"BEST_APPROACH": "For creating designs with multiple elements, use Figma Plugin API via browser_js (see programmatic_design flow). This bypasses all canvas interaction issues.",
|
|
692
|
+
"toolbar_clicks": ["browser_human_click"],
|
|
693
|
+
"canvas_interactions": ["browser_human_click (creates shapes)", "browser_human_click cannot drag — use native drag or CDP mouse sequences for custom sizes", "browser_human_click always clicks at element center — cannot target specific canvas positions"],
|
|
694
|
+
"layer_selection": ["browser_human_click on layer rows"],
|
|
695
|
+
"property_editing": ["JS select()+execCommand('insertText')+Enter/Tab (PREFERRED)", "browser_fill_form (clear:true) + Enter keydown (UNRELIABLE — may append instead of replace)"],
|
|
696
|
+
"menu_navigation": ["browser_human_click to open, browser_js to read items quickly"],
|
|
697
|
+
"file_creation": ["JS .click() on new-design-file-button (browser_human_click does NOT work)"],
|
|
698
|
+
"file_opening": ["JS dblclick event sequence on thumbnail container"],
|
|
699
|
+
"file_renaming": ["browser_human_click on [data-testid='filename'], then browser_fill_form on input[aria-label='Figma Design']"],
|
|
700
|
+
"programmatic_design": ["browser_js with Figma Plugin API: figma.createFrame(), figma.createText(), figma.createRectangle(), figma.createEllipse()"],
|
|
701
|
+
"auto_layout": ["frame.layoutMode='VERTICAL'|'HORIZONTAL', itemSpacing, padding*, primaryAxisAlignItems, counterAxisAlignItems, layoutWrap='WRAP'. GOTCHA: set layoutSizingHorizontal='FILL' AFTER appending child to auto-layout parent"],
|
|
702
|
+
"image_fills": ["const img = await figma.createImageAsync(url); node.fills = [{type:'IMAGE', imageHash:img.hash, scaleMode:'FILL'}]; — works on rect, ellipse, frame"],
|
|
703
|
+
"svg_icons": ["figma.createNodeFromSvg('<svg xmlns=\"http://www.w3.org/2000/svg\" ...>...</svg>') — returns FrameNode, recolor via recursive child traversal"],
|
|
704
|
+
"gradient_effects": ["GRADIENT_RADIAL for glows, GRADIENT_ANGULAR for conic sweeps, GRADIENT_DIAMOND for jewel effects, multi-layer stacked fills for nebula backgrounds"],
|
|
705
|
+
"glassmorphism": ["BACKGROUND_BLUR (real backdrop-filter) + semi-transparent fill + white stroke + inner shadow = glass card. Three variants: light (white 10%), dark (black 30%), tinted (brand 15%)"],
|
|
706
|
+
"components": ["figma.createComponent() + combineAsVariants([...]) for variant sets. ON_HOVER + CHANGE_TO + SMART_ANIMATE for interactive states. createInstance() for reuse"],
|
|
707
|
+
"blend_modes": ["node.blendMode = 'SCREEN'|'MULTIPLY'|'OVERLAY'|... — 19 modes. SCREEN for glows, MULTIPLY for shadows, OVERLAY for contrast"],
|
|
708
|
+
"boolean_ops": ["figma.union/subtract/intersect/exclude([shapes], parent) — creates custom shapes. Badge cutout = subtract circle from rect"],
|
|
709
|
+
"masks": ["shape.isMask = true; then siblings get masked. Star/hex/squircle + image = shaped photos"],
|
|
710
|
+
"vector_paths": ["figma.createVector().vectorPaths = [{windingRule:'NONZERO', data:'M x y L x y Z'}] — NO COMMAS between coords. Fallback: createNodeFromSvg"],
|
|
711
|
+
"text_range_styles": ["setRangeFontSize/FontName/Fills/TextDecoration/TextCase/LetterSpacing(start, end, value) — mixed typography in one node"],
|
|
712
|
+
"design_tokens": ["figma.createPaintStyle() + node.fillStyleId, figma.createTextStyle() + node.textStyleId — reusable styles with Category/Name naming"],
|
|
713
|
+
"constraints": ["node.constraints = {horizontal:'STRETCH', vertical:'MIN'} — MIN/CENTER/MAX/STRETCH/SCALE. Only for non-auto-layout parents"],
|
|
714
|
+
"export": ["node.exportAsync({format:'PNG'|'JPG'|'SVG'|'PDF', constraint:{type:'SCALE',value:2}}) returns Uint8Array. All 4 formats work"],
|
|
715
|
+
"shared_plugin_data": ["node.setSharedPluginData(namespace, key, value) — namespace must be alphanumeric/_ only. getSharedPluginData + getSharedPluginDataKeys. Private setPluginData requires plugin ID"],
|
|
716
|
+
"layout_grids": ["frame.layoutGrids = [{pattern:'COLUMNS', count:12, gutterSize:20, offset:40}]. ROWS for row grid. GRID + sectionSize for pixel grid. Multiple grids stackable. GOTCHA: sectionSize only for GRID"],
|
|
717
|
+
"overlay_interactions": ["navigation:'OVERLAY' with transition:{type:'DISSOLVE'} for modals/tooltips. ON_CLICK for modal, ON_HOVER for tooltip. resetVideoPosition (NOT preserveScrollPosition)"],
|
|
718
|
+
"trigger_types": ["API-working (12): ON_CLICK, ON_HOVER, ON_PRESS, ON_DRAG, ON_MEDIA_END, AFTER_TIMEOUT, MOUSE_ENTER(delay:0), MOUSE_LEAVE(delay:0), MOUSE_UP(delay:0), MOUSE_DOWN(delay:0), ON_KEY_DOWN(device+keyCodes), ON_MEDIA_HIT(mediaHitTime). Previous session wrongly reported MOUSE_ENTER/LEAVE/UP/DOWN as broken — they need {delay:0}"],
|
|
719
|
+
"toggle_interactive": ["Two variants State=Off/On. ON_CLICK + CHANGE_TO both directions + SMART_ANIMATE 0.2s. Off: gray bg + knob left. On: blue bg + knob right (primaryAxisAlignItems MAX)"],
|
|
720
|
+
"tab_switch": ["N variants Tab=Tab1/Tab2/Tab3. Tab bar buttons: non-active → ON_CLICK + CHANGE_TO target variant. SMART_ANIMATE moves active indicator. Each variant has unique content area"],
|
|
721
|
+
"dropdown_menu": ["Button comp ON_CLICK → OVERLAY navigation to menu frame. Menu: vertical auto-layout with item rows. DISSOLVE 0.15s. Menu frame lives on page level (not inside button)"],
|
|
722
|
+
"carousel": ["N slide variants Slide=Slide1/2/3. Prev/Next buttons with CHANGE_TO adjacent variant. Dot indicators (opacity 1 vs 0.3). SMART_ANIMATE 0.3s for smooth slide transitions"],
|
|
723
|
+
"fixed_header": ["Scrollable frame: clipsContent=true + overflowDirection='VERTICAL'. Header child: constraints={horizontal:'STRETCH',vertical:'MIN'}. Content child: tall frame below header"],
|
|
724
|
+
"sections_and_pages": ["createSection for canvas organization (has fills, contains frames). createPage for multi-page files. createSlice for export regions with exportSettings array"],
|
|
725
|
+
"component_properties": ["comp.addComponentProperty(name, 'TEXT'|'BOOLEAN', default). Link via componentPropertyReferences with full key#id from componentPropertyDefinitions. instance.setProperties({[fullKey]:value})"],
|
|
726
|
+
"variables": ["figma.variables.createVariableCollection + createVariable(name, collection, 'COLOR'|'FLOAT'|'STRING'|'BOOLEAN'). setBoundVariable('fills', 0, var). renameMode works. addMode: paid plan only"],
|
|
727
|
+
"effects_and_shadows": ["element.effects = [{type:'DROP_SHADOW'|'INNER_SHADOW'|'LAYER_BLUR'|'BACKGROUND_BLUR', ...}] — stack unlimited effects"],
|
|
728
|
+
"ambient_glow_orbs": ["Ell with low opacity (0.03-0.06) + LAYER_BLUR radius 120-200 — creates cinematic depth"],
|
|
729
|
+
"prototype_setup": ["frame.reactions for interactions, page.flowStartingPoints for starting point, SMART_ANIMATE for transitions"],
|
|
730
|
+
"scrollable_website": ["Single tall frame (1440x7200) with clipsContent=true — scrolls in prototype mode like real website"],
|
|
731
|
+
"design_reference": ["Extract video frames with ffmpeg (fps=2), view as images to analyze design patterns before building"],
|
|
732
|
+
"data_extraction": ["browser_js — query data-testid elements, aria-labels, input values, layer panel rows"],
|
|
733
|
+
"canvas_text_typing": ["Activate Text tool → browser_human_click canvas → focus Chrome → type_text (native OS typing). BUT: browser_human_click always clicks canvas center, so repeated text elements land on same spot. Use Plugin API instead."],
|
|
734
|
+
"plugin_install": ["Actions button (bottom toolbar, aria-label='Actions') → 'Plugins & widgets' tab → search → Save. Then right-click canvas → Plugins → Saved plugins → click to run. Or: click plugin result → 'Run ⏎' button. Re-run last: Cmd+Option+P"],
|
|
735
|
+
"plugin_interaction": ["Plugin UI is sandboxed iframe (src=/plugin-sandbox). Cannot read via JS. Use screenshot/OCR to see content, browser_human_click with coordinates to click, type_text to type. Close with Escape key"],
|
|
736
|
+
"parallax": ["Two frames with matched layer names at different offsets + SMART_ANIMATE 0.8s. Bg moves slow, fg moves fast. Click or AFTER_TIMEOUT to trigger"],
|
|
737
|
+
"scroll_overflow": ["frame.clipsContent=true + overflowDirection='VERTICAL'|'HORIZONTAL'|'BOTH'. Sticky: parent.layoutMode='VERTICAL' + child.layoutPositioning='ABSOLUTE' + constraints"],
|
|
738
|
+
"all_triggers": ["12 trigger types, all set via API: ON_CLICK, ON_HOVER, ON_PRESS, ON_DRAG (inconsistent in prototype), ON_MEDIA_END, AFTER_TIMEOUT({timeout:ms}), MOUSE_ENTER({delay:0}), MOUSE_LEAVE({delay:0}), MOUSE_UP({delay:0}), MOUSE_DOWN({delay:0}), ON_KEY_DOWN({device:'KEYBOARD',keyCodes:[32]}), ON_MEDIA_HIT({mediaHitTime:seconds}). Mouse triggers need {delay:0}. ON_KEY_DOWN needs device+keyCodes. 32=space, 13=enter, 27=escape"],
|
|
739
|
+
"all_easings": ["12 working: EASE_IN, EASE_OUT, EASE_IN_AND_OUT, LINEAR, EASE_IN_BACK, EASE_OUT_BACK, EASE_IN_AND_OUT_BACK, GENTLE, QUICK, BOUNCY, SLOW, CUSTOM_SPRING. Failed: CUSTOM_BEZIER, SPRING, GENTLE_SPRING"],
|
|
740
|
+
"all_transitions": ["2 working: DISSOLVE, SMART_ANIMATE. Failed: SCROLL_ANIMATE, MOVE_IN, MOVE_OUT, PUSH, SLIDE_IN, SLIDE_OUT"],
|
|
741
|
+
"all_navigations": ["5 working: NAVIGATE, SWAP, OVERLAY (any frame), CHANGE_TO (component variants only), SCROLL_TO (null transition only). SCROLL_TO needs: {navigation:'SCROLL_TO', transition:null}"],
|
|
742
|
+
"all_actions": ["NODE (with navigation), BACK (dismiss overlay), URL (open link). Paid only: SET_VARIABLE, CONDITIONAL, multiple actions per trigger"],
|
|
743
|
+
"gif_import": ["const gif = await figma.createImageAsync('https://media.giphy.com/...gif'); frame.fills = [{type:'IMAGE', imageHash:gif.hash, scaleMode:'FIT'}] — loads animated GIF. Plays in prototype mode"],
|
|
744
|
+
"auto_advance": ["AFTER_TIMEOUT trigger: {type:'AFTER_TIMEOUT', timeout:2000} — auto-navigates after N ms. Good for loading screens, onboarding, slideshows"],
|
|
745
|
+
"keyboard_trigger": ["ON_KEY_DOWN: {type:'ON_KEY_DOWN', device:'KEYBOARD', keyCodes:[32]} — keyCode 32=space, 13=enter, 27=escape. MUST include device property"],
|
|
746
|
+
"video_triggers": ["ON_MEDIA_END: fires when video in frame finishes. ON_MEDIA_HIT: {type:'ON_MEDIA_HIT', mediaHitTime:5} fires at timestamp (seconds). Good for chapter markers, timed overlays"],
|
|
747
|
+
"saved_plugins": ["Anima (code export), Vectary 3D Studio Lite (3D models), LottieFiles (Lottie animations), Jitter (motion design). All saved via Actions > Plugins & widgets > Search > Save flow"],
|
|
748
|
+
"NOT_WORKING": [
|
|
749
|
+
"JS dispatchEvent MouseEvent on canvas (WebGL ignores DOM events)",
|
|
750
|
+
"JS KeyboardEvent for keyboard shortcuts (Figma intercepts at lower level)",
|
|
751
|
+
"JS Escape to close dialogs (doesn't reach Figma's event handler)",
|
|
752
|
+
"native drag (bridge timeout on macOS)",
|
|
753
|
+
"screenshot (cg.captureScreen timeout)",
|
|
754
|
+
"browser_human_click on new-design-file-button (click registers but file not created)",
|
|
755
|
+
"browser_fill_form clear:true on some property inputs (appends instead of replacing)",
|
|
756
|
+
"session_start + navigate for Figma (use browser_* tools directly, no session needed)",
|
|
757
|
+
"Figma MCP token can expire mid-session — get_screenshot/get_design_context fail with 're-authorization' error",
|
|
758
|
+
"vectorPaths with commas between coordinates — Figma path parser rejects 'M 50,0' format, use 'M 50 0' spaces only",
|
|
759
|
+
"setPluginData without plugin ID — requires plugin context, use setSharedPluginData instead",
|
|
760
|
+
"SharedPluginData namespace with hyphens — 'my-ns' fails, use 'my_ns' (alphanumeric + _ or . only)",
|
|
761
|
+
"layoutGrids sectionSize on COLUMNS/ROWS pattern — sectionSize only valid for GRID pattern",
|
|
762
|
+
"ON_DRAG trigger — sets on frame (reactions count=1) but behavior is inconsistent in prototype mode",
|
|
763
|
+
"MOVE_IN/MOVE_OUT/SLIDE_IN/SLIDE_OUT/PUSH/SCROLL_ANIMATE transition types — not valid in current reactions API, only DISSOLVE and SMART_ANIMATE work",
|
|
764
|
+
"CUSTOM_BEZIER easing — all formats tried (cubicBezier, easingFunction array) fail validation. Use named easings instead (12 available)",
|
|
765
|
+
"SPRING and GENTLE_SPRING easings — fail validation, use BOUNCY or CUSTOM_SPRING instead",
|
|
766
|
+
"SCROLL_TO with transition — fails, must use transition:null (instant scroll only)",
|
|
767
|
+
"CHANGE_TO on regular frames — only works on component variants within a variant set",
|
|
768
|
+
"SET_VARIABLE action — 'You cannot use Set Variable actions with your current plan' (requires paid plan)",
|
|
769
|
+
"Multiple actions per trigger — 'You cannot create multiple actions on Reactions with your current plan' (requires paid plan)",
|
|
770
|
+
"CONDITIONAL action — requires paid plan + format unclear via API",
|
|
771
|
+
"preserveScrollPosition in reactions — not valid property, use resetVideoPosition instead",
|
|
772
|
+
"variables.addMode on free plan — 'Limited to 1 modes only' error, requires paid Figma plan",
|
|
773
|
+
"createTextPath — returns undefined, 'Expected node, got undefined' error. Not functional via browser_js Plugin API",
|
|
774
|
+
"Community page 'Open in' button for plugins — opens file in new tab but does NOT auto-run the plugin. Use Actions panel method instead",
|
|
775
|
+
"Plugin iframe content — sandboxed iframe (src=/plugin-sandbox) blocks all parent page JS access. Cannot read or query plugin UI elements via browser_js/browser_dom. Use screenshot/OCR instead",
|
|
776
|
+
"Plugins menu via right-click when no plugins saved — only shows 'Manage plugins…' with no plugins listed. Must Save a plugin first via Actions > Plugins & widgets",
|
|
777
|
+
"Main menu > Plugins submenu expansion — JS mouseover/mouseenter events do NOT trigger submenu. Requires real mouse hover via native input. CDP click on 'Plugins' closes the entire menu. Use Actions panel (Cmd+/) or Cmd+Option+P to re-run last plugin instead",
|
|
778
|
+
"Actions panel search results sometimes render empty — input shows correct value but panel content is blank. May be a React rendering timing issue. Workaround: close panel (Escape), reopen (Cmd+/), click input, then type via native keyboard"
|
|
779
|
+
]
|
|
780
|
+
},
|
|
781
|
+
|
|
782
|
+
"errors": [
|
|
783
|
+
{
|
|
784
|
+
"error": "DOM mouse events don't work on Figma canvas",
|
|
785
|
+
"context": "Attempting to draw, click, or drag on the WebGL canvas using JS dispatchEvent(new MouseEvent(...))",
|
|
786
|
+
"solution": "Use CDP-level mouse events via browser_human_click. Figma's canvas is WebGL-based and only responds to real browser input events, not DOM-synthesized ones.",
|
|
787
|
+
"severity": "high"
|
|
788
|
+
},
|
|
789
|
+
{
|
|
790
|
+
"error": "JS keyboard shortcuts don't trigger Figma actions",
|
|
791
|
+
"context": "document.dispatchEvent(new KeyboardEvent('keydown', {key: 'r'})) does not activate Rectangle tool",
|
|
792
|
+
"solution": "Use browser_human_click on toolbar buttons instead. For shortcuts that must go through keyboard, use CDP Input.dispatchKeyEvent or native OS key events.",
|
|
793
|
+
"severity": "high"
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
"error": "File double-click doesn't work with browser_human_click",
|
|
797
|
+
"context": "browser_human_click dispatches a single click on thumbnail, which selects the file but doesn't open it",
|
|
798
|
+
"solution": "Use JS to dispatch full dblclick event sequence: mousedown(detail:1) + mouseup + click(detail:1) + mousedown(detail:2) + mouseup + click(detail:2) + dblclick(detail:2) on the thumbnail container or its parent card element.",
|
|
799
|
+
"severity": "high"
|
|
800
|
+
},
|
|
801
|
+
{
|
|
802
|
+
"error": "Main menu auto-closes before items can be read",
|
|
803
|
+
"context": "Opening main menu via browser_human_click, then reading items in a separate browser_js call — menu is already closed",
|
|
804
|
+
"solution": "Read menu items immediately in the same interaction. Or use direct button clicks for known actions (e.g. Share, Present) instead of navigating menus.",
|
|
805
|
+
"severity": "medium"
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
"error": "Share dialog doesn't close with Escape or canvas click",
|
|
809
|
+
"context": "JS Escape keydown event doesn't reach Figma's dialog handler. Clicking canvas also doesn't dismiss the share dialog.",
|
|
810
|
+
"solution": "Click the Share button again to toggle the dialog closed.",
|
|
811
|
+
"severity": "medium"
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
"error": "Property input shows 'Mixed' after value change",
|
|
815
|
+
"context": "After browser_fill_form + Enter on X-position input, value briefly shows 'Mixed'",
|
|
816
|
+
"solution": "This is a transient state when multiple elements are affected or during React re-render. Re-click the layer and re-read the input — the value should be correctly updated.",
|
|
817
|
+
"severity": "low"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
"error": "Screenshot timeout on macOS",
|
|
821
|
+
"context": "cg.captureScreen timed out after 15000ms when Figma editor is open",
|
|
822
|
+
"solution": "Use browser_js to extract text content and element states instead of screenshots. For visual verification, use browser_dom or read data-testid attributes.",
|
|
823
|
+
"severity": "medium"
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
"error": "Native drag timeout",
|
|
827
|
+
"context": "mcp__sh__drag (cg.mouseDrag) times out when trying to draw shapes by dragging on canvas",
|
|
828
|
+
"solution": "Use single-click approach: activate shape tool + browser_human_click on canvas creates default 100x100 element. Resize via properties panel inputs afterward.",
|
|
829
|
+
"severity": "medium"
|
|
830
|
+
},
|
|
831
|
+
{
|
|
832
|
+
"error": "browser_human_click on new-design-file-button does not create file",
|
|
833
|
+
"context": "Clicking [data-testid='new-design-file-button'] via browser_human_click — click registers at coordinates but no new file is created, page stays on file browser",
|
|
834
|
+
"solution": "Use JS .click() instead: document.querySelector('[data-testid=\"new-design-file-button\"]').click(). This triggers Figma's React click handler correctly. Then browser_wait for URL to include '/design/'.",
|
|
835
|
+
"severity": "high"
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
"error": "browser_fill_form appends instead of replacing on property inputs",
|
|
839
|
+
"context": "browser_fill_form with clear:true on Color input or Font size input appends new value to existing (e.g. 'FFFFFF' + '0F172A' = 'FFFFFF0F172A')",
|
|
840
|
+
"solution": "Use JS: input.focus(); input.select(); document.execCommand('insertText', false, 'newValue'); then dispatch Enter/Tab keydown. The select() ensures all existing text is replaced.",
|
|
841
|
+
"severity": "high"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
"error": "browser_human_click on canvas always hits center — cannot place elements at specific positions",
|
|
845
|
+
"context": "Text tool + browser_human_click on 'canvas' always clicks at canvas element center. Creating multiple text elements places them all at the same spot, and clicking on an existing element enters edit mode instead of creating new.",
|
|
846
|
+
"solution": "Use Figma Plugin API via browser_js for precise element placement: figma.createText() then set t.x and t.y to exact coordinates. Or use mcp__sh__click with specific screen coordinates (requires focus on Chrome first), but this has VS Code focus-stealing issues.",
|
|
847
|
+
"severity": "high"
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
"error": "session_start + navigate fails with 'Session not found' for Figma",
|
|
851
|
+
"context": "Using session_start to create an AX session, then navigate(sessionId, url) — returns 'Session not found' because MCP server restarts between tool calls",
|
|
852
|
+
"solution": "Do NOT use session_start for browser automation. Use browser_tabs to get tab IDs, then browser_navigate(url, tabId) directly. Browser tools connect via CDP and don't need sessions.",
|
|
853
|
+
"severity": "critical"
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
"error": "Text typed into existing text element instead of creating new one",
|
|
857
|
+
"context": "Activating Text tool and clicking canvas center lands on an existing text element, entering edit mode. Native type_text then appends to existing text instead of creating a new text layer.",
|
|
858
|
+
"solution": "Use Figma Plugin API: figma.createText() to create text at exact positions. Or ensure you click on empty canvas area (not on any existing element) — but browser_human_click doesn't support coordinate targeting.",
|
|
859
|
+
"severity": "medium"
|
|
860
|
+
},
|
|
861
|
+
{
|
|
862
|
+
"error": "Figma MCP token expires mid-session",
|
|
863
|
+
"context": "Calling mcp__figma__get_screenshot or get_design_context returns 'MCP server figma requires re-authorization (token expired)'",
|
|
864
|
+
"solution": "Run /mcp in Claude Code to re-authenticate. The Figma MCP server uses OAuth tokens that expire. After re-auth, tools work again. As fallback, use browser_js with Figma Plugin API for design work (doesn't need MCP auth).",
|
|
865
|
+
"severity": "medium"
|
|
866
|
+
},
|
|
867
|
+
{
|
|
868
|
+
"error": "Width/height inputs missing aria-labels",
|
|
869
|
+
"context": "After creating a Frame, the properties panel shows W/H inputs but they lack aria-label='transform-width' / 'transform-height'. They appear as unlabeled inputs with value '100'.",
|
|
870
|
+
"solution": "Find W/H inputs by scanning all inputs in properties panel: look for inputs with value '100' after the X-position, Y-position, Rotation inputs. Or use Plugin API: frame.resize(width, height).",
|
|
871
|
+
"severity": "medium"
|
|
872
|
+
}
|
|
873
|
+
],
|
|
874
|
+
|
|
875
|
+
"policyNotes": {
|
|
876
|
+
"canvas_architecture": "Figma uses WebGL (asm.js/Wasm) for rendering. The canvas element is a single <canvas> tag — there are no DOM elements for design objects. All interaction must go through CDP-level input events or toolbar/panel DOM elements.",
|
|
877
|
+
"auto_layout": "Auto Layout options appear as radio buttons (NONE, VERTICAL, HORIZONTAL, GRID) in properties panel when a frame is selected.",
|
|
878
|
+
"layer_ids": "Layer panel rows use data-testid format '{nodeId}-layers-panel-row' where nodeId matches Figma's internal node ID (e.g. '605:11').",
|
|
879
|
+
"tool_behavior": "Shape tools (Rectangle, Frame) auto-deactivate after creating one element, returning to Move tool. Text tool stays active until explicitly switched.",
|
|
880
|
+
"mode_switching": "Three modes available via toolbelt-mode-segmented-control: Draw (illustration), Design, Dev Mode (handoff).",
|
|
881
|
+
"plugin_api": "The Figma Plugin API (figma.*) is accessible directly in browser_js. No plugin installation needed. FULL TESTED CAPABILITIES: Node creation (createFrame, createText, createRectangle, createEllipse, createLine, createPolygon, createStar, createVector, createComponent, createNodeFromSvg, createImageAsync). Auto Layout (layoutMode, itemSpacing, padding, sizing, alignment, layoutWrap). Fills (SOLID, GRADIENT_LINEAR, GRADIENT_RADIAL, GRADIENT_ANGULAR, GRADIENT_DIAMOND, IMAGE, multi-layer stacking). Effects (DROP_SHADOW, INNER_SHADOW, LAYER_BLUR, BACKGROUND_BLUR — stackable). Blend modes (19 types). Components (createComponent, combineAsVariants, createInstance, reactions with ON_HOVER/CHANGE_TO). Boolean ops (union, subtract, intersect, exclude). Masks (isMask=true). Vector paths (vectorPaths with SVG data). Text range styles (setRangeFontSize/FontName/Fills/TextDecoration/TextCase/LetterSpacing). Design tokens (createPaintStyle, createTextStyle, fillStyleId, textStyleId). Constraints (MIN/CENTER/MAX/STRETCH/SCALE). Prototype (reactions, flowStartingPoints, SMART_ANIMATE transitions). Viewport control. Must loadFontAsync before setting text characters.",
|
|
882
|
+
"filename_input_quirk": "When clicking [data-testid='filename'] to rename, the input that appears always has aria-label='Figma Design' regardless of the current file name.",
|
|
883
|
+
"safety": [
|
|
884
|
+
"Add 1-2s delays between actions to allow Figma's WebSocket sync",
|
|
885
|
+
"Verify tool activation via aria-pressed before canvas interactions",
|
|
886
|
+
"Always select layers via layers panel rather than canvas clicks for reliability",
|
|
887
|
+
"Use JS select()+execCommand('insertText') for property inputs (browser_fill_form clear:true is unreliable)",
|
|
888
|
+
"For complex designs, prefer Figma Plugin API via browser_js over manual canvas interactions",
|
|
889
|
+
"Never use session_start for browser-based Figma — use browser_* tools directly"
|
|
890
|
+
]
|
|
891
|
+
},
|
|
892
|
+
|
|
893
|
+
"_meta": {
|
|
894
|
+
"created": "2026-03-09",
|
|
895
|
+
"updated": "2026-03-09T14:00:00",
|
|
896
|
+
"tested_on": "Figma (browser, Chrome), macOS arm64, Official Figma MCP server",
|
|
897
|
+
"tested_actions": [
|
|
898
|
+
"file_browser_navigation",
|
|
899
|
+
"open_file_via_dblclick",
|
|
900
|
+
"create_new_file_via_js_click",
|
|
901
|
+
"rename_file_via_filename_input",
|
|
902
|
+
"toolbar_tool_activation",
|
|
903
|
+
"create_rectangle_on_canvas",
|
|
904
|
+
"create_frame_on_canvas",
|
|
905
|
+
"text_tool_activation",
|
|
906
|
+
"canvas_text_typing_via_native_key",
|
|
907
|
+
"layer_selection_via_panel",
|
|
908
|
+
"property_value_modification",
|
|
909
|
+
"property_color_change",
|
|
910
|
+
"property_font_size_change",
|
|
911
|
+
"share_dialog_open_close",
|
|
912
|
+
"main_menu_open",
|
|
913
|
+
"design_prototype_tab_switch",
|
|
914
|
+
"data_testid_extraction",
|
|
915
|
+
"plugin_api_createFrame",
|
|
916
|
+
"plugin_api_createText_with_font_loading",
|
|
917
|
+
"plugin_api_createRectangle",
|
|
918
|
+
"plugin_api_createEllipse",
|
|
919
|
+
"plugin_api_appendChild",
|
|
920
|
+
"plugin_api_fills_strokes_cornerRadius",
|
|
921
|
+
"plugin_api_viewport_scrollAndZoomIntoView",
|
|
922
|
+
"plugin_api_full_multi_screen_app_design",
|
|
923
|
+
"plugin_api_effects_drop_shadow_glow",
|
|
924
|
+
"plugin_api_effects_layer_blur_ambient_orbs",
|
|
925
|
+
"plugin_api_glassmorphic_card_pattern",
|
|
926
|
+
"plugin_api_prototype_flow_starting_points",
|
|
927
|
+
"plugin_api_prototype_reactions_smart_animate",
|
|
928
|
+
"plugin_api_single_scrollable_frame_website",
|
|
929
|
+
"plugin_api_node_remove_cleanup",
|
|
930
|
+
"plugin_api_viewport_center_and_zoom",
|
|
931
|
+
"plugin_api_extra_bold_font_weight",
|
|
932
|
+
"plugin_api_clipsContent_for_scrollable_frames",
|
|
933
|
+
"plugin_api_full_7200px_scrollable_website_8_sections",
|
|
934
|
+
"ffmpeg_video_frame_extraction_for_design_reference",
|
|
935
|
+
"figma_mcp_get_screenshot_for_node_capture",
|
|
936
|
+
"plugin_api_auto_layout_horizontal",
|
|
937
|
+
"plugin_api_auto_layout_vertical_nested",
|
|
938
|
+
"plugin_api_auto_layout_wrap",
|
|
939
|
+
"plugin_api_auto_layout_FILL_sizing",
|
|
940
|
+
"plugin_api_auto_layout_SPACE_BETWEEN",
|
|
941
|
+
"plugin_api_auto_layout_pricing_card",
|
|
942
|
+
"plugin_api_createImageAsync_from_url",
|
|
943
|
+
"plugin_api_image_fill_FILL_FIT_CROP_modes",
|
|
944
|
+
"plugin_api_image_fill_on_ellipse_avatars",
|
|
945
|
+
"plugin_api_dashed_strokes_strokeDashes",
|
|
946
|
+
"plugin_api_createNodeFromSvg_lucide_icons",
|
|
947
|
+
"plugin_api_svg_recolor_recursive",
|
|
948
|
+
"plugin_api_complex_svg_shapes",
|
|
949
|
+
"plugin_api_GRADIENT_RADIAL_glow_orb",
|
|
950
|
+
"plugin_api_GRADIENT_ANGULAR_conic_rainbow",
|
|
951
|
+
"plugin_api_GRADIENT_DIAMOND_jewel",
|
|
952
|
+
"plugin_api_multi_layer_stacked_fills",
|
|
953
|
+
"plugin_api_BACKGROUND_BLUR_glassmorphism",
|
|
954
|
+
"plugin_api_glass_card_light_dark_tinted",
|
|
955
|
+
"plugin_api_createComponent",
|
|
956
|
+
"plugin_api_combineAsVariants",
|
|
957
|
+
"plugin_api_ON_HOVER_CHANGE_TO_interaction",
|
|
958
|
+
"plugin_api_createInstance",
|
|
959
|
+
"plugin_api_blendMode_8_modes",
|
|
960
|
+
"plugin_api_boolean_union",
|
|
961
|
+
"plugin_api_boolean_subtract",
|
|
962
|
+
"plugin_api_boolean_intersect",
|
|
963
|
+
"plugin_api_boolean_exclude",
|
|
964
|
+
"plugin_api_badge_cutout_pattern",
|
|
965
|
+
"plugin_api_mask_star_polygon_squircle",
|
|
966
|
+
"plugin_api_mask_with_real_images",
|
|
967
|
+
"plugin_api_createVector_paths",
|
|
968
|
+
"plugin_api_setRangeFontSize_mixed",
|
|
969
|
+
"plugin_api_setRangeFills_colors",
|
|
970
|
+
"plugin_api_setRangeTextDecoration",
|
|
971
|
+
"plugin_api_setRangeTextCase",
|
|
972
|
+
"plugin_api_setRangeLetterSpacing",
|
|
973
|
+
"plugin_api_createPaintStyle",
|
|
974
|
+
"plugin_api_createTextStyle",
|
|
975
|
+
"plugin_api_fillStyleId_textStyleId",
|
|
976
|
+
"plugin_api_constraints_all_5_modes",
|
|
977
|
+
"plugin_api_polygon_pointCount",
|
|
978
|
+
"plugin_api_star_shape",
|
|
979
|
+
"plugin_api_exportAsync_PNG",
|
|
980
|
+
"plugin_api_exportAsync_JPG",
|
|
981
|
+
"plugin_api_exportAsync_SVG",
|
|
982
|
+
"plugin_api_exportAsync_PDF",
|
|
983
|
+
"plugin_api_setSharedPluginData",
|
|
984
|
+
"plugin_api_getSharedPluginData",
|
|
985
|
+
"plugin_api_getSharedPluginDataKeys",
|
|
986
|
+
"plugin_api_layoutGrids_COLUMNS",
|
|
987
|
+
"plugin_api_layoutGrids_ROWS",
|
|
988
|
+
"plugin_api_layoutGrids_GRID_pixel",
|
|
989
|
+
"plugin_api_layoutGrids_multi_stacked",
|
|
990
|
+
"plugin_api_overlay_navigation_modal",
|
|
991
|
+
"plugin_api_overlay_navigation_tooltip",
|
|
992
|
+
"plugin_api_trigger_ON_PRESS",
|
|
993
|
+
"plugin_api_trigger_AFTER_TIMEOUT",
|
|
994
|
+
"plugin_api_addComponentProperty_TEXT",
|
|
995
|
+
"plugin_api_addComponentProperty_BOOLEAN",
|
|
996
|
+
"plugin_api_componentPropertyReferences",
|
|
997
|
+
"plugin_api_setProperties_on_instance",
|
|
998
|
+
"plugin_api_createVariableCollection",
|
|
999
|
+
"plugin_api_createVariable_COLOR",
|
|
1000
|
+
"plugin_api_createVariable_FLOAT",
|
|
1001
|
+
"plugin_api_createVariable_STRING",
|
|
1002
|
+
"plugin_api_createVariable_BOOLEAN",
|
|
1003
|
+
"plugin_api_setBoundVariable",
|
|
1004
|
+
"plugin_api_renameMode",
|
|
1005
|
+
"plugin_api_createSection",
|
|
1006
|
+
"plugin_api_createSlice_exportSettings",
|
|
1007
|
+
"plugin_api_createPage_multi_page",
|
|
1008
|
+
"plugin_api_createImage_from_bytes",
|
|
1009
|
+
"plugin_api_fixed_header_constraints",
|
|
1010
|
+
"plugin_api_toggle_interactive_component",
|
|
1011
|
+
"plugin_api_tab_switch_3_variants",
|
|
1012
|
+
"plugin_api_dropdown_overlay_menu",
|
|
1013
|
+
"plugin_api_carousel_3_slides",
|
|
1014
|
+
"plugin_api_instance_swap_property",
|
|
1015
|
+
"plugin_install_via_actions_panel",
|
|
1016
|
+
"plugin_save_from_community_search",
|
|
1017
|
+
"plugin_run_from_actions_panel",
|
|
1018
|
+
"plugin_context_menu_access",
|
|
1019
|
+
"plugin_ui_iframe_sandbox_detection",
|
|
1020
|
+
"plugin_anima_installed_and_launched",
|
|
1021
|
+
"animation_parallax_smart_animate",
|
|
1022
|
+
"animation_on_drag_trigger",
|
|
1023
|
+
"animation_on_key_down_trigger",
|
|
1024
|
+
"animation_gif_import_giphy",
|
|
1025
|
+
"animation_horizontal_scroll",
|
|
1026
|
+
"animation_2d_pan_both",
|
|
1027
|
+
"animation_after_timeout_auto_advance",
|
|
1028
|
+
"animation_mouse_enter_trigger",
|
|
1029
|
+
"animation_mouse_leave_trigger",
|
|
1030
|
+
"animation_mouse_up_trigger",
|
|
1031
|
+
"animation_mouse_down_trigger",
|
|
1032
|
+
"animation_on_media_end_trigger",
|
|
1033
|
+
"animation_on_media_hit_trigger",
|
|
1034
|
+
"animation_sticky_element",
|
|
1035
|
+
"animation_change_to_variants",
|
|
1036
|
+
"animation_scroll_to_anchor",
|
|
1037
|
+
"animation_swap_navigation",
|
|
1038
|
+
"animation_back_action",
|
|
1039
|
+
"animation_url_action",
|
|
1040
|
+
"animation_easing_11_types",
|
|
1041
|
+
"animation_custom_spring_easing",
|
|
1042
|
+
"animation_smart_animate_bouncy",
|
|
1043
|
+
"plugin_vectary_saved_and_launched",
|
|
1044
|
+
"plugin_lottiefiles_saved",
|
|
1045
|
+
"plugin_jitter_saved",
|
|
1046
|
+
"plugin_lottiefiles_launch_attempted",
|
|
1047
|
+
"plugin_vectary_closed_via_display_none",
|
|
1048
|
+
"animation_createVideoAsync_blocked_paid_plan"
|
|
1049
|
+
],
|
|
1050
|
+
"battle_tested": true,
|
|
1051
|
+
"key_discoveries": [
|
|
1052
|
+
"WebGL canvas only responds to CDP Input.dispatchMouseEvent, not DOM MouseEvent — browser_human_click works, JS dispatchEvent doesn't",
|
|
1053
|
+
"Single CDP click with active shape tool creates default 100x100 element — no drag needed for basic creation",
|
|
1054
|
+
"JS keyboard events don't trigger Figma shortcuts — must use CDP Input.dispatchKeyEvent or toolbar button clicks",
|
|
1055
|
+
"File browser uses custom SPA click handlers — need full JS dblclick event sequence to open files",
|
|
1056
|
+
"Share dialog toggle: click Share button to open AND close — Escape and canvas click don't work",
|
|
1057
|
+
"Main menu auto-closes rapidly — must read items immediately or use direct action buttons instead",
|
|
1058
|
+
"Layer rows use data-testid='{nodeId}-layers-panel-row' format for targeting specific layers",
|
|
1059
|
+
"Tool activation confirmed via aria-pressed='true', tool auto-deactivates to Move after shape creation",
|
|
1060
|
+
"Properties panel inputs respond to browser_fill_form + Enter keydown for value commits",
|
|
1061
|
+
"Figma has 3 editor modes: Draw, Design, Dev Mode — switchable via segmented control in toolbelt",
|
|
1062
|
+
"CRITICAL: browser_human_click on new-design-file-button does NOT create files — use JS .click() instead",
|
|
1063
|
+
"CRITICAL: browser_fill_form clear:true APPENDS on some inputs (Color, Font size) — use JS select()+execCommand('insertText') instead",
|
|
1064
|
+
"CRITICAL: browser_human_click on canvas always clicks center — cannot place elements at different positions. Use Plugin API for multi-element designs",
|
|
1065
|
+
"CRITICAL: Do NOT use session_start + navigate for Figma. Use browser_tabs + browser_navigate directly (no session needed)",
|
|
1066
|
+
"Figma Plugin API (figma.*) is available in browser_js without plugin installation — createFrame, createText, createRectangle, createEllipse all work",
|
|
1067
|
+
"Must call figma.loadFontAsync({family:'Inter', style:'Regular'}) before setting text.characters — supports Regular, Medium, Semi Bold, Bold",
|
|
1068
|
+
"Plugin API approach built a complete 3-screen mobile app design (Dashboard, Tools, Sessions) with 94 elements in ~4 browser_js calls",
|
|
1069
|
+
"Filename input always has aria-label='Figma Design' regardless of current file name",
|
|
1070
|
+
"Width/height property inputs may lack aria-labels — find them by scanning inputs with value '100' in properties panel",
|
|
1071
|
+
"figma.viewport.scrollAndZoomIntoView(figma.currentPage.children) zooms to show all frames",
|
|
1072
|
+
"Text tool + canvas click + native type_text works BUT VS Code steals focus between tool calls, making it unreliable",
|
|
1073
|
+
"Inter 'Extra Bold' font style is available — call figma.loadFontAsync({family:'Inter', style:'Extra Bold'}) for weight 800",
|
|
1074
|
+
"Effects API: DROP_SHADOW with offset {x:0,y:0} + large radius (40-70px) + colored alpha = glow effect (Sintra AI style)",
|
|
1075
|
+
"Effects API: LAYER_BLUR on low-opacity Ellipse nodes = ambient background glow orbs — creates depth and atmosphere",
|
|
1076
|
+
"Glassmorphic card pattern: Frame fills opacity 0.03-0.05, white stroke opacity 0.06-0.1, colored DROP_SHADOW glow — Sintra AI DNA",
|
|
1077
|
+
"Prototype API: frame.reactions = [{trigger:{type:'ON_CLICK'}, actions:[{type:'NODE', destinationId, navigation:'NAVIGATE', transition:{type:'SMART_ANIMATE',...}}]}]",
|
|
1078
|
+
"Prototype API: page.flowStartingPoints = [{nodeId, name}] sets the starting frame for prototype mode",
|
|
1079
|
+
"Single tall frame (1440x7200) with clipsContent=true scrolls in prototype mode like a real website — far better UX than separate frames",
|
|
1080
|
+
"Alternating dark/white sections: insert full-width Rectangle fills at section Y offsets within the single frame",
|
|
1081
|
+
"node.remove() deletes any node — useful for cleaning up and rebuilding designs from scratch",
|
|
1082
|
+
"figma.viewport.center = {x, y} and figma.viewport.zoom = 0.65 for precise viewport control",
|
|
1083
|
+
"Design reference workflow: extract video frames via ffmpeg (fps=2), read as images to analyze design patterns before building",
|
|
1084
|
+
"Official Figma MCP (mcp__figma__*) provides get_screenshot, get_design_context, generate_figma_design — but requires auth token that can expire",
|
|
1085
|
+
"Figma MCP token expiry: 'MCP server figma requires re-authorization (token expired)' — need to re-authenticate via /mcp command",
|
|
1086
|
+
"For complex multi-section websites: split Plugin API calls per section (~1 browser_js call per section) to avoid timeout/size limits",
|
|
1087
|
+
"Sintra AI design DNA: Pure black #030308 bg, glassmorphic cards (white 3-4% opacity), colored glows (violet/blue/gold), Inter font family, character-driven hero, alternating dark/white sections",
|
|
1088
|
+
"CRITICAL: layoutSizingHorizontal='FILL' MUST be set AFTER appending child to auto-layout parent — Figma throws error if set before",
|
|
1089
|
+
"Auto Layout WRAP: frame.layoutWrap = 'WRAP' + counterAxisSpacing for tag clouds and chip grids",
|
|
1090
|
+
"createImageAsync(url) works with Unsplash URLs — add ?w=400&h=300&fit=crop for optimized loading",
|
|
1091
|
+
"Image fills work on ANY shape: Rectangle, Ellipse (circular avatars), Frame — set scaleMode to FILL/FIT/CROP",
|
|
1092
|
+
"Dashed strokes: element.strokeDashes = [4, 4] — creates dashed border effect",
|
|
1093
|
+
"createNodeFromSvg requires xmlns='http://www.w3.org/2000/svg' in the SVG string — omitting it fails silently",
|
|
1094
|
+
"SVG recoloring: traverse children recursively, updating fills and strokes on each vector child",
|
|
1095
|
+
"BACKGROUND_BLUR is REAL glassmorphism (CSS backdrop-filter:blur equivalent) — requires content BEHIND the element",
|
|
1096
|
+
"Glass card recipe: fill at 10% opacity + BACKGROUND_BLUR radius 24-40 + white stroke at 20% + DROP_SHADOW + INNER_SHADOW top edge",
|
|
1097
|
+
"Three glass variants: light (white fill 10%), dark (black fill 30%), tinted (brand color fill 15%)",
|
|
1098
|
+
"combineAsVariants places the variant set on the PAGE (top-level node), not inside the current frame",
|
|
1099
|
+
"Component variant naming: 'Property=Value' format — Figma auto-parses into variant properties",
|
|
1100
|
+
"ON_HOVER + CHANGE_TO + SMART_ANIMATE = interactive hover states in prototype mode (tested, works)",
|
|
1101
|
+
"createInstance() creates a linked copy — changes to main component propagate to all instances",
|
|
1102
|
+
"Blend modes on nodes: all 19 modes work. SCREEN for glows, MULTIPLY for tinting, OVERLAY for contrast",
|
|
1103
|
+
"Boolean operations (union/subtract/intersect/exclude): shapes must be siblings in same parent. Result is BooleanOperationNode",
|
|
1104
|
+
"Badge cutout pattern: subtract(circle, rect) creates notification badge notch — common UI pattern",
|
|
1105
|
+
"Mask pattern: shape.isMask = true, then add content as NEXT sibling in same parent — order matters",
|
|
1106
|
+
"createPolygon().pointCount = 6 for hexagon, createStar() for star shape — both work as masks",
|
|
1107
|
+
"Vector paths: Figma path format is STRICT — use spaces between coordinates, NOT commas. 'M 50 0 L 20 55' not 'M 50,0 L 20,55'",
|
|
1108
|
+
"Vector path fallback: if vectorPaths parsing fails, wrap path in <svg> string and use createNodeFromSvg instead",
|
|
1109
|
+
"Text range methods: setRangeFontSize, setRangeFontName, setRangeFills, setRangeTextDecoration, setRangeTextCase, setRangeLetterSpacing — all take (startIndex, endIndex)",
|
|
1110
|
+
"Design tokens: createPaintStyle + fillStyleId, createTextStyle + textStyleId — styles appear in Figma's style panel",
|
|
1111
|
+
"Constraints: MIN/CENTER/MAX/STRETCH/SCALE — only for children of non-auto-layout frames",
|
|
1112
|
+
"Total Plugin API capabilities tested: 15 out of 16 categories (Export skipped). All work via browser_js without plugin installation",
|
|
1113
|
+
"exportAsync({format:'PNG'|'JPG'|'SVG'|'PDF'}) returns Uint8Array — all 4 formats work, use constraint:{type:'SCALE',value:2} for 2x resolution",
|
|
1114
|
+
"setPluginData('key','value') requires plugin ID — use setSharedPluginData(namespace, key, value) instead for browser_js context",
|
|
1115
|
+
"SharedPluginData namespace MUST be alphanumeric, _ or . only — hyphens cause 'namespace can only consist of alphanumeric characters' error",
|
|
1116
|
+
"Layout grids: frame.layoutGrids = [{pattern:'COLUMNS', count:12, gutterSize:20, offset:40}] — GOTCHA: sectionSize only for GRID, not COLUMNS/ROWS",
|
|
1117
|
+
"Multiple layout grids can be stacked on one frame — combine COLUMNS + ROWS + GRID for full grid system",
|
|
1118
|
+
"Overlay interactions: navigation:'OVERLAY' with transition:{type:'DISSOLVE'} works for modals and tooltips",
|
|
1119
|
+
"Reactions API schema: actions use 'resetVideoPosition' NOT 'preserveScrollPosition'. Transition types: DISSOLVE, SMART_ANIMATE only (SCROLL_ANIMATE, MOVE_IN, SLIDE_IN, SLIDE_OUT, PUSH all fail)",
|
|
1120
|
+
"CORRECTED: Trigger types that WORK via API: ON_CLICK, ON_HOVER, ON_PRESS, ON_DRAG, ON_MEDIA_END, AFTER_TIMEOUT, MOUSE_ENTER(delay:0), MOUSE_LEAVE(delay:0), MOUSE_UP(delay:0), MOUSE_DOWN(delay:0), ON_KEY_DOWN(device+keyCodes), ON_MEDIA_HIT(mediaHitTime). Total: 12 trigger types, 11 confirmed working",
|
|
1121
|
+
"Component properties: addComponentProperty(name, 'TEXT'|'BOOLEAN', defaultValue) — INSTANCE_SWAP type also available but not tested",
|
|
1122
|
+
"CRITICAL: componentPropertyReferences keys must use full key with #id suffix from componentPropertyDefinitions — e.g. 'title#21:0' not just 'title'",
|
|
1123
|
+
"setProperties on instances: instance.setProperties({[fullKey]: value}) — works for TEXT and BOOLEAN property types",
|
|
1124
|
+
"Variables: figma.variables.createVariableCollection('Name') creates collection. createVariable('name', collection, 'COLOR'|'FLOAT'|'STRING'|'BOOLEAN')",
|
|
1125
|
+
"Variable binding: node.setBoundVariable('fills', 0, colorVariable) binds fill[0] to a COLOR variable",
|
|
1126
|
+
"Variable modes: collection.renameMode(modeId, 'Light') works. collection.addMode('Dark') FAILS on free plan ('Limited to 1 modes only')",
|
|
1127
|
+
"Total Plugin API capabilities tested: 22 categories across 20 test sections. All work via browser_js without plugin installation",
|
|
1128
|
+
"createSection: works, has fills, can contain frames. Good for organizing related frames on canvas",
|
|
1129
|
+
"createSlice: works with exportSettings [{format:'PNG', suffix:'@2x', constraint:{type:'SCALE',value:2}}, {format:'SVG'}] for multi-format export regions",
|
|
1130
|
+
"createPage: works. figma.root.children gives all pages. New page is immediately available for appendChild",
|
|
1131
|
+
"createImage(bytes): works with Uint8Array from exportAsync round-trip. Returns {hash} for image fills",
|
|
1132
|
+
"createTextPath: FAILS — returns undefined, 'Expected node, got undefined' error. Not usable via browser_js",
|
|
1133
|
+
"createVideoAsync API exists (typeof === 'function') but requires video Uint8Array data to test",
|
|
1134
|
+
"Fixed-position header pattern: header with constraints STRETCH+MIN inside clipsContent+overflowDirection frame. Header stays fixed while content scrolls",
|
|
1135
|
+
"Interactive toggle: Two component variants (State=Off, State=On) with ON_CLICK + CHANGE_TO both directions + SMART_ANIMATE. Knob slides, color changes",
|
|
1136
|
+
"Tab switch: 3 component variants with tab bar buttons. Each non-active tab button has ON_CLICK + CHANGE_TO to target variant. Active indicator moves via SMART_ANIMATE",
|
|
1137
|
+
"Dropdown menu: Button component with ON_CLICK + OVERLAY navigation to separate menu frame. DISSOLVE transition. Menu has 4 items with hover-ready rows",
|
|
1138
|
+
"Carousel: 3 slide variants with Prev/Next buttons and dot indicators. CHANGE_TO + SMART_ANIMATE. Dots update per slide. Last slide has 'Start' CTA",
|
|
1139
|
+
"INSTANCE_SWAP property: addComponentProperty('icon', 'INSTANCE_SWAP', defaultComponentId). Link via componentPropertyReferences = {mainComponent: key}. setProperties swaps icon on instances",
|
|
1140
|
+
"Total Plugin API capabilities tested: 31 tests across all categories. Only createTextPath fails. Everything else works via browser_js",
|
|
1141
|
+
"Comment workflow: activate comment tool via 'C' key (not browser_click — key works, click doesn't stick). Click canvas to place, type in contenteditable input, click Submit button. Comment appears with #number, author, timestamp",
|
|
1142
|
+
"Comment resolution: 'Mark as resolved' button appears on posted comments. Click to resolve — comment disappears from active view",
|
|
1143
|
+
"Version history: Main menu > File > Show version history. Shows autosave versions with timestamps. Named save dialog has #savepoint-modal-title input + #savepoint-modal-description textarea. Named saves may require Professional plan",
|
|
1144
|
+
"Libraries modal: Main menu > Libraries. Shows 'This file', 'Updates', 'Browse libraries', 'Teams', 'UI kits' tabs. Community libraries addable (iOS, Material 3, Simple DS found). Publishing requires Professional plan",
|
|
1145
|
+
"Multi-screen prototype: 4 screens (Login/Dashboard/Settings/Modal) with 6 interactions. NAVIGATE for screen-to-screen, OVERLAY for modal, BACK action for dismiss. flowStartingPoints for entry point",
|
|
1146
|
+
"BACK action in reactions: {type:'BACK'} — dismisses overlays or navigates back. No destinationId needed",
|
|
1147
|
+
"PLUGIN INSTALL BREAKTHROUGH: Plugins menu via right-click only shows 'Manage plugins…' when NO plugins are saved. Must FIRST save a plugin via Actions panel (bottom toolbar '/' button) > 'Plugins & widgets' tab > search > click result > 'Save' button",
|
|
1148
|
+
"Actions panel (button[aria-label='Actions']) is in the BOTTOM TOOLBAR in Figma UI3 (2025+) — replaces old 'Resources' header button. Two may exist in DOM, use visible one",
|
|
1149
|
+
"Plugin search in Actions panel: type name in .search--searchInput--k55la, click 'Search plugins & widgets for X' to search Community, click result to see detail with Save/Run buttons",
|
|
1150
|
+
"Plugin Save vs Run: 'Save ⌘⏎' permanently installs to account (appears in right-click > Plugins > Saved). 'Run ⏎' executes immediately and closes Actions panel",
|
|
1151
|
+
"Plugin UI runs in sandboxed iframe (src='https://www.figma.com/plugin-sandbox') — parent JS CANNOT access content. Container class: .jsvm_ui--pluginUI--YQm-H. Visible iframe ~348×670px",
|
|
1152
|
+
"Community page 'Open in' button: shows 'Recent files' dropdown with .plugin_try_switch_editor_dropdown class. Clicking a file opens it in new tab but does NOT auto-run the plugin",
|
|
1153
|
+
"Re-run last plugin: Cmd+Option+P (Mac) / Ctrl+Alt+P (Windows) — keyboard shortcut works when canvas has focus",
|
|
1154
|
+
"PARALLAX: Smart Animate interpolates matched layer names independently — bg moves 20px while fg moves 200px = depth illusion. Duration 0.6-1.0s. Bidirectional ON_CLICK for toggle demo",
|
|
1155
|
+
"GIF IMPORT: createImageAsync works with Giphy/animated GIF URLs. Returns imageHash, applied as IMAGE fill with scaleMode FIT/FILL. GIF animates in prototype mode",
|
|
1156
|
+
"HORIZONTAL SCROLL: overflowDirection='HORIZONTAL' with clipsContent=true. Auto-layout row of cards inside creates swipeable carousel in prototype",
|
|
1157
|
+
"2D PAN: overflowDirection='BOTH' creates map-like panning. 800x800 content in 390x300 viewport scrolls both directions",
|
|
1158
|
+
"STICKY ELEMENT: layoutPositioning='ABSOLUTE' makes child fixed during scroll. REQUIRES parent to have layoutMode set (VERTICAL/HORIZONTAL). Then set constraints for positioning",
|
|
1159
|
+
"ON_KEY_DOWN: type is 'ON_KEY_DOWN' NOT 'KEY_DOWN'. Requires device:'KEYBOARD' AND keyCodes:[number]. keyCode 32=space, 13=enter, 27=escape",
|
|
1160
|
+
"MOUSE_ENTER/LEAVE/UP/DOWN: ALL WORK via API. Previous session reported them as broken — the fix is adding delay:0 property. {type:'MOUSE_ENTER', delay:0}",
|
|
1161
|
+
"ON_MEDIA_END: fires when video in frame finishes playing. No extra properties needed. {type:'ON_MEDIA_END'}",
|
|
1162
|
+
"ON_MEDIA_HIT: fires at specific video timestamp. {type:'ON_MEDIA_HIT', mediaHitTime:5} — mediaHitTime in seconds",
|
|
1163
|
+
"12 EASINGS WORK: EASE_IN, EASE_OUT, EASE_IN_AND_OUT, LINEAR, EASE_IN_BACK, EASE_OUT_BACK, EASE_IN_AND_OUT_BACK, GENTLE, QUICK, BOUNCY, SLOW, CUSTOM_SPRING. Only DISSOLVE and SMART_ANIMATE transitions work",
|
|
1164
|
+
"SWAP navigation: replaces current frame with target in prototype flow. Different from NAVIGATE (which pushes). Useful for tab-like switching without back stack",
|
|
1165
|
+
"URL action: {type:'URL', url:'https://...'} opens external link from prototype. Works on any node with reactions",
|
|
1166
|
+
"BACK action: {type:'BACK'} — navigates back in prototype history or dismisses overlay. No destinationId needed",
|
|
1167
|
+
"SCROLL_TO: works with transition:null only (instant scroll). Destination must be a child inside a scrollable frame. Good for anchor links",
|
|
1168
|
+
"MULTIPLE ACTIONS: blocked on free plan — 'You cannot create multiple actions on Reactions with your current plan'. Paid plan enables parallel actions from one trigger",
|
|
1169
|
+
"SET_VARIABLE: blocked on free plan. Paid plan enables variable manipulation from prototype interactions",
|
|
1170
|
+
"PLUGINS SAVED: Anima (figma-to-code), Vectary 3D Studio Lite (3D models, 779k users), LottieFiles (Lottie animations, 953k users), Jitter (animation+export, motion design). All found via Actions panel search",
|
|
1171
|
+
"VECTARY CONFIRMED: Plugin launched successfully via Actions > search > click result. Plugin modal with title 'Vectary | 3D Studio Lite' visible. 850x540 iframe",
|
|
1172
|
+
"createVideoAsync: API works (parses bytes, attempts upload) but BLOCKED — requires paid Pro team + file NOT in Drafts. Error: 'Uploading videos only works for files in a paid Pro team that are not in your Drafts'",
|
|
1173
|
+
"PLUGIN LAUNCH via UI: Actions panel opens via Cmd+/ or button click. Search populates correctly. But running saved plugins hits reliability issues — Figma's menu hover for submenu expansion requires real mouse movement (not JS events), and Actions panel sometimes shows empty results despite correct search text. Vectary was only plugin successfully launched via Actions panel click. LottieFiles/Jitter saved but not fully run-tested",
|
|
1174
|
+
"MENU HOVER LIMITATION: Figma's main menu > Plugins requires real mouse hover to expand submenu. JS mouseover/mouseenter events DO NOT trigger submenu expansion. CDP click closes the menu. Native macOS menu_click doesn't work on Chrome web menus. Workaround: use Actions panel search or Cmd+Option+P to re-run last plugin"
|
|
1175
|
+
],
|
|
1176
|
+
"remaining_tests": [
|
|
1177
|
+
"Run LottieFiles plugin and test Lottie animation import/export workflow",
|
|
1178
|
+
"Run Jitter plugin and test timeline animation creation",
|
|
1179
|
+
"Test Vectary with actual 3D model insertion into frame",
|
|
1180
|
+
"Verify GIF playback in prototype mode (GIF imported, not yet prototype-tested)",
|
|
1181
|
+
"Test video fills with actual video content (blocked by paid plan requirement for createVideoAsync)",
|
|
1182
|
+
"Test ON_MEDIA_END and ON_MEDIA_HIT triggers with actual video (need video frame first)",
|
|
1183
|
+
"Test prototype flow end-to-end: click Present, verify transitions work in prototype viewer"
|
|
1184
|
+
]
|
|
1185
|
+
}
|
|
1186
|
+
}
|