@aglyn/plugins-ai 1.0.0-beta.152 → 1.0.0-beta.154

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.
@@ -725,6 +725,55 @@ function normalizeHex(value) {
725
725
  }
726
726
  return violations;
727
727
  }
728
+ /**
729
+ * An instance that fills none of the props its component declares (AGL-3024).
730
+ *
731
+ * Rule 1 says an instance is `"reusableInstance"` whose `refId` names the
732
+ * component and whose `propValues` FILL its props, and until now nothing read
733
+ * the second half back. A placed instance with no `propValues` is not empty on
734
+ * the page: it renders the component's own `defaultValue` for every prop, so
735
+ * three of them render three identical cards of placeholder text.
736
+ *
737
+ * MEASURED 2026-09-21 on `attorney-profile-template-v2` (`sak5ei_9Wv`), whose
738
+ * three instances of `practice-area-card` each carried `{"refId": …}` and
739
+ * nothing else. It was recorded as the component shipping placeholder content;
740
+ * the component is correct and declares `title`, `description` and `icon` with
741
+ * labels and descriptions. The template placed it three times and filled
742
+ * nothing, so every attorney's page showed the same `[Practice area name]`.
743
+ *
744
+ * Only an instance filling NOTHING is refused. A component may declare a prop
745
+ * whose default is right for most placements, and a partly filled instance is
746
+ * a judgment the doctrine does not have the information to second-guess.
747
+ */ export function detectUnfilledInstances(tree, context) {
748
+ var _context_componentProps, _declared_ref;
749
+ var _first_props;
750
+ const declared = (_context_componentProps = context.componentProps) != null ? _context_componentProps : {};
751
+ const unfilled = [];
752
+ for (const [id, node] of Object.entries(tree.nodes)){
753
+ var _node_props, _node_props1;
754
+ if ((node == null ? void 0 : node.componentId) !== REUSABLE_INSTANCE_COMPONENT_ID) continue;
755
+ const ref = (_node_props = node.props) == null ? void 0 : _node_props[AI_INSTANCE_REF_PROP];
756
+ if (typeof ref !== 'string') continue;
757
+ const props = declared[ref];
758
+ if (!props || !Object.keys(props).length) continue;
759
+ const values = (_node_props1 = node.props) == null ? void 0 : _node_props1['propValues'];
760
+ const filled = typeof values === 'object' && values !== null && !Array.isArray(values) ? Object.keys(values).filter((name)=>name in props) : [];
761
+ if (!filled.length) unfilled.push(id);
762
+ }
763
+ if (!unfilled.length) return [];
764
+ const first = tree.nodes[unfilled[0]];
765
+ const ref = first == null ? void 0 : (_first_props = first.props) == null ? void 0 : _first_props[AI_INSTANCE_REF_PROP];
766
+ const names = Object.keys((_declared_ref = declared[ref]) != null ? _declared_ref : {});
767
+ const one = unfilled.length === 1;
768
+ return [
769
+ {
770
+ rule: 1,
771
+ code: 'instance-props-unfilled',
772
+ message: `${one ? 'An instance places' : `${unfilled.length} instances place`} a component and fill none of its props, so ${one ? 'it shows' : 'they all show'} the placeholder text the component falls back to. Fill "propValues" with what ${one ? 'this one' : 'each one'} should say: ${names.join(', ')}.`,
773
+ nodeIds: unfilled
774
+ }
775
+ ];
776
+ }
728
777
  /** The media-role props of a node, with the declared image props of an instance. */ function mediaValues(node, context) {
729
778
  var _ref, _node_props;
730
779
  var _AI_PALETTE_node_componentId, _node_props1, _node_props2;
@@ -2325,6 +2374,7 @@ const BUDGET_METRICS = [
2325
2374
  ...detectInvisibleLinks(tree, outputKind),
2326
2375
  ...detectOffBrandEmail(tree, outputKind, context.brand),
2327
2376
  ...detectTypedData(tree),
2377
+ ...detectUnfilledInstances(tree, context),
2328
2378
  ...detectImageSources(tree, context),
2329
2379
  ...detectUnrelatedScreenLinks(tree, outputKind, context),
2330
2380
  ...detectLinksWithoutDestination(tree, outputKind, context, writtenOf, storedOf),