FOSMVVM React View Generator
Generate React components that render FOSMVVM ViewModels. Scaffolds ViewModelView pattern with hooks, loading states, and TypeScript types.
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install skilldb:foscomputerservices~fosmvvm-react-view-generatorcURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/skilldb%3Afoscomputerservices~fosmvvm-react-view-generator/file -o fosmvvm-react-view-generator.mdGit 仓库获取源码
git clone https://github.com/openclaw/skills/commit/41bb86d4d45a79ec1ef4152507ea8ff43deecbfe# FOSMVVM React View Generator
Generate React components that render FOSMVVM ViewModels.
## Conceptual Foundation
> For full architecture context, see [FOSMVVMArchitecture.md](../../docs/FOSMVVMArchitecture.md) | [OpenClaw reference]({baseDir}/references/FOSMVVMArchitecture.md)
In FOSMVVM, **React components are thin rendering layers** that display ViewModels:
```
┌─────────────────────────────────────────────────────────────┐
│ ViewModelView Pattern │
├─────────────────────────────────────────────────────────────┤
│ │
│ ViewModel (Data) React Component │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ title: String │────►│ <h1>{vm.title} │ │
│ │ items: [Item] │────►│ {vm.items.map()} │ │
│ │ isEnabled: Bool │────►│ disabled={!...} │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
│ ServerRequest (Actions) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ processRequest() │◄────│ <Component.bind │ │
│ │ │ │ requestType={} │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
**Key principle:** Components don't transform or compute data. They render what the ViewModel provides.
---
## View-ViewModel Alignment
**The component filename should match the ViewModel it renders.**
```
src/
viewmodels/
{Feature}ViewModel.js ←──┐
{Entity}CardViewModel.js ←──┼── Same names
│
components/ │
{Feature}/ │
{Feature}View.jsx ────┤ (renders {Feature}ViewModel)
{Entity}CardView.jsx ────┘ (renders {Entity}CardViewModel)
```
This alignment provides:
- **Discoverability** - Find the component for any ViewModel instantly
- **Consistency** - Same naming discipline as SwiftUI and Leaf
- **Maintainability** - Changes to ViewModel are reflected in component location
---
## TDD Workflow
This skill generates **tests FIRST, implementation SECOND** in a single invocation:
```
1. Reference ViewModel and ServerRequest details from conversation context
2. Generate .test.js file → Tests FAIL (no implementation yet)
3. Generate .jsx file → Tests PASS
4. Verify completeness (both files exist)
5. User runs `npm test` → All tests pass ✓
```
**Context-aware:** Skill references conversation understanding of requirements. No file parsing or Q&A needed.
---
## Core Components
### 1. viewModelComponent() Wrapper
Every component is wrapped with `viewModelComponent()`:
```jsx
const MyView = FOSMVVM.viewModelComponent(({ viewModel }) => {
return <div>{viewModel.title}</div>;
});
export default MyView;
```
**Required:**
- Use `FOSMVVM.viewModelComponent()` from global namespace (loaded via script tag)
- Component function receives `{ viewModel }` prop
- No imports needed - FOSMVVM utilities loaded via `<script>` tags
### 2. The .bind() Pattern
Parent components use `.bind()` to invoke ServerRequests:
```jsx
// Parent component
function Dashboard() {
return (
<div>
<TaskList.bind({
requestType: 'GetTasksRequest',
params: { status: 'active' }
}) />
</div>
);
}
```
**The .bind() pattern:**
- Child components receive data via ServerRequest
- Parent specifies `requestType` and `params`
- WASM bridge handles request → ViewModel → component rendering
- No fetch() calls, no hardcoded URLs
### 3. Error ViewModel Handling
Error ViewModels are rendered like any other ViewModel:
```jsx
const TaskCard = FOSMVVM.viewModelComponent(({ viewModel }) => {
// Handle error ViewModels
if (viewModel.errorType === 'NotFoundError') {
return (
<div className="error">
<p>{viewModel.message}</p>
<p>{viewModel.suggestedAction}</p>
</div>
);
}
if (viewModel.errorType === 'ValidationError') {
return (
<div className="validation-error">
<h3>{viewModel.title}</h3>
<ul>
{viewModel.errors.map(err => (
<li key={err.field}>{err.message}</li>
))}
</ul>
</div>
);
}
// Render success ViewModel
return (
<div className="task-card">
<h3>{viewModel.title}</h3>
<p>{viewModel.description}</p>
</div>
);
});
```
**Key principles:**
- No generic error handling
- Each error type has its own ViewModel
- Component conditionally renders based on `errorType` property
- Error rendering is just data rendering
### 4. Navigation Intents (Not URLs)
Use navigation intents, not hardcoded paths:
```jsx
// FOSMVVM utilities loaded via <script> tag, available on global namespace
// ❌ NEVER
<a href="/tasks/123">View Task</a>
// ✅ ALWAYS
<FOSMVVM.Link to={{ intent: 'viewTask', id: viewModel.id }}>
{viewModel.linkText}
</FOSMVVM.Link>
```
**Navigation patterns:**
- Use `FOSMVVM.Link` from global namespace (loaded via script tag)
- Use `intent` property, not hardcoded paths
- Router maps intents to routes
- Platform-independent navigation
---
## Component Categories
### Display-Only Components
Components that just render data (no user interactions):
```jsx
const InfoCard = FOSMVVM.viewModelComponent(({ viewModel }) => {
return (
<div className="info-card">
<h2>{viewModel.title}</h2>
<p>{viewModel.description}</p>
{viewModel.isActive && (
<span className="badge">{viewModel.activeLabel}</span>
)}
</div>
);
});
export default InfoCard;
```
**Characteristics:**
- Just renders ViewModel properties
- No event handlers (onClick, onSubmit, etc.)
- May have conditional rendering based on ViewModel state
- No .bind() calls to child components
### Interactive Components
Components with user actions that trigger ServerRequests:
```jsx
const ActionCard = FOSMVVM.viewModelComponent(({ viewModel }) => {
return (
<div className="action-card">
<h2>{viewModel.title}</h2>
<p>{viewModel.description}</p>
<div className="actions">
<button
onClick={() => viewModel.operations.performAction()}
disabled={!viewModel.canPerformAction}
>
{viewModel.actionLabel}
</button>
<button onClick={() => viewModel.operations.cancel()}>
{viewModel.cancelLabel}
</button>
</div>
</div>
);
});
export default ActionCard;
```
### List Components
Components that render collections:
```jsx
const TaskList = FOSMVVM.viewModelComponent(({ viewModel }) => {
if (viewModel.isEmpty) {
return <div className="empty">{viewModel.emptyMessage}</div>;
}
return (
<div className="task-list">
<h2>{viewModel.title}</h2>
<p>{viewModel.totalCount}</p>
{viewModel.tasks.map(task => (
<TaskCard.bind({
requestType: 'GetTaskRequest',
params: { id: task.id }
}) />
))}
</div>
);
});
export default TaskList;
```
### Form Components
Components with validated input fields:
```jsx
const SignInForm = FOSMVVM.viewModelComponent(({ viewModel }) => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState({});
const handleSubmit = async (e) => {
e.preventDefault();
const result = await viewModel.operations.submit({
email,
password
});
if (result.validationErrors) {
setErrors(result.validationErrors);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>{viewModel.emailLabel}</label>
<input
type="email"
value={emai