@ggui-ai/ui-gen 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
- package/dist/adapters/base.js +2 -27
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +14 -6
- package/dist/adapters/generation-dispatch.js +40 -19260
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -3561
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/types.js +2 -9
- package/dist/adapters/types.js.map +1 -1
- package/dist/advanced/index.d.ts +13 -12
- package/dist/advanced/index.js +63 -21954
- package/dist/advanced/index.js.map +1 -1
- package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
- package/dist/blueprint-validator.js +6 -2301
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +3 -2
- package/dist/boilerplate.js +6 -1867
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +6 -1923
- package/dist/check/index.js.map +1 -1
- package/dist/chunk-33B3BAWS.js +24 -0
- package/dist/chunk-33B3BAWS.js.map +1 -0
- package/dist/chunk-3HSGA6NI.js +591 -0
- package/dist/chunk-3HSGA6NI.js.map +1 -0
- package/dist/chunk-3NNNGNTO.js +1707 -0
- package/dist/chunk-3NNNGNTO.js.map +1 -0
- package/dist/chunk-3T2HMTON.js +79 -0
- package/dist/chunk-3T2HMTON.js.map +1 -0
- package/dist/chunk-3ZSSDV3L.js +123 -0
- package/dist/chunk-3ZSSDV3L.js.map +1 -0
- package/dist/chunk-4MBR5GA3.js +99 -0
- package/dist/chunk-4MBR5GA3.js.map +1 -0
- package/dist/chunk-56NJFD55.js +337 -0
- package/dist/chunk-56NJFD55.js.map +1 -0
- package/dist/chunk-5AGTY555.js +26 -0
- package/dist/chunk-5AGTY555.js.map +1 -0
- package/dist/chunk-5EJM7EGY.js +115 -0
- package/dist/chunk-5EJM7EGY.js.map +1 -0
- package/dist/chunk-5KXCQLLR.js +529 -0
- package/dist/chunk-5KXCQLLR.js.map +1 -0
- package/dist/chunk-5VBXHVEH.js +171 -0
- package/dist/chunk-5VBXHVEH.js.map +1 -0
- package/dist/chunk-6KDDEING.js +515 -0
- package/dist/chunk-6KDDEING.js.map +1 -0
- package/dist/chunk-6VDRWAP6.js +123 -0
- package/dist/chunk-6VDRWAP6.js.map +1 -0
- package/dist/chunk-732IBFEJ.js +2400 -0
- package/dist/chunk-732IBFEJ.js.map +1 -0
- package/dist/chunk-73AURS3I.js +46 -0
- package/dist/chunk-73AURS3I.js.map +1 -0
- package/dist/chunk-77H5SKLX.js +8 -0
- package/dist/chunk-77H5SKLX.js.map +1 -0
- package/dist/chunk-7QE3AHLE.js +35 -0
- package/dist/chunk-7QE3AHLE.js.map +1 -0
- package/dist/chunk-A375NNWN.js +11 -0
- package/dist/chunk-A375NNWN.js.map +1 -0
- package/dist/chunk-AOU33EFS.js +140 -0
- package/dist/chunk-AOU33EFS.js.map +1 -0
- package/dist/chunk-BA4QYA6E.js +461 -0
- package/dist/chunk-BA4QYA6E.js.map +1 -0
- package/dist/chunk-DBP764GL.js +3 -0
- package/dist/chunk-DBP764GL.js.map +1 -0
- package/dist/chunk-DUU3ZJDQ.js +41 -0
- package/dist/chunk-DUU3ZJDQ.js.map +1 -0
- package/dist/chunk-DXKRBXNU.js +30 -0
- package/dist/chunk-DXKRBXNU.js.map +1 -0
- package/dist/chunk-FB7OEEAH.js +1467 -0
- package/dist/chunk-FB7OEEAH.js.map +1 -0
- package/dist/chunk-FLRNMZV2.js +18 -0
- package/dist/chunk-FLRNMZV2.js.map +1 -0
- package/dist/chunk-FWJIMKFZ.js +2032 -0
- package/dist/chunk-FWJIMKFZ.js.map +1 -0
- package/dist/chunk-FXRHWYIQ.js +163 -0
- package/dist/chunk-FXRHWYIQ.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GAJI7Y3P.js +13 -0
- package/dist/chunk-GAJI7Y3P.js.map +1 -0
- package/dist/chunk-GYINWMPR.js +2052 -0
- package/dist/chunk-GYINWMPR.js.map +1 -0
- package/dist/chunk-HD6ABIK7.js +484 -0
- package/dist/chunk-HD6ABIK7.js.map +1 -0
- package/dist/chunk-HDQVHYO6.js +100 -0
- package/dist/chunk-HDQVHYO6.js.map +1 -0
- package/dist/chunk-IHUT3SIW.js +7 -0
- package/dist/chunk-IHUT3SIW.js.map +1 -0
- package/dist/chunk-JDH5EGNZ.js +126 -0
- package/dist/chunk-JDH5EGNZ.js.map +1 -0
- package/dist/chunk-JMD4RGSV.js +37 -0
- package/dist/chunk-JMD4RGSV.js.map +1 -0
- package/dist/chunk-KJWGZL5B.js +33 -0
- package/dist/chunk-KJWGZL5B.js.map +1 -0
- package/dist/chunk-MKMDQFZ7.js +86 -0
- package/dist/chunk-MKMDQFZ7.js.map +1 -0
- package/dist/chunk-NF6JUZWF.js +85 -0
- package/dist/chunk-NF6JUZWF.js.map +1 -0
- package/dist/chunk-O2YC6XDC.js +1933 -0
- package/dist/chunk-O2YC6XDC.js.map +1 -0
- package/dist/chunk-OVLH5ICV.js +29 -0
- package/dist/chunk-OVLH5ICV.js.map +1 -0
- package/dist/chunk-PBOYHGSD.js +106 -0
- package/dist/chunk-PBOYHGSD.js.map +1 -0
- package/dist/chunk-PJFH7QWA.js +81 -0
- package/dist/chunk-PJFH7QWA.js.map +1 -0
- package/dist/chunk-PUH4CXGZ.js +284 -0
- package/dist/chunk-PUH4CXGZ.js.map +1 -0
- package/dist/chunk-QLXXPGTV.js +18 -0
- package/dist/chunk-QLXXPGTV.js.map +1 -0
- package/dist/chunk-R5GIGPW2.js +2845 -0
- package/dist/chunk-R5GIGPW2.js.map +1 -0
- package/dist/chunk-RH4IUSTO.js +83 -0
- package/dist/chunk-RH4IUSTO.js.map +1 -0
- package/dist/chunk-S2GVXKKY.js +153 -0
- package/dist/chunk-S2GVXKKY.js.map +1 -0
- package/dist/chunk-TVHGKFJS.js +58 -0
- package/dist/chunk-TVHGKFJS.js.map +1 -0
- package/dist/chunk-U7GALWLG.js +66 -0
- package/dist/chunk-U7GALWLG.js.map +1 -0
- package/dist/chunk-VTC263LT.js +256 -0
- package/dist/chunk-VTC263LT.js.map +1 -0
- package/dist/chunk-WEOXFEVE.js +739 -0
- package/dist/chunk-WEOXFEVE.js.map +1 -0
- package/dist/chunk-WYHP46LT.js +28 -0
- package/dist/chunk-WYHP46LT.js.map +1 -0
- package/dist/chunk-XGLO5LGG.js +274 -0
- package/dist/chunk-XGLO5LGG.js.map +1 -0
- package/dist/chunk-XRBCRKA2.js +29 -0
- package/dist/chunk-XRBCRKA2.js.map +1 -0
- package/dist/chunk-XW7ADFFV.js +340 -0
- package/dist/chunk-XW7ADFFV.js.map +1 -0
- package/dist/chunk-YIQ42IUX.js +34 -0
- package/dist/chunk-YIQ42IUX.js.map +1 -0
- package/dist/chunk-YJZTUY7B.js +101 -0
- package/dist/chunk-YJZTUY7B.js.map +1 -0
- package/dist/chunk-Z3CXME3K.js +107 -0
- package/dist/chunk-Z3CXME3K.js.map +1 -0
- package/dist/classifier/index.js +2 -527
- package/dist/classifier/index.js.map +1 -1
- package/dist/client-JBPCXU7W.js +4 -0
- package/dist/client-JBPCXU7W.js.map +1 -0
- package/dist/compose.js +3 -367
- package/dist/compose.js.map +1 -1
- package/dist/cost-tracker-HHGQ4FTW.js +67 -0
- package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
- package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
- package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
- package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
- package/dist/dist-UALN6ZVB.js +543 -0
- package/dist/dist-UALN6ZVB.js.map +1 -0
- package/dist/dupe-break-X6K7QFIS.js +4 -0
- package/dist/dupe-break-X6K7QFIS.js.map +1 -0
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js +6 -1914
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -2
- package/dist/evaluation/axis-checks/registry.js +3 -1844
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +49 -132
- package/dist/evaluation/index.js +302 -6466
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +3 -2
- package/dist/evaluation/types.js +2 -5
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.js +2 -338
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +35 -15
- package/dist/harness/check/runtime-render/index.js +6 -2390
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +23 -5126
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.js +2 -24
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +8 -324
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +6 -5
- package/dist/harness/result-types.js +2 -0
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +22 -5
- package/dist/harness/runtime.js +10 -2313
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -2
- package/dist/harness/types-public.js +16 -2954
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -2
- package/dist/harness/validator-trace-sink.js +2 -26
- package/dist/harness/validator-trace-sink.js.map +1 -1
- package/dist/hash.js +2 -39
- package/dist/hash.js.map +1 -1
- package/dist/hashline-5OKREIRM.js +4 -0
- package/dist/hashline-5OKREIRM.js.map +1 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +57 -22015
- package/dist/index.js.map +1 -1
- package/dist/llm-evaluator-PN3WFCS4.js +653 -0
- package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
- package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
- package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
- package/dist/patch.d.ts +3 -2
- package/dist/patch.js +2 -56
- package/dist/patch.js.map +1 -1
- package/dist/policy-VCYKGSAC.js +5 -0
- package/dist/policy-VCYKGSAC.js.map +1 -0
- package/dist/policy.js +2 -35
- package/dist/policy.js.map +1 -1
- package/dist/primitive-index-2OF6F4TZ.js +131 -0
- package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
- package/dist/primitive-slice-WTMBLYFJ.js +235 -0
- package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
- package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
- package/dist/provider-adapter-contract.js +10407 -14204
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/provider-adapter.js +2 -44
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +89 -1
- package/dist/providers/index.js +115 -44
- package/dist/providers/index.js.map +1 -1
- package/dist/raw-C6KBH5I3.js +141 -0
- package/dist/raw-C6KBH5I3.js.map +1 -0
- package/dist/raw-FXSBXQTZ.js +7 -0
- package/dist/raw-FXSBXQTZ.js.map +1 -0
- package/dist/raw-MURDUPGK.js +8 -0
- package/dist/raw-MURDUPGK.js.map +1 -0
- package/dist/raw-T3GW3UVX.js +8 -0
- package/dist/raw-T3GW3UVX.js.map +1 -0
- package/dist/render-check-host-ZNIS6BJ5.js +5 -0
- package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
- package/dist/sdk-4JNSDNSR.js +6 -0
- package/dist/sdk-4JNSDNSR.js.map +1 -0
- package/dist/sdk-DIVJMHCX.js +7 -0
- package/dist/sdk-DIVJMHCX.js.map +1 -0
- package/dist/sdk-QRJOCRTS.js +7 -0
- package/dist/sdk-QRJOCRTS.js.map +1 -0
- package/dist/tag-balance-CTHCTRJ7.js +123 -0
- package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +3 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +3 -40
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +3 -498
- package/dist/validation/index.js.map +1 -1
- package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
- package/dist/visual-evaluator-TGEXYQTP.js +12 -0
- package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
- package/dist/workflows.d.ts +3 -2
- package/dist/workflows.js +2 -151
- package/dist/workflows.js.map +1 -1
- package/package.json +20 -16
- package/src/adapters/claude/sdk.ts +4 -2
- package/src/adapters/claude/tool-bridge.ts +2 -1
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openai/raw.ts +4 -0
- package/src/adapters/openai/sdk.ts +6 -1
- package/src/adapters/openai/tokens.ts +21 -7
- package/src/adapters/tool-bridge.ts +1 -1
- package/src/adapters/tools.ts +1 -1
- package/src/advanced/feedback.ts +1 -2
- package/src/advanced/generator.ts +4 -3
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/generate.ts +18 -5
- package/src/boilerplate/hard-sections.ts +8 -3
- package/src/boilerplate/react-hooks.ts +56 -0
- package/src/boilerplate/system-prompt.ts +12 -3
- package/src/check/run-tier0.ts +233 -8
- package/src/check/type-checker.ts +128 -16
- package/src/coding-agent/tools.ts +26 -3
- package/src/design-mode.ts +4 -9
- package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
- package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
- package/src/evaluation/axis-checks/checks/tint.ts +197 -0
- package/src/evaluation/axis-checks/helpers.ts +121 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
- package/src/evaluation/criteria/bank.ts +1 -1
- package/src/evaluation/criteria/edge-instruments.ts +14 -0
- package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
- package/src/evaluation/criteria/edge-v2.ts +242 -0
- package/src/evaluation/criteria/resolve.ts +24 -5
- package/src/evaluation/index.ts +3 -5
- package/src/evaluation/mcp-server.ts +0 -50
- package/src/evaluation/types-public.ts +6 -1
- package/src/evaluation/visual-evaluator.ts +161 -30
- package/src/harness/check/runtime-render/index.ts +28 -4
- package/src/harness/check/runtime-render/render-check.ts +20 -21
- package/src/harness/claude-agent-sdk-loader.ts +93 -0
- package/src/harness/claude-code-login-query.ts +1 -1
- package/src/harness/coding/generate-task-runner.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +3 -0
- package/src/harness/coding/run-eval-round.ts +9 -0
- package/src/harness/llm-router.ts +50 -2
- package/src/harness/runtime.ts +17 -0
- package/src/index.ts +9 -0
- package/src/providers/anthropic-tool-call.ts +263 -0
- package/src/providers/index.ts +13 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check.ts +4 -4
- package/src/validation/primitives.ts +2 -1
- package/src/evaluation/loop.ts +0 -218
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* id; a tie or an unanswered id reads `n/a` (the CTO's A2) — a `must` at `n/a` is never a pass when
|
|
9
9
|
* a gate later reads the set.
|
|
10
10
|
*/
|
|
11
|
+
import { EDGE_INSTRUMENT_IDS } from './edge-instruments.js';
|
|
12
|
+
import type { MountFit } from '@ggui-ai/design/rendering';
|
|
11
13
|
import type { CriteriaAnswer } from '../types.js';
|
|
12
14
|
import type { CriteriaAnswerCounts, CriteriaBlock, CriteriaContext, CriteriaVerdict, CriterionVerdict } from '../types-public.js';
|
|
13
15
|
import type { VisionFinishReason } from '../../harness/llm-router.js';
|
|
@@ -50,14 +52,20 @@ export interface CriteriaMeasurements {
|
|
|
50
52
|
* `null` when the probe ran and failed.
|
|
51
53
|
*/
|
|
52
54
|
readonly edge?: EdgeVerdict | null;
|
|
55
|
+
/** #1765 — the v2 edge rule on the same capture, read beside v1: absent / `null` as for `edge`. */
|
|
56
|
+
readonly edgeV2?: EdgeVerdict | null;
|
|
53
57
|
/** ggui#1711 — the capture includes a state after an action ran. Absent = no (every capture today). */
|
|
54
58
|
readonly postActionCapture?: boolean;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
function besideRead(kind: 'edge', m: CriteriaMeasurements): Read {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
return edgeRead(m.edge);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function edgeRead(e: EdgeVerdict | null | undefined): Read {
|
|
66
|
+
if (e === undefined) return { verdict: 'n/a', evidence: 'not measured: this capture had no page to read' };
|
|
67
|
+
if (e === null) return { verdict: 'n/a', evidence: 'edge probe failed on this capture' };
|
|
68
|
+
return { verdict: e.verdict, evidence: e.evidence };
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
type Read = { verdict: CriteriaVerdict; evidence: string };
|
|
@@ -122,7 +130,13 @@ export function resolveCriteriaBlock(args: {
|
|
|
122
130
|
source: s.source,
|
|
123
131
|
verdict: read.verdict,
|
|
124
132
|
evidence: read.evidence,
|
|
125
|
-
...(beside !== undefined && row.evaluation === 'judge'
|
|
133
|
+
...(beside !== undefined && row.evaluation === 'judge'
|
|
134
|
+
? {
|
|
135
|
+
instrument: { ...besideRead(beside, args.measurements), id: EDGE_INSTRUMENT_IDS.v1 },
|
|
136
|
+
// #1765 — v2 beside v1 on the same capture, report-only, until its own fresh sealed draw.
|
|
137
|
+
instrumentV2: { ...edgeRead(args.measurements.edgeV2), id: EDGE_INSTRUMENT_IDS.v2 },
|
|
138
|
+
}
|
|
139
|
+
: {}),
|
|
126
140
|
});
|
|
127
141
|
}
|
|
128
142
|
return {
|
|
@@ -153,6 +167,8 @@ export interface CriteriaJudgeFrame {
|
|
|
153
167
|
readonly height: number;
|
|
154
168
|
/** ggui#1492 — the host whose presentation framed this canvas (e.g. "a chat widget, inline, reference theme"); absent ⇒ the judge's own stand-in. */
|
|
155
169
|
readonly hostLabel?: string;
|
|
170
|
+
/** How the card was composed on this frame (`resolveMountFit`, the one rule the runtime serves by); the sentence follows it. */
|
|
171
|
+
readonly fit: MountFit;
|
|
156
172
|
}
|
|
157
173
|
|
|
158
174
|
/** What the block carries besides the selected rows: the frame's box, and the props the card was rendered with. */
|
|
@@ -178,7 +194,10 @@ export const CRITERIA_PROPS_MAX_CHARS = 2000;
|
|
|
178
194
|
*/
|
|
179
195
|
export function criteriaFrameLine(frame: CriteriaJudgeFrame): string {
|
|
180
196
|
const box = `${frame.width}×${frame.height}`;
|
|
181
|
-
if (frame.
|
|
197
|
+
if (frame.fit === 'framed-card') {
|
|
198
|
+
return `This frame is ${box}${frame.hostLabel !== undefined ? ` (${frame.hostLabel})` : ''} with the card at its natural height at the top, inside the card's own frame — its surface, a thin ring of its ink and rounded corners — on the host's ground: the card is sized to its content, so judge composition and space within the card's own extent, and read the ground round it as the host's, not the card's.`;
|
|
199
|
+
}
|
|
200
|
+
if (frame.fit === 'card') {
|
|
182
201
|
const framedBy =
|
|
183
202
|
frame.hostLabel !== undefined
|
|
184
203
|
? `inside the host's own frame (${frame.hostLabel}), on the host's ground`
|
package/src/evaluation/index.ts
CHANGED
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// - `./types.js` — the post-eval result shape (`DimensionScores`,
|
|
12
12
|
// `EvaluationResult`, `EvaluationIssue`, `QualityMetadata`,
|
|
13
13
|
// `EvaluationConfig`, `EvaluationContext`).
|
|
14
|
-
// Plus the runtime cluster (`
|
|
15
|
-
//
|
|
14
|
+
// Plus the runtime cluster (`runEvaluation`, `runAxisChecks`, score
|
|
15
|
+
// arithmetic, prompts, message parsing).
|
|
16
16
|
|
|
17
17
|
// Vocabulary the harness legs depend on (was: flat `packages/ui-gen/src/evaluation.ts`).
|
|
18
18
|
export type {
|
|
@@ -55,10 +55,8 @@ export {
|
|
|
55
55
|
} from './types-public.js';
|
|
56
56
|
|
|
57
57
|
// Cloud-ported runtime cluster.
|
|
58
|
-
export { runEvaluationLoop } from './loop.js';
|
|
59
|
-
export type { EvaluationLoopOptions, EvaluationLoopResult } from './loop.js';
|
|
60
58
|
export { runEvaluation } from './evaluator.js';
|
|
61
|
-
export {
|
|
59
|
+
export { computeEvaluationScore } from './mcp-server.js';
|
|
62
60
|
export type { EvaluateScoreInput } from './mcp-server.js';
|
|
63
61
|
export { getEvaluatorSystemPrompt, buildFixPrompt } from './prompts.js';
|
|
64
62
|
export {
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
// packages/ui-gen/src/evaluation/mcp-server.ts
|
|
2
2
|
|
|
3
|
-
import { createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk';
|
|
4
|
-
import { z } from 'zod';
|
|
5
3
|
import type { EvaluationIssue, EvaluationResult } from './types';
|
|
6
4
|
|
|
7
5
|
/**
|
|
@@ -51,51 +49,3 @@ export function computeEvaluationScore(
|
|
|
51
49
|
...(args.critique && { critique: args.critique }),
|
|
52
50
|
};
|
|
53
51
|
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Create the evaluation MCP server with the evaluate_score tool.
|
|
57
|
-
*
|
|
58
|
-
* The evaluator LLM provides qualitative scores per dimension.
|
|
59
|
-
* This tool handles the arithmetic (average, pass/fail) so the LLM
|
|
60
|
-
* doesn't need to do math.
|
|
61
|
-
*/
|
|
62
|
-
export function createEvaluationToolsServer(passThreshold = 70) {
|
|
63
|
-
return createSdkMcpServer({
|
|
64
|
-
name: 'eval-tools',
|
|
65
|
-
version: '1.0.0',
|
|
66
|
-
tools: [
|
|
67
|
-
tool(
|
|
68
|
-
'evaluate_score',
|
|
69
|
-
'Compute evaluation score from dimension ratings. Call this after analyzing the component code against all 5 dimensions.',
|
|
70
|
-
{
|
|
71
|
-
completeness: z.number().min(0).max(100).describe('Score for feature completeness (0-100)'),
|
|
72
|
-
visualPolish: z.number().min(0).max(100).describe('Score for visual polish and design (0-100)'),
|
|
73
|
-
interactivity: z.number().min(0).max(100).describe('Score for interactivity and state management (0-100)'),
|
|
74
|
-
accessibility: z.number().min(0).max(100).describe('Score for accessibility (ARIA, keyboard nav, contrast) (0-100)'),
|
|
75
|
-
codeQuality: z.number().min(0).max(100).describe('Score for code quality and structure (0-100)'),
|
|
76
|
-
issues: z.array(
|
|
77
|
-
z.object({
|
|
78
|
-
dimension: z.string().describe('Which dimension this issue affects'),
|
|
79
|
-
description: z.string().describe('What the issue is'),
|
|
80
|
-
severity: z.enum(['critical', 'major', 'minor']).describe('Issue severity'),
|
|
81
|
-
fix: z.string().describe('How to fix this issue'),
|
|
82
|
-
})
|
|
83
|
-
).describe('List of specific issues found'),
|
|
84
|
-
critique: z.string().optional().describe('Optional overall critique summary'),
|
|
85
|
-
},
|
|
86
|
-
async (args) => {
|
|
87
|
-
const result = computeEvaluationScore(args, passThreshold);
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
content: [
|
|
91
|
-
{
|
|
92
|
-
type: 'text',
|
|
93
|
-
text: JSON.stringify(result, null, 2),
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
),
|
|
99
|
-
],
|
|
100
|
-
});
|
|
101
|
-
}
|
|
@@ -477,7 +477,12 @@ export interface CriterionVerdict {
|
|
|
477
477
|
* ggui#1663 — a measured reading beside a judge row, REPORT-ONLY: the row's `verdict` stays the judge's, so this
|
|
478
478
|
* changes no verdict, score or bar. Present only on rows an instrument answers beside the judge (`space.edge`).
|
|
479
479
|
*/
|
|
480
|
-
instrument?: { verdict: CriteriaVerdict; evidence: string };
|
|
480
|
+
instrument?: { verdict: CriteriaVerdict; evidence: string; id?: string };
|
|
481
|
+
/**
|
|
482
|
+
* #1765 — the rewritten edge rule (`space.edge.v2`), a SEPARATE instrument read beside v1 on the same capture,
|
|
483
|
+
* REPORT-ONLY like `instrument`; `id` names the probe that produced it. Present only beside `space.edge`.
|
|
484
|
+
*/
|
|
485
|
+
instrumentV2?: { verdict: CriteriaVerdict; evidence: string; id: string };
|
|
481
486
|
}
|
|
482
487
|
/** ggui#1687 — the criteria call as it came back: its answer's counts, ids it answered outside the bank, its stop. */
|
|
483
488
|
export interface CriteriaCallRecord {
|
|
@@ -21,13 +21,13 @@ import type { CriteriaContextInput } from './criteria/context.js';
|
|
|
21
21
|
import { selectCriteria } from './criteria/select.js';
|
|
22
22
|
import { buildCriteriaJudgeBlock, resolveCriteriaBlock } from './criteria/resolve.js';
|
|
23
23
|
import { build } from 'esbuild';
|
|
24
|
-
import { EXPANDED_FRAME, expandedFramePanelRule, expandedFrameScrimDecls,
|
|
24
|
+
import { EXPANDED_FRAME, FRAMED_CARD_GROUND, GGUI_MOUNT_CANVAS_EXPORT, mountFitCss, expandedFramePanelDecls, expandedFramePanelRule, expandedFrameScrimDecls, getCssTokens, inlineCardRadius, resolveMountFit, type MountFit } from '@ggui-ai/design/rendering';
|
|
25
25
|
import { readInkExtent, type InkExtent } from './ink-extent.js';
|
|
26
26
|
import { judgeDesignIdentity, type JudgeDesignIdentity } from './design-identity.js';
|
|
27
27
|
import { resolve, dirname } from 'path';
|
|
28
28
|
import { fileURLToPath } from 'url';
|
|
29
29
|
import { createRequire } from 'node:module';
|
|
30
|
-
import { existsSync, writeFileSync, mkdtempSync, rmSync } from 'fs';
|
|
30
|
+
import { existsSync, readFileSync, writeFileSync, mkdtempSync, rmSync } from 'fs';
|
|
31
31
|
import { createHash } from 'node:crypto';
|
|
32
32
|
import { tmpdir } from 'os';
|
|
33
33
|
import { createVisionAgent, type AgentConfig, type VisionFinishReason } from '../harness/llm-router';
|
|
@@ -45,6 +45,8 @@ import type { LaunchOptions } from 'puppeteer-core';
|
|
|
45
45
|
import { CANVAS_VIEWPORTS, displayModeForCanvas, type CanvasClass, type CanvasViewport } from '../design-mode.js';
|
|
46
46
|
import { judgeEdges, type EdgeVerdict } from './criteria/edge.js';
|
|
47
47
|
import { EDGE_BLOCKS_EXPRESSION, parseEdgeProbe } from './criteria/edge-probe.js';
|
|
48
|
+
import { EDGE_BLOCKS_V2_EXPRESSION, parseEdgeProbeV2 } from './criteria/edge-probe-v2.js';
|
|
49
|
+
import { judgeEdgesV2 } from './criteria/edge-v2.js';
|
|
48
50
|
|
|
49
51
|
// ---------------------------------------------------------------------------
|
|
50
52
|
// Types
|
|
@@ -176,7 +178,12 @@ export interface CanvasVisualResult {
|
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
/** How a canvas is captured for the judge and what an overflow means there (ggui#1027). */
|
|
179
|
-
|
|
181
|
+
/**
|
|
182
|
+
* How a canvas is captured. `card-box` (ggui#1953): a framed card (a chat-width card on a fullscreen surface,
|
|
183
|
+
* ggui#1924) is captured as the inline card is — its OWN box at its natural extent, plus the frame ground's margin on
|
|
184
|
+
* every side, ring and corners visible — never the pane round it, whose empty ground is the host's, not the card's.
|
|
185
|
+
*/
|
|
186
|
+
export type CaptureMode = 'viewport' | 'full-page' | 'natural' | 'card-box';
|
|
180
187
|
export interface CanvasFitPolicy {
|
|
181
188
|
/**
|
|
182
189
|
* `viewport` = the whole box; `full-page` = the whole scrolled document; `natural` (ggui#1475) = the
|
|
@@ -501,8 +508,24 @@ export interface VisualEvalContext {
|
|
|
501
508
|
* same place the harness's own imports come from.
|
|
502
509
|
*/
|
|
503
510
|
export function resolveUiGenPackageDir(): string {
|
|
504
|
-
|
|
505
|
-
|
|
511
|
+
// ggui#1769: found by walking up to this package's own manifest, never by counting directories. The code moves
|
|
512
|
+
// with the bundle's layout: under `src/evaluation/` and the old per-entry `dist/evaluation/` the package root
|
|
513
|
+
// was two levels up, but a shared chunk sits at `dist/` itself, one level up.
|
|
514
|
+
return findPackageRootAbove(dirname(fileURLToPath(import.meta.url)), '@ggui-ai/ui-gen');
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** The nearest directory at or above `start` whose `package.json` is named `name`; throws when there is none. */
|
|
518
|
+
export function findPackageRootAbove(start: string, name: string): string {
|
|
519
|
+
for (let dir = start; ; dir = dirname(dir)) {
|
|
520
|
+
const manifest = resolve(dir, 'package.json');
|
|
521
|
+
if (existsSync(manifest) && readPackageName(manifest) === name) return dir;
|
|
522
|
+
if (dirname(dir) === dir) throw new Error(`${name}: no package.json named ${name} at or above ${start}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function readPackageName(manifest: string): string | undefined {
|
|
527
|
+
const parsed: unknown = JSON.parse(readFileSync(manifest, 'utf8'));
|
|
528
|
+
return typeof parsed === 'object' && parsed !== null && 'name' in parsed && typeof parsed.name === 'string' ? parsed.name : undefined;
|
|
506
529
|
}
|
|
507
530
|
|
|
508
531
|
/**
|
|
@@ -686,8 +709,11 @@ function buildRenderHTML(
|
|
|
686
709
|
fit?: 'fill',
|
|
687
710
|
chrome?: CanvasChrome,
|
|
688
711
|
presentation?: CanvasHostPresentation,
|
|
712
|
+
/** Set for a `framed-card` mount: the card frame's radius ({@link inlineCardRadius}); `null` keeps the theme's `lg` token. */
|
|
713
|
+
framedCardRadiusPx?: number | null,
|
|
689
714
|
): string {
|
|
690
|
-
|
|
715
|
+
const framedCard = framedCardRadiusPx !== undefined;
|
|
716
|
+
if (chrome === 'panel' && fit !== 'fill' && !framedCard) throw new Error("the judge's panel frames a fill canvas only (ggui#1083 cut 3)");
|
|
691
717
|
if (chrome === 'ground' && fit === 'fill') throw new Error("the host ground frames an inline card only — a fill canvas has no natural height (ggui#1475)");
|
|
692
718
|
// Default to the design tokens production's no-theme branch injects
|
|
693
719
|
// (getCssTokens → default theme, light) — ggui#613: under the s4
|
|
@@ -714,12 +740,15 @@ function buildRenderHTML(
|
|
|
714
740
|
color: var(--ggui-color-neutral-900, #111827);
|
|
715
741
|
}
|
|
716
742
|
.error { color: #dc2626; padding: 16px; font-family: monospace; white-space: pre-wrap; }
|
|
717
|
-
${fit
|
|
743
|
+
${/* ggui#1765 — the fit's rules come from the ONE selection the runtime's composeThemeCss uses. */ mountFitCss(JUDGE_SCOPE_CLASS, fit === 'fill' ? 'fill' : framedCard ? 'framed-card' : 'card', framedCardRadiusPx ?? null)}
|
|
744
|
+
${fit === 'fill' && chrome === 'panel' ? expandedFramePanelRule(JUDGE_PANEL_CLASS, JUDGE_SCOPE_CLASS) : ''}
|
|
745
|
+
${framedCard ? `.${JUDGE_PANEL_CLASS} { ${expandedFramePanelDecls()} background: ${FRAMED_CARD_GROUND}; display: flow-root; } body > .${JUDGE_PANEL_CLASS} { margin: ${EXPANDED_FRAME.insetPx}px; height: calc(100vh - ${2 * EXPANDED_FRAME.insetPx}px); }` : ''}
|
|
746
|
+
${framedCard && chrome !== 'panel' ? `body { background: ${FRAMED_CARD_GROUND}; }` : ''}
|
|
718
747
|
${chrome === 'ground' ? `.${JUDGE_GROUND_CLASS} { padding: ${JUDGE_GROUND_MARGIN_PX}px; } .${JUDGE_INLINE_FRAME_CLASS} { ${inlineFrameDecls(presentation?.frame)} }` : ''}
|
|
719
748
|
</style>
|
|
720
749
|
</head>
|
|
721
750
|
<body>
|
|
722
|
-
${chrome === 'panel' ? `<div class="${JUDGE_PANEL_CLASS}">` : chrome === 'ground' ? `<div class="${JUDGE_GROUND_CLASS}"><div class="${JUDGE_INLINE_FRAME_CLASS}">` : ''}<div id="root"
|
|
751
|
+
${chrome === 'panel' ? `<div class="${JUDGE_PANEL_CLASS}">` : chrome === 'ground' ? `<div class="${JUDGE_GROUND_CLASS}"><div class="${JUDGE_INLINE_FRAME_CLASS}">` : ''}<div id="root" class="${JUDGE_SCOPE_CLASS}"></div>${chrome === 'panel' ? '</div>' : chrome === 'ground' ? '</div></div>' : ''}
|
|
723
752
|
<script type="importmap">
|
|
724
753
|
{
|
|
725
754
|
"imports": {
|
|
@@ -856,6 +885,8 @@ export interface ScreenshotAttempt {
|
|
|
856
885
|
* probe failed. Absent on a failed capture.
|
|
857
886
|
*/
|
|
858
887
|
readonly edge?: EdgeVerdict | null;
|
|
888
|
+
/** #1765 — the v2 edge rule on the same DOM, read beside `edge` (report-only); `null` when its probe failed. */
|
|
889
|
+
readonly edgeV2?: EdgeVerdict | null;
|
|
859
890
|
}
|
|
860
891
|
|
|
861
892
|
/** The expression the fit measurement evaluates in the page — the taller of the two scroll heights. */
|
|
@@ -902,6 +933,33 @@ export function naturalClip(window: { width: number; height: number }, cardHeigh
|
|
|
902
933
|
return { x: 0, y: 0, width: window.width, height: Math.max(1, Math.min(Math.ceil(cardHeightPx), box)) + 2 * m };
|
|
903
934
|
}
|
|
904
935
|
|
|
936
|
+
/** The expression a card-box capture reads: the mount's own border box, in page CSS px (ggui#1953). */
|
|
937
|
+
export const CARD_BOX_EXPRESSION = `(() => { const r = document.querySelector('.${JUDGE_SCOPE_CLASS}'); if (!r) return null; const b = r.getBoundingClientRect(); return { x: b.x, y: b.y, width: b.width, height: b.height }; })()`;
|
|
938
|
+
|
|
939
|
+
async function measureCardBox(page: ScreenshotPage): Promise<{ x: number; y: number; width: number; height: number } | null> {
|
|
940
|
+
try {
|
|
941
|
+
const v: unknown = await page.evaluate(CARD_BOX_EXPRESSION);
|
|
942
|
+
if (typeof v !== 'object' || v === null) return null;
|
|
943
|
+
const n = (k: string): number | null => { const x = Reflect.get(v, k); return typeof x === 'number' && Number.isFinite(x) ? x : null; };
|
|
944
|
+
const [x, y, width, height] = [n('x'), n('y'), n('width'), n('height')];
|
|
945
|
+
return x === null || y === null || width === null || height === null || width <= 0 || height <= 0 ? null : { x, y, width, height };
|
|
946
|
+
} catch (e) {
|
|
947
|
+
console.warn(`[visual-eval] card box unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
948
|
+
return null;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* The card-box capture's region (ggui#1953): the card's own box with the frame ground's margin on every side, clamped
|
|
954
|
+
* to the window. The card is never cropped above its own bottom edge while the window can hold it.
|
|
955
|
+
*/
|
|
956
|
+
export function cardBoxClip(window: { width: number; height: number }, box: { x: number; y: number; width: number; height: number }): ScreenshotClip {
|
|
957
|
+
const m = JUDGE_GROUND_MARGIN_PX;
|
|
958
|
+
const x = Math.max(0, Math.floor(box.x - m));
|
|
959
|
+
const y = Math.max(0, Math.floor(box.y - m));
|
|
960
|
+
return { x, y, width: Math.min(window.width - x, Math.ceil(box.width + 2 * m)), height: Math.max(1, Math.min(window.height - y, Math.ceil(box.height + 2 * m))) };
|
|
961
|
+
}
|
|
962
|
+
|
|
905
963
|
/** Measure the rendered document's height (or the card's, for a natural capture); a failed measurement is reported, never a failed capture. */
|
|
906
964
|
async function measureContentHeight(page: ScreenshotPage, expression: string = CONTENT_HEIGHT_EXPRESSION): Promise<number | null> {
|
|
907
965
|
try {
|
|
@@ -917,7 +975,26 @@ async function measureContentHeight(page: ScreenshotPage, expression: string = C
|
|
|
917
975
|
* ggui#1663 — read the edge rule off the page the screenshot is about to take. The probe only reads layout (it never
|
|
918
976
|
* writes to the DOM), so the frame is unchanged; a failed read is reported and returns `null`, never a failed capture.
|
|
919
977
|
*/
|
|
920
|
-
async function measureEdges(page: ScreenshotPage): Promise<EdgeVerdict | null> {
|
|
978
|
+
async function measureEdges(page: ScreenshotPage): Promise<{ edge: EdgeVerdict | null; edgeV2: EdgeVerdict | null }> {
|
|
979
|
+
return { edge: await measureEdgesV1(page), edgeV2: await measureEdgesV2(page) };
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/** #1765 — the v2 rule on the same page, beside v1; a failed read is reported and returns `null`, never a failed capture. */
|
|
983
|
+
async function measureEdgesV2(page: ScreenshotPage): Promise<EdgeVerdict | null> {
|
|
984
|
+
try {
|
|
985
|
+
const probe = parseEdgeProbeV2(await page.evaluate(EDGE_BLOCKS_V2_EXPRESSION));
|
|
986
|
+
if (probe === null) {
|
|
987
|
+
console.warn('[visual-eval] edge probe v2 unavailable: the page returned no readable blocks');
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
return judgeEdgesV2(probe.blocks, { cardContent: probe.cardContent, placeholdersOnly: probe.placeholdersOnly });
|
|
991
|
+
} catch (e) {
|
|
992
|
+
console.warn(`[visual-eval] edge probe v2 unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
993
|
+
return null;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
async function measureEdgesV1(page: ScreenshotPage): Promise<EdgeVerdict | null> {
|
|
921
998
|
try {
|
|
922
999
|
const probe = parseEdgeProbe(await page.evaluate(EDGE_BLOCKS_EXPRESSION));
|
|
923
1000
|
if (probe === null) {
|
|
@@ -969,14 +1046,26 @@ export async function captureScreenshotDetailed(
|
|
|
969
1046
|
}
|
|
970
1047
|
const framed = cardHeightPx === null ? viewport.height - 2 * natural.padPx : floorPx !== undefined ? Math.max(cardHeightPx, floorPx) : cardHeightPx;
|
|
971
1048
|
const clip = naturalClip(viewport, framed, natural.padPx);
|
|
972
|
-
const
|
|
1049
|
+
const edges = await measureEdges(page);
|
|
973
1050
|
const screenshot = await page.screenshot({ type: 'png', fullPage: false, clip });
|
|
974
|
-
return { png: Buffer.from(screenshot), contentHeight: cardHeightPx, overflowX,
|
|
1051
|
+
return { png: Buffer.from(screenshot), contentHeight: cardHeightPx, overflowX, ...edges };
|
|
1052
|
+
}
|
|
1053
|
+
if (capture === 'card-box') {
|
|
1054
|
+
// ggui#1953 — the card's own box (the mount, which wears the card frame), so a root that stretches itself still
|
|
1055
|
+
// shows its void, and the host's empty ground below a short card is never in the frame.
|
|
1056
|
+
const box = await measureCardBox(page);
|
|
1057
|
+
// Fail closed, named: a card-box capture that cannot find the card's own box never falls back to the pane, which
|
|
1058
|
+
// is the frame this capture exists to stop judging (and would read as the old defect, silently).
|
|
1059
|
+
if (box === null) return { png: null, reason: `card-box capture: the card's own box (.${JUDGE_SCOPE_CLASS}) could not be measured`, contentHeight: null };
|
|
1060
|
+
const clip = cardBoxClip(viewport, box);
|
|
1061
|
+
const edges = await measureEdges(page);
|
|
1062
|
+
const screenshot = await page.screenshot({ type: 'png', fullPage: false, clip });
|
|
1063
|
+
return { png: Buffer.from(screenshot), contentHeight: Math.round(box.height), ...edges };
|
|
975
1064
|
}
|
|
976
1065
|
const contentHeight = await measureContentHeight(page);
|
|
977
|
-
const
|
|
1066
|
+
const edges = await measureEdges(page);
|
|
978
1067
|
const screenshot = await page.screenshot({ type: 'png', fullPage: capture === 'full-page' });
|
|
979
|
-
return { png: Buffer.from(screenshot), contentHeight,
|
|
1068
|
+
return { png: Buffer.from(screenshot), contentHeight, ...edges };
|
|
980
1069
|
} finally {
|
|
981
1070
|
await browser.close();
|
|
982
1071
|
}
|
|
@@ -1137,9 +1226,29 @@ export interface VisualEvalDetailed {
|
|
|
1137
1226
|
* ({@link runVisualFit}) frame through this ONE function, so a fit verdict
|
|
1138
1227
|
* reached without the judge is taken on the frame the judge would have seen.
|
|
1139
1228
|
*/
|
|
1229
|
+
/**
|
|
1230
|
+
* The canvas a compiled bundle declares in its {@link GGUI_MOUNT_CANVAS_EXPORT} export, read from the code as the runtime
|
|
1231
|
+
* reads it from the module: the binding exported under that name (`export{r as gguiMountCanvas}` minified, or the plain
|
|
1232
|
+
* `const gguiMountCanvas`) and its string literal. Untrusted: `resolveMountFit` narrows it, and anything unread is `undefined`.
|
|
1233
|
+
*/
|
|
1234
|
+
export function mountCanvasOf(compiledCode: string): string | undefined {
|
|
1235
|
+
const name = GGUI_MOUNT_CANVAS_EXPORT;
|
|
1236
|
+
const binding = new RegExp(`([A-Za-z_$][\\w$]*)\\s+as\\s+${name}\\b`).exec(compiledCode)?.[1] ?? name;
|
|
1237
|
+
return new RegExp(`(?:^|[^\\w$.])${binding.replace(/\$/g, '\\$')}\\s*=\\s*"([^"\\\\]*)"`).exec(compiledCode)?.[1];
|
|
1238
|
+
}
|
|
1239
|
+
/** The `--ggui-*` custom properties a composed token block declares (the last declaration of a name wins, as in CSS). */
|
|
1240
|
+
function themeVarsOf(css: string): Readonly<Record<string, string>> {
|
|
1241
|
+
const vars: Record<string, string> = {};
|
|
1242
|
+
for (const [, name, value] of css.matchAll(/(--ggui-[\w-]+)\s*:\s*([^;}]+)/g)) {
|
|
1243
|
+
if (name !== undefined && value !== undefined) vars[name] = value.trim();
|
|
1244
|
+
}
|
|
1245
|
+
return vars;
|
|
1246
|
+
}
|
|
1140
1247
|
interface CanvasFrame {
|
|
1141
1248
|
readonly viewport: CanvasViewport;
|
|
1142
1249
|
readonly policy: CanvasFitPolicy;
|
|
1250
|
+
/** How the runtime would compose this card on this canvas (`resolveMountFit`) — the capture and the frame statement follow it. */
|
|
1251
|
+
readonly mountFit: MountFit;
|
|
1143
1252
|
readonly fit: 'fill' | undefined;
|
|
1144
1253
|
/** The host stand-in drawn round the scope (ggui#1083 cut 3) — the window is padded by its gap; `viewport` stays the card's box. */
|
|
1145
1254
|
readonly chrome: CanvasChrome;
|
|
@@ -1174,7 +1283,6 @@ function blankVerdict(canvas: CanvasClass, frame: CanvasFrame): VisualIssue | nu
|
|
|
1174
1283
|
async function frameCanvas(
|
|
1175
1284
|
bundledCode: string,
|
|
1176
1285
|
context: VisualEvalContext,
|
|
1177
|
-
html: string,
|
|
1178
1286
|
canvas: CanvasClass,
|
|
1179
1287
|
canvasViewports: VisualEvalConfig['canvasViewports'],
|
|
1180
1288
|
deps: ScreenshotDeps,
|
|
@@ -1182,27 +1290,48 @@ async function frameCanvas(
|
|
|
1182
1290
|
): Promise<CanvasFrame> {
|
|
1183
1291
|
const declaredBox = canvasViewports?.[canvas];
|
|
1184
1292
|
const viewport = declaredBox !== undefined ? integerBox(declaredBox) : CANVAS_VIEWPORTS[canvas];
|
|
1185
|
-
|
|
1186
|
-
const
|
|
1293
|
+
// The ONE fit rule the runtime serves by: the bundle's canvas export + the display mode this canvas stands for.
|
|
1294
|
+
const mountFit = resolveMountFit({ canvas: mountCanvasOf(context.compiledCode), displayMode: displayModeForCanvas(canvas) });
|
|
1295
|
+
// ggui#1953 — the fit decides the overflow posture: a framed card is a card, seen whole in its room, so a card that
|
|
1296
|
+
// cannot fit its pane fails the canvas as the inline card does (#1475), never scrolls as a page.
|
|
1297
|
+
const policy: CanvasFitPolicy = mountFit === 'framed-card' ? { capture: 'card-box', overflow: 'fail' } : canvasFitPolicy(canvas);
|
|
1298
|
+
const fit = mountFit === 'framed-card' ? undefined : canvasFit(canvas);
|
|
1187
1299
|
const chrome = canvasChrome(canvas);
|
|
1188
1300
|
const outcome = resolvePresentation(canvas, hostPresentations?.[canvas]);
|
|
1189
1301
|
const presentation = outcome?.status === 'applied' ? outcome.applied : undefined;
|
|
1190
1302
|
const geometry = inlineGeometry(presentation?.frame);
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1303
|
+
const framedCardRadiusPx = mountFit === 'framed-card' ? inlineCardRadius(themeVarsOf(context.cssTokens ?? getCssTokens())).radiusPx : undefined;
|
|
1304
|
+
// Every canvas's page is composed by the one builder, from this canvas's fit, chrome and frame: a framed card on a
|
|
1305
|
+
// canvas with no chrome (the phone, ggui#1953) is still composed as the runtime serves it, never the plain page.
|
|
1306
|
+
const canvasHtml = buildRenderHTML(bundledCode, context.cssTokens, fit, chrome, presentation, framedCardRadiusPx);
|
|
1307
|
+
// ggui#1953 — a framed card is captured as a card, on its own box, at every canvas it is framed on.
|
|
1308
|
+
const capture: CaptureMode = policy.capture;
|
|
1309
|
+
const captured = await captureScreenshotDetailed(canvasHtml, judgeWindow(viewport, chrome, geometry.padPx), deps, capture, {
|
|
1194
1310
|
padPx: geometry.padPx,
|
|
1195
1311
|
...(geometry.floorPx !== undefined ? { floorPx: geometry.floorPx } : {}),
|
|
1196
1312
|
});
|
|
1197
1313
|
// ggui#1083 cut 3 — on a panelled page the document is the panel plus its gap; every verdict reads the CARD's height.
|
|
1198
|
-
|
|
1314
|
+
// A card-box capture already measured the card itself; only the full-page document carries the panel's gap.
|
|
1315
|
+
const attempt: ScreenshotAttempt = { ...captured, contentHeight: capture === 'card-box' ? captured.contentHeight : cardHeight(captured.contentHeight, chrome) };
|
|
1316
|
+
// A card-box frame has the inline card's geometry (margin, ring, corner), so its ink is read past the same inset.
|
|
1317
|
+
const cardBoxInk = JUDGE_GROUND_MARGIN_PX + JUDGE_INLINE_FRAME_BORDER_PX + (framedCardRadiusPx ?? JUDGE_INLINE_FRAME_RADIUS_PX);
|
|
1199
1318
|
return {
|
|
1200
|
-
viewport, policy, fit, chrome, declared: declaredBox !== undefined, attempt,
|
|
1201
|
-
ink: readInk(captured.png, chrome, canvas, geometry.inkInsetPx),
|
|
1319
|
+
viewport, policy, mountFit, fit, chrome, declared: declaredBox !== undefined, attempt,
|
|
1320
|
+
ink: capture === 'card-box' ? readInk(captured.png, 'ground', canvas, cardBoxInk) : readInk(captured.png, chrome, canvas, geometry.inkInsetPx),
|
|
1202
1321
|
...(outcome !== undefined ? { presentation: outcome } : {}),
|
|
1203
1322
|
};
|
|
1204
1323
|
}
|
|
1205
1324
|
|
|
1325
|
+
/**
|
|
1326
|
+
* The box a frame's card must fit (ggui#1953): the canvas box, except for a framed card, which sits in its pane with
|
|
1327
|
+
* the frame's gap above and below — so its ceiling is the pane less that gap on both sides (704 in a 736 pane).
|
|
1328
|
+
*/
|
|
1329
|
+
export function fitCeiling(frame: Pick<CanvasFrame, 'viewport' | 'mountFit'>): { width: number; height: number } {
|
|
1330
|
+
return frame.mountFit === 'framed-card'
|
|
1331
|
+
? { width: frame.viewport.width, height: frame.viewport.height - 2 * EXPANDED_FRAME.insetPx }
|
|
1332
|
+
: frame.viewport;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1206
1335
|
/**
|
|
1207
1336
|
* The deterministic fit verdict on one frame (ggui#1027): the issue the
|
|
1208
1337
|
* canvas's policy gives an overflow, or `null` when the content fits, could
|
|
@@ -1210,8 +1339,9 @@ async function frameCanvas(
|
|
|
1210
1339
|
*/
|
|
1211
1340
|
function fitVerdict(canvas: CanvasClass, frame: CanvasFrame): VisualIssue | null {
|
|
1212
1341
|
const contentHeight = frame.attempt.contentHeight;
|
|
1213
|
-
|
|
1214
|
-
|
|
1342
|
+
const ceiling = fitCeiling(frame);
|
|
1343
|
+
if (contentHeight === null || contentHeight <= ceiling.height || frame.policy.overflow === 'none') return null;
|
|
1344
|
+
return canvasOverflowIssue(canvas, ceiling, contentHeight, frame.policy.overflow);
|
|
1215
1345
|
}
|
|
1216
1346
|
|
|
1217
1347
|
/**
|
|
@@ -1471,7 +1601,7 @@ export async function runVisualEvaluationDetailed(
|
|
|
1471
1601
|
const perCanvas: CanvasVisualResult[] = [];
|
|
1472
1602
|
const perCanvasResults: EvaluationResult[] = [];
|
|
1473
1603
|
for (const canvas of config.canvases) {
|
|
1474
|
-
const frame = await frameCanvas(bundledCode, context,
|
|
1604
|
+
const frame = await frameCanvas(bundledCode, context, canvas, config.canvasViewports, deps, config.hostPresentations);
|
|
1475
1605
|
const { viewport, policy, fit, attempt } = frame;
|
|
1476
1606
|
const screenshot = attempt.png;
|
|
1477
1607
|
if (!screenshot) {
|
|
@@ -1490,7 +1620,7 @@ export async function runVisualEvaluationDetailed(
|
|
|
1490
1620
|
const criteriaBlock =
|
|
1491
1621
|
context.criteria !== undefined && criteriaSelected !== undefined
|
|
1492
1622
|
? buildCriteriaJudgeBlock(context.criteria.bank, criteriaSelected, {
|
|
1493
|
-
frame: { canvas, width: viewport.width, height: viewport.height, ...(frame.presentation?.status === 'applied' ? { hostLabel: frame.presentation.applied.label } : {}) },
|
|
1623
|
+
frame: { canvas, width: viewport.width, height: viewport.height, fit: frame.mountFit, ...(frame.presentation?.status === 'applied' ? { hostLabel: frame.presentation.applied.label } : {}) },
|
|
1494
1624
|
...(config.sampleProps !== undefined ? { propsJson: JSON.stringify(config.sampleProps) } : {}),
|
|
1495
1625
|
})
|
|
1496
1626
|
: '';
|
|
@@ -1541,7 +1671,7 @@ export async function runVisualEvaluationDetailed(
|
|
|
1541
1671
|
context: criteriaContext,
|
|
1542
1672
|
selected: criteriaSelected,
|
|
1543
1673
|
answers: criteriaReads(criteriaAnswer),
|
|
1544
|
-
measurements: { overflow, contentHeight, viewportHeight: viewport.height, inkRatio, edge: attempt.edge ?? null },
|
|
1674
|
+
measurements: { overflow, contentHeight, viewportHeight: viewport.height, inkRatio, edge: attempt.edge ?? null, edgeV2: attempt.edgeV2 ?? null },
|
|
1545
1675
|
...criteriaUnansweredReason(criteriaAnswer),
|
|
1546
1676
|
...criteriaCallOf(criteriaAnswer),
|
|
1547
1677
|
})
|
|
@@ -1736,6 +1866,8 @@ export function summarizeVisualResult(result: VisualEvaluationResult): VisualEva
|
|
|
1736
1866
|
|
|
1737
1867
|
/** One stored capture to judge: the frame the judge saw at capture time, plus what the capture measured. */
|
|
1738
1868
|
export interface StoredCaptureInput {
|
|
1869
|
+
/** How the stored capture was composed. Absent ⇒ the canvas's composition before mount fits existed (`fill` on a fullscreen canvas, else `card`). */
|
|
1870
|
+
readonly mountFit?: MountFit;
|
|
1739
1871
|
readonly canvas: CanvasClass;
|
|
1740
1872
|
/** The capture as saved (`canvas-<class>.png`). */
|
|
1741
1873
|
readonly png: Buffer;
|
|
@@ -1800,7 +1932,7 @@ export async function judgeStoredCapture(
|
|
|
1800
1932
|
const criteriaBlock =
|
|
1801
1933
|
input.criteria !== undefined && criteriaSelected !== undefined
|
|
1802
1934
|
? buildCriteriaJudgeBlock(input.criteria.bank, criteriaSelected, {
|
|
1803
|
-
frame: { canvas, width: viewport.width, height: viewport.height },
|
|
1935
|
+
frame: { canvas, width: viewport.width, height: viewport.height, fit: input.mountFit ?? (canvasFit(canvas) === 'fill' ? 'fill' : 'card') },
|
|
1804
1936
|
...(input.sampleProps !== undefined ? { propsJson: JSON.stringify(input.sampleProps) } : {}),
|
|
1805
1937
|
})
|
|
1806
1938
|
: '';
|
|
@@ -2151,11 +2283,10 @@ export async function runVisualFit(
|
|
|
2151
2283
|
} catch (e) {
|
|
2152
2284
|
return { status: 'unavailable', reason: `bundle failed: ${e instanceof Error ? e.message : String(e)}` };
|
|
2153
2285
|
}
|
|
2154
|
-
const html = buildRenderHTML(bundledCode, context.cssTokens);
|
|
2155
2286
|
const issues: EvalIssue[] = [];
|
|
2156
2287
|
const readings: CanvasFitReading[] = [];
|
|
2157
2288
|
for (const canvas of config.canvases) {
|
|
2158
|
-
const frame = await frameCanvas(bundledCode, context,
|
|
2289
|
+
const frame = await frameCanvas(bundledCode, context, canvas, config.canvasViewports, deps, config.hostPresentations);
|
|
2159
2290
|
if (!frame.attempt.png) {
|
|
2160
2291
|
return { status: 'unavailable', reason: `${frame.attempt.reason ?? 'no browser available'} at canvas ${canvas}` };
|
|
2161
2292
|
}
|
|
@@ -90,6 +90,28 @@ export async function warmupRuntimeRenderProbe(): Promise<{
|
|
|
90
90
|
*/
|
|
91
91
|
const WARMUP_COMPONENT = "export default function Warmup() { return <div>warm</div>; }";
|
|
92
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Why a worker warm-up failed (ggui#1839), one kind per exit of
|
|
95
|
+
* {@link warmupRuntimeRenderWorker}:
|
|
96
|
+
*
|
|
97
|
+
* - `incomplete`: the check started and ran out of its bound before it
|
|
98
|
+
* finished. The worker may be fine and the machine slow (a cold node's
|
|
99
|
+
* first spawn); it says nothing about whether a check can pass.
|
|
100
|
+
* - `blocking`: the check ran to an end and reported a `failed` or
|
|
101
|
+
* `unverified` issue: the worker cannot render a trivial component.
|
|
102
|
+
* - `spawn`: the spawn layer refused to run the worker at all (an invalid
|
|
103
|
+
* bound, a missing binary).
|
|
104
|
+
*
|
|
105
|
+
* Branch on this, never on `reason`, which is prose for a human.
|
|
106
|
+
*/
|
|
107
|
+
export const RUNTIME_RENDER_WARMUP_FAILURE_KINDS = ["incomplete", "blocking", "spawn"] as const;
|
|
108
|
+
export type RuntimeRenderWarmupFailureKind = (typeof RUNTIME_RENDER_WARMUP_FAILURE_KINDS)[number];
|
|
109
|
+
|
|
110
|
+
/** The result of {@link warmupRuntimeRenderWorker}. */
|
|
111
|
+
export type RuntimeRenderWorkerWarmup =
|
|
112
|
+
| { ok: true; ms: number }
|
|
113
|
+
| { ok: false; ms: number; kind: RuntimeRenderWarmupFailureKind; reason: string };
|
|
114
|
+
|
|
93
115
|
/**
|
|
94
116
|
* Warm the PROCESS that runs every probe (ggui#1380): spawn one real check
|
|
95
117
|
* worker on a trivial component through the same host every probe uses.
|
|
@@ -100,12 +122,12 @@ const WARMUP_COMPONENT = "export default function Warmup() { return <div>warm</d
|
|
|
100
122
|
*
|
|
101
123
|
* `bounds` are the subprocess bounds for the one run; a serving deployment
|
|
102
124
|
* 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
|
|
104
|
-
* decide
|
|
125
|
+
* cannot run is `{ ok: false, kind, reason }`, so a boot sequence can log
|
|
126
|
+
* it and decide by `kind`, instead of dying in a fire-and-forget.
|
|
105
127
|
*/
|
|
106
128
|
export async function warmupRuntimeRenderWorker(
|
|
107
129
|
bounds?: Partial<RenderCheckHostBounds>
|
|
108
|
-
): Promise<
|
|
130
|
+
): Promise<RuntimeRenderWorkerWarmup> {
|
|
109
131
|
const start = Date.now();
|
|
110
132
|
try {
|
|
111
133
|
// Lazy: the host pulls `@ggui-ai/sandbox` (spawn machinery); keep it off
|
|
@@ -120,6 +142,7 @@ export async function warmupRuntimeRenderWorker(
|
|
|
120
142
|
return {
|
|
121
143
|
ok: false,
|
|
122
144
|
ms,
|
|
145
|
+
kind: "incomplete",
|
|
123
146
|
reason: `the warm-up check did not finish within ${result.incomplete.boundMs} ms (stopped at ${result.incomplete.elapsedMs} ms)`,
|
|
124
147
|
};
|
|
125
148
|
}
|
|
@@ -127,7 +150,7 @@ export async function warmupRuntimeRenderWorker(
|
|
|
127
150
|
(i) => i.outcome === "failed" || i.outcome === "unverified"
|
|
128
151
|
);
|
|
129
152
|
if (blocking !== undefined) {
|
|
130
|
-
return { ok: false, ms, reason: `${blocking.check} ${blocking.outcome}: ${blocking.reason}` };
|
|
153
|
+
return { ok: false, ms, kind: "blocking", reason: `${blocking.check} ${blocking.outcome}: ${blocking.reason}` };
|
|
131
154
|
}
|
|
132
155
|
return { ok: true, ms };
|
|
133
156
|
} catch (err) {
|
|
@@ -136,6 +159,7 @@ export async function warmupRuntimeRenderWorker(
|
|
|
136
159
|
return {
|
|
137
160
|
ok: false,
|
|
138
161
|
ms: Date.now() - start,
|
|
162
|
+
kind: "spawn",
|
|
139
163
|
reason: err instanceof Error ? err.message : String(err),
|
|
140
164
|
};
|
|
141
165
|
}
|