Skill: foscomputerservices/fosmvvm-fields-generator (version 2.0.6)

--- name: fosmvvm-fields-generator description: Generate FOSMVVM Fields protocols with validation rules, FormField definitions, and localized messages. Define form contracts once, validate everywhere. homepage: https://github.com/foscomputerservices/FOSUtilities metadata: {"clawdbot": {"emoji": "📋", "os": ["darwin", "linux"]}} --- # FOSMVVM Fields Generator Generate Form Specifications following FOSMVVM patterns. ## Conceptual Foundation > For full architecture context, see [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) | [OpenClaw reference]({baseDir}/references/FOSMVVMArchitecture.md) A **Form Specification** (implemented as a `{Name}Fields` protocol) is the **single source of truth** for user input. It answers: 1. **What data** can the user provide? (properties) 2. **How should it be presented?** (FormField with type, keyboard, autofill semantics) 3. **What constraints apply?** (validation rules) 4. **What messages should be shown?** (localized titles, placeholders, errors) ### Why This Matters The Form Specification is **defined once, used everywhere**: ```swift // 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: - **Consistent validation** - Same rules on client and server - **Shared localization** - One YAML file, used everywhere - **Single source of truth** - Change once, applies everywhere ### Connection to FOSMVVM Form Specifications integrate with: - **Localization System** - FormField titles/placeholders and validation messages use `LocalizableString` - **Validation System** - Implements `ValidatableModel` protocol - **Request System** - RequestBody types adopt Fields for validated transmission - **ViewModel System** - ViewModels adopt Fields for form rendering ## When to Use This Skill - Defining a new form (create, edit, filter, search) - Adding validation to a request body - Any type that needs to conform to `ValidatableModel` - When [REDACTED_SECRET] needs form fields for a DataModel ## What This Skill Generates A 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) | ## Project Structure Configuration 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 ``` ## How to Use This Skill **Invocation:** /fosmvvm-fields-generator **Prerequisites:** - Form purpose understood from conversation context - Field requirements discussed (names, types, constraints) - Entity relationship identified (what is this form creating/editing) **Workflow integration:** This skill is used when defining form validation and user input contracts. The skill references conversation context automatically—no file paths or Q&A needed. Often precedes fosmvvm-fluent-datamodel-generator for form-backed models. ## Pattern Implementation This skill references conversation context to determine Fields protocol structure: ### Form Analysis From conversation context, the skill identifies: - **Form purpose** (create, edit, filter, login, settings) - **Entity relation** (User, Idea, Document - what's being created/edited) - **Protocol naming** (CreateIdeaFields, UpdateProfile, LoginCredentials) ### Field Design For each field from requirements: - **Property specification** (name, type, optional vs required) - **Presentation type** (FormFieldType: text, textArea, select, checkbox) - **Input semantics** (FormInputType: email, password, tel, date) - **Constraints** (required, length range, value range, date range) - **Localization** (title, placeholder, validation error messages) ### File Generation Order 1. Fields protocol with FormField definitions and validation 2. FieldsMessages struct with @LocalizedString properties 3. FieldsMessages YAML with localized strings ### Context Sources Skill references information from: - **Prior conversation**: Form requirements, field specifications discussed - **Specification files**: If Claude has read form specs into context - **Existing patterns**: From codebase analysis of similar Fields protocols ## Key Patterns ### Protocol Structure ```swift public protocol {Name}Fields: ValidatableModel, Codable, Sendable { var fieldName: FieldType { get set } var {name}ValidationMessages: {Name}FieldsMessages { get } } ``` ### FormField Definition ```swift 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) ) } ``` ### FormField Types Reference | FormFieldType | Use Case | |---------------|----------| | `.text(inputType:)` | Single-line input | | `.textArea(inputType:)` | Multi-line input | | `.checkbox` | Boolean toggle | | `.select` | Dropdown selection | | `.colorPicker` | Color selection | ### FormInputType Reference (common ones) | 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 | ### Validation Method Pattern ```swift 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 } ``` ### Messages Struct Pattern ```swift @FieldValidationModel public struct {Name}FieldsMessages { @LocalizedString("content", messageGroup: "validationMessages", messageKey: "required") public var contentRequiredMessage @LocalizedString("content", messageGroup: "validationMessages", messageKey: "outOfRange") public var contentOutOfRangeMessage } ``` ### YAML Structure ```yaml 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" ``` ## Naming Conventions | 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` | ## See Also - [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) - Full FOSMVVM architecture reference - [fosmvvm-viewmodel-generator](../fosmvvm-viewmodel-generator/SKILL.md) - For ViewModels that adopt Fields - [fosmvvm-fluent-datamodel-generator](../fosmvvm-fluent-datamodel-generator/SKILL.md) - For Fluent DataModels that implement Fields - [reference.md](reference.md) - Complete file templates ## Version History | Version | Date | Changes | |---------|------|---------| | 1.0 | 2024-12-24 | Initial skill | | 2.0 | 2024-12-26 | Rewritten with conceptual foundation; generalized from Kairos-specific | | 2.1 | 2026-01-24 | Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths. |

