@ggui-ai/ui-gen 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/generation-dispatch.js +180 -18
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/advanced/index.d.ts +1 -1
- package/dist/advanced/index.js +180 -18
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +146 -0
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +7 -7
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.js +27 -11
- package/dist/check/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +1 -1
- package/dist/harness/check/runtime-render/index.js +146 -0
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +126 -0
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.js +7 -7
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/runtime.js +7 -7
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.js +7 -7
- package/dist/harness/types-public.js.map +1 -1
- package/dist/index.js +180 -18
- package/dist/index.js.map +1 -1
- package/dist/provider-adapter-contract.js +1161 -387
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/{render-check-BZoIE8-u.d.ts → render-check-WfOVO9kU.d.ts} +1 -1
- package/package.json +13 -13
- package/src/boilerplate/system-prompt.ts +7 -7
- package/src/check/run-tier0.ts +35 -16
- package/src/harness/check/runtime-render/adapter.ts +28 -0
- package/src/harness/check/runtime-render/render-check.ts +224 -1
|
@@ -3720,17 +3720,33 @@ ${errorDetail}`,
|
|
|
3720
3720
|
}
|
|
3721
3721
|
}
|
|
3722
3722
|
const hexMatch = line.match(/#[0-9a-fA-F]{3,8}\b/);
|
|
3723
|
-
if (hexMatch && !
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3723
|
+
if (hexMatch && !assetEscapedLines.has(lineNum)) {
|
|
3724
|
+
const fallbackVarMatch = line.match(
|
|
3725
|
+
/var\((--ggui-[a-zA-Z0-9-]+)\s*,[^)]*#[0-9a-fA-F]{3,8}/
|
|
3726
|
+
);
|
|
3727
|
+
if (fallbackVarMatch) {
|
|
3728
|
+
issues.push({
|
|
3729
|
+
tier: 0,
|
|
3730
|
+
result: "fail",
|
|
3731
|
+
category: "tokens",
|
|
3732
|
+
subcategory: "token-fallback",
|
|
3733
|
+
severity: "critical",
|
|
3734
|
+
description: `Literal fallback "${hexMatch[0]}" inside a token reference \u2014 the runtime injects every token, and the fallback paints the wrong brand exactly when the theme matters most.`,
|
|
3735
|
+
fix: `Drop the fallback: write var(${fallbackVarMatch[1]}) bare. If the token might not exist, that is a vocabulary problem to fix by choosing a token from the design-system docs, never by a literal.`,
|
|
3736
|
+
line: lineNum
|
|
3737
|
+
});
|
|
3738
|
+
} else if (!line.includes("// fallback")) {
|
|
3739
|
+
issues.push({
|
|
3740
|
+
tier: 0,
|
|
3741
|
+
result: "fail",
|
|
3742
|
+
category: "tokens",
|
|
3743
|
+
subcategory: "hex-color",
|
|
3744
|
+
severity: "critical",
|
|
3745
|
+
description: `Hardcoded color "${hexMatch[0]}" breaks theme switching \u2014 use design tokens.`,
|
|
3746
|
+
fix: `Replace with a primitive variant (Button variant="primary"|Badge variant="success"|...) OR a bare token reference like var(--ggui-color-primary-500) \u2014 never a literal fallback. Hardcoded colors mean the operator's theme has no effect on this surface.`,
|
|
3747
|
+
line: lineNum
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3734
3750
|
}
|
|
3735
3751
|
const colorFnMatch = line.match(/\b(rgba?|hsla?)\s*\(/);
|
|
3736
3752
|
if (colorFnMatch && !line.includes("var(--ggui-") && !line.includes("// fallback") && !line.includes("$value") && !assetEscapedLines.has(lineNum)) {
|
|
@@ -11195,15 +11211,15 @@ EVERY color, spacing, typography, shadow, and radius value MUST come from design
|
|
|
11195
11211
|
|
|
11196
11212
|
MANDATORY:
|
|
11197
11213
|
1. NEVER use hardcoded hex colors like \`#7c3aed\` \u2014 ONLY \`var(--ggui-color-*)\` tokens.
|
|
11198
|
-
2. NEVER use CSS gradients with custom colors. If you need a gradient: \`linear-gradient(to bottom, var(--ggui-color-primary-500
|
|
11214
|
+
2. NEVER use CSS gradients with custom colors. If you need a gradient: \`linear-gradient(to bottom, var(--ggui-color-primary-500), var(--ggui-color-primary-700))\`.
|
|
11199
11215
|
3. NEVER invent your own palette. The system provides primary, neutral, success, warning, error, and info \u2014 use ONLY these.
|
|
11200
|
-
4.
|
|
11216
|
+
4. NEVER include literal fallback values in color tokens \u2014 write \`var(--ggui-color-primary-600)\` bare, never \`var(--ggui-color-primary-600, #0284c7)\`. The runtime injects every token; a literal fallback paints the wrong brand exactly when the operator's theme matters most.
|
|
11201
11217
|
|
|
11202
11218
|
Token categories:
|
|
11203
|
-
- Brand: \`var(--ggui-color-primary-600
|
|
11204
|
-
- Text: \`var(--ggui-color-onSurface
|
|
11205
|
-
- Backgrounds: \`var(--ggui-color-surface
|
|
11206
|
-
- Borders: \`var(--ggui-color-outline
|
|
11219
|
+
- Brand: \`var(--ggui-color-primary-600)\`, \`var(--ggui-color-primary-50)\`
|
|
11220
|
+
- Text: \`var(--ggui-color-onSurface)\`, \`var(--ggui-color-onSurfaceVariant)\`
|
|
11221
|
+
- Backgrounds: \`var(--ggui-color-surface)\`, \`var(--ggui-color-surfaceVariant)\`
|
|
11222
|
+
- Borders: \`var(--ggui-color-outline)\`
|
|
11207
11223
|
- Spacing: \`var(--ggui-spacing-4, 16px)\`, \`var(--ggui-spacing-6, 24px)\`
|
|
11208
11224
|
- Typography: \`var(--ggui-font-size-sm, 14px)\`, \`var(--ggui-font-weight-semibold, 600)\`
|
|
11209
11225
|
- Shadows: \`var(--ggui-shape-shadow-sm)\`, \`var(--ggui-shape-shadow-md)\`, \`var(--ggui-shape-shadow-lg)\`
|
|
@@ -11388,7 +11404,7 @@ Hero sections should feel airy. Use \`padding="xl"\` (32px) on the main card, no
|
|
|
11388
11404
|
|
|
11389
11405
|
- [ ] Imports ONLY from: react, @ggui-ai/design, @ggui-ai/wire
|
|
11390
11406
|
- [ ] No raw HTML elements (\`<button>\`, \`<input>\`, \`<div>\` for layout) \u2014 uses primitives
|
|
11391
|
-
- [ ] ZERO hardcoded hex colors \u2014 every color is \`var(--ggui-color
|
|
11407
|
+
- [ ] ZERO hardcoded hex colors \u2014 every color is a bare \`var(--ggui-color-*)\`, no literal fallback
|
|
11392
11408
|
- [ ] No raw pixel values for spacing \u2014 all via \`var(--ggui-spacing-*)\` tokens
|
|
11393
11409
|
- [ ] Primary palette used throughout (headers, borders, icons) \u2014 not just buttons
|
|
11394
11410
|
- [ ] Typed Props interface exported; request-specific data is a prop with default
|
|
@@ -14237,6 +14253,22 @@ async function runRenderCheckInProcess(input) {
|
|
|
14237
14253
|
if (issue) issues.push(issue);
|
|
14238
14254
|
}
|
|
14239
14255
|
}
|
|
14256
|
+
if (input.contract?.actionSpec) {
|
|
14257
|
+
for (const [name, entry] of Object.entries(input.contract.actionSpec)) {
|
|
14258
|
+
const idField = idLikeRequiredStringField(
|
|
14259
|
+
entry.schema
|
|
14260
|
+
);
|
|
14261
|
+
if (idField === null) continue;
|
|
14262
|
+
const issue = await checkSelectionIdentity({
|
|
14263
|
+
container,
|
|
14264
|
+
probe,
|
|
14265
|
+
user,
|
|
14266
|
+
actionName: name,
|
|
14267
|
+
idField
|
|
14268
|
+
});
|
|
14269
|
+
if (issue) issues.push(issue);
|
|
14270
|
+
}
|
|
14271
|
+
}
|
|
14240
14272
|
if (input.contract?.propsSpec) {
|
|
14241
14273
|
for (const issue of checkPropCoverage({
|
|
14242
14274
|
container,
|
|
@@ -14246,6 +14278,48 @@ async function runRenderCheckInProcess(input) {
|
|
|
14246
14278
|
issues.push(issue);
|
|
14247
14279
|
}
|
|
14248
14280
|
}
|
|
14281
|
+
if (input.contract?.propsSpec) {
|
|
14282
|
+
const propsSpec5c = input.contract.propsSpec;
|
|
14283
|
+
const normalize = (s) => (s ?? "").replace(/\s+/g, " ").trim();
|
|
14284
|
+
const baseText = normalize(container.textContent);
|
|
14285
|
+
for (const [propName, entry] of Object.entries(propsSpec5c.properties)) {
|
|
14286
|
+
if (!entry.required) continue;
|
|
14287
|
+
const orig = input.mockupProps[propName];
|
|
14288
|
+
if (typeof orig !== "string" && typeof orig !== "number") continue;
|
|
14289
|
+
const marker = pickScalarMarker(orig);
|
|
14290
|
+
if (marker === null || !baseText.includes(marker)) continue;
|
|
14291
|
+
const varied = typeof orig === "string" ? "Zzyzx Qw" : orig + 137731;
|
|
14292
|
+
const variedProbe = createProbe();
|
|
14293
|
+
const variedConfig = createProbeWireConfig(variedProbe);
|
|
14294
|
+
let variedResult;
|
|
14295
|
+
try {
|
|
14296
|
+
variedResult = render(
|
|
14297
|
+
React.createElement(GguiWireProvider, {
|
|
14298
|
+
config: variedConfig,
|
|
14299
|
+
children: React.createElement(ProbeErrorBoundary, {
|
|
14300
|
+
children: React.createElement(Component, {
|
|
14301
|
+
...input.mockupProps,
|
|
14302
|
+
[propName]: varied
|
|
14303
|
+
})
|
|
14304
|
+
})
|
|
14305
|
+
})
|
|
14306
|
+
);
|
|
14307
|
+
} catch {
|
|
14308
|
+
continue;
|
|
14309
|
+
}
|
|
14310
|
+
await flushPromises();
|
|
14311
|
+
const variedText = normalize(variedResult.container.textContent);
|
|
14312
|
+
variedResult.unmount();
|
|
14313
|
+
if (variedText === baseText) {
|
|
14314
|
+
issues.push({
|
|
14315
|
+
check: "prop-sensitivity",
|
|
14316
|
+
outcome: "failed",
|
|
14317
|
+
subject: propName,
|
|
14318
|
+
reason: `props.${propName} is displayed ('${marker}' appears in the DOM) but changing its value to '${String(varied)}' left the rendered output BYTE-IDENTICAL \u2014 the value is baked into the component instead of derived from the prop. A cached reuse of this blueprint under a different ${propName} would show the original value.`
|
|
14319
|
+
});
|
|
14320
|
+
}
|
|
14321
|
+
}
|
|
14322
|
+
}
|
|
14249
14323
|
if (input.contract?.streamSpec) {
|
|
14250
14324
|
const streamSpec = input.contract.streamSpec;
|
|
14251
14325
|
for (const [name] of Object.entries(streamSpec)) {
|
|
@@ -14577,6 +14651,74 @@ async function checkActionWiring(input) {
|
|
|
14577
14651
|
}
|
|
14578
14652
|
};
|
|
14579
14653
|
}
|
|
14654
|
+
function idLikeRequiredStringField(schema) {
|
|
14655
|
+
if (schema === void 0) return null;
|
|
14656
|
+
const properties = schema["properties"];
|
|
14657
|
+
const required = schema["required"];
|
|
14658
|
+
if (properties === null || typeof properties !== "object") return null;
|
|
14659
|
+
if (!Array.isArray(required)) return null;
|
|
14660
|
+
for (const [propName, propSchema] of Object.entries(
|
|
14661
|
+
properties
|
|
14662
|
+
)) {
|
|
14663
|
+
if (!/id$/i.test(propName)) continue;
|
|
14664
|
+
if (!required.includes(propName)) continue;
|
|
14665
|
+
const t = propSchema !== null && typeof propSchema === "object" ? propSchema["type"] : void 0;
|
|
14666
|
+
if (t === "string") return propName;
|
|
14667
|
+
}
|
|
14668
|
+
return null;
|
|
14669
|
+
}
|
|
14670
|
+
async function checkSelectionIdentity(input) {
|
|
14671
|
+
const { container, probe, user, actionName, idField } = input;
|
|
14672
|
+
const clickables = Array.from(
|
|
14673
|
+
container.querySelectorAll('button, [role="button"]')
|
|
14674
|
+
);
|
|
14675
|
+
const byLabel = /* @__PURE__ */ new Map();
|
|
14676
|
+
for (const el of clickables) {
|
|
14677
|
+
const label = (el.textContent ?? "").replace(/\s+/g, " ").trim();
|
|
14678
|
+
if (label.length === 0) continue;
|
|
14679
|
+
const group = byLabel.get(label);
|
|
14680
|
+
if (group) group.push(el);
|
|
14681
|
+
else byLabel.set(label, [el]);
|
|
14682
|
+
}
|
|
14683
|
+
for (const [label, group] of byLabel) {
|
|
14684
|
+
if (group.length < 2) continue;
|
|
14685
|
+
const payloadFor = async (el) => {
|
|
14686
|
+
const before = probe.getFireLog().length;
|
|
14687
|
+
try {
|
|
14688
|
+
await user.click(el);
|
|
14689
|
+
} catch {
|
|
14690
|
+
return void 0;
|
|
14691
|
+
}
|
|
14692
|
+
await flushPromises();
|
|
14693
|
+
const events = probe.getFireLog().slice(before).filter(
|
|
14694
|
+
(e) => e.kind === "action.fired" && e.name === actionName
|
|
14695
|
+
);
|
|
14696
|
+
return events.length > 0 ? events[events.length - 1].payload : void 0;
|
|
14697
|
+
};
|
|
14698
|
+
const first = await payloadFor(group[0]);
|
|
14699
|
+
const second = await payloadFor(group[1]);
|
|
14700
|
+
if (first === void 0 || second === void 0) continue;
|
|
14701
|
+
const idOf = (payload) => {
|
|
14702
|
+
if (payload === null || typeof payload !== "object") return void 0;
|
|
14703
|
+
const v = payload[idField];
|
|
14704
|
+
return typeof v === "string" ? v : void 0;
|
|
14705
|
+
};
|
|
14706
|
+
const firstId = idOf(first);
|
|
14707
|
+
const secondId = idOf(second);
|
|
14708
|
+
if (firstId === void 0 || secondId === void 0) continue;
|
|
14709
|
+
if (firstId === secondId) {
|
|
14710
|
+
return {
|
|
14711
|
+
check: "selection-identity",
|
|
14712
|
+
outcome: "failed",
|
|
14713
|
+
subject: actionName,
|
|
14714
|
+
reason: `Two clickable elements share the visible label '${label}', and clicking EACH fired '${actionName}' with the same ${idField} ('${firstId}') \u2014 the action payload is keyed on the display value, not the element's identity. Distinct cells must send their own ${idField} (the contract requires it), or selecting one "${label}" acts on every "${label}".`,
|
|
14715
|
+
elementHint: describeElement(group[0])
|
|
14716
|
+
};
|
|
14717
|
+
}
|
|
14718
|
+
return null;
|
|
14719
|
+
}
|
|
14720
|
+
return null;
|
|
14721
|
+
}
|
|
14580
14722
|
async function simulateAndCheck(input) {
|
|
14581
14723
|
const { container, user, probe, wiringKind, actionName, actionLabel, eventKind } = input;
|
|
14582
14724
|
const candidates = findCandidateElements(container, wiringKind, actionName, actionLabel);
|
|
@@ -15075,6 +15217,26 @@ function toEvalIssue(issue) {
|
|
|
15075
15217
|
fix
|
|
15076
15218
|
};
|
|
15077
15219
|
}
|
|
15220
|
+
case "selection-identity":
|
|
15221
|
+
return {
|
|
15222
|
+
tier: 0,
|
|
15223
|
+
result,
|
|
15224
|
+
category: "contract",
|
|
15225
|
+
subcategory,
|
|
15226
|
+
severity: "critical",
|
|
15227
|
+
description: `${issue.reason}${elementHint}${diagSuffix}`,
|
|
15228
|
+
fix: `Key the action payload (and any selection state) on the item's ID from the data, never on its display text. Common fix: map over items and pass the item's id into the handler \u2014 onClick={() => ${subject}({ ...payload, id: item.id })} \u2014 and store selection as the id (setSelected(item.id)), not the label.`
|
|
15229
|
+
};
|
|
15230
|
+
case "prop-sensitivity":
|
|
15231
|
+
return {
|
|
15232
|
+
tier: 0,
|
|
15233
|
+
result,
|
|
15234
|
+
category: "contract",
|
|
15235
|
+
subcategory,
|
|
15236
|
+
severity: "critical",
|
|
15237
|
+
description: `${issue.reason}${diagSuffix}`,
|
|
15238
|
+
fix: `Derive the display AND any logic from props.${subject} \u2014 render {props.${subject}} and branch on the prop's value, never on the literal value the request happened to mention (e.g. props.${subject} ?? 'January' followed by January-special-cased logic bakes the request into the component). The contract delivers the value; trust it.`
|
|
15239
|
+
};
|
|
15078
15240
|
case "prop-coverage":
|
|
15079
15241
|
return {
|
|
15080
15242
|
tier: 0,
|