@polymorphism-tech/morph-spec 7.0.0 → 7.1.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 (79) hide show
  1. package/README.md +367 -367
  2. package/framework/agents.json +132 -127
  3. package/framework/rubrics/backend.json +41 -36
  4. package/framework/rules/ai-agents-standards.md +45 -15
  5. package/framework/rules/neon-standards.md +25 -26
  6. package/framework/standards/STANDARDS.json +1729 -1133
  7. package/framework/standards/ai-agents/agent-session.md +160 -0
  8. package/framework/standards/ai-agents/batching.md +193 -0
  9. package/framework/standards/ai-agents/context-providers.md +188 -0
  10. package/framework/standards/ai-agents/mcp-server.md +250 -0
  11. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  12. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  13. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  14. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  15. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  16. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  17. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  18. package/framework/standards/ai-agents/production.md +114 -258
  19. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  20. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  21. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  22. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  23. package/framework/standards/ai-agents/setup.md +142 -316
  24. package/framework/standards/ai-agents/structured-output.md +249 -0
  25. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  26. package/framework/standards/ai-agents/workflows.md +114 -284
  27. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  28. package/framework/standards/backend/api/validation.md +92 -88
  29. package/framework/standards/backend/database/postgresql/database.md +336 -332
  30. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  31. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  32. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  33. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  34. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  35. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  36. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  37. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  38. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  39. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  40. package/framework/standards/data/nosql/cache/redis.md +106 -97
  41. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  42. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  43. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  44. package/framework/standards/frontend/design-system/animations.md +164 -160
  45. package/framework/standards/frontend/design-system/naming.md +68 -64
  46. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  47. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  48. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  49. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  50. package/framework/standards/frontend/nextjs/components.md +136 -132
  51. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  52. package/framework/standards/frontend/nextjs/forms.md +134 -128
  53. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  54. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  55. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  56. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  57. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  58. package/framework/standards/frontend/nextjs/testing.md +115 -111
  59. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  60. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  61. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  62. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  63. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  64. package/framework/templates/REGISTRY.json +1745 -1239
  65. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  66. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  67. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  68. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  69. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  70. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  71. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  72. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  73. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  74. package/package.json +89 -83
  75. package/src/core/templates/template-registry.js +289 -256
  76. package/src/lib/generators/context-generator.js +516 -518
  77. package/src/lib/generators/scaffold-generator.js +23 -26
  78. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  79. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -1,67 +1,71 @@
