Generate FOSMVVM Form Specifications (Fields protocols) with validation and localization. Use when defining user input contracts for forms, request bodies, or any ValidatableModel.
Generates FOSMVVM Form Specification protocols with validation and localization. Use when defining user input contracts for forms, request bodies, or any ValidatableModel.
/plugin marketplace add foscomputerservices/FOSUtilities/plugin install fosmvvm-generators@fosmvvm-toolsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Generate Form Specifications following FOSMVVM patterns.
For full architecture context, see FOSMVVMArchitecture.md
A Form Specification (implemented as a {Name}Fields protocol) is the single source of truth for user input. It answers:
The Form Specification is defined once, used everywhere:
// Same protocol adopted by different consumers:
struct CreateIdeaRequestBody: ServerRequestBody, IdeaFields { ... } // HTTP transmission
@ViewModel struct IdeaFormViewModel: IdeaFields { ... } // Form rendering
final class Idea: Model, IdeaFields { ... } // Persistence validation
This ensures:
Form Specifications integrate with:
LocalizableStringValidatableModel protocolValidatableModelfosmvvm-fluent-datamodel-generator needs form fields for a DataModelA complete Form Specification consists of 3 files:
| File | Purpose |
|---|---|
{Name}Fields.swift | Protocol + FormField definitions + validation methods |
{Name}FieldsMessages.swift | @FieldValidationModel struct with @LocalizedString properties |
{Name}FieldsMessages.yml | YAML localization (titles, placeholders, error messages) |
Replace placeholders with your project's actual paths:
| Placeholder | Description | Example |
|---|---|---|
{ViewModelsTarget} | Shared ViewModels SPM target | ViewModels, SharedViewModels |
{ResourcesPath} | Localization resources path | Sources/Resources |
Expected Structure:
Sources/
{ViewModelsTarget}/
FieldModels/
{Name}Fields.swift
{Name}FieldsMessages.swift
{ResourcesPath}/
FieldModels/
{Name}FieldsMessages.yml
Before generating, clarify:
CreateIdea, UpdateProfile, LoginCredentialsFor each field, determine:
{Name}Fields.swift - The protocol and validation logic{Name}FieldsMessages.swift - The localized message struct{Name}FieldsMessages.yml - The actual localized stringsAfter generation:
swift build to verify compilationpublic protocol {Name}Fields: ValidatableModel, Codable, Sendable {
var fieldName: FieldType { get set }
var {name}ValidationMessages: {Name}FieldsMessages { get }
}
static var contentField: FormField<String?> { .init(
fieldId: .init(id: "content"),
title: .localized(for: {Name}FieldsMessages.self, propertyName: "content", messageKey: "title"),
placeholder: .localized(for: {Name}FieldsMessages.self, propertyName: "content", messageKey: "placeholder"),
type: .textArea(inputType: .text),
options: [
.required(value: true)
] + FormInputOption.rangeLength(contentRange)
) }
| FormFieldType | Use Case |
|---|---|
.text(inputType:) | Single-line input |
.textArea(inputType:) | Multi-line input |
.checkbox | Boolean toggle |
.select | Dropdown selection |
.colorPicker | Color selection |
| FormInputType | Keyboard/Autofill |
|---|---|
.text | Default keyboard |
.emailAddress | Email keyboard, email autofill |
.password | Secure entry |
.tel | Phone keyboard |
.url | URL keyboard |
.date, .datetimeLocal | Date picker |
.givenName, .familyName | Name autofill |
internal func validateContent(_ fields: [FormFieldBase]?) -> [ValidationResult]? {
guard fields == nil || (fields?.contains(Self.contentField) == true) else {
return nil
}
var result = [ValidationResult]()
if content.isEmpty {
result.append(.init(
status: .error,
field: Self.contentField,
message: {name}ValidationMessages.contentRequiredMessage
))
} else if !Self.contentRange.contains(NSString(string: content).length) {
result.append(.init(
status: .error,
field: Self.contentField,
message: {name}ValidationMessages.contentOutOfRangeMessage
))
}
return result.isEmpty ? nil : result
}
@FieldValidationModel public struct {Name}FieldsMessages {
@LocalizedString("content", messageGroup: "validationMessages", messageKey: "required")
public var contentRequiredMessage
@LocalizedString("content", messageGroup: "validationMessages", messageKey: "outOfRange")
public var contentOutOfRangeMessage
}
en:
{Name}FieldsMessages:
content:
title: "Content"
placeholder: "Enter your content..."
validationMessages:
required: "Content is required"
outOfRange: "Content must be between 1 and 10,000 characters"
| Concept | Convention | Example |
|---|---|---|
| Protocol | {Name}Fields | IdeaFields, CreateIdeaFields |
| Messages struct | {Name}FieldsMessages | IdeaFieldsMessages |
| Messages property | {name}ValidationMessages | ideaValidationMessages |
| Field definition | {fieldName}Field | contentField |
| Range constant | {fieldName}Range | contentRange |
| Validate method | validate{FieldName} | validateContent |
| Required message | {fieldName}RequiredMessage | contentRequiredMessage |
| OutOfRange message | {fieldName}OutOfRangeMessage | contentOutOfRangeMessage |
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2024-12-24 | Initial skill |
| 2.0 | 2024-12-26 | Rewritten with conceptual foundation; generalized from Kairos-specific |
Use when working with Payload CMS projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.