EnterpriseDetailBuilder::make('operation_run', 'workspace-context') ->build()) ->toThrow(LogicException::class, 'Enterprise detail pages require a summary header.'); }); it('filters invisible and empty sections before returning the page payload', function (): void { $page = EnterpriseDetailBuilder::make('operation_run', 'workspace-context') ->header(new SummaryHeaderData( title: 'Policy sync', statusBadges: [['label' => 'Completed', 'color' => 'gray']], keyFacts: [['label' => 'Run', 'value' => '#44']], primaryActions: [ new PageActionData(label: 'View run', url: '/admin/operations/44'), new PageActionData(label: 'Hidden', url: '/admin/operations/hidden', visible: false), ], )) ->addSection( new DetailSectionData( id: 'counts', kind: 'current_status', title: 'Counts', items: [['label' => 'Processed', 'value' => '10']], ), new DetailSectionData( id: 'hidden', kind: 'domain_detail', title: 'Hidden', items: [['label' => 'Ignored', 'value' => '1']], visible: false, ), new DetailSectionData( id: 'empty', kind: 'domain_detail', title: 'Empty', ), ) ->addSupportingCard( new SupportingCardData( kind: 'timestamps', title: 'Timing', items: [['label' => 'Completed', 'value' => '2026-03-10 09:00']], ), new SupportingCardData( kind: 'empty', title: 'Ignored', ), ) ->addTechnicalSection( new TechnicalDetailData( title: 'Context', entries: [['label' => 'Hash', 'value' => 'abc123']], ), new TechnicalDetailData( title: 'Skipped', visible: false, entries: [['label' => 'Ignored', 'value' => '1']], ), ) ->build() ->toArray(); expect($page['header']['primaryActions'])->toHaveCount(1) ->and($page['mainSections'])->toHaveCount(1) ->and($page['mainSections'][0]['title'])->toBe('Counts') ->and($page['supportingCards'])->toHaveCount(1) ->and($page['supportingCards'][0]['title'])->toBe('Timing') ->and($page['technicalSections'])->toHaveCount(1) ->and($page['technicalSections'][0]['title'])->toBe('Context'); });