Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.encoreos.io/llms.txt

Use this file to discover all available pages before exploring further.

Version: 1.0.0
Status: 📝 Planned
Last Updated: 2025-12-19
Constitution Reference: Section 1.3 (Integration Patterns)
This document explains how PF-31 (Page Layouts) and PF-32 (Conditional Field Logic) work together, along with their relationship to PF-16 (Custom Field Definitions) and PF-17 (Entity Field Configuration).

Overview

The Platform Foundation provides a layered approach to form and page configuration:
FeaturePF-16PF-17PF-31PF-32
PurposeDefine custom fieldsConfigure field behaviorDefine static layoutsDynamic visibility logic
ScopeField metadataField-level settingsPage structureConditional rules
When AppliedDesign timeConfiguration timeRender timeRuntime

Architecture Overview

Layer Responsibilities

  1. PF-16 (Custom Field Definitions): Defines what fields exist - field types, validation rules, default values
  2. PF-17 (Entity Field Configuration): Configures how fields behave - visibility, editability, simple conditional rules
  3. PF-31 (Page Layouts): Defines static UI structure - sections, ordering, column spans, role-based visibility
  4. PF-32 (Conditional Field Logic): Provides dynamic runtime behavior - compound conditions, cross-field validation, conditional required

Feature Responsibility Matrix

ConcernPF-17PF-31PF-32
Field visibility✅ Basic role-based✅ Per-layout role-based✅ Dynamic conditions
Field orderingdisplay_order✅ Section-based ordering❌ Not responsible
Sectioning✅ Basic section property✅ Full section support❌ Not responsible
Column spans✅ Full support❌ Not responsible
Conditional show/hide✅ Simple (showIf/hideIf)✅ Compound (AND/OR)
Conditional required✅ Full support
Cross-field validation✅ Full support
Role-based layoutsapplicable_roles
Multi-layout per object✅ Named layouts

Integration Patterns

Pattern A: Static Layout + Simple Conditions

Use when: Basic forms with straightforward visibility logic Example:
import { LayoutRenderer } from '@/platform/page-layouts';

<LayoutRenderer
  objectName="hr_employees"
  viewContext="edit_form"
  data={employee}
  onSubmit={handleSubmit}
/>

Pattern B: Static Layout + Advanced Conditions

Use when: Complex forms requiring AND/OR logic, conditional required fields Example:
import { LayoutRenderer } from '@/platform/page-layouts';
import { useConditionalLogic } from '@/platform/conditional-logic';

function AdvancedForm({ employee }) {
  const { evaluateConditions, fieldStates } = useConditionalLogic({
    objectName: 'hr_employees',
    data: employee,
  });

  return (
    <LayoutRenderer
      objectName="hr_employees"
      viewContext="edit_form"
      data={employee}
      fieldStates={fieldStates}
      onFieldChange={(field, value) => {
        evaluateConditions({ ...employee, [field]: value });
      }}
    />
  );
}
Use when: Maximum flexibility needed

Precedence Rules

When multiple systems configure the same field, the following precedence applies:

Visibility Precedence (Highest to Lowest)

  1. PF-32 Dynamic Condition → If a V2 rule evaluates to “hide”, field is hidden
  2. PF-31 Role-Based Visibility → If layout excludes field for role, field is hidden
  3. PF-17 Simple ConditionshowIf/hideIf evaluated
  4. PF-17 Base Visibilityis_visible setting

Required Precedence (Highest to Lowest)

  1. PF-32 Conditional Required → Dynamic required based on conditions
  2. PF-17 Base Required → Static is_required setting
  3. PF-16 Field Definition → Default required from field definition

Example Scenario

// Field: emergency_contact_phone
// PF-17: is_visible = true, is_required = false
// PF-31: included in layout for role 'staff'
// PF-32: required_if: { emergency_contact_name: { isNotEmpty: true } }

// Result when emergency_contact_name is filled:
// - Visible: true (PF-31 allows, PF-17 allows)
// - Required: true (PF-32 condition met)

// Result when emergency_contact_name is empty:
// - Visible: true (same as above)
// - Required: false (PF-32 condition not met, falls back to PF-17)

Data Flow Diagram


Database Relationship Diagram


Code Examples

Using Platform Imports

// ✅ CORRECT: Import from platform layers
import { LayoutRenderer, usePageLayout } from '@/platform/page-layouts';
import { useConditionalLogic } from '@/platform/conditional-logic';
import { ConfigurableForm } from '@/platform/field-config';

// ❌ WRONG: Direct imports from cores
import { LayoutRenderer } from '@/cores/pf/layouts';

Integrated Form Component

import { usePageLayout } from '@/platform/page-layouts';
import { useConditionalLogic } from '@/platform/conditional-logic';
import { useEntityFieldConfigs } from '@/platform/field-config';

