@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.
Files changed (98) hide show
  1. package/dist/{adapter-Du7P_5Fx.d.ts → adapter-Cg_rkpGq.d.ts} +1 -1
  2. package/dist/adapters/generation-dispatch.d.ts +27 -5
  3. package/dist/adapters/generation-dispatch.js +909 -53
  4. package/dist/adapters/generation-dispatch.js.map +1 -1
  5. package/dist/adapters/index.js +3 -1
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/advanced/index.d.ts +12 -10
  8. package/dist/advanced/index.js +917 -98
  9. package/dist/advanced/index.js.map +1 -1
  10. package/dist/{axes-CzLEMDeB.d.ts → axes-CKVCzR_x.d.ts} +1 -1
  11. package/dist/bank-CT4eEkta.d.ts +118 -0
  12. package/dist/boilerplate.js +24 -12
  13. package/dist/boilerplate.js.map +1 -1
  14. package/dist/check/index.d.ts +11 -4
  15. package/dist/check/index.js +47 -1
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/classifier/index.d.ts +2 -2
  18. package/dist/compose.d.ts +2 -2
  19. package/dist/{create-ui-generator-GIhzx08x.d.ts → create-ui-generator-DFnnArrv.d.ts} +11 -3
  20. package/dist/{dispatch-BnAH56p4.d.ts → dispatch-CStl9eAw.d.ts} +5 -3
  21. package/dist/evaluation/axis-checks/index.d.ts +5 -5
  22. package/dist/evaluation/axis-checks/index.js +90 -2
  23. package/dist/evaluation/axis-checks/index.js.map +1 -1
  24. package/dist/evaluation/axis-checks/registry.d.ts +3 -3
  25. package/dist/evaluation/axis-checks/registry.js +88 -1
  26. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  27. package/dist/evaluation/index.d.ts +300 -23
  28. package/dist/evaluation/index.js +746 -30
  29. package/dist/evaluation/index.js.map +1 -1
  30. package/dist/evaluation/types.d.ts +9 -1
  31. package/dist/evaluation/types.js.map +1 -1
  32. package/dist/fragments/index.d.ts +3 -3
  33. package/dist/harness/check/runtime-render/index.d.ts +5 -5
  34. package/dist/harness/index.d.ts +4 -4
  35. package/dist/harness/index.js +33 -15
  36. package/dist/harness/index.js.map +1 -1
  37. package/dist/harness/result-types.d.ts +3 -3
  38. package/dist/harness/runtime.d.ts +10 -4
  39. package/dist/harness/runtime.js +26 -14
  40. package/dist/harness/runtime.js.map +1 -1
  41. package/dist/harness/types-public.d.ts +3 -3
  42. package/dist/harness/types-public.js +33 -15
  43. package/dist/harness/types-public.js.map +1 -1
  44. package/dist/harness/validator-trace-sink.d.ts +3 -3
  45. package/dist/hash.d.ts +1 -1
  46. package/dist/index.d.ts +25 -9
  47. package/dist/index.js +926 -101
  48. package/dist/index.js.map +1 -1
  49. package/dist/{llm-router-DwwUK1bz.d.ts → llm-router-nfO7wPec.d.ts} +3 -1
  50. package/dist/patch.d.ts +3 -3
  51. package/dist/policy.d.ts +1 -1
  52. package/dist/tools/get-primitives-ts.d.ts +1 -1
  53. package/dist/tools/get-primitives-ts.js +1 -1
  54. package/dist/tools/get-primitives-ts.js.map +1 -1
  55. package/dist/{types-BOvHNG7K.d.ts → types-Dh1s4K9j.d.ts} +1 -1
  56. package/dist/{types-public-Bk6d1WLN.d.ts → types-public-BJTLpzWn.d.ts} +134 -7
  57. package/dist/validation/index.d.ts +1 -1
  58. package/dist/validation/index.js +1 -1
  59. package/dist/validation/index.js.map +1 -1
  60. package/dist/workflows.d.ts +3 -3
  61. package/package.json +12 -12
  62. package/src/adapters/generation-dispatch.ts +36 -2
  63. package/src/adapters/tools.ts +2 -1
  64. package/src/boilerplate/free-design-prompt.ts +1 -1
  65. package/src/boilerplate/generate.ts +26 -1
  66. package/src/boilerplate/hard-sections.ts +13 -13
  67. package/src/boilerplate/system-prompt.ts +1 -1
  68. package/src/check/index.ts +1 -0
  69. package/src/check/sample-props.ts +63 -0
  70. package/src/coding-agent/tools.ts +4 -1
  71. package/src/create-harness.ts +3 -3
  72. package/src/create-ui-generator.ts +20 -1
  73. package/src/evaluation/__fixtures__/png.ts +100 -0
  74. package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
  75. package/src/evaluation/axis-checks/checks/universal.ts +111 -0
  76. package/src/evaluation/axis-checks/dispatch.ts +4 -1
  77. package/src/evaluation/criteria/bank.ts +85 -0
  78. package/src/evaluation/criteria/context.ts +52 -0
  79. package/src/evaluation/criteria/resolve.ts +159 -0
  80. package/src/evaluation/criteria/select.ts +79 -0
  81. package/src/evaluation/index.ts +23 -0
  82. package/src/evaluation/ink-extent.ts +200 -0
  83. package/src/evaluation/theme-css.ts +12 -5
  84. package/src/evaluation/types-public.ts +130 -3
  85. package/src/evaluation/types.ts +9 -0
  86. package/src/evaluation/visual-evaluator.ts +636 -30
  87. package/src/harness/coding/generate-task-runner.ts +3 -0
  88. package/src/harness/coding/run-coding-turn.ts +7 -3
  89. package/src/harness/coding/run-eval-round.ts +19 -0
  90. package/src/harness/runtime.ts +5 -0
  91. package/src/harness-tool-names.ts +38 -0
  92. package/src/index.ts +12 -0
  93. package/src/run-check.ts +2 -0
  94. package/src/tools/get-primitives-ts.ts +1 -1
  95. package/src/tools/get-wire.ts +1 -1
  96. package/src/tools/render-check.ts +0 -60
  97. package/src/tools.ts +11 -1
  98. package/src/validation/primitives.ts +1 -1
@@ -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} (or item.symbol / whatever the entity id field is).`
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: "viewport", overflow: "fail" };
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 buildRenderHTML(bundledCode, cssTokens, fit) {
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: color-mix(in oklch, var(--ggui-scrim-tint, var(--ggui-color-ground, #ffffff)) calc(var(--ggui-scrim-opacity, 0.45) * 100%), var(--ggui-color-neutral-50, #ffffff));
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
- async function measureContentHeight(page) {
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(CONTENT_HEIGHT_EXPRESSION);
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
- async function frameCanvas(bundledCode, context, html, canvas, canvasViewports, deps) {
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 canvasHtml = fit !== void 0 ? buildRenderHTML(bundledCode, context.cssTokens, fit) : html;
4870
- const attempt = await captureScreenshotDetailed(canvasHtml, viewport, deps, policy.capture);
4871
- return { viewport, policy, fit, declared: declaredBox !== void 0, attempt };
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 answers = await Promise.all(
4956
- Array.from({ length: k }, () => judgeAndParse(judge, config, model, screenshot2, context.originalPrompt, profileBlock, canvas))
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({ layer: "page", mode: theme.mode ?? mode, appTheme: theme });
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