@ggui-ai/ui-gen 0.11.0 → 0.12.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 (32) hide show
  1. package/dist/adapters/generation-dispatch.js +180 -18
  2. package/dist/adapters/generation-dispatch.js.map +1 -1
  3. package/dist/advanced/index.d.ts +1 -1
  4. package/dist/advanced/index.js +180 -18
  5. package/dist/advanced/index.js.map +1 -1
  6. package/dist/blueprint-validator.js +146 -0
  7. package/dist/blueprint-validator.js.map +1 -1
  8. package/dist/boilerplate.js +7 -7
  9. package/dist/boilerplate.js.map +1 -1
  10. package/dist/check/index.js +27 -11
  11. package/dist/check/index.js.map +1 -1
  12. package/dist/harness/check/runtime-render/index.d.ts +1 -1
  13. package/dist/harness/check/runtime-render/index.js +146 -0
  14. package/dist/harness/check/runtime-render/index.js.map +1 -1
  15. package/dist/harness/check/runtime-render/render-check-worker.js +126 -0
  16. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  17. package/dist/harness/index.js +7 -7
  18. package/dist/harness/index.js.map +1 -1
  19. package/dist/harness/runtime.js +7 -7
  20. package/dist/harness/runtime.js.map +1 -1
  21. package/dist/harness/types-public.js +7 -7
  22. package/dist/harness/types-public.js.map +1 -1
  23. package/dist/index.js +180 -18
  24. package/dist/index.js.map +1 -1
  25. package/dist/provider-adapter-contract.js +1161 -387
  26. package/dist/provider-adapter-contract.js.map +1 -1
  27. package/dist/{render-check-BZoIE8-u.d.ts → render-check-WfOVO9kU.d.ts} +1 -1
  28. package/package.json +12 -12
  29. package/src/boilerplate/system-prompt.ts +7 -7
  30. package/src/check/run-tier0.ts +35 -16
  31. package/src/harness/check/runtime-render/adapter.ts +28 -0
  32. package/src/harness/check/runtime-render/render-check.ts +224 -1
@@ -871,6 +871,22 @@ async function runRenderCheckInProcess(input) {
871
871
  if (issue) issues.push(issue);
872
872
  }
873
873
  }
