{
  "schemaVersion": "1.0",
  "item": {
    "slug": "fosmvvm-viewmodel-generator",
    "name": "FOSMVVM ViewModel Generator",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-viewmodel-generator",
    "canonicalUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-viewmodel-generator",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/fosmvvm-viewmodel-generator",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fosmvvm-viewmodel-generator",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md",
      "reference.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-04-30T16:55:25.780Z",
      "expiresAt": "2026-05-07T16:55:25.780Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=network",
        "contentDisposition": "attachment; filename=\"network-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/fosmvvm-viewmodel-generator"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/fosmvvm-viewmodel-generator",
    "agentPageUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "FOSMVVM ViewModel Generator",
        "body": "Generate ViewModels following FOSMVVM architecture patterns."
      },
      {
        "title": "Conceptual Foundation",
        "body": "For full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference\n\nA ViewModel is the bridge in the Model-View-ViewModel architecture:\n\n┌─────────────┐      ┌─────────────────┐      ┌─────────────┐\n│    Model    │ ───► │    ViewModel    │ ───► │    View     │\n│   (Data)    │      │  (The Bridge)   │      │  (SwiftUI)  │\n└─────────────┘      └─────────────────┘      └─────────────┘\n\nKey insight: In FOSMVVM, ViewModels are:\n\nCreated by a Factory (either server-side or client-side)\nLocalized during encoding (resolves all @LocalizedString references)\nConsumed by Views which just render the localized data"
      },
      {
        "title": "First Decision: Hosting Mode",
        "body": "This is a per-ViewModel decision. An app can mix both modes - for example, a standalone iPhone app with server-based sign-in.\n\nThe key question: Where does THIS ViewModel's data come from?\n\nData SourceHosting ModeFactoryServer/DatabaseServer-HostedHand-writtenLocal state/preferencesClient-HostedMacro-generatedResponseError (caught error)Client-HostedMacro-generated"
      },
      {
        "title": "Server-Hosted Mode",
        "body": "When data comes from a server:\n\nFactory is hand-written on server (ViewModelFactory protocol)\nFactory queries database, builds ViewModel\nServer localizes during JSON encoding\nClient receives fully localized ViewModel\n\nExamples: Sign-in screen, user profile from API, dashboard with server data"
      },
      {
        "title": "Client-Hosted Mode",
        "body": "When data is local to the device:\n\nUse @ViewModel(options: [.clientHostedFactory])\nMacro auto-generates factory from init parameters\nClient bundles YAML resources\nClient localizes during encoding\n\nExamples: Settings screen, onboarding, offline-first features, error display"
      },
      {
        "title": "Error Display Pattern",
        "body": "Error display is a classic client-hosted scenario. You already have the data from ResponseError - just wrap it in a specific ViewModel for that error:\n\n// Specific ViewModel for MoveIdeaRequest errors\n@ViewModel(options: [.clientHostedFactory])\nstruct MoveIdeaErrorViewModel {\n    let message: LocalizableString\n    let errorCode: String\n\n    public var vmId = ViewModelId()\n\n    // Takes the specific ResponseError\n    init(responseError: MoveIdeaRequest.ResponseError) {\n        self.message = responseError.message\n        self.errorCode = responseError.code.rawValue\n    }\n}\n\nUsage:\n\ncatch let error as MoveIdeaRequest.ResponseError {\n    let vm = MoveIdeaErrorViewModel(responseError: error)\n    return try await req.view.render(\"Shared/ToastView\", vm)\n}\n\nEach error scenario gets its own ViewModel:\n\nMoveIdeaErrorViewModel for MoveIdeaRequest.ResponseError\nCreateIdeaErrorViewModel for CreateIdeaRequest.ResponseError\nSettingsValidationErrorViewModel for settings form errors\n\nDon't create a generic \"ToastViewModel\" or \"ErrorViewModel\" - that's unified error architecture, which we avoid.\n\nKey insights:\n\nNo server request needed - you already caught the error\nThe LocalizableString properties in ResponseError are already localized (server did it)\nStandard ViewModel → View encoding chain handles this correctly; already-localized strings pass through unchanged\nClient-hosted ViewModel wraps existing data; the macro generates the factory"
      },
      {
        "title": "Hybrid Apps",
        "body": "Many apps use both:\n\n┌───────────────────────────────────────────────┐\n│               iPhone App                       │\n├───────────────────────────────────────────────┤\n│ SettingsViewModel           → Client-Hosted   │\n│ OnboardingViewModel         → Client-Hosted   │\n│ MoveIdeaErrorViewModel      → Client-Hosted   │  ← Error display\n│ SignInViewModel             → Server-Hosted   │\n│ UserProfileViewModel        → Server-Hosted   │\n└───────────────────────────────────────────────┘\n\nSame ViewModel patterns work in both modes - only the factory creation differs."
      },
      {
        "title": "Core Responsibility: Shaping Data",
        "body": "A ViewModel's job is shaping data for presentation. This happens in two places:\n\nFactory - what data is needed, how to transform it\nLocalization - how to present it in context (including locale-aware ordering)\n\nThe View just renders - it should never compose, format, or reorder ViewModel properties."
      },
      {
        "title": "What a ViewModel Contains",
        "body": "A ViewModel answers: \"What does the View need to display?\"\n\nContent TypeHow It's RepresentedExampleStatic UI text@LocalizedStringPage titles, button labels (fixed text)Dynamic enum valuesLocalizableString (stored)Status/state display (see Enum Localization Pattern)Dynamic data in text@LocalizedSubs\"Welcome, %{name}!\" with substitutionsComposed text@LocalizedCompoundStringFull name from pieces (locale-aware order)Formatted datesLocalizableDatecreatedAt: LocalizableDateFormatted numbersLocalizableInttotalCount: LocalizableIntDynamic dataPlain propertiescontent: String, count: IntNested componentsChild ViewModelscards: [CardViewModel]"
      },
      {
        "title": "What a ViewModel Does NOT Contain",
        "body": "Database relationships (@Parent, @Siblings)\nBusiness logic or validation (that's in Fields protocols)\nRaw database IDs exposed to templates (use typed properties)\nUnlocalized strings that Views must look up"
      },
      {
        "title": "Anti-Pattern: Composition in Views",
        "body": "// ❌ WRONG - View is composing\nText(viewModel.firstName) + Text(\" \") + Text(viewModel.lastName)\n\n// ✅ RIGHT - ViewModel provides shaped result\nText(viewModel.fullName)  // via @LocalizedCompoundString\n\nIf you see + or string interpolation in a View, the shaping belongs in the ViewModel."
      },
      {
        "title": "ViewModel Protocol Hierarchy",
        "body": "public protocol ViewModel: ServerRequestBody, RetrievablePropertyNames, Identifiable, Stubbable {\n    var vmId: ViewModelId { get }\n}\n\npublic protocol RequestableViewModel: ViewModel {\n    associatedtype Request: ViewModelRequest\n}\n\nViewModel provides:\n\nServerRequestBody - Can be sent over HTTP as JSON\nRetrievablePropertyNames - Enables @LocalizedString binding (via @ViewModel macro)\nIdentifiable - Has vmId for SwiftUI identity\nStubbable - Has stub() for testing/previews\n\nRequestableViewModel adds:\n\nAssociated Request type for fetching from server"
      },
      {
        "title": "1. Top-Level (RequestableViewModel)",
        "body": "Represents a full page or screen. Has:\n\nAn associated ViewModelRequest type\nA ViewModelFactory that builds it from database\nChild ViewModels embedded within it\n\n@ViewModel\npublic struct DashboardViewModel: RequestableViewModel {\n    public typealias Request = DashboardRequest\n\n    @LocalizedString public var pageTitle\n    public let cards: [CardViewModel]  // Children\n    public var vmId: ViewModelId = .init()\n}"
      },
      {
        "title": "2. Child (plain ViewModel)",
        "body": "Nested components built by their parent's factory. No Request type.\n\n@ViewModel\npublic struct CardViewModel: Codable, Sendable {\n    public let id: ModelIdType\n    public let title: String\n    public let createdAt: LocalizableDate\n    public var vmId: ViewModelId = .init()\n}"
      },
      {
        "title": "Display vs Form ViewModels",
        "body": "ViewModels serve two distinct purposes:\n\nPurposeViewModel TypeAdopts Fields?Display data (read-only)Display ViewModelNoCollect user input (editable)Form ViewModelYes"
      },
      {
        "title": "Display ViewModels",
        "body": "For showing data - cards, rows, lists, detail views:\n\n@ViewModel\npublic struct UserCardViewModel {\n    public let id: ModelIdType\n    public let name: String\n    @LocalizedString public var roleDisplayName\n    public let createdAt: LocalizableDate\n    public var vmId: ViewModelId = .init()\n}\n\nCharacteristics:\n\nProperties are let (read-only)\nNo validation needed\nNo FormField definitions\nJust projects Model data for display"
      },
      {
        "title": "Form ViewModels",
        "body": "For collecting input - create forms, edit forms, settings:\n\n@ViewModel\npublic struct UserFormViewModel: UserFields {  // ← Adopts Fields!\n    public var id: ModelIdType?\n    public var email: String\n    public var firstName: String\n    public var lastName: String\n\n    public let userValidationMessages: UserFieldsMessages\n    public var vmId: ViewModelId = .init()\n}\n\nCharacteristics:\n\nProperties are var (editable)\nAdopts a Fields protocol for validation\nGets FormField definitions from Fields\nGets validation logic from Fields\nGets localized error messages from Fields"
      },
      {
        "title": "The Connection",
        "body": "┌─────────────────────────────────────────────────────────────────┐\n│                    UserFields Protocol                          │\n│        (defines editable properties + validation)               │\n│                                                                 │\n│  Adopted by:                                                    │\n│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │\n│  │ CreateUserReq   │  │ UserFormVM      │  │ User (Model)    │ │\n│  │ .RequestBody    │  │ (UI form)       │  │ (persistence)   │ │\n│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │\n│                                                                 │\n│  Same validation logic everywhere!                              │\n└─────────────────────────────────────────────────────────────────┘"
      },
      {
        "title": "Quick Decision Guide",
        "body": "The key question: \"Is the user editing data in this ViewModel?\"\n\nNo → Display ViewModel (no Fields)\nYes → Form ViewModel (adopt Fields)\n\nViewModelUser Edits?Adopt Fields?UserCardViewModelNoNoUserRowViewModelNoNoUserDetailViewModelNoNoUserFormViewModelYesUserFieldsCreateUserViewModelYesUserFieldsEditUserViewModelYesUserFieldsSettingsViewModelYesSettingsFields"
      },
      {
        "title": "When to Use This Skill",
        "body": "Creating a new page or screen\nAdding a new UI component (card, row, modal, etc.)\nDisplaying data from the database in a View\nFollowing an implementation plan that requires new ViewModels"
      },
      {
        "title": "Server-Hosted: Top-Level ViewModel (4 files)",
        "body": "FileLocationPurpose{Name}ViewModel.swift{ViewModelsTarget}/The ViewModel struct{Name}Request.swift{ViewModelsTarget}/The ViewModelRequest type{Name}ViewModel.yml{ResourcesPath}/Localization strings{Name}ViewModel+Factory.swift{WebServerTarget}/Factory that builds from DB"
      },
      {
        "title": "Client-Hosted: Top-Level ViewModel (2 files)",
        "body": "FileLocationPurpose{Name}ViewModel.swift{ViewModelsTarget}/ViewModel with clientHostedFactory option{Name}ViewModel.yml{ResourcesPath}/Localization strings (bundled in app)\n\nNo Request or Factory files needed - macro generates them!"
      },
      {
        "title": "Child ViewModels (1-2 files, either mode)",
        "body": "FileLocationPurpose{Name}ViewModel.swift{ViewModelsTarget}/The ViewModel struct{Name}ViewModel.yml{ResourcesPath}/Localization (if has @LocalizedString)\n\nNote: If child is only used by one parent and represents a summary/reference (not a full ViewModel), nest it inside the parent file instead. See Nested Child Types Pattern under Key Patterns."
      },
      {
        "title": "Project Structure Configuration",
        "body": "PlaceholderDescriptionExample{ViewModelsTarget}Shared ViewModels SPM targetViewModels{ResourcesPath}Localization resourcesSources/Resources{WebServerTarget}Server-side targetWebServer, AppServer"
      },
      {
        "title": "How to Use This Skill",
        "body": "Invocation:\n/fosmvvm-viewmodel-generator\n\nPrerequisites:\n\nView requirements understood from conversation context\nData source determined (server/database vs local state)\nDisplay vs Form decision made (if user input involved, Fields protocol exists)\n\nWorkflow integration:\nThis skill is typically used after discussing View requirements or reading specification files. The skill references conversation context automatically—no file paths or Q&A needed. For Form ViewModels, run fosmvvm-fields-generator first to create the Fields protocol."
      },
      {
        "title": "Pattern Implementation",
        "body": "This skill references conversation context to determine ViewModel structure:"
      },
      {
        "title": "Hosting Mode Detection",
        "body": "From conversation context, the skill identifies:\n\nData source (server/database vs local state/preferences)\nServer-hosted → Hand-written factory, server-side localization\nClient-hosted → Macro-generated factory, client-side localization"
      },
      {
        "title": "ViewModel Design",
        "body": "From requirements already in context:\n\nView purpose (page, modal, card, row component)\nData needs (from database query, from AppState, from caught error)\nStatic UI text (titles, labels, buttons requiring @LocalizedString)\nChild ViewModels (nested components)\nHierarchy level (top-level RequestableViewModel vs child ViewModel)"
      },
      {
        "title": "Property Planning",
        "body": "Based on View requirements:\n\nDisplay properties (data to render)\nLocalization requirements (which properties use @LocalizedString)\nIdentity strategy (singleton vmId vs instance-based vmId)\nForm adoption (whether ViewModel adopts Fields protocol)"
      },
      {
        "title": "File Generation",
        "body": "Server-Hosted Top-Level:\n\nViewModel struct (with RequestableViewModel)\nRequest type\nYAML localization\nFactory implementation\n\nClient-Hosted Top-Level:\n\nViewModel struct (with clientHostedFactory option)\nYAML localization\n\nChild (either mode):\n\nViewModel struct\nYAML localization (if needed)"
      },
      {
        "title": "Context Sources",
        "body": "Skill references information from:\n\nPrior conversation: View requirements, data sources discussed with user\nSpecification files: If Claude has read UI specs or feature docs into context\nFields protocols: From codebase or previous fosmvvm-fields-generator invocation"
      },
      {
        "title": "The @ViewModel Macro",
        "body": "Always use the @ViewModel macro - it generates the propertyNames() method required for localization binding.\n\nServer-Hosted (basic macro):\n\n@ViewModel\npublic struct MyViewModel: RequestableViewModel {\n    public typealias Request = MyRequest\n    @LocalizedString public var title\n    public var vmId: ViewModelId = .init()\n    public init() {}\n}\n\nClient-Hosted (with factory generation):\n\n@ViewModel(options: [.clientHostedFactory])\npublic struct SettingsViewModel {\n    @LocalizedString public var pageTitle\n    public var vmId: ViewModelId = .init()\n\n    public init(theme: Theme, notifications: NotificationSettings) {\n        // Init parameters become AppState properties\n    }\n}\n\n// Macro auto-generates:\n// - typealias Request = ClientHostedRequest\n// - struct AppState { let theme: Theme; let notifications: NotificationSettings }\n// - class ClientHostedRequest: ViewModelRequest { ... }\n// - static func model(context:) async throws -> Self { ... }"
      },
      {
        "title": "Stubbable Pattern",
        "body": "All ViewModels must support stub() for testing and SwiftUI previews:\n\npublic extension MyViewModel {\n    static func stub() -> Self {\n        .init(/* default values */)\n    }\n}"
      },
      {
        "title": "Identity: vmId",
        "body": "Every ViewModel needs a vmId for SwiftUI's identity system:\n\nSingleton (one per page): vmId = .init(type: Self.self)\nInstance (multiple per page): vmId = .init(id: id) where id: ModelIdType"
      },
      {
        "title": "Localization",
        "body": "Static UI text uses @LocalizedString:\n\n@LocalizedString public var pageTitle\n\nWith corresponding YAML:\n\nen:\n  MyViewModel:\n    pageTitle: \"Welcome\""
      },
      {
        "title": "Dates and Numbers",
        "body": "Never send pre-formatted strings. Use localizable types:\n\npublic let createdAt: LocalizableDate    // NOT String\npublic let itemCount: LocalizableInt     // NOT String\n\nThe client formats these according to user's locale and timezone."
      },
      {
        "title": "Enum Localization Pattern",
        "body": "For dynamic enum values (status, state, category), use a stored LocalizableString - NOT @LocalizedString.\n\n@LocalizedString always looks up the same key (the property name). A stored LocalizableString carries the dynamic key from the enum case.\n\n// Enum provides localizableString\npublic enum SessionState: String, CaseIterable, Codable, Sendable {\n    case pending, running, completed, failed\n\n    public var localizableString: LocalizableString {\n        .localized(for: Self.self, propertyName: rawValue)\n    }\n}\n\n// ViewModel stores it (NOT @LocalizedString)\n@ViewModel\npublic struct SessionCardViewModel {\n    public let state: SessionState                // Raw enum for data attributes\n    public let stateDisplay: LocalizableString   // Localized display text\n\n    public init(session: Session) {\n        self.state = session.state\n        self.stateDisplay = session.state.localizableString\n    }\n}\n\n# YAML keys match enum type and case names\nen:\n  SessionState:\n    pending: \"Pending\"\n    running: \"Running\"\n    completed: \"Completed\"\n    failed: \"Failed\"\n\nConstraint: LocalizableString only works in ViewModels encoded with localizingEncoder(). Do not use in Fluent JSONB fields or other persisted types."
      },
      {
        "title": "Child ViewModels",
        "body": "Top-level ViewModels contain their children:\n\n@ViewModel\npublic struct BoardViewModel: RequestableViewModel {\n    public let columns: [ColumnViewModel]\n    public let cards: [CardViewModel]\n}\n\nThe Factory builds all children when building the parent.\n\nNested Child Types Pattern\n\nWhen a child type is only used by one parent and represents a summary or reference (not a full ViewModel), nest it inside the parent:\n\n@ViewModel\npublic struct GovernancePrincipleCardViewModel: Codable, Sendable, Identifiable {\n    // Properties come first\n    public let versionHistory: [GovernancePrincipleVersionSummary]?\n    public let referencingDecisions: [GovernanceDecisionReference]?\n\n    // MARK: - Nested Types\n\n    /// Summary of a principle version for display in version history.\n    public struct GovernancePrincipleVersionSummary: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let version: Int\n        public let createdAt: Date\n\n        public init(id: ModelIdType, version: Int, createdAt: Date) {\n            self.id = id\n            self.version = version\n            self.createdAt = createdAt\n        }\n    }\n\n    /// Reference to a decision that cites this principle.\n    public struct GovernanceDecisionReference: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let title: String\n        public let decisionNumber: String\n        public let createdAt: Date\n\n        public init(id: ModelIdType, title: String, decisionNumber: String, createdAt: Date) {\n            self.id = id\n            self.title = title\n            self.decisionNumber = decisionNumber\n            self.createdAt = createdAt\n        }\n    }\n\n    // vmId and parent init follow\n    public let vmId: ViewModelId\n    // ...\n}\n\nReference: Sources/KairosModels/Governance/GovernancePrincipleCardViewModel.swift\n\nPlacement rules:\n\nNested types go AFTER the properties that reference them\nBefore vmId and the parent's init\nUse // MARK: - Nested Types section marker\nEach nested type gets its own doc comment\n\nConformances for nested types:\n\nCodable - for ViewModel encoding\nSendable - for Swift 6 concurrency\nIdentifiable - for SwiftUI ForEach if used in arrays\nStubbable - for testing/previews\n\nTwo-Tier Stubbable Pattern:\n\nNested types use fully qualified names in their extensions:\n\npublic extension GovernancePrincipleCardViewModel.GovernancePrincipleVersionSummary {\n    // Tier 1: Zero-arg convenience (ALWAYS delegates to tier 2)\n    static func stub() -> Self {\n        .stub(id: .init())\n    }\n\n    // Tier 2: Full parameterized with defaults\n    static func stub(\n        id: ModelIdType = .init(),\n        version: Int = 1,\n        createdAt: Date = .now\n    ) -> Self {\n        .init(id: id, version: version, createdAt: createdAt)\n    }\n}\n\npublic extension GovernancePrincipleCardViewModel.GovernanceDecisionReference {\n    static func stub() -> Self {\n        .stub(id: .init())\n    }\n\n    static func stub(\n        id: ModelIdType = .init(),\n        title: String = \"A Title\",\n        decisionNumber: String = \"DEC-12345\",\n        createdAt: Date = .now\n    ) -> Self {\n        .init(id: id, title: title, decisionNumber: decisionNumber, createdAt: createdAt)\n    }\n}\n\nWhy two tiers:\n\nTests often just need [.stub()] without caring about values\nOther tests need specific values: .stub(name: \"Specific Name\")\nZero-arg ALWAYS calls parameterized version (single source of truth)\n\nWhen to nest vs keep top-level:\n\nNest Inside ParentKeep Top-LevelChild is ONLY used by this parentChild is shared across multiple parentsChild represents subset/summaryChild is a full ViewModelChild has no @ViewModel macroChild has @ViewModel macroChild is not RequestableViewModelChild is RequestableViewModelExample: VersionSummary, ReferenceExample: CardViewModel, ListViewModel\n\nExamples:\n\nCard with nested summaries:\n\n@ViewModel\npublic struct TaskCardViewModel {\n    public let assignees: [AssigneeSummary]?\n\n    public struct AssigneeSummary: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let name: String\n        public let avatarUrl: String?\n        // ...\n    }\n}\n\nList with nested references:\n\n@ViewModel\npublic struct ProjectListViewModel {\n    public let relatedProjects: [ProjectReference]?\n\n    public struct ProjectReference: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let title: String\n        public let status: String\n        // ...\n    }\n}"
      },
      {
        "title": "Codable and Computed Properties",
        "body": "Swift's synthesized Codable only encodes stored properties. Since ViewModels are serialized (for JSON transport, Leaf rendering, etc.), computed properties won't be available.\n\n// Computed - NOT encoded, invisible after serialization\npublic var hasCards: Bool { !cards.isEmpty }\n\n// Stored - encoded, available after serialization\npublic let hasCards: Bool\n\nWhen to pre-compute:\n\nFor Leaf templates, you can often use Leaf's built-in functions directly:\n\n#if(count(cards) > 0) - no need for hasCards property\n#count(cards) - no need for cardCount property\n\nPre-compute only when:\n\nDirect array subscripts needed (firstCard - array indexing not documented in Leaf)\nComplex logic that's cleaner in Swift than in template\nPerformance-sensitive repeated calculations\n\nSee fosmvvm-leaf-view-generator for Leaf template patterns."
      },
      {
        "title": "File Templates",
        "body": "See reference.md for complete file templates."
      },
      {
        "title": "Naming Conventions",
        "body": "ConceptConventionExampleViewModel struct{Name}ViewModelDashboardViewModelRequest class{Name}RequestDashboardRequestFactory extension{Name}ViewModel+Factory.swiftDashboardViewModel+Factory.swiftYAML file{Name}ViewModel.ymlDashboardViewModel.yml"
      },
      {
        "title": "See Also",
        "body": "Architecture Patterns - Mental models (errors are data, type safety, etc.)\nFOSMVVMArchitecture.md - Full FOSMVVM architecture\nfosmvvm-fields-generator - For form validation\nfosmvvm-fluent-datamodel-generator - For Fluent persistence layer\nfosmvvm-leaf-view-generator - For Leaf templates that render ViewModels\nreference.md - Complete file templates"
      },
      {
        "title": "Version History",
        "body": "VersionDateChanges1.02024-12-24Initial skill2.02024-12-26Complete rewrite from architecture; generalized from Kairos-specific2.12024-12-26Added Client-Hosted mode support; per-ViewModel hosting decision2.22024-12-26Added shaping responsibility, @LocalizedSubs/@LocalizedCompoundString, anti-pattern2.32025-12-27Added Display vs Form ViewModels section; clarified Fields adoption2.42026-01-08Added Codable/computed properties section. Clarified when to pre-compute vs use Leaf built-ins.2.52026-01-19Added Enum Localization Pattern section. Clarified @LocalizedString is for static text only; stored LocalizableString for dynamic enum values.2.62026-01-24Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths.2.72026-01-25Added Nested Child Types Pattern section with two-tier Stubbable pattern, placement rules, conformances, and decision criteria for when to nest vs keep top-level."
      }
    ],
    "body": "FOSMVVM ViewModel Generator\n\nGenerate ViewModels following FOSMVVM architecture patterns.\n\nConceptual Foundation\n\nFor full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference\n\nA ViewModel is the bridge in the Model-View-ViewModel architecture:\n\n┌─────────────┐      ┌─────────────────┐      ┌─────────────┐\n│    Model    │ ───► │    ViewModel    │ ───► │    View     │\n│   (Data)    │      │  (The Bridge)   │      │  (SwiftUI)  │\n└─────────────┘      └─────────────────┘      └─────────────┘\n\n\nKey insight: In FOSMVVM, ViewModels are:\n\nCreated by a Factory (either server-side or client-side)\nLocalized during encoding (resolves all @LocalizedString references)\nConsumed by Views which just render the localized data\nFirst Decision: Hosting Mode\n\nThis is a per-ViewModel decision. An app can mix both modes - for example, a standalone iPhone app with server-based sign-in.\n\nThe key question: Where does THIS ViewModel's data come from?\n\nData Source\tHosting Mode\tFactory\nServer/Database\tServer-Hosted\tHand-written\nLocal state/preferences\tClient-Hosted\tMacro-generated\nResponseError (caught error)\tClient-Hosted\tMacro-generated\nServer-Hosted Mode\n\nWhen data comes from a server:\n\nFactory is hand-written on server (ViewModelFactory protocol)\nFactory queries database, builds ViewModel\nServer localizes during JSON encoding\nClient receives fully localized ViewModel\n\nExamples: Sign-in screen, user profile from API, dashboard with server data\n\nClient-Hosted Mode\n\nWhen data is local to the device:\n\nUse @ViewModel(options: [.clientHostedFactory])\nMacro auto-generates factory from init parameters\nClient bundles YAML resources\nClient localizes during encoding\n\nExamples: Settings screen, onboarding, offline-first features, error display\n\nError Display Pattern\n\nError display is a classic client-hosted scenario. You already have the data from ResponseError - just wrap it in a specific ViewModel for that error:\n\n// Specific ViewModel for MoveIdeaRequest errors\n@ViewModel(options: [.clientHostedFactory])\nstruct MoveIdeaErrorViewModel {\n    let message: LocalizableString\n    let errorCode: String\n\n    public var vmId = ViewModelId()\n\n    // Takes the specific ResponseError\n    init(responseError: MoveIdeaRequest.ResponseError) {\n        self.message = responseError.message\n        self.errorCode = responseError.code.rawValue\n    }\n}\n\n\nUsage:\n\ncatch let error as MoveIdeaRequest.ResponseError {\n    let vm = MoveIdeaErrorViewModel(responseError: error)\n    return try await req.view.render(\"Shared/ToastView\", vm)\n}\n\n\nEach error scenario gets its own ViewModel:\n\nMoveIdeaErrorViewModel for MoveIdeaRequest.ResponseError\nCreateIdeaErrorViewModel for CreateIdeaRequest.ResponseError\nSettingsValidationErrorViewModel for settings form errors\n\nDon't create a generic \"ToastViewModel\" or \"ErrorViewModel\" - that's unified error architecture, which we avoid.\n\nKey insights:\n\nNo server request needed - you already caught the error\nThe LocalizableString properties in ResponseError are already localized (server did it)\nStandard ViewModel → View encoding chain handles this correctly; already-localized strings pass through unchanged\nClient-hosted ViewModel wraps existing data; the macro generates the factory\nHybrid Apps\n\nMany apps use both:\n\n┌───────────────────────────────────────────────┐\n│               iPhone App                       │\n├───────────────────────────────────────────────┤\n│ SettingsViewModel           → Client-Hosted   │\n│ OnboardingViewModel         → Client-Hosted   │\n│ MoveIdeaErrorViewModel      → Client-Hosted   │  ← Error display\n│ SignInViewModel             → Server-Hosted   │\n│ UserProfileViewModel        → Server-Hosted   │\n└───────────────────────────────────────────────┘\n\n\nSame ViewModel patterns work in both modes - only the factory creation differs.\n\nCore Responsibility: Shaping Data\n\nA ViewModel's job is shaping data for presentation. This happens in two places:\n\nFactory - what data is needed, how to transform it\nLocalization - how to present it in context (including locale-aware ordering)\n\nThe View just renders - it should never compose, format, or reorder ViewModel properties.\n\nWhat a ViewModel Contains\n\nA ViewModel answers: \"What does the View need to display?\"\n\nContent Type\tHow It's Represented\tExample\nStatic UI text\t@LocalizedString\tPage titles, button labels (fixed text)\nDynamic enum values\tLocalizableString (stored)\tStatus/state display (see Enum Localization Pattern)\nDynamic data in text\t@LocalizedSubs\t\"Welcome, %{name}!\" with substitutions\nComposed text\t@LocalizedCompoundString\tFull name from pieces (locale-aware order)\nFormatted dates\tLocalizableDate\tcreatedAt: LocalizableDate\nFormatted numbers\tLocalizableInt\ttotalCount: LocalizableInt\nDynamic data\tPlain properties\tcontent: String, count: Int\nNested components\tChild ViewModels\tcards: [CardViewModel]\nWhat a ViewModel Does NOT Contain\nDatabase relationships (@Parent, @Siblings)\nBusiness logic or validation (that's in Fields protocols)\nRaw database IDs exposed to templates (use typed properties)\nUnlocalized strings that Views must look up\nAnti-Pattern: Composition in Views\n// ❌ WRONG - View is composing\nText(viewModel.firstName) + Text(\" \") + Text(viewModel.lastName)\n\n// ✅ RIGHT - ViewModel provides shaped result\nText(viewModel.fullName)  // via @LocalizedCompoundString\n\n\nIf you see + or string interpolation in a View, the shaping belongs in the ViewModel.\n\nViewModel Protocol Hierarchy\npublic protocol ViewModel: ServerRequestBody, RetrievablePropertyNames, Identifiable, Stubbable {\n    var vmId: ViewModelId { get }\n}\n\npublic protocol RequestableViewModel: ViewModel {\n    associatedtype Request: ViewModelRequest\n}\n\n\nViewModel provides:\n\nServerRequestBody - Can be sent over HTTP as JSON\nRetrievablePropertyNames - Enables @LocalizedString binding (via @ViewModel macro)\nIdentifiable - Has vmId for SwiftUI identity\nStubbable - Has stub() for testing/previews\n\nRequestableViewModel adds:\n\nAssociated Request type for fetching from server\nTwo Categories of ViewModels\n1. Top-Level (RequestableViewModel)\n\nRepresents a full page or screen. Has:\n\nAn associated ViewModelRequest type\nA ViewModelFactory that builds it from database\nChild ViewModels embedded within it\n@ViewModel\npublic struct DashboardViewModel: RequestableViewModel {\n    public typealias Request = DashboardRequest\n\n    @LocalizedString public var pageTitle\n    public let cards: [CardViewModel]  // Children\n    public var vmId: ViewModelId = .init()\n}\n\n2. Child (plain ViewModel)\n\nNested components built by their parent's factory. No Request type.\n\n@ViewModel\npublic struct CardViewModel: Codable, Sendable {\n    public let id: ModelIdType\n    public let title: String\n    public let createdAt: LocalizableDate\n    public var vmId: ViewModelId = .init()\n}\n\nDisplay vs Form ViewModels\n\nViewModels serve two distinct purposes:\n\nPurpose\tViewModel Type\tAdopts Fields?\nDisplay data (read-only)\tDisplay ViewModel\tNo\nCollect user input (editable)\tForm ViewModel\tYes\nDisplay ViewModels\n\nFor showing data - cards, rows, lists, detail views:\n\n@ViewModel\npublic struct UserCardViewModel {\n    public let id: ModelIdType\n    public let name: String\n    @LocalizedString public var roleDisplayName\n    public let createdAt: LocalizableDate\n    public var vmId: ViewModelId = .init()\n}\n\n\nCharacteristics:\n\nProperties are let (read-only)\nNo validation needed\nNo FormField definitions\nJust projects Model data for display\nForm ViewModels\n\nFor collecting input - create forms, edit forms, settings:\n\n@ViewModel\npublic struct UserFormViewModel: UserFields {  // ← Adopts Fields!\n    public var id: ModelIdType?\n    public var email: String\n    public var firstName: String\n    public var lastName: String\n\n    public let userValidationMessages: UserFieldsMessages\n    public var vmId: ViewModelId = .init()\n}\n\n\nCharacteristics:\n\nProperties are var (editable)\nAdopts a Fields protocol for validation\nGets FormField definitions from Fields\nGets validation logic from Fields\nGets localized error messages from Fields\nThe Connection\n┌─────────────────────────────────────────────────────────────────┐\n│                    UserFields Protocol                          │\n│        (defines editable properties + validation)               │\n│                                                                 │\n│  Adopted by:                                                    │\n│  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐ │\n│  │ CreateUserReq   │  │ UserFormVM      │  │ User (Model)    │ │\n│  │ .RequestBody    │  │ (UI form)       │  │ (persistence)   │ │\n│  └─────────────────┘  └─────────────────┘  └─────────────────┘ │\n│                                                                 │\n│  Same validation logic everywhere!                              │\n└─────────────────────────────────────────────────────────────────┘\n\nQuick Decision Guide\n\nThe key question: \"Is the user editing data in this ViewModel?\"\n\nNo → Display ViewModel (no Fields)\nYes → Form ViewModel (adopt Fields)\nViewModel\tUser Edits?\tAdopt Fields?\nUserCardViewModel\tNo\tNo\nUserRowViewModel\tNo\tNo\nUserDetailViewModel\tNo\tNo\nUserFormViewModel\tYes\tUserFields\nCreateUserViewModel\tYes\tUserFields\nEditUserViewModel\tYes\tUserFields\nSettingsViewModel\tYes\tSettingsFields\nWhen to Use This Skill\nCreating a new page or screen\nAdding a new UI component (card, row, modal, etc.)\nDisplaying data from the database in a View\nFollowing an implementation plan that requires new ViewModels\nWhat This Skill Generates\nServer-Hosted: Top-Level ViewModel (4 files)\nFile\tLocation\tPurpose\n{Name}ViewModel.swift\t{ViewModelsTarget}/\tThe ViewModel struct\n{Name}Request.swift\t{ViewModelsTarget}/\tThe ViewModelRequest type\n{Name}ViewModel.yml\t{ResourcesPath}/\tLocalization strings\n{Name}ViewModel+Factory.swift\t{WebServerTarget}/\tFactory that builds from DB\nClient-Hosted: Top-Level ViewModel (2 files)\nFile\tLocation\tPurpose\n{Name}ViewModel.swift\t{ViewModelsTarget}/\tViewModel with clientHostedFactory option\n{Name}ViewModel.yml\t{ResourcesPath}/\tLocalization strings (bundled in app)\n\nNo Request or Factory files needed - macro generates them!\n\nChild ViewModels (1-2 files, either mode)\nFile\tLocation\tPurpose\n{Name}ViewModel.swift\t{ViewModelsTarget}/\tThe ViewModel struct\n{Name}ViewModel.yml\t{ResourcesPath}/\tLocalization (if has @LocalizedString)\n\nNote: If child is only used by one parent and represents a summary/reference (not a full ViewModel), nest it inside the parent file instead. See Nested Child Types Pattern under Key Patterns.\n\nProject Structure Configuration\nPlaceholder\tDescription\tExample\n{ViewModelsTarget}\tShared ViewModels SPM target\tViewModels\n{ResourcesPath}\tLocalization resources\tSources/Resources\n{WebServerTarget}\tServer-side target\tWebServer, AppServer\nHow to Use This Skill\n\nInvocation: /fosmvvm-viewmodel-generator\n\nPrerequisites:\n\nView requirements understood from conversation context\nData source determined (server/database vs local state)\nDisplay vs Form decision made (if user input involved, Fields protocol exists)\n\nWorkflow integration: This skill is typically used after discussing View requirements or reading specification files. The skill references conversation context automatically—no file paths or Q&A needed. For Form ViewModels, run fosmvvm-fields-generator first to create the Fields protocol.\n\nPattern Implementation\n\nThis skill references conversation context to determine ViewModel structure:\n\nHosting Mode Detection\n\nFrom conversation context, the skill identifies:\n\nData source (server/database vs local state/preferences)\nServer-hosted → Hand-written factory, server-side localization\nClient-hosted → Macro-generated factory, client-side localization\nViewModel Design\n\nFrom requirements already in context:\n\nView purpose (page, modal, card, row component)\nData needs (from database query, from AppState, from caught error)\nStatic UI text (titles, labels, buttons requiring @LocalizedString)\nChild ViewModels (nested components)\nHierarchy level (top-level RequestableViewModel vs child ViewModel)\nProperty Planning\n\nBased on View requirements:\n\nDisplay properties (data to render)\nLocalization requirements (which properties use @LocalizedString)\nIdentity strategy (singleton vmId vs instance-based vmId)\nForm adoption (whether ViewModel adopts Fields protocol)\nFile Generation\n\nServer-Hosted Top-Level:\n\nViewModel struct (with RequestableViewModel)\nRequest type\nYAML localization\nFactory implementation\n\nClient-Hosted Top-Level:\n\nViewModel struct (with clientHostedFactory option)\nYAML localization\n\nChild (either mode):\n\nViewModel struct\nYAML localization (if needed)\nContext Sources\n\nSkill references information from:\n\nPrior conversation: View requirements, data sources discussed with user\nSpecification files: If Claude has read UI specs or feature docs into context\nFields protocols: From codebase or previous fosmvvm-fields-generator invocation\nKey Patterns\nThe @ViewModel Macro\n\nAlways use the @ViewModel macro - it generates the propertyNames() method required for localization binding.\n\nServer-Hosted (basic macro):\n\n@ViewModel\npublic struct MyViewModel: RequestableViewModel {\n    public typealias Request = MyRequest\n    @LocalizedString public var title\n    public var vmId: ViewModelId = .init()\n    public init() {}\n}\n\n\nClient-Hosted (with factory generation):\n\n@ViewModel(options: [.clientHostedFactory])\npublic struct SettingsViewModel {\n    @LocalizedString public var pageTitle\n    public var vmId: ViewModelId = .init()\n\n    public init(theme: Theme, notifications: NotificationSettings) {\n        // Init parameters become AppState properties\n    }\n}\n\n// Macro auto-generates:\n// - typealias Request = ClientHostedRequest\n// - struct AppState { let theme: Theme; let notifications: NotificationSettings }\n// - class ClientHostedRequest: ViewModelRequest { ... }\n// - static func model(context:) async throws -> Self { ... }\n\nStubbable Pattern\n\nAll ViewModels must support stub() for testing and SwiftUI previews:\n\npublic extension MyViewModel {\n    static func stub() -> Self {\n        .init(/* default values */)\n    }\n}\n\nIdentity: vmId\n\nEvery ViewModel needs a vmId for SwiftUI's identity system:\n\nSingleton (one per page): vmId = .init(type: Self.self) Instance (multiple per page): vmId = .init(id: id) where id: ModelIdType\n\nLocalization\n\nStatic UI text uses @LocalizedString:\n\n@LocalizedString public var pageTitle\n\n\nWith corresponding YAML:\n\nen:\n  MyViewModel:\n    pageTitle: \"Welcome\"\n\nDates and Numbers\n\nNever send pre-formatted strings. Use localizable types:\n\npublic let createdAt: LocalizableDate    // NOT String\npublic let itemCount: LocalizableInt     // NOT String\n\n\nThe client formats these according to user's locale and timezone.\n\nEnum Localization Pattern\n\nFor dynamic enum values (status, state, category), use a stored LocalizableString - NOT @LocalizedString.\n\n@LocalizedString always looks up the same key (the property name). A stored LocalizableString carries the dynamic key from the enum case.\n\n// Enum provides localizableString\npublic enum SessionState: String, CaseIterable, Codable, Sendable {\n    case pending, running, completed, failed\n\n    public var localizableString: LocalizableString {\n        .localized(for: Self.self, propertyName: rawValue)\n    }\n}\n\n// ViewModel stores it (NOT @LocalizedString)\n@ViewModel\npublic struct SessionCardViewModel {\n    public let state: SessionState                // Raw enum for data attributes\n    public let stateDisplay: LocalizableString   // Localized display text\n\n    public init(session: Session) {\n        self.state = session.state\n        self.stateDisplay = session.state.localizableString\n    }\n}\n\n# YAML keys match enum type and case names\nen:\n  SessionState:\n    pending: \"Pending\"\n    running: \"Running\"\n    completed: \"Completed\"\n    failed: \"Failed\"\n\n\nConstraint: LocalizableString only works in ViewModels encoded with localizingEncoder(). Do not use in Fluent JSONB fields or other persisted types.\n\nChild ViewModels\n\nTop-level ViewModels contain their children:\n\n@ViewModel\npublic struct BoardViewModel: RequestableViewModel {\n    public let columns: [ColumnViewModel]\n    public let cards: [CardViewModel]\n}\n\n\nThe Factory builds all children when building the parent.\n\nNested Child Types Pattern\n\nWhen a child type is only used by one parent and represents a summary or reference (not a full ViewModel), nest it inside the parent:\n\n@ViewModel\npublic struct GovernancePrincipleCardViewModel: Codable, Sendable, Identifiable {\n    // Properties come first\n    public let versionHistory: [GovernancePrincipleVersionSummary]?\n    public let referencingDecisions: [GovernanceDecisionReference]?\n\n    // MARK: - Nested Types\n\n    /// Summary of a principle version for display in version history.\n    public struct GovernancePrincipleVersionSummary: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let version: Int\n        public let createdAt: Date\n\n        public init(id: ModelIdType, version: Int, createdAt: Date) {\n            self.id = id\n            self.version = version\n            self.createdAt = createdAt\n        }\n    }\n\n    /// Reference to a decision that cites this principle.\n    public struct GovernanceDecisionReference: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let title: String\n        public let decisionNumber: String\n        public let createdAt: Date\n\n        public init(id: ModelIdType, title: String, decisionNumber: String, createdAt: Date) {\n            self.id = id\n            self.title = title\n            self.decisionNumber = decisionNumber\n            self.createdAt = createdAt\n        }\n    }\n\n    // vmId and parent init follow\n    public let vmId: ViewModelId\n    // ...\n}\n\n\nReference: Sources/KairosModels/Governance/GovernancePrincipleCardViewModel.swift\n\nPlacement rules:\n\nNested types go AFTER the properties that reference them\nBefore vmId and the parent's init\nUse // MARK: - Nested Types section marker\nEach nested type gets its own doc comment\n\nConformances for nested types:\n\nCodable - for ViewModel encoding\nSendable - for Swift 6 concurrency\nIdentifiable - for SwiftUI ForEach if used in arrays\nStubbable - for testing/previews\n\nTwo-Tier Stubbable Pattern:\n\nNested types use fully qualified names in their extensions:\n\npublic extension GovernancePrincipleCardViewModel.GovernancePrincipleVersionSummary {\n    // Tier 1: Zero-arg convenience (ALWAYS delegates to tier 2)\n    static func stub() -> Self {\n        .stub(id: .init())\n    }\n\n    // Tier 2: Full parameterized with defaults\n    static func stub(\n        id: ModelIdType = .init(),\n        version: Int = 1,\n        createdAt: Date = .now\n    ) -> Self {\n        .init(id: id, version: version, createdAt: createdAt)\n    }\n}\n\npublic extension GovernancePrincipleCardViewModel.GovernanceDecisionReference {\n    static func stub() -> Self {\n        .stub(id: .init())\n    }\n\n    static func stub(\n        id: ModelIdType = .init(),\n        title: String = \"A Title\",\n        decisionNumber: String = \"DEC-12345\",\n        createdAt: Date = .now\n    ) -> Self {\n        .init(id: id, title: title, decisionNumber: decisionNumber, createdAt: createdAt)\n    }\n}\n\n\nWhy two tiers:\n\nTests often just need [.stub()] without caring about values\nOther tests need specific values: .stub(name: \"Specific Name\")\nZero-arg ALWAYS calls parameterized version (single source of truth)\n\nWhen to nest vs keep top-level:\n\nNest Inside Parent\tKeep Top-Level\nChild is ONLY used by this parent\tChild is shared across multiple parents\nChild represents subset/summary\tChild is a full ViewModel\nChild has no @ViewModel macro\tChild has @ViewModel macro\nChild is not RequestableViewModel\tChild is RequestableViewModel\nExample: VersionSummary, Reference\tExample: CardViewModel, ListViewModel\n\nExamples:\n\nCard with nested summaries:\n\n@ViewModel\npublic struct TaskCardViewModel {\n    public let assignees: [AssigneeSummary]?\n\n    public struct AssigneeSummary: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let name: String\n        public let avatarUrl: String?\n        // ...\n    }\n}\n\n\nList with nested references:\n\n@ViewModel\npublic struct ProjectListViewModel {\n    public let relatedProjects: [ProjectReference]?\n\n    public struct ProjectReference: Codable, Sendable, Identifiable, Stubbable {\n        public let id: ModelIdType\n        public let title: String\n        public let status: String\n        // ...\n    }\n}\n\nCodable and Computed Properties\n\nSwift's synthesized Codable only encodes stored properties. Since ViewModels are serialized (for JSON transport, Leaf rendering, etc.), computed properties won't be available.\n\n// Computed - NOT encoded, invisible after serialization\npublic var hasCards: Bool { !cards.isEmpty }\n\n// Stored - encoded, available after serialization\npublic let hasCards: Bool\n\n\nWhen to pre-compute:\n\nFor Leaf templates, you can often use Leaf's built-in functions directly:\n\n#if(count(cards) > 0) - no need for hasCards property\n#count(cards) - no need for cardCount property\n\nPre-compute only when:\n\nDirect array subscripts needed (firstCard - array indexing not documented in Leaf)\nComplex logic that's cleaner in Swift than in template\nPerformance-sensitive repeated calculations\n\nSee fosmvvm-leaf-view-generator for Leaf template patterns.\n\nFile Templates\n\nSee reference.md for complete file templates.\n\nNaming Conventions\nConcept\tConvention\tExample\nViewModel struct\t{Name}ViewModel\tDashboardViewModel\nRequest class\t{Name}Request\tDashboardRequest\nFactory extension\t{Name}ViewModel+Factory.swift\tDashboardViewModel+Factory.swift\nYAML file\t{Name}ViewModel.yml\tDashboardViewModel.yml\nSee Also\nArchitecture Patterns - Mental models (errors are data, type safety, etc.)\nFOSMVVMArchitecture.md - Full FOSMVVM architecture\nfosmvvm-fields-generator - For form validation\nfosmvvm-fluent-datamodel-generator - For Fluent persistence layer\nfosmvvm-leaf-view-generator - For Leaf templates that render ViewModels\nreference.md - Complete file templates\nVersion History\nVersion\tDate\tChanges\n1.0\t2024-12-24\tInitial skill\n2.0\t2024-12-26\tComplete rewrite from architecture; generalized from Kairos-specific\n2.1\t2024-12-26\tAdded Client-Hosted mode support; per-ViewModel hosting decision\n2.2\t2024-12-26\tAdded shaping responsibility, @LocalizedSubs/@LocalizedCompoundString, anti-pattern\n2.3\t2025-12-27\tAdded Display vs Form ViewModels section; clarified Fields adoption\n2.4\t2026-01-08\tAdded Codable/computed properties section. Clarified when to pre-compute vs use Leaf built-ins.\n2.5\t2026-01-19\tAdded Enum Localization Pattern section. Clarified @LocalizedString is for static text only; stored LocalizableString for dynamic enum values.\n2.6\t2026-01-24\tUpdate to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths.\n2.7\t2026-01-25\tAdded Nested Child Types Pattern section with two-tier Stubbable pattern, placement rules, conformances, and decision criteria for when to nest vs keep top-level."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-viewmodel-generator",
    "publisherUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-viewmodel-generator",
    "owner": "foscomputerservices",
    "version": "2.0.6",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator",
    "downloadUrl": "https://openagent3.xyz/downloads/fosmvvm-viewmodel-generator",
    "agentUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-viewmodel-generator/agent.md"
  }
}