@ggui-ai/ui-gen 0.25.0 → 0.26.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/README.md +2 -2
- package/dist/{adapter-Cg_rkpGq.d.ts → adapter-BJwn74rx.d.ts} +49 -1
- package/dist/adapters/generation-dispatch.d.ts +8 -5
- package/dist/adapters/generation-dispatch.js +1118 -193
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +27 -6
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +10 -9
- package/dist/advanced/index.js +1126 -202
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +416 -92
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +2 -2
- package/dist/boilerplate.js +10 -3
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +9 -2
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-DFnnArrv.d.ts → create-ui-generator-N4gRXpcL.d.ts} +3 -4
- package/dist/{design-mode-DLufhrNp.d.ts → design-mode-DGWN_Gkm.d.ts} +1 -1
- package/dist/{dispatch-CStl9eAw.d.ts → dispatch-BUoXRj_r.d.ts} +2 -2
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +4 -3
- package/dist/evaluation/index.d.ts +107 -539
- package/dist/evaluation/index.js +552 -55
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +17 -0
- package/dist/evaluation/types.js.map +1 -1
- package/dist/{generate-9IeNT4ZY.d.ts → generate-tSO_shQ0.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +5 -5
- package/dist/harness/check/runtime-render/index.js +416 -92
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +393 -88
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +4 -5
- package/dist/harness/index.js +36 -5
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +10 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +17 -3
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +14 -6
- package/dist/harness/runtime.js +11 -4
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +4 -2
- package/dist/harness/types-public.js +10 -3
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +4 -3
- package/dist/index.d.ts +46 -11
- package/dist/index.js +1128 -200
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +3 -3
- package/dist/{prompts-YU9piOgs.d.ts → prompts-BhNUuRoZ.d.ts} +1 -1
- package/dist/tools/render-check-worker.js +8 -1
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-BJTLpzWn.d.ts → types-public-B7wn4ce5.d.ts} +384 -3
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/visual-evaluator-C9L-o6ap.d.ts +639 -0
- package/dist/workflows.d.ts +3 -3
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +2 -0
- package/src/boilerplate/free-design-prompt.ts +3 -3
- package/src/boilerplate/hard-sections.ts +1 -1
- package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +3 -1
- package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +3 -1
- package/src/check/run-tier0.ts +6 -3
- package/src/contract-context.ts +1 -1
- package/src/evaluation/cost-tracker.ts +54 -6
- package/src/evaluation/criteria/edge-probe.ts +286 -0
- package/src/evaluation/criteria/edge.ts +202 -0
- package/src/evaluation/criteria/resolve.ts +55 -7
- package/src/evaluation/index.ts +5 -1
- package/src/evaluation/judge-spend.ts +48 -0
- package/src/evaluation/types-public.ts +91 -0
- package/src/evaluation/types.ts +10 -0
- package/src/evaluation/visual-evaluator.ts +236 -40
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/probe-pending.ts +111 -0
- package/src/harness/check/runtime-render/render-check-host.ts +10 -6
- package/src/harness/check/runtime-render/render-check-worker.ts +6 -3
- package/src/harness/check/runtime-render/render-check.ts +417 -133
- package/src/harness/check/runtime-render/walk.ts +200 -0
- package/src/harness/coding/assemble-result.ts +2 -0
- package/src/harness/coding/generate-task-runner.ts +27 -0
- package/src/harness/coding/init-session.ts +5 -3
- package/src/harness/coding/run-coding-turn.ts +5 -1
- package/src/harness/coding/run-eval-round.ts +63 -3
- package/src/harness/in-loop-evaluation.ts +39 -0
- package/src/harness/llm-router.ts +58 -1
- package/src/harness/result-types.ts +9 -0
- package/src/harness/runtime.ts +9 -0
- package/src/harness/types-public.ts +10 -1
- package/src/index.ts +5 -0
- package/src/internal/verdict-line.ts +33 -0
- package/src/taught-tokens.ts +32 -0
- package/src/tools/render-check-worker.ts +2 -1
- package/src/tools/render-check.ts +8 -7
- package/src/validation/primitives.ts +1 -1
- package/dist/llm-router-nfO7wPec.d.ts +0 -267
- package/dist/{bank-CT4eEkta.d.ts → bank-Ctdp3FUA.d.ts} +3 -3
|
@@ -7,7 +7,22 @@ import ts from 'typescript';
|
|
|
7
7
|
import { createRequire } from 'module';
|
|
8
8
|
import { primeInputs } from '@ggui-ai/ui-visual-tester/prime-inputs';
|
|
9
9
|
|
|
10
|
-
|
|
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
|
+
});
|
|
11
26
|
|
|
12
27
|
// src/harness/check/runtime-render/probe.ts
|
|
13
28
|
function makeInternals() {
|
|
@@ -526,6 +541,169 @@ function editableSurface(el) {
|
|
|
526
541
|
function parentLink(node) {
|
|
527
542
|
return node;
|
|
528
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;
|
|
529
707
|
async function runRenderCheckInProcess(input) {
|
|
530
708
|
const t0 = Date.now();
|
|
531
709
|
const issues = [];
|
|
@@ -583,7 +761,9 @@ async function runRenderCheckInProcess(input) {
|
|
|
583
761
|
const wireConfig = createProbeWireConfig(probe);
|
|
584
762
|
uninstallSpy = probe.installPostMessageSpy();
|
|
585
763
|
installProductionActShim();
|
|
586
|
-
const { render, cleanup } = await import('@testing-library/react');
|
|
764
|
+
const { render, cleanup, act } = await import('@testing-library/react');
|
|
765
|
+
const { ActionPendingContext } = await import('@ggui-ai/wire/internal');
|
|
766
|
+
const pendingSource = createProbePendingSource();
|
|
587
767
|
const userEventModule = await import('@testing-library/user-event');
|
|
588
768
|
const userEvent = userEventModule.default ?? userEventModule;
|
|
589
769
|
const originalConsoleError = console.error;
|
|
@@ -620,7 +800,8 @@ async function runRenderCheckInProcess(input) {
|
|
|
620
800
|
};
|
|
621
801
|
const boundaryRef = {
|
|
622
802
|
stack: null,
|
|
623
|
-
error: null
|
|
803
|
+
error: null,
|
|
804
|
+
catches: 0
|
|
624
805
|
};
|
|
625
806
|
class ProbeErrorBoundary extends React.Component {
|
|
626
807
|
state = { caught: false };
|
|
@@ -628,6 +809,7 @@ async function runRenderCheckInProcess(input) {
|
|
|
628
809
|
return { caught: true };
|
|
629
810
|
}
|
|
630
811
|
componentDidCatch(error, errorInfo) {
|
|
812
|
+
boundaryRef.catches += 1;
|
|
631
813
|
if (errorInfo.componentStack && !boundaryRef.stack) {
|
|
632
814
|
boundaryRef.stack = errorInfo.componentStack;
|
|
633
815
|
}
|
|
@@ -640,19 +822,19 @@ async function runRenderCheckInProcess(input) {
|
|
|
640
822
|
return this.props.children;
|
|
641
823
|
}
|
|
642
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
|
+
});
|
|
643
834
|
let renderResult;
|
|
644
835
|
const RENDER_TIMEOUT_MS = 5e3;
|
|
645
836
|
try {
|
|
646
|
-
const renderPromise = Promise.resolve().then(
|
|
647
|
-
() => render(
|
|
648
|
-
React.createElement(GguiWireProvider, {
|
|
649
|
-
config: wireConfig,
|
|
650
|
-
children: React.createElement(ProbeErrorBoundary, {
|
|
651
|
-
children: React.createElement(Component, input.mockupProps)
|
|
652
|
-
})
|
|
653
|
-
})
|
|
654
|
-
)
|
|
655
|
-
);
|
|
837
|
+
const renderPromise = Promise.resolve().then(() => render(cardTree()));
|
|
656
838
|
const timeoutPromise = new Promise((_, reject) => {
|
|
657
839
|
setTimeout(
|
|
658
840
|
() => reject(
|
|
@@ -704,6 +886,9 @@ async function runRenderCheckInProcess(input) {
|
|
|
704
886
|
const user = buildClickUser(userEvent, hostGlobals().document);
|
|
705
887
|
let actionsChecked = 0;
|
|
706
888
|
let streamsChecked = 0;
|
|
889
|
+
let pendingTally;
|
|
890
|
+
const pendingTargets = [];
|
|
891
|
+
const walkedTexts = [];
|
|
707
892
|
try {
|
|
708
893
|
let inputPriming;
|
|
709
894
|
try {
|
|
@@ -720,15 +905,42 @@ async function runRenderCheckInProcess(input) {
|
|
|
720
905
|
hookName: "useAction",
|
|
721
906
|
hookArg: name
|
|
722
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
|
+
};
|
|
723
929
|
const issue = await checkActionWiring({
|
|
724
|
-
|
|
930
|
+
mount,
|
|
725
931
|
actionName: name,
|
|
726
932
|
actionLabel: entry.label,
|
|
727
933
|
wiring,
|
|
728
934
|
probe,
|
|
729
|
-
user
|
|
935
|
+
user,
|
|
936
|
+
pendingTargets,
|
|
937
|
+
stopAt: walkStopAt(input.deadlineAt),
|
|
938
|
+
walkedTexts
|
|
730
939
|
});
|
|
731
|
-
if (issue)
|
|
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
|
+
}
|
|
732
944
|
}
|
|
733
945
|
}
|
|
734
946
|
if (input.contract?.actionSpec) {
|
|
@@ -750,6 +962,7 @@ async function runRenderCheckInProcess(input) {
|
|
|
750
962
|
if (input.contract?.propsSpec) {
|
|
751
963
|
for (const issue of checkPropCoverage({
|
|
752
964
|
container,
|
|
965
|
+
walkedTexts,
|
|
753
966
|
propsSpec: input.contract.propsSpec,
|
|
754
967
|
mockupProps: input.mockupProps
|
|
755
968
|
})) {
|
|
@@ -855,10 +1068,21 @@ async function runRenderCheckInProcess(input) {
|
|
|
855
1068
|
}
|
|
856
1069
|
}
|
|
857
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
|
+
}
|
|
858
1082
|
} finally {
|
|
859
1083
|
cleanup();
|
|
860
1084
|
}
|
|
861
|
-
return finalize(issues, t0, actionsChecked, streamsChecked);
|
|
1085
|
+
return finalize(issues, t0, actionsChecked, streamsChecked, pendingTally);
|
|
862
1086
|
} finally {
|
|
863
1087
|
try {
|
|
864
1088
|
uninstallSpy?.();
|
|
@@ -1070,7 +1294,7 @@ async function setupHappyDom() {
|
|
|
1070
1294
|
delete g.IS_REACT_ACT_ENVIRONMENT;
|
|
1071
1295
|
};
|
|
1072
1296
|
}
|
|
1073
|
-
function finalize(issues, t0, actionsChecked, streamsChecked) {
|
|
1297
|
+
function finalize(issues, t0, actionsChecked, streamsChecked, pendingTally) {
|
|
1074
1298
|
const ok = !issues.some((i) => i.outcome === "failed");
|
|
1075
1299
|
return {
|
|
1076
1300
|
ok,
|
|
@@ -1078,12 +1302,86 @@ function finalize(issues, t0, actionsChecked, streamsChecked) {
|
|
|
1078
1302
|
stats: {
|
|
1079
1303
|
actionsChecked,
|
|
1080
1304
|
streamsChecked,
|
|
1081
|
-
renderMs: Date.now() - t0
|
|
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
|
+
} : {}
|
|
1082
1314
|
}
|
|
1083
1315
|
};
|
|
1084
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
|
+
}
|
|
1085
1383
|
async function checkActionWiring(input) {
|
|
1086
|
-
const {
|
|
1384
|
+
const { actionName, actionLabel, wiring, probe, user } = input;
|
|
1087
1385
|
const baseDiagnostics = {
|
|
1088
1386
|
observedJsxElements: wiring.observedJsxElements,
|
|
1089
1387
|
observedNativeProps: wiring.observedNativeProps,
|
|
@@ -1107,25 +1405,56 @@ async function checkActionWiring(input) {
|
|
|
1107
1405
|
diagnostics: baseDiagnostics
|
|
1108
1406
|
};
|
|
1109
1407
|
}
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
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
|
|
1118
1425
|
});
|
|
1119
|
-
|
|
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;
|
|
1120
1442
|
return {
|
|
1121
1443
|
check: "action-wiring",
|
|
1122
1444
|
outcome: "unverified",
|
|
1123
1445
|
subject: actionName,
|
|
1124
|
-
reason: `Source confirms ${
|
|
1125
|
-
elementHint:
|
|
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` } : {},
|
|
1126
1448
|
diagnostics: {
|
|
1127
1449
|
...baseDiagnostics,
|
|
1128
|
-
actionsFiredFromClicks:
|
|
1450
|
+
actionsFiredFromClicks: otherActionsFired,
|
|
1451
|
+
walk: {
|
|
1452
|
+
presses: walk.presses,
|
|
1453
|
+
screens: walk.screens,
|
|
1454
|
+
stoppedBy,
|
|
1455
|
+
namedControlSeen: walk.namedControlSeen,
|
|
1456
|
+
lastPressed: walk.lastPressed
|
|
1457
|
+
}
|
|
1129
1458
|
}
|
|
1130
1459
|
};
|
|
1131
1460
|
}
|
|
@@ -1193,32 +1522,6 @@ async function checkSelectionIdentity(input) {
|
|
|
1193
1522
|
}
|
|
1194
1523
|
return null;
|
|
1195
1524
|
}
|
|
1196
|
-
async function simulateAndCheck(input) {
|
|
1197
|
-
const { container, user, probe, wiringKind, actionName, actionLabel, eventKind } = input;
|
|
1198
|
-
const candidates = findCandidateElements(container, wiringKind, actionName, actionLabel);
|
|
1199
|
-
if (candidates.length === 0) {
|
|
1200
|
-
return { fired: false, attemptedHint: `No ${wiringKind}-eligible element found in DOM` };
|
|
1201
|
-
}
|
|
1202
|
-
for (const el of candidates) {
|
|
1203
|
-
const before = probe.getFireLog().length;
|
|
1204
|
-
try {
|
|
1205
|
-
await dispatchTrigger(el, wiringKind, user);
|
|
1206
|
-
} catch {
|
|
1207
|
-
continue;
|
|
1208
|
-
}
|
|
1209
|
-
await flushPromises();
|
|
1210
|
-
const newEvents = probe.getFireLog().slice(before);
|
|
1211
|
-
const matched = newEvents.some((e) => e.kind === eventKind && e.name === actionName);
|
|
1212
|
-
if (matched) return { fired: true };
|
|
1213
|
-
}
|
|
1214
|
-
const allFired = probe.getFireLog().filter((e) => e.kind === "action.fired").map((e) => e.name);
|
|
1215
|
-
const otherActionsFired = Array.from(new Set(allFired)).filter((n) => n !== actionName);
|
|
1216
|
-
return {
|
|
1217
|
-
fired: false,
|
|
1218
|
-
attemptedHint: candidates.length ? describeElement(candidates[0]) : void 0,
|
|
1219
|
-
otherActionsFired
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
1525
|
async function dispatchTrigger(el, kind, user) {
|
|
1223
1526
|
switch (kind) {
|
|
1224
1527
|
case "click":
|
|
@@ -1304,40 +1607,40 @@ function closestForm(el) {
|
|
|
1304
1607
|
}
|
|
1305
1608
|
return null;
|
|
1306
1609
|
}
|
|
1307
|
-
function
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
const all = container.querySelectorAll(
|
|
1313
|
-
'form, button[type="submit"], input[type="submit"]'
|
|
1314
|
-
);
|
|
1315
|
-
return Array.from(all);
|
|
1316
|
-
}
|
|
1317
|
-
case "change": {
|
|
1318
|
-
const all = container.querySelectorAll("select, input, textarea");
|
|
1319
|
-
return Array.from(all);
|
|
1320
|
-
}
|
|
1321
|
-
case "keyboard-enter":
|
|
1322
|
-
return findActionElements(container, name, label);
|
|
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 }));
|
|
1323
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
|
+
];
|
|
1324
1626
|
}
|
|
1325
1627
|
function checkPropCoverage(input) {
|
|
1326
|
-
const { container, propsSpec, mockupProps } = input;
|
|
1628
|
+
const { container, walkedTexts, propsSpec, mockupProps } = input;
|
|
1327
1629
|
const issues = [];
|
|
1328
|
-
const
|
|
1630
|
+
const texts = [container.textContent ?? "", ...walkedTexts];
|
|
1329
1631
|
for (const [propName, entry] of Object.entries(propsSpec.properties)) {
|
|
1330
1632
|
if (!entry.required) continue;
|
|
1331
1633
|
const value = mockupProps[propName];
|
|
1332
1634
|
if (value === void 0 || value === null) continue;
|
|
1333
1635
|
const marker = pickScalarMarker(value);
|
|
1334
1636
|
if (marker === null) continue;
|
|
1335
|
-
if (!text.includes(marker)) {
|
|
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" : "";
|
|
1336
1639
|
issues.push({
|
|
1337
1640
|
check: "prop-coverage",
|
|
1338
1641
|
outcome: "unverified",
|
|
1339
1642
|
subject: propName,
|
|
1340
|
-
reason: `Required prop '${propName}' value (${JSON.stringify(marker).slice(0, 60)}) not visible in rendered DOM`
|
|
1643
|
+
reason: `Required prop '${propName}' value (${JSON.stringify(marker).slice(0, 60)}) not visible in rendered DOM${where}`
|
|
1341
1644
|
});
|
|
1342
1645
|
}
|
|
1343
1646
|
}
|
|
@@ -1377,26 +1680,28 @@ async function checkStreamRerender(input) {
|
|
|
1377
1680
|
}
|
|
1378
1681
|
return null;
|
|
1379
1682
|
}
|
|
1380
|
-
function
|
|
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 = {}) {
|
|
1381
1691
|
const candidates = [];
|
|
1382
1692
|
const seen = /* @__PURE__ */ new Set();
|
|
1383
|
-
const norm = (s) => s.toLowerCase().replace(/[\s_-]+/g, "");
|
|
1384
|
-
const nameKey = norm(name);
|
|
1385
|
-
const labelKey = norm(label);
|
|
1386
1693
|
const allClickable = container.querySelectorAll(
|
|
1387
1694
|
'button, [role="button"], input[type="submit"], input[type="button"], a[href]'
|
|
1388
1695
|
);
|
|
1389
1696
|
for (const el of allClickable) {
|
|
1390
|
-
|
|
1391
|
-
const txt = (el.textContent ?? "").toLowerCase();
|
|
1392
|
-
const dataAction = (el.getAttribute("data-action") ?? "").toLowerCase();
|
|
1393
|
-
if (aria.includes(label.toLowerCase()) || txt.includes(label.toLowerCase()) || norm(aria).includes(nameKey) || norm(txt).includes(nameKey) || dataAction === name.toLowerCase() || norm(txt).includes(labelKey)) {
|
|
1697
|
+
if (matchesActionIdentity(el, name, label)) {
|
|
1394
1698
|
if (!seen.has(el)) {
|
|
1395
1699
|
seen.add(el);
|
|
1396
1700
|
candidates.push(el);
|
|
1397
1701
|
}
|
|
1398
1702
|
}
|
|
1399
1703
|
}
|
|
1704
|
+
if (options.identityOnly) return candidates;
|
|
1400
1705
|
for (const el of allClickable) {
|
|
1401
1706
|
if (!seen.has(el)) {
|
|
1402
1707
|
seen.add(el);
|
|
@@ -1436,6 +1741,7 @@ function flushPromises() {
|
|
|
1436
1741
|
}
|
|
1437
1742
|
|
|
1438
1743
|
// src/harness/check/runtime-render/render-check-worker.ts
|
|
1744
|
+
init_verdict_line();
|
|
1439
1745
|
async function readAllStdin() {
|
|
1440
1746
|
const chunks = [];
|
|
1441
1747
|
for await (const chunk of process.stdin) {
|
|
@@ -1444,8 +1750,7 @@ async function readAllStdin() {
|
|
|
1444
1750
|
return Buffer.concat(chunks).toString("utf8");
|
|
1445
1751
|
}
|
|
1446
1752
|
function emit(value) {
|
|
1447
|
-
process.stdout.write(
|
|
1448
|
-
`);
|
|
1753
|
+
process.stdout.write(formatVerdictLine(value));
|
|
1449
1754
|
}
|
|
1450
1755
|
async function workerMain(raw) {
|
|
1451
1756
|
try {
|