flutterflow-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +124 -0
  2. package/build/api/flutterflow.d.ts +11 -0
  3. package/build/api/flutterflow.js +61 -0
  4. package/build/index.d.ts +2 -0
  5. package/build/index.js +54 -0
  6. package/build/prompts/dev-workflow.d.ts +2 -0
  7. package/build/prompts/dev-workflow.js +68 -0
  8. package/build/prompts/generate-page.d.ts +2 -0
  9. package/build/prompts/generate-page.js +36 -0
  10. package/build/prompts/inspect-project.d.ts +2 -0
  11. package/build/prompts/inspect-project.js +30 -0
  12. package/build/prompts/modify-component.d.ts +2 -0
  13. package/build/prompts/modify-component.js +39 -0
  14. package/build/resources/docs.d.ts +2 -0
  15. package/build/resources/docs.js +76 -0
  16. package/build/resources/projects.d.ts +3 -0
  17. package/build/resources/projects.js +60 -0
  18. package/build/tools/find-component-usages.d.ts +7 -0
  19. package/build/tools/find-component-usages.js +225 -0
  20. package/build/tools/find-page-navigations.d.ts +7 -0
  21. package/build/tools/find-page-navigations.js +228 -0
  22. package/build/tools/get-component-summary.d.ts +22 -0
  23. package/build/tools/get-component-summary.js +193 -0
  24. package/build/tools/get-page-by-name.d.ts +3 -0
  25. package/build/tools/get-page-by-name.js +56 -0
  26. package/build/tools/get-page-summary.d.ts +22 -0
  27. package/build/tools/get-page-summary.js +220 -0
  28. package/build/tools/get-yaml-docs.d.ts +6 -0
  29. package/build/tools/get-yaml-docs.js +217 -0
  30. package/build/tools/get-yaml.d.ts +3 -0
  31. package/build/tools/get-yaml.js +47 -0
  32. package/build/tools/list-files.d.ts +3 -0
  33. package/build/tools/list-files.js +30 -0
  34. package/build/tools/list-pages.d.ts +25 -0
  35. package/build/tools/list-pages.js +101 -0
  36. package/build/tools/list-projects.d.ts +3 -0
  37. package/build/tools/list-projects.js +19 -0
  38. package/build/tools/sync-project.d.ts +3 -0
  39. package/build/tools/sync-project.js +144 -0
  40. package/build/tools/update-yaml.d.ts +3 -0
  41. package/build/tools/update-yaml.js +24 -0
  42. package/build/tools/validate-yaml.d.ts +3 -0
  43. package/build/tools/validate-yaml.js +22 -0
  44. package/build/utils/cache.d.ts +48 -0
  45. package/build/utils/cache.js +162 -0
  46. package/build/utils/decode-yaml.d.ts +7 -0
  47. package/build/utils/decode-yaml.js +31 -0
  48. package/build/utils/page-summary/action-summarizer.d.ts +9 -0
  49. package/build/utils/page-summary/action-summarizer.js +291 -0
  50. package/build/utils/page-summary/formatter.d.ts +13 -0
  51. package/build/utils/page-summary/formatter.js +121 -0
  52. package/build/utils/page-summary/node-extractor.d.ts +17 -0
  53. package/build/utils/page-summary/node-extractor.js +207 -0
  54. package/build/utils/page-summary/tree-walker.d.ts +6 -0
  55. package/build/utils/page-summary/tree-walker.js +55 -0
  56. package/build/utils/page-summary/types.d.ts +56 -0
  57. package/build/utils/page-summary/types.js +4 -0
  58. package/build/utils/parse-folders.d.ts +9 -0
  59. package/build/utils/parse-folders.js +29 -0
  60. package/docs/ff-yaml/00-overview.md +137 -0
  61. package/docs/ff-yaml/01-project-files.md +513 -0
  62. package/docs/ff-yaml/02-pages.md +572 -0
  63. package/docs/ff-yaml/03-components.md +413 -0
  64. package/docs/ff-yaml/04-widgets/README.md +122 -0
  65. package/docs/ff-yaml/04-widgets/button.md +444 -0
  66. package/docs/ff-yaml/04-widgets/container.md +358 -0
  67. package/docs/ff-yaml/04-widgets/dropdown.md +579 -0
  68. package/docs/ff-yaml/04-widgets/form.md +256 -0
  69. package/docs/ff-yaml/04-widgets/image.md +276 -0
  70. package/docs/ff-yaml/04-widgets/layout.md +355 -0
  71. package/docs/ff-yaml/04-widgets/misc.md +553 -0
  72. package/docs/ff-yaml/04-widgets/text-field.md +326 -0
  73. package/docs/ff-yaml/04-widgets/text.md +302 -0
  74. package/docs/ff-yaml/05-actions.md +843 -0
  75. package/docs/ff-yaml/06-variables.md +834 -0
  76. package/docs/ff-yaml/07-data.md +591 -0
  77. package/docs/ff-yaml/08-custom-code.md +715 -0
  78. package/docs/ff-yaml/09-theming.md +592 -0
  79. package/docs/ff-yaml/10-editing-guide.md +454 -0
  80. package/docs/ff-yaml/README.md +105 -0
  81. package/package.json +55 -0
  82. package/skills/ff-widget-patterns.md +141 -0
  83. package/skills/ff-yaml-dev.md +58 -0
