@polymorphism-tech/morph-spec 4.7.1 → 4.7.2
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.
- package/.morph/.morphversion +5 -0
- package/.morph/analytics/threads-log.jsonl +5 -0
- package/.morph/config/config.json +8 -0
- package/.morph/framework/agents.json +1815 -0
- package/.morph/framework/hooks/README.md +205 -0
- package/.morph/framework/hooks/claude-code/notification/approval-reminder.js +54 -0
- package/.morph/framework/hooks/claude-code/post-tool-use/dispatch.js +83 -0
- package/.morph/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +42 -0
- package/.morph/framework/hooks/claude-code/pre-compact/save-morph-context.js +61 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +71 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/protect-readonly-files.js +58 -0
- package/.morph/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +64 -0
- package/.morph/framework/hooks/claude-code/session-start/inject-morph-context.js +94 -0
- package/.morph/framework/hooks/claude-code/statusline.py +538 -0
- package/.morph/framework/hooks/claude-code/statusline.sh +7 -0
- package/.morph/framework/hooks/claude-code/stop/validate-completion.js +88 -0
- package/.morph/framework/hooks/claude-code/user-prompt/enrich-prompt.js +91 -0
- package/.morph/framework/hooks/git/commit-msg/conventional-commits.sh +33 -0
- package/.morph/framework/hooks/git/pre-commit/agents.sh +25 -0
- package/.morph/framework/hooks/git/pre-commit/orchestrator.sh +64 -0
- package/.morph/framework/hooks/git/pre-commit/specs.sh +50 -0
- package/.morph/framework/hooks/git/pre-push/run-tests.sh +44 -0
- package/.morph/framework/hooks/shared/hook-response.js +45 -0
- package/.morph/framework/hooks/shared/phase-utils.js +129 -0
- package/.morph/framework/hooks/shared/state-reader.js +138 -0
- package/.morph/framework/hooks/shared/stdin-reader.js +26 -0
- package/.morph/framework/standards/STANDARDS.json +933 -0
- package/.morph/framework/standards/ai-agents/blazor-ui.md +364 -0
- package/.morph/framework/standards/ai-agents/production.md +415 -0
- package/.morph/framework/standards/ai-agents/setup.md +418 -0
- package/.morph/framework/standards/ai-agents/team-orchestration.md +479 -0
- package/.morph/framework/standards/ai-agents/workflows.md +354 -0
- package/.morph/framework/standards/architecture/ddd/aggregates.md +120 -0
- package/.morph/framework/standards/architecture/ddd/bounded-contexts.md +105 -0
- package/.morph/framework/standards/architecture/ddd/complexity-levels.md +108 -0
- package/.morph/framework/standards/architecture/ddd/entities.md +99 -0
- package/.morph/framework/standards/architecture/ddd/ubiquitous-language.md +58 -0
- package/.morph/framework/standards/architecture/ddd/value-objects.md +124 -0
- package/.morph/framework/standards/backend/api/minimal-api.md +494 -0
- package/.morph/framework/standards/backend/api/rest.md +492 -0
- package/.morph/framework/standards/backend/api/validation.md +88 -0
- package/.morph/framework/standards/backend/authentication/passkeys.md +428 -0
- package/.morph/framework/standards/backend/database/ef-core.md +199 -0
- package/.morph/framework/standards/backend/database/migrations.md +393 -0
- package/.morph/framework/standards/backend/database/postgresql/database.md +352 -0
- package/.morph/framework/standards/backend/database/repository-patterns.md +528 -0
- package/.morph/framework/standards/backend/database/vector-search-rag.md +541 -0
- package/.morph/framework/standards/backend/dotnet/async.md +366 -0
- package/.morph/framework/standards/backend/dotnet/core.md +117 -0
- package/.morph/framework/standards/backend/dotnet/di.md +439 -0
- package/.morph/framework/standards/backend/dotnet/program-cs-checklist.md +92 -0
- package/.morph/framework/standards/backend/integrations/asaas/asaas-api.md +216 -0
- package/.morph/framework/standards/backend/integrations/clerk/clerk-auth.md +290 -0
- package/.morph/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +350 -0
- package/.morph/framework/standards/backend/integrations/resend/resend-email.md +385 -0
- package/.morph/framework/standards/context/analytics.md +96 -0
- package/.morph/framework/standards/context/bundles.md +110 -0
- package/.morph/framework/standards/context/priming.md +78 -0
- package/.morph/framework/standards/core/architecture.md +185 -0
- package/.morph/framework/standards/core/coding.md +214 -0
- package/.morph/framework/standards/core/git-branching-strategy.md +403 -0
- package/.morph/framework/standards/core/git.md +185 -0
- package/.morph/framework/standards/core/testing.md +295 -0
- package/.morph/framework/standards/data/nosql/blob-storage.md +102 -0
- package/.morph/framework/standards/data/nosql/cache/redis.md +97 -0
- package/.morph/framework/standards/data/nosql/cosmos-db.md +118 -0
- package/.morph/framework/standards/data/vector-search/azure-ai-search.md +121 -0
- package/.morph/framework/standards/data/vector-search/rag-chunking.md +104 -0
- package/.morph/framework/standards/frontend/blazor/design-checklist.md +222 -0
- package/.morph/framework/standards/frontend/blazor/fluent-ui-setup.md +595 -0
- package/.morph/framework/standards/frontend/blazor/fluent-ui.md +137 -0
- package/.morph/framework/standards/frontend/blazor/html-conversion.md +184 -0
- package/.morph/framework/standards/frontend/blazor/lifecycle.md +195 -0
- package/.morph/framework/standards/frontend/blazor/pitfalls.md +198 -0
- package/.morph/framework/standards/frontend/blazor/state.md +191 -0
- package/.morph/framework/standards/frontend/design-system/animations.md +151 -0
- package/.morph/framework/standards/frontend/design-system/naming.md +64 -0
- package/.morph/framework/standards/frontend/nextjs/app-router.md +123 -0
- package/.morph/framework/standards/frontend/nextjs/components.md +132 -0
- package/.morph/framework/standards/frontend/nextjs/data-fetching.md +126 -0
- package/.morph/framework/standards/frontend/nextjs/forms.md +128 -0
- package/.morph/framework/standards/frontend/nextjs/naming-conventions.md +67 -0
- package/.morph/framework/standards/frontend/nextjs/nextjs-patterns.md +215 -0
- package/.morph/framework/standards/frontend/nextjs/project-structure.md +102 -0
- package/.morph/framework/standards/frontend/nextjs/state-management.md +72 -0
- package/.morph/framework/standards/frontend/nextjs/testing.md +111 -0
- package/.morph/framework/standards/infrastructure/azure/azure.md +624 -0
- package/.morph/framework/standards/infrastructure/azure/bicep/bicep-patterns.md +422 -0
- package/.morph/framework/standards/infrastructure/azure/devops/azure-devops-setup.md +516 -0
- package/.morph/framework/standards/infrastructure/azure/devops/local-development.md +520 -0
- package/.morph/framework/standards/infrastructure/azure/services/functions.md +486 -0
- package/.morph/framework/standards/infrastructure/azure/services/service-bus.md +459 -0
- package/.morph/framework/standards/infrastructure/azure/services/storage.md +407 -0
- package/.morph/framework/standards/infrastructure/docker/easypanel-deploy.md +196 -0
- package/.morph/framework/standards/infrastructure/supabase/mcp-setup.md +252 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-auth.md +176 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-pgvector.md +169 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-rls.md +184 -0
- package/.morph/framework/standards/infrastructure/supabase/supabase-storage.md +153 -0
- package/.morph/framework/standards/integration/api/graphql.md +91 -0
- package/.morph/framework/standards/integration/api/grpc.md +114 -0
- package/.morph/framework/standards/integration/api/rest-design.md +95 -0
- package/.morph/framework/standards/integration/event-driven/cqrs.md +101 -0
- package/.morph/framework/standards/integration/event-driven/event-sourcing.md +124 -0
- package/.morph/framework/standards/integration/event-driven/service-bus.md +95 -0
- package/.morph/framework/standards/integration/mcp/mcp-tools.md +384 -0
- package/.morph/framework/standards/observability/logging.md +131 -0
- package/.morph/framework/standards/observability/metrics.md +121 -0
- package/.morph/framework/standards/observability/monitoring.md +114 -0
- package/.morph/framework/standards/observability/tracing.md +132 -0
- package/.morph/framework/standards/workflows/parallel-execution.md +112 -0
- package/.morph/framework/standards/workflows/thread-management.md +113 -0
- package/.morph/framework/templates/.idea/morph-templates.xml +92 -0
- package/.morph/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/.morph/framework/templates/IDE-SNIPPETS.md +266 -0
- package/.morph/framework/templates/README.md +814 -0
- package/.morph/framework/templates/REGISTRY.json +1888 -0
- package/.morph/framework/templates/code/dotnet/backend/repository.cs +141 -0
- package/.morph/framework/templates/code/dotnet/backend/service.cs +139 -0
- package/.morph/framework/templates/code/dotnet/contracts/Commands.cs +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/Entities.cs +25 -0
- package/.morph/framework/templates/code/dotnet/contracts/Queries.cs +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/README.md +74 -0
- package/.morph/framework/templates/code/dotnet/contracts/api-contracts.cs +173 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level1.cs +69 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level2.cs +86 -0
- package/.morph/framework/templates/code/dotnet/contracts/contracts-level3.cs +41 -0
- package/.morph/framework/templates/code/dotnet/database/migration.cs +83 -0
- package/.morph/framework/templates/code/dotnet/frontend/component.razor +239 -0
- package/.morph/framework/templates/code/dotnet/jobs/agent.cs +163 -0
- package/.morph/framework/templates/code/dotnet/jobs/job.cs +171 -0
- package/.morph/framework/templates/code/dotnet/test.cs +239 -0
- package/.morph/framework/templates/code/sql/rls-policy.sql +57 -0
- package/.morph/framework/templates/code/sql/supabase-migration.sql +100 -0
- package/.morph/framework/templates/code/sql/supabase-migration.template.sql +113 -0
- package/.morph/framework/templates/code/typescript/contracts.ts +168 -0
- package/.morph/framework/templates/context/CONTEXT-FEATURE.md +276 -0
- package/.morph/framework/templates/context/CONTEXT.md +181 -0
- package/.morph/framework/templates/docs/clarifications.md +253 -0
- package/.morph/framework/templates/docs/onboarding.md +123 -0
- package/.morph/framework/templates/docs/proposal.md +182 -0
- package/.morph/framework/templates/docs/schema-analysis.md +119 -0
- package/.morph/framework/templates/docs/spec.md +198 -0
- package/.morph/framework/templates/docs/ui-components.md +124 -0
- package/.morph/framework/templates/docs/ui-design-system.md +76 -0
- package/.morph/framework/templates/docs/ui-flows.md +167 -0
- package/.morph/framework/templates/docs/ui-mockups.md +98 -0
- package/.morph/framework/templates/docs/user-stories.md +34 -0
- package/.morph/framework/templates/examples/design-system-examples.md +357 -0
- package/.morph/framework/templates/examples/spec-examples.md +90 -0
- package/.morph/framework/templates/feature/decisions.md +187 -0
- package/.morph/framework/templates/feature/recap.md +146 -0
- package/.morph/framework/templates/feature/tasks.md +199 -0
- package/.morph/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -0
- package/.morph/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -0
- package/.morph/framework/templates/frontend/nextjs/env.mjs.hbs +32 -0
- package/.morph/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -0
- package/.morph/framework/templates/frontend/nextjs/page.tsx.hbs +22 -0
- package/.morph/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -0
- package/.morph/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -0
- package/.morph/framework/templates/infrastructure/azure/Dockerfile.example +82 -0
- package/.morph/framework/templates/infrastructure/azure/README.md +286 -0
- package/.morph/framework/templates/infrastructure/azure/app-insights.bicep +63 -0
- package/.morph/framework/templates/infrastructure/azure/app-service.bicep +164 -0
- package/.morph/framework/templates/infrastructure/azure/container-app-env.bicep +49 -0
- package/.morph/framework/templates/infrastructure/azure/container-app.bicep +156 -0
- package/.morph/framework/templates/infrastructure/azure/deploy-checklist.md +426 -0
- package/.morph/framework/templates/infrastructure/azure/deploy.ps1 +229 -0
- package/.morph/framework/templates/infrastructure/azure/deploy.sh +208 -0
- package/.morph/framework/templates/infrastructure/azure/key-vault.bicep +91 -0
- package/.morph/framework/templates/infrastructure/azure/main.bicep +189 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.dev.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.prod.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/parameters.staging.json +29 -0
- package/.morph/framework/templates/infrastructure/azure/sql-database.bicep +103 -0
- package/.morph/framework/templates/infrastructure/azure/storage.bicep +106 -0
- package/.morph/framework/templates/infrastructure/docker/Dockerfile.template +58 -0
- package/.morph/framework/templates/infrastructure/docker/docker-compose.template.yml +67 -0
- package/.morph/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -0
- package/.morph/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -0
- package/.morph/framework/templates/infrastructure/docker/easypanel.template.json +54 -0
- package/.morph/framework/templates/infrastructure/github/README.md +593 -0
- package/.morph/framework/templates/infrastructure/github/actions/azure-auth/action.yml.hbs +22 -0
- package/.morph/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -0
- package/.morph/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -0
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-azure-app-service.yml.hbs +61 -0
- package/.morph/framework/templates/infrastructure/github/workflows/deploy-easypanel.yml.hbs +31 -0
- package/.morph/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -0
- package/.morph/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -0
- package/.morph/framework/templates/integrations/asaas-client.cs +387 -0
- package/.morph/framework/templates/integrations/asaas-webhook.cs +351 -0
- package/.morph/framework/templates/integrations/azure-identity-config.cs +288 -0
- package/.morph/framework/templates/integrations/clerk-config.cs +258 -0
- package/.morph/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -0
- package/.morph/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-retry.md +78 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-validation.md +97 -0
- package/.morph/framework/templates/meta-prompts/hops/hop-wrapper.md +36 -0
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +113 -0
- package/.morph/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +80 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/backend-squad.md +90 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +126 -0
- package/.morph/framework/templates/meta-prompts/squad-leaders/squad-leader.md +43 -0
- package/.morph/framework/templates/meta-prompts/validators/checkpoint-validator.md +107 -0
- package/.morph/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -0
- package/.morph/framework/templates/project-structure/dotnet-ddd.md +70 -0
- package/.morph/framework/templates/saas/subscription.cs +347 -0
- package/.morph/framework/templates/saas/tenant.cs +338 -0
- package/.morph/framework/templates/state.template.json +17 -0
- package/.morph/framework/templates/ui/FluentDesignTheme.cs +149 -0
- package/.morph/framework/templates/ui/MudTheme.cs +281 -0
- package/.morph/framework/templates/ui/design-system.css +226 -0
- package/.morph/logs/tool-failures.log +17 -0
- package/.morph/memory/pre-compact-2026-02-24T17-43-30-049Z.json +16 -0
- package/.morph/plans/eager-watching-bunny.md +105 -0
- package/.morph/plans/temporal-seeking-nebula.md +45 -0
- package/.morph/state.json +48 -0
- package/CLAUDE.md +1 -1
- package/README.md +2 -2
- package/bin/morph-spec.js +0 -9
- package/framework/CLAUDE.md +1 -1
- package/framework/hooks/README.md +10 -6
- package/framework/hooks/claude-code/notification/approval-reminder.js +2 -0
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +1 -1
- package/framework/hooks/claude-code/stop/validate-completion.js +1 -1
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +1 -1
- package/package.json +1 -1
- package/src/commands/project/init.js +15 -42
- package/src/commands/project/update.js +22 -37
- package/src/lib/installers/mcp-installer.js +18 -3
- package/src/utils/hooks-installer.js +5 -15
- package/src/commands/project/detect.js +0 -114
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Next.js App Router Standard
|
|
2
|
+
|
|
3
|
+
> **Scope:** frontend/nextjs/app-router
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** next.js, app router, server component, client component, page, layout, route
|
|
6
|
+
> **Load When:** editing files in `app/` or `features/` directories
|
|
7
|
+
|
|
8
|
+
Next.js App Router with TypeScript strict mode. Server Components by default, Client Components only when interactivity is required.
|
|
9
|
+
|
|
10
|
+
## Core Rules
|
|
11
|
+
|
|
12
|
+
- ALWAYS default to Server Components — add `'use client'` only when needed
|
|
13
|
+
- ALWAYS keep `app/` directory for routing only — no business logic in page files
|
|
14
|
+
- NEVER put data fetching in Client Components when a Server Component can do it
|
|
15
|
+
- NEVER use `useEffect` to fetch data — use Server Components or TanStack Query
|
|
16
|
+
- ALWAYS co-locate loading/error UI: `loading.tsx`, `error.tsx` next to `page.tsx`
|
|
17
|
+
- ALWAYS use TypeScript — no `.js` or `.jsx` files in the project
|
|
18
|
+
|
|
19
|
+
## Server vs Client Components
|
|
20
|
+
|
|
21
|
+
| Use Server Component | Use Client Component |
|
|
22
|
+
|---------------------|---------------------|
|
|
23
|
+
| Fetching from .NET API on load | onClick, onChange, form submit |
|
|
24
|
+
| Rendering static or user-specific data | useState, useEffect, useRef |
|
|
25
|
+
| Accessing backend environment variables | Browser APIs (localStorage, geolocation) |
|
|
26
|
+
| SEO-critical content | TanStack Query hooks |
|
|
27
|
+
| No interactivity needed | shadcn/ui interactive components |
|
|
28
|
+
|
|
29
|
+
## Decision Tree
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Does this component need user interaction (click, input, hover state)?
|
|
33
|
+
YES → 'use client'
|
|
34
|
+
NO → Does it fetch data?
|
|
35
|
+
YES → Server Component (fetch directly)
|
|
36
|
+
NO → Server Component (static)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## App Directory Structure
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
src/app/
|
|
43
|
+
├── (auth)/
|
|
44
|
+
│ ├── login/
|
|
45
|
+
│ │ └── page.tsx # Server Component — renders login form
|
|
46
|
+
│ └── register/
|
|
47
|
+
│ └── page.tsx
|
|
48
|
+
├── (dashboard)/
|
|
49
|
+
│ ├── layout.tsx # Shared layout for dashboard routes
|
|
50
|
+
│ ├── page.tsx # Dashboard home
|
|
51
|
+
│ └── users/
|
|
52
|
+
│ ├── page.tsx # User list (Server Component)
|
|
53
|
+
│ ├── [id]/
|
|
54
|
+
│ │ └── page.tsx # User detail
|
|
55
|
+
│ ├── loading.tsx # Suspense fallback
|
|
56
|
+
│ └── error.tsx # Error boundary
|
|
57
|
+
└── layout.tsx # Root layout — providers, fonts, metadata
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## File Conventions
|
|
61
|
+
|
|
62
|
+
| File | Purpose | Type |
|
|
63
|
+
|------|---------|------|
|
|
64
|
+
| `page.tsx` | Route segment UI | Server Component (default) |
|
|
65
|
+
| `layout.tsx` | Shared UI wrapper | Server Component |
|
|
66
|
+
| `loading.tsx` | Suspense fallback | Server Component |
|
|
67
|
+
| `error.tsx` | Error boundary | **Must be Client Component** |
|
|
68
|
+
| `not-found.tsx` | 404 page | Server Component |
|
|
69
|
+
| `route.ts` | API route (avoid — use .NET API) | — |
|
|
70
|
+
|
|
71
|
+
## Server Component Data Fetch Pattern
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// app/(dashboard)/users/page.tsx
|
|
75
|
+
import { UserList } from '@/features/users/components/user-list';
|
|
76
|
+
|
|
77
|
+
async function getUsers(): Promise<User[]> {
|
|
78
|
+
const res = await fetch(`${process.env.API_URL}/api/users`, {
|
|
79
|
+
next: { revalidate: 60 },
|
|
80
|
+
});
|
|
81
|
+
if (!res.ok) throw new Error('Failed to fetch users');
|
|
82
|
+
return res.json();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export default async function UsersPage() {
|
|
86
|
+
const users = await getUsers();
|
|
87
|
+
return <UserList initialUsers={users} />;
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Root Layout — Required Providers
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// app/layout.tsx
|
|
95
|
+
import { QueryProvider } from '@/lib/query-client';
|
|
96
|
+
import { Toaster } from '@/components/ui/sonner';
|
|
97
|
+
|
|
98
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
99
|
+
return (
|
|
100
|
+
<html lang="pt-BR">
|
|
101
|
+
<body>
|
|
102
|
+
<QueryProvider>
|
|
103
|
+
{children}
|
|
104
|
+
<Toaster />
|
|
105
|
+
</QueryProvider>
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Common Mistakes
|
|
113
|
+
|
|
114
|
+
| Wrong | Right | Why |
|
|
115
|
+
|-------|-------|-----|
|
|
116
|
+
| `'use client'` on every component | Only add when needed | Kills SSR benefits, bloats JS bundle |
|
|
117
|
+
| `useEffect(() => { fetch(...) }, [])` | Server Component fetch or TanStack Query | Two renders, no caching, no SSR |
|
|
118
|
+
| Business logic in `page.tsx` | Move to `features/` | pages are routes, not controllers |
|
|
119
|
+
| `fetch` without error handling | Always check `res.ok` | Silent failures in production |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,132 @@
|
|
|
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*
|
|
@@ -0,0 +1,126 @@
|
|
|
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*
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Next.js Forms Standard
|
|
2
|
+
|
|
3
|
+
> **Scope:** frontend/nextjs/forms
|
|
4
|
+
> **Layer:** 2 (on keyword)
|
|
5
|
+
> **Keywords:** form, react-hook-form, zod, validation, input, submit, zodResolver
|
|
6
|
+
> **Load When:** implementing any form in Next.js
|
|
7
|
+
|
|
8
|
+
react-hook-form + Zod + shadcn Form components. Schema defines both validation rules and TypeScript types.
|
|
9
|
+
|
|
10
|
+
## Core Rules
|
|
11
|
+
|
|
12
|
+
- ALWAYS define the Zod schema first — derive the TypeScript type from it with `z.infer<>`
|
|
13
|
+
- ALWAYS use `zodResolver` to connect schema to react-hook-form
|
|
14
|
+
- ALWAYS use shadcn `<Form>`, `<FormField>`, `<FormItem>`, `<FormMessage>` for consistent UI
|
|
15
|
+
- NEVER use `useState` for form field values — react-hook-form handles this
|
|
16
|
+
- ALWAYS use `useMutation` from TanStack Query for form submission
|
|
17
|
+
|
|
18
|
+
## Complete Form Pattern
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
// features/users/types/user.schemas.ts
|
|
22
|
+
import { z } from 'zod';
|
|
23
|
+
|
|
24
|
+
export const createUserSchema = z.object({
|
|
25
|
+
name: z.string().min(2, 'Name must be at least 2 characters'),
|
|
26
|
+
email: z.string().email('Invalid email address'),
|
|
27
|
+
role: z.enum(['admin', 'user'], { required_error: 'Role is required' }),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export type CreateUserInput = z.infer<typeof createUserSchema>;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
// features/users/components/create-user-form.tsx
|
|
35
|
+
'use client';
|
|
36
|
+
|
|
37
|
+
import { useForm } from 'react-hook-form';
|
|
38
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
39
|
+
import {
|
|
40
|
+
Form, FormControl, FormField, FormItem, FormLabel, FormMessage
|
|
41
|
+
} from '@/components/ui/form';
|
|
42
|
+
import { Input } from '@/components/ui/input';
|
|
43
|
+
import { Button } from '@/components/ui/button';
|
|
44
|
+
import { useCreateUser } from '@/features/users/hooks/use-create-user';
|
|
45
|
+
import { createUserSchema, type CreateUserInput } from '@/features/users/types/user.schemas';
|
|
46
|
+
|
|
47
|
+
export function CreateUserForm({ onSuccess }: { onSuccess?: () => void }) {
|
|
48
|
+
const form = useForm<CreateUserInput>({
|
|
49
|
+
resolver: zodResolver(createUserSchema),
|
|
50
|
+
defaultValues: { name: '', email: '', role: 'user' },
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const { mutate: createUser, isPending } = useCreateUser();
|
|
54
|
+
|
|
55
|
+
function onSubmit(values: CreateUserInput) {
|
|
56
|
+
createUser(values, {
|
|
57
|
+
onSuccess: () => { form.reset(); onSuccess?.(); },
|
|
58
|
+
onError: (error) => form.setError('root', { message: error.message }),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Form {...form}>
|
|
64
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
65
|
+
<FormField
|
|
66
|
+
control={form.control}
|
|
67
|
+
name="name"
|
|
68
|
+
render={({ field }) => (
|
|
69
|
+
<FormItem>
|
|
70
|
+
<FormLabel>Name</FormLabel>
|
|
71
|
+
<FormControl><Input placeholder="John Doe" {...field} /></FormControl>
|
|
72
|
+
<FormMessage />
|
|
73
|
+
</FormItem>
|
|
74
|
+
)}
|
|
75
|
+
/>
|
|
76
|
+
<FormField
|
|
77
|
+
control={form.control}
|
|
78
|
+
name="email"
|
|
79
|
+
render={({ field }) => (
|
|
80
|
+
<FormItem>
|
|
81
|
+
<FormLabel>Email</FormLabel>
|
|
82
|
+
<FormControl><Input type="email" {...field} /></FormControl>
|
|
83
|
+
<FormMessage />
|
|
84
|
+
</FormItem>
|
|
85
|
+
)}
|
|
86
|
+
/>
|
|
87
|
+
{form.formState.errors.root && (
|
|
88
|
+
<p className="text-sm text-destructive">{form.formState.errors.root.message}</p>
|
|
89
|
+
)}
|
|
90
|
+
<Button type="submit" disabled={isPending}>
|
|
91
|
+
{isPending ? 'Creating...' : 'Create User'}
|
|
92
|
+
</Button>
|
|
93
|
+
</form>
|
|
94
|
+
</Form>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Edit Form Pattern
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// Edit forms reuse the same schema with partial()
|
|
103
|
+
const form = useForm<UpdateUserInput>({
|
|
104
|
+
resolver: zodResolver(updateUserSchema),
|
|
105
|
+
defaultValues: { name: user.name, email: user.email },
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Schema Composition
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
export const userSchema = z.object({ id: z.string(), name: z.string(), email: z.string() });
|
|
113
|
+
export const createUserSchema = userSchema.omit({ id: true });
|
|
114
|
+
export const updateUserSchema = createUserSchema.partial();
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Common Mistakes
|
|
118
|
+
|
|
119
|
+
| Wrong | Right | Why |
|
|
120
|
+
|-------|-------|-----|
|
|
121
|
+
| `useState` for each field | `useForm` | Unnecessary re-renders on every keystroke |
|
|
122
|
+
| Manual error display | `<FormMessage />` | Consistent UI, auto-connects to field errors |
|
|
123
|
+
| `fetch` in submit handler | `useMutation` | Loading state, error handling, cache invalidation |
|
|
124
|
+
| Type written manually | `z.infer<typeof schema>` | Single source of truth |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,67 @@
|
|
|
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*
|