@polymorphism-tech/morph-spec 6.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 (114) hide show
  1. package/README.md +367 -366
  2. package/docs/CHEATSHEET.md +3 -3
  3. package/docs/QUICKSTART.md +4 -4
  4. package/framework/CLAUDE.md +13 -0
  5. package/framework/MORPH.md +57 -36
  6. package/framework/agents.json +132 -127
  7. package/framework/commands/morph-apply.md +4 -21
  8. package/framework/commands/morph-archive.md +1 -1
  9. package/framework/commands/morph-proposal.md +2 -0
  10. package/framework/hooks/claude-code/core/enforce-phase-writes.js +1 -1
  11. package/framework/hooks/claude-code/core/protect-spec-files.js +1 -1
  12. package/framework/hooks/claude-code/core/state-sync.js +212 -0
  13. package/framework/hooks/shared/phase-utils.js +5 -4
  14. package/framework/hooks/shared/state-reader.js +1 -1
  15. package/framework/phases.json +1 -1
  16. package/framework/rubrics/backend.json +41 -36
  17. package/framework/rules/ai-agents-standards.md +45 -15
  18. package/framework/rules/morph-workflow.md +15 -34
  19. package/framework/rules/neon-standards.md +25 -26
  20. package/framework/schemas/tasks.schema.json +35 -0
  21. package/framework/skills/core/morph-apply/SKILL.md +154 -0
  22. package/framework/skills/core/morph-brainstorming/SKILL.md +53 -30
  23. package/framework/skills/core/morph-implement/SKILL.md +25 -14
  24. package/framework/skills/core/morph-plan/SKILL.md +91 -23
  25. package/framework/skills/core/morph-proposal/SKILL.md +137 -0
  26. package/framework/skills/core/morph-review/SKILL.md +43 -7
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +40 -1
  28. package/framework/standards/STANDARDS.json +1729 -1134
  29. package/framework/standards/ai-agents/agent-session.md +160 -0
  30. package/framework/standards/ai-agents/batching.md +193 -0
  31. package/framework/standards/ai-agents/context-providers.md +188 -0
  32. package/framework/standards/ai-agents/mcp-server.md +250 -0
  33. package/framework/standards/ai-agents/mcp-tools.md +197 -0
  34. package/framework/standards/ai-agents/middleware-patterns.md +207 -0
  35. package/framework/standards/ai-agents/modalities-image-gen.md +173 -0
  36. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -0
  37. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -0
  38. package/framework/standards/ai-agents/modalities-vision.md +201 -0
  39. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -0
  40. package/framework/standards/ai-agents/production.md +114 -258
  41. package/framework/standards/ai-agents/prompts-in-db.md +214 -0
  42. package/framework/standards/ai-agents/providers/model-registry.md +158 -0
  43. package/framework/standards/ai-agents/rag-custom-pgvector.md +243 -0
  44. package/framework/standards/ai-agents/service-tier-flex.md +135 -0
  45. package/framework/standards/ai-agents/setup.md +142 -316
  46. package/framework/standards/ai-agents/structured-output.md +249 -0
  47. package/framework/standards/ai-agents/sweet-spot.md +109 -0
  48. package/framework/standards/ai-agents/workflows.md +114 -284
  49. package/framework/standards/architecture/vertical-slice/vertical-slice.md +433 -429
  50. package/framework/standards/backend/api/validation.md +92 -88
  51. package/framework/standards/backend/database/postgresql/database.md +336 -332
  52. package/framework/standards/backend/database/vector-search-rag.md +599 -541
  53. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +4 -0
  54. package/framework/standards/backend/dotnet/result-pattern.md +4 -0
  55. package/framework/standards/backend/dotnet/scalar-ui.md +176 -172
  56. package/framework/standards/backend/dotnet/scrutor.md +4 -0
  57. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +278 -274
  58. package/framework/standards/backend/integrations/asaas/asaas-api.md +260 -216
  59. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +354 -350
  60. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +30 -19
  61. package/framework/standards/backend/integrations/resend/resend-email.md +400 -385
  62. package/framework/standards/data/nosql/cache/redis.md +106 -97
  63. package/framework/standards/data/nosql/cosmos-db.md +127 -118
  64. package/framework/standards/data/vector-search/rag-chunking.md +113 -104
  65. package/framework/standards/frontend/design-system/ai-image-generation.md +4 -0
  66. package/framework/standards/frontend/design-system/animations.md +164 -160
  67. package/framework/standards/frontend/design-system/naming.md +68 -64
  68. package/framework/standards/frontend/gsap/gsap-core.md +14 -8
  69. package/framework/standards/frontend/gsap/gsap-plugins.md +26 -21
  70. package/framework/standards/frontend/gsap/gsap-scrolltrigger.md +9 -7
  71. package/framework/standards/frontend/nextjs/app-router.md +127 -123
  72. package/framework/standards/frontend/nextjs/components.md +136 -132
  73. package/framework/standards/frontend/nextjs/data-fetching.md +130 -126
  74. package/framework/standards/frontend/nextjs/forms.md +134 -128
  75. package/framework/standards/frontend/nextjs/motion-patterns.md +328 -322
  76. package/framework/standards/frontend/nextjs/naming-conventions.md +71 -67
  77. package/framework/standards/frontend/nextjs/nextjs-patterns.md +229 -225
  78. package/framework/standards/frontend/nextjs/project-structure.md +106 -102
  79. package/framework/standards/frontend/nextjs/state-management.md +76 -72
  80. package/framework/standards/frontend/nextjs/testing.md +115 -111
  81. package/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -192
  82. package/framework/standards/infrastructure/neon/neon-pgrag.md +6 -2
  83. package/framework/standards/infrastructure/neon/neon-pgvector.md +4 -0
  84. package/framework/standards/infrastructure/neon/neon-setup.md +5 -1
  85. package/framework/standards/integration/mcp/mcp-tools.md +471 -462
  86. package/framework/templates/REGISTRY.json +1745 -1239
  87. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -0
  88. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -0
  89. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -0
  90. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -0
  91. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -0
  92. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -0
  93. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -0
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -0
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -0
  96. package/framework/templates/docs/onboarding.md +4 -4
  97. package/framework/templates/tasks.template.json +17 -0
  98. package/package.json +89 -83
  99. package/src/commands/project/init.js +16 -0
  100. package/src/commands/validation/validate-feature.js +1 -1
  101. package/src/core/paths/output-schema.js +6 -6
  102. package/src/core/state/state-manager.js +1 -1
  103. package/src/core/templates/template-registry.js +289 -256
  104. package/src/lib/detectors/design-system-detector.js +1 -1
  105. package/src/lib/generators/context-generator.js +516 -518
  106. package/src/lib/generators/scaffold-generator.js +23 -26
  107. package/src/lib/installers/memory-installer.js +138 -0
  108. package/src/lib/phase-chain/phase-validator.js +4 -4
  109. package/src/lib/risk-detector.js +80 -0
  110. package/src/lib/tasks/tasks-json-parser.js +185 -0
  111. package/src/scripts/setup-infra.js +25 -0
  112. package/src/utils/hooks-installer.js +59 -10
  113. package/framework/standards/infrastructure/neon/neon-semantic-kernel.md +0 -310
  114. package/framework/templates/integrations/semantic-kernel-neon.cs +0 -125
