@mp3wizard/figma-console-mcp 1.15.3 → 1.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +71 -37
  2. package/dist/cloudflare/core/cloud-websocket-relay.js +2 -3
  3. package/dist/cloudflare/core/config.js +1 -1
  4. package/dist/cloudflare/core/figma-api.js +6 -2
  5. package/dist/cloudflare/core/figma-desktop-connector.js +1 -2
  6. package/dist/cloudflare/core/port-discovery.js +73 -2
  7. package/dist/cloudflare/core/websocket-server.js +151 -21
  8. package/dist/cloudflare/core/write-tools.js +10 -2
  9. package/dist/cloudflare/index.js +701 -746
  10. package/dist/core/config.js +1 -1
  11. package/dist/core/config.js.map +1 -1
  12. package/dist/core/figjam-tools.d.ts +8 -0
  13. package/dist/core/figjam-tools.d.ts.map +1 -0
  14. package/dist/core/figjam-tools.js +486 -0
  15. package/dist/core/figjam-tools.js.map +1 -0
  16. package/dist/core/figma-api.d.ts.map +1 -1
  17. package/dist/core/figma-api.js +6 -2
  18. package/dist/core/figma-api.js.map +1 -1
  19. package/dist/core/figma-connector.d.ts +97 -0
  20. package/dist/core/figma-connector.d.ts.map +1 -1
  21. package/dist/core/figma-desktop-connector.d.ts +23 -0
  22. package/dist/core/figma-desktop-connector.d.ts.map +1 -1
  23. package/dist/core/figma-desktop-connector.js +25 -0
  24. package/dist/core/figma-desktop-connector.js.map +1 -1
  25. package/dist/core/figma-tools.d.ts.map +1 -1
  26. package/dist/core/figma-tools.js +90 -29
  27. package/dist/core/figma-tools.js.map +1 -1
  28. package/dist/core/port-discovery.d.ts.map +1 -1
  29. package/dist/core/port-discovery.js +2 -8
  30. package/dist/core/port-discovery.js.map +1 -1
  31. package/dist/core/slides-tools.d.ts +8 -0
  32. package/dist/core/slides-tools.d.ts.map +1 -0
  33. package/dist/core/slides-tools.js +608 -0
  34. package/dist/core/slides-tools.js.map +1 -0
  35. package/dist/core/websocket-connector.d.ts +97 -0
  36. package/dist/core/websocket-connector.d.ts.map +1 -1
  37. package/dist/core/websocket-connector.js +75 -0
  38. package/dist/core/websocket-connector.js.map +1 -1
  39. package/dist/core/websocket-server.d.ts +6 -0
  40. package/dist/core/websocket-server.d.ts.map +1 -1
  41. package/dist/core/websocket-server.js +11 -0
  42. package/dist/core/websocket-server.js.map +1 -1
  43. package/dist/local.d.ts.map +1 -1
  44. package/dist/local.js +80 -32
  45. package/dist/local.js.map +1 -1
  46. package/figma-desktop-bridge/code.js +1103 -5
  47. package/figma-desktop-bridge/icon.png +0 -0
  48. package/figma-desktop-bridge/manifest.json +1 -1
  49. package/figma-desktop-bridge/ui.html +1300 -173
  50. package/package.json +84 -85
@@ -66,8 +66,33 @@ figma.showUI(__html__, { width: 140, height: 50, visible: true, themeColors: tru
66
66
  }
67
67
  })();
68
68
 
