@ggui-ai/ui-gen 0.28.0 → 0.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
  2. package/dist/adapters/base.js +2 -27
  3. package/dist/adapters/base.js.map +1 -1
  4. package/dist/adapters/generation-dispatch.d.ts +14 -6
  5. package/dist/adapters/generation-dispatch.js +40 -19260
  6. package/dist/adapters/generation-dispatch.js.map +1 -1
  7. package/dist/adapters/index.d.ts +1 -1
  8. package/dist/adapters/index.js +25 -3561
  9. package/dist/adapters/index.js.map +1 -1
  10. package/dist/adapters/types.js +2 -9
  11. package/dist/adapters/types.js.map +1 -1
  12. package/dist/advanced/index.d.ts +13 -12
  13. package/dist/advanced/index.js +63 -21954
  14. package/dist/advanced/index.js.map +1 -1
  15. package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
  16. package/dist/blueprint-validator.js +6 -2301
  17. package/dist/blueprint-validator.js.map +1 -1
  18. package/dist/boilerplate.d.ts +3 -2
  19. package/dist/boilerplate.js +6 -1867
  20. package/dist/boilerplate.js.map +1 -1
  21. package/dist/check/index.d.ts +3 -2
  22. package/dist/check/index.js +6 -1923
  23. package/dist/check/index.js.map +1 -1
  24. package/dist/chunk-33B3BAWS.js +24 -0
  25. package/dist/chunk-33B3BAWS.js.map +1 -0
  26. package/dist/chunk-3HSGA6NI.js +591 -0
  27. package/dist/chunk-3HSGA6NI.js.map +1 -0
  28. package/dist/chunk-3NNNGNTO.js +1707 -0
  29. package/dist/chunk-3NNNGNTO.js.map +1 -0
  30. package/dist/chunk-3T2HMTON.js +79 -0
  31. package/dist/chunk-3T2HMTON.js.map +1 -0
  32. package/dist/chunk-3ZSSDV3L.js +123 -0
  33. package/dist/chunk-3ZSSDV3L.js.map +1 -0
  34. package/dist/chunk-4MBR5GA3.js +99 -0
  35. package/dist/chunk-4MBR5GA3.js.map +1 -0
  36. package/dist/chunk-56NJFD55.js +337 -0
  37. package/dist/chunk-56NJFD55.js.map +1 -0
  38. package/dist/chunk-5AGTY555.js +26 -0
  39. package/dist/chunk-5AGTY555.js.map +1 -0
  40. package/dist/chunk-5EJM7EGY.js +115 -0
  41. package/dist/chunk-5EJM7EGY.js.map +1 -0
  42. package/dist/chunk-5KXCQLLR.js +529 -0
  43. package/dist/chunk-5KXCQLLR.js.map +1 -0
  44. package/dist/chunk-5VBXHVEH.js +171 -0
  45. package/dist/chunk-5VBXHVEH.js.map +1 -0
  46. package/dist/chunk-6KDDEING.js +515 -0
  47. package/dist/chunk-6KDDEING.js.map +1 -0
  48. package/dist/chunk-6VDRWAP6.js +123 -0
  49. package/dist/chunk-6VDRWAP6.js.map +1 -0
  50. package/dist/chunk-732IBFEJ.js +2400 -0
  51. package/dist/chunk-732IBFEJ.js.map +1 -0
  52. package/dist/chunk-73AURS3I.js +46 -0
  53. package/dist/chunk-73AURS3I.js.map +1 -0
  54. package/dist/chunk-77H5SKLX.js +8 -0
  55. package/dist/chunk-77H5SKLX.js.map +1 -0
  56. package/dist/chunk-7QE3AHLE.js +35 -0
  57. package/dist/chunk-7QE3AHLE.js.map +1 -0
  58. package/dist/chunk-A375NNWN.js +11 -0
  59. package/dist/chunk-A375NNWN.js.map +1 -0
  60. package/dist/chunk-AOU33EFS.js +140 -0
  61. package/dist/chunk-AOU33EFS.js.map +1 -0
  62. package/dist/chunk-BA4QYA6E.js +461 -0
  63. package/dist/chunk-BA4QYA6E.js.map +1 -0
  64. package/dist/chunk-DBP764GL.js +3 -0
  65. package/dist/chunk-DBP764GL.js.map +1 -0
  66. package/dist/chunk-DUU3ZJDQ.js +41 -0
  67. package/dist/chunk-DUU3ZJDQ.js.map +1 -0
  68. package/dist/chunk-DXKRBXNU.js +30 -0
  69. package/dist/chunk-DXKRBXNU.js.map +1 -0
  70. package/dist/chunk-FB7OEEAH.js +1467 -0
  71. package/dist/chunk-FB7OEEAH.js.map +1 -0
  72. package/dist/chunk-FLRNMZV2.js +18 -0
  73. package/dist/chunk-FLRNMZV2.js.map +1 -0
  74. package/dist/chunk-FWJIMKFZ.js +2032 -0
  75. package/dist/chunk-FWJIMKFZ.js.map +1 -0
  76. package/dist/chunk-FXRHWYIQ.js +163 -0
  77. package/dist/chunk-FXRHWYIQ.js.map +1 -0
  78. package/dist/chunk-G3PMV62Z.js +33 -0
  79. package/dist/chunk-G3PMV62Z.js.map +1 -0
  80. package/dist/chunk-GAJI7Y3P.js +13 -0
  81. package/dist/chunk-GAJI7Y3P.js.map +1 -0
  82. package/dist/chunk-GYINWMPR.js +2052 -0
  83. package/dist/chunk-GYINWMPR.js.map +1 -0
  84. package/dist/chunk-HD6ABIK7.js +484 -0
  85. package/dist/chunk-HD6ABIK7.js.map +1 -0
  86. package/dist/chunk-HDQVHYO6.js +100 -0
  87. package/dist/chunk-HDQVHYO6.js.map +1 -0
  88. package/dist/chunk-IHUT3SIW.js +7 -0
  89. package/dist/chunk-IHUT3SIW.js.map +1 -0
  90. package/dist/chunk-JDH5EGNZ.js +126 -0
  91. package/dist/chunk-JDH5EGNZ.js.map +1 -0
  92. package/dist/chunk-JMD4RGSV.js +37 -0
  93. package/dist/chunk-JMD4RGSV.js.map +1 -0
  94. package/dist/chunk-KJWGZL5B.js +33 -0
  95. package/dist/chunk-KJWGZL5B.js.map +1 -0
  96. package/dist/chunk-MKMDQFZ7.js +86 -0
  97. package/dist/chunk-MKMDQFZ7.js.map +1 -0
  98. package/dist/chunk-NF6JUZWF.js +85 -0
  99. package/dist/chunk-NF6JUZWF.js.map +1 -0
  100. package/dist/chunk-O2YC6XDC.js +1933 -0
  101. package/dist/chunk-O2YC6XDC.js.map +1 -0
  102. package/dist/chunk-OVLH5ICV.js +29 -0
  103. package/dist/chunk-OVLH5ICV.js.map +1 -0
  104. package/dist/chunk-PBOYHGSD.js +106 -0
  105. package/dist/chunk-PBOYHGSD.js.map +1 -0
  106. package/dist/chunk-PJFH7QWA.js +81 -0
  107. package/dist/chunk-PJFH7QWA.js.map +1 -0
  108. package/dist/chunk-PUH4CXGZ.js +284 -0
  109. package/dist/chunk-PUH4CXGZ.js.map +1 -0
  110. package/dist/chunk-QLXXPGTV.js +18 -0
  111. package/dist/chunk-QLXXPGTV.js.map +1 -0
  112. package/dist/chunk-R5GIGPW2.js +2845 -0
  113. package/dist/chunk-R5GIGPW2.js.map +1 -0
  114. package/dist/chunk-RH4IUSTO.js +83 -0
  115. package/dist/chunk-RH4IUSTO.js.map +1 -0
  116. package/dist/chunk-S2GVXKKY.js +153 -0
  117. package/dist/chunk-S2GVXKKY.js.map +1 -0
  118. package/dist/chunk-TVHGKFJS.js +58 -0
  119. package/dist/chunk-TVHGKFJS.js.map +1 -0
  120. package/dist/chunk-U7GALWLG.js +66 -0
  121. package/dist/chunk-U7GALWLG.js.map +1 -0
  122. package/dist/chunk-VTC263LT.js +256 -0
  123. package/dist/chunk-VTC263LT.js.map +1 -0
  124. package/dist/chunk-WEOXFEVE.js +739 -0
  125. package/dist/chunk-WEOXFEVE.js.map +1 -0
  126. package/dist/chunk-WYHP46LT.js +28 -0
  127. package/dist/chunk-WYHP46LT.js.map +1 -0
  128. package/dist/chunk-XGLO5LGG.js +274 -0
  129. package/dist/chunk-XGLO5LGG.js.map +1 -0
  130. package/dist/chunk-XRBCRKA2.js +29 -0
  131. package/dist/chunk-XRBCRKA2.js.map +1 -0
  132. package/dist/chunk-XW7ADFFV.js +340 -0
  133. package/dist/chunk-XW7ADFFV.js.map +1 -0
  134. package/dist/chunk-YIQ42IUX.js +34 -0
  135. package/dist/chunk-YIQ42IUX.js.map +1 -0
  136. package/dist/chunk-YJZTUY7B.js +101 -0
  137. package/dist/chunk-YJZTUY7B.js.map +1 -0
  138. package/dist/chunk-Z3CXME3K.js +107 -0
  139. package/dist/chunk-Z3CXME3K.js.map +1 -0
  140. package/dist/classifier/index.js +2 -527
  141. package/dist/classifier/index.js.map +1 -1
  142. package/dist/client-JBPCXU7W.js +4 -0
  143. package/dist/client-JBPCXU7W.js.map +1 -0
  144. package/dist/compose.js +3 -367
  145. package/dist/compose.js.map +1 -1
  146. package/dist/cost-tracker-HHGQ4FTW.js +67 -0
  147. package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
  148. package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
  149. package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
  150. package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
  151. package/dist/dist-UALN6ZVB.js +543 -0
  152. package/dist/dist-UALN6ZVB.js.map +1 -0
  153. package/dist/dupe-break-X6K7QFIS.js +4 -0
  154. package/dist/dupe-break-X6K7QFIS.js.map +1 -0
  155. package/dist/evaluation/axis-checks/index.d.ts +5 -4
  156. package/dist/evaluation/axis-checks/index.js +6 -1914
  157. package/dist/evaluation/axis-checks/index.js.map +1 -1
  158. package/dist/evaluation/axis-checks/registry.d.ts +3 -2
  159. package/dist/evaluation/axis-checks/registry.js +3 -1844
  160. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  161. package/dist/evaluation/index.d.ts +49 -132
  162. package/dist/evaluation/index.js +302 -6466
  163. package/dist/evaluation/index.js.map +1 -1
  164. package/dist/evaluation/types.d.ts +3 -2
  165. package/dist/evaluation/types.js +2 -5
  166. package/dist/evaluation/types.js.map +1 -1
  167. package/dist/fragments/index.js +2 -338
  168. package/dist/fragments/index.js.map +1 -1
  169. package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
  170. package/dist/harness/check/runtime-render/index.d.ts +35 -15
  171. package/dist/harness/check/runtime-render/index.js +6 -2390
  172. package/dist/harness/check/runtime-render/index.js.map +1 -1
  173. package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
  174. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  175. package/dist/harness/index.d.ts +3 -2
  176. package/dist/harness/index.js +23 -5126
  177. package/dist/harness/index.js.map +1 -1
  178. package/dist/harness/llm-trace-sink.js +2 -24
  179. package/dist/harness/llm-trace-sink.js.map +1 -1
  180. package/dist/harness/prompts.d.ts +2 -2
  181. package/dist/harness/prompts.js +8 -324
  182. package/dist/harness/prompts.js.map +1 -1
  183. package/dist/harness/result-types.d.ts +6 -5
  184. package/dist/harness/result-types.js +2 -0
  185. package/dist/harness/result-types.js.map +1 -1
  186. package/dist/harness/runtime.d.ts +22 -5
  187. package/dist/harness/runtime.js +10 -2313
  188. package/dist/harness/runtime.js.map +1 -1
  189. package/dist/harness/types-public.d.ts +3 -2
  190. package/dist/harness/types-public.js +16 -2954
  191. package/dist/harness/types-public.js.map +1 -1
  192. package/dist/harness/validator-trace-sink.d.ts +3 -2
  193. package/dist/harness/validator-trace-sink.js +2 -26
  194. package/dist/harness/validator-trace-sink.js.map +1 -1
  195. package/dist/hash.js +2 -39
  196. package/dist/hash.js.map +1 -1
  197. package/dist/hashline-5OKREIRM.js +4 -0
  198. package/dist/hashline-5OKREIRM.js.map +1 -0
  199. package/dist/index.d.ts +50 -11
  200. package/dist/index.js +57 -22015
  201. package/dist/index.js.map +1 -1
  202. package/dist/llm-evaluator-PN3WFCS4.js +653 -0
  203. package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
  204. package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
  205. package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
  206. package/dist/patch.d.ts +3 -2
  207. package/dist/patch.js +2 -56
  208. package/dist/patch.js.map +1 -1
  209. package/dist/policy-VCYKGSAC.js +5 -0
  210. package/dist/policy-VCYKGSAC.js.map +1 -0
  211. package/dist/policy.js +2 -35
  212. package/dist/policy.js.map +1 -1
  213. package/dist/primitive-index-2OF6F4TZ.js +131 -0
  214. package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
  215. package/dist/primitive-slice-WTMBLYFJ.js +235 -0
  216. package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
  217. package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
  218. package/dist/provider-adapter-contract.js +10407 -14204
  219. package/dist/provider-adapter-contract.js.map +1 -1
  220. package/dist/provider-adapter.js +2 -44
  221. package/dist/provider-adapter.js.map +1 -1
  222. package/dist/providers/index.d.ts +89 -1
  223. package/dist/providers/index.js +115 -44
  224. package/dist/providers/index.js.map +1 -1
  225. package/dist/raw-C6KBH5I3.js +141 -0
  226. package/dist/raw-C6KBH5I3.js.map +1 -0
  227. package/dist/raw-FXSBXQTZ.js +7 -0
  228. package/dist/raw-FXSBXQTZ.js.map +1 -0
  229. package/dist/raw-MURDUPGK.js +8 -0
  230. package/dist/raw-MURDUPGK.js.map +1 -0
  231. package/dist/raw-T3GW3UVX.js +8 -0
  232. package/dist/raw-T3GW3UVX.js.map +1 -0
  233. package/dist/render-check-host-ZNIS6BJ5.js +5 -0
  234. package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
  235. package/dist/sdk-4JNSDNSR.js +6 -0
  236. package/dist/sdk-4JNSDNSR.js.map +1 -0
  237. package/dist/sdk-DIVJMHCX.js +7 -0
  238. package/dist/sdk-DIVJMHCX.js.map +1 -0
  239. package/dist/sdk-QRJOCRTS.js +7 -0
  240. package/dist/sdk-QRJOCRTS.js.map +1 -0
  241. package/dist/tag-balance-CTHCTRJ7.js +123 -0
  242. package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
  243. package/dist/tools/get-primitives-ts.d.ts +1 -1
  244. package/dist/tools/get-primitives-ts.js +3 -1
  245. package/dist/tools/get-primitives-ts.js.map +1 -1
  246. package/dist/tools/render-check-worker.js +3 -40
  247. package/dist/tools/render-check-worker.js.map +1 -1
  248. package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
  249. package/dist/validation/index.d.ts +2 -2
  250. package/dist/validation/index.js +3 -498
  251. package/dist/validation/index.js.map +1 -1
  252. package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
  253. package/dist/visual-evaluator-TGEXYQTP.js +12 -0
  254. package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
  255. package/dist/workflows.d.ts +3 -2
  256. package/dist/workflows.js +2 -151
  257. package/dist/workflows.js.map +1 -1
  258. package/package.json +20 -16
  259. package/src/adapters/claude/sdk.ts +4 -2
  260. package/src/adapters/claude/tool-bridge.ts +2 -1
  261. package/src/adapters/generation-dispatch.ts +9 -0
  262. package/src/adapters/openai/raw.ts +4 -0
  263. package/src/adapters/openai/sdk.ts +6 -1
  264. package/src/adapters/openai/tokens.ts +21 -7
  265. package/src/adapters/tool-bridge.ts +1 -1
  266. package/src/adapters/tools.ts +1 -1
  267. package/src/advanced/feedback.ts +1 -2
  268. package/src/advanced/generator.ts +4 -3
  269. package/src/boilerplate/free-design-prompt.ts +7 -0
  270. package/src/boilerplate/generate.ts +18 -5
  271. package/src/boilerplate/hard-sections.ts +8 -3
  272. package/src/boilerplate/react-hooks.ts +56 -0
  273. package/src/boilerplate/system-prompt.ts +12 -3
  274. package/src/check/run-tier0.ts +233 -8
  275. package/src/check/type-checker.ts +128 -16
  276. package/src/coding-agent/tools.ts +26 -3
  277. package/src/design-mode.ts +4 -9
  278. package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
  279. package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
  280. package/src/evaluation/axis-checks/checks/tint.ts +197 -0
  281. package/src/evaluation/axis-checks/helpers.ts +121 -0
  282. package/src/evaluation/axis-checks/registry.ts +2 -0
  283. package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
  284. package/src/evaluation/criteria/bank.ts +1 -1
  285. package/src/evaluation/criteria/edge-instruments.ts +14 -0
  286. package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
  287. package/src/evaluation/criteria/edge-v2.ts +242 -0
  288. package/src/evaluation/criteria/resolve.ts +24 -5
  289. package/src/evaluation/index.ts +3 -5
  290. package/src/evaluation/mcp-server.ts +0 -50
  291. package/src/evaluation/types-public.ts +6 -1
  292. package/src/evaluation/visual-evaluator.ts +161 -30
  293. package/src/harness/check/runtime-render/index.ts +28 -4
  294. package/src/harness/check/runtime-render/render-check.ts +20 -21
  295. package/src/harness/claude-agent-sdk-loader.ts +93 -0
  296. package/src/harness/claude-code-login-query.ts +1 -1
  297. package/src/harness/coding/generate-task-runner.ts +1 -0
  298. package/src/harness/coding/run-coding-turn.ts +3 -0
  299. package/src/harness/coding/run-eval-round.ts +9 -0
  300. package/src/harness/llm-router.ts +50 -2
  301. package/src/harness/runtime.ts +17 -0
  302. package/src/index.ts +9 -0
  303. package/src/providers/anthropic-tool-call.ts +263 -0
  304. package/src/providers/index.ts +13 -0
  305. package/src/tools/get-primitives-ts.ts +1 -1
  306. package/src/tools/render-check.ts +4 -4
  307. package/src/validation/primitives.ts +2 -1
  308. package/src/evaluation/loop.ts +0 -218
