$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')) { $pattern = "(?s)\\.$ClassName\\s*\\{[^}]*\\b$property\\s*:" if ($Content -match $pattern) { throw "G03 must not construct .$ClassName with CSS $property" } } } $root = Split-Path -Parent $PSScriptRoot $pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json $paths = @($pages.pages.path) $g03 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8 $g04 = Join-Path $root 'pages/genealogy/g04-first-ancestor.vue' $panelPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g03-create-flow-panel.png' if (-not ($paths -contains 'pages/genealogy/g03-create-genealogy')) { throw 'G03 route missing' } if ($paths -contains 'pages/genealogy/g04-first-ancestor') { throw 'G04 route must be removed; first-ancestor is a G03 state' } if (Test-Path -LiteralPath $g04) { throw 'G04 page file must be deleted; first-ancestor is a G03 state' } foreach ($required in @( 'const currentStep = ref("create");', 'query.get("step") === "ancestor"', 'window.addEventListener("hashchange", syncFlowFromRoute)', 'step=ancestor&genealogyId=', 'const createState = ref("form");', 'const ancestorState = ref("form");', 'const fieldErrors = reactive({', 'const duplicateReminderVisible = ref(false);', 'class="duplicate-reminder-layer"', 'if (isSubmitting.value) return;', '/pages/genealogy/g05-genealogy-overview?genealogyId=', 'g03-create-flow-panel.png', 'root-header-cinnabar.jpg', 'a01-primary-button.png', 'create-flow-panel', 'flow-primary-action' )) { Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required" } foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=')) { if ($g03 -match [regex]::Escape($forbidden)) { throw "G03 retains forbidden implementation: $forbidden" } } foreach ($className in @('create-flow-panel', 'flow-primary-action')) { Assert-NoCssSurface -Content $g03 -ClassName $className } if (-not (Test-Path -LiteralPath $panelPath)) { throw 'G03 requires a final opaque flow-panel bitmap' } Add-Type -AssemblyName System.Drawing $panel = [System.Drawing.Bitmap]::FromFile($panelPath) try { if ($panel.Width -ne 1122 -or $panel.Height -ne 1500) { throw 'G03 flow-panel asset must be 1122 x 1500px' } $corners = @( $panel.GetPixel(0, 0).A, $panel.GetPixel(($panel.Width - 1), 0).A, $panel.GetPixel(0, ($panel.Height - 1)).A, $panel.GetPixel(($panel.Width - 1), ($panel.Height - 1)).A ) if (($corners | Where-Object { $_ -ne 255 }).Count -ne 0) { throw 'G03 flow-panel outer corners must be opaque' } } finally { $panel.Dispose() } Write-Output 'G03-CREATE-FLOW-CONTRACT PASS'