@@ -1,132 +1,136 @@
1
- # Next.js Component Standards
2
-
3
- > **Scope:** frontend/nextjs/components
4
- > **Layer:** 2 (on keyword)
5
- > **Keywords:** component, shadcn, reusable, shared, ui, three-tier
6
- > **Load When:** creating or editing React components
7
-
8
- Three-tier component hierarchy. `components/ui/` is shadcn primitives (never edit). `components/` is composed shared (no business logic). `features/*/components/` is feature-scoped.
9
-
10
- ## Core Rules
11
-
12
- - NEVER edit files in `components/ui/` — they are regenerated by shadcn CLI
13
- - NEVER import from `features/` inside `components/` — components know nothing about domain
14
- - ALWAYS compose shadcn primitives in `components/` instead of editing them
15
- - ALWAYS add `'use client'` only if the component uses hooks, events, or browser APIs
16
- - NEVER pass raw API data directly to a component — transform to props first
17
-
18
- ## Three-Tier Hierarchy
19
-
20
- ```
21
- Tier 1: components/ui/ ← shadcn/ui CLI output (DO NOT EDIT)
22
- ↓ composed into
23
- Tier 2: components/ ← shared project components (no business logic)
24
- ↓ used by
25
- Tier 3: features/*/components/ ← feature-scoped (knows about users, billing, etc.)
26
- ```
27
-
28
- ## Tier 1 — shadcn/ui Primitives
29
-
30
- ```bash
31
- # Add shadcn components via CLI — never write them manually
32
- npx shadcn@latest add button card dialog form input table
33
- ```
34
-
35
- If you need to change a shadcn component's behavior, wrap it — do not edit the source file.
36
-
37
- ## Tier 2 — Shared Composed Components
38
-
39
- ```tsx
40
- // components/data-table.tsx — composes shadcn Table + TanStack Table
41
- 'use client';
42
-
43
- import {
44
- Table, TableBody, TableCell, TableHead, TableHeader, TableRow
45
- } from '@/components/ui/table';
46
- import {
47
- type ColumnDef, flexRender, getCoreRowModel, useReactTable
48
- } from '@tanstack/react-table';
49
-
50
- interface DataTableProps<TData> {
51
- columns: ColumnDef<TData>[];
52
- data: TData[];
53
- }
54
-
55
- export function DataTable<TData>({ columns, data }: DataTableProps<TData>) {
56
- const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
57
- return (
58
- <Table>
59
- <TableHeader>
60
- {table.getHeaderGroups().map((hg) => (
61
- <TableRow key={hg.id}>
62
- {hg.headers.map((h) => (
63
- <TableHead key={h.id}>
64
- {flexRender(h.column.columnDef.header, h.getContext())}
65
- </TableHead>
66
- ))}
67
- </TableRow>
68
- ))}
69
- </TableHeader>
70
- <TableBody>
71
- {table.getRowModel().rows.map((row) => (
72
- <TableRow key={row.id}>
73
- {row.getVisibleCells().map((cell) => (
74
- <TableCell key={cell.id}>
75
- {flexRender(cell.column.columnDef.cell, cell.getContext())}
76
- </TableCell>
77
- ))}
78
- </TableRow>
79
- ))}
80
- </TableBody>
81
- </Table>
82
- );
83
- }
84
- ```
85
-
86
- ## Tier 3 — Feature Components
87
-
88
- ```tsx
89
- // features/users/components/user-list.tsx
90
- 'use client';
91
-
92
- import { DataTable } from '@/components/data-table';
93
- import { useUsers } from '@/features/users/hooks/use-users';
94
- import type { ColumnDef } from '@tanstack/react-table';
95
- import type { User } from '@/features/users/types/user.types';
96
-
97
- const columns: ColumnDef<User>[] = [
98
- { accessorKey: 'name', header: 'Name' },
99
- { accessorKey: 'email', header: 'Email' },
100
- ];
101
-
102
- export function UserList() {
103
- const { data: users = [], isLoading } = useUsers();
104
- if (isLoading) return <div>Loading...</div>;
105
- return <DataTable columns={columns} data={users} />;
106
- }
107
- ```
108
-
109
- ## Component Props Conventions
110
-
111
- ```tsx
112
- interface UserCardProps {
113
- user: User;
114
- onEdit?: (id: string) => void;
115
- className?: string; // Always allow className for Tailwind override
116
- }
117
-
118
- export function UserCard({ user, onEdit, className }: UserCardProps) {}
119
- ```
120
-
121
- ## Common Mistakes
122
-
123
- | Wrong | Right | Why |
124
- |-------|-------|-----|
125
- | Edit `components/ui/button.tsx` | Wrap in `components/action-button.tsx` | shadcn CLI overwrites it |
126
- | `import { useUsers } from '@/features/users'` in `components/` | Move to a feature component | Breaks tier isolation |
127
- | `'use client'` on all components | Only on interactive ones | Unnecessary client JS |
128
- | Pass raw fetch response as prop | Type and validate with Zod first | Runtime type safety |
129
-
130
- ---
131
-
132
- *MORPH-SPEC by Polymorphism Tech*
1
+ # Next.js Component Standards
2
+
3
+ > **Scope:** frontend/nextjs/components
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** component, shadcn, reusable, shared, ui, three-tier
6
+ > **Load When:** creating or editing React components
7
+
8
+ **Verified against:** Next.js 15 + shadcn/ui CLI + TanStack Table 8. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Three-tier component hierarchy. `components/ui/` is shadcn primitives (never edit). `components/` is composed shared (no business logic). `features/*/components/` is feature-scoped.
13
+
14
+ ## Core Rules
15
+
16
+ - NEVER edit files in `components/ui/` — they are regenerated by shadcn CLI
17
+ - NEVER import from `features/` inside `components/` — components know nothing about domain
18
+ - ALWAYS compose shadcn primitives in `components/` instead of editing them
19
+ - ALWAYS add `'use client'` only if the component uses hooks, events, or browser APIs
20
+ - NEVER pass raw API data directly to a component — transform to props first
21
+
22
+ ## Three-Tier Hierarchy
23
+
24
+ ```
25
+ Tier 1: components/ui/ ← shadcn/ui CLI output (DO NOT EDIT)
26
+ ↓ composed into
27
+ Tier 2: components/ ← shared project components (no business logic)
28
+ ↓ used by
29
+ Tier 3: features/*/components/ ← feature-scoped (knows about users, billing, etc.)
30
+ ```
31
+
32
+ ## Tier 1 — shadcn/ui Primitives
33
+
34
+ ```bash
35
+ # Add shadcn components via CLI — never write them manually
36
+ npx shadcn@latest add button card dialog form input table
37
+ ```
38
+
39
+ If you need to change a shadcn component's behavior, wrap it — do not edit the source file.
40
+
41
+ ## Tier 2 — Shared Composed Components
42
+
43
+ ```tsx
44
+ // components/data-table.tsx — composes shadcn Table + TanStack Table
45
+ 'use client';
46
+
47
+ import {
48
+ Table, TableBody, TableCell, TableHead, TableHeader, TableRow
49
+ } from '@/components/ui/table';
50
+ import {
51
+ type ColumnDef, flexRender, getCoreRowModel, useReactTable
52
+ } from '@tanstack/react-table';
53
+
54
+ interface DataTableProps<TData> {
55
+ columns: ColumnDef<TData>[];
56
+ data: TData[];
57
+ }
58
+
59
+ export function DataTable<TData>({ columns, data }: DataTableProps<TData>) {
60
+ const table = useReactTable({ data, columns, getCoreRowModel: getCoreRowModel() });
61
+ return (
62
+ <Table>
63
+ <TableHeader>
64
+ {table.getHeaderGroups().map((hg) => (
65
+ <TableRow key={hg.id}>
66
+ {hg.headers.map((h) => (
67
+ <TableHead key={h.id}>
68
+ {flexRender(h.column.columnDef.header, h.getContext())}
69
+ </TableHead>
70
+ ))}
71
+ </TableRow>
72
+ ))}
73
+ </TableHeader>
74
+ <TableBody>
75
+ {table.getRowModel().rows.map((row) => (
76
+ <TableRow key={row.id}>
77
+ {row.getVisibleCells().map((cell) => (
78
+ <TableCell key={cell.id}>
79
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
80
+ </TableCell>
81
+ ))}
82
+ </TableRow>
83
+ ))}
84
+ </TableBody>
85
+ </Table>
86
+ );
87
+ }
88
+ ```
89
+
90
+ ## Tier 3 — Feature Components
91
+
92
+ ```tsx
93
+ // features/users/components/user-list.tsx
94
+ 'use client';
95
+
96
+ import { DataTable } from '@/components/data-table';
97
+ import { useUsers } from '@/features/users/hooks/use-users';
98
+ import type { ColumnDef } from '@tanstack/react-table';
99
+ import type { User } from '@/features/users/types/user.types';
100
+
101
+ const columns: ColumnDef<User>[] = [
102
+ { accessorKey: 'name', header: 'Name' },
103
+ { accessorKey: 'email', header: 'Email' },
104
+ ];
105
+
106
+ export function UserList() {
107
+ const { data: users = [], isLoading } = useUsers();
108
+ if (isLoading) return <div>Loading...</div>;
109
+ return <DataTable columns={columns} data={users} />;
110
+ }
111
+ ```
112
+
113
+ ## Component Props Conventions
114
+
115
+ ```tsx
116
+ interface UserCardProps {
117
+ user: User;
118
+ onEdit?: (id: string) => void;
119
+ className?: string; // Always allow className for Tailwind override
120
+ }
121
+
122
+ export function UserCard({ user, onEdit, className }: UserCardProps) {}
123
+ ```
124
+
125
+ ## Common Mistakes
126
+
127
+ | Wrong | Right | Why |
128
+ |-------|-------|-----|
129
+ | Edit `components/ui/button.tsx` | Wrap in `components/action-button.tsx` | shadcn CLI overwrites it |
130
+ | `import { useUsers } from '@/features/users'` in `components/` | Move to a feature component | Breaks tier isolation |
131
+ | `'use client'` on all components | Only on interactive ones | Unnecessary client JS |
132
+ | Pass raw fetch response as prop | Type and validate with Zod first | Runtime type safety |
133
+
134
+ ---
135
+
136
+ *MORPH-SPEC by Polymorphism Tech*
@@ -1,126 +1,130 @@
1
- # Next.js Data Fetching Standard
2
-
3
- > **Scope:** frontend/nextjs/data-fetching
4
- > **Layer:** 2 (on keyword)
5
- > **Keywords:** data fetching, tanstack query, react query, server component, fetch, api, useQuery, useMutation
6
- > **Load When:** fetching data from .NET API
7
-
8
- Server Components for initial page load. TanStack Query for client mutations and interactive data. Never `useEffect` for fetching.
9
-
10
- ## Core Rules
11
-
12
- - ALWAYS use Server Components for initial data that doesn't require interactivity
13
- - ALWAYS use TanStack Query (`useQuery`, `useMutation`) for client-side data needs
14
- - NEVER use `useEffect(() => { fetch(...) }, [])` — this is the old pattern
15
- - ALWAYS validate API responses with Zod before using them
16
- - ALWAYS use query key factories for consistent cache invalidation
17
-
18
- ## TanStack Query Setup
19
-
20
- ```tsx
21
- // lib/query-client.tsx
22
- 'use client';
23
-
24
- import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
25
- import { useState } from 'react';
26
-
27
- export function QueryProvider({ children }: { children: React.ReactNode }) {
28
- const [queryClient] = useState(() => new QueryClient({
29
- defaultOptions: { queries: { staleTime: 60 * 1000, retry: 1 } },
30
- }));
31
- return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
32
- }
33
- ```
34
-
35
- ## Query Key Factory Pattern
36
-
37
- ```ts
38
- // features/users/hooks/query-keys.ts
39
- export const userKeys = {
40
- all: ['users'] as const,
41
- lists: () => [...userKeys.all, 'list'] as const,
42
- list: (filters: Record<string, unknown>) => [...userKeys.lists(), filters] as const,
43
- details: () => [...userKeys.all, 'detail'] as const,
44
- detail: (id: string) => [...userKeys.details(), id] as const,
45
- };
46
- ```
47
-
48
- ## useQuery Hook Pattern
49
-
50
- ```ts
51
- // features/users/hooks/use-users.ts
52
- import { useQuery } from '@tanstack/react-query';
53
- import { userKeys } from './query-keys';
54
- import { z } from 'zod';
55
- import { userSchema } from '@/features/users/types/user.schemas';
56
-
57
- const usersResponseSchema = z.array(userSchema);
58
-
59
- export function useUsers() {
60
- return useQuery({
61
- queryKey: userKeys.lists(),
62
- queryFn: async () => {
63
- const res = await fetch('/api/proxy/users');
64
- if (!res.ok) throw new Error('Failed to fetch users');
65
- return usersResponseSchema.parse(await res.json());
66
- },
67
- });
68
- }
69
- ```
70
-
71
- ## useMutation Hook Pattern
72
-
73
- ```ts
74
- // features/users/hooks/use-create-user.ts
75
- import { useMutation, useQueryClient } from '@tanstack/react-query';
76
- import { userKeys } from './query-keys';
77
- import type { CreateUserInput } from '@/features/users/types/user.types';
78
-
79
- export function useCreateUser() {
80
- const queryClient = useQueryClient();
81
- return useMutation({
82
- mutationFn: async (input: CreateUserInput) => {
83
- const res = await fetch('/api/proxy/users', {
84
- method: 'POST',
85
- headers: { 'Content-Type': 'application/json' },
86
- body: JSON.stringify(input),
87
- });
88
- if (!res.ok) throw new Error('Failed to create user');
89
- return res.json();
90
- },
91
- onSuccess: () => {
92
- queryClient.invalidateQueries({ queryKey: userKeys.lists() });
93
- },
94
- });
95
- }
96
- ```
97
-
98
- ## Server Component + TanStack Query Handoff
99
-
100
- ```tsx
101
- // app/(dashboard)/users/page.tsx — Server Component: initial fetch
102
- export default async function UsersPage() {
103
- const initialUsers = await fetch(`${process.env.API_URL}/api/users`).then(r => r.json());
104
- return <UserListClient initialData={initialUsers} />;
105
- }
106
-
107
- // features/users/components/user-list-client.tsx — Client Component
108
- 'use client';
109
- export function UserListClient({ initialData }: { initialData: User[] }) {
110
- const { data: users = initialData } = useUsers();
111
- // mutations, filtering, etc.
112
- }
113
- ```
114
-
115
- ## Common Mistakes
116
-
117
- | Wrong | Right | Why |
118
- |-------|-------|-----|
119
- | `useEffect(() => { fetch(...) }, [])` | `useQuery(...)` | Two renders, no cache, no SSR |
120
- | No Zod on API response | `schema.parse(await res.json())` | Type safety at runtime |
121
- | Hardcoded query keys: `['users']` | Key factory: `userKeys.lists()` | Consistent invalidation |
122
- | `queryClient.invalidateQueries(['users'])` | `{ queryKey: userKeys.lists() }` | Object syntax required in v5 |
123
-
124
- ---
125
-
126
- *MORPH-SPEC by Polymorphism Tech*
1
+ # Next.js Data Fetching Standard
2
+
3
+ > **Scope:** frontend/nextjs/data-fetching
4
+ > **Layer:** 2 (on keyword)
5
+ > **Keywords:** data fetching, tanstack query, react query, server component, fetch, api, useQuery, useMutation
6
+ > **Load When:** fetching data from .NET API
7
+
8
+ **Verified against:** Next.js 15 + TanStack Query 5 + Zod 4. Last-verified: 2026-05-20.
9
+
10
+ ---
11
+
12
+ Server Components for initial page load. TanStack Query for client mutations and interactive data. Never `useEffect` for fetching.
13
+
14
+ ## Core Rules
15
+
16
+ - ALWAYS use Server Components for initial data that doesn't require interactivity
17
+ - ALWAYS use TanStack Query (`useQuery`, `useMutation`) for client-side data needs
18
+ - NEVER use `useEffect(() => { fetch(...) }, [])` — this is the old pattern
19
+ - ALWAYS validate API responses with Zod before using them
20
+ - ALWAYS use query key factories for consistent cache invalidation
21
+
22
+ ## TanStack Query Setup
23
+
24
+ ```tsx
25
+ // lib/query-client.tsx
26
+ 'use client';
27
+
28
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
29
+ import { useState } from 'react';
30
+
31
+ export function QueryProvider({ children }: { children: React.ReactNode }) {
32
+ const [queryClient] = useState(() => new QueryClient({
33
+ defaultOptions: { queries: { staleTime: 60 * 1000, retry: 1 } },
34
+ }));
35
+ return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
36
+ }
37
+ ```
38
+
39
+ ## Query Key Factory Pattern
40
+
41
+ ```ts
42
+ // features/users/hooks/query-keys.ts
43
+ export const userKeys = {
44
+ all: ['users'] as const,
45
+ lists: () => [...userKeys.all, 'list'] as const,
46
+ list: (filters: Record<string, unknown>) => [...userKeys.lists(), filters] as const,
47
+ details: () => [...userKeys.all, 'detail'] as const,
48
+ detail: (id: string) => [...userKeys.details(), id] as const,
49
+ };
50
+ ```
51
+
52
+ ## useQuery Hook Pattern
53
+
54
+ ```ts
55
+ // features/users/hooks/use-users.ts
56
+ import { useQuery } from '@tanstack/react-query';
57
+ import { userKeys } from './query-keys';
58
+ import { z } from 'zod';
59
+ import { userSchema } from '@/features/users/types/user.schemas';
60
+
61
+ const usersResponseSchema = z.array(userSchema);
62
+
63
+ export function useUsers() {
64
+ return useQuery({
65
+ queryKey: userKeys.lists(),
66
+ queryFn: async () => {
67
+ const res = await fetch('/api/proxy/users');
68
+ if (!res.ok) throw new Error('Failed to fetch users');
69
+ return usersResponseSchema.parse(await res.json());
70
+ },
71
+ });
72
+ }
73
+ ```
74
+
75
+ ## useMutation Hook Pattern
76
+
77
+ ```ts
78
+ // features/users/hooks/use-create-user.ts
79
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
80
+ import { userKeys } from './query-keys';
81
+ import type { CreateUserInput } from '@/features/users/types/user.types';
82
+
83
+ export function useCreateUser() {
84
+ const queryClient = useQueryClient();
85
+ return useMutation({
86
+ mutationFn: async (input: CreateUserInput) => {
87
+ const res = await fetch('/api/proxy/users', {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: JSON.stringify(input),
91
+ });
92
+ if (!res.ok) throw new Error('Failed to create user');
93
+ return res.json();
94
+ },
95
+ onSuccess: () => {
96
+ queryClient.invalidateQueries({ queryKey: userKeys.lists() });
97
+ },
98
+ });
99
+ }
100
+ ```
101
+
102
+ ## Server Component + TanStack Query Handoff
103
+
104
+ ```tsx
105
+ // app/(dashboard)/users/page.tsx — Server Component: initial fetch
106
+ export default async function UsersPage() {
107
+ const initialUsers = await fetch(`${process.env.API_URL}/api/users`).then(r => r.json());
108
+ return <UserListClient initialData={initialUsers} />;
109
+ }
110
+
111
+ // features/users/components/user-list-client.tsx — Client Component
112
+ 'use client';
113
+ export function UserListClient({ initialData }: { initialData: User[] }) {
114
+ const { data: users = initialData } = useUsers();
115
+ // mutations, filtering, etc.
116
+ }
117
+ ```
118
+
119
+ ## Common Mistakes
120
+
121
+ | Wrong | Right | Why |
122
+ |-------|-------|-----|
123
+ | `useEffect(() => { fetch(...) }, [])` | `useQuery(...)` | Two renders, no cache, no SSR |
124
+ | No Zod on API response | `schema.parse(await res.json())` | Type safety at runtime |
125
+ | Hardcoded query keys: `['users']` | Key factory: `userKeys.lists()` | Consistent invalidation |
126
+ | `queryClient.invalidateQueries(['users'])` | `{ queryKey: userKeys.lists() }` | Object syntax required in v5 |
127
+
128
+ ---
129
+
130
+ *MORPH-SPEC by Polymorphism Tech*