@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zod-validation
|
|
3
|
+
description: |
|
|
4
|
+
Zod validation patterns for this Next.js application.
|
|
5
|
+
Covers schema definition, API validation, form integration, error formatting, and type inference.
|
|
6
|
+
Use this skill when implementing validation for APIs, forms, or entity schemas.
|
|
7
|
+
allowed-tools: Read, Glob, Grep
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Zod Validation Skill
|
|
12
|
+
|
|
13
|
+
Patterns for implementing type-safe validation with Zod across APIs, forms, and entity schemas.
|
|
14
|
+
|
|
15
|
+
## Architecture Overview
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
ZOD VALIDATION LAYERS:
|
|
19
|
+
|
|
20
|
+
API Layer:
|
|
21
|
+
├── Request body validation
|
|
22
|
+
├── Query parameter validation
|
|
23
|
+
└── Response type safety
|
|
24
|
+
|
|
25
|
+
Entity Layer:
|
|
26
|
+
├── Entity field validation
|
|
27
|
+
├── Create/Update schemas
|
|
28
|
+
└── Custom field validators
|
|
29
|
+
|
|
30
|
+
Form Layer:
|
|
31
|
+
├── React Hook Form integration
|
|
32
|
+
├── zodResolver for validation
|
|
33
|
+
└── Field-level error display
|
|
34
|
+
|
|
35
|
+
Block Layer:
|
|
36
|
+
├── baseBlockSchema extensions
|
|
37
|
+
├── Field definition schemas
|
|
38
|
+
└── Block-specific validations
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## When to Use This Skill
|
|
42
|
+
|
|
43
|
+
- Validating API request bodies
|
|
44
|
+
- Creating entity schemas
|
|
45
|
+
- Implementing form validation
|
|
46
|
+
- Extending block schemas
|
|
47
|
+
- Type inference from schemas
|
|
48
|
+
|
|
49
|
+
## Common Schema Patterns
|
|
50
|
+
|
|
51
|
+
### Basic Schemas
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { z } from 'zod'
|
|
55
|
+
|
|
56
|
+
// Simple object schema
|
|
57
|
+
const userSchema = z.object({
|
|
58
|
+
name: z.string().min(1, 'Name is required'),
|
|
59
|
+
email: z.string().email('Invalid email format'),
|
|
60
|
+
age: z.number().int().min(18, 'Must be 18+').optional(),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// With enum validation
|
|
64
|
+
const statusSchema = z.enum(['active', 'inactive', 'pending'])
|
|
65
|
+
|
|
66
|
+
// With defaults
|
|
67
|
+
const settingsSchema = z.object({
|
|
68
|
+
theme: z.enum(['light', 'dark']).default('light'),
|
|
69
|
+
notifications: z.boolean().default(true),
|
|
70
|
+
language: z.string().default('en'),
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
// Nullable vs Optional
|
|
74
|
+
const profileSchema = z.object({
|
|
75
|
+
bio: z.string().optional(), // undefined allowed
|
|
76
|
+
avatar: z.string().nullable(), // null allowed
|
|
77
|
+
phone: z.string().nullish(), // null or undefined
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Transformations
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Transform input
|
|
85
|
+
const slugSchema = z.string()
|
|
86
|
+
.transform(s => s.toLowerCase().replace(/\s+/g, '-'))
|
|
87
|
+
|
|
88
|
+
// Trim whitespace
|
|
89
|
+
const cleanStringSchema = z.string().trim()
|
|
90
|
+
|
|
91
|
+
// Parse JSON string
|
|
92
|
+
const jsonSchema = z.string().transform(s => JSON.parse(s))
|
|
93
|
+
|
|
94
|
+
// Coerce types
|
|
95
|
+
const numberFromString = z.coerce.number() // "123" → 123
|
|
96
|
+
const dateFromString = z.coerce.date() // "2024-01-01" → Date
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Refinements
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Single refinement
|
|
103
|
+
const passwordSchema = z.string()
|
|
104
|
+
.min(8, 'Minimum 8 characters')
|
|
105
|
+
.refine(p => /[A-Z]/.test(p), 'Must contain uppercase')
|
|
106
|
+
.refine(p => /[0-9]/.test(p), 'Must contain number')
|
|
107
|
+
.refine(p => /[!@#$%^&*]/.test(p), 'Must contain special character')
|
|
108
|
+
|
|
109
|
+
// Cross-field validation
|
|
110
|
+
const signupSchema = z.object({
|
|
111
|
+
password: z.string().min(8),
|
|
112
|
+
confirmPassword: z.string(),
|
|
113
|
+
}).refine(data => data.password === data.confirmPassword, {
|
|
114
|
+
message: "Passwords don't match",
|
|
115
|
+
path: ['confirmPassword'], // Error appears on this field
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Async refinement (DB check)
|
|
119
|
+
const uniqueEmailSchema = z.string().email().refine(
|
|
120
|
+
async (email) => {
|
|
121
|
+
const exists = await checkEmailExists(email)
|
|
122
|
+
return !exists
|
|
123
|
+
},
|
|
124
|
+
{ message: 'Email already registered' }
|
|
125
|
+
)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Composition
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// Extend schema
|
|
132
|
+
const baseUserSchema = z.object({
|
|
133
|
+
email: z.string().email(),
|
|
134
|
+
name: z.string().min(1),
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
const adminSchema = baseUserSchema.extend({
|
|
138
|
+
role: z.literal('admin'),
|
|
139
|
+
permissions: z.array(z.string()),
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Merge schemas
|
|
143
|
+
const fullSchema = schemaA.merge(schemaB)
|
|
144
|
+
|
|
145
|
+
// Pick/Omit fields
|
|
146
|
+
const loginSchema = userSchema.pick({ email: true, password: true })
|
|
147
|
+
const publicUserSchema = userSchema.omit({ password: true })
|
|
148
|
+
|
|
149
|
+
// Partial (all optional)
|
|
150
|
+
const updateUserSchema = userSchema.partial()
|
|
151
|
+
|
|
152
|
+
// Required (all required)
|
|
153
|
+
const strictSchema = partialSchema.required()
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Type Inference
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Infer type from schema
|
|
160
|
+
const userSchema = z.object({
|
|
161
|
+
name: z.string(),
|
|
162
|
+
email: z.string().email(),
|
|
163
|
+
age: z.number().optional(),
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
type User = z.infer<typeof userSchema>
|
|
167
|
+
// Result: { name: string; email: string; age?: number }
|
|
168
|
+
|
|
169
|
+
// Input vs Output types (when using transform)
|
|
170
|
+
type UserInput = z.input<typeof userSchema> // Before transform
|
|
171
|
+
type UserOutput = z.output<typeof userSchema> // After transform
|
|
172
|
+
|
|
173
|
+
// Partial type
|
|
174
|
+
type UpdateUser = z.infer<typeof userSchema.partial()>
|
|
175
|
+
// Result: { name?: string; email?: string; age?: number }
|
|
176
|
+
|
|
177
|
+
// Pick specific fields
|
|
178
|
+
type UserName = z.infer<typeof userSchema.pick({ name: true })>
|
|
179
|
+
// Result: { name: string }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## API Validation Pattern
|
|
183
|
+
|
|
184
|
+
### Standard Pattern (safeParse)
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { z } from 'zod'
|
|
188
|
+
import { NextRequest } from 'next/server'
|
|
189
|
+
import { createApiError, createApiResponse } from '@/core/lib/api/helpers'
|
|
190
|
+
|
|
191
|
+
// Define schema
|
|
192
|
+
const createCustomerSchema = z.object({
|
|
193
|
+
name: z.string().min(1, 'Name is required'),
|
|
194
|
+
email: z.string().email('Invalid email format'),
|
|
195
|
+
phone: z.string().optional(),
|
|
196
|
+
notes: z.string().max(500).optional(),
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
export async function POST(request: NextRequest) {
|
|
200
|
+
// 1. Parse request body
|
|
201
|
+
const body = await request.json()
|
|
202
|
+
|
|
203
|
+
// 2. Validate with safeParse (no exceptions)
|
|
204
|
+
const result = createCustomerSchema.safeParse(body)
|
|
205
|
+
|
|
206
|
+
// 3. Handle validation error
|
|
207
|
+
if (!result.success) {
|
|
208
|
+
return createApiError(
|
|
209
|
+
'Validation error',
|
|
210
|
+
400,
|
|
211
|
+
result.error.issues, // Array of ZodIssue
|
|
212
|
+
'VALIDATION_ERROR'
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// 4. Use validated, type-safe data
|
|
217
|
+
const validatedData = result.data // Fully typed!
|
|
218
|
+
|
|
219
|
+
// 5. Process
|
|
220
|
+
const customer = await CustomerService.create(
|
|
221
|
+
userId,
|
|
222
|
+
teamId,
|
|
223
|
+
validatedData
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return createApiResponse(customer, 201)
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Query Parameters
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const listQuerySchema = z.object({
|
|
234
|
+
page: z.coerce.number().int().min(1).default(1),
|
|
235
|
+
limit: z.coerce.number().int().min(1).max(100).default(20),
|
|
236
|
+
sort: z.enum(['createdAt', 'name', 'updatedAt']).default('createdAt'),
|
|
237
|
+
order: z.enum(['asc', 'desc']).default('desc'),
|
|
238
|
+
search: z.string().optional(),
|
|
239
|
+
status: z.enum(['active', 'inactive', 'all']).default('all'),
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
export async function GET(request: NextRequest) {
|
|
243
|
+
const { searchParams } = new URL(request.url)
|
|
244
|
+
const queryObject = Object.fromEntries(searchParams)
|
|
245
|
+
|
|
246
|
+
const result = listQuerySchema.safeParse(queryObject)
|
|
247
|
+
if (!result.success) {
|
|
248
|
+
return createApiError('Invalid query parameters', 400, result.error.issues)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const { page, limit, sort, order, search, status } = result.data
|
|
252
|
+
// ... use validated params
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Form Integration (React Hook Form)
|
|
257
|
+
|
|
258
|
+
### Basic Setup
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
'use client'
|
|
262
|
+
|
|
263
|
+
import { useForm } from 'react-hook-form'
|
|
264
|
+
import { zodResolver } from '@hookform/resolvers/zod'
|
|
265
|
+
import { z } from 'zod'
|
|
266
|
+
|
|
267
|
+
// 1. Define schema
|
|
268
|
+
const loginSchema = z.object({
|
|
269
|
+
email: z.string().email('Invalid email'),
|
|
270
|
+
password: z.string().min(6, 'Minimum 6 characters'),
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// 2. Infer type
|
|
274
|
+
type LoginFormData = z.infer<typeof loginSchema>
|
|
275
|
+
|
|
276
|
+
// 3. Use in component
|
|
277
|
+
function LoginForm() {
|
|
278
|
+
const {
|
|
279
|
+
register,
|
|
280
|
+
handleSubmit,
|
|
281
|
+
formState: { errors, isSubmitting },
|
|
282
|
+
} = useForm<LoginFormData>({
|
|
283
|
+
resolver: zodResolver(loginSchema),
|
|
284
|
+
mode: 'onSubmit',
|
|
285
|
+
reValidateMode: 'onChange',
|
|
286
|
+
defaultValues: {
|
|
287
|
+
email: '',
|
|
288
|
+
password: '',
|
|
289
|
+
},
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const onSubmit = async (data: LoginFormData) => {
|
|
293
|
+
// data is fully typed and validated
|
|
294
|
+
await signIn(data.email, data.password)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return (
|
|
298
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
299
|
+
<input {...register('email')} />
|
|
300
|
+
{errors.email && (
|
|
301
|
+
<p className="text-sm text-destructive">{errors.email.message}</p>
|
|
302
|
+
)}
|
|
303
|
+
|
|
304
|
+
<input type="password" {...register('password')} />
|
|
305
|
+
{errors.password && (
|
|
306
|
+
<p className="text-sm text-destructive">{errors.password.message}</p>
|
|
307
|
+
)}
|
|
308
|
+
|
|
309
|
+
<button type="submit" disabled={isSubmitting}>
|
|
310
|
+
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
|
311
|
+
</button>
|
|
312
|
+
</form>
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### With shadcn/ui Form Components
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/core/components/ui/form'
|
|
321
|
+
import { Input } from '@/core/components/ui/input'
|
|
322
|
+
import { Button } from '@/core/components/ui/button'
|
|
323
|
+
|
|
324
|
+
function CustomerForm() {
|
|
325
|
+
const form = useForm<CustomerFormData>({
|
|
326
|
+
resolver: zodResolver(customerSchema),
|
|
327
|
+
defaultValues: { name: '', email: '' },
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
return (
|
|
331
|
+
<Form {...form}>
|
|
332
|
+
<form onSubmit={form.handleSubmit(onSubmit)}>
|
|
333
|
+
<FormField
|
|
334
|
+
control={form.control}
|
|
335
|
+
name="name"
|
|
336
|
+
render={({ field }) => (
|
|
337
|
+
<FormItem>
|
|
338
|
+
<FormLabel>Name</FormLabel>
|
|
339
|
+
<FormControl>
|
|
340
|
+
<Input {...field} />
|
|
341
|
+
</FormControl>
|
|
342
|
+
<FormMessage /> {/* Auto-displays error */}
|
|
343
|
+
</FormItem>
|
|
344
|
+
)}
|
|
345
|
+
/>
|
|
346
|
+
<Button type="submit">Save</Button>
|
|
347
|
+
</form>
|
|
348
|
+
</Form>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Error Formatting
|
|
354
|
+
|
|
355
|
+
### ZodError Structure
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
// When safeParse fails:
|
|
359
|
+
result.error // ZodError instance
|
|
360
|
+
result.error.issues // Array<ZodIssue>
|
|
361
|
+
|
|
362
|
+
// ZodIssue structure:
|
|
363
|
+
interface ZodIssue {
|
|
364
|
+
code: string // "too_small", "invalid_type", "custom"
|
|
365
|
+
message: string // Error message
|
|
366
|
+
path: (string|number)[] // Field path: ["user", "email"]
|
|
367
|
+
minimum?: number // For min validations
|
|
368
|
+
maximum?: number // For max validations
|
|
369
|
+
type?: string // Expected type
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Flatten for API Responses
|
|
374
|
+
|
|
375
|
+
```typescript
|
|
376
|
+
const result = schema.safeParse(data)
|
|
377
|
+
|
|
378
|
+
if (!result.success) {
|
|
379
|
+
// Flatten for cleaner API response
|
|
380
|
+
const flattened = result.error.flatten()
|
|
381
|
+
// {
|
|
382
|
+
// formErrors: [], // Root-level errors
|
|
383
|
+
// fieldErrors: {
|
|
384
|
+
// email: ['Invalid email'],
|
|
385
|
+
// name: ['Required']
|
|
386
|
+
// }
|
|
387
|
+
// }
|
|
388
|
+
|
|
389
|
+
return createApiError('Validation error', 400, flattened.fieldErrors)
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Format for Display
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
const formatted = result.error.format()
|
|
397
|
+
// {
|
|
398
|
+
// _errors: [], // Root errors
|
|
399
|
+
// email: { _errors: ['Invalid email'] },
|
|
400
|
+
// name: { _errors: ['Required'] }
|
|
401
|
+
// }
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Block Schema Extension
|
|
405
|
+
|
|
406
|
+
### Using baseBlockSchema
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { z } from 'zod'
|
|
410
|
+
import { baseBlockSchema } from '@/core/types/blocks'
|
|
411
|
+
|
|
412
|
+
// baseBlockSchema provides:
|
|
413
|
+
// - title: z.string().optional()
|
|
414
|
+
// - content: z.string().optional()
|
|
415
|
+
// - cta: z.object({ text, link, target }).optional()
|
|
416
|
+
// - backgroundColor: z.enum([...]).optional()
|
|
417
|
+
// - className: z.string().optional()
|
|
418
|
+
// - id: z.string().optional()
|
|
419
|
+
|
|
420
|
+
// Extend with custom fields
|
|
421
|
+
export const schema = baseBlockSchema.merge(z.object({
|
|
422
|
+
// Custom fields only - base fields inherited
|
|
423
|
+
features: z.array(z.object({
|
|
424
|
+
icon: z.string().optional(),
|
|
425
|
+
title: z.string().min(1, 'Title required'),
|
|
426
|
+
description: z.string().optional(),
|
|
427
|
+
})).min(1).max(12),
|
|
428
|
+
columns: z.enum(['2', '3', '4']).default('3'),
|
|
429
|
+
showIcons: z.boolean().default(true),
|
|
430
|
+
}))
|
|
431
|
+
|
|
432
|
+
export type FeaturesGridProps = z.infer<typeof schema>
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## i18n Error Messages
|
|
436
|
+
|
|
437
|
+
### Dynamic Schema with Translations
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
// Function that returns schema with translated messages
|
|
441
|
+
export function createLoginSchema(t: (key: string) => string) {
|
|
442
|
+
return z.object({
|
|
443
|
+
email: z.string()
|
|
444
|
+
.min(1, t('validation.email.required'))
|
|
445
|
+
.email(t('validation.email.invalid')),
|
|
446
|
+
password: z.string()
|
|
447
|
+
.min(1, t('validation.password.required'))
|
|
448
|
+
.min(6, t('validation.password.minLength')),
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Usage in component
|
|
453
|
+
const { t } = useTranslations('auth')
|
|
454
|
+
const schema = createLoginSchema(t)
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
## Existing Schemas
|
|
458
|
+
|
|
459
|
+
| Schema | Location | Purpose |
|
|
460
|
+
|--------|----------|---------|
|
|
461
|
+
| `baseBlockSchema` | `core/types/blocks.ts` | Base for all blocks |
|
|
462
|
+
| `ctaSchema` | `core/types/blocks.ts` | CTA button definition |
|
|
463
|
+
| `teamSchema` | `core/lib/teams/schema.ts` | Team CRUD validation |
|
|
464
|
+
| `teamRoleSchema` | `core/lib/teams/schema.ts` | Role validation |
|
|
465
|
+
| `subscriptionStatusSchema` | `core/lib/billing/schema.ts` | Subscription states |
|
|
466
|
+
| `createPlanSchema` | `core/lib/billing/schema.ts` | Plan creation |
|
|
467
|
+
| `loginSchema` | `core/lib/validation-schemas.ts` | Login form |
|
|
468
|
+
| `signupSchema` | `core/lib/validation-schemas.ts` | Signup form |
|
|
469
|
+
|
|
470
|
+
## Anti-Patterns
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
// NEVER: Use parse() in APIs (throws exceptions)
|
|
474
|
+
const data = schema.parse(body) // Throws on error!
|
|
475
|
+
|
|
476
|
+
// CORRECT: Use safeParse (returns result object)
|
|
477
|
+
const result = schema.safeParse(body)
|
|
478
|
+
if (!result.success) { /* handle error */ }
|
|
479
|
+
|
|
480
|
+
// NEVER: Catch and ignore validation errors
|
|
481
|
+
try {
|
|
482
|
+
const data = schema.parse(body)
|
|
483
|
+
} catch (e) {
|
|
484
|
+
// Don't swallow errors!
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// CORRECT: Handle errors explicitly
|
|
488
|
+
const result = schema.safeParse(body)
|
|
489
|
+
if (!result.success) {
|
|
490
|
+
return createApiError('Validation error', 400, result.error.issues)
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// NEVER: Duplicate base schema fields
|
|
494
|
+
const blockSchema = z.object({
|
|
495
|
+
title: z.string(), // Already in baseBlockSchema!
|
|
496
|
+
content: z.string(),
|
|
497
|
+
// ...custom fields
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
// CORRECT: Extend baseBlockSchema
|
|
501
|
+
const blockSchema = baseBlockSchema.merge(z.object({
|
|
502
|
+
// Only custom fields
|
|
503
|
+
customField: z.string(),
|
|
504
|
+
}))
|
|
505
|
+
|
|
506
|
+
// NEVER: Hardcode error messages without i18n support
|
|
507
|
+
const schema = z.string().min(1, 'Este campo es requerido')
|
|
508
|
+
|
|
509
|
+
// CORRECT: Use translation function
|
|
510
|
+
const schema = z.string().min(1, t('validation.required'))
|
|
511
|
+
|
|
512
|
+
// NEVER: Skip type inference
|
|
513
|
+
const handleSubmit = (data: any) => { ... }
|
|
514
|
+
|
|
515
|
+
// CORRECT: Use inferred types
|
|
516
|
+
type FormData = z.infer<typeof schema>
|
|
517
|
+
const handleSubmit = (data: FormData) => { ... }
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## Checklist
|
|
521
|
+
|
|
522
|
+
Before finalizing validation implementation:
|
|
523
|
+
|
|
524
|
+
- [ ] Using `safeParse` instead of `parse` in APIs
|
|
525
|
+
- [ ] Type inference with `z.infer<typeof schema>`
|
|
526
|
+
- [ ] Error messages support i18n
|
|
527
|
+
- [ ] Block schemas extend `baseBlockSchema`
|
|
528
|
+
- [ ] Form uses `zodResolver` with React Hook Form
|
|
529
|
+
- [ ] API returns `error.issues` in response
|
|
530
|
+
- [ ] Cross-field validations use `.refine()` with proper `path`
|
|
531
|
+
- [ ] Optional vs nullable used correctly
|
|
532
|
+
|
|
533
|
+
## Related Skills
|
|
534
|
+
|
|
535
|
+
- `entity-api` - API endpoint patterns with validation
|
|
536
|
+
- `shadcn-components` - Form component patterns
|
|
537
|
+
- `page-builder-blocks` - Block schema patterns
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Block: {{SESSION_NAME}}
|
|
2
|
+
|
|
3
|
+
**Session:** `.claude/sessions/blocks/{{SESSION_FULL}}/`
|
|
4
|
+
**Workflow:** BLOCKS
|
|
5
|
+
**T-Shirt Size:** {{TSHIRT}}
|
|
6
|
+
**Created:** {{DATE}}
|
|
7
|
+
**Status:** In Progress
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Discovery Answers
|
|
12
|
+
|
|
13
|
+
| Question | Answer |
|
|
14
|
+
|----------|--------|
|
|
15
|
+
| **Mock Source** | {{MOCK_SOURCE}} |
|
|
16
|
+
| **Block Type** | {{BLOCK_TYPE}} |
|
|
17
|
+
| **Block Decision** | {{BLOCK_DECISION}} |
|
|
18
|
+
| **Requirements** | {{REQUIREMENTS}} |
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Mock Files
|
|
23
|
+
|
|
24
|
+
| File | Status |
|
|
25
|
+
|------|--------|
|
|
26
|
+
| [Awaiting upload] | Pending |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Analysis Results
|
|
31
|
+
|
|
32
|
+
**Status:** Pending mock upload
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Development Progress
|
|
37
|
+
|
|
38
|
+
- [ ] Mock uploaded
|
|
39
|
+
- [ ] Mock analyzed
|
|
40
|
+
- [ ] analysis.json generated
|
|
41
|
+
- [ ] ds-mapping.json generated
|
|
42
|
+
- [ ] block-plan.json generated
|
|
43
|
+
- [ ] Block config created
|
|
44
|
+
- [ ] Block schema defined
|
|
45
|
+
- [ ] Fields configured
|
|
46
|
+
- [ ] Component implemented
|
|
47
|
+
- [ ] Registry rebuilt
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Visual Validation
|
|
52
|
+
|
|
53
|
+
| Attempt | Result | Notes |
|
|
54
|
+
|---------|--------|-------|
|
|
55
|
+
| - | - | - |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Checklist
|
|
60
|
+
|
|
61
|
+
- [ ] Discovery complete
|
|
62
|
+
- [ ] Mock uploaded
|
|
63
|
+
- [ ] Analysis complete
|
|
64
|
+
- [ ] Block created/modified
|
|
65
|
+
- [ ] Visual validation passed
|
|
66
|
+
- [ ] Ready for commit
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Changes Made
|
|
71
|
+
|
|
72
|
+
### Block Files
|
|
73
|
+
- [Pending implementation]
|
|
74
|
+
|
|
75
|
+
### Registry
|
|
76
|
+
- [Pending rebuild]
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Notes
|
|
81
|
+
|
|
82
|
+
[Additional context]
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
**Last Updated:** {{DATE}}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Changes: Iteration {{ITERATION}}-{{ITERATION_NAME}}
|
|
2
|
+
|
|
3
|
+
**Session:** {{SESSION_FULL}}
|
|
4
|
+
**Iteration:** {{ITERATION}}-{{ITERATION_NAME}}
|
|
5
|
+
**Started:** {{DATE}}
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Summary
|
|
10
|
+
|
|
11
|
+
[Brief description of what was changed in this iteration]
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Files Modified
|
|
16
|
+
|
|
17
|
+
| File | Type | Description |
|
|
18
|
+
|------|------|-------------|
|
|
19
|
+
| `path/to/file.ts` | Modified | [What changed] |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Files Created
|
|
24
|
+
|
|
25
|
+
| File | Type | Description |
|
|
26
|
+
|------|------|-------------|
|
|
27
|
+
| `path/to/new-file.ts` | New | [Purpose] |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Files Deleted
|
|
32
|
+
|
|
33
|
+
| File | Reason |
|
|
34
|
+
|------|--------|
|
|
35
|
+
| `path/to/old-file.ts` | [Why it was deleted] |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Database Changes
|
|
40
|
+
|
|
41
|
+
- [ ] New migration: `migrations/0XX_name.sql`
|
|
42
|
+
- [ ] Sample data updated
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Tests Added/Modified
|
|
47
|
+
|
|
48
|
+
| Test File | Type | Description |
|
|
49
|
+
|-----------|------|-------------|
|
|
50
|
+
| `__tests__/xxx.test.ts` | Unit | [What it tests] |
|
|
51
|
+
| `cypress/e2e/xxx.cy.ts` | E2E | [What it tests] |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Notes
|
|
56
|
+
|
|
57
|
+
[Additional context about changes]
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
**Last Updated:** {{DATE}}
|