openyida 2026.9.21 → 2026.9.22-beta.2
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/README.md +14 -20
- package/bin/yida.js +6 -11
- package/lib/app/app-list.js +3 -7
- package/lib/app/canvas-compile.js +12 -22
- package/lib/app/check-prd-completeness.js +0 -7
- package/lib/app/create-app.js +1 -4
- package/lib/app/create-form/args.js +12 -37
- package/lib/app/create-form/batch.js +11 -86
- package/lib/app/create-form.js +86 -193
- package/lib/app/create-page.js +17 -84
- package/lib/app/custom-theme.js +0 -11
- package/lib/app/form-field-validator.js +41 -26
- package/lib/app/form-open-guard.js +10 -97
- package/lib/app/get-form-config.js +0 -1
- package/lib/app/inline-css-guard.js +31 -1
- package/lib/app/services/form-compiler.js +4 -2
- package/lib/app/theme-from-design.js +41 -144
- package/lib/app/theme-presets.js +1 -1
- package/lib/app/update-app.js +13 -63
- package/lib/asset/ai-image.js +9 -59
- package/lib/asset/asset-cmd.js +7 -18
- package/lib/asset/asset-plan.js +9 -102
- package/lib/asset/asset-resolve.js +10 -55
- package/lib/asset/asset-status.js +4 -5
- package/lib/asset/host-capabilities.js +2 -11
- package/lib/asset/url-verify.js +4 -19
- package/lib/auth/dws-sync.js +148 -0
- package/lib/auth/token-auth.js +73 -9
- package/lib/auth/token-store.js +50 -0
- package/lib/connector/connector-test.js +5 -19
- package/lib/connector/contract.js +1 -3
- package/lib/core/agent-capabilities.js +16 -19
- package/lib/core/command-manifest.js +40 -329
- package/lib/core/locales/en.js +17 -74
- package/lib/core/locales/zh.js +16 -73
- package/lib/core/redact.js +10 -15
- package/lib/core/sample.js +30 -72
- package/lib/core/utils.js +4 -0
- package/lib/design-plan/design-plan.js +4 -51
- package/lib/design-plan/init.js +26 -121
- package/lib/design-plan/materialize.js +348 -451
- package/lib/design-plan/normalize.js +29 -74
- package/lib/design-plan/parallel.js +16 -71
- package/lib/design-plan/patch.js +16 -63
- package/lib/design-plan/preview.js +8 -6
- package/lib/design-plan/validate.js +11 -71
- package/lib/process/create-process.js +50 -84
- package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +90 -20
- package/lib/samples/openyida-scaffold/canvas-nav/content.jsx +13 -93
- package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +18 -100
- package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +4 -6
- package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +5 -5
- package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +2 -1
- package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +1 -1
- package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +9 -15
- package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +83 -9
- package/lib/samples/yida-rechart/trend-combo.canvas.jsx +87 -14
- package/package.json +1 -3
- package/scripts/postinstall.js +3 -3
- package/yida-skills/SKILL.md +16 -19
- package/yida-skills/references/official-example-schema-patterns.md +1 -1
- package/yida-skills/references/yida-api.md +0 -4
- package/yida-skills/skills/yida-app/SKILL.md +23 -46
- package/yida-skills/skills/yida-app/workflow/parallel-work.md +6 -15
- package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +15 -29
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +11 -29
- package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +2 -4
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +9 -23
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -6
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +4 -15
- package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +4 -6
- package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +9 -15
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +11 -25
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +28 -68
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +43 -42
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +44 -60
- package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +50 -72
- package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +1 -13
- package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +1 -140
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +72 -101
- package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +32 -35
- package/yida-skills/skills/yida-canvas-custom-page/scripts/build-canvas-theme.js +3 -29
- package/yida-skills/skills/yida-canvas-custom-page/scripts/canvas-theme-provider.template.jsx +6 -69
- package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +0 -26
- package/yida-skills/skills/yida-create-app/SKILL.md +0 -2
- package/yida-skills/skills/yida-create-form-page/SKILL.md +58 -72
- package/yida-skills/skills/yida-create-form-page/references/advanced-form-modes.md +5 -5
- package/yida-skills/skills/yida-create-form-page/references/association-form-field.md +4 -4
- package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +5 -6
- package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +19 -7
- package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +44 -62
- package/yida-skills/skills/yida-create-page/SKILL.md +6 -14
- package/yida-skills/skills/yida-create-process/SKILL.md +2 -13
- package/yida-skills/skills/yida-custom-page/SKILL.md +0 -2
- package/yida-skills/skills/yida-custom-page/references/design-system.md +1 -1
- package/yida-skills/skills/yida-dashboard/SKILL.md +4 -5
- package/yida-skills/skills/yida-data-management/SKILL.md +2 -4
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +4 -4
- package/yida-skills/skills/yida-design/SKILL.md +14 -29
- package/yida-skills/skills/yida-design/references/app/blueprint.md +70 -0
- package/yida-skills/skills/yida-design/references/app/navigation-patterns.md +43 -0
- package/yida-skills/skills/yida-design/references/app/role-journey.md +30 -0
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +42 -67
- package/yida-skills/skills/yida-design/references/asset-workflow.md +2 -6
- package/yida-skills/skills/yida-design/references/design-mode.md +4 -4
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +38 -26
- package/yida-skills/skills/yida-design/references/style-design-selection.md +137 -0
- package/yida-skills/skills/yida-design/references/style-designs/_design-md-template.md +185 -0
- package/yida-skills/skills/yida-design/references/style-designs/aqua-service-progress-dashboard.md +242 -0
- package/yida-skills/skills/yida-design/references/style-designs/blue-insight-operations-dashboard.md +243 -0
- package/yida-skills/skills/yida-design/references/style-designs/blue-productivity-insight-workbench.md +242 -0
- package/yida-skills/skills/yida-design/references/style-designs/command-filter-card-console.md +304 -0
- package/yida-skills/skills/yida-design/references/style-designs/contrast-command-analytics-workbench.md +261 -0
- package/yida-skills/skills/yida-design/references/style-designs/dark-stage-analytic-dashboard.md +369 -0
- package/yida-skills/skills/yida-design/references/style-designs/filterable-card-catalog.md +354 -0
- package/yida-skills/skills/yida-design/references/style-designs/green-timeline-progress-workbench.md +250 -0
- package/yida-skills/skills/yida-design/references/style-designs/registry.md +117 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-analytic-workbench.md +348 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-blue-grid-analytic-dashboard.md +658 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-bordered-analytic-workbench.md +385 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-curated-filter-gallery.md +300 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-modular-analytic-workbench.md +404 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-progress-analytics-workbench.md +382 -0
- package/yida-skills/skills/yida-design/references/style-designs/soft-timeline-analytics-workbench.md +367 -0
- package/yida-skills/skills/yida-design/references/style-designs/teal-rail-analytics-workbench.md +341 -0
- package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +432 -350
- package/yida-skills/skills/yida-design/references/visual-decision-engine.md +143 -19
- package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +27 -15
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +0 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +8 -22
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/README.md +10 -14
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +4 -9
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +27 -49
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +154 -33
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +127 -48
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +51 -37
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +48 -43
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +190 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +98 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +339 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +348 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +344 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +345 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +355 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +377 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +352 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +389 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +364 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/index.json +329 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +348 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +345 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +379 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +347 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +352 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +386 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +384 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +388 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +341 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +361 -0
- package/yida-skills/skills/yida-design/workflow/output-design.md +375 -117
- package/yida-skills/skills/yida-design/workflow/output-prd.md +185 -0
- package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +29 -33
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +1 -3
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +106 -22
- package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +22 -15
- package/yida-skills/skills/yida-form-permission/SKILL.md +0 -6
- package/yida-skills/skills/yida-image-assets/SKILL.md +50 -73
- package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +52 -82
- package/yida-skills/skills/yida-image-assets/references/source-policy.md +15 -14
- package/yida-skills/skills/yida-integration/SKILL.md +0 -1
- package/yida-skills/skills/yida-login/SKILL.md +0 -2
- package/yida-skills/skills/yida-nav-group/SKILL.md +2 -4
- package/yida-skills/skills/yida-nav-shell/SKILL.md +12 -36
- package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +136 -79
- package/yida-skills/skills/yida-page-config/SKILL.md +4 -13
- package/yida-skills/skills/yida-prd/SKILL.md +3 -5
- package/yida-skills/skills/yida-prd/references/app/blueprint.md +4 -4
- package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +3 -6
- package/yida-skills/skills/yida-prd/references/app/role-journey.md +3 -5
- package/yida-skills/skills/yida-prd/references/plan-content.md +0 -4
- package/yida-skills/skills/yida-prd/workflow/output-prd.md +11 -18
- package/yida-skills/skills/yida-prd/workflow/plan-business.md +4 -4
- package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +8 -11
- package/yida-skills/skills/yida-publish-page/SKILL.md +1 -9
- package/yida-skills/skills/yida-requirement-analysis/SKILL.md +39 -10
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +71 -40
- package/yida-skills/skills-index.json +16 -36
- package/lib/app/application-entry-urls.js +0 -31
- package/lib/app/application-style.js +0 -69
- package/lib/app/canvas-icon-exports.json +0 -6160
- package/lib/app/canvas-icon-guard.js +0 -87
- package/lib/app/canvas-navigation-guard.js +0 -84
- package/lib/app/canvas-path-guard.js +0 -78
- package/lib/app/canvas-theme-guard.js +0 -184
- package/lib/app/form-data-response-guard.js +0 -57
- package/lib/asset/asset-execution.js +0 -79
- package/lib/core/css-structure.js +0 -37
- package/lib/design/check-design.js +0 -51
- package/lib/design/document.js +0 -458
- package/lib/design/icon-contrast.js +0 -85
- package/lib/design/page-content.js +0 -11
- package/lib/design-plan/artifact-updates.js +0 -117
- package/lib/design-plan/confirmation.js +0 -13
- package/lib/design-plan/entry-navigation.js +0 -97
- package/lib/design-plan/navigation-policy.js +0 -38
- package/lib/design-plan/rebase.js +0 -60
- package/lib/design-plan/themes.js +0 -101
- package/lib/design-plan/visual-policy.js +0 -42
- package/lib/samples/openyida-scaffold/canvas-admin-entry.jsx +0 -62
- package/lib/samples/openyida-scaffold/canvas-navigation.jsx +0 -56
- package/lib/samples/openyida-scaffold/canvas-view-state.jsx +0 -74
- package/yida-skills/skills/yida-app/references/entry-navigation.md +0 -193
- package/yida-skills/skills/yida-canvas-custom-page/references/view-state-recovery.md +0 -52
- package/yida-skills/skills/yida-canvas-data-binding/references/business-action-permissions.md +0 -31
- package/yida-skills/skills/yida-design/references/application-style-library.md +0 -93
- package/yida-skills/skills/yida-design/references/application-theme-consistency.md +0 -150
- package/yida-skills/skills/yida-design/references/native-form-styles.md +0 -208
- package/yida-skills/skills/yida-design/references/navigation-decision.md +0 -53
- package/yida-skills/skills/yida-design/references/page-continuity.md +0 -42
- package/yida-skills/skills/yida-design/references/theme/application-style-recipes.css +0 -58
- package/yida-skills/skills/yida-design/references/theme-selection.md +0 -67
- package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +0 -302
- package/yida-skills/skills/yida-design/templates/application-styles.json +0 -398
- package/yida-skills/skills/yida-design/templates/design-themes/README.md +0 -73
- package/yida-skills/skills/yida-design/templates/design-themes/app-amber/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-amber/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-amber/form-layout.json +0 -15
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +0 -754
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +0 -279
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +0 -758
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +0 -281
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +0 -754
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +0 -279
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +0 -752
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +0 -278
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +0 -748
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +0 -274
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/form-layout.json +0 -14
- package/yida-skills/skills/yida-design/templates/design-themes/basic-tokens.json +0 -118
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +0 -690
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +0 -382
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +0 -691
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +0 -380
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/app_theme.css +0 -696
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/design.md +0 -378
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +0 -689
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +0 -238
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/app_theme.css +0 -692
- package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/design.md +0 -375
- package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/app_theme.css +0 -691
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/design.md +0 -362
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +0 -691
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +0 -409
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/index.json +0 -464
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +0 -701
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +0 -379
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +0 -691
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +0 -404
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +0 -698
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +0 -389
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +0 -691
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +0 -372
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +0 -690
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +0 -455
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +0 -698
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +0 -394
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/app_theme.css +0 -692
- package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/design.md +0 -391
- package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +0 -711
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +0 -408
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +0 -692
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +0 -383
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/form-layout.json +0 -13
- package/yida-skills/skills/yida-design/templates/navigation-styles.json +0 -769
- package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +0 -79
- package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +0 -68
|
@@ -4,24 +4,25 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { spawnSync } = require('child_process');
|
|
6
6
|
const { CliError } = require('../core/cli-error');
|
|
7
|
-
const { t } = require('../core/i18n');
|
|
8
7
|
const { normalizePlan, assertThemeProfile } = require('./normalize');
|
|
9
8
|
const { readJson, writeFiles } = require('./files');
|
|
10
|
-
const { prepareUpdates } = require('./artifact-updates');
|
|
11
9
|
const { validateAuthoring } = require('./validate');
|
|
12
|
-
const { buildEntryNavigation, ENTRY_MODE_SUMMARIES } = require('./entry-navigation');
|
|
13
|
-
const { buildPageNavigationPolicy } = require('./navigation-policy');
|
|
14
|
-
const { buildAssetTasks } = require('../asset/asset-plan');
|
|
15
10
|
const { mergeParts } = require('./parallel');
|
|
16
11
|
const { performance } = require('perf_hooks');
|
|
17
12
|
const { readDesignTokens, applyDesignTokens } = require('../app/theme-from-design');
|
|
18
|
-
const { PLAN_SKILL_ROOT, loadThemeIndex, themeTemplatePath, themeNavTheme, resolveThemeColors, resolveColorToken } = require('./themes');
|
|
19
|
-
const { parseDesignDocument, serializeDesignDocument, validateDesignDocument } = require('../design/document');
|
|
20
13
|
|
|
21
14
|
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
15
|
+
const PLAN_SKILL_ROOT = path.join(
|
|
16
|
+
PACKAGE_ROOT,
|
|
17
|
+
'yida-skills',
|
|
18
|
+
'skills',
|
|
19
|
+
'yida-design',
|
|
20
|
+
'sub_skill',
|
|
21
|
+
'yida-design-plan'
|
|
22
|
+
);
|
|
23
|
+
const THEME_DIR = path.join(PLAN_SKILL_ROOT, 'templates', 'design-themes');
|
|
24
|
+
const THEME_INDEX_PATH = path.join(THEME_DIR, 'index.json');
|
|
22
25
|
const HTML_RENDERER_PATH = path.join(PLAN_SKILL_ROOT, 'scripts', 'render_build_plan.py');
|
|
23
|
-
const PAGE_CONTINUITY_PATH = path.join(PACKAGE_ROOT, 'yida-skills', 'skills', 'yida-design', 'references', 'page-continuity.md');
|
|
24
|
-
const THEME_CONSISTENCY_PATH = path.join(PACKAGE_ROOT, 'yida-skills', 'skills', 'yida-design', 'references', 'application-theme-consistency.md');
|
|
25
26
|
|
|
26
27
|
function text(value, fallback = '-') {
|
|
27
28
|
if (value === undefined || value === null || value === '') {
|
|
@@ -65,10 +66,11 @@ function visualSelections(plan) {
|
|
|
65
66
|
description: forUser.styleSummary || '保持清晰、稳定并适合持续业务操作。',
|
|
66
67
|
source: forUser.styleSource || selectedTheme.source || 'legacy_default',
|
|
67
68
|
};
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
const navigationStyle = forUser.navigationStyle || {
|
|
70
|
+
structure: 'side',
|
|
71
|
+
tone: 'light',
|
|
72
|
+
source: 'legacy_default',
|
|
73
|
+
selectionReason: '兼容旧版搭建计划;新计划必须显式记录导航选择。',
|
|
72
74
|
};
|
|
73
75
|
return { visual, forUser, selectedTheme, visualDirection, navigationStyle };
|
|
74
76
|
}
|
|
@@ -77,15 +79,6 @@ function navigationToneLabel(value) {
|
|
|
77
79
|
return value === 'dark' ? '深色' : '浅色';
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
function resolveContentTone(theme, tokens) {
|
|
81
|
-
if (['light', 'dark'].includes(theme.contentTone)) {return theme.contentTone;}
|
|
82
|
-
requireFact(theme.mode === 'creative', `主题 ${theme.themeId} 缺少 contentTone`);
|
|
83
|
-
const pageColor = resolveColorToken(tokens, '--pod-page-bg-color');
|
|
84
|
-
requireFact(/^#[\dA-F]{6}$/i.test(pageColor || ''), '自由创意必须提供可判断界面明暗的 --pod-page-bg-color');
|
|
85
|
-
const rgb = [1, 3, 5].map(index => parseInt(pageColor.slice(index, index + 2), 16));
|
|
86
|
-
return rgb[0] * 0.2126 + rgb[1] * 0.7152 + rgb[2] * 0.0722 < 128 ? 'dark' : 'light';
|
|
87
|
-
}
|
|
88
|
-
|
|
89
82
|
function requireFact(condition, message) {
|
|
90
83
|
if (!condition) {throw new CliError(message, { code: 'DESIGN_PLAN_INCOMPLETE_CONTRACT' });}
|
|
91
84
|
}
|
|
@@ -114,7 +107,7 @@ function validateBusiness(plan, execution) {
|
|
|
114
107
|
}
|
|
115
108
|
requireFact(object(execution), 'execution 必须是对象');
|
|
116
109
|
const arrays = ['resourceBlueprint', 'resourceCreationOrder', 'pageImplementationOrder', 'navigationOrder', 'sampleDataPlan', 'acceptanceCriteria'];
|
|
117
|
-
const allowed = [...arrays, 'navigationFallback', 'interactionStates', 'explicitScope', 'appConfig'
|
|
110
|
+
const allowed = [...arrays, 'navigationFallback', 'interactionStates', 'explicitScope', 'appConfig'];
|
|
118
111
|
for (const key of Object.keys(execution)) {
|
|
119
112
|
requireFact(allowed.includes(key), `未知 execution 字段:${key}`);
|
|
120
113
|
if (arrays.includes(key)) {requireFact(Array.isArray(execution[key]), `execution.${key} 必须是数组`);}
|
|
@@ -136,9 +129,6 @@ function validateBusiness(plan, execution) {
|
|
|
136
129
|
for (const key of ['resourceBlueprint', 'resourceCreationOrder', 'acceptanceCriteria']) {
|
|
137
130
|
if (execution[key] !== undefined) {requireFact(nonEmptyList(execution[key]), `${key} 不能为空`);}
|
|
138
131
|
}
|
|
139
|
-
if (execution.acceptanceCriteria !== undefined) {
|
|
140
|
-
requireFact(execution.acceptanceCriteria.every(nonEmpty), 'acceptanceCriteria 必须使用非空业务验收说明');
|
|
141
|
-
}
|
|
142
132
|
if (execution.navigationFallback !== undefined) {requireFact(nonEmpty(execution.navigationFallback), 'navigationFallback 不能为空');}
|
|
143
133
|
}
|
|
144
134
|
|
|
@@ -197,35 +187,23 @@ function navigationConfig(plan) {
|
|
|
197
187
|
return { navigationType, hideAppNav, layoutDirection };
|
|
198
188
|
}
|
|
199
189
|
|
|
200
|
-
function sceneForPage(page) {
|
|
201
|
-
const patternScenes = {
|
|
202
|
-
'data-insight': 'dashboard', 'catalog-browse': 'list', 'brand-landing': 'landing',
|
|
203
|
-
'progress-narrative': 'detail', 'split-pane-ops': 'detail',
|
|
204
|
-
};
|
|
205
|
-
return page.pageSpecHandoff?.scene || page.scene || patternScenes[(page.layoutPattern || {}).id] || 'workbench';
|
|
206
|
-
}
|
|
207
|
-
|
|
208
190
|
function pageHandoff(page, plan) {
|
|
209
191
|
const supplied = page.pageSpecHandoff === undefined ? {} : page.pageSpecHandoff;
|
|
210
192
|
requireFact(object(supplied), `页面 ${page.name} 的 pageSpecHandoff 必须是对象`);
|
|
211
|
-
const handoffKeys = ['scene', 'pageStructure', 'entryMode', '
|
|
193
|
+
const handoffKeys = ['scene', 'pageStructure', 'entryMode', 'contentBlocks', 'dataSources', 'dataBinding', 'emptyReason', 'primaryAction', 'themeSummary', 'designFile', 'designRefs'];
|
|
212
194
|
requireFact(Object.keys(supplied).every(key => handoffKeys.includes(key)), `页面 ${page.name} 的 pageSpecHandoff 包含未知字段`);
|
|
213
|
-
const
|
|
195
|
+
const patternScenes = {
|
|
196
|
+
'data-insight': 'dashboard', 'catalog-browse': 'list', 'brand-landing': 'landing',
|
|
197
|
+
'progress-narrative': 'detail', 'split-pane-ops': 'detail',
|
|
198
|
+
};
|
|
199
|
+
const scene = supplied.scene || page.scene || patternScenes[(page.layoutPattern || {}).id] || 'workbench';
|
|
214
200
|
const structures = {
|
|
215
201
|
workbench: 'workbench', dashboard: 'dashboard-overview', list: 'business-list',
|
|
216
202
|
detail: 'detail-profile', landing: 'official-homepage', screen: 'data-screen',
|
|
217
203
|
};
|
|
218
204
|
const customNavigation = navigationConfig(plan).navigationType === 'custom';
|
|
219
|
-
const entryMode = supplied.entryMode || page.entryMode || (customNavigation
|
|
205
|
+
const entryMode = supplied.entryMode || page.entryMode || (customNavigation ? 'standalone' : 'platform-shell');
|
|
220
206
|
requireFact(!customNavigation || entryMode === 'standalone', `自定义导航的页面 ${page.name} 必须使用 standalone 入口`);
|
|
221
|
-
if (supplied.navigation !== undefined) {
|
|
222
|
-
const nav = supplied.navigation;
|
|
223
|
-
requireFact(object(nav) && Object.keys(nav).every(key => ['type', 'variant', 'reason'].includes(key)), `页面 ${page.name} 的 navigation 必须只描述页面菜单`);
|
|
224
|
-
requireFact(['custom', 'none'].includes(nav.type), `页面 ${page.name} 的 navigation.type 必须是 custom 或 none`);
|
|
225
|
-
requireFact(entryMode === 'standalone', `页面 ${page.name} 的独立菜单必须使用 standalone 入口,不能叠加平台导航`);
|
|
226
|
-
requireFact(nav.type === 'custom' ? ['top', 'side', 'mixed', 'dock'].includes(nav.variant) : nav.variant === undefined, `页面 ${page.name} 的 navigation.variant 与菜单类型不匹配`);
|
|
227
|
-
requireFact(nonEmpty(nav.reason), `页面 ${page.name} 缺少导航依据`);
|
|
228
|
-
}
|
|
229
207
|
if (!structures[scene] || !['platform-shell', 'standalone'].includes(entryMode)) {
|
|
230
208
|
throw new CliError(`页面 ${page.name} 的 scene 或 entryMode 无效`, { code: 'DESIGN_PLAN_INVALID_PAGE_HANDOFF' });
|
|
231
209
|
}
|
|
@@ -246,9 +224,7 @@ function pageHandoff(page, plan) {
|
|
|
246
224
|
}
|
|
247
225
|
requireFact(nonEmpty(page.name) && nonEmpty(page.primaryTask) && nonEmpty(page.permissionSummary), '页面缺少名称、核心任务或权限说明');
|
|
248
226
|
requireFact(nonEmpty(supplied.primaryAction || page.primaryTask), `页面 ${page.name} 缺少主操作`);
|
|
249
|
-
|
|
250
|
-
requireFact(nonEmptyList(contentBlocks) && contentBlocks.every(nonEmpty), `页面 ${page.name} 的内容区块需使用非空文本,或完整的 name/purpose 结构化输入`);
|
|
251
|
-
requireFact(!Array.isArray(page.blocks) || page.blocks.every(nonEmpty), `页面 ${page.name} 的 blocks 包含未完成的区块说明`);
|
|
227
|
+
requireFact(nonEmptyList(supplied.contentBlocks || page.blocks), `页面 ${page.name} 缺少内容区块`);
|
|
252
228
|
const sceneKey = page.sceneKey || scene;
|
|
253
229
|
if (!/^[\p{L}\p{N}_-]+$/u.test(sceneKey)) {
|
|
254
230
|
throw new CliError(`页面 ${page.name} 的 sceneKey 无效`, { code: 'DESIGN_PLAN_INVALID_PAGE_HANDOFF' });
|
|
@@ -272,24 +248,7 @@ function pageHandoff(page, plan) {
|
|
|
272
248
|
};
|
|
273
249
|
}
|
|
274
250
|
|
|
275
|
-
function
|
|
276
|
-
// Draft previews can display navigation before business details are complete.
|
|
277
|
-
const handoff = page.pageSpecHandoff || {};
|
|
278
|
-
const applicationCustom = navigationConfig(plan).navigationType === 'custom';
|
|
279
|
-
const menu = handoff.navigation;
|
|
280
|
-
const entryMode = handoff.entryMode || page.entryMode || (applicationCustom || menu ? 'standalone' : 'platform-shell');
|
|
281
|
-
const variants = { top: '顶部', side: '侧边', mixed: '顶部与侧边', dock: '底部' };
|
|
282
|
-
return [
|
|
283
|
-
['访问方式', entryMode === 'standalone' ? '独立页面入口' : '应用工作区入口'],
|
|
284
|
-
['页面菜单', menu ? (menu.type === 'custom' ? `自定义${variants[menu.variant] || ''}菜单` : '不设菜单')
|
|
285
|
-
: applicationCustom ? '沿用应用自定义菜单' : entryMode === 'standalone' ? '未规划应用菜单,仅实现业务内容' : '沿用平台导航'],
|
|
286
|
-
['页面实现边界', entryMode === 'platform-shell' ? '只实现业务内容;同任务分类用页内 Tab,跨模块切换交给平台菜单,不重复自绘管理导航' : '按独立入口方案组织内容,不嵌套平台导航'],
|
|
287
|
-
['导航影响范围', applicationCustom ? '应用采用自定义导航' : entryMode === 'standalone' ? '仅当前入口,应用工作区保留平台导航' : '保留平台导航'],
|
|
288
|
-
...(menu ? [['选择依据', menu.reason]] : []),
|
|
289
|
-
];
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function buildExecution(plan, options = {}) {
|
|
251
|
+
function buildExecution(plan) {
|
|
293
252
|
const pages = (plan.pages || {}).customPageDetails || [];
|
|
294
253
|
const models = plan.dataModels || [];
|
|
295
254
|
const { navigationStyle } = visualSelections(plan);
|
|
@@ -305,31 +264,13 @@ function buildExecution(plan, options = {}) {
|
|
|
305
264
|
}
|
|
306
265
|
const interactionStates = {
|
|
307
266
|
empty: '说明当前无数据并提供登记或返回入口', loading: '保留上下文并显示加载状态', error: '显示原因和重试入口',
|
|
308
|
-
formEntry: '
|
|
267
|
+
formEntry: 'PC 使用 50vw 抽屉承载原生表单,移动端整页打开;提交后刷新数据',
|
|
309
268
|
detail: '使用真实 formInstId 打开详情;缺少 ID 时禁用入口',
|
|
310
269
|
};
|
|
311
270
|
const resources = [
|
|
312
271
|
...models.map(model => ({ name: model.name, type: String(model.formType).includes('流程') ? 'process-form' : 'normal-form', purpose: model.description })),
|
|
313
272
|
...pages.map(page => ({ name: page.name, type: 'display-page', pageId: page.pageId, purpose: page.primaryTask })),
|
|
314
273
|
];
|
|
315
|
-
const pageHandoffs = pages.map(page => {
|
|
316
|
-
const spec = pageHandoff(page, plan);
|
|
317
|
-
if (options.designFile) {spec.designFile = options.designFile;}
|
|
318
|
-
return { pageId: page.pageId, name: page.name, sceneKey: page.sceneKey || spec.scene, pageSpecHandoff: spec,
|
|
319
|
-
navigationPolicy: buildPageNavigationPolicy(spec, navigation.navigationType),
|
|
320
|
-
};
|
|
321
|
-
});
|
|
322
|
-
const defaultAcceptanceCriteria = [
|
|
323
|
-
...models.map(model => `${model.name}的字段、必填规则与关系符合数据模型`),
|
|
324
|
-
...(plan.businessFlows || []).map(flow => `${flow.name}按触发条件和业务规则执行`),
|
|
325
|
-
...pages.map(page => `${page.name}支持${page.primaryTask || page.positioning}`),
|
|
326
|
-
...pageHandoffs.filter(page => page.navigationPolicy.applicationMenuOwner === 'platform')
|
|
327
|
-
.map(page => `${page.name}只实现业务内容,同任务分类可用页内 Tab,跨模块使用平台菜单;实际管理入口无重复导航`),
|
|
328
|
-
...(!resourceOnly ? [
|
|
329
|
-
'应用主题按 design.md 配置,页面消费同一组 token',
|
|
330
|
-
'核心普通表单示例数据写入并抽查,或说明跳过原因',
|
|
331
|
-
] : []),
|
|
332
|
-
];
|
|
333
274
|
const handoff = {
|
|
334
275
|
resourceBlueprint: resources,
|
|
335
276
|
resourceCreationOrder: resourceOnly
|
|
@@ -337,13 +278,21 @@ function buildExecution(plan, options = {}) {
|
|
|
337
278
|
: ['应用与主题配置', ...models.map(model => model.name), '初始示例数据', ...pages.map(page => page.name), '发布与导航排序'],
|
|
338
279
|
pageImplementationOrder: pages.map(page => page.pageId || page.name),
|
|
339
280
|
navigationOrder: [],
|
|
340
|
-
navigationFallback: resourceOnly ? '本轮显式窄范围不执行导航排序' : '
|
|
281
|
+
navigationFallback: resourceOnly ? '本轮显式窄范围不执行导航排序' : '发布时使用 --auto-nav-order,不再重复执行排序',
|
|
282
|
+
acceptanceCriteria: [
|
|
283
|
+
...models.map(model => `${model.name}的字段、必填规则与关系符合数据模型`),
|
|
284
|
+
...(plan.businessFlows || []).map(flow => `${flow.name}按触发条件和业务规则执行`),
|
|
285
|
+
...pages.map(page => `${page.name}支持${page.primaryTask || page.positioning}`),
|
|
286
|
+
...(!resourceOnly ? [
|
|
287
|
+
'应用主题按 design.md 配置,页面消费同一组 token',
|
|
288
|
+
'核心普通表单示例数据写入并抽查,或说明跳过原因',
|
|
289
|
+
] : []),
|
|
290
|
+
],
|
|
341
291
|
...execution,
|
|
342
|
-
acceptanceCriteria: [...new Set([...defaultAcceptanceCriteria, ...(execution.acceptanceCriteria || [])])],
|
|
343
292
|
interactionStates: { ...interactionStates, ...execution.interactionStates },
|
|
344
293
|
sampleDataPlan: sampleDataPlan(models, execution.sampleDataPlan === undefined ? undefined : JSON.parse(JSON.stringify(execution.sampleDataPlan))),
|
|
345
294
|
appConfig: { appType: '待创建后回填', corpId: '待登录态确认', baseUrl: '待目标环境确认', ...execution.appConfig, ...visualConfig, ...navigation },
|
|
346
|
-
pages:
|
|
295
|
+
pages: pages.map(page => ({ pageId: page.pageId, name: page.name, pageSpecHandoff: pageHandoff(page, plan) })),
|
|
347
296
|
};
|
|
348
297
|
requireFact(new Set(pages.map(page => page.pageId)).size === pages.length && pages.every(page => nonEmpty(page.pageId)), '页面 pageId 缺失或重复');
|
|
349
298
|
const resourcesByName = new Map(resources.map(resource => [resource.name, resource]));
|
|
@@ -361,25 +310,6 @@ function buildExecution(plan, options = {}) {
|
|
|
361
310
|
}
|
|
362
311
|
}
|
|
363
312
|
requireFact(resources.every(resource => blueprintNames.has(resource.name)), '资源蓝图遗漏业务表单或页面');
|
|
364
|
-
const entryNavigation = buildEntryNavigation(plan, handoff.resourceBlueprint, handoff.pages);
|
|
365
|
-
if (entryNavigation) {
|
|
366
|
-
handoff.entryRecommendation = entryNavigation.recommendation;
|
|
367
|
-
handoff.entryModeSummary = ENTRY_MODE_SUMMARIES[entryNavigation.recommendation.mode];
|
|
368
|
-
const skipOrder = resourceOnly || navigation.navigationType === 'custom' || entryNavigation.recommendation.mode === 'frontend-only';
|
|
369
|
-
if (execution.navigationOrder !== undefined) {
|
|
370
|
-
requireFact(JSON.stringify(execution.navigationOrder) === JSON.stringify(skipOrder ? [] : entryNavigation.platformOrder), 'navigationOrder 与入口管理菜单冲突,请修改入口菜单这一份源事实');
|
|
371
|
-
}
|
|
372
|
-
handoff.navigationOrder = skipOrder ? [] : entryNavigation.platformOrder;
|
|
373
|
-
handoff.navigationFallback = skipOrder
|
|
374
|
-
? (resourceOnly ? '当前窄范围保留无关入口顺序' : '平台排序命令不适用;按各入口 menu 顺序与 defaultMenuKey 实现自定义导航并验证首页')
|
|
375
|
-
: '按业务管理入口的默认任务和菜单顺序排序平台导航;独立前台按自身 menu 与 defaultMenuKey 落实顺序和首页';
|
|
376
|
-
if (!resourceOnly) {
|
|
377
|
-
handoff.acceptanceCriteria.push(entryNavigation.recommendation.mode === 'service-management'
|
|
378
|
-
? '分别验证访客、业务管理者及双身份用户的入口、视图、数据范围和操作权限;导航预览不代替真实身份验证'
|
|
379
|
-
: '按当前入口的实际访问者验证默认任务、视图、数据范围和操作权限;不追加范围外角色或首页');
|
|
380
|
-
handoff.acceptanceCriteria.push('逐入口核对菜单及分组顺序、默认页面和首屏任务;自定义导航不因跳过平台排序命令而免验,排序回读不替代实际入口验证');
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
313
|
handoff.pageNavigation = [...resources, ...handoff.resourceBlueprint.filter(resource => resource.type === 'report')]
|
|
384
314
|
.filter(resource => navigation.navigationType === 'custom' || handoff.pages.some(page => page.name === resource.name && page.pageSpecHandoff.entryMode === 'standalone'))
|
|
385
315
|
.map(resource => ({ name: resource.name, type: resource.type, isRenderNav: false }));
|
|
@@ -389,24 +319,6 @@ function buildExecution(plan, options = {}) {
|
|
|
389
319
|
return handoff;
|
|
390
320
|
}
|
|
391
321
|
|
|
392
|
-
function renderEntryNavigation(recommendation) {
|
|
393
|
-
if (!Array.isArray(recommendation?.entries)) {return [];}
|
|
394
|
-
const lines = ['### 访问态入口与权限', ENTRY_MODE_SUMMARIES[recommendation.mode], '页面隐藏导航不授予业务权限。', ''];
|
|
395
|
-
for (const entry of recommendation.entries) {
|
|
396
|
-
const rows = [];
|
|
397
|
-
const visit = items => {
|
|
398
|
-
for (const item of items || []) {
|
|
399
|
-
if (item.children) {visit(item.children); continue;}
|
|
400
|
-
rows.push([item.label, item.resource, item.targetType, item.viewUuid || item.viewKey,
|
|
401
|
-
(item.access || []).map(access => `${access.resource}:${access.operation};${access.dataScope}`), item.key === entry.defaultMenuKey ? '默认入口' : '']);
|
|
402
|
-
}
|
|
403
|
-
};
|
|
404
|
-
visit(entry.menu);
|
|
405
|
-
lines.push(`#### ${entry.name}`, '', mdTable(['菜单', '资源', '用途', '视图', '权限依赖', '默认落点'], rows), '');
|
|
406
|
-
}
|
|
407
|
-
return lines;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
322
|
function renderPrd(plan, handoff = buildExecution(plan)) {
|
|
411
323
|
const overview = plan.overview || {};
|
|
412
324
|
const models = plan.dataModels || [];
|
|
@@ -435,7 +347,6 @@ function renderPrd(plan, handoff = buildExecution(plan)) {
|
|
|
435
347
|
['导航配色', handoff.appConfig.navTheme === 'dark' ? '深色' : '浅色'],
|
|
436
348
|
]), '',
|
|
437
349
|
'### 页面导航配置', '', mdTable(['页面', '平台页面导航'], handoff.pageNavigation.map(page => [page.name, page.isRenderNav ? '显示' : '隐藏'])), '',
|
|
438
|
-
...renderEntryNavigation(handoff.entryRecommendation || plan.execution?.entryRecommendation),
|
|
439
350
|
'## 3. 数据结构(业务语义,不含细节 ID)',
|
|
440
351
|
];
|
|
441
352
|
for (const model of models) {
|
|
@@ -453,7 +364,6 @@ function renderPrd(plan, handoff = buildExecution(plan)) {
|
|
|
453
364
|
['内容优先级', page.contentPriority], ['功能区块', page.blocks], ['首屏结构', page.firstScreenStructure],
|
|
454
365
|
['标志性交互', page.signatureInteraction], ['页面模式', page.layoutPattern], ['信息密度', page.density],
|
|
455
366
|
['内容丰富度', page.contentRichness], ['权限说明', page.permissionSummary],
|
|
456
|
-
...pageNavigationRows(page, plan),
|
|
457
367
|
...Object.entries(handoff.pages[index].pageSpecHandoff),
|
|
458
368
|
]));
|
|
459
369
|
});
|
|
@@ -485,332 +395,367 @@ function normalizeHex(value) {
|
|
|
485
395
|
return match ? `#${match[1].toUpperCase()}` : null;
|
|
486
396
|
}
|
|
487
397
|
|
|
398
|
+
function mixHex(hex, target, targetRatio) {
|
|
399
|
+
const source = [1, 3, 5].map(index => parseInt(hex.slice(index, index + 2), 16));
|
|
400
|
+
const mixed = source.map((channel, index) => Math.round(channel * (1 - targetRatio) + target[index] * targetRatio));
|
|
401
|
+
return `#${mixed.map(channel => channel.toString(16).padStart(2, '0')).join('').toUpperCase()}`;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function mixTwoHex(left, right, rightRatio) {
|
|
405
|
+
const target = [1, 3, 5].map(index => parseInt(right.slice(index, index + 2), 16));
|
|
406
|
+
return mixHex(left, target, rightRatio);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function hexToRgba(hex, alpha) {
|
|
410
|
+
const channels = [1, 3, 5].map(index => parseInt(hex.slice(index, index + 2), 16));
|
|
411
|
+
return `rgba(${channels.join(', ')}, ${alpha})`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function replaceTokenValue(source, token, value) {
|
|
415
|
+
const tokenPattern = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
416
|
+
return source.replace(new RegExp(`("${tokenPattern}"\\s*:\\s*)"[^"]*"`), `$1"${value}"`);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function deriveBrandColors(primaryColor, themeId) {
|
|
420
|
+
if (themeId === 'dark-luminous-modular') {
|
|
421
|
+
return {
|
|
422
|
+
'--color-brand1-1': mixHex(primaryColor, [255, 255, 255], 0.10),
|
|
423
|
+
'--color-brand1-2': mixTwoHex(primaryColor, '#181818', 0.76),
|
|
424
|
+
'--color-brand1-3': mixTwoHex(primaryColor, '#101010', 0.90),
|
|
425
|
+
'--color-brand1-5': mixHex(primaryColor, [0, 0, 0], 0.18),
|
|
426
|
+
'--color-brand1-9': mixHex(primaryColor, [0, 0, 0], 0.10),
|
|
427
|
+
'--color-brand1-10': mixTwoHex(primaryColor, '#181818', 0.58),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const defaults = {
|
|
431
|
+
brand1: 0.12,
|
|
432
|
+
brand2: 0.88,
|
|
433
|
+
brand3: 0.94,
|
|
434
|
+
brand5: 0.18,
|
|
435
|
+
brand9: 0.12,
|
|
436
|
+
brand10: 0.62,
|
|
437
|
+
};
|
|
438
|
+
const profiles = {
|
|
439
|
+
'airy-media-grid': { brand1: 0.14, brand3: 0.95, brand5: 0.16, brand9: 0.10, brand10: 0.64 },
|
|
440
|
+
'ambient-halo-layered': { brand1: 0.14, brand9: 0.10, brand10: 0.64 },
|
|
441
|
+
'hairline-runway-clarity': { brand1: 0.90, brand2: 0.82, brand3: 0.74, brand10: 0.70 },
|
|
442
|
+
'media-rail-inspector': { brand1: 0.90, brand2: 0.82, brand3: 0.74, brand10: 0.68 },
|
|
443
|
+
'mist-layered-signal': { brand1: 0.14, brand5: 0.16, brand9: 0.10, brand10: 0.64 },
|
|
444
|
+
'modular-rail-signal': { brand1: 0.14, brand9: 0.10, brand10: 0.64 },
|
|
445
|
+
'mono-grid-signal': { brand1: 0.90, brand2: 0.82, brand3: 0.74, brand10: 0.68 },
|
|
446
|
+
'ribbon-ledger-lift': { brand1: 0.90, brand2: 0.82, brand3: 0.74, brand5: 0.16, brand10: 0.68 },
|
|
447
|
+
'segmented-meter-clarity': { brand1: 0.90, brand2: 0.82, brand3: 0.74, brand10: 0.66 },
|
|
448
|
+
'status-framed-media-grid': { brand1: 0.08, brand9: 0.10, brand10: 0.72 },
|
|
449
|
+
};
|
|
450
|
+
const profile = { ...defaults, ...(profiles[themeId] || {}) };
|
|
451
|
+
return {
|
|
452
|
+
'--color-brand1-1': mixHex(primaryColor, [255, 255, 255], profile.brand1),
|
|
453
|
+
'--color-brand1-2': mixHex(primaryColor, [255, 255, 255], profile.brand2),
|
|
454
|
+
'--color-brand1-3': mixHex(primaryColor, [255, 255, 255], profile.brand3),
|
|
455
|
+
'--color-brand1-5': mixHex(primaryColor, [0, 0, 0], profile.brand5),
|
|
456
|
+
'--color-brand1-9': mixHex(primaryColor, [0, 0, 0], profile.brand9),
|
|
457
|
+
'--color-brand1-10': mixHex(primaryColor, [255, 255, 255], profile.brand10),
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function deriveThemeColors(primaryColor, themeId) {
|
|
462
|
+
const themes = {
|
|
463
|
+
'high-contrast-modular': {
|
|
464
|
+
'--oyd-surface-soft': mixTwoHex(primaryColor, '#F1F1F1', 0.95),
|
|
465
|
+
'--oyd-accent-deep': mixTwoHex(primaryColor, '#202020', 0.55),
|
|
466
|
+
},
|
|
467
|
+
'interlocked-vivid-modules': {
|
|
468
|
+
},
|
|
469
|
+
'media-rail-inspector': {
|
|
470
|
+
'--oyd-media-surface': mixTwoHex(primaryColor, '#F7F7F7', 0.96),
|
|
471
|
+
'--oyd-tag-surface': mixTwoHex(primaryColor, '#F2F2F2', 0.92),
|
|
472
|
+
'--oyd-action-deep': mixTwoHex(primaryColor, '#151515', 0.86),
|
|
473
|
+
},
|
|
474
|
+
'mono-grid-signal': {
|
|
475
|
+
'--oyd-pattern-surface': mixTwoHex(primaryColor, '#F3F3F3', 0.92),
|
|
476
|
+
},
|
|
477
|
+
'status-framed-media-grid': {
|
|
478
|
+
'--oyd-brand-focus-soft': mixTwoHex(primaryColor, '#FFFFFF', 0.92),
|
|
479
|
+
},
|
|
480
|
+
'dark-luminous-modular': {
|
|
481
|
+
'--oyd-brand-glow-soft': hexToRgba(primaryColor, 0.18),
|
|
482
|
+
'--oyd-brand-chart-strong': mixHex(primaryColor, [255, 255, 255], 0.06),
|
|
483
|
+
'--oyd-brand-chart-muted': hexToRgba(primaryColor, 0.46),
|
|
484
|
+
'--oyd-brand-chart-area': hexToRgba(primaryColor, 0.14),
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
return themes[themeId] || {};
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function deriveSurfacePalette(primaryColor, surfaceTone, template) {
|
|
491
|
+
if (!['theme', 'brand-tinted'].includes(surfaceTone)) {
|
|
492
|
+
throw new CliError('colorStrategy.surfaceTone 必须是 theme 或 brand-tinted', { code: 'DESIGN_PLAN_INVALID_SURFACE_TONE' });
|
|
493
|
+
}
|
|
494
|
+
const surface = normalizeHex(readDesignTokens(template)['--color-white']);
|
|
495
|
+
// 深色表面保持其既有明度层级,不能因为品牌配色而变成白卡。
|
|
496
|
+
const darkSurface = surface && [1, 3, 5].every(index => parseInt(surface.slice(index, index + 2), 16) < 128);
|
|
497
|
+
if (surfaceTone !== 'brand-tinted' || darkSurface) {return {};}
|
|
498
|
+
return {
|
|
499
|
+
// 品牌氛围属于 Shell;原生页面和 Canvas 共用页面底色,不受导航归属影响。
|
|
500
|
+
'--pod-shell-bg-color-light': mixHex(primaryColor, [255, 255, 255], 0.94),
|
|
501
|
+
'--pod-card-bg-color': 'var(--color-white, #fff)',
|
|
502
|
+
'--color-fill1-1': mixHex(primaryColor, [255, 255, 255], 0.97),
|
|
503
|
+
'--color-fill1-2': mixHex(primaryColor, [255, 255, 255], 0.91),
|
|
504
|
+
'--color-fill1-3': mixHex(primaryColor, [255, 255, 255], 0.84),
|
|
505
|
+
'--color-line1-1': mixHex(primaryColor, [255, 255, 255], 0.86),
|
|
506
|
+
'--color-line1-2': mixHex(primaryColor, [255, 255, 255], 0.76),
|
|
507
|
+
'--pod-card-border': '1px solid var(--color-line1-1)',
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
488
511
|
function resolveTheme(plan) {
|
|
489
512
|
const { selectedTheme: selected } = visualSelections(plan);
|
|
490
|
-
const index =
|
|
513
|
+
const index = readJson(THEME_INDEX_PATH, '主题索引');
|
|
491
514
|
const theme = (index.themes || []).find(item => item.themeId === selected.themeId);
|
|
492
|
-
if (!theme ||
|
|
515
|
+
if (!theme || theme.templatePath !== selected.templatePath) {
|
|
493
516
|
throw new CliError('selectedTheme 的 themeId 与 templatePath 不属于主题索引中的同一记录', {
|
|
494
517
|
code: 'DESIGN_PLAN_THEME_MISMATCH',
|
|
495
518
|
details: { selectedTheme: selected },
|
|
496
519
|
});
|
|
497
520
|
}
|
|
498
521
|
assertThemeProfile(plan.visualStyle?.forUser?.themeProfile, theme);
|
|
499
|
-
const templatePath =
|
|
522
|
+
const templatePath = path.resolve(PLAN_SKILL_ROOT, selected.templatePath);
|
|
523
|
+
if (!templatePath.startsWith(`${THEME_DIR}${path.sep}`) || !fs.existsSync(templatePath)) {
|
|
524
|
+
throw new CliError(`主题模板不可读:${selected.templatePath}`, {
|
|
525
|
+
code: 'DESIGN_PLAN_THEME_UNREADABLE',
|
|
526
|
+
});
|
|
527
|
+
}
|
|
500
528
|
return { theme, templatePath };
|
|
501
529
|
}
|
|
502
530
|
|
|
503
|
-
function
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
function renderProjectFacts(plan, visualDirection, color) {
|
|
531
|
+
function renderProjectVisualSection(visualDirection, color, navigationStyle, navigation) {
|
|
532
|
+
const navigationToken = navigationStyle.tone === 'dark'
|
|
533
|
+
? '--color-brand1-5'
|
|
534
|
+
: '--color-brand1-3';
|
|
508
535
|
return [
|
|
509
|
-
'
|
|
536
|
+
'## 项目视觉选择',
|
|
537
|
+
'',
|
|
510
538
|
`- 视觉方向:${text(visualDirection.label)}`,
|
|
511
539
|
`- 方向说明:${text(visualDirection.description)}`,
|
|
512
540
|
`- 主题色:${text(color.primaryColorName)} \`${text(color.primaryColor)}\``,
|
|
513
|
-
`-
|
|
514
|
-
`-
|
|
515
|
-
`-
|
|
516
|
-
`-
|
|
541
|
+
`- 导航类型:${NAVIGATION_TYPES[navigation.navigationType].label}`,
|
|
542
|
+
`- 导航明暗:${navigationToneLabel(navigationStyle.tone)}`,
|
|
543
|
+
`- 导航背景:\`${navigationToken}\``,
|
|
544
|
+
`- 选择依据:${text(navigationStyle.selectionReason || visualDirection.description)}`,
|
|
517
545
|
].join('\n');
|
|
518
546
|
}
|
|
519
547
|
|
|
520
|
-
function
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const reason = navigationStyle.toneSource === 'theme_derived'
|
|
544
|
-
? String(navigationStyle.selectionReason || '').split(/[,,;;。]/)
|
|
545
|
-
.filter(clause => !/(?:深色|浅色|暗色|亮色|黑色|白色).*(?:导航|菜单|侧栏)|(?:导航|菜单|侧栏).*(?:深色|浅色|暗色|亮色|黑色|白色)|(?:dark|light).*(?:nav|menu|sidebar)|(?:nav|menu|sidebar).*(?:dark|light)/i.test(clause))
|
|
546
|
-
.filter(clause => clause.trim()).join(';')
|
|
547
|
-
: navigationStyle.selectionReason;
|
|
548
|
-
return [
|
|
549
|
-
'#### 本项目导航与应用框架', '',
|
|
550
|
-
`- 导航类型:${NAVIGATION_TYPES[navigationConfig(plan).navigationType].label}`,
|
|
551
|
-
`- 导航明暗:${navigationToneLabel(navigationStyle.tone)}`,
|
|
552
|
-
'- 导航背景:`--pod-shell-theme-bg-color`(最终值见 tokens)',
|
|
553
|
-
`- 布局依据:${text(reason, '按业务入口、模块层级和切换频率安排菜单')}`,
|
|
554
|
-
`- 明暗依据:${navigationStyle.toneSource === 'theme_derived' ? '沿用已选应用风格的导航明暗' : '采用本项目明确设计的导航明暗'},当前为${navigationToneLabel(navigationStyle.tone)}。`, '',
|
|
555
|
-
'- 导航、表单、自定义页面和记录详情共用本项目设计;完整导航状态与尺寸见 tokens.application-global.appearance.navigation。', '',
|
|
556
|
-
mdTable(['角色', '变量', '本项目取值'], Object.entries(roles).filter(([name]) => tokens[name]).map(([name, role]) => [role, name, tokens[name]])), '',
|
|
557
|
-
...(plan.pages?.customPageDetails || []).map(page => [
|
|
558
|
-
`#### ${text(page.name)}的导航`, '', mdTable(['项目', '方案'], pageNavigationRows(page, plan)),
|
|
559
|
-
].join('\n')),
|
|
560
|
-
].join('\n');
|
|
548
|
+
function injectAfterFrontmatter(source, content) {
|
|
549
|
+
const frontmatter = /^---\n[\s\S]*?\n---\n/;
|
|
550
|
+
if (!frontmatter.test(source)) {
|
|
551
|
+
return `${content}\n\n${source}`;
|
|
552
|
+
}
|
|
553
|
+
return source.replace(frontmatter, match => `${match}\n${content}\n\n`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function renderPagePatterns(pages) {
|
|
557
|
+
return (pages.customPageDetails || []).map(page => {
|
|
558
|
+
const pattern = page.layoutPattern || {};
|
|
559
|
+
const richness = page.contentRichness || {};
|
|
560
|
+
return [
|
|
561
|
+
`#### ${text(page.name)}`,
|
|
562
|
+
'',
|
|
563
|
+
`- 模式:${text(pattern.mode)} / ${text(pattern.id)}`,
|
|
564
|
+
`- 选择原因:${text(pattern.reason)}`,
|
|
565
|
+
`- 调整项:${text(pattern.adaptations)}`,
|
|
566
|
+
`- 必须保留:${text(pattern.mustKeep)}`,
|
|
567
|
+
`- 信息密度:${text(page.density)}`,
|
|
568
|
+
`- 内容覆盖:${text(richness.contentLayers)}`,
|
|
569
|
+
].join('\n');
|
|
570
|
+
}).join('\n\n') || '- 无自定义页面';
|
|
561
571
|
}
|
|
562
572
|
|
|
563
|
-
function renderPageApplications(
|
|
564
|
-
|
|
565
|
-
const pages = plan.pages?.customPageDetails || [];
|
|
566
|
-
return pages.map(page => {
|
|
567
|
-
const application = applications.find(item => item.pageId === page.pageId) || {};
|
|
573
|
+
function renderPageApplications(applications) {
|
|
574
|
+
return (applications || []).map(application => {
|
|
568
575
|
const memories = (application.visualMemoryApplications || []).map(memory =>
|
|
569
|
-
|
|
570
|
-
|
|
576
|
+
` - ${text(memory.name)}:${text(memory.target)}(${text(memory.renderPolicy)};${text(memory.reason)})`
|
|
577
|
+
);
|
|
571
578
|
return [
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
`-
|
|
575
|
-
`-
|
|
576
|
-
`- 布局:${text(application.layout, '草稿待补充')};页面模式 ${text(page.layoutPattern?.id)},信息密度 ${text(page.density)}`,
|
|
577
|
-
`- 表面与组件:画布使用 --pod-page-bg-color(${tokens['--pod-page-bg-color']}),主容器使用 --pod-card-bg-color(${tokens['--pod-card-bg-color']}),边界使用 --color-line1-1(${tokens['--color-line1-1']})。${text(application.surface)}${memories ? `;${memories}` : ''}`,
|
|
579
|
+
`#### ${text(application.pageName || application.pageId)}`,
|
|
580
|
+
'',
|
|
581
|
+
`- 视觉应用:${text(application.visualApplication)}`,
|
|
582
|
+
`- 表面:${text(application.surface)}`,
|
|
578
583
|
`- 主操作:${text(application.primaryAction)}`,
|
|
579
584
|
`- 状态:${text(application.states)}`,
|
|
580
|
-
|
|
581
|
-
|
|
585
|
+
'- 视觉记忆点:',
|
|
586
|
+
...(memories.length > 0 ? memories : [' - 无匹配内容,不虚构视觉组件']),
|
|
582
587
|
].join('\n');
|
|
583
|
-
}).join('\n\n') || '
|
|
588
|
+
}).join('\n\n') || '- 无自定义页面';
|
|
584
589
|
}
|
|
585
590
|
|
|
586
|
-
function
|
|
587
|
-
const
|
|
588
|
-
const
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
result.push(`### 主题表达验收\n\n${content}`);
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
// These source paragraphs mix durable rendering constraints with instructions
|
|
597
|
-
// for authoring/instantiating templates. Retain the former sentence by sentence.
|
|
598
|
-
const filtered = content.split('\n').map(line => {
|
|
599
|
-
const bullet = /^- /.test(line) ? '- ' : '';
|
|
600
|
-
const sentences = line.replace(/^- /, '').split(/(?<=[。;;])\s*/u);
|
|
601
|
-
const keep = sentences.filter(sentence => !/themeId|front matter|sRGB|round\(|线性光|生成标记|生成期|占位|四舍五入|输出六位十六进制|用户未提供|用户已指定|未指定时|缺少输入|没有可用种子|由项目流程中的 AI|按依赖顺序计算|先解析唯一主色/.test(sentence));
|
|
602
|
-
const text = keep.join('');
|
|
603
|
-
return text.trim() ? bullet + text : '';
|
|
604
|
-
}).join('\n').replace(/\n{3,}/g, '\n\n').trim();
|
|
605
|
-
if (filtered) {result.push(`### ${heading[1].replace(/实例化$/, '职责与约束')}\n\n${filtered}`);}
|
|
606
|
-
}
|
|
607
|
-
return result.join('\n\n');
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
function renderProjectApplications(plan, tokens, color, chapter) {
|
|
611
|
-
const assets = plan.visualStyle?.forUser?.assetStrategy || { pages: [] };
|
|
612
|
-
const colors = {
|
|
613
|
-
'--color-brand1-6': '主色与主要品牌操作', '--color-brand1-1': '品牌悬停',
|
|
614
|
-
'--color-brand1-9': '品牌按下', '--color-brand1-10': '品牌禁用',
|
|
615
|
-
'--pod-page-bg-color': '页面画布', '--pod-card-bg-color': '内容容器',
|
|
616
|
-
'--color-fill1-5': '独立浮层', '--color-fill1-10': 'Tooltip背景', '--color-text1-5': 'Tooltip前景',
|
|
617
|
-
};
|
|
618
|
-
return [
|
|
619
|
-
'## 5. 项目应用与调整规则', '',
|
|
620
|
-
themeImplementationRules(chapter), '',
|
|
621
|
-
'### 本项目实现边界', '',
|
|
622
|
-
'本项目显式 Token 与页面决定优先于默认值;原生导航采用第二章的导航设计和完整导航 token,自绘导航按本项目入口方案实施。导航、应用框架、表单、自定义页面与详情保持统一风格。业务内容、操作和数据来自 PRD,条件配方用于本项目已有内容。',
|
|
623
|
-
`已确认约束:${text(plan.visualStyle?.constraints, '无额外约束')}。`, '',
|
|
624
|
-
'### 配色与语义', '',
|
|
625
|
-
`本项目主色为 ${text(color.primaryColorName, '')} ${color.primaryColor};来源为 ${text(color.source, 'AI 推断')}。${text(color.usage, '用于主要品牌操作和少量焦点,独立数据色和平台状态色保持各自语义。')}`, '',
|
|
626
|
-
mdTable(['语义', '变量', '最终取值'], Object.entries(colors).filter(([name]) => tokens[name]).map(([name, role]) => [role, name, tokens[name]])), '',
|
|
627
|
-
renderPageApplications(plan, tokens), '',
|
|
628
|
-
'### 素材与实现验收', '',
|
|
629
|
-
...(assets.pages || []).map(page => `- ${text(page.pageId)}:${text(page.imageNeed, 'none')};${text(page.reason)};素材槽位 ${(page.slots || []).map(slot => slot.slotId || slot.slotKey || slot.key || slot.name).filter(Boolean).join('、') || '无'}`),
|
|
630
|
-
`素材缺口:${text(assets.missingAssets, '无')}。`,
|
|
631
|
-
'- 验证页面任务、首屏焦点、布局和窄屏顺序与逐页方案一致;条件配方只出现在已注明的位置。',
|
|
632
|
-
'- 验证表面层级、操作与文字对比、键盘焦点、空态和错误反馈;数据几何、单位和读数须与真实数据一致。',
|
|
633
|
-
].join('\n');
|
|
591
|
+
function assetSummary(assetStrategy) {
|
|
592
|
+
const strategy = assetStrategy || {};
|
|
593
|
+
const pages = Array.isArray(strategy.pages) ? strategy.pages : [];
|
|
594
|
+
return pages.map((page) => {
|
|
595
|
+
const slotCount = Array.isArray(page.slots) ? page.slots.length : 0;
|
|
596
|
+
return `${page.pageId || '未命名页面'}: ${page.imageNeed || 'none'},${slotCount} 个槽位`;
|
|
597
|
+
}).join(';') || '无图片需求';
|
|
634
598
|
}
|
|
635
599
|
|
|
636
|
-
function componentReferences(
|
|
637
|
-
const
|
|
600
|
+
function componentReferences(markdown) {
|
|
601
|
+
const sections = [...markdown.matchAll(/^### (.+)\n([\s\S]*?)(?=^#{1,3} |$(?![\s\S]))/gm)];
|
|
602
|
+
const patterns = { button: /按钮/, input: /输入/, card: /卡片/, table: /表格/, chart: /图表/ };
|
|
638
603
|
const components = {};
|
|
604
|
+
for (const [key, pattern] of Object.entries(patterns)) {
|
|
605
|
+
const section = sections.find(match => pattern.test(match[1]));
|
|
606
|
+
if (section) {components[key] = { rules: section[2].trim() };}
|
|
607
|
+
}
|
|
639
608
|
const states = {};
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
const subsections = headings.map((heading, index) => ({ index: heading.index, title: heading[1],
|
|
645
|
-
content: section.slice(heading.index + heading[0].length, headings[index + 1]?.index) }));
|
|
646
|
-
const anchors = new Map();
|
|
647
|
-
const locate = (anchor, target) => {
|
|
648
|
-
const position = start + target.index;
|
|
649
|
-
anchors.set(position, [...(anchors.get(position) || []), `<a id="${anchor}"></a>`]);
|
|
650
|
-
};
|
|
651
|
-
for (const [key, pattern] of Object.entries({ button: /按钮|主操作|次操作/, input: /输入|搜索|筛选控件/, card: /卡片|面板|容器/, table: /表格|表头|单元格/, chart: /图表|图形|曲线|柱形/ })) {
|
|
652
|
-
const target = subsections.find(item => pattern.test(item.title)) || subsections.find(item => pattern.test(item.content));
|
|
653
|
-
if (!target) {continue;}
|
|
654
|
-
const anchor = `component-${key}`;
|
|
655
|
-
components[key] = { anchor: `#${anchor}` };
|
|
656
|
-
locate(anchor, target);
|
|
657
|
-
}
|
|
658
|
-
const stateSection = subsections.find(item => /^(?:\d+\.\d+ )?(?:组件状态|交互状态|交互与反馈状态|状态与)/.test(item.title))
|
|
659
|
-
|| subsections.find(item => /状态/.test(item.title));
|
|
660
|
-
for (const [key, pattern] of Object.entries({ empty: /empty|空态|无数据|暂无/, loading: /loading|加载/, error: /error|错误|失败|异常/,
|
|
661
|
-
disabled: /disabled|禁用/, focus: /focus|聚焦|焦点|键盘/, selected: /selected|选中/ })) {
|
|
662
|
-
const target = stateSection && pattern.test(stateSection.content) ? stateSection : subsections.find(item => pattern.test(item.content));
|
|
663
|
-
if (!target) {continue;}
|
|
664
|
-
const anchor = `state-${key}`;
|
|
665
|
-
states[key] = { anchor: `#${anchor}` };
|
|
666
|
-
locate(anchor, target);
|
|
667
|
-
}
|
|
668
|
-
let output = body;
|
|
669
|
-
for (const [position, tags] of [...anchors].sort((left, right) => right[0] - left[0])) {
|
|
670
|
-
output = output.slice(0, position) + `${tags.join('\n')}\n` + output.slice(position);
|
|
671
|
-
}
|
|
672
|
-
return { body: output, components, states };
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function overrideTokens(groups, overrides) {
|
|
676
|
-
const remaining = new Set(Object.keys(overrides));
|
|
677
|
-
function update(node) {
|
|
678
|
-
for (const [key, value] of Object.entries(node || {})) {
|
|
679
|
-
if (key.startsWith('--') && remaining.has(key)) {node[key] = overrides[key]; remaining.delete(key);}
|
|
680
|
-
else if (value && typeof value === 'object' && !Array.isArray(value)) {update(value);}
|
|
609
|
+
const section = sections.find(match => /状态与交互/.test(match[1]));
|
|
610
|
+
if (section) {
|
|
611
|
+
for (const key of ['empty', 'loading', 'error', 'disabled', 'focus', 'selected']) {
|
|
612
|
+
states[key] = { rules: section[2].trim() };
|
|
681
613
|
}
|
|
682
614
|
}
|
|
683
|
-
|
|
684
|
-
for (const key of remaining) {
|
|
685
|
-
const scope = /^(?:--pod-|--color-|--font-|--s-|--corner-|--shadow-)/.test(key) ? 'application-global' : 'custom-page';
|
|
686
|
-
groups[scope] = groups[scope] || {};
|
|
687
|
-
const target = scope === 'application-global' ? (groups[scope].appearance = groups[scope].appearance || {}) : groups[scope];
|
|
688
|
-
target['project-overrides'] = target['project-overrides'] || {};
|
|
689
|
-
target['project-overrides'][key] = overrides[key];
|
|
690
|
-
}
|
|
615
|
+
return { components, states };
|
|
691
616
|
}
|
|
692
617
|
|
|
693
|
-
function renderDesign(plan
|
|
694
|
-
const {
|
|
618
|
+
function renderDesign(plan) {
|
|
619
|
+
const { templatePath } = resolveTheme(plan);
|
|
620
|
+
const template = fs.readFileSync(templatePath, 'utf8');
|
|
695
621
|
const meta = plan.meta || {};
|
|
696
622
|
const { visual, forUser, selectedTheme: selected, visualDirection, navigationStyle } = visualSelections(plan);
|
|
623
|
+
const forDesignMd = visual.forDesignMd || {};
|
|
697
624
|
const color = forUser.colorStrategy || {};
|
|
698
625
|
if (!['top', 'side'].includes(navigationStyle.structure)) {
|
|
699
|
-
throw new CliError('visualStyle.forUser.navigationStyle.structure 必须是 top 或 side', {
|
|
626
|
+
throw new CliError('visualStyle.forUser.navigationStyle.structure 必须是 top 或 side', {
|
|
627
|
+
code: 'DESIGN_PLAN_INVALID_NAVIGATION_STRUCTURE',
|
|
628
|
+
});
|
|
700
629
|
}
|
|
701
630
|
if (!['light', 'dark'].includes(navigationStyle.tone)) {
|
|
702
|
-
throw new CliError('visualStyle.forUser.navigationStyle.tone 必须是 light 或 dark', {
|
|
631
|
+
throw new CliError('visualStyle.forUser.navigationStyle.tone 必须是 light 或 dark', {
|
|
632
|
+
code: 'DESIGN_PLAN_INVALID_NAVIGATION_TONE',
|
|
633
|
+
});
|
|
703
634
|
}
|
|
704
635
|
const primaryColor = normalizeHex(color.primaryColor);
|
|
705
636
|
if (!primaryColor) {
|
|
706
637
|
throw new CliError('visualStyle.forUser.colorStrategy.primaryColor 必须是 6 位 HEX 色值', {
|
|
707
|
-
code: 'DESIGN_PLAN_INVALID_PRIMARY_COLOR',
|
|
638
|
+
code: 'DESIGN_PLAN_INVALID_PRIMARY_COLOR',
|
|
639
|
+
details: { primaryColor: color.primaryColor },
|
|
708
640
|
});
|
|
709
641
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
642
|
+
const replacements = {
|
|
643
|
+
PROJECT_NAME: text(meta.appName || meta.projectName, 'OpenYida 应用'),
|
|
644
|
+
BUSINESS_DOMAIN: text(meta.businessDomain),
|
|
645
|
+
EXPERIENCE_TOPOLOGY: text(meta.experienceTopology),
|
|
646
|
+
THEME_SOURCE: text(forUser.styleSource || selected.source),
|
|
647
|
+
PRIMARY_COLOR: primaryColor,
|
|
648
|
+
COLOR_SOURCE: text(color.source),
|
|
649
|
+
PROJECT_CONSTRAINTS: text(visual.constraints, '无额外约束'),
|
|
650
|
+
PRODUCT_TOPOLOGY_APPLICATION: text(forDesignMd.productTopologyApplication),
|
|
651
|
+
PAGE_PATTERN_SUMMARY: renderPagePatterns(plan.pages || {}),
|
|
652
|
+
PAGE_APPLICATIONS: renderPageApplications(forUser.pageApplications),
|
|
653
|
+
PAGE_IMAGE_NEEDS: assetSummary(forUser.assetStrategy),
|
|
654
|
+
ASSET_GAPS: text((forUser.assetStrategy || {}).missingAssets, '无'),
|
|
655
|
+
};
|
|
656
|
+
let output = template.replace(/\{\{([A-Z_]+)\}\}/g, (match, key) => {
|
|
657
|
+
if (!Object.prototype.hasOwnProperty.call(replacements, key)) {
|
|
658
|
+
return match;
|
|
659
|
+
}
|
|
660
|
+
return replacements[key];
|
|
661
|
+
});
|
|
662
|
+
output = output.replace(/^themeId:\s*[^\n]+\n?/m, '');
|
|
663
|
+
if (!/^buildPlanRevision:/m.test(output)) {
|
|
664
|
+
output = output.replace(
|
|
665
|
+
/^---\n/,
|
|
666
|
+
`---\nbuildPlanRevision: "${text(meta.revision, 'unversioned')}"\n`
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
output = injectAfterFrontmatter(
|
|
670
|
+
output,
|
|
671
|
+
renderProjectVisualSection(visualDirection, color, navigationStyle, navigationConfig(plan))
|
|
672
|
+
);
|
|
673
|
+
const derived = {
|
|
674
|
+
...deriveBrandColors(primaryColor, selected.themeId),
|
|
675
|
+
'--oyd-stage-bottom': mixTwoHex(primaryColor, '#121212', 0.72),
|
|
676
|
+
'--oyd-heat-1': mixHex(primaryColor, [255, 255, 255], 0.92),
|
|
677
|
+
'--oyd-heat-2': mixHex(primaryColor, [255, 255, 255], 0.80),
|
|
678
|
+
'--oyd-heat-3': mixHex(primaryColor, [255, 255, 255], 0.62),
|
|
679
|
+
'--oyd-heat-4': mixHex(primaryColor, [255, 255, 255], 0.32),
|
|
680
|
+
'--oyd-heat-5': primaryColor,
|
|
681
|
+
'--oyd-chart-stripe': mixHex(primaryColor, [0, 0, 0], 0.08),
|
|
682
|
+
'--oyd-chart-pale': mixHex(primaryColor, [255, 255, 255], 0.84),
|
|
683
|
+
...deriveThemeColors(primaryColor, selected.themeId),
|
|
684
|
+
};
|
|
685
|
+
for (const [token, value] of Object.entries(derived)) {
|
|
686
|
+
output = replaceTokenValue(output, token, value);
|
|
718
687
|
}
|
|
688
|
+
// 页面底色统一使用平台 token;导航形态不改变底色,项目显式设计仍优先。
|
|
689
|
+
if (!selected.themeId.startsWith('dark-')) {
|
|
690
|
+
output = replaceTokenValue(output, '--pod-page-bg-color', 'var(--color-white, #fff)');
|
|
691
|
+
}
|
|
692
|
+
const explicitTokens = visual.tokens || {};
|
|
719
693
|
if (typeof explicitTokens !== 'object' || Array.isArray(explicitTokens)) {
|
|
720
694
|
throw new CliError('visualStyle.tokens 必须是 CSS token 对象', { code: 'DESIGN_PLAN_INVALID_TOKENS' });
|
|
721
695
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
details: { themeId: theme.themeId, expected: themeNavTheme(theme), actual: navigationStyle.tone },
|
|
726
|
-
});
|
|
727
|
-
}
|
|
728
|
-
const overrides = { ...explicitTokens };
|
|
696
|
+
const palette = deriveSurfacePalette(primaryColor, color.surfaceTone || 'theme', output);
|
|
697
|
+
const overrides = { ...palette, ...explicitTokens };
|
|
698
|
+
const tokens = readDesignTokens(output);
|
|
729
699
|
for (const [token, value] of Object.entries(overrides)) {
|
|
730
700
|
if (!/^--[\w-]+$/.test(token) || /^--color-brand/.test(token) || typeof value !== 'string') {
|
|
731
701
|
throw new CliError(`无效视觉 token:${token};品牌色由 primaryColor 推导`, { code: 'DESIGN_PLAN_INVALID_TOKENS' });
|
|
732
702
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
throw new CliError('主题缺少五章设计结构,请检查模板', { code: 'DESIGN_PLAN_THEME_CONTEXT_MISSING' });
|
|
742
|
-
}
|
|
743
|
-
const summaryTitle = new RegExp('(^## 1\\. 风格摘要\\n\\s*)\\*\\*([^\\n]+)\\*\\*[ \\t]*\\n(?:[ \\t]*\\n)?', 'm');
|
|
744
|
-
chapters[0] = chapters[0].replace(summaryTitle,
|
|
745
|
-
(line, heading, title) => title.includes(theme.label) || title.includes(theme.themeId) ? heading : line);
|
|
746
|
-
chapters[0] += `\n\n${renderProjectFacts(plan, visualDirection, { ...color, primaryColor })}`;
|
|
747
|
-
if (theme.mode === 'creative') {
|
|
748
|
-
const decision = visual.creativeDirection;
|
|
749
|
-
chapters[0] += `\n\n### 业务推演依据\n\n${decision.businessRationale}`;
|
|
750
|
-
chapters[1] += `\n\n### 自主设计决策\n\n${decision.composition}\n\n${decision.typography}\n\n${decision.material}`;
|
|
751
|
-
chapters[2] += `\n\n### 表单布局决策\n\n${decision.formLayout}`;
|
|
752
|
-
}
|
|
753
|
-
chapters[1] = chapters[1].replace(/^(### 2\.2 应用导航\n)([\s\S]*?)(?=^### |(?![\s\S]))/m,
|
|
754
|
-
(_, heading, guidance) => {
|
|
755
|
-
// The resolved project values replace the preset's color description;
|
|
756
|
-
// keep its token consumption, interaction and acceptance guidance.
|
|
757
|
-
const sharedGuidance = (theme.navigationSummary ? guidance.replace(theme.navigationSummary, '') : guidance)
|
|
758
|
-
.replace(/^导航示例:[^\n]*\n?/gm, '')
|
|
759
|
-
.replace(/当前模板使用 contentTone:\s*(?:light|dark)、navTheme:\s*(?:light|dark)。/g, '').trim();
|
|
760
|
-
return `${heading}\n${renderNavigationDesign(plan, navigationStyle, tokens)}\n\n${sharedGuidance}\n\n`;
|
|
703
|
+
tokens[token] = value;
|
|
704
|
+
}
|
|
705
|
+
// Preserve template grouping; only project token values and missing declarations change.
|
|
706
|
+
output = output.replace(/^tokens:\s*\n[\s\S]*?(?=^[^\s#]|$(?![\s\S]))/m, block => {
|
|
707
|
+
const existing = new Set();
|
|
708
|
+
const updated = block.replace(/^(\s*)["']?(--[\w-]+)["']?\s*:[^\n]*$/gm, (line, indent, name) => {
|
|
709
|
+
existing.add(name);
|
|
710
|
+
return name in overrides ? `${indent}${JSON.stringify(name)}: ${JSON.stringify(tokens[name])}` : line;
|
|
761
711
|
});
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
712
|
+
const extra = Object.entries(overrides).filter(([name]) => !existing.has(name));
|
|
713
|
+
return updated + extra.map(([name, value]) => ` ${JSON.stringify(name)}: ${JSON.stringify(value)}\n`).join('');
|
|
714
|
+
});
|
|
715
|
+
if (Object.keys(palette).length) {
|
|
716
|
+
output = injectAfterFrontmatter(output, [
|
|
717
|
+
'## 项目配色适配(优先于模板默认配色)', '',
|
|
718
|
+
`用户选择的 ${text(color.primaryColorName, primaryColor)} 用于整体浅色氛围。Shell 浅色背景、填充和边界已按同一主色派生,项目显式 token 覆盖优先;最终取值以本文 tokens 为准。`,
|
|
719
|
+
'背景分层:Shell 承载外层氛围;原生页面与自定义页统一消费 --pod-page-bg-color,卡片与面板消费 --pod-card-bg-color。导航隐藏不自动改为透明;渐变、纹理、图片属于页面局部装饰层。明确的应用背景设计通过 app-theme.css 配置平台 token。',
|
|
720
|
+
'模板中“固定无彩画布”“中性灰必须 R=G=B”“禁止主题色进入背景”等默认限制在本项目不适用;保留布局、圆角、阴影与组件结构。不要为了保留模板灰底,把用户选择的浅彩导航改白。',
|
|
721
|
+
'页面背景消费 --pod-page-bg-color,卡片消费 --pod-card-bg-color(回退 --color-white),边界消费 --pod-card-border;近白页面与白卡必须保留可见细边框,不能只靠阴影分层。导航、表头、嵌套区域与选中态消费同一组品牌/填充 token。',
|
|
722
|
+
'正文与标题使用 --color-text1-4,表头与 placeholder 使用 --color-text1-10,辅助说明使用 --color-text1-3;保持中性文字明度层级,不把整页文字染成主色。成功、警告、错误沿用应用语义色。',
|
|
723
|
+
'antd 的主色、容器、浮层、文字、边框、填充及语义色都须映射应用 token;浅彩氛围不能仅出现在导航和主按钮。页面实现前先读本节,再读取下面的结构与组件规则。', '',
|
|
724
|
+
].join('\n'));
|
|
775
725
|
}
|
|
776
|
-
chapters[4] = renderProjectApplications(plan, tokens, { ...color, primaryColor, source: colorSource }, chapters[4]);
|
|
777
|
-
let body = `# ${name} design.md\n\n${chapters.join('\n\n')}\n`;
|
|
778
|
-
body = body.replace(/\{\{PROJECT_NAME\}\}/g, () => name).replace(/\{\{COLOR_SOURCE\}\}/g, () => colorSource);
|
|
779
|
-
const references = componentReferences(body);
|
|
780
|
-
body = references.body;
|
|
781
726
|
const scenes = {};
|
|
782
727
|
for (const page of plan.pages?.customPageDetails || []) {
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
scenes[key]
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
name: visualDirection.label, themeColor: primaryColor, themeColorSource: colorSource,
|
|
794
|
-
contentTone: resolveContentTone(theme, tokens), navTheme: navigationStyle.tone, themeDelivery: 'app-custom-theme-file',
|
|
795
|
-
themeFile: path.join(options.outputDir || path.join('prd', meta.projectName || name), 'app-theme.css'),
|
|
796
|
-
...navigationConfig(plan), logoSource: 'appIcon',
|
|
797
|
-
},
|
|
798
|
-
sceneRecipes: scenes, components: references.components, states: references.states,
|
|
799
|
-
assetStrategy: { pages: [], ...forUser.assetStrategy },
|
|
800
|
-
iconSystem: forUser.iconSystem || { library: 'lucide-react', mappings: {} },
|
|
801
|
-
};
|
|
802
|
-
for (const page of options.draft ? [] : plan.pages?.customPageDetails || []) {
|
|
728
|
+
const handoff = pageHandoff(page, plan);
|
|
729
|
+
const key = page.sceneKey || handoff.scene;
|
|
730
|
+
scenes[key] = scenes[key] || { scene: handoff.scene, inherits: 'themeProfile', pages: [] };
|
|
731
|
+
scenes[key].pages.push({
|
|
732
|
+
pageId: page.pageId, density: page.density, layoutPattern: page.layoutPattern,
|
|
733
|
+
visualApplication: (forUser.pageApplications || []).find(item => item.pageId === page.pageId) || {},
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
const references = { themeProfile: {}, sceneRecipes: scenes, ...componentReferences(output) };
|
|
737
|
+
for (const page of plan.pages?.customPageDetails || []) {
|
|
803
738
|
for (const ref of pageHandoff(page, plan).designRefs) {
|
|
804
739
|
requireFact(/^(themeProfile|sceneRecipes\.[\p{L}\p{N}_-]+|components\.[\w-]+|states\.[\w-]+)$/u.test(ref)
|
|
805
|
-
&& ref.split('.').reduce((value, key) => Object.prototype.hasOwnProperty.call(value || {}, key) ? value[key] : undefined,
|
|
740
|
+
&& ref.split('.').reduce((value, key) => Object.prototype.hasOwnProperty.call(value || {}, key) ? value[key] : undefined, references),
|
|
806
741
|
`页面 ${page.name} 的设计引用不存在:${ref}`);
|
|
807
742
|
}
|
|
808
743
|
}
|
|
809
|
-
|
|
810
|
-
|
|
744
|
+
output = output.replace(/^---\n/, () => [
|
|
745
|
+
'---',
|
|
746
|
+
`assetStrategy: ${JSON.stringify({ pages: [], ...forUser.assetStrategy })}`,
|
|
747
|
+
`themeProfile: ${JSON.stringify({ themeColor: primaryColor, navTheme: navigationStyle.tone, ...navigationConfig(plan), logoSource: 'appIcon', themeDelivery: 'app-custom-theme-file' })}`,
|
|
748
|
+
`sceneRecipes: ${JSON.stringify(scenes)}`,
|
|
749
|
+
`components: ${JSON.stringify(references.components)}`,
|
|
750
|
+
`states: ${JSON.stringify(references.states)}`, '',
|
|
751
|
+
].join('\n'));
|
|
752
|
+
const unresolved = output.match(
|
|
753
|
+
/\{\{[^}]+\}\}|<[^>]*(?:实际色值|生成期标记)[^>]*>|"--[^"]+"\s*:\s*"AI 根据[^"]*生成[^"]*"/g
|
|
754
|
+
);
|
|
811
755
|
if (unresolved) {
|
|
812
756
|
throw new CliError('design.md 仍包含未解析占位符或 Token 推导指令', {
|
|
813
|
-
code: 'DESIGN_PLAN_UNRESOLVED_TEMPLATE',
|
|
757
|
+
code: 'DESIGN_PLAN_UNRESOLVED_TEMPLATE',
|
|
758
|
+
details: { unresolved: [...new Set(unresolved)] },
|
|
814
759
|
});
|
|
815
760
|
}
|
|
816
761
|
return output;
|
|
@@ -838,9 +783,8 @@ function renderHtml(plan, sections) {
|
|
|
838
783
|
|
|
839
784
|
// Draft sections share the final PRD renderer; implementation details are filled at final validation.
|
|
840
785
|
function renderDraftPrd(plan) {
|
|
841
|
-
const { navigationStyle } = visualSelections(plan);
|
|
842
786
|
return renderPrd(plan, {
|
|
843
|
-
appConfig: { ...navigationConfig(plan), navTheme: navigationStyle
|
|
787
|
+
appConfig: { ...navigationConfig(plan), navTheme: plan.visualStyle?.forUser?.navigationStyle?.tone },
|
|
844
788
|
pageNavigation: [], sampleDataPlan: [],
|
|
845
789
|
pages: (plan.pages?.customPageDetails || []).map(page => ({ pageSpecHandoff: page.pageSpecHandoff || {} })),
|
|
846
790
|
interactionStates: {}, resourceBlueprint: [], resourceCreationOrder: [], pageImplementationOrder: [],
|
|
@@ -848,7 +792,7 @@ function renderDraftPrd(plan) {
|
|
|
848
792
|
});
|
|
849
793
|
}
|
|
850
794
|
|
|
851
|
-
function prepareArtifacts(sourcePlan, outputDir, themeSeed
|
|
795
|
+
function prepareArtifacts(sourcePlan, outputDir, themeSeed) {
|
|
852
796
|
const timings = {};
|
|
853
797
|
let authoringIssues = [];
|
|
854
798
|
const measure = (name, action) => {
|
|
@@ -865,27 +809,12 @@ function prepareArtifacts(sourcePlan, outputDir, themeSeed, options = {}) {
|
|
|
865
809
|
authoringIssues = validateAuthoring(normalized);
|
|
866
810
|
return normalized;
|
|
867
811
|
});
|
|
868
|
-
const
|
|
869
|
-
const execution = measure('businessValidationMs', () => buildExecution(plan, { designFile }));
|
|
870
|
-
const assetStrategy = plan.visualStyle?.forUser?.assetStrategy;
|
|
871
|
-
const assetTasks = measure('assetValidationMs', () => {
|
|
872
|
-
if (assetStrategy !== undefined && (!assetStrategy || typeof assetStrategy !== 'object' || Array.isArray(assetStrategy))) {
|
|
873
|
-
throw new CliError(t('asset.invalidStrategy'), { code: 'ASSET_DESIGN_INVALID', details: { field: 'visualStyle.forUser.assetStrategy' } });
|
|
874
|
-
}
|
|
875
|
-
const tasks = buildAssetTasks({ pages: [], ...assetStrategy }, outputDir);
|
|
876
|
-
return plan.execution?.explicitScope?.allowInferredResources === false ? [] : tasks;
|
|
877
|
-
});
|
|
812
|
+
const execution = measure('businessValidationMs', () => buildExecution(plan));
|
|
878
813
|
const prd = measure('prdMs', () => renderPrd(plan, execution));
|
|
879
|
-
const design = measure('designMs', () => renderDesign(plan
|
|
880
|
-
measure('designValidationMs', () => validateDesignDocument(design, { prdMarkdown: prd, designFile }));
|
|
814
|
+
const design = measure('designMs', () => renderDesign(plan));
|
|
881
815
|
// Validate the same public CSS pipeline used by both Fast and Plan before writing artifacts.
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
throw new CliError(t('design_document.invalid', 'build-plan.json', authoringIssues.map(issue => `${issue.path}: ${issue.message}`).join('; ')), {
|
|
885
|
-
code: 'DESIGN_PLAN_VALIDATION_FAILED', details: { issues: authoringIssues },
|
|
886
|
-
});
|
|
887
|
-
}
|
|
888
|
-
const html = options.renderHtml === false ? undefined : measure('htmlMs', () => renderHtml({ ...plan, execution }));
|
|
816
|
+
measure('themeValidationMs', () => applyDesignTokens(fs.readFileSync(path.join(PACKAGE_ROOT, 'yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css'), 'utf8'), design));
|
|
817
|
+
const html = measure('htmlMs', () => renderHtml({ ...plan, execution }));
|
|
889
818
|
const outputs = {
|
|
890
819
|
prd: path.join(outputDir, 'prd.md'),
|
|
891
820
|
design: path.join(outputDir, 'design.md'),
|
|
@@ -894,38 +823,14 @@ function prepareArtifacts(sourcePlan, outputDir, themeSeed, options = {}) {
|
|
|
894
823
|
};
|
|
895
824
|
const existingCss = fs.existsSync(outputs.theme) ? fs.readFileSync(outputs.theme, 'utf8') : themeSeed?.css;
|
|
896
825
|
const previousDesign = fs.existsSync(outputs.theme) && fs.existsSync(outputs.design) ? fs.readFileSync(outputs.design, 'utf8') : themeSeed?.design;
|
|
897
|
-
const theme = measure('themeMs', () => existingCss
|
|
826
|
+
const theme = measure('themeMs', () => applyDesignTokens(existingCss || fs.readFileSync(path.join(PACKAGE_ROOT,
|
|
827
|
+
'yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css'), 'utf8'), design, previousDesign));
|
|
898
828
|
return {
|
|
899
|
-
revision: (plan.meta || {}).revision || null, outputs, timings,
|
|
900
|
-
|
|
901
|
-
files: [[outputs.prd, prd], [outputs.design, design], ...(html === undefined ? [] : [[outputs.html, html]]), [outputs.theme, theme]],
|
|
829
|
+
revision: (plan.meta || {}).revision || null, outputs, timings,
|
|
830
|
+
files: [[outputs.prd, prd], [outputs.design, design], [outputs.html, html], [outputs.theme, theme]],
|
|
902
831
|
};
|
|
903
832
|
}
|
|
904
833
|
|
|
905
|
-
function prepareArtifactUpdate(artifacts, outputDir, previousArtifacts) {
|
|
906
|
-
const updates = prepareUpdates(artifacts, outputDir, previousArtifacts);
|
|
907
|
-
const content = file => updates.files.find(([target]) => target === file)?.[1] ?? fs.readFileSync(file, 'utf8');
|
|
908
|
-
const design = content(artifacts.outputs.design);
|
|
909
|
-
const generated = file => artifacts.files.find(([target]) => target === file)?.[1];
|
|
910
|
-
const prd = content(artifacts.outputs.prd);
|
|
911
|
-
// Generated content was already validated; local merges must be checked again.
|
|
912
|
-
if (design !== generated(artifacts.outputs.design) || prd !== generated(artifacts.outputs.prd)) {
|
|
913
|
-
validateDesignDocument(design, { prdMarkdown: prd, designFile: path.resolve(artifacts.outputs.design) });
|
|
914
|
-
}
|
|
915
|
-
// CSS consumes the merged design, including preserved local additions.
|
|
916
|
-
if (fs.existsSync(artifacts.outputs.theme)) {
|
|
917
|
-
const current = fs.readFileSync(artifacts.outputs.theme, 'utf8');
|
|
918
|
-
const previous = updates.previousDesign || (fs.existsSync(artifacts.outputs.design) ? fs.readFileSync(artifacts.outputs.design, 'utf8') : undefined);
|
|
919
|
-
const sameInput = design === generated(artifacts.outputs.design)
|
|
920
|
-
&& current === artifacts.themeInput?.css && previous === artifacts.themeInput?.design;
|
|
921
|
-
const css = sameInput ? generated(artifacts.outputs.theme) : applyDesignTokens(current, design, previous);
|
|
922
|
-
updates.files = updates.files.filter(([file]) => file !== artifacts.outputs.theme);
|
|
923
|
-
updates.updated = updates.updated.filter(file => file !== artifacts.outputs.theme);
|
|
924
|
-
if (css !== current) {updates.files.push([artifacts.outputs.theme, css]); updates.updated.push(artifacts.outputs.theme);}
|
|
925
|
-
}
|
|
926
|
-
return updates;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
834
|
function materializeUnlocked(inputPath, options = {}) {
|
|
930
835
|
const start = performance.now();
|
|
931
836
|
const input = path.resolve(inputPath);
|
|
@@ -934,9 +839,6 @@ function materializeUnlocked(inputPath, options = {}) {
|
|
|
934
839
|
const readMs = performance.now() - start;
|
|
935
840
|
const mergeStart = performance.now();
|
|
936
841
|
const merging = options.businessFile !== undefined || options.visualFile !== undefined;
|
|
937
|
-
if (options.rebaseParts && !merging) {
|
|
938
|
-
throw new CliError('--rebase-parts requires --business-file and --visual-file', { code: 'DESIGN_PLAN_PARTS_REQUIRED' });
|
|
939
|
-
}
|
|
940
842
|
if (merging && (!options.businessFile || !options.visualFile)) {
|
|
941
843
|
throw new CliError('--business-file 与 --visual-file 需同时提供', { code: 'DESIGN_PLAN_PARTS_REQUIRED' });
|
|
942
844
|
}
|
|
@@ -945,34 +847,30 @@ function materializeUnlocked(inputPath, options = {}) {
|
|
|
945
847
|
}
|
|
946
848
|
if (merging && options.fromPreview) { throw new CliError('选择一种规划来源', { code: 'DESIGN_PLAN_PART_CONFLICT' }); }
|
|
947
849
|
const merged = options.fromPreview ? require('./preview').finalizePreview(source, path.join(path.dirname(input), 'preview/.state.json'))
|
|
948
|
-
: merging ? mergeParts(source, options.businessFile, options.visualFile
|
|
850
|
+
: merging ? mergeParts(source, options.businessFile, options.visualFile) : source;
|
|
949
851
|
const updatesSource = merging || options.fromPreview;
|
|
950
852
|
const mergeMs = performance.now() - mergeStart;
|
|
951
853
|
const previewDir = path.join(path.dirname(input), 'preview');
|
|
952
854
|
const previewState = path.join(previewDir, '.state.json');
|
|
953
855
|
let themeSeed;
|
|
954
|
-
|
|
955
|
-
if (savedPreview?.facts?.visualStyle && savedPreview.base?.digest === require('./parallel').planBase(source).digest
|
|
856
|
+
if (fs.existsSync(previewState) && readJson(previewState).facts?.visualStyle && readJson(previewState).base?.digest === require('./parallel').planBase(source).digest
|
|
956
857
|
&& ['design.md', 'app-theme.css'].every(name => fs.existsSync(path.join(previewDir, name)))) {
|
|
957
858
|
themeSeed = { design: fs.readFileSync(path.join(previewDir, 'design.md'), 'utf8'), css: fs.readFileSync(path.join(previewDir, 'app-theme.css'), 'utf8') };
|
|
958
859
|
}
|
|
959
860
|
const artifacts = prepareArtifacts(merged, outputDir, themeSeed);
|
|
960
|
-
|
|
961
|
-
if (updates.files.some(([file]) => path.resolve(file) === input)) {
|
|
861
|
+
if (artifacts.files.some(([file]) => path.resolve(file) === input)) {
|
|
962
862
|
throw new CliError('输出文件不能覆盖 build-plan.json', { code: 'DESIGN_PLAN_OUTPUT_CONFLICT' });
|
|
963
863
|
}
|
|
964
864
|
const writeStart = performance.now();
|
|
965
|
-
if (merging &&
|
|
865
|
+
if (merging && artifacts.files.some(([file]) => [options.businessFile, options.visualFile].some(part => path.resolve(part) === path.resolve(file)))) {
|
|
966
866
|
throw new CliError('输出路径与规划片段冲突', { code: 'DESIGN_PLAN_OUTPUT_CONFLICT' });
|
|
967
867
|
}
|
|
968
868
|
if (!options.check) {
|
|
969
|
-
writeFiles(updatesSource ? [[input, `${JSON.stringify(merged, null, 2)}\n`], ...
|
|
869
|
+
writeFiles(updatesSource ? [[input, `${JSON.stringify(merged, null, 2)}\n`], ...artifacts.files] : artifacts.files);
|
|
970
870
|
}
|
|
971
871
|
const timings = { readMs, ...(merging ? { mergeMs } : {}), ...artifacts.timings, writeMs: performance.now() - writeStart, totalMs: performance.now() - start };
|
|
972
872
|
for (const key of Object.keys(timings)) {timings[key] = Math.round(timings[key] * 100) / 100;}
|
|
973
|
-
return { success: true, checked: options.check === true, input, outputDir, revision: artifacts.revision, outputs: artifacts.outputs,
|
|
974
|
-
...(!options.check ? { confirmation: require('./confirmation').confirmationPayload(artifacts.outputs, artifacts.revision) } : {}),
|
|
975
|
-
...(updatesSource ? { merged: true, previousRevision: source.meta.revision } : {}) };
|
|
873
|
+
return { success: true, checked: options.check === true, input, outputDir, revision: artifacts.revision, outputs: artifacts.outputs, timings, ...(updatesSource ? { merged: true, previousRevision: source.meta.revision } : {}) };
|
|
976
874
|
}
|
|
977
875
|
|
|
978
876
|
function materialize(inputPath, options = {}) {
|
|
@@ -987,12 +885,11 @@ function materialize(inputPath, options = {}) {
|
|
|
987
885
|
|
|
988
886
|
module.exports = {
|
|
989
887
|
prepareArtifacts,
|
|
990
|
-
prepareArtifactUpdate,
|
|
991
888
|
renderDraftPrd,
|
|
992
889
|
renderHtml,
|
|
993
890
|
materialize,
|
|
994
891
|
normalizePlan,
|
|
995
|
-
renderDesign:
|
|
892
|
+
renderDesign: plan => renderDesign(normalizePlan(plan)),
|
|
996
893
|
renderPrd: source => {
|
|
997
894
|
const plan = normalizePlan(source);
|
|
998
895
|
renderDesign(plan);
|