$ErrorActionPreference = 'Stop' function Assert-Contains { param([string]$Content, [string]$Expected, [string]$Message) if ($Content -notmatch [regex]::Escape($Expected)) { throw $Message } } $root = Split-Path -Parent $PSScriptRoot $paths = @( 'pages/tree/t03-member-profile.vue', 'pages/tree/t04-add-relative.vue', 'pages/tree/t05-edit-member.vue', 'pages/tree/t06-edit-relationship.vue', 'pages/tree/t07-member-directory.vue', 'pages/tree/t08-member-states.vue' ) $pages = @{} foreach ($path in $paths) { $content = Get-Content -LiteralPath (Join-Path $root $path) -Raw -Encoding utf8 if ($content -match ')') { throw "$path must not retain ModulePage" } if ($content -match "@/utils/api\.js|\bappApi\b") { throw "$path must not connect the API layer" } $pages[$path] = $content } foreach ($path in @('pages/tree/t03-member-profile.vue', 'pages/tree/t07-member-directory.vue', 'pages/tree/t08-member-states.vue')) { Assert-Contains $pages[$path] 'ModulePageBackground' "$path must use the tree module background" } $t03 = $pages['pages/tree/t03-member-profile.vue'] foreach ($required in @('member-state--detail', 'member-state--error', 'g03-create-flow-panel.png', 'g06-search-input-wide.png', '/pages/tree/t05-edit-member', 'query.personId')) { Assert-Contains $t03 $required "Missing T03 contract: $required" } $formComponentPath = Join-Path $root 'components/tree/TreeMemberForm.vue' if (-not (Test-Path -LiteralPath $formComponentPath)) { throw 'T04-T06 require shared TreeMemberForm component' } $form = Get-Content -LiteralPath $formComponentPath -Raw -Encoding utf8 if ($form -match "@/utils/api\.js|\bappApi\b") { throw 'TreeMemberForm must not connect the API layer' } foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) { if ($form -match [regex]::Escape($nativeUi)) { throw "TreeMemberForm must use project-owned feedback instead of $nativeUi" } } foreach ($required in @('form-state--form', 'form-state--success', 'form-state--conflict', 'form-state--error', 'g03-create-flow-panel.png', 'g06-search-input-wide.png', 'a01-scroll-primary-v3.png', 'a01-scroll-secondary-v3.png', 'AppDialog', 'ModulePageBackground')) { Assert-Contains $form $required "Missing shared form contract: $required" } foreach ($route in @('t04-add-relative.vue', 't05-edit-member.vue', 't06-edit-relationship.vue')) { Assert-Contains $pages["pages/tree/$route"] 'TreeMemberForm' "$route must use TreeMemberForm" } $t07 = $pages['pages/tree/t07-member-directory.vue'] foreach ($required in @('directory-state--list', 'directory-state--empty', 'directory-state--error', 'application-status-card.png', 'g06-search-input-wide.png', '/pages/tree/t03-member-profile')) { Assert-Contains $t07 $required "Missing T07 contract: $required" } $t08 = $pages['pages/tree/t08-member-states.vue'] foreach ($required in @('member-status--privacy', 'member-status--deceased', 'member-status--forbidden', 'application-status-card.png', 'query.state')) { Assert-Contains $t08 $required "Missing T08 contract: $required" } $catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8 foreach ($entry in @('t04:', 't05:', 't06:', 't07:', 't08:')) { if ($catalog -match [regex]::Escape($entry)) { throw "Page catalog must remove migrated entry: $entry" } } Write-Output 'T03-T08-MEMBER-FLOW-CONTRACT PASS'