From automation
Complete guide to implementing forms using the @finstreet/forms library. Covers the full file creation order: options, schema, useFormFields, formAction, getDefaultValues, useFormConfig, FormFields component, and Form component. Use when building, modifying, or debugging any form in the finstreet context.
npx claudepluginhub joshuarweaver/cascade-code-languages-misc-1 --plugin finstreet-fe-claude-pluginsThis skill uses the workspace's default tool permissions.
This project uses `@finstreet/forms`, a type-safe form library built on react-hook-form, Zod, and custom UI components. Every form follows a strict file structure and creation order.
confirmation-modal.mddefault-values.mdediting.mdevals/evals.jsonevals/files/eval-1/ReferenceAccountForm.tsxevals/files/eval-1/ReferenceAccountFormFields.tsxevals/files/eval-1/getReferenceAccountDefaultValues.tsevals/files/eval-1/referenceAccountFormAction.tsevals/files/eval-1/referenceAccountFormSchema.tsevals/files/eval-1/useReferenceAccountFormConfig.tsxevals/files/eval-1/useReferenceAccountFormFields.tsevals/files/eval-2/AdditionalInformationForm.tsxevals/files/eval-2/AdditionalInformationFormFields.tsxevals/files/eval-2/additionalInformationFormAction.tsevals/files/eval-2/additionalInformationSchema.tsevals/files/eval-2/getAdditionalInformationDefaultValues.tsevals/files/eval-2/options/durationOptions.tsevals/files/eval-2/useAdditionalInformationFormConfig.tsxevals/files/eval-2/useAdditionalInformationFormFields.tsevals/files/eval-3/PropertyItemsForm.tsxSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
This project uses @finstreet/forms, a type-safe form library built on react-hook-form, Zod, and custom UI components. Every form follows a strict file structure and creation order.
Form Component ("use client")
└── uses <Form> wrapper from @/shared/components/form/Form
├── react-hook-form (FormProvider) for field state + validation
├── Zod schema for validation (via zodResolver)
├── useActionState for server action integration
├── DynamicFormField for rendering fields by type
└── FormConfig drives everything: schema, fields, actions, UI
Before creating any files, invoke the automation:path-resolver skill with your input parameters (featureName, subFeatureName, featureType, product, role) to resolve the correct paths. Use the returned Feature Path as parentDirectory below. The backend/ directory sits as a sibling at the feature/product/role level.
parentDirectory/
├── options/ # Optional - only if select/radio/selectable-cards fields exist
│ └── use{OptionName}Options.ts
├── hooks/ # Optional - for combobox or cross-field validation hooks
│ ├── use{HookName}Search.ts # Only if combobox fields exist
│ ├── use{HookName}FieldsState.ts # Only if search hook has a "not found" item
│ └── use{Name}CrossValidation.ts # Only if cross-field peer validation exists
├── {formName}Schema.ts
├── use{FormName}FormFields.ts
├── {formName}FormAction.ts
├── get{FormName}DefaultValues.ts
├── use{FormName}FormConfig.tsx
├── {FormName}FormFields.tsx # Skip if ALL fields are hidden
└── {FormName}Form.tsx
When a form is used for both creating and updating a model:
parentDirectory/
├── create/
│ ├── Create{FormName}Form.tsx
│ └── useCreate{FormName}FormConfig.tsx
├── update/
│ ├── Update{FormName}Form.tsx
│ └── useUpdate{FormName}FormConfig.tsx
├── {formName}FormAction.ts # Shared - exports create + update actions
├── {FormName}FormFields.tsx # Shared
├── {formName}Schema.ts # Shared - base schema + extended schema with ID
└── use{FormName}FormFields.ts # Shared
When an inquiry process step also serves as the entry point for creating new inquiries (the "new" page pattern), add a new/ subdirectory. The new/ variant reuses the shared schema, fields, options, and FormFields component but has its own form action (which calls startInquiry + updateDetails), form config (no back button), and form component.
parentDirectory/forms/inquiryDetails/
├── new/ # New inquiry entry point
│ ├── New{FormName}Form.tsx # Uses shared FormFields, own config
│ ├── new{FormName}FormAction.ts # Calls startInquiry + updateDetails
│ └── useNew{FormName}FormConfig.tsx # No back button, portal-aware
├── options/ # Shared
│ └── use{OptionName}Options.ts
├── {formName}Schema.ts # Shared
├── use{FormName}FormFields.ts # Shared
├── {formName}FormAction.ts # Regular update-only action
├── get{FormName}DefaultValues.ts # Shared
├── use{FormName}FormConfig.tsx # Regular config with back button
├── {FormName}FormFields.tsx # Shared
├── {FormName}Form.tsx # Regular form component
└── mapToPayload.ts # Shared payload mapper (optional)
For full details on the new inquiry pattern (form action, config, page setup, default values), see new-inquiry.md.
Plan files in this logical sequence to ensure consistent types and imports:
options/use{OptionName}Options.ts (only for select, radio-group, selectable-cards fields)hooks/use{HookName}Search.ts (combobox fields), hooks/use{HookName}FieldsState.ts (if search hook has a "not found" item), hooks/use{Name}CrossValidation.ts (cross-field peer validation){formName}Schema.tsuse{FormName}FormFields.ts{formName}FormAction.tsget{FormName}DefaultValues.tsuse{FormName}FormConfig.tsx{FormName}FormFields.tsx (skip if only hidden fields){FormName}Form.tsxAll file names, types, and imports are deterministic from the featureName. After planning the content of all files, write them in parallel:
Issue multiple Write tool calls in a single message for each batch. Do NOT create files one at a time.
Do NOT run or check diagnostics until ALL files have been written. Files import from each other, so diagnostics will show false errors until the full set exists.
// Types and core
import { FormConfig, FormState, FormFieldsType, FieldNamesType } from "@finstreet/forms";
import { createFormFieldNames } from "@finstreet/forms/lib";
import { createDynamicFormField } from "@finstreet/forms";
// Custom validations - ALWAYS import from @finstreet/forms/customValidations
import { YesNoValidationSchema, YesNoOptions, trimmedString } from "@finstreet/forms/customValidations";
// Zod - ALWAYS use the project's custom implementation
import * as z from "@/lib/zod";
// DynamicFormField - ALWAYS use the project's shared component
import { DynamicFormField } from "@/shared/components/form/DynamicFormField";
// Form component
import { Form } from "@/shared/components/form/Form";
// react-hook-form - ALWAYS import from @finstreet/forms/rhf
import { DeepPartial, useFieldArray } from "@finstreet/forms/rhf";
// UI components
import { Button } from "@finstreet/ui/components/base/Button";
import { HStack, VStack, Box } from "@styled-system/jsx";
import { Fields, FieldsHStack, FieldsHStackItem } from "@finstreet/ui/components/pageLayout/Fields";
import { Typography } from "@finstreet/ui/components/base/Typography";
import { Fieldset, FieldsetLegend } from "@finstreet/ui/components/base/Form/Fieldset";
// Translations
import { useExtracted } from "next-intl";
// Date parsing for datepicker constraints (minDate/maxDate)
import { parseDate } from "@ark-ui/react";
// Boolean to YesNo mapping for default values
import { transformBooleanToYesNoOption } from "@/shared/components/form/YesNoRadioGroup/options";
// Cache revalidation in form actions
import { revalidatePath } from "next/cache";
// Portal and product context (for modal forms and portal-aware actions)
import { usePortal } from "@/shared/context/portal/portalContext";
import { useProduct } from "@/shared/context/product/productContext";
import { Portal, Product } from "@/shared/types/Portal";
// Router for back navigation in formConfig
import { useRouter } from "next/navigation";
// Conditional rendering in FormFields beyond renderCondition
import { useWatch } from "react-hook-form";
// Combobox types (for search hooks)
import { ComboboxItem } from "@finstreet/ui/components/base/Combobox";
import { UseFormSetValue } from "react-hook-form";
Each step has detailed documentation in a supporting file:
formId, zustand store, and onPendingChange), see external-actions.mdValidatedSubmitButton and ConfirmationModal), see confirmation-modal.mdWhen a form action needs IDs (e.g., financingCaseId), they MUST be:
z.string().min(1) or z.trimmedString().min(1)type: "hidden" in useFormFieldsformData in the form actionFor field-type defaults, templates, transformation patterns, and nested objects, see default-values.md.
Always the same across all forms:
type FormState = {
error: string | null;
message: string | null;
} | null;
Every schema file MUST export these types:
import { FormConfig, FormState } from "@finstreet/forms";
import { DeepPartial } from "@finstreet/forms/rhf";
export type {FormName}Type = z.input<typeof {formName}Schema>;
export type {FormName}OutputType = z.output<typeof {formName}Schema>;
export type {FormName}FormState = FormState;
export type {FormName}FormConfig = FormConfig<{FormName}FormState, {FormName}Type, {FormName}OutputType>;
export type {FormName}DefaultValues = DeepPartial<{FormName}Type>;