@ggui-ai/ui-gen 0.16.0 → 0.18.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 (105) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +6 -3
  2. package/dist/adapters/generation-dispatch.js +5161 -4312
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +8 -5
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +12 -8
  7. package/dist/advanced/index.js +4685 -3801
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +8 -12
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.d.ts +5 -2
  12. package/dist/boilerplate.js +120 -13
  13. package/dist/boilerplate.js.map +1 -1
  14. package/dist/check/index.d.ts +2 -2
  15. package/dist/check/index.js +72 -0
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/compose.js +1 -1
  18. package/dist/compose.js.map +1 -1
  19. package/dist/{create-ui-generator-CCIeQaEe.d.ts → create-ui-generator-C8Ki9Wz1.d.ts} +18 -2
  20. package/dist/{design-mode-BHEE7flj.d.ts → design-mode-DLufhrNp.d.ts} +0 -6
  21. package/dist/{dispatch-BA5dJcg-.d.ts → dispatch-D2htzi9h.d.ts} +2 -2
  22. package/dist/evaluation/axis-checks/index.d.ts +4 -4
  23. package/dist/evaluation/axis-checks/index.js +347 -13
  24. package/dist/evaluation/axis-checks/index.js.map +1 -1
  25. package/dist/evaluation/axis-checks/registry.d.ts +2 -2
  26. package/dist/evaluation/axis-checks/registry.js +302 -1
  27. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  28. package/dist/evaluation/index.d.ts +160 -8
  29. package/dist/evaluation/index.js +752 -81
  30. package/dist/evaluation/index.js.map +1 -1
  31. package/dist/fragments/index.js +1 -1
  32. package/dist/fragments/index.js.map +1 -1
  33. package/dist/{generate-8L_bc2iP.d.ts → generate-9IeNT4ZY.d.ts} +1 -1
  34. package/dist/harness/check/runtime-render/index.d.ts +3 -2
  35. package/dist/harness/check/runtime-render/index.js +8 -12
  36. package/dist/harness/check/runtime-render/index.js.map +1 -1
  37. package/dist/harness/index.d.ts +3 -2
  38. package/dist/harness/index.js +410 -255
  39. package/dist/harness/index.js.map +1 -1
  40. package/dist/harness/prompts.d.ts +2 -2
  41. package/dist/harness/prompts.js +7 -3
  42. package/dist/harness/prompts.js.map +1 -1
  43. package/dist/harness/result-types.d.ts +3 -3
  44. package/dist/harness/runtime.d.ts +13 -4
  45. package/dist/harness/runtime.js +131 -20
  46. package/dist/harness/runtime.js.map +1 -1
  47. package/dist/harness/types-public.d.ts +13 -2
  48. package/dist/harness/types-public.js +410 -255
  49. package/dist/harness/types-public.js.map +1 -1
  50. package/dist/harness/validator-trace-sink.d.ts +2 -2
  51. package/dist/index.d.ts +29 -7
  52. package/dist/index.js +4691 -3805
  53. package/dist/index.js.map +1 -1
  54. package/dist/patch.d.ts +3 -2
  55. package/dist/{prompts-k6m6reqe.d.ts → prompts-YU9piOgs.d.ts} +12 -46
  56. package/dist/styling-profile-CxfvV48g.d.ts +35 -0
  57. package/dist/tools/get-primitives-ts.d.ts +1 -1
  58. package/dist/tools/get-primitives-ts.js +1 -1
  59. package/dist/tools/get-primitives-ts.js.map +1 -1
  60. package/dist/{types-public-D24JLJgl.d.ts → types-public-DVSssdc-.d.ts} +39 -3
  61. package/dist/validation/index.d.ts +1 -1
  62. package/dist/validation/index.js +1 -1
  63. package/dist/validation/index.js.map +1 -1
  64. package/dist/workflows.d.ts +3 -2
  65. package/package.json +12 -12
  66. package/src/adapters/generation-dispatch.ts +6 -1
  67. package/src/boilerplate/free-design-prompt.ts +9 -0
  68. package/src/boilerplate/generate.ts +8 -1
  69. package/src/boilerplate/hard-sections.ts +45 -1
  70. package/src/boilerplate/styling-profile.ts +166 -0
  71. package/src/boilerplate/system-prompt.ts +22 -8
  72. package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +5 -3
  73. package/src/boilerplate/templates/layouts/fullscreen-mobile.tsx.tmpl +3 -3
  74. package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +5 -3
  75. package/src/check/run-tier0.ts +154 -0
  76. package/src/contract-context.ts +28 -14
  77. package/src/create-harness.ts +4 -0
  78. package/src/create-ui-generator.ts +41 -2
  79. package/src/design-mode.ts +11 -1
  80. package/src/design-system-docs.ts +7 -4
  81. package/src/effort.ts +48 -0
  82. package/src/evaluation/axis-checks/checks/grid.ts +148 -0
  83. package/src/evaluation/axis-checks/checks/universal.ts +308 -0
  84. package/src/evaluation/axis-checks/dispatch.ts +78 -13
  85. package/src/evaluation/axis-checks/helpers.ts +12 -0
  86. package/src/evaluation/axis-checks/registry.ts +2 -0
  87. package/src/evaluation/design-identity.ts +96 -0
  88. package/src/evaluation/index.ts +17 -0
  89. package/src/evaluation/llm-evaluator.ts +13 -1
  90. package/src/evaluation/theme-css.ts +14 -0
  91. package/src/evaluation/types-public.ts +35 -1
  92. package/src/evaluation/visual-evaluator.ts +478 -45
  93. package/src/fragments/render.ts +1 -1
  94. package/src/harness/check/runtime-render/prepare-mockup.ts +18 -11
  95. package/src/harness/coding/init-session.ts +1 -0
  96. package/src/harness/coding/run-coding-turn.ts +6 -14
  97. package/src/harness/coding/run-eval-round.ts +31 -3
  98. package/src/harness/runtime.ts +10 -0
  99. package/src/harness/types-public.ts +11 -0
  100. package/src/index.ts +2 -0
  101. package/src/pin-fixtures.ts +98 -0
  102. package/src/run-check.ts +10 -10
  103. package/src/tools/get-primitives-ts.ts +1 -1
  104. package/src/tools.ts +2 -1
  105. package/src/validation/primitives.ts +1 -1
