Vyuh CDX
Configuration

Entity Metadata

Complete guide to entity metadata configuration for display, navigation, and behavior

Entity metadata provides descriptive information about an entity type, controlling how it appears and behaves throughout the system. This guide covers all aspects of metadata configuration, including localization, routing, categorization, and verification behaviors.

Overview

The EntityMetadata class is the cornerstone of entity presentation and behavior configuration:

class EntityMetadata {
  // Core identification
  final String identifier;

  // Display configuration
  final String name;              // Singular form
  final String pluralName;        // Plural form
  final String? description;
  final IconData? icon;
  final Color? themeColor;

  // Localization support (optional)
  final String Function()? nameResolver;
  final String Function()? pluralNameResolver;
  final String Function()? descriptionResolver;

  // Navigation & organization
  final EntityRouteBuilder route;
  final EntityCategory category;
  final MenuCategory? menuCategory;
  final int priority;
  final bool visible;

  // Behavior
  final bool isSingleton;

  // Verification requirements
  final ActionBehavior verifyDraft;
  final ActionBehavior verifyCreate;
  final ActionBehavior verifyUpdate;
  final ActionBehavior verifyStatusChange;

  // Help system
  final Future<EntityHelpModel?> Function()? getHelp;
}

Core Identification

Identifier

The unique key for your entity type used in permissions, routing, and system-wide references:

EntityMetadata(
  identifier: 'reference_standards',  // URL-safe, lowercase, plural
  // ...
)

Best Practices:

  • Use lowercase letters and underscores only
  • Use plural form (e.g., 'users', not 'user')
  • Keep consistent with API endpoints
  • Never change after deployment (breaks permissions and references)

Examples:

identifier: 'equipment'              // ✓ Good
identifier: 'reference_standards'    // ✓ Good
identifier: 'user_groups'            // ✓ Good

identifier: 'Equipment'              // ✗ Bad (uppercase)
identifier: 'user'                   // ✗ Bad (singular)
identifier: 'reference-standards'    // ✗ Bad (hyphen instead of underscore)

Display Configuration

Names and Descriptions

Control how your entity appears in the UI:

EntityMetadata(
  identifier: 'reference_standards',
  name: 'Reference Standard',          // Singular form
  pluralName: 'Reference Standards',    // Plural form
  description: 'Chemical and biological reference materials used for calibration',
  // ...
)

These values are used throughout the system:

  • Page titles - "Reference Standard Details", "Create Reference Standard"
  • Navigation menus - "Reference Standards"
  • Breadcrumbs - "Home / Reference Standards / RS-001"
  • Search results - "Found 5 Reference Standards"
  • Error messages - "Failed to load Reference Standard"
  • Confirmation dialogs - "Delete Reference Standard?"

Localization Support

For internationalization, provide optional resolver functions that return locale-specific values:

EntityMetadata(
  // Fallback values (English)
  name: 'Area',
  pluralName: 'Areas',
  description: 'Physical work areas and locations',

  // Localization resolvers (called dynamically based on current locale)
  nameResolver: () => t.strings.elog.entities.area.singular,
  pluralNameResolver: () => t.strings.elog.entities.area.plural,
  descriptionResolver: () => t.strings.elog.entities.area.description,
  // ...
)

How it works:

  • The name, pluralName, and description getters automatically call resolvers when provided
  • Falls back to static values when resolvers are null
  • Resolvers are called each time the property is accessed, allowing dynamic locale switching
  • No code generation required - pure runtime resolution

Example with fast_i18n:

// translations.i18n.yaml structure:
// elog:
//   entities:
//     equipment:
//       singular: "Equipment"
//       plural: "Equipment"
//       description: "Manufacturing equipment and instruments"

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',  // Fallback
  pluralName: 'Equipment',  // Fallback
  nameResolver: () => t.elog.entities.equipment.singular,
  pluralNameResolver: () => t.elog.entities.equipment.plural,
  descriptionResolver: () => t.elog.entities.equipment.description,
  // ...
)

