cfsa-antigravity 1.0.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.
- package/bin/cli.mjs +324 -0
- package/package.json +34 -0
- package/template/.agent/instructions/commands.md +48 -0
- package/template/.agent/instructions/patterns.md +61 -0
- package/template/.agent/instructions/structure.md +29 -0
- package/template/.agent/instructions/tech-stack.md +43 -0
- package/template/.agent/instructions/workflow.md +41 -0
- package/template/.agent/kit-sync.md +15 -0
- package/template/.agent/rules/boundary-not-placeholder.md +146 -0
- package/template/.agent/rules/completion-checklist.md +48 -0
- package/template/.agent/rules/decision-classification.md +103 -0
- package/template/.agent/rules/extensibility.md +47 -0
- package/template/.agent/rules/question-vs-command.md +81 -0
- package/template/.agent/rules/security-first.md +43 -0
- package/template/.agent/rules/specificity-standards.md +54 -0
- package/template/.agent/rules/tdd-contract-first.md +57 -0
- package/template/.agent/rules/vertical-slices.md +42 -0
- package/template/.agent/skill-library/MANIFEST.md +480 -0
- package/template/.agent/skill-library/README.md +38 -0
- package/template/.agent/skill-library/meta/brand-guidelines/SKILL.md +73 -0
- package/template/.agent/skill-library/meta/claude-code/README.md +9 -0
- package/template/.agent/skill-library/meta/claude-code/agent-development/SKILL.md +415 -0
- package/template/.agent/skill-library/meta/claude-code/hook-development/SKILL.md +712 -0
- package/template/.agent/skill-library/meta/claude-code/plugin-structure/SKILL.md +476 -0
- package/template/.agent/skill-library/meta/git-advanced/SKILL.md +972 -0
- package/template/.agent/skill-library/meta/mcp-builder/SKILL.md +236 -0
- package/template/.agent/skill-library/meta/product-marketing-context/SKILL.md +241 -0
- package/template/.agent/skill-library/meta/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skill-library/meta/tmux-processes/SKILL.md +210 -0
- package/template/.agent/skill-library/meta/using-tmux-for-interactive-commands/SKILL.md +178 -0
- package/template/.agent/skill-library/stack/3d/threejs-pro/SKILL.md +300 -0
- package/template/.agent/skill-library/stack/ai/ai-sdk/SKILL.md +77 -0
- package/template/.agent/skill-library/stack/ai/langchain/SKILL.md +530 -0
- package/template/.agent/skill-library/stack/ai/ollama/SKILL.md +321 -0
- package/template/.agent/skill-library/stack/ai/openai-sdk/SKILL.md +549 -0
- package/template/.agent/skill-library/stack/analytics/google-analytics/SKILL.md +153 -0
- package/template/.agent/skill-library/stack/api/graphql/SKILL.md +1061 -0
- package/template/.agent/skill-library/stack/api/trpc/SKILL.md +576 -0
- package/template/.agent/skill-library/stack/auth/authjs/SKILL.md +569 -0
- package/template/.agent/skill-library/stack/auth/clerk/SKILL.md +590 -0
- package/template/.agent/skill-library/stack/auth/firebase-auth/SKILL.md +734 -0
- package/template/.agent/skill-library/stack/cms/payload-cms/SKILL.md +573 -0
- package/template/.agent/skill-library/stack/cms/shopify/SKILL.md +1193 -0
- package/template/.agent/skill-library/stack/cms/wordpress/SKILL.md +1104 -0
- package/template/.agent/skill-library/stack/css/sass-scss/SKILL.md +1121 -0
- package/template/.agent/skill-library/stack/css/tailwind-css-patterns/SKILL.md +863 -0
- package/template/.agent/skill-library/stack/css/tailwind-design-system/SKILL.md +490 -0
- package/template/.agent/skill-library/stack/css/vanilla-css/SKILL.md +1078 -0
- package/template/.agent/skill-library/stack/databases/clickhouse/SKILL.md +311 -0
- package/template/.agent/skill-library/stack/databases/influxdb/SKILL.md +280 -0
- package/template/.agent/skill-library/stack/databases/lancedb/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/databases/mongodb/SKILL.md +1169 -0
- package/template/.agent/skill-library/stack/databases/neo4j/SKILL.md +839 -0
- package/template/.agent/skill-library/stack/databases/pgvector/SKILL.md +241 -0
- package/template/.agent/skill-library/stack/databases/pinecone/SKILL.md +212 -0
- package/template/.agent/skill-library/stack/databases/postgresql/SKILL.md +658 -0
- package/template/.agent/skill-library/stack/databases/qdrant/SKILL.md +312 -0
- package/template/.agent/skill-library/stack/databases/redis/SKILL.md +1079 -0
- package/template/.agent/skill-library/stack/databases/spacetimedb/SKILL.md +532 -0
- package/template/.agent/skill-library/stack/databases/sqlite/SKILL.md +1132 -0
- package/template/.agent/skill-library/stack/databases/supabase/SKILL.md +640 -0
- package/template/.agent/skill-library/stack/databases/surrealdb-expert/SKILL.md +945 -0
- package/template/.agent/skill-library/stack/databases/timescaledb/SKILL.md +745 -0
- package/template/.agent/skill-library/stack/databases/weaviate/SKILL.md +218 -0
- package/template/.agent/skill-library/stack/devops/github-actions/SKILL.md +554 -0
- package/template/.agent/skill-library/stack/devops/kubernetes/SKILL.md +950 -0
- package/template/.agent/skill-library/stack/devops/nginx/SKILL.md +841 -0
- package/template/.agent/skill-library/stack/devops/terraform/SKILL.md +860 -0
- package/template/.agent/skill-library/stack/email/resend/SKILL.md +391 -0
- package/template/.agent/skill-library/stack/engines/godot/SKILL.md +488 -0
- package/template/.agent/skill-library/stack/extensions/chrome-extension/SKILL.md +375 -0
- package/template/.agent/skill-library/stack/extensions/vscode-extension/SKILL.md +453 -0
- package/template/.agent/skill-library/stack/frameworks/astro-framework/SKILL.md +162 -0
- package/template/.agent/skill-library/stack/frameworks/electron/SKILL.md +1286 -0
- package/template/.agent/skill-library/stack/frameworks/fastapi/SKILL.md +650 -0
- package/template/.agent/skill-library/stack/frameworks/hono/SKILL.md +90 -0
- package/template/.agent/skill-library/stack/frameworks/nestjs/SKILL.md +878 -0
- package/template/.agent/skill-library/stack/frameworks/nextjs/SKILL.md +635 -0
- package/template/.agent/skill-library/stack/frameworks/nuxt/SKILL.md +564 -0
- package/template/.agent/skill-library/stack/frameworks/sveltekit/SKILL.md +614 -0
- package/template/.agent/skill-library/stack/frameworks/tauri/SKILL.md +920 -0
- package/template/.agent/skill-library/stack/gamedev/godot/SKILL.md +1032 -0
- package/template/.agent/skill-library/stack/gamedev/unity/SKILL.md +1175 -0
- package/template/.agent/skill-library/stack/hosting/aws/SKILL.md +467 -0
- package/template/.agent/skill-library/stack/hosting/cloudflare/SKILL.md +201 -0
- package/template/.agent/skill-library/stack/hosting/docker-expert/SKILL.md +409 -0
- package/template/.agent/skill-library/stack/hosting/vercel/SKILL.md +484 -0
- package/template/.agent/skill-library/stack/languages/bash-scripting/SKILL.md +773 -0
- package/template/.agent/skill-library/stack/languages/c-cpp/SKILL.md +712 -0
- package/template/.agent/skill-library/stack/languages/gdscript/SKILL.md +789 -0
- package/template/.agent/skill-library/stack/languages/go/SKILL.md +664 -0
- package/template/.agent/skill-library/stack/languages/java/SKILL.md +778 -0
- package/template/.agent/skill-library/stack/languages/kotlin/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/languages/python/SKILL.md +678 -0
- package/template/.agent/skill-library/stack/languages/rust/SKILL.md +673 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/SKILL.md +141 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/advanced-generics.md +90 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/branded-types.md +57 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/builder-pattern.md +71 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/common-pitfalls.md +135 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/conditional-types.md +27 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/decorators.md +98 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/discriminated-unions.md +62 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/mapped-types.md +53 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/performance-best-practices.md +104 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/template-literal-types.md +49 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/testing-types.md +112 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-guards.md +70 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/type-inference.md +101 -0
- package/template/.agent/skill-library/stack/languages/typescript-advanced-patterns/references/utility-types.md +98 -0
- package/template/.agent/skill-library/stack/languages/vanilla-javascript/SKILL.md +803 -0
- package/template/.agent/skill-library/stack/messaging/kafka/SKILL.md +235 -0
- package/template/.agent/skill-library/stack/mobile/expo-react-native/SKILL.md +665 -0
- package/template/.agent/skill-library/stack/mobile/flutter/SKILL.md +316 -0
- package/template/.agent/skill-library/stack/mobile/react-native/SKILL.md +337 -0
- package/template/.agent/skill-library/stack/monitoring/posthog/SKILL.md +396 -0
- package/template/.agent/skill-library/stack/monitoring/sentry/SKILL.md +509 -0
- package/template/.agent/skill-library/stack/observability/datadog/SKILL.md +179 -0
- package/template/.agent/skill-library/stack/observability/distributed-tracing/SKILL.md +140 -0
- package/template/.agent/skill-library/stack/observability/logging-best-practices/SKILL.md +168 -0
- package/template/.agent/skill-library/stack/observability/opentelemetry/SKILL.md +164 -0
- package/template/.agent/skill-library/stack/observability/prometheus-grafana/SKILL.md +246 -0
- package/template/.agent/skill-library/stack/observability/python-observability/SKILL.md +158 -0
- package/template/.agent/skill-library/stack/orm/drizzle-orm/SKILL.md +613 -0
- package/template/.agent/skill-library/stack/orm/prisma/SKILL.md +744 -0
- package/template/.agent/skill-library/stack/payments/lemonsqueezy/SKILL.md +393 -0
- package/template/.agent/skill-library/stack/payments/stripe-integration/SKILL.md +457 -0
- package/template/.agent/skill-library/stack/queue/bullmq/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/queue/inngest/SKILL.md +438 -0
- package/template/.agent/skill-library/stack/realtime/socketio/SKILL.md +595 -0
- package/template/.agent/skill-library/stack/search/elasticsearch/SKILL.md +248 -0
- package/template/.agent/skill-library/stack/search/meilisearch/SKILL.md +385 -0
- package/template/.agent/skill-library/stack/security/crypto-patterns/SKILL.md +437 -0
- package/template/.agent/skill-library/stack/security/csp-cors-headers/SKILL.md +588 -0
- package/template/.agent/skill-library/stack/security/dependency-auditing/SKILL.md +560 -0
- package/template/.agent/skill-library/stack/security/input-sanitization/SKILL.md +430 -0
- package/template/.agent/skill-library/stack/security/owasp-web-security/SKILL.md +421 -0
- package/template/.agent/skill-library/stack/state/tanstack-query/SKILL.md +637 -0
- package/template/.agent/skill-library/stack/state/zustand/SKILL.md +483 -0
- package/template/.agent/skill-library/stack/storage/aws-s3/SKILL.md +415 -0
- package/template/.agent/skill-library/stack/testing/playwright/SKILL.md +641 -0
- package/template/.agent/skill-library/stack/testing/storybook/SKILL.md +923 -0
- package/template/.agent/skill-library/stack/testing/testing-library/SKILL.md +872 -0
- package/template/.agent/skill-library/stack/testing/vitest/SKILL.md +714 -0
- package/template/.agent/skill-library/stack/ui/react-best-practices/SKILL.md +877 -0
- package/template/.agent/skill-library/stack/ui/react-composition-patterns/SKILL.md +1107 -0
- package/template/.agent/skill-library/stack/ui/react-flow/SKILL.md +425 -0
- package/template/.agent/skill-library/stack/ui/shadcn-ui/SKILL.md +703 -0
- package/template/.agent/skill-library/surface/api/api-caching/SKILL.md +458 -0
- package/template/.agent/skill-library/surface/api/api-documentation-openapi/SKILL.md +697 -0
- package/template/.agent/skill-library/surface/api/api-error-handling/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/api-security-checklist/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/api-versioning/SKILL.md +420 -0
- package/template/.agent/skill-library/surface/api/email-best-practices/SKILL.md +59 -0
- package/template/.agent/skill-library/surface/api/rate-limiting-abuse-protection/SKILL.md +147 -0
- package/template/.agent/skill-library/surface/api/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skill-library/surface/api/webhook-design/SKILL.md +752 -0
- package/template/.agent/skill-library/surface/cli/cli-configuration-management/SKILL.md +445 -0
- package/template/.agent/skill-library/surface/cli/cli-error-diagnostics/SKILL.md +515 -0
- package/template/.agent/skill-library/surface/cli/cli-shell-integration/SKILL.md +479 -0
- package/template/.agent/skill-library/surface/cli/cli-ux-design/SKILL.md +477 -0
- package/template/.agent/skill-library/surface/desktop/desktop-app-distribution/SKILL.md +416 -0
- package/template/.agent/skill-library/surface/desktop/desktop-security-sandboxing/SKILL.md +407 -0
- package/template/.agent/skill-library/surface/desktop/desktop-ux-conventions/SKILL.md +361 -0
- package/template/.agent/skill-library/surface/desktop/native-os-integration/SKILL.md +563 -0
- package/template/.agent/skill-library/surface/extension/browser-extension-patterns/SKILL.md +482 -0
- package/template/.agent/skill-library/surface/extension/plugin-architecture-design/SKILL.md +632 -0
- package/template/.agent/skill-library/surface/extension/vscode-extension-development/SKILL.md +728 -0
- package/template/.agent/skill-library/surface/mobile/app-store-submission/SKILL.md +304 -0
- package/template/.agent/skill-library/surface/mobile/mobile-offline-sync/SKILL.md +443 -0
- package/template/.agent/skill-library/surface/mobile/mobile-responsive-patterns/SKILL.md +432 -0
- package/template/.agent/skill-library/surface/mobile/push-notifications/SKILL.md +495 -0
- package/template/.agent/skill-library/surface/web/accessibility-compliance/SKILL.md +827 -0
- package/template/.agent/skill-library/surface/web/ai-seo/SKILL.md +398 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/content-patterns.md +285 -0
- package/template/.agent/skill-library/surface/web/ai-seo/references/platform-ranking-factors.md +152 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/SKILL.md +309 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/event-library.md +260 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/ga4-implementation.md +300 -0
- package/template/.agent/skill-library/surface/web/analytics-tracking/references/gtm-implementation.md +390 -0
- package/template/.agent/skill-library/surface/web/authentication-ui-flows/SKILL.md +530 -0
- package/template/.agent/skill-library/surface/web/dark-mode-theming/SKILL.md +516 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/SKILL.md +105 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/charts.csv +26 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/colors.csv +97 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/landing.csv +31 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/styles.csv +59 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/typography.csv +58 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/data/ux-guidelines.csv +100 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/core.py +258 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/design_system.py +1067 -0
- package/template/.agent/skill-library/surface/web/design-reference-data/scripts/search.py +106 -0
- package/template/.agent/skill-library/surface/web/form-handling-validation/SKILL.md +675 -0
- package/template/.agent/skill-library/surface/web/frontend-design/SKILL.md +1393 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/cppn-hero.tsx +299 -0
- package/template/.agent/skill-library/surface/web/frontend-design/templates/wave-hero.tsx +875 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/SKILL.md +111 -0
- package/template/.agent/skill-library/surface/web/frontend-verification/scripts/ux_audit.py +739 -0
- package/template/.agent/skill-library/surface/web/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skill-library/surface/web/offline-first-pwa/SKILL.md +657 -0
- package/template/.agent/skill-library/surface/web/page-cro/SKILL.md +182 -0
- package/template/.agent/skill-library/surface/web/page-cro/references/experiments.md +248 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/SKILL.md +238 -0
- package/template/.agent/skill-library/surface/web/programmatic-seo/references/playbooks.md +308 -0
- package/template/.agent/skill-library/surface/web/schema-markup/SKILL.md +179 -0
- package/template/.agent/skill-library/surface/web/schema-markup/references/schema-examples.md +398 -0
- package/template/.agent/skill-library/surface/web/seo-audit/SKILL.md +394 -0
- package/template/.agent/skill-library/surface/web/seo-audit/references/ai-writing-detection.md +200 -0
- package/template/.agent/skill-library/surface/web/web-performance-optimization/SKILL.md +646 -0
- package/template/.agent/skill-library/surface/web/web-scraping/SKILL.md +58 -0
- package/template/.agent/skills/accessibility/SKILL.md +522 -0
- package/template/.agent/skills/accessibility/references/WCAG.md +162 -0
- package/template/.agent/skills/adversarial-review/SKILL.md +90 -0
- package/template/.agent/skills/antigravity-workflows/SKILL.md +81 -0
- package/template/.agent/skills/antigravity-workflows/resources/implementation-playbook.md +36 -0
- package/template/.agent/skills/api-design-principles/SKILL.md +37 -0
- package/template/.agent/skills/api-design-principles/assets/api-design-checklist.md +155 -0
- package/template/.agent/skills/api-design-principles/assets/rest-api-template.py +182 -0
- package/template/.agent/skills/api-design-principles/references/graphql-schema-design.md +583 -0
- package/template/.agent/skills/api-design-principles/references/rest-best-practices.md +408 -0
- package/template/.agent/skills/api-design-principles/resources/implementation-playbook.md +513 -0
- package/template/.agent/skills/api-versioning/SKILL.md +420 -0
- package/template/.agent/skills/architecture-mapping/SKILL.md +219 -0
- package/template/.agent/skills/bootstrap-agents/SKILL.md +259 -0
- package/template/.agent/skills/brainstorming/SKILL.md +236 -0
- package/template/.agent/skills/brand-guidelines/SKILL.md +44 -0
- package/template/.agent/skills/clean-code/SKILL.md +94 -0
- package/template/.agent/skills/code-review-pro/SKILL.md +152 -0
- package/template/.agent/skills/concise-planning/SKILL.md +68 -0
- package/template/.agent/skills/cross-layer-consistency/SKILL.md +117 -0
- package/template/.agent/skills/database-schema-design/SKILL.md +429 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/design-anti-cliche/SKILL.md +159 -0
- package/template/.agent/skills/design-direction/SKILL.md +45 -0
- package/template/.agent/skills/error-handling-patterns/SKILL.md +721 -0
- package/template/.agent/skills/find-skills/SKILL.md +145 -0
- package/template/.agent/skills/git-advanced/SKILL.md +972 -0
- package/template/.agent/skills/git-workflow/SKILL.md +420 -0
- package/template/.agent/skills/idea-extraction/SKILL.md +271 -0
- package/template/.agent/skills/logging-best-practices/SKILL.md +851 -0
- package/template/.agent/skills/migration-management/SKILL.md +384 -0
- package/template/.agent/skills/minimalist-surgical-development/SKILL.md +69 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +165 -0
- package/template/.agent/skills/parallel-debugging/SKILL.md +135 -0
- package/template/.agent/skills/parallel-feature-development/SKILL.md +166 -0
- package/template/.agent/skills/performance-budgeting/SKILL.md +144 -0
- package/template/.agent/skills/pipeline-rubrics/SKILL.md +51 -0
- package/template/.agent/skills/pipeline-rubrics/references/architecture-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +21 -0
- package/template/.agent/skills/pipeline-rubrics/references/fe-rubric.md +20 -0
- package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +19 -0
- package/template/.agent/skills/pipeline-rubrics/references/scoring.md +28 -0
- package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +11 -0
- package/template/.agent/skills/prd-templates/SKILL.md +88 -0
- package/template/.agent/skills/prd-templates/references/architecture-design-template.md +88 -0
- package/template/.agent/skills/prd-templates/references/be-spec-template.md +101 -0
- package/template/.agent/skills/prd-templates/references/data-placement-template.md +74 -0
- package/template/.agent/skills/prd-templates/references/decomposition-templates.md +211 -0
- package/template/.agent/skills/prd-templates/references/design-system-decisions.md +198 -0
- package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +124 -0
- package/template/.agent/skills/prd-templates/references/fe-classification-procedures.md +47 -0
- package/template/.agent/skills/prd-templates/references/fe-spec-template.md +84 -0
- package/template/.agent/skills/prd-templates/references/infrastructure-report-template.md +71 -0
- package/template/.agent/skills/prd-templates/references/operational-templates.md +116 -0
- package/template/.agent/skills/prd-templates/references/placeholder-guard-template.md +21 -0
- package/template/.agent/skills/prd-templates/references/surface-model.md +61 -0
- package/template/.agent/skills/prd-templates/references/vision-template.md +66 -0
- package/template/.agent/skills/prompt-engineer/README.md +659 -0
- package/template/.agent/skills/prompt-engineer/SKILL.md +249 -0
- package/template/.agent/skills/regex-patterns/SKILL.md +751 -0
- package/template/.agent/skills/resolve-ambiguity/SKILL.md +278 -0
- package/template/.agent/skills/rest-api-design/SKILL.md +478 -0
- package/template/.agent/skills/security-scanning-security-hardening/SKILL.md +231 -0
- package/template/.agent/skills/session-continuity/SKILL.md +730 -0
- package/template/.agent/skills/session-continuity/protocols/01-session-resumption.md +38 -0
- package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +85 -0
- package/template/.agent/skills/session-continuity/protocols/03-progress-update.md +70 -0
- package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +60 -0
- package/template/.agent/skills/session-continuity/protocols/05-session-close.md +37 -0
- package/template/.agent/skills/session-continuity/protocols/06-decision-analysis.md +84 -0
- package/template/.agent/skills/session-continuity/protocols/07-spec-pipeline-generation.md +48 -0
- package/template/.agent/skills/session-continuity/protocols/08-spec-pipeline-update.md +43 -0
- package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +122 -0
- package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +104 -0
- package/template/.agent/skills/session-continuity/protocols/ambiguity-gates.md +48 -0
- package/template/.agent/skills/skill-creator/LICENSE.txt +202 -0
- package/template/.agent/skills/skill-creator/README.md +270 -0
- package/template/.agent/skills/skill-creator/SKILL.md +590 -0
- package/template/.agent/skills/skill-creator/references/output-patterns.md +82 -0
- package/template/.agent/skills/skill-creator/references/workflows.md +28 -0
- package/template/.agent/skills/skill-creator/scripts/init_skill.py +303 -0
- package/template/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
- package/template/.agent/skills/skill-creator/scripts/quick_validate.py +95 -0
- package/template/.agent/skills/spec-writing/SKILL.md +110 -0
- package/template/.agent/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +297 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/template/.agent/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/template/.agent/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/template/.agent/skills/systematic-debugging/find-polluter.sh +63 -0
- package/template/.agent/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/template/.agent/skills/systematic-debugging/test-academic.md +14 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/template/.agent/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +409 -0
- package/template/.agent/skills/tech-stack-catalog/SKILL.md +49 -0
- package/template/.agent/skills/tech-stack-catalog/references/constraint-questions.md +21 -0
- package/template/.agent/skills/tech-stack-catalog/references/dev-tooling-decisions.md +37 -0
- package/template/.agent/skills/tech-stack-catalog/references/surface-decision-tables.md +69 -0
- package/template/.agent/skills/technical-writer/SKILL.md +242 -0
- package/template/.agent/skills/testing-strategist/SKILL.md +932 -0
- package/template/.agent/skills/verification-before-completion/SKILL.md +145 -0
- package/template/.agent/skills/workflow-automation/SKILL.md +73 -0
- package/template/.agent/workflows/audit-ambiguity-execute.md +165 -0
- package/template/.agent/workflows/audit-ambiguity-rubrics.md +83 -0
- package/template/.agent/workflows/audit-ambiguity.md +64 -0
- package/template/.agent/workflows/bootstrap-agents-fill.md +201 -0
- package/template/.agent/workflows/bootstrap-agents-provision.md +197 -0
- package/template/.agent/workflows/bootstrap-agents.md +66 -0
- package/template/.agent/workflows/create-prd-architecture.md +119 -0
- package/template/.agent/workflows/create-prd-compile.md +138 -0
- package/template/.agent/workflows/create-prd-design-system.md +135 -0
- package/template/.agent/workflows/create-prd-security.md +113 -0
- package/template/.agent/workflows/create-prd-stack.md +91 -0
- package/template/.agent/workflows/create-prd.md +168 -0
- package/template/.agent/workflows/decompose-architecture-structure.md +82 -0
- package/template/.agent/workflows/decompose-architecture-validate.md +119 -0
- package/template/.agent/workflows/decompose-architecture.md +111 -0
- package/template/.agent/workflows/evolve-contract.md +98 -0
- package/template/.agent/workflows/evolve-feature-cascade.md +140 -0
- package/template/.agent/workflows/evolve-feature-classify.md +116 -0
- package/template/.agent/workflows/evolve-feature.md +56 -0
- package/template/.agent/workflows/ideate-discover.md +144 -0
- package/template/.agent/workflows/ideate-extract.md +129 -0
- package/template/.agent/workflows/ideate-validate.md +117 -0
- package/template/.agent/workflows/ideate.md +113 -0
- package/template/.agent/workflows/implement-slice-setup.md +113 -0
- package/template/.agent/workflows/implement-slice-tdd.md +198 -0
- package/template/.agent/workflows/implement-slice.md +50 -0
- package/template/.agent/workflows/plan-phase.md +202 -0
- package/template/.agent/workflows/propagate-decision-apply.md +135 -0
- package/template/.agent/workflows/propagate-decision-scan.md +147 -0
- package/template/.agent/workflows/propagate-decision.md +56 -0
- package/template/.agent/workflows/remediate-pipeline-assess.md +138 -0
- package/template/.agent/workflows/remediate-pipeline-execute.md +135 -0
- package/template/.agent/workflows/remediate-pipeline.md +55 -0
- package/template/.agent/workflows/resolve-ambiguity.md +82 -0
- package/template/.agent/workflows/sync-kit.md +209 -0
- package/template/.agent/workflows/update-architecture-map.md +74 -0
- package/template/.agent/workflows/validate-phase.md +219 -0
- package/template/.agent/workflows/verify-infrastructure.md +207 -0
- package/template/.agent/workflows/write-architecture-spec-deepen.md +139 -0
- package/template/.agent/workflows/write-architecture-spec-design.md +202 -0
- package/template/.agent/workflows/write-architecture-spec.md +63 -0
- package/template/.agent/workflows/write-be-spec-classify.md +165 -0
- package/template/.agent/workflows/write-be-spec-write.md +98 -0
- package/template/.agent/workflows/write-be-spec.md +76 -0
- package/template/.agent/workflows/write-fe-spec-classify.md +170 -0
- package/template/.agent/workflows/write-fe-spec-write.md +94 -0
- package/template/.agent/workflows/write-fe-spec.md +71 -0
- package/template/AGENTS.md +176 -0
- package/template/GEMINI.md +177 -0
- package/template/docs/README.md +187 -0
- package/template/docs/audits/.gitkeep +0 -0
- package/template/docs/audits/README.md +10 -0
- package/template/docs/plans/.gitkeep +0 -0
- package/template/docs/plans/README.md +21 -0
- package/template/docs/plans/be/.gitkeep +0 -0
- package/template/docs/plans/be/README.md +11 -0
- package/template/docs/plans/fe/.gitkeep +0 -0
- package/template/docs/plans/fe/README.md +11 -0
- package/template/docs/plans/ia/.gitkeep +0 -0
- package/template/docs/plans/ia/README.md +17 -0
- package/template/docs/plans/ia/deep-dives/.gitkeep +0 -0
- package/template/docs/plans/ia/deep-dives/README.md +5 -0
- package/template/docs/plans/phases/.gitkeep +0 -0
- package/template/docs/plans/phases/README.md +11 -0
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: form-handling-validation
|
|
3
|
+
description: "Comprehensive form patterns including client+server validation with shared Zod schemas, react-hook-form integration, multi-step wizards, file uploads, accessible error handling, and dirty state tracking. Use when building forms, validation flows, or form UX."
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Form Handling & Validation
|
|
8
|
+
|
|
9
|
+
Build forms that validate correctly on both client and server, communicate errors clearly, and handle edge cases like file uploads, multi-step flows, and unsaved changes.
|
|
10
|
+
|
|
11
|
+
## Core Principle: Single Schema, Shared Validation
|
|
12
|
+
|
|
13
|
+
Define the validation schema once in Zod. Derive TypeScript types from it. Use the same schema on client and server.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// src/schemas/contact.schema.ts
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
export const ContactFormSchema = z.object({
|
|
20
|
+
name: z.string().min(1, 'Name is required').max(100, 'Name must be under 100 characters'),
|
|
21
|
+
email: z.string().email('Please enter a valid email address'),
|
|
22
|
+
subject: z.enum(['support', 'sales', 'feedback'], {
|
|
23
|
+
errorMap: () => ({ message: 'Please select a subject' }),
|
|
24
|
+
}),
|
|
25
|
+
message: z.string()
|
|
26
|
+
.min(10, 'Message must be at least 10 characters')
|
|
27
|
+
.max(2000, 'Message must be under 2000 characters'),
|
|
28
|
+
attachments: z.array(z.instanceof(File)).max(3, 'Maximum 3 files allowed').optional(),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export type ContactFormData = z.infer<typeof ContactFormSchema>;
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Server-side validation (API route):**
|
|
35
|
+
```typescript
|
|
36
|
+
// src/pages/api/contact.ts
|
|
37
|
+
import { ContactFormSchema } from '@/schemas/contact.schema';
|
|
38
|
+
|
|
39
|
+
export async function POST({ request }: APIContext) {
|
|
40
|
+
const body = await request.json();
|
|
41
|
+
const result = ContactFormSchema.safeParse(body);
|
|
42
|
+
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
return new Response(JSON.stringify({
|
|
45
|
+
error: 'Validation failed',
|
|
46
|
+
fields: result.error.flatten().fieldErrors,
|
|
47
|
+
}), { status: 422 });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// result.data is typed as ContactFormData
|
|
51
|
+
await sendContactEmail(result.data);
|
|
52
|
+
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## react-hook-form Patterns
|
|
59
|
+
|
|
60
|
+
### Basic Form with Zod Resolver
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { useForm } from 'react-hook-form';
|
|
64
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
65
|
+
import { ContactFormSchema, type ContactFormData } from '@/schemas/contact.schema';
|
|
66
|
+
|
|
67
|
+
function ContactForm() {
|
|
68
|
+
const {
|
|
69
|
+
register,
|
|
70
|
+
handleSubmit,
|
|
71
|
+
formState: { errors, isSubmitting, isDirty, isValid },
|
|
72
|
+
reset,
|
|
73
|
+
setError,
|
|
74
|
+
} = useForm<ContactFormData>({
|
|
75
|
+
resolver: zodResolver(ContactFormSchema),
|
|
76
|
+
defaultValues: {
|
|
77
|
+
name: '',
|
|
78
|
+
email: '',
|
|
79
|
+
subject: undefined,
|
|
80
|
+
message: '',
|
|
81
|
+
},
|
|
82
|
+
mode: 'onBlur', // Validate on blur, not on every keystroke
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const onSubmit = async (data: ContactFormData) => {
|
|
86
|
+
const response = await fetch('/api/contact', {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: { 'Content-Type': 'application/json' },
|
|
89
|
+
body: JSON.stringify(data),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
const body = await response.json();
|
|
94
|
+
// Map server errors back to fields
|
|
95
|
+
if (body.fields) {
|
|
96
|
+
for (const [field, messages] of Object.entries(body.fields)) {
|
|
97
|
+
setError(field as keyof ContactFormData, {
|
|
98
|
+
message: (messages as string[])[0],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
} else {
|
|
102
|
+
setError('root', { message: body.error ?? 'Submission failed' });
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
reset();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<form onSubmit={handleSubmit(onSubmit)} noValidate>
|
|
112
|
+
{errors.root && (
|
|
113
|
+
<div role="alert" className="form-error-banner">
|
|
114
|
+
{errors.root.message}
|
|
115
|
+
</div>
|
|
116
|
+
)}
|
|
117
|
+
|
|
118
|
+
<div className="field-group">
|
|
119
|
+
<label htmlFor="name">Name</label>
|
|
120
|
+
<input
|
|
121
|
+
id="name"
|
|
122
|
+
type="text"
|
|
123
|
+
aria-invalid={!!errors.name}
|
|
124
|
+
aria-describedby={errors.name ? 'name-error' : undefined}
|
|
125
|
+
{...register('name')}
|
|
126
|
+
/>
|
|
127
|
+
{errors.name && (
|
|
128
|
+
<p id="name-error" className="field-error" role="alert">
|
|
129
|
+
{errors.name.message}
|
|
130
|
+
</p>
|
|
131
|
+
)}
|
|
132
|
+
</div>
|
|
133
|
+
|
|
134
|
+
<button type="submit" disabled={isSubmitting}>
|
|
135
|
+
{isSubmitting ? 'Sending...' : 'Send Message'}
|
|
136
|
+
</button>
|
|
137
|
+
</form>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Controller for Custom Components
|
|
143
|
+
|
|
144
|
+
Use `Controller` for components that do not expose a standard `ref` (selects, date pickers, rich text editors).
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
import { Controller, useForm } from 'react-hook-form';
|
|
148
|
+
|
|
149
|
+
function FormWithCustomSelect() {
|
|
150
|
+
const { control, handleSubmit } = useForm<ContactFormData>({
|
|
151
|
+
resolver: zodResolver(ContactFormSchema),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
156
|
+
<Controller
|
|
157
|
+
name="subject"
|
|
158
|
+
control={control}
|
|
159
|
+
render={({ field, fieldState }) => (
|
|
160
|
+
<div className="field-group">
|
|
161
|
+
<label htmlFor="subject">Subject</label>
|
|
162
|
+
<CustomSelect
|
|
163
|
+
id="subject"
|
|
164
|
+
options={subjectOptions}
|
|
165
|
+
value={field.value}
|
|
166
|
+
onChange={field.onChange}
|
|
167
|
+
onBlur={field.onBlur}
|
|
168
|
+
aria-invalid={!!fieldState.error}
|
|
169
|
+
aria-describedby={fieldState.error ? 'subject-error' : undefined}
|
|
170
|
+
/>
|
|
171
|
+
{fieldState.error && (
|
|
172
|
+
<p id="subject-error" className="field-error" role="alert">
|
|
173
|
+
{fieldState.error.message}
|
|
174
|
+
</p>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
/>
|
|
179
|
+
</form>
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Error Messaging UX
|
|
187
|
+
|
|
188
|
+
### Timing
|
|
189
|
+
|
|
190
|
+
| Strategy | When to Validate | Best For |
|
|
191
|
+
|----------|-----------------|----------|
|
|
192
|
+
| `onBlur` | When field loses focus | Most forms --- gives user time to finish typing |
|
|
193
|
+
| `onChange` | Every keystroke after first blur | Password strength, character counters |
|
|
194
|
+
| `onSubmit` | Only on submit | Simple forms, less noise |
|
|
195
|
+
| `onTouched` | After first blur, then onChange | Recommended default for complex forms |
|
|
196
|
+
|
|
197
|
+
### Inline Errors
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
// Reusable field error component
|
|
201
|
+
function FieldError({ id, error }: { id: string; error?: string }) {
|
|
202
|
+
if (!error) return null;
|
|
203
|
+
|
|
204
|
+
return (
|
|
205
|
+
<p id={id} className="field-error" role="alert">
|
|
206
|
+
<svg aria-hidden="true" className="error-icon">{/* icon */}</svg>
|
|
207
|
+
{error}
|
|
208
|
+
</p>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Error Summary
|
|
214
|
+
|
|
215
|
+
Show an error summary at the top of the form on submit. Focus it so screen readers announce it.
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
function ErrorSummary({ errors }: { errors: Record<string, { message?: string }> }) {
|
|
219
|
+
const summaryRef = useRef<HTMLDivElement>(null);
|
|
220
|
+
const errorEntries = Object.entries(errors).filter(([, e]) => e.message);
|
|
221
|
+
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
if (errorEntries.length > 0) {
|
|
224
|
+
summaryRef.current?.focus();
|
|
225
|
+
}
|
|
226
|
+
}, [errorEntries.length]);
|
|
227
|
+
|
|
228
|
+
if (errorEntries.length === 0) return null;
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div ref={summaryRef} role="alert" tabIndex={-1} className="error-summary">
|
|
232
|
+
<h2>There {errorEntries.length === 1 ? 'is 1 error' : `are ${errorEntries.length} errors`}</h2>
|
|
233
|
+
<ul>
|
|
234
|
+
{errorEntries.map(([field, error]) => (
|
|
235
|
+
<li key={field}>
|
|
236
|
+
<a href={`#${field}`}>{error.message}</a>
|
|
237
|
+
</li>
|
|
238
|
+
))}
|
|
239
|
+
</ul>
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## Multi-Step Forms (Wizard Pattern)
|
|
248
|
+
|
|
249
|
+
### Schema Per Step
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Step schemas
|
|
253
|
+
export const PersonalInfoSchema = z.object({
|
|
254
|
+
firstName: z.string().min(1, 'First name is required'),
|
|
255
|
+
lastName: z.string().min(1, 'Last name is required'),
|
|
256
|
+
email: z.string().email('Please enter a valid email'),
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
export const AddressSchema = z.object({
|
|
260
|
+
street: z.string().min(1, 'Street is required'),
|
|
261
|
+
city: z.string().min(1, 'City is required'),
|
|
262
|
+
postalCode: z.string().regex(/^\d{5}$/, 'Enter a valid 5-digit postal code'),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
export const PreferencesSchema = z.object({
|
|
266
|
+
newsletter: z.boolean(),
|
|
267
|
+
theme: z.enum(['light', 'dark', 'system']),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// Combined schema for final submission
|
|
271
|
+
export const RegistrationSchema = PersonalInfoSchema
|
|
272
|
+
.merge(AddressSchema)
|
|
273
|
+
.merge(PreferencesSchema);
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Multi-Step State Management
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
type WizardState = z.infer<typeof RegistrationSchema>;
|
|
280
|
+
|
|
281
|
+
function RegistrationWizard() {
|
|
282
|
+
const [step, setStep] = useState(0);
|
|
283
|
+
const [formData, setFormData] = useState<Partial<WizardState>>({});
|
|
284
|
+
|
|
285
|
+
const steps = [
|
|
286
|
+
{ schema: PersonalInfoSchema, title: 'Personal Info', component: PersonalInfoStep },
|
|
287
|
+
{ schema: AddressSchema, title: 'Address', component: AddressStep },
|
|
288
|
+
{ schema: PreferencesSchema, title: 'Preferences', component: PreferencesStep },
|
|
289
|
+
];
|
|
290
|
+
|
|
291
|
+
const currentStep = steps[step];
|
|
292
|
+
|
|
293
|
+
function handleStepComplete(stepData: Record<string, unknown>) {
|
|
294
|
+
const merged = { ...formData, ...stepData };
|
|
295
|
+
setFormData(merged);
|
|
296
|
+
|
|
297
|
+
if (step < steps.length - 1) {
|
|
298
|
+
setStep(step + 1);
|
|
299
|
+
} else {
|
|
300
|
+
// Final submission
|
|
301
|
+
const result = RegistrationSchema.safeParse(merged);
|
|
302
|
+
if (result.success) {
|
|
303
|
+
submitRegistration(result.data);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<div>
|
|
310
|
+
{/* Progress indicator */}
|
|
311
|
+
<nav aria-label="Registration progress">
|
|
312
|
+
<ol>
|
|
313
|
+
{steps.map((s, i) => (
|
|
314
|
+
<li key={s.title} aria-current={i === step ? 'step' : undefined}>
|
|
315
|
+
<span className={i < step ? 'completed' : i === step ? 'current' : 'upcoming'}>
|
|
316
|
+
{s.title}
|
|
317
|
+
</span>
|
|
318
|
+
</li>
|
|
319
|
+
))}
|
|
320
|
+
</ol>
|
|
321
|
+
</nav>
|
|
322
|
+
|
|
323
|
+
<h2>{currentStep.title}</h2>
|
|
324
|
+
<currentStep.component
|
|
325
|
+
defaultValues={formData}
|
|
326
|
+
schema={currentStep.schema}
|
|
327
|
+
onComplete={handleStepComplete}
|
|
328
|
+
onBack={step > 0 ? () => setStep(step - 1) : undefined}
|
|
329
|
+
/>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Persist State Across Page Reloads
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
function useFormPersistence<T>(key: string, defaultValues: T) {
|
|
339
|
+
const [state, setState] = useState<T>(() => {
|
|
340
|
+
if (typeof window === 'undefined') return defaultValues;
|
|
341
|
+
const saved = sessionStorage.getItem(key);
|
|
342
|
+
return saved ? JSON.parse(saved) : defaultValues;
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
useEffect(() => {
|
|
346
|
+
sessionStorage.setItem(key, JSON.stringify(state));
|
|
347
|
+
}, [key, state]);
|
|
348
|
+
|
|
349
|
+
function clear() {
|
|
350
|
+
sessionStorage.removeItem(key);
|
|
351
|
+
setState(defaultValues);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return [state, setState, clear] as const;
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
## File Upload Handling
|
|
361
|
+
|
|
362
|
+
### Zod Schema for Files
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB
|
|
366
|
+
const ACCEPTED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'application/pdf'];
|
|
367
|
+
|
|
368
|
+
export const FileUploadSchema = z.object({
|
|
369
|
+
files: z
|
|
370
|
+
.array(
|
|
371
|
+
z.instanceof(File)
|
|
372
|
+
.refine((file) => file.size <= MAX_FILE_SIZE, 'File must be under 5MB')
|
|
373
|
+
.refine((file) => ACCEPTED_TYPES.includes(file.type), 'Unsupported file type')
|
|
374
|
+
)
|
|
375
|
+
.min(1, 'At least one file is required')
|
|
376
|
+
.max(5, 'Maximum 5 files'),
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Drag-and-Drop Upload Component
|
|
381
|
+
|
|
382
|
+
```tsx
|
|
383
|
+
function FileDropzone({ onFiles, maxFiles = 5 }: { onFiles: (files: File[]) => void; maxFiles?: number }) {
|
|
384
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
385
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
386
|
+
|
|
387
|
+
function handleDrop(event: React.DragEvent) {
|
|
388
|
+
event.preventDefault();
|
|
389
|
+
setIsDragging(false);
|
|
390
|
+
const files = Array.from(event.dataTransfer.files).slice(0, maxFiles);
|
|
391
|
+
onFiles(files);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function handleDragOver(event: React.DragEvent) {
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
setIsDragging(true);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return (
|
|
400
|
+
<div
|
|
401
|
+
onDrop={handleDrop}
|
|
402
|
+
onDragOver={handleDragOver}
|
|
403
|
+
onDragLeave={() => setIsDragging(false)}
|
|
404
|
+
className={`dropzone ${isDragging ? 'dropzone--active' : ''}`}
|
|
405
|
+
role="button"
|
|
406
|
+
tabIndex={0}
|
|
407
|
+
aria-label="Upload files. Click or drag and drop."
|
|
408
|
+
onKeyDown={(e) => {
|
|
409
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
410
|
+
e.preventDefault();
|
|
411
|
+
inputRef.current?.click();
|
|
412
|
+
}
|
|
413
|
+
}}
|
|
414
|
+
>
|
|
415
|
+
<input
|
|
416
|
+
ref={inputRef}
|
|
417
|
+
type="file"
|
|
418
|
+
multiple
|
|
419
|
+
accept={ACCEPTED_TYPES.join(',')}
|
|
420
|
+
onChange={(e) => onFiles(Array.from(e.target.files ?? []))}
|
|
421
|
+
className="visually-hidden"
|
|
422
|
+
aria-hidden="true"
|
|
423
|
+
tabIndex={-1}
|
|
424
|
+
/>
|
|
425
|
+
<p>{isDragging ? 'Drop files here' : 'Drag files here or click to browse'}</p>
|
|
426
|
+
<p className="hint">Max {maxFiles} files, up to 5MB each. JPEG, PNG, WebP, or PDF.</p>
|
|
427
|
+
</div>
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### Upload Progress
|
|
433
|
+
|
|
434
|
+
```typescript
|
|
435
|
+
async function uploadWithProgress(
|
|
436
|
+
file: File,
|
|
437
|
+
url: string,
|
|
438
|
+
onProgress: (percent: number) => void
|
|
439
|
+
): Promise<Response> {
|
|
440
|
+
return new Promise((resolve, reject) => {
|
|
441
|
+
const xhr = new XMLHttpRequest();
|
|
442
|
+
xhr.open('POST', url);
|
|
443
|
+
|
|
444
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
445
|
+
if (event.lengthComputable) {
|
|
446
|
+
onProgress(Math.round((event.loaded / event.total) * 100));
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
xhr.addEventListener('load', () => {
|
|
451
|
+
resolve(new Response(xhr.response, { status: xhr.status }));
|
|
452
|
+
});
|
|
453
|
+
xhr.addEventListener('error', () => reject(new Error('Upload failed')));
|
|
454
|
+
|
|
455
|
+
const formData = new FormData();
|
|
456
|
+
formData.append('file', file);
|
|
457
|
+
xhr.send(formData);
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## Autofill and Autocomplete
|
|
465
|
+
|
|
466
|
+
Use correct `autocomplete` attributes so browsers and password managers can assist users.
|
|
467
|
+
|
|
468
|
+
```html
|
|
469
|
+
<!-- Login form -->
|
|
470
|
+
<input type="email" autocomplete="username" />
|
|
471
|
+
<input type="password" autocomplete="current-password" />
|
|
472
|
+
|
|
473
|
+
<!-- Registration form -->
|
|
474
|
+
<input type="text" autocomplete="given-name" />
|
|
475
|
+
<input type="text" autocomplete="family-name" />
|
|
476
|
+
<input type="email" autocomplete="email" />
|
|
477
|
+
<input type="password" autocomplete="new-password" />
|
|
478
|
+
|
|
479
|
+
<!-- Address form -->
|
|
480
|
+
<input type="text" autocomplete="street-address" />
|
|
481
|
+
<input type="text" autocomplete="address-level2" /> <!-- city -->
|
|
482
|
+
<input type="text" autocomplete="postal-code" />
|
|
483
|
+
<select autocomplete="country">...</select>
|
|
484
|
+
|
|
485
|
+
<!-- Payment form -->
|
|
486
|
+
<input type="text" autocomplete="cc-name" />
|
|
487
|
+
<input type="text" autocomplete="cc-number" inputmode="numeric" />
|
|
488
|
+
<input type="text" autocomplete="cc-exp" />
|
|
489
|
+
<input type="text" autocomplete="cc-csc" />
|
|
490
|
+
|
|
491
|
+
<!-- One-time codes (MFA) -->
|
|
492
|
+
<input type="text" autocomplete="one-time-code" inputmode="numeric" />
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
## Double-Submission Prevention
|
|
498
|
+
|
|
499
|
+
```tsx
|
|
500
|
+
function SubmitButton({ isSubmitting, label = 'Submit' }: { isSubmitting: boolean; label?: string }) {
|
|
501
|
+
return (
|
|
502
|
+
<button type="submit" disabled={isSubmitting} aria-busy={isSubmitting}>
|
|
503
|
+
{isSubmitting ? (
|
|
504
|
+
<>
|
|
505
|
+
<span className="spinner" aria-hidden="true" />
|
|
506
|
+
<span>Submitting...</span>
|
|
507
|
+
</>
|
|
508
|
+
) : (
|
|
509
|
+
label
|
|
510
|
+
)}
|
|
511
|
+
</button>
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
**Server-side idempotency:**
|
|
517
|
+
```typescript
|
|
518
|
+
// Include an idempotency key in the form
|
|
519
|
+
const idempotencyKey = crypto.randomUUID();
|
|
520
|
+
|
|
521
|
+
// Server checks: if this key was already processed, return the cached response
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## Dirty State and Unsaved Changes Warning
|
|
527
|
+
|
|
528
|
+
```tsx
|
|
529
|
+
function useUnsavedChangesWarning(isDirty: boolean): void {
|
|
530
|
+
useEffect(() => {
|
|
531
|
+
if (!isDirty) return;
|
|
532
|
+
|
|
533
|
+
function handleBeforeUnload(event: BeforeUnloadEvent) {
|
|
534
|
+
event.preventDefault();
|
|
535
|
+
// Modern browsers ignore custom messages but still show the prompt
|
|
536
|
+
event.returnValue = '';
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
540
|
+
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
541
|
+
}, [isDirty]);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Usage in form component
|
|
545
|
+
function EditProfileForm() {
|
|
546
|
+
const { formState: { isDirty }, handleSubmit, reset } = useForm(/* ... */);
|
|
547
|
+
|
|
548
|
+
useUnsavedChangesWarning(isDirty);
|
|
549
|
+
|
|
550
|
+
// For SPA navigation, also intercept route changes:
|
|
551
|
+
// - React Router: useBlocker() or <Prompt />
|
|
552
|
+
// - Next.js: router.events.on('routeChangeStart', handler)
|
|
553
|
+
}
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
## Dynamic Form Fields
|
|
559
|
+
|
|
560
|
+
### Field Arrays
|
|
561
|
+
|
|
562
|
+
```tsx
|
|
563
|
+
import { useFieldArray, useForm } from 'react-hook-form';
|
|
564
|
+
|
|
565
|
+
const InvoiceSchema = z.object({
|
|
566
|
+
lineItems: z.array(z.object({
|
|
567
|
+
description: z.string().min(1, 'Description required'),
|
|
568
|
+
quantity: z.number().min(1, 'Minimum 1'),
|
|
569
|
+
unitPrice: z.number().min(0, 'Price cannot be negative'),
|
|
570
|
+
})).min(1, 'At least one line item is required'),
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
function InvoiceForm() {
|
|
574
|
+
const { control, register, handleSubmit } = useForm({
|
|
575
|
+
resolver: zodResolver(InvoiceSchema),
|
|
576
|
+
defaultValues: { lineItems: [{ description: '', quantity: 1, unitPrice: 0 }] },
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
const { fields, append, remove } = useFieldArray({ control, name: 'lineItems' });
|
|
580
|
+
|
|
581
|
+
return (
|
|
582
|
+
<form onSubmit={handleSubmit(onSubmit)}>
|
|
583
|
+
<fieldset>
|
|
584
|
+
<legend>Line Items</legend>
|
|
585
|
+
{fields.map((field, index) => (
|
|
586
|
+
<div key={field.id} className="line-item-row">
|
|
587
|
+
<input {...register(`lineItems.${index}.description`)}
|
|
588
|
+
aria-label={`Item ${index + 1} description`} />
|
|
589
|
+
<input {...register(`lineItems.${index}.quantity`, { valueAsNumber: true })}
|
|
590
|
+
type="number" aria-label={`Item ${index + 1} quantity`} />
|
|
591
|
+
<input {...register(`lineItems.${index}.unitPrice`, { valueAsNumber: true })}
|
|
592
|
+
type="number" step="0.01" aria-label={`Item ${index + 1} unit price`} />
|
|
593
|
+
<button type="button" onClick={() => remove(index)}
|
|
594
|
+
aria-label={`Remove item ${index + 1}`}>
|
|
595
|
+
Remove
|
|
596
|
+
</button>
|
|
597
|
+
</div>
|
|
598
|
+
))}
|
|
599
|
+
<button type="button" onClick={() => append({ description: '', quantity: 1, unitPrice: 0 })}>
|
|
600
|
+
Add line item
|
|
601
|
+
</button>
|
|
602
|
+
</fieldset>
|
|
603
|
+
</form>
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Conditional Fields
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
// Schema with conditional validation
|
|
612
|
+
const EventSchema = z.discriminatedUnion('type', [
|
|
613
|
+
z.object({
|
|
614
|
+
type: z.literal('in-person'),
|
|
615
|
+
venue: z.string().min(1, 'Venue is required for in-person events'),
|
|
616
|
+
capacity: z.number().min(1),
|
|
617
|
+
}),
|
|
618
|
+
z.object({
|
|
619
|
+
type: z.literal('virtual'),
|
|
620
|
+
meetingUrl: z.string().url('Please enter a valid URL'),
|
|
621
|
+
}),
|
|
622
|
+
]);
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
```tsx
|
|
626
|
+
function EventForm() {
|
|
627
|
+
const { watch, register } = useForm({ resolver: zodResolver(EventSchema) });
|
|
628
|
+
const eventType = watch('type');
|
|
629
|
+
|
|
630
|
+
return (
|
|
631
|
+
<form>
|
|
632
|
+
<select {...register('type')}>
|
|
633
|
+
<option value="in-person">In-Person</option>
|
|
634
|
+
<option value="virtual">Virtual</option>
|
|
635
|
+
</select>
|
|
636
|
+
|
|
637
|
+
{eventType === 'in-person' && (
|
|
638
|
+
<>
|
|
639
|
+
<input {...register('venue')} placeholder="Venue name" />
|
|
640
|
+
<input {...register('capacity', { valueAsNumber: true })} type="number" />
|
|
641
|
+
</>
|
|
642
|
+
)}
|
|
643
|
+
|
|
644
|
+
{eventType === 'virtual' && (
|
|
645
|
+
<input {...register('meetingUrl')} placeholder="https://meet.example.com/..." />
|
|
646
|
+
)}
|
|
647
|
+
</form>
|
|
648
|
+
);
|
|
649
|
+
}
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
## Common Anti-Patterns
|
|
655
|
+
|
|
656
|
+
| Anti-Pattern | Why It Is Wrong | Correct Approach |
|
|
657
|
+
|-------------|-----------------|------------------|
|
|
658
|
+
| Validate only on client | Server receives unvalidated data | Use the same Zod schema on both client and server |
|
|
659
|
+
| Validate only on submit | User discovers all errors at once | Validate on blur, show errors inline |
|
|
660
|
+
| `placeholder` as label | Disappears on input, fails a11y | Use `<label>`, placeholder is a hint only |
|
|
661
|
+
| Alert on every keystroke | Noisy, especially for screen readers | Debounce validation, validate on blur |
|
|
662
|
+
| Reset form on error | User loses all input | Keep values, highlight errors |
|
|
663
|
+
| Disable submit until valid | User cannot discover what is wrong | Keep button enabled, show errors on submit |
|
|
664
|
+
| Generic "Something went wrong" | User cannot fix the problem | Show field-level errors with corrective guidance |
|
|
665
|
+
| No loading state on submit | User clicks multiple times | Disable button + show spinner during submission |
|
|
666
|
+
| Custom validation before Zod | Two sources of truth | Put all validation in the Zod schema |
|
|
667
|
+
| `required` without `aria-required` | Screen readers may not announce | Use both `required` and `aria-required="true"` |
|
|
668
|
+
|
|
669
|
+
## References
|
|
670
|
+
|
|
671
|
+
- [react-hook-form docs](https://react-hook-form.com/)
|
|
672
|
+
- [Zod docs](https://zod.dev/)
|
|
673
|
+
- [@hookform/resolvers](https://github.com/react-hook-form/resolvers)
|
|
674
|
+
- [WAI Forms Tutorial](https://www.w3.org/WAI/tutorials/forms/)
|
|
675
|
+
- [GOV.UK Form Design Patterns](https://design-system.service.gov.uk/patterns/)
|