path: reference.md
content: # FOSMVVM Fields Generator - Reference Templates

Complete file templates for generating Form Specifications.

> **Conceptual context:** See [SKILL.md](SKILL.md) for when and why to use this skill.
> **Architecture context:** See [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) for full FOSMVVM understanding.

## Placeholders

| Placeholder | Replace With | Example |
|-------------|--------------|---------|
| `{Name}` | Form specification name (PascalCase) | `CreateIdea`, `User`, `LoginCredentials` |
| `{name}` | Same, but camelCase | `createIdea`, `user`, `loginCredentials` |
| `{ViewModelsTarget}` | Your ViewModels SPM target | `ViewModels`, `SharedViewModels` |
| `{ResourcesPath}` | Your localization resources path | `Sources/Resources` |

---

## File 1: {Name}Fields.swift

**Location:** `Sources/{ViewModelsTarget}/FieldModels/{Name}Fields.swift`

```swift
import FOSFoundation
import FOSMVVM
import Foundation

/// Form Specification for {describe the form purpose}.
///
/// This protocol defines:
/// - The user-editable fields
/// - Form presentation metadata (FormField definitions)
/// - Validation rules and localized error messages
///
/// Adopted by: RequestBody types, ViewModels, and optionally DataModels.
public protocol {Name}Fields: ValidatableModel, Codable, Sendable {
    // MARK: - Field Declarations

    // var fieldName: FieldType { get set }

    // MARK: - Validation Messages

    var {name}ValidationMessages: {Name}FieldsMessages { get }
}

// MARK: - Enums (if needed for constrained fields)

// public enum {Name}Status: String, CaseIterable, Equatable, Codable, Sendable {
//     case draft = "draft"
//     case published = "published"
// }

// MARK: - Field Definitions & Validation

public extension {Name}Fields {
    // MARK: Field Constraints

    // static var fieldNameRange: ClosedRange<Int> { 1...1024 }

    // MARK: FormField Definitions

    // static var fieldNameField: FormField<String?> { .init(
    //     fieldId: .init(id: "field_name"),
    //     title: .localized(for: {Name}FieldsMessages.self, propertyName: "fieldName", messageKey: "title"),
    //     placeholder: .localized(for: {Name}FieldsMessages.self, propertyName: "fieldName", messageKey: "placeholder"),
    //     type: .text(inputType: .text),
    //     options: [
    //         .required(value: true),
    //         .autocomplete(value: .off),
    //         .autocapitalize(value: .never)
    //     ] + FormInputOption.rangeLength(fieldNameRange)
    // ) }

    // MARK: Field Validation Methods

    // internal func validateFieldName(_ fields: [FormFieldBase]?) -> [ValidationResult]? {
    //     guard fields == nil || (fields?.contains(Self.fieldNameField) == true) else {
    //         return nil
    //     }
    //
    //     var result = [ValidationResult]()
    //
    //     if fieldName.isEmpty {
    //         result.append(.init(
    //             status: .error,
    //             field: Self.fieldNameField,
    //             message: {name}ValidationMessages.fieldNameRequiredMessage
    //         ))
    //     } else if !Self.fieldNameRange.contains(NSString(string: fieldName).length) {
    //         result.append(.init(
    //             status: .error,
    //             field: Self.fieldNameField,
    //             message: {name}ValidationMessages.fieldNameOutOfRangeMessage
    //         ))
    //     }
    //
    //     return result.isEmpty ? nil : result
    // }

    // MARK: ValidatableModel Implementation

    func {name}FieldsValidateModel(
        validations: Validations,
        fields: [FormFieldBase]?
    ) -> [ValidationResult]? {
        var result = [ValidationResult]()

        // Aggregate all field validations:
        // result += validateFieldName(fields)

        return result.isEmpty ? nil : result
    }

    func validate(fields: [FormFieldBase]?, validations: Validations) -> ValidationResult.Status? {
        let result = {name}FieldsValidateModel(validations: validations, fields: fields) ?? []

        if !result.isEmpty {
            validations.validations = result
        }

        return .init(for: result)
    }
}
```