Visual Identity

Icons and Colors

Define the visual appearance of your entity:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  icon: Icons.precision_manufacturing,    // Material icon
  themeColor: Colors.blue,                // Primary color
  // ...
)

Icon Usage:

  • Navigation menu items
  • Page headers and app bars
  • Entity cards in grid views
  • Command palette entries
  • Notification badges

Theme Color Usage:

  • App bar backgrounds on entity pages
  • Action button colors (create, edit)
  • Selection highlights in lists
  • Chart visualizations in analytics
  • Status indicators

Color Selection Tips:

// Use semantic colors
icon: Icons.security,
themeColor: Colors.red,  // Security-related entities

icon: Icons.people,
themeColor: Colors.indigo,  // User management

icon: Icons.inventory,
themeColor: Colors.green,  // Inventory items

icon: Icons.science,
themeColor: Colors.purple,  // Laboratory entities

EntityRouteBuilder

The EntityRouteBuilder manages all entity routes with two factory constructors:

Collection Entities (Multiple Instances)

For entities with multiple instances (users, products, etc.):

EntityMetadata(
  identifier: 'users',
  name: 'User',
  pluralName: 'Users',
  route: EntityRouteBuilder.collection('users'),
  // ...
)

Generated Routes:

/entities/users                  # List view
/entities/users/new              # Create form
/entities/users/{id}             # Detail view
/entities/users/{id}/edit        # Edit form
/entities/users/dashboard        # Dashboard (if configured)

Custom Prefix:

route: EntityRouteBuilder.collection(
  'equipment',
  prefix: '/elog',  // Custom prefix instead of default '/entities'
)

// Routes:
// /elog/equipment
// /elog/equipment/new
// /elog/equipment/{id}
// /elog/equipment/{id}/edit

Singleton Entities (Single Instance)

For entities with only one instance (Settings, Company Profile, etc.):

EntityMetadata(
  identifier: 'settings',
  name: 'Settings',
  pluralName: 'Settings',
  isSingleton: true,  // Important: mark as singleton
  route: EntityRouteBuilder.singleton('settings'),
  // ...
)

Generated Routes:

/entities/settings               # View/list (shows the single instance)
/entities/settings/edit          # Edit form (no ID needed)
/entities/settings/dashboard     # Dashboard (if configured)

Note: For singletons:

  • No {id} parameter in routes
  • Create operation is the same as edit
  • List view shows the single instance
  • No "New" button in the UI

Custom Route Builder

For advanced routing needs, create a custom builder:

route: EntityRouteBuilder(
  list: () => '/custom/path/list',
  view: (id) => '/custom/path/$id/details',
  edit: (id) => '/custom/path/$id/modify',
  create: () => '/custom/path/add',
  dashboard: () => '/custom/dashboard',
)

Organization and Categorization

Entity Categories

Group related entities using EntityCategory:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  category: EntityCategory.elog,  // Or EntityCategory.sso, EntityCategory.general
  // ...
)

Available Categories:

  • EntityCategory.general - General purpose entities
  • EntityCategory.sso - Authentication and authorization (users, roles, companies)
  • EntityCategory.elog - Electronic logbook entities

Categories control:

  • Navigation drawer sections
  • Permission grouping
  • Dashboard organization
  • Command palette grouping

Further organize entities within a category using MenuCategory:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  category: EntityCategory.elog,
  menuCategory: MenuCategory.masterData,  // Optional subcategory
  // ...
)

Hierarchical Organization:

Electronic Logbook (EntityCategory.elog)
├── Master Data (MenuCategory.masterData)
│   ├── Equipment
│   ├── Areas
│   └── Reference Standards
├── Operations (MenuCategory.operations)
│   ├── Log Templates
│   └── Checklists
└── Reports (MenuCategory.reports)
    └── Audit Trails

