@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.
Files changed (106) hide show
  1. package/README.md +2 -2
  2. package/dist/{adapter-Cg_rkpGq.d.ts → adapter-BJwn74rx.d.ts} +49 -1
  3. package/dist/adapters/generation-dispatch.d.ts +8 -5
  4. package/dist/adapters/generation-dispatch.js +1118 -193
  5. package/dist/adapters/generation-dispatch.js.map +1 -1
  6. package/dist/adapters/index.js +27 -6
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/advanced/index.d.ts +10 -9
  9. package/dist/advanced/index.js +1126 -202
  10. package/dist/advanced/index.js.map +1 -1
  11. package/dist/blueprint-validator.js +416 -92
  12. package/dist/blueprint-validator.js.map +1 -1
  13. package/dist/boilerplate.d.ts +2 -2
  14. package/dist/boilerplate.js +10 -3
  15. package/dist/boilerplate.js.map +1 -1
  16. package/dist/check/index.d.ts +3 -2
  17. package/dist/check/index.js +9 -2
  18. package/dist/check/index.js.map +1 -1
  19. package/dist/{create-ui-generator-DFnnArrv.d.ts → create-ui-generator-N4gRXpcL.d.ts} +3 -4
  20. package/dist/{design-mode-DLufhrNp.d.ts → design-mode-DGWN_Gkm.d.ts} +1 -1
  21. package/dist/{dispatch-CStl9eAw.d.ts → dispatch-BUoXRj_r.d.ts} +2 -2
  22. package/dist/evaluation/axis-checks/index.d.ts +5 -4
  23. package/dist/evaluation/axis-checks/index.js.map +1 -1
  24. package/dist/evaluation/axis-checks/registry.d.ts +4 -3
  25. package/dist/evaluation/index.d.ts +107 -539
  26. package/dist/evaluation/index.js +552 -55
  27. package/dist/evaluation/index.js.map +1 -1
  28. package/dist/evaluation/types.d.ts +17 -0
  29. package/dist/evaluation/types.js.map +1 -1
  30. package/dist/{generate-9IeNT4ZY.d.ts → generate-tSO_shQ0.d.ts} +1 -1
  31. package/dist/harness/check/runtime-render/index.d.ts +5 -5
  32. package/dist/harness/check/runtime-render/index.js +416 -92
  33. package/dist/harness/check/runtime-render/index.js.map +1 -1
  34. package/dist/harness/check/runtime-render/render-check-worker.js +393 -88
  35. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  36. package/dist/harness/index.d.ts +4 -5
  37. package/dist/harness/index.js +36 -5
  38. package/dist/harness/index.js.map +1 -1
  39. package/dist/harness/prompts.d.ts +2 -2
  40. package/dist/harness/prompts.js +10 -1
  41. package/dist/harness/prompts.js.map +1 -1
  42. package/dist/harness/result-types.d.ts +17 -3
  43. package/dist/harness/result-types.js.map +1 -1
  44. package/dist/harness/runtime.d.ts +14 -6
  45. package/dist/harness/runtime.js +11 -4
  46. package/dist/harness/runtime.js.map +1 -1
  47. package/dist/harness/types-public.d.ts +4 -2
  48. package/dist/harness/types-public.js +10 -3
  49. package/dist/harness/types-public.js.map +1 -1
  50. package/dist/harness/validator-trace-sink.d.ts +4 -3
  51. package/dist/index.d.ts +46 -11
  52. package/dist/index.js +1128 -200
  53. package/dist/index.js.map +1 -1
  54. package/dist/patch.d.ts +3 -3
  55. package/dist/{prompts-YU9piOgs.d.ts → prompts-BhNUuRoZ.d.ts} +1 -1
  56. package/dist/tools/render-check-worker.js +8 -1
  57. package/dist/tools/render-check-worker.js.map +1 -1
  58. package/dist/{types-public-BJTLpzWn.d.ts → types-public-B7wn4ce5.d.ts} +384 -3
  59. package/dist/validation/index.d.ts +1 -1
  60. package/dist/validation/index.js +1 -1
  61. package/dist/validation/index.js.map +1 -1
  62. package/dist/visual-evaluator-C9L-o6ap.d.ts +639 -0
  63. package/dist/workflows.d.ts +3 -3
  64. package/package.json +12 -12
  65. package/src/adapters/generation-dispatch.ts +2 -0
  66. package/src/boilerplate/free-design-prompt.ts +3 -3
  67. package/src/boilerplate/hard-sections.ts +1 -1
  68. package/src/boilerplate/templates/layouts/chat-mobile.tsx.tmpl +3 -1
  69. package/src/boilerplate/templates/layouts/chat-universal.tsx.tmpl +3 -1
  70. package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +3 -1
  71. package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +3 -1
  72. package/src/check/run-tier0.ts +6 -3
  73. package/src/contract-context.ts +1 -1
  74. package/src/evaluation/cost-tracker.ts +54 -6
  75. package/src/evaluation/criteria/edge-probe.ts +286 -0
  76. package/src/evaluation/criteria/edge.ts +202 -0
  77. package/src/evaluation/criteria/resolve.ts +55 -7
  78. package/src/evaluation/index.ts +5 -1
  79. package/src/evaluation/judge-spend.ts +48 -0
  80. package/src/evaluation/types-public.ts +91 -0
  81. package/src/evaluation/types.ts +10 -0
  82. package/src/evaluation/visual-evaluator.ts +236 -40
  83. package/src/harness/check/runtime-render/adapter.ts +18 -0
  84. package/src/harness/check/runtime-render/probe-pending.ts +111 -0
  85. package/src/harness/check/runtime-render/render-check-host.ts +10 -6
  86. package/src/harness/check/runtime-render/render-check-worker.ts +6 -3
  87. package/src/harness/check/runtime-render/render-check.ts +417 -133
  88. package/src/harness/check/runtime-render/walk.ts +200 -0
  89. package/src/harness/coding/assemble-result.ts +2 -0
  90. package/src/harness/coding/generate-task-runner.ts +27 -0
  91. package/src/harness/coding/init-session.ts +5 -3
  92. package/src/harness/coding/run-coding-turn.ts +5 -1
  93. package/src/harness/coding/run-eval-round.ts +63 -3
  94. package/src/harness/in-loop-evaluation.ts +39 -0
  95. package/src/harness/llm-router.ts +58 -1
  96. package/src/harness/result-types.ts +9 -0
  97. package/src/harness/runtime.ts +9 -0
  98. package/src/harness/types-public.ts +10 -1
  99. package/src/index.ts +5 -0
  100. package/src/internal/verdict-line.ts +33 -0
  101. package/src/taught-tokens.ts +32 -0
  102. package/src/tools/render-check-worker.ts +2 -1
  103. package/src/tools/render-check.ts +8 -7
  104. package/src/validation/primitives.ts +1 -1
  105. package/dist/llm-router-nfO7wPec.d.ts +0 -267
  106. 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
