$ErrorActionPreference = 'Stop' $root = Split-Path -Parent $PSScriptRoot function Read-Utf8([string]$relativePath) { return [System.IO.File]::ReadAllText( (Join-Path $root $relativePath), [System.Text.Encoding]::UTF8 ) } $pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding UTF8 | ConvertFrom-Json $responsiveCoverage = Get-Content -LiteralPath (Join-Path $PSScriptRoot 'responsive-layout-coverage.json') -Raw -Encoding UTF8 | ConvertFrom-Json foreach ($page in $pages.pages) { $relativeSource = "$($page.path).vue" $source = Join-Path $root $relativeSource if (-not (Test-Path -LiteralPath $source)) { throw "Registered page has no source file: $($page.path)" } if ($responsiveCoverage.covered -notcontains $relativeSource) { throw "Registered page is missing responsive coverage: $relativeSource" } } $global = Read-Utf8 'styles/global.scss' foreach ($rule in @( '(?s)\.app-single-line\s*\{[^}]*overflow:\s*hidden;[^}]*text-overflow:\s*ellipsis;[^}]*white-space:\s*nowrap;', '(?s)\.app-long-value\s*\{[^}]*overflow-wrap:\s*anywhere;' )) { if ($global -notmatch $rule) { throw "Global text-flow rule missing: $rule" } } foreach ($component in @( 'components/PageHeader.vue', 'components/AppButton.vue', 'components/AppTabbar.vue', 'components/GenealogyCard.vue' )) { if ((Read-Utf8 $component) -notmatch 'app-single-line') { throw "Shared compact-text owner is not using the global rule: $component" } } $profile = Read-Utf8 'pages/profile/m01-profile-home.vue' if ($profile -notmatch 'app-long-value') { throw 'Profile email must use the global long-value rule' } foreach ($relativePath in @( 'pages/genealogy/g03-create-genealogy.vue', 'pages/genealogy/g11-genealogy-settings.vue' )) { $source = Read-Utf8 $relativePath if ($source -notmatch '(?s)\.field-row__label\s*\{[^}]*width:\s*184rpx;[^}]*white-space:\s*nowrap;') { throw "Genealogy form labels must retain a four-character single-line column: $relativePath" } } foreach ($relativePath in @( 'pages/profile/m04-change-password.vue', 'pages/profile/m05-change-phone.vue' )) { $source = Read-Utf8 $relativePath if ($source -notmatch '(?s)\.form-row\s*>\s*text\s*\{[^}]*white-space:\s*nowrap;') { throw "Profile form labels must remain single-line: $relativePath" } } $phone = Read-Utf8 'pages/profile/m05-change-phone.vue' if ($phone -notmatch 'class="code-action"') { throw 'Phone verification action must own a readable secondary treatment' } if ($phone -notmatch '(?s)\.code-action\s*\{[^}]*background:\s*rgba\(255, 250, 238, 0\.96\);[^}]*white-space:\s*nowrap;') { throw 'Phone verification action must not use the cramped scroll frame' } Write-Output 'GLOBAL-TEXT-FLOW-CONTRACT PASS'