@ggui-ai/ui-gen 0.9.0 → 0.10.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 (87) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +9 -2
  2. package/dist/adapters/generation-dispatch.js +547 -85
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +28 -11
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +5 -3
  7. package/dist/advanced/index.js +563 -92
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +109 -3
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.js +14 -2
  12. package/dist/boilerplate.js.map +1 -1
  13. package/dist/check/index.d.ts +1 -1
  14. package/dist/check/index.js +11 -1
  15. package/dist/check/index.js.map +1 -1
  16. package/dist/{create-ui-generator-B9kV4U9h.d.ts → create-ui-generator-CF0EL0Li.d.ts} +13 -1
  17. package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
  18. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  19. package/dist/evaluation/axis-checks/index.js +101 -0
  20. package/dist/evaluation/axis-checks/index.js.map +1 -1
  21. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  22. package/dist/evaluation/axis-checks/registry.js +101 -0
  23. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  24. package/dist/evaluation/index.d.ts +46 -3
  25. package/dist/evaluation/index.js +358 -17
  26. package/dist/evaluation/index.js.map +1 -1
  27. package/dist/harness/check/runtime-render/index.d.ts +2 -2
  28. package/dist/harness/check/runtime-render/index.js +110 -3
  29. package/dist/harness/check/runtime-render/index.js.map +1 -1
  30. package/dist/harness/index.d.ts +2 -2
  31. package/dist/harness/index.js +263 -18
  32. package/dist/harness/index.js.map +1 -1
  33. package/dist/harness/llm-trace-sink.d.ts +1 -1
  34. package/dist/harness/llm-trace-sink.js.map +1 -1
  35. package/dist/harness/prompts.js +17 -3
  36. package/dist/harness/prompts.js.map +1 -1
  37. package/dist/harness/result-types.d.ts +1 -1
  38. package/dist/harness/runtime.d.ts +9 -2
  39. package/dist/harness/runtime.js +20 -6
  40. package/dist/harness/runtime.js.map +1 -1
  41. package/dist/harness/types-public.d.ts +1 -1
  42. package/dist/harness/types-public.js +14 -2
  43. package/dist/harness/types-public.js.map +1 -1
  44. package/dist/harness/validator-trace-sink.d.ts +1 -1
  45. package/dist/index.d.ts +5 -3
  46. package/dist/index.js +563 -92
  47. package/dist/index.js.map +1 -1
  48. package/dist/{llm-router-1TyI0_TV.d.ts → llm-router-BEVtbAb9.d.ts} +61 -4
  49. package/dist/patch.d.ts +1 -1
  50. package/dist/provider-adapter.d.ts +16 -6
  51. package/dist/provider-adapter.js.map +1 -1
  52. package/dist/providers/index.d.ts +19 -6
  53. package/dist/providers/index.js.map +1 -1
  54. package/dist/{render-check-CNZjHD5g.d.ts → render-check-BNYGVDJd.d.ts} +1 -1
  55. package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
  56. package/dist/workflows.d.ts +1 -1
  57. package/package.json +12 -12
  58. package/src/adapters/generation-dispatch.ts +9 -0
  59. package/src/adapters/openrouter/client.ts +2 -2
  60. package/src/adapters/openrouter/types.ts +10 -0
  61. package/src/boilerplate/generate.ts +10 -2
  62. package/src/boilerplate/json-schema-ts.ts +22 -2
  63. package/src/boilerplate/system-prompt.ts +1 -0
  64. package/src/compile.ts +1 -1
  65. package/src/contract-context.ts +10 -2
  66. package/src/create-ui-generator.ts +14 -0
  67. package/src/design-system-docs.ts +6 -4
  68. package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
  69. package/src/evaluation/axis-checks/registry.ts +2 -0
  70. package/src/evaluation/index.ts +11 -0
  71. package/src/evaluation/llm-evaluator.ts +14 -1
  72. package/src/evaluation/visual-evaluator.ts +44 -65
  73. package/src/harness/check/runtime-render/adapter.ts +18 -0
  74. package/src/harness/check/runtime-render/index.ts +8 -0
  75. package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
  76. package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
  77. package/src/harness/check/runtime-render/render-check.ts +72 -1
  78. package/src/harness/coding/run-eval-round.ts +61 -5
  79. package/src/harness/index.ts +1 -1
  80. package/src/harness/llm-router.ts +397 -18
  81. package/src/harness/llm-trace-sink.ts +2 -1
  82. package/src/harness/runtime.ts +7 -0
  83. package/src/index.ts +5 -5
  84. package/src/provider-adapter.ts +16 -6
  85. package/src/providers/bedrock.ts +5 -4
  86. package/src/providers/index.ts +16 -4
  87. package/src/tools/render-check.ts +19 -7