@@ -0,0 +1,96 @@
1
+ // The design tree a judge paints with is an INPUT to the judgement and a
2
+ // part of its identity (ggui#1042, D3): a judge-only re-judge renders every
3
+ // arm with the tree on disk, so the row must say WHICH tree — `srcSha256`
4
+ // over every RENDERING INPUT under the design package's `src`, sorted by
5
+ // relative path — and the local judge path must accept the tree explicitly.
6
+ // A served hello is compared to a judged one only when this hash and the
7
+ // runtime's design agree (`design@judge == design@runtime`, generation-triad.md).
8
+ //
9
+ // Rendering inputs only: tests (`*.test.*`, `__tests__/`), stories
10
+ // (`*.stories.*`) and docs (`*.md`) paint nothing, and a built image ships
11
+ // the design package without them — the first rows on candidate 34 stamped
12
+ // `e2dc89e4…` (123 files) while a checkout of the same git tree computed
13
+ // `2cd62cb7…` (199 files). One identity must read the same on every host
14
+ // that carries the same rendering inputs.
15
+ import { createHash } from 'node:crypto';
16
+ import { readFileSync, readdirSync, statSync } from 'node:fs';
17
+ import { resolve } from 'node:path';
18
+
19
+ export interface JudgeDesignIdentity {
20
+ /** Absolute path of the design `src` tree the judge bundled against. */
21
+ readonly src: string;
22
+ /** sha256 over every rendering input under `src` (see {@link isDesignRenderingInput}) — sorted relative path, then contents. */
23
+ readonly srcSha256: string;
24
+ }
25
+
26
+ /**
27
+ * Whether a file under the design `src` tree is something the judge PAINTS
28
+ * with. Tests, stories and docs are not — and a built image carries the
29
+ * package without them, so they must not move the identity.
30
+ */
31
+ export function isDesignRenderingInput(relativePath: string): boolean {
32
+ const rel = relativePath.split('\\').join('/');
33
+ if (rel.startsWith('__tests__/') || rel.includes('/__tests__/')) return false;
34
+ if (/\.test\.[cm]?[jt]sx?$/.test(rel)) return false;
35
+ if (/\.stories\.[cm]?[jt]sx?$/.test(rel)) return false;
36
+ if (rel.endsWith('.md')) return false;
37
+ return true;
38
+ }
39
+
40
+ /** sha256 over every rendering input under `root` (sorted relative path + contents) — the identity of a design tree as it paints. */
41
+ export function designTreeSha256(root: string): string {
42
+ const files: string[] = [];
43
+ const walk = (dir: string): void => {
44
+ for (const name of readdirSync(dir).sort()) {
45
+ const full = resolve(dir, name);
46
+ if (statSync(full).isDirectory()) walk(full);
47
+ else if (isDesignRenderingInput(full.slice(root.length + 1))) files.push(full);
48
+ }
49
+ };
50
+ walk(root);
51
+ const h = createHash('sha256');
52
+ for (const f of files) {
53
+ h.update(f.slice(root.length + 1) + '\n');
54
+ h.update(readFileSync(f));
55
+ h.update('\n');
56
+ }
57
+ return h.digest('hex');
58
+ }
59
+
60
+ const identityCache = new Map<string, JudgeDesignIdentity | null>();
61
+
62
+ /**
63
+ * The identity of the tree at `src`, computed once per process per path (a
64
+ * judge run paints hundreds of frames from one tree).
65
+ *
66
+ * `null` when the tree cannot be READ (ggui#1110): a deployed image ships the
67
+ * design package as built output with no `src/`, and an identity is a receipt
68
+ * about a judgement — never a precondition for making one. Before this,
69
+ * `scandir` threw ENOENT inside the evaluation round wherever the package ships
70
+ * built output only, the round returned no evaluation at all, and a bar refused
71
+ * a card that had cleared it. A row without the receipt says so; a round that cannot
72
+ * stamp one still runs.
73
+ */
74
+ export function judgeDesignIdentity(src: string): JudgeDesignIdentity | null {
75
+ const key = resolve(src);
76
+ const cached = identityCache.get(key);
77
+ if (cached !== undefined) return cached;
78
+ let identity: JudgeDesignIdentity | null;
79
+ try {
80
+ identity = { src: key, srcSha256: designTreeSha256(key) };
81
+ } catch (e) {
82
+ // Unreadable tree: no source directory (a built package), no permission,
83
+ // a path that is not a directory. The reason is logged ONCE per path —
84
+ // the judgement proceeds unstamped rather than not at all.
85
+ const reason = e instanceof Error ? e.message : String(e);
86
+ console.warn(`[visual-eval] design tree unreadable at ${key} — the judgement carries no design identity (${reason})`);
87
+ identity = null;
88
+ }
89
+ identityCache.set(key, identity);
90
+ return identity;
91
+ }
92
+
93
+ /** Test seam: forget cached identities (a test that rewrites a tree between runs). */
94
+ export function resetJudgeDesignIdentityCache(): void {
95
+ identityCache.clear();
96
+ }
@@ -23,6 +23,7 @@ export type {
23
23
  EvalCategory,
24
24
  EvalResult,
25
25
  VisualEvalSummary,
26
+ CanvasJudgeRecord,
26
27
  CanvasVisualSummary,
27
28
  EvalCriterion,
28
29
  CriterionCoverage,
@@ -92,6 +93,16 @@ export {
92
93
  captureScreenshot,
93
94
  resolveLaunchOptions,
94
95
  runVisualEvaluation,
96
+ resolveDesignPackageDir,
97
+ runVisualEvaluationDetailed,
98
+ VISUAL_JUDGE_MAX_OUTPUT_TOKENS,
99
+ VISUAL_JUDGE_PROMPT_VERSION,
100
+ VISUAL_JUDGE_PROMPT_DIGEST,
101
+ VISUAL_EVAL_PROMPT,
102
+ captureScreenshotDetailed,
103
+ canvasFitPolicy,
104
+ canvasOverflowIssue,
105
+ CONTENT_HEIGHT_EXPRESSION,
95
106
  runVisualEval,
96
107
  summarizeVisualResult,
97
108
  } from './visual-evaluator.js';
@@ -101,7 +112,11 @@ export type {
101
112
  VisualEvalDeps,
102
113
  VisualEvalOutcome,
103
114
  VisualEvaluationResult,
115
+ VisualEvalDetailed,
116
+ ScreenshotAttempt,
104
117
  CanvasVisualResult,
118
+ CanvasFitPolicy,
119
+ CaptureMode,
105
120
  ScreenshotDeps,
106
121
  ScreenshotLauncher,
107
122
  ScreenshotBrowser,
@@ -130,3 +145,5 @@ export type {
130
145
  CanvasDisplayMode,
131
146
  CanvasDescriptor,
132
147
  } from '../design-mode.js';
148
+ export { cssTokensForAppTheme } from './theme-css.js';
149
+ export { designTreeSha256, judgeDesignIdentity, resetJudgeDesignIdentityCache, type JudgeDesignIdentity } from './design-identity.js';
@@ -14,6 +14,8 @@
14
14
  // Caching: Anthropic ephemeral cache, OpenAI auto-cache, Google explicit cache.
15
15
  // Same system prompt across all 7 calls → first call caches, 6 remaining hit cache.
16
16
 
17
+ import { buildStylingProfileJudgeBlock } from '../boilerplate/styling-profile.js';
18
+ import type { GenerationProfileInput } from '../boilerplate/styling-profile.js';
17
19
  import type {
18
20
  EvalIssue,
19
21
  EvalResult,
@@ -75,6 +77,8 @@ export interface LLMEvalContext {
75
77
  * means `constrained` (byte-identical to the pre-`designMode` prompt).
76
78
  */
77
79
  designMode?: DesignMode;
80
+ /** The app's generation profile (#991) — the judge reads the same text the coder was given. */
81
+ profile?: GenerationProfileInput;
78
82
  }
79
83
 
80
84
  export interface LLMEvalConfig {
@@ -119,6 +123,8 @@ export interface EvalContext {
119
123
  designSystemSummary?: string;
120
124
  /** See `LLMEvalContext.designMode`. */
121
125
  designMode?: DesignMode;
126
+ /** See `LLMEvalContext.profile`. */
127
+ profile?: GenerationProfileInput;
122
128
  }
123
129
 
124
130
  /**
@@ -129,6 +135,7 @@ export interface EvalContext {
129
135
  */
130
136
  const FREE_STYLING_RULES = `## Styling Rules (important for evaluation)
131
137
  - Color: every color must be a bare \`var(--ggui-color-*)\` token — a hardcoded #hex, rgb()/hsl() or CSS named color is a fail, and so is a literal fallback inside \`var(--ggui-*, …)\`.
138
+ - Accent text (eyebrow, tagline, link, label) painted with a bare \`var(--ggui-color-primary-300…600)\` is a readability fail — the accent ink is \`var(--ggui-color-link)\`; a tint as text on a primary fill and a deep stop (700–900) on a light tint are fine
132
139
  - Spacing, typography, radius, shadow geometry and layout values MAY be literals (\`padding: '20px'\`, \`fontSize: 18\`, \`borderRadius: 12\`) — NEVER flag them.
133
140
  - Raw HTML elements with inline styles or \`<style>\` blocks are a first-class choice — never flag "not using the design system" or reward using it.
134
141
  - When \`@ggui-ai/design\` primitives ARE used, their enum/scale props (\`variant="primary"\`, \`size="lg"\`, \`gap="md"\`, \`tone="muted"\`, \`surface="accent"\`) are valid.
@@ -136,7 +143,8 @@ const FREE_STYLING_RULES = `## Styling Rules (important for evaluation)
136
143
 
137
144
  const CONSTRAINED_STYLING_RULES = `## Design System Rules (important for evaluation)
138
145
  - Spacing props (\`gap\`, \`padding\`, \`margin\`) take a t-shirt-scale name: \`gap="md"\`, \`padding="lg"\` (\`none|xs|sm|md|lg|xl|2xl\`). **These ARE design tokens** — each resolves to a \`--ggui-spacing-*\` variable. A raw \`var(--ggui-spacing-*)\` string is an accepted escape hatch. NEVER flag a scale name as "hardcoded". Only a numeric prop (\`padding={24}\`) or a raw CSS length (\`gap="13px"\`) bypasses the scale and should be warned.
139
- - All colors must use CSS variables: \`color="var(--ggui-color-primary-600)"\` — hardcoded #hex or rgb() is a fail
146
+ - All colors must use CSS variables: \`color="var(--ggui-color-onContainer)"\` — hardcoded #hex or rgb() is a fail
147
+ - Accent text (eyebrow, tagline, link, label) painted with a bare \`primary-300…600\` stop is a readability fail — the accent ink is \`var(--ggui-color-link)\` (or \`tone="emphasized"\`); tints as text on a primary fill and deep stops on a light tint are fine
140
148
  - Component props that take enum/scale values — \`variant="primary"\`, \`size="lg"\`, \`gap="md"\`, \`padding="lg"\`, \`shadow="md"\`, \`radius="lg"\`, \`tone="muted"\`, \`surface="accent"\` — are all design system tokens and are always valid
141
149
  - One \`export default function Component\` — helper components as named functions above it are fine`;
142
150
 
@@ -323,6 +331,8 @@ ${free ? FREE_RAW_CLICK_TARGET_GAP : CONSTRAINED_RAW_CLICK_TARGET_GAP}
323
331
  - Inverted or skipped heading levels (an \`<Heading level={3}>\` rendered
324
332
  before the page's \`<Heading level={1}>\`).`);
325
333
 
334
+ const profileBlock = buildStylingProfileJudgeBlock(ctx.profile);
335
+ if (profileBlock.length > 0) parts.push(profileBlock);
326
336
  return parts.join('\n\n');
327
337
  }
328
338
 
@@ -733,6 +743,7 @@ export async function preWarmEval(
733
743
  contract: context.contract,
734
744
  designSystemSummary: context.designContext,
735
745
  designMode: context.designMode,
746
+ profile: context.profile,
736
747
  });
737
748
 
738
749
  return { motherPrompt, dynamicCriteria: [] };
@@ -837,6 +848,7 @@ export async function runLLMEvaluation(
837
848
  contract: context.contract,
838
849
  designSystemSummary: context.designContext,
839
850
  designMode,
851
+ profile: context.profile,
840
852
  });
841
853
 
842
854
  // Add line numbers to source
@@ -0,0 +1,14 @@
1
+ import type { AppTheme } from '@ggui-ai/protocol';
2
+ import { composeThemeCss, getCssTokens } from '@ggui-ai/design/rendering';
3
+
4
+ /**
5
+ * The stylesheet a visual judge renders under for an app: the app's theme
6
+ * composed the way the runtime composes it (`composeThemeCss`, layer `page`
7
+ * — one composition, never a second), so a judged canvas paints the tokens a
8
+ * visitor's page paints. Absent theme ⇒ the design's default tokens,
9
+ * byte-for-byte what the judge used before.
10
+ */
11
+ export function cssTokensForAppTheme(theme: AppTheme | undefined, mode: 'light' | 'dark' = 'light'): string {
12
+ if (theme === undefined) return getCssTokens(mode);
13
+ return composeThemeCss({ layer: 'page', mode: theme.mode ?? mode, appTheme: theme });
14
+ }
@@ -188,13 +188,43 @@ export interface EvalResult {
188
188
  }
189
189
 
190
190
  /** One canvas's visual verdict, PNG-free — see `EvalResult.visual`. */
191
+ /**
192
+ * How one canvas's `score` was reached (ggui#1072): `k` vision calls on the
193
+ * SAME captured frame, aggregated by `rule`. Always present — `k: 1` ⇒
194
+ * `samples: [score]`, `sigma: 0`, `notes: [critique]` — so one shape serves
195
+ * a single judgement and a median of many. `samples`/`notes` carry only the
196
+ * calls the judge could parse (in call order); `k` stays the requested count,
197
+ * so a short `samples` is visible on the row rather than filled in.
198
+ */
199
+ export interface CanvasJudgeRecord {
200
+ readonly k: number;
201
+ readonly rule: 'median';
202
+ readonly samples: number[];
203
+ /** Population σ of `samples` (0 when one sample). */
204
+ readonly sigma: number;
205
+ /** The judge's per-canvas critique per sample — the words beside a swing. */
206
+ readonly notes: string[];
207
+ }
208
+
191
209
  export interface CanvasVisualSummary {
192
210
  canvas: CanvasClass;
193
211
  viewport: { width: number; height: number };
194
212
  /** The judge's weighted score at this canvas (0-100). */
195
213
  score: number;
196
- /** `score >= passThreshold`. */
214
+ /** `score >= passThreshold`, and on the inline card: no overflow (ggui#1027). */
197
215
  passed: boolean;
216
+ /** The document's scroll height at this canvas, CSS px; `null` when unmeasurable (ggui#1027). */
217
+ contentHeight: number | null;
218
+ /** `contentHeight > viewport.height` — measured on every canvas, judged per `canvasFitPolicy`. */
219
+ overflow: boolean;
220
+ /** How `score` was reached — always present (ggui#1072). */
221
+ judge: CanvasJudgeRecord;
222
+ /**
223
+ * How the judge composed the mount (ggui#1100): `'fill'` on every
224
+ * fullscreen canvas — the served runtime's fit, the root stretched to
225
+ * the frame; absent on the inline card, which keeps its natural height.
226
+ */
227
+ fit?: 'fill';
198
228
  }
199
229
 
200
230
  /** The visual leg's per-canvas summary — see `EvalResult.visual`. */
@@ -204,6 +234,10 @@ export interface VisualEvalSummary {
204
234
  /** Every canvas passed. */
205
235
  passed: boolean;
206
236
  canvases: CanvasVisualSummary[];
237
+ /** The design tree the judge painted with (ggui#1042): `src` + `srcSha256` — `design@judge`. */
238
+ design?: { readonly src: string; readonly srcSha256: string };
239
+ /** The mode the judge's tokens were composed in, when the caller said (ggui#1076). */
240
+ themeMode?: 'light' | 'dark';
207
241
  }
208
242
 
209
243
  // ─── Quality mode ──────────────────────────────────────────────────────────