@calibrate-ds/core 0.1.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/__tests__/check-component.test.d.ts +2 -0
- package/dist/__tests__/check-component.test.d.ts.map +1 -0
- package/dist/__tests__/check-component.test.js +103 -0
- package/dist/__tests__/check-component.test.js.map +1 -0
- package/dist/__tests__/compare-components.test.d.ts +2 -0
- package/dist/__tests__/compare-components.test.d.ts.map +1 -0
- package/dist/__tests__/compare-components.test.js +145 -0
- package/dist/__tests__/compare-components.test.js.map +1 -0
- package/dist/__tests__/naming-strategy.test.d.ts +2 -0
- package/dist/__tests__/naming-strategy.test.d.ts.map +1 -0
- package/dist/__tests__/naming-strategy.test.js +71 -0
- package/dist/__tests__/naming-strategy.test.js.map +1 -0
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.d.ts.map +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/ai/provider.d.ts +14 -0
- package/dist/ai/provider.d.ts.map +1 -0
- package/dist/ai/provider.js +63 -0
- package/dist/ai/provider.js.map +1 -0
- package/dist/ai/resolve-provider.d.ts +3 -0
- package/dist/ai/resolve-provider.d.ts.map +1 -0
- package/dist/ai/resolve-provider.js +61 -0
- package/dist/ai/resolve-provider.js.map +1 -0
- package/dist/bootstrap/generate-library-files.d.ts +7 -0
- package/dist/bootstrap/generate-library-files.d.ts.map +1 -0
- package/dist/bootstrap/generate-library-files.js +97 -0
- package/dist/bootstrap/generate-library-files.js.map +1 -0
- package/dist/bootstrap/index.d.ts +4 -0
- package/dist/bootstrap/index.d.ts.map +1 -0
- package/dist/bootstrap/index.js +4 -0
- package/dist/bootstrap/index.js.map +1 -0
- package/dist/bootstrap/types.d.ts +27 -0
- package/dist/bootstrap/types.d.ts.map +1 -0
- package/dist/bootstrap/types.js +2 -0
- package/dist/bootstrap/types.js.map +1 -0
- package/dist/bootstrap/write-library-bootstrap.d.ts +7 -0
- package/dist/bootstrap/write-library-bootstrap.d.ts.map +1 -0
- package/dist/bootstrap/write-library-bootstrap.js +35 -0
- package/dist/bootstrap/write-library-bootstrap.js.map +1 -0
- package/dist/config/config-schema.d.ts +131 -0
- package/dist/config/config-schema.d.ts.map +1 -0
- package/dist/config/config-schema.js +93 -0
- package/dist/config/config-schema.js.map +1 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load-config.d.ts +7 -0
- package/dist/config/load-config.d.ts.map +1 -0
- package/dist/config/load-config.js +63 -0
- package/dist/config/load-config.js.map +1 -0
- package/dist/context/compressed-context.d.ts +3 -0
- package/dist/context/compressed-context.d.ts.map +1 -0
- package/dist/context/compressed-context.js +145 -0
- package/dist/context/compressed-context.js.map +1 -0
- package/dist/context/context-index.d.ts +18 -0
- package/dist/context/context-index.d.ts.map +1 -0
- package/dist/context/context-index.js +59 -0
- package/dist/context/context-index.js.map +1 -0
- package/dist/context/export-context.d.ts +61 -0
- package/dist/context/export-context.d.ts.map +1 -0
- package/dist/context/export-context.js +191 -0
- package/dist/context/export-context.js.map +1 -0
- package/dist/context/get-component-context.d.ts +3 -0
- package/dist/context/get-component-context.d.ts.map +1 -0
- package/dist/context/get-component-context.js +898 -0
- package/dist/context/get-component-context.js.map +1 -0
- package/dist/context/hashing.d.ts +14 -0
- package/dist/context/hashing.d.ts.map +1 -0
- package/dist/context/hashing.js +53 -0
- package/dist/context/hashing.js.map +1 -0
- package/dist/context/metadata-extraction.d.ts +15 -0
- package/dist/context/metadata-extraction.d.ts.map +1 -0
- package/dist/context/metadata-extraction.js +47 -0
- package/dist/context/metadata-extraction.js.map +1 -0
- package/dist/design-tool/figma-adapter.d.ts +20 -0
- package/dist/design-tool/figma-adapter.d.ts.map +1 -0
- package/dist/design-tool/figma-adapter.js +46 -0
- package/dist/design-tool/figma-adapter.js.map +1 -0
- package/dist/design-tool/index.d.ts +4 -0
- package/dist/design-tool/index.d.ts.map +1 -0
- package/dist/design-tool/index.js +3 -0
- package/dist/design-tool/index.js.map +1 -0
- package/dist/design-tool/registry.d.ts +12 -0
- package/dist/design-tool/registry.d.ts.map +1 -0
- package/dist/design-tool/registry.js +23 -0
- package/dist/design-tool/registry.js.map +1 -0
- package/dist/design-tool/types.d.ts +28 -0
- package/dist/design-tool/types.d.ts.map +1 -0
- package/dist/design-tool/types.js +2 -0
- package/dist/design-tool/types.js.map +1 -0
- package/dist/diff/compare-components.d.ts +3 -0
- package/dist/diff/compare-components.d.ts.map +1 -0
- package/dist/diff/compare-components.js +198 -0
- package/dist/diff/compare-components.js.map +1 -0
- package/dist/diff/compare-dependencies.d.ts +3 -0
- package/dist/diff/compare-dependencies.d.ts.map +1 -0
- package/dist/diff/compare-dependencies.js +30 -0
- package/dist/diff/compare-dependencies.js.map +1 -0
- package/dist/diff/compare-tokens.d.ts +3 -0
- package/dist/diff/compare-tokens.d.ts.map +1 -0
- package/dist/diff/compare-tokens.js +52 -0
- package/dist/diff/compare-tokens.js.map +1 -0
- package/dist/diff/compute-design-diff.d.ts +3 -0
- package/dist/diff/compute-design-diff.d.ts.map +1 -0
- package/dist/diff/compute-design-diff.js +27 -0
- package/dist/diff/compute-design-diff.js.map +1 -0
- package/dist/diff/format-diff-summary.d.ts +3 -0
- package/dist/diff/format-diff-summary.d.ts.map +1 -0
- package/dist/diff/format-diff-summary.js +59 -0
- package/dist/diff/format-diff-summary.js.map +1 -0
- package/dist/diff/index.d.ts +6 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/index.js +6 -0
- package/dist/diff/index.js.map +1 -0
- package/dist/docs/generate-component-doc.d.ts +34 -0
- package/dist/docs/generate-component-doc.d.ts.map +1 -0
- package/dist/docs/generate-component-doc.js +269 -0
- package/dist/docs/generate-component-doc.js.map +1 -0
- package/dist/docs/generate-component-stories.d.ts +3 -0
- package/dist/docs/generate-component-stories.d.ts.map +1 -0
- package/dist/docs/generate-component-stories.js +116 -0
- package/dist/docs/generate-component-stories.js.map +1 -0
- package/dist/docs/generate-system-doc.d.ts +23 -0
- package/dist/docs/generate-system-doc.d.ts.map +1 -0
- package/dist/docs/generate-system-doc.js +337 -0
- package/dist/docs/generate-system-doc.js.map +1 -0
- package/dist/docs/index.d.ts +5 -0
- package/dist/docs/index.d.ts.map +1 -0
- package/dist/docs/index.js +5 -0
- package/dist/docs/index.js.map +1 -0
- package/dist/docs/setup-storybook.d.ts +26 -0
- package/dist/docs/setup-storybook.d.ts.map +1 -0
- package/dist/docs/setup-storybook.js +216 -0
- package/dist/docs/setup-storybook.js.map +1 -0
- package/dist/doctor/check-component.d.ts +5 -0
- package/dist/doctor/check-component.d.ts.map +1 -0
- package/dist/doctor/check-component.js +120 -0
- package/dist/doctor/check-component.js.map +1 -0
- package/dist/doctor/check-config.d.ts +4 -0
- package/dist/doctor/check-config.d.ts.map +1 -0
- package/dist/doctor/check-config.js +65 -0
- package/dist/doctor/check-config.js.map +1 -0
- package/dist/doctor/check-tokens.d.ts +4 -0
- package/dist/doctor/check-tokens.d.ts.map +1 -0
- package/dist/doctor/check-tokens.js +17 -0
- package/dist/doctor/check-tokens.js.map +1 -0
- package/dist/doctor/index.d.ts +5 -0
- package/dist/doctor/index.d.ts.map +1 -0
- package/dist/doctor/index.js +5 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/types.d.ts +30 -0
- package/dist/doctor/types.d.ts.map +1 -0
- package/dist/doctor/types.js +2 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/filesystem/ensure-dir.d.ts +5 -0
- package/dist/filesystem/ensure-dir.d.ts.map +1 -0
- package/dist/filesystem/ensure-dir.js +17 -0
- package/dist/filesystem/ensure-dir.js.map +1 -0
- package/dist/filesystem/index.d.ts +6 -0
- package/dist/filesystem/index.d.ts.map +1 -0
- package/dist/filesystem/index.js +6 -0
- package/dist/filesystem/index.js.map +1 -0
- package/dist/filesystem/resolve-component-path.d.ts +10 -0
- package/dist/filesystem/resolve-component-path.d.ts.map +1 -0
- package/dist/filesystem/resolve-component-path.js +32 -0
- package/dist/filesystem/resolve-component-path.js.map +1 -0
- package/dist/filesystem/resolve-ds-paths.d.ts +15 -0
- package/dist/filesystem/resolve-ds-paths.d.ts.map +1 -0
- package/dist/filesystem/resolve-ds-paths.js +51 -0
- package/dist/filesystem/resolve-ds-paths.js.map +1 -0
- package/dist/filesystem/workspace-root.d.ts +6 -0
- package/dist/filesystem/workspace-root.d.ts.map +1 -0
- package/dist/filesystem/workspace-root.js +21 -0
- package/dist/filesystem/workspace-root.js.map +1 -0
- package/dist/filesystem/write-json.d.ts +6 -0
- package/dist/filesystem/write-json.d.ts.map +1 -0
- package/dist/filesystem/write-json.js +10 -0
- package/dist/filesystem/write-json.js.map +1 -0
- package/dist/filesystem/write-text.d.ts +10 -0
- package/dist/filesystem/write-text.d.ts.map +1 -0
- package/dist/filesystem/write-text.js +22 -0
- package/dist/filesystem/write-text.js.map +1 -0
- package/dist/generators/components/generate-component-css.d.ts +6 -0
- package/dist/generators/components/generate-component-css.d.ts.map +1 -0
- package/dist/generators/components/generate-component-css.js +92 -0
- package/dist/generators/components/generate-component-css.js.map +1 -0
- package/dist/generators/components/generate-component-file.d.ts +8 -0
- package/dist/generators/components/generate-component-file.d.ts.map +1 -0
- package/dist/generators/components/generate-component-file.js +209 -0
- package/dist/generators/components/generate-component-file.js.map +1 -0
- package/dist/generators/components/generate-composite-file.d.ts +3 -0
- package/dist/generators/components/generate-composite-file.d.ts.map +1 -0
- package/dist/generators/components/generate-composite-file.js +77 -0
- package/dist/generators/components/generate-composite-file.js.map +1 -0
- package/dist/generators/components/generate-composite-types.d.ts +7 -0
- package/dist/generators/components/generate-composite-types.d.ts.map +1 -0
- package/dist/generators/components/generate-composite-types.js +43 -0
- package/dist/generators/components/generate-composite-types.js.map +1 -0
- package/dist/generators/components/generate-types-file.d.ts +8 -0
- package/dist/generators/components/generate-types-file.d.ts.map +1 -0
- package/dist/generators/components/generate-types-file.js +64 -0
- package/dist/generators/components/generate-types-file.js.map +1 -0
- package/dist/generators/components/helpers.d.ts +33 -0
- package/dist/generators/components/helpers.d.ts.map +1 -0
- package/dist/generators/components/helpers.js +124 -0
- package/dist/generators/components/helpers.js.map +1 -0
- package/dist/generators/components/index.d.ts +9 -0
- package/dist/generators/components/index.d.ts.map +1 -0
- package/dist/generators/components/index.js +10 -0
- package/dist/generators/components/index.js.map +1 -0
- package/dist/generators/components/layout-jsx.d.ts +14 -0
- package/dist/generators/components/layout-jsx.d.ts.map +1 -0
- package/dist/generators/components/layout-jsx.js +271 -0
- package/dist/generators/components/layout-jsx.js.map +1 -0
- package/dist/generators/components/prune-components.d.ts +15 -0
- package/dist/generators/components/prune-components.d.ts.map +1 -0
- package/dist/generators/components/prune-components.js +88 -0
- package/dist/generators/components/prune-components.js.map +1 -0
- package/dist/generators/components/token-helpers.d.ts +32 -0
- package/dist/generators/components/token-helpers.d.ts.map +1 -0
- package/dist/generators/components/token-helpers.js +188 -0
- package/dist/generators/components/token-helpers.js.map +1 -0
- package/dist/generators/components/write-components.d.ts +17 -0
- package/dist/generators/components/write-components.d.ts.map +1 -0
- package/dist/generators/components/write-components.js +212 -0
- package/dist/generators/components/write-components.js.map +1 -0
- package/dist/generators/components/write-composites.d.ts +4 -0
- package/dist/generators/components/write-composites.d.ts.map +1 -0
- package/dist/generators/components/write-composites.js +116 -0
- package/dist/generators/components/write-composites.js.map +1 -0
- package/dist/generators/composites/generate-composite-file.d.ts +3 -0
- package/dist/generators/composites/generate-composite-file.d.ts.map +1 -0
- package/dist/generators/composites/generate-composite-file.js +101 -0
- package/dist/generators/composites/generate-composite-file.js.map +1 -0
- package/dist/generators/composites/generate-composite-types.d.ts +7 -0
- package/dist/generators/composites/generate-composite-types.d.ts.map +1 -0
- package/dist/generators/composites/generate-composite-types.js +43 -0
- package/dist/generators/composites/generate-composite-types.js.map +1 -0
- package/dist/generators/composites/index.d.ts +4 -0
- package/dist/generators/composites/index.d.ts.map +1 -0
- package/dist/generators/composites/index.js +4 -0
- package/dist/generators/composites/index.js.map +1 -0
- package/dist/generators/composites/write-composites.d.ts +4 -0
- package/dist/generators/composites/write-composites.d.ts.map +1 -0
- package/dist/generators/composites/write-composites.js +116 -0
- package/dist/generators/composites/write-composites.js.map +1 -0
- package/dist/generators/registry.d.ts +9 -0
- package/dist/generators/registry.d.ts.map +1 -0
- package/dist/generators/registry.js +11 -0
- package/dist/generators/registry.js.map +1 -0
- package/dist/generators/scaffold/generate-placeholder-component-content.d.ts +9 -0
- package/dist/generators/scaffold/generate-placeholder-component-content.d.ts.map +1 -0
- package/dist/generators/scaffold/generate-placeholder-component-content.js +68 -0
- package/dist/generators/scaffold/generate-placeholder-component-content.js.map +1 -0
- package/dist/generators/scaffold/index.d.ts +3 -0
- package/dist/generators/scaffold/index.d.ts.map +1 -0
- package/dist/generators/scaffold/index.js +3 -0
- package/dist/generators/scaffold/index.js.map +1 -0
- package/dist/generators/scaffold/write-scaffold.d.ts +14 -0
- package/dist/generators/scaffold/write-scaffold.d.ts.map +1 -0
- package/dist/generators/scaffold/write-scaffold.js +87 -0
- package/dist/generators/scaffold/write-scaffold.js.map +1 -0
- package/dist/generators/tokens/generate-token-css.d.ts +6 -0
- package/dist/generators/tokens/generate-token-css.d.ts.map +1 -0
- package/dist/generators/tokens/generate-token-css.js +31 -0
- package/dist/generators/tokens/generate-token-css.js.map +1 -0
- package/dist/generators/tokens/generate-token-file-content.d.ts +6 -0
- package/dist/generators/tokens/generate-token-file-content.d.ts.map +1 -0
- package/dist/generators/tokens/generate-token-file-content.js +27 -0
- package/dist/generators/tokens/generate-token-file-content.js.map +1 -0
- package/dist/generators/tokens/index.d.ts +3 -0
- package/dist/generators/tokens/index.d.ts.map +1 -0
- package/dist/generators/tokens/index.js +3 -0
- package/dist/generators/tokens/index.js.map +1 -0
- package/dist/generators/tokens/write-tokens.d.ts +14 -0
- package/dist/generators/tokens/write-tokens.d.ts.map +1 -0
- package/dist/generators/tokens/write-tokens.js +96 -0
- package/dist/generators/tokens/write-tokens.js.map +1 -0
- package/dist/generators/types.d.ts +11 -0
- package/dist/generators/types.d.ts.map +1 -0
- package/dist/generators/types.js +2 -0
- package/dist/generators/types.js.map +1 -0
- package/dist/icons/enrich-icon-svgs.d.ts +10 -0
- package/dist/icons/enrich-icon-svgs.d.ts.map +1 -0
- package/dist/icons/enrich-icon-svgs.js +88 -0
- package/dist/icons/enrich-icon-svgs.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/index.d.ts +8 -0
- package/dist/manifest/index.d.ts.map +1 -0
- package/dist/manifest/index.js +80 -0
- package/dist/manifest/index.js.map +1 -0
- package/dist/mappings/build-file-mappings.d.ts +6 -0
- package/dist/mappings/build-file-mappings.d.ts.map +1 -0
- package/dist/mappings/build-file-mappings.js +37 -0
- package/dist/mappings/build-file-mappings.js.map +1 -0
- package/dist/mappings/index.d.ts +3 -0
- package/dist/mappings/index.d.ts.map +1 -0
- package/dist/mappings/index.js +3 -0
- package/dist/mappings/index.js.map +1 -0
- package/dist/mappings/naming-strategy.d.ts +20 -0
- package/dist/mappings/naming-strategy.d.ts.map +1 -0
- package/dist/mappings/naming-strategy.js +78 -0
- package/dist/mappings/naming-strategy.js.map +1 -0
- package/dist/model/build-design-system-model.d.ts +7 -0
- package/dist/model/build-design-system-model.d.ts.map +1 -0
- package/dist/model/build-design-system-model.js +78 -0
- package/dist/model/build-design-system-model.js.map +1 -0
- package/dist/model/index.d.ts +2 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +2 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/resolve-component.d.ts +11 -0
- package/dist/model/resolve-component.d.ts.map +1 -0
- package/dist/model/resolve-component.js +110 -0
- package/dist/model/resolve-component.js.map +1 -0
- package/dist/normalizer/classify-entities.d.ts +12 -0
- package/dist/normalizer/classify-entities.d.ts.map +1 -0
- package/dist/normalizer/classify-entities.js +62 -0
- package/dist/normalizer/classify-entities.js.map +1 -0
- package/dist/normalizer/helpers.d.ts +13 -0
- package/dist/normalizer/helpers.d.ts.map +1 -0
- package/dist/normalizer/helpers.js +27 -0
- package/dist/normalizer/helpers.js.map +1 -0
- package/dist/normalizer/index.d.ts +7 -0
- package/dist/normalizer/index.d.ts.map +1 -0
- package/dist/normalizer/index.js +7 -0
- package/dist/normalizer/index.js.map +1 -0
- package/dist/normalizer/instance-resolver.d.ts +13 -0
- package/dist/normalizer/instance-resolver.d.ts.map +1 -0
- package/dist/normalizer/instance-resolver.js +747 -0
- package/dist/normalizer/instance-resolver.js.map +1 -0
- package/dist/normalizer/layout-tree.d.ts +12 -0
- package/dist/normalizer/layout-tree.d.ts.map +1 -0
- package/dist/normalizer/layout-tree.js +312 -0
- package/dist/normalizer/layout-tree.js.map +1 -0
- package/dist/normalizer/naming.d.ts +11 -0
- package/dist/normalizer/naming.d.ts.map +1 -0
- package/dist/normalizer/naming.js +44 -0
- package/dist/normalizer/naming.js.map +1 -0
- package/dist/normalizer/normalize-components.d.ts +13 -0
- package/dist/normalizer/normalize-components.d.ts.map +1 -0
- package/dist/normalizer/normalize-components.js +520 -0
- package/dist/normalizer/normalize-components.js.map +1 -0
- package/dist/normalizer/normalize-composites.d.ts +8 -0
- package/dist/normalizer/normalize-composites.d.ts.map +1 -0
- package/dist/normalizer/normalize-composites.js +22 -0
- package/dist/normalizer/normalize-composites.js.map +1 -0
- package/dist/normalizer/normalize-instances.d.ts +9 -0
- package/dist/normalizer/normalize-instances.d.ts.map +1 -0
- package/dist/normalizer/normalize-instances.js +32 -0
- package/dist/normalizer/normalize-instances.js.map +1 -0
- package/dist/normalizer/types.d.ts +22 -0
- package/dist/normalizer/types.d.ts.map +1 -0
- package/dist/normalizer/types.js +2 -0
- package/dist/normalizer/types.js.map +1 -0
- package/dist/normalizer/variant-diff.d.ts +101 -0
- package/dist/normalizer/variant-diff.d.ts.map +1 -0
- package/dist/normalizer/variant-diff.js +307 -0
- package/dist/normalizer/variant-diff.js.map +1 -0
- package/dist/plan/implementation-plan.d.ts +28 -0
- package/dist/plan/implementation-plan.d.ts.map +1 -0
- package/dist/plan/implementation-plan.js +60 -0
- package/dist/plan/implementation-plan.js.map +1 -0
- package/dist/plan/index.d.ts +3 -0
- package/dist/plan/index.d.ts.map +1 -0
- package/dist/plan/index.js +3 -0
- package/dist/plan/index.js.map +1 -0
- package/dist/plan/resolve-affected-files.d.ts +11 -0
- package/dist/plan/resolve-affected-files.d.ts.map +1 -0
- package/dist/plan/resolve-affected-files.js +73 -0
- package/dist/plan/resolve-affected-files.js.map +1 -0
- package/dist/prompt/generate-component-prompt.d.ts +6 -0
- package/dist/prompt/generate-component-prompt.d.ts.map +1 -0
- package/dist/prompt/generate-component-prompt.js +1644 -0
- package/dist/prompt/generate-component-prompt.js.map +1 -0
- package/dist/registry/ai-provider-registry.d.ts +7 -0
- package/dist/registry/ai-provider-registry.d.ts.map +1 -0
- package/dist/registry/ai-provider-registry.js +21 -0
- package/dist/registry/ai-provider-registry.js.map +1 -0
- package/dist/registry/build-component-registry.d.ts +6 -0
- package/dist/registry/build-component-registry.d.ts.map +1 -0
- package/dist/registry/build-component-registry.js +43 -0
- package/dist/registry/build-component-registry.js.map +1 -0
- package/dist/registry/build-dependency-graph.d.ts +7 -0
- package/dist/registry/build-dependency-graph.d.ts.map +1 -0
- package/dist/registry/build-dependency-graph.js +36 -0
- package/dist/registry/build-dependency-graph.js.map +1 -0
- package/dist/registry/generator-registry.d.ts +12 -0
- package/dist/registry/generator-registry.d.ts.map +1 -0
- package/dist/registry/generator-registry.js +22 -0
- package/dist/registry/generator-registry.js.map +1 -0
- package/dist/registry/index.d.ts +3 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +3 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/scaffold-plan/build-scaffold-plan.d.ts +16 -0
- package/dist/scaffold-plan/build-scaffold-plan.d.ts.map +1 -0
- package/dist/scaffold-plan/build-scaffold-plan.js +91 -0
- package/dist/scaffold-plan/build-scaffold-plan.js.map +1 -0
- package/dist/scaffold-plan/index.d.ts +2 -0
- package/dist/scaffold-plan/index.d.ts.map +1 -0
- package/dist/scaffold-plan/index.js +2 -0
- package/dist/scaffold-plan/index.js.map +1 -0
- package/dist/scanner/collect-components.d.ts +10 -0
- package/dist/scanner/collect-components.d.ts.map +1 -0
- package/dist/scanner/collect-components.js +103 -0
- package/dist/scanner/collect-components.js.map +1 -0
- package/dist/scanner/collect-instances.d.ts +6 -0
- package/dist/scanner/collect-instances.d.ts.map +1 -0
- package/dist/scanner/collect-instances.js +31 -0
- package/dist/scanner/collect-instances.js.map +1 -0
- package/dist/scanner/index.d.ts +6 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +6 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/scan-file.d.ts +6 -0
- package/dist/scanner/scan-file.d.ts.map +1 -0
- package/dist/scanner/scan-file.js +28 -0
- package/dist/scanner/scan-file.js.map +1 -0
- package/dist/scanner/traverse-nodes.d.ts +15 -0
- package/dist/scanner/traverse-nodes.d.ts.map +1 -0
- package/dist/scanner/traverse-nodes.js +18 -0
- package/dist/scanner/traverse-nodes.js.map +1 -0
- package/dist/scanner/types.d.ts +89 -0
- package/dist/scanner/types.d.ts.map +1 -0
- package/dist/scanner/types.js +2 -0
- package/dist/scanner/types.js.map +1 -0
- package/dist/snapshots/index.d.ts +6 -0
- package/dist/snapshots/index.d.ts.map +1 -0
- package/dist/snapshots/index.js +6 -0
- package/dist/snapshots/index.js.map +1 -0
- package/dist/snapshots/load-imported-tokens.d.ts +8 -0
- package/dist/snapshots/load-imported-tokens.d.ts.map +1 -0
- package/dist/snapshots/load-imported-tokens.js +27 -0
- package/dist/snapshots/load-imported-tokens.js.map +1 -0
- package/dist/snapshots/load-latest-snapshot.d.ts +3 -0
- package/dist/snapshots/load-latest-snapshot.d.ts.map +1 -0
- package/dist/snapshots/load-latest-snapshot.js +16 -0
- package/dist/snapshots/load-latest-snapshot.js.map +1 -0
- package/dist/snapshots/load-previous-snapshot.d.ts +7 -0
- package/dist/snapshots/load-previous-snapshot.d.ts.map +1 -0
- package/dist/snapshots/load-previous-snapshot.js +39 -0
- package/dist/snapshots/load-previous-snapshot.js.map +1 -0
- package/dist/snapshots/save-imported-tokens.d.ts +4 -0
- package/dist/snapshots/save-imported-tokens.d.ts.map +1 -0
- package/dist/snapshots/save-imported-tokens.js +16 -0
- package/dist/snapshots/save-imported-tokens.js.map +1 -0
- package/dist/snapshots/save-snapshot.d.ts +3 -0
- package/dist/snapshots/save-snapshot.d.ts.map +1 -0
- package/dist/snapshots/save-snapshot.js +49 -0
- package/dist/snapshots/save-snapshot.js.map +1 -0
- package/dist/test-null-safety.d.ts +2 -0
- package/dist/test-null-safety.d.ts.map +1 -0
- package/dist/test-null-safety.js +67 -0
- package/dist/test-null-safety.js.map +1 -0
- package/dist/test-regex.d.ts +2 -0
- package/dist/test-regex.d.ts.map +1 -0
- package/dist/test-regex.js +25 -0
- package/dist/test-regex.js.map +1 -0
- package/dist/test-token-resolution.d.ts +2 -0
- package/dist/test-token-resolution.d.ts.map +1 -0
- package/dist/test-token-resolution.js +54 -0
- package/dist/test-token-resolution.js.map +1 -0
- package/dist/test-typography.d.ts +2 -0
- package/dist/test-typography.d.ts.map +1 -0
- package/dist/test-typography.js +69 -0
- package/dist/test-typography.js.map +1 -0
- package/dist/tokens/adapters/generic-adapter.d.ts +13 -0
- package/dist/tokens/adapters/generic-adapter.d.ts.map +1 -0
- package/dist/tokens/adapters/generic-adapter.js +74 -0
- package/dist/tokens/adapters/generic-adapter.js.map +1 -0
- package/dist/tokens/adapters/index.d.ts +3 -0
- package/dist/tokens/adapters/index.d.ts.map +1 -0
- package/dist/tokens/adapters/index.js +3 -0
- package/dist/tokens/adapters/index.js.map +1 -0
- package/dist/tokens/adapters/ptb-adapter.d.ts +13 -0
- package/dist/tokens/adapters/ptb-adapter.d.ts.map +1 -0
- package/dist/tokens/adapters/ptb-adapter.js +13 -0
- package/dist/tokens/adapters/ptb-adapter.js.map +1 -0
- package/dist/tokens/build-token-model.d.ts +12 -0
- package/dist/tokens/build-token-model.d.ts.map +1 -0
- package/dist/tokens/build-token-model.js +50 -0
- package/dist/tokens/build-token-model.js.map +1 -0
- package/dist/tokens/classify-token-kind.d.ts +7 -0
- package/dist/tokens/classify-token-kind.d.ts.map +1 -0
- package/dist/tokens/classify-token-kind.js +24 -0
- package/dist/tokens/classify-token-kind.js.map +1 -0
- package/dist/tokens/detect-format.d.ts +6 -0
- package/dist/tokens/detect-format.d.ts.map +1 -0
- package/dist/tokens/detect-format.js +23 -0
- package/dist/tokens/detect-format.js.map +1 -0
- package/dist/tokens/import-schema.d.ts +111 -0
- package/dist/tokens/import-schema.d.ts.map +1 -0
- package/dist/tokens/import-schema.js +45 -0
- package/dist/tokens/import-schema.js.map +1 -0
- package/dist/tokens/import-tokens.d.ts +16 -0
- package/dist/tokens/import-tokens.d.ts.map +1 -0
- package/dist/tokens/import-tokens.js +40 -0
- package/dist/tokens/import-tokens.js.map +1 -0
- package/dist/tokens/index.d.ts +12 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +12 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/naming.d.ts +19 -0
- package/dist/tokens/naming.d.ts.map +1 -0
- package/dist/tokens/naming.js +27 -0
- package/dist/tokens/naming.js.map +1 -0
- package/dist/tokens/normalize-token-collections.d.ts +7 -0
- package/dist/tokens/normalize-token-collections.d.ts.map +1 -0
- package/dist/tokens/normalize-token-collections.js +68 -0
- package/dist/tokens/normalize-token-collections.js.map +1 -0
- package/dist/tokens/resolution.d.ts +7 -0
- package/dist/tokens/resolution.d.ts.map +1 -0
- package/dist/tokens/resolution.js +223 -0
- package/dist/tokens/resolution.js.map +1 -0
- package/dist/tokens/resolve-token-source.d.ts +12 -0
- package/dist/tokens/resolve-token-source.d.ts.map +1 -0
- package/dist/tokens/resolve-token-source.js +37 -0
- package/dist/tokens/resolve-token-source.js.map +1 -0
- package/dist/tokens/types.d.ts +40 -0
- package/dist/tokens/types.d.ts.map +1 -0
- package/dist/tokens/types.js +2 -0
- package/dist/tokens/types.js.map +1 -0
- package/package.json +34 -0
|
@@ -0,0 +1,1644 @@
|
|
|
1
|
+
import { isIconComponent } from "../icons/enrich-icon-svgs.js";
|
|
2
|
+
import { normalizeIconDisplayName } from "../mappings/naming-strategy.js";
|
|
3
|
+
function buildCssSkeleton(context) {
|
|
4
|
+
const lines = [];
|
|
5
|
+
// Only emit var() when cssVar is a valid custom property (starts with --)
|
|
6
|
+
// Adds px to bare DIMENSION numbers so fallbacks are valid CSS.
|
|
7
|
+
const cssBinding = (b) => {
|
|
8
|
+
if (!b)
|
|
9
|
+
return null;
|
|
10
|
+
let val = b.cssValue || "";
|
|
11
|
+
// Strip unresolvable Figma variable IDs — they are not valid CSS fallback values
|
|
12
|
+
if (val.includes("VariableID:"))
|
|
13
|
+
val = "";
|
|
14
|
+
if (b.valueType === "DIMENSION" && /^\d+(\.\d+)?$/.test(val))
|
|
15
|
+
val = `${val}px`;
|
|
16
|
+
if (b.cssVar?.startsWith("--"))
|
|
17
|
+
return val ? `var(${b.cssVar}, ${val})` : `var(${b.cssVar})`;
|
|
18
|
+
return val || null;
|
|
19
|
+
};
|
|
20
|
+
// Root container
|
|
21
|
+
const containerLines = [];
|
|
22
|
+
if (context.layout) {
|
|
23
|
+
const l = context.layout;
|
|
24
|
+
containerLines.push(" display: flex;");
|
|
25
|
+
containerLines.push(` flex-direction: ${l.direction === "row" ? "row" : "column"};`);
|
|
26
|
+
if (l.crossAlignment) {
|
|
27
|
+
const a = { start: "flex-start", center: "center", end: "flex-end", baseline: "baseline" };
|
|
28
|
+
containerLines.push(` align-items: ${a[l.crossAlignment] ?? l.crossAlignment};`);
|
|
29
|
+
}
|
|
30
|
+
if (l.mainAlignment && l.mainAlignment !== "space-between") {
|
|
31
|
+
const j = { start: "flex-start", center: "center", end: "flex-end" };
|
|
32
|
+
containerLines.push(` justify-content: ${j[l.mainAlignment] ?? l.mainAlignment};`);
|
|
33
|
+
}
|
|
34
|
+
if (l.gap) {
|
|
35
|
+
if (l.gap.mode === "auto") {
|
|
36
|
+
containerLines.push(" justify-content: space-between;");
|
|
37
|
+
}
|
|
38
|
+
else if (l.gap.rawValue) {
|
|
39
|
+
containerLines.push(` gap: ${l.gap.rawValue}px;`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (l.padding) {
|
|
43
|
+
const p = l.padding;
|
|
44
|
+
const t = p.top.rawValue, r = p.right.rawValue, b = p.bottom.rawValue, lv = p.left.rawValue;
|
|
45
|
+
if (t === b && r === lv) {
|
|
46
|
+
containerLines.push(` padding: ${t}px ${r}px;`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
containerLines.push(` padding: ${t}px ${r}px ${b}px ${lv}px;`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (context.size) {
|
|
54
|
+
const s = context.size;
|
|
55
|
+
const wMode = s.width.mode;
|
|
56
|
+
if (wMode === "hug") {
|
|
57
|
+
containerLines.push(" width: fit-content;");
|
|
58
|
+
}
|
|
59
|
+
else if (wMode === "fill") {
|
|
60
|
+
containerLines.push(" width: 100%;");
|
|
61
|
+
}
|
|
62
|
+
else if (s.width.rawValue) {
|
|
63
|
+
containerLines.push(` /* ptb: fixed Figma value — consider max-width or width: 100% for responsive */`);
|
|
64
|
+
containerLines.push(` width: ${s.width.rawValue}px;`);
|
|
65
|
+
}
|
|
66
|
+
const hMode = s.height.mode;
|
|
67
|
+
if (hMode === "hug") {
|
|
68
|
+
containerLines.push(" height: fit-content;");
|
|
69
|
+
}
|
|
70
|
+
else if (hMode === "fill") {
|
|
71
|
+
containerLines.push(" height: 100%;");
|
|
72
|
+
}
|
|
73
|
+
else if (s.height.rawValue) {
|
|
74
|
+
containerLines.push(` /* ptb: fixed Figma value — consider min-height: ${s.height.rawValue}px or remove for responsive */`);
|
|
75
|
+
containerLines.push(` height: ${s.height.rawValue}px;`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Token-driven properties from resolvedBindings
|
|
79
|
+
const rb = context.resolvedBindings;
|
|
80
|
+
if (rb) {
|
|
81
|
+
const bg = cssBinding(rb.background);
|
|
82
|
+
const bd = cssBinding(rb.border);
|
|
83
|
+
const tx = cssBinding(rb.text);
|
|
84
|
+
const rx = cssBinding(rb.radius);
|
|
85
|
+
if (bg)
|
|
86
|
+
containerLines.push(` background: ${bg};`);
|
|
87
|
+
if (bd)
|
|
88
|
+
containerLines.push(` border: 1px solid ${bd};`);
|
|
89
|
+
if (tx)
|
|
90
|
+
containerLines.push(` color: ${tx};`);
|
|
91
|
+
if (rx)
|
|
92
|
+
containerLines.push(` border-radius: ${rx.replace(/, (\d+(\.\d+)?)\)$/, ', $1px)')};`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
for (const b of context.tokenBindings) {
|
|
96
|
+
const cv = b.resolvedToken?.cssVariable;
|
|
97
|
+
const isValidVar = cv?.startsWith("--");
|
|
98
|
+
if (b.slot === "background") {
|
|
99
|
+
containerLines.push(isValidVar ? ` background: var(${cv});` : ` background: ${b.rawValue};`);
|
|
100
|
+
}
|
|
101
|
+
else if (b.slot === "border") {
|
|
102
|
+
containerLines.push(isValidVar ? ` border-color: var(${cv});` : ` border-color: ${b.rawValue};`);
|
|
103
|
+
}
|
|
104
|
+
else if (b.slot === "text") {
|
|
105
|
+
containerLines.push(isValidVar ? ` color: var(${cv});` : ` color: ${b.rawValue};`);
|
|
106
|
+
}
|
|
107
|
+
else if (b.slot === "radius") {
|
|
108
|
+
containerLines.push(` border-radius: ${b.rawValue}px;`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const containerBody = containerLines.filter(Boolean);
|
|
113
|
+
if (containerBody.length > 0) {
|
|
114
|
+
lines.push(".container {");
|
|
115
|
+
lines.push(...containerBody);
|
|
116
|
+
lines.push("}");
|
|
117
|
+
}
|
|
118
|
+
// Walk full render tree: typography classes for text nodes, layout stubs for containers
|
|
119
|
+
const seen = new Set(["container"]); // never re-emit .container
|
|
120
|
+
const toCls = (name) => name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "");
|
|
121
|
+
const walkTree = (node) => {
|
|
122
|
+
if (!node)
|
|
123
|
+
return;
|
|
124
|
+
const cls = toCls(node.name || "");
|
|
125
|
+
if (cls && !seen.has(cls) && !node.componentId && !node.name?.includes("=")) {
|
|
126
|
+
seen.add(cls);
|
|
127
|
+
const isText = node.role === "text" || node.type === "TEXT";
|
|
128
|
+
const classLines = [];
|
|
129
|
+
if (isText && node.typographyContract) {
|
|
130
|
+
const tc = node.typographyContract;
|
|
131
|
+
if (tc.styleRef?.displayName)
|
|
132
|
+
classLines.push(` /* ${tc.styleRef.displayName} */`);
|
|
133
|
+
const res = tc.resolved ?? {};
|
|
134
|
+
const ff = cssBinding(res.fontFamily);
|
|
135
|
+
if (ff)
|
|
136
|
+
classLines.push(` font-family: ${ff};`);
|
|
137
|
+
const fs = cssBinding(res.fontSize);
|
|
138
|
+
if (fs)
|
|
139
|
+
classLines.push(` font-size: ${fs};`);
|
|
140
|
+
const fw = cssBinding(res.fontWeight);
|
|
141
|
+
if (fw)
|
|
142
|
+
classLines.push(` font-weight: ${fw};`);
|
|
143
|
+
const lh = cssBinding(res.lineHeight);
|
|
144
|
+
if (lh)
|
|
145
|
+
classLines.push(` line-height: ${lh};`);
|
|
146
|
+
const ls = cssBinding(res.letterSpacing);
|
|
147
|
+
if (ls)
|
|
148
|
+
classLines.push(` letter-spacing: ${ls};`);
|
|
149
|
+
}
|
|
150
|
+
else if (!isText && node.layout) {
|
|
151
|
+
const l = node.layout;
|
|
152
|
+
classLines.push(` display: flex;`);
|
|
153
|
+
classLines.push(` flex-direction: ${l.direction === "row" ? "row" : "column"};`);
|
|
154
|
+
const a = { start: "flex-start", center: "center", end: "flex-end", baseline: "baseline" };
|
|
155
|
+
if (l.crossAlignment)
|
|
156
|
+
classLines.push(` align-items: ${a[l.crossAlignment] ?? l.crossAlignment};`);
|
|
157
|
+
if (l.gap?.rawValue && l.gap.mode !== "auto")
|
|
158
|
+
classLines.push(` gap: ${l.gap.rawValue}px;`);
|
|
159
|
+
if (l.padding) {
|
|
160
|
+
const p = l.padding;
|
|
161
|
+
classLines.push(` padding: ${p.top.rawValue}px ${p.right.rawValue}px ${p.bottom.rawValue}px ${p.left.rawValue}px;`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (classLines.length > 0) {
|
|
165
|
+
lines.push("");
|
|
166
|
+
lines.push(`.${cls} {`);
|
|
167
|
+
lines.push(...classLines);
|
|
168
|
+
lines.push("}");
|
|
169
|
+
}
|
|
170
|
+
// Empty stubs suppressed — they add noise without value
|
|
171
|
+
}
|
|
172
|
+
for (const child of (node.children ?? []))
|
|
173
|
+
walkTree(child);
|
|
174
|
+
};
|
|
175
|
+
walkTree(context.renderTree);
|
|
176
|
+
const safeArray = (v) => Array.isArray(v) ? v : [];
|
|
177
|
+
const safeObject = (v) => (v && typeof v === "object" && !Array.isArray(v)) ? v : {};
|
|
178
|
+
// Convert variant diff changes for a single node into CSS declaration lines.
|
|
179
|
+
const diffChangesToCss = (changes) => {
|
|
180
|
+
const css = [];
|
|
181
|
+
const seenProps = new Set();
|
|
182
|
+
for (const ch of changes) {
|
|
183
|
+
const prop = ch.property ?? "";
|
|
184
|
+
if (prop === "style:fill" && !seenProps.has("background")) {
|
|
185
|
+
css.push(ch.toCssVar ? ` background: var(${ch.toCssVar});` : ` background: none; /* ptb: fill removed in this variant */`);
|
|
186
|
+
seenProps.add("background");
|
|
187
|
+
}
|
|
188
|
+
else if (prop === "style:textFill" && ch.toCssVar && !seenProps.has("color")) {
|
|
189
|
+
css.push(` color: var(${ch.toCssVar});`);
|
|
190
|
+
seenProps.add("color");
|
|
191
|
+
}
|
|
192
|
+
else if (prop === "style:stroke" && ch.toCssVar && !seenProps.has("border-color")) {
|
|
193
|
+
css.push(` border-color: var(${ch.toCssVar});`);
|
|
194
|
+
seenProps.add("border-color");
|
|
195
|
+
}
|
|
196
|
+
else if (prop === "layout" && ch.to && !seenProps.has("layout")) {
|
|
197
|
+
const l = ch.to;
|
|
198
|
+
if (l.padding) {
|
|
199
|
+
const p = l.padding;
|
|
200
|
+
const t = p.top ?? 0, r = p.right ?? 0, b = p.bottom ?? 0, lv = p.left ?? 0;
|
|
201
|
+
css.push(` padding: ${t}px ${r}px ${b}px ${lv}px;`);
|
|
202
|
+
}
|
|
203
|
+
if (l.gap !== undefined)
|
|
204
|
+
css.push(` gap: ${l.gap}px;`);
|
|
205
|
+
seenProps.add("layout");
|
|
206
|
+
}
|
|
207
|
+
else if ((prop === "width" || prop === "height") && typeof ch.to === "number" && !seenProps.has(prop)) {
|
|
208
|
+
css.push(` /* ptb: fixed Figma value */ ${prop}: ${ch.to}px;`);
|
|
209
|
+
seenProps.add(prop);
|
|
210
|
+
}
|
|
211
|
+
else if (prop.startsWith("typography:") && !seenProps.has(prop)) {
|
|
212
|
+
const field = prop.slice("typography:".length);
|
|
213
|
+
if (field === "fontSize" && ch.to !== undefined) {
|
|
214
|
+
css.push(` font-size: ${ch.to}px;`);
|
|
215
|
+
}
|
|
216
|
+
else if (field === "fontWeight" && ch.to !== undefined) {
|
|
217
|
+
css.push(` font-weight: ${ch.to};`);
|
|
218
|
+
}
|
|
219
|
+
else if (field === "lineHeightPx" && ch.to !== undefined) {
|
|
220
|
+
css.push(` line-height: ${ch.to}px;`);
|
|
221
|
+
}
|
|
222
|
+
else if (field === "fontFamily" && ch.to !== undefined) {
|
|
223
|
+
css.push(` font-family: ${ch.to};`);
|
|
224
|
+
}
|
|
225
|
+
else if (field === "letterSpacing" && ch.to !== undefined) {
|
|
226
|
+
css.push(` letter-spacing: ${ch.to}px;`);
|
|
227
|
+
}
|
|
228
|
+
else if (field === "textStyleId" && ch.to !== undefined) {
|
|
229
|
+
css.push(` /* ptb: text style → ${ch.to} */`);
|
|
230
|
+
}
|
|
231
|
+
seenProps.add(prop);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return css;
|
|
235
|
+
};
|
|
236
|
+
// Find a variantDiff that matches the given axis=value with all other axes at default.
|
|
237
|
+
const findAxisDiff = (axisName, value) => {
|
|
238
|
+
const defaultSel = safeObject(context.defaultVariantSelection);
|
|
239
|
+
return safeArray(context.variantDiffs).find((d) => {
|
|
240
|
+
const vs = safeObject(d.variantSelection);
|
|
241
|
+
if (vs[axisName] !== value)
|
|
242
|
+
return false;
|
|
243
|
+
// Other axes must be at their default
|
|
244
|
+
for (const [k, v] of Object.entries(defaultSel)) {
|
|
245
|
+
if (k !== axisName && vs[k] !== undefined && vs[k] !== v)
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
return true;
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
// Variant axis selectors — one [data-axis="value"] block per variant value.
|
|
252
|
+
// Track emitted attrs so the state contract loop doesn't re-emit duplicate base selectors.
|
|
253
|
+
const emittedAxisAttrs = new Set();
|
|
254
|
+
const defaultSel = safeObject(context.defaultVariantSelection);
|
|
255
|
+
for (const axis of (context.variantAxes ?? [])) {
|
|
256
|
+
const axisAttr = axis.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
257
|
+
emittedAxisAttrs.add(axisAttr);
|
|
258
|
+
// Use defaultVariantSelection as ground truth — axis.defaultValue can diverge from Figma's actual default
|
|
259
|
+
const effectiveDefault = defaultSel[axis.name] ?? axis.defaultValue;
|
|
260
|
+
for (const value of axis.options) {
|
|
261
|
+
lines.push("");
|
|
262
|
+
lines.push(`.container[data-${axisAttr}="${value}"] {`);
|
|
263
|
+
if (value === effectiveDefault) {
|
|
264
|
+
lines.push(" /* default — styles already in .container */");
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
const axisDiff = findAxisDiff(axis.name, value);
|
|
268
|
+
const rootNode = axisDiff
|
|
269
|
+
? safeArray(axisDiff.changedNodes).find((cn) => cn.nodePath === "root" || cn.nodePath === "")
|
|
270
|
+
: null;
|
|
271
|
+
const cssLines = rootNode ? diffChangesToCss(safeArray(rootNode.changes)) : [];
|
|
272
|
+
if (cssLines.length > 0) {
|
|
273
|
+
lines.push(...cssLines);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
lines.push(" /* ptb:unfilled — derive from variantDiffs[this variant].changedNodes */");
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
lines.push("}");
|
|
280
|
+
// Descendant overrides for children that change in this variant
|
|
281
|
+
if (value !== effectiveDefault) {
|
|
282
|
+
const axisDiff = findAxisDiff(axis.name, value);
|
|
283
|
+
if (axisDiff) {
|
|
284
|
+
const childNodes = safeArray(axisDiff.changedNodes).filter((cn) => cn.nodePath && cn.nodePath !== "root" && cn.nodePath !== ""
|
|
285
|
+
&& !safeArray(cn.changes).some((c) => c.property === "visible"));
|
|
286
|
+
for (const cn of childNodes) {
|
|
287
|
+
const cssLines = diffChangesToCss(safeArray(cn.changes));
|
|
288
|
+
if (cssLines.length === 0)
|
|
289
|
+
continue;
|
|
290
|
+
const cls = cn.nodeName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "");
|
|
291
|
+
if (!cls)
|
|
292
|
+
continue;
|
|
293
|
+
lines.push("");
|
|
294
|
+
lines.push(`.container[data-${axisAttr}="${value}"] .${cls} {`);
|
|
295
|
+
lines.push(...cssLines);
|
|
296
|
+
lines.push("}");
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// State selectors — visibility hints from stateDefaults and variantDiffs.
|
|
303
|
+
// Skip base selector if the axis was already emitted above (prevents duplicate blocks).
|
|
304
|
+
if (context.stateContract) {
|
|
305
|
+
const sc = context.stateContract;
|
|
306
|
+
const attrName = sc.controller.name.toLowerCase();
|
|
307
|
+
const axisAlreadyEmitted = emittedAxisAttrs.has(attrName);
|
|
308
|
+
const safeArr = (v) => Array.isArray(v) ? v : [];
|
|
309
|
+
for (const state of sc.stateAxis.options.slice(1)) {
|
|
310
|
+
const stateDef = sc.stateDefaults?.[state];
|
|
311
|
+
const hiddenFromState = stateDef?.slotVisibility
|
|
312
|
+
? Object.entries(stateDef.slotVisibility)
|
|
313
|
+
.filter(([, v]) => !v).map(([k]) => k)
|
|
314
|
+
: [];
|
|
315
|
+
const matchingDiff = safeArr(context.variantDiffs).find((d) => {
|
|
316
|
+
return d.variantSelection && Object.values(d.variantSelection).includes(state);
|
|
317
|
+
});
|
|
318
|
+
const hiddenFromDiff = safeArr(matchingDiff?.removedNodes).map((n) => n.name);
|
|
319
|
+
// Also catch nodes where visible changes to false in changedNodes
|
|
320
|
+
const hiddenFromVisibilityChange = safeArr(matchingDiff?.changedNodes)
|
|
321
|
+
.filter((n) => safeArr(n.changes).some((c) => c.property === "visible" && c.to === false))
|
|
322
|
+
.map((n) => n.nodePath ? `${n.nodeName} (path: ${n.nodePath})` : n.nodeName);
|
|
323
|
+
const allHidden = [...new Set([...hiddenFromState, ...hiddenFromDiff, ...hiddenFromVisibilityChange])];
|
|
324
|
+
// Prefer slot names (from stateContract) as hints when available — they are unambiguous
|
|
325
|
+
// identifiers that map directly to the Slot Render Guide. Raw node names (from changedNodes)
|
|
326
|
+
// are often generic (e.g. "btn") and cause the AI to wrap all instances of that component.
|
|
327
|
+
const hintNames = hiddenFromState.length > 0
|
|
328
|
+
? [...new Set([...hiddenFromState, ...hiddenFromDiff])]
|
|
329
|
+
: allHidden;
|
|
330
|
+
// Always emit the block — axisAlreadyEmitted only means the variant axes loop
|
|
331
|
+
// already emitted the selector, but that block has no visibility hints. We emit
|
|
332
|
+
// a second block here with ONLY the hint comments; CSS merges them harmlessly.
|
|
333
|
+
if (hintNames.length > 0 || !axisAlreadyEmitted) {
|
|
334
|
+
lines.push("");
|
|
335
|
+
if (hintNames.length > 0) {
|
|
336
|
+
lines.push(`/* ${state}: hides ${hintNames.join(", ")} */`);
|
|
337
|
+
}
|
|
338
|
+
lines.push(`.container[data-${attrName}="${state}"] {`);
|
|
339
|
+
if (!axisAlreadyEmitted) {
|
|
340
|
+
lines.push(" /* ptb:unfilled — derive from variantDiffs[this state].changedNodes */");
|
|
341
|
+
}
|
|
342
|
+
// Elements hidden in this state must be removed from the DOM with a JS conditional
|
|
343
|
+
// in the TSX file — do NOT use display:none here (see anti-pattern #19).
|
|
344
|
+
if (hintNames.length > 0) {
|
|
345
|
+
lines.push(` /* TSX: wrap ONLY the named slot instance in a conditional — other instances of the same component stay always-rendered */`);
|
|
346
|
+
for (const name of hintNames) {
|
|
347
|
+
lines.push(` /* hide slot: ${name} */`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
lines.push("}");
|
|
351
|
+
}
|
|
352
|
+
// Descendant selector stubs for children that change layout/size in this state.
|
|
353
|
+
// data-state only exists on .container — child overrides MUST use descendant selectors.
|
|
354
|
+
const layoutChangedNodes = safeArr(matchingDiff?.changedNodes).filter((n) => safeArr(n.changes).some((c) => ["layout", "width", "height"].includes(c.property))
|
|
355
|
+
&& !safeArr(n.changes).some((c) => c.property === "visible" && c.to === false)
|
|
356
|
+
&& n.nodePath !== "root" // skip the root node itself
|
|
357
|
+
);
|
|
358
|
+
for (const cn of layoutChangedNodes) {
|
|
359
|
+
const cls = cn.nodeName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "");
|
|
360
|
+
if (!cls)
|
|
361
|
+
continue;
|
|
362
|
+
lines.push("");
|
|
363
|
+
lines.push(`/* ${state}: ${cn.nodeName} layout/size override */`);
|
|
364
|
+
lines.push(`/* NOTE: data-state lives on .container — use descendant selector, not .${cls}[data-state] */`);
|
|
365
|
+
lines.push(`.container[data-${attrName}="${state}"] .${cls} {`);
|
|
366
|
+
lines.push(" /* ptb:unfilled — derive from variantDiffs[this state].changedNodes */");
|
|
367
|
+
lines.push("}");
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
return lines.join("\n");
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Traverses a render tree by a "root/0/1/2" path and returns the node, or null if not found.
|
|
375
|
+
*/
|
|
376
|
+
function findNodeAtPath(root, path) {
|
|
377
|
+
if (!path || path === "root")
|
|
378
|
+
return root;
|
|
379
|
+
const parts = path.split("/").filter((p) => p && p !== "root");
|
|
380
|
+
let current = root;
|
|
381
|
+
for (const idx of parts) {
|
|
382
|
+
const i = parseInt(idx, 10);
|
|
383
|
+
if (!current?.children || i >= current.children.length)
|
|
384
|
+
return null;
|
|
385
|
+
current = current.children[i];
|
|
386
|
+
}
|
|
387
|
+
return current;
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Returns a short human-readable label for what distinguishes this node from siblings.
|
|
391
|
+
* Prefers text content of text children; falls back to first child component name.
|
|
392
|
+
*/
|
|
393
|
+
function describeNodeContent(node) {
|
|
394
|
+
if (!node)
|
|
395
|
+
return "";
|
|
396
|
+
const walk = (n) => {
|
|
397
|
+
if ((n.role === "text" || n.type === "leaf" || n.type === "TEXT") && n.textContent) {
|
|
398
|
+
return `contains text "${String(n.textContent).slice(0, 40)}"`;
|
|
399
|
+
}
|
|
400
|
+
for (const child of (n.children ?? [])) {
|
|
401
|
+
const found = walk(child);
|
|
402
|
+
if (found)
|
|
403
|
+
return found;
|
|
404
|
+
}
|
|
405
|
+
return "";
|
|
406
|
+
};
|
|
407
|
+
const desc = walk(node);
|
|
408
|
+
if (desc)
|
|
409
|
+
return desc;
|
|
410
|
+
// Fall back: first child's name that differs from the node itself
|
|
411
|
+
const firstChild = node.children?.[0];
|
|
412
|
+
if (firstChild?.name && firstChild.name !== node.name)
|
|
413
|
+
return `contains ${firstChild.name}`;
|
|
414
|
+
return "";
|
|
415
|
+
}
|
|
416
|
+
function toPropName(name) {
|
|
417
|
+
return name
|
|
418
|
+
.replace(/[-_\s]+([a-zA-Z])/g, (_, l) => l.toUpperCase())
|
|
419
|
+
.replace(/^([A-Z])/, (l) => l.toLowerCase())
|
|
420
|
+
.replace(/[^a-zA-Z0-9_]/g, "");
|
|
421
|
+
}
|
|
422
|
+
function buildPropsInterfaceSkeleton(context, codeName) {
|
|
423
|
+
const lines = [];
|
|
424
|
+
lines.push(`// Props skeleton — implement this in ${codeName}.types.ts`);
|
|
425
|
+
lines.push(`import type * as React from "react";`);
|
|
426
|
+
lines.push(`export type ${codeName}Props = {`);
|
|
427
|
+
if ((context.variantAxes ?? []).length > 0) {
|
|
428
|
+
lines.push(` // Variant axes — use as props, NEVER as useState`);
|
|
429
|
+
for (const axis of context.variantAxes) {
|
|
430
|
+
const propName = toPropName(axis.name);
|
|
431
|
+
const union = axis.options.map(o => `'${o}'`).join(" | ");
|
|
432
|
+
const defNote = axis.defaultValue ? ` // default: '${axis.defaultValue}'` : "";
|
|
433
|
+
lines.push(` ${propName}?: ${union};${defNote}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
const ownSlots = context.slotContract ? Object.entries(context.slotContract) : [];
|
|
437
|
+
const nodeSlots = ownSlots.filter(([, d]) => d.prop && (d.kind === "icon" || d.kind === "child" || d.kind === "asset" || d.dependency));
|
|
438
|
+
const textSlots = ownSlots.filter(([, d]) => d.prop && d.kind === "text");
|
|
439
|
+
if (nodeSlots.length > 0) {
|
|
440
|
+
lines.push(` // Slot props (icon / component swaps)`);
|
|
441
|
+
const seen = new Set();
|
|
442
|
+
for (const [, def] of nodeSlots) {
|
|
443
|
+
if (def.prop && !seen.has(def.prop)) {
|
|
444
|
+
seen.add(def.prop);
|
|
445
|
+
lines.push(` ${def.prop}?: React.ReactNode;`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (textSlots.length > 0) {
|
|
450
|
+
lines.push(` // Text content props`);
|
|
451
|
+
const seen = new Set();
|
|
452
|
+
for (const [, def] of textSlots) {
|
|
453
|
+
if (def.prop && !seen.has(def.prop)) {
|
|
454
|
+
seen.add(def.prop);
|
|
455
|
+
lines.push(` ${def.prop}?: string;`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
const coveredProps = new Set([
|
|
460
|
+
...(context.variantAxes ?? []).map(v => toPropName(v.name)),
|
|
461
|
+
...ownSlots.map(([, d]) => d.prop).filter(Boolean),
|
|
462
|
+
]);
|
|
463
|
+
const extraProps = (context.properties ?? []).filter(p => !coveredProps.has(toPropName(p.name)));
|
|
464
|
+
if (extraProps.length > 0) {
|
|
465
|
+
lines.push(` // Additional props`);
|
|
466
|
+
for (const p of extraProps) {
|
|
467
|
+
const propName = toPropName(p.name);
|
|
468
|
+
const tsType = p.type === "boolean" ? "boolean" : p.type === "instance" ? "React.ReactNode" : "string";
|
|
469
|
+
lines.push(` ${propName}?: ${tsType};`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
lines.push(` className?: string;`);
|
|
473
|
+
lines.push(`};`);
|
|
474
|
+
return lines.join("\n");
|
|
475
|
+
}
|
|
476
|
+
function buildJsxScaffold(context, codeName, requiredImports) {
|
|
477
|
+
const rootEl = context.implementationHints?.semanticElement ?? "div";
|
|
478
|
+
const ownSlots = context.slotContract ? Object.entries(context.slotContract) : [];
|
|
479
|
+
// Track seen prop names to deduplicate (stateContract controller may overlap variant axes)
|
|
480
|
+
const seenProps = new Set();
|
|
481
|
+
const addProp = (entry) => {
|
|
482
|
+
const name = entry.split(" ")[0].split("=")[0];
|
|
483
|
+
if (seenProps.has(name))
|
|
484
|
+
return;
|
|
485
|
+
seenProps.add(name);
|
|
486
|
+
propParts.push(entry);
|
|
487
|
+
};
|
|
488
|
+
// Props destructure with defaults
|
|
489
|
+
const propParts = [];
|
|
490
|
+
for (const axis of (context.variantAxes ?? [])) {
|
|
491
|
+
const p = toPropName(axis.name);
|
|
492
|
+
const def = axis.defaultValue ? ` = '${axis.defaultValue}'` : "";
|
|
493
|
+
addProp(`${p}${def}`);
|
|
494
|
+
}
|
|
495
|
+
if (context.stateContract) {
|
|
496
|
+
const sc = context.stateContract;
|
|
497
|
+
const p = toPropName(sc.controller.name);
|
|
498
|
+
const def = sc.controller.defaultValue !== undefined ? ` = ${JSON.stringify(sc.controller.defaultValue)}` : "";
|
|
499
|
+
addProp(`${p}${def}`);
|
|
500
|
+
}
|
|
501
|
+
for (const [, def] of ownSlots) {
|
|
502
|
+
if (def.kind === "text" && def.prop && !seenProps.has(def.prop)) {
|
|
503
|
+
addProp(def.prop);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
propParts.push("className");
|
|
507
|
+
// data-* attribute lines — deduplicated by attribute name
|
|
508
|
+
const seenAttrs = new Set();
|
|
509
|
+
const dataLines = [];
|
|
510
|
+
for (const axis of (context.variantAxes ?? [])) {
|
|
511
|
+
const attr = axis.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
512
|
+
if (seenAttrs.has(attr))
|
|
513
|
+
continue;
|
|
514
|
+
seenAttrs.add(attr);
|
|
515
|
+
dataLines.push(` data-${attr}={${toPropName(axis.name)}}`);
|
|
516
|
+
}
|
|
517
|
+
if (context.stateContract) {
|
|
518
|
+
const sc = context.stateContract;
|
|
519
|
+
const attr = sc.controller.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
520
|
+
if (!seenAttrs.has(attr)) {
|
|
521
|
+
seenAttrs.add(attr);
|
|
522
|
+
dataLines.push(` data-${attr}={${toPropName(sc.controller.name)}}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
const lines = [];
|
|
526
|
+
// Imports
|
|
527
|
+
lines.push(`import * as React from "react";`);
|
|
528
|
+
lines.push(`import styles from "./${codeName}.module.css";`);
|
|
529
|
+
lines.push(`import type { ${codeName}Props } from "./${codeName}.types";`);
|
|
530
|
+
for (const { compName, importPath } of requiredImports) {
|
|
531
|
+
lines.push(`import { ${compName} } from '${importPath}';`);
|
|
532
|
+
}
|
|
533
|
+
lines.push("");
|
|
534
|
+
// Function signature
|
|
535
|
+
const propsArg = propParts.length <= 3
|
|
536
|
+
? `{ ${propParts.join(", ")} }`
|
|
537
|
+
: `{\n ${propParts.join(",\n ")},\n}`;
|
|
538
|
+
lines.push(`export function ${codeName}(${propsArg}: ${codeName}Props) {`);
|
|
539
|
+
lines.push(` return (`);
|
|
540
|
+
// Root element
|
|
541
|
+
lines.push(` <${rootEl}`);
|
|
542
|
+
lines.push(` className={[styles.container, className].filter(Boolean).join(" ")}`);
|
|
543
|
+
for (const attr of dataLines)
|
|
544
|
+
lines.push(attr);
|
|
545
|
+
lines.push(` >`);
|
|
546
|
+
// Slot bodies — prefer renderPattern from slotContract (most accurate)
|
|
547
|
+
if (ownSlots.length > 0) {
|
|
548
|
+
for (const [slotName, def] of ownSlots) {
|
|
549
|
+
const pattern = def.renderPattern;
|
|
550
|
+
if (!pattern)
|
|
551
|
+
continue;
|
|
552
|
+
lines.push(` {/* ${slotName} */}`);
|
|
553
|
+
lines.push(` ${pattern}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
// Fall back: top-level render tree children (capped at 6)
|
|
558
|
+
const toCls = (n) => n.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-_]/g, "");
|
|
559
|
+
const topChildren = context.renderTree?.children ?? [];
|
|
560
|
+
for (const child of topChildren.slice(0, 6)) {
|
|
561
|
+
const isText = child.role === "text" || child.type === "TEXT";
|
|
562
|
+
const isInstance = !!child.componentId;
|
|
563
|
+
const cls = toCls(child.name ?? "node");
|
|
564
|
+
if (isText) {
|
|
565
|
+
const prop = child.contentContract?.propName;
|
|
566
|
+
// Only reference prop as a variable if it was actually declared in propParts
|
|
567
|
+
const propIsKnown = prop && seenProps.has(prop);
|
|
568
|
+
const content = propIsKnown ? `{${prop}}` : (child.textContent ? `"${child.textContent}"` : `{/* ${child.name} */}`);
|
|
569
|
+
lines.push(` <span className={styles['${cls}']}>${content}</span>`);
|
|
570
|
+
}
|
|
571
|
+
else if (isInstance) {
|
|
572
|
+
const name = child.name.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
573
|
+
lines.push(` <${name} />`);
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
lines.push(` <div className={styles['${cls}']}>`);
|
|
577
|
+
lines.push(` {/* fill from Slot Render Guide */}`);
|
|
578
|
+
lines.push(` </div>`);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (topChildren.length > 6)
|
|
582
|
+
lines.push(` {/* … ${topChildren.length - 6} more children — see Render Structure above */}`);
|
|
583
|
+
if (topChildren.length === 0)
|
|
584
|
+
lines.push(` {/* see Slot Render Guide above */}`);
|
|
585
|
+
}
|
|
586
|
+
lines.push(` </${rootEl}>`);
|
|
587
|
+
lines.push(` );`);
|
|
588
|
+
lines.push(`}`);
|
|
589
|
+
return lines.join("\n");
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Generates a high-quality, structured AI implementation prompt from a ComponentContext.
|
|
593
|
+
*/
|
|
594
|
+
export function generateComponentPrompt(context, framework = null) {
|
|
595
|
+
const safeObject = (val) => val && typeof val === 'object' && !Array.isArray(val) ? val : {};
|
|
596
|
+
const safeArray = (val) => Array.isArray(val) ? val : [];
|
|
597
|
+
// Only filter out UNRESOLVED Figma references (kind:"unknown" or bare componentId objects).
|
|
598
|
+
// Resolved dependencies (kind:"dependency" with componentName) are rendered as JSX props.
|
|
599
|
+
const isFigmaMeta = (v) => v !== null && typeof v === "object" && !Array.isArray(v) &&
|
|
600
|
+
(v.kind === "unknown" || (!v.componentName && "componentId" in v));
|
|
601
|
+
// Filter unresolvable deps — raw Figma node IDs, missing components, and external
|
|
602
|
+
// components (device frames, Figma internals) the resolver couldn't map to our output.
|
|
603
|
+
const isUnresolvable = (dep) => {
|
|
604
|
+
const { name, status } = dep;
|
|
605
|
+
if (/\(\d+:\d+\)/i.test(name))
|
|
606
|
+
return true;
|
|
607
|
+
if (/^\d+:\d+$/.test(name.trim()))
|
|
608
|
+
return true;
|
|
609
|
+
if (/^unknown\b/i.test(name.trim()))
|
|
610
|
+
return true;
|
|
611
|
+
// "missing" = referenced but not in snapshot; "external" = outside design system
|
|
612
|
+
if (status === "missing" || status === "external")
|
|
613
|
+
return true;
|
|
614
|
+
return false;
|
|
615
|
+
};
|
|
616
|
+
const resolvableDeps = context.dependencies.filter(d => !isUnresolvable(d));
|
|
617
|
+
const unresolvableCount = context.dependencies.length - resolvableDeps.length;
|
|
618
|
+
const c = context.component;
|
|
619
|
+
const instructions = [
|
|
620
|
+
"Follow the layout and sizing semantics from the design context",
|
|
621
|
+
"Do not invent extra variants, tokens, or dependencies"
|
|
622
|
+
];
|
|
623
|
+
if (context.stateContract) {
|
|
624
|
+
instructions.push("- **STATE MANAGEMENT**: Use 'stateContract' as the primary source of truth for the component's state model.");
|
|
625
|
+
instructions.push("- Map the 'stateContract.controller' to a prop or local state as specified.");
|
|
626
|
+
}
|
|
627
|
+
if (context.interactionContract) {
|
|
628
|
+
instructions.push("- **INTERACTIONS**: Bind the specified 'interactionContract.events' to the identified source nodes.");
|
|
629
|
+
}
|
|
630
|
+
if (context.prototypeInteractionContract) {
|
|
631
|
+
instructions.push("- **INTERACTIONS & PROTOTYPES**: Use 'prototypeInteractionContract' for Figma-faithful event wiring.");
|
|
632
|
+
instructions.push("- **ANIMATIONS**: Map 'animationHints' to CSS transitions or framer-motion variants.");
|
|
633
|
+
}
|
|
634
|
+
if (context.variantAxes.length > 0) {
|
|
635
|
+
instructions.push(`- Handle variant selection by mapping incoming props to the variantAxes dimensions.`);
|
|
636
|
+
instructions.push(`- Default variant selection: ${JSON.stringify(context.defaultVariantSelection)}`);
|
|
637
|
+
instructions.push(`- Use 'baseRenderTree' as the structural foundation, then apply differences from 'variantDiffs'.`);
|
|
638
|
+
}
|
|
639
|
+
if (resolvableDeps.length > 0) {
|
|
640
|
+
// Ordering instruction — implement deps first, then this component
|
|
641
|
+
const depNameList = resolvableDeps.map(d => d.name).join(", ");
|
|
642
|
+
instructions.push(`PREREQUISITE: Implement dependencies before this component. Order: ${depNameList} → ${c.name}.`);
|
|
643
|
+
// Per-dependency variant hint derived from dependencyInstances
|
|
644
|
+
const depInstances = safeArray(context.dependencyInstances);
|
|
645
|
+
for (const dep of resolvableDeps) {
|
|
646
|
+
const depPascal = dep.name.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
647
|
+
const matches = depInstances.filter((inst) => {
|
|
648
|
+
const instComp = inst.recipe?.invocationContract?.componentName;
|
|
649
|
+
return instComp === depPascal || inst.name === dep.name;
|
|
650
|
+
});
|
|
651
|
+
if (matches.length === 0)
|
|
652
|
+
continue;
|
|
653
|
+
// Deduplicate by variantSelection fingerprint so we don't repeat identical hints
|
|
654
|
+
const seen = new Set();
|
|
655
|
+
for (const inst of matches) {
|
|
656
|
+
const sel = safeObject(inst.variantSelection);
|
|
657
|
+
const overrides = Object.fromEntries(Object.entries(safeObject(inst.propertyOverrides)).filter(([, v]) => !isFigmaMeta(v)));
|
|
658
|
+
const compName = inst.recipe?.invocationContract?.componentName ?? depPascal;
|
|
659
|
+
const selParts = Object.entries(sel).map(([k, v]) => `${k}="${v}"`);
|
|
660
|
+
const overrideParts = Object.entries(overrides).map(([k, v]) => `${k}="${v}"`);
|
|
661
|
+
const allParts = [...selParts, ...overrideParts];
|
|
662
|
+
const fingerprint = allParts.join(",");
|
|
663
|
+
if (seen.has(fingerprint))
|
|
664
|
+
continue;
|
|
665
|
+
seen.add(fingerprint);
|
|
666
|
+
const jsx = allParts.length > 0 ? `<${compName} ${allParts.join(" ")} />` : `<${compName} />`;
|
|
667
|
+
instructions.push(`When rendering ${dep.name} in this component, use exactly: \`${jsx}\``);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
instructions.push(`- CRITICAL: Use the 'dependencyInstances' recipes to configure each nested instance.`);
|
|
671
|
+
instructions.push(`- Do NOT blindly render a generic version of the dependency; follow the instance-specific recipe.`);
|
|
672
|
+
}
|
|
673
|
+
if (framework) {
|
|
674
|
+
instructions.push(`Implement the component using ${framework}`);
|
|
675
|
+
}
|
|
676
|
+
// Prepare Structural Summary
|
|
677
|
+
const structureSummary = {
|
|
678
|
+
kind: c.kind.value,
|
|
679
|
+
hasVariants: context.variantAxes.length > 0,
|
|
680
|
+
hasProperties: context.properties.length > 0,
|
|
681
|
+
hasDependencies: resolvableDeps.length > 0,
|
|
682
|
+
dependencyCount: resolvableDeps.length,
|
|
683
|
+
hasTokenBindings: context.tokenBindings.length > 0,
|
|
684
|
+
hasLayout: context.layout !== null,
|
|
685
|
+
hasSize: context.size !== null,
|
|
686
|
+
hasStateContract: !!context.stateContract,
|
|
687
|
+
hasInteractionContract: !!context.interactionContract,
|
|
688
|
+
hasPrototypeInteractionContract: !!context.prototypeInteractionContract
|
|
689
|
+
};
|
|
690
|
+
// ── Fix 2: detect component interaction kind ──────────────────────────────
|
|
691
|
+
// Three layers of detection — any one is sufficient:
|
|
692
|
+
// 1. Name patterns: component is named like a known form control
|
|
693
|
+
// 2. Boolean axis: exactly two options that read as true/false (on/off, checked/unchecked…)
|
|
694
|
+
// NOTE: "Default/Checked" won't match BOOL_PAIR — name-based detection covers those cases
|
|
695
|
+
// 3. Boolean Figma property: p.type === "boolean" in the component's property list
|
|
696
|
+
const FORM_CONTROL_PATTERNS = [
|
|
697
|
+
/\bcheckbox\b/i, /\bradio\b/i, /\btoggle\b/i, /\bswitch\b/i,
|
|
698
|
+
/\binput\b/i, /\bsearch(bar)?\b/i, /\bselect\b/i, /\bdropdown\b/i,
|
|
699
|
+
/\bcombobox?\b/i, /\bslider\b/i, /\bstepper\b/i, /\bpicker\b/i,
|
|
700
|
+
/\btextfield\b/i, /\btextarea\b/i,
|
|
701
|
+
];
|
|
702
|
+
const isNamedFormControl = FORM_CONTROL_PATTERNS.some(p => p.test(c.name));
|
|
703
|
+
const BOOL_PAIR = [
|
|
704
|
+
/^(true|false)$/i,
|
|
705
|
+
/^(yes|no)$/i,
|
|
706
|
+
/^(on|off)$/i,
|
|
707
|
+
/^(checked|unchecked)$/i,
|
|
708
|
+
/^(selected|unselected|deselected)$/i,
|
|
709
|
+
/^(active|inactive)$/i,
|
|
710
|
+
/^(pressed|unpressed)$/i,
|
|
711
|
+
/^(enabled|disabled)$/i,
|
|
712
|
+
];
|
|
713
|
+
const isBoolPair = (opts) => opts.length === 2 && opts.every(o => BOOL_PAIR.some(p => p.test(String(o))));
|
|
714
|
+
const hasBooleanAxis = safeArray(context.variantAxes).some((ax) => isBoolPair(safeArray(ax.options)));
|
|
715
|
+
const hasBooleanProp = safeArray(context.properties).some((p) => p.type === "boolean");
|
|
716
|
+
const isComposite = resolvableDeps.length > 0;
|
|
717
|
+
const isFormControl = isNamedFormControl || (hasBooleanAxis && !isComposite) || (hasBooleanProp && !isComposite);
|
|
718
|
+
// Icon components with vector SVGs are generated deterministically in implement.ts.
|
|
719
|
+
// This prompt only runs for font-symbol icons (SF Pro Symbols etc.) that need AI fallback.
|
|
720
|
+
// Form controls and interactive components override icon classification.
|
|
721
|
+
// A static icon has no variant state, no state contract, and no interaction contract.
|
|
722
|
+
// If any of these are present, the component is interactive — never an SVG renderer.
|
|
723
|
+
const hasInteractiveBehavior = isFormControl || !!context.stateContract || !!context.interactionContract;
|
|
724
|
+
const isIcon = !hasInteractiveBehavior && isIconComponent(c.name);
|
|
725
|
+
if (isIcon) {
|
|
726
|
+
instructions.unshift("**ICON COMPONENT**: This is an icon. Generate an inline SVG React component.", "- The Figma source uses a font-based icon (SF Pro Symbols) that does not export as vector.", "- Generate a proper SVG shell: `<svg viewBox='0 0 W H' fill='currentColor' aria-hidden='true' {...props}>` with a placeholder `<rect>` and a TODO comment.", "- Accept `React.SVGProps<SVGSVGElement>` as props — no variant props, no CSS module needed.", "- The types file must use: `export type ${c.name}Props = React.SVGProps<SVGSVGElement>;`");
|
|
727
|
+
}
|
|
728
|
+
else if (isFormControl) {
|
|
729
|
+
instructions.unshift("**FORM CONTROL**: This component has boolean Figma properties (checked/selected/on-off style axes).", "- Implement as an interactive HTML element — `<input>`, `<button>`, `<label>`, or a wrapper around one.", "- NEVER generate SVG paths or vector art for this component.", "- The boolean axis maps to a controlled or uncontrolled prop (e.g. `checked`, `selected`, `value`).", "- Wire the boolean axis to an `onChange` / `onClick` handler stub as appropriate.");
|
|
730
|
+
}
|
|
731
|
+
else if (!isIcon && context.variantAxes.length > 0) {
|
|
732
|
+
instructions.unshift("**INTERACTIVE COMPONENT**: This component has variant states.", "- Implement as JSX/TSX with props and conditional rendering.", "- NEVER generate this as an SVG renderer — it is NOT an icon.");
|
|
733
|
+
}
|
|
734
|
+
// 1. Generate High-Quality Human-Readable Prompt text
|
|
735
|
+
const lines = [];
|
|
736
|
+
lines.push(`You are implementing the \`${c.name}\` component.`);
|
|
737
|
+
lines.push("");
|
|
738
|
+
lines.push("## Component Metadata");
|
|
739
|
+
lines.push(`- Kind: ${c.kind.value}${c.kind.inferred ? " (inferred)" : ""}`);
|
|
740
|
+
lines.push(`- Category: ${c.category}`);
|
|
741
|
+
if (c.description)
|
|
742
|
+
lines.push(`- Description: ${c.description}`);
|
|
743
|
+
// Derive PascalCase code name for interface skeleton and import hints
|
|
744
|
+
// Split on whitespace AND hyphens/underscores so "btn-web" → "BtnWeb"
|
|
745
|
+
const codeName = c.name.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
746
|
+
// Build deduplicated required-imports list from dependencies + slot contract
|
|
747
|
+
const requiredImportsList = [];
|
|
748
|
+
const seenImports = new Set();
|
|
749
|
+
// Build a name→importPath map from resolvable deps only
|
|
750
|
+
const depImportPathMap = new Map();
|
|
751
|
+
for (const d of resolvableDeps) {
|
|
752
|
+
const compName = d.name.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
753
|
+
if (d.importPath)
|
|
754
|
+
depImportPathMap.set(compName, d.importPath);
|
|
755
|
+
}
|
|
756
|
+
const addRequiredImport = (rawName, importName) => {
|
|
757
|
+
const normalized = isIconComponent(rawName) ? normalizeIconDisplayName(rawName) : rawName;
|
|
758
|
+
const compName = importName ?? normalized.split(/\s+/).map(p => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
759
|
+
if (seenImports.has(compName))
|
|
760
|
+
return;
|
|
761
|
+
seenImports.add(compName);
|
|
762
|
+
const folder = normalized.toLowerCase().replace(/[\s_]+/g, "-");
|
|
763
|
+
// Prefer the context-computed import path; fall back to the folder-based guess
|
|
764
|
+
const importPath = depImportPathMap.get(compName)
|
|
765
|
+
?? (isIconComponent(rawName) ? `../../icons/${folder}/index.js` : `../${folder}/index.js`);
|
|
766
|
+
requiredImportsList.push({ compName, importPath });
|
|
767
|
+
};
|
|
768
|
+
for (const d of resolvableDeps)
|
|
769
|
+
addRequiredImport(d.name);
|
|
770
|
+
if (context.slotContract) {
|
|
771
|
+
for (const def of Object.values(context.slotContract)) {
|
|
772
|
+
if (!def.dependency?.componentName)
|
|
773
|
+
continue;
|
|
774
|
+
// Only include icons that are actually rendered in JSX:
|
|
775
|
+
// fixed slot (no prop) → always renders <Icon /> directly
|
|
776
|
+
// visible configurable slot → renders as fallback {prop ?? <Icon />}
|
|
777
|
+
// Hidden configurable slots use {prop ?? null} — no render, no import needed.
|
|
778
|
+
// Adding the import for hidden slots forces the AI to "use" it, causing hardwiring.
|
|
779
|
+
if (!def.prop || def.visible) {
|
|
780
|
+
addRequiredImport(def.dependency.componentName, def.dependency.importName || undefined);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
if (requiredImportsList.length > 0) {
|
|
785
|
+
lines.push("");
|
|
786
|
+
lines.push("## Required Imports");
|
|
787
|
+
lines.push("You MUST include ALL of these `import` statements in your `.tsx` component file. Do NOT omit any.");
|
|
788
|
+
lines.push("");
|
|
789
|
+
lines.push("```typescript");
|
|
790
|
+
for (const { compName, importPath } of requiredImportsList) {
|
|
791
|
+
lines.push(`import { ${compName} } from '${importPath}';`);
|
|
792
|
+
}
|
|
793
|
+
lines.push("```");
|
|
794
|
+
}
|
|
795
|
+
lines.push("");
|
|
796
|
+
lines.push("## Structure Summary");
|
|
797
|
+
lines.push(`- Kind: ${structureSummary.kind}`);
|
|
798
|
+
lines.push(`- Variants: ${structureSummary.hasVariants ? "Yes" : "No"}`);
|
|
799
|
+
lines.push(`- Properties: ${structureSummary.hasProperties ? "Yes" : "No"}`);
|
|
800
|
+
lines.push(`- Dependencies: ${structureSummary.hasDependencies ? `${structureSummary.dependencyCount} components` : "None"}`);
|
|
801
|
+
lines.push(`- Token Bindings: ${structureSummary.hasTokenBindings ? "Yes" : "No"}`);
|
|
802
|
+
lines.push(`- Layout: ${structureSummary.hasLayout ? "Defined" : "Not defined"}`);
|
|
803
|
+
lines.push(`- Size: ${structureSummary.hasSize ? "Defined" : "Not defined"}`);
|
|
804
|
+
lines.push(`- Behavioral Contracts: ${structureSummary.hasStateContract ? "State" : ""}${structureSummary.hasInteractionContract ? " & Interaction" : ""}${structureSummary.hasPrototypeInteractionContract ? " & Prototype Fidelity" : ""}`);
|
|
805
|
+
const semanticEl = context.implementationHints?.semanticElement;
|
|
806
|
+
if (semanticEl) {
|
|
807
|
+
lines.push(`- **Root Element**: \`<${semanticEl}>\` — use this HTML element, not \`<div>\``);
|
|
808
|
+
}
|
|
809
|
+
if (context.stateContract) {
|
|
810
|
+
const sc = context.stateContract;
|
|
811
|
+
lines.push("");
|
|
812
|
+
lines.push("## State Contract (Implementation-Ready)");
|
|
813
|
+
lines.push(`This component uses a **${sc.controller.kind}**-driven state model.`);
|
|
814
|
+
lines.push(`- **Controller Name**: \`${sc.controller.name}\` (${sc.controller.dataType})`);
|
|
815
|
+
if (sc.controller.defaultValue !== undefined)
|
|
816
|
+
lines.push(`- **Default Value**: \`${sc.controller.defaultValue}\``);
|
|
817
|
+
lines.push(`- **State Axis**: ${sc.stateAxis.name} (${sc.stateAxis.options.join(" | ")})`);
|
|
818
|
+
lines.push("- **State Defaults** (Compiled):");
|
|
819
|
+
for (const [state, defaults] of Object.entries(safeObject(sc.stateDefaults))) {
|
|
820
|
+
const def = defaults;
|
|
821
|
+
lines.push(` - State \`${state}\`:`);
|
|
822
|
+
if (Object.keys(safeObject(def.content)).length > 0)
|
|
823
|
+
lines.push(` - Content: ${JSON.stringify(def.content)}`);
|
|
824
|
+
if (Object.keys(safeObject(def.slotVisibility)).length > 0)
|
|
825
|
+
lines.push(` - Visibility: ${JSON.stringify(def.slotVisibility)}`);
|
|
826
|
+
}
|
|
827
|
+
lines.push(`- **Priority**: ${sc.overridePriority.join(" > ")}`);
|
|
828
|
+
}
|
|
829
|
+
if (context.interactionContract) {
|
|
830
|
+
const ic = context.interactionContract;
|
|
831
|
+
lines.push("");
|
|
832
|
+
lines.push("## Interaction Contract");
|
|
833
|
+
if (safeArray(ic.events).length > 0) {
|
|
834
|
+
for (const ev of safeArray(ic.events)) {
|
|
835
|
+
lines.push(`- **${ev.event}** on \`${ev.source.nodeName}\` (${ev.source.role || "leaf"}):`);
|
|
836
|
+
lines.push(` - Action: \`${ev.action.type}\` (${ev.action.target}${ev.action.value !== undefined ? ` -> ${ev.action.value}` : ""})`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
else if (safeArray(ic.warnings).length > 0) {
|
|
840
|
+
for (const w of safeArray(ic.warnings))
|
|
841
|
+
lines.push(`> [!WARNING] ${w}`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
if (context.prototypeInteractionContract && safeArray(context.prototypeInteractionContract.interactions).length > 0) {
|
|
845
|
+
const pic = context.prototypeInteractionContract;
|
|
846
|
+
lines.push("");
|
|
847
|
+
lines.push("## Prototype Interaction Fidelity (Direct Figma Logic)");
|
|
848
|
+
lines.push("Follow these verbatim prototype interactions from design:");
|
|
849
|
+
for (const inter of safeArray(pic.interactions)) {
|
|
850
|
+
lines.push(`### ${inter.trigger.runtime} on ${inter.source.nodeName} (${inter.source.role})`);
|
|
851
|
+
lines.push(`- **Action**: ${inter.action.type} -> ${inter.action.targetState || inter.action.targetFrame || inter.action.targetOverlay || "callback"}`);
|
|
852
|
+
if (inter.animation) {
|
|
853
|
+
lines.push(`- **Animation**: ${inter.animation.type} (${inter.animation.curve}, ${inter.animation.durationMs}ms)`);
|
|
854
|
+
}
|
|
855
|
+
if (inter.animationHints) {
|
|
856
|
+
let hint = `- **Implementation Hint**: ${inter.animationHints.preferredImplementation}`;
|
|
857
|
+
if (safeArray(inter.animationHints.changedProperties).length > 0) {
|
|
858
|
+
hint += ` (Watch for: ${inter.animationHints.changedProperties.join(", ")})`;
|
|
859
|
+
}
|
|
860
|
+
lines.push(hint);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
if (context.transitionSummary) {
|
|
865
|
+
lines.push("");
|
|
866
|
+
lines.push("## Transition Summary");
|
|
867
|
+
lines.push("Behavioral summary of what changes between states:");
|
|
868
|
+
for (const [trans, summary] of Object.entries(safeObject(context.transitionSummary))) {
|
|
869
|
+
const s = summary;
|
|
870
|
+
lines.push(`### ${trans}`);
|
|
871
|
+
if (safeArray(s.contentChanges).length > 0)
|
|
872
|
+
lines.push(`- **Content Changes**: ${s.contentChanges.join(", ")}`);
|
|
873
|
+
if (safeArray(s.slotVisibilityChanges).length > 0)
|
|
874
|
+
lines.push(`- **Visibility Changes**: ${s.slotVisibilityChanges.join(", ")}`);
|
|
875
|
+
if (safeArray(s.styleChanges).length > 0)
|
|
876
|
+
lines.push(`- **Style Changes**: ${s.styleChanges.join(", ")}`);
|
|
877
|
+
if (safeArray(s.dependencyChanges).length > 0)
|
|
878
|
+
lines.push(`- **Dependency Changes**: ${s.dependencyChanges.join(", ")}`);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
if (context.variantAxes.length > 0) {
|
|
882
|
+
lines.push("");
|
|
883
|
+
lines.push("## Variant Axes");
|
|
884
|
+
for (const v of context.variantAxes) {
|
|
885
|
+
lines.push(`- ${v.name}: ${v.options.join(" | ")}`);
|
|
886
|
+
if (v.defaultValue)
|
|
887
|
+
lines.push(` - Default: ${v.defaultValue}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (context.properties.length > 0) {
|
|
891
|
+
lines.push("");
|
|
892
|
+
lines.push("## Properties");
|
|
893
|
+
for (const p of context.properties) {
|
|
894
|
+
lines.push(`- ${p.name} (${p.type})`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (resolvableDeps.length > 0 || unresolvableCount > 0) {
|
|
898
|
+
lines.push("");
|
|
899
|
+
lines.push("## Dependencies");
|
|
900
|
+
lines.push("");
|
|
901
|
+
if (unresolvableCount > 0) {
|
|
902
|
+
lines.push(`> **Note:** ${unresolvableCount} Figma node(s) could not be resolved to a named component and have been excluded. Wire them manually if needed.`);
|
|
903
|
+
lines.push("");
|
|
904
|
+
}
|
|
905
|
+
if (resolvableDeps.length > 0) {
|
|
906
|
+
lines.push("Each entry below is a **React component** that must be imported and rendered in JSX.");
|
|
907
|
+
lines.push("NEVER pass a dependency object as a prop value — always render it as `<ComponentName />`.");
|
|
908
|
+
lines.push("");
|
|
909
|
+
}
|
|
910
|
+
for (const d of resolvableDeps) {
|
|
911
|
+
const nameParts = d.name.split(/[\s\-_]+/);
|
|
912
|
+
const pascalName = nameParts.map(p => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
913
|
+
const hasDimension = /\d+x\d+/.test(d.name);
|
|
914
|
+
const lowerName = d.name.toLowerCase();
|
|
915
|
+
const ICON_KEYWORDS = ["icon", "chevron", "arrow", "close", "menu", "more", "check", "plus",
|
|
916
|
+
"minus", "search", "add", "delete", "edit", "clear", "back", "forward",
|
|
917
|
+
"info", "warning", "alert", "error", "star", "heart", "bolt", "flag", "dot"];
|
|
918
|
+
const isIcon = hasDimension || ICON_KEYWORDS.some(k => lowerName.includes(k));
|
|
919
|
+
let renderHint;
|
|
920
|
+
let importNote;
|
|
921
|
+
if (isIcon) {
|
|
922
|
+
renderHint = `render as \`<${pascalName} />\` (no props needed)`;
|
|
923
|
+
importNote = "icon component";
|
|
924
|
+
}
|
|
925
|
+
else if (d.status === "missing") {
|
|
926
|
+
renderHint = `render as \`<${pascalName} />\` — ⚠ not yet implemented, stub the import`;
|
|
927
|
+
importNote = "MISSING — stub required";
|
|
928
|
+
}
|
|
929
|
+
else {
|
|
930
|
+
renderHint = `render as \`<${pascalName} />\` with props from Slot Contract`;
|
|
931
|
+
importNote = d.status === "external" ? "external" : "PTB-managed";
|
|
932
|
+
}
|
|
933
|
+
const countNote = d.count > 1 ? ` (used ${d.count}×)` : "";
|
|
934
|
+
lines.push(`- **${d.name}**${countNote} [${importNote}] — ${renderHint}`);
|
|
935
|
+
const resolvedImportPath = d.importPath
|
|
936
|
+
|| (isIcon ? `../../icons/${normalizeIconDisplayName(d.name).toLowerCase().replace(/\s+/g, "-")}/index.js` : `../${d.name.toLowerCase().replace(/\s+/g, "-")}/index.js`);
|
|
937
|
+
lines.push(` Import: \`import { ${pascalName} } from '${resolvedImportPath}';\``);
|
|
938
|
+
if (d.propHints && d.propHints.length > 0) {
|
|
939
|
+
lines.push(` Accepted props: \`{ ${d.propHints.join(", ")} }\``);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
const missing = resolvableDeps.filter(d => d.status === "missing");
|
|
943
|
+
if (missing.length > 0) {
|
|
944
|
+
lines.push("");
|
|
945
|
+
lines.push("> [!IMPORTANT] Missing dependencies: " + missing.map(m => m.name).join(", "));
|
|
946
|
+
lines.push("> Stub these imports so the file compiles. The real implementations will be added later.");
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
// Slot Render Guide — translates slotContract into concrete React patterns.
|
|
950
|
+
// This is the single source of truth for how each visual slot maps to JSX.
|
|
951
|
+
const ownSlots = context.slotContract ? Object.entries(context.slotContract) : [];
|
|
952
|
+
if (ownSlots.length > 0) {
|
|
953
|
+
lines.push("");
|
|
954
|
+
lines.push("## Slot Render Guide");
|
|
955
|
+
lines.push("These are this component's own visual slots. Render EXACTLY as shown — never pass a slot metadata object as a prop value.");
|
|
956
|
+
lines.push("⚠ CRITICAL: Every prop listed here MUST appear in the JSX render output. Props are not optional overrides — they ARE the elements rendered at that position.");
|
|
957
|
+
lines.push("");
|
|
958
|
+
for (const [slotName, def] of ownSlots) {
|
|
959
|
+
// renderPattern is computed deterministically during context generation — use it directly.
|
|
960
|
+
// Fall back to deriving locally only for legacy contexts that pre-date this field.
|
|
961
|
+
const renderPat = def.renderPattern ?? (() => {
|
|
962
|
+
if (def.kind === "text")
|
|
963
|
+
return def.prop ? `{${def.prop}}` : (def.textContent ? `"${def.textContent}"` : "null");
|
|
964
|
+
if (!def.visible)
|
|
965
|
+
return def.prop ? `{${def.prop} ?? null}` : "null";
|
|
966
|
+
if (def.kind === "child" && def.prop)
|
|
967
|
+
return `{${def.prop}}`;
|
|
968
|
+
const depName = def.dependency?.componentName || def.dependency?.importName || "UnknownComponent";
|
|
969
|
+
if (def.prop)
|
|
970
|
+
return `{${def.prop} ?? <${depName} />}`;
|
|
971
|
+
return `<${depName} />`;
|
|
972
|
+
})();
|
|
973
|
+
const vb = def.visibilityBehavior;
|
|
974
|
+
if (def.kind === "text") {
|
|
975
|
+
lines.push(`- **${slotName}** (text) → \`${renderPat}\``);
|
|
976
|
+
if (def.prop)
|
|
977
|
+
lines.push(` Prop type: \`${def.prop}: string\``);
|
|
978
|
+
}
|
|
979
|
+
else if (def.dependency) {
|
|
980
|
+
const compName = def.dependency.componentName || def.dependency.importName;
|
|
981
|
+
const compNormalized = isIconComponent(compName) ? normalizeIconDisplayName(compName) : compName;
|
|
982
|
+
const depFolder = compNormalized.toLowerCase().replace(/[\s_]+/g, "-");
|
|
983
|
+
const importPath = isIconComponent(compName)
|
|
984
|
+
? `../../icons/${depFolder}/index.js`
|
|
985
|
+
: `../${depFolder}/index.js`;
|
|
986
|
+
if (def.prop) {
|
|
987
|
+
lines.push(`- **${slotName}** → MUST accept as \`${def.prop}: React.ReactNode\` prop`);
|
|
988
|
+
lines.push(` JSX: \`${renderPat}\``);
|
|
989
|
+
if (vb === "caller-controlled") {
|
|
990
|
+
lines.push(` Caller-controlled: undefined = slot absent. NEVER hardcode \`<${compName} />\` here.`);
|
|
991
|
+
}
|
|
992
|
+
else if (vb === "hidden-by-default") {
|
|
993
|
+
lines.push(` Hidden by default. DO NOT import \`${compName}\` — it is never rendered directly.`);
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
// caller-overrideable: import needed for fallback
|
|
997
|
+
lines.push(` Import: \`import { ${compName} } from '${importPath}';\``);
|
|
998
|
+
lines.push(` \`<${compName} />\` is the design default; \`${def.prop}\` overrides it.`);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
lines.push(`- **${slotName}** → \`${renderPat}\` (fixed, no prop)`);
|
|
1003
|
+
lines.push(` Import: \`import { ${compName} } from '${importPath}';\``);
|
|
1004
|
+
if (!def.visible)
|
|
1005
|
+
lines.push(` Hidden by default — some variants show this slot.`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
else {
|
|
1009
|
+
if (def.prop) {
|
|
1010
|
+
lines.push(`- **${slotName}** → \`${renderPat}\` (\`${def.prop}: React.ReactNode\`)`);
|
|
1011
|
+
}
|
|
1012
|
+
else {
|
|
1013
|
+
lines.push(`- **${slotName}** → \`${renderPat}\``);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
// Boolean Prop → Slot Visibility section — for composite components only.
|
|
1019
|
+
// When a composite has boolean Figma properties, they control child instance rendering.
|
|
1020
|
+
// We emit a concrete render pattern so the AI doesn't have to guess.
|
|
1021
|
+
const boolVisProps = safeArray(context.properties).filter((p) => p.type === "boolean");
|
|
1022
|
+
if (isComposite && boolVisProps.length > 0) {
|
|
1023
|
+
// Extract alphabetic prefix for each numbered prop (e.g. "tab3" → prefix "tab")
|
|
1024
|
+
const numberedBoolPropRe = /^([a-zA-Z]+)(\d+)$/;
|
|
1025
|
+
lines.push("");
|
|
1026
|
+
lines.push("## Boolean Props — Slot Visibility");
|
|
1027
|
+
lines.push("Each boolean prop below controls whether a specific child instance renders.");
|
|
1028
|
+
lines.push("**Use this render pattern**: `{propName !== false && <ChildComp />}`");
|
|
1029
|
+
lines.push("");
|
|
1030
|
+
for (const p of boolVisProps) {
|
|
1031
|
+
const propName = toPropName(p.name);
|
|
1032
|
+
const defVal = p.defaultValue;
|
|
1033
|
+
const defNote = defVal !== undefined ? ` (default: ${defVal})` : " (default: true)";
|
|
1034
|
+
// Find the best-matching dep by name
|
|
1035
|
+
const propLower = propName.toLowerCase();
|
|
1036
|
+
const m = propName.match(numberedBoolPropRe);
|
|
1037
|
+
const prefix = m ? m[1].toLowerCase() : propLower;
|
|
1038
|
+
const matchedDep = resolvableDeps.find(d => {
|
|
1039
|
+
const depLower = d.name.toLowerCase().replace(/\s+/g, "");
|
|
1040
|
+
return depLower.includes(prefix) || prefix.includes(depLower);
|
|
1041
|
+
});
|
|
1042
|
+
const depPascal = matchedDep
|
|
1043
|
+
? matchedDep.name.split(/\s+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("")
|
|
1044
|
+
: "ChildComponent";
|
|
1045
|
+
lines.push(`- \`${propName}\`${defNote} → \`{${propName} !== false && <${depPascal} />}\``);
|
|
1046
|
+
}
|
|
1047
|
+
lines.push("");
|
|
1048
|
+
lines.push("Each of these props maps to a DIFFERENT instance of the matched child component.");
|
|
1049
|
+
lines.push("Render them in order — `tab3` maps to the 3rd slot, `tab4` to the 4th, etc.");
|
|
1050
|
+
}
|
|
1051
|
+
if (context.layout) {
|
|
1052
|
+
const l = context.layout;
|
|
1053
|
+
lines.push("");
|
|
1054
|
+
lines.push("## Layout");
|
|
1055
|
+
lines.push(`- Direction: ${l.direction}`);
|
|
1056
|
+
if (l.mainAlignment)
|
|
1057
|
+
lines.push(`- Main Alignment: ${l.mainAlignment}`);
|
|
1058
|
+
if (l.crossAlignment)
|
|
1059
|
+
lines.push(`- Cross Alignment: ${l.crossAlignment}`);
|
|
1060
|
+
if (l.gap)
|
|
1061
|
+
lines.push(`- Gap: ${l.gap.rawValue}px${l.gap.mode === "auto" ? " (auto)" : ""}`);
|
|
1062
|
+
if (l.padding) {
|
|
1063
|
+
const p = l.padding;
|
|
1064
|
+
lines.push(`- Padding: top ${p.top.rawValue}, right ${p.right.rawValue}, bottom ${p.bottom.rawValue}, left ${p.left.rawValue}`);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (context.size) {
|
|
1068
|
+
const s = context.size;
|
|
1069
|
+
lines.push("");
|
|
1070
|
+
lines.push("## Size");
|
|
1071
|
+
lines.push(`- Width: ${s.width.rawValue}${s.width.mode ? ` (${s.width.mode})` : ""}`);
|
|
1072
|
+
lines.push(`- Height: ${s.height.rawValue}${s.height.mode ? ` (${s.height.mode})` : ""}`);
|
|
1073
|
+
}
|
|
1074
|
+
if (context.tokenBindings.length > 0) {
|
|
1075
|
+
lines.push("");
|
|
1076
|
+
lines.push("## Token Bindings");
|
|
1077
|
+
for (const b of context.tokenBindings) {
|
|
1078
|
+
const t = b.resolvedToken;
|
|
1079
|
+
if (t) {
|
|
1080
|
+
let tokenDesc = t.name;
|
|
1081
|
+
if (t.collection)
|
|
1082
|
+
tokenDesc += ` (collection: ${t.collection.name})`;
|
|
1083
|
+
if (t.cssVariable)
|
|
1084
|
+
tokenDesc += ` [CSS: ${t.cssVariable}]`;
|
|
1085
|
+
lines.push(`- ${b.slot} → ${tokenDesc}`);
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
lines.push(`- ${b.slot} → ${b.rawValue} (raw)`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
if (context.resolvedBindings && Object.keys(safeObject(context.resolvedBindings)).length > 0) {
|
|
1093
|
+
lines.push("");
|
|
1094
|
+
lines.push("## Resolved Token Bindings (Implementation-Ready)");
|
|
1095
|
+
lines.push("Use these CSS variables and fallback values for styling:");
|
|
1096
|
+
for (const [slot, b] of Object.entries(safeObject(context.resolvedBindings))) {
|
|
1097
|
+
const bind = b;
|
|
1098
|
+
const fallback = (bind.valueType === "DIMENSION" && /^\d+(\.\d+)?$/.test(String(bind.cssValue)))
|
|
1099
|
+
? `${bind.cssValue}px`
|
|
1100
|
+
: bind.cssValue;
|
|
1101
|
+
lines.push(`- **${slot}** [${bind.valueType}]: \`var(${bind.cssVar}, ${fallback})\``);
|
|
1102
|
+
lines.push(` - Resolution Path: ${safeArray(bind.resolutionPath).join(" -> ")}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
if (context.contentSourceMap && Object.keys(safeObject(context.contentSourceMap)).length > 0) {
|
|
1106
|
+
lines.push("");
|
|
1107
|
+
lines.push("## Content Contracts (Implementation-Ready)");
|
|
1108
|
+
lines.push("Use these contracts to manage text content across props and states:");
|
|
1109
|
+
for (const [nodeName, propName] of Object.entries(safeObject(context.contentSourceMap))) {
|
|
1110
|
+
// Find node in renderTree to get full contract details
|
|
1111
|
+
const findNode = (node) => {
|
|
1112
|
+
if (!node)
|
|
1113
|
+
return null;
|
|
1114
|
+
if (node.name === nodeName)
|
|
1115
|
+
return node;
|
|
1116
|
+
for (const child of safeArray(node.children)) {
|
|
1117
|
+
const f = findNode(child);
|
|
1118
|
+
if (f)
|
|
1119
|
+
return f;
|
|
1120
|
+
}
|
|
1121
|
+
return null;
|
|
1122
|
+
};
|
|
1123
|
+
const node = findNode(context.renderTree);
|
|
1124
|
+
const contract = node?.contentContract;
|
|
1125
|
+
if (contract) {
|
|
1126
|
+
lines.push(`- **${nodeName}** [Mode: \`${contract.bindingMode}\`]:`);
|
|
1127
|
+
if (contract.propName)
|
|
1128
|
+
lines.push(` - Prop: \`props.${contract.propName}\``);
|
|
1129
|
+
lines.push(` - Base Value: "${contract.baseValue}"`);
|
|
1130
|
+
if (contract.stateDefaults) {
|
|
1131
|
+
lines.push(` - State Defaults: ${JSON.stringify(contract.stateDefaults)}`);
|
|
1132
|
+
}
|
|
1133
|
+
if (contract.overridePriority) {
|
|
1134
|
+
lines.push(` - Priority: ${contract.overridePriority.join(" > ")}`);
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
lines.push(`- **${nodeName}** → \`props.${propName}\``);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
if (context.dependencyInstances && context.dependencyInstances.length > 0) {
|
|
1143
|
+
lines.push("");
|
|
1144
|
+
lines.push("## Nested Instance Recipes");
|
|
1145
|
+
lines.push("Use these specific recipes when instantiating the following dependency instances:");
|
|
1146
|
+
for (const inst of context.dependencyInstances) {
|
|
1147
|
+
lines.push(`### Instance: \`${inst.name}\` (Component: ${inst.name})`);
|
|
1148
|
+
if (inst.recipe) {
|
|
1149
|
+
lines.push(`- **Implementation Mode**: \`${inst.recipe.implementationMode || 'invoke-dependency'}\``);
|
|
1150
|
+
if (inst.recipe.classification) {
|
|
1151
|
+
const cl = inst.recipe.classification;
|
|
1152
|
+
if (cl.representable.length > 0)
|
|
1153
|
+
lines.push(` - Representable Overrides: ${cl.representable.join(", ")}`);
|
|
1154
|
+
if (cl.derivedButRepresentable.length > 0)
|
|
1155
|
+
lines.push(` - Derived Representable Overrides: ${cl.derivedButRepresentable.join(", ")}`);
|
|
1156
|
+
if (cl.nonRepresentable.length > 0) {
|
|
1157
|
+
lines.push(` - **NON-REPRESENTABLE OVERRIDES**: ${cl.nonRepresentable.join(", ")}`);
|
|
1158
|
+
for (const w of cl.warnings) {
|
|
1159
|
+
lines.push(` - > [!WARNING] ${w}`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
if (inst.recipe.styleOverrides) {
|
|
1164
|
+
lines.push(`- **Local Style Overrides** (Apply as CSS/style local patches):`);
|
|
1165
|
+
for (const [prop, val] of Object.entries(safeObject(inst.recipe.styleOverrides))) {
|
|
1166
|
+
let valStr = val.toToken ? `var(${val.toCssVar}) /* ${val.toToken} */` : val.toColor;
|
|
1167
|
+
if (val.fromToken || val.fromColor) {
|
|
1168
|
+
let fromStr = val.fromToken ? `var(${val.fromCssVar})` : val.fromColor;
|
|
1169
|
+
valStr = `${fromStr} -> ${valStr}`;
|
|
1170
|
+
}
|
|
1171
|
+
lines.push(` - ${prop}: ${valStr}`);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
lines.push(`- **Resolved Usage**: ${inst.recipe.summary}`);
|
|
1175
|
+
lines.push(`- **Functional Type**: ${inst.recipe.standardType}`);
|
|
1176
|
+
if (inst.recipe.implementationMode === "invoke-dependency-with-local-patch") {
|
|
1177
|
+
lines.push(`- > [!IMPORTANT]`);
|
|
1178
|
+
lines.push(`- > Use a local one-off patch (e.g. wrapper div, style prop) for non-representable overrides.`);
|
|
1179
|
+
}
|
|
1180
|
+
else if (inst.recipe.implementationMode === "recommend-variant-promotion") {
|
|
1181
|
+
lines.push(`- > [!TIP]`);
|
|
1182
|
+
lines.push(`- > Consider promoting these overrides to a formal variant in the dependency component if they reuse often.`);
|
|
1183
|
+
}
|
|
1184
|
+
if (inst.recipe.instanceSummary) {
|
|
1185
|
+
const sm = inst.recipe.instanceSummary;
|
|
1186
|
+
lines.push(`- **Divergence Summary**:`);
|
|
1187
|
+
if (sm.divergenceSummary.contentOverrides.length > 0)
|
|
1188
|
+
lines.push(` - Content Overrides: ${sm.divergenceSummary.contentOverrides.join(", ")}`);
|
|
1189
|
+
if (sm.divergenceSummary.slotVisibilityOverrides.length > 0)
|
|
1190
|
+
lines.push(` - Visibility Overrides: ${sm.divergenceSummary.slotVisibilityOverrides.join(", ")}`);
|
|
1191
|
+
if (sm.divergenceSummary.styleOverrides.length > 0)
|
|
1192
|
+
lines.push(` - Style Overrides: ${sm.divergenceSummary.styleOverrides.join(", ")}`);
|
|
1193
|
+
if (sm.divergenceSummary.dependencyOverrides.length > 0)
|
|
1194
|
+
lines.push(` - Dependency Swaps: ${sm.divergenceSummary.dependencyOverrides.join(", ")}`);
|
|
1195
|
+
}
|
|
1196
|
+
if (inst.recipe.invocationContract) {
|
|
1197
|
+
const ic = inst.recipe.invocationContract;
|
|
1198
|
+
lines.push(`- **Implementation Invocation Contract** (FOLLOW LITERALLY):`);
|
|
1199
|
+
lines.push(` - Component: \`<${ic.componentName} />\``);
|
|
1200
|
+
lines.push(` - Children Policy: \`${ic.childrenPolicy}\``);
|
|
1201
|
+
if (safeArray(ic.omitProps).length > 0)
|
|
1202
|
+
lines.push(` - Omit Props: ${safeArray(ic.omitProps).join(", ")}`);
|
|
1203
|
+
if (Object.keys(safeObject(ic.slotContract)).length > 0) {
|
|
1204
|
+
lines.push(` - **Slot Contract** (Kind-Aware):`);
|
|
1205
|
+
for (const [slot, def] of Object.entries(safeObject(ic.slotContract))) {
|
|
1206
|
+
let slotLine = ` - ${slot}: ${def.kind} (visible: ${def.visible})`;
|
|
1207
|
+
if (def.prop)
|
|
1208
|
+
slotLine += ` [linked prop: ${def.prop}]`;
|
|
1209
|
+
if (def.dependency)
|
|
1210
|
+
slotLine += ` [dependency: <${def.dependency.componentName} />]`;
|
|
1211
|
+
lines.push(slotLine);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
if (Object.keys(safeObject(ic.iconBindingPolicy)).length > 0) {
|
|
1215
|
+
const icons = Object.entries(safeObject(ic.iconBindingPolicy)).map(([k, v]) => `${k}="${v}"`).join(", ");
|
|
1216
|
+
lines.push(` - Icon Bindings: \`${icons}\``);
|
|
1217
|
+
}
|
|
1218
|
+
if (ic.overrideContract) {
|
|
1219
|
+
const oc = ic.overrideContract;
|
|
1220
|
+
lines.push(` - **Override Contract** (Compiled):`);
|
|
1221
|
+
if (Object.keys(safeObject(oc.slotVisibility)).length > 0) {
|
|
1222
|
+
lines.push(` - Slot Visibility: ${JSON.stringify(oc.slotVisibility)}`);
|
|
1223
|
+
}
|
|
1224
|
+
if (Object.keys(safeObject(oc.content)).length > 0) {
|
|
1225
|
+
lines.push(` - Content Overrides: ${JSON.stringify(oc.content)}`);
|
|
1226
|
+
}
|
|
1227
|
+
if (Object.keys(safeObject(oc.dependency)).length > 0) {
|
|
1228
|
+
const deps = Object.entries(safeObject(oc.dependency))
|
|
1229
|
+
.map(([k, v]) => `${k} → <${v.componentName} />`)
|
|
1230
|
+
.join(", ");
|
|
1231
|
+
lines.push(` - Dependency Swaps: ${deps}`);
|
|
1232
|
+
}
|
|
1233
|
+
if (Object.keys(safeObject(oc.style)).length > 0) {
|
|
1234
|
+
lines.push(` - Style Overrides: ${JSON.stringify(oc.style)}`);
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (inst.recipe.designRules) {
|
|
1239
|
+
const dr = inst.recipe.designRules;
|
|
1240
|
+
if (dr.border || dr.spacing || dr.normalizationPolicy || dr.slotVisibilityPolicy || (dr.notes && dr.notes.length > 0)) {
|
|
1241
|
+
lines.push(`- **Design Rules** (Intentional Divergence):`);
|
|
1242
|
+
if (dr.border)
|
|
1243
|
+
lines.push(` - Border: \`${dr.border.policy}\`${dr.border.reason ? ` Reason: ${dr.border.reason}` : ""}`);
|
|
1244
|
+
if (dr.spacing)
|
|
1245
|
+
lines.push(` - Spacing: \`${dr.spacing.policy}\``);
|
|
1246
|
+
if (dr.normalizationPolicy)
|
|
1247
|
+
lines.push(` - Normalization: \`${dr.normalizationPolicy}\``);
|
|
1248
|
+
if (dr.slotVisibilityPolicy)
|
|
1249
|
+
lines.push(` - Slot Visibility: \`${dr.slotVisibilityPolicy}\``);
|
|
1250
|
+
for (const note of safeArray(dr.notes))
|
|
1251
|
+
lines.push(` - NOTE: ${note}`);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (inst.recipe.implementationHints) {
|
|
1255
|
+
const ih = inst.recipe.implementationHints;
|
|
1256
|
+
if (ih.notes && ih.notes.length > 0) {
|
|
1257
|
+
lines.push(`- **Implementation Hints**:`);
|
|
1258
|
+
for (const note of safeArray(ih.notes))
|
|
1259
|
+
lines.push(` - > [!TIP] ${note}`);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
if (inst.recipe.invocationHint) {
|
|
1263
|
+
lines.push(`- **Invocation Hint**: \`${inst.recipe.invocationHint}\``);
|
|
1264
|
+
}
|
|
1265
|
+
const allResolvedProps = safeObject(inst.recipe.resolvedProps);
|
|
1266
|
+
const cleanProps = Object.fromEntries(Object.entries(allResolvedProps).filter(([, v]) => !isFigmaMeta(v)));
|
|
1267
|
+
const metaProps = Object.entries(allResolvedProps)
|
|
1268
|
+
.filter(([, v]) => isFigmaMeta(v))
|
|
1269
|
+
.map(([k, v]) => ({ slot: k, componentId: v.componentId, kind: v.kind }));
|
|
1270
|
+
if (Object.keys(cleanProps).length > 0) {
|
|
1271
|
+
const propsStr = Object.entries(cleanProps).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(", ");
|
|
1272
|
+
lines.push(`- **Resolved Props**: \`${propsStr}\``);
|
|
1273
|
+
}
|
|
1274
|
+
// Unresolvable Figma component refs — tell AI to render as component, not as data
|
|
1275
|
+
for (const mp of metaProps) {
|
|
1276
|
+
lines.push(`- **Slot \`${mp.slot}\`**: render as a React component in JSX — NOT as a prop value.`);
|
|
1277
|
+
lines.push(` Look up the dependency name from the Slot Contract above and render \`<ComponentName />\`.`);
|
|
1278
|
+
}
|
|
1279
|
+
// Concrete JSX snippet — clean scalar props + resolved deps as prop={<Comp />}
|
|
1280
|
+
const icForJsx = inst.recipe.invocationContract;
|
|
1281
|
+
if (icForJsx) {
|
|
1282
|
+
const compName = icForJsx.componentName;
|
|
1283
|
+
const jsxParts = Object.entries(cleanProps).map(([k, v]) => {
|
|
1284
|
+
const dep = v;
|
|
1285
|
+
if (dep && typeof dep === "object" && dep.kind === "dependency" && dep.importName) {
|
|
1286
|
+
const depName = dep.importName
|
|
1287
|
+
.split(/[\s\-_]+/).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join("");
|
|
1288
|
+
return `${k}={<${depName} />}`;
|
|
1289
|
+
}
|
|
1290
|
+
if (typeof v === "boolean")
|
|
1291
|
+
return v ? k : `${k}={false}`;
|
|
1292
|
+
return `${k}="${v}"`;
|
|
1293
|
+
});
|
|
1294
|
+
const jsxSnippet = jsxParts.length > 0
|
|
1295
|
+
? `<${compName} ${jsxParts.join(" ")} />`
|
|
1296
|
+
: `<${compName} />`;
|
|
1297
|
+
lines.push(`- **Concrete JSX** (copy exactly): \`${jsxSnippet}\``);
|
|
1298
|
+
}
|
|
1299
|
+
if (safeArray(inst.recipe.visibleSlots).length > 0)
|
|
1300
|
+
lines.push(`- **Visible Slots**: ${safeArray(inst.recipe.visibleSlots).join(", ")}`);
|
|
1301
|
+
if (safeArray(inst.recipe.hiddenSlots).length > 0)
|
|
1302
|
+
lines.push(`- **Hidden Slots**: ${safeArray(inst.recipe.hiddenSlots).join(", ")}`);
|
|
1303
|
+
}
|
|
1304
|
+
if (inst.childOverrides && inst.childOverrides.length > 0) {
|
|
1305
|
+
lines.push(`- **Child Instance Overrides**:`);
|
|
1306
|
+
for (const co of safeArray(inst.childOverrides)) {
|
|
1307
|
+
const coSummary = co.recipe?.summary || "Deep override";
|
|
1308
|
+
lines.push(` - \`${co.name}\` (${co.role}): ${coSummary}`);
|
|
1309
|
+
if (co.variantSelection) {
|
|
1310
|
+
const sel = Object.entries(safeObject(co.variantSelection)).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
1311
|
+
lines.push(` - Variant Selection: ${sel}`);
|
|
1312
|
+
}
|
|
1313
|
+
if (co.propertyOverrides) {
|
|
1314
|
+
const ovr = Object.entries(safeObject(co.propertyOverrides))
|
|
1315
|
+
.filter(([, v]) => !isFigmaMeta(v) && !(typeof v === "string" && /^\d+:\d+/.test(v)))
|
|
1316
|
+
.map(([k, v]) => `${k}=${v}`).join(", ");
|
|
1317
|
+
if (ovr)
|
|
1318
|
+
lines.push(` - Property Overrides: ${ovr}`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
if (inst.variantSelection) {
|
|
1323
|
+
const sel = Object.entries(safeObject(inst.variantSelection)).map(([k, v]) => `${k}=${v}`).join(", ");
|
|
1324
|
+
lines.push(`- **Variant Selection**: ${sel}`);
|
|
1325
|
+
}
|
|
1326
|
+
if (inst.propertyOverrides) {
|
|
1327
|
+
const ovr = Object.entries(safeObject(inst.propertyOverrides))
|
|
1328
|
+
.filter(([, v]) => !isFigmaMeta(v) && !(typeof v === "string" && /^\d+:\d+/.test(v)))
|
|
1329
|
+
.map(([k, v]) => `${k}=${v}`).join(", ");
|
|
1330
|
+
if (ovr)
|
|
1331
|
+
lines.push(`- **Property Overrides**: ${ovr}`);
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
if (context.renderTree) {
|
|
1336
|
+
lines.push("");
|
|
1337
|
+
lines.push("## Render Structure");
|
|
1338
|
+
lines.push("The component has the following visual structure:");
|
|
1339
|
+
const renderNode = (node, indent) => {
|
|
1340
|
+
let line = `${indent}- ${node.name} (${node.role || "node"})`;
|
|
1341
|
+
if (node.textContent)
|
|
1342
|
+
line += `: "${node.textContent}"`;
|
|
1343
|
+
if (node.contentContract) {
|
|
1344
|
+
const ct = node.contentContract;
|
|
1345
|
+
line += ` [Mode: ${ct.bindingMode}${ct.propName ? `, Prop: ${ct.propName}` : ""}]`;
|
|
1346
|
+
if (ct.stateDefaults)
|
|
1347
|
+
line += ` (State-aware)`;
|
|
1348
|
+
}
|
|
1349
|
+
if (node.typographyContract) {
|
|
1350
|
+
const tc = node.typographyContract;
|
|
1351
|
+
if (tc.styleRef)
|
|
1352
|
+
line += ` [Style: ${tc.styleRef.displayName}]`;
|
|
1353
|
+
if (tc.codeHint?.preferredClassName)
|
|
1354
|
+
line += ` [Class: ${tc.codeHint.preferredClassName}]`;
|
|
1355
|
+
}
|
|
1356
|
+
if (node.componentId)
|
|
1357
|
+
line += ` [Instance: ${node.componentId}]`;
|
|
1358
|
+
lines.push(line);
|
|
1359
|
+
if (node.children) {
|
|
1360
|
+
for (const child of node.children) {
|
|
1361
|
+
renderNode(child, indent + " ");
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
};
|
|
1365
|
+
renderNode(context.renderTree, "");
|
|
1366
|
+
lines.push("");
|
|
1367
|
+
lines.push("> **Typography**: Nodes annotated with `[Class: X]` carry a global utility class from `typography.css`.");
|
|
1368
|
+
lines.push("> Apply it as a plain string alongside the CSS module class — never as a module key lookup:");
|
|
1369
|
+
lines.push("> `[styles['element-name'], 'type-body-sm'].filter(Boolean).join(' ')`");
|
|
1370
|
+
}
|
|
1371
|
+
if (context.variantDiffs && context.variantDiffs.length > 0) {
|
|
1372
|
+
lines.push("");
|
|
1373
|
+
lines.push("## Variant Differences");
|
|
1374
|
+
lines.push("Apply these changes to the `baseRenderTree` when the corresponding variant state is active.");
|
|
1375
|
+
for (const diff of safeArray(context.variantDiffs)) {
|
|
1376
|
+
const selectionStr = Object.entries(safeObject(diff.variantSelection))
|
|
1377
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
1378
|
+
.join(", ");
|
|
1379
|
+
lines.push(`### State: ${selectionStr} (${diff.variantName})`);
|
|
1380
|
+
if (safeArray(diff.addedNodes).length > 0) {
|
|
1381
|
+
lines.push("- Added: " + safeArray(diff.addedNodes).map((n) => {
|
|
1382
|
+
const parent = n.parentName ? ` inside **${n.parentName}**` : "";
|
|
1383
|
+
return `${n.name} (${n.type})${parent}`;
|
|
1384
|
+
}).join(", "));
|
|
1385
|
+
}
|
|
1386
|
+
if (safeArray(diff.removedNodes).length > 0) {
|
|
1387
|
+
lines.push("- Removed: " + safeArray(diff.removedNodes).map((n) => {
|
|
1388
|
+
const parent = n.parentName ? ` from **${n.parentName}**` : "";
|
|
1389
|
+
return `${n.name} (${n.type})${parent}`;
|
|
1390
|
+
}).join(", "));
|
|
1391
|
+
}
|
|
1392
|
+
if (safeArray(diff.changedNodes).length > 0) {
|
|
1393
|
+
for (const node of safeArray(diff.changedNodes)) {
|
|
1394
|
+
lines.push(`- Changed in \`${node.nodeName}\` (at ${node.nodePath}):`);
|
|
1395
|
+
for (const ch of node.changes) {
|
|
1396
|
+
let fromVal = typeof ch.from === "object" ? JSON.stringify(ch.from) : ch.from;
|
|
1397
|
+
let toVal = typeof ch.to === "object" ? JSON.stringify(ch.to) : ch.to;
|
|
1398
|
+
lines.push(` - ${ch.property}: ${fromVal} -> ${toVal}`);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
// Conditional Rendering Guide — derived from variantDiffs removedNodes + stateContract visibility
|
|
1405
|
+
const safeArrOuter = (v) => Array.isArray(v) ? v : [];
|
|
1406
|
+
const conditionalRules = [];
|
|
1407
|
+
const seenConditionalKeys = new Set();
|
|
1408
|
+
const depNames = new Set((resolvableDeps ?? []).map((d) => d.name?.split(/[\s\-_]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("") ?? ""));
|
|
1409
|
+
const toPascal = (name) => name.split(/[\s_-]+/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join("");
|
|
1410
|
+
const toGuard = (selStr) => selStr.replace(/ === '([^']+)'/, " !== '$1'");
|
|
1411
|
+
for (const diff of safeArrOuter(context.variantDiffs)) {
|
|
1412
|
+
const selStr = Object.entries(safeObject(diff.variantSelection)).map(([k, v]) => `${toPropName(k)} === '${v}'`).join(" && ");
|
|
1413
|
+
const guardStr = toGuard(selStr);
|
|
1414
|
+
// Removed nodes
|
|
1415
|
+
for (const node of safeArrOuter(diff.removedNodes)) {
|
|
1416
|
+
const key = `${selStr}::${node.name}::removed`;
|
|
1417
|
+
if (seenConditionalKeys.has(key))
|
|
1418
|
+
continue;
|
|
1419
|
+
seenConditionalKeys.add(key);
|
|
1420
|
+
const pascal = toPascal(node.name);
|
|
1421
|
+
const el = depNames.has(pascal) ? `<${pascal} ... />` : `<${pascal} />`;
|
|
1422
|
+
conditionalRules.push(`- When \`${selStr}\`: **${node.name}** removed → \`{${guardStr} && ${el}}\``);
|
|
1423
|
+
}
|
|
1424
|
+
// Nodes that become invisible (visible: true → false)
|
|
1425
|
+
for (const cn of safeArrOuter(diff.changedNodes)) {
|
|
1426
|
+
const visChange = safeArrOuter(cn.changes).find((c) => c.property === "visible" && c.to === false);
|
|
1427
|
+
if (!visChange)
|
|
1428
|
+
continue;
|
|
1429
|
+
const key = `${selStr}::${cn.nodeName}::vis`;
|
|
1430
|
+
if (seenConditionalKeys.has(key))
|
|
1431
|
+
continue;
|
|
1432
|
+
seenConditionalKeys.add(key);
|
|
1433
|
+
const pascal = toPascal(cn.nodeName);
|
|
1434
|
+
const el = depNames.has(pascal) ? `<${pascal} ... />` : `<${pascal} />`;
|
|
1435
|
+
const tree = context.renderTree ?? context.baseRenderTree;
|
|
1436
|
+
const nodeAtPath = cn.nodePath ? findNodeAtPath(tree, cn.nodePath) : null;
|
|
1437
|
+
const contentDesc = describeNodeContent(nodeAtPath);
|
|
1438
|
+
// Find parent and enumerate ALL siblings with the same name so the AI knows the
|
|
1439
|
+
// full sibling set — it must render ALL of them, wrapping only the hidden one.
|
|
1440
|
+
const parentPath = cn.nodePath ? cn.nodePath.split("/").slice(0, -1).join("/") : null;
|
|
1441
|
+
const parentNode = parentPath ? findNodeAtPath(tree, parentPath || "root") : null;
|
|
1442
|
+
const siblings = parentNode?.children ?? [];
|
|
1443
|
+
const sameName = siblings.filter((s) => s.name === cn.nodeName);
|
|
1444
|
+
if (sameName.length > 1) {
|
|
1445
|
+
// Produce an explicit sibling map for the parent container
|
|
1446
|
+
const parentDesc = parentNode?.name ? ` **${parentNode.name}**` : "";
|
|
1447
|
+
conditionalRules.push(`- When \`${selStr}\`: hide the **${cn.nodeName}** that ${contentDesc || `is at path \`${cn.nodePath}\``} (visible: true → false)`);
|
|
1448
|
+
conditionalRules.push(` Parent container${parentDesc} has ${sameName.length} \`${cn.nodeName}\` instances — render ALL of them, wrap only the hidden one:`);
|
|
1449
|
+
siblings.forEach((sib, i) => {
|
|
1450
|
+
if (sib.name !== cn.nodeName)
|
|
1451
|
+
return;
|
|
1452
|
+
const sibPath = `${parentPath}/${i}`;
|
|
1453
|
+
const sibDesc = describeNodeContent(sib);
|
|
1454
|
+
const isHidden = sibPath === cn.nodePath;
|
|
1455
|
+
const marker = isHidden
|
|
1456
|
+
? ` → \`{${guardStr} && ${el}}\` ← WRAP THIS ONE (${sibDesc || "hidden in this state"})`
|
|
1457
|
+
: ` → always render \`${el}\` ← NEVER wrap (${sibDesc || "always visible"})`;
|
|
1458
|
+
conditionalRules.push(` - [${sibPath}] ${sib.name}: ${marker}`);
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
else {
|
|
1462
|
+
const pathNote = cn.nodePath
|
|
1463
|
+
? ` at path \`${cn.nodePath}\`${contentDesc ? ` (${contentDesc})` : ""}`
|
|
1464
|
+
: "";
|
|
1465
|
+
conditionalRules.push(`- When \`${selStr}\`: **${cn.nodeName}**${pathNote} (visible: true → false) → \`{${guardStr} && ${el}}\``);
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
// Also derive rules from stateContract.stateDefaults where slotVisibility is false
|
|
1470
|
+
if (context.stateContract) {
|
|
1471
|
+
const sc = context.stateContract;
|
|
1472
|
+
const ctrlProp = toPropName(sc.controller.name);
|
|
1473
|
+
for (const [state, defaults] of Object.entries(safeObject(sc.stateDefaults))) {
|
|
1474
|
+
const def = defaults;
|
|
1475
|
+
const hiddenSlots = def?.slotVisibility
|
|
1476
|
+
? Object.entries(def.slotVisibility)
|
|
1477
|
+
.filter(([, visible]) => !visible)
|
|
1478
|
+
.map(([slotName]) => slotName)
|
|
1479
|
+
: [];
|
|
1480
|
+
for (const slotName of hiddenSlots) {
|
|
1481
|
+
const selStr = `${ctrlProp} === '${state}'`;
|
|
1482
|
+
const guardStr = toGuard(selStr);
|
|
1483
|
+
const key = `${selStr}::${slotName}`;
|
|
1484
|
+
if (seenConditionalKeys.has(key))
|
|
1485
|
+
continue;
|
|
1486
|
+
seenConditionalKeys.add(key);
|
|
1487
|
+
const pascal = toPascal(slotName);
|
|
1488
|
+
const el = depNames.has(pascal) ? `<${pascal} ... />` : `<${pascal} />`;
|
|
1489
|
+
conditionalRules.push(`- When \`${selStr}\`: **${slotName}** slot hidden → \`{${guardStr} && ${el}}\``);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (conditionalRules.length > 0) {
|
|
1494
|
+
lines.push("");
|
|
1495
|
+
lines.push("## Conditional Rendering Guide");
|
|
1496
|
+
lines.push("These nodes MUST be conditionally hidden when the listed prop combination is active. Use exactly these patterns:");
|
|
1497
|
+
for (const rule of conditionalRules)
|
|
1498
|
+
lines.push(rule);
|
|
1499
|
+
}
|
|
1500
|
+
// Props Interface Skeleton — concrete starting point for the .types.ts file
|
|
1501
|
+
const propsSkeletonLines = buildPropsInterfaceSkeleton(context, codeName);
|
|
1502
|
+
lines.push("");
|
|
1503
|
+
lines.push("## Props Interface Skeleton");
|
|
1504
|
+
lines.push("Start your `.types.ts` file from this skeleton. ALL props listed are REQUIRED — do not drop any.");
|
|
1505
|
+
lines.push("");
|
|
1506
|
+
lines.push("```typescript");
|
|
1507
|
+
lines.push(propsSkeletonLines);
|
|
1508
|
+
lines.push("```");
|
|
1509
|
+
// CSS Skeleton — deterministic starting point so AI never emits an empty CSS file
|
|
1510
|
+
const cssLines = buildCssSkeleton(context);
|
|
1511
|
+
if (cssLines) {
|
|
1512
|
+
lines.push("");
|
|
1513
|
+
lines.push("## CSS Skeleton");
|
|
1514
|
+
lines.push("Start your CSS module from this skeleton. Fill in missing values and add state/variant rules.");
|
|
1515
|
+
lines.push("");
|
|
1516
|
+
lines.push("```css");
|
|
1517
|
+
lines.push(cssLines);
|
|
1518
|
+
lines.push("```");
|
|
1519
|
+
lines.push("");
|
|
1520
|
+
lines.push("> **data-\\* wiring**: Every `[data-X=\"Y\"]` selector in the skeleton above MUST be driven by a prop.");
|
|
1521
|
+
lines.push("> Wire each variant/state axis to a `data-` attribute on the root element in JSX:");
|
|
1522
|
+
lines.push("> ```tsx");
|
|
1523
|
+
lines.push("> <div");
|
|
1524
|
+
lines.push("> className={styles.container}");
|
|
1525
|
+
if (context.variantAxes.length > 0) {
|
|
1526
|
+
for (const axis of context.variantAxes) {
|
|
1527
|
+
const attrName = axis.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
1528
|
+
const propName = toPropName(axis.name);
|
|
1529
|
+
lines.push(`> data-${attrName}={${propName}}`);
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (context.stateContract) {
|
|
1533
|
+
const sc = context.stateContract;
|
|
1534
|
+
const attrName = sc.controller.name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
|
|
1535
|
+
const propName = toPropName(sc.controller.name);
|
|
1536
|
+
lines.push(`> data-${attrName}={${propName}}`);
|
|
1537
|
+
}
|
|
1538
|
+
lines.push("> />");
|
|
1539
|
+
lines.push("> ```");
|
|
1540
|
+
lines.push("> Selectors in the skeleton are ALREADY written in `[data-*]` form — connect the prop, do NOT convert them to className logic.");
|
|
1541
|
+
}
|
|
1542
|
+
// TSX Scaffold — only for non-icon components (icons get a plain SVG shell instead)
|
|
1543
|
+
if (!isIcon) {
|
|
1544
|
+
const jsxScaffold = buildJsxScaffold(context, codeName, requiredImportsList);
|
|
1545
|
+
lines.push("");
|
|
1546
|
+
lines.push("## TSX Scaffold");
|
|
1547
|
+
lines.push("Start your `.tsx` component file from this scaffold. Imports, prop wiring, and `data-*` attributes are pre-derived from Figma. Fill in the slot bodies using the Slot Render Guide above.");
|
|
1548
|
+
lines.push("");
|
|
1549
|
+
lines.push("```tsx");
|
|
1550
|
+
lines.push(jsxScaffold);
|
|
1551
|
+
lines.push("```");
|
|
1552
|
+
}
|
|
1553
|
+
lines.push("");
|
|
1554
|
+
lines.push("## Implementation Requirements");
|
|
1555
|
+
for (const inst of instructions) {
|
|
1556
|
+
lines.push(`- ${inst}`);
|
|
1557
|
+
}
|
|
1558
|
+
if (framework) {
|
|
1559
|
+
lines.push("");
|
|
1560
|
+
lines.push(`Please implement this component in ${framework}.`);
|
|
1561
|
+
}
|
|
1562
|
+
const promptText = lines.join("\n");
|
|
1563
|
+
// 2. Map to Condensed JSON Package
|
|
1564
|
+
// Orientation fields come first so an AI reading top-to-bottom gets intent before raw data.
|
|
1565
|
+
return {
|
|
1566
|
+
intent: "implement-component",
|
|
1567
|
+
promptText,
|
|
1568
|
+
instructions,
|
|
1569
|
+
structureSummary,
|
|
1570
|
+
component: {
|
|
1571
|
+
name: c.name,
|
|
1572
|
+
id: c.id,
|
|
1573
|
+
kind: c.kind.value,
|
|
1574
|
+
category: c.category,
|
|
1575
|
+
description: c.description
|
|
1576
|
+
},
|
|
1577
|
+
framework: framework ?? null,
|
|
1578
|
+
variants: context.variantAxes.map(v => ({
|
|
1579
|
+
name: v.name,
|
|
1580
|
+
options: v.options,
|
|
1581
|
+
defaultValue: v.defaultValue
|
|
1582
|
+
})),
|
|
1583
|
+
properties: context.properties.map(p => ({
|
|
1584
|
+
name: p.name,
|
|
1585
|
+
type: p.type
|
|
1586
|
+
})),
|
|
1587
|
+
dependencies: context.dependencies.map(d => ({
|
|
1588
|
+
name: d.name,
|
|
1589
|
+
count: d.count,
|
|
1590
|
+
status: d.status,
|
|
1591
|
+
...(d.importPath ? { importPath: d.importPath } : {})
|
|
1592
|
+
})),
|
|
1593
|
+
defaultVariantSelection: context.defaultVariantSelection,
|
|
1594
|
+
layout: context.layout ? {
|
|
1595
|
+
direction: context.layout.direction,
|
|
1596
|
+
mainAlignment: context.layout.mainAlignment || context.layout.alignment,
|
|
1597
|
+
crossAlignment: context.layout.crossAlignment,
|
|
1598
|
+
gap: context.layout.gap ? {
|
|
1599
|
+
value: context.layout.gap.rawValue,
|
|
1600
|
+
mode: context.layout.gap.mode || "fixed"
|
|
1601
|
+
} : undefined,
|
|
1602
|
+
padding: context.layout.padding ? {
|
|
1603
|
+
top: context.layout.padding.top.rawValue,
|
|
1604
|
+
right: context.layout.padding.right.rawValue,
|
|
1605
|
+
bottom: context.layout.padding.bottom.rawValue,
|
|
1606
|
+
left: context.layout.padding.left.rawValue
|
|
1607
|
+
} : undefined
|
|
1608
|
+
} : null,
|
|
1609
|
+
size: context.size ? {
|
|
1610
|
+
width: {
|
|
1611
|
+
value: context.size.width.rawValue,
|
|
1612
|
+
mode: context.size.width.mode || "fixed"
|
|
1613
|
+
},
|
|
1614
|
+
height: {
|
|
1615
|
+
value: context.size.height.rawValue,
|
|
1616
|
+
mode: context.size.height.mode || "fixed"
|
|
1617
|
+
}
|
|
1618
|
+
} : null,
|
|
1619
|
+
tokens: context.tokenBindings.map(b => ({
|
|
1620
|
+
slot: b.slot,
|
|
1621
|
+
token: b.resolvedToken?.name || b.rawValue,
|
|
1622
|
+
collection: b.resolvedToken?.collection?.name,
|
|
1623
|
+
cssVariable: b.resolvedToken?.cssVariable
|
|
1624
|
+
})),
|
|
1625
|
+
resolvedBindings: context.resolvedBindings,
|
|
1626
|
+
slotContract: context.slotContract,
|
|
1627
|
+
stateContract: context.stateContract,
|
|
1628
|
+
interactionContract: context.interactionContract,
|
|
1629
|
+
prototypeInteractionContract: context.prototypeInteractionContract,
|
|
1630
|
+
transitionSummary: context.transitionSummary,
|
|
1631
|
+
variantDiffs: context.variantDiffs,
|
|
1632
|
+
dependencyInstances: context.dependencyInstances,
|
|
1633
|
+
contentSourceMap: context.contentSourceMap,
|
|
1634
|
+
renderTree: context.renderTree,
|
|
1635
|
+
baseRenderTree: context.baseRenderTree,
|
|
1636
|
+
childNodes: context.childNodes,
|
|
1637
|
+
compressedContext: context.compressedContext,
|
|
1638
|
+
designRules: context.designRules,
|
|
1639
|
+
implementationHints: context.implementationHints,
|
|
1640
|
+
metadata: context.metadata,
|
|
1641
|
+
generatedAt: new Date().toISOString()
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
//# sourceMappingURL=generate-component-prompt.js.map
|