When to use Menu Categories:

  • When you have many entities in one category
  • To create logical groupings for users
  • To reduce cognitive load in navigation
  • To match user mental models

When NOT to use:

  • When you have only a few entities in a category
  • When entities don't naturally group
  • When it would create too many single-item groups

Priority and Visibility

Control display order and menu visibility:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  priority: 10,      // Lower numbers appear first (default: 999)
  visible: true,     // Show in navigation menu (default: true)
  // ...
)

Priority Examples:

// High priority (shown first)
EntityMetadata(
  identifier: 'areas',
  priority: 1,  // Appears at top
  // ...
)

// Medium priority
EntityMetadata(
  identifier: 'equipment',
  priority: 50,
  // ...
)

// Low priority (shown last)
EntityMetadata(
  identifier: 'audit_logs',
  priority: 999,  // Default value
  // ...
)

Visibility Control:

// Hidden from navigation (accessible via direct routes only)
EntityMetadata(
  identifier: 'system_config',
  visible: false,  // Not shown in menu
  // ...
)

// Use cases for hidden entities:
// - Internal configuration entities
// - Entities only accessible through other entities
// - System-managed entities
// - Deprecated entities still needed for data migration

Sorting Rules:

  1. Entities sorted by priority (ascending)
  2. Entities with same priority sorted alphabetically by name
  3. Hidden entities (visible: false) excluded from menus entirely

Verification Behaviors

Control when verification (e-signature, approval) is required for operations:

enum ActionBehavior {
  no,     // Never require verification
  auto,   // Automatically require based on configuration
  yes,    // Always require verification
}

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  verifyDraft: ActionBehavior.no,            // Don't verify drafts
  verifyCreate: ActionBehavior.auto,         // Auto-verify creates
  verifyUpdate: ActionBehavior.auto,         // Auto-verify updates
  verifyStatusChange: ActionBehavior.auto,   // Auto-verify status changes
  // ...
)

Verification Types:

  1. Draft Operations (verifyDraft)

    • Saving incomplete forms
    • Usually ActionBehavior.no (no verification needed)
  2. Create Operations (verifyCreate)

    • Creating new entities
    • Usually ActionBehavior.auto (verify if configured)
  3. Update Operations (verifyUpdate)

    • Modifying existing entities
    • Usually ActionBehavior.auto (verify if configured)
  4. Status Changes (verifyStatusChange)

    • Archiving, activating, deleting
    • Usually ActionBehavior.auto (verify if configured)

Examples by Entity Type:

// Critical manufacturing entity - always verify
EntityMetadata(
  identifier: 'batch_records',
  verifyCreate: ActionBehavior.yes,   // Always verify
  verifyUpdate: ActionBehavior.yes,   // Always verify
  verifyStatusChange: ActionBehavior.yes,  // Always verify
  // ...
)

// Configuration entity - selective verification
EntityMetadata(
  identifier: 'system_settings',
  verifyCreate: ActionBehavior.no,    // No verification
  verifyUpdate: ActionBehavior.auto,  // Verify if configured
  verifyStatusChange: ActionBehavior.yes,  // Always verify deletion
  // ...
)

// Reference data - minimal verification
EntityMetadata(
  identifier: 'reference_standards',
  verifyCreate: ActionBehavior.auto,
  verifyUpdate: ActionBehavior.auto,
  verifyStatusChange: ActionBehavior.auto,
  // ...
)

Help System Integration

Provide contextual help for your entities:

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  getHelp: () async {
    // Load from remote CMS
    return await HelpService.loadHelp('equipment');

    // Or return inline help
    return EntityHelpModel(
      content: '''
## Equipment Management

Equipment entities represent manufacturing equipment and instruments.

### Key Concepts
- **Serial Number**: Unique manufacturer identifier
- **Calibration**: Regular calibration required for GMP compliance
- **Status**: Track equipment availability and maintenance

### Common Tasks
1. Register new equipment with serial number
2. Schedule calibration activities
3. Record maintenance history
4. Generate equipment reports
      ''',
      lastUpdated: DateTime.now(),
    );
  },
  // ...
)

