@bovinphang/frontend-craft 2.7.0 → 2.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +34 -0
  4. package/CHANGELOG.zh-CN.md +34 -0
  5. package/README.md +1 -1
  6. package/README.openclaw.md +99 -43
  7. package/README.openclaw.zh-CN.md +99 -43
  8. package/README.zh-CN.md +1 -1
  9. package/agents/fec-architect.md +70 -70
  10. package/agents/fec-code-reviewer.md +82 -82
  11. package/agents/fec-debugger.md +20 -20
  12. package/agents/fec-design-token-mapper.md +36 -36
  13. package/agents/fec-doc-updater.md +13 -13
  14. package/agents/fec-e2e-runner.md +45 -45
  15. package/agents/fec-figma-implementer.md +59 -59
  16. package/agents/fec-performance-optimizer.md +128 -128
  17. package/agents/fec-refactor-cleaner.md +14 -14
  18. package/agents/fec-security-reviewer.md +66 -67
  19. package/agents/fec-test-planner.md +27 -27
  20. package/agents/fec-typescript-reviewer.md +87 -87
  21. package/agents/fec-ui-checker.md +74 -74
  22. package/commands/fec-debug.md +19 -19
  23. package/commands/fec-doc-sync.md +11 -11
  24. package/commands/fec-init.md +37 -38
  25. package/commands/fec-plan.md +58 -58
  26. package/commands/fec-refactor-clean.md +11 -11
  27. package/commands/fec-review.md +41 -41
  28. package/commands/fec-scaffold.md +41 -41
  29. package/commands/fec-tdd.md +13 -13
  30. package/dist/bin/frontend-craft.js +29 -27
  31. package/docs/example-prompts.md +3 -0
  32. package/docs/ja-JP/README.md +1 -1
  33. package/docs/ko-KR/README.md +1 -1
  34. package/docs/project-structure.md +13 -5
  35. package/docs/zh-CN/example-prompts.md +3 -0
  36. package/docs/zh-CN/project-structure.md +6 -1
  37. package/docs/zh-TW/README.md +1 -1
  38. package/localized/zh-CN/agents/fec-architect.md +125 -0
  39. package/localized/zh-CN/agents/fec-code-reviewer.md +152 -0
  40. package/localized/zh-CN/agents/fec-debugger.md +44 -0
  41. package/localized/zh-CN/agents/fec-design-token-mapper.md +75 -0
  42. package/localized/zh-CN/agents/fec-doc-updater.md +30 -0
  43. package/localized/zh-CN/agents/fec-e2e-runner.md +107 -0
  44. package/localized/zh-CN/agents/fec-figma-implementer.md +104 -0
  45. package/localized/zh-CN/agents/fec-performance-optimizer.md +293 -0
  46. package/localized/zh-CN/agents/fec-refactor-cleaner.md +32 -0
  47. package/localized/zh-CN/agents/fec-security-reviewer.md +116 -0
  48. package/localized/zh-CN/agents/fec-test-planner.md +50 -0
  49. package/localized/zh-CN/agents/fec-typescript-reviewer.md +142 -0
  50. package/localized/zh-CN/agents/fec-ui-checker.md +120 -0
  51. package/localized/zh-CN/commands/fec-debug.md +30 -0
  52. package/localized/zh-CN/commands/fec-doc-sync.md +19 -0
  53. package/localized/zh-CN/commands/fec-init.md +70 -0
  54. package/localized/zh-CN/commands/fec-plan.md +94 -0
  55. package/localized/zh-CN/commands/fec-refactor-clean.md +19 -0
  56. package/localized/zh-CN/commands/fec-review.md +66 -0
  57. package/localized/zh-CN/commands/fec-scaffold.md +205 -0
  58. package/localized/zh-CN/commands/fec-tdd.md +21 -0
  59. package/localized/zh-CN/skills/TEMPLATE.md +155 -0
  60. package/localized/zh-CN/skills/eval_queries.json +1013 -0
  61. package/localized/zh-CN/skills/fec-accessibility-check/SKILL.md +39 -0
  62. package/localized/zh-CN/skills/fec-accessibility-check/references/report-template.md +23 -0
  63. package/localized/zh-CN/skills/fec-accessibility-check/references/screen-reader-testing.md +33 -0
  64. package/localized/zh-CN/skills/fec-alchemy/SKILL.md +74 -0
  65. package/localized/zh-CN/skills/fec-alchemy/assets/absorption-plan-template.md +47 -0
  66. package/localized/zh-CN/skills/fec-alchemy/assets/intake-template.md +55 -0
  67. package/localized/zh-CN/skills/fec-alchemy/assets/review-checklist.md +31 -0
  68. package/localized/zh-CN/skills/fec-alchemy/references/methodology.md +94 -0
  69. package/localized/zh-CN/skills/fec-alchemy/references/originality-and-licensing.md +57 -0
  70. package/localized/zh-CN/skills/fec-api-integration/SKILL.md +78 -0
  71. package/localized/zh-CN/skills/fec-api-integration/references/integration-patterns.md +80 -0
  72. package/localized/zh-CN/skills/fec-backend-requirements-handoff/SKILL.md +45 -0
  73. package/localized/zh-CN/skills/fec-browser-storage/SKILL.md +34 -0
  74. package/localized/zh-CN/skills/fec-browser-storage/references/storage-patterns.md +65 -0
  75. package/localized/zh-CN/skills/fec-canvas-threejs/SKILL.md +66 -0
  76. package/localized/zh-CN/skills/fec-canvas-threejs/references/rendering-patterns.md +67 -0
  77. package/localized/zh-CN/skills/fec-canvas-threejs/references/shader-webgl-patterns.md +67 -0
  78. package/localized/zh-CN/skills/fec-code-review/SKILL.md +143 -0
  79. package/localized/zh-CN/skills/fec-code-review/references/report-template.md +29 -0
  80. package/localized/zh-CN/skills/fec-component-testing/SKILL.md +119 -0
  81. package/localized/zh-CN/skills/fec-data-fetching/SKILL.md +62 -0
  82. package/localized/zh-CN/skills/fec-data-fetching/references/query-patterns.md +103 -0
  83. package/localized/zh-CN/skills/fec-debug-framework/SKILL.md +157 -0
  84. package/localized/zh-CN/skills/fec-debug-framework/references/report-template.md +58 -0
  85. package/localized/zh-CN/skills/fec-dependency-upgrade/SKILL.md +34 -0
  86. package/localized/zh-CN/skills/fec-dependency-upgrade/references/dependency-upgrade-workflow.md +56 -0
  87. package/localized/zh-CN/skills/fec-doc-sync/SKILL.md +39 -0
  88. package/localized/zh-CN/skills/fec-drawio-studio/SKILL.md +73 -0
  89. package/localized/zh-CN/skills/fec-drawio-studio/data/THIRD_PARTY_NOTICES.md +10 -0
  90. package/localized/zh-CN/skills/fec-drawio-studio/data/brand-icons.json +878 -0
  91. package/localized/zh-CN/skills/fec-drawio-studio/data/shape-index.json +83570 -0
  92. package/localized/zh-CN/skills/fec-drawio-studio/references/data-residency.md +27 -0
  93. package/localized/zh-CN/skills/fec-drawio-studio/references/diagram-patterns.md +60 -0
  94. package/localized/zh-CN/skills/fec-drawio-studio/references/flowchart-quality.md +49 -0
  95. package/localized/zh-CN/skills/fec-drawio-studio/references/layout-and-export.md +72 -0
  96. package/localized/zh-CN/skills/fec-drawio-studio/references/troubleshooting.md +29 -0
  97. package/localized/zh-CN/skills/fec-drawio-studio/references/xml-and-mermaid.md +58 -0
  98. package/localized/zh-CN/skills/fec-drawio-studio/scripts/brand-symbols.mjs +99 -0
  99. package/localized/zh-CN/skills/fec-drawio-studio/scripts/diagram-lint.mjs +205 -0
  100. package/localized/zh-CN/skills/fec-drawio-studio/scripts/diagram-url.mjs +102 -0
  101. package/localized/zh-CN/skills/fec-drawio-studio/scripts/drawio-export.mjs +134 -0
  102. package/localized/zh-CN/skills/fec-drawio-studio/scripts/layout-graph.mjs +107 -0
  103. package/localized/zh-CN/skills/fec-drawio-studio/scripts/png-embed-fix.mjs +26 -0
  104. package/localized/zh-CN/skills/fec-drawio-studio/scripts/scan-go-packages.mjs +36 -0
  105. package/localized/zh-CN/skills/fec-drawio-studio/scripts/scan-js-modules.mjs +52 -0
  106. package/localized/zh-CN/skills/fec-drawio-studio/scripts/scan-python-classes.mjs +30 -0
  107. package/localized/zh-CN/skills/fec-drawio-studio/scripts/scan-python-modules.mjs +55 -0
  108. package/localized/zh-CN/skills/fec-drawio-studio/scripts/scan-rust-modules.mjs +35 -0
  109. package/localized/zh-CN/skills/fec-drawio-studio/scripts/scan-ts-modules.mjs +7 -0
  110. package/localized/zh-CN/skills/fec-drawio-studio/scripts/shape-query.mjs +121 -0
  111. package/localized/zh-CN/skills/fec-drawio-studio/scripts/studio-core.mjs +139 -0
  112. package/localized/zh-CN/skills/fec-drawio-studio/styles/built-in/corporate.json +49 -0
  113. package/localized/zh-CN/skills/fec-drawio-studio/styles/built-in/default.json +49 -0
  114. package/localized/zh-CN/skills/fec-drawio-studio/styles/built-in/handdrawn.json +49 -0
  115. package/localized/zh-CN/skills/fec-drawio-studio/styles/schema.json +112 -0
  116. package/localized/zh-CN/skills/fec-e2e-testing/SKILL.md +42 -0
  117. package/localized/zh-CN/skills/fec-e2e-testing/references/e2e-ci-reporting.md +62 -0
  118. package/localized/zh-CN/skills/fec-e2e-testing/references/e2e-special-scenarios.md +52 -0
  119. package/localized/zh-CN/skills/fec-e2e-testing/references/e2e-visual-regression.md +60 -0
  120. package/localized/zh-CN/skills/fec-e2e-testing/references/playwright-patterns.md +95 -0
  121. package/localized/zh-CN/skills/fec-form-handling/SKILL.md +76 -0
  122. package/localized/zh-CN/skills/fec-form-handling/references/advanced-form-patterns.md +101 -0
  123. package/localized/zh-CN/skills/fec-image-generation/SKILL.md +80 -0
  124. package/localized/zh-CN/skills/fec-image-generation/assets/interactive-diagram.html +371 -0
  125. package/localized/zh-CN/skills/fec-image-generation/references/artifact-routing.md +80 -0
  126. package/localized/zh-CN/skills/fec-image-generation/references/diagram-workflows.md +74 -0
  127. package/localized/zh-CN/skills/fec-image-generation/references/html-technical-diagrams.md +236 -0
  128. package/localized/zh-CN/skills/fec-image-generation/references/png-qa-autofix.md +30 -0
  129. package/localized/zh-CN/skills/fec-image-generation/scripts/export-diagram.mjs +364 -0
  130. package/localized/zh-CN/skills/fec-image-generation/scripts/interactive-diagram-server.mjs +654 -0
  131. package/localized/zh-CN/skills/fec-image-generation/scripts/png-qa.mjs +431 -0
  132. package/localized/zh-CN/skills/fec-image-generation/scripts/tech-diagram-render.mjs +1397 -0
  133. package/localized/zh-CN/skills/fec-implement-from-design/SKILL.md +129 -0
  134. package/localized/zh-CN/skills/fec-implement-from-design/references/design-plan-template.md +36 -0
  135. package/localized/zh-CN/skills/fec-legacy-to-modern-migration/SKILL.md +46 -0
  136. package/localized/zh-CN/skills/fec-legacy-to-modern-migration/references/migration-execution-checklist.md +110 -0
  137. package/localized/zh-CN/skills/fec-legacy-to-modern-migration/references/migration-report-template.md +41 -0
  138. package/localized/zh-CN/skills/fec-legacy-to-modern-migration/references/migration-strategy-and-mapping.md +48 -0
  139. package/localized/zh-CN/skills/fec-legacy-web-standard/SKILL.md +35 -0
  140. package/localized/zh-CN/skills/fec-legacy-web-standard/references/legacy-patterns.md +85 -0
  141. package/localized/zh-CN/skills/fec-list-virtualization/SKILL.md +34 -0
  142. package/localized/zh-CN/skills/fec-list-virtualization/references/virtualization-patterns.md +80 -0
  143. package/localized/zh-CN/skills/fec-monorepo-project-standard/SKILL.md +142 -0
  144. package/localized/zh-CN/skills/fec-motion-interaction/SKILL.md +73 -0
  145. package/localized/zh-CN/skills/fec-motion-interaction/references/motion-patterns.md +112 -0
  146. package/localized/zh-CN/skills/fec-nextjs-project-standard/SKILL.md +123 -0
  147. package/localized/zh-CN/skills/fec-nuxt-project-standard/SKILL.md +122 -0
  148. package/localized/zh-CN/skills/fec-performance-optimization/SKILL.md +80 -0
  149. package/localized/zh-CN/skills/fec-performance-optimization/references/framework-performance-patterns.md +38 -0
  150. package/localized/zh-CN/skills/fec-performance-optimization/references/report-template.md +59 -0
  151. package/localized/zh-CN/skills/fec-pwa-implementation/SKILL.md +35 -0
  152. package/localized/zh-CN/skills/fec-pwa-implementation/references/manifest-and-icons.md +53 -0
  153. package/localized/zh-CN/skills/fec-pwa-implementation/references/service-worker-workbox.md +68 -0
  154. package/localized/zh-CN/skills/fec-react-project-standard/SKILL.md +91 -0
  155. package/localized/zh-CN/skills/fec-react-project-standard/references/react-performance-patterns.md +35 -0
  156. package/localized/zh-CN/skills/fec-react-project-standard/references/react-project-structure.md +197 -0
  157. package/localized/zh-CN/skills/fec-react-project-standard/references/react-quality-patterns.md +131 -0
  158. package/localized/zh-CN/skills/fec-react-project-standard/references/react-runtime-patterns.md +293 -0
  159. package/localized/zh-CN/skills/fec-refactor-clean/SKILL.md +51 -0
  160. package/localized/zh-CN/skills/fec-refactor-clean/references/report-template.md +42 -0
  161. package/localized/zh-CN/skills/fec-responsive-layout/SKILL.md +34 -0
  162. package/localized/zh-CN/skills/fec-responsive-layout/references/responsive-layout-patterns.md +70 -0
  163. package/localized/zh-CN/skills/fec-route-protection/SKILL.md +135 -0
  164. package/localized/zh-CN/skills/fec-security-review/SKILL.md +38 -0
  165. package/localized/zh-CN/skills/fec-security-review/references/report-template.md +27 -0
  166. package/localized/zh-CN/skills/fec-security-review/references/security-checklist.md +64 -0
  167. package/localized/zh-CN/skills/fec-source-driven-development/SKILL.md +87 -0
  168. package/localized/zh-CN/skills/fec-state-management/SKILL.md +140 -0
  169. package/localized/zh-CN/skills/fec-state-management/references/state-patterns.md +109 -0
  170. package/localized/zh-CN/skills/fec-storybook-component-doc/SKILL.md +37 -0
  171. package/localized/zh-CN/skills/fec-storybook-component-doc/references/story-patterns.md +118 -0
  172. package/localized/zh-CN/skills/fec-svg-animation/SKILL.md +148 -0
  173. package/localized/zh-CN/skills/fec-tailwind-design-system/SKILL.md +34 -0
  174. package/localized/zh-CN/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +59 -0
  175. package/localized/zh-CN/skills/fec-tdd-workflow/SKILL.md +52 -0
  176. package/localized/zh-CN/skills/fec-testing-strategy/SKILL.md +45 -0
  177. package/localized/zh-CN/skills/fec-typescript-project-standard/SKILL.md +54 -0
  178. package/localized/zh-CN/skills/fec-typescript-project-standard/references/type-safety-patterns.md +102 -0
  179. package/localized/zh-CN/skills/fec-typescript-project-standard/references/type-safety.md +148 -0
  180. package/localized/zh-CN/skills/fec-ui-design/SKILL.md +108 -0
  181. package/localized/zh-CN/skills/fec-ui-design/data/product-rules.json +231 -0
  182. package/localized/zh-CN/skills/fec-ui-design/data/stack-ui-rules.json +146 -0
  183. package/localized/zh-CN/skills/fec-ui-design/data/style-archetypes.json +373 -0
  184. package/localized/zh-CN/skills/fec-ui-design/data/ux-quality-rules.json +203 -0
  185. package/localized/zh-CN/skills/fec-ui-design/references/design-intelligence.md +97 -0
  186. package/localized/zh-CN/skills/fec-ui-design/references/master-page-overrides.md +30 -0
  187. package/localized/zh-CN/skills/fec-ui-design/references/pre-delivery-checklist.md +39 -0
  188. package/localized/zh-CN/skills/fec-ui-design/scripts/design-system.mjs +648 -0
  189. package/localized/zh-CN/skills/fec-validation-fix/SKILL.md +51 -0
  190. package/localized/zh-CN/skills/fec-validation-fix/references/report-template.md +43 -0
  191. package/localized/zh-CN/skills/fec-vite-project-standard/SKILL.md +83 -0
  192. package/localized/zh-CN/skills/fec-vue3-project-standard/SKILL.md +58 -0
  193. package/localized/zh-CN/skills/fec-vue3-project-standard/references/vue3-composables-routing.md +162 -0
  194. package/localized/zh-CN/skills/fec-vue3-project-standard/references/vue3-performance-patterns.md +13 -0
  195. package/localized/zh-CN/skills/fec-vue3-project-standard/references/vue3-project-structure.md +176 -0
  196. package/localized/zh-CN/skills/fec-vue3-project-standard/references/vue3-state-api-quality.md +216 -0
  197. package/localized/zh-CN/skills/fec-web-video-presentation/SKILL.md +61 -0
  198. package/localized/zh-CN/skills/fec-web-video-presentation/data/starter-themes.json +59 -0
  199. package/localized/zh-CN/skills/fec-web-video-presentation/references/chapter-craft.md +37 -0
  200. package/localized/zh-CN/skills/fec-web-video-presentation/references/content-plan.md +38 -0
  201. package/localized/zh-CN/skills/fec-web-video-presentation/references/recording-and-audio.md +33 -0
  202. package/localized/zh-CN/skills/fec-web-video-presentation/references/theme-system.md +36 -0
  203. package/localized/zh-CN/skills/fec-web-video-presentation/scripts/create-video-presentation.mjs +430 -0
  204. package/localized/zh-CN/skills/fec-web-workers/SKILL.md +35 -0
  205. package/localized/zh-CN/skills/fec-web-workers/references/advanced-workers.md +54 -0
  206. package/localized/zh-CN/skills/fec-web-workers/references/basic-worker.md +67 -0
  207. package/localized/zh-CN/skills/metadata.json +1646 -0
  208. package/localized/zh-CN/skills/relations.json +755 -0
  209. package/localized/zh-CN/templates/claude/CLAUDE.md +106 -0
  210. package/localized/zh-CN/templates/claude/settings.json +62 -0
  211. package/localized/zh-CN/templates/codex/AGENTS.md +105 -0
  212. package/localized/zh-CN/templates/codex/config.toml +32 -0
  213. package/localized/zh-CN/templates/openclaw/AGENTS.md +101 -0
  214. package/localized/zh-CN/templates/openclaw/OPENCLAW-CONFIG.md +12 -0
  215. package/localized/zh-CN/templates/shared/rules/fec-agent-workflow.md +54 -0
  216. package/localized/zh-CN/templates/shared/rules/fec-api-layer.md +99 -0
  217. package/localized/zh-CN/templates/shared/rules/fec-ci-cd.md +40 -0
  218. package/localized/zh-CN/templates/shared/rules/fec-code-comments.md +33 -0
  219. package/localized/zh-CN/templates/shared/rules/fec-design-system.md +72 -0
  220. package/localized/zh-CN/templates/shared/rules/fec-error-handling.md +56 -0
  221. package/localized/zh-CN/templates/shared/rules/fec-git-conventions.md +73 -0
  222. package/localized/zh-CN/templates/shared/rules/fec-i18n.md +64 -0
  223. package/localized/zh-CN/templates/shared/rules/fec-naming-conventions.md +120 -0
  224. package/localized/zh-CN/templates/shared/rules/fec-performance.md +101 -0
  225. package/localized/zh-CN/templates/shared/rules/fec-react.md +82 -0
  226. package/localized/zh-CN/templates/shared/rules/fec-refactoring.md +47 -0
  227. package/localized/zh-CN/templates/shared/rules/fec-rendering-patterns.md +40 -0
  228. package/localized/zh-CN/templates/shared/rules/fec-responsive-design.md +50 -0
  229. package/localized/zh-CN/templates/shared/rules/fec-source-driven-development.md +31 -0
  230. package/localized/zh-CN/templates/shared/rules/fec-state-management.md +60 -0
  231. package/localized/zh-CN/templates/shared/rules/fec-testing.md +98 -0
  232. package/localized/zh-CN/templates/shared/rules/fec-typescript.md +112 -0
  233. package/localized/zh-CN/templates/shared/rules/fec-vue.md +64 -0
  234. package/localized/zh-CN/templates/shared/rules/fec-working-modes.md +50 -0
  235. package/openclaw.plugin.json +1 -1
  236. package/package.json +3 -2
  237. package/skills/TEMPLATE.md +74 -74
  238. package/skills/eval_queries.json +678 -678
  239. package/skills/fec-accessibility-check/SKILL.md +21 -21
  240. package/skills/fec-accessibility-check/references/report-template.md +12 -12
  241. package/skills/fec-accessibility-check/references/screen-reader-testing.md +24 -24
  242. package/skills/fec-alchemy/SKILL.md +50 -50
  243. package/skills/fec-alchemy/assets/absorption-plan-template.md +32 -32
  244. package/skills/fec-alchemy/assets/intake-template.md +55 -55
  245. package/skills/fec-alchemy/assets/review-checklist.md +23 -23
  246. package/skills/fec-alchemy/references/methodology.md +66 -66
  247. package/skills/fec-alchemy/references/originality-and-licensing.md +42 -42
  248. package/skills/fec-api-integration/SKILL.md +61 -61
  249. package/skills/fec-api-integration/references/integration-patterns.md +30 -30
  250. package/skills/fec-backend-requirements-handoff/SKILL.md +25 -25
  251. package/skills/fec-browser-storage/SKILL.md +16 -16
  252. package/skills/fec-browser-storage/references/storage-patterns.md +16 -16
  253. package/skills/fec-canvas-threejs/SKILL.md +21 -21
  254. package/skills/fec-canvas-threejs/references/rendering-patterns.md +10 -10
  255. package/skills/fec-canvas-threejs/references/shader-webgl-patterns.md +37 -37
  256. package/skills/fec-code-review/SKILL.md +94 -94
  257. package/skills/fec-code-review/references/report-template.md +13 -13
  258. package/skills/fec-component-testing/SKILL.md +39 -39
  259. package/skills/fec-data-fetching/SKILL.md +16 -16
  260. package/skills/fec-data-fetching/references/query-patterns.md +27 -27
  261. package/skills/fec-debug-framework/SKILL.md +95 -95
  262. package/skills/fec-debug-framework/references/report-template.md +34 -34
  263. package/skills/fec-dependency-upgrade/SKILL.md +17 -17
  264. package/skills/fec-dependency-upgrade/references/dependency-upgrade-workflow.md +31 -31
  265. package/skills/fec-doc-sync/SKILL.md +23 -23
  266. package/skills/fec-drawio-studio/SKILL.md +42 -38
  267. package/skills/fec-drawio-studio/data/THIRD_PARTY_NOTICES.md +1 -1
  268. package/skills/fec-drawio-studio/data/shape-index.json +83570 -0
  269. package/skills/fec-drawio-studio/references/diagram-patterns.md +19 -0
  270. package/skills/fec-drawio-studio/references/flowchart-quality.md +49 -0
  271. package/skills/fec-drawio-studio/references/layout-and-export.md +18 -3
  272. package/skills/fec-drawio-studio/references/xml-and-mermaid.md +5 -1
  273. package/skills/fec-drawio-studio/scripts/diagram-lint.mjs +62 -0
  274. package/skills/fec-drawio-studio/scripts/drawio-export.mjs +134 -0
  275. package/skills/fec-drawio-studio/scripts/shape-query.mjs +55 -15
  276. package/skills/fec-e2e-testing/SKILL.md +24 -24
  277. package/skills/fec-e2e-testing/references/e2e-ci-reporting.md +20 -20
  278. package/skills/fec-e2e-testing/references/e2e-special-scenarios.md +10 -10
  279. package/skills/fec-e2e-testing/references/e2e-visual-regression.md +17 -17
  280. package/skills/fec-e2e-testing/references/playwright-patterns.md +6 -6
  281. package/skills/fec-form-handling/SKILL.md +21 -21
  282. package/skills/fec-form-handling/references/advanced-form-patterns.md +41 -41
  283. package/skills/fec-image-generation/SKILL.md +54 -29
  284. package/skills/fec-image-generation/assets/interactive-diagram.html +371 -0
  285. package/skills/fec-image-generation/references/diagram-workflows.md +33 -2
  286. package/skills/fec-image-generation/references/html-technical-diagrams.md +236 -0
  287. package/skills/fec-image-generation/scripts/export-diagram.mjs +364 -0
  288. package/skills/fec-image-generation/scripts/interactive-diagram-server.mjs +654 -0
  289. package/skills/fec-image-generation/scripts/tech-diagram-render.mjs +1397 -0
  290. package/skills/fec-implement-from-design/SKILL.md +81 -81
  291. package/skills/fec-implement-from-design/references/design-plan-template.md +16 -16
  292. package/skills/fec-legacy-to-modern-migration/SKILL.md +29 -29
  293. package/skills/fec-legacy-to-modern-migration/references/migration-execution-checklist.md +81 -81
  294. package/skills/fec-legacy-to-modern-migration/references/migration-report-template.md +37 -37
  295. package/skills/fec-legacy-to-modern-migration/references/migration-strategy-and-mapping.md +31 -31
  296. package/skills/fec-legacy-web-standard/SKILL.md +17 -17
  297. package/skills/fec-legacy-web-standard/references/legacy-patterns.md +16 -16
  298. package/skills/fec-list-virtualization/SKILL.md +16 -16
  299. package/skills/fec-list-virtualization/references/virtualization-patterns.md +31 -31
  300. package/skills/fec-monorepo-project-standard/SKILL.md +52 -52
  301. package/skills/fec-motion-interaction/SKILL.md +51 -51
  302. package/skills/fec-motion-interaction/references/motion-patterns.md +11 -11
  303. package/skills/fec-nextjs-project-standard/SKILL.md +64 -64
  304. package/skills/fec-nuxt-project-standard/SKILL.md +57 -57
  305. package/skills/fec-performance-optimization/SKILL.md +54 -54
  306. package/skills/fec-performance-optimization/references/framework-performance-patterns.md +23 -23
  307. package/skills/fec-performance-optimization/references/report-template.md +31 -31
  308. package/skills/fec-pwa-implementation/SKILL.md +17 -17
  309. package/skills/fec-pwa-implementation/references/manifest-and-icons.md +12 -12
  310. package/skills/fec-pwa-implementation/references/service-worker-workbox.md +13 -13
  311. package/skills/fec-react-project-standard/SKILL.md +72 -72
  312. package/skills/fec-react-project-standard/references/react-performance-patterns.md +28 -28
  313. package/skills/fec-react-project-standard/references/react-project-structure.md +112 -112
  314. package/skills/fec-react-project-standard/references/react-quality-patterns.md +72 -72
  315. package/skills/fec-react-project-standard/references/react-runtime-patterns.md +94 -94
  316. package/skills/fec-refactor-clean/SKILL.md +30 -30
  317. package/skills/fec-refactor-clean/references/report-template.md +23 -23
  318. package/skills/fec-responsive-layout/SKILL.md +17 -17
  319. package/skills/fec-responsive-layout/references/responsive-layout-patterns.md +37 -37
  320. package/skills/fec-route-protection/SKILL.md +26 -26
  321. package/skills/fec-security-review/SKILL.md +20 -20
  322. package/skills/fec-security-review/references/report-template.md +12 -12
  323. package/skills/fec-security-review/references/security-checklist.md +27 -27
  324. package/skills/fec-source-driven-development/SKILL.md +59 -59
  325. package/skills/fec-state-management/SKILL.md +34 -34
  326. package/skills/fec-state-management/references/state-patterns.md +19 -19
  327. package/skills/fec-storybook-component-doc/SKILL.md +19 -19
  328. package/skills/fec-storybook-component-doc/references/story-patterns.md +12 -12
  329. package/skills/fec-svg-animation/SKILL.md +32 -32
  330. package/skills/fec-tailwind-design-system/SKILL.md +17 -17
  331. package/skills/fec-tailwind-design-system/references/tailwind-system-patterns.md +36 -36
  332. package/skills/fec-tdd-workflow/SKILL.md +28 -28
  333. package/skills/fec-testing-strategy/SKILL.md +29 -29
  334. package/skills/fec-typescript-project-standard/SKILL.md +38 -38
  335. package/skills/fec-typescript-project-standard/references/type-safety-patterns.md +20 -20
  336. package/skills/fec-typescript-project-standard/references/type-safety.md +18 -18
  337. package/skills/fec-ui-design/SKILL.md +90 -90
  338. package/skills/fec-ui-design/references/design-intelligence.md +61 -61
  339. package/skills/fec-ui-design/references/master-page-overrides.md +15 -15
  340. package/skills/fec-ui-design/references/pre-delivery-checklist.md +38 -38
  341. package/skills/fec-validation-fix/SKILL.md +26 -26
  342. package/skills/fec-validation-fix/references/report-template.md +25 -25
  343. package/skills/fec-vite-project-standard/SKILL.md +39 -39
  344. package/skills/fec-vue3-project-standard/SKILL.md +38 -38
  345. package/skills/fec-vue3-project-standard/references/vue3-composables-routing.md +36 -36
  346. package/skills/fec-vue3-project-standard/references/vue3-performance-patterns.md +8 -8
  347. package/skills/fec-vue3-project-standard/references/vue3-project-structure.md +112 -112
  348. package/skills/fec-vue3-project-standard/references/vue3-state-api-quality.md +74 -74
  349. package/skills/fec-web-video-presentation/SKILL.md +33 -33
  350. package/skills/fec-web-workers/SKILL.md +16 -16
  351. package/skills/fec-web-workers/references/advanced-workers.md +13 -13
  352. package/skills/fec-web-workers/references/basic-worker.md +8 -8
  353. package/skills/metadata.json +121 -115
  354. package/skills/relations.json +4 -0
  355. package/templates/claude/CLAUDE.md +60 -60
  356. package/templates/codex/AGENTS.md +76 -76
  357. package/templates/codex/config.toml +4 -4
  358. package/templates/openclaw/AGENTS.md +68 -68
  359. package/templates/openclaw/OPENCLAW-CONFIG.md +8 -8
  360. package/templates/shared/rules/fec-agent-workflow.md +40 -40
  361. package/templates/shared/rules/fec-api-layer.md +52 -52
  362. package/templates/shared/rules/fec-ci-cd.md +29 -29
  363. package/templates/shared/rules/fec-code-comments.md +22 -22
  364. package/templates/shared/rules/fec-design-system.md +53 -53
  365. package/templates/shared/rules/fec-error-handling.md +40 -40
  366. package/templates/shared/rules/fec-git-conventions.md +42 -42
  367. package/templates/shared/rules/fec-i18n.md +48 -48
  368. package/templates/shared/rules/fec-naming-conventions.md +91 -91
  369. package/templates/shared/rules/fec-performance.md +78 -78
  370. package/templates/shared/rules/fec-react.md +58 -58
  371. package/templates/shared/rules/fec-refactoring.md +34 -34
  372. package/templates/shared/rules/fec-rendering-patterns.md +36 -36
  373. package/templates/shared/rules/fec-responsive-design.md +37 -37
  374. package/templates/shared/rules/fec-source-driven-development.md +22 -22
  375. package/templates/shared/rules/fec-state-management.md +56 -56
  376. package/templates/shared/rules/fec-testing.md +64 -64
  377. package/templates/shared/rules/fec-typescript.md +75 -75
  378. package/templates/shared/rules/fec-vue.md +42 -42
  379. package/templates/shared/rules/fec-working-modes.md +34 -34
  380. package/skills/fec-drawio-studio/data/shape-index.json.gz +0 -0
