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:
| Feature | PF-16 | PF-17 | PF-31 | PF-32 |
|---|
| Purpose | Define custom fields | Configure field behavior | Define static layouts | Dynamic visibility logic |
| Scope | Field metadata | Field-level settings | Page structure | Conditional rules |
| When Applied | Design time | Configuration time | Render time | Runtime |
Architecture Overview
Layer Responsibilities
- PF-16 (Custom Field Definitions): Defines what fields exist - field types, validation rules, default values
- PF-17 (Entity Field Configuration): Configures how fields behave - visibility, editability, simple conditional rules
- PF-31 (Page Layouts): Defines static UI structure - sections, ordering, column spans, role-based visibility
- PF-32 (Conditional Field Logic): Provides dynamic runtime behavior - compound conditions, cross-field validation, conditional required
Feature Responsibility Matrix
| Concern | PF-17 | PF-31 | PF-32 |
|---|
| Field visibility | ✅ Basic role-based | ✅ Per-layout role-based | ✅ Dynamic conditions |
| Field ordering | ✅ display_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 layouts | ❌ | ✅ applicable_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 });
}}
/>
);
}
Pattern C: Full Integration (Recommended)
Use when: Maximum flexibility needed
Precedence Rules
When multiple systems configure the same field, the following precedence applies:
Visibility Precedence (Highest to Lowest)
- PF-32 Dynamic Condition → If a V2 rule evaluates to “hide”, field is hidden
- PF-31 Role-Based Visibility → If layout excludes field for role, field is hidden
- PF-17 Simple Condition →
showIf/hideIf evaluated
- PF-17 Base Visibility →
is_visible setting
Required Precedence (Highest to Lowest)
- PF-32 Conditional Required → Dynamic required based on conditions
- PF-17 Base Required → Static
is_required setting
- 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
// ✅ 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';
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
| Phase | Feature | Status | Target |
|---|
| PF-31 Phase 1 | Database schema | ✅ Complete | 2025-12-19 |
| PF-31 Phase 2 | TypeScript types & hooks | 📝 Planned | Week 2 |
| PF-31 Phase 3 | Layout Editor UI | 📝 Planned | Week 3 |
| PF-31 Phase 4 | LayoutRenderer component | 📝 Planned | Week 4 |
| PF-31 Phase 5 | Platform integration layer | 📝 Planned | Week 5 |
| PF-31 Phase 6 | Core module adoption | 📝 Planned | Week 6-7 |
| PF-32 Phase 1 | Conditional rules schema | 📝 Planned | After PF-31 |
| PF-32 Phase 2 | Condition Builder UI | 📝 Planned | After PF-31 |
| PF-32 Phase 3 | Runtime evaluator | 📝 Planned | After PF-31 |
| Integration | Full PF-31 + PF-32 integration | 📝 Planned | After 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);
});
});
});
-
Specs:
-
Implementation:
-
Architecture:
Next Review: After PF-31 Phase 2 completion