@@ -0,0 +1,291 @@
1
+ /**
2
+ * Summarize trigger_actions chains for a widget node.
3
+ *
4
+ * Walks the rootAction→followUpAction→conditionActions chain in the trigger
5
+ * file, reads individual action files, and classifies each action by type.
6
+ */
7
+ import YAML from "yaml";
8
+ import { cacheRead, listCachedKeys } from "../cache.js";
9
+ /**
10
+ * Collect all action keys referenced in a trigger chain.
11
+ * Flattens followUpAction chains, conditionActions, and parallelActions.
12
+ */
13
+ function collectActionKeys(node) {
14
+ const keys = [];
15
+ // Direct action reference
16
+ const action = node.action;
17
+ if (action?.key) {
18
+ keys.push(action.key);
19
+ }
20
+ // Conditional branches
21
+ const cond = node.conditionActions;
22
+ if (cond) {
23
+ // True branches
24
+ const trueActions = cond.trueActions;
25
+ if (Array.isArray(trueActions)) {
26
+ for (const branch of trueActions) {
27
+ const trueAction = branch.trueAction;
28
+ if (trueAction) {
29
+ keys.push(...collectActionKeys(trueAction));
30
+ }
31
+ }
32
+ }
33
+ // False branch
34
+ const falseAction = cond.falseAction;
35
+ if (falseAction && !("terminate" in falseAction)) {
36
+ keys.push(...collectActionKeys(falseAction));
37
+ }
38
+ // followUpAction after conditionActions
39
+ const condFollowUp = cond.followUpAction;
40
+ if (condFollowUp) {
41
+ keys.push(...collectActionKeys(condFollowUp));
42
+ }
43
+ }
44
+ // Parallel actions
45
+ const parallel = node.parallelActions;
46
+ if (parallel) {
47
+ const actions = parallel.actions;
48
+ if (Array.isArray(actions)) {
49
+ for (const branch of actions) {
50
+ keys.push(...collectActionKeys(branch));
51
+ }
52
+ }
53
+ }
54
+ // Follow-up chain
55
+ const followUp = node.followUpAction;
56
+ if (followUp) {
57
+ keys.push(...collectActionKeys(followUp));
58
+ }
59
+ return keys;
60
+ }
61
+ /** Known top-level keys that identify an action type. */
62
+ const ACTION_TYPE_KEYS = [
63
+ "navigate", "customAction", "database", "localStateUpdate", "waitAction",
64
+ "alertDialog", "bottomSheet", "revenueCat", "auth", "rebuild", "scrollTo",
65
+ "copyToClipboard", "share", "hapticFeedback",
66
+ ];
67
+ /**
68
+ * Recursively search an object tree for the first recognizable action.
69
+ * Used to unwrap disableAction nodes where the real action is buried
70
+ * inside conditionalActions or other nesting.
71
+ */
72
+ function findDeepAction(obj, depth = 0) {
73
+ if (!obj || typeof obj !== "object" || depth > 8)
74
+ return null;
75
+ const o = obj;
76
+ // Check if this object itself is a classifiable action
77
+ for (const key of ACTION_TYPE_KEYS) {
78
+ if (key in o)
79
+ return classifyAction(o);
80
+ }
81
+ // Recurse into object values
82
+ for (const val of Object.values(o)) {
83
+ if (val && typeof val === "object") {
84
+ const found = findDeepAction(val, depth + 1);
85
+ if (found)
86
+ return found;
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+ /**
92
+ * Classify an action YAML into a human-readable summary.
93
+ */
94
+ function classifyAction(doc) {
95
+ // Disabled action — unwrap and recursively find the inner action
96
+ if ("disableAction" in doc) {
97
+ const da = doc.disableAction;
98
+ const actionNode = da.actionNode;
99
+ if (actionNode) {
100
+ const inner = findDeepAction(actionNode);
101
+ if (inner) {
102
+ return {
103
+ type: `[DISABLED] ${inner.type}`,
104
+ detail: inner.detail,
105
+ };
106
+ }
107
+ }
108
+ return { type: "[DISABLED]", detail: "" };
109
+ }
110
+ // Navigate
111
+ if ("navigate" in doc) {
112
+ const nav = doc.navigate;
113
+ if (nav.isNavigateBack) {
114
+ return { type: "navigate", detail: "back" };
115
+ }
116
+ return { type: "navigate", detail: "to page" };
117
+ }
118
+ // Custom action
119
+ if ("customAction" in doc) {
120
+ const ca = doc.customAction;
121
+ const id = ca.customActionIdentifier;
122
+ const name = doc.outputVariableName;
123
+ return {
124
+ type: "customAction",
125
+ detail: name || id?.key || "unknown",
126
+ };
127
+ }
128
+ // Database
129
+ if ("database" in doc) {
130
+ const db = doc.database;
131
+ const pg = db.postgresAction;
132
+ if (pg) {
133
+ const table = pg.tableIdentifier;
134
+ const tableName = table?.name || "table";
135
+ const op = "insert" in pg ? "insert" : "update" in pg ? "update" : "query" in pg ? "query" : "delete" in pg ? "delete" : "op";
136
+ return { type: "database", detail: `${op} ${tableName}` };
137
+ }
138
+ const firestore = db.firestoreAction;
139
+ if (firestore) {
140
+ return { type: "database", detail: "firestore" };
141
+ }
142
+ return { type: "database", detail: "" };
143
+ }
144
+ // Local state update
145
+ if ("localStateUpdate" in doc) {
146
+ const lsu = doc.localStateUpdate;
147
+ const stateType = lsu.stateVariableType || "";
148
+ const updates = lsu.updates;
149
+ if (stateType === "APP_STATE") {
150
+ return { type: "updateAppState", detail: "" };
151
+ }
152
+ if (updates && updates.length > 0) {
153
+ const first = updates[0];
154
+ if ("increment" in first)
155
+ return { type: "updateState", detail: "increment" };
156
+ if ("dataStructUpdate" in first)
157
+ return { type: "updateState", detail: "struct" };
158
+ }
159
+ return { type: "updateState", detail: "" };
160
+ }
161
+ // Wait
162
+ if ("waitAction" in doc) {
163
+ const wa = doc.waitAction;
164
+ const dur = wa.durationMillisValue;
165
+ const ms = dur?.inputValue;
166
+ return { type: "wait", detail: ms ? `${ms}ms` : "" };
167
+ }
168
+ // Alert dialog
169
+ if ("alertDialog" in doc) {
170
+ return { type: "alertDialog", detail: "" };
171
+ }
172
+ // Bottom sheet
173
+ if ("bottomSheet" in doc) {
174
+ return { type: "bottomSheet", detail: "" };
175
+ }
176
+ // RevenueCat
177
+ if ("revenueCat" in doc) {
178
+ const rc = doc.revenueCat;
179
+ if ("purchase" in rc)
180
+ return { type: "revenueCat", detail: "purchase" };
181
+ if ("restore" in rc)
182
+ return { type: "revenueCat", detail: "restore" };
183
+ if ("paywall" in rc) {
184
+ const pw = rc.paywall;
185
+ const eid = pw.entitlementId;
186
+ const iv = eid?.inputValue;
187
+ const name = iv?.serializedValue || "";
188
+ return { type: "revenueCat", detail: name ? `paywall (${name})` : "paywall" };
189
+ }
190
+ return { type: "revenueCat", detail: "" };
191
+ }
192
+ // Authentication
193
+ if ("auth" in doc) {
194
+ return { type: "auth", detail: "" };
195
+ }
196
+ // Rebuild (update widget / rebuild)
197
+ if ("rebuild" in doc) {
198
+ return { type: "rebuild", detail: "" };
199
+ }
200
+ // Scroll to
201
+ if ("scrollTo" in doc) {
202
+ return { type: "scrollTo", detail: "" };
203
+ }
204
+ // Copy to clipboard
205
+ if ("copyToClipboard" in doc) {
206
+ return { type: "copyToClipboard", detail: "" };
207
+ }
208
+ // Share
209
+ if ("share" in doc) {
210
+ return { type: "share", detail: "" };
211
+ }
212
+ // Haptic feedback
213
+ if ("hapticFeedback" in doc) {
214
+ return { type: "haptic", detail: "" };
215
+ }
216
+ // Terminate sentinel — skip
217
+ if ("terminate" in doc) {
218
+ return { type: "terminate", detail: "" };
219
+ }
220
+ // Fallback: use first key
221
+ const actionKeys = Object.keys(doc).filter((k) => k !== "key" && k !== "outputVariableName");
222
+ return { type: actionKeys[0] || "unknown", detail: "" };
223
+ }
224
+ /**
225
+ * Read all trigger_actions for a node and return summaries.
226
+ *
227
+ * @param projectId - FF project ID
228
+ * @param nodeFileKeyPrefix - Cache prefix for the node's trigger actions,
229
+ * e.g. "page/id-Scaffold_xxx/page-widget-tree-outline/node/id-Button_yyy"
230
+ */
231
+ export async function summarizeTriggers(projectId, nodeFileKeyPrefix) {
232
+ const triggerPrefix = `${nodeFileKeyPrefix}/trigger_actions/`;
233
+ const allKeys = await listCachedKeys(projectId, triggerPrefix);
234
+ // Find trigger definition files (e.g. trigger_actions/id-ON_TAP)
235
+ // These don't have /action/ in the path
236
+ const triggerDefKeys = allKeys.filter((k) => !k.includes("/action/") && k.startsWith(triggerPrefix + "id-"));
237
+ // Deduplicate: a key like "trigger_actions/id-ON_TAP" may also appear as a
238
+ // directory prefix "trigger_actions/id-ON_TAP/action/..." — we only want the
239
+ // file, which is the shortest matching key
240
+ const triggerFiles = triggerDefKeys.filter((k) => {
241
+ const afterPrefix = k.slice(triggerPrefix.length);
242
+ // Should be just "id-ON_TAP" with no further slashes
243
+ return !afterPrefix.includes("/");
244
+ });
245
+ const results = [];
246
+ for (const triggerFileKey of triggerFiles) {
247
+ const content = await cacheRead(projectId, triggerFileKey);
248
+ if (!content)
249
+ continue;
250
+ let doc;
251
+ try {
252
+ doc = YAML.parse(content);
253
+ }
254
+ catch {
255
+ continue;
256
+ }
257
+ // Extract trigger type
258
+ const trigger = doc.trigger;
259
+ const triggerType = trigger?.triggerType || "UNKNOWN";
260
+ // Collect action keys from chain
261
+ const rootAction = doc.rootAction;
262
+ if (!rootAction)
263
+ continue;
264
+ const actionKeys = collectActionKeys(rootAction);
265
+ // Deduplicate while preserving order
266
+ const uniqueKeys = [...new Set(actionKeys)];
267
+ // Read each action file
268
+ const actionPrefix = `${triggerFileKey}/action/`;
269
+ const actions = [];
270
+ for (const actionKey of uniqueKeys) {
271
+ const actionFileKey = `${actionPrefix}id-${actionKey}`;
272
+ const actionContent = await cacheRead(projectId, actionFileKey);
273
+ if (!actionContent)
274
+ continue;
275
+ try {
276
+ const actionDoc = YAML.parse(actionContent);
277
+ const summary = classifyAction(actionDoc);
278
+ if (summary.type !== "terminate") {
279
+ actions.push(summary);
280
+ }
281
+ }
282
+ catch {
283
+ actions.push({ type: "unknown", detail: actionKey });
284
+ }
285
+ }
286
+ if (actions.length > 0) {
287
+ results.push({ trigger: triggerType, actions });
288
+ }
289
+ }
290
+ return results;
291
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Render a PageMeta + SummaryNode tree as a readable text summary
3
+ * with box-drawing characters and inline trigger summaries.
4
+ */
5
+ import { ComponentMeta, PageMeta, SummaryNode } from "./types.js";
6
+ /**
7
+ * Format a complete page summary as text.
8
+ */
9
+ export declare function formatPageSummary(meta: PageMeta, tree: SummaryNode): string;
10
+ /**
11
+ * Format a complete component summary as text.
12
+ */
13
+ export declare function formatComponentSummary(meta: ComponentMeta, tree: SummaryNode): string;
@@ -0,0 +1,121 @@
1
+ /** Format a single action into a compact string. */
2
+ function fmtAction(a) {
3
+ if (a.detail)
4
+ return `${a.type}: ${a.detail}`;
5
+ return a.type;
6
+ }
7
+ /** Format a trigger into a compact inline string like "ON_TAP: [navigate to page, customAction: foo]". */
8
+ function fmtTrigger(t) {
9
+ const acts = t.actions.map(fmtAction).join(", ");
10
+ return `${t.trigger} → [${acts}]`;
11
+ }
12
+ /** Format the slot prefix for display. */
13
+ function fmtSlot(slot) {
14
+ if (slot === "children" || slot === "root")
15
+ return "";
16
+ return `[${slot}] `;
17
+ }
18
+ /** Build a display label for a node. */
19
+ function nodeLabel(node) {
20
+ const parts = [];
21
+ parts.push(fmtSlot(node.slot));
22
+ parts.push(node.type);
23
+ if (node.name) {
24
+ parts.push(` (${node.name})`);
25
+ }
26
+ if (node.detail) {
27
+ parts.push(` ${node.detail}`);
28
+ }
29
+ return parts.join("");
30
+ }
31
+ /**
32
+ * Render the widget tree recursively with box-drawing characters.
33
+ */
34
+ function renderTree(node, prefix, isLast, isRoot, lines) {
35
+ // Build connector
36
+ const connector = isRoot ? "" : isLast ? "└── " : "├── ";
37
+ const label = nodeLabel(node);
38
+ // Triggers inline
39
+ const triggerStr = node.triggers.length > 0
40
+ ? " → " + node.triggers.map(fmtTrigger).join("; ")
41
+ : "";
42
+ lines.push(`${prefix}${connector}${label}${triggerStr}`);
43
+ // Child prefix
44
+ const childPrefix = isRoot ? prefix : prefix + (isLast ? " " : "│ ");
45
+ for (let i = 0; i < node.children.length; i++) {
46
+ renderTree(node.children[i], childPrefix, i === node.children.length - 1, false, lines);
47
+ }
48
+ }
49
+ /**
50
+ * Format a complete page summary as text.
51
+ */
52
+ export function formatPageSummary(meta, tree) {
53
+ const lines = [];
54
+ // Header
55
+ lines.push(`${meta.pageName} (${meta.scaffoldId}) — folder: ${meta.folder}`);
56
+ // Params
57
+ if (meta.params.length > 0) {
58
+ const paramStrs = meta.params.map((p) => {
59
+ const def = p.defaultValue ? `, default: ${p.defaultValue}` : "";
60
+ return `${p.name} (${p.dataType}${def})`;
61
+ });
62
+ lines.push(`Params: ${paramStrs.join(", ")}`);
63
+ }
64
+ // State fields
65
+ if (meta.stateFields.length > 0) {
66
+ const stateStrs = meta.stateFields.map((s) => {
67
+ const def = s.defaultValue ? `, default: ${s.defaultValue}` : "";
68
+ return `${s.name} (${s.dataType}${def})`;
69
+ });
70
+ lines.push(`State: ${stateStrs.join(", ")}`);
71
+ }
72
+ // Scaffold-level triggers
73
+ if (tree.triggers.length > 0) {
74
+ lines.push("");
75
+ for (const t of tree.triggers) {
76
+ lines.push(fmtTrigger(t));
77
+ }
78
+ }
79
+ // Widget tree
80
+ lines.push("");
81
+ lines.push("Widget Tree:");
82
+ // Render children directly (skip the Scaffold root itself)
83
+ for (let i = 0; i < tree.children.length; i++) {
84
+ renderTree(tree.children[i], "", i === tree.children.length - 1, false, lines);
85
+ }
86
+ return lines.join("\n");
87
+ }
88
+ /**
89
+ * Format a complete component summary as text.
90
+ */
91
+ export function formatComponentSummary(meta, tree) {
92
+ const lines = [];
93
+ // Header
94
+ lines.push(`${meta.componentName} (${meta.containerId})`);
95
+ if (meta.description) {
96
+ lines.push(`Description: ${meta.description}`);
97
+ }
98
+ // Params
99
+ if (meta.params.length > 0) {
100
+ const paramStrs = meta.params.map((p) => {
101
+ const def = p.defaultValue ? `, default: ${p.defaultValue}` : "";
102
+ return `${p.name} (${p.dataType}${def})`;
103
+ });
104
+ lines.push(`Params: ${paramStrs.join(", ")}`);
105
+ }
106
+ // Root-level triggers
107
+ if (tree.triggers.length > 0) {
108
+ lines.push("");
109
+ for (const t of tree.triggers) {
110
+ lines.push(fmtTrigger(t));
111
+ }
112
+ }
113
+ // Widget tree
114
+ lines.push("");
115
+ lines.push("Widget Tree:");
116
+ // Render children directly (skip the Container root itself)
117
+ for (let i = 0; i < tree.children.length; i++) {
118
+ renderTree(tree.children[i], "", i === tree.children.length - 1, false, lines);
119
+ }
120
+ return lines.join("\n");
121
+ }
@@ -0,0 +1,17 @@
1
+ export interface NodeInfo {
2
+ type: string;
3
+ name: string;
4
+ detail: string;
5
+ }
6
+ /**
7
+ * Infer widget type from the key prefix (e.g. "Text_abc123" → "Text").
8
+ */
9
+ export declare function inferTypeFromKey(key: string): string;
10
+ /**
11
+ * Read a node's cached file and extract type, name, and detail.
12
+ *
13
+ * @param projectId - The FF project ID
14
+ * @param pagePrefix - Cache key prefix for the page, e.g. "page/id-Scaffold_xxx/page-widget-tree-outline"
15
+ * @param nodeKey - The node key, e.g. "Button_uaqbabys"
16
+ */
17
+ export declare function extractNodeInfo(projectId: string, pagePrefix: string, nodeKey: string): Promise<NodeInfo>;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Read a node's cached YAML and extract human-readable details
3
+ * based on widget type (text value, button label, image path, etc.).
4
+ */
5
+ import YAML from "yaml";
6
+ import { cacheRead } from "../cache.js";
7
+ /**
8
+ * Resolve the inputValue from a FF value object.
9
+ * Returns the literal string, or "[dynamic]" for variable references.
10
+ */
11
+ function resolveValue(obj) {
12
+ if (obj == null)
13
+ return "";
14
+ if (typeof obj === "string" || typeof obj === "number")
15
+ return String(obj);
16
+ if (typeof obj !== "object")
17
+ return "";
18
+ const o = obj;
19
+ // Direct inputValue
20
+ if ("inputValue" in o) {
21
+ const iv = o.inputValue;
22
+ if (typeof iv === "string" || typeof iv === "number")
23
+ return String(iv);
24
+ if (iv && typeof iv === "object") {
25
+ const ivo = iv;
26
+ // serializedValue pattern
27
+ if ("serializedValue" in ivo)
28
+ return String(ivo.serializedValue);
29
+ // themeColor pattern
30
+ if ("themeColor" in ivo)
31
+ return `[theme:${ivo.themeColor}]`;
32
+ // color value pattern
33
+ if ("value" in ivo)
34
+ return String(ivo.value);
35
+ }
36
+ return "";
37
+ }
38
+ // Variable reference
39
+ if ("variable" in o)
40
+ return "[dynamic]";
41
+ return "";
42
+ }
43
+ /** Extract text value from a text widget's props. */
44
+ function extractText(props) {
45
+ const text = props.text;
46
+ if (!text)
47
+ return "";
48
+ const textValue = text.textValue;
49
+ if (!textValue)
50
+ return "";
51
+ const val = resolveValue(textValue);
52
+ return val ? `"${val}"` : "";
53
+ }
54
+ /** Extract button label from a button widget's props. */
55
+ function extractButton(props) {
56
+ const button = props.button;
57
+ if (!button)
58
+ return "";
59
+ const text = button.text;
60
+ if (!text)
61
+ return "";
62
+ const textValue = text.textValue;
63
+ if (!textValue)
64
+ return "";
65
+ const val = resolveValue(textValue);
66
+ return val ? `"${val}"` : "";
67
+ }
68
+ /** Extract image info from an image widget's props. */
69
+ function extractImage(props) {
70
+ const image = props.image;
71
+ if (!image)
72
+ return "";
73
+ const parts = [];
74
+ // Path
75
+ const pathValue = image.pathValue;
76
+ if (pathValue) {
77
+ const path = resolveValue(pathValue);
78
+ if (path && path !== "[dynamic]") {
79
+ // Extract just the filename from the full asset path
80
+ const filename = path.split("/").pop() || path;
81
+ parts.push(filename);
82
+ }
83
+ else if (path === "[dynamic]") {
84
+ parts.push("[dynamic]");
85
+ }
86
+ }
87
+ // Dimensions
88
+ const dims = image.dimensions;
89
+ if (dims) {
90
+ const w = dims.width;
91
+ const h = dims.height;
92
+ const wVal = w?.pixelsValue ? resolveValue(w.pixelsValue) : "";
93
+ const hVal = h?.pixelsValue ? resolveValue(h.pixelsValue) : "";
94
+ if (wVal && hVal && wVal !== "Infinity" && hVal !== "Infinity") {
95
+ parts.push(`[${wVal}x${hVal}]`);
96
+ }
97
+ }
98
+ return parts.join(" ");
99
+ }
100
+ /** Extract icon info. */
101
+ function extractIcon(props) {
102
+ const icon = props.icon;
103
+ if (!icon)
104
+ return "";
105
+ const iconData = icon.iconDataValue;
106
+ if (!iconData)
107
+ return "";
108
+ const iv = iconData.inputValue;
109
+ if (!iv)
110
+ return "";
111
+ return iv.name || "";
112
+ }
113
+ /** Extract text field hint. */
114
+ function extractTextField(props) {
115
+ const tf = props.textField;
116
+ if (!tf)
117
+ return "";
118
+ const decoration = tf.inputDecoration;
119
+ if (!decoration)
120
+ return "";
121
+ const hintText = decoration.hintText;
122
+ if (!hintText)
123
+ return "";
124
+ const textValue = hintText.textValue;
125
+ if (!textValue)
126
+ return "";
127
+ const val = resolveValue(textValue);
128
+ return val ? `hint: "${val}"` : "";
129
+ }
130
+ /** Extract checkbox/toggle/switch label. */
131
+ function extractCheckbox(props) {
132
+ const cb = (props.checkbox || props.toggle || props.switchWidget);
133
+ if (!cb)
134
+ return "";
135
+ const label = cb.labelValue;
136
+ if (!label)
137
+ return "";
138
+ return resolveValue(label);
139
+ }
140
+ /** Type-specific detail extraction. */
141
+ function extractDetail(type, props) {
142
+ switch (type) {
143
+ case "Text":
144
+ case "RichText":
145
+ case "AutoSizeText":
146
+ return extractText(props);
147
+ case "Button":
148
+ case "IconButton":
149
+ case "FFButtonWidget":
150
+ return extractButton(props);
151
+ case "Image":
152
+ case "CachedNetworkImage":
153
+ return extractImage(props);
154
+ case "Icon":
155
+ return extractIcon(props);
156
+ case "TextField":
157
+ case "TextFormField":
158
+ return extractTextField(props);
159
+ case "Checkbox":
160
+ case "CheckboxListTile":
161
+ case "Switch":
162
+ case "ToggleIcon":
163
+ return extractCheckbox(props);
164
+ default:
165
+ return "";
166
+ }
167
+ }
168
+ /**
169
+ * Infer widget type from the key prefix (e.g. "Text_abc123" → "Text").
170
+ */
171
+ export function inferTypeFromKey(key) {
172
+ const match = key.match(/^([A-Z][a-zA-Z]*)_/);
173
+ return match ? match[1] : "Unknown";
174
+ }
175
+ /**
176
+ * Read a node's cached file and extract type, name, and detail.
177
+ *
178
+ * @param projectId - The FF project ID
179
+ * @param pagePrefix - Cache key prefix for the page, e.g. "page/id-Scaffold_xxx/page-widget-tree-outline"
180
+ * @param nodeKey - The node key, e.g. "Button_uaqbabys"
181
+ */
182
+ export async function extractNodeInfo(projectId, pagePrefix, nodeKey) {
183
+ const fileKey = `${pagePrefix}/node/id-${nodeKey}`;
184
+ const content = await cacheRead(projectId, fileKey);
185
+ if (!content) {
186
+ return {
187
+ type: inferTypeFromKey(nodeKey),
188
+ name: "",
189
+ detail: "",
190
+ };
191
+ }
192
+ try {
193
+ const doc = YAML.parse(content);
194
+ const type = doc.type || inferTypeFromKey(nodeKey);
195
+ const name = doc.name || "";
196
+ const props = doc.props || {};
197
+ const detail = extractDetail(type, props);
198
+ return { type, name, detail };
199
+ }
200
+ catch {
201
+ return {
202
+ type: inferTypeFromKey(nodeKey),
203
+ name: "",
204
+ detail: "",
205
+ };
206
+ }
207
+ }
@@ -0,0 +1,6 @@
1
+ import { OutlineNode } from "./types.js";
2
+ /**
3
+ * Parse the page-widget-tree-outline YAML string into an OutlineNode tree.
4
+ * Returns the root node (typically the Scaffold).
5
+ */
6
+ export declare function parseTreeOutline(yamlContent: string): OutlineNode;