$ErrorActionPreference = 'Stop' function Assert-Contains { param([string]$Content, [string]$Expected, [string]$Message) if ($Content -notmatch [regex]::Escape($Expected)) { throw $Message } } function Assert-NoCssSurface { param([string]$Content, [string]$ClassName) foreach ($property in @('border', 'background', 'border-radius')) { if ($Content -match "(?s)\\.$ClassName\\s*\\{[^}]*\\b$property\\s*:") { throw "Application flow must not construct .$ClassName with CSS $property" } } } $root = Split-Path -Parent $PSScriptRoot $g08 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g08-join-application.vue') -Raw -Encoding utf8 $g09 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g09-my-applications.vue') -Raw -Encoding utf8 $g10 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g10-application-review.vue') -Raw -Encoding utf8 $catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8 foreach ($page in @($g08, $g09, $g10)) { if ($page -match '', 'const fieldErrors = reactive({', '/pages/genealogy/g09-my-applications', '/pages/genealogy/g01-my-genealogies?genealogyId=' )) { Assert-Contains $g08 $required "Missing G08 join contract: $required" } foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) { if ($g08 -match [regex]::Escape($nativeUi)) { throw "G08 must not use native UniApp UI: $nativeUi" } } foreach ($required in @( "const applicationState = ref('loading')", 'application-state--list', 'application-state--empty', 'application-state--error', 'application-status-card.png', "'PENDING'", "'APPROVED'", "'REJECTED'", "'WITHDRAWN'", 'const applicationSamples', 'const actionFor = (item) =>', 'const withdrawTarget = ref(null)', 'class="withdraw-layer"', '/pages/genealogy/g08-join-application?source=search&previous=rejected&genealogyId=' )) { Assert-Contains $g09 $required "Missing G09 application contract: $required" } foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) { if ($g09 -match [regex]::Escape($nativeUi)) { throw "G09 must not use native UniApp UI: $nativeUi" } } foreach ($required in @( "const reviewState = ref('loading')", 'review-state--list', 'review-state--empty', 'review-state--error', 'application-record-card.png', 'application-status-card.png', 'a01-primary-button.png', 'a01-secondary-button.png', 'const reviewSamples', 'const confirmation = ref(null)', 'const helpVisible = ref(false)', 'class="review-dialog-layer"', 'review-state--no-permission' )) { Assert-Contains $g10 $required "Missing G10 review contract: $required" } foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) { if ($g10 -match [regex]::Escape($nativeUi)) { throw "G10 must not use native UniApp UI: $nativeUi" } } foreach ($className in @('join-panel', 'join-action', 'application-card', 'review-action')) { foreach ($page in @($g08, $g09, $g10)) { Assert-NoCssSurface $page $className } } foreach ($entry in @('g07:', 'g08:', 'g09:')) { if ($catalog -match [regex]::Escape($entry)) { throw "Page catalog must remove migrated generic entry: $entry" } } $asset = Join-Path $root 'static/assets/modules/genealogy/opaque/application-record-card.png' if (-not (Test-Path -LiteralPath $asset)) { throw 'G08-G10 require application-record-card.png' } $statusAsset = Join-Path $root 'static/assets/modules/genealogy/opaque/application-status-card.png' if (-not (Test-Path -LiteralPath $statusAsset)) { throw 'G09 and application empty states require application-status-card.png' } Write-Output 'G08-G10-APPLICATION-FLOW-CONTRACT PASS'