@cherishxn/agent-scaffold 0.0.0-stage → 1.0.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 (659) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -2
  3. package/THIRD_PARTY_NOTICES.md +14 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +100 -0
  6. package/dist/generate.d.ts +55 -0
  7. package/dist/generate.js +154 -0
  8. package/dist/model.d.ts +33 -0
  9. package/dist/model.js +24 -0
  10. package/dist/verify.d.ts +61 -0
  11. package/dist/verify.js +264 -0
  12. package/package.json +49 -4
  13. package/schemas/manifest.json +171 -0
  14. package/schemas/plan.json +164 -0
  15. package/schemas/result.json +97 -0
  16. package/schemas/spec.json +50 -0
  17. package/skills/scaffold-use/SKILL.md +6 -0
  18. package/templates/catalog.json +37 -0
  19. package/templates/next-fullstack-antd/.agents/skills/design-contract/SKILL.md +23 -0
  20. package/templates/next-fullstack-antd/.agents/skills/project-work/SKILL.md +8 -0
  21. package/templates/next-fullstack-antd/.claude/skills/design-contract/SKILL.md +23 -0
  22. package/templates/next-fullstack-antd/.claude/skills/project-work/SKILL.md +8 -0
  23. package/templates/next-fullstack-antd/.dockerignore +18 -0
  24. package/templates/next-fullstack-antd/.env.example +13 -0
  25. package/templates/next-fullstack-antd/.grok/skills/design-contract/SKILL.md +23 -0
  26. package/templates/next-fullstack-antd/.grok/skills/project-work/SKILL.md +8 -0
  27. package/templates/next-fullstack-antd/.scaffold-checks.json +7 -0
  28. package/templates/next-fullstack-antd/AGENTS.md +11 -0
  29. package/templates/next-fullstack-antd/CLAUDE.md +1 -0
  30. package/templates/next-fullstack-antd/DESIGN.md +208 -0
  31. package/templates/next-fullstack-antd/LICENSE +21 -0
  32. package/templates/next-fullstack-antd/README.md +55 -0
  33. package/templates/next-fullstack-antd/THIRD_PARTY_NOTICES.md +14 -0
  34. package/templates/next-fullstack-antd/compose.yaml +18 -0
  35. package/templates/next-fullstack-antd/design/guide.md +61 -0
  36. package/templates/next-fullstack-antd/design/layout.css +6 -0
  37. package/templates/next-fullstack-antd/design/structure.json +107 -0
  38. package/templates/next-fullstack-antd/design/tokens.json +171 -0
  39. package/templates/next-fullstack-antd/design/widgets.css +30 -0
  40. package/templates/next-fullstack-antd/design/widgets.json +233 -0
  41. package/templates/next-fullstack-antd/docs/PROJECT.md +9 -0
  42. package/templates/next-fullstack-antd/docs/api.d.ts +516 -0
  43. package/templates/next-fullstack-antd/docs/openapi.json +439 -0
  44. package/templates/next-fullstack-antd/gitignore.template +19 -0
  45. package/templates/next-fullstack-antd/licenses/fastapi.txt +21 -0
  46. package/templates/next-fullstack-antd/licenses/nest.txt +22 -0
  47. package/templates/next-fullstack-antd/licenses/next.txt +21 -0
  48. package/templates/next-fullstack-antd/licenses/shadcn.txt +21 -0
  49. package/templates/next-fullstack-antd/licenses/sources.json +27 -0
  50. package/templates/next-fullstack-antd/licenses/spring.txt +202 -0
  51. package/templates/next-fullstack-antd/package-lock.json +440 -0
  52. package/templates/next-fullstack-antd/package.json +21 -0
  53. package/templates/next-fullstack-antd/playwright.config.ts +3 -0
  54. package/templates/next-fullstack-antd/project.json +8 -0
  55. package/templates/next-fullstack-antd/scripts/design-coverage.mjs +20 -0
  56. package/templates/next-fullstack-antd/scripts/design-next-cleanup.mjs +13 -0
  57. package/templates/next-fullstack-antd/scripts/design-verify.mjs +154 -0
  58. package/templates/next-fullstack-antd/scripts/design.mjs +108 -0
  59. package/templates/next-fullstack-antd/scripts/project.mjs +118 -0
  60. package/templates/next-fullstack-antd/tests/contract.spec.ts +47 -0
  61. package/templates/next-fullstack-antd/tests/design/harness.tsx +63 -0
  62. package/templates/next-fullstack-antd/tests/ui.spec.ts +61 -0
  63. package/templates/next-fullstack-antd/web/Dockerfile +12 -0
  64. package/templates/next-fullstack-antd/web/app/api/auth/login/route.ts +1 -0
  65. package/templates/next-fullstack-antd/web/app/api/auth/logout/route.ts +1 -0
  66. package/templates/next-fullstack-antd/web/app/api/auth/me/route.ts +1 -0
  67. package/templates/next-fullstack-antd/web/app/api/health/route.ts +1 -0
  68. package/templates/next-fullstack-antd/web/app/api/records/[id]/route.ts +5 -0
  69. package/templates/next-fullstack-antd/web/app/api/records/route.ts +3 -0
  70. package/templates/next-fullstack-antd/web/app/layout.tsx +5 -0
  71. package/templates/next-fullstack-antd/web/app/page.tsx +2 -0
  72. package/templates/next-fullstack-antd/web/eslint.config.mjs +3 -0
  73. package/templates/next-fullstack-antd/web/lib/api.ts +35 -0
  74. package/templates/next-fullstack-antd/web/lib/auth.ts +10 -0
  75. package/templates/next-fullstack-antd/web/lib/db.ts +3 -0
  76. package/templates/next-fullstack-antd/web/lib/session.test.ts +15 -0
  77. package/templates/next-fullstack-antd/web/lib/validation.test.ts +3 -0
  78. package/templates/next-fullstack-antd/web/lib/validation.ts +5 -0
  79. package/templates/next-fullstack-antd/web/next-env.d.ts +7 -0
  80. package/templates/next-fullstack-antd/web/next.config.ts +3 -0
  81. package/templates/next-fullstack-antd/web/package.json +35 -0
  82. package/templates/next-fullstack-antd/web/pnpm-lock.yaml +3756 -0
  83. package/templates/next-fullstack-antd/web/pnpm-workspace.yaml +7 -0
  84. package/templates/next-fullstack-antd/web/prisma/migrations/202610190001_initial/migration.sql +99 -0
  85. package/templates/next-fullstack-antd/web/prisma/migrations/migration_lock.toml +1 -0
  86. package/templates/next-fullstack-antd/web/prisma/schema.prisma +72 -0
  87. package/templates/next-fullstack-antd/web/scripts/seed.ts +4 -0
  88. package/templates/next-fullstack-antd/web/src/App.tsx +29 -0
  89. package/templates/next-fullstack-antd/web/src/design-layout.css +7 -0
  90. package/templates/next-fullstack-antd/web/src/design-runtime.json +511 -0
  91. package/templates/next-fullstack-antd/web/src/design-tokens.css +168 -0
  92. package/templates/next-fullstack-antd/web/src/design-widgets.css +31 -0
  93. package/templates/next-fullstack-antd/web/src/structure-view.tsx +16 -0
  94. package/templates/next-fullstack-antd/web/src/styles.css +3 -0
  95. package/templates/next-fullstack-antd/web/src/ui.tsx +30 -0
  96. package/templates/next-fullstack-antd/web/src/widget-icon.tsx +5 -0
  97. package/templates/next-fullstack-antd/web/src/widget-types.ts +13 -0
  98. package/templates/next-fullstack-antd/web/src/workspace-model.ts +85 -0
  99. package/templates/next-fullstack-antd/web/tsconfig.json +42 -0
  100. package/templates/next-fullstack-antd/web/vitest.config.ts +2 -0
  101. package/templates/next-fullstack-shadcn/.agents/skills/design-contract/SKILL.md +23 -0
  102. package/templates/next-fullstack-shadcn/.agents/skills/project-work/SKILL.md +8 -0
  103. package/templates/next-fullstack-shadcn/.claude/skills/design-contract/SKILL.md +23 -0
  104. package/templates/next-fullstack-shadcn/.claude/skills/project-work/SKILL.md +8 -0
  105. package/templates/next-fullstack-shadcn/.dockerignore +18 -0
  106. package/templates/next-fullstack-shadcn/.env.example +13 -0
  107. package/templates/next-fullstack-shadcn/.grok/skills/design-contract/SKILL.md +23 -0
  108. package/templates/next-fullstack-shadcn/.grok/skills/project-work/SKILL.md +8 -0
  109. package/templates/next-fullstack-shadcn/.scaffold-checks.json +7 -0
  110. package/templates/next-fullstack-shadcn/AGENTS.md +11 -0
  111. package/templates/next-fullstack-shadcn/CLAUDE.md +1 -0
  112. package/templates/next-fullstack-shadcn/DESIGN.md +208 -0
  113. package/templates/next-fullstack-shadcn/LICENSE +21 -0
  114. package/templates/next-fullstack-shadcn/README.md +55 -0
  115. package/templates/next-fullstack-shadcn/THIRD_PARTY_NOTICES.md +14 -0
  116. package/templates/next-fullstack-shadcn/compose.yaml +18 -0
  117. package/templates/next-fullstack-shadcn/design/guide.md +61 -0
  118. package/templates/next-fullstack-shadcn/design/layout.css +6 -0
  119. package/templates/next-fullstack-shadcn/design/structure.json +107 -0
  120. package/templates/next-fullstack-shadcn/design/tokens.json +171 -0
  121. package/templates/next-fullstack-shadcn/design/widgets.css +30 -0
  122. package/templates/next-fullstack-shadcn/design/widgets.json +233 -0
  123. package/templates/next-fullstack-shadcn/docs/PROJECT.md +9 -0
  124. package/templates/next-fullstack-shadcn/docs/api.d.ts +516 -0
  125. package/templates/next-fullstack-shadcn/docs/openapi.json +439 -0
  126. package/templates/next-fullstack-shadcn/gitignore.template +19 -0
  127. package/templates/next-fullstack-shadcn/licenses/fastapi.txt +21 -0
  128. package/templates/next-fullstack-shadcn/licenses/nest.txt +22 -0
  129. package/templates/next-fullstack-shadcn/licenses/next.txt +21 -0
  130. package/templates/next-fullstack-shadcn/licenses/shadcn.txt +21 -0
  131. package/templates/next-fullstack-shadcn/licenses/sources.json +27 -0
  132. package/templates/next-fullstack-shadcn/licenses/spring.txt +202 -0
  133. package/templates/next-fullstack-shadcn/package-lock.json +440 -0
  134. package/templates/next-fullstack-shadcn/package.json +21 -0
  135. package/templates/next-fullstack-shadcn/playwright.config.ts +3 -0
  136. package/templates/next-fullstack-shadcn/project.json +8 -0
  137. package/templates/next-fullstack-shadcn/scripts/design-coverage.mjs +20 -0
  138. package/templates/next-fullstack-shadcn/scripts/design-next-cleanup.mjs +13 -0
  139. package/templates/next-fullstack-shadcn/scripts/design-verify.mjs +154 -0
  140. package/templates/next-fullstack-shadcn/scripts/design.mjs +108 -0
  141. package/templates/next-fullstack-shadcn/scripts/project.mjs +118 -0
  142. package/templates/next-fullstack-shadcn/tests/contract.spec.ts +47 -0
  143. package/templates/next-fullstack-shadcn/tests/design/harness.tsx +63 -0
  144. package/templates/next-fullstack-shadcn/tests/ui.spec.ts +61 -0
  145. package/templates/next-fullstack-shadcn/web/Dockerfile +12 -0
  146. package/templates/next-fullstack-shadcn/web/app/api/auth/login/route.ts +1 -0
  147. package/templates/next-fullstack-shadcn/web/app/api/auth/logout/route.ts +1 -0
  148. package/templates/next-fullstack-shadcn/web/app/api/auth/me/route.ts +1 -0
  149. package/templates/next-fullstack-shadcn/web/app/api/health/route.ts +1 -0
  150. package/templates/next-fullstack-shadcn/web/app/api/records/[id]/route.ts +5 -0
  151. package/templates/next-fullstack-shadcn/web/app/api/records/route.ts +3 -0
  152. package/templates/next-fullstack-shadcn/web/app/layout.tsx +4 -0
  153. package/templates/next-fullstack-shadcn/web/app/page.tsx +2 -0
  154. package/templates/next-fullstack-shadcn/web/eslint.config.mjs +3 -0
  155. package/templates/next-fullstack-shadcn/web/lib/api.ts +35 -0
  156. package/templates/next-fullstack-shadcn/web/lib/auth.ts +10 -0
  157. package/templates/next-fullstack-shadcn/web/lib/db.ts +3 -0
  158. package/templates/next-fullstack-shadcn/web/lib/session.test.ts +15 -0
  159. package/templates/next-fullstack-shadcn/web/lib/validation.test.ts +3 -0
  160. package/templates/next-fullstack-shadcn/web/lib/validation.ts +5 -0
  161. package/templates/next-fullstack-shadcn/web/next-env.d.ts +7 -0
  162. package/templates/next-fullstack-shadcn/web/next.config.ts +3 -0
  163. package/templates/next-fullstack-shadcn/web/package.json +39 -0
  164. package/templates/next-fullstack-shadcn/web/pnpm-lock.yaml +3286 -0
  165. package/templates/next-fullstack-shadcn/web/pnpm-workspace.yaml +7 -0
  166. package/templates/next-fullstack-shadcn/web/postcss.config.mjs +1 -0
  167. package/templates/next-fullstack-shadcn/web/prisma/migrations/202610190001_initial/migration.sql +99 -0
  168. package/templates/next-fullstack-shadcn/web/prisma/migrations/migration_lock.toml +1 -0
  169. package/templates/next-fullstack-shadcn/web/prisma/schema.prisma +72 -0
  170. package/templates/next-fullstack-shadcn/web/scripts/seed.ts +4 -0
  171. package/templates/next-fullstack-shadcn/web/src/App.tsx +30 -0
  172. package/templates/next-fullstack-shadcn/web/src/design-layout.css +7 -0
  173. package/templates/next-fullstack-shadcn/web/src/design-runtime.json +511 -0
  174. package/templates/next-fullstack-shadcn/web/src/design-tokens.css +168 -0
  175. package/templates/next-fullstack-shadcn/web/src/design-widgets.css +31 -0
  176. package/templates/next-fullstack-shadcn/web/src/structure-view.tsx +16 -0
  177. package/templates/next-fullstack-shadcn/web/src/styles.css +3 -0
  178. package/templates/next-fullstack-shadcn/web/src/tailwind.css +2 -0
  179. package/templates/next-fullstack-shadcn/web/src/ui.tsx +13 -0
  180. package/templates/next-fullstack-shadcn/web/src/widget-icon.tsx +5 -0
  181. package/templates/next-fullstack-shadcn/web/src/widget-types.ts +13 -0
  182. package/templates/next-fullstack-shadcn/web/src/workspace-model.ts +85 -0
  183. package/templates/next-fullstack-shadcn/web/tsconfig.json +42 -0
  184. package/templates/next-fullstack-shadcn/web/vitest.config.ts +2 -0
  185. package/templates/react-fastapi-antd/.agents/skills/design-contract/SKILL.md +23 -0
  186. package/templates/react-fastapi-antd/.agents/skills/project-work/SKILL.md +8 -0
  187. package/templates/react-fastapi-antd/.claude/skills/design-contract/SKILL.md +23 -0
  188. package/templates/react-fastapi-antd/.claude/skills/project-work/SKILL.md +8 -0
  189. package/templates/react-fastapi-antd/.dockerignore +18 -0
  190. package/templates/react-fastapi-antd/.env.example +13 -0
  191. package/templates/react-fastapi-antd/.grok/skills/design-contract/SKILL.md +23 -0
  192. package/templates/react-fastapi-antd/.grok/skills/project-work/SKILL.md +8 -0
  193. package/templates/react-fastapi-antd/.scaffold-checks.json +7 -0
  194. package/templates/react-fastapi-antd/AGENTS.md +11 -0
  195. package/templates/react-fastapi-antd/CLAUDE.md +1 -0
  196. package/templates/react-fastapi-antd/DESIGN.md +208 -0
  197. package/templates/react-fastapi-antd/LICENSE +21 -0
  198. package/templates/react-fastapi-antd/README.md +55 -0
  199. package/templates/react-fastapi-antd/THIRD_PARTY_NOTICES.md +14 -0
  200. package/templates/react-fastapi-antd/backend/.python-version +1 -0
  201. package/templates/react-fastapi-antd/backend/Dockerfile +10 -0
  202. package/templates/react-fastapi-antd/backend/alembic.ini +3 -0
  203. package/templates/react-fastapi-antd/backend/app/__init__.py +0 -0
  204. package/templates/react-fastapi-antd/backend/app/core/__init__.py +0 -0
  205. package/templates/react-fastapi-antd/backend/app/core/security.py +25 -0
  206. package/templates/react-fastapi-antd/backend/app/db.py +11 -0
  207. package/templates/react-fastapi-antd/backend/app/main.py +146 -0
  208. package/templates/react-fastapi-antd/backend/app/models.py +21 -0
  209. package/templates/react-fastapi-antd/backend/app/seed.py +21 -0
  210. package/templates/react-fastapi-antd/backend/migrations/env.py +9 -0
  211. package/templates/react-fastapi-antd/backend/migrations/versions/0001_initial.py +18 -0
  212. package/templates/react-fastapi-antd/backend/pyproject.toml +16 -0
  213. package/templates/react-fastapi-antd/backend/tests/test_api.py +27 -0
  214. package/templates/react-fastapi-antd/backend/uv.lock +1085 -0
  215. package/templates/react-fastapi-antd/compose.yaml +18 -0
  216. package/templates/react-fastapi-antd/design/guide.md +61 -0
  217. package/templates/react-fastapi-antd/design/layout.css +6 -0
  218. package/templates/react-fastapi-antd/design/structure.json +107 -0
  219. package/templates/react-fastapi-antd/design/tokens.json +171 -0
  220. package/templates/react-fastapi-antd/design/widgets.css +30 -0
  221. package/templates/react-fastapi-antd/design/widgets.json +233 -0
  222. package/templates/react-fastapi-antd/docs/PROJECT.md +9 -0
  223. package/templates/react-fastapi-antd/docs/api.d.ts +516 -0
  224. package/templates/react-fastapi-antd/docs/openapi.json +439 -0
  225. package/templates/react-fastapi-antd/gitignore.template +19 -0
  226. package/templates/react-fastapi-antd/licenses/fastapi.txt +21 -0
  227. package/templates/react-fastapi-antd/licenses/nest.txt +22 -0
  228. package/templates/react-fastapi-antd/licenses/next.txt +21 -0
  229. package/templates/react-fastapi-antd/licenses/shadcn.txt +21 -0
  230. package/templates/react-fastapi-antd/licenses/sources.json +27 -0
  231. package/templates/react-fastapi-antd/licenses/spring.txt +202 -0
  232. package/templates/react-fastapi-antd/package-lock.json +440 -0
  233. package/templates/react-fastapi-antd/package.json +21 -0
  234. package/templates/react-fastapi-antd/playwright.config.ts +3 -0
  235. package/templates/react-fastapi-antd/project.json +8 -0
  236. package/templates/react-fastapi-antd/scripts/design-coverage.mjs +20 -0
  237. package/templates/react-fastapi-antd/scripts/design-next-cleanup.mjs +13 -0
  238. package/templates/react-fastapi-antd/scripts/design-verify.mjs +154 -0
  239. package/templates/react-fastapi-antd/scripts/design.mjs +108 -0
  240. package/templates/react-fastapi-antd/scripts/project.mjs +118 -0
  241. package/templates/react-fastapi-antd/tests/contract.spec.ts +47 -0
  242. package/templates/react-fastapi-antd/tests/design/harness.tsx +63 -0
  243. package/templates/react-fastapi-antd/tests/ui.spec.ts +61 -0
  244. package/templates/react-fastapi-antd/web/bun.lock +442 -0
  245. package/templates/react-fastapi-antd/web/eslint.config.mjs +3 -0
  246. package/templates/react-fastapi-antd/web/index.html +1 -0
  247. package/templates/react-fastapi-antd/web/package.json +28 -0
  248. package/templates/react-fastapi-antd/web/src/App.tsx +29 -0
  249. package/templates/react-fastapi-antd/web/src/design-layout.css +7 -0
  250. package/templates/react-fastapi-antd/web/src/design-runtime.json +511 -0
  251. package/templates/react-fastapi-antd/web/src/design-tokens.css +168 -0
  252. package/templates/react-fastapi-antd/web/src/design-widgets.css +31 -0
  253. package/templates/react-fastapi-antd/web/src/main.tsx +6 -0
  254. package/templates/react-fastapi-antd/web/src/structure-view.tsx +16 -0
  255. package/templates/react-fastapi-antd/web/src/styles.css +3 -0
  256. package/templates/react-fastapi-antd/web/src/ui.tsx +30 -0
  257. package/templates/react-fastapi-antd/web/src/widget-icon.tsx +5 -0
  258. package/templates/react-fastapi-antd/web/src/widget-types.ts +13 -0
  259. package/templates/react-fastapi-antd/web/src/workspace-model.ts +85 -0
  260. package/templates/react-fastapi-antd/web/tsconfig.json +22 -0
  261. package/templates/react-fastapi-antd/web/vite.config.ts +3 -0
  262. package/templates/react-fastapi-shadcn/.agents/skills/design-contract/SKILL.md +23 -0
  263. package/templates/react-fastapi-shadcn/.agents/skills/project-work/SKILL.md +8 -0
  264. package/templates/react-fastapi-shadcn/.claude/skills/design-contract/SKILL.md +23 -0
  265. package/templates/react-fastapi-shadcn/.claude/skills/project-work/SKILL.md +8 -0
  266. package/templates/react-fastapi-shadcn/.dockerignore +18 -0
  267. package/templates/react-fastapi-shadcn/.env.example +13 -0
  268. package/templates/react-fastapi-shadcn/.grok/skills/design-contract/SKILL.md +23 -0
  269. package/templates/react-fastapi-shadcn/.grok/skills/project-work/SKILL.md +8 -0
  270. package/templates/react-fastapi-shadcn/.scaffold-checks.json +7 -0
  271. package/templates/react-fastapi-shadcn/AGENTS.md +11 -0
  272. package/templates/react-fastapi-shadcn/CLAUDE.md +1 -0
  273. package/templates/react-fastapi-shadcn/DESIGN.md +208 -0
  274. package/templates/react-fastapi-shadcn/LICENSE +21 -0
  275. package/templates/react-fastapi-shadcn/README.md +55 -0
  276. package/templates/react-fastapi-shadcn/THIRD_PARTY_NOTICES.md +14 -0
  277. package/templates/react-fastapi-shadcn/backend/.python-version +1 -0
  278. package/templates/react-fastapi-shadcn/backend/Dockerfile +10 -0
  279. package/templates/react-fastapi-shadcn/backend/alembic.ini +3 -0
  280. package/templates/react-fastapi-shadcn/backend/app/__init__.py +0 -0
  281. package/templates/react-fastapi-shadcn/backend/app/core/__init__.py +0 -0
  282. package/templates/react-fastapi-shadcn/backend/app/core/security.py +25 -0
  283. package/templates/react-fastapi-shadcn/backend/app/db.py +11 -0
  284. package/templates/react-fastapi-shadcn/backend/app/main.py +146 -0
  285. package/templates/react-fastapi-shadcn/backend/app/models.py +21 -0
  286. package/templates/react-fastapi-shadcn/backend/app/seed.py +21 -0
  287. package/templates/react-fastapi-shadcn/backend/migrations/env.py +9 -0
  288. package/templates/react-fastapi-shadcn/backend/migrations/versions/0001_initial.py +18 -0
  289. package/templates/react-fastapi-shadcn/backend/pyproject.toml +16 -0
  290. package/templates/react-fastapi-shadcn/backend/tests/test_api.py +27 -0
  291. package/templates/react-fastapi-shadcn/backend/uv.lock +1085 -0
  292. package/templates/react-fastapi-shadcn/compose.yaml +18 -0
  293. package/templates/react-fastapi-shadcn/design/guide.md +61 -0
  294. package/templates/react-fastapi-shadcn/design/layout.css +6 -0
  295. package/templates/react-fastapi-shadcn/design/structure.json +107 -0
  296. package/templates/react-fastapi-shadcn/design/tokens.json +171 -0
  297. package/templates/react-fastapi-shadcn/design/widgets.css +30 -0
  298. package/templates/react-fastapi-shadcn/design/widgets.json +233 -0
  299. package/templates/react-fastapi-shadcn/docs/PROJECT.md +9 -0
  300. package/templates/react-fastapi-shadcn/docs/api.d.ts +516 -0
  301. package/templates/react-fastapi-shadcn/docs/openapi.json +439 -0
  302. package/templates/react-fastapi-shadcn/gitignore.template +19 -0
  303. package/templates/react-fastapi-shadcn/licenses/fastapi.txt +21 -0
  304. package/templates/react-fastapi-shadcn/licenses/nest.txt +22 -0
  305. package/templates/react-fastapi-shadcn/licenses/next.txt +21 -0
  306. package/templates/react-fastapi-shadcn/licenses/shadcn.txt +21 -0
  307. package/templates/react-fastapi-shadcn/licenses/sources.json +27 -0
  308. package/templates/react-fastapi-shadcn/licenses/spring.txt +202 -0
  309. package/templates/react-fastapi-shadcn/package-lock.json +440 -0
  310. package/templates/react-fastapi-shadcn/package.json +21 -0
  311. package/templates/react-fastapi-shadcn/playwright.config.ts +3 -0
  312. package/templates/react-fastapi-shadcn/project.json +8 -0
  313. package/templates/react-fastapi-shadcn/scripts/design-coverage.mjs +20 -0
  314. package/templates/react-fastapi-shadcn/scripts/design-next-cleanup.mjs +13 -0
  315. package/templates/react-fastapi-shadcn/scripts/design-verify.mjs +154 -0
  316. package/templates/react-fastapi-shadcn/scripts/design.mjs +108 -0
  317. package/templates/react-fastapi-shadcn/scripts/project.mjs +118 -0
  318. package/templates/react-fastapi-shadcn/tests/contract.spec.ts +47 -0
  319. package/templates/react-fastapi-shadcn/tests/design/harness.tsx +63 -0
  320. package/templates/react-fastapi-shadcn/tests/ui.spec.ts +61 -0
  321. package/templates/react-fastapi-shadcn/web/bun.lock +413 -0
  322. package/templates/react-fastapi-shadcn/web/eslint.config.mjs +3 -0
  323. package/templates/react-fastapi-shadcn/web/index.html +1 -0
  324. package/templates/react-fastapi-shadcn/web/package.json +33 -0
  325. package/templates/react-fastapi-shadcn/web/src/App.tsx +30 -0
  326. package/templates/react-fastapi-shadcn/web/src/design-layout.css +7 -0
  327. package/templates/react-fastapi-shadcn/web/src/design-runtime.json +511 -0
  328. package/templates/react-fastapi-shadcn/web/src/design-tokens.css +168 -0
  329. package/templates/react-fastapi-shadcn/web/src/design-widgets.css +31 -0
  330. package/templates/react-fastapi-shadcn/web/src/main.tsx +6 -0
  331. package/templates/react-fastapi-shadcn/web/src/structure-view.tsx +16 -0
  332. package/templates/react-fastapi-shadcn/web/src/styles.css +3 -0
  333. package/templates/react-fastapi-shadcn/web/src/tailwind.css +2 -0
  334. package/templates/react-fastapi-shadcn/web/src/ui.tsx +13 -0
  335. package/templates/react-fastapi-shadcn/web/src/widget-icon.tsx +5 -0
  336. package/templates/react-fastapi-shadcn/web/src/widget-types.ts +13 -0
  337. package/templates/react-fastapi-shadcn/web/src/workspace-model.ts +85 -0
  338. package/templates/react-fastapi-shadcn/web/tsconfig.json +22 -0
  339. package/templates/react-fastapi-shadcn/web/vite.config.ts +4 -0
  340. package/templates/react-nest-antd/.agents/skills/design-contract/SKILL.md +23 -0
  341. package/templates/react-nest-antd/.agents/skills/project-work/SKILL.md +8 -0
  342. package/templates/react-nest-antd/.claude/skills/design-contract/SKILL.md +23 -0
  343. package/templates/react-nest-antd/.claude/skills/project-work/SKILL.md +8 -0
  344. package/templates/react-nest-antd/.dockerignore +18 -0
  345. package/templates/react-nest-antd/.env.example +13 -0
  346. package/templates/react-nest-antd/.grok/skills/design-contract/SKILL.md +23 -0
  347. package/templates/react-nest-antd/.grok/skills/project-work/SKILL.md +8 -0
  348. package/templates/react-nest-antd/.scaffold-checks.json +7 -0
  349. package/templates/react-nest-antd/AGENTS.md +11 -0
  350. package/templates/react-nest-antd/CLAUDE.md +1 -0
  351. package/templates/react-nest-antd/DESIGN.md +208 -0
  352. package/templates/react-nest-antd/LICENSE +21 -0
  353. package/templates/react-nest-antd/README.md +55 -0
  354. package/templates/react-nest-antd/THIRD_PARTY_NOTICES.md +14 -0
  355. package/templates/react-nest-antd/backend/Dockerfile +12 -0
  356. package/templates/react-nest-antd/backend/eslint.config.mjs +3 -0
  357. package/templates/react-nest-antd/backend/jest.config.cjs +1 -0
  358. package/templates/react-nest-antd/backend/package.json +40 -0
  359. package/templates/react-nest-antd/backend/pnpm-lock.yaml +5165 -0
  360. package/templates/react-nest-antd/backend/pnpm-workspace.yaml +6 -0
  361. package/templates/react-nest-antd/backend/prisma/migrations/202610190001_initial/migration.sql +3 -0
  362. package/templates/react-nest-antd/backend/prisma/migrations/migration_lock.toml +1 -0
  363. package/templates/react-nest-antd/backend/prisma/schema.prisma +25 -0
  364. package/templates/react-nest-antd/backend/scripts/seed.ts +4 -0
  365. package/templates/react-nest-antd/backend/src/app.module.ts +43 -0
  366. package/templates/react-nest-antd/backend/src/main.ts +6 -0
  367. package/templates/react-nest-antd/backend/src/password.ts +3 -0
  368. package/templates/react-nest-antd/backend/src/validation.ts +7 -0
  369. package/templates/react-nest-antd/backend/test/api.test.ts +25 -0
  370. package/templates/react-nest-antd/backend/tsconfig.build.json +15 -0
  371. package/templates/react-nest-antd/backend/tsconfig.json +17 -0
  372. package/templates/react-nest-antd/compose.yaml +18 -0
  373. package/templates/react-nest-antd/design/guide.md +61 -0
  374. package/templates/react-nest-antd/design/layout.css +6 -0
  375. package/templates/react-nest-antd/design/structure.json +107 -0
  376. package/templates/react-nest-antd/design/tokens.json +171 -0
  377. package/templates/react-nest-antd/design/widgets.css +30 -0
  378. package/templates/react-nest-antd/design/widgets.json +233 -0
  379. package/templates/react-nest-antd/docs/PROJECT.md +9 -0
  380. package/templates/react-nest-antd/docs/api.d.ts +516 -0
  381. package/templates/react-nest-antd/docs/openapi.json +439 -0
  382. package/templates/react-nest-antd/gitignore.template +19 -0
  383. package/templates/react-nest-antd/licenses/fastapi.txt +21 -0
  384. package/templates/react-nest-antd/licenses/nest.txt +22 -0
  385. package/templates/react-nest-antd/licenses/next.txt +21 -0
  386. package/templates/react-nest-antd/licenses/shadcn.txt +21 -0
  387. package/templates/react-nest-antd/licenses/sources.json +27 -0
  388. package/templates/react-nest-antd/licenses/spring.txt +202 -0
  389. package/templates/react-nest-antd/package-lock.json +440 -0
  390. package/templates/react-nest-antd/package.json +21 -0
  391. package/templates/react-nest-antd/playwright.config.ts +3 -0
  392. package/templates/react-nest-antd/project.json +8 -0
  393. package/templates/react-nest-antd/scripts/design-coverage.mjs +20 -0
  394. package/templates/react-nest-antd/scripts/design-next-cleanup.mjs +13 -0
  395. package/templates/react-nest-antd/scripts/design-verify.mjs +154 -0
  396. package/templates/react-nest-antd/scripts/design.mjs +108 -0
  397. package/templates/react-nest-antd/scripts/project.mjs +118 -0
  398. package/templates/react-nest-antd/tests/contract.spec.ts +47 -0
  399. package/templates/react-nest-antd/tests/design/harness.tsx +63 -0
  400. package/templates/react-nest-antd/tests/ui.spec.ts +61 -0
  401. package/templates/react-nest-antd/web/eslint.config.mjs +3 -0
  402. package/templates/react-nest-antd/web/index.html +1 -0
  403. package/templates/react-nest-antd/web/package.json +28 -0
  404. package/templates/react-nest-antd/web/pnpm-lock.yaml +2215 -0
  405. package/templates/react-nest-antd/web/src/App.tsx +29 -0
  406. package/templates/react-nest-antd/web/src/design-layout.css +7 -0
  407. package/templates/react-nest-antd/web/src/design-runtime.json +511 -0
  408. package/templates/react-nest-antd/web/src/design-tokens.css +168 -0
  409. package/templates/react-nest-antd/web/src/design-widgets.css +31 -0
  410. package/templates/react-nest-antd/web/src/main.tsx +6 -0
  411. package/templates/react-nest-antd/web/src/structure-view.tsx +16 -0
  412. package/templates/react-nest-antd/web/src/styles.css +3 -0
  413. package/templates/react-nest-antd/web/src/ui.tsx +30 -0
  414. package/templates/react-nest-antd/web/src/widget-icon.tsx +5 -0
  415. package/templates/react-nest-antd/web/src/widget-types.ts +13 -0
  416. package/templates/react-nest-antd/web/src/workspace-model.ts +85 -0
  417. package/templates/react-nest-antd/web/tsconfig.json +22 -0
  418. package/templates/react-nest-antd/web/vite.config.ts +3 -0
  419. package/templates/react-nest-shadcn/.agents/skills/design-contract/SKILL.md +23 -0
  420. package/templates/react-nest-shadcn/.agents/skills/project-work/SKILL.md +8 -0
  421. package/templates/react-nest-shadcn/.claude/skills/design-contract/SKILL.md +23 -0
  422. package/templates/react-nest-shadcn/.claude/skills/project-work/SKILL.md +8 -0
  423. package/templates/react-nest-shadcn/.dockerignore +18 -0
  424. package/templates/react-nest-shadcn/.env.example +13 -0
  425. package/templates/react-nest-shadcn/.grok/skills/design-contract/SKILL.md +23 -0
  426. package/templates/react-nest-shadcn/.grok/skills/project-work/SKILL.md +8 -0
  427. package/templates/react-nest-shadcn/.scaffold-checks.json +7 -0
  428. package/templates/react-nest-shadcn/AGENTS.md +11 -0
  429. package/templates/react-nest-shadcn/CLAUDE.md +1 -0
  430. package/templates/react-nest-shadcn/DESIGN.md +208 -0
  431. package/templates/react-nest-shadcn/LICENSE +21 -0
  432. package/templates/react-nest-shadcn/README.md +55 -0
  433. package/templates/react-nest-shadcn/THIRD_PARTY_NOTICES.md +14 -0
  434. package/templates/react-nest-shadcn/backend/Dockerfile +12 -0
  435. package/templates/react-nest-shadcn/backend/eslint.config.mjs +3 -0
  436. package/templates/react-nest-shadcn/backend/jest.config.cjs +1 -0
  437. package/templates/react-nest-shadcn/backend/package.json +40 -0
  438. package/templates/react-nest-shadcn/backend/pnpm-lock.yaml +5165 -0
  439. package/templates/react-nest-shadcn/backend/pnpm-workspace.yaml +6 -0
  440. package/templates/react-nest-shadcn/backend/prisma/migrations/202610190001_initial/migration.sql +3 -0
  441. package/templates/react-nest-shadcn/backend/prisma/migrations/migration_lock.toml +1 -0
  442. package/templates/react-nest-shadcn/backend/prisma/schema.prisma +25 -0
  443. package/templates/react-nest-shadcn/backend/scripts/seed.ts +4 -0
  444. package/templates/react-nest-shadcn/backend/src/app.module.ts +43 -0
  445. package/templates/react-nest-shadcn/backend/src/main.ts +6 -0
  446. package/templates/react-nest-shadcn/backend/src/password.ts +3 -0
  447. package/templates/react-nest-shadcn/backend/src/validation.ts +7 -0
  448. package/templates/react-nest-shadcn/backend/test/api.test.ts +25 -0
  449. package/templates/react-nest-shadcn/backend/tsconfig.build.json +15 -0
  450. package/templates/react-nest-shadcn/backend/tsconfig.json +17 -0
  451. package/templates/react-nest-shadcn/compose.yaml +18 -0
  452. package/templates/react-nest-shadcn/design/guide.md +61 -0
  453. package/templates/react-nest-shadcn/design/layout.css +6 -0
  454. package/templates/react-nest-shadcn/design/structure.json +107 -0
  455. package/templates/react-nest-shadcn/design/tokens.json +171 -0
  456. package/templates/react-nest-shadcn/design/widgets.css +30 -0
  457. package/templates/react-nest-shadcn/design/widgets.json +233 -0
  458. package/templates/react-nest-shadcn/docs/PROJECT.md +9 -0
  459. package/templates/react-nest-shadcn/docs/api.d.ts +516 -0
  460. package/templates/react-nest-shadcn/docs/openapi.json +439 -0
  461. package/templates/react-nest-shadcn/gitignore.template +19 -0
  462. package/templates/react-nest-shadcn/licenses/fastapi.txt +21 -0
  463. package/templates/react-nest-shadcn/licenses/nest.txt +22 -0
  464. package/templates/react-nest-shadcn/licenses/next.txt +21 -0
  465. package/templates/react-nest-shadcn/licenses/shadcn.txt +21 -0
  466. package/templates/react-nest-shadcn/licenses/sources.json +27 -0
  467. package/templates/react-nest-shadcn/licenses/spring.txt +202 -0
  468. package/templates/react-nest-shadcn/package-lock.json +440 -0
  469. package/templates/react-nest-shadcn/package.json +21 -0
  470. package/templates/react-nest-shadcn/playwright.config.ts +3 -0
  471. package/templates/react-nest-shadcn/project.json +8 -0
  472. package/templates/react-nest-shadcn/scripts/design-coverage.mjs +20 -0
  473. package/templates/react-nest-shadcn/scripts/design-next-cleanup.mjs +13 -0
  474. package/templates/react-nest-shadcn/scripts/design-verify.mjs +154 -0
  475. package/templates/react-nest-shadcn/scripts/design.mjs +108 -0
  476. package/templates/react-nest-shadcn/scripts/project.mjs +118 -0
  477. package/templates/react-nest-shadcn/tests/contract.spec.ts +47 -0
  478. package/templates/react-nest-shadcn/tests/design/harness.tsx +63 -0
  479. package/templates/react-nest-shadcn/tests/ui.spec.ts +61 -0
  480. package/templates/react-nest-shadcn/web/eslint.config.mjs +3 -0
  481. package/templates/react-nest-shadcn/web/index.html +1 -0
  482. package/templates/react-nest-shadcn/web/package.json +33 -0
  483. package/templates/react-nest-shadcn/web/pnpm-lock.yaml +1788 -0
  484. package/templates/react-nest-shadcn/web/src/App.tsx +30 -0
  485. package/templates/react-nest-shadcn/web/src/design-layout.css +7 -0
  486. package/templates/react-nest-shadcn/web/src/design-runtime.json +511 -0
  487. package/templates/react-nest-shadcn/web/src/design-tokens.css +168 -0
  488. package/templates/react-nest-shadcn/web/src/design-widgets.css +31 -0
  489. package/templates/react-nest-shadcn/web/src/main.tsx +6 -0
  490. package/templates/react-nest-shadcn/web/src/structure-view.tsx +16 -0
  491. package/templates/react-nest-shadcn/web/src/styles.css +3 -0
  492. package/templates/react-nest-shadcn/web/src/tailwind.css +2 -0
  493. package/templates/react-nest-shadcn/web/src/ui.tsx +13 -0
  494. package/templates/react-nest-shadcn/web/src/widget-icon.tsx +5 -0
  495. package/templates/react-nest-shadcn/web/src/widget-types.ts +13 -0
  496. package/templates/react-nest-shadcn/web/src/workspace-model.ts +85 -0
  497. package/templates/react-nest-shadcn/web/tsconfig.json +22 -0
  498. package/templates/react-nest-shadcn/web/vite.config.ts +4 -0
  499. package/templates/react-spring-antd/.agents/skills/design-contract/SKILL.md +23 -0
  500. package/templates/react-spring-antd/.agents/skills/project-work/SKILL.md +8 -0
  501. package/templates/react-spring-antd/.claude/skills/design-contract/SKILL.md +23 -0
  502. package/templates/react-spring-antd/.claude/skills/project-work/SKILL.md +8 -0
  503. package/templates/react-spring-antd/.dockerignore +18 -0
  504. package/templates/react-spring-antd/.env.example +13 -0
  505. package/templates/react-spring-antd/.grok/skills/design-contract/SKILL.md +23 -0
  506. package/templates/react-spring-antd/.grok/skills/project-work/SKILL.md +8 -0
  507. package/templates/react-spring-antd/.scaffold-checks.json +7 -0
  508. package/templates/react-spring-antd/AGENTS.md +11 -0
  509. package/templates/react-spring-antd/CLAUDE.md +1 -0
  510. package/templates/react-spring-antd/DESIGN.md +208 -0
  511. package/templates/react-spring-antd/LICENSE +21 -0
  512. package/templates/react-spring-antd/README.md +55 -0
  513. package/templates/react-spring-antd/THIRD_PARTY_NOTICES.md +14 -0
  514. package/templates/react-spring-antd/backend/.mvn/wrapper/maven-wrapper.properties +3 -0
  515. package/templates/react-spring-antd/backend/Dockerfile +11 -0
  516. package/templates/react-spring-antd/backend/mvnw +295 -0
  517. package/templates/react-spring-antd/backend/pom.xml +15 -0
  518. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Api.java +38 -0
  519. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/AppUser.java +11 -0
  520. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Application.java +7 -0
  521. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Errors.java +13 -0
  522. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/PersonalRecord.java +15 -0
  523. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Records.java +14 -0
  524. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Security.java +28 -0
  525. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Seed.java +13 -0
  526. package/templates/react-spring-antd/backend/src/main/java/dev/scaffold/app/Users.java +5 -0
  527. package/templates/react-spring-antd/backend/src/main/resources/application.properties +17 -0
  528. package/templates/react-spring-antd/backend/src/main/resources/db/migration/V1__initial.sql +3 -0
  529. package/templates/react-spring-antd/backend/src/main/resources/openapi.json +439 -0
  530. package/templates/react-spring-antd/backend/src/test/java/dev/scaffold/app/ApiTest.java +28 -0
  531. package/templates/react-spring-antd/backend/src/test/java/dev/scaffold/app/SessionExpiryTest.java +37 -0
  532. package/templates/react-spring-antd/compose.yaml +18 -0
  533. package/templates/react-spring-antd/design/guide.md +61 -0
  534. package/templates/react-spring-antd/design/layout.css +6 -0
  535. package/templates/react-spring-antd/design/structure.json +107 -0
  536. package/templates/react-spring-antd/design/tokens.json +171 -0
  537. package/templates/react-spring-antd/design/widgets.css +30 -0
  538. package/templates/react-spring-antd/design/widgets.json +233 -0
  539. package/templates/react-spring-antd/docs/PROJECT.md +9 -0
  540. package/templates/react-spring-antd/docs/api.d.ts +516 -0
  541. package/templates/react-spring-antd/docs/openapi.json +439 -0
  542. package/templates/react-spring-antd/gitignore.template +19 -0
  543. package/templates/react-spring-antd/licenses/fastapi.txt +21 -0
  544. package/templates/react-spring-antd/licenses/nest.txt +22 -0
  545. package/templates/react-spring-antd/licenses/next.txt +21 -0
  546. package/templates/react-spring-antd/licenses/shadcn.txt +21 -0
  547. package/templates/react-spring-antd/licenses/sources.json +27 -0
  548. package/templates/react-spring-antd/licenses/spring.txt +202 -0
  549. package/templates/react-spring-antd/package-lock.json +440 -0
  550. package/templates/react-spring-antd/package.json +21 -0
  551. package/templates/react-spring-antd/playwright.config.ts +3 -0
  552. package/templates/react-spring-antd/project.json +8 -0
  553. package/templates/react-spring-antd/scripts/design-coverage.mjs +20 -0
  554. package/templates/react-spring-antd/scripts/design-next-cleanup.mjs +13 -0
  555. package/templates/react-spring-antd/scripts/design-verify.mjs +154 -0
  556. package/templates/react-spring-antd/scripts/design.mjs +108 -0
  557. package/templates/react-spring-antd/scripts/project.mjs +118 -0
  558. package/templates/react-spring-antd/tests/contract.spec.ts +47 -0
  559. package/templates/react-spring-antd/tests/design/harness.tsx +63 -0
  560. package/templates/react-spring-antd/tests/ui.spec.ts +61 -0
  561. package/templates/react-spring-antd/web/eslint.config.mjs +3 -0
  562. package/templates/react-spring-antd/web/index.html +1 -0
  563. package/templates/react-spring-antd/web/package.json +28 -0
  564. package/templates/react-spring-antd/web/pnpm-lock.yaml +2215 -0
  565. package/templates/react-spring-antd/web/src/App.tsx +29 -0
  566. package/templates/react-spring-antd/web/src/design-layout.css +7 -0
  567. package/templates/react-spring-antd/web/src/design-runtime.json +511 -0
  568. package/templates/react-spring-antd/web/src/design-tokens.css +168 -0
  569. package/templates/react-spring-antd/web/src/design-widgets.css +31 -0
  570. package/templates/react-spring-antd/web/src/main.tsx +6 -0
  571. package/templates/react-spring-antd/web/src/structure-view.tsx +16 -0
  572. package/templates/react-spring-antd/web/src/styles.css +3 -0
  573. package/templates/react-spring-antd/web/src/ui.tsx +30 -0
  574. package/templates/react-spring-antd/web/src/widget-icon.tsx +5 -0
  575. package/templates/react-spring-antd/web/src/widget-types.ts +13 -0
  576. package/templates/react-spring-antd/web/src/workspace-model.ts +85 -0
  577. package/templates/react-spring-antd/web/tsconfig.json +22 -0
  578. package/templates/react-spring-antd/web/vite.config.ts +3 -0
  579. package/templates/react-spring-shadcn/.agents/skills/design-contract/SKILL.md +23 -0
  580. package/templates/react-spring-shadcn/.agents/skills/project-work/SKILL.md +8 -0
  581. package/templates/react-spring-shadcn/.claude/skills/design-contract/SKILL.md +23 -0
  582. package/templates/react-spring-shadcn/.claude/skills/project-work/SKILL.md +8 -0
  583. package/templates/react-spring-shadcn/.dockerignore +18 -0
  584. package/templates/react-spring-shadcn/.env.example +13 -0
  585. package/templates/react-spring-shadcn/.grok/skills/design-contract/SKILL.md +23 -0
  586. package/templates/react-spring-shadcn/.grok/skills/project-work/SKILL.md +8 -0
  587. package/templates/react-spring-shadcn/.scaffold-checks.json +7 -0
  588. package/templates/react-spring-shadcn/AGENTS.md +11 -0
  589. package/templates/react-spring-shadcn/CLAUDE.md +1 -0
  590. package/templates/react-spring-shadcn/DESIGN.md +208 -0
  591. package/templates/react-spring-shadcn/LICENSE +21 -0
  592. package/templates/react-spring-shadcn/README.md +55 -0
  593. package/templates/react-spring-shadcn/THIRD_PARTY_NOTICES.md +14 -0
  594. package/templates/react-spring-shadcn/backend/.mvn/wrapper/maven-wrapper.properties +3 -0
  595. package/templates/react-spring-shadcn/backend/Dockerfile +11 -0
  596. package/templates/react-spring-shadcn/backend/mvnw +295 -0
  597. package/templates/react-spring-shadcn/backend/pom.xml +15 -0
  598. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Api.java +38 -0
  599. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/AppUser.java +11 -0
  600. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Application.java +7 -0
  601. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Errors.java +13 -0
  602. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/PersonalRecord.java +15 -0
  603. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Records.java +14 -0
  604. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Security.java +28 -0
  605. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Seed.java +13 -0
  606. package/templates/react-spring-shadcn/backend/src/main/java/dev/scaffold/app/Users.java +5 -0
  607. package/templates/react-spring-shadcn/backend/src/main/resources/application.properties +17 -0
  608. package/templates/react-spring-shadcn/backend/src/main/resources/db/migration/V1__initial.sql +3 -0
  609. package/templates/react-spring-shadcn/backend/src/main/resources/openapi.json +439 -0
  610. package/templates/react-spring-shadcn/backend/src/test/java/dev/scaffold/app/ApiTest.java +28 -0
  611. package/templates/react-spring-shadcn/backend/src/test/java/dev/scaffold/app/SessionExpiryTest.java +37 -0
  612. package/templates/react-spring-shadcn/compose.yaml +18 -0
  613. package/templates/react-spring-shadcn/design/guide.md +61 -0
  614. package/templates/react-spring-shadcn/design/layout.css +6 -0
  615. package/templates/react-spring-shadcn/design/structure.json +107 -0
  616. package/templates/react-spring-shadcn/design/tokens.json +171 -0
  617. package/templates/react-spring-shadcn/design/widgets.css +30 -0
  618. package/templates/react-spring-shadcn/design/widgets.json +233 -0
  619. package/templates/react-spring-shadcn/docs/PROJECT.md +9 -0
  620. package/templates/react-spring-shadcn/docs/api.d.ts +516 -0
  621. package/templates/react-spring-shadcn/docs/openapi.json +439 -0
  622. package/templates/react-spring-shadcn/gitignore.template +19 -0
  623. package/templates/react-spring-shadcn/licenses/fastapi.txt +21 -0
  624. package/templates/react-spring-shadcn/licenses/nest.txt +22 -0
  625. package/templates/react-spring-shadcn/licenses/next.txt +21 -0
  626. package/templates/react-spring-shadcn/licenses/shadcn.txt +21 -0
  627. package/templates/react-spring-shadcn/licenses/sources.json +27 -0
  628. package/templates/react-spring-shadcn/licenses/spring.txt +202 -0
  629. package/templates/react-spring-shadcn/package-lock.json +440 -0
  630. package/templates/react-spring-shadcn/package.json +21 -0
  631. package/templates/react-spring-shadcn/playwright.config.ts +3 -0
  632. package/templates/react-spring-shadcn/project.json +8 -0
  633. package/templates/react-spring-shadcn/scripts/design-coverage.mjs +20 -0
  634. package/templates/react-spring-shadcn/scripts/design-next-cleanup.mjs +13 -0
  635. package/templates/react-spring-shadcn/scripts/design-verify.mjs +154 -0
  636. package/templates/react-spring-shadcn/scripts/design.mjs +108 -0
  637. package/templates/react-spring-shadcn/scripts/project.mjs +118 -0
  638. package/templates/react-spring-shadcn/tests/contract.spec.ts +47 -0
  639. package/templates/react-spring-shadcn/tests/design/harness.tsx +63 -0
  640. package/templates/react-spring-shadcn/tests/ui.spec.ts +61 -0
  641. package/templates/react-spring-shadcn/web/eslint.config.mjs +3 -0
  642. package/templates/react-spring-shadcn/web/index.html +1 -0
  643. package/templates/react-spring-shadcn/web/package.json +33 -0
  644. package/templates/react-spring-shadcn/web/pnpm-lock.yaml +1788 -0
  645. package/templates/react-spring-shadcn/web/src/App.tsx +30 -0
  646. package/templates/react-spring-shadcn/web/src/design-layout.css +7 -0
  647. package/templates/react-spring-shadcn/web/src/design-runtime.json +511 -0
  648. package/templates/react-spring-shadcn/web/src/design-tokens.css +168 -0
  649. package/templates/react-spring-shadcn/web/src/design-widgets.css +31 -0
  650. package/templates/react-spring-shadcn/web/src/main.tsx +6 -0
  651. package/templates/react-spring-shadcn/web/src/structure-view.tsx +16 -0
  652. package/templates/react-spring-shadcn/web/src/styles.css +3 -0
  653. package/templates/react-spring-shadcn/web/src/tailwind.css +2 -0
  654. package/templates/react-spring-shadcn/web/src/ui.tsx +13 -0
  655. package/templates/react-spring-shadcn/web/src/widget-icon.tsx +5 -0
  656. package/templates/react-spring-shadcn/web/src/widget-types.ts +13 -0
  657. package/templates/react-spring-shadcn/web/src/workspace-model.ts +85 -0
  658. package/templates/react-spring-shadcn/web/tsconfig.json +22 -0
  659. package/templates/react-spring-shadcn/web/vite.config.ts +4 -0
