Agent skill

Create Entity ViewModel

Create a new entity ViewModel with property ViewModels following Phoenix 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.

Stars 163
Forks 31

Install this agent skill to your Project

npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/testing/create-entity-viewmodel-biggs3d-tools-b559a289

SKILL.md

Create Entity ViewModel

This skill scaffolds a complete entity ViewModel following Phoenix 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 /model/*.model/src/ and be generated
  • Run npm run generate-model if model was just created
  • 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 CommandedArrayViewModel, NOT ArrayViewModel for commanded arrays
  • Use RangedCommandedNumberViewModel for numbers with min/max, NOT CommandedNumberViewModel
  • 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
typescript
// ✅ 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:

typescript
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:

  • getEntityClassName() - Returns ModelClass.class
  • getEntityCtr() - Returns the model constructor

Reference: COOKBOOK_PATTERNS_ENHANCED.md - Complete Entity ViewModel

Standard Import Pattern:

typescript
// Framework interfaces and types
import { IEntityConstructor, IFrameworkServices, IPropertyVM, ICommandedVM, IEnumFormatOptions, IStringFormatOptions, INumberFormatOptions } from '@tektonux/framework-api';

// Entity ViewModel base and property VMs (from phoenix-core)
import { EntityViewModel, CommandedStringViewModel, CommandedEnumViewModel, RangedCommandedNumberViewModel, CommandedArrayViewModel } from '@tektonux/phoenix-core';

// MobX decorators
import { computed, makeObservable } from 'mobx';

// Model and types
import { MyEntity } from '@tektonux/your-model-package';
import { MyEnumType } from '@tektonux/your-model-package';

// Label converters (from adapters)
import { MyEnumTypeLabel } from '../adapters/types';

Step 6: Add MobX Support

CRITICAL: Call makeObservable(this) in constructor!

typescript
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:

typescript
export * from './lib/viewModels/yourEntityViewModel';

Add label converters to {lib}.core/src/lib/adapters/index.ts:

typescript
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

  1. ❌ Don't use BaseEntityViewModel - Use EntityViewModel from phoenix-core
  2. ❌ Don't use ArrayViewModel for commanded arrays - Use CommandedArrayViewModel
  3. ❌ Don't forget makeObservable(this) in constructor
  4. ❌ Don't use CommandedNumberViewModel for constrained numbers - Use RangedCommandedNumberViewModel
  5. ❌ Don't hardcode labels - Create label converters in adapters
  6. ❌ Don't forget type hints on labelConverter functions
  7. ❌ Don't return 'N/A' for null - Use '---'
  8. ❌ Don't forget @computed on property VMs with configuration or derived values

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:

  1. Run ./tools/build-helpers/count-client-errors.sh - Should be 0
  2. Run npm test - New tests should pass
  3. Verify exports in index.ts
  4. Check label converters work in UI components

Ask User If Unclear

  • Which library to create the VM in (faad.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)

Didn't find tool you were looking for?

Be as detailed as possible for better results