@@ -18,6 +18,8 @@
18
18
  // blueprint-reuse shape, and brand-bearing color via the closed
19
19
  // `--ggui-color-*` token manifest (no literals, no fallbacks).
20
20
 
21
+ import type { CanvasClass } from "@ggui-ai/design/rendering";
22
+
21
23
  export const DESIGN_MODES = ["constrained", "free"] as const;
22
24
 
23
25
  export type DesignMode = (typeof DESIGN_MODES)[number];
@@ -38,15 +40,8 @@ export const DEFAULT_DESIGN_MODE: DesignMode = "constrained";
38
40
  // breakpoints. `canvasForViewportWidth` is monotonic in width per
39
41
  // display mode (pinned by `design-mode.test.ts`).
40
42
 
41
- export const CANVAS_CLASSES = [
42
- "xs-chat-card",
43
- "mobile-fullscreen-small",
44
- "md",
45
- "lg",
46
- "xl",
47
- ] as const;
48
-
49
- export type CanvasClass = (typeof CANVAS_CLASSES)[number];
43
+ // The vocabulary itself lives in `@ggui-ai/design/rendering`, where the runtime reads it too.
44
+ export { CANVAS_CLASSES, type CanvasClass } from "@ggui-ai/design/rendering";
50
45
 
51
46
  export interface CanvasViewport {
52
47
  readonly width: number;
@@ -12,6 +12,7 @@ import {
12
12
  getEntityCollections,
13
13
  getMutatedEntityCollections,
14
14
  mkIssue,
15
+ seedsFromProp,
15
16
  } from "../helpers.js";
16
17
 
17
18
  function runStateSeededFromProps(input: AxisCheckInput): EvalIssue[] {
@@ -21,10 +22,7 @@ function runStateSeededFromProps(input: AxisCheckInput): EvalIssue[] {
21
22
  const entities = getMutatedEntityCollections(input.contract, all);
22
23
  const issues: EvalIssue[] = [];
23
24
  for (const e of entities) {
24
- const re = new RegExp(
25
- `useState(?:<[^>]*>)?\\s*\\([\\s\\S]{0,400}?props\\.${e.name}\\b`,
26
- );
27
- if (re.test(src)) continue;
25
+ if (seedsFromProp(src, e.name)) continue;
28
26
  issues.push(
29
27
  mkIssue(
30
28
  "state.merge.seeded_from_props",
@@ -12,6 +12,7 @@ import {
12
12
  getInitialValuePropNames,
13
13
  getSubmitActions,
14
14
  mkIssue,
15
+ seedsFromProp,
15
16
  } from "../helpers.js";
16
17
 
17
18
  function runSubmitHookPresent(input: AxisCheckInput): EvalIssue[] {
@@ -79,34 +80,6 @@ function runStateCoversPayload(input: AxisCheckInput): EvalIssue[] {
79
80
  return issues;
80
81
  }
81
82
 
82
- /**
83
- * Whether a `useState` initializer reads `props.<name>`: directly, or through a local bound from
84
- * it (`const initial = normalize(props.initialProfile)` then `useState(initial.name)`, or a
85
- * destructure `const { name } = props.initialProfile ?? {}` then `useState(name)`). The property
86
- * this check guards is "form state is seeded from the prop", not a spelling: ggui#1261's Exp 011
87
- * cell re-seeded correctly through a normalized local, was refused, and spent a turn restoring
88
- * the literal form.
89
- */
90
- function seedsFromProp(src: string, name: string): boolean {
91
- const propRef = `props\\??\\.${name}\\b`;
92
- const initializerReads = (ref: string): boolean =>
93
- new RegExp(`useState(?:<[^>]*>)?\\s*\\([\\s\\S]{0,400}?${ref}`).test(src);
94
- if (initializerReads(propRef)) return true;
95
- const locals = new Set<string>();
96
- for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*(?::[^=]{1,80})?=\\s*[^;]{0,300}?${propRef}`, "g"))) {
97
- const id = m[1];
98
- if (id !== undefined) locals.add(id);
99
- }
100
- for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s*\\{([^}]{1,400})\\}\\s*=\\s*[^;]{0,300}?${propRef}`, "g"))) {
101
- for (const part of (m[1] ?? "").split(",")) {
102
- const id = part.split(":").pop()?.split("=")[0]?.trim();
103
- if (id !== undefined && /^\w+$/.test(id)) locals.add(id);
104
- }
105
- }
106
- for (const id of locals) if (initializerReads(`\\b${id}\\b`)) return true;
107
- return false;
108
- }
109
-
110
83
  function runInitialValuesSeeded(input: AxisCheckInput): EvalIssue[] {
111
84
  if (input.compiledCode === null) return [];
112
85
  const src = input.sourceCode;
@@ -0,0 +1,197 @@
1
+ // packages/ui-gen/src/evaluation/axis-checks/checks/tint.ts
2
+ //
3
+ // ggui#1846 — the categorical `tint` fill's usage rules, as WARNINGS. The kit
4
+ // gives `Box` / `Card` eight theme-tracked steps (`tint-1` … `tint-8`) for DATA
5
+ // categories. The rules (ggui-team-cdo's, taught in the system prompt) are a
6
+ // property of the rendered card, and from source each can only be read by a
7
+ // heuristic over names or JSX structure. A heuristic that FAILS loops a
8
+ // generation that is correct but trips it, so these warn: the model sees the
9
+ // repair line as feedback, nothing blocks, and the follow and false-fire rates
10
+ // are measured. An out-of-range step fails through the prop's own type instead.
11
+ //
12
+ // Not checked here: "the same category bound to two steps in one card". Reading
13
+ // that needs data flow, not the tag's text.
14
+
15
+ import type { EvalIssue } from "../../types-public.js";
16
+ import type { AxisCheck, AxisCheckInput } from "../types.js";
17
+ import { mkIssue } from "../helpers.js";
18
+
19
+ const ALL_RENDER_VALUES = [
20
+ "static", "list", "grid", "spatial", "timeline", "chart", "master-detail",
21
+ ] as const;
22
+
23
+ /** One tinted `Box` / `Card`: the tag, the `tint` value's source text, and the JSX between its open and close tags. */
24
+ export interface TintedElement {
25
+ readonly tag: "Box" | "Card";
26
+ readonly tint: string;
27
+ /** The JSX between the open and close tag; empty when self-closing. */
28
+ readonly inner: string;
29
+ /** For a self-closing element, its following siblings up to the next close tag (a swatch's label); else empty. */
30
+ readonly next: string;
31
+ }
32
+
33
+ interface OpenTag {
34
+ readonly attrs: string;
35
+ readonly end: number;
36
+ readonly selfClosing: boolean;
37
+ }
38
+
39
+ /**
40
+ * Read a JSX open tag whose `<Name` starts at `from`. Braces and string
41
+ * literals are skipped as units, so an `onClick={() => …}` arrow does not end
42
+ * the tag the way a `[^<>]` pattern would.
43
+ */
44
+ function scanOpenTag(src: string, from: number, nameLength: number): OpenTag | null {
45
+ let depth = 0;
46
+ let quote = "";
47
+ for (let i = from + 1 + nameLength; i < src.length; i++) {
48
+ const ch = src[i];
49
+ if (quote !== "") {
50
+ if (ch === "\\") i++;
51
+ else if (ch === quote) quote = "";
52
+ } else if (ch === '"' || ch === "'" || ch === "`") {
53
+ quote = ch;
54
+ } else if (ch === "{") {
55
+ depth++;
56
+ } else if (ch === "}") {
57
+ depth--;
58
+ } else if (ch === ">" && depth === 0) {
59
+ const attrs = src.slice(from + 1 + nameLength, i);
60
+ return { attrs, end: i + 1, selfClosing: attrs.trimEnd().endsWith("/") };
61
+ }
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /** The value of a top-level `tint=` attribute, braces or quotes included; null when absent. */
67
+ function readTintAttr(attrs: string): string | null {
68
+ let depth = 0;
69
+ let quote = "";
70
+ for (let i = 0; i < attrs.length; i++) {
71
+ const ch = attrs[i];
72
+ if (quote !== "") {
73
+ if (ch === "\\") i++;
74
+ else if (ch === quote) quote = "";
75
+ continue;
76
+ }
77
+ if (ch === '"' || ch === "'" || ch === "`") quote = ch;
78
+ else if (ch === "{") depth++;
79
+ else if (ch === "}") depth--;
80
+ else if (depth === 0 && /\s/.test(attrs[i - 1] ?? " ")) {
81
+ const m = /^tint\s*=\s*/.exec(attrs.slice(i));
82
+ if (m === null) continue;
83
+ const v = i + m[0].length;
84
+ const open = attrs[v];
85
+ if (open === '"' || open === "'") {
86
+ const close = attrs.indexOf(open, v + 1);
87
+ return close < 0 ? null : attrs.slice(v, close + 1);
88
+ }
89
+ if (open !== "{") return null;
90
+ let d = 0;
91
+ for (let j = v; j < attrs.length; j++) {
92
+ if (attrs[j] === "{") d++;
93
+ else if (attrs[j] === "}" && --d === 0) return attrs.slice(v, j + 1);
94
+ }
95
+ return null;
96
+ }
97
+ }
98
+ return null;
99
+ }
100
+
101
+ /** Every tinted `Box` / `Card` in the source, with its inner JSX (empty when self-closing). */
102
+ export function findTintedElements(sourceCode: string): TintedElement[] {
103
+ const found: TintedElement[] = [];
104
+ for (const m of sourceCode.matchAll(/<(Box|Card)\b/g)) {
105
+ const tag = m[1] === "Card" ? "Card" : "Box";
106
+ const from = m.index ?? 0;
107
+ const open = scanOpenTag(sourceCode, from, tag.length);
108
+ if (open === null) continue;
109
+ const tint = readTintAttr(open.attrs);
110
+ if (tint === null) continue;
111
+ if (open.selfClosing) {
112
+ const close = sourceCode.indexOf("</", open.end);
113
+ found.push({ tag, tint, inner: "", next: sourceCode.slice(open.end, close < 0 ? sourceCode.length : close) });
114
+ continue;
115
+ }
116
+ // Walk to the matching close tag, counting nested opens of the same name.
117
+ const tagRx = new RegExp(`<(/?)${tag}\\b`, "g");
118
+ tagRx.lastIndex = open.end;
119
+ let depth = 1;
120
+ let end = sourceCode.length;
121
+ for (let t = tagRx.exec(sourceCode); t !== null; t = tagRx.exec(sourceCode)) {
122
+ if (t[1] === "/") {
123
+ if (--depth === 0) {
124
+ end = t.index;
125
+ break;
126
+ }
127
+ continue;
128
+ }
129
+ const nested = scanOpenTag(sourceCode, t.index, tag.length);
130
+ if (nested === null) break;
131
+ tagRx.lastIndex = nested.end;
132
+ if (!nested.selfClosing) depth++;
133
+ }
134
+ found.push({ tag, tint, inner: sourceCode.slice(open.end, end), next: "" });
135
+ }
136
+ return found;
137
+ }
138
+
139
+ const STATE_WORD_RX = /\b(status|state|isLive|live|draft|signal|error|success|warning|danger|isActive|active|selected|isSelected|online|offline|pending|loading|disabled)\b/i;
140
+
141
+ /** Text a reader sees inside the cell: a text primitive, a JSX expression child, or literal JSX text. */
142
+ function hasWords(inner: string): boolean {
143
+ if (/<(Text|Heading|Label|Badge|Caption|Link)\b/.test(inner)) return true;
144
+ if (/\{[^{}]+\}/.test(inner.replace(/\b[a-zA-Z]+=\{[^{}]*\}/g, ""))) return true;
145
+ return />\s*[^<>{}\s][^<>{}]*</.test(`>${inner}<`);
146
+ }
147
+
148
+ function runTintStateBound(input: AxisCheckInput): EvalIssue[] {
149
+ if (input.compiledCode === null) return [];
150
+ const bound = findTintedElements(input.sourceCode).filter((e) => e.tint.startsWith("{") && STATE_WORD_RX.test(e.tint));
151
+ if (bound.length === 0) return [];
152
+ return [
153
+ mkIssue(
154
+ "universal.tint_state_bound",
155
+ `\`tint\` reads as bound to a state (${bound.map((e) => `\`${e.tint}\``).join(", ")}) — tints are for data categories, never for state.`,
156
+ "A status takes live, draft or signal, not a tint. Keep `tint` for a data category (series, lane, kind, group), and show state with its own props.",
157
+ "warn",
158
+ ),
159
+ ];
160
+ }
161
+
162
+ function runTintCategoryInWords(input: AxisCheckInput): EvalIssue[] {
163
+ if (input.compiledCode === null) return [];
164
+ const tinted = findTintedElements(input.sourceCode);
165
+ if (tinted.length === 0) return [];
166
+ if (/\blegend\b/i.test(input.sourceCode)) return [];
167
+ if (tinted.every((e) => hasWords(e.inner) || hasWords(e.next))) return [];
168
+ return [
169
+ mkIssue(
170
+ "universal.tint_category_in_words",
171
+ "A tinted cell carries no words, and the card has no legend — colour is the only way to tell its category.",
172
+ "Give this cell a label, or add a legend to the card. The category is always also in words.",
173
+ "warn",
174
+ ),
175
+ ];
176
+ }
177
+
178
+ const MUTED_TONE_RX = /\btone\s*=\s*["'](subtle|muted)["']/;
179
+
180
+ function runTintMutedText(input: AxisCheckInput): EvalIssue[] {
181
+ if (input.compiledCode === null) return [];
182
+ if (!findTintedElements(input.sourceCode).some((e) => MUTED_TONE_RX.test(e.inner))) return [];
183
+ return [
184
+ mkIssue(
185
+ "universal.tint_muted_text",
186
+ 'Muted text (`tone="subtle"` / `tone="muted"`) sits on a tint — it can read under 4.5:1 there.',
187
+ "Text on a tint is the theme's ink only, one short line (a label, a symbol, a value). Put secondary or muted text beside the cell or in the legend.",
188
+ "warn",
189
+ ),
190
+ ];
191
+ }
192
+
193
+ export const TINT_CHECKS: readonly AxisCheck[] = [
194
+ { id: "universal.tint_state_bound", axis: "render", values: ALL_RENDER_VALUES, run: runTintStateBound },
195
+ { id: "universal.tint_category_in_words", axis: "render", values: ALL_RENDER_VALUES, run: runTintCategoryInWords },
196
+ { id: "universal.tint_muted_text", axis: "render", values: ALL_RENDER_VALUES, run: runTintMutedText },
197
+ ];
@@ -411,6 +411,127 @@ export function isMultiStepPrompt(prompt: string): boolean {
411
411
  // Issue factory
412
412
  // =============================================================================
413
413
 
414
+ /**
415
+ * Whether a `useState` initializer reads `props.<name>`: directly, or through a local bound from
416
+ * it (`const initial = normalize(props.initialProfile)` then `useState(initial.name)`, or a
417
+ * destructure `const { name } = props.initialProfile ?? {}` then `useState(name)`). The property
418
+ * this check guards is "form state is seeded from the prop", not a spelling: ggui#1261's Exp 011
419
+ * cell re-seeded correctly through a normalized local, was refused, and spent a turn restoring
420
+ * the literal form. ggui#1869: a nested generic (`useState<Array<Task>>(…)`), `props?.`,
421
+ * destructured props and destructured component parameters are all correct seeding too, and
422
+ * `state.merge.seeded_from_props` — a FAIL — refused them.
423
+ */
424
+ export function seedsFromProp(src: string, name: string): boolean {
425
+ const propRef = `props\\??\\.${name}\\b`;
426
+ const inits = useStateInitializers(src);
427
+ const anyInitReads = (rx: RegExp): boolean => inits.some((t) => rx.test(t));
428
+ if (anyInitReads(new RegExp(`\\b${propRef}`))) return true;
429
+ const locals = new Set<string>();
430
+ for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*(?::[^=]{1,80})?=\\s*[^;]{0,300}?\\b${propRef}`, "g"))) {
431
+ const id = m[1];
432
+ if (id !== undefined) locals.add(id);
433
+ }
434
+ // `const { a, b } = props.<name> ?? {}` — every name bound reads the prop.
435
+ for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s*\\{([^}]{1,400})\\}\\s*=\\s*[^;]{0,300}?\\b${propRef}`, "g"))) {
436
+ for (const part of (m[1] ?? "").split(",")) {
437
+ const id = part.split(":").pop()?.split("=")[0]?.trim();
438
+ if (id !== undefined && /^\w+$/.test(id)) locals.add(id);
439
+ }
440
+ }
441
+ // `const { <name>: alias } = props` and `export default function Component({ <name>: alias }: Props)` — only the
442
+ // binding of <name>, and only the component's own parameter (a helper's destructured parameter is not its props).
443
+ const fromProps = /(?:const|let|var)\s*\{([^}]{1,400})\}\s*(?::[^=]{1,200})?=\s*props\b(?!\s*\??\.)|export\s+default\s+function\s*\w*\s*\(\s*\{([^}]{1,400})\}/g;
444
+ for (const m of src.matchAll(fromProps)) {
445
+ for (const part of (m[1] ?? m[2] ?? "").split(",")) {
446
+ const [key, alias] = part.split(":");
447
+ if (key?.split("=")[0]?.trim() !== name) continue;
448
+ const id = (alias ?? key).split("=")[0]?.trim();
449
+ if (id !== undefined && /^\w+$/.test(id)) locals.add(id);
450
+ }
451
+ }
452
+ for (const id of locals) if (anyInitReads(new RegExp(`\\b${id}\\b`))) return true;
453
+ return seedsContextSlotFromProp(src, [new RegExp(`\\b${propRef}`), ...[...locals].map((id) => new RegExp(`\\b${id}\\b`))]);
454
+ }
455
+
456
+ /**
457
+ * ggui#1869 — a generated context slot is seeded without any `useState`. The boilerplate
458
+ * declares each slot as `const [value, setValue] = useGguiContext<T>('key')`, the runtime owns
459
+ * its state, and the hook takes no initial value, so a form built on slots can only pre-fill in
460
+ * two ways, and both count:
461
+ * - an effect calls a slot setter with an argument that reads the prop
462
+ * (`useEffect(() => { if (p.name && !name) setName(p.name); }, [props.initialProfile])`);
463
+ * - a slot's value falls back to the prop (`const nameValue = name ?? props.initialProfile?.name`).
464
+ * A setter called from an event handler is not seeding (it runs on a click, not on mount), and a
465
+ * prop read that is merely near a slot is not one either.
466
+ */
467
+ function seedsContextSlotFromProp(src: string, readsProp: readonly RegExp[]): boolean {
468
+ const reads = (text: string): boolean => readsProp.some((rx) => rx.test(text));
469
+ const values: string[] = [];
470
+ const setters: string[] = [];
471
+ for (const m of src.matchAll(/(?:const|let|var)\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=\s*useGguiContext\b/g)) {
472
+ if (m[1] !== undefined) values.push(m[1]);
473
+ if (m[2] !== undefined) setters.push(m[2]);
474
+ }
475
+ if (values.length === 0) return false;
476
+ for (const v of values) {
477
+ for (const m of src.matchAll(new RegExp(`(?<![.\\w$])${v}\\s*\\?\\?\\s*([^;\\n]{1,200})`, "g"))) {
478
+ if (reads(m[1] ?? "")) return true;
479
+ }
480
+ }
481
+ for (const body of callArguments(src, /\buse(?:Layout)?Effect\b/g)) {
482
+ for (const setter of setters) {
483
+ for (const arg of callArguments(body, new RegExp(`\\b${setter}\\b`, "g"))) if (reads(arg)) return true;
484
+ }
485
+ }
486
+ return false;
487
+ }
488
+
489
+ /**
490
+ * The argument text of every `useState(...)` call. A type argument is skipped with its angle
491
+ * brackets balanced (`useState<Record<string, Task[]>>(…)`; an arrow's `=>` inside it is not a
492
+ * close), and the argument runs to its matching paren, string literals skipped as units.
493
+ */
494
+ function useStateInitializers(src: string): string[] {
495
+ return callArguments(src, /\buseState\b/g);
496
+ }
497
+
498
+ /**
499
+ * The argument text of every call to a name `callee` matches (a global regex). A type argument is
500
+ * skipped with its angle brackets balanced, and the argument runs to its matching paren, string
501
+ * literals skipped as units. A match not followed by `(` is not a call and is skipped.
502
+ */
503
+ function callArguments(src: string, callee: RegExp): string[] {
504
+ const out: string[] = [];
505
+ for (const m of src.matchAll(callee)) {
506
+ let i = (m.index ?? 0) + m[0].length;
507
+ while (/\s/.test(src[i] ?? "")) i++;
508
+ if (src[i] === "<") {
509
+ let depth = 0;
510
+ for (; i < src.length; i++) {
511
+ if (src[i] === "<") depth++;
512
+ else if (src[i] === ">" && src[i - 1] !== "=" && --depth === 0) break;
513
+ }
514
+ i++;
515
+ while (/\s/.test(src[i] ?? "")) i++;
516
+ }
517
+ if (src[i] !== "(") continue;
518
+ const start = i + 1;
519
+ let depth = 0;
520
+ let quote = "";
521
+ for (; i < src.length; i++) {
522
+ const ch = src[i];
523
+ if (quote !== "") {
524
+ if (ch === "\\") i++;
525
+ else if (ch === quote) quote = "";
526
+ } else if (ch === '"' || ch === "'" || ch === "`") quote = ch;
527
+ else if (ch === "(") depth++;
528
+ else if (ch === ")" && --depth === 0) break;
529
+ }
530
+ out.push(src.slice(start, i));
531
+ }
532
+ return out;
533
+ }
534
+
414
535
  export function mkIssue(
415
536
  subcategory: string,
416
537
  description: string,
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { AxisCheck } from "./types.js";
7
7
  import { UNIVERSAL_CHECKS } from "./checks/universal.js";
8
+ import { TINT_CHECKS } from "./checks/tint.js";
8
9
  import { STATE_MERGE_CHECKS } from "./checks/state-merge.js";
9
10
  import { STATE_UI_AFFORDANCE_CHECKS } from "./checks/state-ui-affordance.js";
10
11
  import { REALTIME_CHECKS } from "./checks/realtime.js";
@@ -16,6 +17,7 @@ import { EXTRA_CHECKS } from "./extras.js";
16
17
 
17
18
  export const REGISTRY: readonly AxisCheck[] = [
18
19
  ...UNIVERSAL_CHECKS,
20
+ ...TINT_CHECKS,
19
21
  ...STATE_MERGE_CHECKS,
20
22
  ...STATE_UI_AFFORDANCE_CHECKS,
21
23
  ...REALTIME_CHECKS,