@ggui-ai/ui-gen 0.22.0 → 0.24.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/adapter-Du7P_5Fx.d.ts +201 -0
- package/dist/adapters/generation-dispatch.d.ts +33 -12
- package/dist/adapters/generation-dispatch.js +549 -129
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +1 -1
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +601 -144
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +178 -54
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +1 -0
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +2 -1
- package/dist/check/index.js +47 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-BEkzEtNz.d.ts → create-ui-generator-GIhzx08x.d.ts} +34 -10
- package/dist/{dispatch-DJNpQ7DI.d.ts → dispatch-BnAH56p4.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +4 -3
- package/dist/evaluation/axis-checks/index.js +19 -4
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -1
- package/dist/evaluation/axis-checks/registry.js +19 -4
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +4 -3
- package/dist/evaluation/index.js +21 -6
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +27 -8
- package/dist/harness/check/runtime-render/index.js +217 -54
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +2 -1
- package/dist/harness/index.js +3 -2
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +2 -1
- package/dist/harness/runtime.d.ts +2 -1
- package/dist/harness/runtime.js +2 -1
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +18 -5
- package/dist/harness/types-public.js +1 -0
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -1
- package/dist/index.d.ts +38 -3
- package/dist/index.js +592 -144
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +2 -1
- package/dist/policy.d.ts +7 -4
- package/dist/policy.js.map +1 -1
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.d.ts +7 -1
- package/dist/tools/render-check-worker.js +55 -23
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-mM_-1Yk3.d.ts → types-public-Bk6d1WLN.d.ts} +98 -9
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/workflows.d.ts +2 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +137 -96
- package/src/advanced/generator.ts +16 -1
- package/src/blueprint-validator.ts +21 -6
- package/src/boilerplate/hard-sections.ts +1 -0
- package/src/check/run-tier0.ts +80 -1
- package/src/coding-agent/tools.ts +6 -1
- package/src/create-ui-generator.ts +185 -95
- package/src/evaluation/axis-checks/checks/state-payload.ts +29 -4
- package/src/evaluation/types-public.ts +94 -8
- package/src/harness/check/runtime-render/adapter.ts +223 -58
- package/src/harness/check/runtime-render/index.ts +75 -2
- package/src/harness/check/runtime-render/render-check-host.ts +111 -26
- package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
- package/src/harness/check/runtime-render/render-check.ts +173 -117
- package/src/harness/coding/generate-task-runner.ts +57 -4
- package/src/harness/coding/init-session.ts +13 -0
- package/src/harness/coding/run-eval-round.ts +238 -13
- package/src/harness/llm-router.ts +3 -3
- package/src/harness/policy.ts +11 -0
- package/src/harness/primitive-slice.ts +104 -0
- package/src/harness/types-public.ts +17 -5
- package/src/index.ts +4 -0
- package/src/policy.ts +7 -4
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check-worker.ts +74 -23
- package/src/validation/primitives.ts +1 -1
- package/dist/render-check-C0CqVceF.d.ts +0 -87
package/dist/workflows.d.ts
CHANGED
|
@@ -2,7 +2,8 @@ import { C as Classification } from './axes-CzLEMDeB.js';
|
|
|
2
2
|
import { Workflow } from './harness/types-public.js';
|
|
3
3
|
import '@ggui-ai/protocol';
|
|
4
4
|
import './styling-profile-CxfvV48g.js';
|
|
5
|
-
import './types-public-
|
|
5
|
+
import './types-public-Bk6d1WLN.js';
|
|
6
|
+
import '@ggui-ai/mcp-server-core';
|
|
6
7
|
import './design-mode-DLufhrNp.js';
|
|
7
8
|
import './types-BOvHNG7K.js';
|
|
8
9
|
import './llm.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ggui-ai/ui-gen",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "UI generation harness for ggui — the LLM system prompt, multi-turn coding loop, and evaluation engine that turn a DataContract into a React component. Implements the UiGenerator contract from @ggui-ai/mcp-server-core; consumed by @ggui-ai/mcp-server.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"keywords": [
|
|
@@ -189,9 +189,9 @@
|
|
|
189
189
|
"puppeteer-core": "^24.40.0 || ^25.0.0",
|
|
190
190
|
"react": "^18.0.0 || ^19.0.0",
|
|
191
191
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
192
|
-
"@ggui-ai/
|
|
193
|
-
"@ggui-ai/
|
|
194
|
-
"@ggui-ai/
|
|
192
|
+
"@ggui-ai/project-config": "0.24.0",
|
|
193
|
+
"@ggui-ai/protocol": "0.24.0",
|
|
194
|
+
"@ggui-ai/mcp-server-core": "0.24.0"
|
|
195
195
|
},
|
|
196
196
|
"peerDependenciesMeta": {
|
|
197
197
|
"@google/adk": {
|
|
@@ -233,11 +233,11 @@
|
|
|
233
233
|
"openai": "^7.9.0",
|
|
234
234
|
"typescript": "^5.0.0",
|
|
235
235
|
"zod": "^4.3.6",
|
|
236
|
-
"@ggui-ai/design": "0.
|
|
237
|
-
"@ggui-ai/
|
|
238
|
-
"@ggui-ai/
|
|
239
|
-
"@ggui-ai/ui-visual-tester": "0.
|
|
240
|
-
"@ggui-ai/wire": "0.
|
|
236
|
+
"@ggui-ai/design": "0.24.0",
|
|
237
|
+
"@ggui-ai/gadgets": "0.24.0",
|
|
238
|
+
"@ggui-ai/sandbox": "0.24.0",
|
|
239
|
+
"@ggui-ai/ui-visual-tester": "0.24.0",
|
|
240
|
+
"@ggui-ai/wire": "0.24.0"
|
|
241
241
|
},
|
|
242
242
|
"devDependencies": {
|
|
243
243
|
"@google/adk": "^2.0.0",
|
|
@@ -252,9 +252,9 @@
|
|
|
252
252
|
"react-dom": "^19.0.0",
|
|
253
253
|
"tsup": "^8.5.0",
|
|
254
254
|
"vitest": "^3.2.6",
|
|
255
|
-
"@ggui-ai/mcp-server-core": "0.
|
|
256
|
-
"@ggui-ai/project-config": "0.
|
|
257
|
-
"@ggui-ai/protocol": "0.
|
|
255
|
+
"@ggui-ai/mcp-server-core": "0.24.0",
|
|
256
|
+
"@ggui-ai/project-config": "0.24.0",
|
|
257
|
+
"@ggui-ai/protocol": "0.24.0"
|
|
258
258
|
},
|
|
259
259
|
"engines": {
|
|
260
260
|
"node": ">=22.0.0"
|
|
@@ -33,11 +33,15 @@ import { buildSystemPrompt as buildSystemPromptWithFunnel } from "../harness/run
|
|
|
33
33
|
// predicate against the full registry and passes the filtered result.
|
|
34
34
|
import { REGISTRY as AXIS_CHECK_REGISTRY } from "../evaluation/axis-checks/registry.js";
|
|
35
35
|
import { matches as axisCheckMatches } from "../evaluation/types-public.js";
|
|
36
|
-
// Runtime-render
|
|
37
|
-
// optional runtimeRender; this callsite
|
|
38
|
-
// `
|
|
39
|
-
// backed contract-wiring verification
|
|
36
|
+
// Runtime-render check. The `createHarness` constructor accepts an
|
|
37
|
+
// optional runtimeRender; this callsite wires the caller's instance
|
|
38
|
+
// (`params.runtimeRender`) or, absent one, `DEFAULT_RUNTIME_RENDER_CHECK`,
|
|
39
|
+
// so production keeps the happy-dom-backed contract-wiring verification
|
|
40
|
+
// step. The dispatch never BUILDS a check: the instance carries the
|
|
41
|
+
// concurrency cap, so its lifetime must be the caller's (one per
|
|
42
|
+
// generator), never one per generation.
|
|
40
43
|
import { DEFAULT_RUNTIME_RENDER_CHECK } from "../harness/check/runtime-render/adapter.js";
|
|
44
|
+
import type { RuntimeRenderCheck } from "../harness/types-public.js";
|
|
41
45
|
import { warmupRuntimeRenderProbe } from "../harness/check/runtime-render/index.js";
|
|
42
46
|
import { assembleGenerationResult } from "../harness/coding/assemble-result.js";
|
|
43
47
|
import {
|
|
@@ -54,10 +58,7 @@ import type {
|
|
|
54
58
|
import type { ProviderName, ToolDefinition } from "./types.js";
|
|
55
59
|
import type { QualityConfig } from "../evaluation/types-public.js";
|
|
56
60
|
import type { AgentConfig } from "../harness/llm-router.js";
|
|
57
|
-
import {
|
|
58
|
-
type GadgetDescriptor,
|
|
59
|
-
type JsonObject,
|
|
60
|
-
} from "@ggui-ai/protocol";
|
|
61
|
+
import { type GadgetDescriptor, type JsonObject } from "@ggui-ai/protocol";
|
|
61
62
|
import { canvasForRendering, type CanvasClass, type DesignMode } from "../design-mode.js";
|
|
62
63
|
import type { GenerationProfileInput } from "../boilerplate/styling-profile.js";
|
|
63
64
|
|
|
@@ -118,20 +119,38 @@ export interface GenerationDispatchParams {
|
|
|
118
119
|
*/
|
|
119
120
|
fixtureProps?: JsonObject;
|
|
120
121
|
/**
|
|
121
|
-
* Whether to wire `
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
122
|
+
* Whether to wire the runtime-render check (`runtimeRender` when given,
|
|
123
|
+
* else `DEFAULT_RUNTIME_RENDER_CHECK`) into the harness's check leg.
|
|
124
|
+
* When `true` (default), the runtime-render probe runs ONCE
|
|
125
|
+
* at the exit decision, after the coding turns, in an isolated
|
|
126
|
+
* subprocess — never on the per-turn hot path. Without an `evaluation`
|
|
127
|
+
* config that round is observe + one repair turn (ggui#1380): a
|
|
128
|
+
* recoverable render crash buys exactly one repair turn and one
|
|
129
|
+
* re-probe, then the generation serves what it has; a probe that timed
|
|
130
|
+
* out, could not run, or had nothing to probe never blocks and never
|
|
131
|
+
* repairs. With an `evaluation` config the probe is the evaluation
|
|
132
|
+
* round's exit gate. Its status and timing land on
|
|
133
|
+
* `evalResult.runtimeProbe` (and on the generation's metadata through
|
|
134
|
+
* `createUiGenerator`).
|
|
125
135
|
*
|
|
126
|
-
* When `false`, the harness omits runtime-render
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
* Bench callers can run the probe externally as a final post-gen
|
|
130
|
-
* check to get pass/fail visibility without slowing the loop.
|
|
136
|
+
* When `false`, the harness omits runtime-render entirely: no probe
|
|
137
|
+
* round runs, and a caller can run the probe externally as a final
|
|
138
|
+
* post-generation check instead.
|
|
131
139
|
*
|
|
132
140
|
* Default: `true`.
|
|
133
141
|
*/
|
|
134
142
|
enableRuntimeRender?: boolean;
|
|
143
|
+
/**
|
|
144
|
+
* The runtime-render check to wire (ggui#1380) — built by the CALLER with
|
|
145
|
+
* `createRuntimeRenderCheck(config)` and kept for the caller's lifetime,
|
|
146
|
+
* because the check instance owns the probe's concurrency cap: one
|
|
147
|
+
* instance per generator bounds the workers a serving deployment has
|
|
148
|
+
* live at once; one per dispatch would give every generation its own
|
|
149
|
+
* free slots and bound nothing. Absent ⇒ `DEFAULT_RUNTIME_RENDER_CHECK`
|
|
150
|
+
* (the evaluation lane's bounds, no concurrency cap). Only read when
|
|
151
|
+
* `enableRuntimeRender` is on.
|
|
152
|
+
*/
|
|
153
|
+
runtimeRender?: RuntimeRenderCheck;
|
|
135
154
|
/**
|
|
136
155
|
* Operator-registered gadget catalog forwarded to the
|
|
137
156
|
* code-gen system prompt's `clientCapabilities — registered
|
|
@@ -200,7 +219,9 @@ export interface GenerationDispatchParams {
|
|
|
200
219
|
* Map ProviderName ('claude' | 'openai' | 'google') to AgentConfig provider ('anthropic' | 'openai' | 'google').
|
|
201
220
|
* The LLM router uses 'anthropic' while the rest of the codebase uses 'claude'.
|
|
202
221
|
*/
|
|
203
|
-
function mapProviderForLLMRouter(
|
|
222
|
+
function mapProviderForLLMRouter(
|
|
223
|
+
provider: ProviderName
|
|
224
|
+
): "anthropic" | "openai" | "google" | "openrouter" {
|
|
204
225
|
return provider === "claude" ? "anthropic" : provider;
|
|
205
226
|
}
|
|
206
227
|
|
|
@@ -209,7 +230,7 @@ function mapProviderForLLMRouter(provider: ProviderName): "anthropic" | "openai"
|
|
|
209
230
|
* provider-agnostic.
|
|
210
231
|
*/
|
|
211
232
|
export async function dispatchGeneration(
|
|
212
|
-
params: GenerationDispatchParams
|
|
233
|
+
params: GenerationDispatchParams
|
|
213
234
|
): Promise<GenerationResult> {
|
|
214
235
|
// NOTE: userPrompt arrives already enriched with rendering context + contract
|
|
215
236
|
// (including examples) from the caller (runner.ts / generator.ts).
|
|
@@ -294,17 +315,13 @@ export async function dispatchGeneration(
|
|
|
294
315
|
// from '<package>'` lines for thin contract refs. The closure
|
|
295
316
|
// capture below (systemPromptBuilder) wires the SAME catalog
|
|
296
317
|
// into the HOW leg.
|
|
297
|
-
...(params.appGadgets !== undefined
|
|
298
|
-
? { appGadgets: params.appGadgets }
|
|
299
|
-
: {}),
|
|
318
|
+
...(params.appGadgets !== undefined ? { appGadgets: params.appGadgets } : {}),
|
|
300
319
|
// Forward the third-party wrapper `.d.ts` map so
|
|
301
320
|
// `WhatLeg.gadgetTypes` reaches the coding-agent's typecheck
|
|
302
321
|
// overlay (autoCommit → runTier0Checks → typecheck). The
|
|
303
322
|
// systemPromptBuilder closure below also captures `gadgetTypes`
|
|
304
323
|
// from `params` directly for the prompt `Type:` lines.
|
|
305
|
-
...(params.gadgetTypes !== undefined
|
|
306
|
-
? { gadgetTypes: params.gadgetTypes }
|
|
307
|
-
: {}),
|
|
324
|
+
...(params.gadgetTypes !== undefined ? { gadgetTypes: params.gadgetTypes } : {}),
|
|
308
325
|
// Design mode + canvas — the harness carries them to every leg.
|
|
309
326
|
...(params.designMode !== undefined ? { designMode: params.designMode } : {}),
|
|
310
327
|
// ggui#1117 — the canvas is ALWAYS carried: a stated one, else the class
|
|
@@ -334,7 +351,15 @@ export async function dispatchGeneration(
|
|
|
334
351
|
// prompt's gadget catalog renders a `Type:` line per third-party
|
|
335
352
|
// gadget (the LLM sees a wrapper's real call shape it otherwise
|
|
336
353
|
// can't know).
|
|
337
|
-
systemPromptBuilder: ({
|
|
354
|
+
systemPromptBuilder: ({
|
|
355
|
+
userRequest,
|
|
356
|
+
shellType,
|
|
357
|
+
screen,
|
|
358
|
+
axisDelta,
|
|
359
|
+
designMode,
|
|
360
|
+
canvas,
|
|
361
|
+
profile,
|
|
362
|
+
}) =>
|
|
338
363
|
buildSystemPromptWithFunnel(
|
|
339
364
|
userRequest,
|
|
340
365
|
shellType,
|
|
@@ -344,29 +369,30 @@ export async function dispatchGeneration(
|
|
|
344
369
|
params.gadgetTypes,
|
|
345
370
|
designMode,
|
|
346
371
|
canvas,
|
|
347
|
-
profile
|
|
372
|
+
profile
|
|
348
373
|
),
|
|
349
374
|
// Pre-filtered axis-check registry. The `matches()` predicate
|
|
350
375
|
// selects the checks relevant to this generation's axis vector.
|
|
351
376
|
axisChecks: AXIS_CHECK_REGISTRY.filter((check) =>
|
|
352
|
-
axisCheckMatches(classification.vector, check)
|
|
377
|
+
axisCheckMatches(classification.vector, check)
|
|
353
378
|
),
|
|
354
|
-
// The
|
|
355
|
-
// design) stay on the dispatch side of
|
|
356
|
-
// createHarness defaults this to undefined.
|
|
379
|
+
// The runtime-render check, built under the caller's probe config.
|
|
380
|
+
// Heavy deps (happy-dom + wire + design) stay on the dispatch side of
|
|
381
|
+
// the boundary; the skeleton createHarness defaults this to undefined.
|
|
357
382
|
//
|
|
358
383
|
// Gated behind `enableRuntimeRender` (default true) so
|
|
359
384
|
// benchmark/dev callers can opt out of in-loop probe execution and
|
|
360
385
|
// run the probe externally as a final post-generation check.
|
|
361
|
-
runtimeRender:
|
|
362
|
-
|
|
363
|
-
|
|
386
|
+
runtimeRender:
|
|
387
|
+
(params.enableRuntimeRender ?? true)
|
|
388
|
+
? (params.runtimeRender ?? DEFAULT_RUNTIME_RENDER_CHECK)
|
|
389
|
+
: undefined,
|
|
364
390
|
});
|
|
365
391
|
console.log(
|
|
366
|
-
`[harness] id=${harness.id} name=${harness.name} workflow=${harness.process.workflow.name} overrides=${harness.meta.overrides.join(",") || "none"}
|
|
392
|
+
`[harness] id=${harness.id} name=${harness.name} workflow=${harness.process.workflow.name} overrides=${harness.meta.overrides.join(",") || "none"}`
|
|
367
393
|
);
|
|
368
394
|
console.log(
|
|
369
|
-
`[simple] axis-vector: ${JSON.stringify(classification.vector)} risk=${classification.riskTier}
|
|
395
|
+
`[simple] axis-vector: ${JSON.stringify(classification.vector)} risk=${classification.riskTier}`
|
|
370
396
|
);
|
|
371
397
|
|
|
372
398
|
// ── Guard: staged workflow is not wired on the dispatch path. ──
|
|
@@ -392,7 +418,7 @@ export async function dispatchGeneration(
|
|
|
392
418
|
`This is a misconfiguration (probably pickProcessMode returning WORKFLOWS.staged). ` +
|
|
393
419
|
`For staged-like behaviour, set process.mode="staged" + workflow.name="single_pass" instead — the A1 ` +
|
|
394
420
|
`scaffold→fill phase machine lives inside the generate runner and fires without the workflow topology. ` +
|
|
395
|
-
`See ralph #3 report (2026-04-14) for the full rationale
|
|
421
|
+
`See ralph #3 report (2026-04-14) for the full rationale.`
|
|
396
422
|
);
|
|
397
423
|
}
|
|
398
424
|
|
|
@@ -409,29 +435,58 @@ export async function dispatchGeneration(
|
|
|
409
435
|
// resolveRunPolicyForProfile falls through to the identity
|
|
410
436
|
// resolveRunPolicy — byte-identical to the default behavior.
|
|
411
437
|
const { resolveRunPolicyForProfile } = await import("../harness/policy.js");
|
|
412
|
-
const resolvedPolicy = resolveRunPolicyForProfile(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
);
|
|
438
|
+
const resolvedPolicy = resolveRunPolicyForProfile(process.env.GGUI_POLICY_PROFILE, harness, {
|
|
439
|
+
provider: routerProvider,
|
|
440
|
+
modelId: resolvedModel,
|
|
441
|
+
});
|
|
417
442
|
|
|
418
|
-
// ──
|
|
419
|
-
//
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
//
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
//
|
|
443
|
+
// ── The primitives reference the first-turn prompt carries ──
|
|
444
|
+
// The system prompt embeds PRIMITIVES_DOCUMENTATION (markdown) verbatim.
|
|
445
|
+
// Three policy knobs decide what is SENT in its place, composed in this
|
|
446
|
+
// order (ggui#1324, speed/002 — the slice applies to the format in force;
|
|
447
|
+
// slicing the markdown before the TS swap sent a sliced markdown doc about
|
|
448
|
+
// the size of the full TS doc, which speed/001 measured as a trim):
|
|
449
|
+
// 1. primitiveDocFormat: "markdown" (the embedded doc) or "ts" (its
|
|
450
|
+
// TypeScript-interface form, ~55% smaller, same information);
|
|
451
|
+
// 2. primitiveDocSlice: "full", or an axis-keyed allowlist from the
|
|
452
|
+
// classification ("axis-keyed", or "axis-keyed+contract" which adds the
|
|
453
|
+
// inputs the contract implies) applied to THAT format's doc;
|
|
454
|
+
// 3. primitiveIndex: a name-only index that replaces whatever is left
|
|
455
|
+
// (tool-driven docs; wins when on).
|
|
456
|
+
// Every cell logs `[simple] primitive-doc-bytes … mode=…` (the slice) and
|
|
457
|
+
// `[simple] system-prompt-bytes … doc-format=… doc-bytes=…` (what is sent),
|
|
458
|
+
// so a prompt-size claim is auditable from the log, not inferred.
|
|
426
459
|
let systemPromptOverride: string | undefined;
|
|
427
460
|
{
|
|
428
461
|
const { PRIMITIVES_DOCUMENTATION } = await import("../validation/index.js");
|
|
429
462
|
const fullBytes = Buffer.byteLength(PRIMITIVES_DOCUMENTATION, "utf8");
|
|
463
|
+
const docFormat = resolvedPolicy.context.primitiveDocFormat ?? "markdown";
|
|
464
|
+
const indexMode = resolvedPolicy.context.primitiveIndex ?? "off";
|
|
430
465
|
const mode = resolvedPolicy.context.primitiveDocSlice ?? "full";
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
466
|
+
|
|
467
|
+
let doc = PRIMITIVES_DOCUMENTATION;
|
|
468
|
+
if (docFormat === "ts" && indexMode === "off") {
|
|
469
|
+
const { PRIMITIVES_DOCUMENTATION_TS } = await import("../tools/get-primitives-ts.js");
|
|
470
|
+
doc = PRIMITIVES_DOCUMENTATION_TS;
|
|
471
|
+
const tsBytes = Buffer.byteLength(doc, "utf8");
|
|
472
|
+
console.log(
|
|
473
|
+
`[simple] primitive-doc-ts-bytes=${tsBytes} baseline=${fullBytes} drop=${((1 - tsBytes / fullBytes) * 100).toFixed(1)}%`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
const formatBytes = Buffer.byteLength(doc, "utf8");
|
|
477
|
+
|
|
478
|
+
if (mode === "axis-keyed" || mode === "axis-keyed+contract") {
|
|
479
|
+
const {
|
|
480
|
+
computePrimitiveAllowlist,
|
|
481
|
+
computeContractPrimitives,
|
|
482
|
+
slicePrimitiveDocumentation,
|
|
483
|
+
slicePrimitiveDocumentationTs,
|
|
484
|
+
} = await import("../harness/primitive-slice.js");
|
|
434
485
|
let allowlist = computePrimitiveAllowlist(classification);
|
|
486
|
+
if (mode === "axis-keyed+contract") {
|
|
487
|
+
const fromContract = computeContractPrimitives(effectiveContract);
|
|
488
|
+
allowlist = [...new Set([...allowlist, ...fromContract])].sort();
|
|
489
|
+
}
|
|
435
490
|
// Apply policy-driven excludes — some policy profiles drop
|
|
436
491
|
// near-synonym layout primitives (e.g. Row/Box/Spacer).
|
|
437
492
|
const excludes = resolvedPolicy.context.primitiveDocExcludes ?? [];
|
|
@@ -439,61 +494,41 @@ export async function dispatchGeneration(
|
|
|
439
494
|
const excludeSet = new Set(excludes);
|
|
440
495
|
allowlist = allowlist.filter((p) => !excludeSet.has(p));
|
|
441
496
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const slicedBytes = Buffer.byteLength(
|
|
497
|
+
doc =
|
|
498
|
+
docFormat === "ts" && indexMode === "off"
|
|
499
|
+
? slicePrimitiveDocumentationTs(doc, allowlist)
|
|
500
|
+
: slicePrimitiveDocumentation(doc, allowlist);
|
|
501
|
+
const slicedBytes = Buffer.byteLength(doc, "utf8");
|
|
447
502
|
const excludeTag = excludes.length ? ` excludes=${excludes.join(",")}` : "";
|
|
448
503
|
console.log(
|
|
449
|
-
`[simple] primitive-doc-bytes=${slicedBytes} mode
|
|
450
|
-
);
|
|
451
|
-
systemPromptOverride = harness.how.systemPrompt.replace(
|
|
452
|
-
PRIMITIVES_DOCUMENTATION,
|
|
453
|
-
sliced,
|
|
504
|
+
`[simple] primitive-doc-bytes=${slicedBytes} mode=${mode} baseline=${formatBytes} drop=${((1 - slicedBytes / formatBytes) * 100).toFixed(1)}% allowlist=${allowlist.length}/${allowlist.join(",")}${excludeTag}`
|
|
454
505
|
);
|
|
455
506
|
} else {
|
|
456
507
|
console.log(
|
|
457
|
-
`[simple] primitive-doc-bytes=${
|
|
508
|
+
`[simple] primitive-doc-bytes=${formatBytes} mode=full baseline=${formatBytes} drop=0.0%`
|
|
458
509
|
);
|
|
459
510
|
}
|
|
460
511
|
|
|
461
512
|
// ── Tool-driven primitive docs ──
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
// mutually exclusive in practice via profile selection; this
|
|
466
|
-
// ordering just defines the tiebreak.
|
|
467
|
-
const indexMode = resolvedPolicy.context.primitiveIndex ?? "off";
|
|
513
|
+
// A name-only index replaces whatever is left (it is the more
|
|
514
|
+
// aggressive cut). The two are mutually exclusive in practice via
|
|
515
|
+
// profile selection; this ordering just defines the tiebreak.
|
|
468
516
|
if (indexMode !== "off") {
|
|
469
517
|
const { buildPrimitiveIndex } = await import("../harness/primitive-index.js");
|
|
470
|
-
|
|
471
|
-
const indexBytes = Buffer.byteLength(
|
|
518
|
+
doc = buildPrimitiveIndex(PRIMITIVES_DOCUMENTATION, indexMode);
|
|
519
|
+
const indexBytes = Buffer.byteLength(doc, "utf8");
|
|
472
520
|
console.log(
|
|
473
|
-
`[simple] primitive-index-bytes=${indexBytes} mode=${indexMode} baseline=${fullBytes} drop=${((1 - indexBytes / fullBytes) * 100).toFixed(1)}
|
|
474
|
-
);
|
|
475
|
-
systemPromptOverride = (systemPromptOverride ?? harness.how.systemPrompt).replace(
|
|
476
|
-
PRIMITIVES_DOCUMENTATION,
|
|
477
|
-
index,
|
|
521
|
+
`[simple] primitive-index-bytes=${indexBytes} mode=${indexMode} baseline=${fullBytes} drop=${((1 - indexBytes / fullBytes) * 100).toFixed(1)}%`
|
|
478
522
|
);
|
|
479
523
|
}
|
|
480
524
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
// version (~55% smaller, same info). Mutually exclusive with index
|
|
484
|
-
// modes.
|
|
485
|
-
const docFormat = resolvedPolicy.context.primitiveDocFormat ?? "markdown";
|
|
486
|
-
if (docFormat === "ts" && indexMode === "off") {
|
|
487
|
-
const { PRIMITIVES_DOCUMENTATION_TS } = await import("../tools/get-primitives-ts.js");
|
|
488
|
-
const tsBytes = Buffer.byteLength(PRIMITIVES_DOCUMENTATION_TS, "utf8");
|
|
489
|
-
console.log(
|
|
490
|
-
`[simple] primitive-doc-ts-bytes=${tsBytes} baseline=${fullBytes} drop=${((1 - tsBytes / fullBytes) * 100).toFixed(1)}%`,
|
|
491
|
-
);
|
|
492
|
-
systemPromptOverride = (systemPromptOverride ?? harness.how.systemPrompt).replace(
|
|
493
|
-
PRIMITIVES_DOCUMENTATION,
|
|
494
|
-
PRIMITIVES_DOCUMENTATION_TS,
|
|
495
|
-
);
|
|
525
|
+
if (doc !== PRIMITIVES_DOCUMENTATION) {
|
|
526
|
+
systemPromptOverride = harness.how.systemPrompt.replace(PRIMITIVES_DOCUMENTATION, doc);
|
|
496
527
|
}
|
|
528
|
+
const sentPrompt = systemPromptOverride ?? harness.how.systemPrompt;
|
|
529
|
+
console.log(
|
|
530
|
+
`[simple] system-prompt-bytes=${Buffer.byteLength(sentPrompt, "utf8")} doc-format=${docFormat === "ts" && indexMode === "off" ? "ts" : docFormat} doc-bytes=${Buffer.byteLength(doc, "utf8")} mode=${indexMode !== "off" ? `index:${indexMode}` : mode}`
|
|
531
|
+
);
|
|
497
532
|
|
|
498
533
|
// ── plan→impl pipeline augmentation ──
|
|
499
534
|
// Append a 2-phase flow description when planFirstTurn is active so
|
|
@@ -548,7 +583,7 @@ export async function dispatchGeneration(
|
|
|
548
583
|
console.error(
|
|
549
584
|
`[dispatch] runHarness terminated: ${runResult.reason} ` +
|
|
550
585
|
`(source=${runResult.finalSource ? runResult.finalSource.length + "B" : "null"}, ` +
|
|
551
|
-
`compiled=${runResult.finalCompiled ? runResult.finalCompiled.length + "B" : "null"})
|
|
586
|
+
`compiled=${runResult.finalCompiled ? runResult.finalCompiled.length + "B" : "null"})`
|
|
552
587
|
);
|
|
553
588
|
}
|
|
554
589
|
|
|
@@ -560,7 +595,11 @@ export async function dispatchGeneration(
|
|
|
560
595
|
// `assembleGenerationResult` is async — the spread below must see the
|
|
561
596
|
// RESOLVED result, never the promise (a spread promise is `{}`: every
|
|
562
597
|
// consumer downstream lost `compiledCode` / `tokens` on 2026-09-09).
|
|
563
|
-
const result: GenerationResult = await assembleGenerationResult({
|
|
598
|
+
const result: GenerationResult = await assembleGenerationResult({
|
|
599
|
+
session,
|
|
600
|
+
telemetry,
|
|
601
|
+
source: finalSource,
|
|
602
|
+
});
|
|
564
603
|
// Record the arm this generation ran under (bench reports read it per
|
|
565
604
|
// cell). The canvas is the one the free prompt stated: explicit when
|
|
566
605
|
// passed, else the same shell × screen derivation the prompt used.
|
|
@@ -579,11 +618,13 @@ export async function dispatchGeneration(
|
|
|
579
618
|
*/
|
|
580
619
|
function withArmRecord(
|
|
581
620
|
result: GenerationResult,
|
|
582
|
-
params: Pick<GenerationDispatchParams, "designMode" | "canvas" | "shellType" | "screen"
|
|
621
|
+
params: Pick<GenerationDispatchParams, "designMode" | "canvas" | "shellType" | "screen">
|
|
583
622
|
): GenerationResult {
|
|
584
623
|
const recordedCanvas: CanvasClass | undefined =
|
|
585
624
|
params.canvas ??
|
|
586
|
-
(params.designMode === "free"
|
|
625
|
+
(params.designMode === "free"
|
|
626
|
+
? canvasForRendering(params.shellType, params.screen)
|
|
627
|
+
: undefined);
|
|
587
628
|
return {
|
|
588
629
|
...result,
|
|
589
630
|
...(params.designMode !== undefined ? { designMode: params.designMode } : {}),
|
|
@@ -349,6 +349,18 @@ async function runFastStage(
|
|
|
349
349
|
...(input.contract ? { contract: input.contract } : {}),
|
|
350
350
|
};
|
|
351
351
|
const result = await runRenderCheck(checkInput);
|
|
352
|
+
if (result.incomplete !== undefined) {
|
|
353
|
+
// ggui#1299: the check ran out of wall-clock time, so there is no
|
|
354
|
+
// evidence either way — the same posture as an unverified check
|
|
355
|
+
// (non-failing, the unverified score). The slow stage still gates.
|
|
356
|
+
return {
|
|
357
|
+
stage: 'fast',
|
|
358
|
+
ok: true,
|
|
359
|
+
score: UNVERIFIED_FAST_SCORE,
|
|
360
|
+
diagnostics: [],
|
|
361
|
+
durationMs: Date.now() - start,
|
|
362
|
+
};
|
|
363
|
+
}
|
|
352
364
|
const diagnostics: StageDiagnostic[] = buildFastStageComplaints(
|
|
353
365
|
result.issues as readonly RenderCheckIssue[],
|
|
354
366
|
);
|
|
@@ -405,11 +417,14 @@ async function runSlowStage(
|
|
|
405
417
|
* job is just to land above or below it. Per-issue gradations matter
|
|
406
418
|
* less than getting the pass/fail boundary right.
|
|
407
419
|
*/
|
|
420
|
+
/** Fast-stage score when nothing failed but something went unverified. */
|
|
421
|
+
const UNVERIFIED_FAST_SCORE = 0.85;
|
|
422
|
+
|
|
408
423
|
function scoreFromRenderCheck(issues: readonly RenderCheckIssue[]): number {
|
|
409
424
|
const failed = issues.filter((i) => i.outcome === 'failed').length;
|
|
410
425
|
const unverified = issues.filter((i) => i.outcome === 'unverified').length;
|
|
411
426
|
if (failed === 0 && unverified === 0) return 1.0;
|
|
412
|
-
if (failed === 0) return
|
|
427
|
+
if (failed === 0) return UNVERIFIED_FAST_SCORE;
|
|
413
428
|
if (failed === 1) return 0.4;
|
|
414
429
|
if (failed === 2) return 0.3;
|
|
415
430
|
return 0.2;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import type { DataContract, JsonObject } from '@ggui-ai/protocol';
|
|
25
25
|
import { DEFAULT_RUNTIME_RENDER_CHECK } from './harness/check/runtime-render/index.js';
|
|
26
|
-
import type { EvalIssue } from './evaluation/types-public.js';
|
|
26
|
+
import type { EvalIssue, RuntimeProbeStatus } from './evaluation/types-public.js';
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
29
|
* Caller-supplied blueprint payload. `contract` is `unknown` at the
|
|
@@ -74,6 +74,23 @@ export interface ValidateBlueprintInput {
|
|
|
74
74
|
* the absence cleanly (selfCheck skips prop-coverage warnings; runtime
|
|
75
75
|
* probe skip-warns).
|
|
76
76
|
*/
|
|
77
|
+
/**
|
|
78
|
+
* The warning code for a probe that did not produce evidence. Exhaustive over
|
|
79
|
+
* the did-not-run statuses, so a new status is a compile error here instead of
|
|
80
|
+
* silently reading as whichever arm a ternary falls through to (ggui#1299:
|
|
81
|
+
* `timed-out` would otherwise have been labelled `probe-not-applicable`).
|
|
82
|
+
*/
|
|
83
|
+
function probeDidNotRunCode(status: Exclude<RuntimeProbeStatus, 'ran'>): string {
|
|
84
|
+
switch (status) {
|
|
85
|
+
case 'infra-skipped':
|
|
86
|
+
return 'runtime:probe-infra-failure';
|
|
87
|
+
case 'not-applicable':
|
|
88
|
+
return 'runtime:probe-not-applicable';
|
|
89
|
+
case 'timed-out':
|
|
90
|
+
return 'runtime:probe-timeout';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
77
94
|
function asContract(x: RawContract): DataContract | undefined {
|
|
78
95
|
if (typeof x !== 'object' || x === null) return undefined;
|
|
79
96
|
return x as DataContract;
|
|
@@ -247,17 +264,15 @@ async function runtimeTier(input: RuntimeTierInput): Promise<RuntimeResult> {
|
|
|
247
264
|
fixtureProps: asJsonObject(input.fixtureProps),
|
|
248
265
|
});
|
|
249
266
|
if (outcome.status !== 'ran') {
|
|
250
|
-
// Probe
|
|
267
|
+
// Probe produced no evidence — infra failure, nothing to probe, or it
|
|
268
|
+
// ran out of wall-clock time before finishing.
|
|
251
269
|
// Surface as a warning rather than a hard error so a flaky probe
|
|
252
270
|
// doesn't permanently block legitimate blueprints — Claude can
|
|
253
271
|
// retry, and host-level logs record the diagnostic for ops review.
|
|
254
272
|
warnings.push({
|
|
255
273
|
_kind: 'warning',
|
|
256
274
|
tier: 'runtime',
|
|
257
|
-
code:
|
|
258
|
-
outcome.status === 'infra-skipped'
|
|
259
|
-
? 'runtime:probe-infra-failure'
|
|
260
|
-
: 'runtime:probe-not-applicable',
|
|
275
|
+
code: probeDidNotRunCode(outcome.status),
|
|
261
276
|
message: `Runtime probe did not run (${outcome.status}): ${outcome.reason ?? 'no reason recorded'}`,
|
|
262
277
|
});
|
|
263
278
|
return { errors, warnings };
|
|
@@ -250,6 +250,7 @@ export const FRAME_SIZING = `## Sizing — the frame owns the height and the wid
|
|
|
250
250
|
The host sizes the iframe; the component never sizes itself to the viewport. NEVER write \`100vh\` / \`100dvh\` / \`100svh\` (or an \`h-screen\` / \`min-h-screen\` class) as a height on any element:
|
|
251
251
|
- An inline card takes its natural height. A content-sized frame is measured FROM the content, so a \`100vh\` root can never shrink and grows the frame on every re-measure.
|
|
252
252
|
- Under fullscreen the frame already stretches your root to its full height. Centre inside it with flex (\`display: flex; flex-direction: column; justify-content: center\`), never with a viewport height.
|
|
253
|
+
- Under fullscreen the frame also keeps a 16 px inset between your content and the panel's edge — leave it. A \`surface="hero"\` band that OPENS the card runs edge to edge on its own. Any other band that is MEANT to run edge to edge (a full-width strip of rows, a closing action bar) takes \`bleed\` (\`<Box surface="sunken" bleed>…</Box>\`); only such a band should, and \`bleed\` is ignored outside fullscreen.
|
|
253
254
|
- To fill a parent that has a height use \`height: 100%\` or \`flex: 1\`; a viewport unit is only ever a \`max-height\` cap on a scroll region.
|
|
254
255
|
|
|
255
256
|
The width works the same way, and the mistake is the mirror image: **your outermost element FILLS the frame; a width cap belongs INSIDE it, on the column that holds text.**
|
package/src/check/run-tier0.ts
CHANGED
|
@@ -295,6 +295,85 @@ function detectCertainDoubleWiredActions(
|
|
|
295
295
|
* fallback ban it paints nothing.
|
|
296
296
|
*/
|
|
297
297
|
const MANIFEST_TOKENS: ReadonlySet<string> = new Set(consumedTokenManifest);
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Role names retired by ggui#987 that generated code still invents, each
|
|
301
|
+
* with the manifest name that replaced it (ggui#1325). The self-check names
|
|
302
|
+
* the successor, so the model does not spend a turn guessing another name.
|
|
303
|
+
*/
|
|
304
|
+
const RETIRED_TOKEN_SUCCESSORS: ReadonlyMap<string, string> = new Map([
|
|
305
|
+
['--ggui-color-surface', '--ggui-color-container'],
|
|
306
|
+
['--ggui-color-onSurface', '--ggui-color-onContainer'],
|
|
307
|
+
['--ggui-color-surfaceVariant', '--ggui-color-sunken'],
|
|
308
|
+
['--ggui-color-onSurfaceVariant', '--ggui-color-onSunken'],
|
|
309
|
+
]);
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The manifest names nearest an invented one (ggui#1325): a retired role's
|
|
313
|
+
* successor when there is one; otherwise the names sharing the invented
|
|
314
|
+
* name's longest family prefix at a segment boundary
|
|
315
|
+
* (`--ggui-motion-duration-normal` → fast, base, slow); otherwise the names
|
|
316
|
+
* sharing any of its segments of four letters or more
|
|
317
|
+
* (`--ggui-duration-slow` → the motion durations). Empty when nothing is
|
|
318
|
+
* near, so the hint falls back to the manifest's general shape.
|
|
319
|
+
*/
|
|
320
|
+
export function suggestManifestTokens(name: string): readonly string[] {
|
|
321
|
+
const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
|
|
322
|
+
if (successor !== undefined) return [successor];
|
|
323
|
+
// Family prefixes at the name's own segment boundaries, longest first: for
|
|
324
|
+
// `--ggui-motion-duration-normal` that is `--ggui-motion-duration-`, then
|
|
325
|
+
// `--ggui-motion-`; an empty-suffix name such as `--ggui-color-` (the model
|
|
326
|
+
// stopped mid-name) is its own longest prefix, so it gets the colour family.
|
|
327
|
+
const segments = name.split('-').filter((segment) => segment.length > 0);
|
|
328
|
+
const own = name.endsWith('-') ? segments.length : segments.length - 1;
|
|
329
|
+
for (let n = own; n >= 2; n--) {
|
|
330
|
+
const prefix = `--${segments.slice(0, n).join('-')}-`;
|
|
331
|
+
const family = consumedTokenManifest.filter((token) => token.startsWith(prefix));
|
|
332
|
+
if (family.length > 0) return wordHitsFirst(name, rolesFirst(family));
|
|
333
|
+
}
|
|
334
|
+
const words = new Set(
|
|
335
|
+
segments
|
|
336
|
+
.slice(1)
|
|
337
|
+
.filter((segment) => segment.length >= 4)
|
|
338
|
+
.map((segment) => segment.toLowerCase()),
|
|
339
|
+
);
|
|
340
|
+
return rolesFirst(
|
|
341
|
+
consumedTokenManifest.filter((token) =>
|
|
342
|
+
token.split('-').some((segment) => words.has(segment.toLowerCase())),
|
|
343
|
+
),
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Names that carry the invented name's last word lead the list: `--ggui-color-inverse` names `inverseLink` and
|
|
349
|
+
* `inverseOutline` before the rest of the colour family, since those are what the model was reaching for. The word is
|
|
350
|
+
* the last segment when it has four letters or more; shorter or empty suffixes leave the order alone.
|
|
351
|
+
*/
|
|
352
|
+
function wordHitsFirst(name: string, family: readonly string[]): readonly string[] {
|
|
353
|
+
const word = name.slice(name.lastIndexOf('-') + 1).toLowerCase();
|
|
354
|
+
if (word.length < 4) return family;
|
|
355
|
+
const hit = (token: string): boolean => token.slice(token.lastIndexOf('-') + 1).toLowerCase().includes(word);
|
|
356
|
+
return [...family.filter(hit), ...family.filter((token) => !hit(token))];
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/** Role names (a non-numeric last segment) before ramp steps, so the taught vocabulary leads a long family. */
|
|
360
|
+
function rolesFirst(tokens: readonly string[]): readonly string[] {
|
|
361
|
+
const isRole = (token: string): boolean => !/^\d+$/.test(token.slice(token.lastIndexOf('-') + 1));
|
|
362
|
+
return [...tokens.filter(isRole), ...tokens.filter((token) => !isRole(token))];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** The sentence that names the real token first (ggui#1325); empty when nothing in the manifest is near. */
|
|
366
|
+
function offManifestHint(name: string): string {
|
|
367
|
+
const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
|
|
368
|
+
if (successor !== undefined) {
|
|
369
|
+
return `"${name}" was retired (ggui#987); the current name is var(${successor}). `;
|
|
370
|
+
}
|
|
371
|
+
const near = suggestManifestTokens(name);
|
|
372
|
+
if (near.length === 0) return '';
|
|
373
|
+
const shown = near.slice(0, 8).map((token) => `var(${token})`).join(', ');
|
|
374
|
+
const more = near.length > 8 ? ` (and ${near.length - 8} more in that family)` : '';
|
|
375
|
+
return `Nearest names in the manifest: ${shown}${more}. `;
|
|
376
|
+
}
|
|
298
377
|
const TOKEN_REF_RE = /var\(\s*(--ggui-[a-zA-Z0-9-]+)/g;
|
|
299
378
|
|
|
300
379
|
/** Map wire kind to the matching `@ggui-ai/wire` hook name. */
|
|
@@ -790,7 +869,7 @@ export async function runTier0Checks(
|
|
|
790
869
|
subcategory: 'off-manifest-token',
|
|
791
870
|
severity: 'critical',
|
|
792
871
|
description: `Token "${name}" is not in the design system's token manifest — no theme defines it, so it renders unset.`,
|
|
793
|
-
fix:
|
|
872
|
+
fix: `${offManifestHint(name)}Choose a token that exists: the closed vocabulary is the manifest the design-system docs list (colors: var(--ggui-color-primary-500), var(--ggui-color-onContainer), …; spacing: var(--ggui-spacing-4); type: var(--ggui-font-size-sm)). Never invent a name and never add a literal fallback.`,
|
|
794
873
|
line: lineNum,
|
|
795
874
|
});
|
|
796
875
|
}
|