Claude-skill-registry Create Entity ViewModel
Create a new entity ViewModel with property ViewModels following {{sharedLib}} MVVM patterns. Use when adding new data models, creating entity wrappers, or scaffolding ViewModels for entities. Handles property VM creation, label converters, base class selection, and test generation.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/create-entity-viewmodel" ~/.claude/skills/majiayu000-claude-skill-registry-create-entity-viewmodel && rm -rf "$T"
skills/data/create-entity-viewmodel/SKILL.mdCreate Entity ViewModel
This skill scaffolds a complete entity ViewModel following {{sharedLib}} framework patterns.
When to Use
- Creating a new entity ViewModel for a data model
- Wrapping an existing model with UI logic
- Adding property ViewModels for entity properties
- Setting up label converters for enum types
Prerequisites
- Data model must exist in
and be generated/model/*.model/src/ - Run
if model was just creatednpm run generate-model - Entity should be defined with proper property types (ValueProperty, CommandedProperty, etc.)
Process
Step 1: Understand the Data Model
Read the source model to understand:
- Property types (ValueProperty, CommandedProperty, RangedCommandedProperty)
- Enum types that need label converters
- Geographic properties (lat/lon/alt)
- Parent class (Entity, GeoEntity, Platform, etc.)
Reference: ENTITY_ARCHITECTURE.md
Step 2: Choose the Right Base Class
Determine appropriate base class:
- EntityViewModel - Standard entities (settings, configs)
- GeoEntityBaseVM - Entities with location data (platforms, vehicles)
- GeoPointBaseVM - Simple geographic points (waypoints, markers)
- Custom abstract base - Multiple related entities share functionality
Reference: ENTITY_ARCHITECTURE.md - Best Practices
Step 3: Create Property ViewModels
For each property on the model, create appropriate property VM:
Property Type Mapping:
→ValueProperty<string>StringViewModel
→CommandedProperty<string>CommandedStringViewModel
→ValueProperty<number>NumberViewModel
→RangedCommandedProperty<number>RangedCommandedNumberViewModel
→CommandedProperty<boolean>CommandedBooleanViewModel
→CommandedProperty<EnumType>CommandedEnumViewModel<EnumType>
→ValueProperty<string[]>ArrayViewModel<string>
→CommandedProperty<string[]>CommandedArrayViewModel<string>
CRITICAL Patterns:
- Use
, NOTCommandedArrayViewModel
for commanded arraysArrayViewModel - Use
for numbers with min/max, NOTRangedCommandedNumberViewModelCommandedNumberViewModel - Always add type hints for labelConverter:
(value: number | null | undefined) => string - Return
for null/undefined values in labelConverter'---'
@computed Decorator Usage:
Use
@computed when getter:
- Includes configuration (labelConverter, defaultValue, etc.) - prevents re-running configure
- Derives/computes values from observables
- Filters or transforms data
// ✅ With configuration - use @computed @computed get modeVM(): ICommandedVM<ModeType, IEnumFormatOptions<ModeType>> { const vm = this.createPropertyVM('mode', CommandedEnumViewModel<ModeType>); vm.configure({ labelConverter: ModeTypeLabel, defaultValue: ModeType.DEFAULT }); return vm; } // ✅ Simple forwarding - @computed optional (micro-optimization) get nameVM(): IPropertyVM<string, IStringFormatOptions> { return this.createPropertyVM('name', CommandedStringViewModel); } // ✅ Derived values - @computed required @computed get activeItems(): EntityViewModel[] { return this.items.filter(item => item.isActive); }
Reference: ENTITY_ARCHITECTURE.md - Property ViewModel Patterns
Step 4: Create Label Converters for Enums
For each enum type, create a label converter in
adapters/types.ts:
export const YourEnumTypeLabel: Record<YourEnumType, string> = { [YourEnumType.VALUE1]: 'Display Label 1', [YourEnumType.VALUE2]: 'Display Label 2', };
Reference: ENTITY_ARCHITECTURE.md - Label Converter Creation
Step 5: Implement Required Methods
All entity ViewModels must implement:
- ReturnsgetEntityClassName()ModelClass.class
- Returns the model constructorgetEntityCtr()
Reference: COOKBOOK_PATTERNS_ENHANCED.md - Complete Entity ViewModel
Standard Import Pattern:
// Framework interfaces and types import { IEntityConstructor, IFrameworkServices, IPropertyVM, ICommandedVM, IEnumFormatOptions, IStringFormatOptions, INumberFormatOptions } from '@{{company}}/framework-api'; // Entity ViewModel base and property VMs (from {{sharedLib}}-core) import { EntityViewModel, CommandedStringViewModel, CommandedEnumViewModel, RangedCommandedNumberViewModel, CommandedArrayViewModel } from '@{{company}}/{{sharedLib}}-core'; // MobX decorators import { computed, makeObservable } from 'mobx'; // Model and types import { MyEntity } from '@{{company}}/your-model-package'; import { MyEnumType } from '@{{company}}/your-model-package'; // Label converters (from adapters) import { MyEnumTypeLabel } from '../adapters/types';
Step 6: Add MobX Support
CRITICAL: Call
makeObservable(this) in constructor!
constructor(services: IFrameworkServices) { super(services); makeObservable(this); // REQUIRED for reactivity }
Reference: MOBX_ESSENTIALS.md - Constructor Pattern
Step 7: Update Barrel Exports
Add to
{lib}.core/src/index.ts:
export * from './lib/viewModels/yourEntityViewModel';
Add label converters to
{lib}.core/src/lib/adapters/index.ts:
export * from './types';
Step 8: Create Tests
Generate unit tests following patterns:
- Mock IFrameworkServices
- Test property VM creation
- Test getEntityClassName/getEntityCtr
- Test label converters
Reference: TESTING_GUIDE.md
Common Pitfalls to Avoid
Reference: COMMON_PITFALLS.md
- ❌ Don't use
- UseBaseEntityViewModel
from {{sharedLib}}-coreEntityViewModel - ❌ Don't use
for commanded arrays - UseArrayViewModelCommandedArrayViewModel - ❌ Don't forget
in constructormakeObservable(this) - ❌ Don't use
for constrained numbers - UseCommandedNumberViewModelRangedCommandedNumberViewModel - ❌ Don't hardcode labels - Create label converters in adapters
- ❌ Don't forget type hints on labelConverter functions
- ❌ Don't return 'N/A' for null - Use
'---' - ❌ Don't forget
on property VMs with configuration or derived values@computed
Complete Template
Reference: COOKBOOK_PATTERNS_ENHANCED.md - Complete Entity ViewModel
File Locations
- Entity ViewModels:
{lib}.core/src/lib/viewModels/ - Label Converters:
{lib}.core/src/lib/adapters/types.ts - Tests:
{lib}.core/src/lib/viewModels/__tests__/
Verification Steps
After creation:
- Run
- Should be 0./tools/build-helpers/count-client-errors.sh - Run
- New tests should passnpm test - Verify exports in index.ts
- Check label converters work in UI components
Ask User If Unclear
- Which library to create the VM in ({{projectName}}.core, alpha.core, etc.)
- Whether this is a geographic entity (needs GeoEntityBaseVM)
- Default values for enum properties
- Whether to create abstract base class (if multiple similar entities)