@ggui-ai/ui-gen 0.28.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{adapter-h4_V69LI.d.ts → adapter-DLQ-5COm.d.ts} +1 -1
- package/dist/adapters/base.js +2 -27
- package/dist/adapters/base.js.map +1 -1
- package/dist/adapters/generation-dispatch.d.ts +14 -6
- package/dist/adapters/generation-dispatch.js +40 -19260
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -1
- package/dist/adapters/index.js +25 -3561
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/types.js +2 -9
- package/dist/adapters/types.js.map +1 -1
- package/dist/advanced/index.d.ts +13 -12
- package/dist/advanced/index.js +63 -21954
- package/dist/advanced/index.js.map +1 -1
- package/dist/{bank-Ctdp3FUA.d.ts → bank-LWaiAtaA.d.ts} +1 -1
- package/dist/blueprint-validator.js +6 -2301
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +3 -2
- package/dist/boilerplate.js +6 -1867
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +3 -2
- package/dist/check/index.js +6 -1923
- package/dist/check/index.js.map +1 -1
- package/dist/chunk-33B3BAWS.js +24 -0
- package/dist/chunk-33B3BAWS.js.map +1 -0
- package/dist/chunk-3HSGA6NI.js +591 -0
- package/dist/chunk-3HSGA6NI.js.map +1 -0
- package/dist/chunk-3NNNGNTO.js +1707 -0
- package/dist/chunk-3NNNGNTO.js.map +1 -0
- package/dist/chunk-3T2HMTON.js +79 -0
- package/dist/chunk-3T2HMTON.js.map +1 -0
- package/dist/chunk-3ZSSDV3L.js +123 -0
- package/dist/chunk-3ZSSDV3L.js.map +1 -0
- package/dist/chunk-4MBR5GA3.js +99 -0
- package/dist/chunk-4MBR5GA3.js.map +1 -0
- package/dist/chunk-56NJFD55.js +337 -0
- package/dist/chunk-56NJFD55.js.map +1 -0
- package/dist/chunk-5AGTY555.js +26 -0
- package/dist/chunk-5AGTY555.js.map +1 -0
- package/dist/chunk-5EJM7EGY.js +115 -0
- package/dist/chunk-5EJM7EGY.js.map +1 -0
- package/dist/chunk-5KXCQLLR.js +529 -0
- package/dist/chunk-5KXCQLLR.js.map +1 -0
- package/dist/chunk-5VBXHVEH.js +171 -0
- package/dist/chunk-5VBXHVEH.js.map +1 -0
- package/dist/chunk-6KDDEING.js +515 -0
- package/dist/chunk-6KDDEING.js.map +1 -0
- package/dist/chunk-6VDRWAP6.js +123 -0
- package/dist/chunk-6VDRWAP6.js.map +1 -0
- package/dist/chunk-732IBFEJ.js +2400 -0
- package/dist/chunk-732IBFEJ.js.map +1 -0
- package/dist/chunk-73AURS3I.js +46 -0
- package/dist/chunk-73AURS3I.js.map +1 -0
- package/dist/chunk-77H5SKLX.js +8 -0
- package/dist/chunk-77H5SKLX.js.map +1 -0
- package/dist/chunk-7QE3AHLE.js +35 -0
- package/dist/chunk-7QE3AHLE.js.map +1 -0
- package/dist/chunk-A375NNWN.js +11 -0
- package/dist/chunk-A375NNWN.js.map +1 -0
- package/dist/chunk-AOU33EFS.js +140 -0
- package/dist/chunk-AOU33EFS.js.map +1 -0
- package/dist/chunk-BA4QYA6E.js +461 -0
- package/dist/chunk-BA4QYA6E.js.map +1 -0
- package/dist/chunk-DBP764GL.js +3 -0
- package/dist/chunk-DBP764GL.js.map +1 -0
- package/dist/chunk-DUU3ZJDQ.js +41 -0
- package/dist/chunk-DUU3ZJDQ.js.map +1 -0
- package/dist/chunk-DXKRBXNU.js +30 -0
- package/dist/chunk-DXKRBXNU.js.map +1 -0
- package/dist/chunk-FB7OEEAH.js +1467 -0
- package/dist/chunk-FB7OEEAH.js.map +1 -0
- package/dist/chunk-FLRNMZV2.js +18 -0
- package/dist/chunk-FLRNMZV2.js.map +1 -0
- package/dist/chunk-FWJIMKFZ.js +2032 -0
- package/dist/chunk-FWJIMKFZ.js.map +1 -0
- package/dist/chunk-FXRHWYIQ.js +163 -0
- package/dist/chunk-FXRHWYIQ.js.map +1 -0
- package/dist/chunk-G3PMV62Z.js +33 -0
- package/dist/chunk-G3PMV62Z.js.map +1 -0
- package/dist/chunk-GAJI7Y3P.js +13 -0
- package/dist/chunk-GAJI7Y3P.js.map +1 -0
- package/dist/chunk-GYINWMPR.js +2052 -0
- package/dist/chunk-GYINWMPR.js.map +1 -0
- package/dist/chunk-HD6ABIK7.js +484 -0
- package/dist/chunk-HD6ABIK7.js.map +1 -0
- package/dist/chunk-HDQVHYO6.js +100 -0
- package/dist/chunk-HDQVHYO6.js.map +1 -0
- package/dist/chunk-IHUT3SIW.js +7 -0
- package/dist/chunk-IHUT3SIW.js.map +1 -0
- package/dist/chunk-JDH5EGNZ.js +126 -0
- package/dist/chunk-JDH5EGNZ.js.map +1 -0
- package/dist/chunk-JMD4RGSV.js +37 -0
- package/dist/chunk-JMD4RGSV.js.map +1 -0
- package/dist/chunk-KJWGZL5B.js +33 -0
- package/dist/chunk-KJWGZL5B.js.map +1 -0
- package/dist/chunk-MKMDQFZ7.js +86 -0
- package/dist/chunk-MKMDQFZ7.js.map +1 -0
- package/dist/chunk-NF6JUZWF.js +85 -0
- package/dist/chunk-NF6JUZWF.js.map +1 -0
- package/dist/chunk-O2YC6XDC.js +1933 -0
- package/dist/chunk-O2YC6XDC.js.map +1 -0
- package/dist/chunk-OVLH5ICV.js +29 -0
- package/dist/chunk-OVLH5ICV.js.map +1 -0
- package/dist/chunk-PBOYHGSD.js +106 -0
- package/dist/chunk-PBOYHGSD.js.map +1 -0
- package/dist/chunk-PJFH7QWA.js +81 -0
- package/dist/chunk-PJFH7QWA.js.map +1 -0
- package/dist/chunk-PUH4CXGZ.js +284 -0
- package/dist/chunk-PUH4CXGZ.js.map +1 -0
- package/dist/chunk-QLXXPGTV.js +18 -0
- package/dist/chunk-QLXXPGTV.js.map +1 -0
- package/dist/chunk-R5GIGPW2.js +2845 -0
- package/dist/chunk-R5GIGPW2.js.map +1 -0
- package/dist/chunk-RH4IUSTO.js +83 -0
- package/dist/chunk-RH4IUSTO.js.map +1 -0
- package/dist/chunk-S2GVXKKY.js +153 -0
- package/dist/chunk-S2GVXKKY.js.map +1 -0
- package/dist/chunk-TVHGKFJS.js +58 -0
- package/dist/chunk-TVHGKFJS.js.map +1 -0
- package/dist/chunk-U7GALWLG.js +66 -0
- package/dist/chunk-U7GALWLG.js.map +1 -0
- package/dist/chunk-VTC263LT.js +256 -0
- package/dist/chunk-VTC263LT.js.map +1 -0
- package/dist/chunk-WEOXFEVE.js +739 -0
- package/dist/chunk-WEOXFEVE.js.map +1 -0
- package/dist/chunk-WYHP46LT.js +28 -0
- package/dist/chunk-WYHP46LT.js.map +1 -0
- package/dist/chunk-XGLO5LGG.js +274 -0
- package/dist/chunk-XGLO5LGG.js.map +1 -0
- package/dist/chunk-XRBCRKA2.js +29 -0
- package/dist/chunk-XRBCRKA2.js.map +1 -0
- package/dist/chunk-XW7ADFFV.js +340 -0
- package/dist/chunk-XW7ADFFV.js.map +1 -0
- package/dist/chunk-YIQ42IUX.js +34 -0
- package/dist/chunk-YIQ42IUX.js.map +1 -0
- package/dist/chunk-YJZTUY7B.js +101 -0
- package/dist/chunk-YJZTUY7B.js.map +1 -0
- package/dist/chunk-Z3CXME3K.js +107 -0
- package/dist/chunk-Z3CXME3K.js.map +1 -0
- package/dist/classifier/index.js +2 -527
- package/dist/classifier/index.js.map +1 -1
- package/dist/client-JBPCXU7W.js +4 -0
- package/dist/client-JBPCXU7W.js.map +1 -0
- package/dist/compose.js +3 -367
- package/dist/compose.js.map +1 -1
- package/dist/cost-tracker-HHGQ4FTW.js +67 -0
- package/dist/cost-tracker-HHGQ4FTW.js.map +1 -0
- package/dist/{create-ui-generator-BrZHF0cf.d.ts → create-ui-generator-BY-WeaPi.d.ts} +3 -3
- package/dist/{design-mode-DGWN_Gkm.d.ts → design-mode-XyOQmzE0.d.ts} +4 -3
- package/dist/{dispatch-B07TI30d.d.ts → dispatch-8LzTddKw.d.ts} +3 -2
- package/dist/dist-UALN6ZVB.js +543 -0
- package/dist/dist-UALN6ZVB.js.map +1 -0
- package/dist/dupe-break-X6K7QFIS.js +4 -0
- package/dist/dupe-break-X6K7QFIS.js.map +1 -0
- package/dist/evaluation/axis-checks/index.d.ts +5 -4
- package/dist/evaluation/axis-checks/index.js +6 -1914
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +3 -2
- package/dist/evaluation/axis-checks/registry.js +3 -1844
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +49 -132
- package/dist/evaluation/index.js +302 -6466
- package/dist/evaluation/index.js.map +1 -1
- package/dist/evaluation/types.d.ts +3 -2
- package/dist/evaluation/types.js +2 -5
- package/dist/evaluation/types.js.map +1 -1
- package/dist/fragments/index.js +2 -338
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-tSO_shQ0.d.ts → generate-DQ4ci90s.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +35 -15
- package/dist/harness/check/runtime-render/index.js +6 -2390
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.js +4 -1739
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +23 -5126
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.js +2 -24
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +8 -324
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +6 -5
- package/dist/harness/result-types.js +2 -0
- package/dist/harness/result-types.js.map +1 -1
- package/dist/harness/runtime.d.ts +22 -5
- package/dist/harness/runtime.js +10 -2313
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +3 -2
- package/dist/harness/types-public.js +16 -2954
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +3 -2
- package/dist/harness/validator-trace-sink.js +2 -26
- package/dist/harness/validator-trace-sink.js.map +1 -1
- package/dist/hash.js +2 -39
- package/dist/hash.js.map +1 -1
- package/dist/hashline-5OKREIRM.js +4 -0
- package/dist/hashline-5OKREIRM.js.map +1 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.js +57 -22015
- package/dist/index.js.map +1 -1
- package/dist/llm-evaluator-PN3WFCS4.js +653 -0
- package/dist/llm-evaluator-PN3WFCS4.js.map +1 -0
- package/dist/magic-string.es-2NZTC4GZ.js +1307 -0
- package/dist/magic-string.es-2NZTC4GZ.js.map +1 -0
- package/dist/patch.d.ts +3 -2
- package/dist/patch.js +2 -56
- package/dist/patch.js.map +1 -1
- package/dist/policy-VCYKGSAC.js +5 -0
- package/dist/policy-VCYKGSAC.js.map +1 -0
- package/dist/policy.js +2 -35
- package/dist/policy.js.map +1 -1
- package/dist/primitive-index-2OF6F4TZ.js +131 -0
- package/dist/primitive-index-2OF6F4TZ.js.map +1 -0
- package/dist/primitive-slice-WTMBLYFJ.js +235 -0
- package/dist/primitive-slice-WTMBLYFJ.js.map +1 -0
- package/dist/{prompts-BhNUuRoZ.d.ts → prompts-CLZGywMP.d.ts} +1 -1
- package/dist/provider-adapter-contract.js +10407 -14204
- package/dist/provider-adapter-contract.js.map +1 -1
- package/dist/provider-adapter.js +2 -44
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +89 -1
- package/dist/providers/index.js +115 -44
- package/dist/providers/index.js.map +1 -1
- package/dist/raw-C6KBH5I3.js +141 -0
- package/dist/raw-C6KBH5I3.js.map +1 -0
- package/dist/raw-FXSBXQTZ.js +7 -0
- package/dist/raw-FXSBXQTZ.js.map +1 -0
- package/dist/raw-MURDUPGK.js +8 -0
- package/dist/raw-MURDUPGK.js.map +1 -0
- package/dist/raw-T3GW3UVX.js +8 -0
- package/dist/raw-T3GW3UVX.js.map +1 -0
- package/dist/render-check-host-ZNIS6BJ5.js +5 -0
- package/dist/render-check-host-ZNIS6BJ5.js.map +1 -0
- package/dist/sdk-4JNSDNSR.js +6 -0
- package/dist/sdk-4JNSDNSR.js.map +1 -0
- package/dist/sdk-DIVJMHCX.js +7 -0
- package/dist/sdk-DIVJMHCX.js.map +1 -0
- package/dist/sdk-QRJOCRTS.js +7 -0
- package/dist/sdk-QRJOCRTS.js.map +1 -0
- package/dist/tag-balance-CTHCTRJ7.js +123 -0
- package/dist/tag-balance-CTHCTRJ7.js.map +1 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +3 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/tools/render-check-worker.js +3 -40
- package/dist/tools/render-check-worker.js.map +1 -1
- package/dist/{types-public-h8IMnYuh.d.ts → types-public-hYlrerZh.d.ts} +35 -2
- package/dist/validation/index.d.ts +2 -2
- package/dist/validation/index.js +3 -498
- package/dist/validation/index.js.map +1 -1
- package/dist/{visual-evaluator-C2strxqD.d.ts → visual-evaluator-KskajxD5.d.ts} +14 -4
- package/dist/visual-evaluator-TGEXYQTP.js +12 -0
- package/dist/visual-evaluator-TGEXYQTP.js.map +1 -0
- package/dist/workflows.d.ts +3 -2
- package/dist/workflows.js +2 -151
- package/dist/workflows.js.map +1 -1
- package/package.json +20 -16
- package/src/adapters/claude/sdk.ts +4 -2
- package/src/adapters/claude/tool-bridge.ts +2 -1
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openai/raw.ts +4 -0
- package/src/adapters/openai/sdk.ts +6 -1
- package/src/adapters/openai/tokens.ts +21 -7
- package/src/adapters/tool-bridge.ts +1 -1
- package/src/adapters/tools.ts +1 -1
- package/src/advanced/feedback.ts +1 -2
- package/src/advanced/generator.ts +4 -3
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/generate.ts +18 -5
- package/src/boilerplate/hard-sections.ts +8 -3
- package/src/boilerplate/react-hooks.ts +56 -0
- package/src/boilerplate/system-prompt.ts +12 -3
- package/src/check/run-tier0.ts +233 -8
- package/src/check/type-checker.ts +128 -16
- package/src/coding-agent/tools.ts +26 -3
- package/src/design-mode.ts +4 -9
- package/src/evaluation/axis-checks/checks/state-merge.ts +2 -4
- package/src/evaluation/axis-checks/checks/state-payload.ts +1 -28
- package/src/evaluation/axis-checks/checks/tint.ts +197 -0
- package/src/evaluation/axis-checks/helpers.ts +121 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/criteria/__fixtures__/edge-v2-draw2.json +4629 -0
- package/src/evaluation/criteria/bank.ts +1 -1
- package/src/evaluation/criteria/edge-instruments.ts +14 -0
- package/src/evaluation/criteria/edge-probe-v2.ts +352 -0
- package/src/evaluation/criteria/edge-v2.ts +242 -0
- package/src/evaluation/criteria/resolve.ts +24 -5
- package/src/evaluation/index.ts +3 -5
- package/src/evaluation/mcp-server.ts +0 -50
- package/src/evaluation/types-public.ts +6 -1
- package/src/evaluation/visual-evaluator.ts +161 -30
- package/src/harness/check/runtime-render/index.ts +28 -4
- package/src/harness/check/runtime-render/render-check.ts +20 -21
- package/src/harness/claude-agent-sdk-loader.ts +93 -0
- package/src/harness/claude-code-login-query.ts +1 -1
- package/src/harness/coding/generate-task-runner.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +3 -0
- package/src/harness/coding/run-eval-round.ts +9 -0
- package/src/harness/llm-router.ts +50 -2
- package/src/harness/runtime.ts +17 -0
- package/src/index.ts +9 -0
- package/src/providers/anthropic-tool-call.ts +263 -0
- package/src/providers/index.ts +13 -0
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools/render-check.ts +4 -4
- package/src/validation/primitives.ts +2 -1
- package/src/evaluation/loop.ts +0 -218
|
@@ -445,9 +445,10 @@ EXACT primitive prop values (other values are silently ignored — the design sy
|
|
|
445
445
|
- \`<Icon name="..." tone="...">\` / \`<Spinner tone="...">\` / \`<Link href="..." tone="...">\` / \`<Divider tone="...">\` — same \`tone\` vocabulary as Text. Default = \`currentColor\` (Icon), primary-tinted (Spinner / Link), outlineVariant (Divider). Use \`tone="inherit"\` when you want the element to track the parent's foreground color (e.g. an Icon next to muted text).
|
|
446
446
|
- \`<Button variant="...">\` — \`primary | secondary | outline | ghost | danger\`. Sizes \`xs | sm | md | lg\`. Use \`primary\` for the main action — renders in the brand color automatically.
|
|
447
447
|
- \`<Card padding="lg" shadow="md" radius="lg" surface="default">\` — shadow \`none|sm|md|lg|xl\`, radius \`none|sm|md|lg|xl\`. \`surface\` slot picks the fill: \`default | elevated | sunken | accent | inverted | hero | transparent\`. Use \`inverted\` for dark testimonial-style cards on a light theme; \`accent\` for branded fills; \`hero\` for the one hero / greeting panel (the theme's hero ground: brand-tinted on a light host, the ink pair on a dark one). \`inverted\` and \`hero\` OWN their ground — never set \`background\` in their \`style\` (tier-0 fails it); the surface paints it.
|
|
448
|
-
- \`<Box surface="...">\` — same surface slots as Card. \`surface\`
|
|
448
|
+
- \`<Box surface="...">\` — same surface slots as Card. \`surface\` (layout slots) and \`tint\` (data categories) are the ONLY theme-tracking background props; the legacy \`background="..."\` prop has been removed. To colour by a DATA CATEGORY (series, lanes, kinds, tags, element groups), use \`tint="tint-1"\`…\`"tint-8"\` (see "Categorical tint" below). For non-theme-mapped brand colors (a partner's exact brand hex like Stripe purple), use the typed escape \`<Box assetColor="#635BFF" assetSemantic="stripe-brand-purple">\` — both props are required, and \`assetSemantic\` MUST be a non-empty human-readable label. Tier-0 self-check rejects every other hex / rgba on Box.
|
|
449
449
|
- \`<Stack gap="...">\` / \`<Row gap="...">\` — \`gap\` takes the **spacing scale** (next bullet). \`align\` (cross-axis) is ONLY \`start | center | end | stretch\` and \`justify\` (main-axis) is ONLY \`start | center | end | between | around | evenly\` — NEVER the raw CSS values \`flex-start\` / \`flex-end\` / \`space-between\`, which are type errors.
|
|
450
450
|
- **Spacing scale** — \`gap\` (Stack / Row / Grid) and \`padding\` (Card / Box / Container) take a t-shirt size: \`none | xs | sm | md | lg | xl | 2xl\`. Each resolves to a \`--ggui-spacing-*\` token (xs≈4px, sm≈8px, md≈16px, lg≈24px, xl≈32px, 2xl≈48px). A bare number is treated as pixels. NEVER pass a raw CSS length such as \`gap="8px"\` — it is silently dropped by the browser and the gap collapses to 0; use the scale name (\`gap="sm"\`).
|
|
451
|
+
- **One content edge per card** — on \`Card\` and \`Box\`, \`padding\` sets the VERTICAL rhythm; the card owns the horizontal inset. Every block's content sits on the card's one left edge, and a filled or bordered surface steps its own content in by exactly one inset. So never use \`paddingX\` to push content sideways or a padded unstyled wrapper to indent a list. A badge, date or action on the right of a line belongs in a \`<Row justify="between">\` with content on its left, never alone on its own line.
|
|
451
452
|
- \`<Grid columns={N} gap="md">\` — 2-D layout (rows AND columns). Reach for it for card galleries, stat grids and dashboards — NEVER hand-roll \`style={{ display: 'grid' }}\`. When the request names exact per-breakpoint counts ("3 per row on desktop, 1 on mobile"), pass a map: \`<Grid columns={{ base: 1, md: 3 }}>\` (breakpoints \`sm\`/\`md\`/\`lg\`/\`xl\`; the design system emits the media queries). For an open-ended gallery where any column count is fine, use \`<Grid minColumnWidth={220}>\` — it fits as many equal columns as the width allows. \`radius\` (Card / Box / Image) takes the scale \`none | sm | md | lg | xl\`.
|
|
452
453
|
- \`<Stat label="…" value="…" delta="+12%" trend="up">\` — KPI display (label + big value + trend-coloured delta + optional \`icon\`). \`trend\` is \`up | down | neutral\` (delta renders green / red / muted). Reach for it for any "show a number" UI; drop several into a \`<Grid>\` for a stat grid instead of hand-building label+value pairs.
|
|
453
454
|
- \`<Stepper steps={STEPS} current={step} />\` — display-only step indicator for wizards/checkouts. \`steps\` is a top-level \`const\` array of labels; \`current\` is YOUR zero-indexed \`useState\` value; \`orientation\` is \`horizontal | vertical\`; optional \`onStepClick={(i) => …}\` makes steps clickable. Stepper never owns navigation state — your Next/Back handlers move \`current\`.
|
|
@@ -457,6 +458,14 @@ EXACT primitive prop values (other values are silently ignored — the design sy
|
|
|
457
458
|
|
|
458
459
|
**Asset-color escape (Box only).** When you genuinely need a non-theme color — a partner's exact brand hex (Stripe purple \`#635BFF\`, Slack aubergine \`#4A154B\`), a fixed product surface — use \`<Box assetColor="#635BFF" assetSemantic="stripe-brand-purple">…</Box>\`. The \`assetSemantic\` is REQUIRED and MUST be a non-empty human-readable label that documents intent. Tier-0 allows hex inside this typed pair; one without the other fails the check. Reach for \`surface\` first — \`assetColor\` is rare.
|
|
459
460
|
|
|
461
|
+
**Categorical tint (data categories).** When cells, rows or swatches must be told apart by a data category — element groups in a periodic table, lanes on a board, series in a chart legend, tags — set \`tint="tint-1"\` … \`tint="tint-8"\` on the Box or Card. The eight steps are theme colours: they follow the operator's theme in light and dark, and the tinted surface sets its own readable text colour. Rules:
|
|
462
|
+
- Tints are for **data categories only** — never for state (a status takes live, draft or signal, not a tint) and never as decoration.
|
|
463
|
+
- The category is **always also in words** — a label in the cell or a legend on the card. Colour is never the only way to tell categories apart.
|
|
464
|
+
- **Text on a tint is the theme's ink only, one short line** — a label, a symbol or a value, never \`tone="subtle"\` or muted text. Secondary or muted text sits beside the cell or in the legend, never on the tint.
|
|
465
|
+
- **A category keeps its tint for the life of the card.** Assign tints deterministically from the data, in the order the categories are first listed or sorted, never by render order, so an update or repaint never recolours a category.
|
|
466
|
+
- **More than eight categories:** group them to eight before assigning, or show the category in words only.
|
|
467
|
+
- Never put a theme colour through \`assetColor\` — that escape is only for an exact external brand hex.
|
|
468
|
+
|
|
460
469
|
## Accessibility (REQUIRED)
|
|
461
470
|
|
|
462
471
|
The design-system primitives are accessible by construction — they emit their own roles, labels, keyboard handlers, and error wiring. Your job is to USE them correctly, NOT to re-declare ARIA on top of them.
|
|
@@ -481,7 +490,7 @@ MANDATORY:
|
|
|
481
490
|
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))\`.
|
|
482
491
|
3. NEVER invent your own palette. The system provides primary, neutral, success, warning, error, and info — use ONLY these.
|
|
483
492
|
4. NEVER include literal fallback values in ANY token reference — colors, spacing, typography, radius, shadows alike. Write \`var(--ggui-spacing-4)\` bare, never \`var(--ggui-spacing-4, 16px)\`; \`var(--ggui-color-primary-600)\` bare, never \`var(--ggui-color-primary-600, #0284c7)\`. The runtime injects every token; a literal fallback paints the wrong value exactly when the operator's theme matters most.
|
|
484
|
-
5. NEVER set an inline \`border\`, \`borderColor\`, \`borderWidth\` or \`borderStyle\` on a kit control — \`Button\`, \`Input\`, \`Select\`, \`TextArea\`, \`Checkbox\`, \`RadioGroup\`, \`Toggle\`, \`SearchField\`. The control draws its own boundary, and it clears 3:1 on its surface in every theme: an outline chip is \`<Button variant="outline">\` exactly as it comes (its edge is \`controlAccentOutline\`). Where you draw a control-like edge on your own element, use \`var(--ggui-color-controlOutline)\`. A ramp step such as \`primary-200\` as a control's border is nearly invisible (about 1.1:1).
|
|
493
|
+
5. NEVER set an inline \`border\`, \`borderColor\`, \`borderWidth\` or \`borderStyle\` on a kit control — \`Button\`, \`Input\`, \`Select\`, \`TextArea\`, \`Checkbox\`, \`RadioGroup\`, \`Toggle\`, \`SearchField\`. The control draws its own boundary, and it clears 3:1 on its surface in every theme: an outline chip is \`<Button variant="outline">\` exactly as it comes (its edge is \`controlAccentOutline\`). Where you draw a control-like edge on your own element, use \`var(--ggui-color-controlOutline)\`. A ramp step such as \`primary-200\` as a control's border is nearly invisible (about 1.1:1). Its ink and fill are the variant's too: NEVER set an inline \`color\`, \`background\` or \`backgroundColor\` on a kit control, a token as much as a literal — pick the variant that carries the look.
|
|
485
494
|
|
|
486
495
|
Token categories:
|
|
487
496
|
- Brand fills: \`var(--ggui-color-primary-600)\`, \`var(--ggui-color-primary-50)\`
|
|
@@ -584,7 +593,7 @@ If your component is 100% purple text on purple backgrounds, you've lost the eye
|
|
|
584
593
|
### Visual rhythm — vary your card treatments
|
|
585
594
|
|
|
586
595
|
A row of identical flat tiles feels monotone. Use card-treatment variation to create rhythm:
|
|
587
|
-
- **Hero card**: \`<Card surface="hero" padding="xl" shadow="lg" radius="xl">\` — the theme's hero ground (brand-tinted on a light host, the ink pair on a dark one); anchors the eye
|
|
596
|
+
- **Hero card**: \`<Card surface="hero" padding="xl" shadow="lg" radius="xl">\` — the theme's hero ground (brand-tinted on a light host, the ink pair on a dark one); anchors the eye.
|
|
588
597
|
- **Stat tiles**: \`<Card padding="md" shadow="sm" radius="md">\` with surface bg — secondary
|
|
589
598
|
- **Inline rows / list items**: no card chrome at all, just \`<Stack gap="sm">\` with dividers — tertiary
|
|
590
599
|
|
package/src/check/run-tier0.ts
CHANGED
|
@@ -571,6 +571,171 @@ function detectComponentNeverReturns(
|
|
|
571
571
|
* Wraps the same logic as runSelfChecks (adapters/tools.ts) but emits EvalIssue[].
|
|
572
572
|
* Also runs contract validation when contract are provided.
|
|
573
573
|
*/
|
|
574
|
+
/**
|
|
575
|
+
* ggui#1977 / #1981: an opening band that lost its edge. The card keeps one content edge in every fit; only a hero
|
|
576
|
+
* Box that is the root's FIRST child, a hero Card that is its ONLY child (the runtime's legacy inference) or a root
|
|
577
|
+
* child that declares `bleed` meets the card's edge (design's `EXPANDED_FRAME_BLEEDS`). The shapes that miss it (the
|
|
578
|
+
* #1981 corpus analyzer's version of `hero-not-first` read 14 of 14 true on dev + staging, by the CDO; the cases
|
|
579
|
+
* added since, and `hero-card-first`, are pinned by unit tests and were not counted on the corpus, which is compiled
|
|
580
|
+
* JS this source reader does not parse):
|
|
581
|
+
* - `hero-not-first`: a band (a `surface="hero"` Box with no edge of its own: no `radius` but `none`, no style
|
|
582
|
+
* radius, shadow or border; a Card is a tile, never a band) that is a root child but not the first and does not
|
|
583
|
+
* declare `bleed`, OR the first child of a plain wrapper (a Stack, Box, Container or div that paints no ground
|
|
584
|
+
* and is not a horizontal Stack) that opens the root, `bleed` or not: the runtime honours `bleed` on root
|
|
585
|
+
* children only. It renders inside the inset, a box floating in the card.
|
|
586
|
+
* - `hero-card-first` (#1981 c.6314477145): a `surface="hero"` Card without `bleed` that is the root's first child
|
|
587
|
+
* AMONG siblings. A Card there is a tile and keeps the inset; if it is meant as the opening band, the band is a
|
|
588
|
+
* Box. Never a sole child: a Card that is the root's only child is the card's body and keeps its bleed.
|
|
589
|
+
* A root that already opens on a band is left alone (a second band is not this shape). Conditional children are read
|
|
590
|
+
* where they render: `a && <El/>`, `a || <El/>` and every ternary branch. When the band renders conditionally, or
|
|
591
|
+
* every slot before it does, the warning says so. The component is `export default function`, or
|
|
592
|
+
* `export default C` naming a function or an arrow in the file. Read from the source, never from a helper's JSX.
|
|
593
|
+
* A `warn`, not a `fail`.
|
|
594
|
+
* Retired (#1981, the CDO's read): `painted-band`, 7 of 8 served hits were contained Cards (tiles), not bands.
|
|
595
|
+
*/
|
|
596
|
+
function detectBandEdgeLost(
|
|
597
|
+
sourceCode: string,
|
|
598
|
+
): { readonly shape: 'hero-not-first' | 'hero-card-first'; readonly line: number; readonly conditional: boolean } | null {
|
|
599
|
+
const sf = ts.createSourceFile('source.tsx', sourceCode, ts.ScriptTarget.Latest, /* setParentNodes */ true, ts.ScriptKind.TSX);
|
|
600
|
+
type El = ts.JsxElement | ts.JsxSelfClosingElement;
|
|
601
|
+
const unwrap = (e: ts.Expression): ts.Expression => (ts.isParenthesizedExpression(e) ? unwrap(e.expression) : e);
|
|
602
|
+
const isEl = (n: ts.Node): n is El => ts.isJsxElement(n) || ts.isJsxSelfClosingElement(n);
|
|
603
|
+
const opening = (el: El): ts.JsxOpeningElement | ts.JsxSelfClosingElement => (ts.isJsxElement(el) ? el.openingElement : el);
|
|
604
|
+
const tag = (el: El): string => opening(el).tagName.getText(sf);
|
|
605
|
+
const attr = (el: El, name: string): ts.JsxAttribute | undefined =>
|
|
606
|
+
opening(el).attributes.properties.find((a): a is ts.JsxAttribute => ts.isJsxAttribute(a) && a.name.getText(sf) === name);
|
|
607
|
+
const stringAttr = (el: El, name: string): string | undefined => {
|
|
608
|
+
const a = attr(el, name);
|
|
609
|
+
if (a?.initializer === undefined) return undefined;
|
|
610
|
+
if (ts.isStringLiteral(a.initializer)) return a.initializer.text;
|
|
611
|
+
if (ts.isJsxExpression(a.initializer) && a.initializer.expression !== undefined && ts.isStringLiteral(a.initializer.expression)) {
|
|
612
|
+
return a.initializer.expression.text;
|
|
613
|
+
}
|
|
614
|
+
return undefined;
|
|
615
|
+
};
|
|
616
|
+
const flagOn = (el: El, name: string): boolean => {
|
|
617
|
+
const a = attr(el, name);
|
|
618
|
+
if (a === undefined) return false;
|
|
619
|
+
if (a.initializer === undefined) return true;
|
|
620
|
+
return !(ts.isJsxExpression(a.initializer) && a.initializer.expression?.kind === ts.SyntaxKind.FalseKeyword);
|
|
621
|
+
};
|
|
622
|
+
type Slot = { readonly alts: El[]; readonly conditional: boolean };
|
|
623
|
+
/**
|
|
624
|
+
* The elements a JSX child can render: itself, or every element branch of `a && <El/>`, `a || <El/>` and
|
|
625
|
+
* `a ? <El/> : <Other/>` (nested ternaries included).
|
|
626
|
+
*/
|
|
627
|
+
const branches = (x: ts.Expression): El[] => {
|
|
628
|
+
const e = unwrap(x);
|
|
629
|
+
if (isEl(e)) return [e];
|
|
630
|
+
const guard = ts.isBinaryExpression(e) && (e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || e.operatorToken.kind === ts.SyntaxKind.BarBarToken);
|
|
631
|
+
if (guard) return branches(e.right);
|
|
632
|
+
if (ts.isConditionalExpression(e)) return [...branches(e.whenTrue), ...branches(e.whenFalse)];
|
|
633
|
+
return [];
|
|
634
|
+
};
|
|
635
|
+
/** One rendered slot per JSX child; an expression child is conditional and lists the elements it may render. */
|
|
636
|
+
const slots = (el: El): Slot[] => {
|
|
637
|
+
if (!ts.isJsxElement(el)) return [];
|
|
638
|
+
const out: Slot[] = [];
|
|
639
|
+
for (const n of el.children) {
|
|
640
|
+
if (isEl(n)) out.push({ alts: [n], conditional: false });
|
|
641
|
+
else if (ts.isJsxExpression(n) && n.expression !== undefined) {
|
|
642
|
+
const alts = branches(n.expression);
|
|
643
|
+
if (alts.length > 0) out.push({ alts, conditional: true });
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return out;
|
|
647
|
+
};
|
|
648
|
+
const lineOf = (el: El): number => sf.getLineAndCharacterOfPosition(el.getStart(sf)).line + 1;
|
|
649
|
+
|
|
650
|
+
const styleKeys = (el: El): string[] => {
|
|
651
|
+
const style = attr(el, 'style')?.initializer;
|
|
652
|
+
if (style === undefined || !ts.isJsxExpression(style) || style.expression === undefined) return [];
|
|
653
|
+
const obj = unwrap(style.expression);
|
|
654
|
+
if (!ts.isObjectLiteralExpression(obj)) return [];
|
|
655
|
+
return obj.properties.filter(ts.isPropertyAssignment).map((pr) => pr.name.getText(sf).replace(/['"]/g, ''));
|
|
656
|
+
};
|
|
657
|
+
const paints = (el: El): boolean => {
|
|
658
|
+
const surface = stringAttr(el, 'surface');
|
|
659
|
+
if (surface !== undefined && surface !== 'transparent') return true;
|
|
660
|
+
if (attr(el, 'tint') !== undefined) return true;
|
|
661
|
+
return styleKeys(el).some((k) => k === 'background' || k === 'backgroundColor');
|
|
662
|
+
};
|
|
663
|
+
/** A Box with an edge of its own is a tile: a `radius` other than `none`, or a style radius, shadow or border. */
|
|
664
|
+
const hasOwnEdge = (el: El): boolean => {
|
|
665
|
+
if (attr(el, 'radius') !== undefined && stringAttr(el, 'radius') !== 'none') return true;
|
|
666
|
+
return styleKeys(el).some((k) => k === 'borderRadius' || k === 'boxShadow' || /^border(Top|Bottom|Left|Right)?(Width|Style|Color)?$/.test(k));
|
|
667
|
+
};
|
|
668
|
+
/** A hero Box with no edge of its own: a band. A Card is a tile, never a band. */
|
|
669
|
+
const isHeroBand = (el: El): boolean => tag(el) === 'Box' && stringAttr(el, 'surface') === 'hero' && !hasOwnEdge(el);
|
|
670
|
+
const isHeroCard = (el: El): boolean => tag(el) === 'Card' && stringAttr(el, 'surface') === 'hero' && !flagOn(el, 'bleed');
|
|
671
|
+
/** A wrapper that only stacks its children: it paints nothing and does not lay them out side by side. */
|
|
672
|
+
const isPlainWrapper = (el: El): boolean =>
|
|
673
|
+
['Stack', 'Box', 'Container', 'div'].includes(tag(el)) && !paints(el) && !(tag(el) === 'Stack' && stringAttr(el, 'direction') === 'horizontal');
|
|
674
|
+
|
|
675
|
+
// The default export's component: `export default function`, or `export default C` naming a function or a
|
|
676
|
+
// `const C = (…) => …` in this file, or an inline arrow / function expression.
|
|
677
|
+
type Fn = ts.FunctionDeclaration | ts.ArrowFunction | ts.FunctionExpression;
|
|
678
|
+
const byName = new Map<string, Fn>();
|
|
679
|
+
let component: Fn | undefined;
|
|
680
|
+
for (const st of sf.statements) {
|
|
681
|
+
if (ts.isFunctionDeclaration(st)) {
|
|
682
|
+
if (st.name !== undefined) byName.set(st.name.text, st);
|
|
683
|
+
if (st.modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword)) component = st;
|
|
684
|
+
} else if (ts.isVariableStatement(st)) {
|
|
685
|
+
for (const d of st.declarationList.declarations) {
|
|
686
|
+
const init = d.initializer === undefined ? undefined : unwrap(d.initializer);
|
|
687
|
+
if (ts.isIdentifier(d.name) && init !== undefined && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) byName.set(d.name.text, init);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
for (const st of sf.statements) {
|
|
692
|
+
if (component !== undefined || !ts.isExportAssignment(st) || st.isExportEquals === true) continue;
|
|
693
|
+
const e = unwrap(st.expression);
|
|
694
|
+
if (ts.isIdentifier(e)) component = byName.get(e.text);
|
|
695
|
+
else if (ts.isArrowFunction(e) || ts.isFunctionExpression(e)) component = e;
|
|
696
|
+
}
|
|
697
|
+
if (component?.body === undefined) return null;
|
|
698
|
+
let root: El | undefined;
|
|
699
|
+
if (!ts.isBlock(component.body)) {
|
|
700
|
+
const e = unwrap(component.body);
|
|
701
|
+
if (isEl(e)) root = e;
|
|
702
|
+
} else {
|
|
703
|
+
for (const st of component.body.statements) {
|
|
704
|
+
if (ts.isReturnStatement(st) && st.expression !== undefined) {
|
|
705
|
+
const e = unwrap(st.expression);
|
|
706
|
+
if (isEl(e)) root = e;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (root === undefined) return null;
|
|
711
|
+
|
|
712
|
+
const kids = slots(root);
|
|
713
|
+
const first = kids[0];
|
|
714
|
+
if (first === undefined) return null;
|
|
715
|
+
// A root that already opens on a band (always rendered) has its band; a second one is not this shape.
|
|
716
|
+
if (!first.conditional && first.alts.some(isHeroBand)) return null;
|
|
717
|
+
// A hero Card first AMONG siblings is a tile: it keeps the inset. Never a sole child (that Card is the card's body).
|
|
718
|
+
const heroCard = kids.length > 1 ? first.alts.find(isHeroCard) : undefined;
|
|
719
|
+
if (heroCard !== undefined) return { shape: 'hero-card-first', line: lineOf(heroCard), conditional: first.conditional };
|
|
720
|
+
// A band that is a later root child: the root's inset holds it unless it declares `bleed` (honoured on a root
|
|
721
|
+
// child). Conditional when it, or every slot before it, renders conditionally.
|
|
722
|
+
for (let i = 1; i < kids.length; i++) {
|
|
723
|
+
const k = kids[i]!;
|
|
724
|
+
const band = k.alts.find((a) => isHeroBand(a) && !flagOn(a, 'bleed'));
|
|
725
|
+
if (band !== undefined) {
|
|
726
|
+
return { shape: 'hero-not-first', line: lineOf(band), conditional: k.conditional || kids.slice(0, i).every((s) => s.conditional) };
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// A band first inside a plain wrapper that opens the root: `bleed` is not honoured that deep, so it warns either way.
|
|
730
|
+
const opener = first.alts.find(isPlainWrapper);
|
|
731
|
+
const inner = opener === undefined ? undefined : slots(opener)[0];
|
|
732
|
+
const wrapped = inner?.alts.find(isHeroBand);
|
|
733
|
+
if (wrapped !== undefined && inner !== undefined) {
|
|
734
|
+
return { shape: 'hero-not-first', line: lineOf(wrapped), conditional: first.conditional || inner.conditional };
|
|
735
|
+
}
|
|
736
|
+
return null;
|
|
737
|
+
}
|
|
738
|
+
|
|
574
739
|
/** The kit controls whose boundary the design system draws (ggui#1697). */
|
|
575
740
|
const KIT_CONTROLS = ['Button', 'Input', 'Select', 'TextArea', 'Checkbox', 'RadioGroup', 'Toggle', 'SearchField'] as const;
|
|
576
741
|
|
|
@@ -600,18 +765,22 @@ function kitControlTags(source: string): { name: string; attrs: string; line: nu
|
|
|
600
765
|
return out;
|
|
601
766
|
}
|
|
602
767
|
|
|
603
|
-
/** A border key
|
|
604
|
-
|
|
605
|
-
|
|
768
|
+
/** A border key, never a radius (ggui#1697). */
|
|
769
|
+
const CONTROL_BORDER_KEY = /\b(border(?:Top|Right|Bottom|Left)?(?:Color|Width|Style)?)\s*:/;
|
|
770
|
+
/** An ink or fill key (ggui#1966): a kit control's variant carries its ink and its fill, token or not. */
|
|
771
|
+
const CONTROL_COLOR_KEY = /\b(color|background|backgroundColor)\s*:/;
|
|
772
|
+
|
|
773
|
+
/** A matching key in the tag's inline style, or in a same-file object its `style={name}` points at. */
|
|
774
|
+
function inlineStyleKey(attrs: string, source: string, key: RegExp): string | null {
|
|
606
775
|
const style = attrs.match(/\bstyle=\{\{([\s\S]*)\}\}/);
|
|
607
776
|
if (style !== null) {
|
|
608
|
-
const hit = (style[1] ?? '').match(
|
|
777
|
+
const hit = (style[1] ?? '').match(key);
|
|
609
778
|
if (hit !== null) return hit[1] ?? null;
|
|
610
779
|
}
|
|
611
780
|
const named = attrs.match(/\bstyle=\{\s*([A-Za-z_$][\w$]*)\s*\}/);
|
|
612
781
|
if (named === null) return null;
|
|
613
782
|
const decl = source.match(new RegExp(`\\b(?:const|let)\\s+${named[1]}\\b[^=]*=\\s*\\{([^{}]*)\\}`));
|
|
614
|
-
const hit = decl !== null ? (decl[1] ?? '').match(
|
|
783
|
+
const hit = decl !== null ? (decl[1] ?? '').match(key) : null;
|
|
615
784
|
return hit !== null ? hit[1] ?? null : null;
|
|
616
785
|
}
|
|
617
786
|
|
|
@@ -703,6 +872,27 @@ export async function runTier0Checks(
|
|
|
703
872
|
const assetSemanticAttr = tagAttrs.match(/\bassetSemantic\s*=\s*["']([^"']*)["']/);
|
|
704
873
|
const startLine = sourceCode.slice(0, match.index ?? 0).split('\n').length;
|
|
705
874
|
const tagLineCount = match[0].split('\n').length;
|
|
875
|
+
// ggui#1846 — the escape is for an exact EXTERNAL brand colour. A theme
|
|
876
|
+
// token through it (`assetColor="var(--ggui-…)"`, or an expression whose
|
|
877
|
+
// text carries one) bypasses the door the theme already has: a categorical
|
|
878
|
+
// fill is `tint`, a theme-tracking fill is `surface`. Deterministic from the
|
|
879
|
+
// tag's own text, and always satisfiable, so it fails.
|
|
880
|
+
if (/var\(\s*--ggui-/.test(assetColorAttr[0])) {
|
|
881
|
+
issues.push({
|
|
882
|
+
tier: 0,
|
|
883
|
+
result: 'fail',
|
|
884
|
+
category: 'tokens',
|
|
885
|
+
subcategory: 'asset-color-theme-token',
|
|
886
|
+
severity: 'critical',
|
|
887
|
+
description:
|
|
888
|
+
'Box `assetColor` carries a theme token (`var(--ggui-…)`) — the brand-color escape is only for an exact external brand hex, and the theme already has its own doors.',
|
|
889
|
+
fix:
|
|
890
|
+
'To colour by a data category, use `tint="tint-1"` … `tint="tint-8"` (one step per category, in the data\'s order). ' +
|
|
891
|
+
'For a theme-tracking fill, use `surface="..."`. Drop `assetColor` and `assetSemantic`.',
|
|
892
|
+
line: startLine,
|
|
893
|
+
});
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
706
896
|
// Both attributes must be present, AND assetSemantic must be a
|
|
707
897
|
// non-empty string literal. A `{expr}` value for assetSemantic is
|
|
708
898
|
// treated as not-statically-verifiable; the escape is opt-in
|
|
@@ -722,8 +912,8 @@ export async function runTier0Checks(
|
|
|
722
912
|
'`assetSemantic` is a human-readable label that documents why this color bypasses the theme.',
|
|
723
913
|
fix:
|
|
724
914
|
'Pair the `assetColor` with a non-empty `assetSemantic` literal, e.g. ' +
|
|
725
|
-
'`<Box assetColor="#635BFF" assetSemantic="stripe-brand-purple">`.
|
|
726
|
-
'whenever the color SHOULD track the operator\'s theme.',
|
|
915
|
+
'`<Box assetColor="#635BFF" assetSemantic="stripe-brand-purple">`. The escape is only for an exact external brand hex: ' +
|
|
916
|
+
'to colour by a data category use `tint="tint-1"` … `tint="tint-8"`, and use `surface="..."` whenever the color SHOULD track the operator\'s theme.',
|
|
727
917
|
line: startLine,
|
|
728
918
|
});
|
|
729
919
|
continue;
|
|
@@ -1220,7 +1410,7 @@ export async function runTier0Checks(
|
|
|
1220
1410
|
// this check enforces what the model was told. The description starts with the check's id so the
|
|
1221
1411
|
// generator's logged violation lines (`✗ …`) count its fires per generation, in-turn repairs included.
|
|
1222
1412
|
for (const tag of enforceDesignVocabulary ? kitControlTags(sourceCode) : []) {
|
|
1223
|
-
const key =
|
|
1413
|
+
const key = inlineStyleKey(tag.attrs, sourceCode, CONTROL_BORDER_KEY);
|
|
1224
1414
|
if (key === null) continue;
|
|
1225
1415
|
issues.push({
|
|
1226
1416
|
tier: 0, result: 'fail', category: 'tokens', subcategory: 'control-inline-border', severity: 'critical',
|
|
@@ -1230,6 +1420,41 @@ export async function runTier0Checks(
|
|
|
1230
1420
|
});
|
|
1231
1421
|
}
|
|
1232
1422
|
|
|
1423
|
+
// ── A kit control's ink and fill are the kit's (ggui#1966) ──
|
|
1424
|
+
// The variant carries a control's ink and fill, derived to read on its surface in every theme; an inline
|
|
1425
|
+
// `color` or `background` paints over it, a token as much as a literal (`var(--ggui-color-onContainer)` on an
|
|
1426
|
+
// outline Button is still a restyle). The prompt's rule 5 says the same.
|
|
1427
|
+
for (const tag of enforceDesignVocabulary ? kitControlTags(sourceCode) : []) {
|
|
1428
|
+
const key = inlineStyleKey(tag.attrs, sourceCode, CONTROL_COLOR_KEY);
|
|
1429
|
+
if (key === null) continue;
|
|
1430
|
+
issues.push({
|
|
1431
|
+
tier: 0, result: 'fail', category: 'tokens', subcategory: 'control-inline-color', severity: 'critical',
|
|
1432
|
+
description: `control-inline-color: <${tag.name}> sets an inline \`${key}\` — a kit control's variant carries its ink and fill, and an inline one paints over it`,
|
|
1433
|
+
fix: `Remove \`${key}\` from <${tag.name}>'s style and pick the variant that carries the look (e.g. <Button variant="primary"> for the main action, variant="outline" for a chip). Colour your own element, not the kit's control.`,
|
|
1434
|
+
line: tag.line,
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// ── An opening band that lost its edge (ggui#1977 / #1981) ──
|
|
1439
|
+
// Only a first-child hero Box, a sole-child hero Card or a root-child `bleed` element meets the card's edge. The
|
|
1440
|
+
// prompt (FRAME_SIZING) teaches the band as a hero Box, the root's first child; this names a hero band placed
|
|
1441
|
+
// anywhere else, and a hero Card opening the root among siblings. A warn.
|
|
1442
|
+
const bandEdge = enforceDesignVocabulary ? detectBandEdgeLost(sourceCode) : null;
|
|
1443
|
+
if (bandEdge !== null) {
|
|
1444
|
+
issues.push({
|
|
1445
|
+
tier: 0, result: 'warn', category: 'layout', subcategory: `band-edge-lost:${bandEdge.shape}`, severity: 'major',
|
|
1446
|
+
description:
|
|
1447
|
+
bandEdge.shape === 'hero-card-first'
|
|
1448
|
+
? `band-edge-lost: a surface="hero" Card opens the card among other children; a Card is a tile and keeps the card's inset (read from the source${bandEdge.conditional ? '; it renders conditionally' : ''})`
|
|
1449
|
+
: `band-edge-lost: a surface="hero" band that is not the root's first child floats inside the card's inset (read from the source${bandEdge.conditional ? '; it, or everything before it, renders conditionally, and it was read where it renders' : ''})`,
|
|
1450
|
+
fix:
|
|
1451
|
+
bandEdge.shape === 'hero-card-first'
|
|
1452
|
+
? 'If this is the opening band, make it <Box surface="hero" bleed> (a Box, no radius, shadow or border of its own). A hero tile belongs among other tiles, in a grid or a row, not opening the card.'
|
|
1453
|
+
: 'Make the opening band the root\'s FIRST child as <Box surface="hero" bleed> (not wrapped in a Stack, not after other content). A band is a Box with no radius, shadow or border of its own; a hero Card is a tile and stays where it is. Put a conditional notice after the band, not before it.',
|
|
1454
|
+
line: bandEdge.line,
|
|
1455
|
+
});
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1233
1458
|
// ── Missing Props interface ───────────────────────────────
|
|
1234
1459
|
if (!sourceCode.includes('interface Props') && !sourceCode.includes('type Props')) {
|
|
1235
1460
|
issues.push({
|
|
@@ -59,7 +59,9 @@ import ts from 'typescript';
|
|
|
59
59
|
import fs from 'fs';
|
|
60
60
|
import path from 'path';
|
|
61
61
|
import { createRequire } from 'module';
|
|
62
|
+
import { TINT_STEPS } from '@ggui-ai/design';
|
|
62
63
|
import { describeAllowedImports } from '../validation/allowed-imports.js';
|
|
64
|
+
import { BOILERPLATE_REACT_HOOKS, BOILERPLATE_REACT_IMPORT } from '../boilerplate/react-hooks.js';
|
|
63
65
|
|
|
64
66
|
// ---------------------------------------------------------------------------
|
|
65
67
|
// Types
|
|
@@ -83,11 +85,12 @@ export interface TypeCheckResult {
|
|
|
83
85
|
|
|
84
86
|
const BLOCKING_CODES = new Set([
|
|
85
87
|
2304, // Cannot find name
|
|
86
|
-
// 2307 (Cannot find module) is NOT blocking
|
|
87
|
-
//
|
|
88
|
-
//
|
|
88
|
+
// 2307 (Cannot find module) is NOT blocking: a third-party gadget package
|
|
89
|
+
// with no `.d.ts` overlay can't be resolved by design, and forbidden imports
|
|
90
|
+
// are caught by runSelfChecks regex instead.
|
|
89
91
|
2305, // Module has no exported member
|
|
90
92
|
2322, // Type not assignable
|
|
93
|
+
2820, // Type not assignable — the "did you mean '…'?" form of 2322, for a near-miss literal (ggui#1849)
|
|
91
94
|
2339, // Property does not exist on type
|
|
92
95
|
2741, // Missing required property
|
|
93
96
|
2769, // No overload matches this call
|
|
@@ -100,6 +103,50 @@ const BLOCKING_CODES = new Set([
|
|
|
100
103
|
// Fix suggestions per error code
|
|
101
104
|
// ---------------------------------------------------------------------------
|
|
102
105
|
|
|
106
|
+
/**
|
|
107
|
+
* ggui#1933 — props a model reaches for from other kits, mapped to the design primitive's own name for the
|
|
108
|
+
* same thing. Applied only when the component's props type (read from the checker) actually has the target.
|
|
109
|
+
*/
|
|
110
|
+
const PROP_INTENT_ALIASES: Readonly<Record<string, string>> = { spacing: 'gap', space: 'gap', gutter: 'gap', color: 'tone' };
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* ggui#1933 — fix text for a prop the component does not take: TypeScript says the prop does not exist, never
|
|
114
|
+
* what the component accepts, so the generic text sent the model guessing (a fast arm looped on `<Stack spacing>`).
|
|
115
|
+
* `accepted` is the component's own props, read from its props type.
|
|
116
|
+
*/
|
|
117
|
+
export function unknownPropFix(component: string, prop: string, accepted: readonly string[], sourceHint: string, native = false): string {
|
|
118
|
+
const meant = PROP_INTENT_ALIASES[prop];
|
|
119
|
+
const named = meant !== undefined && accepted.includes(meant) ? ` For what \`${prop}\` does in other kits, \`<${component}>\` takes \`${meant}\`.` : '';
|
|
120
|
+
return `\`<${component}>\` takes no \`${prop}\` prop.${named} Its props: ${accepted.map((a) => `\`${a}\``).join(', ')}${native ? ', plus the native HTML attributes' : ''}. Use one of these or drop the prop.${sourceHint}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The JSX element a diagnostic sits in, and the prop names its component declares; `undefined` when unreadable.
|
|
125
|
+
* Props inherited from React's own attribute types (a control extending `InputHTMLAttributes`) are not listed one
|
|
126
|
+
* by one, so a native-extending control's list stays its own props; `native` says the native set is also accepted.
|
|
127
|
+
*/
|
|
128
|
+
function acceptedPropsAt(program: ts.Program, file: ts.SourceFile, pos: number): { component: string; accepted: string[]; native: boolean } | undefined {
|
|
129
|
+
const find = (node: ts.Node): ts.Node | undefined =>
|
|
130
|
+
pos >= node.getStart(file) && pos < node.getEnd() ? (ts.forEachChild(node, find) ?? node) : undefined;
|
|
131
|
+
let node = find(file);
|
|
132
|
+
while (node !== undefined && !ts.isJsxOpeningElement(node) && !ts.isJsxSelfClosingElement(node)) node = node.parent;
|
|
133
|
+
if (node === undefined) return undefined;
|
|
134
|
+
const checker = program.getTypeChecker();
|
|
135
|
+
const tagType = checker.getTypeAtLocation(node.tagName);
|
|
136
|
+
const signature = [...tagType.getCallSignatures(), ...tagType.getConstructSignatures()][0];
|
|
137
|
+
const param = signature?.getParameters()[0];
|
|
138
|
+
if (param === undefined) return undefined;
|
|
139
|
+
const propsType = checker.getTypeOfSymbolAtLocation(param, node);
|
|
140
|
+
const listed = checker
|
|
141
|
+
.getPropertiesOfType(propsType)
|
|
142
|
+
.filter((p) => !['children', 'key', 'ref'].includes(p.getName()) && !p.getName().startsWith('aria-') && !p.getName().startsWith('data-'));
|
|
143
|
+
const fromReact = (p: ts.Symbol): boolean =>
|
|
144
|
+
(p.getDeclarations() ?? []).every((d) => /[\\/]@types[\\/]react[\\/]/.test(d.getSourceFile().fileName));
|
|
145
|
+
const own = listed.filter((p) => !fromReact(p));
|
|
146
|
+
const accepted = own.map((p) => p.getName()).sort();
|
|
147
|
+
return accepted.length > 0 ? { component: node.tagName.getText(file), accepted, native: own.length < listed.length } : undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
103
150
|
function generateFix(code: number, message?: string, sourceLine?: string): string {
|
|
104
151
|
// Source-line hint: when the failing line is short and self-contained,
|
|
105
152
|
// appending it gives the LLM a precise target. Skip for very long lines
|
|
@@ -122,11 +169,32 @@ function generateFix(code: number, message?: string, sourceLine?: string): strin
|
|
|
122
169
|
return `You renamed a prop with a leading underscore to silence \`no-unused-vars\`, but the prop on \`Props\` doesn't have that prefix. Restore the original name; better, don't destructure props you won't render — access them via \`props.fieldName\` only when needed.${sourceHint}`;
|
|
123
170
|
}
|
|
124
171
|
|
|
172
|
+
// A React hook the boilerplate always imports is missing: the DO NOT EDIT React import line was
|
|
173
|
+
// dropped or rewritten. The generic text below (props / a removed helper) points the model away
|
|
174
|
+
// from the one line that fixes it, and it loops (google-fast × chat-interface reached 30 turns).
|
|
175
|
+
const missing = code === 2304 && message !== undefined ? /Cannot find name '([^']+)'/.exec(message)?.[1] : undefined;
|
|
176
|
+
if (missing !== undefined && BOILERPLATE_REACT_HOOKS.some((h) => h === missing)) {
|
|
177
|
+
return `\`${missing}\` is a React hook, and the boilerplate's React import lists it: \`${BOILERPLATE_REACT_IMPORT}\` is a DO NOT EDIT line. This patch dropped or rewrote it. Restore that line exactly as the boilerplate wrote it, at the top of the file; do not import the hook a second way.${sourceHint}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
125
180
|
// Detect "Cannot find name" for destructured props
|
|
126
181
|
if (code === 2304 && message && !message.includes('module') && !message.includes('import')) {
|
|
127
182
|
return `This name is not defined in scope. Either you destructured props (use \`props.fieldName\` directly instead) OR you removed a helper declaration in this patch but kept a JSX/expression reference to it. Read your full patch and either restore the declaration or remove the reference.${sourceHint}`;
|
|
128
183
|
}
|
|
129
184
|
|
|
185
|
+
// ggui#1846 — a categorical tint passed to `surface`. `surface` takes a layout slot; a data
|
|
186
|
+
// category takes `tint`. TypeScript names the slot union but never the prop, and the line it
|
|
187
|
+
// reports is the attribute's own (`surface={tint}`), so the key is that line plus a
|
|
188
|
+
// string-literal target type. A wrong prop on the same line reports the props object instead
|
|
189
|
+
// (`Type '{ … }' is not assignable to …`) and keeps the generic text. With both colour props on
|
|
190
|
+
// one line, the target tells them apart: the `tint` prop's union is the tint steps, the surface
|
|
191
|
+
// union never contains one. Only the TARGET is read, because the rejected value may itself be a
|
|
192
|
+
// tint (`Type '"tint-3"' is not assignable to type '"default" | …'`).
|
|
193
|
+
const target = message === undefined ? undefined : /is not assignable to type '(.*)/.exec(message)?.[1];
|
|
194
|
+
if ((code === 2322 || code === 2820) && target !== undefined && target.startsWith('"') && sourceLine && /(?<![\w-])surface=/.test(sourceLine) && !TINT_STEPS.some((s) => target.includes(`"${s}"`))) {
|
|
195
|
+
return `\`surface\` takes a layout slot only, one of the values the message lists. To colour by a data category (series, lanes, kinds, element groups), set \`tint\` instead, on the same Box or Card: ${TINT_STEPS.map((s) => `tint="${s}"`).join(', ')}. When the value comes from a map or a helper, type it as the tint union (${TINT_STEPS.map((s) => `'${s}'`).join(' | ')}), not \`string\`. Never cast it.${sourceHint}`;
|
|
196
|
+
}
|
|
197
|
+
|
|
130
198
|
// Detect "unknown is not assignable to ReactNode"
|
|
131
199
|
if ((code === 2322 || code === 2769) && message && /unknown.*ReactNode|ReactNode.*unknown/.test(message)) {
|
|
132
200
|
return `This value has type 'unknown' and cannot be rendered in JSX. Cast it: String(value) or add a type annotation.${sourceHint}`;
|
|
@@ -136,6 +204,7 @@ function generateFix(code: number, message?: string, sourceLine?: string): strin
|
|
|
136
204
|
case 2307:
|
|
137
205
|
return `Only these imports are allowed: ${describeAllowedImports()}${sourceHint}`;
|
|
138
206
|
case 2322:
|
|
207
|
+
case 2820:
|
|
139
208
|
case 2769:
|
|
140
209
|
return `Type mismatch on this expression. Check the offending line below — the prop name in JSX is what TypeScript is rejecting; the message tells you the expected type.${sourceHint}`;
|
|
141
210
|
case 2339:
|
|
@@ -163,6 +232,12 @@ interface VfsEntry {
|
|
|
163
232
|
|
|
164
233
|
let vfsCache: Map<string, VfsEntry> | null = null;
|
|
165
234
|
|
|
235
|
+
/**
|
|
236
|
+
* Where `csstype`'s declarations sit in the sandbox (ggui#1871). `@types/react`'s
|
|
237
|
+
* `import … from "csstype"` resolves here through the generic bare-specifier lookup.
|
|
238
|
+
*/
|
|
239
|
+
const CSSTYPE_VFS_PATH = 'node_modules/csstype/index.d.ts';
|
|
240
|
+
|
|
166
241
|
function parseAndStore(
|
|
167
242
|
vfs: Map<string, VfsEntry>,
|
|
168
243
|
virtualPath: string,
|
|
@@ -204,14 +279,41 @@ function walkDir(dir: string, filter: (f: string) => boolean): string[] {
|
|
|
204
279
|
* walk lands in the wrong place).
|
|
205
280
|
*/
|
|
206
281
|
function findReactTypesDir(): string | null {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
282
|
+
// `@types/react` (19.2+) declares an `exports` map whose entries carry only
|
|
283
|
+
// `types` conditions, so `require.resolve` refuses every subpath of it —
|
|
284
|
+
// `@types/react/index.d.ts` included. That made this function return null
|
|
285
|
+
// and the sandbox ran WITHOUT React's types (ggui#1849). Plain components
|
|
286
|
+
// kept their own props through the global JSX shim, but every prop typed
|
|
287
|
+
// through React (`ref`, `style`, handlers) degraded to `any` (`style` needs
|
|
288
|
+
// `csstype` as well — {@link findCsstypeDir}, ggui#1871), and so did every
|
|
289
|
+
// prop of the trait-composed primitives (Box, Card, Grid, Row, Stack): their
|
|
290
|
+
// `WithTrait` union has an arm built from React's types, and a union with an
|
|
291
|
+
// `any` arm is `any`. Walk Node's own
|
|
292
|
+
// lookup paths for the package directory instead; `resolve.paths` applies no
|
|
293
|
+
// `exports` restriction, and the index file is checked on disk.
|
|
294
|
+
const lookup = createRequire(import.meta.url).resolve.paths('@types/react') ?? [];
|
|
295
|
+
for (const dir of lookup) {
|
|
296
|
+
const candidate = path.join(dir, '@types', 'react');
|
|
297
|
+
if (fs.existsSync(path.join(candidate, 'index.d.ts'))) return candidate;
|
|
298
|
+
}
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Resolve `csstype`'s root directory (ggui#1871). `@types/react/index.d.ts`
|
|
304
|
+
* opens with `import * as CSS from "csstype"`; without it `CSS` is unresolved,
|
|
305
|
+
* `CSSProperties` has no members and every `style={{…}}` goes unchecked.
|
|
306
|
+
* `csstype` is `@types/react`'s own dependency, so it is looked up from that
|
|
307
|
+
* package's real directory — the same place Node would find it for React's
|
|
308
|
+
* types, nested or hoisted.
|
|
309
|
+
*/
|
|
310
|
+
function findCsstypeDir(reactDir: string): string | null {
|
|
311
|
+
const lookup = createRequire(path.join(fs.realpathSync(reactDir), 'index.d.ts')).resolve.paths('csstype') ?? [];
|
|
312
|
+
for (const dir of lookup) {
|
|
313
|
+
const candidate = path.join(dir, 'csstype');
|
|
314
|
+
if (fs.existsSync(path.join(candidate, 'index.d.ts'))) return candidate;
|
|
214
315
|
}
|
|
316
|
+
return null;
|
|
215
317
|
}
|
|
216
318
|
|
|
217
319
|
/**
|
|
@@ -249,11 +351,13 @@ async function loadVfs(): Promise<Map<string, VfsEntry>> {
|
|
|
249
351
|
}
|
|
250
352
|
|
|
251
353
|
// ---- 2. React types ----
|
|
252
|
-
//
|
|
253
|
-
//
|
|
254
|
-
//
|
|
255
|
-
// installs
|
|
256
|
-
//
|
|
354
|
+
// Found by `findReactTypesDir` (Node's lookup paths, no `exports`
|
|
355
|
+
// restriction) — position-independent: the same in the monorepo
|
|
356
|
+
// (hoisted to root), in a deployed closure, and in standalone
|
|
357
|
+
// `packages/`-only installs. `@types/react` is a runtime DEPENDENCY of
|
|
358
|
+
// this package, not a dev one (ggui#1849): a deployed closure never
|
|
359
|
+
// installs devDependencies, and without these types every JSX prop
|
|
360
|
+
// degrades to `any`.
|
|
257
361
|
const reactDir = findReactTypesDir();
|
|
258
362
|
if (reactDir) {
|
|
259
363
|
const reactDts = fs.readdirSync(reactDir).filter((f) => f.endsWith('.d.ts'));
|
|
@@ -261,6 +365,10 @@ async function loadVfs(): Promise<Map<string, VfsEntry>> {
|
|
|
261
365
|
const content = fs.readFileSync(path.join(reactDir, file), 'utf-8');
|
|
262
366
|
parseAndStore(vfs, `node_modules/@types/react/${file}`, content);
|
|
263
367
|
}
|
|
368
|
+
const csstypeDir = findCsstypeDir(reactDir);
|
|
369
|
+
if (csstypeDir) {
|
|
370
|
+
parseAndStore(vfs, CSSTYPE_VFS_PATH, fs.readFileSync(path.join(csstypeDir, 'index.d.ts'), 'utf-8'));
|
|
371
|
+
}
|
|
264
372
|
}
|
|
265
373
|
|
|
266
374
|
// ---- 3. Design system types ----
|
|
@@ -664,11 +772,15 @@ export async function typecheck(
|
|
|
664
772
|
? sourceLines[rawLine - 1]?.trim() ?? ''
|
|
665
773
|
: '';
|
|
666
774
|
|
|
775
|
+
// ggui#1933 — a prop the component does not take: name the props it does take, from its own props type.
|
|
776
|
+
const unknownProp = code_ === 2322 ? /Property '([^']+)' does not exist on type '[^']*Props[^']*'/.exec(message)?.[1] : undefined;
|
|
777
|
+
const props = unknownProp !== undefined ? acceptedPropsAt(program, diag.file, diag.start ?? 0) : undefined;
|
|
778
|
+
const hint = sourceLine.length > 0 && sourceLine.length <= 140 ? ` Offending line: \`${sourceLine}\`` : '';
|
|
667
779
|
const entry: TypeCheckDiagnostic = {
|
|
668
780
|
code: code_,
|
|
669
781
|
line,
|
|
670
782
|
message,
|
|
671
|
-
fix: generateFix(code_, message, sourceLine),
|
|
783
|
+
fix: unknownProp !== undefined && props !== undefined ? unknownPropFix(props.component, unknownProp, props.accepted, hint, props.native) : generateFix(code_, message, sourceLine),
|
|
672
784
|
};
|
|
673
785
|
|
|
674
786
|
if (BLOCKING_CODES.has(code_)) {
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
import * as esbuild from 'esbuild';
|
|
12
12
|
import { AgentWorkspace } from './workspace';
|
|
13
13
|
import { getSoftWarnings } from './self-check';
|
|
14
|
+
import { restoreReactImport } from '../boilerplate/react-hooks.js';
|
|
14
15
|
import { runTier0Checks } from '../check/index.js';
|
|
15
16
|
import { runGatedAxisChecks, traceAxisChecksSkipped } from '../evaluation/axis-checks/dispatch.js';
|
|
16
|
-
import type { AxisCheck, AxisCheckInput } from '../evaluation/types-public.js';
|
|
17
|
+
import type { AxisCheck, AxisCheckInput, EvalIssue } from '../evaluation/types-public.js';
|
|
17
18
|
import type { Classification } from '../classifier/axes.js';
|
|
18
19
|
import type { CanvasClass } from '../design-mode.js';
|
|
19
20
|
import { PRIMITIVES_DOCUMENTATION } from '../validation/index.js';
|
|
@@ -24,6 +25,21 @@ import type {
|
|
|
24
25
|
} from './types';
|
|
25
26
|
import { DEFAULT_DESIGN_MODE, type DesignMode } from '../design-mode.js';
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* ggui#1876 — the auto-commit line's axis field: the F/W counts, then the check ids behind them, so a
|
|
30
|
+
* WARN the model saw at a turn is attributable in the log (the `[WARN]` text otherwise appears only at
|
|
31
|
+
* the final evaluation). `0F/1W[W:universal.tint_category_in_words]`; nothing appended at `0F/0W`.
|
|
32
|
+
* The `<n>F/<n>W` prefix is unchanged, so anything that reads the counts reads them as before. An
|
|
33
|
+
* issue with no `subcategory` (the axis checks always set one, as their id) is named by its category.
|
|
34
|
+
*/
|
|
35
|
+
export function axisCountsWithIds(issues: readonly EvalIssue[]): string {
|
|
36
|
+
const idOf = (i: EvalIssue): string => i.subcategory ?? i.category;
|
|
37
|
+
const warns = issues.filter((i) => i.result === 'warn').map(idOf);
|
|
38
|
+
const fails = issues.filter((i) => i.result !== 'warn').map(idOf);
|
|
39
|
+
const groups = [...(fails.length > 0 ? [`F:${fails.join(',')}`] : []), ...(warns.length > 0 ? [`W:${warns.join(',')}`] : [])];
|
|
40
|
+
return `${fails.length}F/${warns.length}W${groups.length > 0 ? `[${groups.join(' ')}]` : ''}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
27
43
|
/**
|
|
28
44
|
* Extract documentation for a single component from the full primitives docs.
|
|
29
45
|
* Returns ~200-500 tokens instead of 43K.
|
|
@@ -106,7 +122,14 @@ async function autoCommit(
|
|
|
106
122
|
// No post-compile formatting — the generated TSX is built as-is.
|
|
107
123
|
// (Prettier auto-format was removed; it added a dependency + repair
|
|
108
124
|
// churn without a quality gain.)
|
|
109
|
-
|
|
125
|
+
// ggui#1932: the boilerplate's DO NOT EDIT React import is code's to keep — restored here, before
|
|
126
|
+
// the build and tier-0, so a dropped hook never becomes a refusal the model spends a turn on.
|
|
127
|
+
const restore = restoreReactImport(raw);
|
|
128
|
+
if (restore.added.length > 0) {
|
|
129
|
+
workspace.write(restore.source);
|
|
130
|
+
console.log(`[coding-agent] react-import restored: ${restore.added.map((h) => `+${h}`).join(',')}`);
|
|
131
|
+
}
|
|
132
|
+
const formatted = restore.source;
|
|
110
133
|
|
|
111
134
|
// Build with esbuild — keep all imports (react, @ggui-ai/design)
|
|
112
135
|
// esbuild.transform() passes them through as-is, the runtime resolves them
|
|
@@ -197,7 +220,7 @@ async function autoCommit(
|
|
|
197
220
|
|
|
198
221
|
const status = buildSuccess && selfCheckPassed ? 'PASS' : 'FAIL';
|
|
199
222
|
console.log(
|
|
200
|
-
`[coding-agent] auto-commit: ${status} | build=${buildMs}ms self-check=${selfCheckMs}ms git=${gitMs}ms total=${Date.now() - commitStart}ms | violations=${violations.length} | axis=${axisRun === undefined ? 'off' :
|
|
223
|
+
`[coding-agent] auto-commit: ${status} | build=${buildMs}ms self-check=${selfCheckMs}ms git=${gitMs}ms total=${Date.now() - commitStart}ms | violations=${violations.length} | axis=${axisRun === undefined ? 'off' : axisCountsWithIds(axisIssues)}`,
|
|
201
224
|
);
|
|
202
225
|
if (violations.length > 0) {
|
|
203
226
|
for (const v of violations) {
|