@polymorphism-tech/morph-spec 4.5.0 → 4.7.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 (292) hide show
  1. package/CLAUDE.md +77 -56
  2. package/README.md +394 -700
  3. package/docs/ARCHITECTURE.md +331 -0
  4. package/docs/CHEATSHEET.md +221 -0
  5. package/docs/COMMAND-FLOWS.md +368 -0
  6. package/docs/QUICKSTART.md +212 -0
  7. package/docs/examples/order-management/contracts.cs +84 -0
  8. package/docs/examples/order-management/proposal.md +24 -0
  9. package/docs/examples/order-management/spec.md +162 -0
  10. package/docs/plans/2026-02-23-ddd-architecture-refactor.md +1153 -0
  11. package/docs/plans/2026-02-23-ddd-nextsteps.md +682 -0
  12. package/docs/plans/2026-02-23-infra-architect-refactor.md +437 -0
  13. package/docs/plans/2026-02-23-nextjs-code-review-design.md +156 -0
  14. package/docs/plans/2026-02-23-nextjs-code-review-impl.md +1254 -0
  15. package/docs/plans/2026-02-23-nextjs-standards-design.md +149 -0
  16. package/docs/plans/2026-02-23-nextjs-standards-impl.md +1846 -0
  17. package/framework/{skills/level-2-domains → agents}/README.md +14 -14
  18. package/framework/{skills/level-2-domains → agents}/ai-agents/ai-system-architect.md +1 -4
  19. package/framework/{skills/level-2-domains → agents}/architecture/po-pm-advisor.md +1 -2
  20. package/framework/{skills/level-2-domains → agents}/architecture/prompt-engineer.md +1 -2
  21. package/framework/{skills/level-2-domains → agents}/architecture/seo-growth-hacker.md +1 -2
  22. package/framework/{skills/level-2-domains → agents}/architecture/standards-architect.md +159 -162
  23. package/framework/agents/backend/api-designer.md +103 -0
  24. package/framework/{skills/level-2-domains → agents}/backend/dotnet-senior.md +1 -2
  25. package/framework/agents/backend/ef-modeler.md +119 -0
  26. package/framework/{skills/level-2-domains → agents}/backend/hangfire-orchestrator.md +1 -4
  27. package/framework/{skills/level-2-domains → agents}/backend/ms-agent-expert.md +1 -4
  28. package/framework/{skills/level-2-domains → agents}/frontend/blazor-builder.md +1 -4
  29. package/framework/agents/frontend/nextjs-expert.md +118 -0
  30. package/framework/{skills/level-2-domains → agents}/frontend/ui-ux-designer.md +1 -2
  31. package/framework/{skills/level-2-domains → agents}/infrastructure/azure-architect.md +147 -148
  32. package/framework/{skills/level-2-domains → agents}/infrastructure/azure-deploy-specialist.md +1 -2
  33. package/framework/{skills/level-2-domains → agents}/infrastructure/bicep-architect.md +1 -4
  34. package/framework/{skills/level-2-domains → agents}/infrastructure/container-specialist.md +1 -4
  35. package/framework/{skills/level-2-domains → agents}/infrastructure/devops-engineer.md +1 -4
  36. package/framework/agents/infrastructure/infra-architect.md +45 -0
  37. package/framework/{skills/level-2-domains → agents}/integrations/asaas-financial.md +1 -4
  38. package/framework/{skills/level-2-domains → agents}/integrations/azure-identity.md +1 -4
  39. package/framework/{skills/level-2-domains → agents}/integrations/clerk-auth.md +1 -4
  40. package/framework/{skills/level-2-domains → agents}/integrations/hangfire-integration.md +1 -2
  41. package/framework/{skills/level-2-domains → agents}/integrations/resend-email.md +1 -4
  42. package/framework/{skills/level-2-domains → agents}/quality/code-analyzer.md +1 -4
  43. package/framework/{skills/level-2-domains → agents}/quality/testing-specialist.md +1 -4
  44. package/framework/agents.json +1145 -278
  45. package/framework/hooks/claude-code/statusline.py +384 -85
  46. package/framework/hooks/shared/phase-utils.js +129 -129
  47. package/framework/rules/frontend-standards.md +0 -3
  48. package/framework/rules/nextjs-standards.md +17 -0
  49. package/framework/skills/README.md +66 -0
  50. package/framework/skills/level-0-meta/{brainstorming.md → brainstorming/SKILL.md} +3 -1
  51. package/framework/skills/level-0-meta/brainstorming/references/proposal-example.md +138 -0
  52. package/framework/skills/level-0-meta/{code-review.md → code-review/SKILL.md} +3 -2
  53. package/framework/skills/level-0-meta/code-review/references/review-example.md +164 -0
  54. package/framework/skills/level-0-meta/code-review/scripts/scan-csharp.mjs +121 -0
  55. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +147 -0
  56. package/framework/skills/level-0-meta/code-review-nextjs/references/review-example-nextjs.md +254 -0
  57. package/framework/skills/level-0-meta/{morph-checklist.md → morph-checklist/SKILL.md} +2 -5
  58. package/framework/skills/{level-1-workflows/morph-replicate.md → level-0-meta/morph-replicate/SKILL.md} +6 -7
  59. package/framework/skills/level-0-meta/{simulation-checklist.md → simulation-checklist/SKILL.md} +3 -6
  60. package/framework/skills/level-0-meta/{tool-usage-guide.md → tool-usage-guide/SKILL.md} +4 -5
  61. package/framework/skills/level-0-meta/{verification-before-completion.md → verification-before-completion/SKILL.md} +3 -1
  62. package/framework/skills/level-0-meta/verification-before-completion/scripts/check-phase-outputs.mjs +110 -0
  63. package/framework/skills/level-1-workflows/{phase-clarify.md → phase-clarify/SKILL.md} +3 -3
  64. package/framework/skills/level-1-workflows/phase-clarify/references/clarifications-example.md +117 -0
  65. package/framework/skills/level-1-workflows/{phase-codebase-analysis.md → phase-codebase-analysis/SKILL.md} +2 -3
  66. package/framework/skills/level-1-workflows/{phase-design.md → phase-design/SKILL.md} +46 -182
  67. package/framework/skills/level-1-workflows/phase-design/references/spec-example.md +253 -0
  68. package/framework/skills/level-1-workflows/{phase-implement.md → phase-implement/SKILL.md} +3 -3
  69. package/framework/skills/level-1-workflows/phase-implement/references/recap-example.md +132 -0
  70. package/framework/skills/level-1-workflows/{phase-setup.md → phase-setup/SKILL.md} +2 -3
  71. package/framework/skills/level-1-workflows/{phase-tasks.md → phase-tasks/SKILL.md} +42 -3
  72. package/framework/skills/level-1-workflows/phase-tasks/references/tasks-example.md +231 -0
  73. package/framework/skills/level-1-workflows/phase-tasks/scripts/validate-tasks.mjs +112 -0
  74. package/framework/skills/level-1-workflows/{phase-uiux.md → phase-uiux/SKILL.md} +2 -3
  75. package/framework/standards/STANDARDS.json +121 -0
  76. package/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
  77. package/framework/standards/architecture/ddd/complexity-levels.md +108 -0
  78. package/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
  79. package/framework/standards/frontend/nextjs/app-router.md +123 -0
  80. package/framework/standards/frontend/nextjs/components.md +132 -0
  81. package/framework/standards/frontend/nextjs/data-fetching.md +126 -0
  82. package/framework/standards/frontend/nextjs/forms.md +128 -0
  83. package/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
  84. package/framework/standards/frontend/nextjs/project-structure.md +102 -0
  85. package/framework/standards/frontend/nextjs/state-management.md +72 -0
  86. package/framework/standards/frontend/nextjs/testing.md +111 -0
  87. package/framework/templates/REGISTRY.json +538 -142
  88. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
  89. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
  90. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
  91. package/framework/templates/docs/spec.md +49 -0
  92. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
  93. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
  94. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
  95. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
  96. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
  97. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
  98. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
  99. package/framework/templates/project-structure/dotnet-ddd.md +70 -0
  100. package/framework/workflows/docs/enforcement-pipeline.md +2 -1
  101. package/package.json +1 -1
  102. package/scripts/scan-nextjs.mjs +169 -0
  103. package/src/commands/project/doctor.js +52 -1
  104. package/src/commands/project/init.js +19 -65
  105. package/src/commands/project/update.js +7 -63
  106. package/src/lib/detectors/claude-config-detector.js +1 -3
  107. package/src/lib/standards/standards-context-injector.js +5 -0
  108. package/src/lib/validators/nextjs/index.js +6 -0
  109. package/src/lib/validators/nextjs/next-component-validator.js +181 -0
  110. package/src/lib/validators/validation-runner.js +5 -0
  111. package/src/utils/agents-installer.js +16 -4
  112. package/src/utils/skills-installer.js +59 -15
  113. package/.morph/.morphversion +0 -5
  114. package/.morph/analytics/threads-log.jsonl +0 -44
  115. package/.morph/config/config.json +0 -8
  116. package/.morph/context/README.md +0 -17
  117. package/.morph/framework/agents.json +0 -948
  118. package/.morph/framework/standards/STANDARDS.json +0 -812
  119. package/.morph/framework/standards/ai-agents/blazor-ui.md +0 -364
  120. package/.morph/framework/standards/ai-agents/production.md +0 -415
  121. package/.morph/framework/standards/ai-agents/setup.md +0 -418
  122. package/.morph/framework/standards/ai-agents/team-orchestration.md +0 -479
  123. package/.morph/framework/standards/ai-agents/workflows.md +0 -354
  124. package/.morph/framework/standards/architecture/ddd/aggregates.md +0 -120
  125. package/.morph/framework/standards/architecture/ddd/entities.md +0 -99
  126. package/.morph/framework/standards/architecture/ddd/value-objects.md +0 -124
  127. package/.morph/framework/standards/backend/api/minimal-api.md +0 -494
  128. package/.morph/framework/standards/backend/api/rest.md +0 -492
  129. package/.morph/framework/standards/backend/api/validation.md +0 -88
  130. package/.morph/framework/standards/backend/authentication/passkeys.md +0 -428
  131. package/.morph/framework/standards/backend/database/ef-core.md +0 -199
  132. package/.morph/framework/standards/backend/database/migrations.md +0 -393
  133. package/.morph/framework/standards/backend/database/postgresql/database.md +0 -352
  134. package/.morph/framework/standards/backend/database/repository-patterns.md +0 -528
  135. package/.morph/framework/standards/backend/database/vector-search-rag.md +0 -541
  136. package/.morph/framework/standards/backend/dotnet/async.md +0 -366
  137. package/.morph/framework/standards/backend/dotnet/core.md +0 -117
  138. package/.morph/framework/standards/backend/dotnet/di.md +0 -439
  139. package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  140. package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +0 -216
  141. package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -290
  142. package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +0 -350
  143. package/.morph/framework/standards/backend/integrations/resend/resend-email.md +0 -385
  144. package/.morph/framework/standards/context/analytics.md +0 -96
  145. package/.morph/framework/standards/context/bundles.md +0 -110
  146. package/.morph/framework/standards/context/priming.md +0 -78
  147. package/.morph/framework/standards/core/architecture.md +0 -185
  148. package/.morph/framework/standards/core/coding.md +0 -214
  149. package/.morph/framework/standards/core/git-branching-strategy.md +0 -403
  150. package/.morph/framework/standards/core/git.md +0 -185
  151. package/.morph/framework/standards/core/testing.md +0 -295
  152. package/.morph/framework/standards/data/nosql/blob-storage.md +0 -102
  153. package/.morph/framework/standards/data/nosql/cache/redis.md +0 -97
  154. package/.morph/framework/standards/data/nosql/cosmos-db.md +0 -118
  155. package/.morph/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  156. package/.morph/framework/standards/data/vector-search/rag-chunking.md +0 -104
  157. package/.morph/framework/standards/frontend/blazor/design-checklist.md +0 -222
  158. package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  159. package/.morph/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  160. package/.morph/framework/standards/frontend/blazor/html-conversion.md +0 -184
  161. package/.morph/framework/standards/frontend/blazor/lifecycle.md +0 -195
  162. package/.morph/framework/standards/frontend/blazor/pitfalls.md +0 -198
  163. package/.morph/framework/standards/frontend/blazor/state.md +0 -191
  164. package/.morph/framework/standards/frontend/design-system/animations.md +0 -151
  165. package/.morph/framework/standards/frontend/design-system/naming.md +0 -64
  166. package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +0 -198
  167. package/.morph/framework/standards/infrastructure/azure/azure.md +0 -624
  168. package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  169. package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  170. package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  171. package/.morph/framework/standards/infrastructure/azure/services/functions.md +0 -486
  172. package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  173. package/.morph/framework/standards/infrastructure/azure/services/storage.md +0 -407
  174. package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +0 -196
  175. package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +0 -252
  176. package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
  177. package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
  178. package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
  179. package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
  180. package/.morph/framework/standards/integration/api/graphql.md +0 -91
  181. package/.morph/framework/standards/integration/api/grpc.md +0 -114
  182. package/.morph/framework/standards/integration/api/rest-design.md +0 -95
  183. package/.morph/framework/standards/integration/event-driven/cqrs.md +0 -101
  184. package/.morph/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  185. package/.morph/framework/standards/integration/event-driven/service-bus.md +0 -95
  186. package/.morph/framework/standards/integration/mcp/mcp-tools.md +0 -384
  187. package/.morph/framework/standards/observability/logging.md +0 -131
  188. package/.morph/framework/standards/observability/metrics.md +0 -121
  189. package/.morph/framework/standards/observability/monitoring.md +0 -114
  190. package/.morph/framework/standards/observability/tracing.md +0 -132
  191. package/.morph/framework/standards/workflows/parallel-execution.md +0 -112
  192. package/.morph/framework/standards/workflows/thread-management.md +0 -113
  193. package/.morph/framework/templates/.idea/morph-templates.xml +0 -92
  194. package/.morph/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  195. package/.morph/framework/templates/IDE-SNIPPETS.md +0 -266
  196. package/.morph/framework/templates/README.md +0 -814
  197. package/.morph/framework/templates/REGISTRY.json +0 -1492
  198. package/.morph/framework/templates/code/dotnet/backend/repository.cs +0 -141
  199. package/.morph/framework/templates/code/dotnet/backend/service.cs +0 -139
  200. package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  201. package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  202. package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  203. package/.morph/framework/templates/code/dotnet/contracts/README.md +0 -74
  204. package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  205. package/.morph/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
  206. package/.morph/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
  207. package/.morph/framework/templates/code/dotnet/database/migration.cs +0 -83
  208. package/.morph/framework/templates/code/dotnet/frontend/component.razor +0 -239
  209. package/.morph/framework/templates/code/dotnet/jobs/agent.cs +0 -163
  210. package/.morph/framework/templates/code/dotnet/jobs/job.cs +0 -171
  211. package/.morph/framework/templates/code/dotnet/test.cs +0 -239
  212. package/.morph/framework/templates/code/sql/rls-policy.sql +0 -57
  213. package/.morph/framework/templates/code/sql/supabase-migration.sql +0 -100
  214. package/.morph/framework/templates/code/sql/supabase-migration.template.sql +0 -113
  215. package/.morph/framework/templates/code/typescript/contracts.ts +0 -168
  216. package/.morph/framework/templates/context/CONTEXT-FEATURE.md +0 -276
  217. package/.morph/framework/templates/context/CONTEXT.md +0 -181
  218. package/.morph/framework/templates/docs/clarifications.md +0 -253
  219. package/.morph/framework/templates/docs/onboarding.md +0 -123
  220. package/.morph/framework/templates/docs/proposal.md +0 -182
  221. package/.morph/framework/templates/docs/schema-analysis.md +0 -119
  222. package/.morph/framework/templates/docs/spec.md +0 -149
  223. package/.morph/framework/templates/docs/ui-components.md +0 -124
  224. package/.morph/framework/templates/docs/ui-design-system.md +0 -76
  225. package/.morph/framework/templates/docs/ui-flows.md +0 -167
  226. package/.morph/framework/templates/docs/ui-mockups.md +0 -98
  227. package/.morph/framework/templates/examples/design-system-examples.md +0 -357
  228. package/.morph/framework/templates/examples/spec-examples.md +0 -90
  229. package/.morph/framework/templates/feature/decisions.md +0 -187
  230. package/.morph/framework/templates/feature/recap.md +0 -146
  231. package/.morph/framework/templates/feature/tasks.md +0 -199
  232. package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  233. package/.morph/framework/templates/infrastructure/azure/README.md +0 -286
  234. package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  235. package/.morph/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  236. package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  237. package/.morph/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  238. package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  239. package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  240. package/.morph/framework/templates/infrastructure/azure/deploy.sh +0 -208
  241. package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  242. package/.morph/framework/templates/infrastructure/azure/main.bicep +0 -189
  243. package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  244. package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  245. package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  246. package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  247. package/.morph/framework/templates/infrastructure/azure/storage.bicep +0 -106
  248. package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +0 -58
  249. package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +0 -67
  250. package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +0 -38
  251. package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +0 -48
  252. package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +0 -54
  253. package/.morph/framework/templates/infrastructure/github/README.md +0 -593
  254. package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  255. package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +0 -45
  256. package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +0 -27
  257. package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  258. package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
  259. package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +0 -59
  260. package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +0 -39
  261. package/.morph/framework/templates/integrations/asaas-client.cs +0 -387
  262. package/.morph/framework/templates/integrations/asaas-webhook.cs +0 -351
  263. package/.morph/framework/templates/integrations/azure-identity-config.cs +0 -288
  264. package/.morph/framework/templates/integrations/clerk-config.cs +0 -258
  265. package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
  266. package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
  267. package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  268. package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  269. package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  270. package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  271. package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  272. package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
  273. package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -126
  274. package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -43
  275. package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
  276. package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +0 -95
  277. package/.morph/framework/templates/saas/subscription.cs +0 -347
  278. package/.morph/framework/templates/saas/tenant.cs +0 -338
  279. package/.morph/framework/templates/state.template.json +0 -17
  280. package/.morph/framework/templates/ui/FluentDesignTheme.cs +0 -149
  281. package/.morph/framework/templates/ui/MudTheme.cs +0 -281
  282. package/.morph/framework/templates/ui/design-system.css +0 -226
  283. package/.morph/logs/tool-failures.log +0 -51
  284. package/.morph/memory/pre-compact-2026-02-22T17-01-01-658Z.json +0 -16
  285. package/.morph/state.json +0 -48
  286. package/framework/skills/level-2-domains/backend/api-designer.md +0 -66
  287. package/framework/skills/level-2-domains/backend/ef-modeler.md +0 -65
  288. package/framework/skills/level-2-domains/frontend/nextjs-expert.md +0 -161
  289. package/framework/skills/level-3-technologies/README.md +0 -7
  290. package/framework/skills/level-4-patterns/README.md +0 -7
  291. package/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
  292. package/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