1
- # Next.js Naming Conventions Standard
2
-
3
- > **Scope:** frontend/nextjs/naming-conventions
4
- > **Layer:** 1 (always)
5
- > **Keywords:** naming, conventions, file names, component names, hooks, typescript
6
- > **Load When:** creating any new file in a Next.js project
7
-
8
- Consistent naming prevents filesystem bugs (Linux case-sensitivity) and keeps the codebase predictable.
9
-
10
- ## Core Rules
11
-
12
- - ALWAYS use kebab-case for file names — never PascalCase or camelCase
13
- - ALWAYS use PascalCase for React component exports
14
- - ALWAYS use camelCase starting with `use` for hook exports
15
- - ALWAYS suffix schema files with `.schemas.ts` and type files with `.types.ts`
16
- - NEVER mix cases in the same category — no `UserCard.tsx` alongside `user-profile.tsx`
17
-
18
- ## Complete Reference Table
19
-
20
- | Artifact | File Name | Export Name | Example |
21
- |----------|-----------|-------------|---------|
22
- | React Component | `user-card.tsx` | `UserCard` | `export function UserCard()` |
23
- | Client Component | `user-form.tsx` | `UserForm` | `export function UserForm()` + `'use client'` |
24
- | TanStack Query hook | `use-users.ts` | `useUsers` | `export function useUsers()` |
25
- | Mutation hook | `use-create-user.ts` | `useCreateUser` | `export function useCreateUser()` |
26
- | Utility hook | `use-debounce.ts` | `useDebounce` | `export function useDebounce()` |
27
- | Zod schema file | `user.schemas.ts` | `createUserSchema` | `export const createUserSchema = z.object(...)` |
28
- | TypeScript types | `user.types.ts` | `User`, `CreateUserInput` | `export type User = z.infer<typeof userSchema>` |
29
- | Feature index | `index.ts` | (re-exports) | `export { UserCard } from './components/user-card'` |
30
- | Page file | `page.tsx` | `default` | `export default function UsersPage()` |
31
-
32
- ## Hook Naming Rules
33
-
34
- ```
35
- GET list: use-users.ts → useUsers()
36
- GET single: use-user.ts → useUser(id: string)
37
- POST: use-create-user.ts → useCreateUser()
38
- PUT/PATCH: use-update-user.ts → useUpdateUser()
39
- DELETE: use-delete-user.ts → useDeleteUser()
40
- ```
41
-
42
- ## Schema and Type Naming Rules
43
-
44
- ```typescript
45
- // user.schemas.ts
46
- export const userSchema = z.object({ id: z.string(), name: z.string() });
47
- export const createUserSchema = z.object({ name: z.string().min(2), email: z.string().email() });
48
- export const updateUserSchema = createUserSchema.partial();
49
-
50
- // user.types.ts — derive from schemas, don't duplicate
51
- export type User = z.infer<typeof userSchema>;
52
- export type CreateUserInput = z.infer<typeof createUserSchema>;
53
- export type UpdateUserInput = z.infer<typeof updateUserSchema>;
54
- ```
55
-
56
- ## Common Mistakes
57
-
58
- | Wrong | Right | Why |
59
- |-------|-------|-----|
60
- | `UserCard.tsx` | `user-card.tsx` | Linux servers are case-sensitive |
61
- | `useUsers.ts` | `use-users.ts` | Inconsistent with Next.js file conventions |
62
- | `export default function UserCard` | `export function UserCard` | Named exports are more refactor-safe |
63
- | `type User = { id: string }` | `type User = z.infer<typeof userSchema>` | Single source of truth |
64
-
65
- ---
66
-
67
- *MORPH-SPEC by Polymorphism Tech*
1
+ # Next.js Naming Conventions Standard
2
+
3
+ > **Scope:** frontend/nextjs/naming-conventions
4
+ > **Layer:** 1 (always)
5
+ > **Keywords:** naming, conventions, file names, component names, hooks, typescript
6
+ > **Load When:** creating any new file in a Next.js project
7
+
8
+ **Verified against:** Next.js 15 + Zod 4. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Consistent naming prevents filesystem bugs (Linux case-sensitivity) and keeps the codebase predictable.
13
+
14
+ ## Core Rules
15
+
16
+ - ALWAYS use kebab-case for file names — never PascalCase or camelCase
17
+ - ALWAYS use PascalCase for React component exports
18
+ - ALWAYS use camelCase starting with `use` for hook exports
19
+ - ALWAYS suffix schema files with `.schemas.ts` and type files with `.types.ts`
20
+ - NEVER mix cases in the same category — no `UserCard.tsx` alongside `user-profile.tsx`
21
+
22
+ ## Complete Reference Table
23
+
24
+ | Artifact | File Name | Export Name | Example |
25
+ |----------|-----------|-------------|---------|
26
+ | React Component | `user-card.tsx` | `UserCard` | `export function UserCard()` |
27
+ | Client Component | `user-form.tsx` | `UserForm` | `export function UserForm()` + `'use client'` |
28
+ | TanStack Query hook | `use-users.ts` | `useUsers` | `export function useUsers()` |
29
+ | Mutation hook | `use-create-user.ts` | `useCreateUser` | `export function useCreateUser()` |
30
+ | Utility hook | `use-debounce.ts` | `useDebounce` | `export function useDebounce()` |
31
+ | Zod schema file | `user.schemas.ts` | `createUserSchema` | `export const createUserSchema = z.object(...)` |
32
+ | TypeScript types | `user.types.ts` | `User`, `CreateUserInput` | `export type User = z.infer<typeof userSchema>` |
33
+ | Feature index | `index.ts` | (re-exports) | `export { UserCard } from './components/user-card'` |
34
+ | Page file | `page.tsx` | `default` | `export default function UsersPage()` |
35
+
36
+ ## Hook Naming Rules
37
+
38
+ ```
39
+ GET list: use-users.ts → useUsers()
40
+ GET single: use-user.ts → useUser(id: string)
41
+ POST: use-create-user.ts → useCreateUser()
42
+ PUT/PATCH: use-update-user.ts → useUpdateUser()
43
+ DELETE: use-delete-user.ts → useDeleteUser()
44
+ ```
45
+
46
+ ## Schema and Type Naming Rules
47
+
48
+ ```typescript
49
+ // user.schemas.ts
50
+ export const userSchema = z.object({ id: z.string(), name: z.string() });
51
+ export const createUserSchema = z.object({ name: z.string().min(2), email: z.email() });
52
+ export const updateUserSchema = createUserSchema.partial();
53
+
54
+ // user.types.ts — derive from schemas, don't duplicate
55
+ export type User = z.infer<typeof userSchema>;
56
+ export type CreateUserInput = z.infer<typeof createUserSchema>;
57
+ export type UpdateUserInput = z.infer<typeof updateUserSchema>;
58
+ ```
59
+
60
+ ## Common Mistakes
61
+
62
+ | Wrong | Right | Why |
63
+ |-------|-------|-----|
64
+ | `UserCard.tsx` | `user-card.tsx` | Linux servers are case-sensitive |
65
+ | `useUsers.ts` | `use-users.ts` | Inconsistent with Next.js file conventions |
66
+ | `export default function UserCard` | `export function UserCard` | Named exports are more refactor-safe |
67
+ | `type User = { id: string }` | `type User = z.infer<typeof userSchema>` | Single source of truth |
68
+
69
+ ---
70
+
71
+ *MORPH-SPEC by Polymorphism Tech*
@@ -1,225 +1,229 @@
1
- # Next.js 15 Patterns Standard
2
-
3
- > **Scope:** nextjs-neon
4
- > **Layer:** 2 (on keyword)
5
- > **Keywords:** nextjs, react, app router, server component, client component
6
- > **Load When:** nextjs keywords detected
7
-
8
- Stack: Next.js 15 + Neon PostgreSQL + .NET Backend
9
-
10
- ## Core Rules
11
-
12
- - ALWAYS use App Router (not Pages Router)
13
- - Default to Server Components -- add `'use client'` only when needed
14
- - ALWAYS colocate loading.tsx and error.tsx with page.tsx
15
- - NEVER call the database directly from client -- use Route Handlers as BFF
16
- - ALWAYS validate inputs with Zod on both client and server
17
- - Use TypeScript strict mode (`"strict": true`)
18
-
19
- ## Server vs Client Components
20
-
21
- | Aspect | Server Component (default) | Client Component (`'use client'`) |
22
- |--------|---------------------------|-----------------------------------|
23
- | Renders | Server only | Server SSR + Client hydration |
24
- | Access to | DB, env vars, fs, async/await | Browser APIs, useState, useEffect, events |
25
- | Bundle | Not included | Included in JS bundle |
26
- | Use when | Data fetching, static content | Interactivity, forms, real-time |
27
-
28
- Decision: Need useState/useEffect/onClick/browser APIs? Client Component. Otherwise Server Component.
29
-
30
- ## File-Based Routing
31
-
32
- ```
33
- app/
34
- layout.tsx # Root layout
35
- page.tsx # / (home)
36
- loading.tsx / error.tsx # Loading UI / Error boundary
37
- not-found.tsx # 404
38
- dashboard/
39
- layout.tsx # Nested layout
40
- page.tsx # /dashboard
41
- loading.tsx
42
- api/documents/
43
- route.ts # GET/POST /api/documents
44
- [id]/route.ts # GET/PUT/DELETE /api/documents/:id
45
- ```
46
-
47
- ## Layout and Error Boundaries
48
-
49
- ```tsx
50
- export default function RootLayout({ children }: { children: React.ReactNode }) {
51
- return <html lang="en"><body><Providers>{children}</Providers></body></html>;
52
- }
53
-
54
- // loading.tsx
55
- export default function Loading() { return <div className="animate-pulse">Loading...</div>; }
56
-
57
- // error.tsx — MUST be 'use client'
58
- 'use client';
59
- export default function Error({ error, reset }: { error: Error; reset: () => void }) {
60
- return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
61
- }
62
- ```
63
-
64
- ## Route Handlers (BFF Pattern)
65
-
66
- ```ts
67
- // app/api/documents/route.ts — BFF pattern (delegates to .NET API)
68
- import { auth } from "@/lib/auth/server";
69
- import { NextResponse } from "next/server";
70
- import { z } from "zod";
71
-
72
- export const dynamic = "force-dynamic";
73
-
74
- const API_URL = process.env.API_URL!; // .NET backend
75
-
76
- const CreateSchema = z.object({
77
- title: z.string().min(1).max(200),
78
- content: z.string().min(1),
79
- });
80
-
81
- export async function GET() {
82
- const { data: session } = await auth.getSession();
83
- if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
84
-
85
- const res = await fetch(`${API_URL}/api/documents`, {
86
- headers: { Authorization: `Bearer ${session.session.access_token}` },
87
- });
88
- return NextResponse.json(await res.json());
89
- }
90
-
91
- export async function POST(request: Request) {
92
- const { data: session } = await auth.getSession();
93
- if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
94
-
95
- const parsed = CreateSchema.safeParse(await request.json());
96
- if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
97
-
98
- const res = await fetch(`${API_URL}/api/documents`, {
99
- method: "POST",
100
- headers: { Authorization: `Bearer ${session.session.access_token}`, "Content-Type": "application/json" },
101
- body: JSON.stringify(parsed.data),
102
- });
103
- return NextResponse.json(await res.json(), { status: res.status });
104
- }
105
- ```
106
-
107
- > **Rule:** Route handlers act as BFF (Backend-For-Frontend) — they delegate to the .NET API. Database access lives in the .NET backend via EF Core, never in Next.js route handlers.
108
-
109
- ## React Query + Neon
110
-
111
- ```tsx
112
- // providers/query-provider.tsx
113
- 'use client';
114
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
115
- import { useState } from "react";
116
-
117
- export function QueryProvider({ children }: { children: React.ReactNode }) {
118
- const [client] = useState(() => new QueryClient({
119
- defaultOptions: { queries: { staleTime: 60_000, retry: 1 } },
120
- }));
121
- return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
122
- }
123
- ```
124
-
125
- ```tsx
126
- // hooks/use-documents.ts
127
- export function useDocuments() {
128
- return useQuery({
129
- queryKey: ["documents"],
130
- queryFn: async () => {
131
- const res = await fetch("/api/documents");
132
- if (!res.ok) throw new Error("Failed to fetch");
133
- return res.json();
134
- },
135
- });
136
- }
137
-
138
- export function useCreateDocument() {
139
- const qc = useQueryClient();
140
- return useMutation({
141
- mutationFn: async (data: { title: string; content: string }) => {
142
- const res = await fetch("/api/documents", {
143
- method: "POST", headers: { "Content-Type": "application/json" },
144
- body: JSON.stringify(data),
145
- });
146
- if (!res.ok) throw new Error("Failed to create");
147
- return res.json();
148
- },
149
- onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }),
150
- });
151
- }
152
- ```
153
-
154
- ## Form Handling (react-hook-form + Zod)
155
-
156
- ```tsx
157
- 'use client';
158
- import { useForm } from "react-hook-form";
159
- import { zodResolver } from "@hookform/resolvers/zod";
160
- import { z } from "zod";
161
-
162
- const schema = z.object({
163
- title: z.string().min(1, "Required").max(200),
164
- content: z.string().min(1, "Required"),
165
- });
166
-
167
- export function DocumentForm() {
168
- const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<z.infer<typeof schema>>({
169
- resolver: zodResolver(schema),
170
- });
171
- const create = useCreateDocument();
172
- return (
173
- <form onSubmit={handleSubmit((data) => create.mutateAsync(data))}>
174
- <input {...register("title")} />
175
- {errors.title && <span>{errors.title.message}</span>}
176
- <textarea {...register("content")} />
177
- {errors.content && <span>{errors.content.message}</span>}
178
- <button type="submit" disabled={isSubmitting}>Save</button>
179
- </form>
180
- );
181
- }
182
- ```
183
-
184
- ## shadcn/ui
185
-
186
- Install: `npx shadcn@latest init` then `npx shadcn@latest add button input card dialog form`.
187
- Components are copied to `components/ui/` -- NOT an npm dependency, your code to customize.
188
-
189
- ## TypeScript Strict Patterns
190
-
191
- | Pattern | Approach |
192
- |---------|----------|
193
- | API responses | Zod schema + `z.infer<typeof schema>` |
194
- | Props | Explicit interface, no `any` |
195
- | Event handlers | `React.ChangeEvent<HTMLInputElement>` |
196
- | Null safety | `?.` over type assertions, `if (!data) return null` |
197
-
198
- ## Common Mistakes
199
-
200
- | Wrong | Right | Why |
201
- |-------|-------|-----|
202
- | `'use client'` on every component | Default to Server Components | Unnecessary JS bundle size |
203
- | Direct DB from client | Route Handler `/api/*` as BFF | Exposes queries, harder to secure |
204
- | `any` for API responses | Zod schema + infer | No runtime safety |
205
- | Missing loading.tsx | Colocate with page.tsx | Blank page during load |
206
- | `useEffect` for data fetching | React Query `useQuery` | No caching, race conditions |
207
- | Form validation on submit only | Zod resolver + react-hook-form | Delayed error feedback |
208
- | shadcn as npm package | `npx shadcn@latest add` | Copy-paste system, not a dependency |
209
-
210
- ## TypeScript Strict Mode (Required)
211
-
212
- Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
213
- agent-assisted development because agents rely on compiler errors to self-correct.
214
- Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
215
-
216
- ```json
217
- {
218
- "compilerOptions": {
219
- "strict": true,
220
- "noUncheckedIndexedAccess": true
221
- }
222
- }
223
- ```
224
-
225
- Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.
1
+ # Next.js 15 Patterns Standard
2
+
3
+ > **Scope:** nextjs-neon
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** nextjs, react, app router, server component, client component
6
+ > **Load When:** nextjs keywords detected
7
+
8
+ **Verified against:** Next.js 15 + TanStack Query 5 + Zod 4 + react-hook-form 7. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Stack: Next.js 15 + Neon PostgreSQL + .NET Backend
13
+
14
+ ## Core Rules
15
+
16
+ - ALWAYS use App Router (not Pages Router)
17
+ - Default to Server Components -- add `'use client'` only when needed
18
+ - ALWAYS colocate loading.tsx and error.tsx with page.tsx
19
+ - NEVER call the database directly from client -- use Route Handlers as BFF
20
+ - ALWAYS validate inputs with Zod on both client and server
21
+ - Use TypeScript strict mode (`"strict": true`)
22
+
23
+ ## Server vs Client Components
24
+
25
+ | Aspect | Server Component (default) | Client Component (`'use client'`) |
26
+ |--------|---------------------------|-----------------------------------|
27
+ | Renders | Server only | Server SSR + Client hydration |
28
+ | Access to | DB, env vars, fs, async/await | Browser APIs, useState, useEffect, events |
29
+ | Bundle | Not included | Included in JS bundle |
30
+ | Use when | Data fetching, static content | Interactivity, forms, real-time |
31
+
32
+ Decision: Need useState/useEffect/onClick/browser APIs? Client Component. Otherwise Server Component.
33
+
34
+ ## File-Based Routing
35
+
36
+ ```
37
+ app/
38
+ layout.tsx # Root layout
39
+ page.tsx # / (home)
40
+ loading.tsx / error.tsx # Loading UI / Error boundary
41
+ not-found.tsx # 404
42
+ dashboard/
43
+ layout.tsx # Nested layout
44
+ page.tsx # /dashboard
45
+ loading.tsx
46
+ api/documents/
47
+ route.ts # GET/POST /api/documents
48
+ [id]/route.ts # GET/PUT/DELETE /api/documents/:id
49
+ ```
50
+
51
+ ## Layout and Error Boundaries
52
+
53
+ ```tsx
54
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
55
+ return <html lang="en"><body><Providers>{children}</Providers></body></html>;
56
+ }
57
+
58
+ // loading.tsx
59
+ export default function Loading() { return <div className="animate-pulse">Loading...</div>; }
60
+
61
+ // error.tsx — MUST be 'use client'
62
+ 'use client';
63
+ export default function Error({ error, reset }: { error: Error; reset: () => void }) {
64
+ return <div><h2>Something went wrong</h2><button onClick={reset}>Try again</button></div>;
65
+ }
66
+ ```
67
+
68
+ ## Route Handlers (BFF Pattern)
69
+
70
+ ```ts
71
+ // app/api/documents/route.ts — BFF pattern (delegates to .NET API)
72
+ import { auth } from "@/lib/auth/server";
73
+ import { NextResponse } from "next/server";
74
+ import { z } from "zod";
75
+
76
+ export const dynamic = "force-dynamic";
77
+
78
+ const API_URL = process.env.API_URL!; // .NET backend
79
+
80
+ const CreateSchema = z.object({
81
+ title: z.string().min(1).max(200),
82
+ content: z.string().min(1),
83
+ });
84
+
85
+ export async function GET() {
86
+ const { data: session } = await auth.getSession();
87
+ if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
88
+
89
+ const res = await fetch(`${API_URL}/api/documents`, {
90
+ headers: { Authorization: `Bearer ${session.session.access_token}` },
91
+ });
92
+ return NextResponse.json(await res.json());
93
+ }
94
+
95
+ export async function POST(request: Request) {
96
+ const { data: session } = await auth.getSession();
97
+ if (!session?.user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
98
+
99
+ const parsed = CreateSchema.safeParse(await request.json());
100
+ if (!parsed.success) return NextResponse.json({ error: parsed.error.flatten() }, { status: 400 });
101
+
102
+ const res = await fetch(`${API_URL}/api/documents`, {
103
+ method: "POST",
104
+ headers: { Authorization: `Bearer ${session.session.access_token}`, "Content-Type": "application/json" },
105
+ body: JSON.stringify(parsed.data),
106
+ });
107
+ return NextResponse.json(await res.json(), { status: res.status });
108
+ }
109
+ ```
110
+
111
+ > **Rule:** Route handlers act as BFF (Backend-For-Frontend) — they delegate to the .NET API. Database access lives in the .NET backend via EF Core, never in Next.js route handlers.
112
+
113
+ ## React Query + Neon
114
+
115
+ ```tsx
116
+ // providers/query-provider.tsx
117
+ 'use client';
118
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
119
+ import { useState } from "react";
120
+
121
+ export function QueryProvider({ children }: { children: React.ReactNode }) {
122
+ const [client] = useState(() => new QueryClient({
123
+ defaultOptions: { queries: { staleTime: 60_000, retry: 1 } },
124
+ }));
125
+ return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
126
+ }
127
+ ```
128
+
129
+ ```tsx
130
+ // hooks/use-documents.ts
131
+ export function useDocuments() {
132
+ return useQuery({
133
+ queryKey: ["documents"],
134
+ queryFn: async () => {
135
+ const res = await fetch("/api/documents");
136
+ if (!res.ok) throw new Error("Failed to fetch");
137
+ return res.json();
138
+ },
139
+ });
140
+ }
141
+
142
+ export function useCreateDocument() {
143
+ const qc = useQueryClient();
144
+ return useMutation({
145
+ mutationFn: async (data: { title: string; content: string }) => {
146
+ const res = await fetch("/api/documents", {
147
+ method: "POST", headers: { "Content-Type": "application/json" },
148
+ body: JSON.stringify(data),
149
+ });
150
+ if (!res.ok) throw new Error("Failed to create");
151
+ return res.json();
152
+ },
153
+ onSuccess: () => qc.invalidateQueries({ queryKey: ["documents"] }),
154
+ });
155
+ }
156
+ ```
157
+
158
+ ## Form Handling (react-hook-form + Zod)
159
+
160
+ ```tsx
161
+ 'use client';
162
+ import { useForm } from "react-hook-form";
163
+ import { zodResolver } from "@hookform/resolvers/zod";
164
+ import { z } from "zod";
165
+
166
+ const schema = z.object({
167
+ title: z.string().min(1, "Required").max(200),
168
+ content: z.string().min(1, "Required"),
169
+ });
170
+
171
+ export function DocumentForm() {
172
+ const { register, handleSubmit, formState: { errors, isSubmitting } } = useForm<z.infer<typeof schema>>({
173
+ resolver: zodResolver(schema),
174
+ });
175
+ const create = useCreateDocument();
176
+ return (
177
+ <form onSubmit={handleSubmit((data) => create.mutateAsync(data))}>
178
+ <input {...register("title")} />
179
+ {errors.title && <span>{errors.title.message}</span>}
180
+ <textarea {...register("content")} />
181
+ {errors.content && <span>{errors.content.message}</span>}
182
+ <button type="submit" disabled={isSubmitting}>Save</button>
183
+ </form>
184
+ );
185
+ }
186
+ ```
187
+
188
+ ## shadcn/ui
189
+
190
+ Install: `npx shadcn@latest init` then `npx shadcn@latest add button input card dialog form`.
191
+ Components are copied to `components/ui/` -- NOT an npm dependency, your code to customize.
192
+
193
+ ## TypeScript Strict Patterns
194
+
195
+ | Pattern | Approach |
196
+ |---------|----------|
197
+ | API responses | Zod schema + `z.infer<typeof schema>` |
198
+ | Props | Explicit interface, no `any` |
199
+ | Event handlers | `React.ChangeEvent<HTMLInputElement>` |
200
+ | Null safety | `?.` over type assertions, `if (!data) return null` |
201
+
202
+ ## Common Mistakes
203
+
204
+ | Wrong | Right | Why |
205
+ |-------|-------|-----|
206
+ | `'use client'` on every component | Default to Server Components | Unnecessary JS bundle size |
207
+ | Direct DB from client | Route Handler `/api/*` as BFF | Exposes queries, harder to secure |
208
+ | `any` for API responses | Zod schema + infer | No runtime safety |
209
+ | Missing loading.tsx | Colocate with page.tsx | Blank page during load |
210
+ | `useEffect` for data fetching | React Query `useQuery` | No caching, race conditions |
211
+ | Form validation on submit only | Zod resolver + react-hook-form | Delayed error feedback |
212
+ | shadcn as npm package | `npx shadcn@latest add` | Copy-paste system, not a dependency |
213
+
214
+ ## TypeScript Strict Mode (Required)
215
+
216
+ Always enable `"strict": true` in `tsconfig.json`. This is non-negotiable for
217
+ agent-assisted development because agents rely on compiler errors to self-correct.
218
+ Without strict mode, type/null errors only surface at runtime where agents cannot observe them.
219
+
220
+ ```json
221
+ {
222
+ "compilerOptions": {
223
+ "strict": true,
224
+ "noUncheckedIndexedAccess": true
225
+ }
226
+ }
227
+ ```
228
+
229
+ Strict mode enables: `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, `strictPropertyInitialization`.