874
+ if (input.contract?.actionSpec) {
875
+ for (const [name, entry] of Object.entries(input.contract.actionSpec)) {
876
+ const idField = idLikeRequiredStringField(
877
+ entry.schema
878
+ );
879
+ if (idField === null) continue;
880
+ const issue = await checkSelectionIdentity({
881
+ container,
882
+ probe,
883
+ user,
884
+ actionName: name,
885
+ idField
886
+ });
887
+ if (issue) issues.push(issue);
888
+ }
889
+ }
874
890
  if (input.contract?.propsSpec) {
875
891
  for (const issue of checkPropCoverage({
876
892
  container,
@@ -880,6 +896,48 @@ async function runRenderCheckInProcess(input) {
880
896
  issues.push(issue);
881
897
  }
882
898
  }
899
+ if (input.contract?.propsSpec) {
900
+ const propsSpec5c = input.contract.propsSpec;
901
+ const normalize = (s) => (s ?? "").replace(/\s+/g, " ").trim();
902
+ const baseText = normalize(container.textContent);
903
+ for (const [propName, entry] of Object.entries(propsSpec5c.properties)) {
904
+ if (!entry.required) continue;
905
+ const orig = input.mockupProps[propName];
906
+ if (typeof orig !== "string" && typeof orig !== "number") continue;
907
+ const marker = pickScalarMarker(orig);
908
+ if (marker === null || !baseText.includes(marker)) continue;
909
+ const varied = typeof orig === "string" ? "Zzyzx Qw" : orig + 137731;
910
+ const variedProbe = createProbe();
911
+ const variedConfig = createProbeWireConfig(variedProbe);
912
+ let variedResult;
913
+ try {
914
+ variedResult = render(
915
+ React.createElement(GguiWireProvider, {
916
+ config: variedConfig,
917
+ children: React.createElement(ProbeErrorBoundary, {
918
+ children: React.createElement(Component, {
919
+ ...input.mockupProps,
920
+ [propName]: varied
921
+ })
922
+ })
923
+ })
924
+ );
925
+ } catch {
926
+ continue;
927
+ }
928
+ await flushPromises();
929
+ const variedText = normalize(variedResult.container.textContent);
930
+ variedResult.unmount();
931
+ if (variedText === baseText) {
932
+ issues.push({
933
+ check: "prop-sensitivity",
934
+ outcome: "failed",
935
+ subject: propName,
936
+ reason: `props.${propName} is displayed ('${marker}' appears in the DOM) but changing its value to '${String(varied)}' left the rendered output BYTE-IDENTICAL \u2014 the value is baked into the component instead of derived from the prop. A cached reuse of this blueprint under a different ${propName} would show the original value.`
937
+ });
938
+ }
939
+ }
940
+ }
883
941
  if (input.contract?.streamSpec) {
884
942
  const streamSpec = input.contract.streamSpec;
885
943
  for (const [name] of Object.entries(streamSpec)) {
@@ -1211,6 +1269,74 @@ async function checkActionWiring(input) {
1211
1269
  }
1212
1270
  };
1213
1271
  }
1272
+ function idLikeRequiredStringField(schema) {
1273
+ if (schema === void 0) return null;
1274
+ const properties = schema["properties"];
1275
+ const required = schema["required"];
1276
+ if (properties === null || typeof properties !== "object") return null;
1277
+ if (!Array.isArray(required)) return null;
1278
+ for (const [propName, propSchema] of Object.entries(
1279
+ properties
1280
+ )) {
1281
+ if (!/id$/i.test(propName)) continue;
1282
+ if (!required.includes(propName)) continue;
1283
+ const t = propSchema !== null && typeof propSchema === "object" ? propSchema["type"] : void 0;
1284
+ if (t === "string") return propName;
1285
+ }
1286
+ return null;
1287
+ }
1288
+ async function checkSelectionIdentity(input) {
1289
+ const { container, probe, user, actionName, idField } = input;
1290
+ const clickables = Array.from(
1291
+ container.querySelectorAll('button, [role="button"]')
1292
+ );
1293
+ const byLabel = /* @__PURE__ */ new Map();
1294
+ for (const el of clickables) {
1295
+ const label = (el.textContent ?? "").replace(/\s+/g, " ").trim();
1296
+ if (label.length === 0) continue;
1297
+ const group = byLabel.get(label);
1298
+ if (group) group.push(el);
1299
+ else byLabel.set(label, [el]);
1300
+ }
1301
+ for (const [label, group] of byLabel) {
1302
+ if (group.length < 2) continue;
1303
+ const payloadFor = async (el) => {
1304
+ const before = probe.getFireLog().length;
1305
+ try {
1306
+ await user.click(el);
1307
+ } catch {
1308
+ return void 0;
1309
+ }
1310
+ await flushPromises();
1311
+ const events = probe.getFireLog().slice(before).filter(
1312
+ (e) => e.kind === "action.fired" && e.name === actionName
1313
+ );
1314
+ return events.length > 0 ? events[events.length - 1].payload : void 0;
1315
+ };
1316
+ const first = await payloadFor(group[0]);
1317
+ const second = await payloadFor(group[1]);
1318
+ if (first === void 0 || second === void 0) continue;
1319
+ const idOf = (payload) => {
1320
+ if (payload === null || typeof payload !== "object") return void 0;
1321
+ const v = payload[idField];
1322
+ return typeof v === "string" ? v : void 0;
1323
+ };
1324
+ const firstId = idOf(first);
1325
+ const secondId = idOf(second);
1326
+ if (firstId === void 0 || secondId === void 0) continue;
1327
+ if (firstId === secondId) {
1328
+ return {
1329
+ check: "selection-identity",
1330
+ outcome: "failed",
1331
+ subject: actionName,
1332
+ reason: `Two clickable elements share the visible label '${label}', and clicking EACH fired '${actionName}' with the same ${idField} ('${firstId}') \u2014 the action payload is keyed on the display value, not the element's identity. Distinct cells must send their own ${idField} (the contract requires it), or selecting one "${label}" acts on every "${label}".`,
1333
+ elementHint: describeElement(group[0])
1334
+ };
1335
+ }
1336
+ return null;
1337
+ }
1338
+ return null;
1339
+ }
1214
1340
  async function simulateAndCheck(input) {
1215
1341
  const { container, user, probe, wiringKind, actionName, actionLabel, eventKind } = input;
1216
1342
  const candidates = findCandidateElements(container, wiringKind, actionName, actionLabel);
@@ -1694,6 +1820,26 @@ function toEvalIssue(issue) {
1694
1820
  fix
1695
1821
  };
1696
1822
  }
1823
+ case "selection-identity":
1824
+ return {
1825
+ tier: 0,
1826
+ result,
1827
+ category: "contract",
1828
+ subcategory,
1829
+ severity: "critical",
1830
+ description: `${issue.reason}${elementHint}${diagSuffix}`,
1831
+ fix: `Key the action payload (and any selection state) on the item's ID from the data, never on its display text. Common fix: map over items and pass the item's id into the handler \u2014 onClick={() => ${subject}({ ...payload, id: item.id })} \u2014 and store selection as the id (setSelected(item.id)), not the label.`
1832
+ };
1833
+ case "prop-sensitivity":
1834
+ return {
1835
+ tier: 0,
1836
+ result,
1837
+ category: "contract",
1838
+ subcategory,
1839
+ severity: "critical",
1840
+ description: `${issue.reason}${diagSuffix}`,
1841
+ fix: `Derive the display AND any logic from props.${subject} \u2014 render {props.${subject}} and branch on the prop's value, never on the literal value the request happened to mention (e.g. props.${subject} ?? 'January' followed by January-special-cased logic bakes the request into the component). The contract delivers the value; trust it.`
1842
+ };
1697
1843
  case "prop-coverage":
1698
1844
  return {
1699
1845
  tier: 0,