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.
Files changed (334) hide show
  1. package/README.md +14 -20
  2. package/bin/yida.js +6 -11
  3. package/lib/app/app-list.js +3 -7
  4. package/lib/app/canvas-compile.js +12 -22
  5. package/lib/app/check-prd-completeness.js +0 -7
  6. package/lib/app/create-app.js +1 -4
  7. package/lib/app/create-form/args.js +12 -37
  8. package/lib/app/create-form/batch.js +11 -86
  9. package/lib/app/create-form.js +86 -193
  10. package/lib/app/create-page.js +17 -84
  11. package/lib/app/custom-theme.js +0 -11
  12. package/lib/app/form-field-validator.js +41 -26
  13. package/lib/app/form-open-guard.js +10 -97
  14. package/lib/app/get-form-config.js +0 -1
  15. package/lib/app/inline-css-guard.js +31 -1
  16. package/lib/app/services/form-compiler.js +4 -2
  17. package/lib/app/theme-from-design.js +41 -144
  18. package/lib/app/theme-presets.js +1 -1
  19. package/lib/app/update-app.js +13 -63
  20. package/lib/asset/ai-image.js +9 -59
  21. package/lib/asset/asset-cmd.js +7 -18
  22. package/lib/asset/asset-plan.js +9 -102
  23. package/lib/asset/asset-resolve.js +10 -55
  24. package/lib/asset/asset-status.js +4 -5
  25. package/lib/asset/host-capabilities.js +2 -11
  26. package/lib/asset/url-verify.js +4 -19
  27. package/lib/auth/dws-sync.js +148 -0
  28. package/lib/auth/token-auth.js +73 -9
  29. package/lib/auth/token-store.js +50 -0
  30. package/lib/connector/connector-test.js +5 -19
  31. package/lib/connector/contract.js +1 -3
  32. package/lib/core/agent-capabilities.js +16 -19
  33. package/lib/core/command-manifest.js +40 -329
  34. package/lib/core/locales/en.js +17 -74
  35. package/lib/core/locales/zh.js +16 -73
  36. package/lib/core/redact.js +10 -15
  37. package/lib/core/sample.js +30 -72
  38. package/lib/core/utils.js +4 -0
  39. package/lib/design-plan/design-plan.js +4 -51
  40. package/lib/design-plan/init.js +26 -121
  41. package/lib/design-plan/materialize.js +348 -451
  42. package/lib/design-plan/normalize.js +29 -74
  43. package/lib/design-plan/parallel.js +16 -71
  44. package/lib/design-plan/patch.js +16 -63
  45. package/lib/design-plan/preview.js +8 -6
  46. package/lib/design-plan/validate.js +11 -71
  47. package/lib/process/create-process.js +50 -84
  48. package/lib/samples/openyida-scaffold/canvas-form-drawer.canvas.jsx +90 -20
  49. package/lib/samples/openyida-scaffold/canvas-nav/content.jsx +13 -93
  50. package/lib/samples/openyida-scaffold/canvas-nav/data.jsx +18 -100
  51. package/lib/samples/openyida-scaffold/canvas-nav/mixed.jsx +4 -6
  52. package/lib/samples/openyida-scaffold/canvas-nav/shared.jsx +5 -5
  53. package/lib/samples/openyida-scaffold/canvas-nav/side.jsx +2 -1
  54. package/lib/samples/openyida-scaffold/canvas-nav/sidebar.jsx +1 -1
  55. package/lib/samples/openyida-scaffold/canvas-nav/top.jsx +9 -15
  56. package/lib/samples/yida-canvas-table-form/table-form-batch-submit.canvas.jsx +83 -9
  57. package/lib/samples/yida-rechart/trend-combo.canvas.jsx +87 -14
  58. package/package.json +1 -3
  59. package/scripts/postinstall.js +3 -3
  60. package/yida-skills/SKILL.md +16 -19
  61. package/yida-skills/references/official-example-schema-patterns.md +1 -1
  62. package/yida-skills/references/yida-api.md +0 -4
  63. package/yida-skills/skills/yida-app/SKILL.md +23 -46
  64. package/yida-skills/skills/yida-app/workflow/parallel-work.md +6 -15
  65. package/yida-skills/skills/yida-app/workflow/plan/step-1-understand.md +1 -1
  66. package/yida-skills/skills/yida-app/workflow/plan/step-2-confirm.md +1 -1
  67. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +15 -29
  68. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +11 -29
  69. package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +2 -4
  70. package/yida-skills/skills/yida-app/workflow/step-2-design.md +9 -23
  71. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -6
  72. package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +4 -15
  73. package/yida-skills/skills/yida-app/workflow/step-6-main-page.md +4 -6
  74. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +9 -15
  75. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +11 -25
  76. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +28 -68
  77. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +43 -42
  78. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-style-implementation-guide.md +44 -60
  79. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-theme-provider.md +50 -72
  80. package/yida-skills/skills/yida-canvas-custom-page/references/component-library-guide.md +1 -13
  81. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +1 -140
  82. package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +72 -101
  83. package/yida-skills/skills/yida-canvas-custom-page/references/page-generation-guide.md +32 -35
  84. package/yida-skills/skills/yida-canvas-custom-page/scripts/build-canvas-theme.js +3 -29
  85. package/yida-skills/skills/yida-canvas-custom-page/scripts/canvas-theme-provider.template.jsx +6 -69
  86. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +0 -26
  87. package/yida-skills/skills/yida-create-app/SKILL.md +0 -2
  88. package/yida-skills/skills/yida-create-form-page/SKILL.md +58 -72
  89. package/yida-skills/skills/yida-create-form-page/references/advanced-form-modes.md +5 -5
  90. package/yida-skills/skills/yida-create-form-page/references/association-form-field.md +4 -4
  91. package/yida-skills/skills/yida-create-form-page/references/batch-forms.md +5 -6
  92. package/yida-skills/skills/yida-create-form-page/references/field-definition-guide.md +19 -7
  93. package/yida-skills/skills/yida-create-form-page/references/form-field-properties.md +44 -62
  94. package/yida-skills/skills/yida-create-page/SKILL.md +6 -14
  95. package/yida-skills/skills/yida-create-process/SKILL.md +2 -13
  96. package/yida-skills/skills/yida-custom-page/SKILL.md +0 -2
  97. package/yida-skills/skills/yida-custom-page/references/design-system.md +1 -1
  98. package/yida-skills/skills/yida-dashboard/SKILL.md +4 -5
  99. package/yida-skills/skills/yida-data-management/SKILL.md +2 -4
  100. package/yida-skills/skills/yida-data-management/references/data-format-guide.md +4 -4
  101. package/yida-skills/skills/yida-design/SKILL.md +14 -29
  102. package/yida-skills/skills/yida-design/references/app/blueprint.md +70 -0
  103. package/yida-skills/skills/yida-design/references/app/navigation-patterns.md +43 -0
  104. package/yida-skills/skills/yida-design/references/app/role-journey.md +30 -0
  105. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +42 -67
  106. package/yida-skills/skills/yida-design/references/asset-workflow.md +2 -6
  107. package/yida-skills/skills/yida-design/references/design-mode.md +4 -4
  108. package/yida-skills/skills/yida-design/references/page-quality-gates.md +38 -26
  109. package/yida-skills/skills/yida-design/references/style-design-selection.md +137 -0
  110. package/yida-skills/skills/yida-design/references/style-designs/_design-md-template.md +185 -0
  111. package/yida-skills/skills/yida-design/references/style-designs/aqua-service-progress-dashboard.md +242 -0
  112. package/yida-skills/skills/yida-design/references/style-designs/blue-insight-operations-dashboard.md +243 -0
  113. package/yida-skills/skills/yida-design/references/style-designs/blue-productivity-insight-workbench.md +242 -0
  114. package/yida-skills/skills/yida-design/references/style-designs/command-filter-card-console.md +304 -0
  115. package/yida-skills/skills/yida-design/references/style-designs/contrast-command-analytics-workbench.md +261 -0
  116. package/yida-skills/skills/yida-design/references/style-designs/dark-stage-analytic-dashboard.md +369 -0
  117. package/yida-skills/skills/yida-design/references/style-designs/filterable-card-catalog.md +354 -0
  118. package/yida-skills/skills/yida-design/references/style-designs/green-timeline-progress-workbench.md +250 -0
  119. package/yida-skills/skills/yida-design/references/style-designs/registry.md +117 -0
  120. package/yida-skills/skills/yida-design/references/style-designs/soft-analytic-workbench.md +348 -0
  121. package/yida-skills/skills/yida-design/references/style-designs/soft-blue-grid-analytic-dashboard.md +658 -0
  122. package/yida-skills/skills/yida-design/references/style-designs/soft-bordered-analytic-workbench.md +385 -0
  123. package/yida-skills/skills/yida-design/references/style-designs/soft-curated-filter-gallery.md +300 -0
  124. package/yida-skills/skills/yida-design/references/style-designs/soft-modular-analytic-workbench.md +404 -0
  125. package/yida-skills/skills/yida-design/references/style-designs/soft-progress-analytics-workbench.md +382 -0
  126. package/yida-skills/skills/yida-design/references/style-designs/soft-timeline-analytics-workbench.md +367 -0
  127. package/yida-skills/skills/yida-design/references/style-designs/teal-rail-analytics-workbench.md +341 -0
  128. package/yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css +432 -350
  129. package/yida-skills/skills/yida-design/references/visual-decision-engine.md +143 -19
  130. package/yida-skills/skills/yida-design/references/visual-scaffold-recipes.md +27 -15
  131. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +0 -4
  132. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +8 -22
  133. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/README.md +10 -14
  134. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +4 -9
  135. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-compact-schema.md +27 -49
  136. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/build-plan-schema.md +154 -33
  137. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-design.md +127 -48
  138. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +51 -37
  139. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +48 -43
  140. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/validate_design_themes.py +190 -0
  141. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/README.md +98 -0
  142. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-media-grid.md +339 -0
  143. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-modular-clarity.md +348 -0
  144. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/airy-structured-clarity.md +344 -0
  145. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ambient-halo-layered.md +345 -0
  146. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-focus-layered.md +355 -0
  147. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/dark-luminous-modular.md +377 -0
  148. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/deep-stage-duotone.md +352 -0
  149. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/hairline-runway-clarity.md +389 -0
  150. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/high-contrast-modular.md +364 -0
  151. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/index.json +329 -0
  152. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/interlocked-vivid-modules.md +348 -0
  153. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/matrix-grid-focus.md +345 -0
  154. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/media-rail-inspector.md +379 -0
  155. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mist-layered-signal.md +347 -0
  156. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/modular-rail-signal.md +352 -0
  157. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/mono-grid-signal.md +386 -0
  158. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/ribbon-ledger-lift.md +384 -0
  159. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/segmented-meter-clarity.md +388 -0
  160. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/split-rail-precision.md +341 -0
  161. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/templates/design-themes/status-framed-media-grid.md +361 -0
  162. package/yida-skills/skills/yida-design/workflow/output-design.md +375 -117
  163. package/yida-skills/skills/yida-design/workflow/output-prd.md +185 -0
  164. package/yida-skills/skills/yida-design/workflow/step-1-read-brief.md +1 -1
  165. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +29 -33
  166. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +1 -3
  167. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +106 -22
  168. package/yida-skills/skills/yida-design/workflow/step-6-handoff.md +22 -15
  169. package/yida-skills/skills/yida-form-permission/SKILL.md +0 -6
  170. package/yida-skills/skills/yida-image-assets/SKILL.md +50 -73
  171. package/yida-skills/skills/yida-image-assets/references/manifest-contract.md +52 -82
  172. package/yida-skills/skills/yida-image-assets/references/source-policy.md +15 -14
  173. package/yida-skills/skills/yida-integration/SKILL.md +0 -1
  174. package/yida-skills/skills/yida-login/SKILL.md +0 -2
  175. package/yida-skills/skills/yida-nav-group/SKILL.md +2 -4
  176. package/yida-skills/skills/yida-nav-shell/SKILL.md +12 -36
  177. package/yida-skills/skills/yida-nav-shell/references/nav-shell-patterns.md +136 -79
  178. package/yida-skills/skills/yida-page-config/SKILL.md +4 -13
  179. package/yida-skills/skills/yida-prd/SKILL.md +3 -5
  180. package/yida-skills/skills/yida-prd/references/app/blueprint.md +4 -4
  181. package/yida-skills/skills/yida-prd/references/app/navigation-patterns.md +3 -6
  182. package/yida-skills/skills/yida-prd/references/app/role-journey.md +3 -5
  183. package/yida-skills/skills/yida-prd/references/plan-content.md +0 -4
  184. package/yida-skills/skills/yida-prd/workflow/output-prd.md +11 -18
  185. package/yida-skills/skills/yida-prd/workflow/plan-business.md +4 -4
  186. package/yida-skills/skills/yida-prd/workflow/step-2-information-architecture.md +8 -11
  187. package/yida-skills/skills/yida-publish-page/SKILL.md +1 -9
  188. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +39 -10
  189. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +71 -40
  190. package/yida-skills/skills-index.json +16 -36
  191. package/lib/app/application-entry-urls.js +0 -31
  192. package/lib/app/application-style.js +0 -69
  193. package/lib/app/canvas-icon-exports.json +0 -6160
  194. package/lib/app/canvas-icon-guard.js +0 -87
  195. package/lib/app/canvas-navigation-guard.js +0 -84
  196. package/lib/app/canvas-path-guard.js +0 -78
  197. package/lib/app/canvas-theme-guard.js +0 -184
  198. package/lib/app/form-data-response-guard.js +0 -57
  199. package/lib/asset/asset-execution.js +0 -79
  200. package/lib/core/css-structure.js +0 -37
  201. package/lib/design/check-design.js +0 -51
  202. package/lib/design/document.js +0 -458
  203. package/lib/design/icon-contrast.js +0 -85
  204. package/lib/design/page-content.js +0 -11
  205. package/lib/design-plan/artifact-updates.js +0 -117
  206. package/lib/design-plan/confirmation.js +0 -13
  207. package/lib/design-plan/entry-navigation.js +0 -97
  208. package/lib/design-plan/navigation-policy.js +0 -38
  209. package/lib/design-plan/rebase.js +0 -60
  210. package/lib/design-plan/themes.js +0 -101
  211. package/lib/design-plan/visual-policy.js +0 -42
  212. package/lib/samples/openyida-scaffold/canvas-admin-entry.jsx +0 -62
  213. package/lib/samples/openyida-scaffold/canvas-navigation.jsx +0 -56
  214. package/lib/samples/openyida-scaffold/canvas-view-state.jsx +0 -74
  215. package/yida-skills/skills/yida-app/references/entry-navigation.md +0 -193
  216. package/yida-skills/skills/yida-canvas-custom-page/references/view-state-recovery.md +0 -52
  217. package/yida-skills/skills/yida-canvas-data-binding/references/business-action-permissions.md +0 -31
  218. package/yida-skills/skills/yida-design/references/application-style-library.md +0 -93
  219. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +0 -150
  220. package/yida-skills/skills/yida-design/references/native-form-styles.md +0 -208
  221. package/yida-skills/skills/yida-design/references/navigation-decision.md +0 -53
  222. package/yida-skills/skills/yida-design/references/page-continuity.md +0 -42
  223. package/yida-skills/skills/yida-design/references/theme/application-style-recipes.css +0 -58
  224. package/yida-skills/skills/yida-design/references/theme-selection.md +0 -67
  225. package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +0 -302
  226. package/yida-skills/skills/yida-design/templates/application-styles.json +0 -398
  227. package/yida-skills/skills/yida-design/templates/design-themes/README.md +0 -73
  228. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/app_theme.css +0 -748
  229. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/design.md +0 -274
  230. package/yida-skills/skills/yida-design/templates/design-themes/app-amber/form-layout.json +0 -15
  231. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +0 -748
  232. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +0 -274
  233. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/form-layout.json +0 -14
  234. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +0 -748
  235. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +0 -274
  236. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/form-layout.json +0 -13
  237. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +0 -748
  238. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +0 -274
  239. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/form-layout.json +0 -14
  240. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +0 -748
  241. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +0 -274
  242. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/form-layout.json +0 -14
  243. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +0 -748
  244. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +0 -274
  245. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/form-layout.json +0 -14
  246. package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +0 -748
  247. package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +0 -274
  248. package/yida-skills/skills/yida-design/templates/design-themes/app-line/form-layout.json +0 -13
  249. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +0 -748
  250. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +0 -274
  251. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/form-layout.json +0 -14
  252. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +0 -754
  253. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +0 -279
  254. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/form-layout.json +0 -13
  255. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +0 -748
  256. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +0 -274
  257. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/form-layout.json +0 -13
  258. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +0 -748
  259. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +0 -274
  260. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/form-layout.json +0 -14
  261. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +0 -748
  262. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +0 -274
  263. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/form-layout.json +0 -14
  264. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +0 -758
  265. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +0 -281
  266. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/form-layout.json +0 -14
  267. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +0 -748
  268. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +0 -274
  269. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/form-layout.json +0 -14
  270. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +0 -748
  271. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +0 -274
  272. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/form-layout.json +0 -14
  273. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +0 -754
  274. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +0 -279
  275. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/form-layout.json +0 -14
  276. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +0 -752
  277. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +0 -278
  278. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/form-layout.json +0 -13
  279. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +0 -748
  280. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +0 -274
  281. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/form-layout.json +0 -14
  282. package/yida-skills/skills/yida-design/templates/design-themes/basic-tokens.json +0 -118
  283. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +0 -690
  284. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +0 -382
  285. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/form-layout.json +0 -13
  286. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +0 -691
  287. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +0 -380
  288. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/form-layout.json +0 -13
  289. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/app_theme.css +0 -696
  290. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/design.md +0 -378
  291. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-signal-panels/form-layout.json +0 -13
  292. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +0 -689
  293. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +0 -238
  294. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/form-layout.json +0 -13
  295. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/app_theme.css +0 -692
  296. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/design.md +0 -375
  297. package/yida-skills/skills/yida-design/templates/design-themes/graphite-bevel-grid/form-layout.json +0 -13
  298. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/app_theme.css +0 -691
  299. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/design.md +0 -362
  300. package/yida-skills/skills/yida-design/templates/design-themes/hairline-shared-bands/form-layout.json +0 -13
  301. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +0 -691
  302. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +0 -409
  303. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/form-layout.json +0 -13
  304. package/yida-skills/skills/yida-design/templates/design-themes/index.json +0 -464
  305. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +0 -701
  306. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +0 -379
  307. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/form-layout.json +0 -13
  308. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +0 -691
  309. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +0 -404
  310. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/form-layout.json +0 -13
  311. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +0 -698
  312. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +0 -389
  313. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/form-layout.json +0 -13
  314. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +0 -691
  315. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +0 -372
  316. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/form-layout.json +0 -13
  317. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +0 -690
  318. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +0 -455
  319. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/form-layout.json +0 -13
  320. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +0 -698
  321. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +0 -394
  322. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/form-layout.json +0 -13
  323. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/app_theme.css +0 -692
  324. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/design.md +0 -391
  325. package/yida-skills/skills/yida-design/templates/design-themes/soft-spectral-panels/form-layout.json +0 -13
  326. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +0 -711
  327. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +0 -408
  328. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/form-layout.json +0 -13
  329. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +0 -692
  330. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +0 -383
  331. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/form-layout.json +0 -13
  332. package/yida-skills/skills/yida-design/templates/navigation-styles.json +0 -769
  333. package/yida-skills/skills/yida-requirement-analysis/references/experience-groups.md +0 -79
  334. 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 theme = loadThemeIndex().themes.find(item => item.themeId === selectedTheme.themeId);