---

## File 2: {Name}FieldsMessages.swift

**Location:** `Sources/{ViewModelsTarget}/FieldModels/{Name}FieldsMessages.swift`

```swift
import FOSFoundation
import FOSMVVM
import Foundation

/// Localized validation messages for {Name}Fields.
///
/// Each property maps to a key path in {Name}FieldsMessages.yml.
/// The @FieldValidationModel macro generates propertyNames() for localization binding.
@FieldValidationModel public struct {Name}FieldsMessages {
    // MARK: - Field Titles & Placeholders (used by FormField definitions)

    // Note: Titles and placeholders are referenced directly in FormField definitions
    // via .localized(for:propertyName:messageKey:) - no properties needed here.

    // MARK: - Validation Messages

    // @LocalizedString("fieldName", messageGroup: "validationMessages", messageKey: "required")
    // public var fieldNameRequiredMessage

    // @LocalizedString("fieldName", messageGroup: "validationMessages", messageKey: "outOfRange")
    // public var fieldNameOutOfRangeMessage

    public init() {}
}
```

---

## File 3: {Name}FieldsMessages.yml

**Location:** `{ResourcesPath}/FieldModels/{Name}FieldsMessages.yml`

```yaml
en:
  {Name}FieldsMessages:
    fieldName:
      title: "Field Display Name"
      placeholder: "Enter value..."
      validationMessages:
        required: "Field name is required"
        outOfRange: "Field name must be between X and Y characters"
```

---

## Complete Example: IdeaFields

A full implementation showing multiple fields, enums, and validation.

### IdeaFields.swift

```swift
import FOSFoundation
import FOSMVVM
import Foundation

/// Form Specification for Idea entities.
///
/// Defines the editable fields, validation rules, and localized messages
/// for creating and editing Ideas.
public protocol IdeaFields: ValidatableModel, Codable, Sendable {
    var id: ModelIdType? { get set }
    var content: String { get set }
    var department: Department { get set }
    var status: IdeaStatus { get set }
    var metadata: [String: String]? { get set }

    var ideaValidationMessages: IdeaFieldsMessages { get }
}

public enum Department: String, CaseIterable, Equatable, Codable, Sendable {
    case strategic
    case product
    case content
    case consulting
    case operations
}

public enum IdeaStatus: String, CaseIterable, Equatable, Codable, Sendable {
    case queued
    case exploring
    case parking
    case implementing
    case complete
    case discarded
}

public extension IdeaFields {
    // MARK: Field Constraints

    static var contentRange: ClosedRange<Int> { 1...10000 }

    // MARK: FormField Definitions

    static var contentField: FormField<String?> { .init(
        fieldId: .init(id: "content"),
        title: .localized(for: IdeaFieldsMessages.self, propertyName: "content", messageKey: "title"),
        placeholder: .localized(for: IdeaFieldsMessages.self, propertyName: "content", messageKey: "placeholder"),
        type: .textArea(inputType: .text),
        options: [
            .required(value: true)
        ] + FormInputOption.rangeLength(contentRange)
    ) }

    static var departmentField: FormField<String?> { .init(
        fieldId: .init(id: "department"),
        title: .localized(for: IdeaFieldsMessages.self, propertyName: "department", messageKey: "title"),
        type: .select,
        options: [.required(value: true)]
    ) }

    static var statusField: FormField<String?> { .init(
        fieldId: .init(id: "status"),
        title: .localized(for: IdeaFieldsMessages.self, propertyName: "status", messageKey: "title"),
        type: .select,
        options: [.required(value: true)]
    ) }

    // MARK: Validation Methods

    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: ideaValidationMessages.contentRequiredMessage
            ))
        } else if !Self.contentRange.contains(NSString(string: content).length) {
            result.append(.init(
                status: .error,
                field: Self.contentField,
                message: ideaValidationMessages.contentOutOfRangeMessage
            ))
        }

        return result.isEmpty ? nil : result
    }

    // MARK: ValidatableModel

    func ideaFieldsValidateModel(
        validations: Validations,
        fields: [FormFieldBase]?
    ) -> [ValidationResult]? {
        var result = [ValidationResult]()
        result += validateContent(fields)
        return result.isEmpty ? nil : result
    }

    func validate(fields: [FormFieldBase]?, validations: Validations) -> ValidationResult.Status? {
        let result = ideaFieldsValidateModel(validations: validations, fields: fields) ?? []
        if !result.isEmpty {
            validations.validations = result
        }
        return .init(for: result)
    }
}
```

