{
  "schemaVersion": "1.0",
  "item": {
    "slug": "fosmvvm-leaf-view-generator",
    "name": "FOSMVVM Leaf View Generator",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-leaf-view-generator",
    "canonicalUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-leaf-view-generator",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/fosmvvm-leaf-view-generator",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fosmvvm-leaf-view-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-leaf-view-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-leaf-view-generator",
    "agentPageUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-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 Leaf View Generator",
        "body": "Generate Leaf templates that render ViewModels for web clients.\n\nArchitecture context: See FOSMVVMArchitecture.md | OpenClaw reference"
      },
      {
        "title": "The View Layer for WebApps",
        "body": "In FOSMVVM, Leaf templates are the View in M-V-VM for web clients:\n\nModel → ViewModel → Leaf Template → HTML\n              ↑           ↑\n        (localized)  (renders it)\n\nKey principle: The ViewModel is already localized when it reaches the template. The template just renders what it receives."
      },
      {
        "title": "Core Principle: View-ViewModel Alignment",
        "body": "The Leaf filename should match the ViewModel it renders.\n\nSources/\n  {ViewModelsTarget}/\n    ViewModels/\n      {Feature}ViewModel.swift        ←──┐\n      {Entity}CardViewModel.swift     ←──┼── Same names\n                                          │\n  {WebAppTarget}/                         │\n    Resources/Views/                      │\n      {Feature}/                          │\n        {Feature}View.leaf            ────┤  (renders {Feature}ViewModel)\n        {Entity}CardView.leaf         ────┘  (renders {Entity}CardViewModel)\n\nThis alignment provides:\n\nDiscoverability - Find the template for any ViewModel instantly\nConsistency - Same naming discipline as SwiftUI\nMaintainability - Changes to ViewModel are reflected in template location"
      },
      {
        "title": "Full-Page Templates",
        "body": "Render a complete page with layout, navigation, CSS/JS includes.\n\n{Feature}View.leaf\n├── Extends base layout\n├── Includes <html>, <head>, <body>\n├── Renders {Feature}ViewModel\n└── May embed fragment templates for components\n\nUse for: Initial page loads, navigation destinations."
      },
      {
        "title": "Fragment Templates",
        "body": "Render a single component - no layout, no page structure.\n\n{Entity}CardView.leaf\n├── NO layout extension\n├── Single root element\n├── Renders {Entity}CardViewModel\n├── Has data-* attributes for state\n└── Returned to JS for DOM swapping\n\nUse for: Partial updates, HTML-over-the-wire responses."
      },
      {
        "title": "The HTML-Over-The-Wire Pattern",
        "body": "For dynamic updates without full page reloads:\n\nJS Event → WebApp Route → ServerRequest.processRequest() → Controller\n                                                              ↓\n                                                          ViewModel\n                                                              ↓\nHTML ← JS DOM swap ← WebApp returns ← Leaf renders ←────────┘\n\nThe WebApp route:\n\napp.post(\"move-{entity}\") { req async throws -> Response in\n    let body = try req.content.decode(Move{Entity}Request.RequestBody.self)\n    let serverRequest = Move{Entity}Request(requestBody: body)\n    guard let response = try await serverRequest.processRequest(baseURL: app.serverBaseURL) else {\n        throw Abort(.internalServerError)\n    }\n\n    // Render fragment template with ViewModel\n    return try await req.view.render(\n        \"{Feature}/{Entity}CardView\",\n        [\"card\": response.viewModel]\n    ).encodeResponse(for: req)\n}\n\nJS receives HTML and swaps it into the DOM - no JSON parsing, no client-side rendering."
      },
      {
        "title": "When to Use This Skill",
        "body": "Creating a new page template (full-page)\nCreating a new card, row, or component template (fragment)\nAdding data attributes for JS event handling\nTroubleshooting Localizable types not rendering correctly\nSetting up templates for HTML-over-the-wire responses"
      },
      {
        "title": "Pattern 1: Data Attributes for State",
        "body": "Fragments must embed all state that JS needs for future actions:\n\n<div class=\"{entity}-card\"\n     data-{entity}-id=\"#(card.id)\"\n     data-status=\"#(card.status)\"\n     data-category=\"#(card.category)\"\n     draggable=\"true\">\n\nRules:\n\ndata-{entity}-id for the primary identifier\ndata-{field} for state values (kebab-case)\nStore raw values (enum cases), not localized display names\nJS reads these to build ServerRequest payloads\n\nconst request = {\n    {entity}Id: element.dataset.{entity}Id,\n    newStatus: targetColumn.dataset.status\n};"
      },
      {
        "title": "Pattern 2: Localizable Types in Leaf",
        "body": "FOSMVVM's LeafDataRepresentable conformance handles Localizable types automatically.\n\nIn templates, just use the property:\n\n<span class=\"date\">#(card.createdAt)</span>\n<!-- Renders: \"Dec 27, 2025\" (localized) -->\n\nIf Localizable types render incorrectly (showing [ds: \"2\", ls: \"...\", v: \"...\"]):\n\nEnsure FOSMVVMVapor is imported\nCheck Localizable+Leaf.swift exists with conformances\nClean build: swift package clean && swift build"
      },
      {
        "title": "Pattern 3: Display Values vs Identifiers",
        "body": "ViewModels should provide both raw values (for data attributes) and localized strings (for display). For enum localization, see the Enum Localization Pattern.\n\n@ViewModel\npublic struct {Entity}CardViewModel {\n    public let id: ModelIdType              // For data-{entity}-id\n    public let status: {Entity}Status       // Raw enum for data-status\n    public let statusDisplay: LocalizableString  // Localized (stored, not @LocalizedString)\n}\n\n<div data-status=\"#(card.status)\">           <!-- Raw: \"queued\" for JS -->\n    <span class=\"badge\">#(card.statusDisplay)</span>  <!-- Localized: \"In Queue\" -->\n</div>"
      },
      {
        "title": "Pattern 4: Fragment Structure",
        "body": "Fragments are minimal - just the component:\n\n<!-- {Entity}CardView.leaf -->\n<div class=\"{entity}-card\"\n     data-{entity}-id=\"#(card.id)\"\n     data-status=\"#(card.status)\">\n\n    <div class=\"card-content\">\n        <p class=\"text\">#(card.contentPreview)</p>\n    </div>\n\n    <div class=\"card-footer\">\n        <span class=\"creator\">#(card.creatorName)</span>\n        <span class=\"date\">#(card.createdAt)</span>\n    </div>\n</div>\n\nRules:\n\nNO #extend(\"base\") - fragments don't use layouts\nSingle root element - makes DOM swapping clean\nAll required state in data-* attributes\nLocalized values from ViewModel properties"
      },
      {
        "title": "Pattern 5: Full-Page Structure",
        "body": "Full pages extend a base layout:\n\n<!-- {Feature}View.leaf -->\n#extend(\"base\"):\n#export(\"content\"):\n\n<div class=\"{feature}-container\">\n    <header class=\"{feature}-header\">\n        <h1>#(viewModel.title)</h1>\n    </header>\n\n    <main class=\"{feature}-content\">\n        #for(card in viewModel.cards):\n        #extend(\"{Feature}/{Entity}CardView\")\n        #endfor\n    </main>\n</div>\n\n#endexport\n#endextend"
      },
      {
        "title": "Pattern 6: Conditional Rendering",
        "body": "#if(card.isHighPriority):\n<span class=\"priority-badge\">#(card.priorityLabel)</span>\n#endif\n\n#if(card.assignee):\n<div class=\"assignee\">\n    <span class=\"name\">#(card.assignee.name)</span>\n</div>\n#else:\n<div class=\"unassigned\">#(card.unassignedLabel)</div>\n#endif"
      },
      {
        "title": "Pattern 7: Looping with Embedded Fragments",
        "body": "<div class=\"column\" data-status=\"#(column.status)\">\n    <div class=\"column-header\">\n        <h3>#(column.displayName)</h3>\n        <span class=\"count\">#(column.count)</span>\n    </div>\n\n    <div class=\"column-cards\">\n        #for(card in column.cards):\n        #extend(\"{Feature}/{Entity}CardView\")\n        #endfor\n\n        #if(column.cards.count == 0):\n        <div class=\"empty-state\">#(column.emptyMessage)</div>\n        #endif\n    </div>\n</div>"
      },
      {
        "title": "File Organization",
        "body": "Sources/{WebAppTarget}/Resources/Views/\n├── base.leaf                          # Base layout (all pages extend this)\n├── {Feature}/\n│   ├── {Feature}View.leaf             # Full page → {Feature}ViewModel\n│   ├── {Entity}CardView.leaf          # Fragment → {Entity}CardViewModel\n│   ├── {Entity}RowView.leaf           # Fragment → {Entity}RowViewModel\n│   └── {Modal}View.leaf               # Fragment → {Modal}ViewModel\n└── Shared/\n    ├── HeaderView.leaf                # Shared components\n    └── FooterView.leaf"
      },
      {
        "title": "Leaf Built-in Functions",
        "body": "Leaf provides useful functions for working with arrays:\n\n<!-- Count items -->\n#if(count(cards) > 0):\n<p>You have #count(cards) cards</p>\n#endif\n\n<!-- Check if array contains value -->\n#if(contains(statuses, \"active\")):\n<span class=\"badge\">Active</span>\n#endif"
      },
      {
        "title": "Loop Variables",
        "body": "Inside #for loops, Leaf provides progress variables:\n\n#for(item in items):\n    #if(isFirst):<span class=\"first\">#endif\n    #(item.name)\n    #if(!isLast):, #endif\n#endfor\n\nVariableDescriptionisFirstTrue on first iterationisLastTrue on last iterationindexCurrent iteration (0-based)"
      },
      {
        "title": "Array Index Access",
        "body": "Direct array subscripts (array[0]) are not documented in Leaf. For accessing specific elements, pre-compute in the ViewModel:\n\npublic let firstCard: CardViewModel?\n\npublic init(cards: [CardViewModel]) {\n    self.cards = cards\n    self.firstCard = cards.first\n}"
      },
      {
        "title": "Codable and Computed Properties",
        "body": "Swift's synthesized Codable only encodes stored properties. Since ViewModels are passed to Leaf via Codable encoding, computed properties won't be available.\n\n// Computed property - NOT encoded by Codable, invisible in Leaf\npublic var hasCards: Bool { !cards.isEmpty }\n\n// Stored property - encoded by Codable, available in Leaf\npublic let hasCards: Bool\n\nIf you need a derived value in a Leaf template, calculate it in init() and store it:\n\npublic let hasCards: Bool\npublic let cardCount: Int\n\npublic init(cards: [CardViewModel]) {\n    self.cards = cards\n    self.hasCards = !cards.isEmpty\n    self.cardCount = cards.count\n}"
      },
      {
        "title": "ViewModelId Initialization - CRITICAL",
        "body": "IMPORTANT: Even though Leaf templates don't use vmId directly, the ViewModels being rendered must initialize vmId correctly for SwiftUI clients.\n\n❌ WRONG - Never use this:\n\npublic var vmId: ViewModelId = .init()  // NO! Generic identity\n\n✅ MINIMUM - Use type-based identity:\n\npublic var vmId: ViewModelId = .init(type: Self.self)\n\n✅ IDEAL - Use data-based identity when available:\n\npublic struct TaskCardViewModel {\n    public let id: ModelIdType\n    public var vmId: ViewModelId\n\n    public init(id: ModelIdType, /* other params */) {\n        self.id = id\n        self.vmId = .init(id: id)  // Ties view identity to data identity\n        // ...\n    }\n}\n\nWhy this matters for Leaf ViewModels:\n\nViewModels are shared between Leaf (web) and SwiftUI (native) clients\nSwiftUI uses .id(vmId) to determine when to recreate vs update views\nWrong identity = SwiftUI views don't update when data changes\nData-based identity (.init(id:)) is best practice"
      },
      {
        "title": "Missing Data Attributes",
        "body": "<!-- BAD - JS can't identify this element -->\n<div class=\"{entity}-card\">\n\n<!-- GOOD - JS reads data-{entity}-id -->\n<div class=\"{entity}-card\" data-{entity}-id=\"#(card.id)\">"
      },
      {
        "title": "Storing Display Names Instead of Identifiers",
        "body": "<!-- BAD - localized string can't be sent to server -->\n<div data-status=\"#(card.statusDisplayName)\">\n\n<!-- GOOD - raw enum value works for requests -->\n<div data-status=\"#(card.status)\">"
      },
      {
        "title": "Using Layout in Fragments",
        "body": "<!-- BAD - fragment should not extend layout -->\n#extend(\"base\"):\n#export(\"content\"):\n<div class=\"card\">...</div>\n#endexport\n#endextend\n\n<!-- GOOD - fragment is just the component -->\n<div class=\"card\">...</div>"
      },
      {
        "title": "Hardcoding Text",
        "body": "<!-- BAD - not localizable -->\n<span class=\"status\">Queued</span>\n\n<!-- GOOD - ViewModel provides localized value -->\n<span class=\"status\">#(card.statusDisplayName)</span>"
      },
      {
        "title": "Concatenating Localized Values",
        "body": "<!-- BAD - breaks RTL languages and locale-specific word order -->\n#(conversation.messageCount) #(conversation.messagesLabel)\n\n<!-- GOOD - ViewModel composes via @LocalizedSubs -->\n#(conversation.messageCountDisplay)\n\nTemplate-level concatenation assumes left-to-right order. Use @LocalizedSubs in the ViewModel so YAML can define locale-appropriate ordering:\n\nen:\n  ConversationViewModel:\n    messageCountDisplay: \"%{messageCount} %{messagesLabel}\"\nar:\n  ConversationViewModel:\n    messageCountDisplay: \"%{messagesLabel} %{messageCount}\""
      },
      {
        "title": "Formatting Dates in Templates",
        "body": "<!-- BAD - hardcoded format, not locale-aware, concatenation issue -->\n<span>#(content.createdPrefix) #date(content.createdAt, \"MMM d, yyyy\")</span>\n\n<!-- GOOD - LocalizableDate handles locale formatting, @LocalizedSubs composes -->\n<span>#(content.createdDisplay)</span>\n\nUse LocalizableDate in the ViewModel - it formats according to user locale. If combining with a prefix, use @LocalizedSubs:\n\npublic let createdAt: LocalizableDate\n\n@LocalizedSubs(\\.createdPrefix, \\.createdAt)\npublic var createdDisplay"
      },
      {
        "title": "Mismatched Filenames",
        "body": "<!-- BAD - filename doesn't match ViewModel -->\nViewModel: UserProfileCardViewModel\nTemplate:  ProfileCard.leaf\n\n<!-- GOOD - aligned names -->\nViewModel: UserProfileCardViewModel\nTemplate:  UserProfileCardView.leaf"
      },
      {
        "title": "Incorrect ViewModelId Initialization",
        "body": "// ❌ BAD - Generic identity (breaks SwiftUI clients)\npublic var vmId: ViewModelId = .init()\n\n// ✅ MINIMUM - Type-based identity\npublic var vmId: ViewModelId = .init(type: Self.self)\n\n// ✅ IDEAL - Data-based identity (when id available)\npublic init(id: ModelIdType) {\n    self.id = id\n    self.vmId = .init(id: id)\n}\n\nViewModels rendered by Leaf are often shared with SwiftUI clients. Correct vmId initialization is critical for SwiftUI's view identity system."
      },
      {
        "title": "Rendering Errors in Leaf Templates",
        "body": "When a WebApp route catches an error, the error type is known at compile time. You don't need generic \"ErrorViewModel\" patterns:\n\n// WebApp route - you KNOW the request type, so you KNOW the error type\napp.post(\"move-idea\") { req async throws -> Response in\n    let body = try req.content.decode(MoveIdeaRequest.RequestBody.self)\n    let serverRequest = MoveIdeaRequest(requestBody: body)\n\n    do {\n        try await serverRequest.processRequest(mvvmEnv: req.application.mvvmEnv)\n        // success path...\n    } catch let error as MoveIdeaRequest.ResponseError {\n        // I KNOW this is MoveIdeaRequest.ResponseError\n        // I KNOW it has .code and .message\n        return try await req.view.render(\n            \"Shared/ToastView\",\n            [\"message\": error.message.value, \"type\": \"error\"]\n        ).encodeResponse(for: req)\n    }\n}\n\nThe anti-pattern (JavaScript brain):\n\n// ❌ WRONG - treating errors as opaque\ncatch let error as ServerRequestError {\n    // \"How do I extract the message? The protocol doesn't guarantee it!\"\n    // This is wrong thinking. You catch the CONCRETE type.\n}\n\nEach route handles its own specific error type. There's no mystery about what properties are available."
      },
      {
        "title": "How to Use This Skill",
        "body": "Invocation:\n/fosmvvm-leaf-view-generator\n\nPrerequisites:\n\nViewModel structure understood from conversation context\nTemplate type determined (full-page vs fragment)\nData attributes needed for JS interactions identified\nHTML-over-the-wire pattern understood if using fragments\n\nWorkflow integration:\nThis skill is used when creating Leaf templates for web clients. The skill references conversation context automatically—no file paths or Q&A needed. Typically follows fosmvvm-viewmodel-generator."
      },
      {
        "title": "Pattern Implementation",
        "body": "This skill references conversation context to determine template structure:"
      },
      {
        "title": "ViewModel Analysis",
        "body": "From conversation context, the skill identifies:\n\nViewModel type (from prior discussion or server implementation)\nProperties (what data the template will display)\nLocalization (which properties are Localizable types)\nNested ViewModels (child components)"
      },
      {
        "title": "Template Type Detection",
        "body": "From ViewModel purpose:\n\nPage content → Full-page template (extends layout)\nList item/Card → Fragment (no layout, single root)\nModal content → Fragment\nInline component → Fragment"
      },
      {
        "title": "Property Mapping",
        "body": "For each ViewModel property:\n\nid: ModelIdType → data-{entity}-id=\"#(vm.id)\" (for JS)\nRaw enum → data-{field}=\"#(vm.field)\" (for state)\nLocalizableString → #(vm.displayName) (display text)\nLocalizableDate → #(vm.createdAt) (formatted date)\nNested ViewModel → Embed fragment or access properties"
      },
      {
        "title": "Data Attributes Planning",
        "body": "Based on JS interaction needs:\n\nEntity identifier (for operations)\nState values (enum raw values for requests)\nDrag/drop attributes (if interactive)\nCategory/grouping (for filtering/sorting)"
      },
      {
        "title": "Template Generation",
        "body": "Full-page:\n\nLayout extension\nContent export\nEmbedded fragments for components\n\nFragment:\n\nSingle root element\nData attributes for state\nLocalized text from ViewModel\nNo layout extension"
      },
      {
        "title": "Context Sources",
        "body": "Skill references information from:\n\nPrior conversation: Template requirements, user flows discussed\nViewModel: If Claude has read ViewModel code into context\nExisting templates: From codebase analysis of similar views"
      },
      {
        "title": "See Also",
        "body": "Architecture Patterns - Mental models (errors are data, type safety, etc.)\nFOSMVVMArchitecture.md - Full architecture\nfosmvvm-viewmodel-generator - Creates the ViewModels this skill renders\nfosmvvm-serverrequest-generator - Creates requests that return ViewModels\nreference.md - Complete template examples"
      },
      {
        "title": "Version History",
        "body": "VersionDateChanges1.02025-12-24Initial Kairos-specific skill2.02025-12-27Generalized for FOSMVVM, added View-ViewModel alignment principle, full-page templates, architecture connection2.12026-01-08Added Leaf Built-in Functions section (count, contains, loop variables). Clarified Codable/computed properties. Corrected earlier false claims about #count() not working.2.22026-01-19Updated Pattern 3 to use stored LocalizableString for dynamic enum displays; linked to Enum Localization Pattern. Added anti-patterns for concatenating localized values and formatting dates in templates.2.32026-01-20Added \"Rendering Errors in Leaf Templates\" section - error types are known at compile time, no need for generic ErrorViewModel patterns. Prevents JavaScript-brain thinking about runtime type discovery.2.42026-01-24Update to context-aware approach (remove file-parsing/Q&A). Skill references conversation context instead of asking questions or accepting file paths."
      }
    ],
    "body": "FOSMVVM Leaf View Generator\n\nGenerate Leaf templates that render ViewModels for web clients.\n\nArchitecture context: See FOSMVVMArchitecture.md | OpenClaw reference\n\nThe View Layer for WebApps\n\nIn FOSMVVM, Leaf templates are the View in M-V-VM for web clients:\n\nModel → ViewModel → Leaf Template → HTML\n              ↑           ↑\n        (localized)  (renders it)\n\n\nKey principle: The ViewModel is already localized when it reaches the template. The template just renders what it receives.\n\nCore Principle: View-ViewModel Alignment\n\nThe Leaf filename should match the ViewModel it renders.\n\nSources/\n  {ViewModelsTarget}/\n    ViewModels/\n      {Feature}ViewModel.swift        ←──┐\n      {Entity}CardViewModel.swift     ←──┼── Same names\n                                          │\n  {WebAppTarget}/                         │\n    Resources/Views/                      │\n      {Feature}/                          │\n        {Feature}View.leaf            ────┤  (renders {Feature}ViewModel)\n        {Entity}CardView.leaf         ────┘  (renders {Entity}CardViewModel)\n\n\nThis alignment provides:\n\nDiscoverability - Find the template for any ViewModel instantly\nConsistency - Same naming discipline as SwiftUI\nMaintainability - Changes to ViewModel are reflected in template location\nTwo Template Types\nFull-Page Templates\n\nRender a complete page with layout, navigation, CSS/JS includes.\n\n{Feature}View.leaf\n├── Extends base layout\n├── Includes <html>, <head>, <body>\n├── Renders {Feature}ViewModel\n└── May embed fragment templates for components\n\n\nUse for: Initial page loads, navigation destinations.\n\nFragment Templates\n\nRender a single component - no layout, no page structure.\n\n{Entity}CardView.leaf\n├── NO layout extension\n├── Single root element\n├── Renders {Entity}CardViewModel\n├── Has data-* attributes for state\n└── Returned to JS for DOM swapping\n\n\nUse for: Partial updates, HTML-over-the-wire responses.\n\nThe HTML-Over-The-Wire Pattern\n\nFor dynamic updates without full page reloads:\n\nJS Event → WebApp Route → ServerRequest.processRequest() → Controller\n                                                              ↓\n                                                          ViewModel\n                                                              ↓\nHTML ← JS DOM swap ← WebApp returns ← Leaf renders ←────────┘\n\n\nThe WebApp route:\n\napp.post(\"move-{entity}\") { req async throws -> Response in\n    let body = try req.content.decode(Move{Entity}Request.RequestBody.self)\n    let serverRequest = Move{Entity}Request(requestBody: body)\n    guard let response = try await serverRequest.processRequest(baseURL: app.serverBaseURL) else {\n        throw Abort(.internalServerError)\n    }\n\n    // Render fragment template with ViewModel\n    return try await req.view.render(\n        \"{Feature}/{Entity}CardView\",\n        [\"card\": response.viewModel]\n    ).encodeResponse(for: req)\n}\n\n\nJS receives HTML and swaps it into the DOM - no JSON parsing, no client-side rendering.\n\nWhen to Use This Skill\nCreating a new page template (full-page)\nCreating a new card, row, or component template (fragment)\nAdding data attributes for JS event handling\nTroubleshooting Localizable types not rendering correctly\nSetting up templates for HTML-over-the-wire responses\nKey Patterns\nPattern 1: Data Attributes for State\n\nFragments must embed all state that JS needs for future actions:\n\n<div class=\"{entity}-card\"\n     data-{entity}-id=\"#(card.id)\"\n     data-status=\"#(card.status)\"\n     data-category=\"#(card.category)\"\n     draggable=\"true\">\n\n\nRules:\n\ndata-{entity}-id for the primary identifier\ndata-{field} for state values (kebab-case)\nStore raw values (enum cases), not localized display names\nJS reads these to build ServerRequest payloads\nconst request = {\n    {entity}Id: element.dataset.{entity}Id,\n    newStatus: targetColumn.dataset.status\n};\n\nPattern 2: Localizable Types in Leaf\n\nFOSMVVM's LeafDataRepresentable conformance handles Localizable types automatically.\n\nIn templates, just use the property:\n\n<span class=\"date\">#(card.createdAt)</span>\n<!-- Renders: \"Dec 27, 2025\" (localized) -->\n\n\nIf Localizable types render incorrectly (showing [ds: \"2\", ls: \"...\", v: \"...\"]):\n\nEnsure FOSMVVMVapor is imported\nCheck Localizable+Leaf.swift exists with conformances\nClean build: swift package clean && swift build\nPattern 3: Display Values vs Identifiers\n\nViewModels should provide both raw values (for data attributes) and localized strings (for display). For enum localization, see the Enum Localization Pattern.\n\n@ViewModel\npublic struct {Entity}CardViewModel {\n    public let id: ModelIdType              // For data-{entity}-id\n    public let status: {Entity}Status       // Raw enum for data-status\n    public let statusDisplay: LocalizableString  // Localized (stored, not @LocalizedString)\n}\n\n<div data-status=\"#(card.status)\">           <!-- Raw: \"queued\" for JS -->\n    <span class=\"badge\">#(card.statusDisplay)</span>  <!-- Localized: \"In Queue\" -->\n</div>\n\nPattern 4: Fragment Structure\n\nFragments are minimal - just the component:\n\n<!-- {Entity}CardView.leaf -->\n<div class=\"{entity}-card\"\n     data-{entity}-id=\"#(card.id)\"\n     data-status=\"#(card.status)\">\n\n    <div class=\"card-content\">\n        <p class=\"text\">#(card.contentPreview)</p>\n    </div>\n\n    <div class=\"card-footer\">\n        <span class=\"creator\">#(card.creatorName)</span>\n        <span class=\"date\">#(card.createdAt)</span>\n    </div>\n</div>\n\n\nRules:\n\nNO #extend(\"base\") - fragments don't use layouts\nSingle root element - makes DOM swapping clean\nAll required state in data-* attributes\nLocalized values from ViewModel properties\nPattern 5: Full-Page Structure\n\nFull pages extend a base layout:\n\n<!-- {Feature}View.leaf -->\n#extend(\"base\"):\n#export(\"content\"):\n\n<div class=\"{feature}-container\">\n    <header class=\"{feature}-header\">\n        <h1>#(viewModel.title)</h1>\n    </header>\n\n    <main class=\"{feature}-content\">\n        #for(card in viewModel.cards):\n        #extend(\"{Feature}/{Entity}CardView\")\n        #endfor\n    </main>\n</div>\n\n#endexport\n#endextend\n\nPattern 6: Conditional Rendering\n#if(card.isHighPriority):\n<span class=\"priority-badge\">#(card.priorityLabel)</span>\n#endif\n\n#if(card.assignee):\n<div class=\"assignee\">\n    <span class=\"name\">#(card.assignee.name)</span>\n</div>\n#else:\n<div class=\"unassigned\">#(card.unassignedLabel)</div>\n#endif\n\nPattern 7: Looping with Embedded Fragments\n<div class=\"column\" data-status=\"#(column.status)\">\n    <div class=\"column-header\">\n        <h3>#(column.displayName)</h3>\n        <span class=\"count\">#(column.count)</span>\n    </div>\n\n    <div class=\"column-cards\">\n        #for(card in column.cards):\n        #extend(\"{Feature}/{Entity}CardView\")\n        #endfor\n\n        #if(column.cards.count == 0):\n        <div class=\"empty-state\">#(column.emptyMessage)</div>\n        #endif\n    </div>\n</div>\n\nFile Organization\nSources/{WebAppTarget}/Resources/Views/\n├── base.leaf                          # Base layout (all pages extend this)\n├── {Feature}/\n│   ├── {Feature}View.leaf             # Full page → {Feature}ViewModel\n│   ├── {Entity}CardView.leaf          # Fragment → {Entity}CardViewModel\n│   ├── {Entity}RowView.leaf           # Fragment → {Entity}RowViewModel\n│   └── {Modal}View.leaf               # Fragment → {Modal}ViewModel\n└── Shared/\n    ├── HeaderView.leaf                # Shared components\n    └── FooterView.leaf\n\nLeaf Built-in Functions\n\nLeaf provides useful functions for working with arrays:\n\n<!-- Count items -->\n#if(count(cards) > 0):\n<p>You have #count(cards) cards</p>\n#endif\n\n<!-- Check if array contains value -->\n#if(contains(statuses, \"active\")):\n<span class=\"badge\">Active</span>\n#endif\n\nLoop Variables\n\nInside #for loops, Leaf provides progress variables:\n\n#for(item in items):\n    #if(isFirst):<span class=\"first\">#endif\n    #(item.name)\n    #if(!isLast):, #endif\n#endfor\n\nVariable\tDescription\nisFirst\tTrue on first iteration\nisLast\tTrue on last iteration\nindex\tCurrent iteration (0-based)\nArray Index Access\n\nDirect array subscripts (array[0]) are not documented in Leaf. For accessing specific elements, pre-compute in the ViewModel:\n\npublic let firstCard: CardViewModel?\n\npublic init(cards: [CardViewModel]) {\n    self.cards = cards\n    self.firstCard = cards.first\n}\n\nCodable and Computed Properties\n\nSwift's synthesized Codable only encodes stored properties. Since ViewModels are passed to Leaf via Codable encoding, computed properties won't be available.\n\n// Computed property - NOT encoded by Codable, invisible in Leaf\npublic var hasCards: Bool { !cards.isEmpty }\n\n// Stored property - encoded by Codable, available in Leaf\npublic let hasCards: Bool\n\n\nIf you need a derived value in a Leaf template, calculate it in init() and store it:\n\npublic let hasCards: Bool\npublic let cardCount: Int\n\npublic init(cards: [CardViewModel]) {\n    self.cards = cards\n    self.hasCards = !cards.isEmpty\n    self.cardCount = cards.count\n}\n\nViewModelId Initialization - CRITICAL\n\nIMPORTANT: Even though Leaf templates don't use vmId directly, the ViewModels being rendered must initialize vmId correctly for SwiftUI clients.\n\n❌ WRONG - Never use this:\n\npublic var vmId: ViewModelId = .init()  // NO! Generic identity\n\n\n✅ MINIMUM - Use type-based identity:\n\npublic var vmId: ViewModelId = .init(type: Self.self)\n\n\n✅ IDEAL - Use data-based identity when available:\n\npublic struct TaskCardViewModel {\n    public let id: ModelIdType\n    public var vmId: ViewModelId\n\n    public init(id: ModelIdType, /* other params */) {\n        self.id = id\n        self.vmId = .init(id: id)  // Ties view identity to data identity\n        // ...\n    }\n}\n\n\nWhy this matters for Leaf ViewModels:\n\nViewModels are shared between Leaf (web) and SwiftUI (native) clients\nSwiftUI uses .id(vmId) to determine when to recreate vs update views\nWrong identity = SwiftUI views don't update when data changes\nData-based identity (.init(id:)) is best practice\nCommon Mistakes\nMissing Data Attributes\n<!-- BAD - JS can't identify this element -->\n<div class=\"{entity}-card\">\n\n<!-- GOOD - JS reads data-{entity}-id -->\n<div class=\"{entity}-card\" data-{entity}-id=\"#(card.id)\">\n\nStoring Display Names Instead of Identifiers\n<!-- BAD - localized string can't be sent to server -->\n<div data-status=\"#(card.statusDisplayName)\">\n\n<!-- GOOD - raw enum value works for requests -->\n<div data-status=\"#(card.status)\">\n\nUsing Layout in Fragments\n<!-- BAD - fragment should not extend layout -->\n#extend(\"base\"):\n#export(\"content\"):\n<div class=\"card\">...</div>\n#endexport\n#endextend\n\n<!-- GOOD - fragment is just the component -->\n<div class=\"card\">...</div>\n\nHardcoding Text\n<!-- BAD - not localizable -->\n<span class=\"status\">Queued</span>\n\n<!-- GOOD - ViewModel provides localized value -->\n<span class=\"status\">#(card.statusDisplayName)</span>\n\nConcatenating Localized Values\n<!-- BAD - breaks RTL languages and locale-specific word order -->\n#(conversation.messageCount) #(conversation.messagesLabel)\n\n<!-- GOOD - ViewModel composes via @LocalizedSubs -->\n#(conversation.messageCountDisplay)\n\n\nTemplate-level concatenation assumes left-to-right order. Use @LocalizedSubs in the ViewModel so YAML can define locale-appropriate ordering:\n\nen:\n  ConversationViewModel:\n    messageCountDisplay: \"%{messageCount} %{messagesLabel}\"\nar:\n  ConversationViewModel:\n    messageCountDisplay: \"%{messagesLabel} %{messageCount}\"\n\nFormatting Dates in Templates\n<!-- BAD - hardcoded format, not locale-aware, concatenation issue -->\n<span>#(content.createdPrefix) #date(content.createdAt, \"MMM d, yyyy\")</span>\n\n<!-- GOOD - LocalizableDate handles locale formatting, @LocalizedSubs composes -->\n<span>#(content.createdDisplay)</span>\n\n\nUse LocalizableDate in the ViewModel - it formats according to user locale. If combining with a prefix, use @LocalizedSubs:\n\npublic let createdAt: LocalizableDate\n\n@LocalizedSubs(\\.createdPrefix, \\.createdAt)\npublic var createdDisplay\n\nMismatched Filenames\n<!-- BAD - filename doesn't match ViewModel -->\nViewModel: UserProfileCardViewModel\nTemplate:  ProfileCard.leaf\n\n<!-- GOOD - aligned names -->\nViewModel: UserProfileCardViewModel\nTemplate:  UserProfileCardView.leaf\n\nIncorrect ViewModelId Initialization\n// ❌ BAD - Generic identity (breaks SwiftUI clients)\npublic var vmId: ViewModelId = .init()\n\n// ✅ MINIMUM - Type-based identity\npublic var vmId: ViewModelId = .init(type: Self.self)\n\n// ✅ IDEAL - Data-based identity (when id available)\npublic init(id: ModelIdType) {\n    self.id = id\n    self.vmId = .init(id: id)\n}\n\n\nViewModels rendered by Leaf are often shared with SwiftUI clients. Correct vmId initialization is critical for SwiftUI's view identity system.\n\nRendering Errors in Leaf Templates\n\nWhen a WebApp route catches an error, the error type is known at compile time. You don't need generic \"ErrorViewModel\" patterns:\n\n// WebApp route - you KNOW the request type, so you KNOW the error type\napp.post(\"move-idea\") { req async throws -> Response in\n    let body = try req.content.decode(MoveIdeaRequest.RequestBody.self)\n    let serverRequest = MoveIdeaRequest(requestBody: body)\n\n    do {\n        try await serverRequest.processRequest(mvvmEnv: req.application.mvvmEnv)\n        // success path...\n    } catch let error as MoveIdeaRequest.ResponseError {\n        // I KNOW this is MoveIdeaRequest.ResponseError\n        // I KNOW it has .code and .message\n        return try await req.view.render(\n            \"Shared/ToastView\",\n            [\"message\": error.message.value, \"type\": \"error\"]\n        ).encodeResponse(for: req)\n    }\n}\n\n\nThe anti-pattern (JavaScript brain):\n\n// ❌ WRONG - treating errors as opaque\ncatch let error as ServerRequestError {\n    // \"How do I extract the message? The protocol doesn't guarantee it!\"\n    // This is wrong thinking. You catch the CONCRETE type.\n}\n\n\nEach route handles its own specific error type. There's no mystery about what properties are available.\n\nHow to Use This Skill\n\nInvocation: /fosmvvm-leaf-view-generator\n\nPrerequisites:\n\nViewModel structure understood from conversation context\nTemplate type determined (full-page vs fragment)\nData attributes needed for JS interactions identified\nHTML-over-the-wire pattern understood if using fragments\n\nWorkflow integration: This skill is used when creating Leaf templates for web clients. The skill references conversation context automatically—no file paths or Q&A needed. Typically follows fosmvvm-viewmodel-generator.\n\nPattern Implementation\n\nThis skill references conversation context to determine template structure:\n\nViewModel Analysis\n\nFrom conversation context, the skill identifies:\n\nViewModel type (from prior discussion or server implementation)\nProperties (what data the template will display)\nLocalization (which properties are Localizable types)\nNested ViewModels (child components)\nTemplate Type Detection\n\nFrom ViewModel purpose:\n\nPage content → Full-page template (extends layout)\nList item/Card → Fragment (no layout, single root)\nModal content → Fragment\nInline component → Fragment\nProperty Mapping\n\nFor each ViewModel property:\n\nid: ModelIdType → data-{entity}-id=\"#(vm.id)\" (for JS)\nRaw enum → data-{field}=\"#(vm.field)\" (for state)\nLocalizableString → #(vm.displayName) (display text)\nLocalizableDate → #(vm.createdAt) (formatted date)\nNested ViewModel → Embed fragment or access properties\nData Attributes Planning\n\nBased on JS interaction needs:\n\nEntity identifier (for operations)\nState values (enum raw values for requests)\nDrag/drop attributes (if interactive)\nCategory/grouping (for filtering/sorting)\nTemplate Generation\n\nFull-page:\n\nLayout extension\nContent export\nEmbedded fragments for components\n\nFragment:\n\nSingle root element\nData attributes for state\nLocalized text from ViewModel\nNo layout extension\nContext Sources\n\nSkill references information from:\n\nPrior conversation: Template requirements, user flows discussed\nViewModel: If Claude has read ViewModel code into context\nExisting templates: From codebase analysis of similar views\nSee Also\nArchitecture Patterns - Mental models (errors are data, type safety, etc.)\nFOSMVVMArchitecture.md - Full architecture\nfosmvvm-viewmodel-generator - Creates the ViewModels this skill renders\nfosmvvm-serverrequest-generator - Creates requests that return ViewModels\nreference.md - Complete template examples\nVersion History\nVersion\tDate\tChanges\n1.0\t2025-12-24\tInitial Kairos-specific skill\n2.0\t2025-12-27\tGeneralized for FOSMVVM, added View-ViewModel alignment principle, full-page templates, architecture connection\n2.1\t2026-01-08\tAdded Leaf Built-in Functions section (count, contains, loop variables). Clarified Codable/computed properties. Corrected earlier false claims about #count() not working.\n2.2\t2026-01-19\tUpdated Pattern 3 to use stored LocalizableString for dynamic enum displays; linked to Enum Localization Pattern. Added anti-patterns for concatenating localized values and formatting dates in templates.\n2.3\t2026-01-20\tAdded \"Rendering Errors in Leaf Templates\" section - error types are known at compile time, no need for generic ErrorViewModel patterns. Prevents JavaScript-brain thinking about runtime type discovery.\n2.4\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."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-leaf-view-generator",
    "publisherUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-leaf-view-generator",
    "owner": "foscomputerservices",
    "version": "2.0.6",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-generator",
    "downloadUrl": "https://openagent3.xyz/downloads/fosmvvm-leaf-view-generator",
    "agentUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-leaf-view-generator/agent.md"
  }
}