Help Display Context:

  • Accessible from page headers via help icon
  • Shown in side panel or modal
  • Supports markdown formatting
  • Can include links to external documentation
  • Searchable through command palette

Help Content Best Practices:

  • Start with overview of entity purpose
  • Explain key fields and their meaning
  • Provide common task walkthroughs
  • Include examples and edge cases
  • Keep content concise and scannable

Complete Configuration Examples

Standard Entity (Collection)

EntityMetadata(
  identifier: 'equipment',
  name: 'Equipment',
  pluralName: 'Equipment',
  description: 'Manufacturing equipment and instruments',
  icon: Icons.precision_manufacturing,
  themeColor: Colors.blue,
  category: EntityCategory.elog,
  menuCategory: MenuCategory.masterData,
  priority: 10,
  visible: true,
  isSingleton: false,
  route: EntityRouteBuilder.collection('equipment'),
  verifyDraft: ActionBehavior.no,
  verifyCreate: ActionBehavior.auto,
  verifyUpdate: ActionBehavior.auto,
  verifyStatusChange: ActionBehavior.auto,
  getHelp: () async => await HelpService.loadHelp('equipment'),
)

Localized Entity

EntityMetadata(
  identifier: 'areas',
  name: 'Area',  // Fallback
  pluralName: 'Areas',  // Fallback
  description: 'Physical work areas',  // Fallback
  nameResolver: () => t.elog.entities.areas.singular,
  pluralNameResolver: () => t.elog.entities.areas.plural,
  descriptionResolver: () => t.elog.entities.areas.description,
  icon: Icons.location_on,
  themeColor: Colors.green,
  category: EntityCategory.elog,
  menuCategory: MenuCategory.masterData,
  priority: 5,
  route: EntityRouteBuilder.collection('areas'),
  verifyCreate: ActionBehavior.auto,
  verifyUpdate: ActionBehavior.auto,
  verifyStatusChange: ActionBehavior.auto,
)

Singleton Entity

EntityMetadata(
  identifier: 'settings',
  name: 'Settings',
  pluralName: 'Settings',
  description: 'Application settings and configuration',
  icon: Icons.settings,
  themeColor: Colors.grey,
  category: EntityCategory.general,
  priority: 999,  // Show last in menu
  visible: true,
  isSingleton: true,  // Important: mark as singleton
  route: EntityRouteBuilder.singleton('settings'),
  verifyUpdate: ActionBehavior.yes,  // Always verify settings changes
  verifyStatusChange: ActionBehavior.yes,
  getHelp: () async => EntityHelpModel(
    content: '''
## Application Settings

Configure global application behavior and preferences.

**Warning**: Changes to settings affect all users.
    ''',
  ),
)

Hidden Configuration Entity

EntityMetadata(
  identifier: 'system_config',
  name: 'System Configuration',
  pluralName: 'System Configurations',
  description: 'Internal system configuration (admin only)',
  icon: Icons.admin_panel_settings,
  themeColor: Colors.red,
  category: EntityCategory.general,
  priority: 1000,
  visible: false,  // Hidden from navigation
  isSingleton: true,
  route: EntityRouteBuilder.singleton('system-config'),
  verifyCreate: ActionBehavior.yes,
  verifyUpdate: ActionBehavior.yes,
  verifyStatusChange: ActionBehavior.yes,
)

Dynamic Metadata

Metadata can be dynamic based on user context:

