@ggui-ai/ui-gen 0.22.0 → 0.24.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-Du7P_5Fx.d.ts +201 -0
- package/dist/adapters/generation-dispatch.d.ts +33 -12
- package/dist/adapters/generation-dispatch.js +549 -129
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +601 -144
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +178 -54
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +1 -0
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +2 -1
- package/dist/check/index.js +47 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-BEkzEtNz.d.ts → create-ui-generator-GIhzx08x.d.ts} +34 -10
- package/dist/{dispatch-DJNpQ7DI.d.ts → dispatch-BnAH56p4.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +4 -3
- package/dist/evaluation/axis-checks/index.js +19 -4
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -1
- package/dist/evaluation/axis-checks/registry.js +19 -4
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +4 -3
- package/dist/evaluation/index.js +21 -6
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +27 -8
- package/dist/harness/check/runtime-render/index.js +217 -54
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +2 -1
- package/dist/harness/index.js +3 -2
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +2 -1
- package/dist/harness/runtime.d.ts +2 -1
- package/dist/harness/runtime.js +2 -1
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +18 -5
- package/dist/harness/types-public.js +1 -0
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -1
- package/dist/index.d.ts +38 -3
- package/dist/index.js +592 -144
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +2 -1
- package/dist/policy.d.ts +7 -4
- package/dist/policy.js.map +1 -1
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.d.ts +7 -1
- package/dist/tools/render-check-worker.js +55 -23
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-mM_-1Yk3.d.ts → types-public-Bk6d1WLN.d.ts} +98 -9
- 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/workflows.d.ts +2 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +137 -96
- package/src/advanced/generator.ts +16 -1
- package/src/blueprint-validator.ts +21 -6
- package/src/boilerplate/hard-sections.ts +1 -0
- package/src/check/run-tier0.ts +80 -1
- package/src/coding-agent/tools.ts +6 -1
- package/src/create-ui-generator.ts +185 -95
- package/src/evaluation/axis-checks/checks/state-payload.ts +29 -4
- package/src/evaluation/types-public.ts +94 -8
- package/src/harness/check/runtime-render/adapter.ts +223 -58
- package/src/harness/check/runtime-render/index.ts +75 -2
- package/src/harness/check/runtime-render/render-check-host.ts +111 -26
- package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
- package/src/harness/check/runtime-render/render-check.ts +173 -117
- package/src/harness/coding/generate-task-runner.ts +57 -4
- package/src/harness/coding/init-session.ts +13 -0
- package/src/harness/coding/run-eval-round.ts +238 -13
- package/src/harness/llm-router.ts +3 -3
- package/src/harness/policy.ts +11 -0
- package/src/harness/primitive-slice.ts +104 -0
- package/src/harness/types-public.ts +17 -5
- package/src/index.ts +4 -0
- package/src/policy.ts +7 -4
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check-worker.ts +74 -23
- package/src/validation/primitives.ts +1 -1
- package/dist/render-check-C0CqVceF.d.ts +0 -87
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
export type Priority = "P0" | "P1" | "P2";
|
|
22
22
|
|
|
23
|
+
import type { GenerationRuntimeProbeOutcome, GenerationRuntimeProbeStatus } from "@ggui-ai/mcp-server-core";
|
|
23
24
|
import type { CanvasClass, DesignMode } from "../design-mode.js";
|
|
24
25
|
|
|
25
26
|
// ─── Issue shape ───────────────────────────────────────────────────────────
|
|
@@ -63,8 +64,10 @@ export interface EvalIssue {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
* Runtime-render probe execution status —
|
|
67
|
-
* never ran" is never conflated with "the probe ran clean".
|
|
67
|
+
* Runtime-render probe execution status — four-valued so "the probe
|
|
68
|
+
* never ran" is never conflated with "the probe ran clean". ONE union: the
|
|
69
|
+
* same values a generation's metadata reports (`GenerationRuntimeProbeStatus`
|
|
70
|
+
* in `@ggui-ai/mcp-server-core`), declared there and named here.
|
|
68
71
|
*
|
|
69
72
|
* - `ran` — probe executed; its findings (possibly none) are
|
|
70
73
|
* in the issue stream under `runtime:*` subcategories.
|
|
@@ -74,20 +77,96 @@ export interface EvalIssue {
|
|
|
74
77
|
* consumers MUST NOT count it as a pass.
|
|
75
78
|
* - `not-applicable`— nothing to probe (no compiled code, no contract
|
|
76
79
|
* surface, or no runtimeRender check configured).
|
|
80
|
+
* - `timed-out` — probe started but did not finish inside its
|
|
81
|
+
* wall-clock bound (ggui#1299). Carries ZERO
|
|
82
|
+
* evidence about the component: on a contended
|
|
83
|
+
* host an ordinary component crosses the bound.
|
|
84
|
+
* Consumers MUST NOT count it as a pass, and MUST
|
|
85
|
+
* NOT count it as a crash either. `elapsedMs` and
|
|
86
|
+
* `hostLoad` say how long it ran and how busy the
|
|
87
|
+
* host was, so a reader can tell the two cases apart.
|
|
77
88
|
*/
|
|
78
|
-
export type RuntimeProbeStatus =
|
|
89
|
+
export type RuntimeProbeStatus = GenerationRuntimeProbeStatus;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* The host's 1-minute load average at a probe's start and end, beside the
|
|
93
|
+
* number of CPUs the process can use (ggui#1299). A load well above
|
|
94
|
+
* `cores` means the probe competed for CPU; a wall-clock bound crossed
|
|
95
|
+
* there says more about the host than about the component.
|
|
96
|
+
*/
|
|
97
|
+
export interface ProbeHostLoad {
|
|
98
|
+
readonly start: number;
|
|
99
|
+
readonly end: number;
|
|
100
|
+
readonly cores: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The engine's own fields on a probe's meta, beside the outcome a
|
|
105
|
+
* generation's metadata reports: why a probe did not finish, its render
|
|
106
|
+
* time, the host's load around it. Present on every arm of
|
|
107
|
+
* {@link RuntimeProbeMeta}; each is absent when the probe had nothing to
|
|
108
|
+
* say about it.
|
|
109
|
+
*/
|
|
110
|
+
export interface RuntimeProbeMetaDetail {
|
|
111
|
+
/** Populated for `infra-skipped` / `not-applicable` / `timed-out` — why the probe didn't finish. */
|
|
112
|
+
readonly reason?: string;
|
|
113
|
+
/** `ran` only: the check's own render time, inside `elapsedMs`. */
|
|
114
|
+
readonly renderMs?: number;
|
|
115
|
+
/** Host load around the probe, when it ran isolated (absent for an in-process probe). */
|
|
116
|
+
readonly hostLoad?: ProbeHostLoad;
|
|
117
|
+
/** Time the check waited for a probe slot before it started, ms; present only when > 0 (ggui#1380 C2b). */
|
|
118
|
+
readonly queuedMs?: number;
|
|
119
|
+
}
|
|
79
120
|
|
|
80
121
|
/**
|
|
81
122
|
* Probe execution meta stamped onto an `EvalResult` at the exit-decision
|
|
82
123
|
* points that invoke the probe. Absence of this field means the eval
|
|
83
124
|
* path never reached a probe invocation at all — consumers treat that
|
|
84
125
|
* the same as not-run, never as a pass.
|
|
126
|
+
*
|
|
127
|
+
* ONE union with the generation metadata's probe outcome
|
|
128
|
+
* (`GenerationRuntimeProbeOutcome` in `@ggui-ai/mcp-server-core`, declared
|
|
129
|
+
* there and carried here with {@link RuntimeProbeMetaDetail} on every arm),
|
|
130
|
+
* so the round's stamp and the metadata's record cannot say different
|
|
131
|
+
* things (ggui#1380):
|
|
132
|
+
*
|
|
133
|
+
* - `ran` + `verdict: 'pass'` — the probe executed and no check failed;
|
|
134
|
+
* - `ran` + `verdict: 'fail'` — ANY check failed; `failChecks` lists the
|
|
135
|
+
* distinct check kinds that did (`RenderCheckKind`, the engine's name
|
|
136
|
+
* for the same union), never empty, in the order `RENDER_CHECK_KINDS`
|
|
137
|
+
* declares them, parsed from the probe's own
|
|
138
|
+
* `runtime:<check>[:<subject>]` issues;
|
|
139
|
+
* - `timed-out` / `infra-skipped` / `not-applicable` — no verdict, and the
|
|
140
|
+
* type carries none. `elapsedMs` (on every status that reached the
|
|
141
|
+
* check) and `hostLoad` say how long it ran and how busy the host was.
|
|
142
|
+
*
|
|
143
|
+
* The verdict and the repair trigger are two different questions. The
|
|
144
|
+
* verdict is `fail` on any failing check. The one repair turn a probe-only
|
|
145
|
+
* round buys fires on a `render-no-throw` fail the engine recognises
|
|
146
|
+
* (`isRecoverableRenderCrash`) and on nothing else: a `fail` verdict whose
|
|
147
|
+
* checks are `prop-sensitivity`, `action-wiring` or any other kind is
|
|
148
|
+
* recorded here and never fed back.
|
|
85
149
|
*/
|
|
86
|
-
export
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
150
|
+
export type RuntimeProbeMeta = GenerationRuntimeProbeOutcome & RuntimeProbeMetaDetail;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The one repair turn a probe-only round buys (ggui#1380). A serving
|
|
154
|
+
* deployment that wires the runtime probe and configures no evaluator runs
|
|
155
|
+
* the probe once after the coding turns; a render crash the engine
|
|
156
|
+
* recognises buys exactly one repair turn, then one re-probe. This records
|
|
157
|
+
* what came of it:
|
|
158
|
+
* - `compiled: false` — the repair turn's code did not pass self-check, so
|
|
159
|
+
* no re-probe ran (there is no `after`) and the pre-repair card is the
|
|
160
|
+
* one served;
|
|
161
|
+
* - `compiled: true` — the re-probe ran and `after` is its whole meta:
|
|
162
|
+
* its status, its verdict when it ran, and the checks that still fail.
|
|
163
|
+
* Whether the crash class the turn was bought for is still there is
|
|
164
|
+
* `after.verdict === 'fail' && after.failChecks.includes('render-no-throw')`.
|
|
165
|
+
* The two arms are exclusive by type.
|
|
166
|
+
*/
|
|
167
|
+
export type RuntimeProbeRepair =
|
|
168
|
+
| { readonly attempted: true; readonly compiled: false; readonly trigger?: never }
|
|
169
|
+
| { readonly attempted: true; readonly compiled: true; readonly trigger: RuntimeProbeMeta };
|
|
91
170
|
|
|
92
171
|
/**
|
|
93
172
|
* Per-criterion execution status — three-valued so "the criterion
|
|
@@ -202,6 +281,13 @@ export interface EvalResult {
|
|
|
202
281
|
* probe runner; absent on eval paths that never invoke the probe.
|
|
203
282
|
*/
|
|
204
283
|
runtimeProbe?: RuntimeProbeMeta;
|
|
284
|
+
/**
|
|
285
|
+
* The probe-only round's repair record (ggui#1380) — present only when a
|
|
286
|
+
* recoverable render crash bought the one repair turn. See
|
|
287
|
+
* {@link RuntimeProbeRepair}. `runtimeProbe` beside it is the re-probe's
|
|
288
|
+
* verdict when the repair compiled, else the pre-repair probe's.
|
|
289
|
+
*/
|
|
290
|
+
runtimeProbeRepair?: RuntimeProbeRepair;
|
|
205
291
|
/**
|
|
206
292
|
* The contract-feedback round, when one fired this generation (ggui#1261):
|
|
207
293
|
* which exit-probe findings bought it and the source as it stood when the
|
|
@@ -6,62 +6,205 @@
|
|
|
6
6
|
|
|
7
7
|
import type { EvalIssue } from "../../../evaluation/types-public.js";
|
|
8
8
|
import type { RuntimeRenderCheck } from "../../types-public.js";
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
RENDER_CHECK_KINDS,
|
|
11
|
+
runRenderCheck,
|
|
12
|
+
type RenderCheckIssue,
|
|
13
|
+
type RenderCheckKind,
|
|
14
|
+
type RunRenderCheckOptions,
|
|
15
|
+
} from "./render-check.js";
|
|
16
|
+
import { resolveRenderCheckHostBounds } from "./render-check-host.js";
|
|
10
17
|
import { prepareMockupProps } from "./prepare-mockup.js";
|
|
11
18
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
/**
|
|
20
|
+
* How one runtime-render check instance runs its isolated worker
|
|
21
|
+
* (ggui#1380). A serving deployment sets the probe's wall-clock bound,
|
|
22
|
+
* worker heap and concurrency here; every field omitted is the evaluation
|
|
23
|
+
* lane's behaviour — the host's default bounds and no concurrency cap.
|
|
24
|
+
*/
|
|
25
|
+
export interface RuntimeRenderProbeConfig {
|
|
26
|
+
/** Wall-clock bound of one isolated check, ms. Default: the host's (30 000). */
|
|
27
|
+
readonly timeoutMs?: number;
|
|
28
|
+
/** V8 heap cap of the worker, MB. Default: the host's (512). */
|
|
29
|
+
readonly heapMb?: number;
|
|
30
|
+
/**
|
|
31
|
+
* Live workers this instance may have at once. The (K+1)th check waits
|
|
32
|
+
* in FIFO order and spawns only when a slot frees; a check that throws
|
|
33
|
+
* releases its slot. Default: unbounded. Must be a positive integer.
|
|
34
|
+
*/
|
|
35
|
+
readonly maxConcurrent?: number;
|
|
36
|
+
}
|
|
16
37
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
38
|
+
/**
|
|
39
|
+
* K-slot FIFO limiter. `undefined` slots = no limiter (the task runs at
|
|
40
|
+
* once). A slot is released in `finally`, so a rejected task never holds
|
|
41
|
+
* one.
|
|
42
|
+
*/
|
|
43
|
+
function createSlotLimiter(
|
|
44
|
+
maxConcurrent: number | undefined
|
|
45
|
+
): <T>(task: (waitedMs: number) => Promise<T>) => Promise<T> {
|
|
46
|
+
if (maxConcurrent === undefined) return (task) => task(0);
|
|
47
|
+
if (!Number.isInteger(maxConcurrent) || maxConcurrent < 1) {
|
|
48
|
+
throw new RangeError(
|
|
49
|
+
`createRuntimeRenderCheck: maxConcurrent must be a positive integer, got ${maxConcurrent}`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
let active = 0;
|
|
53
|
+
const waiting: Array<() => void> = [];
|
|
54
|
+
/** Resolves with the time the caller actually WAITED for a slot: 0 when one was free. */
|
|
55
|
+
const acquire = (): Promise<number> => {
|
|
56
|
+
if (active < maxConcurrent) {
|
|
57
|
+
active += 1;
|
|
58
|
+
return Promise.resolve(0);
|
|
21
59
|
}
|
|
22
|
-
|
|
23
|
-
|
|
60
|
+
const enqueuedAt = Date.now();
|
|
61
|
+
return new Promise<number>((resolve) => {
|
|
62
|
+
waiting.push(() => {
|
|
63
|
+
active += 1;
|
|
64
|
+
resolve(Date.now() - enqueuedAt);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
const release = (): void => {
|
|
69
|
+
active -= 1;
|
|
70
|
+
const next = waiting.shift();
|
|
71
|
+
if (next !== undefined) next();
|
|
72
|
+
};
|
|
73
|
+
return async (task) => {
|
|
74
|
+
const waitedMs = await acquire();
|
|
75
|
+
try {
|
|
76
|
+
return await task(waitedMs);
|
|
77
|
+
} finally {
|
|
78
|
+
release();
|
|
24
79
|
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
25
82
|
|
|
26
|
-
|
|
83
|
+
/** `{ queuedMs }` when the check actually waited for a slot, `{}` when one was free. */
|
|
84
|
+
function queuedField(queuedMs: number): { readonly queuedMs?: number } {
|
|
85
|
+
return queuedMs > 0 ? { queuedMs } : {};
|
|
86
|
+
}
|
|
27
87
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
88
|
+
/** The `runRenderCheck` options a config maps to — `undefined` when no bound is set. */
|
|
89
|
+
function toRenderCheckOptions(config: RuntimeRenderProbeConfig): RunRenderCheckOptions | undefined {
|
|
90
|
+
if (config.timeoutMs === undefined && config.heapMb === undefined) return undefined;
|
|
91
|
+
return {
|
|
92
|
+
bounds: {
|
|
93
|
+
...(config.timeoutMs !== undefined ? { timeoutMs: config.timeoutMs } : {}),
|
|
94
|
+
...(config.heapMb !== undefined ? { heapMb: config.heapMb } : {}),
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Build a runtime-render check instance. One FIFO limiter per instance;
|
|
101
|
+
* the bounds reach `runRenderCheck` on every run. `createRuntimeRenderCheck()`
|
|
102
|
+
* with no config is {@link DEFAULT_RUNTIME_RENDER_CHECK}'s behaviour.
|
|
103
|
+
*/
|
|
104
|
+
export function createRuntimeRenderCheck(
|
|
105
|
+
config: RuntimeRenderProbeConfig = {}
|
|
106
|
+
): RuntimeRenderCheck {
|
|
107
|
+
const limit = createSlotLimiter(config.maxConcurrent);
|
|
108
|
+
const options = toRenderCheckOptions(config);
|
|
109
|
+
// A bad bound is refused here, at construction, never at the first probe.
|
|
110
|
+
if (options !== undefined) resolveRenderCheckHostBounds(options.bounds);
|
|
111
|
+
return {
|
|
112
|
+
id: "runtime-render",
|
|
113
|
+
run: async (input) => {
|
|
114
|
+
const { sourceCode, compiledCode, contract, fixtureProps } = input;
|
|
56
115
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
116
|
+
// Nothing to render / no contract surface to verify — the probe has
|
|
117
|
+
// no subject, which is different from the probe failing to run.
|
|
118
|
+
if (compiledCode === null) {
|
|
119
|
+
return { status: "not-applicable", issues: [], reason: "no compiled code" };
|
|
120
|
+
}
|
|
121
|
+
if (!contract) {
|
|
122
|
+
return { status: "not-applicable", issues: [], reason: "no contract surface" };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const mockup = prepareMockupProps({ contract, fixtureProps });
|
|
126
|
+
|
|
127
|
+
// ggui#1380 — the adapter's own clock around the check: every status
|
|
128
|
+
// that reached the check (`ran`, `timed-out`, `infra-skipped`) reports
|
|
129
|
+
// how long it took, so a reader of the probe meta can tell a 2 s probe
|
|
130
|
+
// from a 30 s one on the same status. The two not-applicable returns
|
|
131
|
+
// above carry nothing: nothing ran.
|
|
132
|
+
// The clock starts when the slot is acquired, so `elapsedMs` is the
|
|
133
|
+
// check's own wall-clock; the time the check actually WAITED for a slot
|
|
134
|
+
// is `queuedMs` (present only when it queued) — a latency reader needs
|
|
135
|
+
// the two apart exactly when a serving deployment queues more probes
|
|
136
|
+
// than it has slots. A check that found a slot free carries no key.
|
|
137
|
+
let queuedMs = 0;
|
|
138
|
+
let t0 = Date.now();
|
|
139
|
+
let result;
|
|
140
|
+
try {
|
|
141
|
+
result = await limit((waitedMs) => {
|
|
142
|
+
queuedMs = waitedMs;
|
|
143
|
+
t0 = Date.now();
|
|
144
|
+
return runRenderCheck(
|
|
145
|
+
{
|
|
146
|
+
sourceCode,
|
|
147
|
+
mockupProps: mockup.props,
|
|
148
|
+
contract,
|
|
149
|
+
},
|
|
150
|
+
options
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
} catch (e) {
|
|
154
|
+
// Triad audit (2026-04-27): every error that escapes `runRenderCheck`
|
|
155
|
+
// is an INFRA problem — happy-dom import failure, ESM/CJS interop
|
|
156
|
+
// (`Dynamic require of "events"`), bundler name collision (`Window2
|
|
157
|
+
// is not a constructor`), missing `@testing-library/react`, etc.
|
|
158
|
+
// Component-level failures are caught and emitted as `RenderCheckIssue`
|
|
159
|
+
// entries from inside `runRenderCheck`; they don't propagate out.
|
|
160
|
+
//
|
|
161
|
+
// An infra failure must never become an eval issue (the coding
|
|
162
|
+
// agent can't fix the environment — pre-2026-04-27 that phantom
|
|
163
|
+
// issue dragged every score-below-80 cell's eval-fix loop), but it
|
|
164
|
+
// must also never be silent to scoring: ggui#403 found the bench
|
|
165
|
+
// reporting `probe_pass 3/3` on cells where this branch fired on
|
|
166
|
+
// every invocation. The `infra-skipped` status is the No-Silent-
|
|
167
|
+
// Block channel — consumers surface it as did-not-run, never pass.
|
|
168
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
169
|
+
console.warn(`[runtime-render] probe skipped — infra failure: ${message}`);
|
|
170
|
+
return { status: "infra-skipped", issues: [], reason: message, elapsedMs: Date.now() - t0, ...queuedField(queuedMs) };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const hostLoad = result.stats.hostLoad;
|
|
174
|
+
const load = hostLoad !== undefined ? { hostLoad } : {};
|
|
175
|
+
|
|
176
|
+
// ggui#1299: a check that ran out of wall-clock time is not a verdict on
|
|
177
|
+
// the component. It becomes the `timed-out` status (did-not-run, never a
|
|
178
|
+
// pass, never a crash), never an eval issue — so the coding agent is never
|
|
179
|
+
// told to fix a crash that did not happen.
|
|
180
|
+
if (result.incomplete !== undefined) {
|
|
181
|
+
const { elapsedMs, boundMs } = result.incomplete;
|
|
182
|
+
const loadNote =
|
|
183
|
+
hostLoad !== undefined
|
|
184
|
+
? `; host load ${hostLoad.start.toFixed(1)} → ${hostLoad.end.toFixed(1)} on ${hostLoad.cores} CPUs`
|
|
185
|
+
: "";
|
|
186
|
+
const reason = `render check did not finish within ${boundMs} ms (stopped at ${elapsedMs} ms)${loadNote}`;
|
|
187
|
+
console.warn(`[runtime-render] probe timed out — ${reason}`);
|
|
188
|
+
// One clock for every status that reached the worker: the adapter's
|
|
189
|
+
// wall-clock around the check. The worker's own reading survives in
|
|
190
|
+
// `reason` ("stopped at N ms").
|
|
191
|
+
return { status: "timed-out", issues: [], reason, elapsedMs: Date.now() - t0, ...queuedField(queuedMs), ...load };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
status: "ran",
|
|
196
|
+
issues: result.issues.map(toEvalIssue).filter((x): x is EvalIssue => x !== null),
|
|
197
|
+
elapsedMs: Date.now() - t0,
|
|
198
|
+
...queuedField(queuedMs),
|
|
199
|
+
renderMs: result.stats.renderMs,
|
|
200
|
+
...load,
|
|
201
|
+
};
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** The evaluation lane's instance: the host's default bounds, no concurrency cap. */
|
|
207
|
+
export const DEFAULT_RUNTIME_RENDER_CHECK: RuntimeRenderCheck = createRuntimeRenderCheck();
|
|
65
208
|
|
|
66
209
|
/**
|
|
67
210
|
* Map a runtime-render crash reason to a class-specific fix string.
|
|
@@ -158,11 +301,7 @@ export function classifyRenderCrashFix(reason: string): string {
|
|
|
158
301
|
"(b) check it's an array before iterating (`Array.isArray(x) && ...`)."
|
|
159
302
|
);
|
|
160
303
|
}
|
|
161
|
-
if (
|
|
162
|
-
r.includes("cannot read") ||
|
|
163
|
-
r.includes("undefined is not") ||
|
|
164
|
-
r.includes("null is not")
|
|
165
|
-
) {
|
|
304
|
+
if (r.includes("cannot read") || r.includes("undefined is not") || r.includes("null is not")) {
|
|
166
305
|
return (
|
|
167
306
|
"Null/undefined access. Add optional chaining (`obj?.field`) and " +
|
|
168
307
|
"default values for optional props/state before reading nested fields. " +
|
|
@@ -176,7 +315,32 @@ export function classifyRenderCrashFix(reason: string): string {
|
|
|
176
315
|
);
|
|
177
316
|
}
|
|
178
317
|
|
|
179
|
-
|
|
318
|
+
/** The prefix every probe issue's subcategory carries: `runtime:<check>[:<subject>]`. */
|
|
319
|
+
const RUNTIME_SUBCATEGORY_PREFIX = "runtime:";
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* The check a probe issue's subcategory names (ggui#1380) — the inverse of
|
|
323
|
+
* the `runtime:<check>[:<subject>]` form {@link toEvalIssue} writes, so a
|
|
324
|
+
* round can list WHICH checks failed from the issues alone. `undefined` for
|
|
325
|
+
* anything that is not a probe subcategory naming a member of
|
|
326
|
+
* {@link RENDER_CHECK_KINDS} (an axis check's subcategory, a did-not-run
|
|
327
|
+
* code such as `runtime:probe-timeout`, a bare check name).
|
|
328
|
+
*/
|
|
329
|
+
export function parseRuntimeSubcategory(subcategory: string): RenderCheckKind | undefined {
|
|
330
|
+
if (!subcategory.startsWith(RUNTIME_SUBCATEGORY_PREFIX)) return undefined;
|
|
331
|
+
const rest = subcategory.slice(RUNTIME_SUBCATEGORY_PREFIX.length);
|
|
332
|
+
const separator = rest.indexOf(":");
|
|
333
|
+
const check = separator === -1 ? rest : rest.slice(0, separator);
|
|
334
|
+
return RENDER_CHECK_KINDS.find((kind) => kind === check);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* One probe finding → the eval issue the loop feeds back and the metadata
|
|
339
|
+
* counts. Exported for the pin that every switch case below is a member of
|
|
340
|
+
* {@link RENDER_CHECK_KINDS} and round-trips through
|
|
341
|
+
* {@link parseRuntimeSubcategory}.
|
|
342
|
+
*/
|
|
343
|
+
export function toEvalIssue(issue: RenderCheckIssue): EvalIssue | null {
|
|
180
344
|
// verified → no issue (silent pass)
|
|
181
345
|
if (issue.outcome === "verified" || issue.outcome === "skipped") return null;
|
|
182
346
|
|
|
@@ -209,7 +373,7 @@ function toEvalIssue(issue: RenderCheckIssue): EvalIssue | null {
|
|
|
209
373
|
diagParts.push(
|
|
210
374
|
"primed" in diag.inputPriming
|
|
211
375
|
? `input priming: ${diag.inputPriming.primed} control(s) filled before the click`
|
|
212
|
-
: `input priming failed: ${diag.inputPriming.error}
|
|
376
|
+
: `input priming failed: ${diag.inputPriming.error}`
|
|
213
377
|
);
|
|
214
378
|
}
|
|
215
379
|
const diagSuffix = diagParts.length ? ` [observed: ${diagParts.join("; ")}]` : "";
|
|
@@ -245,9 +409,10 @@ function toEvalIssue(issue: RenderCheckIssue): EvalIssue | null {
|
|
|
245
409
|
};
|
|
246
410
|
|
|
247
411
|
case "action-wiring": {
|
|
248
|
-
const fix =
|
|
249
|
-
|
|
250
|
-
|
|
412
|
+
const fix =
|
|
413
|
+
issue.outcome === "unverified"
|
|
414
|
+
? `Source shows the action callback flowing into a non-native or custom-component prop. If wiring is intentional (e.g., Dropdown.onChange, drag-drop), this warn is informational — manual/browser verification is required to confirm. Otherwise wire to a native onClick={() => ${subject}(payload)} on <button> or design-system <Button>.`
|
|
415
|
+
: `Wire ${subject}() to a native event prop. Common fix: <Button onClick={() => ${subject}(payload)}>Label</Button>. Source-AST analysis didn't find this wiring in your JSX.`;
|
|
251
416
|
return {
|
|
252
417
|
tier: 0,
|
|
253
418
|
result,
|
|
@@ -10,15 +10,26 @@
|
|
|
10
10
|
// - declared stream events update the DOM when emitted (warn)
|
|
11
11
|
|
|
12
12
|
import { installProductionActShim } from "./production-act-shim.js";
|
|
13
|
+
import type { RenderCheckHostBounds } from "./render-check-host.js";
|
|
13
14
|
|
|
14
|
-
export {
|
|
15
|
+
export {
|
|
16
|
+
RENDER_CHECK_KINDS,
|
|
17
|
+
runRenderCheck,
|
|
18
|
+
type RenderCheckKind,
|
|
19
|
+
type RenderCheckResult,
|
|
20
|
+
type RenderCheckIssue,
|
|
21
|
+
} from "./render-check.js";
|
|
15
22
|
export { createProbe, createProbeWireConfig, type Probe } from "./probe.js";
|
|
16
23
|
export { prepareMockupProps, type MockupPropsResult } from "./prepare-mockup.js";
|
|
17
24
|
export {
|
|
18
25
|
DEFAULT_RUNTIME_RENDER_CHECK,
|
|
19
26
|
classifyRenderCrashFix,
|
|
27
|
+
createRuntimeRenderCheck,
|
|
20
28
|
isRecoverableRenderCrash,
|
|
29
|
+
parseRuntimeSubcategory,
|
|
30
|
+
type RuntimeRenderProbeConfig,
|
|
21
31
|
} from "./adapter.js";
|
|
32
|
+
export type { RenderCheckHostBounds, RenderCheckHostOptions } from "./render-check-host.js";
|
|
22
33
|
|
|
23
34
|
/**
|
|
24
35
|
* Pre-warm the runtime-render probe's runtime dependencies so the first
|
|
@@ -41,7 +52,11 @@ export {
|
|
|
41
52
|
*
|
|
42
53
|
* Returns the wall-clock spent loading.
|
|
43
54
|
*/
|
|
44
|
-
export async function warmupRuntimeRenderProbe(): Promise<{
|
|
55
|
+
export async function warmupRuntimeRenderProbe(): Promise<{
|
|
56
|
+
ms: number;
|
|
57
|
+
loaded: number;
|
|
58
|
+
missing: number;
|
|
59
|
+
}> {
|
|
45
60
|
const start = Date.now();
|
|
46
61
|
let loaded = 0;
|
|
47
62
|
let missing = 0;
|
|
@@ -67,3 +82,61 @@ export async function warmupRuntimeRenderProbe(): Promise<{ ms: number; loaded:
|
|
|
67
82
|
]);
|
|
68
83
|
return { ms: Date.now() - start, loaded, missing };
|
|
69
84
|
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The component the boot warm renders: a constant element, an empty
|
|
88
|
+
* contract — nothing to click, nothing to cover, one compile and one
|
|
89
|
+
* render in the worker.
|
|
90
|
+
*/
|
|
91
|
+
const WARMUP_COMPONENT = "export default function Warmup() { return <div>warm</div>; }";
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Warm the PROCESS that runs every probe (ggui#1380): spawn one real check
|
|
95
|
+
* worker on a trivial component through the same host every probe uses.
|
|
96
|
+
* Afterwards the node binary, the worker's module tree and esbuild's
|
|
97
|
+
* service have all run once on this machine (OS page cache warm), and the
|
|
98
|
+
* result says whether that process can run a check at all — which
|
|
99
|
+
* `warmupRuntimeRenderProbe` (an import in THIS process) cannot tell.
|
|
100
|
+
*
|
|
101
|
+
* `bounds` are the subprocess bounds for the one run; a serving deployment
|
|
102
|
+
* passes the same bounds it will probe with. Never throws: a worker that
|
|
103
|
+
* cannot run is `{ ok: false, reason }`, so a boot sequence can log it and
|
|
104
|
+
* decide, instead of dying in a fire-and-forget.
|
|
105
|
+
*/
|
|
106
|
+
export async function warmupRuntimeRenderWorker(
|
|
107
|
+
bounds?: Partial<RenderCheckHostBounds>
|
|
108
|
+
): Promise<{ ok: true; ms: number } | { ok: false; ms: number; reason: string }> {
|
|
109
|
+
const start = Date.now();
|
|
110
|
+
try {
|
|
111
|
+
// Lazy: the host pulls `@ggui-ai/sandbox` (spawn machinery); keep it off
|
|
112
|
+
// the import graph of everything that only needs the in-process check.
|
|
113
|
+
const { runRenderCheckViaWorker } = await import("./render-check-host.js");
|
|
114
|
+
const result = await runRenderCheckViaWorker(
|
|
115
|
+
{ sourceCode: WARMUP_COMPONENT, mockupProps: {}, contract: {} },
|
|
116
|
+
bounds !== undefined ? { bounds } : {}
|
|
117
|
+
);
|
|
118
|
+
const ms = Date.now() - start;
|
|
119
|
+
if (result.incomplete !== undefined) {
|
|
120
|
+
return {
|
|
121
|
+
ok: false,
|
|
122
|
+
ms,
|
|
123
|
+
reason: `the warm-up check did not finish within ${result.incomplete.boundMs} ms (stopped at ${result.incomplete.elapsedMs} ms)`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const blocking = result.issues.find(
|
|
127
|
+
(i) => i.outcome === "failed" || i.outcome === "unverified"
|
|
128
|
+
);
|
|
129
|
+
if (blocking !== undefined) {
|
|
130
|
+
return { ok: false, ms, reason: `${blocking.check} ${blocking.outcome}: ${blocking.reason}` };
|
|
131
|
+
}
|
|
132
|
+
return { ok: true, ms };
|
|
133
|
+
} catch (err) {
|
|
134
|
+
// The host itself never throws for a worker fault; what reaches here is
|
|
135
|
+
// the spawn layer refusing to run at all (an invalid bound, no binary).
|
|
136
|
+
return {
|
|
137
|
+
ok: false,
|
|
138
|
+
ms: Date.now() - start,
|
|
139
|
+
reason: err instanceof Error ? err.message : String(err),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|