### IdeaFieldsMessages.swift

```swift
import FOSFoundation
import FOSMVVM
import Foundation

@FieldValidationModel public struct IdeaFieldsMessages {
    @LocalizedString("content", messageGroup: "validationMessages", messageKey: "required")
    public var contentRequiredMessage

    @LocalizedString("content", messageGroup: "validationMessages", messageKey: "outOfRange")
    public var contentOutOfRangeMessage

    public init() {}
}
```

### IdeaFieldsMessages.yml

```yaml
en:
  IdeaFieldsMessages:
    content:
      title: "Idea Content"
      placeholder: "Describe your idea..."
      validationMessages:
        required: "Content is required"
        outOfRange: "Content must be between 1 and 10,000 characters"
    department:
      title: "Department"
    status:
      title: "Status"
```

---

## Adopting the Form Specification

### In a DataModel (Fluent)

```swift
final class Idea: DataModel, IdeaFields, Hashable, @unchecked Sendable {
    @ID(key: .id) var id: ModelIdType?
    @Field(key: "content") var content: String
    @Field(key: "department") var department: Department
    @Field(key: "status") var status: IdeaStatus
    @OptionalField(key: "metadata") var metadata: [String: String]?

    let ideaValidationMessages: IdeaFieldsMessages

    init() {
        self.ideaValidationMessages = .init()
    }
}
```

### In a RequestBody

```swift
public final class CreateIdeaRequest: CreateRequest, @unchecked Sendable {
    public struct RequestBody: IdeaFields, ServerRequestBody, Stubbable {
        public var id: ModelIdType? = nil
        public var content: String
        public var department: Department
        public var status: IdeaStatus = .queued
        public var metadata: [String: String]?

        public let ideaValidationMessages: IdeaFieldsMessages

        public init(content: String, department: Department) {
            self.content = content
            self.department = department
            self.ideaValidationMessages = .init()
        }

        public static func stub() -> Self {
            .init(content: "Stub idea", department: .product)
        }
    }
}
```

### In Tests

```swift
private struct TestIdea: IdeaFields {
    var id: ModelIdType?
    var content: String
    var department: Department
    var status: IdeaStatus
    var metadata: [String: String]?

    private(set) var ideaValidationMessages: IdeaFieldsMessages

    mutating func localizeMessages(encoder: JSONEncoder) throws {
        ideaValidationMessages = try IdeaFieldsMessages().toJSON(encoder: encoder).fromJSON()
    }

    init(
        id: ModelIdType? = .init(),
        content: String = "Test content",
        department: Department = .product,
        status: IdeaStatus = .queued,
        metadata: [String: String]? = nil
    ) {
        self.id = id
        self.content = content
        self.department = department
        self.status = status
        self.metadata = metadata
        self.ideaValidationMessages = .init()
    }
}
```

---

## Quick Reference: Naming Conventions

| Concept | Pattern | Example |
|---------|---------|---------|
| Protocol | `{Name}Fields` | `IdeaFields` |
| Messages struct | `{Name}FieldsMessages` | `IdeaFieldsMessages` |
| Messages property | `{name}ValidationMessages` | `ideaValidationMessages` |
| FormField definition | `{fieldName}Field` | `contentField` |
| Range constant | `{fieldName}Range` | `contentRange` |
| Validate method | `validate{FieldName}` | `validateContent` |
| Required message | `{fieldName}RequiredMessage` | `contentRequiredMessage` |
| OutOfRange message | `{fieldName}OutOfRangeMessage` | `contentOutOfRangeMessage` |


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)