@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.
- package/dist/adapters/generation-dispatch.d.ts +6 -3
- package/dist/adapters/generation-dispatch.js +5161 -4312
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +8 -5
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +12 -8
- package/dist/advanced/index.js +4685 -3801
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +8 -12
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +5 -2
- package/dist/boilerplate.js +120 -13
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +2 -2
- package/dist/check/index.js +72 -0
- package/dist/check/index.js.map +1 -1
- package/dist/compose.js +1 -1
- package/dist/compose.js.map +1 -1
- package/dist/{create-ui-generator-CCIeQaEe.d.ts → create-ui-generator-C8Ki9Wz1.d.ts} +18 -2
- package/dist/{design-mode-BHEE7flj.d.ts → design-mode-DLufhrNp.d.ts} +0 -6
- package/dist/{dispatch-BA5dJcg-.d.ts → dispatch-D2htzi9h.d.ts} +2 -2
- package/dist/evaluation/axis-checks/index.d.ts +4 -4
- package/dist/evaluation/axis-checks/index.js +347 -13
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -2
- package/dist/evaluation/axis-checks/registry.js +302 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +160 -8
- package/dist/evaluation/index.js +752 -81
- package/dist/evaluation/index.js.map +1 -1
- package/dist/fragments/index.js +1 -1
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-8L_bc2iP.d.ts → generate-9IeNT4ZY.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +3 -2
- package/dist/harness/check/runtime-render/index.js +8 -12
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +410 -255
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +7 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +3 -3
- package/dist/harness/runtime.d.ts +13 -4
- package/dist/harness/runtime.js +131 -20
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +13 -2
- package/dist/harness/types-public.js +410 -255
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -2
- package/dist/index.d.ts +29 -7
- package/dist/index.js +4691 -3805
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +3 -2
- package/dist/{prompts-k6m6reqe.d.ts → prompts-YU9piOgs.d.ts} +12 -46
- package/dist/styling-profile-CxfvV48g.d.ts +35 -0
- 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/{types-public-D24JLJgl.d.ts → types-public-DVSssdc-.d.ts} +39 -3
- 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 +3 -2
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +6 -1
- package/src/boilerplate/free-design-prompt.ts +9 -0
- package/src/boilerplate/generate.ts +8 -1
- package/src/boilerplate/hard-sections.ts +45 -1
- package/src/boilerplate/styling-profile.ts +166 -0
- package/src/boilerplate/system-prompt.ts +22 -8
- package/src/boilerplate/templates/layouts/fullscreen-desktop.tsx.tmpl +5 -3
- package/src/boilerplate/templates/layouts/fullscreen-mobile.tsx.tmpl +3 -3
- package/src/boilerplate/templates/layouts/fullscreen-universal.tsx.tmpl +5 -3
- package/src/check/run-tier0.ts +154 -0
- package/src/contract-context.ts +28 -14
- package/src/create-harness.ts +4 -0
- package/src/create-ui-generator.ts +41 -2
- package/src/design-mode.ts +11 -1
- package/src/design-system-docs.ts +7 -4
- package/src/effort.ts +48 -0
- package/src/evaluation/axis-checks/checks/grid.ts +148 -0
- package/src/evaluation/axis-checks/checks/universal.ts +308 -0
- package/src/evaluation/axis-checks/dispatch.ts +78 -13
- package/src/evaluation/axis-checks/helpers.ts +12 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/design-identity.ts +96 -0
- package/src/evaluation/index.ts +17 -0
- package/src/evaluation/llm-evaluator.ts +13 -1
- package/src/evaluation/theme-css.ts +14 -0
- package/src/evaluation/types-public.ts +35 -1
- package/src/evaluation/visual-evaluator.ts +478 -45
- package/src/fragments/render.ts +1 -1
- package/src/harness/check/runtime-render/prepare-mockup.ts +18 -11
- package/src/harness/coding/init-session.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +6 -14
- package/src/harness/coding/run-eval-round.ts +31 -3
- package/src/harness/runtime.ts +10 -0
- package/src/harness/types-public.ts +11 -0
- package/src/index.ts +2 -0
- package/src/pin-fixtures.ts +98 -0
- package/src/run-check.ts +10 -10
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools.ts +2 -1
- package/src/validation/primitives.ts +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { A as AxisCheck } from '../../types-public-
|
|
2
|
-
import '../../design-mode-
|
|
1
|
+
import { A as AxisCheck } from '../../types-public-DVSssdc-.js';
|
|
2
|
+
import '../../design-mode-DLufhrNp.js';
|
|
3
3
|
import '@ggui-ai/protocol';
|
|
4
4
|
import '../../axes-CzLEMDeB.js';
|
|
5
5
|
import '../../types-BOvHNG7K.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { LUCIDE_ICON_NAMES } from '@ggui-ai/design';
|
|
1
2
|
import { listContractGadgets, HOOK_NAME_RE, STDLIB_GADGETS_PACKAGE } from '@ggui-ai/protocol';
|
|
2
3
|
|
|
3
|
-
// src/evaluation/axis-checks/
|
|
4
|
+
// src/evaluation/axis-checks/checks/universal.ts
|
|
4
5
|
var ID_FIELD_CANDIDATES = ["id", "uuid", "symbol", "key", "slug", "code"];
|
|
5
6
|
function getItemsProperties(p) {
|
|
6
7
|
return p.items?.properties ?? p.schema?.items?.properties;
|
|
@@ -157,6 +158,9 @@ function mkIssue(subcategory, description, fix, result = "fail") {
|
|
|
157
158
|
function cap(s) {
|
|
158
159
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
159
160
|
}
|
|
161
|
+
function axisCheckTraceEnabled() {
|
|
162
|
+
return process.env["GGUI_AXIS_CHECK_TRACE"] === "1";
|
|
163
|
+
}
|
|
160
164
|
|
|
161
165
|
// src/evaluation/axis-checks/checks/universal.ts
|
|
162
166
|
var ALL_RENDER_VALUES = [
|
|
@@ -260,7 +264,222 @@ function runPropSeedNoResync(input) {
|
|
|
260
264
|
}
|
|
261
265
|
return issues;
|
|
262
266
|
}
|
|
267
|
+
var KNOWN_ICON_KEYS = new Set(
|
|
268
|
+
LUCIDE_ICON_NAMES.map((n) => n.replace(/[-_]/g, "").toLowerCase())
|
|
269
|
+
);
|
|
270
|
+
function isKnownIconName(name) {
|
|
271
|
+
return KNOWN_ICON_KEYS.has(name.replace(/[-_]/g, "").toLowerCase());
|
|
272
|
+
}
|
|
273
|
+
var ICON_NAME_LITERAL_RX = /<Icon\b[^>]*?\bname\s*=\s*(?:"([^"]*)"|'([^']*)'|\{\s*"([^"]*)"\s*\}|\{\s*'([^']*)'\s*\})/g;
|
|
274
|
+
function findUnknownIconNames(sourceCode) {
|
|
275
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
276
|
+
for (const m of sourceCode.matchAll(ICON_NAME_LITERAL_RX)) {
|
|
277
|
+
const name = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
|
|
278
|
+
if (name.length === 0) continue;
|
|
279
|
+
if (/[^\x20-\x7e]/.test(name)) continue;
|
|
280
|
+
if (!isKnownIconName(name)) unknown.add(name);
|
|
281
|
+
}
|
|
282
|
+
return [...unknown];
|
|
283
|
+
}
|
|
284
|
+
function runIconNameKnown(input) {
|
|
285
|
+
if (input.compiledCode === null) return [];
|
|
286
|
+
const unknown = findUnknownIconNames(input.sourceCode);
|
|
287
|
+
if (unknown.length === 0) return [];
|
|
288
|
+
const list = unknown.map((n) => `"${n}"`).join(", ");
|
|
289
|
+
return [
|
|
290
|
+
mkIssue(
|
|
291
|
+
"universal.icon_name_known",
|
|
292
|
+
`<Icon name=\u2026> uses ${unknown.length} name(s) outside the design system's Lucide subset: ${list} \u2014 an unknown name renders an empty box.`,
|
|
293
|
+
"Call get_available_icons and pick a listed name (kebab-case), or use an emoji directly."
|
|
294
|
+
)
|
|
295
|
+
];
|
|
296
|
+
}
|
|
297
|
+
var LADDER_STOP_AS_TEXT_RX = /(?<![\w-])color\s*(?:=\s*\{?\s*|:\s*)["'`]?\s*var\(--ggui-color-primary-(\d{2,3})\s*[,)]/g;
|
|
298
|
+
var TEXT_INK_STOP_MIN = 300;
|
|
299
|
+
var TEXT_INK_STOP_MAX = 600;
|
|
300
|
+
function findLadderStopsAsText(sourceCode) {
|
|
301
|
+
const found = /* @__PURE__ */ new Set();
|
|
302
|
+
for (const m of sourceCode.matchAll(LADDER_STOP_AS_TEXT_RX)) {
|
|
303
|
+
const digits = m[1] ?? "";
|
|
304
|
+
const stop = Number(digits);
|
|
305
|
+
if (stop >= TEXT_INK_STOP_MIN && stop <= TEXT_INK_STOP_MAX) found.add(`primary-${digits}`);
|
|
306
|
+
}
|
|
307
|
+
return [...found];
|
|
308
|
+
}
|
|
309
|
+
function runAccentTextInk(input) {
|
|
310
|
+
if (input.compiledCode === null) return [];
|
|
311
|
+
const stops = findLadderStopsAsText(input.sourceCode);
|
|
312
|
+
if (stops.length === 0) return [];
|
|
313
|
+
const list = stops.map((s) => `\`${s}\``).join(", ");
|
|
314
|
+
return [
|
|
315
|
+
mkIssue(
|
|
316
|
+
"universal.accent_text_ink",
|
|
317
|
+
`Text is painted with a brand-ladder fill stop as its color (${list}) \u2014 on a low-saturation brand these stops are mid-grey on the surface and the text fails readability.`,
|
|
318
|
+
'Accent text (eyebrows, taglines, links, labels, inline arrows) takes var(--ggui-color-link) or <Text tone="emphasized">; body text takes var(--ggui-color-onContainer); keep primary-* stops on fills and borders.'
|
|
319
|
+
)
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
var SCOPED_SURFACE_TAG_RX = /<(Card|Box)\b([^<>]*?)surface\s*=\s*["'](inverted|hero)["']([^<>]*?)>/g;
|
|
323
|
+
var STYLE_BACKGROUND_RX = /\bstyle\s*=\s*\{\{[^}]*\b(background|backgroundColor|backgroundImage)\s*:/;
|
|
324
|
+
function findScopedSurfaceBackgrounds(sourceCode) {
|
|
325
|
+
const hits = [];
|
|
326
|
+
for (const m of sourceCode.matchAll(SCOPED_SURFACE_TAG_RX)) {
|
|
327
|
+
const attrs = `${m[2] ?? ""} ${m[4] ?? ""}`;
|
|
328
|
+
if (STYLE_BACKGROUND_RX.test(attrs)) hits.push([m[1] ?? "", m[3] ?? ""]);
|
|
329
|
+
}
|
|
330
|
+
return hits;
|
|
331
|
+
}
|
|
332
|
+
function runScopedSurfaceOwnsGround(input) {
|
|
333
|
+
if (input.compiledCode === null) return [];
|
|
334
|
+
if (input.designMode === "free") return [];
|
|
335
|
+
const hits = findScopedSurfaceBackgrounds(input.sourceCode);
|
|
336
|
+
if (hits.length === 0) return [];
|
|
337
|
+
const [tag, surface] = hits[0];
|
|
338
|
+
return [
|
|
339
|
+
mkIssue(
|
|
340
|
+
"universal.scoped_surface_owns_ground",
|
|
341
|
+
`<${tag} surface="${surface}"> sets its own background in style \u2014 a scoped surface owns its ground; repainting it puts the scope's swapped inks over the wrong ground (ink on ink).`,
|
|
342
|
+
`Remove the background from that ${tag}'s style \u2014 surface="${surface}" paints the ground; pick another surface if a different ground is wanted.`
|
|
343
|
+
)
|
|
344
|
+
];
|
|
345
|
+
}
|
|
346
|
+
var CAPS_TEXT_RX = /<Text\b(?=[^>]*\bcaps\b)[^>]*>([\s\S]*?)<\/Text>/g;
|
|
347
|
+
function findInventedCapsLabels(sourceCode) {
|
|
348
|
+
const out = [];
|
|
349
|
+
for (const m of sourceCode.matchAll(CAPS_TEXT_RX)) {
|
|
350
|
+
const inner = (m[1] ?? "").trim();
|
|
351
|
+
if (inner.length === 0) continue;
|
|
352
|
+
if (inner.includes("{")) continue;
|
|
353
|
+
out.push(inner.replace(/\s+/g, " ").slice(0, 60));
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
function runCapsLabelInvented(input) {
|
|
358
|
+
if (input.compiledCode === null) return [];
|
|
359
|
+
const invented = findInventedCapsLabels(input.sourceCode);
|
|
360
|
+
if (invented.length === 0) return [];
|
|
361
|
+
const list = invented.map((t) => `"${t}"`).join(", ");
|
|
362
|
+
return [
|
|
363
|
+
mkIssue(
|
|
364
|
+
"universal.caps_label_invented",
|
|
365
|
+
`${invented.length} caps label(s) carry invented copy the props do not supply: ${list} \u2014 an eyebrow, kicker or overline the agent cannot change is chatter on every render.`,
|
|
366
|
+
"Remove the label, or bind its text to a prop (`<Text caps>{props.section}</Text>`); copy comes from the contract, never from the component."
|
|
367
|
+
)
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
var VIEWPORT_UNIT = String.raw`(?:calc\(\s*)?100(?:vh|dvh|svh|lvh)\b(?:[^)"'\x60;\n]*\))?`;
|
|
371
|
+
var VIEWPORT_STYLE_OBJECT_RX = new RegExp(
|
|
372
|
+
String.raw`\b(?:minHeight|height|minBlockSize|blockSize)\s*:\s*["'\x60]\s*${VIEWPORT_UNIT}["'\x60]?`,
|
|
373
|
+
"g"
|
|
374
|
+
);
|
|
375
|
+
var VIEWPORT_CSS_RX = new RegExp(
|
|
376
|
+
String.raw`(?<![\w-])(?:min-)?(?:height|block-size)\s*:\s*${VIEWPORT_UNIT}`,
|
|
377
|
+
"g"
|
|
378
|
+
);
|
|
379
|
+
var VIEWPORT_CLASS_RX = /(?<![\w-])(?:min-)?h-(?:screen|dvh|svh|lvh)\b/g;
|
|
380
|
+
function findViewportSizing(sourceCode) {
|
|
381
|
+
const hits = [];
|
|
382
|
+
for (const rx of [VIEWPORT_STYLE_OBJECT_RX, VIEWPORT_CSS_RX, VIEWPORT_CLASS_RX]) {
|
|
383
|
+
for (const m of sourceCode.matchAll(rx)) {
|
|
384
|
+
hits.push({ at: m.index ?? 0, text: m[0].replace(/\s+/g, " ").trim().slice(0, 60) });
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return hits.sort((a, b) => a.at - b.at).map((h) => h.text);
|
|
388
|
+
}
|
|
389
|
+
function runViewportSized(input) {
|
|
390
|
+
if (input.compiledCode === null) return [];
|
|
391
|
+
const sized = findViewportSizing(input.sourceCode);
|
|
392
|
+
if (sized.length === 0) return [];
|
|
393
|
+
const list = sized.map((t) => "`" + t + "`").join(", ");
|
|
394
|
+
return [
|
|
395
|
+
mkIssue(
|
|
396
|
+
"universal.viewport_sized",
|
|
397
|
+
`${sized.length} element(s) are sized to the viewport: ${list} \u2014 the frame owns the height; a content-sized frame is measured from the content, so a viewport-sized root never shrinks and grows the frame on every re-measure.`,
|
|
398
|
+
"Remove the viewport height \u2014 size to content, never to the viewport. Under fullscreen the frame stretches your root already (centre inside it with `display: flex; flex-direction: column; justify-content: center`); to fill a parent that has a height use `height: 100%` or `flex: 1`."
|
|
399
|
+
)
|
|
400
|
+
];
|
|
401
|
+
}
|
|
402
|
+
var TERMINAL_WORDS = /* @__PURE__ */ new Set([
|
|
403
|
+
"submit",
|
|
404
|
+
"confirm",
|
|
405
|
+
"approve",
|
|
406
|
+
"schedule",
|
|
407
|
+
"reserve",
|
|
408
|
+
"book",
|
|
409
|
+
"pay",
|
|
410
|
+
"checkout",
|
|
411
|
+
"purchase",
|
|
412
|
+
"order"
|
|
413
|
+
]);
|
|
414
|
+
var TERMINAL_LABEL_RX = /\b(submit|confirm|approve|schedule|reserve|book|pay|checkout|purchase|place\s+order)\b/i;
|
|
415
|
+
var GUARD_RX = /\b(disabled|aria-disabled)\s*=/;
|
|
416
|
+
function nameWords(name) {
|
|
417
|
+
return name.split(/(?=[A-Z])|[_\-\s]+/).map((w) => w.toLowerCase()).filter((w) => w.length > 0);
|
|
418
|
+
}
|
|
419
|
+
function findUnguardedTerminalControls(sourceCode) {
|
|
420
|
+
if (GUARD_RX.test(sourceCode)) return [];
|
|
421
|
+
const names = /* @__PURE__ */ new Set();
|
|
422
|
+
for (const m of sourceCode.matchAll(/useAction(?:<[^>]*>)?\(\s*['"`]([^'"`]+)['"`]/g)) {
|
|
423
|
+
const name = m[1] ?? "";
|
|
424
|
+
if (nameWords(name).some((w) => TERMINAL_WORDS.has(w))) names.add(name);
|
|
425
|
+
}
|
|
426
|
+
for (const m of sourceCode.matchAll(/>([^<>{}]{1,60})<\/Button>/g)) {
|
|
427
|
+
const label = (m[1] ?? "").replace(/\s+/g, " ").trim();
|
|
428
|
+
if (label.length > 0 && TERMINAL_LABEL_RX.test(label)) names.add(label.slice(0, 40));
|
|
429
|
+
}
|
|
430
|
+
return [...names];
|
|
431
|
+
}
|
|
432
|
+
function runTerminalActionUnguarded(input) {
|
|
433
|
+
if (input.compiledCode === null) return [];
|
|
434
|
+
const found = findUnguardedTerminalControls(input.sourceCode);
|
|
435
|
+
if (found.length === 0) return [];
|
|
436
|
+
const list = found.map((t) => `"${t}"`).join(", ");
|
|
437
|
+
return [
|
|
438
|
+
mkIssue(
|
|
439
|
+
"universal.terminal_action_unguarded",
|
|
440
|
+
`${list} reads as an action the user means once, and nothing in this component disables the control after it fires \u2014 a double-click sends it twice.`,
|
|
441
|
+
'Hold the fired state (`const [submitted, setSubmitted] = useState(false)`), set it in the handler, and pass `disabled={submitted}` to that control with a word that says so ("Submitted"). If the action IS meant to repeat \u2014 sending a message, adding an item \u2014 leave it armed and ignore this.',
|
|
442
|
+
"warn"
|
|
443
|
+
)
|
|
444
|
+
];
|
|
445
|
+
}
|
|
263
446
|
var UNIVERSAL_CHECKS = [
|
|
447
|
+
{
|
|
448
|
+
id: "universal.icon_name_known",
|
|
449
|
+
axis: "render",
|
|
450
|
+
values: ALL_RENDER_VALUES,
|
|
451
|
+
run: runIconNameKnown
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
id: "universal.caps_label_invented",
|
|
455
|
+
axis: "render",
|
|
456
|
+
values: ALL_RENDER_VALUES,
|
|
457
|
+
run: runCapsLabelInvented
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: "universal.viewport_sized",
|
|
461
|
+
axis: "render",
|
|
462
|
+
values: ALL_RENDER_VALUES,
|
|
463
|
+
run: runViewportSized
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
id: "universal.terminal_action_unguarded",
|
|
467
|
+
axis: "render",
|
|
468
|
+
values: ALL_RENDER_VALUES,
|
|
469
|
+
run: runTerminalActionUnguarded
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
id: "universal.accent_text_ink",
|
|
473
|
+
axis: "render",
|
|
474
|
+
values: ALL_RENDER_VALUES,
|
|
475
|
+
run: runAccentTextInk
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
id: "universal.scoped_surface_owns_ground",
|
|
479
|
+
axis: "render",
|
|
480
|
+
values: ALL_RENDER_VALUES,
|
|
481
|
+
run: runScopedSurfaceOwnsGround
|
|
482
|
+
},
|
|
264
483
|
{
|
|
265
484
|
id: "universal.prop_coverage",
|
|
266
485
|
axis: "render",
|
|
@@ -1063,6 +1282,87 @@ var TOOLING_CHECKS = [
|
|
|
1063
1282
|
}
|
|
1064
1283
|
];
|
|
1065
1284
|
|
|
1285
|
+
// src/evaluation/axis-checks/checks/grid.ts
|
|
1286
|
+
var BOARD_PROP_RX = /^(columns?|lanes?|stages?|lists?|buckets?|swimlanes?)$/i;
|
|
1287
|
+
var BOARD_PROMPT_RX = /\b(kanban|board)\b/i;
|
|
1288
|
+
var BOARD_NAMES = ["columns", "lanes", "stages", "lists", "buckets", "swimlanes"];
|
|
1289
|
+
var LAYOUT_TAG_RX = /<(\/?)(Grid|Row|Stack|Card|Box|Container|ScrollArea|div|section)\b([^<>]*?)>/g;
|
|
1290
|
+
var SIDE_BY_SIDE = /* @__PURE__ */ new Set(["Grid", "Row"]);
|
|
1291
|
+
function boardPropNames(contract) {
|
|
1292
|
+
const propsField = contract?.propsSpec;
|
|
1293
|
+
const properties = propsField?.properties ?? {};
|
|
1294
|
+
return Object.entries(properties).filter(([name, p]) => BOARD_PROP_RX.test(name) && (p?.type === "array" || p?.schema?.type === "array")).map(([name]) => name);
|
|
1295
|
+
}
|
|
1296
|
+
function jsxMapPositions(sourceCode, name) {
|
|
1297
|
+
const rx = new RegExp(
|
|
1298
|
+
`\\b${name}(?:\\?\\.)?\\.map\\(\\s*\\(?\\s*\\w+(?:\\s*,\\s*\\w+)?\\s*\\)?\\s*=>\\s*(?:\\(?\\s*<|\\{[^{}]{0,600}?\\breturn\\s*\\(?\\s*<)`,
|
|
1299
|
+
"g"
|
|
1300
|
+
);
|
|
1301
|
+
return [...sourceCode.matchAll(rx)].map((m) => m.index ?? 0);
|
|
1302
|
+
}
|
|
1303
|
+
function enclosingLayoutTag(sourceCode, pos) {
|
|
1304
|
+
const stack = [];
|
|
1305
|
+
for (const m of sourceCode.slice(0, pos).matchAll(LAYOUT_TAG_RX)) {
|
|
1306
|
+
const closing = m[1] === "/";
|
|
1307
|
+
const name = m[2] ?? "";
|
|
1308
|
+
const attrs = m[3] ?? "";
|
|
1309
|
+
if (closing) {
|
|
1310
|
+
if (stack[stack.length - 1] === name) stack.pop();
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
if (attrs.trimEnd().endsWith("/")) continue;
|
|
1314
|
+
stack.push(name);
|
|
1315
|
+
}
|
|
1316
|
+
return stack[stack.length - 1];
|
|
1317
|
+
}
|
|
1318
|
+
function describeBoardRead(input) {
|
|
1319
|
+
const declared = boardPropNames(input.contract);
|
|
1320
|
+
const names = declared.length > 0 ? declared : BOARD_PROMPT_RX.test(input.originalPrompt) ? [...BOARD_NAMES] : BOARD_NAMES.filter((n) => jsxMapPositions(input.sourceCode, n).length > 0);
|
|
1321
|
+
const perName = names.map((name) => {
|
|
1322
|
+
const positions = jsxMapPositions(input.sourceCode, name);
|
|
1323
|
+
return { name, maps: positions.length, enclosing: positions.map((pos) => enclosingLayoutTag(input.sourceCode, pos) ?? "nothing") };
|
|
1324
|
+
});
|
|
1325
|
+
return { declared, names, perName };
|
|
1326
|
+
}
|
|
1327
|
+
function findStackedBoardColumns(input) {
|
|
1328
|
+
const stacked = [];
|
|
1329
|
+
for (const { name, enclosing } of describeBoardRead(input).perName) {
|
|
1330
|
+
for (const tag of enclosing) if (!SIDE_BY_SIDE.has(tag)) stacked.push([name, tag]);
|
|
1331
|
+
}
|
|
1332
|
+
return stacked;
|
|
1333
|
+
}
|
|
1334
|
+
function runBoardColumnsSideBySide(input) {
|
|
1335
|
+
if (input.compiledCode === null) return [];
|
|
1336
|
+
const standDown = input.designMode === "free";
|
|
1337
|
+
const stacked = standDown ? [] : findStackedBoardColumns(input);
|
|
1338
|
+
if (axisCheckTraceEnabled()) {
|
|
1339
|
+
const line = {
|
|
1340
|
+
...describeBoardRead(input),
|
|
1341
|
+
designMode: input.designMode ?? "constrained",
|
|
1342
|
+
standDown,
|
|
1343
|
+
result: standDown ? "stand-down" : stacked.length > 0 ? "fail" : "pass"
|
|
1344
|
+
};
|
|
1345
|
+
console.log(JSON.stringify({ boardColumnsTrace: line }));
|
|
1346
|
+
}
|
|
1347
|
+
if (stacked.length === 0) return [];
|
|
1348
|
+
const [name, tag] = stacked[0];
|
|
1349
|
+
return [
|
|
1350
|
+
mkIssue(
|
|
1351
|
+
"grid.board_columns_side_by_side",
|
|
1352
|
+
`The board's \`${name}\` are composed under <${tag}> \u2014 a vertical stack of column sections; at md and wider the columns must sit side by side.`,
|
|
1353
|
+
`Wrap the ${name} map in <Grid columns={{ base: 1, md: ${name}.length }} gap="md"> (each column its own <Stack> of cards); never a <Stack> of column sections.`
|
|
1354
|
+
)
|
|
1355
|
+
];
|
|
1356
|
+
}
|
|
1357
|
+
var GRID_CHECKS = [
|
|
1358
|
+
{
|
|
1359
|
+
id: "grid.board_columns_side_by_side",
|
|
1360
|
+
axis: "render",
|
|
1361
|
+
values: ["grid"],
|
|
1362
|
+
run: runBoardColumnsSideBySide
|
|
1363
|
+
}
|
|
1364
|
+
];
|
|
1365
|
+
|
|
1066
1366
|
// src/evaluation/axis-checks/extras.ts
|
|
1067
1367
|
function mkIssue2(subcategory, description, fix, result = "warn") {
|
|
1068
1368
|
return { tier: 0, result, category: "mode", subcategory, description, fix };
|
|
@@ -1202,6 +1502,7 @@ var REGISTRY = [
|
|
|
1202
1502
|
...WRITES_CHECKS,
|
|
1203
1503
|
...STATE_PAYLOAD_CHECKS,
|
|
1204
1504
|
...TOOLING_CHECKS,
|
|
1505
|
+
...GRID_CHECKS,
|
|
1205
1506
|
...EXTRA_CHECKS
|
|
1206
1507
|
];
|
|
1207
1508
|
|