69
- const navigationStyle = {
70
- ...(forUser.navigationStyle || {}),
71
- ...(theme && theme.mode !== 'creative' ? { tone: themeNavTheme(theme), toneSource: 'theme_derived' } : {}),
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', 'entryRecommendation'];
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', 'navigation', 'contentBlocks', 'dataSources', 'dataBinding', 'emptyReason', 'primaryAction', 'themeSummary', 'designFile', 'designRefs'];
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 scene = sceneForPage(page);
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 || supplied.navigation ? 'standalone' : 'platform-shell');
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
- const contentBlocks = supplied.contentBlocks || page.blocks;
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 pageNavigationRows(page, plan) {
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: '复用原生表单时,PC 使用 50vw 抽屉,移动端整页打开;前台全码填写按页面交互设计接入真实数据,提交后刷新结果',
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: pageHandoffs,
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 = loadThemeIndex();
513
+ const index = readJson(THEME_INDEX_PATH, '主题索引');
491
514
  const theme = (index.themes || []).find(item => item.themeId === selected.themeId);
492
- if (!theme || (theme.templatePath !== selected.templatePath && (!theme.legacyTemplatePath || theme.legacyTemplatePath !== selected.templatePath))) {
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 = themeTemplatePath(theme);
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 pageAnchor(pageId) {
504
- return `page-${Buffer.from(String(pageId), 'utf8').toString('hex')}`;
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
- `- 业务领域:${text(plan.meta?.businessDomain)}`,
514
- `- 产品形态:${text(plan.meta?.experienceTopology)}`,
515
- `- 主题应用与前后台边界:${text(plan.visualStyle?.forDesignMd?.productTopologyApplication)}`,
516
- `- 已确认视觉约束:${text(plan.visualStyle?.constraints, '无额外约束')}`,
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 renderNavigationDesign(plan, navigationStyle, tokens) {
521
- const roles = {
522
- '--pod-shell-theme-bg-color': '导航背景', '--pod-nav-item-text-color': '普通文字',
523
- '--pod-nav-item-text-hover-color': '悬停文字', '--pod-nav-item-text-selected-color': '选中文字',
524
- '--pod-nav-menu-bg-hover-color': '悬停背景', '--pod-nav-menu-bg-selected-color': '选中背景',
525
- '--pod-page-header-bg-color': '页头背景', '--pod-nav-logo-text': '应用名称',
526
- '--pod-nav-l-group-label-color': '分组文字', '--pod-nav-sub-divider-color': '分组边界',
527
- '--pod-nav-search-bg-color': '搜索背景', '--pod-nav-search-text-color': '搜索文字',
528
- '--pod-nav-popup-bg-color': '弹层背景', '--pod-nav-popup-border-radius': '弹层圆角',
529
- '--pod-nav-menu-item-height': '菜单高度', '--pod-nav-menu-item-radius': '菜单圆角',
530
- '--pod-nav-menu-item-border': '普通边框', '--pod-nav-menu-item-hover-border': '悬停边框',
531
- '--pod-nav-menu-item-selected-border': '选中边框', '--pod-nav-menu-item-padding': '菜单内距',
532
- '--pod-nav-menu-font-size': '菜单字号', '--pod-nav-menu-line-height': '菜单文字行高', '--pod-nav-menu-gap': '菜单间距',
533
- '--pod-nav-menu-item-selected-shadow': '选中项阴影',
534
- '--pod-nav-slide-aside-gap': '侧栏分区间距', '--pod-nav-slide-aside-padding': '侧栏内距',
535
- '--pod-nav-platform-header-height': '顶部框架高度', '--pod-nav-top-header-padding': '顶栏内距',
536
- '--pod-nav-top-tab-height': '顶部菜单高度', '--pod-nav-top-tab-item-padding': '顶部菜单内距',
537
- '--pod-nav-top-tab-item-max-width': '顶部菜单最大宽度',
538
- '--pod-nav-l-header-padding': 'L 型顶栏内距', '--pod-nav-l-aside-padding': 'L 型侧栏内距',
539
- '--pod-shell-lshape-border-radius': '框架圆角',
540
- };
541
- // Layout reasons can come from an older visual choice. Keep their business
542
- // clauses while stating the current navigation tone separately.
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(plan, tokens) {
564
- const applications = plan.visualStyle?.forUser?.pageApplications || [];
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
- `${text(memory.name)}用于${text(memory.target)}(${text(memory.reason)})`).join(';');
570
- const checks = Array.isArray(application.acceptanceChecks) ? application.acceptanceChecks : [];
576
+ ` - ${text(memory.name)}:${text(memory.target)}(${text(memory.renderPolicy)};${text(memory.reason)})`
577
+ );
571
578
  return [
572
- `<a id="${pageAnchor(page.pageId)}"></a>`,
573
- `### ${text(page.name)}`, '',
574
- `- 页面任务:${text(page.primaryTask)}。内容覆盖:${text(page.contentRichness?.contentLayers || page.blocks)}`,
575
- `- 首屏焦点:${text(application.firstScreenFocus, '草稿待补充')}`,
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
- `- 响应式:${text(application.responsive, '草稿待补充')}`,
581
- `- 验收:${checks.length ? checks.join(';') : '草稿待补充'}`,
585
+ '- 视觉记忆点:',
586
+ ...(memories.length > 0 ? memories : [' - 无匹配内容,不虚构视觉组件']),
582
587
  ].join('\n');
583
- }).join('\n\n') || '本项目使用原生表单与平台页面,没有自定义页面设计。';
588
+ }).join('\n\n') || '- 无自定义页面';
584
589
  }
