@deneb-ui/cli 2.0.49 → 2.0.51

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.
@@ -9,7 +9,7 @@ const { appendStyleBindTransforms } = require('./style-candidates.cjs');
9
9
  function recipeBoost(candidate, recipe) {
10
10
  if (!recipe) return 0;
11
11
  let boost = 0;
12
- if (recipe.actionRules?.splitActionAndLabel && candidate.operation === 'split-action-contract') boost += 0.03;
12
+ if (recipe.actionRules?.splitActionAndLabel && (candidate.operation === 'split-action-contract' || candidate.operation === 'form-submit-action')) boost += 0.03;
13
13
  const keywords = recipe.signatures?.keywords || [];
14
14
  const hay = `${candidate.tag} ${candidate.value || ''} ${candidate.label || ''} ${candidate.file || ''}`.toLowerCase();
15
15
  if (keywords.some((kw) => hay.includes(String(kw).toLowerCase()))) boost += 0.02;
@@ -66,8 +66,8 @@ function planTransformations({ profile, analyses, recipe }) {
66
66
  }
67
67
 
68
68
  const extra = { ...(candidate.extra || {}) };
69
- if (candidate.operation === 'split-action-contract') {
70
- extra.action = extra.action || classifyHref(candidate.value);
69
+ if (candidate.operation === 'split-action-contract' || candidate.operation === 'form-submit-action') {
70
+ extra.action = extra.action || (candidate.operation === 'form-submit-action' ? 'form-submit' : classifyHref(candidate.value));
71
71
  extra.paired = true;
72
72
  }
73
73
 
@@ -107,7 +107,7 @@ function planTransformations({ profile, analyses, recipe }) {
107
107
  continue;
108
108
  }
109
109
 
110
- if (candidate.operation === 'split-action-contract') {
110
+ if (candidate.operation === 'split-action-contract' || candidate.operation === 'form-submit-action') {
111
111
  const urlName = inferFieldName('url', candidate.tag, candidate.label, extra);
112
112
  const labelName = inferFieldName('label', candidate.tag, candidate.label, { ...extra, paired: true });
113
113
  const actionSection = sectionForAction(scope, section, extra);
@@ -117,6 +117,9 @@ function planTransformations({ profile, analyses, recipe }) {
117
117
  transform.labelField = uniquePath(usedPaths, sibling.join('.'));
118
118
  transform.fieldType = 'url';
119
119
  transform.labelFieldType = 'text';
120
+ if (candidate.operation === 'form-submit-action') {
121
+ transform.formContext = true;
122
+ }
120
123
  } else if (candidate.operation === 'extract-url') {
121
124
  transform.field = buildFieldPath({
122
125
  scope,
@@ -177,8 +180,16 @@ function planTransformations({ profile, analyses, recipe }) {
177
180
  }));
178
181
  transform.items = candidate.value.map((item) => item.value);
179
182
  transform.itemParam = extra.itemParam;
180
- transform.indexParam = extra.indexParam;
181
- if (!transform.itemFields.length || !extra.objectItems) transform.decision = 'skip';
183
+ if (!transform.itemFields.length && extra.objectItems && candidate.value?.length > 0) {
184
+ const sample = candidate.value[0]?.value || {};
185
+ transform.itemFields = Object.keys(sample)
186
+ .filter((k) => typeof sample[k] === 'string' || typeof sample[k] === 'number')
187
+ .map((k) => ({
188
+ key: k,
189
+ type: typeof sample[k] === 'number' ? 'number' : /image|photo|avatar/i.test(k) ? 'image' : /url|link/i.test(k) ? 'url' : 'text',
190
+ }));
191
+ }
192
+ if (!transform.itemFields.length || !extra.objectItems || extra.hasComponentRef) transform.decision = 'skip';
182
193
  } else {
183
194
  transform.field = buildFieldPath({
184
195
  scope,
@@ -230,7 +241,7 @@ function sectionForAction(scope, section, extra) {
230
241
  if (scope === 'common' && (extra.social || ['instagram', 'facebook', 'twitter', 'tiktok', 'youtube', 'linkedin'].includes(extra.action))) {
231
242
  return 'footer';
232
243
  }
233
- if (['whatsapp', 'phone', 'email', 'directions', 'location'].includes(extra.action)) {
244
+ if (['whatsapp', 'phone', 'email', 'directions', 'location', 'form-submit'].includes(extra.action)) {
234
245
  return section || 'contact';
235
246
  }
236
247
  if (extra.action === 'shop') {
@@ -202,6 +202,22 @@ function printAiSummary(adapted, skipped, totalTokens) {
202
202
  console.log(`\n ${C.cyan}⚡${C.reset} AI Agent Summary: ${C.green}${adapted} adapted${C.reset}, ${C.yellow}${skipped} skipped${C.reset}, ~${totalTokens.toLocaleString()} tokens used`);
203
203
  }
204
204
 
205
+ function printAiEvaluatorStart() {
206
+ console.log(`\n ${C.cyan}⚡${C.reset} ${C.bold}AI Evaluator:${C.reset} Running pre-flight runtime integrity audit...`);
207
+ }
208
+
209
+ function printAiEvaluatorPass(message) {
210
+ console.log(` ${C.green}✓${C.reset} ${message}`);
211
+ }
212
+
213
+ function printAiEvaluatorHealed(message) {
214
+ console.log(` ${C.cyan}🔧${C.reset} ${C.bold}Auto-healed:${C.reset} ${message}`);
215
+ }
216
+
217
+ function printAiEvaluatorIssue(message) {
218
+ console.log(` ${C.yellow}⚠${C.reset} ${message}`);
219
+ }
220
+
205
221
  module.exports = {
206
222
  printBanner,
207
223
  printProfile,
@@ -223,6 +239,10 @@ module.exports = {
223
239
  printAiSkipped,
224
240
  printAiPr,
225
241
  printAiSummary,
242
+ printAiEvaluatorStart,
243
+ printAiEvaluatorPass,
244
+ printAiEvaluatorHealed,
245
+ printAiEvaluatorIssue,
226
246
  ok,
227
247
  warn,
228
248
  info,
@@ -23,6 +23,8 @@ const {
23
23
  TEXT_TAGS,
24
24
  ACTION_TAGS,
25
25
  IMAGE_TAGS,
26
+ FORM_INPUT_TAGS,
27
+ FORM_CONTAINER_TAGS,
26
28
  DECORATIVE_TAGS,
27
29
  } = require('./adapters.cjs');
28
30
  const { shortHash } = require('./fs-utils.cjs');
@@ -49,35 +51,53 @@ function isStaticSkipText(text) {
49
51
  return false;
50
52
  }
51
53
 
54
+ function unwrapTypeCasts(node) {
55
+ let curr = node;
56
+ while (
57
+ curr &&
58
+ (curr.type === 'TSAsExpression' ||
59
+ curr.type === 'TSTypeAssertion' ||
60
+ curr.type === 'TypeCastExpression' ||
61
+ curr.type === 'TSNonNullExpression')
62
+ ) {
63
+ curr = curr.expression;
64
+ }
65
+ return curr;
66
+ }
67
+
52
68
  function collectStringBindings(ast) {
53
69
  const bindings = new Map();
54
70
  recast.types.visit(ast, {
55
71
  visitVariableDeclarator(pathNode) {
56
72
  const node = pathNode.node;
57
73
  if (node.id && node.id.type === 'Identifier' && node.init) {
58
- if (node.init.type === 'StringLiteral' || node.init.type === 'Literal' && typeof node.init.value === 'string') {
59
- bindings.set(node.id.name, String(node.init.value));
74
+ const init = unwrapTypeCasts(node.init);
75
+ if (init.type === 'StringLiteral' || (init.type === 'Literal' && typeof init.value === 'string')) {
76
+ bindings.set(node.id.name, String(init.value));
60
77
  }
61
- if (node.init.type === 'ArrayExpression') {
78
+ if (init.type === 'ArrayExpression') {
62
79
  const items = [];
63
- let allLiteral = true;
64
- for (const el of node.init.elements || []) {
80
+ for (const el of init.elements || []) {
65
81
  if (!el) continue;
66
- if (el.type === 'StringLiteral' || (el.type === 'Literal' && typeof el.value === 'string')) {
67
- items.push({ type: 'string', value: String(el.value) });
68
- } else if (el.type === 'ObjectExpression') {
69
- const obj = objectLiteralToPlain(el);
70
- if (obj) items.push({ type: 'object', value: obj });
71
- else allLiteral = false;
72
- } else {
73
- allLiteral = false;
82
+ const inner = unwrapTypeCasts(el);
83
+ if (inner.type === 'StringLiteral' || (inner.type === 'Literal' && typeof inner.value === 'string')) {
84
+ items.push({ type: 'string', value: String(inner.value) });
85
+ } else if (inner.type === 'ObjectExpression') {
86
+ const obj = objectLiteralToPlain(inner);
87
+ if (obj && Object.keys(obj).length > 0) {
88
+ items.push({ type: 'object', value: obj });
89
+ }
74
90
  }
75
91
  }
76
- if (allLiteral && items.length) bindings.set(node.id.name, { kind: 'array', items });
92
+ if (items.length > 0) {
93
+ bindings.set(node.id.name, { kind: 'array', items });
94
+ }
77
95
  }
78
- if (node.init.type === 'ObjectExpression') {
79
- const obj = objectLiteralToPlain(node.init);
80
- if (obj) bindings.set(node.id.name, { kind: 'object', value: obj });
96
+ if (init.type === 'ObjectExpression') {
97
+ const obj = objectLiteralToPlain(init);
98
+ if (obj && Object.keys(obj).length > 0) {
99
+ bindings.set(node.id.name, { kind: 'object', value: obj });
100
+ }
81
101
  }
82
102
  }
83
103
  this.traverse(pathNode);
@@ -90,18 +110,57 @@ function objectLiteralToPlain(node) {
90
110
  if (!node || node.type !== 'ObjectExpression') return null;
91
111
  const out = {};
92
112
  for (const prop of node.properties || []) {
93
- if (prop.type !== 'ObjectProperty' && prop.type !== 'Property') return null;
113
+ if (prop.type !== 'ObjectProperty' && prop.type !== 'Property') continue;
94
114
  const key = prop.key && (prop.key.name || prop.key.value);
95
- if (!key || prop.computed) return null;
96
- const val = prop.value;
115
+ if (!key || prop.computed) continue;
116
+ const val = unwrapTypeCasts(prop.value);
117
+ if (!val) continue;
118
+
97
119
  if (val.type === 'StringLiteral' || (val.type === 'Literal' && typeof val.value === 'string')) {
98
120
  out[key] = String(val.value);
99
121
  } else if (val.type === 'NumericLiteral' || (val.type === 'Literal' && typeof val.value === 'number')) {
100
122
  out[key] = val.value;
101
123
  } else if (val.type === 'BooleanLiteral' || (val.type === 'Literal' && typeof val.value === 'boolean')) {
102
124
  out[key] = val.value;
103
- } else {
104
- return null;
125
+ } else if (val.type === 'NullLiteral' || (val.type === 'Literal' && val.value === null)) {
126
+ out[key] = null;
127
+ } else if (val.type === 'TemplateLiteral' && (!val.expressions || val.expressions.length === 0)) {
128
+ out[key] = val.quasis?.map((q) => q.value?.cooked || q.value?.raw || '').join('') || '';
129
+ } else if (val.type === 'Identifier') {
130
+ if (/^[A-Z]/.test(val.name)) {
131
+ // Component references (e.g. icon: Truck, Icon: ShieldCheck) are not merchant content
132
+ return null;
133
+ }
134
+ out[key] = val.name;
135
+ } else if (val.type === 'UnaryExpression' && val.argument) {
136
+ if (val.operator === '-' && (val.argument.type === 'NumericLiteral' || typeof val.argument.value === 'number')) {
137
+ out[key] = -val.argument.value;
138
+ } else if (val.operator === '!' && (val.argument.type === 'BooleanLiteral' || typeof val.argument.value === 'boolean')) {
139
+ out[key] = !val.argument.value;
140
+ }
141
+ } else if (val.type === 'ArrayExpression') {
142
+ const arr = [];
143
+ for (const el of val.elements || []) {
144
+ if (!el) continue;
145
+ const inner = unwrapTypeCasts(el);
146
+ if (!inner) continue;
147
+ if (inner.type === 'StringLiteral' || (inner.type === 'Literal' && typeof inner.value === 'string')) {
148
+ arr.push(String(inner.value));
149
+ } else if (inner.type === 'NumericLiteral' || (inner.type === 'Literal' && typeof inner.value === 'number')) {
150
+ arr.push(inner.value);
151
+ } else if (inner.type === 'BooleanLiteral' || (inner.type === 'Literal' && typeof inner.value === 'boolean')) {
152
+ arr.push(inner.value);
153
+ } else if (inner.type === 'ObjectExpression') {
154
+ const nestedObj = objectLiteralToPlain(inner);
155
+ if (nestedObj) arr.push(nestedObj);
156
+ } else if (inner.type === 'Identifier') {
157
+ arr.push(inner.name);
158
+ }
159
+ }
160
+ out[key] = arr;
161
+ } else if (val.type === 'ObjectExpression') {
162
+ const nestedObj = objectLiteralToPlain(val);
163
+ if (nestedObj) out[key] = nestedObj;
105
164
  }
106
165
  }
107
166
  return out;
@@ -262,16 +321,47 @@ function collectItemFieldUsage(callback, itemParam) {
262
321
  usage.set(property, role);
263
322
  }
264
323
 
265
- function memberProperty(expr) {
266
- if (!expr || expr.type !== 'MemberExpression' || expr.computed) return null;
267
- if (expr.object?.type !== 'Identifier' || expr.object.name !== itemParam) return null;
268
- return expr.property?.name || null;
324
+ function findItemMemberProperties(expr) {
325
+ const props = [];
326
+ if (!expr) return props;
327
+ if (expr.type === 'MemberExpression' && !expr.computed) {
328
+ if (expr.object?.type === 'Identifier' && expr.object.name === itemParam) {
329
+ if (expr.property?.name) props.push(expr.property.name);
330
+ } else if (expr.object?.type === 'MemberExpression') {
331
+ const sub = findItemMemberProperties(expr.object);
332
+ if (sub.length > 0 && expr.property?.name) {
333
+ props.push(expr.property.name);
334
+ }
335
+ }
336
+ } else if (expr.type === 'LogicalExpression' || expr.type === 'BinaryExpression') {
337
+ props.push(...findItemMemberProperties(expr.left));
338
+ props.push(...findItemMemberProperties(expr.right));
339
+ } else if (expr.type === 'ConditionalExpression') {
340
+ props.push(...findItemMemberProperties(expr.test));
341
+ props.push(...findItemMemberProperties(expr.consequent));
342
+ props.push(...findItemMemberProperties(expr.alternate));
343
+ } else if (expr.type === 'TemplateLiteral') {
344
+ for (const sub of expr.expressions || []) {
345
+ props.push(...findItemMemberProperties(sub));
346
+ }
347
+ }
348
+ return props;
269
349
  }
270
350
 
351
+ let usesItemAsComponent = false;
271
352
  recast.types.visit(callback, {
353
+ visitJSXOpeningElement(pathNode) {
354
+ const name = pathNode.node.name;
355
+ if (name?.type === 'JSXMemberExpression') {
356
+ if (name.object?.type === 'Identifier' && name.object.name === itemParam) {
357
+ usesItemAsComponent = true;
358
+ }
359
+ }
360
+ this.traverse(pathNode);
361
+ },
272
362
  visitJSXExpressionContainer(pathNode) {
273
- const property = memberProperty(pathNode.node.expression);
274
- if (property) {
363
+ const properties = findItemMemberProperties(pathNode.node.expression);
364
+ for (const property of properties) {
275
365
  const parent = pathNode.parent?.node || pathNode.parent?.value;
276
366
  if (parent?.type === 'JSXAttribute') {
277
367
  const attribute = parent.name?.name;
@@ -290,7 +380,7 @@ function collectItemFieldUsage(callback, itemParam) {
290
380
  },
291
381
  });
292
382
 
293
- return usage;
383
+ return { usage, usesItemAsComponent };
294
384
  }
295
385
 
296
386
  function classNameOf(node) {
@@ -304,7 +394,7 @@ function confidenceFor(kind, extras = {}) {
304
394
  if (extras.icon) return 0.1;
305
395
  if (kind === 'url' && extras.action === 'whatsapp') return 0.96;
306
396
  if (kind === 'url' && extras.social) return 0.93;
307
- if (kind === 'split-action-contract') return 0.94;
397
+ if (kind === 'split-action-contract' || kind === 'form-submit-action') return 0.94;
308
398
  if (kind === 'text' && HEADING_TAGS.has(extras.tag)) return 0.95;
309
399
  if (kind === 'text' && extras.tag === 'p') return 0.9;
310
400
  if (kind === 'image') return 0.88;
@@ -474,14 +564,47 @@ function analyzeFile({ code, relativeFile, profile, graph, ownerScope, component
474
564
  });
475
565
  }
476
566
 
567
+ const insideForm = parents.some((p) => FORM_CONTAINER_TAGS.has(p));
568
+
477
569
  const actionableHref = href || (name === 'Button' ? getJsxAttributeLiteral(node, 'href') : null);
478
570
  const innerText = textInfo.dynamic ? '' : textInfo.text;
479
571
  const typeAttr = getJsxAttributeLiteral(node, 'type');
480
572
  const isSubmit = typeAttr === 'submit';
481
573
 
482
- // Check for action intent via classifyActionIntent (covers WhatsApp, Call/Phone, Directions, Location, Shop, Email)
483
- const actionIntent = !isSubmit && !apiOwned ? classifyActionIntent(innerText, actionableHref) : null;
484
- const isAction = (Boolean(actionableHref) || Boolean(actionIntent)) && (ACTION_TAGS.has(name) || isLikelyCtaClass(className));
574
+ // Check for action intent via classifyActionIntent (covers Form Submit, WhatsApp, Call/Phone, Directions, Location, Shop, Email)
575
+ const actionIntent = !apiOwned ? classifyActionIntent(innerText, actionableHref) : null;
576
+
577
+ const isFormSubmit =
578
+ !apiOwned &&
579
+ (ACTION_TAGS.has(name) || isLikelyCtaClass(className)) &&
580
+ (isSubmit ||
581
+ (insideForm && (actionIntent?.action === 'form-submit' || (innerText && !actionableHref))) ||
582
+ actionIntent?.action === 'form-submit');
583
+
584
+ if (isFormSubmit && innerText && !textInfo.dynamic) {
585
+ usedLocs.add(loc);
586
+ const resolvedHref = actionableHref || (actionIntent ? actionIntent.defaultUrl : 'https://wa.me/1234567890');
587
+ candidates.push({
588
+ ...baseMeta,
589
+ kind: 'form-submit-action',
590
+ operation: 'form-submit-action',
591
+ value: resolvedHref,
592
+ label: innerText,
593
+ extra: {
594
+ action: 'form-submit',
595
+ external: true,
596
+ insideForm,
597
+ isSubmit,
598
+ },
599
+ confidence: confidenceFor('form-submit-action'),
600
+ reason: 'form-submit-whatsapp-dispatch',
601
+ fingerprint: fingerprintCandidate({ tag: name, kind: 'form-submit-action', action: 'form-submit' }),
602
+ });
603
+ this.traverse(pathNode);
604
+ return;
605
+ }
606
+
607
+ const isAction = !isSubmit && (Boolean(actionableHref) || Boolean(actionIntent)) && (ACTION_TAGS.has(name) || isLikelyCtaClass(className));
485
608
 
486
609
  if (isAction && (actionableHref || actionIntent)) {
487
610
  const action = actionIntent ? actionIntent.action : classifyHref(actionableHref);
@@ -592,13 +715,14 @@ function analyzeFile({ code, relativeFile, profile, graph, ownerScope, component
592
715
  bindings.get(mapInfo.objectName)?.kind === 'array'
593
716
  ) {
594
717
  const arr = bindings.get(mapInfo.objectName);
595
- const itemUsage = collectItemFieldUsage(mapInfo.callback, mapInfo.itemParam);
718
+ const { usage: itemUsage, usesItemAsComponent } = collectItemFieldUsage(mapInfo.callback, mapInfo.itemParam);
596
719
  const objectItems = arr.items.every((item) => item.type === 'object');
597
720
  const boundProperties = [...itemUsage.keys()];
598
721
  const convertible =
722
+ !usesItemAsComponent &&
599
723
  objectItems &&
600
724
  boundProperties.length > 0 &&
601
- arr.items.every((item) => boundProperties.every((key) => key in item.value));
725
+ arr.items.some((item) => boundProperties.some((key) => key in item.value));
602
726
 
603
727
  candidates.push({
604
728
  ...baseMeta,
@@ -612,11 +736,18 @@ function analyzeFile({ code, relativeFile, profile, graph, ownerScope, component
612
736
  indexParam: mapInfo.indexParam,
613
737
  itemFields: [...itemUsage.entries()].map(([key, role]) => ({ key, role })),
614
738
  objectItems,
739
+ hasComponentRef: usesItemAsComponent,
615
740
  },
616
741
  confidence: convertible
617
742
  ? confidenceFor('collection', { staticCollection: true })
618
- : 0.3,
619
- reason: convertible ? 'static-array-map' : 'collection-shape-not-uniform',
743
+ : usesItemAsComponent
744
+ ? 0.2
745
+ : 0.82,
746
+ reason: usesItemAsComponent
747
+ ? 'collection-holds-component-ref'
748
+ : convertible
749
+ ? 'static-array-map'
750
+ : 'collection-shape-partial',
620
751
  fingerprint: fingerprintCandidate({ tag: name, kind: 'collection', size: arr.items.length }),
621
752
  });
622
753
  }
@@ -5,6 +5,7 @@ const BUTTON_TAGS = new Set(['button', 'Button', 'CTAButton']);
5
5
 
6
6
  function inferStyleKind(transform) {
7
7
  if (transform.operation === 'collection-conversion') return 'grid';
8
+ if (transform.operation === 'form-submit-action') return 'button';
8
9
  if (transform.operation === 'split-action-contract') return 'text';
9
10
  if (BUTTON_TAGS.has(transform.tag) && transform.operation === 'extract-text') return 'button';
10
11
  if (TEXT_OPS.has(transform.operation)) return 'text';
@@ -14,7 +15,7 @@ function inferStyleKind(transform) {
14
15
  function stylePathFor(transform, kind) {
15
16
  if (kind === 'grid' && transform.listField) return `${transform.listField}.grid`;
16
17
  if (kind === 'card' && transform.listField) return `${transform.listField}[*].card`;
17
- if (transform.operation === 'split-action-contract') return transform.labelField;
18
+ if (transform.operation === 'split-action-contract' || transform.operation === 'form-submit-action') return transform.labelField;
18
19
  return transform.field || transform.labelField || null;
19
20
  }
20
21
 
@@ -24,6 +24,7 @@ const {
24
24
  b,
25
25
  } = require('./ast.cjs');
26
26
  const { toPosix } = require('./fs-utils.cjs');
27
+ const { BROAD_CONTENT_CONTAINERS } = require('./fivora-contract.cjs');
27
28
 
28
29
  function findElementByLoc(ast, loc) {
29
30
  let found = null;
@@ -151,6 +152,27 @@ function wrapHiddenUrlSibling(pathNode, urlField, fallback) {
151
152
 
152
153
  function applyTransformToElement(pathNode, transform) {
153
154
  const node = pathNode.node;
155
+ if (transform.operation === 'form-submit-action') {
156
+ const tagName = getJsxName(node);
157
+ if (!hasJsxAttribute(node, 'type') && tagName === 'button') {
158
+ node.openingElement.attributes.push(
159
+ b.jsxAttribute(b.jsxIdentifier('type'), b.stringLiteral('submit'))
160
+ );
161
+ }
162
+ if (!hasJsxAttribute(node, 'data-preview-static')) {
163
+ node.openingElement.attributes.push(
164
+ b.jsxAttribute(b.jsxIdentifier('data-preview-static'), b.stringLiteral('action-button'))
165
+ );
166
+ }
167
+ if (hasJsxAttribute(node, 'data-preview-field-path')) {
168
+ node.openingElement.attributes = node.openingElement.attributes.filter(
169
+ (attr) => !(attr.type === 'JSXAttribute' && attr.name && attr.name.name === 'data-preview-field-path')
170
+ );
171
+ }
172
+ splitActionChildren(node, transform.labelField, transform.labelFallback || '');
173
+ wrapHiddenUrlSibling(pathNode, transform.urlField, transform.fallback);
174
+ return;
175
+ }
154
176
  if (transform.operation === 'split-action-contract') {
155
177
  const tagName = getJsxName(node);
156
178
  if (tagName === 'button') {
@@ -222,6 +244,11 @@ function applyTransformToElement(pathNode, transform) {
222
244
  return;
223
245
  }
224
246
  if (transform.operation === 'extract-text') {
247
+ const tagName = getJsxName(node);
248
+ if (BROAD_CONTENT_CONTAINERS.has(tagName)) {
249
+ wrapLiteralTextChildren(node, transform.field, transform.fallback);
250
+ return;
251
+ }
225
252
  ensurePreviewPath(node, transform.field);
226
253
  ensureStyleAttrs(node, transform.field, inferButtonKind(transform.tag));
227
254
  replaceTextChildren(node, transform.field, transform.fallback, transform.fieldType || 'text');
@@ -619,6 +646,7 @@ function applyFilePlan(filePlan, profile) {
619
646
 
620
647
  const supported = new Set([
621
648
  'split-action-contract',
649
+ 'form-submit-action',
622
650
  'extract-url',
623
651
  'extract-image',
624
652
  'extract-alt',
@@ -851,7 +879,7 @@ function instrumentLayoutSource(code, siteDataImport, providerImport = '@deneb-u
851
879
  ensureDefaultImport(ast, siteDataImport, jsonIdent);
852
880
  }
853
881
 
854
- const hasProvider = /SiteDataProvider|DenebDataProvider/.test(code);
882
+ const hasProvider = /SiteDataProvider|DenebDataProvider|<Providers\b/.test(code);
855
883
  let wrapped = hasProvider;
856
884
 
857
885
  if (!hasProvider) {