@@ -0,0 +1,654 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+
4
+ import http from "node:http";
5
+ import fs from "node:fs";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+
10
+ const DEFAULT_HOST = "127.0.0.1";
11
+ const DEFAULT_PORT = 6100;
12
+ const PORT_TRIES = 20;
13
+ const MAX_BODY_BYTES = 1024 * 1024;
14
+ const SESSION_DIR = path.join(os.tmpdir(), "frontend-craft-interactive-diagrams");
15
+
16
+ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
17
+ const rendererPath = path.join(root, "assets", "interactive-diagram.html");
18
+
19
+ /**
20
+ * @typedef {{ cmd: string, [key: string]: unknown }} DiagramCommand
21
+ * @typedef {{ commands: DiagramCommand[], clients: Set<http.ServerResponse> }} DiagramSession
22
+ * @typedef {{ id: string, label: string, type: string, x?: number, y?: number, width?: number, height?: number, children?: string[], [key: string]: unknown }} GraphNode
23
+ * @typedef {{ id: string, from: string, to: string, label?: string, [key: string]: unknown }} GraphEdge
24
+ * @typedef {{ title: string, direction: string, nodes: GraphNode[], edges: GraphEdge[] }} GraphModel
25
+ */
26
+
27
+ /** @type {Map<string, DiagramSession>} */
28
+ const sessions = new Map();
29
+
30
+ const options = parseArgs(process.argv.slice(2));
31
+ fs.mkdirSync(SESSION_DIR, { recursive: true });
32
+
33
+ if (options.help) {
34
+ printHelp();
35
+ process.exit(0);
36
+ }
37
+
38
+ const server = http.createServer((req, res) => {
39
+ void route(req, res);
40
+ });
41
+
42
+ server.on("clientError", (_err, socket) => {
43
+ socket.end("HTTP/1.1 400 Bad Request\r\n\r\n");
44
+ });
45
+
46
+ listenWithFallback(server, options.port, options.host)
47
+ .then((port) => {
48
+ const status = { ok: true, host: options.host, port, url: `http://${options.host}:${port}/` };
49
+ console.log(JSON.stringify(status));
50
+ })
51
+ .catch((error) => {
52
+ console.error(error instanceof Error ? error.message : String(error));
53
+ process.exit(1);
54
+ });
55
+
56
+ /**
57
+ * @param {string[]} args
58
+ * @returns {{ host: string, port: number, help: boolean }}
59
+ */
60
+ function parseArgs(args) {
61
+ let host = DEFAULT_HOST;
62
+ let port = DEFAULT_PORT;
63
+ let help = false;
64
+
65
+ for (let index = 0; index < args.length; index += 1) {
66
+ const arg = args[index];
67
+ if (arg === "--help" || arg === "-h") help = true;
68
+ else if (arg === "--host") host = args[++index] ?? host;
69
+ else if (arg === "--port") port = Number(args[++index] ?? port);
70
+ }
71
+
72
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
73
+ throw new Error(`Invalid --port value: ${port}`);
74
+ }
75
+
76
+ return { host, port, help };
77
+ }
78
+
79
+ function printHelp() {
80
+ console.log(`Usage: node interactive-diagram-server.mjs [--host 127.0.0.1] [--port 6100]
81
+
82
+ Starts a local interactive diagram server with session-scoped SSE updates.
83
+ Open http://127.0.0.1:6100/?s=my-session and POST commands to /cmd?s=my-session.`);
84
+ }
85
+
86
+ /**
87
+ * @param {http.Server} target
88
+ * @param {number} startPort
89
+ * @param {string} host
90
+ * @returns {Promise<number>}
91
+ */
92
+ function listenWithFallback(target, startPort, host) {
93
+ if (startPort === 0) {
94
+ return listenOnce(target, 0, host);
95
+ }
96
+
97
+ let nextPort = startPort;
98
+ let attempts = 0;
99
+
100
+ return new Promise((resolve, reject) => {
101
+ const tryListen = () => {
102
+ attempts += 1;
103
+ target.once("error", onError);
104
+ target.listen(nextPort, host, () => {
105
+ target.off("error", onError);
106
+ resolve(nextPort);
107
+ });
108
+ };
109
+
110
+ /** @param {NodeJS.ErrnoException} error */
111
+ const onError = (error) => {
112
+ target.off("error", onError);
113
+ if (error.code === "EADDRINUSE" && attempts < PORT_TRIES) {
114
+ nextPort += 1;
115
+ tryListen();
116
+ return;
117
+ }
118
+ reject(error);
119
+ };
120
+
121
+ tryListen();
122
+ });
123
+ }
124
+
125
+ /**
126
+ * @param {http.Server} target
127
+ * @param {number} port
128
+ * @param {string} host
129
+ * @returns {Promise<number>}
130
+ */
131
+ function listenOnce(target, port, host) {
132
+ return new Promise((resolve, reject) => {
133
+ target.once("error", reject);
134
+ target.listen(port, host, () => {
135
+ target.off("error", reject);
136
+ const address = target.address();
137
+ if (!address || typeof address === "string") {
138
+ reject(new Error("Unable to resolve listening port"));
139
+ return;
140
+ }
141
+ resolve(address.port);
142
+ });
143
+ });
144
+ }
145
+
146
+ /**
147
+ * @param {http.IncomingMessage} req
148
+ * @param {http.ServerResponse} res
149
+ */
150
+ async function route(req, res) {
151
+ const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
152
+ const sessionId = sanitizeSessionId(url.searchParams.get("s") ?? "default");
153
+
154
+ try {
155
+ if (req.method === "GET" && url.pathname === "/") {
156
+ serveRenderer(res);
157
+ } else if (req.method === "GET" && url.pathname === "/events") {
158
+ serveEvents(sessionId, res);
159
+ } else if (req.method === "GET" && url.pathname === "/state") {
160
+ sendJson(res, 200, getSession(sessionId).commands);
161
+ } else if (req.method === "GET" && url.pathname === "/status") {
162
+ sendJson(res, 200, getStatus());
163
+ } else if (req.method === "GET" && url.pathname === "/sessions") {
164
+ sendJson(res, 200, listSessions());
165
+ } else if (req.method === "POST" && url.pathname === "/cmd") {
166
+ await handleCommand(req, res, sessionId);
167
+ } else if (req.method === "POST" && url.pathname === "/clear") {
168
+ clearSession(sessionId);
169
+ sendJson(res, 200, { ok: true, session: sessionId, commands: 0 });
170
+ } else if (req.method === "GET" && url.pathname === "/export") {
171
+ handleExport(res, sessionId, url.searchParams.get("format") ?? "json");
172
+ } else if (req.method === "POST" && url.pathname === "/export") {
173
+ const payload = await readJsonBody(req);
174
+ const format = typeof payload.format === "string" ? payload.format : "json";
175
+ handleExport(res, sessionId, format);
176
+ } else {
177
+ sendJson(res, 404, { ok: false, error: `Unknown route ${req.method ?? "GET"} ${url.pathname}` });
178
+ }
179
+ } catch (error) {
180
+ sendJson(res, 500, { ok: false, error: error instanceof Error ? error.message : String(error) });
181
+ }
182
+ }
183
+
184
+ /** @param {http.ServerResponse} res */
185
+ function serveRenderer(res) {
186
+ const html = fs.readFileSync(rendererPath, "utf8");
187
+ res.writeHead(200, {
188
+ "Content-Type": "text/html; charset=utf-8",
189
+ "Cache-Control": "no-store",
190
+ });
191
+ res.end(html);
192
+ }
193
+
194
+ /**
195
+ * @param {string} sessionId
196
+ * @param {http.ServerResponse} res
197
+ */
198
+ function serveEvents(sessionId, res) {
199
+ const session = getSession(sessionId);
200
+ res.writeHead(200, {
201
+ "Content-Type": "text/event-stream; charset=utf-8",
202
+ "Cache-Control": "no-cache, no-transform",
203
+ Connection: "keep-alive",
204
+ "X-Accel-Buffering": "no",
205
+ });
206
+ res.write(`event: ready\ndata: ${JSON.stringify({ session: sessionId })}\n\n`);
207
+ session.clients.add(res);
208
+ res.on("close", () => {
209
+ session.clients.delete(res);
210
+ });
211
+ }
212
+
213
+ /**
214
+ * @param {http.IncomingMessage} req
215
+ * @param {http.ServerResponse} res
216
+ * @param {string} sessionId
217
+ */
218
+ async function handleCommand(req, res, sessionId) {
219
+ let command;
220
+ try {
221
+ command = await readJsonBody(req);
222
+ } catch (error) {
223
+ sendJson(res, 400, { ok: false, error: error instanceof Error ? error.message : String(error) });
224
+ return;
225
+ }
226
+
227
+ if (!isCommand(command)) {
228
+ sendJson(res, 400, { ok: false, error: "Body must be a JSON object with a string cmd field" });
229
+ return;
230
+ }
231
+
232
+ const session = getSession(sessionId);
233
+ if (command.cmd === "clear") {
234
+ session.commands = [];
235
+ } else if (command.cmd === "init") {
236
+ session.commands = [command];
237
+ } else {
238
+ session.commands.push(command);
239
+ }
240
+
241
+ saveSession(sessionId, session.commands);
242
+ broadcast(session, command);
243
+ sendJson(res, 200, { ok: true, session: sessionId, commands: session.commands.length });
244
+ }
245
+
246
+ /**
247
+ * @param {http.ServerResponse} res
248
+ * @param {string} sessionId
249
+ * @param {string} format
250
+ */
251
+ function handleExport(res, sessionId, format) {
252
+ const session = getSession(sessionId);
253
+ const model = buildGraphModel(session.commands);
254
+ if (format === "json") {
255
+ sendJson(res, 200, { ok: true, session: sessionId, commands: session.commands, graph: model });
256
+ return;
257
+ }
258
+ if (format === "svg") {
259
+ const svg = renderStaticSvg(model);
260
+ res.writeHead(200, {
261
+ "Content-Type": "image/svg+xml; charset=utf-8",
262
+ "Cache-Control": "no-store",
263
+ });
264
+ res.end(svg);
265
+ return;
266
+ }
267
+ if (format === "drawio") {
268
+ res.writeHead(200, {
269
+ "Content-Type": "application/xml; charset=utf-8",
270
+ "Cache-Control": "no-store",
271
+ });
272
+ res.end(renderDrawioXml(model));
273
+ return;
274
+ }
275
+ sendJson(res, 422, { ok: false, error: `Unsupported export format: ${format}` });
276
+ }
277
+
278
+ /**
279
+ * @param {http.IncomingMessage} req
280
+ * @returns {Promise<Record<string, unknown>>}
281
+ */
282
+ function readJsonBody(req) {
283
+ return new Promise((resolve, reject) => {
284
+ /** @type {Buffer[]} */
285
+ const chunks = [];
286
+ let size = 0;
287
+ req.on("data", (chunk) => {
288
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
289
+ size += buffer.length;
290
+ if (size > MAX_BODY_BYTES) {
291
+ reject(new Error("Request body is too large"));
292
+ req.destroy();
293
+ return;
294
+ }
295
+ chunks.push(buffer);
296
+ });
297
+ req.on("end", () => {
298
+ try {
299
+ const body = Buffer.concat(chunks).toString("utf8").trim();
300
+ resolve(body ? JSON.parse(body) : {});
301
+ } catch {
302
+ reject(new Error("Request body must be valid JSON"));
303
+ }
304
+ });
305
+ req.on("error", reject);
306
+ });
307
+ }
308
+
309
+ /**
310
+ * @param {unknown} value
311
+ * @returns {value is DiagramCommand}
312
+ */
313
+ function isCommand(value) {
314
+ return Boolean(value && typeof value === "object" && typeof /** @type {{ cmd?: unknown }} */ (value).cmd === "string");
315
+ }
316
+
317
+ /**
318
+ * @param {string} sessionId
319
+ * @returns {DiagramSession}
320
+ */
321
+ function getSession(sessionId) {
322
+ const existing = sessions.get(sessionId);
323
+ if (existing) return existing;
324
+ const loaded = loadSession(sessionId);
325
+ const session = { commands: loaded, clients: new Set() };
326
+ sessions.set(sessionId, session);
327
+ return session;
328
+ }
329
+
330
+ /** @param {string} value */
331
+ function sanitizeSessionId(value) {
332
+ const clean = value.trim().replace(/[^A-Za-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
333
+ return clean.slice(0, 80) || "default";
334
+ }
335
+
336
+ /** @param {string} sessionId */
337
+ function sessionFile(sessionId) {
338
+ return path.join(SESSION_DIR, `${sessionId}.json`);
339
+ }
340
+
341
+ /** @param {string} sessionId */
342
+ function loadSession(sessionId) {
343
+ try {
344
+ const parsed = JSON.parse(fs.readFileSync(sessionFile(sessionId), "utf8"));
345
+ return Array.isArray(parsed) ? parsed.filter(isCommand) : [];
346
+ } catch {
347
+ return [];
348
+ }
349
+ }
350
+
351
+ /**
352
+ * @param {string} sessionId
353
+ * @param {DiagramCommand[]} commands
354
+ */
355
+ function saveSession(sessionId, commands) {
356
+ fs.writeFileSync(sessionFile(sessionId), `${JSON.stringify(commands, null, 2)}\n`, "utf8");
357
+ }
358
+
359
+ /** @param {string} sessionId */
360
+ function clearSession(sessionId) {
361
+ const session = getSession(sessionId);
362
+ session.commands = [];
363
+ saveSession(sessionId, []);
364
+ broadcast(session, { cmd: "clear" });
365
+ }
366
+
367
+ /**
368
+ * @param {DiagramSession} session
369
+ * @param {DiagramCommand} command
370
+ */
371
+ function broadcast(session, command) {
372
+ const payload = `data: ${JSON.stringify(command)}\n\n`;
373
+ for (const client of session.clients) {
374
+ client.write(payload);
375
+ }
376
+ }
377
+
378
+ function getStatus() {
379
+ let clientCount = 0;
380
+ let commandCount = 0;
381
+ for (const session of sessions.values()) {
382
+ clientCount += session.clients.size;
383
+ commandCount += session.commands.length;
384
+ }
385
+ return { ok: true, sessions: sessions.size, clients: clientCount, commands: commandCount };
386
+ }
387
+
388
+ function listSessions() {
389
+ return [...sessions.entries()]
390
+ .map(([id, session]) => ({ id, commands: session.commands.length, clients: session.clients.size }))
391
+ .sort((left, right) => left.id.localeCompare(right.id));
392
+ }
393
+
394
+ /** @param {DiagramCommand[]} commands */
395
+ function buildGraphModel(commands) {
396
+ /** @type {GraphModel} */
397
+ const model = { title: "Interactive Diagram", direction: "TB", nodes: [], edges: [] };
398
+ /** @type {Map<string, GraphNode>} */
399
+ const nodes = new Map();
400
+ /** @type {GraphEdge[]} */
401
+ const edges = [];
402
+
403
+ for (const command of commands) {
404
+ if (command.cmd === "init") {
405
+ model.title = stringField(command.title, model.title);
406
+ model.direction = stringField(command.direction, model.direction);
407
+ nodes.clear();
408
+ edges.length = 0;
409
+ } else if (command.cmd === "title") {
410
+ model.title = stringField(command.text, model.title);
411
+ } else if (command.cmd === "node" || command.cmd === "container") {
412
+ const id = stringField(command.id, "");
413
+ if (!id) continue;
414
+ const type = command.cmd === "container" ? "container" : stringField(command.type, "process");
415
+ const node = {
416
+ id,
417
+ label: stringField(command.label, id),
418
+ type,
419
+ width: numberField(command.width),
420
+ height: numberField(command.height),
421
+ x: numberField(command.x),
422
+ y: numberField(command.y),
423
+ children: arrayStringField(command.children),
424
+ ...command,
425
+ };
426
+ nodes.set(id, node);
427
+ } else if (command.cmd === "edge") {
428
+ const from = stringField(command.from, "");
429
+ const to = stringField(command.to, "");
430
+ if (!from || !to) continue;
431
+ edges.push({
432
+ id: stringField(command.id, `${from}-${to}-${edges.length + 1}`),
433
+ from,
434
+ to,
435
+ label: stringField(command.label, ""),
436
+ ...command,
437
+ });
438
+ } else if (command.cmd === "remove") {
439
+ const id = stringField(command.id, "");
440
+ nodes.delete(id);
441
+ for (let index = edges.length - 1; index >= 0; index -= 1) {
442
+ if (edges[index]?.from === id || edges[index]?.to === id) edges.splice(index, 1);
443
+ }
444
+ }
445
+ }
446
+
447
+ model.nodes = [...nodes.values()];
448
+ model.edges = edges;
449
+ return applyLayeredLayout(model);
450
+ }
451
+
452
+ /** @param {GraphModel} model */
453
+ function applyLayeredLayout(model) {
454
+ const placed = new Set(model.nodes.filter((node) => Number.isFinite(node.x) && Number.isFinite(node.y)).map((node) => node.id));
455
+ const byId = new Map(model.nodes.map((node) => [node.id, node]));
456
+ const incoming = new Map(model.nodes.map((node) => [node.id, 0]));
457
+ const outgoing = new Map(model.nodes.map((node) => [node.id, /** @type {string[]} */ ([])]));
458
+
459
+ for (const edge of model.edges) {
460
+ if (!byId.has(edge.from) || !byId.has(edge.to)) continue;
461
+ incoming.set(edge.to, (incoming.get(edge.to) ?? 0) + 1);
462
+ outgoing.get(edge.from)?.push(edge.to);
463
+ }
464
+
465
+ const queue = model.nodes.filter((node) => (incoming.get(node.id) ?? 0) === 0);
466
+ /** @type {Map<string, number>} */
467
+ const layer = new Map();
468
+ for (const node of queue) layer.set(node.id, 0);
469
+
470
+ while (queue.length > 0) {
471
+ const node = queue.shift();
472
+ if (!node) break;
473
+ const nextLayer = (layer.get(node.id) ?? 0) + 1;
474
+ for (const target of outgoing.get(node.id) ?? []) {
475
+ layer.set(target, Math.max(layer.get(target) ?? 0, nextLayer));
476
+ const targetNode = byId.get(target);
477
+ if (targetNode) queue.push(targetNode);
478
+ }
479
+ }
480
+
481
+ for (const node of model.nodes) {
482
+ if (!layer.has(node.id)) layer.set(node.id, 0);
483
+ }
484
+
485
+ const horizontal = model.direction === "LR" || model.direction === "RL";
486
+ /** @type {Map<number, GraphNode[]>} */
487
+ const groups = new Map();
488
+ for (const node of model.nodes.filter((candidate) => candidate.type !== "container")) {
489
+ const rank = layer.get(node.id) ?? 0;
490
+ const group = groups.get(rank) ?? [];
491
+ group.push(node);
492
+ groups.set(rank, group);
493
+ }
494
+
495
+ for (const [rank, nodes] of groups) {
496
+ nodes.forEach((node, index) => {
497
+ if (placed.has(node.id)) return;
498
+ const main = 80 + rank * 190;
499
+ const cross = 80 + index * 120;
500
+ node.x = horizontal ? main : cross;
501
+ node.y = horizontal ? cross : main;
502
+ node.width = node.width ?? defaultNodeWidth(node);
503
+ node.height = node.height ?? defaultNodeHeight(node);
504
+ });
505
+ }
506
+
507
+ for (const node of model.nodes.filter((candidate) => candidate.type === "container")) {
508
+ /** @type {GraphNode[]} */
509
+ const children = [];
510
+ for (const childId of node.children ?? []) {
511
+ const child = byId.get(childId);
512
+ if (child && child.type !== "container") children.push(child);
513
+ }
514
+ if (children.length === 0) {
515
+ node.x = node.x ?? 40;
516
+ node.y = node.y ?? 40;
517
+ node.width = node.width ?? 240;
518
+ node.height = node.height ?? 160;
519
+ continue;
520
+ }
521
+ const left = Math.min(...children.map((child) => child.x ?? 0));
522
+ const top = Math.min(...children.map((child) => child.y ?? 0));
523
+ const right = Math.max(...children.map((child) => (child.x ?? 0) + (child.width ?? defaultNodeWidth(child))));
524
+ const bottom = Math.max(...children.map((child) => (child.y ?? 0) + (child.height ?? defaultNodeHeight(child))));
525
+ node.x = left - 36;
526
+ node.y = top - 54;
527
+ node.width = right - left + 72;
528
+ node.height = bottom - top + 90;
529
+ }
530
+
531
+ return model;
532
+ }
533
+
534
+ /** @param {GraphNode} node */
535
+ function defaultNodeWidth(node) {
536
+ return node.type === "decision" ? 132 : node.type === "database" ? 136 : 150;
537
+ }
538
+
539
+ /** @param {GraphNode} node */
540
+ function defaultNodeHeight(node) {
541
+ return node.type === "decision" ? 88 : node.type === "container" ? 160 : 62;
542
+ }
543
+
544
+ /** @param {unknown} value @param {string} fallback */
545
+ function stringField(value, fallback) {
546
+ return typeof value === "string" && value.length > 0 ? value : fallback;
547
+ }
548
+
549
+ /** @param {unknown} value */
550
+ function numberField(value) {
551
+ const parsed = Number(value);
552
+ return Number.isFinite(parsed) ? parsed : undefined;
553
+ }
554
+
555
+ /** @param {unknown} value */
556
+ function arrayStringField(value) {
557
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : undefined;
558
+ }
559
+
560
+ /**
561
+ * @param {http.ServerResponse} res
562
+ * @param {number} status
563
+ * @param {unknown} value
564
+ */
565
+ function sendJson(res, status, value) {
566
+ res.writeHead(status, {
567
+ "Content-Type": "application/json; charset=utf-8",
568
+ "Cache-Control": "no-store",
569
+ });
570
+ res.end(`${JSON.stringify(value, null, 2)}\n`);
571
+ }
572
+
573
+ /** @param {GraphModel} model */
574
+ function renderStaticSvg(model) {
575
+ const width = Math.max(720, ...model.nodes.map((node) => (node.x ?? 0) + (node.width ?? defaultNodeWidth(node)) + 80));
576
+ const height = Math.max(420, ...model.nodes.map((node) => (node.y ?? 0) + (node.height ?? defaultNodeHeight(node)) + 80));
577
+ const nodes = model.nodes.map(renderSvgNode).join("\n");
578
+ const byId = new Map(model.nodes.map((node) => [node.id, node]));
579
+ const edges = model.edges.map((edge) => renderSvgEdge(edge, byId)).join("\n");
580
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}">
581
+ <style>text{font-family:Arial,sans-serif;font-size:13px;fill:#172033}.node{fill:#f8fafc;stroke:#4064d7;stroke-width:2}.container{fill:#f8fbff;stroke:#7a8aa0;stroke-dasharray:7 5}.edge{stroke:#718096;stroke-width:2;fill:none}.label-bg{fill:#fff;stroke:#d7dde8}</style>
582
+ <defs><marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto"><path d="M0,0 L0,6 L9,3 z" fill="#718096"/></marker></defs>
583
+ <text x="32" y="36" font-size="20" font-weight="700">${escapeXml(model.title)}</text>
584
+ ${edges}
585
+ ${nodes}
586
+ </svg>`;
587
+ }
588
+
589
+ /** @param {GraphNode} node */
590
+ function renderSvgNode(node) {
591
+ const x = node.x ?? 0;
592
+ const y = node.y ?? 0;
593
+ const width = node.width ?? defaultNodeWidth(node);
594
+ const height = node.height ?? defaultNodeHeight(node);
595
+ const label = escapeXml(node.label);
596
+ if (node.type === "container") {
597
+ return `<g><rect class="container" x="${x}" y="${y}" width="${width}" height="${height}" rx="8"/><text x="${x + 14}" y="${y + 24}" font-weight="700">${label}</text></g>`;
598
+ }
599
+ if (node.type === "decision") {
600
+ const points = `${x + width / 2},${y} ${x + width},${y + height / 2} ${x + width / 2},${y + height} ${x},${y + height / 2}`;
601
+ return `<g><polygon class="node" points="${points}" fill="#fff8dc" stroke="#d69e2e"/><text x="${x + width / 2}" y="${y + height / 2 + 5}" text-anchor="middle">${label}</text></g>`;
602
+ }
603
+ if (node.type === "terminal" || node.type === "terminal-end") {
604
+ const stroke = node.type === "terminal" ? "#2f9e44" : "#d9480f";
605
+ return `<g><ellipse cx="${x + width / 2}" cy="${y + height / 2}" rx="${width / 2}" ry="${height / 2}" fill="#f8fafc" stroke="${stroke}" stroke-width="2"/><text x="${x + width / 2}" y="${y + height / 2 + 5}" text-anchor="middle">${label}</text></g>`;
606
+ }
607
+ return `<g><rect class="node" x="${x}" y="${y}" width="${width}" height="${height}" rx="8"/><text x="${x + width / 2}" y="${y + height / 2 + 5}" text-anchor="middle">${label}</text></g>`;
608
+ }
609
+
610
+ /**
611
+ * @param {GraphEdge} edge
612
+ * @param {Map<string, GraphNode>} byId
613
+ */
614
+ function renderSvgEdge(edge, byId) {
615
+ const from = byId.get(edge.from);
616
+ const to = byId.get(edge.to);
617
+ if (!from || !to) return "";
618
+ const x1 = (from.x ?? 0) + (from.width ?? defaultNodeWidth(from)) / 2;
619
+ const y1 = (from.y ?? 0) + (from.height ?? defaultNodeHeight(from));
620
+ const x2 = (to.x ?? 0) + (to.width ?? defaultNodeWidth(to)) / 2;
621
+ const y2 = to.y ?? 0;
622
+ const label = edge.label ? `<text x="${(x1 + x2) / 2}" y="${(y1 + y2) / 2 - 6}" text-anchor="middle">${escapeXml(edge.label)}</text>` : "";
623
+ return `<g><path class="edge" marker-end="url(#arrow)" d="M${x1},${y1} C${x1},${(y1 + y2) / 2} ${x2},${(y1 + y2) / 2} ${x2},${y2}"/>${label}</g>`;
624
+ }
625
+
626
+ /** @param {GraphModel} model */
627
+ function renderDrawioXml(model) {
628
+ let id = 2;
629
+ /** @type {Map<string, number>} */
630
+ const ids = new Map();
631
+ const cells = ['<mxCell id="0"/>', '<mxCell id="1" parent="0"/>'];
632
+ for (const node of model.nodes) {
633
+ const mxId = id++;
634
+ ids.set(node.id, mxId);
635
+ const style = node.type === "decision" ? "rhombus;whiteSpace=wrap;html=1;" : "rounded=1;whiteSpace=wrap;html=1;";
636
+ cells.push(`<mxCell id="${mxId}" value="${escapeXml(node.label)}" style="${style}" vertex="1" parent="1"><mxGeometry x="${node.x ?? 0}" y="${node.y ?? 0}" width="${node.width ?? defaultNodeWidth(node)}" height="${node.height ?? defaultNodeHeight(node)}" as="geometry"/></mxCell>`);
637
+ }
638
+ for (const edge of model.edges) {
639
+ const source = ids.get(edge.from);
640
+ const target = ids.get(edge.to);
641
+ if (!source || !target) continue;
642
+ cells.push(`<mxCell id="${id++}" value="${escapeXml(edge.label ?? "")}" edge="1" parent="1" source="${source}" target="${target}" style="edgeStyle=orthogonalEdgeStyle;rounded=1;html=1;"><mxGeometry relative="1" as="geometry"/></mxCell>`);
643
+ }
644
+ return `<mxfile><diagram name="${escapeXml(model.title)}"><mxGraphModel><root>${cells.join("")}</root></mxGraphModel></diagram></mxfile>`;
645
+ }
646
+
647
+ /** @param {unknown} value */
648
+ function escapeXml(value) {
649
+ return String(value)
650
+ .replaceAll("&", "&amp;")
651
+ .replaceAll("<", "&lt;")
652
+ .replaceAll(">", "&gt;")
653
+ .replaceAll('"', "&quot;");
654
+ }