@@ -2,6 +2,7 @@ import { query, createSdkMcpServer, tool } from '@anthropic-ai/claude-agent-sdk'
2
2
  import Anthropic from '@anthropic-ai/sdk';
3
3
  import { z } from 'zod';
4
4
  import { listContractGadgets, HOOK_NAME_RE, STDLIB_GADGETS_PACKAGE } from '@ggui-ai/protocol';
5
+ import 'esbuild';
5
6
 
6
7
  var __defProp = Object.defineProperty;
7
8
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -23,14 +24,16 @@ var OpenRouterError;
23
24
  var init_types = __esm({
24
25
  "src/adapters/openrouter/types.ts"() {
25
26
  OpenRouterError = class extends Error {
26
- constructor(message, status, code) {
27
+ constructor(message, status, code, headers) {
27
28
  super(message);
28
29
  this.status = status;
29
30
  this.code = code;
31
+ this.headers = headers;
30
32
  this.name = "OpenRouterError";
31
33
  }
32
34
  status;
33
35
  code;
36
+ headers;
34
37
  get isTransient() {
35
38
  return this.status === 408 || this.status === 429 || this.status === 502 || this.status === 503;
36
39
  }
@@ -75,7 +78,7 @@ var init_client = __esm({
75
78
  if (!response.ok) {
76
79
  const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
77
80
  const message = errorBody.error?.message ?? response.statusText;
78
- throw new OpenRouterError(message, response.status);
81
+ throw new OpenRouterError(message, response.status, void 0, response.headers);
79
82
  }
80
83
  return response.json();
81
84
  }
@@ -93,7 +96,7 @@ var init_client = __esm({
93
96
  if (!response.ok) {
94
97
  const errorBody = await response.json().catch(() => ({ error: { message: response.statusText } }));
95
98
  const message = errorBody.error?.message ?? response.statusText;
96
- throw new OpenRouterError(message, response.status);
99
+ throw new OpenRouterError(message, response.status, void 0, response.headers);
97
100
  }
98
101
  if (!response.body) {
99
102
  throw new OpenRouterError("No response body for streaming request", 500);
@@ -455,6 +458,34 @@ function errorSummary(e) {
455
458
  }
456
459
  return `${e.constructor.name}${status}${code}: ${e.message.slice(0, 120)}${body}`;
457
460
  }
461
+ function isRateLimited(e) {
462
+ if (!(e instanceof Error) || !("status" in e)) return false;
463
+ return e.status === 429;
464
+ }
465
+ function extractStatus(e) {
466
+ if (!(e instanceof Error) || !("status" in e)) return void 0;
467
+ const status = e.status;
468
+ return typeof status === "number" ? status : void 0;
469
+ }
470
+ function extractRetryAfterSec(e) {
471
+ if (!(e instanceof Error) || !("headers" in e)) return void 0;
472
+ const headers = e.headers;
473
+ if (headers === null || typeof headers !== "object" || !("get" in headers) || typeof headers.get !== "function") {
474
+ return void 0;
475
+ }
476
+ const raw = headers.get("retry-after");
477
+ if (raw === null) return void 0;
478
+ const parsed = Number(raw);
479
+ return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : void 0;
480
+ }
481
+ function sleep(ms) {
482
+ return new Promise((resolve) => setTimeout(resolve, ms));
483
+ }
484
+ var MAX_RETRY_ATTEMPTS = 2;
485
+ var RETRY_AFTER_CAP_SEC = 15;
486
+ var DEFAULT_BACKOFF_BASE_MS = 1e3;
487
+ var DEFAULT_BACKOFF_MAX_MS = 8e3;
488
+ var MAX_TOTAL_RETRY_DELAY_MS = 2e4;
458
489
  var LLMAgent = class {
459
490
  client = null;
460
491
  // ── Session state for server-side chaining ──────────────
@@ -469,8 +500,11 @@ var LLMAgent = class {
469
500
  * back to `process.env` exactly as before.
470
501
  */
471
502
  routeOverride;
472
- constructor(routeOverride) {
503
+ /** See `AgentConfig.onRetry` (#489). Absent is a no-op. */
504
+ onRetry;
505
+ constructor(routeOverride, onRetry) {
473
506
  this.routeOverride = routeOverride;
507
+ this.onRetry = onRetry;
474
508
  }
475
509
  async getClient() {
476
510
  if (!this.client) {
@@ -505,17 +539,83 @@ var LLMAgent = class {
505
539
  async sendToolResult(_results) {
506
540
  }
507
541
  /**
508
- * Execute an API call. No retry — if it fails, it fails.
509
- * Logs the error with provider context and re-throws.
542
+ * Execute an API call, retrying once or twice on HTTP 429
543
+ * (rate-limited) before giving up (#489).
544
+ *
545
+ * Policy: on a 429, honor the provider's `Retry-After` header when
546
+ * present (capped at `RETRY_AFTER_CAP_SEC` — a provider asking for a
547
+ * longer wait is treated as "don't retry", not "wait longer": making
548
+ * the caller sit through 15+ more seconds on a single failed call is
549
+ * a bad experience regardless of what's driving the request);
550
+ * otherwise fall back to exponential backoff with jitter, capped at
551
+ * `DEFAULT_BACKOFF_MAX_MS` per attempt. Stops after
552
+ * `MAX_RETRY_ATTEMPTS` retries OR once the cumulative delay would
553
+ * exceed `MAX_TOTAL_RETRY_DELAY_MS`, whichever comes first. Any
554
+ * non-429 error, or a 429 with no attempts left, is logged and
555
+ * re-thrown immediately — unchanged from the pre-#489 behavior.
556
+ *
557
+ * **Budget scope (final-review correction):** `MAX_TOTAL_RETRY_DELAY_MS`
558
+ * bounds a single `apiCall()` invocation, not a whole generation. A
559
+ * generation issues many sequential `apiCall()` calls (coding turns,
560
+ * tool-result round-trips, eval rounds); under sustained throttling
561
+ * each one can independently add up to `MAX_TOTAL_RETRY_DELAY_MS`, so
562
+ * a generation with k rate-limited calls can add up to roughly `k *
563
+ * MAX_TOTAL_RETRY_DELAY_MS` to how long it holds its caller's
564
+ * resources (e.g. the caller's concurrency slot) — not a flat 20s
565
+ * ceiling. No shared per-generation budget is threaded here by
566
+ * design (accepted ruling, not an oversight): the caller's own
567
+ * queue/admission backstop is what bounds how long other
568
+ * queued work waits, independent of any one generation's retry
569
+ * total.
510
570
  */
511
571
  async apiCall(fn) {
512
572
  const start = Date.now();
513
- try {
514
- return await fn();
515
- } catch (e) {
516
- const ms = Date.now() - start;
517
- console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
518
- throw e;
573
+ let totalDelayMs = 0;
574
+ for (let attempt = 0; ; attempt++) {
575
+ try {
576
+ return await fn();
577
+ } catch (e) {
578
+ const ms = Date.now() - start;
579
+ if (attempt >= MAX_RETRY_ATTEMPTS || !isRateLimited(e)) {
580
+ console.error(`[${this.provider}] API error after ${ms}ms: ${errorSummary(e)}`);
581
+ throw e;
582
+ }
583
+ const retryAfterSec = extractRetryAfterSec(e);
584
+ if (retryAfterSec !== void 0 && retryAfterSec > RETRY_AFTER_CAP_SEC) {
585
+ console.error(
586
+ `[${this.provider}] API error after ${ms}ms (retry-after ${retryAfterSec}s exceeds ${RETRY_AFTER_CAP_SEC}s cap, not retrying): ${errorSummary(e)}`
587
+ );
588
+ throw e;
589
+ }
590
+ const delayMs = retryAfterSec !== void 0 ? retryAfterSec * 1e3 : Math.min(DEFAULT_BACKOFF_BASE_MS * 2 ** attempt, DEFAULT_BACKOFF_MAX_MS) + Math.floor(Math.random() * 250);
591
+ if (totalDelayMs + delayMs > MAX_TOTAL_RETRY_DELAY_MS) {
592
+ console.error(
593
+ `[${this.provider}] API error after ${ms}ms (retry budget exhausted, not retrying): ${errorSummary(e)}`
594
+ );
595
+ throw e;
596
+ }
597
+ totalDelayMs += delayMs;
598
+ const info = {
599
+ provider: this.provider,
600
+ attempt: attempt + 1,
601
+ maxAttempts: MAX_RETRY_ATTEMPTS,
602
+ status: extractStatus(e),
603
+ retryAfterSec,
604
+ delayMs,
605
+ message: errorSummary(e)
606
+ };
607
+ console.warn(
608
+ `[${this.provider}] rate-limited (attempt ${info.attempt}/${info.maxAttempts}), retrying in ${delayMs}ms`
609
+ );
610
+ try {
611
+ this.onRetry?.(info);
612
+ } catch (observerError) {
613
+ console.warn(
614
+ `[${this.provider}] onRetry observer threw (ignored, retry proceeds): ${errorSummary(observerError)}`
615
+ );
616
+ }
617
+ await sleep(delayMs);
618
+ }
519
619
  }
520
620
  }
521
621
  };
@@ -598,6 +698,63 @@ var AnthropicAgent = class extends LLMAgent {
598
698
  throw e;
599
699
  }
600
700
  }
701
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
702
+ const client = await this.getClient();
703
+ const traceId = newLlmTraceId();
704
+ const startedAt = Date.now();
705
+ const resolvedModel = this.resolveModel(model);
706
+ const imageBlock = {
707
+ type: "image",
708
+ source: {
709
+ type: "base64",
710
+ media_type: image.mediaType,
711
+ data: image.base64
712
+ }
713
+ };
714
+ try {
715
+ const response = await this.apiCall(
716
+ () => client.messages.create({
717
+ model: resolvedModel,
718
+ max_tokens: maxTokens ?? 1500,
719
+ system: systemPrompt,
720
+ messages: [
721
+ {
722
+ role: "user",
723
+ content: [imageBlock, { type: "text", text: userPrompt }]
724
+ }
725
+ ]
726
+ })
727
+ );
728
+ const text = response.content.filter((block) => block.type === "text").map((block) => block.text).join("");
729
+ const endedAt = Date.now();
730
+ emitLlmTraceEvent({
731
+ id: traceId,
732
+ at: startedAt,
733
+ endedAt,
734
+ durationMs: endedAt - startedAt,
735
+ provider: "anthropic",
736
+ model: resolvedModel,
737
+ kind: "callVision",
738
+ systemPrompt,
739
+ userPrompt,
740
+ result: {
741
+ inputTokens: response.usage.input_tokens,
742
+ outputTokens: response.usage.output_tokens,
743
+ text
744
+ }
745
+ });
746
+ return {
747
+ text,
748
+ inputTokens: response.usage.input_tokens,
749
+ outputTokens: response.usage.output_tokens
750
+ };
751
+ } catch (e) {
752
+ emitLlmTraceEvent({
753
+ error: { message: e instanceof Error ? e.message : String(e) }
754
+ });
755
+ throw e;
756
+ }
757
+ }
601
758
  async callTools(model, systemPrompt, userPrompt, tools, toolChoice = "required", _scopedTools) {
602
759
  const client = await this.getClient();
603
760
  const traceId = newLlmTraceId();
@@ -973,6 +1130,62 @@ var GoogleAgent = class extends LLMAgent {
973
1130
  outputTokens: interaction.usage?.total_output_tokens ?? 0
974
1131
  };
975
1132
  }
1133
+ async callVision(model, systemPrompt, userPrompt, image, maxTokens) {
1134
+ const client = await this.getClient();
1135
+ const traceId = newLlmTraceId();
1136
+ const startedAt = Date.now();
1137
+ const resolvedModel = this.resolveModel(model);
1138
+ try {
1139
+ const response = await this.apiCall(
1140
+ () => client.models.generateContent({
1141
+ model: resolvedModel,
1142
+ contents: [
1143
+ {
1144
+ role: "user",
1145
+ parts: [
1146
+ { inlineData: { mimeType: image.mediaType, data: image.base64 } },
1147
+ { text: userPrompt }
1148
+ ]
1149
+ }
1150
+ ],
1151
+ config: {
1152
+ systemInstruction: systemPrompt,
1153
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {}
1154
+ }
1155
+ })
1156
+ );
1157
+ const text = response.candidates?.[0]?.content?.parts?.map((p) => p.text || "").join("") || "";
1158
+ const usage = response.usageMetadata;
1159
+ const result = {
1160
+ text,
1161
+ inputTokens: usage?.promptTokenCount ?? 0,
1162
+ outputTokens: usage?.candidatesTokenCount ?? 0
1163
+ };
1164
+ const endedAt = Date.now();
1165
+ emitLlmTraceEvent({
1166
+ id: traceId,
1167
+ at: startedAt,
1168
+ endedAt,
1169
+ durationMs: endedAt - startedAt,
1170
+ provider: "google",
1171
+ model: resolvedModel,
1172
+ kind: "callVision",
1173
+ systemPrompt,
1174
+ userPrompt,
1175
+ result: {
1176
+ inputTokens: result.inputTokens,
1177
+ outputTokens: result.outputTokens,
1178
+ text
1179
+ }
1180
+ });
1181
+ return result;
1182
+ } catch (e) {
1183
+ emitLlmTraceEvent({
1184
+ error: { message: e instanceof Error ? e.message : String(e) }
1185
+ });
1186
+ throw e;
1187
+ }
1188
+ }
976
1189
  async callTools(model, systemPrompt, userPrompt, tools, _toolChoice = "required", scopedTools) {
977
1190
  const client = await this.getClient();
978
1191
  const interactionTools = tools.map((t) => ({
@@ -1304,15 +1517,24 @@ var OpenRouterAgent = class extends LLMAgent {
1304
1517
  function createAgent(providerOrConfig) {
1305
1518
  const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
1306
1519
  const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
1520
+ const onRetry = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.onRetry;
1307
1521
  switch (provider) {
1308
1522
  case "anthropic":
1309
- return new AnthropicAgent(routeOverride);
1523
+ return new AnthropicAgent(routeOverride, onRetry);
1310
1524
  case "openai":
1311
- return new OpenAIAgent(routeOverride);
1525
+ return new OpenAIAgent(routeOverride, onRetry);
1312
1526
  case "google":
1313
- return new GoogleAgent(routeOverride);
1527
+ return new GoogleAgent(routeOverride, onRetry);
1314
1528
  case "openrouter":
1315
- return new OpenRouterAgent(routeOverride);
1529
+ return new OpenRouterAgent(routeOverride, onRetry);
1530
+ }
1531
+ }
1532
+ function createVisionAgent(config) {
1533
+ switch (config.provider) {
1534
+ case "anthropic":
1535
+ return new AnthropicAgent(config.routeOverride, config.onRetry);
1536
+ case "google":
1537
+ return new GoogleAgent(config.routeOverride, config.onRetry);
1316
1538
  }
1317
1539
  }
1318
1540
 
@@ -2203,6 +2425,106 @@ var STATE_MERGE_CHECKS = [
2203
2425
  }
2204
2426
  ];
2205
2427
 
2428
+ // src/evaluation/axis-checks/checks/state-ui-affordance.ts
2429
+ var STATE_ARIA_OR_ROLE = /aria-(checked|pressed|selected|expanded|current)\s*=|role\s*=/;
2430
+ var STATE_PRIMITIVE_IN_SUBTREE = /<(Checkbox|Toggle|Tabs|Accordion)[\s/>]/;
2431
+ var SEMANTIC_PRIMITIVES = /* @__PURE__ */ new Set(["Button", "Checkbox", "Toggle", "Link", "MenuItem"]);
2432
+ function collectSetters(src) {
2433
+ const setters = /* @__PURE__ */ new Set();
2434
+ for (const m of src.matchAll(/const\s*\[\s*\w+\s*,\s*(set\w+)\s*\]\s*=\s*useState/g)) {
2435
+ setters.add(m[1]);
2436
+ }
2437
+ return setters;
2438
+ }
2439
+ function collectHandlers(src, setters) {
2440
+ const handlers = /* @__PURE__ */ new Set();
2441
+ for (const m of src.matchAll(/const\s+(\w+)\s*=\s*(?:\([^)]*\)|\w+)\s*=>\s*\{?/g)) {
2442
+ const body = src.slice(m.index + m[0].length, m.index + m[0].length + 400);
2443
+ for (const s of setters) {
2444
+ if (body.includes(s)) {
2445
+ handlers.add(m[1]);
2446
+ break;
2447
+ }
2448
+ }
2449
+ }
2450
+ return handlers;
2451
+ }
2452
+ function attrBlock(src, start) {
2453
+ let depth = 0;
2454
+ for (let i = start; i < src.length; i++) {
2455
+ const c = src[i];
2456
+ if (c === "{") depth++;
2457
+ else if (c === "}") depth--;
2458
+ else if (c === ">" && depth === 0) return { block: src.slice(start, i), end: i };
2459
+ }
2460
+ return { block: src.slice(start, start + 900), end: start + 900 };
2461
+ }
2462
+ function onClickValue(block) {
2463
+ const m = block.match(/onClick\s*=\s*\{/);
2464
+ if (!m || m.index === void 0) return null;
2465
+ const open = m.index + m[0].length - 1;
2466
+ let depth = 0;
2467
+ for (let i = open; i < block.length; i++) {
2468
+ if (block[i] === "{") depth++;
2469
+ else if (block[i] === "}") {
2470
+ depth--;
2471
+ if (depth === 0) return block.slice(open + 1, i);
2472
+ }
2473
+ }
2474
+ return block.slice(open + 1);
2475
+ }
2476
+ function subtree(src, pos, tag) {
2477
+ const close = src.indexOf(`</${tag}>`, pos);
2478
+ return src.slice(pos, close === -1 ? pos + 700 : close);
2479
+ }
2480
+ function runStateAriaPresent(input) {
2481
+ if (input.compiledCode === null) return [];
2482
+ const src = input.sourceCode;
2483
+ const setters = collectSetters(src);
2484
+ const handlers = collectHandlers(src, setters);
2485
+ const issues = [];
2486
+ const flaggedLines = /* @__PURE__ */ new Set();
2487
+ const localComponents = /* @__PURE__ */ new Set();
2488
+ for (const m of src.matchAll(/(?:function\s+([A-Z]\w*)\s*\(|const\s+([A-Z]\w*)\s*=)/g)) {
2489
+ localComponents.add(m[1] ?? m[2]);
2490
+ }
2491
+ for (const m of src.matchAll(/<([A-Za-z][A-Za-z0-9]*)\b/g)) {
2492
+ const tag = m[1];
2493
+ if (m.index === void 0) continue;
2494
+ if (localComponents.has(tag)) continue;
2495
+ const { block, end } = attrBlock(src, m.index + m[0].length);
2496
+ const clickValue = onClickValue(block);
2497
+ if (clickValue === null) continue;
2498
+ const mutatesAffordance = [...setters].some((s) => clickValue.includes(s)) || [...handlers].some((h) => new RegExp(`\\b${h}\\b`).test(clickValue)) || // Extracted-subcomponent shape: the toggle handler arrives as an
2499
+ // `on[A-Z]…` callback prop and is invoked from onClick — inside a
2500
+ // ui-affordance component that IS the affordance mutation.
2501
+ /\bon[A-Z]\w*\b/.test(clickValue);
2502
+ if (!mutatesAffordance) continue;
2503
+ if (SEMANTIC_PRIMITIVES.has(tag)) continue;
2504
+ if (STATE_ARIA_OR_ROLE.test(block)) continue;
2505
+ if (STATE_PRIMITIVE_IN_SUBTREE.test(subtree(src, end, tag))) continue;
2506
+ const line = src.slice(0, m.index).split("\n").length;
2507
+ if (flaggedLines.has(line)) continue;
2508
+ flaggedLines.add(line);
2509
+ issues.push(
2510
+ mkIssue(
2511
+ "state.ui_affordance.state_aria_present",
2512
+ `Line ${line}: <${tag}> toggles the UI affordance from its onClick but exposes no state ARIA \u2014 its checked/selected/expanded state is invisible to assistive technology (styling is the only carrier).`,
2513
+ `Add the matching state attribute to the SAME element that carries the onClick: \`role="checkbox"\` + \`aria-checked={\u2026}\` for a toggle row, \`aria-pressed\` for a toggle button, \`aria-selected\` for a selection, \`aria-expanded\` for an open/close affordance \u2014 or replace the hand-rolled control with the state-carrying primitive (Checkbox / Toggle / Tabs / Accordion). \`as={Clickable}\` adds button + keyboard semantics but NOT state.`
2514
+ )
2515
+ );
2516
+ }
2517
+ return issues;
2518
+ }
2519
+ var STATE_UI_AFFORDANCE_CHECKS = [
2520
+ {
2521
+ id: "state.ui_affordance.state_aria_present",
2522
+ axis: "state",
2523
+ values: ["ui-affordance"],
2524
+ run: runStateAriaPresent
2525
+ }
2526
+ ];
2527
+
2206
2528
  // src/evaluation/axis-checks/checks/realtime.ts
2207
2529
  var REALTIME_ACTIVE = ["merge", "append", "status", "presence", "mixed"];
2208
2530
  function runStreamHandlerPerEvent(input) {
@@ -2892,6 +3214,7 @@ var EXTRA_CHECKS = [
2892
3214
  var REGISTRY = [
2893
3215
  ...UNIVERSAL_CHECKS,
2894
3216
  ...STATE_MERGE_CHECKS,
3217
+ ...STATE_UI_AFFORDANCE_CHECKS,
2895
3218
  ...REALTIME_CHECKS,
2896
3219
  ...WRITES_CHECKS,
2897
3220
  ...STATE_PAYLOAD_CHECKS,
@@ -2919,7 +3242,25 @@ function runAxisChecks(classification, input) {
2919
3242
  }
2920
3243
  return issues;
2921
3244
  }
3245
+ async function callMultimodalLLM(config, model, prompt, screenshot, originalPrompt) {
3246
+ const userPrompt = `## Original Request
3247
+ ${originalPrompt}
3248
+
3249
+ Evaluate the screenshot of the generated component.`;
3250
+ const agent = createVisionAgent({
3251
+ provider: config.provider === "claude" ? "anthropic" : config.provider,
3252
+ routeOverride: config.routeOverride,
3253
+ onRetry: config.onRetry
3254
+ });
3255
+ return agent.callVision(
3256
+ model,
3257
+ prompt,
3258
+ userPrompt,
3259
+ { mediaType: "image/png", base64: screenshot.toString("base64") },
3260
+ 1500
3261
+ );
3262
+ }
2922
3263
 
2923
- export { CRITERIA, DEFAULT_EVAL_MAX_ROUNDS, DEFAULT_QUALITY_CONFIG, MAX_EVAL_ROUNDS_HARD_LIMIT, buildCodingCriteriaSummary, buildFixPrompt, computeEvaluationScore, createEvaluationToolsServer, extractCompiledCode, extractCompiledCodeFromMessage, extractEvalResult, extractSourceCode, extractSourceCodeFromMessage, extractToolResultTexts, getActionableIssues, getCriteriaByPriority, getCriterionById, getEvaluatorSystemPrompt, getLLMCriteria, isBlocked, matches, priorityForIssue, runAxisChecks, runEvaluation, runEvaluationLoop };
3264
+ export { CRITERIA, DEFAULT_EVAL_MAX_ROUNDS, DEFAULT_QUALITY_CONFIG, MAX_EVAL_ROUNDS_HARD_LIMIT, buildCodingCriteriaSummary, buildFixPrompt, callMultimodalLLM, computeEvaluationScore, createEvaluationToolsServer, extractCompiledCode, extractCompiledCodeFromMessage, extractEvalResult, extractSourceCode, extractSourceCodeFromMessage, extractToolResultTexts, getActionableIssues, getCriteriaByPriority, getCriterionById, getEvaluatorSystemPrompt, getLLMCriteria, isBlocked, matches, priorityForIssue, runAxisChecks, runEvaluation, runEvaluationLoop };
2924
3265
  //# sourceMappingURL=index.js.map
2925
3266
  //# sourceMappingURL=index.js.map