@polymorphism-tech/morph-spec 4.6.0 → 4.7.1

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 (239) hide show
  1. package/README.md +414 -700
  2. package/docs/ARCHITECTURE.md +331 -0
  3. package/docs/CHEATSHEET.md +221 -0
  4. package/docs/COMMAND-FLOWS.md +368 -0
  5. package/docs/QUICKSTART.md +212 -0
  6. package/docs/examples/order-management/contracts.cs +84 -0
  7. package/docs/examples/order-management/proposal.md +24 -0
  8. package/docs/examples/order-management/spec.md +162 -0
  9. package/docs/plans/2026-02-23-ddd-architecture-refactor.md +1153 -0
  10. package/docs/plans/2026-02-23-ddd-nextsteps.md +682 -0
  11. package/docs/plans/2026-02-23-infra-architect-refactor.md +437 -0
  12. package/docs/plans/2026-02-23-nextjs-code-review-design.md +156 -0
  13. package/docs/plans/2026-02-23-nextjs-code-review-impl.md +1254 -0
  14. package/docs/plans/2026-02-23-nextjs-standards-design.md +149 -0
  15. package/docs/plans/2026-02-23-nextjs-standards-impl.md +1846 -0
  16. package/framework/agents/README.md +14 -14
  17. package/framework/agents/architecture/standards-architect.md +159 -159
  18. package/framework/agents/frontend/nextjs-expert.md +87 -127
  19. package/framework/agents/infrastructure/azure-architect.md +147 -147
  20. package/framework/agents/infrastructure/infra-architect.md +45 -0
  21. package/framework/agents.json +1145 -278
  22. package/framework/rules/frontend-standards.md +0 -3
  23. package/framework/rules/nextjs-standards.md +17 -0
  24. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +147 -0
  25. package/framework/skills/level-0-meta/code-review-nextjs/references/review-example-nextjs.md +254 -0
  26. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +3 -3
  27. package/framework/skills/level-1-workflows/phase-design/SKILL.md +45 -9
  28. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +38 -0
  29. package/framework/standards/STANDARDS.json +121 -0
  30. package/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
  31. package/framework/standards/architecture/ddd/complexity-levels.md +108 -0
  32. package/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
  33. package/framework/standards/frontend/nextjs/app-router.md +123 -0
  34. package/framework/standards/frontend/nextjs/components.md +132 -0
  35. package/framework/standards/frontend/nextjs/data-fetching.md +126 -0
  36. package/framework/standards/frontend/nextjs/forms.md +128 -0
  37. package/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
  38. package/framework/standards/frontend/nextjs/project-structure.md +102 -0
  39. package/framework/standards/frontend/nextjs/state-management.md +72 -0
  40. package/framework/standards/frontend/nextjs/testing.md +111 -0
  41. package/framework/templates/REGISTRY.json +538 -142
  42. package/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
  43. package/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
  44. package/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
  45. package/framework/templates/docs/spec.md +49 -0
  46. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
  47. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
  48. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
  49. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
  50. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
  51. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
  52. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
  53. package/framework/templates/project-structure/dotnet-ddd.md +70 -0
  54. package/framework/workflows/docs/enforcement-pipeline.md +2 -1
  55. package/package.json +1 -1
  56. package/scripts/scan-nextjs.mjs +169 -0
  57. package/src/commands/project/doctor.js +52 -1
  58. package/src/commands/project/init.js +15 -1
  59. package/src/commands/project/update.js +6 -1
  60. package/src/lib/standards/standards-context-injector.js +5 -0
  61. package/src/lib/validators/nextjs/index.js +6 -0
  62. package/src/lib/validators/nextjs/next-component-validator.js +181 -0
  63. package/src/lib/validators/validation-runner.js +5 -0
  64. package/src/utils/agents-installer.js +14 -2
  65. package/.morph/.morphversion +0 -5
  66. package/.morph/analytics/threads-log.jsonl +0 -6
  67. package/.morph/config/config.json +0 -8
  68. package/.morph/framework/agents.json +0 -948
  69. package/.morph/framework/standards/STANDARDS.json +0 -812
  70. package/.morph/framework/standards/ai-agents/blazor-ui.md +0 -364
  71. package/.morph/framework/standards/ai-agents/production.md +0 -415
  72. package/.morph/framework/standards/ai-agents/setup.md +0 -418
  73. package/.morph/framework/standards/ai-agents/team-orchestration.md +0 -479
  74. package/.morph/framework/standards/ai-agents/workflows.md +0 -354
  75. package/.morph/framework/standards/architecture/ddd/aggregates.md +0 -120
  76. package/.morph/framework/standards/architecture/ddd/entities.md +0 -99
  77. package/.morph/framework/standards/architecture/ddd/value-objects.md +0 -124
  78. package/.morph/framework/standards/backend/api/minimal-api.md +0 -494
  79. package/.morph/framework/standards/backend/api/rest.md +0 -492
  80. package/.morph/framework/standards/backend/api/validation.md +0 -88
  81. package/.morph/framework/standards/backend/authentication/passkeys.md +0 -428
  82. package/.morph/framework/standards/backend/database/ef-core.md +0 -199
  83. package/.morph/framework/standards/backend/database/migrations.md +0 -393
  84. package/.morph/framework/standards/backend/database/postgresql/database.md +0 -352
  85. package/.morph/framework/standards/backend/database/repository-patterns.md +0 -528
  86. package/.morph/framework/standards/backend/database/vector-search-rag.md +0 -541
  87. package/.morph/framework/standards/backend/dotnet/async.md +0 -366
  88. package/.morph/framework/standards/backend/dotnet/core.md +0 -117
  89. package/.morph/framework/standards/backend/dotnet/di.md +0 -439
  90. package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +0 -92
  91. package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +0 -216
  92. package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +0 -290
  93. package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +0 -350
  94. package/.morph/framework/standards/backend/integrations/resend/resend-email.md +0 -385
  95. package/.morph/framework/standards/context/analytics.md +0 -96
  96. package/.morph/framework/standards/context/bundles.md +0 -110
  97. package/.morph/framework/standards/context/priming.md +0 -78
  98. package/.morph/framework/standards/core/architecture.md +0 -185
  99. package/.morph/framework/standards/core/coding.md +0 -214
  100. package/.morph/framework/standards/core/git-branching-strategy.md +0 -403
  101. package/.morph/framework/standards/core/git.md +0 -185
  102. package/.morph/framework/standards/core/testing.md +0 -295
  103. package/.morph/framework/standards/data/nosql/blob-storage.md +0 -102
  104. package/.morph/framework/standards/data/nosql/cache/redis.md +0 -97
  105. package/.morph/framework/standards/data/nosql/cosmos-db.md +0 -118
  106. package/.morph/framework/standards/data/vector-search/azure-ai-search.md +0 -121
  107. package/.morph/framework/standards/data/vector-search/rag-chunking.md +0 -104
  108. package/.morph/framework/standards/frontend/blazor/design-checklist.md +0 -222
  109. package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +0 -595
  110. package/.morph/framework/standards/frontend/blazor/fluent-ui.md +0 -137
  111. package/.morph/framework/standards/frontend/blazor/html-conversion.md +0 -184
  112. package/.morph/framework/standards/frontend/blazor/lifecycle.md +0 -195
  113. package/.morph/framework/standards/frontend/blazor/pitfalls.md +0 -198
  114. package/.morph/framework/standards/frontend/blazor/state.md +0 -191
  115. package/.morph/framework/standards/frontend/design-system/animations.md +0 -151
  116. package/.morph/framework/standards/frontend/design-system/naming.md +0 -64
  117. package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +0 -215
  118. package/.morph/framework/standards/infrastructure/azure/azure.md +0 -624
  119. package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +0 -422
  120. package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +0 -516
  121. package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +0 -520
  122. package/.morph/framework/standards/infrastructure/azure/services/functions.md +0 -486
  123. package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +0 -459
  124. package/.morph/framework/standards/infrastructure/azure/services/storage.md +0 -407
  125. package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +0 -196
  126. package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +0 -252
  127. package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +0 -176
  128. package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +0 -169
  129. package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +0 -184
  130. package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +0 -153
  131. package/.morph/framework/standards/integration/api/graphql.md +0 -91
  132. package/.morph/framework/standards/integration/api/grpc.md +0 -114
  133. package/.morph/framework/standards/integration/api/rest-design.md +0 -95
  134. package/.morph/framework/standards/integration/event-driven/cqrs.md +0 -101
  135. package/.morph/framework/standards/integration/event-driven/event-sourcing.md +0 -124
  136. package/.morph/framework/standards/integration/event-driven/service-bus.md +0 -95
  137. package/.morph/framework/standards/integration/mcp/mcp-tools.md +0 -384
  138. package/.morph/framework/standards/observability/logging.md +0 -131
  139. package/.morph/framework/standards/observability/metrics.md +0 -121
  140. package/.morph/framework/standards/observability/monitoring.md +0 -114
  141. package/.morph/framework/standards/observability/tracing.md +0 -132
  142. package/.morph/framework/standards/workflows/parallel-execution.md +0 -112
  143. package/.morph/framework/standards/workflows/thread-management.md +0 -113
  144. package/.morph/framework/templates/.idea/morph-templates.xml +0 -92
  145. package/.morph/framework/templates/.vscode/morph-templates.code-snippets +0 -186
  146. package/.morph/framework/templates/IDE-SNIPPETS.md +0 -266
  147. package/.morph/framework/templates/README.md +0 -814
  148. package/.morph/framework/templates/REGISTRY.json +0 -1492
  149. package/.morph/framework/templates/code/dotnet/backend/repository.cs +0 -141
  150. package/.morph/framework/templates/code/dotnet/backend/service.cs +0 -139
  151. package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +0 -74
  152. package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +0 -25
  153. package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +0 -74
  154. package/.morph/framework/templates/code/dotnet/contracts/README.md +0 -74
  155. package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +0 -173
  156. package/.morph/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
  157. package/.morph/framework/templates/code/dotnet/contracts/contracts.cs.hbs +0 -172
  158. package/.morph/framework/templates/code/dotnet/database/migration.cs +0 -83
  159. package/.morph/framework/templates/code/dotnet/frontend/component.razor +0 -239
  160. package/.morph/framework/templates/code/dotnet/jobs/agent.cs +0 -163
  161. package/.morph/framework/templates/code/dotnet/jobs/job.cs +0 -171
  162. package/.morph/framework/templates/code/dotnet/test.cs +0 -239
  163. package/.morph/framework/templates/code/sql/rls-policy.sql +0 -57
  164. package/.morph/framework/templates/code/sql/supabase-migration.sql +0 -100
  165. package/.morph/framework/templates/code/sql/supabase-migration.template.sql +0 -113
  166. package/.morph/framework/templates/code/typescript/contracts.ts +0 -168
  167. package/.morph/framework/templates/context/CONTEXT-FEATURE.md +0 -276
  168. package/.morph/framework/templates/context/CONTEXT.md +0 -181
  169. package/.morph/framework/templates/docs/clarifications.md +0 -253
  170. package/.morph/framework/templates/docs/onboarding.md +0 -123
  171. package/.morph/framework/templates/docs/proposal.md +0 -182
  172. package/.morph/framework/templates/docs/schema-analysis.md +0 -119
  173. package/.morph/framework/templates/docs/spec.md +0 -149
  174. package/.morph/framework/templates/docs/ui-components.md +0 -124
  175. package/.morph/framework/templates/docs/ui-design-system.md +0 -76
  176. package/.morph/framework/templates/docs/ui-flows.md +0 -167
  177. package/.morph/framework/templates/docs/ui-mockups.md +0 -98
  178. package/.morph/framework/templates/docs/user-stories.md +0 -34
  179. package/.morph/framework/templates/examples/design-system-examples.md +0 -357
  180. package/.morph/framework/templates/examples/spec-examples.md +0 -90
  181. package/.morph/framework/templates/feature/decisions.md +0 -187
  182. package/.morph/framework/templates/feature/recap.md +0 -146
  183. package/.morph/framework/templates/feature/tasks.md +0 -199
  184. package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +0 -82
  185. package/.morph/framework/templates/infrastructure/azure/README.md +0 -286
  186. package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +0 -63
  187. package/.morph/framework/templates/infrastructure/azure/app-service.bicep +0 -164
  188. package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +0 -49
  189. package/.morph/framework/templates/infrastructure/azure/container-app.bicep +0 -156
  190. package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +0 -426
  191. package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +0 -229
  192. package/.morph/framework/templates/infrastructure/azure/deploy.sh +0 -208
  193. package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +0 -91
  194. package/.morph/framework/templates/infrastructure/azure/main.bicep +0 -189
  195. package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +0 -29
  196. package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +0 -29
  197. package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +0 -29
  198. package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +0 -103
  199. package/.morph/framework/templates/infrastructure/azure/storage.bicep +0 -106
  200. package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +0 -58
  201. package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +0 -67
  202. package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +0 -38
  203. package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +0 -48
  204. package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +0 -54
  205. package/.morph/framework/templates/infrastructure/github/README.md +0 -593
  206. package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +0 -22
  207. package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +0 -45
  208. package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +0 -27
  209. package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +0 -61
  210. package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +0 -31
  211. package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +0 -59
  212. package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +0 -39
  213. package/.morph/framework/templates/integrations/asaas-client.cs +0 -387
  214. package/.morph/framework/templates/integrations/asaas-webhook.cs +0 -351
  215. package/.morph/framework/templates/integrations/azure-identity-config.cs +0 -288
  216. package/.morph/framework/templates/integrations/clerk-config.cs +0 -258
  217. package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +0 -76
  218. package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +0 -100
  219. package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  220. package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  221. package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  222. package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  223. package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  224. package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +0 -90
  225. package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +0 -126
  226. package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +0 -43
  227. package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +0 -107
  228. package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +0 -95
  229. package/.morph/framework/templates/saas/subscription.cs +0 -347
  230. package/.morph/framework/templates/saas/tenant.cs +0 -338
  231. package/.morph/framework/templates/state.template.json +0 -17
  232. package/.morph/framework/templates/ui/FluentDesignTheme.cs +0 -149
  233. package/.morph/framework/templates/ui/MudTheme.cs +0 -281
  234. package/.morph/framework/templates/ui/design-system.css +0 -226
  235. package/.morph/logs/tool-failures.log +0 -7
  236. package/.morph/memory/pre-compact-2026-02-23T15-43-03-521Z.json +0 -16
  237. package/.morph/state.json +0 -48
  238. package/framework/templates/code/dotnet/contracts/contracts.cs +0 -217
  239. 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
+ ```