$ErrorActionPreference = 'Stop' Add-Type -AssemblyName System.Drawing $root = Split-Path -Parent $PSScriptRoot $componentsRoot = Join-Path $root 'components' $pagesRoot = Join-Path $root 'pages' function Read-Utf8([string]$Path) { return Get-Content -Raw -Encoding UTF8 $Path } foreach ($component in @('AppButton.vue', 'AppDialog.vue', 'AppToast.vue', 'AppLoading.vue', 'ModulePageBackground.vue')) { $path = Join-Path $componentsRoot $component if (-not (Test-Path -LiteralPath $path)) { throw "Missing global visual component: $component" } } $button = Read-Utf8 (Join-Path $componentsRoot 'AppButton.vue') if ($button -notmatch 'a01-scroll-primary-v3\.png') { throw 'AppButton does not own the approved A-series primary scroll asset.' } if ($button -notmatch 'a01-scroll-secondary-v3\.png') { throw 'AppButton does not own the approved A-series secondary scroll asset.' } $dialog = Read-Utf8 (Join-Path $componentsRoot 'AppDialog.vue') if ($dialog -notmatch 'a01-scroll-dialog-v3\.png') { throw 'AppDialog does not own the approved A-series dialog asset.' } $toast = Read-Utf8 (Join-Path $componentsRoot 'AppToast.vue') if ($toast -notmatch 'a01-scroll-toast-v3\.png') { throw 'AppToast does not own the approved A-series toast asset.' } $header = Read-Utf8 (Join-Path $componentsRoot 'PageHeader.vue') if ($header -notmatch 'chevron-right\.png') { throw 'PageHeader does not use the approved image arrow.' } if ($header -match '\{\{\s*backLabel\s*\}\}') { throw 'PageHeader still renders a text back label.' } if ($header -notmatch 'scaleX\(-1\)') { throw 'PageHeader back arrow is not oriented toward the previous page.' } $activeSources = @( Get-ChildItem -LiteralPath $componentsRoot -Recurse -File -Filter '*.vue' Get-ChildItem -LiteralPath $pagesRoot -Recurse -File -Filter '*.vue' | Where-Object { $_.Name -ne 'a06-auth-status.vue' } ) foreach ($source in $activeSources) { $content = Read-Utf8 $source.FullName foreach ($forbidden in @( 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/static/assets/foundation/opaque/a01-primary-button.png', '/static/assets/foundation/opaque/a01-secondary-button.png' )) { if ($content.Contains($forbidden)) { throw "$($source.FullName) still contains forbidden global UI path: $forbidden" } } } $background = Read-Utf8 (Join-Path $componentsRoot 'ModulePageBackground.vue') $moduleAssets = @{ tree = 'tree-page-background-long.png' family = 'family-page-background-long.png' records = 'records-page-background-long.png' notification = 'notification-page-background-long.png' profile = 'profile-page-background-long.png' } foreach ($entry in $moduleAssets.GetEnumerator()) { if ($background -notmatch [regex]::Escape($entry.Value)) { throw "ModulePageBackground is missing $($entry.Key) mapping." } $asset = Get-ChildItem -LiteralPath (Join-Path $root 'static/assets/modules') -Recurse -File -Filter $entry.Value if (-not $asset) { throw "Missing module background asset: $($entry.Value)" } $image = [System.Drawing.Image]::FromFile($asset.FullName) try { if ($image.Width -ne 1440 -or $image.Height -ne 3600) { throw "$($entry.Value) must be 1440x3600, got $($image.Width)x$($image.Height)." } } finally { $image.Dispose() } } $modulePage = Read-Utf8 (Join-Path $componentsRoot 'ModulePage.vue') foreach ($token in @('