@@ -0,0 +1,8 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "nameZh": "模板工作空间",
4
+ "nameEn": "template-project",
5
+ "description": "模板验证",
6
+ "template": "react-fastapi",
7
+ "ui": "shadcn"
8
+ }
@@ -0,0 +1,20 @@
1
+ // Each entry corresponds to a concrete branch in tests/design/harness.tsx.
2
+ const supported={
3
+ button:{variants:['primary','secondary','danger'],states:['default','hover','active','focus','disabled','loading']},
4
+ input:{variants:['text','email','password'],states:['default','hover','focus','disabled','invalid','readonly']},
5
+ textarea:{variants:['default'],states:['default','hover','focus','disabled','invalid','readonly']},
6
+ alert:{variants:['error','success'],states:['default']},
7
+ badge:{variants:['default'],states:['default']},panel:{variants:['default'],states:['default']},field:{variants:['default'],states:['default']}
8
+ };
9
+ const scenarios=['checking','login','login-error','login-busy','workspace-empty','workspace-loading','workspace-records','editor-new','editor-existing','editor-error'];
10
+ export function validateFixtureCoverage(design){
11
+ const acceptance=design.widgets.acceptance;
12
+ if(!Array.isArray(acceptance.viewports)||!acceptance.viewports.length||acceptance.viewports.some(view=>!Number.isInteger(view.width)||!Number.isInteger(view.height)||view.width<1||view.height<1))throw new Error('Design verification requires nonempty valid viewports');
13
+ if(acceptance.positionTolerance!==0.6)throw new Error('Position tolerance must remain 0.6 CSS px');
14
+ if(!Object.keys(design.widgets.widgets).length)throw new Error('No widget coverage');
15
+ for(const [name,spec] of Object.entries(design.widgets.widgets)){
16
+ if(!supported[name])throw new Error('Unimplemented widget fixture: '+name);
17
+ for(const key of ['variants','states'])if(!Array.isArray(spec[key])||!spec[key].length||spec[key].some(value=>!supported[name][key].includes(value)))throw new Error('Unimplemented fixture '+name+'.'+key);
18
+ }
19
+ if(!Array.isArray(design.structure.scenarios)||!design.structure.scenarios.length||design.structure.scenarios.some(value=>!scenarios.includes(value)))throw new Error('Unimplemented page scenario');
20
+ }
@@ -0,0 +1,13 @@
1
+ // Remove only paths owned by this fixture run; preserve concurrent source edits.
2
+ export function cleanNextFixtureConfig(file,current,original,dist) {
3
+ if(file==='tsconfig.json'){
4
+ const value=JSON.parse(current),before=JSON.parse(original);
5
+ if(Array.isArray(value.include))value.include=value.include.filter(path=>path!==`${dist}/types/**/*.ts`&&path!==`${dist}/dev/types/**/*.ts`);
6
+ return JSON.stringify(value)===JSON.stringify(before)?original:JSON.stringify(value,null,2)+'\n';
7
+ }
8
+ return current.split('\n').map(line=>{
9
+ if(!line.startsWith(`import "./${dist}/`))return line;
10
+ const suffix=line.slice(line.lastIndexOf('/types/'));
11
+ return original.split('\n').find(old=>old.startsWith('import ')&&old.endsWith(suffix))||'';
12
+ }).join('\n');
13
+ }
@@ -0,0 +1,154 @@
1
+ import {spawn} from 'node:child_process';
2
+ import {createServer} from 'node:net';
3
+ import {createRequire} from 'node:module';
4
+ import {mkdir,readFile,writeFile,rm} from 'node:fs/promises';
5
+ import {dirname,resolve,join} from 'node:path';
6
+ import {fileURLToPath} from 'node:url';
7
+ import {createHash,randomUUID} from 'node:crypto';
8
+ import {cleanNextFixtureConfig} from './design-next-cleanup.mjs';
9
+ import {syncDesign} from './design.mjs';
10
+ import {validateFixtureCoverage} from './design-coverage.mjs';
11
+ const root=resolve(dirname(fileURLToPath(import.meta.url)),'..');
12
+ const require=createRequire(join(root,'package.json'));
13
+ const {chromium}=require('@playwright/test');
14
+ const project=JSON.parse(await readFile(join(root,'project.json'),'utf8'));
15
+ const design=JSON.parse(await readFile(join(root,'web/src/design-runtime.json'),'utf8'));
16
+ const output=join(root,'reports/design-check');await mkdir(output,{recursive:true});
17
+ const nonce=randomUUID().slice(0,8),next=project.template==='next-fullstack';
18
+ const nextDist=`.next/scaffold-design-cache-${nonce}`,nextFiles=new Map();
19
+ const harness=join(root,`web/src/scaffold-design-check-${nonce}.tsx`);
20
+ const entry=next?join(root,`web/app/scaffold-design-check-${nonce}/page.tsx`):join(root,`web/scaffold-design-check-${nonce}.html`);
21
+ let server,browser,serverLog='',interrupted=false,createdHarness=false,createdEntry=false,createdDirectory=false;
22
+ const signals=()=>{interrupted=true;server?.kill('SIGTERM');};
23
+ process.once('SIGINT',signals);process.once('SIGTERM',signals);
24
+ const report={schemaVersion:1,status:'FAIL',template:project.template,ui:project.ui,platform:process.platform,startedAt:new Date().toISOString(),cases:[],failures:[]};
25
+ function compare(expected,actual,path='') {
26
+ if(typeof expected==='number'&&typeof actual==='number')return Math.abs(expected-actual)<=design.widgets.acceptance.positionTolerance?[]:[`${path}: ${expected} != ${actual}`];
27
+ if(expected===null||actual===null||typeof expected!=='object'||typeof actual!=='object')return expected===actual?[]:[`${path}: ${JSON.stringify(expected)} != ${JSON.stringify(actual)}`];
28
+ const keys=new Set([...Object.keys(expected),...Object.keys(actual)]);
29
+ return [...keys].flatMap(key=>compare(expected[key],actual[key],path?path+'.'+key:key));
30
+ }
31
+ async function collect(page){return page.evaluate(()=>{
32
+ const selectors='[data-widget],[data-part],[data-region],h1,h2,h3,p,label,button,input,textarea,a';
33
+ const elements=[...document.querySelectorAll(selectors)].filter(el=>el.getClientRects().length&&getComputedStyle(el).display!=='none');
34
+ const styles=['color','backgroundColor','borderTopColor','borderRightColor','borderBottomColor','borderLeftColor','borderTopLeftRadius','borderTopRightRadius','borderBottomLeftRadius','borderBottomRightRadius','borderTopWidth','borderRightWidth','borderBottomWidth','borderLeftWidth','fontFamily','fontSize','fontWeight','lineHeight','letterSpacing','paddingTop','paddingRight','paddingBottom','paddingLeft','opacity','outlineColor','outlineWidth','outlineStyle','outlineOffset','boxShadow'];
35
+ const box=rect=>({x:rect.x,y:rect.y,width:rect.width,height:rect.height});
36
+ return elements.map(el=>{
37
+ const css=getComputedStyle(el),walker=document.createTreeWalker(el,NodeFilter.SHOW_TEXT),text=[];
38
+ let node;while((node=walker.nextNode()))if(node.textContent.trim()){const range=document.createRange();range.selectNodeContents(node);text.push({value:node.textContent,box:box(range.getBoundingClientRect())});}
39
+ return {role:el.getAttribute('data-widget')||el.getAttribute('data-part')||el.getAttribute('data-region')||el.tagName.toLowerCase(),box:box(el.getBoundingClientRect()),styles:Object.fromEntries(styles.map(name=>[name,css[name]])),text,icon:el.tagName.toLowerCase()==='svg'?{viewBox:el.getAttribute('viewBox'),stroke:el.getAttribute('stroke'),strokeWidth:el.getAttribute('stroke-width'),linecap:el.getAttribute('stroke-linecap'),linejoin:el.getAttribute('stroke-linejoin'),paths:[...el.querySelectorAll('path')].map(path=>path.getAttribute('d'))}:null};
40
+ });
41
+ });}
42
+ async function checkSpecification(page){
43
+ const failures=await page.evaluate(design=>{
44
+ const failures=[],colors=design.tokens.colors;
45
+ const normal=(property,value)=>{const probe=document.createElement('span');probe.style.borderStyle='solid';probe.style[property]=String(value);document.body.append(probe);const result=getComputedStyle(probe)[property];probe.remove();return result;};
46
+ for(const element of document.querySelectorAll('[data-widget]')){
47
+ const name=element.getAttribute('data-widget'),spec=design.widgets.widgets[name];
48
+ if(!spec){failures.push('Undeclared rendered widget '+name);continue;}
49
+ for(const part of spec.parts)if(part!=='root'&&!element.querySelector(`[data-part="${part}"]`))failures.push(name+' missing part '+part);
50
+ const target=name==='field'?element.querySelector('[data-part="label"]'):element,css=getComputedStyle(target);
51
+ const map={height:'height',fontSize:'fontSize',fontWeight:'fontWeight',lineHeight:'lineHeight',radius:'borderTopLeftRadius',borderWidth:'borderTopWidth',paddingX:'paddingLeft',paddingY:'paddingTop'};
52
+ for(const [key,property] of Object.entries(map))if(spec.styles[key]!==undefined){const expected=normal(property,spec.styles[key]);if(css[property]!==expected)failures.push(name+'.'+property+': expected '+expected+', got '+css[property]);}
53
+ const assertColor=(property,value)=>{const expected=normal(property,value);if(css[property]!==expected)failures.push(name+'.'+property+': expected '+expected+', got '+css[property]);};
54
+ if(name==='button'){
55
+ const variant=element.getAttribute('data-variant'),hover=element.matches(':hover'),active=element.matches(':active');
56
+ const key=variant==='primary'?(active?'primary-active':hover?'primary-hover':'primary'):variant==='secondary'?(hover||active?'secondary-hover':'control-background'):(hover||active?'danger-hover':'danger-background');
57
+ assertColor('backgroundColor',colors[key]);assertColor('color',colors[variant+'-text']);
58
+ }
59
+ if(name==='input'||name==='textarea'){assertColor('backgroundColor',colors['control-background']);assertColor('color',colors.text);}
60
+ if(name==='alert'){const variant=element.getAttribute('data-variant');assertColor('backgroundColor',colors[variant+'-background']);assertColor('color',colors[variant+'-text']);
61
+ const icon=element.querySelector('[data-part="icon"]'),expected=design.widgets.icons[variant];
62
+ if(icon?.getAttribute('viewBox')!==expected.viewBox||icon?.getAttribute('stroke-width')!==String(expected.strokeWidth)||JSON.stringify([...icon.querySelectorAll('path')].map(path=>path.getAttribute('d')))!==JSON.stringify(expected.paths))failures.push('Icon geometry differs from declared '+variant);
63
+ }
64
+ if(name==='panel')assertColor('backgroundColor',colors.surface);
65
+ const rootCss=getComputedStyle(element);
66
+ const dimension=(node,property,value)=>{const actual=getComputedStyle(node)[property],expected=normal(property,value);if(actual!==expected)failures.push(name+'.'+property+': expected '+expected+', got '+actual);};
67
+ if(spec.styles.gap)dimension(element,'gap',spec.styles.gap);
68
+ if(spec.styles.marginY){dimension(element,'marginTop',spec.styles.marginY);dimension(element,'marginBottom',spec.styles.marginY);}
69
+ if(spec.styles.padding)for(const side of ['Top','Right','Bottom','Left'])dimension(element,'padding'+side,innerWidth<=parseFloat(design.tokens.spacing['mobile-breakpoint'])?design.tokens.spacing.s20:spec.styles.padding);
70
+ if(spec.styles.iconSize){const icon=element.querySelector('[data-part="icon"]');dimension(icon,'width',spec.styles.iconSize);dimension(icon,'height',spec.styles.iconSize);}
71
+ if(element.matches(':focus-visible')&&name==='button'||element.matches(':focus')&&['input','textarea'].includes(name)){
72
+ dimension(element,'outlineWidth','2px');if(rootCss.outlineStyle!=='solid')failures.push(name+' missing focus outline');assertColor('outlineColor',colors['control-focus']);
73
+ }
74
+ if(name==='input'||name==='textarea')assertColor('borderTopColor',colors[element.getAttribute('aria-invalid')==='true'?'error-border':element.matches(':hover')?'control-focus':'control-border']);
75
+ if(element.matches(':disabled')&&rootCss.opacity!=='0.5')failures.push(name+' missing disabled appearance');
76
+ }
77
+ return failures;
78
+ },design);
79
+ if(failures.length)throw new Error(failures.slice(0,12).join('\n'));
80
+ }
81
+
82
+ async function interact(page,id){
83
+ const [widget,,state]=id.split('.');if(widget==='page')return;
84
+ const control=page.locator(`[data-widget="${widget}"]`).first();
85
+ if(state==='hover'||state==='active'){await control.hover();if(state==='active')await page.mouse.down();}
86
+ if(state==='focus'){await page.keyboard.press('Tab');if(!await control.evaluate(el=>el===document.activeElement))throw new Error(id+' did not receive focus');}
87
+ if(state==='disabled'||state==='loading'){if(!await control.isDisabled())throw new Error(id+' must disable interaction');}
88
+ if(state==='loading'&&await control.getAttribute('aria-busy')!=='true')throw new Error('Loading state is not exposed');
89
+ if(state==='invalid'&&await control.getAttribute('aria-invalid')!=='true')throw new Error('Invalid state is not exposed');
90
+ if(widget==='input'&&id.split('.')[1]==='password'&&await control.getAttribute('type')!=='password')throw new Error('Password must stay masked');
91
+ }
92
+ async function checkKeyboard(page,id){
93
+ const [widget,,state]=id.split('.'),control=page.locator(`[data-widget="${widget}"]`).first();
94
+ if(widget==='button'&&state==='default'){
95
+ await control.focus();await page.keyboard.press('Enter');await page.keyboard.press('Space');
96
+ if(await page.locator('[data-events]').getAttribute('data-events')!=='2')throw new Error('Button must activate once per Enter/Space');
97
+ }
98
+ if((widget==='input'||widget==='textarea')&&state==='readonly'){
99
+ const before=await control.inputValue();await control.focus();await page.keyboard.type('x');if(await control.inputValue()!==before)throw new Error('Readonly value changed');
100
+ }
101
+ }
102
+ try {
103
+ await syncDesign(root,true);
104
+ if(next)for(const file of ['tsconfig.json','next-env.d.ts'])nextFiles.set(file,await readFile(join(root,'web',file)));
105
+ validateFixtureCoverage(design);
106
+ const digest=createHash('sha256');for(const file of ['design/tokens.json','design/widgets.json','design/structure.json','design/layout.css','design/widgets.css','web/src/ui.tsx','web/src/App.tsx','web/src/workspace-model.ts','tests/design/harness.tsx','scripts/design-verify.mjs','scripts/design-coverage.mjs','scripts/design-next-cleanup.mjs','scripts/design.mjs','web/src/structure-view.tsx','web/src/widget-icon.tsx','web/src/widget-types.ts','web/src/styles.css','web/src/design-tokens.css','web/src/design-layout.css','web/src/design-widgets.css','web/src/design-runtime.json','project.json',...(next?['web/next.config.ts']:[]),...(project.ui==='shadcn'?['web/src/tailwind.css']:[])])digest.update(file).update(await readFile(join(root,file)));report.sourceDigest=digest.digest('hex');
107
+ await writeFile(harness,await readFile(join(root,'tests/design/harness.tsx')),{flag:'wx'});createdHarness=true;
108
+ if(next){await mkdir(dirname(entry));createdDirectory=true;await writeFile(entry,`export {default} from '../../src/scaffold-design-check-${nonce}';\n`,{flag:'wx'});}
109
+ else await writeFile(entry,`<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body><div id="root"></div><script type="module">import React from 'react';import{createRoot}from'react-dom/client';import Harness from './src/scaffold-design-check-${nonce}.tsx';createRoot(document.getElementById('root')).render(React.createElement(Harness));</script></body></html>`,{flag:'wx'});
110
+ createdEntry=true;
111
+ const port=await new Promise((yes,no)=>{const socket=createServer();socket.on('error',no);socket.listen(0,'127.0.0.1',()=>{const port=socket.address().port;socket.close(()=>yes(port));});});
112
+ const web=join(root,'web'),webRequire=createRequire(join(web,'package.json'));
113
+ const executable=next?webRequire.resolve('next/dist/bin/next'):resolve(dirname(webRequire.resolve('vite/package.json')),'bin/vite.js');
114
+ server=spawn(process.execPath,[executable,...(next?['dev','--webpack','--hostname','127.0.0.1','--port',String(port)]:['--host','127.0.0.1','--port',String(port),'--strictPort'])],{cwd:web,env:{...process.env,NEXT_TELEMETRY_DISABLED:'1',...(next?{SCAFFOLD_DESIGN_DIST_DIR:nextDist}:{})},stdio:['ignore','pipe','pipe']});
115
+ for(const stream of [server.stdout,server.stderr])stream.on('data',data=>{serverLog=(serverLog+data).slice(-50000);});
116
+ server.on('error',error=>{serverLog+=error.message;});
117
+ const base=`http://127.0.0.1:${port}/scaffold-design-check-${nonce}${next?'':'.html'}`;
118
+ let ready=false;for(let attempt=0;attempt<120;attempt++){if(interrupted)throw new Error('Interrupted');if(server.exitCode!==null)throw new Error('Fixture server exited');try{const response=await fetch(base);if(response.ok){ready=true;break;}}catch{}await new Promise(resolve=>setTimeout(resolve,500));}
119
+ if(!ready)throw new Error('Fixture server did not become ready');
120
+ browser=await chromium.launch({headless:true});report.browser=browser.version();
121
+ const cases=Object.entries(design.widgets.widgets).flatMap(([name,spec])=>spec.variants.flatMap(variant=>spec.states.map(state=>`${name}.${variant}.${state}`))).concat(design.structure.scenarios.map(name=>'page.'+name));
122
+ report.expectedCases=cases.length*design.widgets.acceptance.viewports.length;
123
+ for(const viewport of design.widgets.acceptance.viewports){
124
+ const context=await browser.newContext({viewport,deviceScaleFactor:1,reducedMotion:'reduce',locale:'zh-CN',timezoneId:'UTC'});
125
+ const page=await context.newPage();
126
+ for(const id of cases){
127
+ if(interrupted)throw new Error('Interrupted');
128
+ const measurements={};let failure;
129
+ try{
130
+ for(const mode of ['reference','actual']){
131
+ await page.mouse.move(viewport.width-1,viewport.height-1);await page.goto(base+`?case=${encodeURIComponent(id)}&mode=${mode}`,{waitUntil:'networkidle'});
132
+ await page.locator(`[data-design-ready="${id}"]`).waitFor();await page.evaluate(()=>document.fonts.ready);
133
+ await interact(page,id);await page.evaluate(()=>new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(resolve))));await checkSpecification(page);measurements[mode]=await collect(page);await page.mouse.up();await checkKeyboard(page,id);
134
+ }
135
+ const differences=compare(measurements.reference,measurements.actual);if(differences.length)throw new Error(differences.slice(0,12).join('\n'));
136
+ if(!measurements.actual.length)throw new Error('No measurable contract elements');
137
+ }catch(error){failure=error.message;report.failures.push({id,viewport,failure});await page.screenshot({path:join(output,`${viewport.width}-${id}.png`),fullPage:true}).catch(()=>{});}
138
+ report.cases.push({id,viewport,status:failure?'FAIL':'PASS',...(failure?{failure}:{}),measurements});
139
+ }
140
+ await context.close();
141
+ }
142
+ if(report.cases.length!==report.expectedCases)throw new Error('Incomplete declared coverage');
143
+ report.status=report.failures.length?'FAIL':'PASS';
144
+ }catch(error){report.failures.push({failure:error.message});}
145
+ finally{
146
+ await browser?.close();server?.kill('SIGTERM');
147
+ if(server&&server.exitCode===null)await Promise.race([new Promise(resolve=>server.once('exit',resolve)),new Promise(resolve=>setTimeout(()=>{server.kill('SIGKILL');resolve();},3000))]);
148
+ if(createdHarness)await rm(harness,{force:true});if(createdDirectory)await rm(dirname(entry),{recursive:true,force:true});else if(createdEntry)await rm(entry,{force:true});
149
+ if(next){await rm(join(root,'web',nextDist),{recursive:true,force:true});for(const [file,content] of nextFiles){const path=join(root,'web',file),current=await readFile(path,'utf8');const cleaned=cleanNextFixtureConfig(file,current,content.toString(),nextDist);if(cleaned!==current)await writeFile(path,cleaned);}}
150
+ report.finishedAt=new Date().toISOString();await writeFile(join(output,'result.json'),JSON.stringify(report,null,2)+'\n');await writeFile(join(output,'server.log'),serverLog);
151
+ process.removeListener('SIGINT',signals);process.removeListener('SIGTERM',signals);
152
+ }
153
+ console.log(JSON.stringify({status:report.status,template:project.template,ui:project.ui,expectedCases:report.expectedCases,completedCases:report.cases.length,failures:report.failures.slice(0,8),report:'reports/design-check/result.json'},null,2));
154
+ if(report.status!=='PASS')process.exitCode=1;
@@ -0,0 +1,108 @@
1
+ import {readFile, writeFile, mkdir, lstat, realpath} from 'node:fs/promises';
2
+ import {realpathSync} from 'node:fs';
3
+ import {resolve, dirname, join, relative, sep} from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+
6
+ const sections=['Overview','Colors','Typography','Layout','Elevation & Depth','Shapes','Components',"Do's and Don'ts"];
7
+ const generated=['DESIGN.md','web/src/design-tokens.css','web/src/design-layout.css','web/src/design-widgets.css','web/src/design-runtime.json'];
8
+ const variable=path=>'--design-'+path.split('.').map(part=>part.replace(/[A-Z]/g,c=>'-'+c.toLowerCase())).join('-');
9
+ function leaves(value,path='') {
10
+ return Object.entries(value).flatMap(([key,child])=>{
11
+ if(!/^[a-zA-Z][a-zA-Z0-9-]*$/.test(key))throw new Error('Invalid design token key: '+key);
12
+ const at=path?path+'.'+key:key;
13
+ if(child && typeof child==='object' && !Array.isArray(child))return leaves(child,at);
14
+ if(typeof child!=='string' && (typeof child!=='number'||!Number.isFinite(child)))throw new Error('Invalid token value: '+at);
15
+ return [[at,child]];
16
+ });
17
+ }
18
+ function resolver(tokens) {
19
+ const flat=new Map(leaves(tokens));
20
+ function get(path,seen=[]) {
21
+ if(!flat.has(path))throw new Error('Unknown design token reference: '+path);
22
+ if(seen.includes(path))throw new Error('Cyclic design token reference: '+path);
23
+ const value=flat.get(path);
24
+ const match=typeof value==='string' && /^\{([\w.-]+)\}$/.exec(value);
25
+ if(match)return get(match[1],[...seen,path]);
26
+ if(typeof value==='string' && /[{};\n\r<>]/.test(value))throw new Error('Unsafe or partial design token reference: '+path);
27
+ return value;
28
+ }
29
+ for(const path of flat.keys())get(path);
30
+ return {flat,get};
31
+ }
32
+ function yaml(value,indent=0) {
33
+ return Object.entries(value).map(([key,child])=>' '.repeat(indent)+key+':'+(child && typeof child==='object'
34
+ ? '\n'+yaml(child,indent+2) : ' '+JSON.stringify(child))).join('\n');
35
+ }
36
+ function resolveTree(value,get) {
37
+ if(Array.isArray(value))return value.map(child=>resolveTree(child,get));
38
+ if(value && typeof value==='object')return Object.fromEntries(Object.entries(value).map(([key,child])=>[key,resolveTree(child,get)]));
39
+ const match=typeof value==='string' && /^\{([\w.-]+)\}$/.exec(value);
40
+ return match?get(match[1]):value;
41
+ }
42
+ export async function designFiles(root) {
43
+ const source=JSON.parse(await readFile(join(root,'design/tokens.json'),'utf8'));
44
+ const widgets=JSON.parse(await readFile(join(root,'design/widgets.json'),'utf8'));
45
+ const structure=JSON.parse(await readFile(join(root,'design/structure.json'),'utf8'));
46
+ if(widgets.schemaVersion!==1||structure.schemaVersion!==1)throw new Error('Unsupported design specification version');
47
+ for(const [name,spec] of Object.entries(widgets.widgets)) {
48
+ for(const key of ['parts','variants','states'])if(!Array.isArray(spec[key])||!spec[key].length||new Set(spec[key]).size!==spec[key].length)throw new Error('Invalid widget '+name+'.'+key);
49
+ }
50
+ const tokens=source.tokens;
51
+ const {flat,get}=resolver({...tokens,effects:source.effects,widgets:Object.fromEntries(Object.entries(widgets.widgets).map(([name,spec])=>[name,spec.styles]))});
52
+ const substitute=text=>text.replace(/\{\{([\w.-]+)\}\}/g,(_,path)=>String(get(path)));
53
+ const guide=substitute(await readFile(join(root,'design/guide.md'),'utf8'));
54
+ if(JSON.stringify([...guide.matchAll(/^## (.+)$/gm)].map(m=>m[1]))!==JSON.stringify(sections))throw new Error('Design guide must contain the eight ordered sections');
55
+ const cssEntries=[...flat].filter(([path])=>!['version','name','description'].includes(path));
56
+ const variables=new Set(cssEntries.map(([path])=>variable(path)));
57
+ if(variables.size!==cssEntries.length)throw new Error('Design token names produce colliding CSS variables');
58
+ const css='/* Generated by design:sync. Edit design/tokens.json or design/widgets.json. */\n:root {\n'+cssEntries.map(([path])=>` ${variable(path)}: ${get(path)};`).join('\n')+'\n}\n';
59
+ const layout=substitute(await readFile(join(root,'design/layout.css'),'utf8'));
60
+ const widgetCss=substitute(await readFile(join(root,'design/widgets.css'),'utf8'));
61
+ for(const match of (layout+widgetCss).matchAll(/var\(\s*(--design-[\w-]+)\s*(?:,|\))/g))if(!variables.has(match[1]))throw new Error('Unknown CSS design variable: '+match[1]);
62
+ return new Map([
63
+ [generated[0],'---\n'+yaml(tokens)+'\n---\n\n'+guide],
64
+ [generated[1],css],
65
+ [generated[2],'/* Generated by design:sync. Edit design/layout.css. */\n'+layout],
66
+ [generated[3],'/* Generated by design:sync. Edit design/widgets.css. */\n'+widgetCss],
67
+ [generated[4],JSON.stringify({tokens:resolveTree(tokens,get),effects:resolveTree(source.effects,get),widgets:resolveTree(widgets,get),structure},null,2)+'\n'],
68
+ ]);
69
+ }
70
+ async function validateOutputPaths(root,paths) {
71
+ for(const path of paths) {
72
+ const file=join(root,path);
73
+ if(relative(root,file).split(sep).includes('..'))throw new Error('Design output escapes project');
74
+ let current=root;
75
+ for(const segment of path.split('/')) {
76
+ current=join(current,segment);
77
+ try {
78
+ const info=await lstat(current);
79
+ if(info.isSymbolicLink())throw new Error('Refusing design symlink: '+relative(root,current));
80
+ if(current!==file&&!info.isDirectory())throw new Error('Design output parent is not a directory');
81
+ }catch(error){if(error.code!=='ENOENT')throw error;}
82
+ }
83
+ }
84
+ }
85
+ export async function syncDesign(root,check=false) {
86
+ root=await realpath(root);
87
+ const files=await designFiles(root);
88
+ // Check every ancestor before the first write so failure cannot partly overwrite outputs.
89
+ await validateOutputPaths(root,files.keys());
90
+ for(const [path,content] of files) {
91
+ const file=join(root,path);
92
+ if(check) {
93
+ let actual;try{actual=await readFile(file,'utf8');}catch{throw new Error('Missing generated design file: '+path);}
94
+ if(actual!==content)throw new Error('Design drift: '+path+'; run npm run design:sync');
95
+ }else{
96
+ await mkdir(dirname(file),{recursive:true});
97
+ await writeFile(file,content);
98
+ }
99
+ }
100
+ }
101
+ if(process.argv[1] && realpathSync(process.argv[1])===fileURLToPath(import.meta.url)) {
102
+ try {
103
+ const command=process.argv[2];
104
+ if(!['sync','check'].includes(command))throw new Error('Use: node scripts/design.mjs sync|check');
105
+ await syncDesign(resolve(dirname(fileURLToPath(import.meta.url)),'..'),command==='check');
106
+ console.log(command==='check'?'Design documentation and UI outputs match their source.':'Generated DESIGN.md and UI design outputs.');
107
+ }catch(error){console.error(error.message);process.exitCode=1;}
108
+ }
@@ -0,0 +1,118 @@
1
+ import { spawn, execFileSync } from 'node:child_process';
2
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'node:fs';
3
+ import { resolve, dirname } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { randomBytes } from 'node:crypto';
6
+ const root=resolve(dirname(fileURLToPath(import.meta.url)),'..');
7
+ process.chdir(root);
8
+ const project=JSON.parse(readFileSync('project.json','utf8'));
9
+ const stack=project.template, next=stack==='next-fullstack', fast=stack==='react-fastapi';
10
+ if(process.env.SCAFFOLD_TEST!=='1' && existsSync('.env')) process.loadEnvFile('.env');
11
+ const children=new Set();
12
+ let stopping=false;
13
+ function stop(signal='SIGTERM') { stopping=true;for(const c of children)c.kill(signal); }
14
+ for(const sig of ['SIGTERM','SIGINT'])process.on(sig,()=>{stop(sig);process.exitCode=sig==='SIGINT'?130:143;});
15
+ async function run(program,args,cwd='.',extra={}) {
16
+ if(stopping)throw new Error('Interrupted');
17
+ return new Promise((yes,no)=>{
18
+ const c=spawn(program,args,{cwd:resolve(root,cwd),stdio:'inherit',env:{...process.env,...extra}});children.add(c);
19
+ c.on('error',e=>{children.delete(c);no(e);});
20
+ c.on('exit',(code,signal)=>{children.delete(c);code===0?yes():no(new Error(`${program} failed (${code??signal})`));});
21
+ });
22
+ }
23
+ function env() {
24
+ for(const key of ['DATABASE_URL','AUTH_SECRET','APP_ORIGIN']) if(!process.env[key])throw new Error(`Missing ${key}; run dev:setup or provide an isolated test environment.`);
25
+ if(process.env.AUTH_SECRET.length<32)throw new Error('AUTH_SECRET must contain at least 32 characters.');
26
+ }
27
+ async function migrationGuard() {
28
+ env();
29
+ if(process.env.SCAFFOLD_TEST==='1') {
30
+ const context=JSON.parse(readFileSync(process.env.SCAFFOLD_TEST_CONTEXT,'utf8'));
31
+ const u=new URL(process.env.DATABASE_URL);
32
+ if(context.databaseUrl!==process.env.DATABASE_URL || u.hostname!=='127.0.0.1' || !/^[a-f0-9]{64}$/.test(context.containerId||'') || !/^[a-f0-9-]{36}$/.test(context.runId||''))throw new Error('Test database identity mismatch.');
33
+ // Inspect privately: Docker configuration contains the ephemeral database password.
34
+ const actual=JSON.parse(execFileSync('docker',['inspect','--format','{{json .}}',context.containerId],{encoding:'utf8',stdio:['ignore','pipe','pipe']}));
35
+ const ports=actual.NetworkSettings?.Ports?.['5432/tcp']||[];
36
+ const variables=actual.Config?.Env||[];
37
+ if(actual.Id!==context.containerId || actual.Config?.Labels?.['agent-scaffold.run']!==context.runId || actual.State?.Running!==true || ports.length!==1 || ports[0].HostIp!=='127.0.0.1' || ports[0].HostPort!==u.port || u.pathname!=='/app' || decodeURIComponent(u.username)!=='app' || !variables.includes('POSTGRES_USER=app') || !variables.includes('POSTGRES_DB=app') || !variables.includes('POSTGRES_PASSWORD='+decodeURIComponent(u.password)))throw new Error('Test database identity mismatch.');
38
+ }
39
+ }
40
+ async function migrate() {
41
+ await migrationGuard();
42
+ if(fast)await run('uv',['run','--frozen','alembic','upgrade','head'],'backend');
43
+ else if(stack==='react-spring')await run('./mvnw',['-q','-DskipTests','spring-boot:run','-Dspring-boot.run.arguments=--scaffold.migrate-only=true'],'backend',jdbc());
44
+ else await run('pnpm',['exec','prisma','migrate','deploy'],next?'web':'backend');
45
+ }
46
+ function jdbc() {
47
+ const u=new URL(process.env.DATABASE_URL);
48
+ return {SESSION_COOKIE_SECURE:String(process.env.APP_ORIGIN.startsWith('https://')),SPRING_DATASOURCE_URL:`jdbc:postgresql://${u.hostname}:${u.port||5432}${u.pathname}`,SPRING_DATASOURCE_USERNAME:decodeURIComponent(u.username),SPRING_DATASOURCE_PASSWORD:decodeURIComponent(u.password)};
49
+ }
50
+ async function seed() {
51
+ await migrationGuard();
52
+ for(const k of ['SEED_A_EMAIL','SEED_B_EMAIL','SEED_PASSWORD']) if(!process.env[k])throw new Error(`Missing explicit ${k}`);
53
+ if(fast)await run('uv',['run','--frozen','python','-m','app.seed'],'backend');
54
+ else if(stack==='react-spring')await run('./mvnw',['-q','-DskipTests','spring-boot:run','-Dspring-boot.run.arguments=--scaffold.seed-only=true'],'backend',jdbc());
55
+ else await run('pnpm',['exec','tsx','scripts/seed.ts'],next?'web':'backend',{SCAFFOLD_SEED:'1'});
56
+ }
57
+ async function serve() {
58
+ env();
59
+ const tasks=[];
60
+ if(fast)tasks.push(run('uv',['run','--frozen','uvicorn','app.main:app','--host','127.0.0.1','--port',process.env.PORT||'8000'],'backend'));
61
+ if(stack==='react-spring')tasks.push(run('java',['-jar','target/backend.jar'],'backend',jdbc()));
62
+ if(stack==='react-nest')tasks.push(run('node',['dist/main.js'],'backend'));
63
+ if(next)tasks.push(run('pnpm',['exec','next','start','--hostname','127.0.0.1','--port',process.env.WEB_PORT||'3000'],'web'));
64
+ else tasks.push(run(fast?'bun':'pnpm',['run','preview','--host','127.0.0.1','--port',process.env.WEB_PORT||'5173','--strictPort'],'web'));
65
+ try {await Promise.all(tasks);} finally {stop();}
66
+ }
67
+ async function install() {
68
+ await run('npm',['ci']);
69
+ await run(fast?'bun':'pnpm',fast?['install','--frozen-lockfile']:['install','--frozen-lockfile'],'web');
70
+ if(fast)await run('uv',['sync','--frozen'],'backend');
71
+ else if(stack==='react-nest')await run('pnpm',['install','--frozen-lockfile'],'backend');
72
+ else if(stack==='react-spring')await run('./mvnw',['-q','-DskipTests','compile'],'backend');
73
+ if(next||stack==='react-nest')await run('pnpm',['exec','prisma','generate'],next?'web':'backend');
74
+ await run('npx',['--no-install','playwright','install','chromium']);
75
+ }
76
+ async function setup() {
77
+ if(process.env.SCAFFOLD_TEST==='1')throw new Error('dev:setup cannot run in test mode.');
78
+ if(!existsSync('.env')) {
79
+ const db=randomBytes(24).toString('hex'), secret=randomBytes(32).toString('hex'), pass=randomBytes(16).toString('hex');
80
+ const web=process.env.WEB_PORT||String(next?3000:5173), dbPort=process.env.POSTGRES_PORT||'54329';
81
+ const vars={POSTGRES_USER:'app',POSTGRES_DB:'app',POSTGRES_PASSWORD:db,POSTGRES_PORT:dbPort,DATABASE_URL:`postgresql://app:${db}@127.0.0.1:${dbPort}/app`,AUTH_SECRET:secret,APP_ORIGIN:`http://localhost:${web}`,PORT:process.env.PORT||'8000',WEB_PORT:String(web),SEED_A_EMAIL:'alice@example.test',SEED_B_EMAIL:'bob@example.test',SEED_PASSWORD:pass};
82
+ writeFileSync('.env',Object.entries(vars).map(([k,v])=>`${k}=${v}`).join('\n')+'\n',{flag:'wx',mode:0o600});
83
+ writeFileSync('.scaffold-local-credentials',`Local development only\nEmail: ${vars.SEED_A_EMAIL}\nPassword: ${pass}\n`,{flag:'wx',mode:0o600});
84
+ process.loadEnvFile('.env');
85
+ }
86
+ await run('docker',['compose','up','-d','--wait','db']);await migrate();await seed();
87
+ console.log('Development database ready. Credentials are in .scaffold-local-credentials (ignored by Git).');
88
+ }
89
+ const command=process.argv[2];
90
+ try {
91
+ mkdirSync('reports',{recursive:true});
92
+ if(command==='install')await install();
93
+ else if(command==='setup'){await install();await setup();}
94
+ else if(command==='dev:setup')await setup();
95
+ else if(command==='migrate')await migrate();
96
+ else if(command==='seed')await seed();
97
+ else if(command==='serve')await serve();
98
+ else if(command==='dev') {await run('node',['scripts/project.mjs','build']);await serve();}
99
+ else if(command==='build'||command==='typecheck'||command==='lint') {
100
+ await run('node',['scripts/design.mjs','check']);
101
+ if(next||stack==='react-nest')await run('pnpm',['exec','prisma','generate'],next?'web':'backend');
102
+ await run(fast?'bun':'pnpm',['run',command],'web');
103
+ if(fast) {
104
+ if(command==='build')await run('uv',['run','--frozen','python','-m','compileall','-q','app'],'backend');
105
+ if(command==='typecheck')await run('uv',['run','--frozen','mypy','app'],'backend');
106
+ if(command==='lint')await run('uv',['run','--frozen','ruff','check','app','tests'],'backend');
107
+ } else if(stack==='react-nest')await run('pnpm',['run',command],'backend');
108
+ else if(stack==='react-spring')await run('./mvnw',['-q','-DskipTests',command==='build'?'package':'compile'],'backend');
109
+ } else if(command==='test:native') {
110
+ await migrationGuard();
111
+ if(fast)await run('uv',['run','--frozen','python','-m','pytest','--junitxml='+resolve(process.env.SCAFFOLD_REPORT_DIR||'reports','native.xml'),'-q'],'backend');
112
+ else if(stack==='react-spring')await run('./mvnw',['-q','test','-Dscaffold.reports='+resolve(process.env.SCAFFOLD_REPORT_DIR||'reports','spring')],'backend',jdbc());
113
+ else if(stack==='react-nest')await run('pnpm',['run','test'],'backend');
114
+ else await run('pnpm',['run','test'],'web');
115
+ } else if(command==='test:api'||command==='test:e2e')await run('npx',['--no-install','playwright','test',command==='test:api'?'tests/contract.spec.ts':'tests/ui.spec.ts']);
116
+ else if(command==='api:generate')await run('npx',['--no-install','openapi-typescript','docs/openapi.json','-o','docs/api.d.ts']);
117
+ else throw new Error('Use setup, install, dev:setup, dev, build, typecheck, lint, migrate, seed, test:native, test:api, test:e2e, api:generate.');
118
+ } catch(e) {stop();console.error(e instanceof Error?e.message:'Project command failed');process.exitCode=process.exitCode||1;}
@@ -0,0 +1,47 @@
1
+ import {test,expect,request as requests,type APIRequestContext} from '@playwright/test';
2
+ import project from '../project.json' with {type:'json'};
3
+ const baseURL=`http://127.0.0.1:${process.env.WEB_PORT||5173}`;
4
+ const origin=process.env.APP_ORIGIN||baseURL;
5
+ async function login(email:string){const context=await requests.newContext({baseURL,extraHTTPHeaders:{Origin:origin}});const res=await context.post('/api/auth/login',{data:{email,password:process.env.SEED_PASSWORD}});expect(res.status()).toBe(200);const auth=await res.json();if(auth.token)await context.dispose();const client:APIRequestContext=auth.token?await requests.newContext({baseURL,extraHTTPHeaders:{Origin:origin,Authorization:`Bearer ${auth.token}`}}):context;return {client,auth};}
6
+ test('HTTP identity, persistent CRUD, pagination and owner isolation',async()=>{
7
+ const anonymous=await requests.newContext({baseURL,extraHTTPHeaders:{Origin:origin}});
8
+ expect((await anonymous.get('/api/records')).status()).toBe(401);
9
+ expect((await anonymous.post('/api/records',{data:{title:'x',content:''}})).status()).toBe(401);
10
+ expect((await anonymous.post('/api/auth/login',{data:{email:process.env.SEED_A_EMAIL,password:'invalid-password'}})).status()).toBe(401);
11
+ const {client:a}=await login(process.env.SEED_A_EMAIL!);const {client:b}=await login(process.env.SEED_B_EMAIL!);
12
+ const body=process.env.TEST_BODY_MARKER||'synthetic-content';
13
+ const res=await a.post('/api/records',{data:{title:' Contract record ',content:body}});expect(res.status()).toBe(201);const row=await res.json();const path=`/api/records/${row.id}`;
14
+ expect(row.title).toBe('Contract record');expect((await a.get(path)).status()).toBe(200);
15
+ expect((await (await a.get(path)).json()).content).toBe(body);
16
+ expect((await b.get(path)).status()).toBe(404);expect((await b.put(path,{data:{title:'hijacked',content:''}})).status()).toBe(404);expect((await b.delete(path)).status()).toBe(404);
17
+ const other=await (await b.get('/api/records?page=1&pageSize=100')).json();expect(other.items.some((item:{id:string})=>item.id===row.id)).toBe(false);
18
+ const page=await (await a.get('/api/records?page=1&pageSize=1')).json();expect(page.items.length).toBeLessThanOrEqual(1);expect(page.total).toBeGreaterThan(0);expect(page.pageSize).toBe(1);
19
+ const changed=await a.put(path,{data:{title:'Updated title',content:body}});expect(changed.status()).toBe(200);expect(new Date((await changed.json()).updated_at).getTime()).toBeGreaterThanOrEqual(new Date(row.created_at).getTime());
20
+ const relogin=await login(process.env.SEED_A_EMAIL!);expect((await (await relogin.client.get(path)).json()).title).toBe('Updated title');await relogin.client.dispose();
21
+ expect((await a.delete(path)).status()).toBe(204);expect((await a.get(path)).status()).toBe(404);
22
+ const after=await (await a.get('/api/records?page=1&pageSize=100')).json();expect(after.items.some((item:{id:string})=>item.id===row.id)).toBe(false);
23
+ await Promise.all([a.dispose(),b.dispose(),anonymous.dispose()]);
24
+ });
25
+ test('rejects invalid and protected fields without writing data',async()=>{
26
+ const {client:a,auth}=await login(process.env.SEED_A_EMAIL!);
27
+ const count=(await (await a.get('/api/records')).json()).total;
28
+ for(const data of [{title:'',content:''},{title:' ',content:''},{title:'x'.repeat(121),content:''},{title:'x',content:'x'.repeat(10001)},{title:'x',content:'',owner_id:auth.user.id},{title:'x',content:'',created_at:new Date().toISOString()},{title:42,content:''}])expect((await a.post('/api/records',{data})).status()).toBe(422);
29
+ expect((await (await a.get('/api/records')).json()).total).toBe(count);
30
+ for(const query of ['page=0','pageSize=101','page=bad'])expect((await a.get('/api/records?'+query)).status()).toBe(422);
31
+ expect((await a.get('/api/records/not-a-uuid')).status()).toBe(422);
32
+ const unicode=await a.post('/api/records',{data:{title:'😀'.repeat(120),content:''}});expect(unicode.status()).toBe(201);const row=await unicode.json();
33
+ expect((await a.put(`/api/records/${row.id}`,{data:{title:'bad',content:'',owner_id:auth.user.id}})).status()).toBe(422);
34
+ expect((await (await a.get(`/api/records/${row.id}`)).json()).title).toBe('😀'.repeat(120));
35
+ await a.delete(`/api/records/${row.id}`);await a.dispose();
36
+ });
37
+ test('logout and same-origin session protection',async()=>{
38
+ const {client,auth}=await login(process.env.SEED_A_EMAIL!);
39
+ if(auth.mode==='session'){
40
+ const state=await client.storageState();const replay=await requests.newContext({baseURL,storageState:state,extraHTTPHeaders:{Origin:origin}});
41
+ const cross=await requests.newContext({baseURL,storageState:state,extraHTTPHeaders:{Origin:'https://untrusted.example'}});
42
+ expect((await cross.post('/api/records',{data:{title:'csrf',content:''}})).status()).toBe(403);await cross.dispose();
43
+ expect((await client.post('/api/auth/logout',{data:{}})).status()).toBe(204);
44
+ expect((await replay.get('/api/auth/me')).status()).toBe(401);await replay.dispose();
45
+ }else{expect(['react-fastapi','react-nest']).toContain(project.template);expect((await client.post('/api/auth/logout',{data:{}})).status()).toBe(204);}
46
+ await client.dispose();
47
+ });
@@ -0,0 +1,63 @@
1
+ "use client";
2
+ import {useEffect,useState} from 'react';
3
+ import type * as React from 'react';
4
+ import {WorkspaceView} from './App';
5
+ import * as actualWidgets from './ui';
6
+ import {WidgetIcon} from './widget-icon';
7
+ import type {WidgetSet} from './widget-types';
8
+ import {createWorkspaceModel,initialWorkspaceState,type WorkspaceState} from './workspace-model';
9
+ import design from './design-runtime.json';
10
+ // Independent semantic HTML oracle; it imports no component library.
11
+ const reference:WidgetSet={
12
+ Button:({children,variant='primary',loading=false,disabled,type='button',...props})=><button {...props} type={type} data-widget="button" data-variant={variant} aria-busy={loading||undefined} disabled={disabled||loading}><span data-part="label">{children}</span></button>,
13
+ Input:({invalid,...props})=><input {...props} data-widget="input" aria-invalid={invalid||undefined}/>,
14
+ TextArea:({invalid,...props})=><textarea {...props} data-widget="textarea" aria-invalid={invalid||undefined}/>,
15
+ Alert:({children,variant='error'})=><div data-widget="alert" data-variant={variant} role={variant==='error'?'alert':'status'}><WidgetIcon kind={variant}/><div data-part="message">{children}</div></div>,
16
+ Badge:({children})=><span data-widget="badge" className="badge"><span data-part="label">{children}</span></span>,
17
+ Panel:({children,className='',...props})=><section {...props} data-widget="panel" className={`panel ${className}`}>{children}</section>,
18
+ Field:({children,label,htmlFor})=><div data-widget="field" className="field"><label data-part="label" htmlFor={htmlFor}>{label}</label><div data-part="control">{children}</div></div>,
19
+ Theme:({children})=><>{children}</>
20
+ };
21
+ const record={id:'record-1',title:'一条用于对齐的记录',content:'相同内容,相同交互。\nSecond line.',created_at:'2026-01-01T00:00:00Z',updated_at:'2026-01-01T00:00:00Z'};
22
+ function scenario(name:string):WorkspaceState {
23
+ const state={...initialWorkspaceState(),checking:false};
24
+ if(name==='checking')return {...state,checking:true};
25
+ if(name==='login')return state;
26
+ if(name==='login-error')return {...state,error:'账号或密码不正确,请重试。'};
27
+ if(name==='login-busy')return {...state,busy:true,email:'user@example.test',password:'sample-password'};
28
+ state.user={id:'user-1',email:'user@example.test'};
29
+ if(name==='workspace-empty')return state;
30
+ if(name==='workspace-loading')return {...state,loading:true};
31
+ state.list={items:[record],total:11,page:1,pageSize:10};
32
+ if(name==='workspace-records')return state;
33
+ if(name==='editor-new')return {...state,editing:true};
34
+ if(name==='editor-existing')return {...state,editing:true,selected:record.id,title:record.title,content:record.content,notice:'已保存'};
35
+ if(name==='editor-error')return {...state,editing:true,selected:record.id,title:record.title,content:record.content,error:'请求失败或超时,输入已保留。'};
36
+ throw new Error('Unimplemented declared page scenario: '+name);
37
+ }
38
+ export default function Harness(){
39
+ const [query,setQuery]=useState<URLSearchParams|null>(null),[events,setEvents]=useState(0);
40
+ useEffect(()=>{setQuery(new URLSearchParams(window.location.search));},[]);
41
+ if(!query)return null;
42
+ const mode=query.get('mode'),id=query.get('case')||'';
43
+ const widgets=mode==='reference'?reference:actualWidgets;
44
+ const {Button,Input,TextArea,Alert,Badge,Panel,Field,Theme}=widgets;
45
+ let content:React.ReactNode;
46
+ if(id.startsWith('page.'))content=<WorkspaceView state={scenario(id.slice(5))} model={createWorkspaceModel(async()=>{throw new Error('Unexpected network call in deterministic fixture');})} widgets={widgets}/>;
47
+ else {
48
+ const [name,variant,state]=id.split('.');
49
+ const spec=design.widgets.widgets[name as keyof typeof design.widgets.widgets];
50
+ if(!spec||!(spec.variants as readonly string[]).includes(variant)||!(spec.states as readonly string[]).includes(state))throw new Error('Undeclared widget fixture: '+id);
51
+ const controlProps={disabled:state==='disabled',readOnly:state==='readonly',invalid:state==='invalid',onChange:()=>setEvents(value=>value+1)};
52
+ if(name==='button')content=<Button variant={variant as 'primary'|'secondary'|'danger'} disabled={state==='disabled'} loading={state==='loading'} onClick={()=>setEvents(value=>value+1)}>{state==='loading'?'正在处理…':'保存记录'}</Button>;
53
+ else if(name==='input')content=<Input {...controlProps} id="control" aria-label="测试输入" type={variant} defaultValue={variant==='email'?'user@example.test':'示例内容 Sample'} autoComplete="off"/>;
54
+ else if(name==='textarea')content=<TextArea {...controlProps} aria-label="测试输入" rows={3} defaultValue={'第一行内容\nSecond line'}/>;
55
+ else if(name==='alert')content=<Alert variant={variant as 'error'|'success'}>操作结果说明,请检查后继续。</Alert>;
56
+ else if(name==='badge')content=<Badge>个人工作空间</Badge>;
57
+ else if(name==='panel')content=<Panel><h2>记录面板</h2><p>内容可读,层级明确。</p></Panel>;
58
+ else if(name==='field')content=<Field label="邮箱" htmlFor="field-control"><Input id="field-control" type="email" defaultValue="user@example.test"/></Field>;
59
+ else throw new Error('Unimplemented declared widget: '+name);
60
+ content=<Theme><div style={{width:320,margin:24}}>{content}</div></Theme>;
61
+ }
62
+ return <div data-design-ready={id} data-design-mode={mode} data-events={events}>{content}</div>;
63
+ }