con un clic
client-form-validation
Use when adding or changing forms in apps/client.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Use when adding or changing forms in apps/client.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Use when adding, changing, extracting, or reusing client components, including Storybook stories and controls.
Use when changing GitHub OAuth, session cookies, SessionGuard, logout, or authenticated API request identity.
Use when adding or changing routes in apps/client.
Use when adding or changing client state, React Query data flow, immutable context values, or Zustand stores.
Use when styling apps/client with Tailwind CSS v4.
Use when adding or changing client API query or mutation hooks.
| name | client-form-validation |
| description | Use when adding or changing forms in apps/client. |
Generated from ai/registry.json. Do not edit manually.
Canonical skill: ../../../ai/skills/client-form-validation.md.
Referenced context:
../../../ai/rules/client-form-rules.md../../../ai/rules/client-component-rules.md../../../ai/rules/client-api-hook-rules.md../../../ai/examples/good-client-form.mdThis file is compiled from canonical AI knowledge files. Edit canonical files under ai, then run npm run ai:sync.
ai/skills/client-form-validation.mdUse this skill when adding or changing forms in apps/client.
Build forms whose field state, schema validation, payload normalization, and mutation boundaries match existing client patterns.
ai/rules/client-form-rules.mdai/rules/client-component-rules.mdai/rules/client-api-hook-rules.mdai/examples/good-client-form.mddefaultValues, zodResolver, and handleSubmit.className, aria-invalid, and ref.noValidate.required and manual FormData parsing do not own validation.npm --workspace @capture-flag/client run build.ai/rules/client-form-rules.mdRules for forms in apps/client.
react-hook-form for mutation forms and forms with meaningful client validation.zod for form schemas when a form submits data to API mutations or has reusable validation rules.zodResolver from @hookform/resolvers/zod when using React Hook Form with Zod.defaultValues for every registered React Hook Form field.noValidate on forms so Zod owns validation messages."".aria-invalid on invalid fields.FormField in complex forms when they reduce repeated label/control/error markup.FormData manually in React components when React Hook Form or controlled state owns the form.required validation for app-level messages.ai/rules/client-component-rules.mdRules for React component boundaries in apps/client.
src/components.src/core/<category>/<name>.ts, with one exported function or hook per file.src/layouts/<LayoutName>.src/pages.src/pages/<PageName> when they are not shared outside that page.src/core utilities and hooks from their direct alias file path such as @core/json/formatJson; do not add index.ts barrels under src/core.@components/Button; do not assume a central src/components/index.ts barrel exists.apps/client at or below 400 lines; when touching larger existing files, prefer splitting real UI responsibilities instead of expanding them further.children for layout wrappers such as cards, shells, and empty states.className, aria-invalid, and ref.apps/client.stories/ child folder next to the component folder they cover, using *.stories.tsx; route/panel grouping stories belong in the owning route folder's stories/ folder.src/components/stories and member component stories in src/components/members/stories.src/layouts/<LayoutName>/stories.src/pages/<PageName>/stories or src/pages/<PageName>/<section>/stories.src/pages/stories.src/stories; do not put component stories there.src/core.args and argTypes updates.*.stories.tsx beside component files when a nearby stories/ folder is available..map() when rendering API data, dynamic collections, long repeated groups, or lists whose members are not all known at author time.src/core/date, src/core/json, src/core/strings, src/core/validation, or src/core/hooks only for helpers that are independent of Capture Flag domain context.src/core/<category>/__tests__/<name>.test.ts.@core/json/formatJson over barrels or grouped core imports.*.stories.tsx file per component or cohesive route grouping.src/stories/mockData.ts when stories need realistic Capture Flag data..storybook/preview.tsx for route and panel stories that call client API hooks.parameters.router.initialEntries when a story depends on React Router params.parameters.layout = "fullscreen" for route, layout, shell, and panel stories that need app-width context.npm --workspace @capture-flag/client run storybook:build after Storybook, component, or story changes.npm --workspace @capture-flag/client run build after component moves.ai/rules/client-api-hook-rules.mdRules for client API operations in apps/client/src/api.
apps/client/src/api.index.ts.src/api/client.ts so private API calls share the /api/v1 base URL, JSON handling, API error handling, and credentials: "include" behavior.useQuery or useInfiniteQuery in query hooks according to the API shape, and useMutation in mutation hooks.queryKeys.ts when hooks or mutations share them.enabled in query hooks when required IDs or inputs are unavailable.queryKeys into components.src/api/queryKeys.ts unless data is genuinely cross-domain.fetch directly outside src/api/client.ts unless the request is intentionally outside the app API contract.apps/client/src/api/<domain>/<operation>/<operation>.ts.apps/client/src/api/<domain>/<operation>/use<Operation>.ts.apps/client/src/api/<domain>/<operation>/index.ts.apps/client/src/api/<domain>/index.ts.apps/client/src/api/<domain>/queryKeys.ts.ai/examples/good-client-form.mdSource: apps/client/src/components/CreateNameForm.tsx (sha256: e60e8092b9d41bd7a2b5136ff048d6b5455abe40c796386dea079369b823e1e3)
Source: apps/client/src/pages/FlagsPage/featureFlags/CreateFeatureFlagForm.tsx (sha256: a7d1e86cdb9b5082b43efe4959ecae33eb6eeda011e2a78d8df8fc7ed8a76b02)
Why this is canonical:
zodResolver and noValidate.FormField wrapper in complex forms to reduce repeated label/control/error markup.Canonical form pattern from apps/client/src/components/CreateNameForm.tsx.
const createNameFormSchema = z.object({
name: z.string().trim().min(1, "Enter a name.").max(120, "Use up to 120 characters."),
});
type CreateNameFormValues = z.infer<typeof createNameFormSchema>;
const {
formState: { errors, isSubmitting },
handleSubmit,
register,
reset,
} = useForm<CreateNameFormValues>({
defaultValues: {
name: "",
},
resolver: zodResolver(createNameFormSchema),
});
<form
className="grid gap-2 sm:grid-cols-[minmax(0,1fr)_auto] sm:items-start"
noValidate
onSubmit={handleSubmit(submit)}
>
<div>
<TextInput
aria-invalid={errors.name ? true : undefined}
disabled={isDisabled}
placeholder={placeholder}
{...register("name")}
/>
<FieldError>{errors.name?.message}</FieldError>
</div>
<Button className="justify-self-start" disabled={isDisabled} type="submit">
Create
</Button>
</form>
This pattern keeps schema, default values, noValidate, field errors, and submit reset close to the owning form.
<FormField error={errors.key?.message} label="SDK key" required htmlFor={keyId}>
<TextInput
aria-invalid={errors.key ? true : undefined}
autoComplete="off"
disabled={isDisabled}
id={keyId}
placeholder="newCheckout"
{...register("key")}
/>
</FormField>
function FormField({ children, error, htmlFor, label, required = false }: FormFieldProps) {
return (
<div className="grid gap-2">
<label className="text-sm font-medium text-foreground" htmlFor={htmlFor}>
{label}
{required ? <span className="text-destructive"> *</span> : null}
</label>
{children}
<FieldError>{error}</FieldError>
</div>
);
}
This pattern is local to the owning form and avoids promoting page-specific field structure into shared components too early.