69
- // Immediately fetch and send variables data to UI
69
+ // Detect editor type (figma | figjam | slides | dev)
70
+ var __editorType = figma.editorType || 'figma';
71
+ console.log('🌉 [Desktop Bridge] Editor type:', __editorType);
72
+
73
+ // Shared sticky color map — used by CREATE_STICKY and CREATE_STICKIES
74
+ var __stickyColors = {
75
+ 'YELLOW': { r: 1, g: 0.85, b: 0.4 },
76
+ 'BLUE': { r: 0.53, g: 0.78, b: 1 },
77
+ 'GREEN': { r: 0.55, g: 0.87, b: 0.53 },
78
+ 'PINK': { r: 1, g: 0.6, b: 0.78 },
79
+ 'ORANGE': { r: 1, g: 0.71, b: 0.42 },
80
+ 'PURPLE': { r: 0.78, g: 0.65, b: 1 },
81
+ 'RED': { r: 1, g: 0.55, b: 0.55 },
82
+ 'LIGHT_GRAY': { r: 0.9, g: 0.9, b: 0.9 },
83
+ 'GRAY': { r: 0.7, g: 0.7, b: 0.7 }
84
+ };
85
+
86
+ // Immediately fetch and send variables data to UI (skip in FigJam — no variables API)
70
87
  (async () => {
88
+ if (__editorType === 'figjam' || __editorType === 'slides') {
89
+ console.log('🌉 [Desktop Bridge] ' + __editorType + ' mode — skipping variables fetch');
90
+ figma.ui.postMessage({
91
+ type: 'VARIABLES_DATA',
92
+ data: { success: true, timestamp: Date.now(), fileKey: figma.fileKey || null, variables: [], variableCollections: [], editorType: __editorType }
93
+ });
94
+ return;
95
+ }
71
96
  try {
72
97
  console.log('🌉 [Desktop Bridge] Fetching variables...');
73
98
 
@@ -90,6 +115,7 @@ figma.showUI(__html__, { width: 140, height: 50, visible: true, themeColors: tru
90
115
  valuesByMode: v.valuesByMode,
91
116
  variableCollectionId: v.variableCollectionId,
92
117
  scopes: v.scopes,
118
+ codeSyntax: v.codeSyntax || {},
93
119
  description: v.description,
94
120
  hiddenFromPublishing: v.hiddenFromPublishing
95
121
  })),
@@ -131,6 +157,7 @@ function serializeVariable(v) {
131
157
  valuesByMode: v.valuesByMode,
132
158
  variableCollectionId: v.variableCollectionId,
133
159
  scopes: v.scopes,
160
+ codeSyntax: v.codeSyntax || {},
134
161
  description: v.description,
135
162
  hiddenFromPublishing: v.hiddenFromPublishing
136
163
  };
@@ -218,7 +245,7 @@ figma.ui.onmessage = async (msg) => {
218
245
  variables: variables.map(function(v) { return {
219
246
  id: v.id, name: v.name, key: v.key, resolvedType: v.resolvedType,
220
247
  valuesByMode: v.valuesByMode, variableCollectionId: v.variableCollectionId,
221
- scopes: v.scopes, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing
248
+ scopes: v.scopes, codeSyntax: v.codeSyntax || {}, description: v.description, hiddenFromPublishing: v.hiddenFromPublishing
222
249
  }; }),
223
250
  variableCollections: collections.map(function(c) { return {
224
251
  id: c.id, name: c.name, key: c.key, modes: c.modes,
@@ -1082,10 +1109,38 @@ figma.ui.onmessage = async (msg) => {
1082
1109
 
1083
1110
  // Try published library first (by key), then fall back to local component (by nodeId)
1084
1111
  if (msg.componentKey) {
1112
+ // Try importComponentByKeyAsync first (for COMPONENT nodes)
1085
1113
  try {
1086
- component = await figma.importComponentByKeyAsync(msg.componentKey);
1114
+ var importResult = await Promise.race([
1115
+ figma.importComponentByKeyAsync(msg.componentKey),
1116
+ new Promise(function(_, reject) {
1117
+ setTimeout(function() { reject(new Error('Import timed out after 15s — component may not be published to a team library')); }, 15000);
1118
+ })
1119
+ ]);
1120
+ component = importResult;
1087
1121
  } catch (importError) {
1088
- console.log('🌉 [Desktop Bridge] Not a published component, trying local...');
1122
+ var importErrMsg = importError && importError.message ? importError.message : String(importError);
1123
+ console.log('🌉 [Desktop Bridge] importComponentByKeyAsync failed: ' + importErrMsg);
1124
+ }
1125
+
1126
+ // If that failed, try importComponentSetByKeyAsync (for COMPONENT_SET nodes)
1127
+ if (!component) {
1128
+ try {
1129
+ var setResult = await Promise.race([
1130
+ figma.importComponentSetByKeyAsync(msg.componentKey),
1131
+ new Promise(function(_, reject) {
1132
+ setTimeout(function() { reject(new Error('ComponentSet import timed out after 15s')); }, 15000);
1133
+ })
1134
+ ]);
1135
+ // Got the component set — use its default variant (first child)
1136
+ if (setResult && setResult.type === 'COMPONENT_SET') {
1137
+ console.log('🌉 [Desktop Bridge] Imported component set "' + setResult.name + '" with ' + setResult.children.length + ' variants');
1138
+ component = setResult.defaultVariant || setResult.children[0];
1139
+ }
1140
+ } catch (setError) {
1141
+ var setErrMsg = setError && setError.message ? setError.message : String(setError);
1142
+ console.log('🌉 [Desktop Bridge] importComponentSetByKeyAsync also failed: ' + setErrMsg + ', trying local...');
1143
+ }
1089
1144
  }
1090
1145
  }
1091
1146
 
@@ -2100,7 +2155,8 @@ figma.ui.onmessage = async (msg) => {
2100
2155
  currentPage: figma.currentPage.name,
2101
2156
  currentPageId: figma.currentPage.id,
2102
2157
  selectionCount: selection ? selection.length : 0,
2103
- pluginVersion: PLUGIN_VERSION
2158
+ pluginVersion: PLUGIN_VERSION,
2159
+ editorType: __editorType
2104
2160
  }
2105
2161
  });
2106
2162
  } catch (error) {
@@ -2783,6 +2839,1048 @@ figma.ui.onmessage = async (msg) => {
2783
2839
  });
2784
2840
  }
2785
2841
  }
2842
+
2843
+ // ============================================================================
2844
+ // FIGJAM TOOLS — Only functional when editorType === 'figjam'
2845
+ // ============================================================================
2846
+
2847
+ // CREATE_STICKY - Create a sticky note
2848
+ else if (msg.type === 'CREATE_STICKY') {
2849
+ try {
2850
+ if (__editorType !== 'figjam') {
2851
+ throw new Error('CREATE_STICKY is only available in FigJam files');
2852
+ }
2853
+ console.log('🌉 [Desktop Bridge] Creating sticky note');
2854
+
2855
+ var sticky = figma.createSticky();
2856
+ await figma.loadFontAsync(sticky.text.fontName);
2857
+ sticky.text.characters = msg.text || '';
2858
+
2859
+ if (typeof msg.x === 'number') sticky.x = msg.x;
2860
+ if (typeof msg.y === 'number') sticky.y = msg.y;
2861
+
2862
+ // Set sticky color if provided
2863
+ if (msg.color) {
2864
+ var stickyColor = __stickyColors[msg.color.toUpperCase()];
2865
+ if (stickyColor) {
2866
+ sticky.fills = [{ type: 'SOLID', color: stickyColor }];
2867
+ }
2868
+ }
2869
+
2870
+ figma.ui.postMessage({
2871
+ type: 'CREATE_STICKY_RESULT',
2872
+ requestId: msg.requestId,
2873
+ success: true,
2874
+ data: { id: sticky.id, type: sticky.type, name: sticky.name, x: sticky.x, y: sticky.y }
2875
+ });
2876
+
2877
+ } catch (error) {
2878
+ console.error('🌉 [Desktop Bridge] Create sticky error:', error);
2879
+ figma.ui.postMessage({
2880
+ type: 'CREATE_STICKY_RESULT',
2881
+ requestId: msg.requestId,
2882
+ success: false,
2883
+ error: error.message || String(error)
2884
+ });
2885
+ }
2886
+ }
2887
+
2888
+ // CREATE_STICKIES - Batch create sticky notes
2889
+ else if (msg.type === 'CREATE_STICKIES') {
2890
+ try {
2891
+ if (__editorType !== 'figjam') {
2892
+ throw new Error('CREATE_STICKIES is only available in FigJam files');
2893
+ }
2894
+ console.log('🌉 [Desktop Bridge] Batch creating sticky notes:', msg.stickies.length);
2895
+
2896
+ var created = [];
2897
+ var failed = [];
2898
+
2899
+ // Load font once for all stickies (they all share the same default font)
2900
+ var stickyFontLoaded = false;
2901
+
2902
+ for (var si = 0; si < msg.stickies.length; si++) {
2903
+ try {
2904
+ var spec = msg.stickies[si];
2905
+ var sticky = figma.createSticky();
2906
+ if (!stickyFontLoaded) {
2907
+ await figma.loadFontAsync(sticky.text.fontName);
2908
+ stickyFontLoaded = true;
2909
+ }
2910
+ sticky.text.characters = spec.text || '';
2911
+
2912
+ if (typeof spec.x === 'number') sticky.x = spec.x;
2913
+ if (typeof spec.y === 'number') sticky.y = spec.y;
2914
+
2915
+ if (spec.color) {
2916
+ var sc = __stickyColors[spec.color.toUpperCase()];
2917
+ if (sc) {
2918
+ sticky.fills = [{ type: 'SOLID', color: sc }];
2919
+ }
2920
+ }
2921
+
2922
+ created.push({ id: sticky.id, type: sticky.type, name: sticky.name, x: sticky.x, y: sticky.y });
2923
+ } catch (e) {
2924
+ failed.push({ index: si, error: e.message || String(e) });
2925
+ }
2926
+ }
2927
+
2928
+ figma.ui.postMessage({
2929
+ type: 'CREATE_STICKIES_RESULT',
2930
+ requestId: msg.requestId,
2931
+ success: failed.length === 0,
2932
+ data: { created: created.length, failed: failed.length, results: created, errors: failed }
2933
+ });
2934
+
2935
+ } catch (error) {
2936
+ console.error('🌉 [Desktop Bridge] Batch create stickies error:', error);
2937
+ figma.ui.postMessage({
2938
+ type: 'CREATE_STICKIES_RESULT',
2939
+ requestId: msg.requestId,
2940
+ success: false,
2941
+ error: error.message || String(error)
2942
+ });
2943
+ }
2944
+ }
2945
+
2946
+ // CREATE_CONNECTOR - Connect two nodes with a connector
2947
+ else if (msg.type === 'CREATE_CONNECTOR') {
2948
+ try {
2949
+ if (__editorType !== 'figjam') {
2950
+ throw new Error('CREATE_CONNECTOR is only available in FigJam files');
2951
+ }
2952
+ console.log('🌉 [Desktop Bridge] Creating connector');
2953
+
2954
+ var connector = figma.createConnector();
2955
+
2956
+ // Set start and end endpoints
2957
+ var startNode = await figma.getNodeByIdAsync(msg.startNodeId);
2958
+ var endNode = await figma.getNodeByIdAsync(msg.endNodeId);
2959
+
2960
+ if (!startNode) throw new Error('Start node not found: ' + msg.startNodeId);
2961
+ if (!endNode) throw new Error('End node not found: ' + msg.endNodeId);
2962
+
2963
+ connector.connectorStart = {
2964
+ endpointNodeId: msg.startNodeId,
2965
+ magnet: 'AUTO'
2966
+ };
2967
+ connector.connectorEnd = {
2968
+ endpointNodeId: msg.endNodeId,
2969
+ magnet: 'AUTO'
2970
+ };
2971
+
2972
+ // Set label text if provided
2973
+ if (msg.label) {
2974
+ try {
2975
+ await figma.loadFontAsync(connector.text.fontName);
2976
+ } catch (e) {
2977
+ // Connector default font may not be loadable — fall back to Inter
2978
+ await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
2979
+ connector.text.fontName = { family: 'Inter', style: 'Medium' };
2980
+ }
2981
+ connector.text.characters = msg.label;
2982
+ }
2983
+
2984
+ figma.ui.postMessage({
2985
+ type: 'CREATE_CONNECTOR_RESULT',
2986
+ requestId: msg.requestId,
2987
+ success: true,
2988
+ data: { id: connector.id, type: connector.type, name: connector.name }
2989
+ });
2990
+
2991
+ } catch (error) {
2992
+ console.error('🌉 [Desktop Bridge] Create connector error:', error);
2993
+ figma.ui.postMessage({
2994
+ type: 'CREATE_CONNECTOR_RESULT',
2995
+ requestId: msg.requestId,
2996
+ success: false,
2997
+ error: error.message || String(error)
2998
+ });
2999
+ }
3000
+ }
3001
+
3002
+ // CREATE_SHAPE_WITH_TEXT - Create a labeled shape
3003
+ else if (msg.type === 'CREATE_SHAPE_WITH_TEXT') {
3004
+ try {
3005
+ if (__editorType !== 'figjam') {
3006
+ throw new Error('CREATE_SHAPE_WITH_TEXT is only available in FigJam files');
3007
+ }
3008
+ console.log('🌉 [Desktop Bridge] Creating shape with text');
3009
+
3010
+ var shape = figma.createShapeWithText();
3011
+
3012
+ // Set shape type if provided
3013
+ if (msg.shapeType) {
3014
+ shape.shapeType = msg.shapeType;
3015
+ }
3016
+
3017
+ // Set text
3018
+ if (msg.text) {
3019
+ try {
3020
+ await figma.loadFontAsync(shape.text.fontName);
3021
+ } catch (e) {
3022
+ await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
3023
+ shape.text.fontName = { family: 'Inter', style: 'Medium' };
3024
+ }
3025
+ shape.text.characters = msg.text;
3026
+ }
3027
+
3028
+ if (typeof msg.x === 'number') shape.x = msg.x;
3029
+ if (typeof msg.y === 'number') shape.y = msg.y;
3030
+
3031
+ figma.ui.postMessage({
3032
+ type: 'CREATE_SHAPE_WITH_TEXT_RESULT',
3033
+ requestId: msg.requestId,
3034
+ success: true,
3035
+ data: { id: shape.id, type: shape.type, name: shape.name, x: shape.x, y: shape.y }
3036
+ });
3037
+
3038
+ } catch (error) {
3039
+ console.error('🌉 [Desktop Bridge] Create shape with text error:', error);
3040
+ figma.ui.postMessage({
3041
+ type: 'CREATE_SHAPE_WITH_TEXT_RESULT',
3042
+ requestId: msg.requestId,
3043
+ success: false,
3044
+ error: error.message || String(error)
3045
+ });
3046
+ }
3047
+ }
3048
+
3049
+ // CREATE_TABLE - Create a table with data
3050
+ else if (msg.type === 'CREATE_TABLE') {
3051
+ try {
3052
+ if (__editorType !== 'figjam') {
3053
+ throw new Error('CREATE_TABLE is only available in FigJam files');
3054
+ }
3055
+ console.log('🌉 [Desktop Bridge] Creating table:', msg.rows, 'x', msg.columns);
3056
+
3057
+ var table = figma.createTable(msg.rows, msg.columns);
3058
+
3059
+ if (typeof msg.x === 'number') table.x = msg.x;
3060
+ if (typeof msg.y === 'number') table.y = msg.y;
3061
+
3062
+ // Populate cells if data provided
3063
+ if (msg.data && Array.isArray(msg.data)) {
3064
+ for (var row = 0; row < msg.data.length && row < msg.rows; row++) {
3065
+ for (var col = 0; col < msg.data[row].length && col < msg.columns; col++) {
3066
+ var cell = table.cellAt(row, col);
3067
+ if (cell && msg.data[row][col] != null) {
3068
+ await figma.loadFontAsync(cell.text.fontName);
3069
+ cell.text.characters = String(msg.data[row][col]);
3070
+ }
3071
+ }
3072
+ }
3073
+ }
3074
+
3075
+ figma.ui.postMessage({
3076
+ type: 'CREATE_TABLE_RESULT',
3077
+ requestId: msg.requestId,
3078
+ success: true,
3079
+ data: { id: table.id, type: table.type, name: table.name, rows: msg.rows, columns: msg.columns }
3080
+ });
3081
+
3082
+ } catch (error) {
3083
+ console.error('🌉 [Desktop Bridge] Create table error:', error);
3084
+ figma.ui.postMessage({
3085
+ type: 'CREATE_TABLE_RESULT',
3086
+ requestId: msg.requestId,
3087
+ success: false,
3088
+ error: error.message || String(error)
3089
+ });
3090
+ }
3091
+ }
3092
+
3093
+ // CREATE_CODE_BLOCK - Create a code block
3094
+ else if (msg.type === 'CREATE_CODE_BLOCK') {
3095
+ try {
3096
+ if (__editorType !== 'figjam') {
3097
+ throw new Error('CREATE_CODE_BLOCK is only available in FigJam files');
3098
+ }
3099
+ console.log('🌉 [Desktop Bridge] Creating code block');
3100
+
3101
+ var codeBlock = figma.createCodeBlock();
3102
+
3103
+ // Code blocks require Source Code Pro font, fall back to Inter if unavailable
3104
+ try {
3105
+ await figma.loadFontAsync({ family: 'Source Code Pro', style: 'Medium' });
3106
+ } catch (e) {
3107
+ await figma.loadFontAsync({ family: 'Inter', style: 'Medium' });
3108
+ }
3109
+
3110
+ if (msg.code) {
3111
+ codeBlock.code = msg.code;
3112
+ }
3113
+ if (msg.language) {
3114
+ codeBlock.codeLanguage = msg.language;
3115
+ }
3116
+
3117
+ if (typeof msg.x === 'number') codeBlock.x = msg.x;
3118
+ if (typeof msg.y === 'number') codeBlock.y = msg.y;
3119
+
3120
+ figma.ui.postMessage({
3121
+ type: 'CREATE_CODE_BLOCK_RESULT',
3122
+ requestId: msg.requestId,
3123
+ success: true,
3124
+ data: { id: codeBlock.id, type: codeBlock.type, name: codeBlock.name, x: codeBlock.x, y: codeBlock.y }
3125
+ });
3126
+
3127
+ } catch (error) {
3128
+ console.error('🌉 [Desktop Bridge] Create code block error:', error);
3129
+ figma.ui.postMessage({
3130
+ type: 'CREATE_CODE_BLOCK_RESULT',
3131
+ requestId: msg.requestId,
3132
+ success: false,
3133
+ error: error.message || String(error)
3134
+ });
3135
+ }
3136
+ }
3137
+
3138
+ // GET_BOARD_CONTENTS - Read all FigJam nodes from the current page
3139
+ else if (msg.type === 'GET_BOARD_CONTENTS') {
3140
+ try {
3141
+ if (__editorType !== 'figjam') {
3142
+ throw new Error('GET_BOARD_CONTENTS is only available in FigJam files');
3143
+ }
3144
+ console.log('🌉 [Desktop Bridge] Reading board contents');
3145
+
3146
+ var maxNodes = msg.maxNodes || 500;
3147
+ var filterTypes = msg.nodeTypes || null;
3148
+
3149
+ // FigJam node types we care about
3150
+ var figjamTypes = ['STICKY', 'SHAPE_WITH_TEXT', 'CONNECTOR', 'TABLE', 'CODE_BLOCK', 'SECTION', 'FRAME', 'TEXT'];
3151
+
3152
+ var allNodes = figma.currentPage.children;
3153
+ var results = [];
3154
+ var truncated = false;
3155
+
3156
+ for (var ni = 0; ni < allNodes.length && results.length < maxNodes; ni++) {
3157
+ var node = allNodes[ni];
3158
+
3159
+ // Skip if filtering and this type isn't in the filter list
3160
+ if (filterTypes && filterTypes.indexOf(node.type) === -1) continue;
3161
+ // Skip if not a FigJam-relevant type
3162
+ if (!filterTypes && figjamTypes.indexOf(node.type) === -1) continue;
3163
+
3164
+ var entry = {
3165
+ id: node.id,
3166
+ type: node.type,
3167
+ name: node.name,
3168
+ x: node.x,
3169
+ y: node.y,
3170
+ width: node.width,
3171
+ height: node.height
3172
+ };
3173
+
3174
+ // Extract text content based on node type
3175
+ if (node.type === 'STICKY') {
3176
+ entry.text = node.text ? node.text.characters : '';
3177
+ if (node.fills && node.fills.length > 0 && node.fills[0].color) {
3178
+ entry.color = node.fills[0].color;
3179
+ }
3180
+ } else if (node.type === 'SHAPE_WITH_TEXT') {
3181
+ entry.text = node.text ? node.text.characters : '';
3182
+ entry.shapeType = node.shapeType || 'ROUNDED_RECTANGLE';
3183
+ } else if (node.type === 'CONNECTOR') {
3184
+ entry.connectorStart = node.connectorStart || null;
3185
+ entry.connectorEnd = node.connectorEnd || null;
3186
+ entry.text = node.text ? node.text.characters : '';
3187
+ } else if (node.type === 'CODE_BLOCK') {
3188
+ entry.code = node.code || '';
3189
+ entry.codeLanguage = node.codeLanguage || '';
3190
+ } else if (node.type === 'TABLE') {
3191
+ entry.numRows = node.numRows;
3192
+ entry.numColumns = node.numColumns;
3193
+ // Read first 10 rows of cell data to avoid huge payloads
3194
+ var cellData = [];
3195
+ var maxCellRows = Math.min(node.numRows, 10);
3196
+ for (var row = 0; row < maxCellRows; row++) {
3197
+ var rowData = [];
3198
+ for (var col = 0; col < node.numColumns; col++) {
3199
+ try {
3200
+ var cell = node.cellAt(row, col);
3201
+ rowData.push(cell && cell.text ? cell.text.characters : '');
3202
+ } catch (e) {
3203
+ rowData.push('');
3204
+ }
3205
+ }
3206
+ cellData.push(rowData);
3207
+ }
3208
+ entry.cellData = cellData;
3209
+ if (node.numRows > 10) entry.cellDataTruncated = true;
3210
+ } else if (node.type === 'SECTION') {
3211
+ entry.childCount = node.children ? node.children.length : 0;
3212
+ } else if (node.type === 'TEXT') {
3213
+ entry.text = node.characters || '';
3214
+ }
3215
+
3216
+ results.push(entry);
3217
+ }
3218
+
3219
+ if (results.length >= maxNodes) truncated = true;
3220
+
3221
+ figma.ui.postMessage({
3222
+ type: 'GET_BOARD_CONTENTS_RESULT',
3223
+ requestId: msg.requestId,
3224
+ success: true,
3225
+ data: {
3226
+ nodes: results,
3227
+ totalFound: results.length,
3228
+ truncated: truncated,
3229
+ page: figma.currentPage.name
3230
+ }
3231
+ });
3232
+
3233
+ } catch (error) {
3234
+ console.error('🌉 [Desktop Bridge] Get board contents error:', error);
3235
+ figma.ui.postMessage({
3236
+ type: 'GET_BOARD_CONTENTS_RESULT',
3237
+ requestId: msg.requestId,
3238
+ success: false,
3239
+ error: error.message || String(error)
3240
+ });
3241
+ }
3242
+ }
3243
+
3244
+ // GET_CONNECTIONS - Read the connection graph from the board
3245
+ else if (msg.type === 'GET_CONNECTIONS') {
3246
+ try {
3247
+ if (__editorType !== 'figjam') {
3248
+ throw new Error('GET_CONNECTIONS is only available in FigJam files');
3249
+ }
3250
+ console.log('🌉 [Desktop Bridge] Reading connection graph');
3251
+
3252
+ var connectors = figma.currentPage.findAll(function(n) { return n.type === 'CONNECTOR'; });
3253
+ var edges = [];
3254
+ var nodeMap = {};
3255
+
3256
+ for (var ci = 0; ci < connectors.length; ci++) {
3257
+ var conn = connectors[ci];
3258
+ var startId = conn.connectorStart ? conn.connectorStart.endpointNodeId : null;
3259
+ var endId = conn.connectorEnd ? conn.connectorEnd.endpointNodeId : null;
3260
+ var label = conn.text ? conn.text.characters : '';
3261
+
3262
+ edges.push({
3263
+ connectorId: conn.id,
3264
+ startNodeId: startId,
3265
+ endNodeId: endId,
3266
+ label: label
3267
+ });
3268
+
3269
+ // Build a lookup of connected nodes with their names/types
3270
+ if (startId && !nodeMap[startId]) {
3271
+ var startNode = await figma.getNodeByIdAsync(startId);
3272
+ if (startNode) {
3273
+ nodeMap[startId] = {
3274
+ id: startId,
3275
+ type: startNode.type,
3276
+ name: startNode.name,
3277
+ text: startNode.text ? startNode.text.characters : (startNode.characters || '')
3278
+ };
3279
+ }
3280
+ }
3281
+ if (endId && !nodeMap[endId]) {
3282
+ var endNode = await figma.getNodeByIdAsync(endId);
3283
+ if (endNode) {
3284
+ nodeMap[endId] = {
3285
+ id: endId,
3286
+ type: endNode.type,
3287
+ name: endNode.name,
3288
+ text: endNode.text ? endNode.text.characters : (endNode.characters || '')
3289
+ };
3290
+ }
3291
+ }
3292
+ }
3293
+
3294
+ figma.ui.postMessage({
3295
+ type: 'GET_CONNECTIONS_RESULT',
3296
+ requestId: msg.requestId,
3297
+ success: true,
3298
+ data: {
3299
+ edges: edges,
3300
+ connectedNodes: nodeMap,
3301
+ totalConnectors: connectors.length,
3302
+ totalConnectedNodes: Object.keys(nodeMap).length
3303
+ }
3304
+ });
3305
+
3306
+ } catch (error) {
3307
+ console.error('🌉 [Desktop Bridge] Get connections error:', error);
3308
+ figma.ui.postMessage({
3309
+ type: 'GET_CONNECTIONS_RESULT',
3310
+ requestId: msg.requestId,
3311
+ success: false,
3312
+ error: error.message || String(error)
3313
+ });
3314
+ }
3315
+ }
3316
+ // ==========================================================================
3317
+ // SLIDES TOOLS — Figma Slides command handlers
3318
+ // ==========================================================================
3319
+
3320
+ // LIST_SLIDES - List all slides in the presentation
3321
+ else if (msg.type === 'LIST_SLIDES') {
3322
+ try {
3323
+ if (__editorType !== 'slides') {
3324
+ throw new Error('LIST_SLIDES is only available in Slides files');
3325
+ }
3326
+ console.log('🌉 [Desktop Bridge] Listing slides');
3327
+
3328
+ var grid = figma.getSlideGrid();
3329
+ var slides = [];
3330
+ for (var rowIdx = 0; rowIdx < grid.length; rowIdx++) {
3331
+ var row = grid[rowIdx];
3332
+ // SlideGrid rows are array-like (iterable with numeric indices), not objects with .children
3333
+ for (var colIdx = 0; colIdx < row.length; colIdx++) {
3334
+ var slide = row[colIdx];
3335
+ slides.push({
3336
+ id: slide.id,
3337
+ name: slide.name,
3338
+ row: rowIdx,
3339
+ col: colIdx,
3340
+ isSkippedSlide: slide.isSkippedSlide,
3341
+ childCount: slide.children ? slide.children.length : 0
3342
+ });
3343
+ }
3344
+ }
3345
+
3346
+ figma.ui.postMessage({
3347
+ type: 'LIST_SLIDES_RESULT',
3348
+ requestId: msg.requestId,
3349
+ success: true,
3350
+ data: { slides: slides, totalSlides: slides.length, totalRows: grid.length }
3351
+ });
3352
+
3353
+ } catch (error) {
3354
+ console.error('🌉 [Desktop Bridge] List slides error:', error);
3355
+ figma.ui.postMessage({
3356
+ type: 'LIST_SLIDES_RESULT',
3357
+ requestId: msg.requestId,
3358
+ success: false,
3359
+ error: error.message || String(error)
3360
+ });
3361
+ }
3362
+ }
3363
+
3364
+ // GET_SLIDE_CONTENT - Get node tree of a slide
3365
+ else if (msg.type === 'GET_SLIDE_CONTENT') {
3366
+ try {
3367
+ if (__editorType !== 'slides') {
3368
+ throw new Error('GET_SLIDE_CONTENT is only available in Slides files');
3369
+ }
3370
+ var slideNode = await figma.getNodeByIdAsync(msg.slideId);
3371
+ if (!slideNode || slideNode.type !== 'SLIDE') {
3372
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3373
+ }
3374
+ console.log('🌉 [Desktop Bridge] Getting slide content:', slideNode.name);
3375
+
3376
+ function serializeNode(n) {
3377
+ var result = { id: n.id, type: n.type, name: n.name, x: n.x, y: n.y, width: n.width, height: n.height };
3378
+ if (n.type === 'TEXT') {
3379
+ result.characters = n.characters;
3380
+ result.fontSize = n.fontSize;
3381
+ }
3382
+ if (n.children && n.children.length > 0) {
3383
+ result.children = [];
3384
+ for (var i = 0; i < n.children.length; i++) {
3385
+ result.children.push(serializeNode(n.children[i]));
3386
+ }
3387
+ }
3388
+ return result;
3389
+ }
3390
+
3391
+ figma.ui.postMessage({
3392
+ type: 'GET_SLIDE_CONTENT_RESULT',
3393
+ requestId: msg.requestId,
3394
+ success: true,
3395
+ data: serializeNode(slideNode)
3396
+ });
3397
+
3398
+ } catch (error) {
3399
+ console.error('🌉 [Desktop Bridge] Get slide content error:', error);
3400
+ figma.ui.postMessage({
3401
+ type: 'GET_SLIDE_CONTENT_RESULT',
3402
+ requestId: msg.requestId,
3403
+ success: false,
3404
+ error: error.message || String(error)
3405
+ });
3406
+ }
3407
+ }
3408
+
3409
+ // CREATE_SLIDE - Create a new slide
3410
+ else if (msg.type === 'CREATE_SLIDE') {
3411
+ try {
3412
+ if (__editorType !== 'slides') {
3413
+ throw new Error('CREATE_SLIDE is only available in Slides files');
3414
+ }
3415
+ console.log('🌉 [Desktop Bridge] Creating slide');
3416
+
3417
+ var newSlide;
3418
+ if (typeof msg.row === 'number' && typeof msg.col === 'number') {
3419
+ newSlide = figma.createSlide({ row: msg.row, col: msg.col });
3420
+ } else {
3421
+ newSlide = figma.createSlide();
3422
+ }
3423
+
3424
+ figma.ui.postMessage({
3425
+ type: 'CREATE_SLIDE_RESULT',
3426
+ requestId: msg.requestId,
3427
+ success: true,
3428
+ data: { id: newSlide.id, name: newSlide.name }
3429
+ });
3430
+
3431
+ } catch (error) {
3432
+ console.error('🌉 [Desktop Bridge] Create slide error:', error);
3433
+ figma.ui.postMessage({
3434
+ type: 'CREATE_SLIDE_RESULT',
3435
+ requestId: msg.requestId,
3436
+ success: false,
3437
+ error: error.message || String(error)
3438
+ });
3439
+ }
3440
+ }
3441
+
3442
+ // DELETE_SLIDE - Delete a slide
3443
+ else if (msg.type === 'DELETE_SLIDE') {
3444
+ try {
3445
+ if (__editorType !== 'slides') {
3446
+ throw new Error('DELETE_SLIDE is only available in Slides files');
3447
+ }
3448
+ var delSlide = await figma.getNodeByIdAsync(msg.slideId);
3449
+ if (!delSlide || delSlide.type !== 'SLIDE') {
3450
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3451
+ }
3452
+ console.log('🌉 [Desktop Bridge] Deleting slide:', delSlide.name);
3453
+
3454
+ var delName = delSlide.name;
3455
+ delSlide.remove();
3456
+
3457
+ figma.ui.postMessage({
3458
+ type: 'DELETE_SLIDE_RESULT',
3459
+ requestId: msg.requestId,
3460
+ success: true,
3461
+ data: { deleted: msg.slideId, name: delName }
3462
+ });
3463
+
3464
+ } catch (error) {
3465
+ console.error('🌉 [Desktop Bridge] Delete slide error:', error);
3466
+ figma.ui.postMessage({
3467
+ type: 'DELETE_SLIDE_RESULT',
3468
+ requestId: msg.requestId,
3469
+ success: false,
3470
+ error: error.message || String(error)
3471
+ });
3472
+ }
3473
+ }
3474
+
3475
+ // DUPLICATE_SLIDE - Clone a slide
3476
+ else if (msg.type === 'DUPLICATE_SLIDE') {
3477
+ try {
3478
+ if (__editorType !== 'slides') {
3479
+ throw new Error('DUPLICATE_SLIDE is only available in Slides files');
3480
+ }
3481
+ var srcSlide = await figma.getNodeByIdAsync(msg.slideId);
3482
+ if (!srcSlide || srcSlide.type !== 'SLIDE') {
3483
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3484
+ }
3485
+ console.log('🌉 [Desktop Bridge] Duplicating slide:', srcSlide.name);
3486
+
3487
+ var clone = srcSlide.clone();
3488
+
3489
+ figma.ui.postMessage({
3490
+ type: 'DUPLICATE_SLIDE_RESULT',
3491
+ requestId: msg.requestId,
3492
+ success: true,
3493
+ data: { originalId: msg.slideId, newId: clone.id, name: clone.name }
3494
+ });
3495
+
3496
+ } catch (error) {
3497
+ console.error('🌉 [Desktop Bridge] Duplicate slide error:', error);
3498
+ figma.ui.postMessage({
3499
+ type: 'DUPLICATE_SLIDE_RESULT',
3500
+ requestId: msg.requestId,
3501
+ success: false,
3502
+ error: error.message || String(error)
3503
+ });
3504
+ }
3505
+ }
3506
+
3507
+ // GET_SLIDE_GRID - Get 2D grid layout
3508
+ else if (msg.type === 'GET_SLIDE_GRID') {
3509
+ try {
3510
+ if (__editorType !== 'slides') {
3511
+ throw new Error('GET_SLIDE_GRID is only available in Slides files');
3512
+ }
3513
+ console.log('🌉 [Desktop Bridge] Getting slide grid');
3514
+
3515
+ var slideGrid = figma.getSlideGrid();
3516
+ var gridData = [];
3517
+ for (var ri = 0; ri < slideGrid.length; ri++) {
3518
+ var gridRow = slideGrid[ri];
3519
+ var rowSlides = [];
3520
+ // SlideGrid rows are array-like (iterable with numeric indices)
3521
+ for (var ci = 0; ci < gridRow.length; ci++) {
3522
+ var gs = gridRow[ci];
3523
+ rowSlides.push({ id: gs.id, name: gs.name, col: ci, isSkippedSlide: gs.isSkippedSlide });
3524
+ }
3525
+ gridData.push({ rowIndex: ri, slides: rowSlides });
3526
+ }
3527
+
3528
+ figma.ui.postMessage({
3529
+ type: 'GET_SLIDE_GRID_RESULT',
3530
+ requestId: msg.requestId,
3531
+ success: true,
3532
+ data: { grid: gridData, totalRows: gridData.length }
3533
+ });
3534
+
3535
+ } catch (error) {
3536
+ console.error('🌉 [Desktop Bridge] Get slide grid error:', error);
3537
+ figma.ui.postMessage({
3538
+ type: 'GET_SLIDE_GRID_RESULT',
3539
+ requestId: msg.requestId,
3540
+ success: false,
3541
+ error: error.message || String(error)
3542
+ });
3543
+ }
3544
+ }
3545
+
3546
+ // REORDER_SLIDES - Reorder slides via 2D grid of slide IDs
3547
+ else if (msg.type === 'REORDER_SLIDES') {
3548
+ try {
3549
+ if (__editorType !== 'slides') {
3550
+ throw new Error('REORDER_SLIDES is only available in Slides files');
3551
+ }
3552
+ console.log('🌉 [Desktop Bridge] Reordering slides');
3553
+
3554
+ var newGrid = msg.grid; // 2D array of slide IDs
3555
+
3556
+ // Build a lookup: slideId → SlideNode from the current grid
3557
+ var currentGrid = figma.getSlideGrid();
3558
+ var slideMap = {};
3559
+ for (var gri = 0; gri < currentGrid.length; gri++) {
3560
+ var grow = currentGrid[gri];
3561
+ for (var gci = 0; gci < grow.length; gci++) {
3562
+ slideMap[grow[gci].id] = grow[gci];
3563
+ }
3564
+ }
3565
+
3566
+ // Build new grid as arrays of SlideNode references
3567
+ var reorderedRows = [];
3568
+ for (var rri = 0; rri < newGrid.length; rri++) {
3569
+ var rowIds = newGrid[rri];
3570
+ var rowSlides = [];
3571
+ for (var cci = 0; cci < rowIds.length; cci++) {
3572
+ var slideRef = slideMap[rowIds[cci]];
3573
+ if (!slideRef) throw new Error('Slide not found in current grid: ' + rowIds[cci]);
3574
+ rowSlides.push(slideRef);
3575
+ }
3576
+ reorderedRows.push(rowSlides);
3577
+ }
3578
+
3579
+ figma.setSlideGrid(reorderedRows);
3580
+
3581
+ figma.ui.postMessage({
3582
+ type: 'REORDER_SLIDES_RESULT',
3583
+ requestId: msg.requestId,
3584
+ success: true,
3585
+ data: { success: true, rows: reorderedRows.length }
3586
+ });
3587
+
3588
+ } catch (error) {
3589
+ console.error('🌉 [Desktop Bridge] Reorder slides error:', error);
3590
+ figma.ui.postMessage({
3591
+ type: 'REORDER_SLIDES_RESULT',
3592
+ requestId: msg.requestId,
3593
+ success: false,
3594
+ error: error.message || String(error)
3595
+ });
3596
+ }
3597
+ }
3598
+
3599
+ // SET_SLIDE_TRANSITION - Set transition on a slide
3600
+ else if (msg.type === 'SET_SLIDE_TRANSITION') {
3601
+ try {
3602
+ if (__editorType !== 'slides') {
3603
+ throw new Error('SET_SLIDE_TRANSITION is only available in Slides files');
3604
+ }
3605
+ var transSlide = await figma.getNodeByIdAsync(msg.slideId);
3606
+ if (!transSlide || transSlide.type !== 'SLIDE') {
3607
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3608
+ }
3609
+ console.log('🌉 [Desktop Bridge] Setting slide transition:', transSlide.name);
3610
+
3611
+ var transConfig = {
3612
+ style: msg.style,
3613
+ duration: msg.duration,
3614
+ curve: msg.curve,
3615
+ timing: { type: 'ON_CLICK' }
3616
+ };
3617
+ if (msg.timing) {
3618
+ transConfig.timing = msg.timing;
3619
+ }
3620
+ transSlide.setSlideTransition(transConfig);
3621
+
3622
+ figma.ui.postMessage({
3623
+ type: 'SET_SLIDE_TRANSITION_RESULT',
3624
+ requestId: msg.requestId,
3625
+ success: true,
3626
+ data: { id: transSlide.id, transition: transSlide.getSlideTransition() }
3627
+ });
3628
+
3629
+ } catch (error) {
3630
+ console.error('🌉 [Desktop Bridge] Set slide transition error:', error);
3631
+ figma.ui.postMessage({
3632
+ type: 'SET_SLIDE_TRANSITION_RESULT',
3633
+ requestId: msg.requestId,
3634
+ success: false,
3635
+ error: error.message || String(error)
3636
+ });
3637
+ }
3638
+ }
3639
+
3640
+ // GET_SLIDE_TRANSITION - Read transition from a slide
3641
+ else if (msg.type === 'GET_SLIDE_TRANSITION') {
3642
+ try {
3643
+ if (__editorType !== 'slides') {
3644
+ throw new Error('GET_SLIDE_TRANSITION is only available in Slides files');
3645
+ }
3646
+ var readTransSlide = await figma.getNodeByIdAsync(msg.slideId);
3647
+ if (!readTransSlide || readTransSlide.type !== 'SLIDE') {
3648
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3649
+ }
3650
+ console.log('🌉 [Desktop Bridge] Getting slide transition:', readTransSlide.name);
3651
+
3652
+ figma.ui.postMessage({
3653
+ type: 'GET_SLIDE_TRANSITION_RESULT',
3654
+ requestId: msg.requestId,
3655
+ success: true,
3656
+ data: { id: readTransSlide.id, transition: readTransSlide.getSlideTransition() }
3657
+ });
3658
+
3659
+ } catch (error) {
3660
+ console.error('🌉 [Desktop Bridge] Get slide transition error:', error);
3661
+ figma.ui.postMessage({
3662
+ type: 'GET_SLIDE_TRANSITION_RESULT',
3663
+ requestId: msg.requestId,
3664
+ success: false,
3665
+ error: error.message || String(error)
3666
+ });
3667
+ }
3668
+ }
3669
+
3670
+ // SET_SLIDES_VIEW_MODE - Toggle between grid and single-slide view
3671
+ else if (msg.type === 'SET_SLIDES_VIEW_MODE') {
3672
+ try {
3673
+ if (__editorType !== 'slides') {
3674
+ throw new Error('SET_SLIDES_VIEW_MODE is only available in Slides files');
3675
+ }
3676
+ console.log('🌉 [Desktop Bridge] Setting slides view mode:', msg.mode);
3677
+
3678
+ figma.viewport.slidesView = msg.mode;
3679
+
3680
+ figma.ui.postMessage({
3681
+ type: 'SET_SLIDES_VIEW_MODE_RESULT',
3682
+ requestId: msg.requestId,
3683
+ success: true,
3684
+ data: { mode: figma.viewport.slidesView }
3685
+ });
3686
+
3687
+ } catch (error) {
3688
+ console.error('🌉 [Desktop Bridge] Set slides view mode error:', error);
3689
+ figma.ui.postMessage({
3690
+ type: 'SET_SLIDES_VIEW_MODE_RESULT',
3691
+ requestId: msg.requestId,
3692
+ success: false,
3693
+ error: error.message || String(error)
3694
+ });
3695
+ }
3696
+ }
3697
+
3698
+ // GET_FOCUSED_SLIDE - Get currently focused slide
3699
+ else if (msg.type === 'GET_FOCUSED_SLIDE') {
3700
+ try {
3701
+ if (__editorType !== 'slides') {
3702
+ throw new Error('GET_FOCUSED_SLIDE is only available in Slides files');
3703
+ }
3704
+ console.log('🌉 [Desktop Bridge] Getting focused slide');
3705
+
3706
+ var focused = figma.currentPage.focusedSlide;
3707
+ var focusData = focused ? { id: focused.id, name: focused.name } : { focused: null };
3708
+
3709
+ figma.ui.postMessage({
3710
+ type: 'GET_FOCUSED_SLIDE_RESULT',
3711
+ requestId: msg.requestId,
3712
+ success: true,
3713
+ data: focusData
3714
+ });
3715
+
3716
+ } catch (error) {
3717
+ console.error('🌉 [Desktop Bridge] Get focused slide error:', error);
3718
+ figma.ui.postMessage({
3719
+ type: 'GET_FOCUSED_SLIDE_RESULT',
3720
+ requestId: msg.requestId,
3721
+ success: false,
3722
+ error: error.message || String(error)
3723
+ });
3724
+ }
3725
+ }
3726
+
3727
+ // FOCUS_SLIDE - Navigate to a specific slide
3728
+ else if (msg.type === 'FOCUS_SLIDE') {
3729
+ try {
3730
+ if (__editorType !== 'slides') {
3731
+ throw new Error('FOCUS_SLIDE is only available in Slides files');
3732
+ }
3733
+ var focusTarget = await figma.getNodeByIdAsync(msg.slideId);
3734
+ if (!focusTarget || focusTarget.type !== 'SLIDE') {
3735
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3736
+ }
3737
+ console.log('🌉 [Desktop Bridge] Focusing slide:', focusTarget.name);
3738
+
3739
+ figma.viewport.slidesView = 'single-slide';
3740
+ figma.currentPage.focusedSlide = focusTarget;
3741
+
3742
+ figma.ui.postMessage({
3743
+ type: 'FOCUS_SLIDE_RESULT',
3744
+ requestId: msg.requestId,
3745
+ success: true,
3746
+ data: { focused: focusTarget.id, name: focusTarget.name }
3747
+ });
3748
+
3749
+ } catch (error) {
3750
+ console.error('🌉 [Desktop Bridge] Focus slide error:', error);
3751
+ figma.ui.postMessage({
3752
+ type: 'FOCUS_SLIDE_RESULT',
3753
+ requestId: msg.requestId,
3754
+ success: false,
3755
+ error: error.message || String(error)
3756
+ });
3757
+ }
3758
+ }
3759
+
3760
+ // SKIP_SLIDE - Toggle skip on a slide
3761
+ else if (msg.type === 'SKIP_SLIDE') {
3762
+ try {
3763
+ if (__editorType !== 'slides') {
3764
+ throw new Error('SKIP_SLIDE is only available in Slides files');
3765
+ }
3766
+ var skipSlide = await figma.getNodeByIdAsync(msg.slideId);
3767
+ if (!skipSlide || skipSlide.type !== 'SLIDE') {
3768
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3769
+ }
3770
+ console.log('🌉 [Desktop Bridge] Toggling slide skip:', skipSlide.name, '→', msg.skip);
3771
+
3772
+ skipSlide.isSkippedSlide = !!msg.skip;
3773
+
3774
+ figma.ui.postMessage({
3775
+ type: 'SKIP_SLIDE_RESULT',
3776
+ requestId: msg.requestId,
3777
+ success: true,
3778
+ data: { id: skipSlide.id, isSkippedSlide: skipSlide.isSkippedSlide }
3779
+ });
3780
+
3781
+ } catch (error) {
3782
+ console.error('🌉 [Desktop Bridge] Skip slide error:', error);
3783
+ figma.ui.postMessage({
3784
+ type: 'SKIP_SLIDE_RESULT',
3785
+ requestId: msg.requestId,
3786
+ success: false,
3787
+ error: error.message || String(error)
3788
+ });
3789
+ }
3790
+ }
3791
+
3792
+ // ADD_TEXT_TO_SLIDE - Add a text node to a slide
3793
+ else if (msg.type === 'ADD_TEXT_TO_SLIDE') {
3794
+ try {
3795
+ if (__editorType !== 'slides') {
3796
+ throw new Error('ADD_TEXT_TO_SLIDE is only available in Slides files');
3797
+ }
3798
+ var textSlide = await figma.getNodeByIdAsync(msg.slideId);
3799
+ if (!textSlide || textSlide.type !== 'SLIDE') {
3800
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3801
+ }
3802
+ console.log('🌉 [Desktop Bridge] Adding text to slide:', textSlide.name);
3803
+
3804
+ var textNode = figma.createText();
3805
+ await figma.loadFontAsync({ family: 'Inter', style: 'Regular' });
3806
+ textNode.characters = msg.text || '';
3807
+ textNode.fontSize = msg.fontSize || 24;
3808
+ textNode.x = typeof msg.x === 'number' ? msg.x : 100;
3809
+ textNode.y = typeof msg.y === 'number' ? msg.y : 100;
3810
+ textSlide.appendChild(textNode);
3811
+
3812
+ figma.ui.postMessage({
3813
+ type: 'ADD_TEXT_TO_SLIDE_RESULT',
3814
+ requestId: msg.requestId,
3815
+ success: true,
3816
+ data: { id: textNode.id, text: textNode.characters }
3817
+ });
3818
+
3819
+ } catch (error) {
3820
+ console.error('🌉 [Desktop Bridge] Add text to slide error:', error);
3821
+ figma.ui.postMessage({
3822
+ type: 'ADD_TEXT_TO_SLIDE_RESULT',
3823
+ requestId: msg.requestId,
3824
+ success: false,
3825
+ error: error.message || String(error)
3826
+ });
3827
+ }
3828
+ }
3829
+
3830
+ // ADD_SHAPE_TO_SLIDE - Add a shape to a slide
3831
+ else if (msg.type === 'ADD_SHAPE_TO_SLIDE') {
3832
+ try {
3833
+ if (__editorType !== 'slides') {
3834
+ throw new Error('ADD_SHAPE_TO_SLIDE is only available in Slides files');
3835
+ }
3836
+ var shapeSlide = await figma.getNodeByIdAsync(msg.slideId);
3837
+ if (!shapeSlide || shapeSlide.type !== 'SLIDE') {
3838
+ throw new Error('Node ' + msg.slideId + ' is not a SLIDE');
3839
+ }
3840
+ console.log('🌉 [Desktop Bridge] Adding shape to slide:', shapeSlide.name);
3841
+
3842
+ var shape;
3843
+ if (msg.shapeType === 'ELLIPSE') {
3844
+ shape = figma.createEllipse();
3845
+ } else {
3846
+ shape = figma.createRectangle();
3847
+ }
3848
+ shape.x = typeof msg.x === 'number' ? msg.x : 100;
3849
+ shape.y = typeof msg.y === 'number' ? msg.y : 100;
3850
+ shape.resize(typeof msg.width === 'number' ? msg.width : 200, typeof msg.height === 'number' ? msg.height : 200);
3851
+
3852
+ if (msg.color && typeof msg.color === 'string') {
3853
+ var hex = msg.color.replace('#', '');
3854
+ if (/^[0-9a-fA-F]{6}$/.test(hex)) {
3855
+ shape.fills = [{
3856
+ type: 'SOLID',
3857
+ color: {
3858
+ r: parseInt(hex.substring(0, 2), 16) / 255,
3859
+ g: parseInt(hex.substring(2, 4), 16) / 255,
3860
+ b: parseInt(hex.substring(4, 6), 16) / 255
3861
+ }
3862
+ }];
3863
+ }
3864
+ }
3865
+ shapeSlide.appendChild(shape);
3866
+
3867
+ figma.ui.postMessage({
3868
+ type: 'ADD_SHAPE_TO_SLIDE_RESULT',
3869
+ requestId: msg.requestId,
3870
+ success: true,
3871
+ data: { id: shape.id, type: shape.type }
3872
+ });
3873
+
3874
+ } catch (error) {
3875
+ console.error('🌉 [Desktop Bridge] Add shape to slide error:', error);
3876
+ figma.ui.postMessage({
3877
+ type: 'ADD_SHAPE_TO_SLIDE_RESULT',
3878
+ requestId: msg.requestId,
3879
+ success: false,
3880
+ error: error.message || String(error)
3881
+ });
3882
+ }
3883
+ }
2786
3884
  };
2787
3885
 
2788
3886
  // ============================================================================