585
590
 
586
- function themeImplementationRules(chapter) {
587
- const headings = [...chapter.matchAll(/^### ([^\n]+)\n/gm)];
588
- const result = [];
589
- for (const [index, heading] of headings.entries()) {
590
- if (heading[1] === '项目页面设计') {continue;}
591
- const content = chapter.slice(heading.index + heading[0].length, headings[index + 1]?.index).trim();
592
- if (heading[1] === '页面设计验收') {
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(body) {
637
- const match = /^## 3\. 基础组件表达\n([\s\S]*?)(?=^## |$(?![\s\S]))/m.exec(body);
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
- if (!match) {return { body, components, states };}
641
- const section = match[1];
642
- const start = match.index + match[0].indexOf(section);
643
- const headings = [...section.matchAll(/^### (.+)\n/gm)];
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
- update(groups);
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, options = {}) {
694
- const { theme, templatePath } = resolveTheme(plan);
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', { code: 'DESIGN_PLAN_INVALID_NAVIGATION_STRUCTURE' });
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', { code: 'DESIGN_PLAN_INVALID_NAVIGATION_TONE' });
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', details: { primaryColor: color.primaryColor },
638
+ code: 'DESIGN_PLAN_INVALID_PRIMARY_COLOR',
639
+ details: { primaryColor: color.primaryColor },
708
640
  });
709
641
  }
710
- // Parse template metadata before inserting user text; YAML quoting belongs to
711
- // the common serializer, not string interpolation inside template quotes.
712
- const source = resolveThemeColors(fs.readFileSync(templatePath, 'utf8').replace(/\{\{PRIMARY_COLOR\}\}/g, primaryColor));
713
- const parsed = parseDesignDocument(source);
714
- const themeTokens = readDesignTokens(source);
715
- const explicitTokens = visual.tokens || {};
716
- if (theme.mode === 'creative') {
717
- require('../app/application-style').validateCreativeDirection(visual.creativeDirection, explicitTokens);
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
- if (theme.mode !== 'creative' && themeNavTheme(theme) !== navigationStyle.tone) {
723
- throw new CliError('navigationStyle.tone 必须由所选主题模板的 navTheme 派生', {
724
- code: 'DESIGN_PLAN_NAVIGATION_TONE_MISMATCH',
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
- overrideTokens(parsed.metadata.tokens, overrides);
735
- const tokens = { ...themeTokens, ...overrides };
736
- const name = text(meta.appName || meta.projectName, 'OpenYida 应用');
737
- const colorSource = color.source || forUser.styleSource || selected.source || 'AI 推断';
738
- const headings = [...parsed.body.matchAll(/^## ([1-5])\. [^\n]+\n/gm)];
739
- const chapters = headings.map((heading, index) => parsed.body.slice(heading.index, headings[index + 1]?.index).trim());
740
- if (chapters.length !== 5) {
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
- const themeGuidance = fs.readFileSync(THEME_CONSISTENCY_PATH, 'utf8');
763
- for (const section of ['指标卡与按钮配色', '分组标题与分割线']) {
764
- const guidance = themeGuidance.match(new RegExp(`(?:^|\\n)(## ${section}\\n[\\s\\S]*?)(?=\\n## |$)`))?.[1];
765
- if (!guidance) {
766
- throw new CliError(t('design_document.invalid', THEME_CONSISTENCY_PATH, '## ' + section), {
767
- code: 'DESIGN_PLAN_THEME_CONTEXT_MISSING', details: { section },
768
- });
769
- }
770
- chapters[1] += '\n\n' + guidance.trim().replace(/^## /, '### ');
771
- }
772
- chapters[2] += '\n\n### 图标与相邻底色\n\n图标前景和底盒分别取色,按上述配色规则检查对比度。颜色配对记录在 iconSystem.colorPairs,页面按同一变量实现,并检查颜色继承、描边和透明度。';
773
- if (plan.pages?.customPageDetails?.length) {
774
- chapters[1] += '\n\n' + fs.readFileSync(PAGE_CONTINUITY_PATH, 'utf8').trim().replace(/^(#{1,5}) /gm, '#$1 ');
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 key = page.sceneKey || (options.draft ? sceneForPage(page) : pageHandoff(page, plan).scene);
784
- scenes[key] = scenes[key] || { pages: [] };
785
- scenes[key].pages.push({ pageId: page.pageId, anchor: `#${pageAnchor(page.pageId)}` });
786
- }
787
- const metadata = {
788
- schemaVersion: '1.0', name, description: parsed.metadata.description,
789
- ...(theme.collection === 'application-styles' ? { applicationStyle: { recipe: 'application-style-v1', mode: theme.mode } } : {}),
790
- ...(theme.mode === 'creative' ? { creativeDirection: visual.creativeDirection } : {}),
791
- buildPlanRevision: text(meta.revision, 'unversioned'), tokens: parsed.metadata.tokens,
792
- themeProfile: {
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, metadata),
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
- const output = serializeDesignDocument(metadata, body);
810
- const unresolved = output.match(/\{\{[^}]+\}\}|<[^>]*(?:实际色值|生成期标记)[^>]*>|"--[^"]+"\s*:\s*"AI 根据[^"]*生成[^"]*"/g);
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', details: { unresolved: [...new Set(unresolved)] },
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.tone },
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, options = {}) {
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 designFile = path.resolve(outputDir, 'design.md');
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, { outputDir }));
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
- const baseTheme = measure('themeValidationMs', () => applyDesignTokens(fs.readFileSync(path.join(PACKAGE_ROOT, 'yida-skills/skills/yida-design/references/theme/app-custom-theme-template.css'), 'utf8'), design));
883
- if (options.renderHtml === false && authoringIssues.length) {
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 !== undefined ? applyDesignTokens(existingCss, design, previousDesign) : baseTheme);
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, assetTasks,
900
- themeInput: { css: existingCss, design: previousDesign },
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, { input, rebaseParts: options.rebaseParts }) : source;
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
- const savedPreview = fs.existsSync(previewState) ? readJson(previewState) : null;
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
- const updates = prepareArtifactUpdate(artifacts, outputDir);
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 && updates.files.some(([file]) => [options.businessFile, options.visualFile].some(part => path.resolve(part) === path.resolve(file)))) {
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`], ...updates.files] : updates.files);
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, updated: options.check ? [] : updates.updated, assetTasks: artifacts.assetTasks, timings,
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: (plan, options) => renderDesign(normalizePlan(plan), options),
892
+ renderDesign: plan => renderDesign(normalizePlan(plan)),
996
893
  renderPrd: source => {
997
894
  const plan = normalizePlan(source);
998
895
  renderDesign(plan);