{
  "schemaVersion": "1.0",
  "item": {
    "slug": "fosmvvm-react-view-generator",
    "name": "FOSMVVM React View Generator",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-react-view-generator",
    "canonicalUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-react-view-generator",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/fosmvvm-react-view-generator",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=fosmvvm-react-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-react-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-react-view-generator",
    "agentPageUrl": "https://openagent3.xyz/skills/fosmvvm-react-view-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-react-view-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-react-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 React View Generator",
        "body": "Generate React components that render FOSMVVM ViewModels."
      },
      {
        "title": "Conceptual Foundation",
        "body": "For full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference\n\nIn FOSMVVM, React components are thin rendering layers that display ViewModels:\n\n┌─────────────────────────────────────────────────────────────┐\n│                    ViewModelView Pattern                     │\n├─────────────────────────────────────────────────────────────┤\n│                                                              │\n│  ViewModel (Data)          React Component                  │\n│  ┌──────────────────┐     ┌──────────────────┐             │\n│  │ title: String    │────►│ <h1>{vm.title}   │             │\n│  │ items: [Item]    │────►│ {vm.items.map()} │             │\n│  │ isEnabled: Bool  │────►│ disabled={!...}  │             │\n│  └──────────────────┘     └──────────────────┘             │\n│                                                              │\n│  ServerRequest (Actions)                                     │\n│  ┌──────────────────┐     ┌──────────────────┐             │\n│  │ processRequest() │◄────│ <Component.bind  │             │\n│  │                  │     │   requestType={} │             │\n│  └──────────────────┘     └──────────────────┘             │\n│                                                              │\n└─────────────────────────────────────────────────────────────┘\n\nKey principle: Components don't transform or compute data. They render what the ViewModel provides."
      },
      {
        "title": "View-ViewModel Alignment",
        "body": "The component filename should match the ViewModel it renders.\n\nsrc/\n  viewmodels/\n    {Feature}ViewModel.js           ←──┐\n    {Entity}CardViewModel.js        ←──┼── Same names\n                                        │\n  components/                           │\n    {Feature}/                          │\n      {Feature}View.jsx             ────┤  (renders {Feature}ViewModel)\n      {Entity}CardView.jsx          ────┘  (renders {Entity}CardViewModel)\n\nThis alignment provides:\n\nDiscoverability - Find the component for any ViewModel instantly\nConsistency - Same naming discipline as SwiftUI and Leaf\nMaintainability - Changes to ViewModel are reflected in component location"
      },
      {
        "title": "TDD Workflow",
        "body": "This skill generates tests FIRST, implementation SECOND in a single invocation:\n\n1. Reference ViewModel and ServerRequest details from conversation context\n2. Generate .test.js file → Tests FAIL (no implementation yet)\n3. Generate .jsx file → Tests PASS\n4. Verify completeness (both files exist)\n5. User runs `npm test` → All tests pass ✓\n\nContext-aware: Skill references conversation understanding of requirements. No file parsing or Q&A needed."
      },
      {
        "title": "1. viewModelComponent() Wrapper",
        "body": "Every component is wrapped with viewModelComponent():\n\nconst MyView = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.title}</div>;\n});\n\nexport default MyView;\n\nRequired:\n\nUse FOSMVVM.viewModelComponent() from global namespace (loaded via script tag)\nComponent function receives { viewModel } prop\nNo imports needed - FOSMVVM utilities loaded via <script> tags"
      },
      {
        "title": "2. The .bind() Pattern",
        "body": "Parent components use .bind() to invoke ServerRequests:\n\n// Parent component\nfunction Dashboard() {\n  return (\n    <div>\n      <TaskList.bind({\n        requestType: 'GetTasksRequest',\n        params: { status: 'active' }\n      }) />\n    </div>\n  );\n}\n\nThe .bind() pattern:\n\nChild components receive data via ServerRequest\nParent specifies requestType and params\nWASM bridge handles request → ViewModel → component rendering\nNo fetch() calls, no hardcoded URLs"
      },
      {
        "title": "3. Error ViewModel Handling",
        "body": "Error ViewModels are rendered like any other ViewModel:\n\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  // Handle error ViewModels\n  if (viewModel.errorType === 'NotFoundError') {\n    return (\n      <div className=\"error\">\n        <p>{viewModel.message}</p>\n        <p>{viewModel.suggestedAction}</p>\n      </div>\n    );\n  }\n\n  if (viewModel.errorType === 'ValidationError') {\n    return (\n      <div className=\"validation-error\">\n        <h3>{viewModel.title}</h3>\n        <ul>\n          {viewModel.errors.map(err => (\n            <li key={err.field}>{err.message}</li>\n          ))}\n        </ul>\n      </div>\n    );\n  }\n\n  // Render success ViewModel\n  return (\n    <div className=\"task-card\">\n      <h3>{viewModel.title}</h3>\n      <p>{viewModel.description}</p>\n    </div>\n  );\n});\n\nKey principles:\n\nNo generic error handling\nEach error type has its own ViewModel\nComponent conditionally renders based on errorType property\nError rendering is just data rendering"
      },
      {
        "title": "4. Navigation Intents (Not URLs)",
        "body": "Use navigation intents, not hardcoded paths:\n\n// FOSMVVM utilities loaded via <script> tag, available on global namespace\n\n// ❌ NEVER\n<a href=\"/tasks/123\">View Task</a>\n\n// ✅ ALWAYS\n<FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>\n  {viewModel.linkText}\n</FOSMVVM.Link>\n\nNavigation patterns:\n\nUse FOSMVVM.Link from global namespace (loaded via script tag)\nUse intent property, not hardcoded paths\nRouter maps intents to routes\nPlatform-independent navigation"
      },
      {
        "title": "Display-Only Components",
        "body": "Components that just render data (no user interactions):\n\nconst InfoCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div className=\"info-card\">\n      <h2>{viewModel.title}</h2>\n      <p>{viewModel.description}</p>\n\n      {viewModel.isActive && (\n        <span className=\"badge\">{viewModel.activeLabel}</span>\n      )}\n    </div>\n  );\n});\n\nexport default InfoCard;\n\nCharacteristics:\n\nJust renders ViewModel properties\nNo event handlers (onClick, onSubmit, etc.)\nMay have conditional rendering based on ViewModel state\nNo .bind() calls to child components"
      },
      {
        "title": "Interactive Components",
        "body": "Components with user actions that trigger ServerRequests:\n\nconst ActionCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div className=\"action-card\">\n      <h2>{viewModel.title}</h2>\n      <p>{viewModel.description}</p>\n\n      <div className=\"actions\">\n        <button\n          onClick={() => viewModel.operations.performAction()}\n          disabled={!viewModel.canPerformAction}\n        >\n          {viewModel.actionLabel}\n        </button>\n\n        <button onClick={() => viewModel.operations.cancel()}>\n          {viewModel.cancelLabel}\n        </button>\n      </div>\n    </div>\n  );\n});\n\nexport default ActionCard;"
      },
      {
        "title": "List Components",
        "body": "Components that render collections:\n\nconst TaskList = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  if (viewModel.isEmpty) {\n    return <div className=\"empty\">{viewModel.emptyMessage}</div>;\n  }\n\n  return (\n    <div className=\"task-list\">\n      <h2>{viewModel.title}</h2>\n      <p>{viewModel.totalCount}</p>\n\n      {viewModel.tasks.map(task => (\n        <TaskCard.bind({\n          requestType: 'GetTaskRequest',\n          params: { id: task.id }\n        }) />\n      ))}\n    </div>\n  );\n});\n\nexport default TaskList;"
      },
      {
        "title": "Form Components",
        "body": "Components with validated input fields:\n\nconst SignInForm = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [errors, setErrors] = useState({});\n\n  const handleSubmit = async (e) => {\n    e.preventDefault();\n\n    const result = await viewModel.operations.submit({\n      email,\n      password\n    });\n\n    if (result.validationErrors) {\n      setErrors(result.validationErrors);\n    }\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <div>\n        <label>{viewModel.emailLabel}</label>\n        <input\n          type=\"email\"\n          value={email}\n          onChange={(e) => setEmail(e.target.value)}\n          placeholder={viewModel.emailPlaceholder}\n        />\n        {errors.email && <span className=\"error\">{errors.email}</span>}\n      </div>\n\n      <div>\n        <label>{viewModel.passwordLabel}</label>\n        <input\n          type=\"password\"\n          value={password}\n          onChange={(e) => setPassword(e.target.value)}\n          placeholder={viewModel.passwordPlaceholder}\n        />\n        {errors.password && <span className=\"error\">{errors.password}</span>}\n      </div>\n\n      <button type=\"submit\" disabled={viewModel.submitDisabled}>\n        {viewModel.submitLabel}\n      </button>\n    </form>\n  );\n});\n\nexport default SignInForm;"
      },
      {
        "title": "When to Use This Skill",
        "body": "Creating a new React component for a FOSMVVM app\nBuilding UI to render a ViewModel\nMigrating Leaf templates to React\nFollowing an implementation plan that requires new views\nCreating forms with validation\nBuilding list views that compose child components"
      },
      {
        "title": "What This Skill Generates",
        "body": "Two files per invocation:\n\nFileLocationPurpose{ViewName}View.test.jssrc/components/{Feature}/Jest + React Testing Library tests{ViewName}View.jsxsrc/components/{Feature}/React component\n\nTest file generated FIRST (tests fail initially)\nImplementation file generated SECOND (tests pass)\n\nNote: The corresponding ViewModel and ServerRequest should already exist (use other FOSMVVM generator skills)."
      },
      {
        "title": "Project Structure Configuration",
        "body": "PlaceholderDescriptionExample{ViewName}View name (without \"View\" suffix)TaskList, SignIn{Feature}Feature/module groupingTasks, Auth"
      },
      {
        "title": "Pattern Implementation",
        "body": "This skill references conversation context to determine component structure:"
      },
      {
        "title": "Component Type Detection",
        "body": "From conversation context, the skill identifies:\n\nViewModel structure (from prior discussion or specifications read by Claude)\nServerRequest details (from requirements already in context)\nComponent category: Display-only, interactive, form, or list\nError ViewModels to handle"
      },
      {
        "title": "Test Generation (FIRST)",
        "body": "Based on component type, generates .test.js with:\n\nAll components: Success ViewModel rendering, error ViewModel rendering\nInteractive: Button clicks, operation verification\nForm: Input changes, validation errors, submission\nList: Empty state, multiple items, child binding"
      },
      {
        "title": "Component Generation (SECOND)",
        "body": "Generates .jsx following patterns:\n\nImport viewModelComponent wrapper\nHandle error ViewModels with conditional rendering\nRender success ViewModel\nAdd interactions (if interactive)\nAdd form state (if form)\nAdd child .bind() calls (if container)\nExport wrapped component"
      },
      {
        "title": "Context Sources",
        "body": "Skill references information from:\n\nPrior conversation: Requirements discussed with user\nSpecification files: If Claude has read specifications into context\nViewModel definitions: From codebase or discussion"
      },
      {
        "title": "Step 5: Verify Completeness",
        "body": "Check:\n\n.test.js file exists\n .jsx file exists\n Component uses FOSMVVM.viewModelComponent() wrapper\n Component accesses FOSMVVM functions from global namespace\n Tests cover success and error ViewModels\n Tests cover user interactions (if applicable)"
      },
      {
        "title": "Pattern: No Business Logic in Components",
        "body": "// ❌ BAD - Component is transforming data\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const daysLeft = Math.ceil((viewModel.dueDate - Date.now()) / 86400000);\n  return <span>{daysLeft} days remaining</span>;\n});\n\n// ✅ GOOD - ViewModel provides shaped result\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <span>{viewModel.daysRemainingText}</span>;\n});"
      },
      {
        "title": "Pattern: No fetch() Calls",
        "body": "// ❌ BAD - Component making HTTP requests\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const [data, setData] = useState(null);\n\n  useEffect(() => {\n    fetch(`/api/tasks/${viewModel.id}`)\n      .then(r => r.json())\n      .then(setData);\n  }, [viewModel.id]);\n\n  return <div>{data?.title}</div>;\n});\n\n// ✅ GOOD - Parent uses .bind() to invoke ServerRequest\n<TaskCard.bind({\n  requestType: 'GetTaskRequest',\n  params: { id: taskId }\n}) />"
      },
      {
        "title": "Pattern: Error ViewModels Are Data",
        "body": "// ❌ BAD - Generic error handling\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  if (viewModel.error) {\n    return <div>Error: {viewModel.error.message}</div>;\n  }\n  return <div>{viewModel.title}</div>;\n});\n\n// ✅ GOOD - Specific error ViewModels\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  if (viewModel.errorType === 'NotFoundError') {\n    return (\n      <div className=\"not-found\">\n        <h3>{viewModel.errorTitle}</h3>\n        <p>{viewModel.errorMessage}</p>\n        <p>{viewModel.suggestedAction}</p>\n      </div>\n    );\n  }\n\n  if (viewModel.errorType === 'ValidationError') {\n    return (\n      <div className=\"validation-error\">\n        <h3>{viewModel.errorTitle}</h3>\n        <ul>\n          {viewModel.validationErrors.map(err => (\n            <li key={err.field}>{err.message}</li>\n          ))}\n        </ul>\n      </div>\n    );\n  }\n\n  return <div>{viewModel.title}</div>;\n});"
      },
      {
        "title": "Pattern: Navigation Intents",
        "body": "// ❌ BAD - Hardcoded URLs\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div>\n      <a href={`/tasks/${viewModel.id}`}>{viewModel.title}</a>\n    </div>\n  );\n});\n\n// ✅ GOOD - Navigation intents\n// FOSMVVM utilities loaded via <script> tag, available on global namespace\n\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div>\n      <FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>\n        {viewModel.title}\n      </FOSMVVM.Link>\n    </div>\n  );\n});"
      },
      {
        "title": "File Organization",
        "body": "src/components/\n├── {Feature}/\n│   ├── {Feature}View.jsx             # Full page → {Feature}ViewModel\n│   ├── {Feature}View.test.js         # Tests for {Feature}View\n│   ├── {Entity}CardView.jsx          # Child component → {Entity}CardViewModel\n│   ├── {Entity}CardView.test.js      # Tests for {Entity}CardView\n│   └── {Entity}RowView.jsx           # Child component → {Entity}RowViewModel\n├── Shared/\n│   ├── HeaderView.jsx                # Shared components\n│   └── FooterView.jsx"
      },
      {
        "title": "Computing Data in Components",
        "body": "// ❌ BAD - Component is transforming data\nconst UserCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.firstName} {viewModel.lastName}</div>;\n});\n\n// ✅ GOOD - ViewModel provides shaped result\nconst UserCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.fullName}</div>;\n});"
      },
      {
        "title": "Making HTTP Requests Directly",
        "body": "// ❌ BAD - fetch() call in component\nconst TaskList = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const [tasks, setTasks] = useState([]);\n\n  useEffect(() => {\n    fetch('/api/tasks').then(r => r.json()).then(setTasks);\n  }, []);\n\n  return <div>{tasks.map(t => <div key={t.id}>{t.title}</div>)}</div>;\n});\n\n// ✅ GOOD - Parent uses .bind() with ServerRequest\n<TaskList.bind({\n  requestType: 'GetTasksRequest',\n  params: {}\n}) />"
      },
      {
        "title": "Hardcoding Text",
        "body": "// ❌ BAD - Not localizable\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <button onClick={viewModel.operations.submit}>\n      Submit\n    </button>\n  );\n});\n\n// ✅ GOOD - ViewModel provides localized text\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <button onClick={viewModel.operations.submit}>\n      {viewModel.submitLabel}\n    </button>\n  );\n});"
      },
      {
        "title": "Using Hardcoded URLs",
        "body": "// ❌ BAD - Hardcoded path\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <a href={`/tasks/${viewModel.id}`}>{viewModel.title}</a>;\n});\n\n// ✅ GOOD - Navigation intent\n// FOSMVVM utilities loaded via <script> tag, available on global namespace\n\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>\n      {viewModel.title}\n    </FOSMVVM.Link>\n  );\n});"
      },
      {
        "title": "Not Wrapping with viewModelComponent()",
        "body": "// ❌ BAD - Missing viewModelComponent() wrapper\nconst TaskCard = ({ viewModel }) => {\n  return <div>{viewModel.title}</div>;\n};\nexport default TaskCard;\n\n// ✅ GOOD - Wrapped with viewModelComponent()\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.title}</div>;\n});\nexport default TaskCard;"
      },
      {
        "title": "Mismatched Filenames",
        "body": "// ❌ BAD - Filename doesn't match ViewModel\nViewModel: TaskListViewModel\nComponent: Tasks.jsx\n\n// ✅ GOOD - Aligned names\nViewModel: TaskListViewModel\nComponent: TaskListView.jsx"
      },
      {
        "title": "File Templates",
        "body": "See reference.md for complete file templates."
      },
      {
        "title": "Naming Conventions",
        "body": "ConceptConventionExampleComponent file{Name}View.jsxTaskListView.jsx, SignInView.jsxTest file{Name}View.test.jsTaskListView.test.jsComponent function{Name}ViewTaskListView, SignInViewViewModel propviewModelAlways viewModel"
      },
      {
        "title": "Test: Rendering with Success ViewModel",
        "body": "it('renders task card with ViewModel', () => {\n  const viewModel = {\n    title: 'Test Task',\n    description: 'Test Description',\n    dueDate: 'Jan 30, 2026'\n  };\n\n  render(<TaskCard viewModel={viewModel} />);\n\n  expect(screen.getByText('Test Task')).toBeInTheDocument();\n  expect(screen.getByText('Test Description')).toBeInTheDocument();\n});"
      },
      {
        "title": "Test: Rendering with Error ViewModel",
        "body": "it('renders NotFoundViewModel', () => {\n  const viewModel = {\n    errorType: 'NotFoundError',\n    errorTitle: 'Task Not Found',\n    errorMessage: 'The task you requested does not exist',\n    suggestedAction: 'Try searching for a different task'\n  };\n\n  render(<TaskCard viewModel={viewModel} />);\n\n  expect(screen.getByText('Task Not Found')).toBeInTheDocument();\n  expect(screen.getByText(/does not exist/)).toBeInTheDocument();\n});"
      },
      {
        "title": "Test: User Interaction",
        "body": "it('calls operation when button clicked', () => {\n  const mockOperation = jest.fn();\n  const viewModel = {\n    title: 'Test Task',\n    submitLabel: 'Complete Task',\n    operations: {\n      complete: mockOperation\n    }\n  };\n\n  render(<TaskCard viewModel={viewModel} />);\n\n  fireEvent.click(screen.getByText('Complete Task'));\n\n  expect(mockOperation).toHaveBeenCalled();\n});"
      },
      {
        "title": "How to Use This Skill",
        "body": "Invocation:\n\n/fosmvvm-react-view-generator\n\nPrerequisites:\n\nViewModel and ServerRequest details are understood from conversation\nOptionally, specification files have been read into context\nComponent requirements (display-only, interactive, form, list) are clear from discussion\n\nOutput:\n\n{ComponentName}.test.js - Generated FIRST (tests fail)\n{ComponentName}.jsx - Generated SECOND (tests pass)\n\nWorkflow integration:\nThis skill is typically used after discussing requirements or reading specification files. The skill references that context automatically—no file paths or Q&A needed."
      },
      {
        "title": "See Also",
        "body": "Architecture Patterns - Mental models and patterns\nFOSMVVMArchitecture.md - Full FOSMVVM architecture\nfosmvvm-swiftui-view-generator - SwiftUI equivalent\nfosmvvm-leaf-view-generator - Leaf equivalent\nreference.md - Complete file templates"
      },
      {
        "title": "Version History",
        "body": "VersionDateChanges1.02026-01-23Initial skill for React view generation based on Kairos requirements"
      }
    ],
    "body": "FOSMVVM React View Generator\n\nGenerate React components that render FOSMVVM ViewModels.\n\nConceptual Foundation\n\nFor full architecture context, see FOSMVVMArchitecture.md | OpenClaw reference\n\nIn FOSMVVM, React components are thin rendering layers that display ViewModels:\n\n┌─────────────────────────────────────────────────────────────┐\n│                    ViewModelView Pattern                     │\n├─────────────────────────────────────────────────────────────┤\n│                                                              │\n│  ViewModel (Data)          React Component                  │\n│  ┌──────────────────┐     ┌──────────────────┐             │\n│  │ title: String    │────►│ <h1>{vm.title}   │             │\n│  │ items: [Item]    │────►│ {vm.items.map()} │             │\n│  │ isEnabled: Bool  │────►│ disabled={!...}  │             │\n│  └──────────────────┘     └──────────────────┘             │\n│                                                              │\n│  ServerRequest (Actions)                                     │\n│  ┌──────────────────┐     ┌──────────────────┐             │\n│  │ processRequest() │◄────│ <Component.bind  │             │\n│  │                  │     │   requestType={} │             │\n│  └──────────────────┘     └──────────────────┘             │\n│                                                              │\n└─────────────────────────────────────────────────────────────┘\n\n\nKey principle: Components don't transform or compute data. They render what the ViewModel provides.\n\nView-ViewModel Alignment\n\nThe component filename should match the ViewModel it renders.\n\nsrc/\n  viewmodels/\n    {Feature}ViewModel.js           ←──┐\n    {Entity}CardViewModel.js        ←──┼── Same names\n                                        │\n  components/                           │\n    {Feature}/                          │\n      {Feature}View.jsx             ────┤  (renders {Feature}ViewModel)\n      {Entity}CardView.jsx          ────┘  (renders {Entity}CardViewModel)\n\n\nThis alignment provides:\n\nDiscoverability - Find the component for any ViewModel instantly\nConsistency - Same naming discipline as SwiftUI and Leaf\nMaintainability - Changes to ViewModel are reflected in component location\nTDD Workflow\n\nThis skill generates tests FIRST, implementation SECOND in a single invocation:\n\n1. Reference ViewModel and ServerRequest details from conversation context\n2. Generate .test.js file → Tests FAIL (no implementation yet)\n3. Generate .jsx file → Tests PASS\n4. Verify completeness (both files exist)\n5. User runs `npm test` → All tests pass ✓\n\n\nContext-aware: Skill references conversation understanding of requirements. No file parsing or Q&A needed.\n\nCore Components\n1. viewModelComponent() Wrapper\n\nEvery component is wrapped with viewModelComponent():\n\nconst MyView = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.title}</div>;\n});\n\nexport default MyView;\n\n\nRequired:\n\nUse FOSMVVM.viewModelComponent() from global namespace (loaded via script tag)\nComponent function receives { viewModel } prop\nNo imports needed - FOSMVVM utilities loaded via <script> tags\n2. The .bind() Pattern\n\nParent components use .bind() to invoke ServerRequests:\n\n// Parent component\nfunction Dashboard() {\n  return (\n    <div>\n      <TaskList.bind({\n        requestType: 'GetTasksRequest',\n        params: { status: 'active' }\n      }) />\n    </div>\n  );\n}\n\n\nThe .bind() pattern:\n\nChild components receive data via ServerRequest\nParent specifies requestType and params\nWASM bridge handles request → ViewModel → component rendering\nNo fetch() calls, no hardcoded URLs\n3. Error ViewModel Handling\n\nError ViewModels are rendered like any other ViewModel:\n\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  // Handle error ViewModels\n  if (viewModel.errorType === 'NotFoundError') {\n    return (\n      <div className=\"error\">\n        <p>{viewModel.message}</p>\n        <p>{viewModel.suggestedAction}</p>\n      </div>\n    );\n  }\n\n  if (viewModel.errorType === 'ValidationError') {\n    return (\n      <div className=\"validation-error\">\n        <h3>{viewModel.title}</h3>\n        <ul>\n          {viewModel.errors.map(err => (\n            <li key={err.field}>{err.message}</li>\n          ))}\n        </ul>\n      </div>\n    );\n  }\n\n  // Render success ViewModel\n  return (\n    <div className=\"task-card\">\n      <h3>{viewModel.title}</h3>\n      <p>{viewModel.description}</p>\n    </div>\n  );\n});\n\n\nKey principles:\n\nNo generic error handling\nEach error type has its own ViewModel\nComponent conditionally renders based on errorType property\nError rendering is just data rendering\n4. Navigation Intents (Not URLs)\n\nUse navigation intents, not hardcoded paths:\n\n// FOSMVVM utilities loaded via <script> tag, available on global namespace\n\n// ❌ NEVER\n<a href=\"/tasks/123\">View Task</a>\n\n// ✅ ALWAYS\n<FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>\n  {viewModel.linkText}\n</FOSMVVM.Link>\n\n\nNavigation patterns:\n\nUse FOSMVVM.Link from global namespace (loaded via script tag)\nUse intent property, not hardcoded paths\nRouter maps intents to routes\nPlatform-independent navigation\nComponent Categories\nDisplay-Only Components\n\nComponents that just render data (no user interactions):\n\nconst InfoCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div className=\"info-card\">\n      <h2>{viewModel.title}</h2>\n      <p>{viewModel.description}</p>\n\n      {viewModel.isActive && (\n        <span className=\"badge\">{viewModel.activeLabel}</span>\n      )}\n    </div>\n  );\n});\n\nexport default InfoCard;\n\n\nCharacteristics:\n\nJust renders ViewModel properties\nNo event handlers (onClick, onSubmit, etc.)\nMay have conditional rendering based on ViewModel state\nNo .bind() calls to child components\nInteractive Components\n\nComponents with user actions that trigger ServerRequests:\n\nconst ActionCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div className=\"action-card\">\n      <h2>{viewModel.title}</h2>\n      <p>{viewModel.description}</p>\n\n      <div className=\"actions\">\n        <button\n          onClick={() => viewModel.operations.performAction()}\n          disabled={!viewModel.canPerformAction}\n        >\n          {viewModel.actionLabel}\n        </button>\n\n        <button onClick={() => viewModel.operations.cancel()}>\n          {viewModel.cancelLabel}\n        </button>\n      </div>\n    </div>\n  );\n});\n\nexport default ActionCard;\n\nList Components\n\nComponents that render collections:\n\nconst TaskList = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  if (viewModel.isEmpty) {\n    return <div className=\"empty\">{viewModel.emptyMessage}</div>;\n  }\n\n  return (\n    <div className=\"task-list\">\n      <h2>{viewModel.title}</h2>\n      <p>{viewModel.totalCount}</p>\n\n      {viewModel.tasks.map(task => (\n        <TaskCard.bind({\n          requestType: 'GetTaskRequest',\n          params: { id: task.id }\n        }) />\n      ))}\n    </div>\n  );\n});\n\nexport default TaskList;\n\nForm Components\n\nComponents with validated input fields:\n\nconst SignInForm = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const [email, setEmail] = useState('');\n  const [password, setPassword] = useState('');\n  const [errors, setErrors] = useState({});\n\n  const handleSubmit = async (e) => {\n    e.preventDefault();\n\n    const result = await viewModel.operations.submit({\n      email,\n      password\n    });\n\n    if (result.validationErrors) {\n      setErrors(result.validationErrors);\n    }\n  };\n\n  return (\n    <form onSubmit={handleSubmit}>\n      <div>\n        <label>{viewModel.emailLabel}</label>\n        <input\n          type=\"email\"\n          value={email}\n          onChange={(e) => setEmail(e.target.value)}\n          placeholder={viewModel.emailPlaceholder}\n        />\n        {errors.email && <span className=\"error\">{errors.email}</span>}\n      </div>\n\n      <div>\n        <label>{viewModel.passwordLabel}</label>\n        <input\n          type=\"password\"\n          value={password}\n          onChange={(e) => setPassword(e.target.value)}\n          placeholder={viewModel.passwordPlaceholder}\n        />\n        {errors.password && <span className=\"error\">{errors.password}</span>}\n      </div>\n\n      <button type=\"submit\" disabled={viewModel.submitDisabled}>\n        {viewModel.submitLabel}\n      </button>\n    </form>\n  );\n});\n\nexport default SignInForm;\n\nWhen to Use This Skill\nCreating a new React component for a FOSMVVM app\nBuilding UI to render a ViewModel\nMigrating Leaf templates to React\nFollowing an implementation plan that requires new views\nCreating forms with validation\nBuilding list views that compose child components\nWhat This Skill Generates\n\nTwo files per invocation:\n\nFile\tLocation\tPurpose\n{ViewName}View.test.js\tsrc/components/{Feature}/\tJest + React Testing Library tests\n{ViewName}View.jsx\tsrc/components/{Feature}/\tReact component\n\nTest file generated FIRST (tests fail initially) Implementation file generated SECOND (tests pass)\n\nNote: The corresponding ViewModel and ServerRequest should already exist (use other FOSMVVM generator skills).\n\nProject Structure Configuration\nPlaceholder\tDescription\tExample\n{ViewName}\tView name (without \"View\" suffix)\tTaskList, SignIn\n{Feature}\tFeature/module grouping\tTasks, Auth\nPattern Implementation\n\nThis skill references conversation context to determine component structure:\n\nComponent Type Detection\n\nFrom conversation context, the skill identifies:\n\nViewModel structure (from prior discussion or specifications read by Claude)\nServerRequest details (from requirements already in context)\nComponent category: Display-only, interactive, form, or list\nError ViewModels to handle\nTest Generation (FIRST)\n\nBased on component type, generates .test.js with:\n\nAll components: Success ViewModel rendering, error ViewModel rendering\nInteractive: Button clicks, operation verification\nForm: Input changes, validation errors, submission\nList: Empty state, multiple items, child binding\nComponent Generation (SECOND)\n\nGenerates .jsx following patterns:\n\nImport viewModelComponent wrapper\nHandle error ViewModels with conditional rendering\nRender success ViewModel\nAdd interactions (if interactive)\nAdd form state (if form)\nAdd child .bind() calls (if container)\nExport wrapped component\nContext Sources\n\nSkill references information from:\n\nPrior conversation: Requirements discussed with user\nSpecification files: If Claude has read specifications into context\nViewModel definitions: From codebase or discussion\nStep 5: Verify Completeness\n\nCheck:\n\n .test.js file exists\n .jsx file exists\n Component uses FOSMVVM.viewModelComponent() wrapper\n Component accesses FOSMVVM functions from global namespace\n Tests cover success and error ViewModels\n Tests cover user interactions (if applicable)\nKey Patterns\nPattern: No Business Logic in Components\n// ❌ BAD - Component is transforming data\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const daysLeft = Math.ceil((viewModel.dueDate - Date.now()) / 86400000);\n  return <span>{daysLeft} days remaining</span>;\n});\n\n// ✅ GOOD - ViewModel provides shaped result\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <span>{viewModel.daysRemainingText}</span>;\n});\n\nPattern: No fetch() Calls\n// ❌ BAD - Component making HTTP requests\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const [data, setData] = useState(null);\n\n  useEffect(() => {\n    fetch(`/api/tasks/${viewModel.id}`)\n      .then(r => r.json())\n      .then(setData);\n  }, [viewModel.id]);\n\n  return <div>{data?.title}</div>;\n});\n\n// ✅ GOOD - Parent uses .bind() to invoke ServerRequest\n<TaskCard.bind({\n  requestType: 'GetTaskRequest',\n  params: { id: taskId }\n}) />\n\nPattern: Error ViewModels Are Data\n// ❌ BAD - Generic error handling\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  if (viewModel.error) {\n    return <div>Error: {viewModel.error.message}</div>;\n  }\n  return <div>{viewModel.title}</div>;\n});\n\n// ✅ GOOD - Specific error ViewModels\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  if (viewModel.errorType === 'NotFoundError') {\n    return (\n      <div className=\"not-found\">\n        <h3>{viewModel.errorTitle}</h3>\n        <p>{viewModel.errorMessage}</p>\n        <p>{viewModel.suggestedAction}</p>\n      </div>\n    );\n  }\n\n  if (viewModel.errorType === 'ValidationError') {\n    return (\n      <div className=\"validation-error\">\n        <h3>{viewModel.errorTitle}</h3>\n        <ul>\n          {viewModel.validationErrors.map(err => (\n            <li key={err.field}>{err.message}</li>\n          ))}\n        </ul>\n      </div>\n    );\n  }\n\n  return <div>{viewModel.title}</div>;\n});\n\nPattern: Navigation Intents\n// ❌ BAD - Hardcoded URLs\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div>\n      <a href={`/tasks/${viewModel.id}`}>{viewModel.title}</a>\n    </div>\n  );\n});\n\n// ✅ GOOD - Navigation intents\n// FOSMVVM utilities loaded via <script> tag, available on global namespace\n\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <div>\n      <FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>\n        {viewModel.title}\n      </FOSMVVM.Link>\n    </div>\n  );\n});\n\nFile Organization\nsrc/components/\n├── {Feature}/\n│   ├── {Feature}View.jsx             # Full page → {Feature}ViewModel\n│   ├── {Feature}View.test.js         # Tests for {Feature}View\n│   ├── {Entity}CardView.jsx          # Child component → {Entity}CardViewModel\n│   ├── {Entity}CardView.test.js      # Tests for {Entity}CardView\n│   └── {Entity}RowView.jsx           # Child component → {Entity}RowViewModel\n├── Shared/\n│   ├── HeaderView.jsx                # Shared components\n│   └── FooterView.jsx\n\nCommon Mistakes\nComputing Data in Components\n// ❌ BAD - Component is transforming data\nconst UserCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.firstName} {viewModel.lastName}</div>;\n});\n\n// ✅ GOOD - ViewModel provides shaped result\nconst UserCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.fullName}</div>;\n});\n\nMaking HTTP Requests Directly\n// ❌ BAD - fetch() call in component\nconst TaskList = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  const [tasks, setTasks] = useState([]);\n\n  useEffect(() => {\n    fetch('/api/tasks').then(r => r.json()).then(setTasks);\n  }, []);\n\n  return <div>{tasks.map(t => <div key={t.id}>{t.title}</div>)}</div>;\n});\n\n// ✅ GOOD - Parent uses .bind() with ServerRequest\n<TaskList.bind({\n  requestType: 'GetTasksRequest',\n  params: {}\n}) />\n\nHardcoding Text\n// ❌ BAD - Not localizable\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <button onClick={viewModel.operations.submit}>\n      Submit\n    </button>\n  );\n});\n\n// ✅ GOOD - ViewModel provides localized text\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <button onClick={viewModel.operations.submit}>\n      {viewModel.submitLabel}\n    </button>\n  );\n});\n\nUsing Hardcoded URLs\n// ❌ BAD - Hardcoded path\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <a href={`/tasks/${viewModel.id}`}>{viewModel.title}</a>;\n});\n\n// ✅ GOOD - Navigation intent\n// FOSMVVM utilities loaded via <script> tag, available on global namespace\n\nconst TaskRow = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return (\n    <FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>\n      {viewModel.title}\n    </FOSMVVM.Link>\n  );\n});\n\nNot Wrapping with viewModelComponent()\n// ❌ BAD - Missing viewModelComponent() wrapper\nconst TaskCard = ({ viewModel }) => {\n  return <div>{viewModel.title}</div>;\n};\nexport default TaskCard;\n\n// ✅ GOOD - Wrapped with viewModelComponent()\nconst TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {\n  return <div>{viewModel.title}</div>;\n});\nexport default TaskCard;\n\nMismatched Filenames\n// ❌ BAD - Filename doesn't match ViewModel\nViewModel: TaskListViewModel\nComponent: Tasks.jsx\n\n// ✅ GOOD - Aligned names\nViewModel: TaskListViewModel\nComponent: TaskListView.jsx\n\nFile Templates\n\nSee reference.md for complete file templates.\n\nNaming Conventions\nConcept\tConvention\tExample\nComponent file\t{Name}View.jsx\tTaskListView.jsx, SignInView.jsx\nTest file\t{Name}View.test.js\tTaskListView.test.js\nComponent function\t{Name}View\tTaskListView, SignInView\nViewModel prop\tviewModel\tAlways viewModel\nTesting Patterns\nTest: Rendering with Success ViewModel\nit('renders task card with ViewModel', () => {\n  const viewModel = {\n    title: 'Test Task',\n    description: 'Test Description',\n    dueDate: 'Jan 30, 2026'\n  };\n\n  render(<TaskCard viewModel={viewModel} />);\n\n  expect(screen.getByText('Test Task')).toBeInTheDocument();\n  expect(screen.getByText('Test Description')).toBeInTheDocument();\n});\n\nTest: Rendering with Error ViewModel\nit('renders NotFoundViewModel', () => {\n  const viewModel = {\n    errorType: 'NotFoundError',\n    errorTitle: 'Task Not Found',\n    errorMessage: 'The task you requested does not exist',\n    suggestedAction: 'Try searching for a different task'\n  };\n\n  render(<TaskCard viewModel={viewModel} />);\n\n  expect(screen.getByText('Task Not Found')).toBeInTheDocument();\n  expect(screen.getByText(/does not exist/)).toBeInTheDocument();\n});\n\nTest: User Interaction\nit('calls operation when button clicked', () => {\n  const mockOperation = jest.fn();\n  const viewModel = {\n    title: 'Test Task',\n    submitLabel: 'Complete Task',\n    operations: {\n      complete: mockOperation\n    }\n  };\n\n  render(<TaskCard viewModel={viewModel} />);\n\n  fireEvent.click(screen.getByText('Complete Task'));\n\n  expect(mockOperation).toHaveBeenCalled();\n});\n\nHow to Use This Skill\n\nInvocation:\n\n/fosmvvm-react-view-generator\n\n\nPrerequisites:\n\nViewModel and ServerRequest details are understood from conversation\nOptionally, specification files have been read into context\nComponent requirements (display-only, interactive, form, list) are clear from discussion\n\nOutput:\n\n{ComponentName}.test.js - Generated FIRST (tests fail)\n{ComponentName}.jsx - Generated SECOND (tests pass)\n\nWorkflow integration: This skill is typically used after discussing requirements or reading specification files. The skill references that context automatically—no file paths or Q&A needed.\n\nSee Also\nArchitecture Patterns - Mental models and patterns\nFOSMVVMArchitecture.md - Full FOSMVVM architecture\nfosmvvm-swiftui-view-generator - SwiftUI equivalent\nfosmvvm-leaf-view-generator - Leaf equivalent\nreference.md - Complete file templates\nVersion History\nVersion\tDate\tChanges\n1.0\t2026-01-23\tInitial skill for React view generation based on Kairos requirements"
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-react-view-generator",
    "publisherUrl": "https://clawhub.ai/foscomputerservices/fosmvvm-react-view-generator",
    "owner": "foscomputerservices",
    "version": "2.0.6",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/fosmvvm-react-view-generator",
    "downloadUrl": "https://openagent3.xyz/downloads/fosmvvm-react-view-generator",
    "agentUrl": "https://openagent3.xyz/skills/fosmvvm-react-view-generator/agent",
    "manifestUrl": "https://openagent3.xyz/skills/fosmvvm-react-view-generator/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/fosmvvm-react-view-generator/agent.md"
  }
}