@@ -0,0 +1,1846 @@
1
+ # Next.js Standards Package Implementation Plan
2
+
3
+ > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
+
5
+ **Goal:** Add a comprehensive Next.js standards package to morph-spec-framework covering standards files, a rule file, a validator, templates, and an enhanced agent for the stack: Next.js App Router + shadcn/ui + TanStack Query + react-hook-form/Zod + EasyPanel/Docker.
6
+
7
+ **Architecture:** 8 standards files in `framework/standards/frontend/nextjs/`, 1 Claude Code rule file in `framework/rules/`, 1 validator module in `src/lib/validators/nextjs/`, 6 Handlebars templates in `framework/templates/frontend/nextjs/`, an updated `nextjs-expert` agent, and a regenerated STANDARDS.json. All content is opinionated for the locked stack — no generic fallbacks.
8
+
9
+ **Tech Stack:** Node.js/ESM, Handlebars v2.0, Markdown, existing validator pattern from `src/lib/validators/blazor/blazor-validator.js`
10
+
11
+ ---
12
+
13
+ ## Locked Conventions (reference throughout)
14
+
15
+ - **Files:** kebab-case (`user-card.tsx`)
16
+ - **Exports:** PascalCase components (`UserCard`), camelCase hooks (`useCreateUser`)
17
+ - **Hooks:** file `use-{action}.ts`, export `use{Action}()`
18
+ - **Schemas:** `{feature}.schemas.ts` with `z.infer<>` for types
19
+ - **Folders:** feature-based with `features/{name}/components|hooks|types/`
20
+ - **Shared components:** `components/ui/` (shadcn, never edit) → `components/` (composed, no business logic) → `features/*/components/` (feature-scoped)
21
+ - **Data:** Server Components for initial fetch, TanStack Query for mutations/client state
22
+ - **Forms:** react-hook-form + Zod + shadcn `<Form>`
23
+ - **Deployment:** EasyPanel → Docker multi-stage
24
+
25
+ ---
26
+
27
+ ### Task 1: Create 8 standards files
28
+
29
+ **Files:**
30
+ - Create: `framework/standards/frontend/nextjs/app-router.md`
31
+ - Create: `framework/standards/frontend/nextjs/project-structure.md`
32
+ - Create: `framework/standards/frontend/nextjs/naming-conventions.md`
33
+ - Create: `framework/standards/frontend/nextjs/components.md`
34
+ - Create: `framework/standards/frontend/nextjs/data-fetching.md`
35
+ - Create: `framework/standards/frontend/nextjs/forms.md`
36
+ - Create: `framework/standards/frontend/nextjs/state-management.md`
37
+ - Create: `framework/standards/frontend/nextjs/testing.md`
38
+
39
+ **Note:** No tests for content files. Write directly.
40
+
41
+ **Step 1: Create `framework/standards/frontend/nextjs/app-router.md`**
42
+
43
+ ```markdown
44
+ # Next.js App Router Standard
45
+
46
+ > **Scope:** frontend/nextjs/app-router
47
+ > **Layer:** 2 (on keyword)
48
+ > **Keywords:** next.js, app router, server component, client component, page, layout, route
49
+ > **Load When:** editing files in `app/` or `features/` directories
50
+
51
+ Next.js App Router with TypeScript strict mode. Server Components by default, Client Components only when interactivity is required.
52
+
53
+ ## Core Rules
54
+
55
+ - ALWAYS default to Server Components — add `'use client'` only when needed
56
+ - ALWAYS keep `app/` directory for routing only — no business logic in page files
57
+ - NEVER put data fetching in Client Components when a Server Component can do it
58
+ - NEVER use `useEffect` to fetch data — use Server Components or TanStack Query
59
+ - ALWAYS co-locate loading/error UI: `loading.tsx`, `error.tsx` next to `page.tsx`
60
+ - ALWAYS use TypeScript — no `.js` or `.jsx` files in the project
61
+
62
+ ## Server vs Client Components
63
+
64
+ | Use Server Component | Use Client Component |
65
+ |---------------------|---------------------|
66
+ | Fetching from .NET API on load | onClick, onChange, form submit |
67
+ | Rendering static or user-specific data | useState, useEffect, useRef |
68
+ | Accessing backend environment variables | Browser APIs (localStorage, geolocation) |
69
+ | SEO-critical content | TanStack Query hooks |
70
+ | No interactivity needed | shadcn/ui interactive components |
71
+
72
+ ## Decision Tree
73
+
74
+ ```
75
+ Does this component need user interaction (click, input, hover state)?
76
+ YES → 'use client'
77
+ NO → Does it fetch data?
78
+ YES → Server Component (fetch directly)
79
+ NO → Server Component (static)
80
+ ```
81
+
82
+ ## App Directory Structure
83
+
84
+ ```
85
+ src/app/
86
+ ├── (auth)/
87
+ │ ├── login/
88
+ │ │ └── page.tsx # Server Component — renders login form
89
+ │ └── register/
90
+ │ └── page.tsx
91
+ ├── (dashboard)/
92
+ │ ├── layout.tsx # Shared layout for dashboard routes
93
+ │ ├── page.tsx # Dashboard home
94
+ │ └── users/
95
+ │ ├── page.tsx # User list (Server Component)
96
+ │ ├── [id]/
97
+ │ │ └── page.tsx # User detail
98
+ │ ├── loading.tsx # Suspense fallback
99
+ │ └── error.tsx # Error boundary
100
+ └── layout.tsx # Root layout — providers, fonts, metadata
101
+ ```
102
+
103
+ ## File Conventions
104
+
105
+ | File | Purpose | Type |
106
+ |------|---------|------|
107
+ | `page.tsx` | Route segment UI | Server Component (default) |
108
+ | `layout.tsx` | Shared UI wrapper | Server Component |
109
+ | `loading.tsx` | Suspense fallback | Server Component |
110
+ | `error.tsx` | Error boundary | **Must be Client Component** |
111
+ | `not-found.tsx` | 404 page | Server Component |
112
+ | `route.ts` | API route (avoid — use .NET API) | — |
113
+
114
+ ## Server Component Data Fetch Pattern
115
+
116
+ ```tsx
117
+ // app/(dashboard)/users/page.tsx
118
+ import { UserList } from '@/features/users/components/user-list';
119
+
120
+ async function getUsers(): Promise<User[]> {
121
+ const res = await fetch(`${process.env.API_URL}/api/users`, {
122
+ headers: { Authorization: `Bearer ${await getServerToken()}` },
123
+ next: { revalidate: 60 }, // ISR: revalidate every 60s
124
+ });
125
+ if (!res.ok) throw new Error('Failed to fetch users');
126
+ return res.json();
127
+ }
128
+
129
+ export default async function UsersPage() {
130
+ const users = await getUsers();
131
+ return <UserList initialUsers={users} />;
132
+ }
133
+ ```
134
+
135
+ ## Root Layout — Required Providers
136
+
137
+ ```tsx
138
+ // app/layout.tsx
139
+ import { QueryProvider } from '@/lib/query-client';
140
+ import { Toaster } from '@/components/ui/sonner';
141
+
142
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
143
+ return (
144
+ <html lang="pt-BR">
145
+ <body>
146
+ <QueryProvider>
147
+ {children}
148
+ <Toaster />
149
+ </QueryProvider>
150
+ </body>
151
+ </html>
152
+ );
153
+ }
154
+ ```
155
+
156
+ ## Common Mistakes
157
+
158
+ | Wrong | Right | Why |
159
+ |-------|-------|-----|
160
+ | `'use client'` on every component | Only add when needed | Kills SSR benefits, bloats JS bundle |
161
+ | `useEffect(() => fetch(...), [])` | Server Component fetch or TanStack Query | Two renders, no caching, no SSR |
162
+ | Business logic in `page.tsx` | Move to `features/` | pages are routes, not controllers |
163
+ | `fetch` without error handling | Always check `res.ok` | Silent failures in production |
164
+
165
+ ---
166
+
167
+ *MORPH-SPEC by Polymorphism Tech*
168
+ ```
169
+
170
+ **Step 2: Create `framework/standards/frontend/nextjs/project-structure.md`**
171
+
172
+ ```markdown
173
+ # Next.js Project Structure Standard
174
+
175
+ > **Scope:** frontend/nextjs/project-structure
176
+ > **Layer:** 2 (on keyword)
177
+ > **Keywords:** project structure, folder structure, feature folder, src layout
178
+ > **Load When:** creating new files or features
179
+
180
+ Feature-based architecture with a shared core. Features are self-contained; shared code is explicit.
181
+
182
+ ## Core Rules
183
+
184
+ - ALWAYS use feature-based folders: `features/{feature-name}/`
185
+ - ALWAYS keep `app/` for routing only — import from `features/`, not the other way
186
+ - NEVER import from one feature into another — extract to `components/` or `lib/` if shared
187
+ - ALWAYS use `src/` directory (configured in `tsconfig.json` with `@/` alias)
188
+ - NEVER put business logic in `components/` — it is presentation only
189
+
190
+ ## Canonical Folder Tree
191
+
192
+ ```
193
+ src/
194
+ ├── app/ # Next.js App Router — routes only
195
+ │ ├── layout.tsx # Root layout + providers
196
+ │ ├── page.tsx # Home page
197
+ │ └── (dashboard)/
198
+ │ ├── layout.tsx
199
+ │ ├── users/
200
+ │ │ ├── page.tsx # Renders UserPage from features/users
201
+ │ │ ├── loading.tsx
202
+ │ │ └── error.tsx
203
+ │ └── billing/
204
+ │ └── page.tsx
205
+
206
+ ├── features/ # Domain features
207
+ │ └── {feature-name}/
208
+ │ ├── components/ # Feature-specific UI components
209
+ │ │ ├── user-list.tsx
210
+ │ │ └── user-card.tsx
211
+ │ ├── hooks/ # TanStack Query hooks for this feature
212
+ │ │ ├── use-users.ts
213
+ │ │ └── use-create-user.ts
214
+ │ ├── types/
215
+ │ │ ├── user.types.ts # TypeScript types
216
+ │ │ └── user.schemas.ts # Zod schemas + z.infer<> types
217
+ │ └── index.ts # Public API — only export what's needed
218
+
219
+ ├── components/ # Shared UI — no business logic
220
+ │ ├── ui/ # shadcn/ui — never edit directly
221
+ │ │ ├── button.tsx
222
+ │ │ └── card.tsx
223
+ │ ├── data-table.tsx # Composed: TanStack Table + shadcn Table
224
+ │ ├── page-header.tsx
225
+ │ └── empty-state.tsx
226
+
227
+ ├── hooks/ # Shared utility hooks — no API calls
228
+ │ ├── use-debounce.ts
229
+ │ └── use-media-query.ts
230
+
231
+ ├── lib/
232
+ │ ├── api-client.ts # Typed fetch wrapper for .NET API
233
+ │ └── query-client.tsx # TanStack Query client + provider
234
+
235
+ ├── types/
236
+ │ ├── api.ts # Shared API shapes: PaginatedResponse<T>, ApiError
237
+ │ └── env.d.ts # Environment variable types
238
+
239
+ └── env.mjs # Zod-validated env vars — crashes at startup if missing
240
+ ```
241
+
242
+ ## Feature Index Pattern
243
+
244
+ ```ts
245
+ // features/users/index.ts — explicit public API
246
+ export { UserList } from './components/user-list';
247
+ export { UserCard } from './components/user-card';
248
+ export { useUsers } from './hooks/use-users';
249
+ export { useCreateUser } from './hooks/use-create-user';
250
+ export type { User, CreateUserInput } from './types/user.types';
251
+ ```
252
+
253
+ Import from the index, not deep paths:
254
+ ```ts
255
+ // GOOD
256
+ import { UserList, useUsers } from '@/features/users';
257
+
258
+ // BAD
259
+ import { UserList } from '@/features/users/components/user-list';
260
+ ```
261
+
262
+ ## Feature Boundary Rule
263
+
264
+ ```
265
+ app/ → imports from features/ ✓
266
+ features/users/ → imports from components/ and lib/ ✓
267
+ features/users/ → imports from features/billing/ ✗ (extract shared code instead)
268
+ components/ → imports from components/ui/ ✓
269
+ components/ → imports from features/ ✗
270
+ ```
271
+
272
+ ---
273
+
274
+ *MORPH-SPEC by Polymorphism Tech*
275
+ ```
276
+
277
+ **Step 3: Create `framework/standards/frontend/nextjs/naming-conventions.md`**
278
+
279
+ ```markdown
280
+ # Next.js Naming Conventions Standard
281
+
282
+ > **Scope:** frontend/nextjs/naming-conventions
283
+ > **Layer:** 1 (always)
284
+ > **Keywords:** naming, conventions, file names, component names, hooks, typescript
285
+ > **Load When:** creating any new file in a Next.js project
286
+
287
+ Consistent naming prevents filesystem bugs (Linux case-sensitivity) and keeps the codebase predictable.
288
+
289
+ ## Core Rules
290
+
291
+ - ALWAYS use kebab-case for file names — never PascalCase or camelCase
292
+ - ALWAYS use PascalCase for React component exports
293
+ - ALWAYS use camelCase starting with `use` for hook exports
294
+ - ALWAYS suffix schema files with `.schemas.ts` and type files with `.types.ts`
295
+ - NEVER mix cases in the same category — no `UserCard.tsx` alongside `user-profile.tsx`
296
+
297
+ ## Complete Reference Table
298
+
299
+ | Artifact | File Name | Export Name | Example |
300
+ |----------|-----------|-------------|---------|
301
+ | React Component | `user-card.tsx` | `UserCard` | `export function UserCard()` |
302
+ | Client Component | `user-form.tsx` | `UserForm` | `export function UserForm()` (+ `'use client'`) |
303
+ | TanStack Query hook | `use-users.ts` | `useUsers` | `export function useUsers()` |
304
+ | Mutation hook | `use-create-user.ts` | `useCreateUser` | `export function useCreateUser()` |
305
+ | Utility hook | `use-debounce.ts` | `useDebounce` | `export function useDebounce()` |
306
+ | Zod schema file | `user.schemas.ts` | `createUserSchema` | `export const createUserSchema = z.object(...)` |
307
+ | TypeScript types | `user.types.ts` | `User`, `CreateUserInput` | `export type User = z.infer<typeof userSchema>` |
308
+ | API utilities | `users-api.ts` | `fetchUsers` | `export async function fetchUsers()` |
309
+ | Feature index | `index.ts` | (re-exports) | `export { UserCard } from './components/user-card'` |
310
+ | Page file | `page.tsx` | `default` | `export default function UsersPage()` |
311
+
312
+ ## Component Naming Rules
313
+
314
+ ```
315
+ Feature name: users
316
+ List component: user-list.tsx → UserList
317
+ Card component: user-card.tsx → UserCard
318
+ Form component: user-form.tsx → UserForm
319
+ Dialog: create-user-dialog.tsx → CreateUserDialog
320
+ Table: users-table.tsx → UsersTable
321
+ ```
322
+
323
+ ## Hook Naming Rules
324
+
325
+ ```
326
+ GET list: use-users.ts → useUsers()
327
+ GET single: use-user.ts → useUser(id: string)
328
+ POST: use-create-user.ts → useCreateUser()
329
+ PUT/PATCH: use-update-user.ts → useUpdateUser()
330
+ DELETE: use-delete-user.ts → useDeleteUser()
331
+ ```
332
+
333
+ ## Schema and Type Naming Rules
334
+
335
+ ```typescript
336
+ // user.schemas.ts
337
+ export const userSchema = z.object({ id: z.string(), name: z.string() });
338
+ export const createUserSchema = z.object({ name: z.string().min(2), email: z.string().email() });
339
+ export const updateUserSchema = createUserSchema.partial();
340
+
341
+ // user.types.ts — derive from schemas, don't duplicate
342
+ export type User = z.infer<typeof userSchema>;
343
+ export type CreateUserInput = z.infer<typeof createUserSchema>;
344
+ export type UpdateUserInput = z.infer<typeof updateUserSchema>;
345
+ ```
346
+
347
+ ## Zod Schema Conventions
348
+
349
+ | Operation | Schema Name | Derived Type |
350
+ |-----------|-------------|--------------|
351
+ | Response shape | `userSchema` | `User` |
352
+ | Create request | `createUserSchema` | `CreateUserInput` |
353
+ | Update request | `updateUserSchema` | `UpdateUserInput` |
354
+ | Form values | `userFormSchema` | `UserFormValues` |
355
+ | API params | `getUsersParamsSchema` | `GetUsersParams` |
356
+
357
+ ## Common Mistakes
358
+
359
+ | Wrong | Right | Why |
360
+ |-------|-------|-----|
361
+ | `UserCard.tsx` | `user-card.tsx` | Linux servers are case-sensitive |
362
+ | `useUsers.ts` | `use-users.ts` | Inconsistent with Next.js file conventions |
363
+ | `export default function UserCard` | `export function UserCard` | Named exports are more refactor-safe |
364
+ | `type User = { id: string; name: string }` | `type User = z.infer<typeof userSchema>` | Single source of truth |
365
+
366
+ ---
367
+
368
+ *MORPH-SPEC by Polymorphism Tech*
369
+ ```
370
+
371
+ **Step 4: Create `framework/standards/frontend/nextjs/components.md`**
372
+
373
+ ```markdown
374
+ # Next.js Component Standards
375
+
376
+ > **Scope:** frontend/nextjs/components
377
+ > **Layer:** 2 (on keyword)
378
+ > **Keywords:** component, shadcn, reusable, shared, ui, three-tier
379
+ > **Load When:** creating or editing React components
380
+
381
+ Three-tier component hierarchy. `components/ui/` is shadcn primitives (never edit). `components/` is composed shared (no business logic). `features/*/components/` is feature-scoped.
382
+
383
+ ## Core Rules
384
+
385
+ - NEVER edit files in `components/ui/` — they are regenerated by shadcn CLI
386
+ - NEVER import from `features/` inside `components/` — components know nothing about domain
387
+ - ALWAYS compose shadcn primitives in `components/` instead of editing them
388
+ - ALWAYS add `'use client'` only if the component uses hooks, events, or browser APIs
389
+ - NEVER pass raw API data directly to a component — transform to props first
390
+
391
+ ## Three-Tier Hierarchy
392
+
393
+ ```
394
+ Tier 1: components/ui/ ← shadcn/ui CLI output (DO NOT EDIT)
395
+ ↓ composed into
396
+ Tier 2: components/ ← shared project components (no business logic)
397
+ ↓ used by
398
+ Tier 3: features/*/components/ ← feature-scoped (knows about users, billing, etc.)
399
+ ```
400
+
401
+ ## Tier 1 — shadcn/ui Primitives
402
+
403
+ ```bash
404
+ # Add shadcn components via CLI — never write them manually
405
+ npx shadcn@latest add button card dialog form input table
406
+
407
+ # Available at:
408
+ src/components/ui/button.tsx
409
+ src/components/ui/card.tsx
410
+ src/components/ui/dialog.tsx
411
+ ```
412
+
413
+ If you need to change a shadcn component's behavior, wrap it — do not edit the source file.
414
+
415
+ ## Tier 2 — Shared Composed Components
416
+
417
+ ```tsx
418
+ // components/data-table.tsx — composes shadcn Table + TanStack Table
419
+ 'use client'; // TanStack Table requires client
420
+
421
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
422
+ import { type ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
423
+
424
+ interface DataTableProps<TData> {
425
+ columns: ColumnDef<TData>[];
426
+ data: TData[];
427
+ }
428
+
429
+ export function DataTable<TData>({ columns, data }: DataTableProps<TData>) {
430
+ const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
431
+ return (
432
+ <Table>
433
+ <TableHeader>
434
+ {table.getHeaderGroups().map((headerGroup) => (
435
+ <TableRow key={headerGroup.id}>
436
+ {headerGroup.headers.map((header) => (
437
+ <TableHead key={header.id}>
438
+ {flexRender(header.column.columnDef.header, header.getContext())}
439
+ </TableHead>
440
+ ))}
441
+ </TableRow>
442
+ ))}
443
+ </TableHeader>
444
+ <TableBody>
445
+ {table.getRowModel().rows.map((row) => (
446
+ <TableRow key={row.id}>
447
+ {row.getVisibleCells().map((cell) => (
448
+ <TableCell key={cell.id}>
449
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
450
+ </TableCell>
451
+ ))}
452
+ </TableRow>
453
+ ))}
454
+ </TableBody>
455
+ </Table>
456
+ );
457
+ }
458
+ ```
459
+
460
+ ## Tier 3 — Feature Components
461
+
462
+ ```tsx
463
+ // features/users/components/user-list.tsx
464
+ // Uses DataTable (Tier 2) and UserCard (Tier 3)
465
+ 'use client';
466
+
467
+ import { DataTable } from '@/components/data-table';
468
+ import { useUsers } from '@/features/users/hooks/use-users';
469
+ import { type ColumnDef } from '@tanstack/react-table';
470
+ import type { User } from '@/features/users/types/user.types';
471
+
472
+ const columns: ColumnDef<User>[] = [
473
+ { accessorKey: 'name', header: 'Name' },
474
+ { accessorKey: 'email', header: 'Email' },
475
+ ];
476
+
477
+ export function UserList() {
478
+ const { data: users = [], isLoading } = useUsers();
479
+ if (isLoading) return <div>Loading...</div>;
480
+ return <DataTable columns={columns} data={users} />;
481
+ }
482
+ ```
483
+
484
+ ## Component Props Conventions
485
+
486
+ ```tsx
487
+ // GOOD — explicit, typed props
488
+ interface UserCardProps {
489
+ user: User;
490
+ onEdit?: (id: string) => void;
491
+ className?: string; // Always allow className for Tailwind override
492
+ }
493
+
494
+ export function UserCard({ user, onEdit, className }: UserCardProps) {}
495
+
496
+ // BAD — spreading unknown props, no types
497
+ export function UserCard({ ...props }) {}
498
+ ```
499
+
500
+ ## Common Mistakes
501
+
502
+ | Wrong | Right | Why |
503
+ |-------|-------|-----|
504
+ | Edit `components/ui/button.tsx` | Wrap it in `components/action-button.tsx` | shadcn CLI overwrites it |
505
+ | `import { useUsers } from '@/features/users'` inside `components/` | Move to a feature component | Breaks tier isolation |
506
+ | `'use client'` on all components | Only on interactive ones | Unnecessary client JS |
507
+ | Pass raw fetch response as prop | Type and validate with Zod first | Runtime type safety |
508
+
509
+ ---
510
+
511
+ *MORPH-SPEC by Polymorphism Tech*
512
+ ```
513
+
514
+ **Step 5: Create `framework/standards/frontend/nextjs/data-fetching.md`**
515
+
516
+ ```markdown
517
+ # Next.js Data Fetching Standard
518
+
519
+ > **Scope:** frontend/nextjs/data-fetching
520
+ > **Layer:** 2 (on keyword)
521
+ > **Keywords:** data fetching, tanstack query, react query, server component, fetch, api
522
+ > **Load When:** fetching data from .NET API
523
+
524
+ Server Components for initial page load. TanStack Query for client mutations and interactive data. Never `useEffect` for fetching.
525
+
526
+ ## Core Rules
527
+
528
+ - ALWAYS use Server Components for initial data that doesn't require interactivity
529
+ - ALWAYS use TanStack Query (`useQuery`, `useMutation`) for client-side data needs
530
+ - NEVER use `useEffect(() => { fetch(...) }, [])` — this is the old pattern
531
+ - ALWAYS validate API responses with Zod before using them
532
+ - ALWAYS use query key factories for consistent cache invalidation
533
+
534
+ ## TanStack Query Setup
535
+
536
+ ```tsx
537
+ // lib/query-client.tsx
538
+ 'use client';
539
+
540
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
541
+ import { useState } from 'react';
542
+
543
+ export function QueryProvider({ children }: { children: React.ReactNode }) {
544
+ const [queryClient] = useState(() => new QueryClient({
545
+ defaultOptions: {
546
+ queries: { staleTime: 60 * 1000, retry: 1 },
547
+ },
548
+ }));
549
+ return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
550
+ }
551
+ ```
552
+
553
+ ## Query Key Factory Pattern
554
+
555
+ ```ts
556
+ // features/users/hooks/query-keys.ts
557
+ export const userKeys = {
558
+ all: ['users'] as const,
559
+ lists: () => [...userKeys.all, 'list'] as const,
560
+ list: (filters: Record<string, unknown>) => [...userKeys.lists(), filters] as const,
561
+ details: () => [...userKeys.all, 'detail'] as const,
562
+ detail: (id: string) => [...userKeys.details(), id] as const,
563
+ };
564
+ ```
565
+
566
+ ## useQuery Hook Pattern
567
+
568
+ ```ts
569
+ // features/users/hooks/use-users.ts
570
+ import { useQuery } from '@tanstack/react-query';
571
+ import { userKeys } from './query-keys';
572
+ import { userSchema } from '@/features/users/types/user.schemas';
573
+ import { z } from 'zod';
574
+
575
+ const usersResponseSchema = z.array(userSchema);
576
+
577
+ export function useUsers() {
578
+ return useQuery({
579
+ queryKey: userKeys.lists(),
580
+ queryFn: async () => {
581
+ const res = await fetch('/api/users'); // proxy to .NET API
582
+ if (!res.ok) throw new Error('Failed to fetch users');
583
+ return usersResponseSchema.parse(await res.json()); // Zod validation
584
+ },
585
+ });
586
+ }
587
+ ```
588
+
589
+ ## useMutation Hook Pattern
590
+
591
+ ```ts
592
+ // features/users/hooks/use-create-user.ts
593
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
594
+ import { userKeys } from './query-keys';
595
+ import { type CreateUserInput } from '@/features/users/types/user.types';
596
+
597
+ export function useCreateUser() {
598
+ const queryClient = useQueryClient();
599
+ return useMutation({
600
+ mutationFn: async (input: CreateUserInput) => {
601
+ const res = await fetch('/api/users', {
602
+ method: 'POST',
603
+ headers: { 'Content-Type': 'application/json' },
604
+ body: JSON.stringify(input),
605
+ });
606
+ if (!res.ok) throw new Error('Failed to create user');
607
+ return res.json();
608
+ },
609
+ onSuccess: () => {
610
+ queryClient.invalidateQueries({ queryKey: userKeys.lists() });
611
+ },
612
+ });
613
+ }
614
+ ```
615
+
616
+ ## Server Component + TanStack Query Handoff
617
+
618
+ ```tsx
619
+ // app/(dashboard)/users/page.tsx — Server Component: initial fetch
620
+ async function getInitialUsers() {
621
+ const res = await fetch(`${process.env.API_URL}/api/users`, { next: { revalidate: 30 } });
622
+ return res.json();
623
+ }
624
+
625
+ export default async function UsersPage() {
626
+ const initialUsers = await getInitialUsers();
627
+ return <UserListClient initialData={initialUsers} />;
628
+ }
629
+
630
+ // features/users/components/user-list-client.tsx — Client Component: mutations
631
+ 'use client';
632
+ import { useUsers } from '@/features/users/hooks/use-users';
633
+
634
+ export function UserListClient({ initialData }: { initialData: User[] }) {
635
+ const { data: users = initialData } = useUsers(); // initialData hydrates cache
636
+ // ...mutations, filtering, etc.
637
+ }
638
+ ```
639
+
640
+ ## Common Mistakes
641
+
642
+ | Wrong | Right | Why |
643
+ |-------|-------|-----|
644
+ | `useEffect(() => { fetch(...) }, [])` | `useQuery(...)` | Two renders, no cache, no SSR |
645
+ | No Zod on API response | `schema.parse(await res.json())` | Type safety at runtime |
646
+ | Hardcoded query keys: `['users']` | Key factory: `userKeys.lists()` | Consistent invalidation |
647
+ | `queryClient.invalidateQueries(['users'])` | `queryClient.invalidateQueries({ queryKey: userKeys.lists() })` | Object syntax is required in v5 |
648
+
649
+ ---
650
+
651
+ *MORPH-SPEC by Polymorphism Tech*
652
+ ```
653
+
654
+ **Step 6: Create `framework/standards/frontend/nextjs/forms.md`**
655
+
656
+ ```markdown
657
+ # Next.js Forms Standard
658
+
659
+ > **Scope:** frontend/nextjs/forms
660
+ > **Layer:** 2 (on keyword)
661
+ > **Keywords:** form, react-hook-form, zod, validation, input, submit
662
+ > **Load When:** implementing any form in Next.js
663
+
664
+ react-hook-form + Zod + shadcn Form components. Schema defines both validation rules and TypeScript types.
665
+
666
+ ## Core Rules
667
+
668
+ - ALWAYS define the Zod schema first — derive the TypeScript type from it
669
+ - ALWAYS use `zodResolver` to connect schema to react-hook-form
670
+ - ALWAYS use shadcn `<Form>`, `<FormField>`, `<FormItem>`, `<FormMessage>` for consistent UI
671
+ - NEVER use `useState` for form field values — react-hook-form handles this
672
+ - ALWAYS use `useMutation` from TanStack Query for form submission
673
+
674
+ ## Complete Form Pattern
675
+
676
+ ```tsx
677
+ // features/users/types/user.schemas.ts
678
+ import { z } from 'zod';
679
+
680
+ export const createUserSchema = z.object({
681
+ name: z.string().min(2, 'Name must be at least 2 characters'),
682
+ email: z.string().email('Invalid email address'),
683
+ role: z.enum(['admin', 'user'], { required_error: 'Role is required' }),
684
+ });
685
+
686
+ export type CreateUserInput = z.infer<typeof createUserSchema>;
687
+ ```
688
+
689
+ ```tsx
690
+ // features/users/components/create-user-form.tsx
691
+ 'use client';
692
+
693
+ import { useForm } from 'react-hook-form';
694
+ import { zodResolver } from '@hookform/resolvers/zod';
695
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
696
+ import { Input } from '@/components/ui/input';
697
+ import { Button } from '@/components/ui/button';
698
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
699
+ import { useCreateUser } from '@/features/users/hooks/use-create-user';
700
+ import { createUserSchema, type CreateUserInput } from '@/features/users/types/user.schemas';
701
+
702
+ export function CreateUserForm({ onSuccess }: { onSuccess?: () => void }) {
703
+ const form = useForm<CreateUserInput>({
704
+ resolver: zodResolver(createUserSchema),
705
+ defaultValues: { name: '', email: '', role: 'user' },
706
+ });
707
+
708
+ const { mutate: createUser, isPending } = useCreateUser();
709
+
710
+ function onSubmit(values: CreateUserInput) {
711
+ createUser(values, {
712
+ onSuccess: () => { form.reset(); onSuccess?.(); },
713
+ onError: (error) => { form.setError('root', { message: error.message }); },
714
+ });
715
+ }
716
+
717
+ return (
718
+ <Form {...form}>
719
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
720
+ <FormField
721
+ control={form.control}
722
+ name="name"
723
+ render={({ field }) => (
724
+ <FormItem>
725
+ <FormLabel>Name</FormLabel>
726
+ <FormControl><Input placeholder="John Doe" {...field} /></FormControl>
727
+ <FormMessage />
728
+ </FormItem>
729
+ )}
730
+ />
731
+ <FormField
732
+ control={form.control}
733
+ name="email"
734
+ render={({ field }) => (
735
+ <FormItem>
736
+ <FormLabel>Email</FormLabel>
737
+ <FormControl><Input type="email" placeholder="john@example.com" {...field} /></FormControl>
738
+ <FormMessage />
739
+ </FormItem>
740
+ )}
741
+ />
742
+ <FormField
743
+ control={form.control}
744
+ name="role"
745
+ render={({ field }) => (
746
+ <FormItem>
747
+ <FormLabel>Role</FormLabel>
748
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
749
+ <FormControl>
750
+ <SelectTrigger><SelectValue placeholder="Select role" /></SelectTrigger>
751
+ </FormControl>
752
+ <SelectContent>
753
+ <SelectItem value="admin">Admin</SelectItem>
754
+ <SelectItem value="user">User</SelectItem>
755
+ </SelectContent>
756
+ </Select>
757
+ <FormMessage />
758
+ </FormItem>
759
+ )}
760
+ />
761
+ {form.formState.errors.root && (
762
+ <p className="text-sm text-destructive">{form.formState.errors.root.message}</p>
763
+ )}
764
+ <Button type="submit" disabled={isPending}>
765
+ {isPending ? 'Creating...' : 'Create User'}
766
+ </Button>
767
+ </form>
768
+ </Form>
769
+ );
770
+ }
771
+ ```
772
+
773
+ ## Edit Form Pattern (pre-populated)
774
+
775
+ ```tsx
776
+ // Edit forms use the same schema but with defaultValues from existing data
777
+ const form = useForm<UpdateUserInput>({
778
+ resolver: zodResolver(updateUserSchema), // .partial() version
779
+ defaultValues: { name: user.name, email: user.email, role: user.role },
780
+ });
781
+ ```
782
+
783
+ ## Schema Composition
784
+
785
+ ```ts
786
+ // Reuse schemas — don't duplicate
787
+ export const userSchema = z.object({ id: z.string(), name: z.string(), email: z.string() });
788
+ export const createUserSchema = userSchema.omit({ id: true });
789
+ export const updateUserSchema = createUserSchema.partial();
790
+ ```
791
+
792
+ ## Common Mistakes
793
+
794
+ | Wrong | Right | Why |
795
+ |-------|-------|-----|
796
+ | `useState` for each field | `useForm` | Unnecessary re-renders on every keystroke |
797
+ | Manual error display | `<FormMessage />` | Consistent UI, auto-connects to field errors |
798
+ | Submit in `useEffect` | `form.handleSubmit(onSubmit)` | Handles validation before calling onSubmit |
799
+ | `fetch` in submit handler | `useMutation` | Loading state, error handling, cache invalidation |
800
+
801
+ ---
802
+
803
+ *MORPH-SPEC by Polymorphism Tech*
804
+ ```
805
+
806
+ **Step 7: Create `framework/standards/frontend/nextjs/state-management.md`**
807
+
808
+ ```markdown
809
+ # Next.js State Management Standard
810
+
811
+ > **Scope:** frontend/nextjs/state-management
812
+ > **Layer:** 2 (on keyword)
813
+ > **Keywords:** state, zustand, context, redux, client state, server state
814
+ > **Load When:** deciding how to manage state in Next.js
815
+
816
+ Server state (API data) lives in TanStack Query. UI state lives in React. No global state library unless genuinely needed.
817
+
818
+ ## Core Rules
819
+
820
+ - ALWAYS use TanStack Query for anything that comes from the .NET API
821
+ - ALWAYS use `useState` for local UI state (modal open, selected tab, form step)
822
+ - NEVER install Zustand or Redux without first exhausting Server Components + TanStack Query
823
+ - NEVER use React Context for server state — Context does not cache or deduplicate
824
+ - ALWAYS derive state from server data rather than syncing it to local state
825
+
826
+ ## State Decision Tree
827
+
828
+ ```
829
+ Is this data from the .NET API?
830
+ YES → TanStack Query (useQuery / useMutation)
831
+
832
+ Is this local UI state (open/closed, selected, current step)?
833
+ YES → useState in the component that needs it
834
+
835
+ Does multiple components need this UI state?
836
+ YES — is it parent-child? → prop drilling (2-3 levels is fine)
837
+ YES — is it truly global? → React Context (theme, auth user, locale)
838
+ YES — is it complex with many actions? → Consider Zustand (last resort)
839
+ ```
840
+
841
+ ## Server State (TanStack Query)
842
+
843
+ ```ts
844
+ // GOOD — server state in TanStack Query
845
+ const { data: users } = useUsers(); // cached, deduplicated, background-refetched
846
+
847
+ // BAD — copying server state to useState
848
+ const [users, setUsers] = useState([]);
849
+ useEffect(() => { fetchUsers().then(setUsers); }, []);
850
+ ```
851
+
852
+ ## Local UI State (useState)
853
+
854
+ ```tsx
855
+ // GOOD — local UI state in useState
856
+ function UserList() {
857
+ const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
858
+ const [selectedUserId, setSelectedUserId] = useState<string | null>(null);
859
+ // ...
860
+ }
861
+ ```
862
+
863
+ ## Shared UI State (React Context)
864
+
865
+ Use Context only for truly global UI concerns:
866
+
867
+ ```tsx
868
+ // lib/auth-context.tsx — OK for Context (session-level, read-only)
869
+ const AuthContext = createContext<{ user: User | null }>({ user: null });
870
+
871
+ // NOT OK for Context
872
+ // - List of users (use TanStack Query)
873
+ // - Form values (use react-hook-form)
874
+ // - Modal state only used in one feature (use useState)
875
+ ```
876
+
877
+ ## When Zustand Is Justified
878
+
879
+ Only add Zustand if ALL of these are true:
880
+ 1. State is needed in 5+ unrelated components
881
+ 2. State has complex update logic (not just toggle/set)
882
+ 3. Context causes measurable re-render performance issues
883
+ 4. State is not from the server
884
+
885
+ ## Common Mistakes
886
+
887
+ | Wrong | Right | Why |
888
+ |-------|-------|-----|
889
+ | `useEffect` + `useState` for API data | TanStack Query | Stale data, no cache, double-render |
890
+ | Global store for user list | `useUsers()` | TanStack Query handles sync/cache |
891
+ | Context for everything | useState + prop drilling first | Context causes all consumers to re-render |
892
+ | Zustand by default | Start with useState | YAGNI — most state is local |
893
+
894
+ ---
895
+
896
+ *MORPH-SPEC by Polymorphism Tech*
897
+ ```
898
+
899
+ **Step 8: Create `framework/standards/frontend/nextjs/testing.md`**
900
+
901
+ ```markdown
902
+ # Next.js Testing Standard
903
+
904
+ > **Scope:** frontend/nextjs/testing
905
+ > **Layer:** 2 (on keyword)
906
+ > **Keywords:** testing, jest, vitest, testing-library, msw, component test
907
+ > **Load When:** writing tests for Next.js components or hooks
908
+
909
+ Jest + React Testing Library for component tests. MSW for API mocking. Co-locate tests with the code they test.
910
+
911
+ ## Core Rules
912
+
913
+ - ALWAYS co-locate test files: `user-card.test.tsx` next to `user-card.tsx`
914
+ - ALWAYS use `@testing-library/user-event` for interactions — not `fireEvent`
915
+ - ALWAYS mock the API layer with MSW — never mock `fetch` directly
916
+ - NEVER test implementation details — test what the user sees
917
+ - ALWAYS test the happy path + one error path per component
918
+
919
+ ## Test File Co-location
920
+
921
+ ```
922
+ features/users/
923
+ ├── components/
924
+ │ ├── user-card.tsx
925
+ │ ├── user-card.test.tsx ← co-located
926
+ │ ├── user-list.tsx
927
+ │ └── user-list.test.tsx
928
+ ├── hooks/
929
+ │ ├── use-users.ts
930
+ │ └── use-users.test.ts ← co-located
931
+ ```
932
+
933
+ ## Component Test Pattern
934
+
935
+ ```tsx
936
+ // features/users/components/user-card.test.tsx
937
+ import { render, screen } from '@testing-library/react';
938
+ import userEvent from '@testing-library/user-event';
939
+ import { UserCard } from './user-card';
940
+
941
+ const mockUser = { id: '1', name: 'João Silva', email: 'joao@example.com', role: 'user' };
942
+
943
+ describe('UserCard', () => {
944
+ it('renders user name and email', () => {
945
+ render(<UserCard user={mockUser} />);
946
+ expect(screen.getByText('João Silva')).toBeInTheDocument();
947
+ expect(screen.getByText('joao@example.com')).toBeInTheDocument();
948
+ });
949
+
950
+ it('calls onEdit with user id when edit button is clicked', async () => {
951
+ const onEdit = vi.fn();
952
+ render(<UserCard user={mockUser} onEdit={onEdit} />);
953
+ await userEvent.click(screen.getByRole('button', { name: /edit/i }));
954
+ expect(onEdit).toHaveBeenCalledWith('1');
955
+ });
956
+ });
957
+ ```
958
+
959
+ ## Hook Test with MSW
960
+
961
+ ```ts
962
+ // features/users/hooks/use-users.test.ts
963
+ import { renderHook, waitFor } from '@testing-library/react';
964
+ import { QueryClientWrapper } from '@/test/helpers';
965
+ import { http, HttpResponse } from 'msw';
966
+ import { server } from '@/test/msw-server';
967
+ import { useUsers } from './use-users';
968
+
969
+ describe('useUsers', () => {
970
+ it('returns users from API', async () => {
971
+ server.use(
972
+ http.get('/api/users', () =>
973
+ HttpResponse.json([{ id: '1', name: 'João', email: 'j@ex.com' }])
974
+ )
975
+ );
976
+ const { result } = renderHook(() => useUsers(), { wrapper: QueryClientWrapper });
977
+ await waitFor(() => expect(result.current.isSuccess).toBe(true));
978
+ expect(result.current.data).toHaveLength(1);
979
+ });
980
+
981
+ it('returns error state when API fails', async () => {
982
+ server.use(http.get('/api/users', () => new HttpResponse(null, { status: 500 })));
983
+ const { result } = renderHook(() => useUsers(), { wrapper: QueryClientWrapper });
984
+ await waitFor(() => expect(result.current.isError).toBe(true));
985
+ });
986
+ });
987
+ ```
988
+
989
+ ## Test Helpers Setup
990
+
991
+ ```tsx
992
+ // test/helpers.tsx
993
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
994
+
995
+ export function QueryClientWrapper({ children }: { children: React.ReactNode }) {
996
+ const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } });
997
+ return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
998
+ }
999
+ ```
1000
+
1001
+ ## Common Mistakes
1002
+
1003
+ | Wrong | Right | Why |
1004
+ |-------|-------|-----|
1005
+ | `fireEvent.click(button)` | `await userEvent.click(button)` | userEvent simulates real browser events |
1006
+ | Mock `global.fetch` | Mock with MSW | MSW intercepts at network level, more realistic |
1007
+ | `expect(component).toMatchSnapshot()` | `expect(screen.getByText(...))` | Snapshots break on any change |
1008
+ | Test file in `__tests__/` folder | Co-locate with source | Easier to find, deleted with the component |
1009
+
1010
+ ---
1011
+
1012
+ *MORPH-SPEC by Polymorphism Tech*
1013
+ ```
1014
+
1015
+ **Step 9: Verify all 8 files exist**
1016
+
1017
+ Run:
1018
+ ```bash
1019
+ ls "R:/Polymorphism Tech/repos/morph-spec-framework/framework/standards/frontend/nextjs/"
1020
+ ```
1021
+ Expected: 8 new files + the existing `nextjs-patterns.md` = 9 files total.
1022
+
1023
+ **Step 10: Commit**
1024
+
1025
+ ```bash
1026
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && git add framework/standards/frontend/nextjs/ && git commit -m "feat(standards): add 8 Next.js standards files — app-router, components, data-fetching, forms, state-management, naming-conventions, project-structure, testing"
1027
+ ```
1028
+
1029
+ ---
1030
+
1031
+ ### Task 2: Create nextjs-standards.md rule file
1032
+
1033
+ **Files:**
1034
+ - Create: `framework/rules/nextjs-standards.md`
1035
+ - Modify: `framework/rules/frontend-standards.md` (add nextjs-standards reference)
1036
+
1037
+ **Step 1: Create `framework/rules/nextjs-standards.md`**
1038
+
1039
+ ```markdown
1040
+ ---
1041
+ paths:
1042
+ - "**/*.tsx"
1043
+ - "**/*.ts"
1044
+ - "!**/*.cs"
1045
+ - "!**/*.csproj"
1046
+ ---
1047
+
1048
+ # Next.js Standards
1049
+
1050
+ @.morph/framework/standards/frontend/nextjs/naming-conventions.md
1051
+ @.morph/framework/standards/frontend/nextjs/project-structure.md
1052
+ @.morph/framework/standards/frontend/nextjs/app-router.md
1053
+ @.morph/framework/standards/frontend/nextjs/components.md
1054
+ @.morph/framework/standards/frontend/nextjs/data-fetching.md
1055
+ @.morph/framework/standards/frontend/nextjs/forms.md
1056
+ @.morph/framework/standards/frontend/nextjs/state-management.md
1057
+ @.morph/framework/standards/frontend/nextjs/testing.md
1058
+ @.morph/framework/standards/frontend/nextjs/nextjs-patterns.md
1059
+ ```
1060
+
1061
+ **Step 2: Read `framework/rules/frontend-standards.md` to check current content**
1062
+
1063
+ It currently imports `nextjs-patterns.md`. Since `nextjs-standards.md` is the dedicated rule for `.tsx`/`.ts` files, the `frontend-standards.md` can remove its nextjs reference to avoid duplication (it should remain focused on Blazor + shared CSS). Update it to only cover Blazor/CSS paths.
1064
+
1065
+ Read the file first, then remove the nextjs-patterns.md line if present.
1066
+
1067
+ **Step 3: Verify**
1068
+
1069
+ ```bash
1070
+ cat "R:/Polymorphism Tech/repos/morph-spec-framework/framework/rules/nextjs-standards.md"
1071
+ ```
1072
+
1073
+ **Step 4: Commit**
1074
+
1075
+ ```bash
1076
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && git add framework/rules/nextjs-standards.md framework/rules/frontend-standards.md && git commit -m "feat(rules): add nextjs-standards.md rule file scoped to tsx/ts files"
1077
+ ```
1078
+
1079
+ ---
1080
+
1081
+ ### Task 3: Create next-component-validator (TDD)
1082
+
1083
+ **Files:**
1084
+ - Create: `src/lib/validators/nextjs/next-component-validator.js`
1085
+ - Create: `src/lib/validators/nextjs/index.js`
1086
+ - Create: `test/validators/nextjs/next-component-validator.test.js`
1087
+
1088
+ **Step 1: Write the failing tests first**
1089
+
1090
+ Create `test/validators/nextjs/next-component-validator.test.js`:
1091
+
1092
+ ```js
1093
+ import { test, describe } from 'node:test';
1094
+ import assert from 'node:assert/strict';
1095
+ import { validateNextComponent } from '../../../src/lib/validators/nextjs/next-component-validator.js';
1096
+
1097
+ describe('validateNextComponent', () => {
1098
+
1099
+ describe('use client directive checks', () => {
1100
+ test('warns when use client is present but no interactivity detected', () => {
1101
+ const content = `'use client';\n\nexport function StaticCard() {\n return <div>Hello</div>;\n}`;
1102
+ const issues = validateNextComponent(content, 'components/static-card.tsx');
1103
+ const warnings = issues.filter(i => i.type === 'warning' && i.message.includes('use client'));
1104
+ assert.ok(warnings.length > 0, 'Should warn about unnecessary use client');
1105
+ });
1106
+
1107
+ test('no warning when use client has useState', () => {
1108
+ const content = `'use client';\nimport { useState } from 'react';\nexport function Counter() {\n const [count, setCount] = useState(0);\n return <button onClick={() => setCount(c => c+1)}>{count}</button>;\n}`;
1109
+ const issues = validateNextComponent(content, 'components/counter.tsx');
1110
+ const useClientWarnings = issues.filter(i => i.message.includes('use client'));
1111
+ assert.equal(useClientWarnings.length, 0, 'useState justifies use client');
1112
+ });
1113
+
1114
+ test('no warning when use client has useEffect', () => {
1115
+ const content = `'use client';\nimport { useEffect } from 'react';\nexport function Tracker() {\n useEffect(() => {}, []);\n return <div/>;\n}`;
1116
+ const issues = validateNextComponent(content, 'components/tracker.tsx');
1117
+ const useClientWarnings = issues.filter(i => i.message.includes('use client'));
1118
+ assert.equal(useClientWarnings.length, 0, 'useEffect justifies use client');
1119
+ });
1120
+
1121
+ test('no warning when use client has onClick handler', () => {
1122
+ const content = `'use client';\nexport function Btn() {\n return <button onClick={() => alert('hi')}>Click</button>;\n}`;
1123
+ const issues = validateNextComponent(content, 'components/btn.tsx');
1124
+ const useClientWarnings = issues.filter(i => i.message.includes('use client'));
1125
+ assert.equal(useClientWarnings.length, 0, 'onClick justifies use client');
1126
+ });
1127
+ });
1128
+
1129
+ describe('missing use client checks', () => {
1130
+ test('errors when useState used without use client', () => {
1131
+ const content = `import { useState } from 'react';\nexport function Counter() {\n const [c, setC] = useState(0);\n return <div>{c}</div>;\n}`;
1132
+ const issues = validateNextComponent(content, 'components/counter.tsx');
1133
+ const errors = issues.filter(i => i.type === 'error' && i.message.includes('use client'));
1134
+ assert.ok(errors.length > 0, 'Should error when useState missing use client');
1135
+ });
1136
+
1137
+ test('errors when useEffect used without use client', () => {
1138
+ const content = `import { useEffect } from 'react';\nexport function Effect() {\n useEffect(() => {}, []);\n return <div/>;\n}`;
1139
+ const issues = validateNextComponent(content, 'components/effect.tsx');
1140
+ const errors = issues.filter(i => i.type === 'error' && i.message.includes('use client'));
1141
+ assert.ok(errors.length > 0, 'Should error when useEffect missing use client');
1142
+ });
1143
+
1144
+ test('no error for server component with no hooks', () => {
1145
+ const content = `export default async function Page() {\n return <div>Hello</div>;\n}`;
1146
+ const issues = validateNextComponent(content, 'app/page.tsx');
1147
+ assert.equal(issues.length, 0, 'Clean server component should have no issues');
1148
+ });
1149
+ });
1150
+
1151
+ describe('file naming checks', () => {
1152
+ test('warns on PascalCase component file', () => {
1153
+ const content = `export function UserCard() { return <div/>; }`;
1154
+ const issues = validateNextComponent(content, 'components/UserCard.tsx');
1155
+ const warnings = issues.filter(i => i.message.includes('kebab-case'));
1156
+ assert.ok(warnings.length > 0, 'Should warn on PascalCase file name');
1157
+ });
1158
+
1159
+ test('no warning on kebab-case file', () => {
1160
+ const content = `export function UserCard() { return <div/>; }`;
1161
+ const issues = validateNextComponent(content, 'components/user-card.tsx');
1162
+ const namingWarnings = issues.filter(i => i.message.includes('kebab-case'));
1163
+ assert.equal(namingWarnings.length, 0, 'kebab-case file name is correct');
1164
+ });
1165
+
1166
+ test('no warning on Next.js special files', () => {
1167
+ const content = `export default function Page() { return <div/>; }`;
1168
+ ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx'].forEach(file => {
1169
+ const issues = validateNextComponent(content, `app/${file}`);
1170
+ const namingWarnings = issues.filter(i => i.message.includes('kebab-case'));
1171
+ assert.equal(namingWarnings.length, 0, `${file} should not warn about kebab-case`);
1172
+ });
1173
+ });
1174
+ });
1175
+
1176
+ describe('returns empty array for non-component files', () => {
1177
+ test('no issues for .ts utility files without JSX', () => {
1178
+ const content = `export function formatDate(date: Date) { return date.toISOString(); }`;
1179
+ const issues = validateNextComponent(content, 'lib/utils.ts');
1180
+ assert.equal(issues.length, 0);
1181
+ });
1182
+ });
1183
+
1184
+ });
1185
+ ```
1186
+
1187
+ **Step 2: Run tests to confirm they fail**
1188
+
1189
+ ```bash
1190
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && node --test test/validators/nextjs/next-component-validator.test.js 2>&1 | head -20
1191
+ ```
1192
+
1193
+ Expected: FAIL with "Cannot find module" or similar.
1194
+
1195
+ **Step 3: Create `src/lib/validators/nextjs/next-component-validator.js`**
1196
+
1197
+ ```js
1198
+ /**
1199
+ * Next.js Component Validator
1200
+ *
1201
+ * Validates .tsx files for:
1202
+ * 1. Unnecessary 'use client' directives (no interactivity)
1203
+ * 2. Missing 'use client' when React hooks are used
1204
+ * 3. File naming convention (kebab-case)
1205
+ *
1206
+ * @module next-component-validator
1207
+ */
1208
+
1209
+ // ============================================
1210
+ // CONSTANTS
1211
+ // ============================================
1212
+
1213
+ /** Hooks/patterns that require 'use client' */
1214
+ const CLIENT_HOOKS = ['useState', 'useEffect', 'useRef', 'useReducer', 'useCallback', 'useMemo', 'useContext'];
1215
+
1216
+ /** Patterns that indicate interactivity requiring 'use client' */
1217
+ const INTERACTIVITY_PATTERNS = [
1218
+ /\bonClick\b/,
1219
+ /\bonChange\b/,
1220
+ /\bonSubmit\b/,
1221
+ /\bonFocus\b/,
1222
+ /\bonBlur\b/,
1223
+ /\bonKeyDown\b/,
1224
+ /\buseState\b/,
1225
+ /\buseEffect\b/,
1226
+ /\buseRef\b/,
1227
+ /\buseReducer\b/,
1228
+ /\buseCallback\b/,
1229
+ /\buseMemo\b/,
1230
+ /\buseContext\b/,
1231
+ ];
1232
+
1233
+ /** Next.js special files exempt from naming checks */
1234
+ const NEXTJS_SPECIAL_FILES = ['page.tsx', 'layout.tsx', 'loading.tsx', 'error.tsx', 'not-found.tsx', 'route.ts', 'middleware.ts'];
1235
+
1236
+ // ============================================
1237
+ // MAIN VALIDATION FUNCTION
1238
+ // ============================================
1239
+
1240
+ /**
1241
+ * @typedef {Object} ValidationIssue
1242
+ * @property {'error' | 'warning' | 'info'} type
1243
+ * @property {string} message
1244
+ * @property {string} [suggestion]
1245
+ * @property {string} file
1246
+ * @property {number} [line]
1247
+ */
1248
+
1249
+ /**
1250
+ * Validates a Next.js component file.
1251
+ *
1252
+ * @param {string} content - File content
1253
+ * @param {string} filePath - Relative file path
1254
+ * @returns {ValidationIssue[]}
1255
+ */
1256
+ export function validateNextComponent(content, filePath) {
1257
+ const issues = [];
1258
+
1259
+ // Only validate .tsx and .ts files
1260
+ if (!filePath.endsWith('.tsx') && !filePath.endsWith('.ts')) {
1261
+ return issues;
1262
+ }
1263
+
1264
+ // Skip non-component utility files (no JSX)
1265
+ const hasJsx = /<[A-Z][a-zA-Z]*|<[a-z]+[\s>]/.test(content);
1266
+ const hasHooks = CLIENT_HOOKS.some(hook => content.includes(hook));
1267
+
1268
+ if (!hasJsx && !hasHooks) {
1269
+ return issues;
1270
+ }
1271
+
1272
+ const hasUseClient = /^['"]use client['"]/.test(content.trimStart());
1273
+
1274
+ // Check 1: use client present but no interactivity
1275
+ if (hasUseClient) {
1276
+ const hasInteractivity = INTERACTIVITY_PATTERNS.some(pattern => pattern.test(content));
1277
+ if (!hasInteractivity) {
1278
+ issues.push({
1279
+ type: 'warning',
1280
+ message: "'use client' directive present but no interactivity detected (no hooks, no event handlers)",
1281
+ suggestion: "Remove 'use client' to make this a Server Component, or add interactive behavior",
1282
+ file: filePath,
1283
+ });
1284
+ }
1285
+ }
1286
+
1287
+ // Check 2: hooks used without use client
1288
+ if (!hasUseClient) {
1289
+ const usedHooks = CLIENT_HOOKS.filter(hook => new RegExp(`\\b${hook}\\b`).test(content));
1290
+ if (usedHooks.length > 0) {
1291
+ issues.push({
1292
+ type: 'error',
1293
+ message: `React hook(s) used (${usedHooks.join(', ')}) without 'use client' directive`,
1294
+ suggestion: "Add 'use client' as the first line of the file",
1295
+ file: filePath,
1296
+ line: 1,
1297
+ });
1298
+ }
1299
+ }
1300
+
1301
+ // Check 3: file naming convention (kebab-case)
1302
+ const fileName = filePath.split('/').pop() ?? '';
1303
+ const isSpecialFile = NEXTJS_SPECIAL_FILES.includes(fileName);
1304
+
1305
+ if (!isSpecialFile && fileName.endsWith('.tsx')) {
1306
+ const baseName = fileName.replace('.tsx', '');
1307
+ const hasUpperCase = /[A-Z]/.test(baseName);
1308
+ if (hasUpperCase) {
1309
+ issues.push({
1310
+ type: 'warning',
1311
+ message: `Component file '${fileName}' should use kebab-case naming`,
1312
+ suggestion: `Rename to '${toKebabCase(baseName)}.tsx'`,
1313
+ file: filePath,
1314
+ });
1315
+ }
1316
+ }
1317
+
1318
+ return issues;
1319
+ }
1320
+
1321
+ // ============================================
1322
+ // HELPERS
1323
+ // ============================================
1324
+
1325
+ function toKebabCase(str) {
1326
+ return str
1327
+ .replace(/([A-Z])/g, '-$1')
1328
+ .toLowerCase()
1329
+ .replace(/^-/, '');
1330
+ }
1331
+ ```
1332
+
1333
+ **Step 4: Create `src/lib/validators/nextjs/index.js`**
1334
+
1335
+ ```js
1336
+ export { validateNextComponent } from './next-component-validator.js';
1337
+ ```
1338
+
1339
+ **Step 5: Run tests — must all pass**
1340
+
1341
+ ```bash
1342
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && node --test test/validators/nextjs/next-component-validator.test.js 2>&1
1343
+ ```
1344
+
1345
+ Expected: All tests pass, 0 failures.
1346
+
1347
+ **Step 6: Run full test suite**
1348
+
1349
+ ```bash
1350
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && npm test 2>&1 | tail -10
1351
+ ```
1352
+
1353
+ Expected: Same pass count + new tests passing, 0 failures.
1354
+
1355
+ **Step 7: Commit**
1356
+
1357
+ ```bash
1358
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && git add src/lib/validators/nextjs/ test/validators/nextjs/ && git commit -m "feat(validators): add next-component-validator — use client, hooks, kebab-case checks"
1359
+ ```
1360
+
1361
+ ---
1362
+
1363
+ ### Task 4: Create 6 Next.js templates
1364
+
1365
+ **Files:**
1366
+ - Create: `framework/templates/frontend/nextjs/page.tsx.hbs`
1367
+ - Create: `framework/templates/frontend/nextjs/client-component.tsx.hbs`
1368
+ - Create: `framework/templates/frontend/nextjs/feature-form.tsx.hbs`
1369
+ - Create: `framework/templates/frontend/nextjs/env.mjs.hbs`
1370
+ - Create: `framework/templates/frontend/nextjs/tsconfig.json.hbs`
1371
+ - Create: `framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs`
1372
+
1373
+ **Step 1: Create `framework/templates/frontend/nextjs/page.tsx.hbs`**
1374
+
1375
+ ```handlebars
1376
+ // app/(dashboard)/{{kebabCase featureName}}/page.tsx
1377
+ // Server Component — fetches initial data, renders feature component
1378
+ import { {{pascalCase featureName}}List } from '@/features/{{kebabCase featureName}}';
1379
+
1380
+ async function get{{pascalCase featureName}}List() {
1381
+ const res = await fetch(`${process.env.API_URL}/api/{{kebabCase featureName}}`, {
1382
+ next: { revalidate: 30 },
1383
+ headers: { 'Content-Type': 'application/json' },
1384
+ });
1385
+ if (!res.ok) throw new Error('Failed to fetch {{featureName}}');
1386
+ return res.json();
1387
+ }
1388
+
1389
+ export default async function {{pascalCase featureName}}Page() {
1390
+ const data = await get{{pascalCase featureName}}List();
1391
+ return (
1392
+ <div className="container py-6">
1393
+ <h1 className="text-2xl font-bold mb-6">{{pascalCase featureName}}</h1>
1394
+ <{{pascalCase featureName}}List initialData={data} />
1395
+ </div>
1396
+ );
1397
+ }
1398
+ ```
1399
+
1400
+ **Step 2: Create `framework/templates/frontend/nextjs/client-component.tsx.hbs`**
1401
+
1402
+ ```handlebars
1403
+ // features/{{kebabCase featureName}}/components/{{kebabCase featureName}}-list.tsx
1404
+ 'use client';
1405
+
1406
+ import { use{{pascalCase featureName}}s } from '@/features/{{kebabCase featureName}}/hooks/use-{{kebabCase featureName}}s';
1407
+ import { DataTable } from '@/components/data-table';
1408
+ import type { {{pascalCase featureName}} } from '@/features/{{kebabCase featureName}}/types/{{kebabCase featureName}}.types';
1409
+ import type { ColumnDef } from '@tanstack/react-table';
1410
+
1411
+ const columns: ColumnDef<{{pascalCase featureName}}>[] = [
1412
+ { accessorKey: 'id', header: 'ID' },
1413
+ { accessorKey: 'name', header: 'Name' },
1414
+ ];
1415
+
1416
+ interface {{pascalCase featureName}}ListProps {
1417
+ initialData?: {{pascalCase featureName}}[];
1418
+ }
1419
+
1420
+ export function {{pascalCase featureName}}List({ initialData }: {{pascalCase featureName}}ListProps) {
1421
+ const { data = initialData ?? [], isLoading } = use{{pascalCase featureName}}s();
1422
+
1423
+ if (isLoading) {
1424
+ return <div className="flex items-center justify-center h-32">Loading...</div>;
1425
+ }
1426
+
1427
+ return <DataTable columns={columns} data={data} />;
1428
+ }
1429
+ ```
1430
+
1431
+ **Step 3: Create `framework/templates/frontend/nextjs/feature-form.tsx.hbs`**
1432
+
1433
+ ```handlebars
1434
+ // features/{{kebabCase featureName}}/components/create-{{kebabCase featureName}}-form.tsx
1435
+ 'use client';
1436
+
1437
+ import { useForm } from 'react-hook-form';
1438
+ import { zodResolver } from '@hookform/resolvers/zod';
1439
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
1440
+ import { Input } from '@/components/ui/input';
1441
+ import { Button } from '@/components/ui/button';
1442
+ import { useCreate{{pascalCase featureName}} } from '@/features/{{kebabCase featureName}}/hooks/use-create-{{kebabCase featureName}}';
1443
+ import { create{{pascalCase featureName}}Schema, type Create{{pascalCase featureName}}Input } from '@/features/{{kebabCase featureName}}/types/{{kebabCase featureName}}.schemas';
1444
+
1445
+ interface Create{{pascalCase featureName}}FormProps {
1446
+ onSuccess?: () => void;
1447
+ }
1448
+
1449
+ export function Create{{pascalCase featureName}}Form({ onSuccess }: Create{{pascalCase featureName}}FormProps) {
1450
+ const form = useForm<Create{{pascalCase featureName}}Input>({
1451
+ resolver: zodResolver(create{{pascalCase featureName}}Schema),
1452
+ defaultValues: { name: '' },
1453
+ });
1454
+
1455
+ const { mutate: create{{pascalCase featureName}}, isPending } = useCreate{{pascalCase featureName}}();
1456
+
1457
+ function onSubmit(values: Create{{pascalCase featureName}}Input) {
1458
+ create{{pascalCase featureName}}(values, {
1459
+ onSuccess: () => { form.reset(); onSuccess?.(); },
1460
+ onError: (error) => { form.setError('root', { message: error.message }); },
1461
+ });
1462
+ }
1463
+
1464
+ return (
1465
+ <Form {...form}>
1466
+ <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
1467
+ <FormField
1468
+ control={form.control}
1469
+ name="name"
1470
+ render={({ field }) => (
1471
+ <FormItem>
1472
+ <FormLabel>Name</FormLabel>
1473
+ <FormControl>
1474
+ <Input placeholder="Enter name" {...field} />
1475
+ </FormControl>
1476
+ <FormMessage />
1477
+ </FormItem>
1478
+ )}
1479
+ />
1480
+ {form.formState.errors.root && (
1481
+ <p className="text-sm text-destructive">{form.formState.errors.root.message}</p>
1482
+ )}
1483
+ <Button type="submit" disabled={isPending}>
1484
+ {isPending ? 'Creating...' : 'Create {{pascalCase featureName}}'}
1485
+ </Button>
1486
+ </form>
1487
+ </Form>
1488
+ );
1489
+ }
1490
+ ```
1491
+
1492
+ **Step 4: Create `framework/templates/frontend/nextjs/env.mjs.hbs`**
1493
+
1494
+ ```handlebars
1495
+ // env.mjs — Zod-validated environment variables
1496
+ // Crashes at startup if required vars are missing — no silent undefined
1497
+ import { z } from 'zod';
1498
+
1499
+ const serverSchema = z.object({
1500
+ API_URL: z.string().url('API_URL must be a valid URL'),
1501
+ NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
1502
+ });
1503
+
1504
+ const clientSchema = z.object({
1505
+ NEXT_PUBLIC_APP_URL: z.string().url().optional(),
1506
+ });
1507
+
1508
+ // Validate server-side env (only available on server)
1509
+ const serverEnv = serverSchema.safeParse(process.env);
1510
+ if (!serverEnv.success) {
1511
+ console.error('❌ Invalid server environment variables:');
1512
+ console.error(serverEnv.error.flatten().fieldErrors);
1513
+ throw new Error('Invalid environment variables');
1514
+ }
1515
+
1516
+ // Validate client-side env
1517
+ const clientEnv = clientSchema.safeParse({
1518
+ NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
1519
+ });
1520
+ if (!clientEnv.success) {
1521
+ console.error('❌ Invalid client environment variables:');
1522
+ console.error(clientEnv.error.flatten().fieldErrors);
1523
+ throw new Error('Invalid client environment variables');
1524
+ }
1525
+
1526
+ export const env = { ...serverEnv.data, ...clientEnv.data };
1527
+ ```
1528
+
1529
+ **Step 5: Create `framework/templates/frontend/nextjs/tsconfig.json.hbs`**
1530
+
1531
+ ```handlebars
1532
+ {
1533
+ "compilerOptions": {
1534
+ "target": "ES2022",
1535
+ "lib": ["dom", "dom.iterable", "esnext"],
1536
+ "allowJs": false,
1537
+ "skipLibCheck": true,
1538
+ "strict": true,
1539
+ "noUncheckedIndexedAccess": true,
1540
+ "noImplicitOverride": true,
1541
+ "forceConsistentCasingInFileNames": true,
1542
+ "noEmit": true,
1543
+ "esModuleInterop": true,
1544
+ "module": "esnext",
1545
+ "moduleResolution": "bundler",
1546
+ "resolveJsonModule": true,
1547
+ "isolatedModules": true,
1548
+ "jsx": "preserve",
1549
+ "incremental": true,
1550
+ "plugins": [{ "name": "next" }],
1551
+ "paths": {
1552
+ "@/*": ["./src/*"]
1553
+ }
1554
+ },
1555
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
1556
+ "exclude": ["node_modules"]
1557
+ }
1558
+ ```
1559
+
1560
+ **Step 6: Create `framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs`**
1561
+
1562
+ ```handlebars
1563
+ # Multi-stage Next.js production build for EasyPanel
1564
+ # Stage 1: Dependencies
1565
+ FROM node:20-alpine AS deps
1566
+ RUN apk add --no-cache libc6-compat
1567
+ WORKDIR /app
1568
+
1569
+ COPY package.json package-lock.json* ./
1570
+ RUN npm ci --only=production && npm cache clean --force
1571
+
1572
+ # Stage 2: Build
1573
+ FROM node:20-alpine AS builder
1574
+ WORKDIR /app
1575
+
1576
+ COPY --from=deps /app/node_modules ./node_modules
1577
+ COPY . .
1578
+
1579
+ # Build args for NEXT_PUBLIC_ variables (must be available at build time)
1580
+ ARG NEXT_PUBLIC_APP_URL
1581
+ ENV NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL
1582
+
1583
+ RUN npm run build
1584
+
1585
+ # Stage 3: Production runner
1586
+ FROM node:20-alpine AS runner
1587
+ WORKDIR /app
1588
+
1589
+ ENV NODE_ENV=production
1590
+ ENV NEXT_TELEMETRY_DISABLED=1
1591
+
1592
+ RUN addgroup --system --gid 1001 nodejs
1593
+ RUN adduser --system --uid 1001 nextjs
1594
+
1595
+ COPY --from=builder /app/public ./public
1596
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
1597
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
1598
+
1599
+ USER nextjs
1600
+
1601
+ EXPOSE 3000
1602
+ ENV PORT=3000
1603
+ ENV HOSTNAME="0.0.0.0"
1604
+
1605
+ CMD ["node", "server.js"]
1606
+ ```
1607
+
1608
+ **Step 7: Verify all 6 template files exist**
1609
+
1610
+ ```bash
1611
+ ls "R:/Polymorphism Tech/repos/morph-spec-framework/framework/templates/frontend/nextjs/"
1612
+ ```
1613
+
1614
+ Expected: 6 files.
1615
+
1616
+ **Step 8: Commit**
1617
+
1618
+ ```bash
1619
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && git add framework/templates/frontend/nextjs/ && git commit -m "feat(templates): add 6 Next.js templates — page, client-component, feature-form, env.mjs, tsconfig, Dockerfile"
1620
+ ```
1621
+
1622
+ ---
1623
+
1624
+ ### Task 5: Update nextjs-expert agent
1625
+
1626
+ **Files:**
1627
+ - Modify: `framework/agents/frontend/nextjs-expert.md`
1628
+ - Modify: `framework/agents.json`
1629
+
1630
+ **Step 1: Read the current `framework/agents/frontend/nextjs-expert.md`**
1631
+
1632
+ Read the file fully to understand current content.
1633
+
1634
+ **Step 2: Rewrite `framework/agents/frontend/nextjs-expert.md`**
1635
+
1636
+ Replace the full content with:
1637
+
1638
+ ```markdown
1639
+ ---
1640
+ name: nextjs-expert
1641
+ description: Next.js specialist for App Router, shadcn/ui, TanStack Query, react-hook-form/Zod, and EasyPanel deployment. Use when building Next.js pages or components, implementing forms, setting up data fetching from a .NET API, or configuring TypeScript/Docker for Next.js.
1642
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep
1643
+ ---
1644
+
1645
+ # Next.js Expert
1646
+
1647
+ Specialist for the locked stack: **Next.js App Router + shadcn/ui + TanStack Query + react-hook-form/Zod + EasyPanel/Docker**.
1648
+
1649
+ ## Stack
1650
+
1651
+ | Layer | Technology |
1652
+ |-------|-----------|
1653
+ | Framework | Next.js App Router (TypeScript strict) |
1654
+ | UI Components | shadcn/ui — `npx shadcn@latest add` |
1655
+ | Styling | Tailwind CSS |
1656
+ | Server state | TanStack Query v5 |
1657
+ | Forms | react-hook-form + Zod + zodResolver |
1658
+ | Deployment | Docker multi-stage → EasyPanel (VPS) |
1659
+
1660
+ ## Critical Standards (Read FIRST)
1661
+
1662
+ | Standard | What |
1663
+ |----------|------|
1664
+ | `frontend/nextjs/naming-conventions.md` | **kebab-case files, PascalCase exports, use- hooks** |
1665
+ | `frontend/nextjs/project-structure.md` | **Feature-based folders, component tiers** |
1666
+ | `frontend/nextjs/app-router.md` | **Server vs Client components decision tree** |
1667
+ | `frontend/nextjs/components.md` | **Three-tier hierarchy, never edit components/ui/** |
1668
+ | `frontend/nextjs/data-fetching.md` | **TanStack Query patterns, query key factories** |
1669
+ | `frontend/nextjs/forms.md` | **react-hook-form + Zod + shadcn Form** |
1670
+ | `frontend/nextjs/state-management.md` | **No Zustand by default** |
1671
+ | `frontend/nextjs/testing.md` | **Jest + Testing Library + MSW** |
1672
+
1673
+ ## Quick Checklist
1674
+
1675
+ - [ ] File names are kebab-case (`user-card.tsx` not `UserCard.tsx`)
1676
+ - [ ] `'use client'` only on components with hooks or event handlers
1677
+ - [ ] No `useEffect` for data fetching — use Server Components or TanStack Query
1678
+ - [ ] Zod schema defined before TypeScript type (`type X = z.infer<typeof xSchema>`)
1679
+ - [ ] Query keys use factory pattern (`userKeys.lists()` not `['users']`)
1680
+ - [ ] Forms use `zodResolver` + shadcn `<Form>` + `<FormMessage>`
1681
+ - [ ] `components/ui/` files never edited directly
1682
+ - [ ] New features go in `features/{name}/components|hooks|types/`
1683
+
1684
+ ## Naming Conventions (Non-Negotiable)
1685
+
1686
+ ```
1687
+ Component file: user-card.tsx → export function UserCard()
1688
+ Hook file: use-create-user.ts → export function useCreateUser()
1689
+ Schema file: user.schemas.ts → export const createUserSchema = z.object(...)
1690
+ Type file: user.types.ts → export type User = z.infer<typeof userSchema>
1691
+ Feature folder: features/user-mgmt/
1692
+ ```
1693
+
1694
+ ## Component Tier Rules
1695
+
1696
+ ```
1697
+ components/ui/ ← shadcn primitives. NEVER edit.
1698
+ components/ ← Composed shared components. No API calls. No feature imports.
1699
+ features/*/components ← Feature-scoped. May use components/ but not other features/.
1700
+ ```
1701
+
1702
+ ## Data Fetching Patterns
1703
+
1704
+ ```tsx
1705
+ // Server Component (initial load)
1706
+ export default async function Page() {
1707
+ const data = await fetch(`${process.env.API_URL}/api/users`).then(r => r.json());
1708
+ return <UserList initialData={data} />;
1709
+ }
1710
+
1711
+ // Client Component (mutations/interactive)
1712
+ 'use client';
1713
+ const { data } = useUsers(); // TanStack Query
1714
+ const { mutate } = useCreateUser(); // useMutation
1715
+ ```
1716
+
1717
+ ## Form Pattern
1718
+
1719
+ ```tsx
1720
+ // Schema first, type derived
1721
+ const schema = z.object({ name: z.string().min(2) });
1722
+ type Input = z.infer<typeof schema>;
1723
+
1724
+ // Form wired to schema
1725
+ const form = useForm<Input>({ resolver: zodResolver(schema) });
1726
+ ```
1727
+
1728
+ ## EasyPanel Deployment
1729
+
1730
+ - Use `framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs` as base
1731
+ - Requires `output: 'standalone'` in `next.config.js`
1732
+ - `NEXT_PUBLIC_*` vars must be available at **build time** (Docker build args)
1733
+ - Server-only vars (API_URL, secrets) go in EasyPanel environment at **runtime**
1734
+
1735
+ ## Project Structure
1736
+
1737
+ ```
1738
+ src/
1739
+ ├── app/ # Routes only
1740
+ ├── features/{name}/
1741
+ │ ├── components/ # Feature UI
1742
+ │ ├── hooks/ # TanStack Query hooks
1743
+ │ └── types/ # Zod schemas + inferred types
1744
+ ├── components/
1745
+ │ ├── ui/ # shadcn — do not edit
1746
+ │ └── {shared}.tsx # Composed shared components
1747
+ ├── hooks/ # Utility hooks (no API calls)
1748
+ ├── lib/
1749
+ │ ├── api-client.ts
1750
+ │ └── query-client.tsx
1751
+ └── env.mjs # Zod env validation
1752
+ ```
1753
+
1754
+ ---
1755
+
1756
+ *MORPH-SPEC by Polymorphism Tech*
1757
+ ```
1758
+
1759
+ **Step 3: Update `framework/agents.json` — nextjs-expert standards array**
1760
+
1761
+ Find the `nextjs-expert` entry and update its `standards` array to include all 8 new standards files:
1762
+
1763
+ ```json
1764
+ "standards": [
1765
+ "core/coding.md",
1766
+ "core/architecture.md",
1767
+ "frontend/nextjs/naming-conventions.md",
1768
+ "frontend/nextjs/project-structure.md",
1769
+ "frontend/nextjs/app-router.md",
1770
+ "frontend/nextjs/components.md",
1771
+ "frontend/nextjs/data-fetching.md",
1772
+ "frontend/nextjs/forms.md",
1773
+ "frontend/nextjs/state-management.md",
1774
+ "frontend/nextjs/testing.md",
1775
+ "frontend/nextjs/nextjs-patterns.md"
1776
+ ]
1777
+ ```
1778
+
1779
+ Also update `nextjs-expert.keywords` to include the new stack terms:
1780
+ ```json
1781
+ "keywords": ["next.js", "nextjs", "react", "tsx", "shadcn", "tailwind", "tanstack", "react-query", "zod", "react-hook-form", "app router", "server component", "client component"]
1782
+ ```
1783
+
1784
+ **Step 4: Validate JSON**
1785
+
1786
+ ```bash
1787
+ node -e "JSON.parse(require('fs').readFileSync('framework/agents.json','utf8')); console.log('OK')"
1788
+ ```
1789
+
1790
+ **Step 5: Commit**
1791
+
1792
+ ```bash
1793
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && git add framework/agents/frontend/nextjs-expert.md framework/agents.json && git commit -m "feat(agents): enhance nextjs-expert with full stack standards, naming rules, EasyPanel deployment"
1794
+ ```
1795
+
1796
+ ---
1797
+
1798
+ ### Task 6: Regenerate STANDARDS.json and final verification
1799
+
1800
+ **Files:**
1801
+ - Modify: `framework/standards/STANDARDS.json` (regenerated)
1802
+
1803
+ **Step 1: Run the standards registry generator**
1804
+
1805
+ ```bash
1806
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && node scripts/generate-standards-registry.js
1807
+ ```
1808
+
1809
+ **Step 2: Verify new entries appear in STANDARDS.json**
1810
+
1811
+ ```bash
1812
+ node -e "
1813
+ const s = JSON.parse(require('fs').readFileSync('framework/standards/STANDARDS.json','utf8'));
1814
+ const nextjs = s.standards.filter(x => x.path.includes('nextjs'));
1815
+ console.log('Next.js standards in registry:', nextjs.length, '(expected 9)');
1816
+ nextjs.forEach(x => console.log(' -', x.id));
1817
+ "
1818
+ ```
1819
+
1820
+ Expected: 9 Next.js standards entries (8 new + 1 existing nextjs-patterns).
1821
+
1822
+ **Step 3: Run full test suite**
1823
+
1824
+ ```bash
1825
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && npm test 2>&1 | tail -10
1826
+ ```
1827
+
1828
+ Expected: All previously passing tests still pass, new validator tests pass, 0 failures.
1829
+
1830
+ **Step 4: Commit**
1831
+
1832
+ ```bash
1833
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && git add framework/standards/STANDARDS.json && git commit -m "feat(standards): regenerate STANDARDS.json — 9 Next.js entries added (82 total)"
1834
+ ```
1835
+
1836
+ **Step 5: Final audit**
1837
+
1838
+ ```bash
1839
+ cd "R:/Polymorphism Tech/repos/morph-spec-framework" && node -e "
1840
+ const s = JSON.parse(require('fs').readFileSync('framework/standards/STANDARDS.json','utf8'));
1841
+ console.log('Total standards:', s.standards.length);
1842
+ const byCategory = {};
1843
+ s.standards.forEach(x => { byCategory[x.category] = (byCategory[x.category] || 0) + 1; });
1844
+ Object.entries(byCategory).sort((a,b) => b[1]-a[1]).forEach(([k,v]) => console.log(' ', k + ':', v));
1845
+ "
1846
+ ```