export function IntegratedForm({ 
  objectName, 
  viewContext, 
  data, 
  onSubmit 
}: IntegratedFormProps) {
  const { user } = useCurrentUser();
  
  // PF-31: Get layout structure
  const { layout, sections, fields } = usePageLayout({
    objectName,
    viewContext,
    userRole: user?.role,
  });
  
  // PF-17: Get field configurations
  const { configs } = useEntityFieldConfigs({
    entityType: objectName,
    organizationId: user?.organizationId,
  });
  
  // PF-32: Get conditional logic
  const { fieldStates, evaluateConditions } = useConditionalLogic({
    objectName,
    data,
    configs,
  });
  
  // Merge all configurations
  const mergedFields = useMemo(() => {
    return mergeFieldConfigurations(fields, configs, fieldStates);
  }, [fields, configs, fieldStates]);
  
  return (
    <Form onSubmit={onSubmit}>
      {sections.map(section => (
        <FormSection 
          key={section.id} 
          section={section}
          fields={mergedFields.filter(f => f.sectionId === section.id)}
          data={data}
          onChange={(field, value) => {
            evaluateConditions({ ...data, [field]: value });
          }}
        />
      ))}
    </Form>
  );
}

Conditional Required Field

// PF-32 rule definition (stored in database)
const conditionalRule = {
  field_name: 'emergency_phone',
  rule_type: 'required_if',
  conditions: {
    operator: 'AND',
    conditions: [
      { field: 'has_emergency_contact', operator: 'equals', value: true },
      { field: 'emergency_name', operator: 'isNotEmpty' }
    ]
  }
};

// Runtime evaluation
const isRequired = useConditionalRequired({
  fieldName: 'emergency_phone',
  formData: currentFormData,
  rules: conditionalRules,
});

<FormField
  name="emergency_phone"
  required={isRequired}
  label={isRequired ? 'Emergency Phone *' : 'Emergency Phone'}
/>

Implementation Timeline

PhaseFeatureStatusTarget
PF-31 Phase 1Database schema✅ Complete2025-12-19
PF-31 Phase 2TypeScript types & hooks📝 PlannedWeek 2
PF-31 Phase 3Layout Editor UI📝 PlannedWeek 3
PF-31 Phase 4LayoutRenderer component📝 PlannedWeek 4
PF-31 Phase 5Platform integration layer📝 PlannedWeek 5
PF-31 Phase 6Core module adoption📝 PlannedWeek 6-7
PF-32 Phase 1Conditional rules schema📝 PlannedAfter PF-31
PF-32 Phase 2Condition Builder UI📝 PlannedAfter PF-31
PF-32 Phase 3Runtime evaluator📝 PlannedAfter PF-31
IntegrationFull PF-31 + PF-32 integration📝 PlannedAfter both

Testing Strategy

Unit Tests

describe('LayoutRenderer with Conditional Logic', () => {
  it('should hide field when PF-32 condition evaluates to hide', () => {
    const { queryByTestId } = render(
      <LayoutRenderer
        objectName="hr_employees"
        data={{ employment_type: 'contractor' }}
        fieldStates={{ benefits_enrollment: { visible: false } }}
      />
    );
    expect(queryByTestId('field-benefits_enrollment')).not.toBeInTheDocument();
  });
  
  it('should mark field required when PF-32 condition met', () => {
    const { getByTestId } = render(
      <LayoutRenderer
        objectName="hr_employees"
        data={{ has_emergency_contact: true }}
        fieldStates={{ emergency_phone: { required: true } }}
      />
    );
    expect(getByTestId('field-emergency_phone')).toHaveAttribute('required');
  });
});

Integration Tests

describe('PF-31 + PF-32 Integration', () => {
  it('should load layout and apply conditional rules', async () => {
    // Setup: Create layout and conditional rules in test database
    await createTestLayout('hr_employees', 'edit_form');
    await createConditionalRule('hr_employees', 'emergency_phone', 'required_if');
    
    // Act: Render form with data that triggers condition
    const { getByTestId } = render(
      <IntegratedForm 
        objectName="hr_employees" 
        viewContext="edit_form"
        data={{ has_emergency_contact: true, emergency_name: 'John' }}
      />
    );
    
    // Assert: Field should be required
    await waitFor(() => {
      expect(getByTestId('field-emergency_phone')).toHaveAttribute('required');
    });
  });
});

RLS Tests

describe('Layout RLS', () => {
  it('should only return layouts for user organization', async () => {
    const { data } = await supabase
      .from('pf_page_layouts')
      .select('*');
    
    data.forEach(layout => {
      expect(layout.organization_id).toBe(testOrganizationId);
    });
  });
});


Next Review: After PF-31 Phase 2 completion