@ggui-ai/ui-gen 0.24.0 → 0.25.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 → adapter-Cg_rkpGq.d.ts} +1 -1
- package/dist/adapters/generation-dispatch.d.ts +27 -5
- package/dist/adapters/generation-dispatch.js +909 -53
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +3 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +12 -10
- package/dist/advanced/index.js +917 -98
- package/dist/advanced/index.js.map +1 -1
- package/dist/{axes-CzLEMDeB.d.ts → axes-CKVCzR_x.d.ts} +1 -1
- package/dist/bank-CT4eEkta.d.ts +118 -0
- package/dist/boilerplate.js +24 -12
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +11 -4
- package/dist/check/index.js +47 -1
- package/dist/check/index.js.map +1 -1
- package/dist/classifier/index.d.ts +2 -2
- package/dist/compose.d.ts +2 -2
- package/dist/{create-ui-generator-GIhzx08x.d.ts → create-ui-generator-DFnnArrv.d.ts} +11 -3
- package/dist/{dispatch-BnAH56p4.d.ts → dispatch-CStl9eAw.d.ts} +5 -3
- package/dist/evaluation/axis-checks/index.d.ts +5 -5
- package/dist/evaluation/axis-checks/index.js +90 -2
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -3
- package/dist/evaluation/axis-checks/registry.js +88 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +300 -23
- package/dist/evaluation/index.js +746 -30
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +9 -1
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.d.ts +3 -3
- package/dist/harness/check/runtime-render/index.d.ts +5 -5
- package/dist/harness/index.d.ts +4 -4
- package/dist/harness/index.js +33 -15
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/result-types.d.ts +3 -3
- package/dist/harness/runtime.d.ts +10 -4
- package/dist/harness/runtime.js +26 -14
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -3
- package/dist/harness/types-public.js +33 -15
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -3
- package/dist/hash.d.ts +1 -1
- package/dist/index.d.ts +25 -9
- package/dist/index.js +926 -101
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-DwwUK1bz.d.ts → llm-router-nfO7wPec.d.ts} +3 -1
- package/dist/patch.d.ts +3 -3
- package/dist/policy.d.ts +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/{types-BOvHNG7K.d.ts → types-Dh1s4K9j.d.ts} +1 -1
- package/dist/{types-public-Bk6d1WLN.d.ts → types-public-BJTLpzWn.d.ts} +134 -7
- 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 +3 -3
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +36 -2
- package/src/adapters/tools.ts +2 -1
- package/src/boilerplate/free-design-prompt.ts +1 -1
- package/src/boilerplate/generate.ts +26 -1
- package/src/boilerplate/hard-sections.ts +13 -13
- package/src/boilerplate/system-prompt.ts +1 -1
- package/src/check/index.ts +1 -0
- package/src/check/sample-props.ts +63 -0
- package/src/coding-agent/tools.ts +4 -1
- package/src/create-harness.ts +3 -3
- package/src/create-ui-generator.ts +20 -1
- package/src/evaluation/__fixtures__/png.ts +100 -0
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/universal.ts +111 -0
- package/src/evaluation/axis-checks/dispatch.ts +4 -1
- package/src/evaluation/criteria/bank.ts +85 -0
- package/src/evaluation/criteria/context.ts +52 -0
- package/src/evaluation/criteria/resolve.ts +159 -0
- package/src/evaluation/criteria/select.ts +79 -0
- package/src/evaluation/index.ts +23 -0
- package/src/evaluation/ink-extent.ts +200 -0
- package/src/evaluation/theme-css.ts +12 -5
- package/src/evaluation/types-public.ts +130 -3
- package/src/evaluation/types.ts +9 -0
- package/src/evaluation/visual-evaluator.ts +636 -30
- package/src/harness/coding/generate-task-runner.ts +3 -0
- package/src/harness/coding/run-coding-turn.ts +7 -3
- package/src/harness/coding/run-eval-round.ts +19 -0
- package/src/harness/runtime.ts +5 -0
- package/src/harness-tool-names.ts +38 -0
- package/src/index.ts +12 -0
- package/src/run-check.ts +2 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/get-wire.ts +1 -1
- package/src/tools/render-check.ts +0 -60
- package/src/tools.ts +11 -1
- package/src/validation/primitives.ts +1 -1
package/dist/evaluation/index.js
CHANGED
|
@@ -5,7 +5,8 @@ import { z } from 'zod';
|
|
|
5
5
|
import { LUCIDE_ICON_NAMES, maxWidth } from '@ggui-ai/design';
|
|
6
6
|
import { createHash } from 'crypto';
|
|
7
7
|
import { build } from 'esbuild';
|
|
8
|
-
import { getCssTokens, composeThemeCss, fillFitRule } from '@ggui-ai/design/rendering';
|
|
8
|
+
import { EXPANDED_FRAME, getCssTokens, composeThemeCss, expandedFrameScrimDecls, fillFitRule, expandedFramePanelRule } from '@ggui-ai/design/rendering';
|
|
9
|
+
import { inflateSync } from 'zlib';
|
|
9
10
|
import { readFileSync, readdirSync, statSync, mkdtempSync, writeFileSync, rmSync, existsSync } from 'fs';
|
|
10
11
|
import { resolve, relative, dirname } from 'path';
|
|
11
12
|
import { fileURLToPath } from 'url';
|
|
@@ -3295,6 +3296,81 @@ function runActionLabelDropped(input) {
|
|
|
3295
3296
|
)
|
|
3296
3297
|
);
|
|
3297
3298
|
}
|
|
3299
|
+
var MIN_ECHO_CHARS = 12;
|
|
3300
|
+
function collectStrings(value, out) {
|
|
3301
|
+
if (typeof value === "string") {
|
|
3302
|
+
out.push(value);
|
|
3303
|
+
} else if (Array.isArray(value)) {
|
|
3304
|
+
for (const v of value) collectStrings(v, out);
|
|
3305
|
+
} else if (value !== null && typeof value === "object") {
|
|
3306
|
+
for (const v of Object.values(value)) collectStrings(v, out);
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
function fixtureOnlyStrings(input) {
|
|
3310
|
+
if (input.fixtureProps === void 0) return [];
|
|
3311
|
+
const values = [];
|
|
3312
|
+
collectStrings(input.fixtureProps, values);
|
|
3313
|
+
const stated = `${input.originalPrompt}
|
|
3314
|
+
${input.contract === void 0 ? "" : JSON.stringify(input.contract)}`;
|
|
3315
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3316
|
+
const out = [];
|
|
3317
|
+
for (const v of values) {
|
|
3318
|
+
const t = v.trim();
|
|
3319
|
+
if (t.length < MIN_ECHO_CHARS || !t.includes(" ") || seen.has(t)) continue;
|
|
3320
|
+
seen.add(t);
|
|
3321
|
+
if (stated.includes(t)) continue;
|
|
3322
|
+
out.push(t);
|
|
3323
|
+
}
|
|
3324
|
+
return out;
|
|
3325
|
+
}
|
|
3326
|
+
function runFixtureDefaultEcho(input) {
|
|
3327
|
+
if (input.compiledCode === null) return [];
|
|
3328
|
+
const src = input.sourceCode;
|
|
3329
|
+
const issues = [];
|
|
3330
|
+
for (const value of fixtureOnlyStrings(input)) {
|
|
3331
|
+
if (!src.includes(value)) continue;
|
|
3332
|
+
issues.push(
|
|
3333
|
+
mkIssue(
|
|
3334
|
+
"universal.fixture_default_echo",
|
|
3335
|
+
`A literal in the source equals a fixture value the request never stated: "${value.length > 60 ? `${value.slice(0, 57)}\u2026` : value}". The fixture is judging data; a visitor sees this copy whenever the agent leaves the prop out.`,
|
|
3336
|
+
"Take that copy from the prop it renders and drop the literal \u2014 no default, fallback or placeholder that repeats a fixture value. A default may only carry text the request itself states.",
|
|
3337
|
+
"warn"
|
|
3338
|
+
)
|
|
3339
|
+
);
|
|
3340
|
+
}
|
|
3341
|
+
return issues;
|
|
3342
|
+
}
|
|
3343
|
+
var FORM_OPEN_RX = /<form\b[^<>]*\bonSubmit\s*=[^<>]*>/g;
|
|
3344
|
+
var CONTROL_TAG_RX = /<(?:Button|button|input)\b[^<>]*>/g;
|
|
3345
|
+
var SUBMIT_TYPE_RX = /\btype\s*=\s*(?:["']submit["']|\{\s*["']submit["']\s*\})/;
|
|
3346
|
+
function submitForms(sourceCode) {
|
|
3347
|
+
const out = [];
|
|
3348
|
+
for (const m of sourceCode.matchAll(FORM_OPEN_RX)) {
|
|
3349
|
+
const start = m.index ?? 0;
|
|
3350
|
+
const end = sourceCode.indexOf("</form>", start + m[0].length);
|
|
3351
|
+
out.push(end === -1 ? sourceCode.slice(start) : sourceCode.slice(start, end + "</form>".length));
|
|
3352
|
+
}
|
|
3353
|
+
return out;
|
|
3354
|
+
}
|
|
3355
|
+
function runFormSubmitControl(input) {
|
|
3356
|
+
if (input.compiledCode === null) return [];
|
|
3357
|
+
const issues = [];
|
|
3358
|
+
for (const form of submitForms(input.sourceCode)) {
|
|
3359
|
+
const controls = [...form.matchAll(CONTROL_TAG_RX)].map((m) => m[0]);
|
|
3360
|
+
if (controls.length === 0) continue;
|
|
3361
|
+
if (controls.some((tag) => SUBMIT_TYPE_RX.test(tag))) continue;
|
|
3362
|
+
const named = controls.find((tag) => /<Button\b/.test(tag)) ?? controls[0];
|
|
3363
|
+
issues.push(
|
|
3364
|
+
mkIssue(
|
|
3365
|
+
"universal.form_submit_control",
|
|
3366
|
+
`A <form onSubmit> renders ${controls.length} control(s) and none submits it: the design Button defaults to type="button", so clicking ${named.length > 60 ? `${named.slice(0, 57)}\u2026` : named} does nothing \u2014 only Enter in a field submits.`,
|
|
3367
|
+
'Give the control that submits this form type="submit" \u2014 a Button inside a form defaults to type="button" and never submits on click; keep type="button" on the others.',
|
|
3368
|
+
"fail"
|
|
3369
|
+
)
|
|
3370
|
+
);
|
|
3371
|
+
}
|
|
3372
|
+
return issues;
|
|
3373
|
+
}
|
|
3298
3374
|
var UNIVERSAL_CHECKS = [
|
|
3299
3375
|
{
|
|
3300
3376
|
id: "universal.icon_name_known",
|
|
@@ -3332,6 +3408,18 @@ var UNIVERSAL_CHECKS = [
|
|
|
3332
3408
|
values: ALL_RENDER_VALUES,
|
|
3333
3409
|
run: runTerminalActionUnguarded
|
|
3334
3410
|
},
|
|
3411
|
+
{
|
|
3412
|
+
id: "universal.form_submit_control",
|
|
3413
|
+
axis: "render",
|
|
3414
|
+
values: ALL_RENDER_VALUES,
|
|
3415
|
+
run: runFormSubmitControl
|
|
3416
|
+
},
|
|
3417
|
+
{
|
|
3418
|
+
id: "universal.fixture_default_echo",
|
|
3419
|
+
axis: "render",
|
|
3420
|
+
values: ALL_RENDER_VALUES,
|
|
3421
|
+
run: runFixtureDefaultEcho
|
|
3422
|
+
},
|
|
3335
3423
|
{
|
|
3336
3424
|
id: "universal.accent_text_ink",
|
|
3337
3425
|
axis: "render",
|
|
@@ -3455,7 +3543,7 @@ function runMapKeyIsId(input) {
|
|
|
3455
3543
|
mkIssue(
|
|
3456
3544
|
"render.map_key_is_id",
|
|
3457
3545
|
`Array key uses index variable "${key}" \u2014 reorders and stream merges will break React reconciliation.`,
|
|
3458
|
-
`Replace key={${key}} with key={item.id}
|
|
3546
|
+
`Replace key={${key}} with key={item.id} \u2014 or, when the items carry no id, with the field that names each one (item.label / item.symbol / item.name).`
|
|
3459
3547
|
)
|
|
3460
3548
|
);
|
|
3461
3549
|
}
|
|
@@ -4391,7 +4479,8 @@ function runAxisChecks(classification, input) {
|
|
|
4391
4479
|
originalPrompt: input.originalPrompt,
|
|
4392
4480
|
classification,
|
|
4393
4481
|
...input.designMode !== void 0 ? { designMode: input.designMode } : {},
|
|
4394
|
-
...input.canvas !== void 0 ? { canvas: input.canvas } : {}
|
|
4482
|
+
...input.canvas !== void 0 ? { canvas: input.canvas } : {},
|
|
4483
|
+
...input.fixtureProps !== void 0 ? { fixtureProps: input.fixtureProps } : {}
|
|
4395
4484
|
};
|
|
4396
4485
|
if (input.compiledCode === null) {
|
|
4397
4486
|
traceAxisChecksSkipped(facts, "compiledCode null \u2014 no check ran");
|
|
@@ -4498,6 +4587,311 @@ function buildStylingProfileJudgeBlock(profile) {
|
|
|
4498
4587
|
if (quote.length === 0) return "";
|
|
4499
4588
|
return ["## Declared styling profile", "", STYLING_PROFILE_JUDGE_FRAME, "", quote].join("\n");
|
|
4500
4589
|
}
|
|
4590
|
+
var CRITERION_ID = /^[a-z][a-z0-9-]*(?:\.[a-zA-Z][a-zA-Z0-9-]*)+$/;
|
|
4591
|
+
var criteriaScopeSchema = z.object({
|
|
4592
|
+
kind: z.string().min(1).optional(),
|
|
4593
|
+
canvas: z.string().min(1).optional(),
|
|
4594
|
+
preset: z.string().min(1).optional()
|
|
4595
|
+
});
|
|
4596
|
+
var bankRowSchema = z.object({
|
|
4597
|
+
id: z.string().regex(CRITERION_ID, "a criterion id is family.property[.facet], lower-case family"),
|
|
4598
|
+
group: z.string().optional(),
|
|
4599
|
+
scope: criteriaScopeSchema.default({}),
|
|
4600
|
+
/** Importance, independent of whether anything automated checks it yet. */
|
|
4601
|
+
severity: z.enum(["must", "should"]),
|
|
4602
|
+
/** The method that produces the verdict. */
|
|
4603
|
+
evaluation: z.enum(["instrument", "judge", "human"]),
|
|
4604
|
+
/** The method's readiness — the only source of an `n/a`. */
|
|
4605
|
+
status: z.enum(["live", "reference", "planned", "manual"]),
|
|
4606
|
+
/** The question, answerable yes/no from the frame. */
|
|
4607
|
+
text: z.string().min(1),
|
|
4608
|
+
/** The evidence rule: what the one-sentence evidence must name (a region or element). */
|
|
4609
|
+
evidence: z.string().min(1),
|
|
4610
|
+
/** The templated fix line a feedback channel may consume later; never read in v1. */
|
|
4611
|
+
fix: z.string().optional()
|
|
4612
|
+
});
|
|
4613
|
+
var criteriaBankSchema = z.object({
|
|
4614
|
+
version: z.string().min(1),
|
|
4615
|
+
applies: z.object({
|
|
4616
|
+
/** `["*"]` = every kind. */
|
|
4617
|
+
kind: z.array(z.string().min(1)).optional(),
|
|
4618
|
+
canvases: z.array(z.string().min(1)).optional()
|
|
4619
|
+
}).optional(),
|
|
4620
|
+
criteria: z.array(bankRowSchema).min(1),
|
|
4621
|
+
recipes: z.array(bankRowSchema).optional()
|
|
4622
|
+
}).superRefine((bank, ctx) => {
|
|
4623
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4624
|
+
const check = (rows, key) => {
|
|
4625
|
+
rows.forEach((c, i) => {
|
|
4626
|
+
if (seen.has(c.id)) ctx.addIssue({ code: "custom", path: [key, i, "id"], message: `duplicate criterion id ${c.id}` });
|
|
4627
|
+
seen.add(c.id);
|
|
4628
|
+
});
|
|
4629
|
+
};
|
|
4630
|
+
check(bank.criteria, "criteria");
|
|
4631
|
+
check(bank.recipes ?? [], "recipes");
|
|
4632
|
+
});
|
|
4633
|
+
function bankRows(bank) {
|
|
4634
|
+
return [...bank.criteria, ...bank.recipes ?? []];
|
|
4635
|
+
}
|
|
4636
|
+
function parseCriteriaBank(raw) {
|
|
4637
|
+
const parsed = criteriaBankSchema.safeParse(raw);
|
|
4638
|
+
if (!parsed.success) {
|
|
4639
|
+
const issues = parsed.error.issues.map((i) => `${i.path.map(String).join(".") || "<root>"}: ${i.message}`).join("; ");
|
|
4640
|
+
throw new Error(`criteria bank invalid \u2014 ${issues}`);
|
|
4641
|
+
}
|
|
4642
|
+
return parsed.data;
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
// src/evaluation/criteria/select.ts
|
|
4646
|
+
var CRITERIA_SELECTOR_VERSION = "selector@2";
|
|
4647
|
+
function canonicalCriteriaContext(ctx) {
|
|
4648
|
+
const sortKeys = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? Object.fromEntries(
|
|
4649
|
+
Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => [k, sortKeys(v)])
|
|
4650
|
+
) : value;
|
|
4651
|
+
return JSON.stringify(sortKeys(ctx));
|
|
4652
|
+
}
|
|
4653
|
+
function criteriaSetIdFor(bankVersion, ctx) {
|
|
4654
|
+
return createHash("sha256").update(`${bankVersion}|${CRITERIA_SELECTOR_VERSION}|${canonicalCriteriaContext(ctx)}`, "utf8").digest("hex").slice(0, 16);
|
|
4655
|
+
}
|
|
4656
|
+
function matchedScope(row, ctx) {
|
|
4657
|
+
const s = row.scope;
|
|
4658
|
+
const matched = [];
|
|
4659
|
+
if (s.kind !== void 0) {
|
|
4660
|
+
if (ctx.kind !== s.kind) return null;
|
|
4661
|
+
matched.push(`kind=${s.kind}`);
|
|
4662
|
+
}
|
|
4663
|
+
if (s.canvas !== void 0) {
|
|
4664
|
+
if (ctx.canvas !== s.canvas) return null;
|
|
4665
|
+
matched.push(`canvas=${s.canvas}`);
|
|
4666
|
+
}
|
|
4667
|
+
if (s.preset !== void 0) {
|
|
4668
|
+
if (ctx.preset !== s.preset) return null;
|
|
4669
|
+
matched.push(`preset=${s.preset}`);
|
|
4670
|
+
}
|
|
4671
|
+
return matched;
|
|
4672
|
+
}
|
|
4673
|
+
function selectCriteria(bank, ctx) {
|
|
4674
|
+
const criteriaSetId = criteriaSetIdFor(bank.version, ctx);
|
|
4675
|
+
const applies = bank.applies;
|
|
4676
|
+
if (applies?.canvases !== void 0 && !applies.canvases.includes(ctx.canvas)) return { criteriaSetId, selection: [] };
|
|
4677
|
+
if (applies?.kind !== void 0 && ctx.kind !== void 0 && !applies.kind.includes("*") && !applies.kind.includes(ctx.kind)) {
|
|
4678
|
+
return { criteriaSetId, selection: [] };
|
|
4679
|
+
}
|
|
4680
|
+
const selection = [];
|
|
4681
|
+
for (const row of bankRows(bank)) {
|
|
4682
|
+
const matched = matchedScope(row, ctx);
|
|
4683
|
+
if (matched === null) continue;
|
|
4684
|
+
selection.push(matched.length === 0 ? { id: row.id, source: "static", reason: "static" } : { id: row.id, source: "context", reason: `context: ${matched.join(", ")}` });
|
|
4685
|
+
}
|
|
4686
|
+
return { criteriaSetId, selection };
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4689
|
+
// src/evaluation/criteria/resolve.ts
|
|
4690
|
+
var INSTRUMENT_BY_ID = {
|
|
4691
|
+
"comp.fit": "fit",
|
|
4692
|
+
"floor.fit": "fit",
|
|
4693
|
+
"finish.canvas.inhabited": "fill"
|
|
4694
|
+
};
|
|
4695
|
+
function majority(id, answers) {
|
|
4696
|
+
const votes = answers.flatMap((a) => a.filter((x) => x.id === id));
|
|
4697
|
+
if (votes.length === 0) return { verdict: "n/a", evidence: "not answered" };
|
|
4698
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4699
|
+
for (const v of votes) counts.set(v.verdict, (counts.get(v.verdict) ?? 0) + 1);
|
|
4700
|
+
const ranked = [...counts.entries()].sort((a, b) => b[1] - a[1]);
|
|
4701
|
+
const top = ranked[0];
|
|
4702
|
+
if (ranked.length > 1 && ranked[1][1] === top[1]) return { verdict: "n/a", evidence: "no majority" };
|
|
4703
|
+
const evidence = votes.find((v) => v.verdict === top[0])?.evidence ?? "";
|
|
4704
|
+
return { verdict: top[0], evidence };
|
|
4705
|
+
}
|
|
4706
|
+
function instrumentRead(kind, m) {
|
|
4707
|
+
if (kind === "fit") {
|
|
4708
|
+
if (m.contentHeight === null) return { verdict: "n/a", evidence: "content height unmeasurable" };
|
|
4709
|
+
return { verdict: m.overflow ? "fail" : "pass", evidence: `content ${m.contentHeight}px against a ${m.viewportHeight}px box` };
|
|
4710
|
+
}
|
|
4711
|
+
if (m.inkRatio === null) return { verdict: "n/a", evidence: "capture unreadable" };
|
|
4712
|
+
return { verdict: "n/a", evidence: `fill ${m.inkRatio.toFixed(3)} reported; no floor ruled` };
|
|
4713
|
+
}
|
|
4714
|
+
function readRow(row, answers, m) {
|
|
4715
|
+
if (row.evaluation === "judge") return majority(row.id, answers);
|
|
4716
|
+
if (row.evaluation === "human") return { verdict: "n/a", evidence: `reader's column (status ${row.status})` };
|
|
4717
|
+
if (row.status !== "live") return { verdict: "n/a", evidence: `not implemented (status ${row.status})` };
|
|
4718
|
+
const kind = INSTRUMENT_BY_ID[row.id];
|
|
4719
|
+
return kind === void 0 ? { verdict: "n/a", evidence: "instrument not wired (status live, no binding)" } : instrumentRead(kind, m);
|
|
4720
|
+
}
|
|
4721
|
+
function resolveCriteriaBlock(args) {
|
|
4722
|
+
const byId = new Map(bankRows(args.bank).map((r) => [r.id, r]));
|
|
4723
|
+
const verdicts = [];
|
|
4724
|
+
for (const s of args.selected.selection) {
|
|
4725
|
+
const row = byId.get(s.id);
|
|
4726
|
+
if (row === void 0) continue;
|
|
4727
|
+
const read = readRow(row, args.answers, args.measurements);
|
|
4728
|
+
verdicts.push({ id: row.id, severity: row.severity, method: row.evaluation, status: row.status, source: s.source, verdict: read.verdict, evidence: read.evidence });
|
|
4729
|
+
}
|
|
4730
|
+
return {
|
|
4731
|
+
criteriaSetId: args.selected.criteriaSetId,
|
|
4732
|
+
bankVersion: args.bank.version,
|
|
4733
|
+
selectorVersion: CRITERIA_SELECTOR_VERSION,
|
|
4734
|
+
context: args.context,
|
|
4735
|
+
selection: args.selected.selection,
|
|
4736
|
+
verdicts
|
|
4737
|
+
};
|
|
4738
|
+
}
|
|
4739
|
+
var CRITERIA_PROPS_MAX_CHARS = 2e3;
|
|
4740
|
+
function criteriaFrameLine(frame) {
|
|
4741
|
+
const box = `${frame.width}\xD7${frame.height}`;
|
|
4742
|
+
if (frame.canvas === "xs-chat-card") {
|
|
4743
|
+
const framedBy = frame.hostLabel !== void 0 ? `inside the host's own frame (${frame.hostLabel}), on the host's ground` : "inside a generic stand-in for the host's frame \u2014 the card's surface with a thin ring and rounded corners, on the page ground";
|
|
4744
|
+
return `This frame is an inline chat card captured at its natural height (at most ${box}, the host's ceiling), ${framedBy}: the host sizes the card to its content and draws that frame, so judge composition and space within the card's own extent, and read the frame and the ground round it as the host's, not the card's.`;
|
|
4745
|
+
}
|
|
4746
|
+
return `This frame is ${box}, a fixed full-screen box the card fills${frame.hostLabel !== void 0 ? ` (${frame.hostLabel})` : ""}: judge composition and space against the whole box, edge to edge.`;
|
|
4747
|
+
}
|
|
4748
|
+
function criteriaPropsSection(propsJson) {
|
|
4749
|
+
const text = propsJson.length > CRITERIA_PROPS_MAX_CHARS ? `${propsJson.slice(0, CRITERIA_PROPS_MAX_CHARS)}\u2026 (truncated)` : propsJson;
|
|
4750
|
+
return `The one input besides the frame \u2014 the props this card was rendered with. Read them ONLY for rows whose evidence names props (for example, whether a rendered string came from them); every other row is read off the frame alone:
|
|
4751
|
+
${text}
|
|
4752
|
+
`;
|
|
4753
|
+
}
|
|
4754
|
+
function buildCriteriaJudgeBlock(bank, selected, inputs = {}) {
|
|
4755
|
+
const byId = new Map(bankRows(bank).map((r) => [r.id, r]));
|
|
4756
|
+
const asked = selected.selection.map((s) => byId.get(s.id)).filter((r) => r !== void 0 && r.evaluation === "judge");
|
|
4757
|
+
if (asked.length === 0) return "";
|
|
4758
|
+
const lines = asked.map((r) => `- ${r.id} (${r.severity}): ${r.text} Evidence must name: ${r.evidence}`);
|
|
4759
|
+
return "## Criteria \u2014 answer each one from the FRAME only\nRead these off the screenshot. Never infer them from the request, the styling profile or any direction text above.\n" + (inputs.frame !== void 0 ? `${criteriaFrameLine(inputs.frame)}
|
|
4760
|
+
` : "") + (inputs.propsJson !== void 0 ? criteriaPropsSection(inputs.propsJson) : "") + `${lines.join("\n")}
|
|
4761
|
+
Add to your JSON a top-level "criteria" array with exactly these ids: [{ "id": "<id>", "verdict": "pass" | "fail" | "n/a", "evidence": "<one sentence naming the region or element you read it from>" }]. Every row here was selected as applicable to this card, so "n/a" never means "does not apply": use it ONLY when this frame cannot show the criterion \u2014 it is then recorded as not evaluated.`;
|
|
4762
|
+
}
|
|
4763
|
+
var INK_TOLERANCE = 8;
|
|
4764
|
+
var PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
4765
|
+
function decodePng(png) {
|
|
4766
|
+
const buf = Buffer.from(png.buffer, png.byteOffset, png.byteLength);
|
|
4767
|
+
if (buf.length < 8 + 25) return { reason: `not a PNG: ${buf.length} bytes` };
|
|
4768
|
+
for (let i = 0; i < PNG_SIGNATURE.length; i++) {
|
|
4769
|
+
if (buf[i] !== PNG_SIGNATURE[i]) return { reason: "not a PNG: signature mismatch" };
|
|
4770
|
+
}
|
|
4771
|
+
let width = 0;
|
|
4772
|
+
let height = 0;
|
|
4773
|
+
let channels = null;
|
|
4774
|
+
const idat = [];
|
|
4775
|
+
let offset = 8;
|
|
4776
|
+
while (offset + 8 <= buf.length) {
|
|
4777
|
+
const length = buf.readUInt32BE(offset);
|
|
4778
|
+
const type = buf.toString("latin1", offset + 4, offset + 8);
|
|
4779
|
+
const start = offset + 8;
|
|
4780
|
+
if (start + length + 4 > buf.length) return { reason: `truncated PNG: ${type} chunk runs past the end` };
|
|
4781
|
+
const data = buf.subarray(start, start + length);
|
|
4782
|
+
if (type === "IHDR") {
|
|
4783
|
+
if (length !== 13) return { reason: `malformed IHDR: ${length} bytes` };
|
|
4784
|
+
width = data.readUInt32BE(0);
|
|
4785
|
+
height = data.readUInt32BE(4);
|
|
4786
|
+
const bitDepth = data[8];
|
|
4787
|
+
const colorType = data[9];
|
|
4788
|
+
const interlace = data[12];
|
|
4789
|
+
if (bitDepth !== 8) return { reason: `unsupported PNG: bit depth ${bitDepth}` };
|
|
4790
|
+
if (interlace !== 0) return { reason: "unsupported PNG: interlaced" };
|
|
4791
|
+
if (colorType === 2) channels = 3;
|
|
4792
|
+
else if (colorType === 6) channels = 4;
|
|
4793
|
+
else return { reason: `unsupported PNG: colour type ${colorType}` };
|
|
4794
|
+
} else if (type === "IDAT") {
|
|
4795
|
+
idat.push(data);
|
|
4796
|
+
} else if (type === "IEND") {
|
|
4797
|
+
break;
|
|
4798
|
+
}
|
|
4799
|
+
offset = start + length + 4;
|
|
4800
|
+
}
|
|
4801
|
+
if (channels === null) return { reason: "malformed PNG: no IHDR" };
|
|
4802
|
+
if (width === 0 || height === 0) return { reason: `empty PNG: ${width}\xD7${height}` };
|
|
4803
|
+
if (idat.length === 0) return { reason: "malformed PNG: no IDAT" };
|
|
4804
|
+
let raw;
|
|
4805
|
+
try {
|
|
4806
|
+
raw = inflateSync(Buffer.concat(idat));
|
|
4807
|
+
} catch (error) {
|
|
4808
|
+
return { reason: `corrupt IDAT stream: ${error instanceof Error ? error.message : String(error)}` };
|
|
4809
|
+
}
|
|
4810
|
+
const stride = width * channels;
|
|
4811
|
+
if (raw.length !== (stride + 1) * height) {
|
|
4812
|
+
return { reason: `malformed PNG: ${raw.length} raw bytes for ${width}\xD7${height}\xD7${channels}` };
|
|
4813
|
+
}
|
|
4814
|
+
const pixels = new Uint8Array(stride * height);
|
|
4815
|
+
const bpp = channels;
|
|
4816
|
+
for (let y = 0; y < height; y++) {
|
|
4817
|
+
const filter = raw[y * (stride + 1)];
|
|
4818
|
+
const inRow = y * (stride + 1) + 1;
|
|
4819
|
+
const outRow = y * stride;
|
|
4820
|
+
const prevRow = outRow - stride;
|
|
4821
|
+
for (let i = 0; i < stride; i++) {
|
|
4822
|
+
const x = raw[inRow + i];
|
|
4823
|
+
const a = i >= bpp ? pixels[outRow + i - bpp] : 0;
|
|
4824
|
+
const b = y > 0 ? pixels[prevRow + i] : 0;
|
|
4825
|
+
const c = y > 0 && i >= bpp ? pixels[prevRow + i - bpp] : 0;
|
|
4826
|
+
let value;
|
|
4827
|
+
switch (filter) {
|
|
4828
|
+
case 0:
|
|
4829
|
+
value = x;
|
|
4830
|
+
break;
|
|
4831
|
+
case 1:
|
|
4832
|
+
value = x + a;
|
|
4833
|
+
break;
|
|
4834
|
+
case 2:
|
|
4835
|
+
value = x + b;
|
|
4836
|
+
break;
|
|
4837
|
+
case 3:
|
|
4838
|
+
value = x + (a + b >> 1);
|
|
4839
|
+
break;
|
|
4840
|
+
case 4: {
|
|
4841
|
+
const p = a + b - c;
|
|
4842
|
+
const pa = Math.abs(p - a);
|
|
4843
|
+
const pb = Math.abs(p - b);
|
|
4844
|
+
const pc = Math.abs(p - c);
|
|
4845
|
+
value = x + (pa <= pb && pa <= pc ? a : pb <= pc ? b : c);
|
|
4846
|
+
break;
|
|
4847
|
+
}
|
|
4848
|
+
default:
|
|
4849
|
+
return { reason: `malformed PNG: scanline filter ${filter} at row ${y}` };
|
|
4850
|
+
}
|
|
4851
|
+
pixels[outRow + i] = value & 255;
|
|
4852
|
+
}
|
|
4853
|
+
}
|
|
4854
|
+
return { width, height, channels, pixels };
|
|
4855
|
+
}
|
|
4856
|
+
function readInkExtent(png, insetPx = 0) {
|
|
4857
|
+
const image = decodePng(png);
|
|
4858
|
+
if ("reason" in image) return image;
|
|
4859
|
+
const region = { x: insetPx, y: insetPx, width: image.width - 2 * insetPx, height: image.height - 2 * insetPx };
|
|
4860
|
+
if (insetPx < 0 || region.width <= 0 || region.height <= 0) {
|
|
4861
|
+
return { reason: `an inset of ${insetPx}px leaves nothing of a ${image.width}\xD7${image.height} image` };
|
|
4862
|
+
}
|
|
4863
|
+
const { channels, pixels, width } = image;
|
|
4864
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4865
|
+
let ground = 0;
|
|
4866
|
+
let groundCount = 0;
|
|
4867
|
+
for (let y = region.y; y < region.y + region.height; y++) {
|
|
4868
|
+
for (let x = region.x; x < region.x + region.width; x++) {
|
|
4869
|
+
const i = (y * width + x) * channels;
|
|
4870
|
+
const key = pixels[i] << 16 | pixels[i + 1] << 8 | pixels[i + 2];
|
|
4871
|
+
const n = (counts.get(key) ?? 0) + 1;
|
|
4872
|
+
counts.set(key, n);
|
|
4873
|
+
if (n > groundCount) {
|
|
4874
|
+
groundCount = n;
|
|
4875
|
+
ground = key;
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
const gr = ground >> 16 & 255;
|
|
4880
|
+
const gg = ground >> 8 & 255;
|
|
4881
|
+
const gb = ground & 255;
|
|
4882
|
+
let lastInkRow = null;
|
|
4883
|
+
for (let y = region.y; y < region.y + region.height; y++) {
|
|
4884
|
+
for (let x = region.x; x < region.x + region.width; x++) {
|
|
4885
|
+
const i = (y * width + x) * channels;
|
|
4886
|
+
if (Math.abs(pixels[i] - gr) > INK_TOLERANCE || Math.abs(pixels[i + 1] - gg) > INK_TOLERANCE || Math.abs(pixels[i + 2] - gb) > INK_TOLERANCE) {
|
|
4887
|
+
lastInkRow = y - region.y;
|
|
4888
|
+
break;
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
}
|
|
4892
|
+
const ratio = lastInkRow === null ? 0 : Math.round((lastInkRow + 1) / region.height * 1e3) / 1e3;
|
|
4893
|
+
return { ratio, lastInkRow, region, ground: [gr, gg, gb] };
|
|
4894
|
+
}
|
|
4501
4895
|
function isDesignRenderingInput(relativePath) {
|
|
4502
4896
|
const rel = relativePath.split("\\").join("/");
|
|
4503
4897
|
if (rel.startsWith("__tests__/") || rel.includes("/__tests__/")) return false;
|
|
@@ -4547,7 +4941,7 @@ function resetJudgeDesignIdentityCache() {
|
|
|
4547
4941
|
function canvasFitPolicy(canvas) {
|
|
4548
4942
|
switch (canvas) {
|
|
4549
4943
|
case "xs-chat-card":
|
|
4550
|
-
return { capture: "
|
|
4944
|
+
return { capture: "natural", overflow: "fail" };
|
|
4551
4945
|
case "mobile-fullscreen-small":
|
|
4552
4946
|
return { capture: "full-page", overflow: "warn" };
|
|
4553
4947
|
default:
|
|
@@ -4555,9 +4949,65 @@ function canvasFitPolicy(canvas) {
|
|
|
4555
4949
|
}
|
|
4556
4950
|
}
|
|
4557
4951
|
var JUDGE_SCOPE_CLASS = "ggui-judge-scope";
|
|
4952
|
+
var JUDGE_PANEL_CLASS = "ggui-judge-panel";
|
|
4953
|
+
var JUDGE_INK_INSET_PX = EXPANDED_FRAME.insetPx + 24 + 1;
|
|
4558
4954
|
function canvasFit(canvas) {
|
|
4559
4955
|
return displayModeForCanvas(canvas) === "fullscreen" ? "fill" : void 0;
|
|
4560
4956
|
}
|
|
4957
|
+
var JUDGE_GROUND_MARGIN_PX = EXPANDED_FRAME.insetPx;
|
|
4958
|
+
var JUDGE_GROUND_CLASS = "ggui-judge-ground";
|
|
4959
|
+
var JUDGE_INLINE_FRAME_CLASS = "ggui-judge-inline-frame";
|
|
4960
|
+
var JUDGE_INLINE_FRAME_BORDER_PX = 1;
|
|
4961
|
+
var JUDGE_INLINE_FRAME_RADIUS_PX = 16;
|
|
4962
|
+
var JUDGE_INLINE_PAD_PX = JUDGE_GROUND_MARGIN_PX + JUDGE_INLINE_FRAME_BORDER_PX;
|
|
4963
|
+
var JUDGE_INLINE_INK_INSET_PX = JUDGE_INLINE_PAD_PX + JUDGE_INLINE_FRAME_RADIUS_PX;
|
|
4964
|
+
var HOST_COLOUR_PATTERN = /^#[0-9a-f]{6}$/;
|
|
4965
|
+
var HOST_FRAME_BOUNDS = {
|
|
4966
|
+
ringWidthPx: { min: 0, max: 8 },
|
|
4967
|
+
ringAlpha: { min: 0, max: 1 },
|
|
4968
|
+
radiusPx: { min: 0, max: 64 },
|
|
4969
|
+
minHeightPx: { min: 0, max: 2560 }
|
|
4970
|
+
};
|
|
4971
|
+
function resolvePresentation(canvas, p) {
|
|
4972
|
+
if (p === void 0) return void 0;
|
|
4973
|
+
const ignored = (reason, why) => {
|
|
4974
|
+
console.warn(`[visual-eval] host presentation for ${canvas} ignored (${reason}): ${why}`);
|
|
4975
|
+
return { status: "ignored", reason };
|
|
4976
|
+
};
|
|
4977
|
+
const inline = canvasFitPolicy(canvas).capture === "natural";
|
|
4978
|
+
if (typeof p.label !== "string" || p.label.length === 0) return ignored("label_missing", "no label");
|
|
4979
|
+
if (p.ground !== void 0 && !HOST_COLOUR_PATTERN.test(p.ground)) return ignored("color_not_hex", "ground is not #rrggbb");
|
|
4980
|
+
if (inline && p.ground !== void 0) return ignored("ground_on_inline", "the inline card's room is its frame's ground");
|
|
4981
|
+
const f = p.frame;
|
|
4982
|
+
if (f !== void 0) {
|
|
4983
|
+
if (!inline) return ignored("frame_off_inline", "a frame on a canvas that is not the inline card");
|
|
4984
|
+
if (![f.surface, f.ground, f.ring.color].every((c) => HOST_COLOUR_PATTERN.test(c))) return ignored("color_not_hex", "a frame colour is not #rrggbb");
|
|
4985
|
+
const B = HOST_FRAME_BOUNDS;
|
|
4986
|
+
const inRange = (n, b) => Number.isFinite(n) && n >= b.min && n <= b.max;
|
|
4987
|
+
if (!inRange(f.ring.widthPx, B.ringWidthPx) || !inRange(f.ring.alpha, B.ringAlpha) || !inRange(f.radiusPx, B.radiusPx)) return ignored("frame_malformed", "a frame number out of range");
|
|
4988
|
+
if (f.minHeightPx !== void 0 && !inRange(f.minHeightPx, B.minHeightPx)) return ignored("frame_malformed", "minHeightPx out of range");
|
|
4989
|
+
}
|
|
4990
|
+
return { status: "applied", applied: p };
|
|
4991
|
+
}
|
|
4992
|
+
function inlineGeometry(frame) {
|
|
4993
|
+
if (frame === void 0) return { padPx: JUDGE_INLINE_PAD_PX, inkInsetPx: JUDGE_INLINE_INK_INSET_PX, floorPx: void 0 };
|
|
4994
|
+
const padPx = JUDGE_GROUND_MARGIN_PX + frame.ring.widthPx;
|
|
4995
|
+
return { padPx, inkInsetPx: padPx + frame.radiusPx, floorPx: frame.minHeightPx };
|
|
4996
|
+
}
|
|
4997
|
+
function canvasChrome(canvas) {
|
|
4998
|
+
if (canvasFitPolicy(canvas).capture === "natural") return "ground";
|
|
4999
|
+
return canvasFit(canvas) === "fill" && canvas !== "mobile-fullscreen-small" ? "panel" : void 0;
|
|
5000
|
+
}
|
|
5001
|
+
function judgeWindow(viewport, chrome, inlinePadPx = JUDGE_INLINE_PAD_PX) {
|
|
5002
|
+
if (chrome === void 0) return viewport;
|
|
5003
|
+
const gap = 2 * (chrome === "ground" ? inlinePadPx : EXPANDED_FRAME.insetPx);
|
|
5004
|
+
return { width: viewport.width + gap, height: viewport.height + gap };
|
|
5005
|
+
}
|
|
5006
|
+
function cardHeight(documentHeight, chrome) {
|
|
5007
|
+
if (documentHeight === null || chrome === void 0 || chrome === "ground") return documentHeight;
|
|
5008
|
+
const gap = 2 * EXPANDED_FRAME.insetPx;
|
|
5009
|
+
return documentHeight < gap ? null : documentHeight - gap;
|
|
5010
|
+
}
|
|
4561
5011
|
function integerBox(viewport) {
|
|
4562
5012
|
return { width: Math.round(viewport.width), height: Math.round(viewport.height) };
|
|
4563
5013
|
}
|
|
@@ -4576,6 +5026,24 @@ function canvasOverflowIssue(canvas, viewport, contentHeight, verdict) {
|
|
|
4576
5026
|
fix: "Fit the composition to the canvas: fewer and shorter sections, one compact row of chips, no hero taller than a third of the box, no fixed min-heights or tall paddings \u2014 measure against the canvas, not the page."
|
|
4577
5027
|
};
|
|
4578
5028
|
}
|
|
5029
|
+
function canvasOverflowXIssue(canvas, viewport, hiddenPx, verdict) {
|
|
5030
|
+
return {
|
|
5031
|
+
dimension: "canvas-overflow-x",
|
|
5032
|
+
severity: verdict === "fail" ? "critical" : "major",
|
|
5033
|
+
description: `Content runs ${hiddenPx}px past the ${canvas} card's ${viewport.width}px width \u2014 the card's frame clips it, so a visitor sees a cut-off row or a sideways scroll.`,
|
|
5034
|
+
fix: "Keep every row within the card: wrap long text (overflow-wrap: anywhere), let a wide table or code block scroll inside its own container, and give no child a fixed width wider than the card."
|
|
5035
|
+
};
|
|
5036
|
+
}
|
|
5037
|
+
function canvasBlankIssue(canvas, viewport) {
|
|
5038
|
+
const classBox = CANVAS_VIEWPORTS[canvas];
|
|
5039
|
+
const box = isDeclaredViewport(canvas, viewport) ? `declared ${viewport.width}\xD7${viewport.height}; class box ${classBox.width}\xD7${classBox.height}` : `${viewport.width}\xD7${viewport.height}`;
|
|
5040
|
+
return {
|
|
5041
|
+
dimension: "canvas-blank",
|
|
5042
|
+
severity: "critical",
|
|
5043
|
+
description: `Nothing painted on the ${canvas} canvas (${box}): the capture is one flat colour \u2014 the component mounted and rendered no pixels.`,
|
|
5044
|
+
fix: "Return and paint the component tree: a root that renders nothing is a blank, not an empty state. Check that the default export returns its JSX, that no early return yields null on the sample props, and that the first paint shows visible content."
|
|
5045
|
+
};
|
|
5046
|
+
}
|
|
4579
5047
|
function resolveUiGenPackageDir() {
|
|
4580
5048
|
const selfDir = dirname(fileURLToPath(import.meta.url));
|
|
4581
5049
|
return resolve(selfDir, "..", "..");
|
|
@@ -4691,7 +5159,16 @@ try {
|
|
|
4691
5159
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
4692
5160
|
}
|
|
4693
5161
|
}
|
|
4694
|
-
function
|
|
5162
|
+
function inlineFrameDecls(frame) {
|
|
5163
|
+
if (frame === void 0) {
|
|
5164
|
+
return `background: var(--ggui-color-container); border: ${JUDGE_INLINE_FRAME_BORDER_PX}px solid color-mix(in srgb, var(--ggui-color-onContainer) 8%, transparent); border-radius: ${JUDGE_INLINE_FRAME_RADIUS_PX}px; overflow: hidden;`;
|
|
5165
|
+
}
|
|
5166
|
+
const alphaPct = Math.round(frame.ring.alpha * 1e3) / 10;
|
|
5167
|
+
return `background: ${frame.surface}; border: ${frame.ring.widthPx}px solid color-mix(in srgb, ${frame.ring.color} ${alphaPct}%, transparent); border-radius: ${frame.radiusPx}px; overflow: hidden;`;
|
|
5168
|
+
}
|
|
5169
|
+
function buildRenderHTML(bundledCode, cssTokens, fit, chrome, presentation) {
|
|
5170
|
+
if (chrome === "panel" && fit !== "fill") throw new Error("the judge's panel frames a fill canvas only (ggui#1083 cut 3)");
|
|
5171
|
+
if (chrome === "ground" && fit === "fill") throw new Error("the host ground frames an inline card only \u2014 a fill canvas has no natural height (ggui#1475)");
|
|
4695
5172
|
const tokens = cssTokens ?? getCssTokens();
|
|
4696
5173
|
return `<!DOCTYPE html>
|
|
4697
5174
|
<html>
|
|
@@ -4706,15 +5183,16 @@ function buildRenderHTML(bundledCode, cssTokens, fit) {
|
|
|
4706
5183
|
/* ggui#1083 \u2014 the host stand-in (neutral-50) under the theme's scrim (its tint at
|
|
4707
5184
|
its opacity): the ground a frosted host puts under the card, so the judge scores
|
|
4708
5185
|
the card where the visitor sees it. */
|
|
4709
|
-
background:
|
|
5186
|
+
${presentation?.frame !== void 0 ? `background: ${presentation.frame.ground};` : presentation?.ground !== void 0 ? `background: ${presentation.ground};` : expandedFrameScrimDecls()}
|
|
4710
5187
|
color: var(--ggui-color-neutral-900, #111827);
|
|
4711
5188
|
}
|
|
4712
5189
|
.error { color: #dc2626; padding: 16px; font-family: monospace; white-space: pre-wrap; }
|
|
4713
|
-
${fit === "fill" ? fillFitRule(JUDGE_SCOPE_CLASS) : ""}
|
|
5190
|
+
${fit === "fill" ? fillFitRule(JUDGE_SCOPE_CLASS) + (chrome === "panel" ? expandedFramePanelRule(JUDGE_PANEL_CLASS, JUDGE_SCOPE_CLASS) : "") : ""}
|
|
5191
|
+
${chrome === "ground" ? `.${JUDGE_GROUND_CLASS} { padding: ${JUDGE_GROUND_MARGIN_PX}px; } .${JUDGE_INLINE_FRAME_CLASS} { ${inlineFrameDecls(presentation?.frame)} }` : ""}
|
|
4714
5192
|
</style>
|
|
4715
5193
|
</head>
|
|
4716
5194
|
<body>
|
|
4717
|
-
<div id="root"${fit === "fill" ? ` class="${JUDGE_SCOPE_CLASS}"` : ""}></div>
|
|
5195
|
+
${chrome === "panel" ? `<div class="${JUDGE_PANEL_CLASS}">` : chrome === "ground" ? `<div class="${JUDGE_GROUND_CLASS}"><div class="${JUDGE_INLINE_FRAME_CLASS}">` : ""}<div id="root"${fit === "fill" ? ` class="${JUDGE_SCOPE_CLASS}"` : ""}></div>${chrome === "panel" ? "</div>" : chrome === "ground" ? "</div></div>" : ""}
|
|
4718
5196
|
<script type="importmap">
|
|
4719
5197
|
{
|
|
4720
5198
|
"imports": {
|
|
@@ -4762,16 +5240,27 @@ async function launchWithPuppeteer(options) {
|
|
|
4762
5240
|
return puppeteer.default.launch(options);
|
|
4763
5241
|
}
|
|
4764
5242
|
var CONTENT_HEIGHT_EXPRESSION = "Math.max(document.documentElement.scrollHeight, document.body ? document.body.scrollHeight : 0)";
|
|
4765
|
-
|
|
5243
|
+
function cardHeightExpression(padPx) {
|
|
5244
|
+
return `(() => { const el = document.documentElement; const prev = el.style.height; el.style.height = 'max-content'; const h = Math.ceil(el.getBoundingClientRect().height); el.style.height = prev; return h - ${2 * padPx}; })()`;
|
|
5245
|
+
}
|
|
5246
|
+
var CARD_HEIGHT_EXPRESSION = cardHeightExpression(JUDGE_INLINE_PAD_PX);
|
|
5247
|
+
var OVERFLOW_X_ROUNDING_PX = 1;
|
|
5248
|
+
var CARD_OVERFLOW_X_EXPRESSION = "(() => { const r = document.getElementById('root'); return r ? Math.max(0, Math.ceil(r.scrollWidth - r.clientWidth)) : null; })()";
|
|
5249
|
+
function naturalClip(window, cardHeightPx, padPx = JUDGE_INLINE_PAD_PX) {
|
|
5250
|
+
const m = padPx;
|
|
5251
|
+
const box = window.height - 2 * m;
|
|
5252
|
+
return { x: 0, y: 0, width: window.width, height: Math.max(1, Math.min(Math.ceil(cardHeightPx), box)) + 2 * m };
|
|
5253
|
+
}
|
|
5254
|
+
async function measureContentHeight(page, expression = CONTENT_HEIGHT_EXPRESSION) {
|
|
4766
5255
|
try {
|
|
4767
|
-
const h = await page.evaluate(
|
|
5256
|
+
const h = await page.evaluate(expression);
|
|
4768
5257
|
return typeof h === "number" && Number.isFinite(h) ? Math.round(h) : null;
|
|
4769
5258
|
} catch (e) {
|
|
4770
5259
|
console.warn(`[visual-eval] content height unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
4771
5260
|
return null;
|
|
4772
5261
|
}
|
|
4773
5262
|
}
|
|
4774
|
-
async function captureScreenshotDetailed(html, viewport = DEFAULT_SCREENSHOT_VIEWPORT, deps = {}, capture = "full-page") {
|
|
5263
|
+
async function captureScreenshotDetailed(html, viewport = DEFAULT_SCREENSHOT_VIEWPORT, deps = {}, capture = "full-page", natural = { padPx: JUDGE_INLINE_PAD_PX }) {
|
|
4775
5264
|
try {
|
|
4776
5265
|
const launchOptions = await resolveLaunchOptions(viewport, deps);
|
|
4777
5266
|
const browser = await (deps.launch ?? launchWithPuppeteer)(launchOptions);
|
|
@@ -4783,6 +5272,18 @@ async function captureScreenshotDetailed(html, viewport = DEFAULT_SCREENSHOT_VIE
|
|
|
4783
5272
|
await page.waitForSelector("#root > *", { timeout: 1e4 }).catch(() => {
|
|
4784
5273
|
});
|
|
4785
5274
|
await new Promise((r) => setTimeout(r, deps.settleMs ?? 1e3));
|
|
5275
|
+
if (capture === "natural") {
|
|
5276
|
+
const cardHeightPx = await measureContentHeight(page, natural.padPx === JUDGE_INLINE_PAD_PX ? CARD_HEIGHT_EXPRESSION : cardHeightExpression(natural.padPx));
|
|
5277
|
+
const overflowX = await measureContentHeight(page, CARD_OVERFLOW_X_EXPRESSION);
|
|
5278
|
+
const floorPx = natural.floorPx;
|
|
5279
|
+
if (floorPx !== void 0 && cardHeightPx !== null && cardHeightPx < floorPx) {
|
|
5280
|
+
await page.evaluate(`(() => { const f = document.querySelector('.${JUDGE_INLINE_FRAME_CLASS}'); if (f) f.style.minHeight = '${floorPx}px'; })()`);
|
|
5281
|
+
}
|
|
5282
|
+
const framed = cardHeightPx === null ? viewport.height - 2 * natural.padPx : floorPx !== void 0 ? Math.max(cardHeightPx, floorPx) : cardHeightPx;
|
|
5283
|
+
const clip = naturalClip(viewport, framed, natural.padPx);
|
|
5284
|
+
const screenshot2 = await page.screenshot({ type: "png", fullPage: false, clip });
|
|
5285
|
+
return { png: Buffer.from(screenshot2), contentHeight: cardHeightPx, overflowX };
|
|
5286
|
+
}
|
|
4786
5287
|
const contentHeight = await measureContentHeight(page);
|
|
4787
5288
|
const screenshot = await page.screenshot({ type: "png", fullPage: capture === "full-page" });
|
|
4788
5289
|
return { png: Buffer.from(screenshot), contentHeight };
|
|
@@ -4841,12 +5342,14 @@ Respond with ONLY a JSON object (no markdown, no explanation):
|
|
|
4841
5342
|
var VISUAL_JUDGE_MAX_OUTPUT_TOKENS = 4096;
|
|
4842
5343
|
var VISUAL_JUDGE_PROMPT_VERSION = "v2-bounded-issues";
|
|
4843
5344
|
var VISUAL_JUDGE_PROMPT_DIGEST = createHash("sha256").update(VISUAL_EVAL_PROMPT, "utf8").digest("hex");
|
|
4844
|
-
async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt, profileBlock = "") {
|
|
5345
|
+
async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt, profileBlock = "", criteriaBlock = "") {
|
|
4845
5346
|
const userPrompt = `## Original Request
|
|
4846
5347
|
${originalPrompt}
|
|
4847
5348
|
|
|
4848
5349
|
` + (profileBlock.length > 0 ? `${profileBlock}
|
|
4849
5350
|
|
|
5351
|
+
` : "") + (criteriaBlock.length > 0 ? `${criteriaBlock}
|
|
5352
|
+
|
|
4850
5353
|
` : "") + "Evaluate the screenshot of the generated component.";
|
|
4851
5354
|
const agent = createVisionAgent({
|
|
4852
5355
|
provider: config.provider === "claude" ? "anthropic" : config.provider,
|
|
@@ -4861,20 +5364,70 @@ ${originalPrompt}
|
|
|
4861
5364
|
VISUAL_JUDGE_MAX_OUTPUT_TOKENS
|
|
4862
5365
|
);
|
|
4863
5366
|
}
|
|
4864
|
-
|
|
5367
|
+
function readInk(png, chrome, canvas, inlineInkInsetPx = JUDGE_INLINE_INK_INSET_PX) {
|
|
5368
|
+
if (png === null) return null;
|
|
5369
|
+
const ink = readInkExtent(png, chrome === "panel" ? JUDGE_INK_INSET_PX : chrome === "ground" ? inlineInkInsetPx : 0);
|
|
5370
|
+
if ("reason" in ink) {
|
|
5371
|
+
console.warn(`[visual-eval] ink extent unreadable at canvas ${canvas}: ${ink.reason}`);
|
|
5372
|
+
return null;
|
|
5373
|
+
}
|
|
5374
|
+
return ink;
|
|
5375
|
+
}
|
|
5376
|
+
function blankVerdict(canvas, frame) {
|
|
5377
|
+
if (frame.chrome === "ground" && frame.attempt.contentHeight !== null && frame.attempt.contentHeight <= 0) {
|
|
5378
|
+
return canvasBlankIssue(canvas, frame.viewport);
|
|
5379
|
+
}
|
|
5380
|
+
return frame.ink !== null && frame.ink.lastInkRow === null ? canvasBlankIssue(canvas, frame.viewport) : null;
|
|
5381
|
+
}
|
|
5382
|
+
async function frameCanvas(bundledCode, context, html, canvas, canvasViewports, deps, hostPresentations) {
|
|
4865
5383
|
const declaredBox = canvasViewports?.[canvas];
|
|
4866
5384
|
const viewport = declaredBox !== void 0 ? integerBox(declaredBox) : CANVAS_VIEWPORTS[canvas];
|
|
4867
5385
|
const policy = canvasFitPolicy(canvas);
|
|
4868
5386
|
const fit = canvasFit(canvas);
|
|
4869
|
-
const
|
|
4870
|
-
const
|
|
4871
|
-
|
|
5387
|
+
const chrome = canvasChrome(canvas);
|
|
5388
|
+
const outcome = resolvePresentation(canvas, hostPresentations?.[canvas]);
|
|
5389
|
+
const presentation = outcome?.status === "applied" ? outcome.applied : void 0;
|
|
5390
|
+
const geometry = inlineGeometry(presentation?.frame);
|
|
5391
|
+
const canvasHtml = fit !== void 0 || chrome !== void 0 ? buildRenderHTML(bundledCode, context.cssTokens, fit, chrome, presentation) : html;
|
|
5392
|
+
const captured = await captureScreenshotDetailed(canvasHtml, judgeWindow(viewport, chrome, geometry.padPx), deps, policy.capture, {
|
|
5393
|
+
padPx: geometry.padPx,
|
|
5394
|
+
...geometry.floorPx !== void 0 ? { floorPx: geometry.floorPx } : {}
|
|
5395
|
+
});
|
|
5396
|
+
const attempt = { ...captured, contentHeight: cardHeight(captured.contentHeight, chrome) };
|
|
5397
|
+
return {
|
|
5398
|
+
viewport,
|
|
5399
|
+
policy,
|
|
5400
|
+
fit,
|
|
5401
|
+
chrome,
|
|
5402
|
+
declared: declaredBox !== void 0,
|
|
5403
|
+
attempt,
|
|
5404
|
+
ink: readInk(captured.png, chrome, canvas, geometry.inkInsetPx),
|
|
5405
|
+
...outcome !== void 0 ? { presentation: outcome } : {}
|
|
5406
|
+
};
|
|
4872
5407
|
}
|
|
4873
5408
|
function fitVerdict(canvas, frame) {
|
|
4874
5409
|
const contentHeight = frame.attempt.contentHeight;
|
|
4875
5410
|
if (contentHeight === null || contentHeight <= frame.viewport.height || frame.policy.overflow === "none") return null;
|
|
4876
5411
|
return canvasOverflowIssue(canvas, frame.viewport, contentHeight, frame.policy.overflow);
|
|
4877
5412
|
}
|
|
5413
|
+
function overflowXVerdict(canvas, frame) {
|
|
5414
|
+
const px = frame.attempt.overflowX;
|
|
5415
|
+
if (px === void 0 || px === null || px <= OVERFLOW_X_ROUNDING_PX || frame.policy.overflow === "none") return null;
|
|
5416
|
+
return canvasOverflowXIssue(canvas, frame.viewport, px, frame.policy.overflow);
|
|
5417
|
+
}
|
|
5418
|
+
async function judgeFrame(judge, config, model, png, originalPrompt, profileBlock, canvas, k, criteriaBlock) {
|
|
5419
|
+
const [scoring, criteria] = await Promise.all([
|
|
5420
|
+
Promise.all(Array.from({ length: k }, () => judgeAndParse(judge, config, model, png, originalPrompt, profileBlock, canvas))),
|
|
5421
|
+
criteriaBlock.length > 0 ? judgeAndParse(judge, config, model, png, originalPrompt, profileBlock, canvas, criteriaBlock) : void 0
|
|
5422
|
+
]);
|
|
5423
|
+
return { scoring, criteria };
|
|
5424
|
+
}
|
|
5425
|
+
function criteriaCallTokens(criteria) {
|
|
5426
|
+
return criteria?.kind === "ok" ? { inputTokens: criteria.inputTokens, outputTokens: criteria.outputTokens } : void 0;
|
|
5427
|
+
}
|
|
5428
|
+
function criteriaReads(criteria) {
|
|
5429
|
+
return criteria?.kind === "ok" ? [criteria.result.criteriaAnswers ?? []] : [];
|
|
5430
|
+
}
|
|
4878
5431
|
function judgeCountFor(config, canvas) {
|
|
4879
5432
|
const k = Math.max(1, Math.floor(config.judgeK ?? 1));
|
|
4880
5433
|
if (k === 1) return 1;
|
|
@@ -4890,10 +5443,10 @@ function populationSigma(values) {
|
|
|
4890
5443
|
const variance = values.reduce((a, b) => a + (b - mean) ** 2, 0) / values.length;
|
|
4891
5444
|
return Math.round(Math.sqrt(variance) * 10) / 10;
|
|
4892
5445
|
}
|
|
4893
|
-
async function judgeAndParse(judge, config, model, screenshot, originalPrompt, profileBlock, canvas) {
|
|
5446
|
+
async function judgeAndParse(judge, config, model, screenshot, originalPrompt, profileBlock, canvas, criteriaBlock = "") {
|
|
4894
5447
|
let firstReason;
|
|
4895
5448
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
4896
|
-
const response = await judge(config, model, VISUAL_EVAL_PROMPT, screenshot, originalPrompt, profileBlock);
|
|
5449
|
+
const response = await judge(config, model, VISUAL_EVAL_PROMPT, screenshot, originalPrompt, profileBlock, criteriaBlock);
|
|
4897
5450
|
try {
|
|
4898
5451
|
const result = parseVisualResponse(response.text, config.passThreshold);
|
|
4899
5452
|
return { kind: "ok", result, inputTokens: response.inputTokens, outputTokens: response.outputTokens };
|
|
@@ -4942,7 +5495,7 @@ async function runVisualEvaluationDetailed(context, config, deps = {}) {
|
|
|
4942
5495
|
const perCanvas = [];
|
|
4943
5496
|
const perCanvasResults = [];
|
|
4944
5497
|
for (const canvas of config.canvases) {
|
|
4945
|
-
const frame = await frameCanvas(bundledCode, context, html, canvas, config.canvasViewports, deps);
|
|
5498
|
+
const frame = await frameCanvas(bundledCode, context, html, canvas, config.canvasViewports, deps, config.hostPresentations);
|
|
4946
5499
|
const { viewport, policy, fit, attempt: attempt2 } = frame;
|
|
4947
5500
|
const screenshot2 = attempt2.png;
|
|
4948
5501
|
if (!screenshot2) {
|
|
@@ -4952,8 +5505,22 @@ async function runVisualEvaluationDetailed(context, config, deps = {}) {
|
|
|
4952
5505
|
}
|
|
4953
5506
|
const k = judgeCountFor(config, canvas);
|
|
4954
5507
|
const profileBlock = buildStylingProfileJudgeBlock(context.profile);
|
|
4955
|
-
const
|
|
4956
|
-
|
|
5508
|
+
const criteriaContext = context.criteria !== void 0 ? { ...context.criteria.context, canvas } : void 0;
|
|
5509
|
+
const criteriaSelected = context.criteria !== void 0 && criteriaContext !== void 0 ? selectCriteria(context.criteria.bank, criteriaContext) : void 0;
|
|
5510
|
+
const criteriaBlock = context.criteria !== void 0 && criteriaSelected !== void 0 ? buildCriteriaJudgeBlock(context.criteria.bank, criteriaSelected, {
|
|
5511
|
+
frame: { canvas, width: viewport.width, height: viewport.height, ...frame.presentation?.status === "applied" ? { hostLabel: frame.presentation.applied.label } : {} },
|
|
5512
|
+
...config.sampleProps !== void 0 ? { propsJson: JSON.stringify(config.sampleProps) } : {}
|
|
5513
|
+
}) : "";
|
|
5514
|
+
const { scoring: answers, criteria: criteriaAnswer } = await judgeFrame(
|
|
5515
|
+
judge,
|
|
5516
|
+
config,
|
|
5517
|
+
model,
|
|
5518
|
+
screenshot2,
|
|
5519
|
+
context.originalPrompt,
|
|
5520
|
+
profileBlock,
|
|
5521
|
+
canvas,
|
|
5522
|
+
k,
|
|
5523
|
+
criteriaBlock
|
|
4957
5524
|
);
|
|
4958
5525
|
const parsed = answers.filter((a) => a.kind === "ok");
|
|
4959
5526
|
if (parsed.length === 0) {
|
|
@@ -4969,6 +5536,7 @@ async function runVisualEvaluationDetailed(context, config, deps = {}) {
|
|
|
4969
5536
|
result2.inputTokens = parsed.reduce((sum, a) => sum + a.inputTokens, 0);
|
|
4970
5537
|
result2.outputTokens = parsed.reduce((sum, a) => sum + a.outputTokens, 0);
|
|
4971
5538
|
const response2 = { inputTokens: result2.inputTokens, outputTokens: result2.outputTokens };
|
|
5539
|
+
const criteriaTokens = criteriaCallTokens(criteriaAnswer);
|
|
4972
5540
|
const judgeRecord = {
|
|
4973
5541
|
k,
|
|
4974
5542
|
rule: "median",
|
|
@@ -4983,6 +5551,24 @@ async function runVisualEvaluationDetailed(context, config, deps = {}) {
|
|
|
4983
5551
|
result2.issues.push(fitIssue);
|
|
4984
5552
|
if (policy.overflow === "fail") result2.passed = false;
|
|
4985
5553
|
}
|
|
5554
|
+
const overflowXIssue = overflowXVerdict(canvas, frame);
|
|
5555
|
+
if (overflowXIssue !== null) {
|
|
5556
|
+
result2.issues.push(overflowXIssue);
|
|
5557
|
+
if (policy.overflow === "fail") result2.passed = false;
|
|
5558
|
+
}
|
|
5559
|
+
const blankIssue = blankVerdict(canvas, frame);
|
|
5560
|
+
if (blankIssue !== null) {
|
|
5561
|
+
result2.issues.push(blankIssue);
|
|
5562
|
+
result2.passed = false;
|
|
5563
|
+
}
|
|
5564
|
+
const inkRatio = frame.ink?.ratio ?? null;
|
|
5565
|
+
const criteria = context.criteria !== void 0 && criteriaContext !== void 0 && criteriaSelected !== void 0 ? resolveCriteriaBlock({
|
|
5566
|
+
bank: context.criteria.bank,
|
|
5567
|
+
context: criteriaContext,
|
|
5568
|
+
selected: criteriaSelected,
|
|
5569
|
+
answers: criteriaReads(criteriaAnswer),
|
|
5570
|
+
measurements: { overflow, contentHeight, viewportHeight: viewport.height, inkRatio }
|
|
5571
|
+
}) : void 0;
|
|
4986
5572
|
perCanvasResults.push(result2);
|
|
4987
5573
|
perCanvas.push({
|
|
4988
5574
|
canvas,
|
|
@@ -4992,12 +5578,16 @@ async function runVisualEvaluationDetailed(context, config, deps = {}) {
|
|
|
4992
5578
|
screenshotPng: screenshot2,
|
|
4993
5579
|
contentHeight,
|
|
4994
5580
|
overflow,
|
|
5581
|
+
inkRatio,
|
|
4995
5582
|
judge: judgeRecord,
|
|
4996
5583
|
...fit !== void 0 ? { fit } : {},
|
|
4997
|
-
...frame.declared ? { declared: true } : {}
|
|
5584
|
+
...frame.declared ? { declared: true } : {},
|
|
5585
|
+
...criteria !== void 0 ? { criteria } : {},
|
|
5586
|
+
...criteriaTokens !== void 0 ? { criteriaTokens } : {},
|
|
5587
|
+
...frame.presentation !== void 0 ? { presentation: frame.presentation } : {}
|
|
4998
5588
|
});
|
|
4999
5589
|
console.log(
|
|
5000
|
-
`[visual-eval] canvas=${canvas} ${viewport.width}\xD7${viewport.height} score=${result2.finalScore}${k > 1 ? ` (median of ${judgeRecord.samples.length}/${k}: ${judgeRecord.samples.join(",")} \u03C3=${judgeRecord.sigma})` : ""} ${result2.passed ? "pass" : "FAIL"} | content=${contentHeight ?? "?"}px${overflow ? ` OVERFLOW (${policy.overflow})` : ""} | in=${response2.inputTokens} out=${response2.outputTokens}`
|
|
5590
|
+
`[visual-eval] canvas=${canvas} ${viewport.width}\xD7${viewport.height} score=${result2.finalScore}${k > 1 ? ` (median of ${judgeRecord.samples.length}/${k}: ${judgeRecord.samples.join(",")} \u03C3=${judgeRecord.sigma})` : ""} ${result2.passed ? "pass" : "FAIL"} | content=${contentHeight ?? "?"}px${overflow ? ` OVERFLOW (${policy.overflow})` : ""} ink=${inkRatio ?? "?"}${blankIssue !== null ? " BLANK" : ""} | in=${response2.inputTokens} out=${response2.outputTokens}` + (criteriaTokens !== void 0 ? ` | criteria in=${criteriaTokens.inputTokens} out=${criteriaTokens.outputTokens}` : "")
|
|
5001
5591
|
);
|
|
5002
5592
|
}
|
|
5003
5593
|
const aggregate = aggregateCanvasResults(perCanvas, perCanvasResults);
|
|
@@ -5058,6 +5648,7 @@ function aggregateCanvasResults(perCanvas, results) {
|
|
|
5058
5648
|
inputTokens += r.inputTokens ?? 0;
|
|
5059
5649
|
outputTokens += r.outputTokens ?? 0;
|
|
5060
5650
|
});
|
|
5651
|
+
const criteriaSpent = perCanvas.flatMap((c) => c.criteriaTokens !== void 0 ? [c.criteriaTokens] : []);
|
|
5061
5652
|
return {
|
|
5062
5653
|
passed: perCanvas.every((c) => c.passed),
|
|
5063
5654
|
finalScore: mean((r) => r.finalScore),
|
|
@@ -5066,6 +5657,12 @@ function aggregateCanvasResults(perCanvas, results) {
|
|
|
5066
5657
|
...critiques.length > 0 ? { critique: critiques.join("\n") } : {},
|
|
5067
5658
|
inputTokens,
|
|
5068
5659
|
outputTokens,
|
|
5660
|
+
...criteriaSpent.length > 0 ? {
|
|
5661
|
+
criteriaTokens: {
|
|
5662
|
+
inputTokens: criteriaSpent.reduce((sum, t) => sum + t.inputTokens, 0),
|
|
5663
|
+
outputTokens: criteriaSpent.reduce((sum, t) => sum + t.outputTokens, 0)
|
|
5664
|
+
}
|
|
5665
|
+
} : {},
|
|
5069
5666
|
canvases: [...perCanvas]
|
|
5070
5667
|
};
|
|
5071
5668
|
}
|
|
@@ -5078,9 +5675,17 @@ function summarizeVisualResult(result) {
|
|
|
5078
5675
|
passed: c.passed,
|
|
5079
5676
|
contentHeight: c.contentHeight,
|
|
5080
5677
|
overflow: c.overflow,
|
|
5678
|
+
inkRatio: c.inkRatio,
|
|
5081
5679
|
judge: c.judge,
|
|
5082
|
-
...c.fit !== void 0 ? { fit: c.fit } : {}
|
|
5680
|
+
...c.fit !== void 0 ? { fit: c.fit } : {},
|
|
5681
|
+
...c.criteria !== void 0 ? { criteria: c.criteria } : {},
|
|
5682
|
+
...c.presentation !== void 0 ? { presentation: c.presentation } : {}
|
|
5083
5683
|
}));
|
|
5684
|
+
const blocks = result.canvases.map((c) => c.criteria).filter((b) => b !== void 0);
|
|
5685
|
+
const idsWhere = (severity, verdict) => [
|
|
5686
|
+
...new Set(blocks.flatMap((b) => b.verdicts.filter((v) => v.severity === severity && v.verdict === verdict).map((v) => v.id)))
|
|
5687
|
+
];
|
|
5688
|
+
const rollup = blocks.length > 0 ? { mustFailed: idsWhere("must", "fail"), shouldFailed: idsWhere("should", "fail"), mustNa: idsWhere("must", "n/a") } : void 0;
|
|
5084
5689
|
const fitCanvas = result.canvases.find((c) => canvasFitPolicy(c.canvas).overflow === "fail" && c.contentHeight !== null);
|
|
5085
5690
|
const fit = fitCanvas !== void 0 && fitCanvas.contentHeight !== null ? {
|
|
5086
5691
|
canvas: fitCanvas.canvas,
|
|
@@ -5095,7 +5700,72 @@ function summarizeVisualResult(result) {
|
|
|
5095
5700
|
canvases,
|
|
5096
5701
|
...fit !== void 0 ? { fit } : {},
|
|
5097
5702
|
...result.design !== void 0 ? { design: result.design } : {},
|
|
5098
|
-
...result.themeMode !== void 0 ? { themeMode: result.themeMode } : {}
|
|
5703
|
+
...result.themeMode !== void 0 ? { themeMode: result.themeMode } : {},
|
|
5704
|
+
...rollup !== void 0 ? { criteria: rollup } : {}
|
|
5705
|
+
};
|
|
5706
|
+
}
|
|
5707
|
+
async function judgeStoredCapture(input, config, deps = {}) {
|
|
5708
|
+
const judge = deps.judge ?? callMultimodalLLM;
|
|
5709
|
+
const model = config.model ?? getDefaultVisualModel(config.provider);
|
|
5710
|
+
const { canvas, viewport, png } = input;
|
|
5711
|
+
const chrome = canvasChrome(canvas);
|
|
5712
|
+
const ink = readInk(png, chrome, canvas);
|
|
5713
|
+
const inkRatio = ink?.ratio ?? null;
|
|
5714
|
+
const k = judgeCountFor(config, canvas);
|
|
5715
|
+
const profileBlock = buildStylingProfileJudgeBlock(input.profile);
|
|
5716
|
+
const criteriaContext = input.criteria !== void 0 ? { ...input.criteria.context, canvas } : void 0;
|
|
5717
|
+
const criteriaSelected = input.criteria !== void 0 && criteriaContext !== void 0 ? selectCriteria(input.criteria.bank, criteriaContext) : void 0;
|
|
5718
|
+
const criteriaBlock = input.criteria !== void 0 && criteriaSelected !== void 0 ? buildCriteriaJudgeBlock(input.criteria.bank, criteriaSelected, {
|
|
5719
|
+
frame: { canvas, width: viewport.width, height: viewport.height },
|
|
5720
|
+
...input.sampleProps !== void 0 ? { propsJson: JSON.stringify(input.sampleProps) } : {}
|
|
5721
|
+
}) : "";
|
|
5722
|
+
const { scoring: answers, criteria: criteriaAnswer } = await judgeFrame(judge, config, model, png, input.originalPrompt, profileBlock, canvas, k, criteriaBlock);
|
|
5723
|
+
const parsed = answers.filter((a) => a.kind === "ok");
|
|
5724
|
+
if (parsed.length === 0) {
|
|
5725
|
+
const first = answers.find((a) => a.kind === "unparsable");
|
|
5726
|
+
return { kind: "unavailable", reason: first?.reason ?? "judge answer unparsable" };
|
|
5727
|
+
}
|
|
5728
|
+
const samples = parsed.map((a) => a.result.finalScore);
|
|
5729
|
+
const median = medianOf(samples);
|
|
5730
|
+
const result = parsed[samples.indexOf(median)].result;
|
|
5731
|
+
const issues = [...result.issues];
|
|
5732
|
+
let passed = median >= config.passThreshold;
|
|
5733
|
+
const contentHeight = input.contentHeight;
|
|
5734
|
+
const overflow = contentHeight !== null && contentHeight > viewport.height;
|
|
5735
|
+
const policy = canvasFitPolicy(canvas);
|
|
5736
|
+
if (overflow && contentHeight !== null && policy.overflow !== "none") {
|
|
5737
|
+
issues.push(canvasOverflowIssue(canvas, viewport, contentHeight, policy.overflow));
|
|
5738
|
+
if (policy.overflow === "fail") passed = false;
|
|
5739
|
+
}
|
|
5740
|
+
if (ink !== null && ink.lastInkRow === null) {
|
|
5741
|
+
issues.push(canvasBlankIssue(canvas, viewport));
|
|
5742
|
+
passed = false;
|
|
5743
|
+
}
|
|
5744
|
+
const criteria = input.criteria !== void 0 && criteriaContext !== void 0 && criteriaSelected !== void 0 ? resolveCriteriaBlock({
|
|
5745
|
+
bank: input.criteria.bank,
|
|
5746
|
+
context: criteriaContext,
|
|
5747
|
+
selected: criteriaSelected,
|
|
5748
|
+
answers: criteriaReads(criteriaAnswer),
|
|
5749
|
+
measurements: { overflow, contentHeight, viewportHeight: viewport.height, inkRatio }
|
|
5750
|
+
}) : void 0;
|
|
5751
|
+
const criteriaTokens = criteriaCallTokens(criteriaAnswer);
|
|
5752
|
+
return {
|
|
5753
|
+
kind: "ok",
|
|
5754
|
+
verdict: {
|
|
5755
|
+
canvas,
|
|
5756
|
+
viewport,
|
|
5757
|
+
score: median,
|
|
5758
|
+
passed,
|
|
5759
|
+
issues,
|
|
5760
|
+
judge: { k, rule: "median", samples, sigma: populationSigma(samples), notes: parsed.map((a) => a.result.critique ?? "") },
|
|
5761
|
+
contentHeight,
|
|
5762
|
+
overflow,
|
|
5763
|
+
inkRatio,
|
|
5764
|
+
...criteria !== void 0 ? { criteria } : {},
|
|
5765
|
+
inputTokens: parsed.reduce((sum, a) => sum + a.inputTokens, 0),
|
|
5766
|
+
outputTokens: parsed.reduce((sum, a) => sum + a.outputTokens, 0),
|
|
5767
|
+
...criteriaTokens !== void 0 ? { criteriaTokens } : {}
|
|
5768
|
+
}
|
|
5099
5769
|
};
|
|
5100
5770
|
}
|
|
5101
5771
|
function salvageTruncatedVisualAnswer(text, passThreshold) {
|
|
@@ -5165,14 +5835,28 @@ function parseVisualResponse(text, passThreshold) {
|
|
|
5165
5835
|
description: i.description || "",
|
|
5166
5836
|
fix: i.fix || ""
|
|
5167
5837
|
}));
|
|
5838
|
+
const criteriaAnswers = parseCriteriaAnswers(raw.criteria);
|
|
5168
5839
|
return {
|
|
5169
5840
|
passed: finalScore >= passThreshold,
|
|
5170
5841
|
finalScore,
|
|
5171
5842
|
dimensions,
|
|
5172
5843
|
issues,
|
|
5173
|
-
critique: raw.critique
|
|
5844
|
+
critique: raw.critique,
|
|
5845
|
+
...criteriaAnswers !== void 0 ? { criteriaAnswers } : {}
|
|
5174
5846
|
};
|
|
5175
5847
|
}
|
|
5848
|
+
function parseCriteriaAnswers(raw) {
|
|
5849
|
+
if (!Array.isArray(raw)) return void 0;
|
|
5850
|
+
const out = [];
|
|
5851
|
+
for (const entry of raw) {
|
|
5852
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
5853
|
+
const o = entry;
|
|
5854
|
+
if (typeof o.id !== "string") continue;
|
|
5855
|
+
if (o.verdict !== "pass" && o.verdict !== "fail" && o.verdict !== "n/a") continue;
|
|
5856
|
+
out.push({ id: o.id, verdict: o.verdict, evidence: typeof o.evidence === "string" ? o.evidence : "" });
|
|
5857
|
+
}
|
|
5858
|
+
return out;
|
|
5859
|
+
}
|
|
5176
5860
|
function getDefaultVisualModel(provider) {
|
|
5177
5861
|
switch (provider) {
|
|
5178
5862
|
case "claude":
|
|
@@ -5204,11 +5888,43 @@ function toEvalIssue(issue) {
|
|
|
5204
5888
|
fix: issue.fix || ""
|
|
5205
5889
|
};
|
|
5206
5890
|
}
|
|
5207
|
-
function cssTokensForAppTheme(theme, mode = "light") {
|
|
5208
|
-
if (theme === void 0) return getCssTokens(mode);
|
|
5209
|
-
return composeThemeCss({
|
|
5891
|
+
function cssTokensForAppTheme(theme, mode = "light", themeId) {
|
|
5892
|
+
if (theme === void 0 && themeId === void 0) return getCssTokens(mode);
|
|
5893
|
+
return composeThemeCss({
|
|
5894
|
+
layer: "page",
|
|
5895
|
+
mode: theme?.mode ?? mode,
|
|
5896
|
+
...themeId !== void 0 ? { themeId } : {},
|
|
5897
|
+
...theme !== void 0 ? { appTheme: theme } : {}
|
|
5898
|
+
});
|
|
5899
|
+
}
|
|
5900
|
+
|
|
5901
|
+
// src/evaluation/criteria/context.ts
|
|
5902
|
+
var PRIMARY_500 = /--ggui-color-primary-500:\s*(#[0-9a-fA-F]{3}|#[0-9a-fA-F]{6})\s*;/;
|
|
5903
|
+
var ACHROMATIC_SPREAD = 0.08;
|
|
5904
|
+
function chromaOfCssTokens(cssTokens) {
|
|
5905
|
+
const m = cssTokens?.match(PRIMARY_500);
|
|
5906
|
+
if (!m) return "unknown";
|
|
5907
|
+
const hex = m[1].slice(1);
|
|
5908
|
+
const full = hex.length === 3 ? hex.split("").map((c) => c + c).join("") : hex;
|
|
5909
|
+
const channels = [0, 2, 4].map((i) => parseInt(full.slice(i, i + 2), 16));
|
|
5910
|
+
const spread = (Math.max(...channels) - Math.min(...channels)) / 255;
|
|
5911
|
+
return spread < ACHROMATIC_SPREAD ? "achromatic" : "chromatic";
|
|
5912
|
+
}
|
|
5913
|
+
function criteriaContextFor(src) {
|
|
5914
|
+
const v = src.classification.vector;
|
|
5915
|
+
const preset = src.profile?.aesthetic?.id;
|
|
5916
|
+
return {
|
|
5917
|
+
hasActions: Object.keys(src.contract?.actionSpec ?? {}).length > 0,
|
|
5918
|
+
riskTier: src.classification.riskTier,
|
|
5919
|
+
axes: { render: v.render, state: v.state, writes: v.writes, fetch: v.fetch, layout: v.layout },
|
|
5920
|
+
chroma: chromaOfCssTokens(src.cssTokens),
|
|
5921
|
+
profilePresent: src.profile !== void 0,
|
|
5922
|
+
shell: src.shell,
|
|
5923
|
+
...src.kind !== void 0 ? { kind: src.kind } : {},
|
|
5924
|
+
...preset !== void 0 ? { preset } : {}
|
|
5925
|
+
};
|
|
5210
5926
|
}
|
|
5211
5927
|
|
|
5212
|
-
export { CANVAS_BREAKPOINTS, CANVAS_CLASSES, CANVAS_DESCRIPTORS, CANVAS_VIEWPORTS, CONTENT_HEIGHT_EXPRESSION, CRITERIA, DEFAULT_EVAL_MAX_ROUNDS, DEFAULT_QUALITY_CONFIG, LLM_EVAL_STATIC_CRITERIA, MAX_EVAL_ROUNDS_HARD_LIMIT, VISUAL_EVAL_PROMPT, VISUAL_JUDGE_MAX_OUTPUT_TOKENS, VISUAL_JUDGE_PROMPT_DIGEST, VISUAL_JUDGE_PROMPT_VERSION, buildCodingCriteriaSummary, buildFixPrompt, callMultimodalLLM, canvasDisplayModeForShell, canvasFitPolicy, canvasForRendering, canvasForViewportWidth, canvasOverflowIssue, captureScreenshot, captureScreenshotDetailed, computeEvaluationScore, createEvaluationToolsServer, criteriaFor, cssTokensForAppTheme, describeCanvas, designTreeSha256, extractCompiledCode, extractCompiledCodeFromMessage, extractEvalResult, extractSourceCode, extractSourceCodeFromMessage, extractToolResultTexts, getActionableIssues, getCriteriaByPriority, getCriterionById, getEvaluatorSystemPrompt, getLLMCriteria, isBlocked, judgeDesignIdentity, matches, notApplicableCoverage, priorityForIssue, resetJudgeDesignIdentityCache, resolveDesignPackageDir, resolveLaunchOptions, runAxisChecks, runEvaluation, runEvaluationLoop, runVisualEval, runVisualEvaluation, runVisualEvaluationDetailed, summarizeVisualResult };
|
|
5928
|
+
export { CANVAS_BREAKPOINTS, CANVAS_CLASSES, CANVAS_DESCRIPTORS, CANVAS_VIEWPORTS, CARD_HEIGHT_EXPRESSION, CONTENT_HEIGHT_EXPRESSION, CRITERIA, CRITERIA_SELECTOR_VERSION, DEFAULT_EVAL_MAX_ROUNDS, DEFAULT_QUALITY_CONFIG, HOST_COLOUR_PATTERN, HOST_FRAME_BOUNDS, INK_TOLERANCE, INSTRUMENT_BY_ID, JUDGE_GROUND_MARGIN_PX, JUDGE_INK_INSET_PX, LLM_EVAL_STATIC_CRITERIA, MAX_EVAL_ROUNDS_HARD_LIMIT, VISUAL_EVAL_PROMPT, VISUAL_JUDGE_MAX_OUTPUT_TOKENS, VISUAL_JUDGE_PROMPT_DIGEST, VISUAL_JUDGE_PROMPT_VERSION, bankRows, buildCodingCriteriaSummary, buildCriteriaJudgeBlock, buildFixPrompt, callMultimodalLLM, canonicalCriteriaContext, canvasBlankIssue, canvasDisplayModeForShell, canvasFitPolicy, canvasForRendering, canvasForViewportWidth, canvasOverflowIssue, captureScreenshot, captureScreenshotDetailed, chromaOfCssTokens, computeEvaluationScore, createEvaluationToolsServer, criteriaBankSchema, criteriaContextFor, criteriaFor, criteriaSetIdFor, cssTokensForAppTheme, decodePng, describeCanvas, designTreeSha256, extractCompiledCode, extractCompiledCodeFromMessage, extractEvalResult, extractSourceCode, extractSourceCodeFromMessage, extractToolResultTexts, getActionableIssues, getCriteriaByPriority, getCriterionById, getEvaluatorSystemPrompt, getLLMCriteria, isBlocked, judgeDesignIdentity, judgeStoredCapture, matches, notApplicableCoverage, parseCriteriaBank, priorityForIssue, readInkExtent, resetJudgeDesignIdentityCache, resolveCriteriaBlock, resolveDesignPackageDir, resolveLaunchOptions, runAxisChecks, runEvaluation, runEvaluationLoop, runVisualEval, runVisualEvaluation, runVisualEvaluationDetailed, selectCriteria, summarizeVisualResult };
|
|
5213
5929
|
//# sourceMappingURL=index.js.map
|
|
5214
5930
|
//# sourceMappingURL=index.js.map
|