EntityMetadata createUserMetadata(BuildContext context) {
  final userRole = context.read<AuthService>().currentUser?.role;
  final isAdmin = userRole == 'admin';

  return EntityMetadata(
    identifier: 'users',
    name: 'User',
    pluralName: 'Users',
    icon: Icons.person,
    themeColor: isAdmin ? Colors.red : Colors.indigo,  // Different for admin
    category: EntityCategory.sso,
    menuCategory: isAdmin ? MenuCategory.administration : null,
    priority: isAdmin ? 1 : 50,  // Higher priority for admins
    route: isAdmin
        ? EntityRouteBuilder.collection('users', prefix: '/admin')
        : EntityRouteBuilder.collection('users'),
    verifyUpdate: isAdmin
        ? ActionBehavior.yes  // Admins always verify
        : ActionBehavior.auto,
    getHelp: () async {
      return isAdmin
          ? await loadAdminHelp()
          : await loadUserHelp();
    },
  );
}

Best Practices

Naming Conventions

  1. Identifiers

    • Use plural form: 'users', 'equipment'
    • Lowercase with underscores: 'reference_standards'
    • Match database table names when possible
  2. Display Names

    • Use proper capitalization: 'Reference Standard'
    • Keep concise (1-3 words)
    • Match user terminology, not technical jargon
  3. Descriptions

    • One sentence overview of entity purpose
    • Focus on "what" not "how"
    • Avoid technical implementation details

Organization

  1. Categories

    • Use sparingly - only major functional areas
    • Keep category count under 5 for good UX
    • Match user mental models, not code structure
  2. Menu Categories

    • Create only when you have 5+ entities in a category
    • Use user-facing terminology
    • Avoid single-item categories
  3. Priority

    • Reserve 1-10 for most important entities
    • Use 50-100 for standard entities
    • Use 900+ for administrative/rarely used entities

Verification

  1. Default Strategy

    verifyDraft: ActionBehavior.no,           // Never verify drafts
    verifyCreate: ActionBehavior.auto,        // Auto based on config
    verifyUpdate: ActionBehavior.auto,        // Auto based on config
    verifyStatusChange: ActionBehavior.auto,  // Auto based on config
  2. Critical Entities

    • Use ActionBehavior.yes for GxP-critical operations
    • Always verify status changes that affect data integrity
    • Consider regulatory requirements
  3. Configuration Entities

    • May use ActionBehavior.no for drafts and creates
    • Always verify deletes: verifyStatusChange: ActionBehavior.yes

Help Content

  1. Structure

    • Start with purpose/overview
    • Explain key concepts and fields
    • Provide step-by-step common tasks
    • Include troubleshooting tips
  2. Maintenance

    • Use remote CMS for easy updates
    • Version control help content
    • Keep synchronized with features
    • Regular review and updates
  3. Localization

    • Provide help in user's language
    • Use same i18n system as metadata
    • Consider cultural context in examples

Testing Metadata

test('equipment metadata is correctly configured', () {
  final metadata = EquipmentConfig.instance.metadata;

  expect(metadata.identifier, 'equipment');
  expect(metadata.name, 'Equipment');
  expect(metadata.pluralName, 'Equipment');
  expect(metadata.icon, Icons.precision_manufacturing);
  expect(metadata.category, EntityCategory.elog);
  expect(metadata.priority, 10);
  expect(metadata.visible, true);
  expect(metadata.isSingleton, false);
});

test('singleton route builder generates correct paths', () {
  final route = EntityRouteBuilder.singleton('settings');

  expect(route.view('any-id'), '/entities/settings');  // No ID in path
  expect(route.edit('any-id'), '/entities/settings/edit');
  expect(route.list(), '/entities/settings');
  expect(route.create(), '/entities/settings/edit');  // Same as edit
});

test('localization resolvers work correctly', () {
  // Set locale to Spanish
  LocaleSettings.setLocale(AppLocale.es);

  final metadata = EntityMetadata(
    identifier: 'equipment',
    name: 'Equipment',  // Fallback
    nameResolver: () => t.entities.equipment.singular,
    // ...
  );

  expect(metadata.name, 'Equipo');  // Spanish translation

  // Switch to English
  LocaleSettings.setLocale(AppLocale.en);
  expect(metadata.name, 'Equipment');  // English translation
});

Next: Entity API - Deep dive into entity API architecture and data operations