@ggui-ai/ui-gen 0.28.0 → 0.29.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/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
- package/dist/adapters/base.js +2 -27
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +14 -6
- package/dist/adapters/generation-dispatch.js +40 -19260
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -3561
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/types.js +2 -9
- package/dist/adapters/types.js.map +1 -1
- package/dist/advanced/index.d.ts +13 -12
- package/dist/advanced/index.js +63 -21954
- package/dist/advanced/index.js.map +1 -1
- package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
- package/dist/blueprint-validator.js +6 -2301
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +3 -2
- package/dist/boilerplate.js +6 -1867
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +6 -1923
- package/dist/check/index.js.map +1 -1
- package/dist/chunk-33B3BAWS.js +24 -0
- package/dist/chunk-33B3BAWS.js.map +1 -0
- package/dist/chunk-3HSGA6NI.js +591 -0
- package/dist/chunk-3HSGA6NI.js.map +1 -0
- package/dist/chunk-3NNNGNTO.js +1707 -0
- package/dist/chunk-3NNNGNTO.js.map +1 -0
- package/dist/chunk-3T2HMTON.js +79 -0
- package/dist/chunk-3T2HMTON.js.map +1 -0
- package/dist/chunk-3ZSSDV3L.js +123 -0
- package/dist/chunk-3ZSSDV3L.js.map +1 -0
- package/dist/chunk-4MBR5GA3.js +99 -0
- package/dist/chunk-4MBR5GA3.js.map +1 -0
- package/dist/chunk-56NJFD55.js +337 -0
- package/dist/chunk-56NJFD55.js.map +1 -0
- package/dist/chunk-5AGTY555.js +26 -0
- package/dist/chunk-5AGTY555.js.map +1 -0
- package/dist/chunk-5EJM7EGY.js +115 -0
- package/dist/chunk-5EJM7EGY.js.map +1 -0
- package/dist/chunk-5KXCQLLR.js +529 -0
- package/dist/chunk-5KXCQLLR.js.map +1 -0
- package/dist/chunk-5VBXHVEH.js +171 -0
- package/dist/chunk-5VBXHVEH.js.map +1 -0
- package/dist/chunk-6KDDEING.js +515 -0
- package/dist/chunk-6KDDEING.js.map +1 -0
- package/dist/chunk-6VDRWAP6.js +123 -0
- package/dist/chunk-6VDRWAP6.js.map +1 -0
- package/dist/chunk-732IBFEJ.js +2400 -0
- package/dist/chunk-732IBFEJ.js.map +1 -0
- package/dist/chunk-73AURS3I.js +46 -0
- package/dist/chunk-73AURS3I.js.map +1 -0
- package/dist/chunk-77H5SKLX.js +8 -0
- package/dist/chunk-77H5SKLX.js.map +1 -0
- package/dist/chunk-7QE3AHLE.js +35 -0
- package/dist/chunk-7QE3AHLE.js.map +1 -0
- package/dist/chunk-A375NNWN.js +11 -0
- package/dist/chunk-A375NNWN.js.map +1 -0
- package/dist/chunk-AOU33EFS.js +140 -0
- package/dist/chunk-AOU33EFS.js.map +1 -0
- package/dist/chunk-BA4QYA6E.js +461 -0
- package/dist/chunk-BA4QYA6E.js.map +1 -0
- package/dist/chunk-DBP764GL.js +3 -0
- package/dist/chunk-DBP764GL.js.map +1 -0
- package/dist/chunk-DUU3ZJDQ.js +41 -0
- package/dist/chunk-DUU3ZJDQ.js.map +1 -0
- package/dist/chunk-DXKRBXNU.js +30 -0
- package/dist/chunk-DXKRBXNU.js.map +1 -0
- package/dist/chunk-FB7OEEAH.js +1467 -0
- package/dist/chunk-FB7OEEAH.js.map +1 -0
- package/dist/chunk-FLRNMZV2.js +18 -0
- package/dist/chunk-FLRNMZV2.js.map +1 -0
- package/dist/chunk-FWJIMKFZ.js +2032 -0
- package/dist/chunk-FWJIMKFZ.js.map +1 -0
- package/dist/chunk-FXRHWYIQ.js +163 -0
- package/dist/chunk-FXRHWYIQ.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GAJI7Y3P.js +13 -0
- package/dist/chunk-GAJI7Y3P.js.map +1 -0
- package/dist/chunk-GYINWMPR.js +2052 -0
- package/dist/chunk-GYINWMPR.js.map +1 -0
- package/dist/chunk-HD6ABIK7.js +484 -0
- package/dist/chunk-HD6ABIK7.js.map +1 -0
- package/dist/chunk-HDQVHYO6.js +100 -0
- package/dist/chunk-HDQVHYO6.js.map +1 -0
- package/dist/chunk-IHUT3SIW.js +7 -0
- package/dist/chunk-IHUT3SIW.js.map +1 -0
- package/dist/chunk-JDH5EGNZ.js +126 -0
- package/dist/chunk-JDH5EGNZ.js.map +1 -0
- package/dist/chunk-JMD4RGSV.js +37 -0
- package/dist/chunk-JMD4RGSV.js.map +1 -0
- package/dist/chunk-KJWGZL5B.js +33 -0
- package/dist/chunk-KJWGZL5B.js.map +1 -0
- package/dist/chunk-MKMDQFZ7.js +86 -0
- package/dist/chunk-MKMDQFZ7.js.map +1 -0
- package/dist/chunk-NF6JUZWF.js +85 -0
- package/dist/chunk-NF6JUZWF.js.map +1 -0
- package/dist/chunk-O2YC6XDC.js +1933 -0
- package/dist/chunk-O2YC6XDC.js.map +1 -0
- package/dist/chunk-OVLH5ICV.js +29 -0
- package/dist/chunk-OVLH5ICV.js.map +1 -0
- package/dist/chunk-PBOYHGSD.js +106 -0
- package/dist/chunk-PBOYHGSD.js.map +1 -0
- package/dist/chunk-PJFH7QWA.js +81 -0
- package/dist/chunk-PJFH7QWA.js.map +1 -0
- package/dist/chunk-PUH4CXGZ.js +284 -0
- package/dist/chunk-PUH4CXGZ.js.map +1 -0
- package/dist/chunk-QLXXPGTV.js +18 -0
- package/dist/chunk-QLXXPGTV.js.map +1 -0
- package/dist/chunk-R5GIGPW2.js +2845 -0
- package/dist/chunk-R5GIGPW2.js.map +1 -0
- package/dist/chunk-RH4IUSTO.js +83 -0
- package/dist/chunk-RH4IUSTO.js.map +1 -0
- package/dist/chunk-S2GVXKKY.js +153 -0
- package/dist/chunk-S2GVXKKY.js.map +1 -0
- package/dist/chunk-TVHGKFJS.js +58 -0
- package/dist/chunk-TVHGKFJS.js.map +1 -0
- package/dist/chunk-U7GALWLG.js +66 -0
- package/dist/chunk-U7GALWLG.js.map +1 -0
- package/dist/chunk-VTC263LT.js +256 -0
- package/dist/chunk-VTC263LT.js.map +1 -0
- package/dist/chunk-WEOXFEVE.js +739 -0
- package/dist/chunk-WEOXFEVE.js.map +1 -0
- package/dist/chunk-WYHP46LT.js +28 -0
- package/dist/chunk-WYHP46LT.js.map +1 -0
- package/dist/chunk-XGLO5LGG.js +274 -0
- package/dist/chunk-XGLO5LGG.js.map +1 -0
- package/dist/chunk-XRBCRKA2.js +29 -0
- package/dist/chunk-XRBCRKA2.js.map +1 -0
- package/dist/chunk-XW7ADFFV.js +340 -0
- package/dist/chunk-XW7ADFFV.js.map +1 -0
- package/dist/chunk-YIQ42IUX.js +34 -0
- package/dist/chunk-YIQ42IUX.js.map +1 -0
- package/dist/chunk-YJZTUY7B.js +101 -0
- package/dist/chunk-YJZTUY7B.js.map +1 -0
- package/dist/chunk-Z3CXME3K.js +107 -0
- package/dist/chunk-Z3CXME3K.js.map +1 -0
- package/dist/classifier/index.js +2 -527
- package/dist/classifier/index.js.map +1 -1
- package/dist/client-JBPCXU7W.js +4 -0
- package/dist/client-JBPCXU7W.js.map +1 -0
- package/dist/compose.js +3 -367
- package/dist/compose.js.map +1 -1
- package/dist/cost-tracker-HHGQ4FTW.js +67 -0
- package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
- package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
- package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
- package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
- package/dist/dist-UALN6ZVB.js +543 -0
- package/dist/dist-UALN6ZVB.js.map +1 -0
- package/dist/dupe-break-X6K7QFIS.js +4 -0
- package/dist/dupe-break-X6K7QFIS.js.map +1 -0
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js +6 -1914
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -2
- package/dist/evaluation/axis-checks/registry.js +3 -1844
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +49 -132
- package/dist/evaluation/index.js +302 -6466
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +3 -2
- package/dist/evaluation/types.js +2 -5
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.js +2 -338
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +35 -15
- package/dist/harness/check/runtime-render/index.js +6 -2390
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +23 -5126
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.js +2 -24
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +8 -324
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +6 -5
- package/dist/harness/result-types.js +2 -0
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +22 -5
- package/dist/harness/runtime.js +10 -2313
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -2
- package/dist/harness/types-public.js +16 -2954
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -2
- package/dist/harness/validator-trace-sink.js +2 -26
- package/dist/harness/validator-trace-sink.js.map +1 -1
- package/dist/hash.js +2 -39
- package/dist/hash.js.map +1 -1
- package/dist/hashline-5OKREIRM.js +4 -0
- package/dist/hashline-5OKREIRM.js.map +1 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +57 -22015
- package/dist/index.js.map +1 -1
- package/dist/llm-evaluator-PN3WFCS4.js +653 -0
- package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
- package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
- package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
- package/dist/patch.d.ts +3 -2
- package/dist/patch.js +2 -56
- package/dist/patch.js.map +1 -1
- package/dist/policy-VCYKGSAC.js +5 -0
- package/dist/policy-VCYKGSAC.js.map +1 -0
- package/dist/policy.js +2 -35
- package/dist/policy.js.map +1 -1
- package/dist/primitive-index-2OF6F4TZ.js +131 -0
- package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
- package/dist/primitive-slice-WTMBLYFJ.js +235 -0
- package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
- package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
- package/dist/provider-adapter-contract.js +10407 -14204
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/provider-adapter.js +2 -44
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +89 -1
- package/dist/providers/index.js +115 -44
- package/dist/providers/index.js.map +1 -1
- package/dist/raw-C6KBH5I3.js +141 -0
- package/dist/raw-C6KBH5I3.js.map +1 -0
- package/dist/raw-FXSBXQTZ.js +7 -0
- package/dist/raw-FXSBXQTZ.js.map +1 -0
- package/dist/raw-MURDUPGK.js +8 -0
- package/dist/raw-MURDUPGK.js.map +1 -0
- package/dist/raw-T3GW3UVX.js +8 -0
- package/dist/raw-T3GW3UVX.js.map +1 -0
- package/dist/render-check-host-ZNIS6BJ5.js +5 -0
- package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
- package/dist/sdk-4JNSDNSR.js +6 -0
- package/dist/sdk-4JNSDNSR.js.map +1 -0
- package/dist/sdk-DIVJMHCX.js +7 -0
- package/dist/sdk-DIVJMHCX.js.map +1 -0
- package/dist/sdk-QRJOCRTS.js +7 -0
- package/dist/sdk-QRJOCRTS.js.map +1 -0
- package/dist/tag-balance-CTHCTRJ7.js +123 -0
- package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +3 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +3 -40
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +3 -498
- package/dist/validation/index.js.map +1 -1
- package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
- package/dist/visual-evaluator-TGEXYQTP.js +12 -0
- package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
- package/dist/workflows.d.ts +3 -2
- package/dist/workflows.js +2 -151
- package/dist/workflows.js.map +1 -1
- package/package.json +20 -16
- package/src/adapters/claude/sdk.ts +4 -2
- package/src/adapters/claude/tool-bridge.ts +2 -1
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openai/raw.ts +4 -0
- package/src/adapters/openai/sdk.ts +6 -1
- package/src/adapters/openai/tokens.ts +21 -7
- package/src/adapters/tool-bridge.ts +1 -1
- package/src/adapters/tools.ts +1 -1
- package/src/advanced/feedback.ts +1 -2
- package/src/advanced/generator.ts +4 -3
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/generate.ts +18 -5
- package/src/boilerplate/hard-sections.ts +8 -3
- package/src/boilerplate/react-hooks.ts +56 -0
- package/src/boilerplate/system-prompt.ts +12 -3
- package/src/check/run-tier0.ts +233 -8
- package/src/check/type-checker.ts +128 -16
- package/src/coding-agent/tools.ts +26 -3
- package/src/design-mode.ts +4 -9
- package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
- package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
- package/src/evaluation/axis-checks/checks/tint.ts +197 -0
- package/src/evaluation/axis-checks/helpers.ts +121 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
- package/src/evaluation/criteria/bank.ts +1 -1
- package/src/evaluation/criteria/edge-instruments.ts +14 -0
- package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
- package/src/evaluation/criteria/edge-v2.ts +242 -0
- package/src/evaluation/criteria/resolve.ts +24 -5
- package/src/evaluation/index.ts +3 -5
- package/src/evaluation/mcp-server.ts +0 -50
- package/src/evaluation/types-public.ts +6 -1
- package/src/evaluation/visual-evaluator.ts +161 -30
- package/src/harness/check/runtime-render/index.ts +28 -4
- package/src/harness/check/runtime-render/render-check.ts +20 -21
- package/src/harness/claude-agent-sdk-loader.ts +93 -0
- package/src/harness/claude-code-login-query.ts +1 -1
- package/src/harness/coding/generate-task-runner.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +3 -0
- package/src/harness/coding/run-eval-round.ts +9 -0
- package/src/harness/llm-router.ts +50 -2
- package/src/harness/runtime.ts +17 -0
- package/src/index.ts +9 -0
- package/src/providers/anthropic-tool-call.ts +263 -0
- package/src/providers/index.ts +13 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check.ts +4 -4
- package/src/validation/primitives.ts +2 -1
- package/src/evaluation/loop.ts +0 -218
|
@@ -1,1747 +1,12 @@
|
|
|
1
|
+
import { runRenderCheckInProcess } from '../../../chunk-3NNNGNTO.js';
|
|
2
|
+
import '../../../chunk-KJWGZL5B.js';
|
|
3
|
+
import { formatVerdictLine } from '../../../chunk-FLRNMZV2.js';
|
|
4
|
+
import '../../../chunk-G3PMV62Z.js';
|
|
1
5
|
import { realpathSync } from 'fs';
|
|
2
6
|
import { resolve } from 'path';
|
|
3
7
|
import { fileURLToPath } from 'url';
|
|
4
8
|
import * as esbuild from 'esbuild';
|
|
5
|
-
import { listContractGadgets, HOOK_NAME_RE } from '@ggui-ai/protocol';
|
|
6
|
-
import ts from 'typescript';
|
|
7
|
-
import { createRequire } from 'module';
|
|
8
|
-
import { primeInputs } from '@ggui-ai/ui-visual-tester/prime-inputs';
|
|
9
9
|
|
|
10
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
11
|
-
var __esm = (fn, res) => function __init() {
|
|
12
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
// src/internal/verdict-line.ts
|
|
16
|
-
function formatVerdictLine(value) {
|
|
17
|
-
return `${VERDICT_PREFIX} ${JSON.stringify(value)}
|
|
18
|
-
`;
|
|
19
|
-
}
|
|
20
|
-
var VERDICT_PREFIX;
|
|
21
|
-
var init_verdict_line = __esm({
|
|
22
|
-
"src/internal/verdict-line.ts"() {
|
|
23
|
-
VERDICT_PREFIX = "@@ggui-render-check-verdict@@";
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// src/harness/check/runtime-render/probe.ts
|
|
28
|
-
function makeInternals() {
|
|
29
|
-
return {
|
|
30
|
-
fireLog: [],
|
|
31
|
-
streamHandlers: /* @__PURE__ */ new Map(),
|
|
32
|
-
registeredStreams: /* @__PURE__ */ new Set()
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
function createProbe() {
|
|
36
|
-
let internals = makeInternals();
|
|
37
|
-
const probe = {
|
|
38
|
-
emitStream: (eventName, payload) => {
|
|
39
|
-
const handlers = internals.streamHandlers.get(eventName);
|
|
40
|
-
if (!handlers) return;
|
|
41
|
-
for (const handler of handlers) handler(payload);
|
|
42
|
-
},
|
|
43
|
-
getFireLog: () => internals.fireLog.slice(),
|
|
44
|
-
getRegistered: () => ({
|
|
45
|
-
streams: Array.from(internals.registeredStreams)
|
|
46
|
-
}),
|
|
47
|
-
fired: (actionName) => internals.fireLog.some((e) => e.kind === "action.fired" && e.name === actionName),
|
|
48
|
-
installPostMessageSpy: () => installPostMessageSpy(probe),
|
|
49
|
-
reset: () => {
|
|
50
|
-
internals = makeInternals();
|
|
51
|
-
probe.__internals = internals;
|
|
52
|
-
},
|
|
53
|
-
__internals: internals
|
|
54
|
-
};
|
|
55
|
-
return probe;
|
|
56
|
-
}
|
|
57
|
-
function installPostMessageSpy(probe) {
|
|
58
|
-
const parent = globalThis.window?.parent;
|
|
59
|
-
if (!parent || typeof parent.postMessage !== "function") {
|
|
60
|
-
return () => {
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
const original = parent.postMessage;
|
|
64
|
-
const spy = (...args) => {
|
|
65
|
-
const envelope = args[0];
|
|
66
|
-
recordEnvelope(probe.__internals, envelope);
|
|
67
|
-
return original.apply(parent, args);
|
|
68
|
-
};
|
|
69
|
-
try {
|
|
70
|
-
Object.defineProperty(parent, "postMessage", {
|
|
71
|
-
value: spy,
|
|
72
|
-
configurable: true,
|
|
73
|
-
writable: true
|
|
74
|
-
});
|
|
75
|
-
} catch {
|
|
76
|
-
return () => {
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
return () => {
|
|
80
|
-
try {
|
|
81
|
-
Object.defineProperty(parent, "postMessage", {
|
|
82
|
-
value: original,
|
|
83
|
-
configurable: true,
|
|
84
|
-
writable: true
|
|
85
|
-
});
|
|
86
|
-
} catch {
|
|
87
|
-
}
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function recordEnvelope(internals, envelope) {
|
|
91
|
-
if (!envelope || typeof envelope !== "object") return;
|
|
92
|
-
const e = envelope;
|
|
93
|
-
if (typeof e.method !== "string") return;
|
|
94
|
-
const params = e.params ?? {};
|
|
95
|
-
switch (e.method) {
|
|
96
|
-
case "ui/open-link": {
|
|
97
|
-
const url = typeof params.url === "string" ? params.url : "";
|
|
98
|
-
internals.fireLog.push({
|
|
99
|
-
kind: "link.opened",
|
|
100
|
-
url,
|
|
101
|
-
ts: Date.now()
|
|
102
|
-
});
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
case "ui/request-display-mode": {
|
|
106
|
-
const mode = typeof params.mode === "string" ? params.mode : "";
|
|
107
|
-
internals.fireLog.push({
|
|
108
|
-
kind: "displayMode.requested",
|
|
109
|
-
mode,
|
|
110
|
-
ts: Date.now()
|
|
111
|
-
});
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
case "tools/call": {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
default:
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function createProbeWireConfig(probe) {
|
|
122
|
-
return {
|
|
123
|
-
app: {
|
|
124
|
-
appId: "probe-app",
|
|
125
|
-
appName: "Probe",
|
|
126
|
-
appDescription: "Eval-time probe wire config"
|
|
127
|
-
},
|
|
128
|
-
render: {
|
|
129
|
-
sessionId: "probe-render",
|
|
130
|
-
isConnected: true
|
|
131
|
-
},
|
|
132
|
-
auth: {
|
|
133
|
-
isAuthenticated: false
|
|
134
|
-
},
|
|
135
|
-
dispatch: (actionName, data) => {
|
|
136
|
-
probe.__internals.fireLog.push({
|
|
137
|
-
kind: "action.fired",
|
|
138
|
-
name: actionName,
|
|
139
|
-
payload: data,
|
|
140
|
-
ts: Date.now()
|
|
141
|
-
});
|
|
142
|
-
},
|
|
143
|
-
subscribe: (eventType, handler) => {
|
|
144
|
-
probe.__internals.registeredStreams.add(eventType);
|
|
145
|
-
let set = probe.__internals.streamHandlers.get(eventType);
|
|
146
|
-
if (!set) {
|
|
147
|
-
set = /* @__PURE__ */ new Set();
|
|
148
|
-
probe.__internals.streamHandlers.set(eventType, set);
|
|
149
|
-
}
|
|
150
|
-
const wrapped = handler;
|
|
151
|
-
set.add(wrapped);
|
|
152
|
-
return () => {
|
|
153
|
-
set.delete(wrapped);
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
// The WireConfig surface is dispatch + subscribe only. Capability
|
|
157
|
-
// hooks live in `@ggui-ai/gadgets` and own their own lifecycle —
|
|
158
|
-
// they never route through WireConfig.
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// src/internal/evaluate-component-source.ts
|
|
163
|
-
async function evaluateComponentSource(sourceCode, sandboxRequire) {
|
|
164
|
-
const esbuild2 = await import('esbuild');
|
|
165
|
-
const { Script } = await import('vm');
|
|
166
|
-
const cjsResult = await esbuild2.transform(sourceCode, {
|
|
167
|
-
loader: "tsx",
|
|
168
|
-
target: "es2020",
|
|
169
|
-
format: "cjs",
|
|
170
|
-
jsx: "automatic",
|
|
171
|
-
jsxImportSource: "react",
|
|
172
|
-
sourcefile: "Component.tsx"
|
|
173
|
-
});
|
|
174
|
-
const sandboxModule = { exports: {} };
|
|
175
|
-
const wrappedCode = `(function(require, exports, module) {
|
|
176
|
-
${cjsResult.code}
|
|
177
|
-
})`;
|
|
178
|
-
const script = new Script(wrappedCode, { filename: "Component.cjs" });
|
|
179
|
-
const fn = script.runInThisContext();
|
|
180
|
-
fn(sandboxRequire, sandboxModule.exports, sandboxModule);
|
|
181
|
-
return sandboxModule.exports;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// src/harness/check/runtime-render/load-component.ts
|
|
185
|
-
async function loadComponent(input) {
|
|
186
|
-
const { sourceCode, moduleResolutions = {} } = input;
|
|
187
|
-
const { createRequire: createRequire2 } = await import('module');
|
|
188
|
-
const require_ = createRequire2(import.meta.url);
|
|
189
|
-
const sandboxRequire = (id) => {
|
|
190
|
-
if (id in moduleResolutions) return moduleResolutions[id];
|
|
191
|
-
if (id === "react/jsx-runtime" || id === "react/jsx-dev-runtime") {
|
|
192
|
-
return require_("react/jsx-runtime");
|
|
193
|
-
}
|
|
194
|
-
if (id === "react") return require_("react");
|
|
195
|
-
if (id === "react-dom" || id.startsWith("react-dom/")) {
|
|
196
|
-
return require_(id);
|
|
197
|
-
}
|
|
198
|
-
if (id === "@ggui-ai/wire") return require_("@ggui-ai/wire");
|
|
199
|
-
if (id.startsWith("@ggui-ai/design")) {
|
|
200
|
-
try {
|
|
201
|
-
return require_(id);
|
|
202
|
-
} catch {
|
|
203
|
-
throw new Error(
|
|
204
|
-
`Required dependency not available in render-check sandbox: ${id}. Install @ggui-ai/design or run render-check from an environment that has it.`
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
if (id === "@ggui-ai/gadgets") {
|
|
209
|
-
try {
|
|
210
|
-
return require_("@ggui-ai/gadgets");
|
|
211
|
-
} catch {
|
|
212
|
-
throw new Error(
|
|
213
|
-
`Required dependency not available in render-check sandbox: ${id}. Install @ggui-ai/gadgets or pass it through moduleResolutions.`
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
throw new Error(`Import not allowed in render-check sandbox: ${id}`);
|
|
218
|
-
};
|
|
219
|
-
const exportsRecord = await evaluateComponentSource(sourceCode, sandboxRequire);
|
|
220
|
-
const candidate = exportsRecord.default;
|
|
221
|
-
if (typeof candidate !== "function") {
|
|
222
|
-
throw new Error("Compiled component has no default-exported function");
|
|
223
|
-
}
|
|
224
|
-
return { Component: candidate };
|
|
225
|
-
}
|
|
226
|
-
var NATIVE_CLICK_PROPS = /* @__PURE__ */ new Set(["onClick"]);
|
|
227
|
-
var NATIVE_SUBMIT_PROPS = /* @__PURE__ */ new Set(["onSubmit"]);
|
|
228
|
-
var NATIVE_CHANGE_PROPS = /* @__PURE__ */ new Set(["onChange"]);
|
|
229
|
-
var NATIVE_KEY_PROPS = /* @__PURE__ */ new Set(["onKeyDown", "onKeyUp", "onKeyPress"]);
|
|
230
|
-
var HOST_CLICK_TAGS = /* @__PURE__ */ new Set(["button", "a", "div", "span", "li", "input"]);
|
|
231
|
-
var HOST_CHANGE_TAGS = /* @__PURE__ */ new Set(["select", "input", "textarea"]);
|
|
232
|
-
function findWiring(input) {
|
|
233
|
-
const { sourceCode, hookName, hookArg } = input;
|
|
234
|
-
const sf = ts.createSourceFile("Component.tsx", sourceCode, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
235
|
-
const hookBindings = findHookBindings(sf, hookName, hookArg);
|
|
236
|
-
if (hookBindings.length === 0) {
|
|
237
|
-
return {
|
|
238
|
-
kind: "missing",
|
|
239
|
-
reason: `Hook ${hookName}('${hookArg}') is not destructured in the component`,
|
|
240
|
-
observedJsxElements: [],
|
|
241
|
-
observedNativeProps: [],
|
|
242
|
-
observedCustomProps: []
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
const callableNames = expandAliases(sf, hookBindings);
|
|
246
|
-
const observedJsxElements = [];
|
|
247
|
-
const observedNativeProps = [];
|
|
248
|
-
const observedCustomProps = [];
|
|
249
|
-
let sawClickOnHost = false;
|
|
250
|
-
let sawSubmitOnForm = false;
|
|
251
|
-
let sawChangeOnNativeInput = false;
|
|
252
|
-
let sawKeyOnAnything = false;
|
|
253
|
-
let sawNonNativeProp = false;
|
|
254
|
-
const customPropElements = [];
|
|
255
|
-
const submitButton = { found: false };
|
|
256
|
-
function visit(node) {
|
|
257
|
-
if (ts.isJsxAttribute(node) && node.initializer) {
|
|
258
|
-
const attrName = node.name.getText(sf);
|
|
259
|
-
const initRefs = findReferencedNames(node.initializer, callableNames);
|
|
260
|
-
if (initRefs.size > 0) {
|
|
261
|
-
const parent = node.parent.parent;
|
|
262
|
-
const tagName = getTagName(parent);
|
|
263
|
-
if (tagName) observedJsxElements.push(tagName);
|
|
264
|
-
const isHostTag = isHostElementTag(tagName ?? "");
|
|
265
|
-
if (NATIVE_CLICK_PROPS.has(attrName)) {
|
|
266
|
-
observedNativeProps.push(attrName);
|
|
267
|
-
if (isHostTag && (HOST_CLICK_TAGS.has((tagName ?? "").toLowerCase()) || tagName === "button")) {
|
|
268
|
-
sawClickOnHost = true;
|
|
269
|
-
} else {
|
|
270
|
-
sawClickOnHost = true;
|
|
271
|
-
}
|
|
272
|
-
} else if (NATIVE_SUBMIT_PROPS.has(attrName)) {
|
|
273
|
-
observedNativeProps.push(attrName);
|
|
274
|
-
if ((tagName ?? "").toLowerCase() === "form") sawSubmitOnForm = true;
|
|
275
|
-
else sawSubmitOnForm = true;
|
|
276
|
-
} else if (NATIVE_CHANGE_PROPS.has(attrName)) {
|
|
277
|
-
observedNativeProps.push(attrName);
|
|
278
|
-
if (HOST_CHANGE_TAGS.has((tagName ?? "").toLowerCase())) {
|
|
279
|
-
sawChangeOnNativeInput = true;
|
|
280
|
-
} else {
|
|
281
|
-
observedCustomProps.push(attrName);
|
|
282
|
-
sawNonNativeProp = true;
|
|
283
|
-
if (tagName) customPropElements.push(tagName);
|
|
284
|
-
}
|
|
285
|
-
} else if (NATIVE_KEY_PROPS.has(attrName)) {
|
|
286
|
-
observedNativeProps.push(attrName);
|
|
287
|
-
sawKeyOnAnything = true;
|
|
288
|
-
} else {
|
|
289
|
-
observedCustomProps.push(attrName);
|
|
290
|
-
sawNonNativeProp = true;
|
|
291
|
-
if (tagName) customPropElements.push(tagName);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
if (ts.isJsxOpeningLikeElement(node)) {
|
|
296
|
-
const tag = getTagName(node);
|
|
297
|
-
if ((tag ?? "").toLowerCase() === "button") {
|
|
298
|
-
const typeAttr = node.attributes.properties.find(
|
|
299
|
-
(p) => ts.isJsxAttribute(p) && p.name.getText(sf) === "type"
|
|
300
|
-
);
|
|
301
|
-
if (typeAttr && ts.isJsxAttribute(typeAttr) && typeAttr.initializer) {
|
|
302
|
-
const v = typeAttr.initializer.getText(sf).toLowerCase();
|
|
303
|
-
if (v.includes("submit")) submitButton.found = true;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
ts.forEachChild(node, visit);
|
|
308
|
-
}
|
|
309
|
-
visit(sf);
|
|
310
|
-
if (sawClickOnHost) {
|
|
311
|
-
return wrapDetection("click", observedJsxElements, observedNativeProps, observedCustomProps);
|
|
312
|
-
}
|
|
313
|
-
if (sawSubmitOnForm || submitButton.found) {
|
|
314
|
-
return wrapDetection("submit", observedJsxElements, observedNativeProps, observedCustomProps);
|
|
315
|
-
}
|
|
316
|
-
if (sawChangeOnNativeInput) {
|
|
317
|
-
return wrapDetection("change", observedJsxElements, observedNativeProps, observedCustomProps);
|
|
318
|
-
}
|
|
319
|
-
if (sawKeyOnAnything) {
|
|
320
|
-
return wrapDetection("keyboard-enter", observedJsxElements, observedNativeProps, observedCustomProps);
|
|
321
|
-
}
|
|
322
|
-
if (sawNonNativeProp) {
|
|
323
|
-
const props = Array.from(new Set(observedCustomProps)).join(", ");
|
|
324
|
-
const tags = Array.from(new Set(customPropElements)).join(", ");
|
|
325
|
-
return {
|
|
326
|
-
kind: "unverified",
|
|
327
|
-
reason: `Source indicates non-click or non-native wiring; static probe did not verify execution deterministically. Callback flows into ${props} on <${tags}>.`,
|
|
328
|
-
observedJsxElements: dedupe(observedJsxElements),
|
|
329
|
-
observedNativeProps: dedupe(observedNativeProps),
|
|
330
|
-
observedCustomProps: dedupe(observedCustomProps)
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
return {
|
|
334
|
-
kind: "missing",
|
|
335
|
-
reason: `Hook ${hookName}('${hookArg}') is destructured but never referenced in any JSX attribute`,
|
|
336
|
-
observedJsxElements: [],
|
|
337
|
-
observedNativeProps: [],
|
|
338
|
-
observedCustomProps: []
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
function findHookBindings(sf, hookName, hookArg) {
|
|
342
|
-
const bindings = [];
|
|
343
|
-
function visit(node) {
|
|
344
|
-
if (ts.isVariableDeclaration(node) && node.initializer && ts.isCallExpression(node.initializer)) {
|
|
345
|
-
const call = node.initializer;
|
|
346
|
-
const callee = call.expression.getText(sf);
|
|
347
|
-
if (callee !== hookName) {
|
|
348
|
-
ts.forEachChild(node, visit);
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const firstArg = call.arguments[0];
|
|
352
|
-
if (!firstArg || !ts.isStringLiteral(firstArg)) {
|
|
353
|
-
ts.forEachChild(node, visit);
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
if (firstArg.text !== hookArg) {
|
|
357
|
-
ts.forEachChild(node, visit);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (ts.isIdentifier(node.name)) {
|
|
361
|
-
bindings.push(node.name.text);
|
|
362
|
-
} else if (ts.isObjectBindingPattern(node.name)) {
|
|
363
|
-
for (const elem of node.name.elements) {
|
|
364
|
-
const target = elem.name;
|
|
365
|
-
if (ts.isIdentifier(target)) bindings.push(target.text);
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
ts.forEachChild(node, visit);
|
|
370
|
-
}
|
|
371
|
-
visit(sf);
|
|
372
|
-
return bindings;
|
|
373
|
-
}
|
|
374
|
-
function expandAliases(sf, bindings) {
|
|
375
|
-
const all = new Set(bindings);
|
|
376
|
-
function visit(node) {
|
|
377
|
-
if (ts.isVariableDeclaration(node) && node.initializer && ts.isIdentifier(node.name)) {
|
|
378
|
-
const newName = node.name.text;
|
|
379
|
-
const init = node.initializer;
|
|
380
|
-
if (ts.isIdentifier(init) && all.has(init.text)) {
|
|
381
|
-
all.add(newName);
|
|
382
|
-
}
|
|
383
|
-
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
|
|
384
|
-
if (bodyReferences(init.body, all)) {
|
|
385
|
-
all.add(newName);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (ts.isCallExpression(init)) {
|
|
389
|
-
if (bodyReferences(init, all)) {
|
|
390
|
-
all.add(newName);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
ts.forEachChild(node, visit);
|
|
395
|
-
}
|
|
396
|
-
visit(sf);
|
|
397
|
-
return all;
|
|
398
|
-
}
|
|
399
|
-
function bodyReferences(body, names) {
|
|
400
|
-
let found = false;
|
|
401
|
-
function walk(n) {
|
|
402
|
-
if (found) return;
|
|
403
|
-
if (ts.isIdentifier(n) && names.has(n.text)) {
|
|
404
|
-
found = true;
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
ts.forEachChild(n, walk);
|
|
408
|
-
}
|
|
409
|
-
walk(body);
|
|
410
|
-
return found;
|
|
411
|
-
}
|
|
412
|
-
function findReferencedNames(node, names) {
|
|
413
|
-
const found = /* @__PURE__ */ new Set();
|
|
414
|
-
function walk(n) {
|
|
415
|
-
if (ts.isIdentifier(n) && names.has(n.text)) {
|
|
416
|
-
found.add(n.text);
|
|
417
|
-
}
|
|
418
|
-
ts.forEachChild(n, walk);
|
|
419
|
-
}
|
|
420
|
-
walk(node);
|
|
421
|
-
return found;
|
|
422
|
-
}
|
|
423
|
-
function getTagName(el) {
|
|
424
|
-
if (ts.isJsxOpeningLikeElement(el)) {
|
|
425
|
-
return el.tagName.getText();
|
|
426
|
-
}
|
|
427
|
-
return null;
|
|
428
|
-
}
|
|
429
|
-
function isHostElementTag(name) {
|
|
430
|
-
if (!name) return false;
|
|
431
|
-
return name[0] === name[0].toLowerCase();
|
|
432
|
-
}
|
|
433
|
-
function wrapDetection(kind, jsx, native, custom) {
|
|
434
|
-
return {
|
|
435
|
-
kind,
|
|
436
|
-
observedJsxElements: dedupe(jsx),
|
|
437
|
-
observedNativeProps: dedupe(native),
|
|
438
|
-
observedCustomProps: dedupe(custom)
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
function dedupe(arr) {
|
|
442
|
-
return Array.from(new Set(arr));
|
|
443
|
-
}
|
|
444
|
-
var installed = false;
|
|
445
|
-
function installProductionActShim() {
|
|
446
|
-
if (installed) return;
|
|
447
|
-
installed = true;
|
|
448
|
-
const require_ = createRequire(import.meta.url);
|
|
449
|
-
let reactCjs;
|
|
450
|
-
try {
|
|
451
|
-
reactCjs = require_("react");
|
|
452
|
-
} catch {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
if (typeof reactCjs.act === "function") {
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
try {
|
|
459
|
-
const testUtils = require_("react-dom/test-utils");
|
|
460
|
-
const reactDom = require_("react-dom");
|
|
461
|
-
testUtils.act = createFlushSyncAct(reactDom.flushSync);
|
|
462
|
-
} catch {
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
function createFlushSyncAct(flushSync) {
|
|
466
|
-
return (callback) => {
|
|
467
|
-
let callbackResult;
|
|
468
|
-
flushSync(() => {
|
|
469
|
-
callbackResult = callback();
|
|
470
|
-
});
|
|
471
|
-
if (isThenable(callbackResult)) {
|
|
472
|
-
const pending = callbackResult;
|
|
473
|
-
return new Promise((resolve, reject) => {
|
|
474
|
-
pending.then(() => resolve(), reject);
|
|
475
|
-
});
|
|
476
|
-
}
|
|
477
|
-
return new Promise((resolve) => {
|
|
478
|
-
queueMicrotask(resolve);
|
|
479
|
-
});
|
|
480
|
-
};
|
|
481
|
-
}
|
|
482
|
-
function isThenable(value) {
|
|
483
|
-
return typeof value === "object" && value !== null && typeof value.then === "function";
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// src/internal/open-record.ts
|
|
487
|
-
function openRecord(o) {
|
|
488
|
-
return o;
|
|
489
|
-
}
|
|
490
|
-
function hostGlobals() {
|
|
491
|
-
return openRecord(globalThis);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
// src/harness/check/runtime-render/host-boundary.ts
|
|
495
|
-
function getMethod(o, name) {
|
|
496
|
-
if (typeof o !== "object" && typeof o !== "function") return void 0;
|
|
497
|
-
if (o === null) return void 0;
|
|
498
|
-
const v = openRecord(o)[name];
|
|
499
|
-
if (typeof v !== "function") return void 0;
|
|
500
|
-
return v.bind(o);
|
|
501
|
-
}
|
|
502
|
-
function toMinimalElement(v) {
|
|
503
|
-
if (typeof v === "object" && v !== null && typeof openRecord(v).tagName === "string" && typeof openRecord(v).getAttribute === "function" && typeof openRecord(v).querySelectorAll === "function") {
|
|
504
|
-
return v;
|
|
505
|
-
}
|
|
506
|
-
throw new Error(
|
|
507
|
-
"[runtime-render] host value lacks the element surface (tagName/getAttribute/querySelectorAll)"
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
function buildClickUser(userEventModule, doc) {
|
|
511
|
-
const setup = getMethod(userEventModule, "setup");
|
|
512
|
-
const instance = setup ? setup({ document: doc }) : userEventModule;
|
|
513
|
-
const click = getMethod(instance, "click");
|
|
514
|
-
if (!click) {
|
|
515
|
-
throw new Error(
|
|
516
|
-
"[runtime-render] user-event module exposes neither setup().click nor click()"
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
return {
|
|
520
|
-
click: async (el) => {
|
|
521
|
-
await click(el);
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
}
|
|
525
|
-
function toEventDispatcher(el) {
|
|
526
|
-
const dispatch = getMethod(el, "dispatchEvent");
|
|
527
|
-
if (!dispatch) {
|
|
528
|
-
throw new Error(
|
|
529
|
-
`[runtime-render] element <${el.tagName.toLowerCase()}> has no dispatchEvent`
|
|
530
|
-
);
|
|
531
|
-
}
|
|
532
|
-
return {
|
|
533
|
-
dispatchEvent: (ev) => {
|
|
534
|
-
dispatch(ev);
|
|
535
|
-
}
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
function editableSurface(el) {
|
|
539
|
-
return el;
|
|
540
|
-
}
|
|
541
|
-
function parentLink(node) {
|
|
542
|
-
return node;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// src/harness/check/runtime-render/probe-pending.ts
|
|
546
|
-
function createProbePendingSource() {
|
|
547
|
-
const pending = /* @__PURE__ */ new Set();
|
|
548
|
-
const listeners = /* @__PURE__ */ new Set();
|
|
549
|
-
const notify = () => {
|
|
550
|
-
for (const listener of [...listeners]) listener();
|
|
551
|
-
};
|
|
552
|
-
return {
|
|
553
|
-
isPending: (actionName) => pending.has(actionName),
|
|
554
|
-
subscribe: (listener) => {
|
|
555
|
-
listeners.add(listener);
|
|
556
|
-
return () => {
|
|
557
|
-
listeners.delete(listener);
|
|
558
|
-
};
|
|
559
|
-
},
|
|
560
|
-
mark: (actionName) => {
|
|
561
|
-
pending.add(actionName);
|
|
562
|
-
notify();
|
|
563
|
-
},
|
|
564
|
-
clear: () => {
|
|
565
|
-
if (pending.size === 0) return;
|
|
566
|
-
pending.clear();
|
|
567
|
-
notify();
|
|
568
|
-
}
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
function lookOf(el) {
|
|
572
|
-
return {
|
|
573
|
-
connected: el.isConnected !== false,
|
|
574
|
-
// React renders `disabled={true}` as `disabled=""`, so presence is the test.
|
|
575
|
-
disabled: el.getAttribute("disabled") !== null || el.getAttribute("aria-disabled") === "true",
|
|
576
|
-
ariaBusy: el.getAttribute("aria-busy") === "true",
|
|
577
|
-
text: el.tagName?.toLowerCase() === "input" ? String(typeof el.value === "string" ? el.value : el.getAttribute("value") ?? "") : el.textContent ?? ""
|
|
578
|
-
};
|
|
579
|
-
}
|
|
580
|
-
async function settlesWithin(ms, work) {
|
|
581
|
-
let timer;
|
|
582
|
-
const expired = new Promise((resolve) => {
|
|
583
|
-
timer = setTimeout(() => resolve(false), Math.max(0, ms));
|
|
584
|
-
});
|
|
585
|
-
try {
|
|
586
|
-
return await Promise.race([work.then(() => true), expired]);
|
|
587
|
-
} finally {
|
|
588
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
function lookChanged(before, after) {
|
|
592
|
-
if (before.connected && !after.connected) return true;
|
|
593
|
-
if (!before.disabled && after.disabled) return true;
|
|
594
|
-
if (!before.ariaBusy && after.ariaBusy) return true;
|
|
595
|
-
return before.text.trim() !== after.text.trim();
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
// src/harness/check/runtime-render/walk.ts
|
|
599
|
-
var LAST_PRESSED_KEPT = 4;
|
|
600
|
-
function isDisabled(el) {
|
|
601
|
-
return el.getAttribute("disabled") !== null || el.getAttribute("aria-disabled") === "true";
|
|
602
|
-
}
|
|
603
|
-
function labelOf(el) {
|
|
604
|
-
const named = el.getAttribute("aria-label") ?? el.getAttribute("placeholder");
|
|
605
|
-
return (named ?? el.textContent ?? "").replace(/\s+/g, " ").trim().slice(0, 60);
|
|
606
|
-
}
|
|
607
|
-
function screenFingerprint(container) {
|
|
608
|
-
const parts = [];
|
|
609
|
-
for (const el of container.querySelectorAll(
|
|
610
|
-
'button, [role="button"], a[href], input, select, textarea, form'
|
|
611
|
-
)) {
|
|
612
|
-
parts.push(
|
|
613
|
-
[el.tagName.toLowerCase(), el.getAttribute("type") ?? "", labelOf(el), isDisabled(el) ? "disabled" : ""].join("|")
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
return parts.join("\n");
|
|
617
|
-
}
|
|
618
|
-
function keyed(controls, describe) {
|
|
619
|
-
const seen = /* @__PURE__ */ new Map();
|
|
620
|
-
const out = /* @__PURE__ */ new Map();
|
|
621
|
-
for (const c of controls) {
|
|
622
|
-
const d = describe(c.element);
|
|
623
|
-
const n = seen.get(d) ?? 0;
|
|
624
|
-
seen.set(d, n + 1);
|
|
625
|
-
out.set(`${d}#${n}`, c);
|
|
626
|
-
}
|
|
627
|
-
return out;
|
|
628
|
-
}
|
|
629
|
-
function firstStepToUnpressed(screens, from) {
|
|
630
|
-
const firstStep = /* @__PURE__ */ new Map([[from, void 0]]);
|
|
631
|
-
const queue = [from];
|
|
632
|
-
while (queue.length > 0) {
|
|
633
|
-
const at = queue.shift();
|
|
634
|
-
const screen = screens.get(at);
|
|
635
|
-
if (screen === void 0) continue;
|
|
636
|
-
if (at !== from && screen.order.some((k) => !screen.led.has(k))) return firstStep.get(at);
|
|
637
|
-
for (const [key, to] of screen.led) {
|
|
638
|
-
if (to === at || firstStep.has(to)) continue;
|
|
639
|
-
firstStep.set(to, firstStep.get(at) ?? key);
|
|
640
|
-
queue.push(to);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return void 0;
|
|
644
|
-
}
|
|
645
|
-
async function walkToAction(input) {
|
|
646
|
-
const lastPressed = [];
|
|
647
|
-
const screenTexts = [];
|
|
648
|
-
const screens = /* @__PURE__ */ new Map();
|
|
649
|
-
let presses = 0;
|
|
650
|
-
let namedControlSeen = false;
|
|
651
|
-
const outcome = (stoppedBy, extra = {}) => ({
|
|
652
|
-
fired: false,
|
|
653
|
-
presses,
|
|
654
|
-
screens: screens.size,
|
|
655
|
-
stoppedBy,
|
|
656
|
-
namedControlSeen,
|
|
657
|
-
lastPressed: [...lastPressed],
|
|
658
|
-
screenTexts: [...screenTexts],
|
|
659
|
-
...extra
|
|
660
|
-
});
|
|
661
|
-
const mounted = await input.mount();
|
|
662
|
-
if (mounted === void 0) return outcome("no-mount");
|
|
663
|
-
const { container } = mounted;
|
|
664
|
-
const arrive = () => {
|
|
665
|
-
mounted.prime();
|
|
666
|
-
const controls = keyed(input.controls(container), input.describe);
|
|
667
|
-
if ([...controls.values()].some((c) => c.namesAction)) namedControlSeen = true;
|
|
668
|
-
const id = screenFingerprint(container);
|
|
669
|
-
if (!screens.has(id)) {
|
|
670
|
-
screens.set(id, { order: [...controls.keys()], led: /* @__PURE__ */ new Map() });
|
|
671
|
-
screenTexts.push(container.textContent ?? "");
|
|
672
|
-
}
|
|
673
|
-
return { id, controls };
|
|
674
|
-
};
|
|
675
|
-
let here = arrive();
|
|
676
|
-
const first = here.id;
|
|
677
|
-
for (; ; ) {
|
|
678
|
-
const screen = screens.get(here.id);
|
|
679
|
-
const onOpenFirstScreen = here.id === first && screen.order.some((k) => !screen.led.has(k));
|
|
680
|
-
if (!onOpenFirstScreen) {
|
|
681
|
-
if (presses >= input.maxPresses) return outcome("press-cap");
|
|
682
|
-
if (Date.now() >= input.stopAt) return outcome("deadline");
|
|
683
|
-
}
|
|
684
|
-
const key = screen.order.find((k) => !screen.led.has(k)) ?? firstStepToUnpressed(screens, here.id);
|
|
685
|
-
if (key === void 0) return outcome("explored");
|
|
686
|
-
const control = here.controls.get(key);
|
|
687
|
-
if (control === void 0) {
|
|
688
|
-
screen.led.set(key, here.id);
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
|
-
presses += 1;
|
|
692
|
-
lastPressed.push(input.describe(control.element));
|
|
693
|
-
if (lastPressed.length > LAST_PRESSED_KEPT) lastPressed.shift();
|
|
694
|
-
if (await input.press(control)) {
|
|
695
|
-
return outcome("dispatched", { fired: true, element: control.element, container });
|
|
696
|
-
}
|
|
697
|
-
here = arrive();
|
|
698
|
-
if (!screen.led.has(key)) screen.led.set(key, here.id);
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
// src/harness/check/runtime-render/render-check.ts
|
|
703
|
-
var PENDING_PHASE_RESERVE_MS = 5e3;
|
|
704
|
-
var WALK_MAX_PRESSES = 60;
|
|
705
|
-
var WALK_ACTION_CAP_MS = 3e3;
|
|
706
|
-
var WALK_DEADLINE_MARGIN_MS = 2e3;
|
|
707
|
-
async function runRenderCheckInProcess(input) {
|
|
708
|
-
const t0 = Date.now();
|
|
709
|
-
const issues = [];
|
|
710
|
-
const teardown = await setupHappyDom();
|
|
711
|
-
let uninstallSpy;
|
|
712
|
-
let uninstallGadgetRegistry;
|
|
713
|
-
try {
|
|
714
|
-
const React = await import('react');
|
|
715
|
-
const ReactJsxRuntime = await import('react/jsx-runtime');
|
|
716
|
-
const Wire = await import('@ggui-ai/wire');
|
|
717
|
-
const { GguiWireProvider } = Wire;
|
|
718
|
-
const moduleResolutions = {
|
|
719
|
-
react: React,
|
|
720
|
-
"react/jsx-runtime": ReactJsxRuntime,
|
|
721
|
-
"@ggui-ai/wire": Wire
|
|
722
|
-
};
|
|
723
|
-
const designSpecifiers = [
|
|
724
|
-
"@ggui-ai/design",
|
|
725
|
-
"@ggui-ai/design/primitives",
|
|
726
|
-
"@ggui-ai/design/components",
|
|
727
|
-
"@ggui-ai/design/compositions",
|
|
728
|
-
"@ggui-ai/design/interact"
|
|
729
|
-
];
|
|
730
|
-
for (const id of designSpecifiers) {
|
|
731
|
-
try {
|
|
732
|
-
moduleResolutions[id] = await import(id);
|
|
733
|
-
} catch {
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
try {
|
|
737
|
-
moduleResolutions["@ggui-ai/gadgets"] = await import('@ggui-ai/gadgets');
|
|
738
|
-
} catch {
|
|
739
|
-
}
|
|
740
|
-
uninstallGadgetRegistry = installGadgetStubRegistry(input.contract);
|
|
741
|
-
for (const pkg of collectThirdPartyGadgetPackages(input.contract)) {
|
|
742
|
-
if (pkg in moduleResolutions) continue;
|
|
743
|
-
moduleResolutions[pkg] = buildGadgetPackageProbeShim(pkg);
|
|
744
|
-
}
|
|
745
|
-
let Component;
|
|
746
|
-
try {
|
|
747
|
-
const loaded = await loadComponent({
|
|
748
|
-
sourceCode: input.sourceCode,
|
|
749
|
-
moduleResolutions
|
|
750
|
-
});
|
|
751
|
-
Component = loaded.Component;
|
|
752
|
-
} catch (e) {
|
|
753
|
-
issues.push({
|
|
754
|
-
check: "render-no-throw",
|
|
755
|
-
outcome: "failed",
|
|
756
|
-
reason: `Failed to load component: ${e instanceof Error ? e.message : String(e)}`
|
|
757
|
-
});
|
|
758
|
-
return finalize(issues, t0, 0, 0);
|
|
759
|
-
}
|
|
760
|
-
const probe = createProbe();
|
|
761
|
-
const wireConfig = createProbeWireConfig(probe);
|
|
762
|
-
uninstallSpy = probe.installPostMessageSpy();
|
|
763
|
-
installProductionActShim();
|
|
764
|
-
const { render, cleanup, act } = await import('@testing-library/react');
|
|
765
|
-
const { ActionPendingContext } = await import('@ggui-ai/wire/internal');
|
|
766
|
-
const pendingSource = createProbePendingSource();
|
|
767
|
-
const userEventModule = await import('@testing-library/user-event');
|
|
768
|
-
const userEvent = userEventModule.default ?? userEventModule;
|
|
769
|
-
const originalConsoleError = console.error;
|
|
770
|
-
let loopSignature = null;
|
|
771
|
-
let capturedComponentStack = null;
|
|
772
|
-
const loopPatterns = [
|
|
773
|
-
[/Maximum update depth exceeded/i, "max-update-depth"],
|
|
774
|
-
[/Too many re-renders/i, "too-many-renders"],
|
|
775
|
-
[/Rendered more hooks than during the previous render/i, "hook-count-drift"]
|
|
776
|
-
];
|
|
777
|
-
console.error = (...args) => {
|
|
778
|
-
const text = args.map((a) => typeof a === "string" ? a : "").join(" ");
|
|
779
|
-
if (!capturedComponentStack) {
|
|
780
|
-
for (const arg of args) {
|
|
781
|
-
if (typeof arg === "string" && /^\s*at\s+\S/m.test(arg) && arg.length > 20) {
|
|
782
|
-
capturedComponentStack = arg;
|
|
783
|
-
break;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
if (!capturedComponentStack) {
|
|
787
|
-
const stackMatch = text.match(/(?:^|\n)((?:\s*at\s+\S[^\n]*\n?){2,})/);
|
|
788
|
-
if (stackMatch) {
|
|
789
|
-
capturedComponentStack = stackMatch[1].trim();
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
for (const [pattern, tag] of loopPatterns) {
|
|
794
|
-
if (pattern.test(text)) {
|
|
795
|
-
loopSignature = tag;
|
|
796
|
-
throw new Error(`[runtime-render] infinite render loop detected (${tag})`);
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
originalConsoleError.apply(console, args);
|
|
800
|
-
};
|
|
801
|
-
const boundaryRef = {
|
|
802
|
-
stack: null,
|
|
803
|
-
error: null,
|
|
804
|
-
catches: 0
|
|
805
|
-
};
|
|
806
|
-
class ProbeErrorBoundary extends React.Component {
|
|
807
|
-
state = { caught: false };
|
|
808
|
-
static getDerivedStateFromError() {
|
|
809
|
-
return { caught: true };
|
|
810
|
-
}
|
|
811
|
-
componentDidCatch(error, errorInfo) {
|
|
812
|
-
boundaryRef.catches += 1;
|
|
813
|
-
if (errorInfo.componentStack && !boundaryRef.stack) {
|
|
814
|
-
boundaryRef.stack = errorInfo.componentStack;
|
|
815
|
-
}
|
|
816
|
-
if (!boundaryRef.error) {
|
|
817
|
-
boundaryRef.error = error;
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
render() {
|
|
821
|
-
if (this.state.caught) return null;
|
|
822
|
-
return this.props.children;
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
const cardTree = () => React.createElement(GguiWireProvider, {
|
|
826
|
-
config: wireConfig,
|
|
827
|
-
children: React.createElement(ProbeErrorBoundary, {
|
|
828
|
-
children: React.createElement(ActionPendingContext.Provider, {
|
|
829
|
-
value: pendingSource,
|
|
830
|
-
children: React.createElement(Component, input.mockupProps)
|
|
831
|
-
})
|
|
832
|
-
})
|
|
833
|
-
});
|
|
834
|
-
let renderResult;
|
|
835
|
-
const RENDER_TIMEOUT_MS = 5e3;
|
|
836
|
-
try {
|
|
837
|
-
const renderPromise = Promise.resolve().then(() => render(cardTree()));
|
|
838
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
839
|
-
setTimeout(
|
|
840
|
-
() => reject(
|
|
841
|
-
new Error(
|
|
842
|
-
`[runtime-render] render exceeded ${RENDER_TIMEOUT_MS}ms \u2014 classified as runtime-hang`
|
|
843
|
-
)
|
|
844
|
-
),
|
|
845
|
-
RENDER_TIMEOUT_MS
|
|
846
|
-
).unref?.();
|
|
847
|
-
});
|
|
848
|
-
renderResult = await Promise.race([renderPromise, timeoutPromise]);
|
|
849
|
-
} catch (e) {
|
|
850
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
851
|
-
const errStack = e instanceof Error ? e.stack ?? "" : "";
|
|
852
|
-
const baseReason = loopSignature ? `Infinite render loop (${loopSignature}) \u2014 likely setState/dispatch inside useEffect with missing or unstable dependency` : /runtime-hang/.test(msg) ? `Render wall-clock exceeded ${RENDER_TIMEOUT_MS}ms \u2014 likely async effect loop or unresolved promise` : `Render threw: ${msg}`;
|
|
853
|
-
const userFrames = errStack.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ")).filter(
|
|
854
|
-
(l) => !/(node_modules|happy-dom|@testing-library|react-dom|node:internal|node:async)/.test(l)
|
|
855
|
-
).slice(0, 5);
|
|
856
|
-
const stackSource = boundaryRef.stack ?? capturedComponentStack ?? "";
|
|
857
|
-
const componentFrames = stackSource.split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ")).slice(0, 4);
|
|
858
|
-
const stackBlock = userFrames.length ? `
|
|
859
|
-
Stack (user frames): ${userFrames.join(" | ")}` : "";
|
|
860
|
-
const componentBlock = componentFrames.length ? `
|
|
861
|
-
Component stack: ${componentFrames.join(" > ")}` : "";
|
|
862
|
-
const loopReason = `${baseReason}${stackBlock}${componentBlock}`;
|
|
863
|
-
issues.push({
|
|
864
|
-
check: "render-no-throw",
|
|
865
|
-
outcome: "failed",
|
|
866
|
-
reason: loopReason
|
|
867
|
-
});
|
|
868
|
-
console.error = originalConsoleError;
|
|
869
|
-
return finalize(issues, t0, 0, 0);
|
|
870
|
-
}
|
|
871
|
-
console.error = originalConsoleError;
|
|
872
|
-
if (boundaryRef.stack || boundaryRef.error) {
|
|
873
|
-
const componentFrames = (boundaryRef.stack ?? "").split("\n").map((l) => l.trim()).filter((l) => l.startsWith("at ")).slice(0, 4);
|
|
874
|
-
const compBlock = componentFrames.length ? `
|
|
875
|
-
Component stack: ${componentFrames.join(" > ")}` : "";
|
|
876
|
-
const errMsg = boundaryRef.error ? boundaryRef.error.message : "(no error message)";
|
|
877
|
-
issues.push({
|
|
878
|
-
check: "render-no-throw",
|
|
879
|
-
outcome: "failed",
|
|
880
|
-
reason: `Render threw: ${errMsg}${compBlock}`
|
|
881
|
-
});
|
|
882
|
-
cleanup();
|
|
883
|
-
return finalize(issues, t0, 0, 0);
|
|
884
|
-
}
|
|
885
|
-
const container = toMinimalElement(renderResult.container);
|
|
886
|
-
const user = buildClickUser(userEvent, hostGlobals().document);
|
|
887
|
-
let actionsChecked = 0;
|
|
888
|
-
let streamsChecked = 0;
|
|
889
|
-
let pendingTally;
|
|
890
|
-
const pendingTargets = [];
|
|
891
|
-
const walkedTexts = [];
|
|
892
|
-
try {
|
|
893
|
-
let inputPriming;
|
|
894
|
-
try {
|
|
895
|
-
inputPriming = { primed: primeInputs(renderResult.container) };
|
|
896
|
-
} catch (err) {
|
|
897
|
-
inputPriming = { error: err instanceof Error ? err.message : String(err) };
|
|
898
|
-
}
|
|
899
|
-
if (input.contract?.actionSpec) {
|
|
900
|
-
const actionSpec = input.contract.actionSpec;
|
|
901
|
-
for (const [name, entry] of Object.entries(actionSpec)) {
|
|
902
|
-
actionsChecked++;
|
|
903
|
-
const wiring = findWiring({
|
|
904
|
-
sourceCode: input.sourceCode,
|
|
905
|
-
hookName: "useAction",
|
|
906
|
-
hookArg: name
|
|
907
|
-
});
|
|
908
|
-
const walkPriming = {};
|
|
909
|
-
const mount = async () => {
|
|
910
|
-
let mounted;
|
|
911
|
-
try {
|
|
912
|
-
mounted = render(cardTree());
|
|
913
|
-
} catch {
|
|
914
|
-
return void 0;
|
|
915
|
-
}
|
|
916
|
-
await flushPromises();
|
|
917
|
-
const root = mounted.container;
|
|
918
|
-
return {
|
|
919
|
-
container: toMinimalElement(root),
|
|
920
|
-
prime: () => {
|
|
921
|
-
try {
|
|
922
|
-
primeInputs(root);
|
|
923
|
-
} catch (err) {
|
|
924
|
-
walkPriming.error ??= err instanceof Error ? err.message : String(err);
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
};
|
|
928
|
-
};
|
|
929
|
-
const issue = await checkActionWiring({
|
|
930
|
-
mount,
|
|
931
|
-
actionName: name,
|
|
932
|
-
actionLabel: entry.label,
|
|
933
|
-
wiring,
|
|
934
|
-
probe,
|
|
935
|
-
user,
|
|
936
|
-
pendingTargets,
|
|
937
|
-
stopAt: walkStopAt(input.deadlineAt),
|
|
938
|
-
walkedTexts
|
|
939
|
-
});
|
|
940
|
-
if (issue) {
|
|
941
|
-
const priming = "primed" in inputPriming && walkPriming.error !== void 0 ? { error: `on a screen the action walk reached: ${walkPriming.error}` } : inputPriming;
|
|
942
|
-
issues.push({ ...issue, diagnostics: { ...issue.diagnostics, inputPriming: priming } });
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
if (input.contract?.actionSpec) {
|
|
947
|
-
for (const [name, entry] of Object.entries(input.contract.actionSpec)) {
|
|
948
|
-
const idField = idLikeRequiredStringField(
|
|
949
|
-
entry.schema
|
|
950
|
-
);
|
|
951
|
-
if (idField === null) continue;
|
|
952
|
-
const issue = await checkSelectionIdentity({
|
|
953
|
-
container,
|
|
954
|
-
probe,
|
|
955
|
-
user,
|
|
956
|
-
actionName: name,
|
|
957
|
-
idField
|
|
958
|
-
});
|
|
959
|
-
if (issue) issues.push(issue);
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
if (input.contract?.propsSpec) {
|
|
963
|
-
for (const issue of checkPropCoverage({
|
|
964
|
-
container,
|
|
965
|
-
walkedTexts,
|
|
966
|
-
propsSpec: input.contract.propsSpec,
|
|
967
|
-
mockupProps: input.mockupProps
|
|
968
|
-
})) {
|
|
969
|
-
issues.push(issue);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
if (input.contract?.propsSpec) {
|
|
973
|
-
const propsSpec5c = input.contract.propsSpec;
|
|
974
|
-
const normalize = (s) => (s ?? "").replace(/\s+/g, " ").trim();
|
|
975
|
-
const baseText = normalize(container.textContent);
|
|
976
|
-
for (const [propName, entry] of Object.entries(propsSpec5c.properties)) {
|
|
977
|
-
if (!entry.required) continue;
|
|
978
|
-
const orig = input.mockupProps[propName];
|
|
979
|
-
if (typeof orig !== "string" && typeof orig !== "number") continue;
|
|
980
|
-
const marker = pickScalarMarker(orig);
|
|
981
|
-
if (marker === null || !baseText.includes(marker)) continue;
|
|
982
|
-
const varied = typeof orig === "string" ? "Zzyzx Qw" : orig + 137731;
|
|
983
|
-
const variedProbe = createProbe();
|
|
984
|
-
const variedConfig = createProbeWireConfig(variedProbe);
|
|
985
|
-
let variedResult;
|
|
986
|
-
try {
|
|
987
|
-
variedResult = render(
|
|
988
|
-
React.createElement(GguiWireProvider, {
|
|
989
|
-
config: variedConfig,
|
|
990
|
-
children: React.createElement(ProbeErrorBoundary, {
|
|
991
|
-
children: React.createElement(Component, {
|
|
992
|
-
...input.mockupProps,
|
|
993
|
-
[propName]: varied
|
|
994
|
-
})
|
|
995
|
-
})
|
|
996
|
-
})
|
|
997
|
-
);
|
|
998
|
-
} catch {
|
|
999
|
-
continue;
|
|
1000
|
-
}
|
|
1001
|
-
await flushPromises();
|
|
1002
|
-
const variedText = normalize(variedResult.container.textContent);
|
|
1003
|
-
variedResult.unmount();
|
|
1004
|
-
if (variedText === baseText) {
|
|
1005
|
-
issues.push({
|
|
1006
|
-
check: "prop-sensitivity",
|
|
1007
|
-
outcome: "failed",
|
|
1008
|
-
subject: propName,
|
|
1009
|
-
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.`
|
|
1010
|
-
});
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
if (input.contract?.streamSpec) {
|
|
1015
|
-
const streamSpec = input.contract.streamSpec;
|
|
1016
|
-
for (const [name] of Object.entries(streamSpec)) {
|
|
1017
|
-
streamsChecked++;
|
|
1018
|
-
const issue = await checkStreamRerender({
|
|
1019
|
-
container,
|
|
1020
|
-
eventName: name,
|
|
1021
|
-
probe,
|
|
1022
|
-
React
|
|
1023
|
-
});
|
|
1024
|
-
if (issue) issues.push(issue);
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
const declaredProps = input.contract?.propsSpec?.properties;
|
|
1028
|
-
if (declaredProps) {
|
|
1029
|
-
const optionalFilled = Object.entries(declaredProps).filter(
|
|
1030
|
-
([name, entry]) => entry.required !== true && input.mockupProps[name] !== void 0
|
|
1031
|
-
).map(([name]) => name);
|
|
1032
|
-
if (optionalFilled.length > 0) {
|
|
1033
|
-
const requiredOnlyProps = { ...input.mockupProps };
|
|
1034
|
-
for (const name of optionalFilled) delete requiredOnlyProps[name];
|
|
1035
|
-
const omitBoundaryRef = {};
|
|
1036
|
-
class OmitErrorBoundary extends React.Component {
|
|
1037
|
-
state = { caught: false };
|
|
1038
|
-
static getDerivedStateFromError() {
|
|
1039
|
-
return { caught: true };
|
|
1040
|
-
}
|
|
1041
|
-
componentDidCatch(error) {
|
|
1042
|
-
omitBoundaryRef.error ??= error;
|
|
1043
|
-
}
|
|
1044
|
-
render() {
|
|
1045
|
-
if (this.state.caught) return null;
|
|
1046
|
-
return this.props.children;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
try {
|
|
1050
|
-
render(
|
|
1051
|
-
React.createElement(GguiWireProvider, {
|
|
1052
|
-
config: wireConfig,
|
|
1053
|
-
children: React.createElement(OmitErrorBoundary, {
|
|
1054
|
-
children: React.createElement(Component, requiredOnlyProps)
|
|
1055
|
-
})
|
|
1056
|
-
})
|
|
1057
|
-
);
|
|
1058
|
-
} catch (e) {
|
|
1059
|
-
omitBoundaryRef.error ??= e instanceof Error ? e : new Error(String(e));
|
|
1060
|
-
}
|
|
1061
|
-
if (omitBoundaryRef.error) {
|
|
1062
|
-
issues.push({
|
|
1063
|
-
check: "optional-props-omitted",
|
|
1064
|
-
outcome: "failed",
|
|
1065
|
-
subject: optionalFilled.join(", "),
|
|
1066
|
-
reason: `Render crashed when the optional prop(s) ${optionalFilled.map((n) => `'${n}'`).join(", ")} were omitted: ${omitBoundaryRef.error.message}. The contract marks them optional (no \`required: true\`), so a legitimate render may leave them out.`
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
const timeLeft = input.deadlineAt === void 0 ? Number.POSITIVE_INFINITY : input.deadlineAt - Date.now();
|
|
1072
|
-
if (input.contract?.actionSpec && timeLeft >= PENDING_PHASE_RESERVE_MS) {
|
|
1073
|
-
pendingTally = await runPendingPhase({
|
|
1074
|
-
targets: pendingTargets,
|
|
1075
|
-
source: pendingSource,
|
|
1076
|
-
act,
|
|
1077
|
-
catches: () => boundaryRef.catches,
|
|
1078
|
-
actions: Object.entries(input.contract.actionSpec).map(([name, entry]) => ({ name, label: entry.label })),
|
|
1079
|
-
...input.deadlineAt !== void 0 ? { deadlineAt: input.deadlineAt } : {}
|
|
1080
|
-
});
|
|
1081
|
-
}
|
|
1082
|
-
} finally {
|
|
1083
|
-
cleanup();
|
|
1084
|
-
}
|
|
1085
|
-
return finalize(issues, t0, actionsChecked, streamsChecked, pendingTally);
|
|
1086
|
-
} finally {
|
|
1087
|
-
try {
|
|
1088
|
-
uninstallSpy?.();
|
|
1089
|
-
} catch {
|
|
1090
|
-
}
|
|
1091
|
-
try {
|
|
1092
|
-
uninstallGadgetRegistry?.();
|
|
1093
|
-
} catch {
|
|
1094
|
-
}
|
|
1095
|
-
teardown();
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
function installGadgetStubRegistry(contract) {
|
|
1099
|
-
const declared = contract?.clientCapabilities?.gadgets;
|
|
1100
|
-
if (!declared || Object.keys(declared).length === 0) {
|
|
1101
|
-
return () => {
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
const noop = () => {
|
|
1105
|
-
};
|
|
1106
|
-
const valueStub = new Proxy(
|
|
1107
|
-
{},
|
|
1108
|
-
{
|
|
1109
|
-
get: (_t, key) => key === "then" || typeof key === "symbol" ? void 0 : noop
|
|
1110
|
-
}
|
|
1111
|
-
);
|
|
1112
|
-
const result = { status: "idle", value: valueStub, start: noop };
|
|
1113
|
-
const resultStub = new Proxy(result, {
|
|
1114
|
-
get: (t, key) => {
|
|
1115
|
-
if (key === "status" || key === "value" || key === "start") {
|
|
1116
|
-
return t[key];
|
|
1117
|
-
}
|
|
1118
|
-
return key === "then" || typeof key === "symbol" ? void 0 : noop;
|
|
1119
|
-
}
|
|
1120
|
-
});
|
|
1121
|
-
const gadgets = {};
|
|
1122
|
-
for (const use of contract ? listContractGadgets(contract) : []) {
|
|
1123
|
-
const pkgSlot = gadgets[use.package] ??= {};
|
|
1124
|
-
if (HOOK_NAME_RE.test(use.name)) {
|
|
1125
|
-
pkgSlot[use.name] = () => resultStub;
|
|
1126
|
-
} else {
|
|
1127
|
-
pkgSlot[use.name] = () => null;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
const root = globalThis;
|
|
1131
|
-
const prior = root.__ggui__;
|
|
1132
|
-
root.__ggui__ = { gadgets, publicEnv: {} };
|
|
1133
|
-
return () => {
|
|
1134
|
-
if (prior === void 0) {
|
|
1135
|
-
delete root.__ggui__;
|
|
1136
|
-
} else {
|
|
1137
|
-
root.__ggui__ = prior;
|
|
1138
|
-
}
|
|
1139
|
-
};
|
|
1140
|
-
}
|
|
1141
|
-
function collectThirdPartyGadgetPackages(contract) {
|
|
1142
|
-
if (!contract) return [];
|
|
1143
|
-
const packages = /* @__PURE__ */ new Set();
|
|
1144
|
-
for (const use of listContractGadgets(contract)) {
|
|
1145
|
-
if (use.package !== "@ggui-ai/gadgets") packages.add(use.package);
|
|
1146
|
-
}
|
|
1147
|
-
return [...packages];
|
|
1148
|
-
}
|
|
1149
|
-
function buildGadgetPackageProbeShim(packageName) {
|
|
1150
|
-
const resolveExport = (name) => {
|
|
1151
|
-
const root = globalThis;
|
|
1152
|
-
return root.__ggui__?.gadgets?.[packageName]?.[name];
|
|
1153
|
-
};
|
|
1154
|
-
const makeThunk = (name) => (...args) => {
|
|
1155
|
-
const impl = resolveExport(name);
|
|
1156
|
-
if (typeof impl !== "function") {
|
|
1157
|
-
throw new Error(
|
|
1158
|
-
`[gadget] export '${name}' from '${packageName}' is not loaded in the render-check probe \u2014 the component imports it but the contract's clientCapabilities.gadgets never declared it, so installGadgetStubRegistry planted no stub.`
|
|
1159
|
-
);
|
|
1160
|
-
}
|
|
1161
|
-
return impl(...args);
|
|
1162
|
-
};
|
|
1163
|
-
return new Proxy(
|
|
1164
|
-
{},
|
|
1165
|
-
{
|
|
1166
|
-
get: (_t, key) => {
|
|
1167
|
-
if (typeof key === "symbol" || key === "default" || key === "then") {
|
|
1168
|
-
return void 0;
|
|
1169
|
-
}
|
|
1170
|
-
return makeThunk(key);
|
|
1171
|
-
},
|
|
1172
|
-
// `key in ns` checks (esbuild interop may probe) report true so
|
|
1173
|
-
// the named import binds to the thunk rather than `undefined`.
|
|
1174
|
-
has: (_t, key) => typeof key !== "symbol" && key !== "then" && key !== "default"
|
|
1175
|
-
}
|
|
1176
|
-
);
|
|
1177
|
-
}
|
|
1178
|
-
async function setupHappyDom() {
|
|
1179
|
-
const g = hostGlobals();
|
|
1180
|
-
const pendingTimeouts = /* @__PURE__ */ new Set();
|
|
1181
|
-
const pendingIntervals = /* @__PURE__ */ new Set();
|
|
1182
|
-
const origSetTimeout = globalThis.setTimeout;
|
|
1183
|
-
const origSetInterval = globalThis.setInterval;
|
|
1184
|
-
const origClearTimeout = globalThis.clearTimeout;
|
|
1185
|
-
const origClearInterval = globalThis.clearInterval;
|
|
1186
|
-
globalThis.setTimeout = ((...args) => {
|
|
1187
|
-
const id = origSetTimeout(...args);
|
|
1188
|
-
pendingTimeouts.add(id);
|
|
1189
|
-
return id;
|
|
1190
|
-
});
|
|
1191
|
-
globalThis.setInterval = ((...args) => {
|
|
1192
|
-
const id = origSetInterval(...args);
|
|
1193
|
-
pendingIntervals.add(id);
|
|
1194
|
-
return id;
|
|
1195
|
-
});
|
|
1196
|
-
globalThis.clearTimeout = (id) => {
|
|
1197
|
-
if (id !== void 0) pendingTimeouts.delete(id);
|
|
1198
|
-
return origClearTimeout(id);
|
|
1199
|
-
};
|
|
1200
|
-
globalThis.clearInterval = (id) => {
|
|
1201
|
-
if (id !== void 0) pendingIntervals.delete(id);
|
|
1202
|
-
return origClearInterval(id);
|
|
1203
|
-
};
|
|
1204
|
-
const isTeardownArtifact = (err) => {
|
|
1205
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1206
|
-
return /window is not defined/i.test(msg) || /document is not defined/i.test(msg) || /Cannot read propert(?:y|ies) of undefined \(reading 'event'\)/i.test(msg) || /requestAnimationFrame is not defined/i.test(msg);
|
|
1207
|
-
};
|
|
1208
|
-
const uncaughtHandler = (err) => {
|
|
1209
|
-
if (isTeardownArtifact(err)) {
|
|
1210
|
-
return;
|
|
1211
|
-
}
|
|
1212
|
-
process.nextTick(() => {
|
|
1213
|
-
throw err;
|
|
1214
|
-
});
|
|
1215
|
-
};
|
|
1216
|
-
const unhandledHandler = (reason) => {
|
|
1217
|
-
if (isTeardownArtifact(reason)) return;
|
|
1218
|
-
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
1219
|
-
process.nextTick(() => {
|
|
1220
|
-
throw err;
|
|
1221
|
-
});
|
|
1222
|
-
};
|
|
1223
|
-
process.on("uncaughtException", uncaughtHandler);
|
|
1224
|
-
process.on("unhandledRejection", unhandledHandler);
|
|
1225
|
-
const cleanupAsyncInfra = () => {
|
|
1226
|
-
for (const id of pendingTimeouts) origClearTimeout(id);
|
|
1227
|
-
for (const id of pendingIntervals) origClearInterval(id);
|
|
1228
|
-
pendingTimeouts.clear();
|
|
1229
|
-
pendingIntervals.clear();
|
|
1230
|
-
globalThis.setTimeout = origSetTimeout;
|
|
1231
|
-
globalThis.setInterval = origSetInterval;
|
|
1232
|
-
globalThis.clearTimeout = origClearTimeout;
|
|
1233
|
-
globalThis.clearInterval = origClearInterval;
|
|
1234
|
-
origSetTimeout(() => {
|
|
1235
|
-
process.off("uncaughtException", uncaughtHandler);
|
|
1236
|
-
process.off("unhandledRejection", unhandledHandler);
|
|
1237
|
-
}, 100).unref?.();
|
|
1238
|
-
};
|
|
1239
|
-
if ("window" in g && "document" in g) {
|
|
1240
|
-
const priorActFlag = g.IS_REACT_ACT_ENVIRONMENT;
|
|
1241
|
-
g.IS_REACT_ACT_ENVIRONMENT = true;
|
|
1242
|
-
return () => {
|
|
1243
|
-
cleanupAsyncInfra();
|
|
1244
|
-
if (priorActFlag === void 0) delete g.IS_REACT_ACT_ENVIRONMENT;
|
|
1245
|
-
else g.IS_REACT_ACT_ENVIRONMENT = priorActFlag;
|
|
1246
|
-
};
|
|
1247
|
-
}
|
|
1248
|
-
const { Window } = await import('happy-dom');
|
|
1249
|
-
const window = new Window({ url: "https://render-check.local" });
|
|
1250
|
-
const windowAny = openRecord(window);
|
|
1251
|
-
const keys = [
|
|
1252
|
-
"window",
|
|
1253
|
-
"document",
|
|
1254
|
-
"navigator",
|
|
1255
|
-
"HTMLElement",
|
|
1256
|
-
"Node",
|
|
1257
|
-
"Element",
|
|
1258
|
-
"Event",
|
|
1259
|
-
"MouseEvent",
|
|
1260
|
-
"KeyboardEvent",
|
|
1261
|
-
"getComputedStyle",
|
|
1262
|
-
"requestAnimationFrame",
|
|
1263
|
-
"cancelAnimationFrame"
|
|
1264
|
-
];
|
|
1265
|
-
const prior = {};
|
|
1266
|
-
for (const k of keys) {
|
|
1267
|
-
prior[k] = { value: g[k], existed: k in g };
|
|
1268
|
-
try {
|
|
1269
|
-
Object.defineProperty(g, k, {
|
|
1270
|
-
value: windowAny[k],
|
|
1271
|
-
writable: true,
|
|
1272
|
-
configurable: true
|
|
1273
|
-
});
|
|
1274
|
-
} catch {
|
|
1275
|
-
}
|
|
1276
|
-
}
|
|
1277
|
-
g.IS_REACT_ACT_ENVIRONMENT = true;
|
|
1278
|
-
return () => {
|
|
1279
|
-
cleanupAsyncInfra();
|
|
1280
|
-
for (const k of keys) {
|
|
1281
|
-
try {
|
|
1282
|
-
if (prior[k].existed) {
|
|
1283
|
-
Object.defineProperty(g, k, {
|
|
1284
|
-
value: prior[k].value,
|
|
1285
|
-
writable: true,
|
|
1286
|
-
configurable: true
|
|
1287
|
-
});
|
|
1288
|
-
} else {
|
|
1289
|
-
delete g[k];
|
|
1290
|
-
}
|
|
1291
|
-
} catch {
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1294
|
-
delete g.IS_REACT_ACT_ENVIRONMENT;
|
|
1295
|
-
};
|
|
1296
|
-
}
|
|
1297
|
-
function finalize(issues, t0, actionsChecked, streamsChecked, pendingTally) {
|
|
1298
|
-
const ok = !issues.some((i) => i.outcome === "failed");
|
|
1299
|
-
return {
|
|
1300
|
-
ok,
|
|
1301
|
-
issues,
|
|
1302
|
-
stats: {
|
|
1303
|
-
actionsChecked,
|
|
1304
|
-
streamsChecked,
|
|
1305
|
-
renderMs: Date.now() - t0,
|
|
1306
|
-
...pendingTally !== void 0 ? {
|
|
1307
|
-
pendingAffordance: {
|
|
1308
|
-
dispatched: pendingTally.dispatched,
|
|
1309
|
-
visible: pendingTally.visible,
|
|
1310
|
-
missing: [...pendingTally.missing],
|
|
1311
|
-
gone: [...pendingTally.gone]
|
|
1312
|
-
}
|
|
1313
|
-
} : {}
|
|
1314
|
-
}
|
|
1315
|
-
};
|
|
1316
|
-
}
|
|
1317
|
-
var PENDING_TARGET_CAP_MS = 2e3;
|
|
1318
|
-
var PENDING_PHASE_TAIL_MS = 1e3;
|
|
1319
|
-
async function runPendingPhase(input) {
|
|
1320
|
-
const tally = { dispatched: 0, visible: 0, missing: [], gone: [] };
|
|
1321
|
-
let stopped = false;
|
|
1322
|
-
for (const target of input.targets) {
|
|
1323
|
-
const budget = Math.min(
|
|
1324
|
-
PENDING_TARGET_CAP_MS,
|
|
1325
|
-
input.deadlineAt === void 0 ? Number.POSITIVE_INFINITY : input.deadlineAt - Date.now() - PENDING_PHASE_TAIL_MS
|
|
1326
|
-
);
|
|
1327
|
-
const found = stopped || budget <= 0 ? void 0 : pendingControlOf(target, input.actions);
|
|
1328
|
-
if (found === void 0) {
|
|
1329
|
-
tally.gone.push(target.actionName);
|
|
1330
|
-
if (budget <= 0) stopped = true;
|
|
1331
|
-
continue;
|
|
1332
|
-
}
|
|
1333
|
-
const el = controlToRead(found, target);
|
|
1334
|
-
const before = lookOf(el);
|
|
1335
|
-
const caughtBefore = input.catches();
|
|
1336
|
-
const settled = await settlesWithin(
|
|
1337
|
-
budget,
|
|
1338
|
-
(async () => {
|
|
1339
|
-
await input.act(async () => {
|
|
1340
|
-
input.source.mark(target.actionName);
|
|
1341
|
-
});
|
|
1342
|
-
await flushPromises();
|
|
1343
|
-
})()
|
|
1344
|
-
);
|
|
1345
|
-
const crashed = input.catches() !== caughtBefore;
|
|
1346
|
-
tally.dispatched += 1;
|
|
1347
|
-
if (settled && !crashed && lookChanged(before, lookOf(el))) tally.visible += 1;
|
|
1348
|
-
else tally.missing.push(target.actionName);
|
|
1349
|
-
if (!settled) {
|
|
1350
|
-
stopped = true;
|
|
1351
|
-
continue;
|
|
1352
|
-
}
|
|
1353
|
-
await input.act(async () => {
|
|
1354
|
-
input.source.clear();
|
|
1355
|
-
});
|
|
1356
|
-
await flushPromises();
|
|
1357
|
-
if (crashed) stopped = true;
|
|
1358
|
-
}
|
|
1359
|
-
return tally;
|
|
1360
|
-
}
|
|
1361
|
-
function pendingControlOf(target, actions) {
|
|
1362
|
-
const movedToAnother = !matchesActionIdentity(target.element, target.actionName, target.actionLabel) && actions.some((a) => a.name !== target.actionName && matchesActionIdentity(target.element, a.name, a.label));
|
|
1363
|
-
if (lookOf(target.element).connected && !movedToAnother) return target.element;
|
|
1364
|
-
if (target.wiringKind !== "click") return void 0;
|
|
1365
|
-
return findActionElements(target.container, target.actionName, target.actionLabel, { identityOnly: true })[0];
|
|
1366
|
-
}
|
|
1367
|
-
function controlToRead(el, target) {
|
|
1368
|
-
if (el.tagName.toLowerCase() !== "form") return el;
|
|
1369
|
-
for (const c of el.querySelectorAll('button, input[type="submit"], input[type="button"], [role="button"]')) {
|
|
1370
|
-
if (matchesActionIdentity(c, target.actionName, target.actionLabel)) return c;
|
|
1371
|
-
}
|
|
1372
|
-
for (const submitter of el.querySelectorAll(
|
|
1373
|
-
'button[type="submit"], input[type="submit"], button:not([type])'
|
|
1374
|
-
)) {
|
|
1375
|
-
return submitter;
|
|
1376
|
-
}
|
|
1377
|
-
return el;
|
|
1378
|
-
}
|
|
1379
|
-
function walkStopAt(deadlineAt) {
|
|
1380
|
-
const cap = Date.now() + WALK_ACTION_CAP_MS;
|
|
1381
|
-
return deadlineAt === void 0 ? cap : Math.min(cap, deadlineAt - PENDING_PHASE_RESERVE_MS - WALK_DEADLINE_MARGIN_MS);
|
|
1382
|
-
}
|
|
1383
|
-
async function checkActionWiring(input) {
|
|
1384
|
-
const { actionName, actionLabel, wiring, probe, user } = input;
|
|
1385
|
-
const baseDiagnostics = {
|
|
1386
|
-
observedJsxElements: wiring.observedJsxElements,
|
|
1387
|
-
observedNativeProps: wiring.observedNativeProps,
|
|
1388
|
-
observedCustomProps: wiring.observedCustomProps
|
|
1389
|
-
};
|
|
1390
|
-
if (wiring.kind === "missing") {
|
|
1391
|
-
return {
|
|
1392
|
-
check: "action-wiring",
|
|
1393
|
-
outcome: "failed",
|
|
1394
|
-
subject: actionName,
|
|
1395
|
-
reason: wiring.reason ?? `Action '${actionName}' is declared in contract but useAction('${actionName}') is not wired to any UI element`,
|
|
1396
|
-
diagnostics: baseDiagnostics
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
if (wiring.kind === "unverified") {
|
|
1400
|
-
return {
|
|
1401
|
-
check: "action-wiring",
|
|
1402
|
-
outcome: "unverified",
|
|
1403
|
-
subject: actionName,
|
|
1404
|
-
reason: wiring.reason ?? "Source indicates non-click or non-native wiring; static probe did not verify execution deterministically.",
|
|
1405
|
-
diagnostics: baseDiagnostics
|
|
1406
|
-
};
|
|
1407
|
-
}
|
|
1408
|
-
const kind = wiring.kind;
|
|
1409
|
-
const walk = await walkToAction({
|
|
1410
|
-
mount: input.mount,
|
|
1411
|
-
controls: (container) => actionControls(container, kind, actionName, actionLabel),
|
|
1412
|
-
press: async (control) => {
|
|
1413
|
-
const before = probe.getFireLog().length;
|
|
1414
|
-
try {
|
|
1415
|
-
await dispatchTrigger(control.element, control.how, user);
|
|
1416
|
-
} catch {
|
|
1417
|
-
return false;
|
|
1418
|
-
}
|
|
1419
|
-
await flushPromises();
|
|
1420
|
-
return probe.getFireLog().slice(before).some((e) => e.kind === "action.fired" && e.name === actionName);
|
|
1421
|
-
},
|
|
1422
|
-
describe: describeElement,
|
|
1423
|
-
stopAt: input.stopAt,
|
|
1424
|
-
maxPresses: WALK_MAX_PRESSES
|
|
1425
|
-
});
|
|
1426
|
-
input.walkedTexts.push(...walk.screenTexts);
|
|
1427
|
-
if (walk.fired) {
|
|
1428
|
-
if (input.pendingTargets && walk.element && walk.container && (kind === "click" || kind === "submit")) {
|
|
1429
|
-
input.pendingTargets.push({
|
|
1430
|
-
actionName,
|
|
1431
|
-
actionLabel,
|
|
1432
|
-
wiringKind: kind,
|
|
1433
|
-
element: walk.element,
|
|
1434
|
-
container: walk.container
|
|
1435
|
-
});
|
|
1436
|
-
}
|
|
1437
|
-
return null;
|
|
1438
|
-
}
|
|
1439
|
-
const allFired = probe.getFireLog().filter((e) => e.kind === "action.fired").map((e) => e.name);
|
|
1440
|
-
const otherActionsFired = Array.from(new Set(allFired)).filter((n) => n !== actionName);
|
|
1441
|
-
const stoppedBy = walk.stoppedBy === "dispatched" ? "explored" : walk.stoppedBy;
|
|
1442
|
-
return {
|
|
1443
|
-
check: "action-wiring",
|
|
1444
|
-
outcome: "unverified",
|
|
1445
|
-
subject: actionName,
|
|
1446
|
-
reason: `Source confirms ${kind} wiring exists for action '${actionName}', but synthetic ${kind} did not dispatch it. Likely cause: conditional rendering, required input/form fill, or a multi-step interaction the static probe cannot complete.`,
|
|
1447
|
-
...walk.presses === 0 && stoppedBy === "explored" ? { elementHint: `No ${kind}-eligible element found in DOM` } : {},
|
|
1448
|
-
diagnostics: {
|
|
1449
|
-
...baseDiagnostics,
|
|
1450
|
-
actionsFiredFromClicks: otherActionsFired,
|
|
1451
|
-
walk: {
|
|
1452
|
-
presses: walk.presses,
|
|
1453
|
-
screens: walk.screens,
|
|
1454
|
-
stoppedBy,
|
|
1455
|
-
namedControlSeen: walk.namedControlSeen,
|
|
1456
|
-
lastPressed: walk.lastPressed
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
};
|
|
1460
|
-
}
|
|
1461
|
-
function idLikeRequiredStringField(schema) {
|
|
1462
|
-
if (schema === void 0) return null;
|
|
1463
|
-
const properties = schema["properties"];
|
|
1464
|
-
const required = schema["required"];
|
|
1465
|
-
if (properties === null || typeof properties !== "object") return null;
|
|
1466
|
-
if (!Array.isArray(required)) return null;
|
|
1467
|
-
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
1468
|
-
if (!/id$/i.test(propName)) continue;
|
|
1469
|
-
if (!required.includes(propName)) continue;
|
|
1470
|
-
const t = propSchema !== null && typeof propSchema === "object" ? propSchema["type"] : void 0;
|
|
1471
|
-
if (t === "string") return propName;
|
|
1472
|
-
}
|
|
1473
|
-
return null;
|
|
1474
|
-
}
|
|
1475
|
-
async function checkSelectionIdentity(input) {
|
|
1476
|
-
const { container, probe, user, actionName, idField } = input;
|
|
1477
|
-
const clickables = Array.from(
|
|
1478
|
-
container.querySelectorAll('button, [role="button"]')
|
|
1479
|
-
);
|
|
1480
|
-
const byLabel = /* @__PURE__ */ new Map();
|
|
1481
|
-
for (const el of clickables) {
|
|
1482
|
-
const label = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
1483
|
-
if (label.length === 0) continue;
|
|
1484
|
-
const group = byLabel.get(label);
|
|
1485
|
-
if (group) group.push(el);
|
|
1486
|
-
else byLabel.set(label, [el]);
|
|
1487
|
-
}
|
|
1488
|
-
for (const [label, group] of byLabel) {
|
|
1489
|
-
if (group.length < 2) continue;
|
|
1490
|
-
const payloadFor = async (el) => {
|
|
1491
|
-
const before = probe.getFireLog().length;
|
|
1492
|
-
try {
|
|
1493
|
-
await user.click(el);
|
|
1494
|
-
} catch {
|
|
1495
|
-
return void 0;
|
|
1496
|
-
}
|
|
1497
|
-
await flushPromises();
|
|
1498
|
-
const events = probe.getFireLog().slice(before).filter((e) => e.kind === "action.fired" && e.name === actionName);
|
|
1499
|
-
return events.length > 0 ? events[events.length - 1].payload : void 0;
|
|
1500
|
-
};
|
|
1501
|
-
const first = await payloadFor(group[0]);
|
|
1502
|
-
const second = await payloadFor(group[1]);
|
|
1503
|
-
if (first === void 0 || second === void 0) continue;
|
|
1504
|
-
const idOf = (payload) => {
|
|
1505
|
-
if (payload === null || typeof payload !== "object") return void 0;
|
|
1506
|
-
const v = payload[idField];
|
|
1507
|
-
return typeof v === "string" ? v : void 0;
|
|
1508
|
-
};
|
|
1509
|
-
const firstId = idOf(first);
|
|
1510
|
-
const secondId = idOf(second);
|
|
1511
|
-
if (firstId === void 0 || secondId === void 0) continue;
|
|
1512
|
-
if (firstId === secondId) {
|
|
1513
|
-
return {
|
|
1514
|
-
check: "selection-identity",
|
|
1515
|
-
outcome: "failed",
|
|
1516
|
-
subject: actionName,
|
|
1517
|
-
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}".`,
|
|
1518
|
-
elementHint: describeElement(group[0])
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
return null;
|
|
1522
|
-
}
|
|
1523
|
-
return null;
|
|
1524
|
-
}
|
|
1525
|
-
async function dispatchTrigger(el, kind, user) {
|
|
1526
|
-
switch (kind) {
|
|
1527
|
-
case "click":
|
|
1528
|
-
await user.click(el);
|
|
1529
|
-
return;
|
|
1530
|
-
case "submit": {
|
|
1531
|
-
const form = closestForm(el);
|
|
1532
|
-
if (!form) return;
|
|
1533
|
-
const ev = new globalThis.Event("submit", {
|
|
1534
|
-
bubbles: true,
|
|
1535
|
-
cancelable: true
|
|
1536
|
-
});
|
|
1537
|
-
toEventDispatcher(form).dispatchEvent(ev);
|
|
1538
|
-
return;
|
|
1539
|
-
}
|
|
1540
|
-
case "change": {
|
|
1541
|
-
setSyntheticValue(el);
|
|
1542
|
-
const ev = new globalThis.Event("change", { bubbles: true });
|
|
1543
|
-
toEventDispatcher(el).dispatchEvent(ev);
|
|
1544
|
-
return;
|
|
1545
|
-
}
|
|
1546
|
-
case "keyboard-enter": {
|
|
1547
|
-
try {
|
|
1548
|
-
await user.click(el);
|
|
1549
|
-
} catch {
|
|
1550
|
-
}
|
|
1551
|
-
const KeyboardEventCtor = globalThis.KeyboardEvent;
|
|
1552
|
-
if (KeyboardEventCtor) {
|
|
1553
|
-
const ev = new KeyboardEventCtor("keydown", { key: "Enter", bubbles: true });
|
|
1554
|
-
toEventDispatcher(el).dispatchEvent(ev);
|
|
1555
|
-
}
|
|
1556
|
-
return;
|
|
1557
|
-
}
|
|
1558
|
-
}
|
|
1559
|
-
}
|
|
1560
|
-
function setSyntheticValue(el) {
|
|
1561
|
-
try {
|
|
1562
|
-
const tag = el.tagName.toLowerCase();
|
|
1563
|
-
const node = editableSurface(el);
|
|
1564
|
-
if (tag === "select") {
|
|
1565
|
-
const opts = node.options;
|
|
1566
|
-
if (opts && opts.length > 0) {
|
|
1567
|
-
const current = node.value ?? "";
|
|
1568
|
-
let pick = opts[0].value;
|
|
1569
|
-
for (let i = 0; i < opts.length; i++) {
|
|
1570
|
-
if (opts[i].value !== current) {
|
|
1571
|
-
pick = opts[i].value;
|
|
1572
|
-
break;
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
node.value = pick;
|
|
1576
|
-
}
|
|
1577
|
-
return;
|
|
1578
|
-
}
|
|
1579
|
-
if (tag === "input") {
|
|
1580
|
-
const type = (node.type ?? "text").toLowerCase();
|
|
1581
|
-
if (type === "checkbox" || type === "radio") {
|
|
1582
|
-
node.checked = !node.checked;
|
|
1583
|
-
return;
|
|
1584
|
-
}
|
|
1585
|
-
if (type === "number" || type === "range") {
|
|
1586
|
-
const cur = Number(node.value ?? "");
|
|
1587
|
-
node.value = String(Number.isFinite(cur) ? cur + 1 : 1);
|
|
1588
|
-
return;
|
|
1589
|
-
}
|
|
1590
|
-
node.value = node.value && node.value.length > 0 ? `${node.value}x` : "test";
|
|
1591
|
-
return;
|
|
1592
|
-
}
|
|
1593
|
-
if (tag === "textarea") {
|
|
1594
|
-
node.value = node.value && node.value.length > 0 ? `${node.value}x` : "test";
|
|
1595
|
-
return;
|
|
1596
|
-
}
|
|
1597
|
-
} catch {
|
|
1598
|
-
}
|
|
1599
|
-
}
|
|
1600
|
-
function closestForm(el) {
|
|
1601
|
-
let cur = parentLink(el);
|
|
1602
|
-
while (cur) {
|
|
1603
|
-
if (cur.tagName && cur.tagName.toLowerCase() === "form") {
|
|
1604
|
-
return toMinimalElement(cur);
|
|
1605
|
-
}
|
|
1606
|
-
cur = typeof cur.parentNode === "object" && cur.parentNode !== null ? parentLink(cur.parentNode) : null;
|
|
1607
|
-
}
|
|
1608
|
-
return null;
|
|
1609
|
-
}
|
|
1610
|
-
function actionControls(container, kind, name, label) {
|
|
1611
|
-
const clickables = findActionElements(container, name, label);
|
|
1612
|
-
const named = (el) => matchesActionIdentity(el, name, label);
|
|
1613
|
-
if (kind === "click" || kind === "keyboard-enter") {
|
|
1614
|
-
return clickables.map((element) => ({ element, namesAction: named(element), how: kind }));
|
|
1615
|
-
}
|
|
1616
|
-
const eligible = Array.from(
|
|
1617
|
-
container.querySelectorAll(
|
|
1618
|
-
kind === "submit" ? 'form, button[type="submit"], input[type="submit"]' : "select, input, textarea"
|
|
1619
|
-
)
|
|
1620
|
-
);
|
|
1621
|
-
const seen = new Set(eligible);
|
|
1622
|
-
return [
|
|
1623
|
-
...eligible.map((element) => ({ element, namesAction: named(element), how: kind })),
|
|
1624
|
-
...clickables.filter((el) => !seen.has(el)).map((element) => ({ element, namesAction: named(element), how: "click" }))
|
|
1625
|
-
];
|
|
1626
|
-
}
|
|
1627
|
-
function checkPropCoverage(input) {
|
|
1628
|
-
const { container, walkedTexts, propsSpec, mockupProps } = input;
|
|
1629
|
-
const issues = [];
|
|
1630
|
-
const texts = [container.textContent ?? "", ...walkedTexts];
|
|
1631
|
-
for (const [propName, entry] of Object.entries(propsSpec.properties)) {
|
|
1632
|
-
if (!entry.required) continue;
|
|
1633
|
-
const value = mockupProps[propName];
|
|
1634
|
-
if (value === void 0 || value === null) continue;
|
|
1635
|
-
const marker = pickScalarMarker(value);
|
|
1636
|
-
if (marker === null) continue;
|
|
1637
|
-
if (!texts.some((text) => text.includes(marker))) {
|
|
1638
|
-
const where = walkedTexts.length > 0 ? ", on the first screen or any screen the action walk reached" : "";
|
|
1639
|
-
issues.push({
|
|
1640
|
-
check: "prop-coverage",
|
|
1641
|
-
outcome: "unverified",
|
|
1642
|
-
subject: propName,
|
|
1643
|
-
reason: `Required prop '${propName}' value (${JSON.stringify(marker).slice(0, 60)}) not visible in rendered DOM${where}`
|
|
1644
|
-
});
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
return issues;
|
|
1648
|
-
}
|
|
1649
|
-
async function checkStreamRerender(input) {
|
|
1650
|
-
const { container, eventName, probe } = input;
|
|
1651
|
-
if (!probe.getRegistered().streams.includes(eventName)) {
|
|
1652
|
-
return {
|
|
1653
|
-
check: "stream-rerender",
|
|
1654
|
-
outcome: "unverified",
|
|
1655
|
-
subject: eventName,
|
|
1656
|
-
reason: `useStream('${eventName}') was never called \u2014 the stream event is declared in the contract but the component does not subscribe to it`
|
|
1657
|
-
};
|
|
1658
|
-
}
|
|
1659
|
-
const before = container.textContent ?? "";
|
|
1660
|
-
const marker = `__probe_marker_${Date.now()}__`;
|
|
1661
|
-
const payload = { id: marker, text: marker, value: marker, message: marker, name: marker };
|
|
1662
|
-
await new Promise((resolve) => {
|
|
1663
|
-
setTimeout(() => {
|
|
1664
|
-
probe.emitStream(eventName, payload);
|
|
1665
|
-
resolve();
|
|
1666
|
-
}, 0);
|
|
1667
|
-
});
|
|
1668
|
-
await flushPromises();
|
|
1669
|
-
const after = container.textContent ?? "";
|
|
1670
|
-
if (after === before) {
|
|
1671
|
-
return {
|
|
1672
|
-
check: "stream-rerender",
|
|
1673
|
-
outcome: "unverified",
|
|
1674
|
-
subject: eventName,
|
|
1675
|
-
reason: `useStream('${eventName}') is subscribed but emitting a payload did not change the DOM`
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
if (!after.includes(marker)) {
|
|
1679
|
-
return null;
|
|
1680
|
-
}
|
|
1681
|
-
return null;
|
|
1682
|
-
}
|
|
1683
|
-
function matchesActionIdentity(el, name, label) {
|
|
1684
|
-
const norm = (s) => s.toLowerCase().replace(/[\s_-]+/g, "");
|
|
1685
|
-
const aria = (el.getAttribute("aria-label") ?? "").toLowerCase();
|
|
1686
|
-
const txt = (el.textContent ?? "").toLowerCase();
|
|
1687
|
-
const dataAction = (el.getAttribute("data-action") ?? "").toLowerCase();
|
|
1688
|
-
return aria.includes(label.toLowerCase()) || txt.includes(label.toLowerCase()) || norm(aria).includes(norm(name)) || norm(txt).includes(norm(name)) || dataAction === name.toLowerCase() || norm(txt).includes(norm(label));
|
|
1689
|
-
}
|
|
1690
|
-
function findActionElements(container, name, label, options = {}) {
|
|
1691
|
-
const candidates = [];
|
|
1692
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1693
|
-
const allClickable = container.querySelectorAll(
|
|
1694
|
-
'button, [role="button"], input[type="submit"], input[type="button"], a[href]'
|
|
1695
|
-
);
|
|
1696
|
-
for (const el of allClickable) {
|
|
1697
|
-
if (matchesActionIdentity(el, name, label)) {
|
|
1698
|
-
if (!seen.has(el)) {
|
|
1699
|
-
seen.add(el);
|
|
1700
|
-
candidates.push(el);
|
|
1701
|
-
}
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
if (options.identityOnly) return candidates;
|
|
1705
|
-
for (const el of allClickable) {
|
|
1706
|
-
if (!seen.has(el)) {
|
|
1707
|
-
seen.add(el);
|
|
1708
|
-
candidates.push(el);
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
return candidates;
|
|
1712
|
-
}
|
|
1713
|
-
function describeElement(el) {
|
|
1714
|
-
const tag = el.tagName.toLowerCase();
|
|
1715
|
-
const aria = el.getAttribute("aria-label");
|
|
1716
|
-
const txt = (el.textContent ?? "").trim().slice(0, 30);
|
|
1717
|
-
if (aria) return `<${tag} aria-label="${aria}">`;
|
|
1718
|
-
if (txt) return `<${tag}>${txt}</${tag}>`;
|
|
1719
|
-
return `<${tag}>`;
|
|
1720
|
-
}
|
|
1721
|
-
function pickScalarMarker(value) {
|
|
1722
|
-
if (typeof value === "string" && value.length > 0) return value;
|
|
1723
|
-
if (typeof value === "number") return String(value);
|
|
1724
|
-
if (Array.isArray(value)) {
|
|
1725
|
-
for (const item of value) {
|
|
1726
|
-
const m = pickScalarMarker(item);
|
|
1727
|
-
if (m) return m;
|
|
1728
|
-
}
|
|
1729
|
-
return null;
|
|
1730
|
-
}
|
|
1731
|
-
if (value && typeof value === "object") {
|
|
1732
|
-
for (const v of Object.values(value)) {
|
|
1733
|
-
const m = pickScalarMarker(v);
|
|
1734
|
-
if (m) return m;
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
return null;
|
|
1738
|
-
}
|
|
1739
|
-
function flushPromises() {
|
|
1740
|
-
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
1741
|
-
}
|
|
1742
|
-
|
|
1743
|
-
// src/harness/check/runtime-render/render-check-worker.ts
|
|
1744
|
-
init_verdict_line();
|
|
1745
10
|
async function readAllStdin() {
|
|
1746
11
|
const chunks = [];
|
|
1747
12
|
for await (const chunk of process.stdin) {
|