@ggui-ai/ui-gen 0.11.0 → 0.13.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.
- package/dist/adapters/generation-dispatch.js +180 -18
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/advanced/index.d.ts +1 -1
- package/dist/advanced/index.js +180 -18
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +146 -0
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +7 -7
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.js +27 -11
- package/dist/check/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +1 -1
- package/dist/harness/check/runtime-render/index.js +146 -0
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +126 -0
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.js +7 -7
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/runtime.js +7 -7
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.js +7 -7
- package/dist/harness/types-public.js.map +1 -1
- package/dist/index.js +180 -18
- package/dist/index.js.map +1 -1
- package/dist/provider-adapter-contract.js +1161 -387
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/{render-check-BZoIE8-u.d.ts → render-check-WfOVO9kU.d.ts} +1 -1
- package/package.json +13 -13
- package/src/boilerplate/system-prompt.ts +7 -7
- package/src/check/run-tier0.ts +35 -16
- package/src/harness/check/runtime-render/adapter.ts +28 -0
- 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,
|