- // src/harness/check/runtime-render/render-check-worker.ts
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
- container,
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) issues.push({ ...issue, diagnostics: { ...issue.diagnostics, inputPriming } });
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 { container, actionName, actionLabel, wiring, probe, user } = input;
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 fired = await simulateAndCheck({
1111
- container,
1112
- user,
1113
- probe,
1114
- wiringKind: wiring.kind,
1115
- actionName,
1116
- actionLabel,
1117
- eventKind: "action.fired"
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
- if (fired.fired) return null;
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 ${wiring.kind} wiring exists for action '${actionName}', but synthetic ${wiring.kind} did not dispatch it. Likely cause: conditional rendering, required input/form fill, or a multi-step interaction the static probe cannot complete.`,
1125
- elementHint: fired.attemptedHint,
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: fired.otherActionsFired
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 findCandidateElements(container, kind, name, label) {
1308
- switch (kind) {
1309
- case "click":
1310
- return findActionElements(container, name, label);
1311
- case "submit": {
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 text = container.textContent ?? "";
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 findActionElements(container, name, label) {
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
- const aria = (el.getAttribute("aria-label") ?? "").toLowerCase();
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(`${JSON.stringify(value)}
1448
- `);
1753
+ process.stdout.write(formatVerdictLine(value));
1449
1754
  }
1450
1755
  async function workerMain(raw) {
1451
1756
  try {