feat: unify project responsive layouts
This commit is contained in:
@@ -35,6 +35,7 @@ $shellPath = Join-Path $root 'components/AuthPageShell.vue'
|
||||
$legacyLoginPath = Join-Path $root 'pages/auth/a02-login.vue'
|
||||
$pagesPath = Join-Path $root 'pages.json'
|
||||
$globalStylesPath = Join-Path $root 'styles/global.scss'
|
||||
$profilePath = Join-Path $root 'styles/adaptive-frame-profiles.scss'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $entryPath)) {
|
||||
throw 'Missing the single A01 login page.'
|
||||
@@ -57,6 +58,7 @@ if ($pages.pages.Count -ne 52) {
|
||||
$entry = Get-Content -LiteralPath $entryPath -Raw -Encoding utf8
|
||||
$shell = Get-Content -LiteralPath $shellPath -Raw -Encoding utf8
|
||||
$globalStyles = Get-Content -LiteralPath $globalStylesPath -Raw -Encoding utf8
|
||||
$profiles = Get-Content -LiteralPath $profilePath -Raw -Encoding utf8
|
||||
|
||||
# A01 owns content in normal document flow. Decorative artwork must not become
|
||||
# a second full-page coordinate system.
|
||||
@@ -157,7 +159,7 @@ foreach ($asset in @(
|
||||
'a02-agreement-unchecked.png',
|
||||
'a02-agreement-checked.png'
|
||||
)) {
|
||||
Assert-Contains -Content ($entry + $shell) -Expected $asset -Message "A01 is missing asset reference: $asset"
|
||||
Assert-Contains -Content ($entry + $shell + $profiles) -Expected $asset -Message "A01 is missing asset reference: $asset"
|
||||
}
|
||||
foreach ($obsoleteVisualAsset in @(
|
||||
'auth-header.png',
|
||||
@@ -244,7 +246,5 @@ $feedbackToastRule = [regex]::Match($entry, '(?ms)^\.feedback-toast\s*\{(?<Body>
|
||||
if (-not $feedbackToastRule.Success) {
|
||||
throw 'A01 is missing the custom feedback Toast style rule.'
|
||||
}
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected '/static/assets/foundation/transparent/a01-scroll-toast-v3.png' -Message 'A01 feedback Toast must use the approved v3 nine-slice asset.'
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'border-image-slice:' -Message 'A01 feedback Toast must declare fixed decorative slices.'
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected 'fill' -Message 'A01 feedback Toast nine-slice must fill the paper center.'
|
||||
Assert-Contains -Content $feedbackToastRule.Groups['Body'].Value -Expected '@include adaptive.adaptive-feedback-toast;' -Message 'A01 feedback Toast must consume the approved shared nine-slice profile.'
|
||||
Write-Output 'A01-LOGIN-MERGE-CONTRACT PASS'
|
||||
|
||||
@@ -32,7 +32,7 @@ foreach ($required in @(
|
||||
'import AuthPageShell from "@/components/AuthPageShell.vue";',
|
||||
'a01-vnext-divider-v1.png',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-toast-v3.png',
|
||||
'adaptive.adaptive-feedback-toast',
|
||||
'a02-agreement-unchecked.png',
|
||||
'a02-agreement-checked.png',
|
||||
'v-model.trim="phone"',
|
||||
@@ -79,7 +79,7 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
|
||||
foreach ($required in @(
|
||||
'register-divider',
|
||||
'mode="aspectFit"',
|
||||
'border-image-slice: 58 280 fill;',
|
||||
'adaptive.adaptive-feedback-toast;',
|
||||
'top: calc(var(--status-bar-height, 0px) + 24rpx);'
|
||||
)) {
|
||||
Assert-Contains -Content $register -Expected $required -Message "Missing A-04 visual balance contract: $required"
|
||||
|
||||
@@ -33,7 +33,7 @@ foreach ($required in @(
|
||||
'import AuthPageShell from "@/components/AuthPageShell.vue";',
|
||||
'a01-vnext-divider-v1.png',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-toast-v3.png',
|
||||
'adaptive.adaptive-feedback-toast',
|
||||
'a01-scroll-dialog-v3.png',
|
||||
'chevron-right.png',
|
||||
'v-model.trim="phone"',
|
||||
@@ -52,7 +52,7 @@ foreach ($required in @(
|
||||
'if (!/^\d{6}$/.test(verificationCode.value))',
|
||||
'if (!password.value)',
|
||||
'if (password.value !== confirmPassword.value)',
|
||||
'border-image-slice: 58 280 fill;',
|
||||
'adaptive.adaptive-feedback-toast;',
|
||||
'top: calc(var(--status-bar-height, 0px) + 24rpx);',
|
||||
'max-height: calc(var(--app-viewport-height) - 40px);'
|
||||
)) {
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$profilePath = Join-Path $root 'styles/adaptive-frame-profiles.scss'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $profilePath)) {
|
||||
throw 'Missing adaptive frame profile owner: styles/adaptive-frame-profiles.scss'
|
||||
}
|
||||
|
||||
$profile = Get-Content -LiteralPath $profilePath -Raw -Encoding UTF8
|
||||
foreach ($token in @(
|
||||
'@mixin adaptive-auth-dialog',
|
||||
'@mixin adaptive-scroll-button($type)',
|
||||
'@mixin adaptive-module-content',
|
||||
'@mixin adaptive-module-field',
|
||||
'@mixin adaptive-feedback-toast',
|
||||
'@mixin adaptive-genealogy-current-slip',
|
||||
'@mixin adaptive-genealogy-list-card',
|
||||
'@mixin adaptive-genealogy-state-panel',
|
||||
'@mixin adaptive-genealogy-form-field',
|
||||
'@mixin adaptive-g05-overview-surface',
|
||||
'@mixin adaptive-g06-search-field',
|
||||
'@mixin adaptive-g06-search-action',
|
||||
'@mixin adaptive-g01-add-sheet',
|
||||
'@mixin adaptive-g01-switcher',
|
||||
'@mixin adaptive-family-content',
|
||||
'@mixin adaptive-family-panel',
|
||||
'@mixin adaptive-family-field',
|
||||
'@mixin adaptive-family-letter',
|
||||
'@mixin adaptive-notification-content',
|
||||
'@mixin adaptive-profile-content',
|
||||
'@mixin adaptive-profile-field',
|
||||
'@mixin adaptive-profile-summary',
|
||||
'@mixin adaptive-records-content',
|
||||
'@mixin adaptive-records-field',
|
||||
'@mixin adaptive-records-person',
|
||||
'@mixin adaptive-tree-panel',
|
||||
'@mixin adaptive-tree-field',
|
||||
'a01-scroll-dialog-v3.png',
|
||||
'border-image-slice: 260 240 360 240 fill;',
|
||||
'border-image-slice: 56 300 fill;',
|
||||
'border-image-slice: 105 150;',
|
||||
'border-image-slice: 70 100 fill;'
|
||||
)) {
|
||||
if (-not $profile.Contains($token)) {
|
||||
throw "Adaptive frame profile missing: $token"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output 'ADAPTIVE-FRAME-PROFILES-CONTRACT PASS'
|
||||
@@ -0,0 +1,54 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
function Read-Utf8([string]$path) {
|
||||
Get-Content -LiteralPath (Join-Path $root $path) -Raw -Encoding UTF8
|
||||
}
|
||||
|
||||
$expected = @{
|
||||
'components/AppToast.vue' = @('adaptive.adaptive-feedback-toast')
|
||||
'pages/auth/a01-entry.vue' = @('adaptive.adaptive-feedback-toast')
|
||||
'pages/auth/a04-register.vue' = @('adaptive.adaptive-feedback-toast')
|
||||
'pages/auth/a05-reset-password.vue' = @('adaptive.adaptive-feedback-toast')
|
||||
'pages/genealogy/g01-my-genealogies.vue' = @(
|
||||
'adaptive.adaptive-genealogy-current-slip',
|
||||
'adaptive.adaptive-genealogy-list-card',
|
||||
'adaptive.adaptive-genealogy-state-panel',
|
||||
'adaptive.adaptive-g01-add-sheet',
|
||||
'adaptive.adaptive-g01-switcher'
|
||||
)
|
||||
'pages/genealogy/g09-my-applications.vue' = @('adaptive.adaptive-genealogy-list-card')
|
||||
'pages/genealogy/g10-application-review.vue' = @(
|
||||
'adaptive.adaptive-genealogy-form-field',
|
||||
'adaptive.adaptive-genealogy-list-card',
|
||||
'adaptive.adaptive-scroll-button',
|
||||
'adaptive.adaptive-feedback-toast'
|
||||
)
|
||||
}
|
||||
|
||||
foreach ($path in $expected.Keys) {
|
||||
$source = Read-Utf8 $path
|
||||
if (-not $source.Contains('@use "../../styles/adaptive-frame-profiles.scss" as adaptive;') -and
|
||||
-not $source.Contains('@use "../styles/adaptive-frame-profiles.scss" as adaptive;')) {
|
||||
throw "$path must import the adaptive frame profile owner"
|
||||
}
|
||||
foreach ($token in $expected[$path]) {
|
||||
if (-not $source.Contains($token)) { throw "$path missing adaptive surface: $token" }
|
||||
}
|
||||
if ($source -match '(?im)border-image-slice\s*:') {
|
||||
throw "$path must not own border-image slice values"
|
||||
}
|
||||
}
|
||||
|
||||
$g01 = Read-Utf8 'pages/genealogy/g01-my-genealogies.vue'
|
||||
$g10 = Read-Utf8 'pages/genealogy/g10-application-review.vue'
|
||||
foreach ($source in @($g01, $g10)) {
|
||||
if ($source -match '(?im)background\s*:[^;\r\n]*100%\s+100%[^;\r\n]*;') {
|
||||
throw 'A/G variable surfaces must not stretch full images to 100% 100%'
|
||||
}
|
||||
}
|
||||
|
||||
$tabbar = Read-Utf8 'components/AppTabbar.vue'
|
||||
if ($tabbar -match '(?im)(?<![-\w])height\s*:\s*112rpx;') { throw 'AppTabbar must use a minimum baseline instead of fixed content height' }
|
||||
|
||||
Write-Output 'AG-RESPONSIVE-SURFACES-CONTRACT PASS'
|
||||
@@ -0,0 +1,28 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$button = Get-Content -LiteralPath (Join-Path $root 'components/AppButton.vue') -Raw -Encoding UTF8
|
||||
|
||||
foreach ($token in @(
|
||||
'compact: { type: Boolean, default: false }',
|
||||
"'app-button--compact': compact",
|
||||
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-scroll-button(primary);',
|
||||
'@include adaptive.adaptive-scroll-button(secondary);',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-secondary-v3.png'
|
||||
)) {
|
||||
if (-not $button.Contains($token)) { throw "AppButton compact contract missing: $token" }
|
||||
}
|
||||
|
||||
if ($button.Contains('border-image-slice: 56 300 fill;')) {
|
||||
throw 'AppButton must consume the shared adaptive frame profile instead of owning slice values'
|
||||
}
|
||||
|
||||
if ($button -notmatch '(?s)\.app-button--compact \.app-button__skin\s*\{[^}]*display:\s*none;') {
|
||||
throw 'AppButton compact mode must hide the aspectFit image skin'
|
||||
}
|
||||
if ($button -notmatch '(?s)\.app-button--compact \.app-button__label\s*\{[^}]*padding:\s*0;[^}]*font-size:\s*23rpx;[^}]*letter-spacing:\s*0;[^}]*white-space:\s*nowrap;') {
|
||||
throw 'AppButton compact label must preserve four-character action capacity'
|
||||
}
|
||||
|
||||
Write-Output 'APP-BUTTON-COMPACT-CONTRACT PASS'
|
||||
@@ -0,0 +1,32 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$dialog = Get-Content -LiteralPath (Join-Path $root 'components/AppDialog.vue') -Raw -Encoding UTF8
|
||||
|
||||
foreach ($token in @(
|
||||
'compactActions: { type: Boolean, default: false }',
|
||||
'class="app-dialog__copy"',
|
||||
':compact="compactActions && showCancel"',
|
||||
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-auth-dialog;',
|
||||
'max-height: calc(var(--app-viewport-height, 100vh) - 160rpx);',
|
||||
'margin-top: 26rpx;'
|
||||
)) {
|
||||
if (-not $dialog.Contains($token)) { throw "AppDialog responsive contract missing: $token" }
|
||||
}
|
||||
|
||||
foreach ($forbidden in @(
|
||||
'min-height: 520rpx;',
|
||||
'center / contain no-repeat',
|
||||
'margin-top: auto;'
|
||||
)) {
|
||||
if ($dialog.Contains($forbidden)) { throw "AppDialog responsive contract forbids: $forbidden" }
|
||||
}
|
||||
|
||||
foreach ($rule in @(
|
||||
'(?s)\.app-dialog__copy\s*\{[^}]*display:\s*flex;[^}]*width:\s*100%;[^}]*flex-direction:\s*column;',
|
||||
'(?s)\.app-dialog__title,\s*\.app-dialog__message\s*\{[^}]*display:\s*block;[^}]*width:\s*100%;'
|
||||
)) {
|
||||
if ($dialog -notmatch $rule) { throw "AppDialog responsive layout rule missing: $rule" }
|
||||
}
|
||||
|
||||
Write-Output 'APP-DIALOG-RESPONSIVE-CONTENT-CONTRACT PASS'
|
||||
@@ -3,9 +3,9 @@ $root = Split-Path $PSScriptRoot -Parent
|
||||
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/genealogy/g01-my-genealogies.vue'), [System.Text.Encoding]::UTF8)
|
||||
|
||||
foreach ($expected in @(
|
||||
'background: url("/static/assets/modules/genealogy/transparent/current-slip-frame.png") center / 100% 100% no-repeat',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png") center / 100% 100% no-repeat',
|
||||
'@include adaptive.adaptive-genealogy-current-slip',
|
||||
'@include adaptive.adaptive-genealogy-list-card',
|
||||
'@include adaptive.adaptive-genealogy-state-panel',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png") center / contain no-repeat',
|
||||
'grid-template-columns: minmax(0, 1fr) 96rpx'
|
||||
)) {
|
||||
|
||||
@@ -59,6 +59,7 @@ if ($tabbar -notmatch 'font-size:\s*30rpx') { throw 'Tabbar label size does not
|
||||
if ($tabbar -notmatch 'width:\s*64rpx') { throw 'Tabbar icon size does not match the approved visual weight.' }
|
||||
|
||||
$page = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'pages/genealogy/g01-my-genealogies.vue')
|
||||
$profiles = Get-Content -Raw -Encoding UTF8 (Join-Path $root 'styles/adaptive-frame-profiles.scss')
|
||||
if ($page -match "from '@/utils/api\.js'") { throw 'G-01 visual phase must not call the remote API.' }
|
||||
foreach ($state in @('isLoading', 'hasGenealogies', 'createdGenealogies', 'joinedGenealogies')) {
|
||||
if ($page -notmatch $state) { throw "G-01 is missing presentation state: $state" }
|
||||
@@ -127,10 +128,10 @@ if ($page -match '(?s)\.add-dialog__heading\s*\{[^}]*top\s*:') {
|
||||
if ($page -notmatch '(?s)\.add-dialog__close\s*\{[^}]*grid-column:\s*2;[^}]*grid-row:\s*1\s*/\s*span\s+2;[^}]*margin-right:\s*-22rpx;') {
|
||||
throw 'G-01 add sheet close control does not follow the heading grid.'
|
||||
}
|
||||
if ($page -notmatch [regex]::Escape('/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png')) {
|
||||
if ($page -notmatch 'adaptive\.adaptive-g01-add-sheet' -or $profiles -notmatch [regex]::Escape('/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3.png')) {
|
||||
throw 'G-01 add sheet does not use the approved complete background asset.'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.add-dialog\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3\.png"\);[^}]*border-image-slice:\s*220\s+0\s+1\s+0\s+fill;[^}]*border-image-width:\s*118rpx\s+0\s+1rpx;') {
|
||||
if ($profiles -notmatch '(?s)@mixin\s+adaptive-g01-add-sheet\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/genealogy/transparent/g01-add-sheet-background-v3\.png"\);[^}]*border-image-slice:\s*220\s+0\s+1\s+0\s+fill;[^}]*border-image-width:\s*118rpx\s+0\s+1rpx;') {
|
||||
throw 'G-01 add sheet does not preserve the complete sheet top while stretching only the paper body.'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.add-dialog__close-icon\s*\{[^}]*width:\s*80rpx;[^}]*height:\s*80rpx;') {
|
||||
@@ -155,7 +156,7 @@ if ($page -notmatch '(?s)\.genealogy-switcher-layer\s*\{[^}]*align-items:\s*cent
|
||||
if ($page -match 'class="genealogy-switcher__skin"') {
|
||||
throw 'G-01 switcher must not render the complete background as a fixed aspectFit image.'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.genealogy-switcher\s*\{[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);[^}]*border-image-source:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\);[^}]*border-image-slice:\s*300\s+260\s+360\s+260\s+fill;[^}]*border-image-width:\s*110rpx\s+48rpx\s+132rpx\s+48rpx;') {
|
||||
if ($page -notmatch '(?s)\.genealogy-switcher\s*\{[^}]*@include\s+adaptive\.adaptive-g01-switcher;[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);' -or $profiles -notmatch '(?s)@mixin\s+adaptive-g01-switcher\s*\{[^}]*border-image-source:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\);[^}]*border-image-slice:\s*300\s+260\s+360\s+260\s+fill;[^}]*border-image-width:\s*110rpx\s+48rpx\s+132rpx\s+48rpx;') {
|
||||
throw 'G-01 switcher does not use the approved stretchable complete background.'
|
||||
}
|
||||
if ($page -notmatch '(?s)\.genealogy-switcher__content\s*\{[^}]*min-height:\s*600rpx;[^}]*max-height:\s*calc\(100vh\s*-\s*120rpx\);[^}]*padding:\s*120rpx\s+58rpx\s+140rpx;') {
|
||||
@@ -182,7 +183,7 @@ foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'un
|
||||
foreach ($asset in @('shortcut-tree.png', 'shortcut-members.png', 'shortcut-generation-poem.png', 'shortcut-application.png', 'add.png')) {
|
||||
if ($page -notmatch [regex]::Escape($asset)) { throw "G-01 does not consume $asset" }
|
||||
}
|
||||
if ($page -notmatch 'current-slip-frame\.png') { throw 'G-01 does not use the approved current genealogy frame asset.' }
|
||||
if ($page -notmatch 'adaptive\.adaptive-genealogy-current-slip' -or $profiles -notmatch 'current-slip-frame\.png') { throw 'G-01 does not consume the approved current genealogy frame profile.' }
|
||||
foreach ($token in @('current-summary', 'current-meta-item', 'current-seal-frame', 'meta-location.png', 'meta-member.png', 'meta-admin.png', 'create-cloud.png')) {
|
||||
if ($page -notmatch $token) { throw "G-01 current panel is missing $token" }
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ $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
|
||||
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
|
||||
$g04 = Join-Path $root 'pages/genealogy/g04-first-ancestor.vue'
|
||||
$panelPath = Join-Path $root 'static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png'
|
||||
|
||||
@@ -36,7 +37,7 @@ foreach ($required in @(
|
||||
'class="duplicate-reminder-layer"',
|
||||
'if (isSubmitting.value) return;',
|
||||
'/pages/genealogy/g05-genealogy-overview?genealogyId=',
|
||||
'g01-empty-panel-frame.png',
|
||||
'adaptive.adaptive-genealogy-state-panel',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'create-flow-panel',
|
||||
'flow-primary-action',
|
||||
@@ -47,6 +48,7 @@ foreach ($required in @(
|
||||
)) {
|
||||
Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required"
|
||||
}
|
||||
Assert-Contains -Content $profiles -Expected 'g01-empty-panel-frame.png' -Message 'Adaptive genealogy panel profile must own the G03 panel asset'
|
||||
|
||||
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=', 'class="flow-header"', 'flow-header__back', 'root-header-cinnabar.jpg')) {
|
||||
if ($g03 -match [regex]::Escape($forbidden)) { throw "G03 retains forbidden implementation: $forbidden" }
|
||||
|
||||
@@ -17,6 +17,7 @@ function Assert-NoCssSurface {
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$g01 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g01-my-genealogies.vue') -Raw -Encoding utf8
|
||||
$g05 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g05-genealogy-overview.vue') -Raw -Encoding utf8
|
||||
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
|
||||
$runtimeSmoke = Get-Content -LiteralPath (Join-Path $root 'tests/g05-overview-runtime-smoke.js') -Raw -Encoding utf8
|
||||
$asset = Join-Path $root 'static/assets/modules/genealogy/opaque/g05-overview-surface.png'
|
||||
|
||||
@@ -34,7 +35,7 @@ foreach ($required in @(
|
||||
'overview-state--empty',
|
||||
'overview-state--error',
|
||||
'overview-state--no-permission',
|
||||
'g05-overview-surface.png',
|
||||
'adaptive.adaptive-g05-overview-surface',
|
||||
'/pages/genealogy/g03-create-genealogy?step=ancestor&genealogyId=',
|
||||
'/pages/tree/t01-tree-overview?genealogyId=',
|
||||
'/pages/genealogy/g10-application-review?genealogyId=',
|
||||
@@ -44,6 +45,7 @@ foreach ($required in @(
|
||||
)) {
|
||||
Assert-Contains $g05 $required "Missing G05 overview contract: $required"
|
||||
}
|
||||
Assert-Contains $profiles 'g05-overview-surface.png' 'Adaptive G05 surface profile must own the overview asset'
|
||||
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
|
||||
if ($g05 -match [regex]::Escape($forbidden)) { throw "G05 retains forbidden implementation: $forbidden" }
|
||||
}
|
||||
|
||||
@@ -13,17 +13,22 @@ if ($positions.Count -ne 0) {
|
||||
}
|
||||
|
||||
foreach ($required in @(
|
||||
'background: url("/static/assets/modules/genealogy/opaque/g06-search-title-strip.png")',
|
||||
'background: url("/static/assets/modules/genealogy/opaque/g06-search-input-wide.png")',
|
||||
'background: url("/static/assets/modules/genealogy/opaque/g06-search-button.png")',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png")',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
|
||||
)) {
|
||||
if ($page -notmatch [regex]::Escape($required)) {
|
||||
if ($styleWithoutComments -notmatch [regex]::Escape($required)) {
|
||||
throw "G06 document-flow migration is missing asset-backed surface: $required"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($forbidden in @('search-title-strip', 'g06-search-title-strip.png')) {
|
||||
if ($page -match [regex]::Escape($forbidden)) {
|
||||
throw "G06 must not retain removed title-strip token: $forbidden"
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($obsoleteClass in @('search-title-strip__skin', 'search-field__skin', 'search-action__skin', 'result-card__skin')) {
|
||||
if ($page -match "class=`"$obsoleteClass`"") {
|
||||
throw "G06 must not retain decorative image layer .$obsoleteClass"
|
||||
|
||||
@@ -17,8 +17,8 @@ $root = Split-Path -Parent $PSScriptRoot
|
||||
$pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 | ConvertFrom-Json
|
||||
$paths = @($pages.pages.path)
|
||||
$g06 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g06-search-genealogies.vue') -Raw -Encoding utf8
|
||||
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
|
||||
$g07 = Join-Path $root 'pages/genealogy/g07-search-result.vue'
|
||||
$titleStripPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g06-search-title-strip.png'
|
||||
$inputSkinPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g06-search-input-wide.png'
|
||||
$buttonSkinPath = Join-Path $root 'static/assets/modules/genealogy/opaque/g06-search-button.png'
|
||||
|
||||
@@ -45,10 +45,8 @@ foreach ($required in @(
|
||||
'/pages/genealogy/g08-join-application?source=invite&genealogyId=',
|
||||
'/pages/genealogy/g05-genealogy-overview?mode=public&genealogyId=',
|
||||
'search-page__header',
|
||||
'g06-search-input-wide.png',
|
||||
'g06-search-button.png',
|
||||
'g06-search-title-strip.png',
|
||||
'search-title-strip',
|
||||
'adaptive.adaptive-g06-search-field',
|
||||
'adaptive.adaptive-g06-search-action',
|
||||
'section-divider.png',
|
||||
'root-header-hall.png',
|
||||
'search-controls',
|
||||
@@ -59,8 +57,11 @@ foreach ($required in @(
|
||||
)) {
|
||||
Assert-Contains -Content $g06 -Expected $required -Message "Missing G06 flow contract: $required"
|
||||
}
|
||||
foreach ($assetName in @('g06-search-input-wide.png', 'g06-search-button.png')) {
|
||||
Assert-Contains $profiles $assetName "Adaptive G06 profile must own: $assetName"
|
||||
}
|
||||
|
||||
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
|
||||
foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', 'search-title-strip', 'g06-search-title-strip.png')) {
|
||||
if ($g06 -match [regex]::Escape($forbidden)) { throw "G06 retains forbidden implementation: $forbidden" }
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ if ($g06 -match [regex]::Escape('g06-search-panel-v2.png')) { throw 'G06 must no
|
||||
if ($g06 -match [regex]::Escape('search-hero')) { throw 'G06 must replace the floating hero with the selected title-strip hierarchy' }
|
||||
if ($g06 -match [regex]::Escape('g06-search-input.png')) { throw 'G06 must use the wide selected-design input skin instead of the tall legacy skin' }
|
||||
|
||||
foreach ($className in @('search-title-strip', 'search-field', 'search-action')) {
|
||||
foreach ($className in @('search-field', 'search-action')) {
|
||||
Assert-NoCssSurface -Content $g06 -ClassName $className
|
||||
}
|
||||
|
||||
@@ -84,7 +85,6 @@ if ($g06 -notmatch '(?s)\.status-retry\s*\{[^}]*width:\s*300rpx;[^}]*height:\s*7
|
||||
|
||||
Add-Type -AssemblyName System.Drawing
|
||||
foreach ($asset in @(
|
||||
@{ Path = $titleStripPath; Width = 1122; Height = 220; Label = 'search-title-strip' },
|
||||
@{ Path = $inputSkinPath; Width = 1120; Height = 248; Label = 'search-input' },
|
||||
@{ Path = $buttonSkinPath; Width = 300; Height = 132; Label = 'search-button' }
|
||||
)) {
|
||||
|
||||
@@ -18,6 +18,7 @@ $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
|
||||
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
|
||||
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
|
||||
|
||||
foreach ($page in @($g08, $g09, $g10)) {
|
||||
@@ -31,8 +32,8 @@ foreach ($required in @(
|
||||
'join-state--form',
|
||||
'join-state--success',
|
||||
'join-state--error',
|
||||
'g01-empty-panel-frame.png',
|
||||
'g-form-field-frame.png',
|
||||
'adaptive.adaptive-genealogy-state-panel',
|
||||
'adaptive.adaptive-genealogy-form-field',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'const genealogyPreview',
|
||||
'query.genealogyId',
|
||||
@@ -42,6 +43,9 @@ foreach ($required in @(
|
||||
'/pages/genealogy/g09-my-applications',
|
||||
'/pages/genealogy/g01-my-genealogies?genealogyId='
|
||||
)) { Assert-Contains $g08 $required "Missing G08 join contract: $required" }
|
||||
foreach ($assetName in @('g01-empty-panel-frame.png', 'g-form-field-frame.png')) {
|
||||
Assert-Contains $profiles $assetName "Adaptive genealogy profile must own: $assetName"
|
||||
}
|
||||
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" }
|
||||
}
|
||||
@@ -54,7 +58,7 @@ foreach ($required in @(
|
||||
'application-state--list',
|
||||
'application-state--empty',
|
||||
'application-state--error',
|
||||
'list-slip-frame.png',
|
||||
'adaptive.adaptive-genealogy-list-card',
|
||||
'"PENDING"',
|
||||
'"APPROVED"',
|
||||
'"REJECTED"',
|
||||
@@ -65,6 +69,7 @@ foreach ($required in @(
|
||||
'<AppDialog',
|
||||
'/pages/genealogy/g08-join-application?source=search&previous=rejected&genealogyId='
|
||||
)) { Assert-Contains $g09 $required "Missing G09 application contract: $required" }
|
||||
Assert-Contains $profiles 'list-slip-frame.png' 'Adaptive genealogy list profile must own the G09 card asset'
|
||||
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" }
|
||||
}
|
||||
@@ -74,7 +79,7 @@ foreach ($required in @(
|
||||
'review-state--list',
|
||||
'review-state--empty',
|
||||
'review-state--error',
|
||||
'list-slip-frame.png',
|
||||
'adaptive.adaptive-genealogy-list-card',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-secondary-v3.png',
|
||||
'const reviewSamples',
|
||||
|
||||
@@ -9,8 +9,14 @@ $rules = @(
|
||||
'(?s)\.application-card__status\s*\{[^}]*font-size:\s*25rpx;',
|
||||
'(?s)\.application-card__hint\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__action\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__action\s*\{[^}]*white-space:\s*nowrap;',
|
||||
'(?s)\.application-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;[^}]*min-height:\s*210rpx;[^}]*margin-bottom:\s*12rpx;',
|
||||
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*174rpx;[^}]*gap:\s*8rpx 14rpx;[^}]*padding:\s*26rpx 28rpx 22rpx 40rpx;',
|
||||
'(?s)\.application-state-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;'
|
||||
)
|
||||
if ($page -match '(?s)\.application-card\s*\{[^}]*background:\s*url\("/static/assets/modules/genealogy/transparent/list-slip-frame\.png"\)[^}]*100%\s+100%') {
|
||||
throw 'G09 application card must not stretch the complete frame asset to 100% 100%'
|
||||
}
|
||||
foreach ($rule in $rules) {
|
||||
if ($page -notmatch $rule) { throw "G09 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
@@ -6,8 +6,12 @@ foreach ($required in @(
|
||||
'class="review-result"',
|
||||
'review-result--approved',
|
||||
'review-result--rejected',
|
||||
'a01-secondary-button-v2.png',
|
||||
'a01-primary-button-v2.png',
|
||||
'<AppButton',
|
||||
'compact',
|
||||
'type="secondary"',
|
||||
'<AppDialog',
|
||||
'compact-actions',
|
||||
'class="review-page__retry"',
|
||||
'a01-scroll-secondary-v3.png',
|
||||
'a01-scroll-primary-v3.png'
|
||||
)) {
|
||||
@@ -19,11 +23,24 @@ $rules = @(
|
||||
'(?s)\.application-card__phone\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__time\s*\{[^}]*font-size:\s*23rpx;',
|
||||
'(?s)\.application-card__relation\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.review-action\s*\{[^}]*width:\s*148rpx;[^}]*height:\s*68rpx;',
|
||||
'(?s)\.review-action text\s*\{[^}]*font-size:\s*24rpx;',
|
||||
'(?s)\.review-result\s*\{[^}]*width:\s*308rpx;[^}]*height:\s*68rpx;',
|
||||
'(?s)\.application-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;[^}]*min-height:\s*218rpx;[^}]*margin-bottom:\s*12rpx;',
|
||||
'(?s)\.application-card__body\s*\{[^}]*min-height:\s*182rpx;[^}]*gap:\s*8rpx 0;[^}]*padding:\s*28rpx 28rpx 22rpx 40rpx;',
|
||||
'(?s)\.review-actions \.app-button\s*\{[^}]*width:\s*148rpx;[^}]*min-height:\s*68rpx;',
|
||||
'(?s)\.review-result\s*\{[^}]*@include\s+adaptive\.adaptive-scroll-button\(primary\);[^}]*width:\s*308rpx;[^}]*min-height:\s*68rpx;',
|
||||
'(?s)\.review-state-card\s*\{[^}]*@include\s+adaptive\.adaptive-genealogy-list-card;',
|
||||
'(?s)\.review-state-card__copy text:last-child\s*\{[^}]*font-size:\s*24rpx;'
|
||||
)
|
||||
if ($page -match 'a01-(?:primary|secondary)-button-v2\.png') {
|
||||
throw 'G10 must not render the ultra-wide v2 assets through aspectFit inside narrow action buttons'
|
||||
}
|
||||
if ($page -match '(?s)class="review-page__retry"[^>]*>\s*<image') {
|
||||
throw 'G10 retry action must use AppButton instead of detached image and text nodes'
|
||||
}
|
||||
foreach ($selector in @('application-card', 'review-state-card')) {
|
||||
if ($page -match "(?s)\.$selector\s*\{[^}]*background:[^}]*list-slip-frame\.png[^}]*100%\s+100%") {
|
||||
throw "G10 .$selector must not stretch the complete frame asset to 100% 100%"
|
||||
}
|
||||
}
|
||||
foreach ($rule in $rules) {
|
||||
if ($page -notmatch $rule) { throw "G10 readable visual rule missing: $rule" }
|
||||
}
|
||||
|
||||
@@ -3,18 +3,21 @@ $root = Split-Path $PSScriptRoot -Parent
|
||||
$page = [System.IO.File]::ReadAllText((Join-Path $root 'pages/genealogy/g10-application-review.vue'), [System.Text.Encoding]::UTF8)
|
||||
|
||||
foreach ($expected in @(
|
||||
'background: url("/static/assets/modules/genealogy/transparent/list-slip-frame.png") center / 100% 100% no-repeat',
|
||||
'@include adaptive.adaptive-genealogy-list-card',
|
||||
'@include adaptive.adaptive-feedback-toast',
|
||||
'background: url("/static/assets/modules/genealogy/transparent/section-divider.png") bottom center / 100% 14rpx no-repeat',
|
||||
'grid-template-columns: auto minmax(0, 1fr) auto',
|
||||
'padding: 44rpx 52rpx 29rpx 64rpx',
|
||||
'grid-area: 1 / 1',
|
||||
'padding: 28rpx 28rpx 22rpx 40rpx',
|
||||
'<AppButton',
|
||||
'compact-actions',
|
||||
'z-index: 1',
|
||||
'background: url("/static/assets/foundation/transparent/a01-scroll-toast-v3.png") center / 100% 100% no-repeat'
|
||||
'@include adaptive.adaptive-genealogy-form-field'
|
||||
)) {
|
||||
if (-not $page.Contains($expected)) { throw "G10 document-flow contract missing: $expected" }
|
||||
}
|
||||
if ($page -notmatch '(?s)\.review-feedback\s*\{[^}]*position:\s*fixed;') { throw 'G10 feedback must remain fixed' }
|
||||
if ([regex]::Matches($page, 'position\s*:').Count -ne 1) { throw 'G10 must keep only its fixed feedback positioned' }
|
||||
if ($page -match 'class="(?:application-card__skin|review-state-card__skin)"|review-intro[^>]*>.*?section-divider\.png|review-feedback[^>]*>\s*<image') { throw 'G10 decorative frames must be container backgrounds' }
|
||||
if ($page -match '(?s)\.(?:application-card|review-state-card)\s*\{[^}]*background:[^}]*list-slip-frame\.png[^}]*100%\s+100%') { throw 'G10 card frames must use nine-slice border images instead of complete-image stretching' }
|
||||
|
||||
Write-Output 'G10-DOCUMENT-FLOW-CONTRACT PASS'
|
||||
|
||||
@@ -17,12 +17,13 @@ function Assert-NoCssSurface {
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$g11 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g11-genealogy-settings.vue') -Raw -Encoding utf8
|
||||
$g12 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g12-generation-poems.vue') -Raw -Encoding utf8
|
||||
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
|
||||
$catalog = Get-Content -LiteralPath (Join-Path $root 'data/page-catalog.js') -Raw -Encoding utf8
|
||||
|
||||
foreach ($page in @($g11, $g12)) {
|
||||
if ($page -match '<ModulePage') { throw 'G11-G12 must not retain the generic ModulePage shell' }
|
||||
Assert-Contains $page '<GenealogyPageBackground />' 'G11-G12 must use the accepted shared genealogy background'
|
||||
Assert-Contains $page 'g01-empty-panel-frame.png' 'G11-G12 forms must reuse the complete genealogy paper panel'
|
||||
Assert-Contains $page 'adaptive.adaptive-genealogy-state-panel' 'G11-G12 forms must reuse the adaptive genealogy paper panel profile'
|
||||
if ($page -match "@/utils/api\.js|\bappApi\b") { throw 'G11-G12 design phase must not connect the API layer' }
|
||||
}
|
||||
|
||||
@@ -34,7 +35,7 @@ foreach ($required in @(
|
||||
'settings-state--no-permission',
|
||||
'const nameError = ref',
|
||||
'class="settings-feedback"',
|
||||
'g-form-field-frame.png',
|
||||
'adaptive.adaptive-genealogy-form-field',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'const genealogyDraft',
|
||||
'query.genealogyId',
|
||||
@@ -51,13 +52,16 @@ foreach ($required in @(
|
||||
'poem-state--no-permission',
|
||||
'const poemError = ref',
|
||||
'class="poem-feedback"',
|
||||
'g-form-field-frame.png',
|
||||
'adaptive.adaptive-genealogy-form-field',
|
||||
'a01-scroll-primary-v3.png',
|
||||
'a01-scroll-secondary-v3.png',
|
||||
'const poemRows',
|
||||
'stopMissingOldGeneration',
|
||||
'query.genealogyId'
|
||||
)) { Assert-Contains $g12 $required "Missing G12 generation-poem contract: $required" }
|
||||
foreach ($assetName in @('g01-empty-panel-frame.png', 'g-form-field-frame.png')) {
|
||||
Assert-Contains $profiles $assetName "Adaptive genealogy profile must own: $assetName"
|
||||
}
|
||||
|
||||
foreach ($page in @($g11, $g12)) {
|
||||
foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$vueFiles = @(
|
||||
Get-ChildItem -LiteralPath (Join-Path $root 'pages') -Recurse -Filter '*.vue'
|
||||
Get-ChildItem -LiteralPath (Join-Path $root 'components') -Recurse -Filter '*.vue'
|
||||
)
|
||||
$specificCopy = @(
|
||||
'\u6C64\u6B63\u534E',
|
||||
'\u6C64\u6B63\u56FD',
|
||||
'\u6C64\u6587\u8FDC',
|
||||
'\u6C64\u6C0F\u5BB6\u8C31',
|
||||
'\u5982\uFF1A\u6C64'
|
||||
)
|
||||
|
||||
foreach ($file in $vueFiles) {
|
||||
$lineNumber = 0
|
||||
foreach ($line in Get-Content -LiteralPath $file.FullName -Encoding utf8) {
|
||||
$lineNumber += 1
|
||||
if ($line -notmatch 'placeholder|例如|比如|如:') { continue }
|
||||
foreach ($token in $specificCopy) {
|
||||
if ($line -match $token) {
|
||||
throw "User guidance must not contain specific copy '$token': $($file.FullName):$lineNumber"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$g08 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g08-join-application.vue') -Raw -Encoding utf8
|
||||
foreach ($required in @(
|
||||
'placeholder="\u4F8B\u5982\uFF1A\u67D0\u67D0\u67D0\u5802\u4F84"',
|
||||
'\u8BF7\u4EE5\u5BB6\u8C31\u4E2D\u4E00\u4F4D\u5DF2\u77E5\u957F\u8F88\u4E3A\u53C2\u7167\uFF0C\u4F8B\u5982\uFF1A\u67D0\u67D0\u67D0\u5802\u4F84'
|
||||
)) {
|
||||
if ($g08 -notmatch $required) { throw "G08 generic relationship guidance missing: $required" }
|
||||
}
|
||||
|
||||
Write-Output 'GENERIC-GUIDANCE-COPY-CONTRACT PASS'
|
||||
@@ -15,14 +15,15 @@ foreach ($component in @('AppButton.vue', 'AppDialog.vue', 'AppToast.vue', 'AppL
|
||||
}
|
||||
|
||||
$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.' }
|
||||
$profiles = Read-Utf8 (Join-Path $root 'styles/adaptive-frame-profiles.scss')
|
||||
if ($button -notmatch 'adaptive-scroll-button' -or $profiles -notmatch 'a01-scroll-primary-v3\.png') { throw 'AppButton does not consume the approved A-series primary scroll profile.' }
|
||||
if ($button -notmatch 'adaptive-scroll-button' -or $profiles -notmatch 'a01-scroll-secondary-v3\.png') { throw 'AppButton does not consume the approved A-series secondary scroll profile.' }
|
||||
|
||||
$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.' }
|
||||
if ($dialog -notmatch 'adaptive-auth-dialog' -or $profiles -notmatch 'a01-scroll-dialog-v3\.png') { throw 'AppDialog does not consume the approved A-series dialog profile.' }
|
||||
|
||||
$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.' }
|
||||
if ($toast -notmatch 'adaptive-feedback-toast' -or $profiles -notmatch 'a01-scroll-toast-v3\.png') { throw 'AppToast does not consume the approved A-series toast profile.' }
|
||||
|
||||
$header = Read-Utf8 (Join-Path $componentsRoot 'PageHeader.vue')
|
||||
if ($header -notmatch 'chevron-right\.png') { throw 'PageHeader does not use the approved image arrow.' }
|
||||
|
||||
@@ -19,6 +19,9 @@ foreach ($anchor in @(
|
||||
'<AppToast',
|
||||
'<ModulePageBackground',
|
||||
':style="moduleAssetStyle"',
|
||||
'@use "../styles/adaptive-frame-profiles.scss" as adaptive;',
|
||||
'@include adaptive.adaptive-module-field;',
|
||||
'@include adaptive.adaptive-module-content;',
|
||||
'const moduleAssetStyle = computed(() => ({',
|
||||
'"--module-content-asset": `url(${contentAsset.value})`',
|
||||
'"--module-field-asset": `url(${fieldAsset.value})`',
|
||||
@@ -31,7 +34,10 @@ foreach ($forbidden in @(
|
||||
'class="page-intro"',
|
||||
'g03-create-flow-panel.png',
|
||||
'g06-search-input-wide.png',
|
||||
'application-status-card.png'
|
||||
'application-status-card.png',
|
||||
'background: var(--module-field-asset) center / 100% 100% no-repeat;',
|
||||
'background: var(--module-content-asset) center / 100% 100% no-repeat;',
|
||||
'height: 100%;'
|
||||
)) {
|
||||
if ($component -match [regex]::Escape($forbidden)) { throw "ModulePage must not keep obsolete shared surface: $forbidden" }
|
||||
}
|
||||
@@ -52,8 +58,9 @@ foreach ($rule in @(
|
||||
'(?s)\.module-page\s*\{[^}]*display:\s*flex;[^}]*flex-direction:\s*column;',
|
||||
'(?s)\.module-page__header,\s*\.module-page__content,\s*\.module-page__loading\s*\{[^}]*z-index:\s*1;',
|
||||
'(?s)\.module-lead\s*\{[^}]*background:\s*url\("/static/assets/modules/genealogy/transparent/section-divider\.png"\) center / 100% 100% no-repeat;',
|
||||
'(?s)\.form-row,\s*\.settings-row\s*\{[^}]*background:\s*var\(--module-field-asset\) center / 100% 100% no-repeat;',
|
||||
'(?s)\.list-card,\s*\.detail-card,\s*\.timeline-row,\s*\.status-card\s*\{[^}]*background:\s*var\(--module-content-asset\) center / 100% 100% no-repeat;'
|
||||
'(?s)\.module-lead\s*\{[^}]*display:\s*flex;[^}]*align-items:\s*center;[^}]*justify-content:\s*center;',
|
||||
'(?s)\.form-row,\s*\.settings-row\s*\{[^}]*@include\s+adaptive\.adaptive-module-field;',
|
||||
'(?s)\.list-card,\s*\.detail-card,\s*\.timeline-row,\s*\.status-card\s*\{[^}]*@include\s+adaptive\.adaptive-module-content;'
|
||||
)) {
|
||||
if ($component -notmatch $rule) { throw "ModulePage document-flow background rule missing: $rule" }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$coveragePath = Join-Path $PSScriptRoot 'responsive-layout-coverage.json'
|
||||
$allowlistPath = Join-Path $PSScriptRoot 'responsive-layout-allowlist.json'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $coveragePath)) { throw 'Missing responsive layout coverage owner' }
|
||||
if (-not (Test-Path -LiteralPath $allowlistPath)) { throw 'Missing responsive layout allowlist owner' }
|
||||
|
||||
$coverage = Get-Content -LiteralPath $coveragePath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$allowlist = Get-Content -LiteralPath $allowlistPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
$requiredFoundation = @(
|
||||
'components/AppButton.vue',
|
||||
'components/AppDialog.vue',
|
||||
'components/ModulePage.vue'
|
||||
)
|
||||
foreach ($required in $requiredFoundation) {
|
||||
if ($coverage.covered -notcontains $required) {
|
||||
throw "Responsive coverage cannot remove migrated foundation file: $required"
|
||||
}
|
||||
}
|
||||
|
||||
function Normalize-Selector([string]$selector) {
|
||||
return (($selector -replace '\s+', ' ').Trim())
|
||||
}
|
||||
|
||||
function Test-Allowlisted([string]$path, [string]$selector, [string]$property) {
|
||||
$normalizedSelector = Normalize-Selector $selector
|
||||
foreach ($entry in $allowlist.exceptions) {
|
||||
if ($entry.path -eq $path -and
|
||||
(Normalize-Selector $entry.selector) -eq $normalizedSelector -and
|
||||
$entry.property -eq $property) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
$violations = [System.Collections.Generic.List[string]]::new()
|
||||
foreach ($relativePath in $coverage.covered) {
|
||||
$fullPath = Join-Path $root $relativePath
|
||||
if (-not (Test-Path -LiteralPath $fullPath)) {
|
||||
$violations.Add("missing covered file: $relativePath")
|
||||
continue
|
||||
}
|
||||
|
||||
$source = Get-Content -LiteralPath $fullPath -Raw -Encoding UTF8
|
||||
$styleMatches = [regex]::Matches($source, '(?is)<style\b[^>]*>(?<css>.*?)</style>')
|
||||
foreach ($styleMatch in $styleMatches) {
|
||||
$css = $styleMatch.Groups['css'].Value
|
||||
$ruleMatches = [regex]::Matches($css, '(?ms)(?<selector>[^{}]+)\{(?<body>[^{}]*)\}')
|
||||
foreach ($rule in $ruleMatches) {
|
||||
$selector = Normalize-Selector $rule.Groups['selector'].Value
|
||||
$body = $rule.Groups['body'].Value
|
||||
$checks = @(
|
||||
@{ Name = 'fixed-height'; Pattern = '(?im)(?<![-\w])height\s*:\s*\d+(?:\.\d+)?rpx\b' },
|
||||
@{ Name = 'background-stretch'; Pattern = '(?im)background\s*:[^;\r\n]*100%\s+100%[^;\r\n]*;' },
|
||||
@{ Name = 'direct-border-image-slice'; Pattern = '(?im)border-image-slice\s*:' }
|
||||
)
|
||||
foreach ($check in $checks) {
|
||||
if ($body -match $check.Pattern -and -not (Test-Allowlisted $relativePath $selector $check.Name)) {
|
||||
$violations.Add("$relativePath :: $selector :: $($check.Name)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($violations.Count -gt 0) {
|
||||
throw ("Responsive layout contract violations:`n - " + ($violations -join "`n - "))
|
||||
}
|
||||
|
||||
Write-Output 'PROJECT-RESPONSIVE-LAYOUT-CONTRACT PASS'
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"exceptions": [
|
||||
{ "path": "components/AppLoading.vue", "selector": ".app-loading--page .app-loading__seal", "property": "fixed-height", "reason": "Fixed-ratio loading artwork." },
|
||||
{ "path": "components/AppLoading.vue", "selector": ".app-loading--page .app-loading__knot", "property": "fixed-height", "reason": "Fixed-ratio loading ornament." },
|
||||
{ "path": "components/AppLoading.vue", "selector": ".app-loading--section .app-loading__seal", "property": "fixed-height", "reason": "Fixed-ratio loading artwork." },
|
||||
{ "path": "components/AppLoading.vue", "selector": ".app-loading--section .app-loading__knot", "property": "fixed-height", "reason": "Fixed-ratio loading ornament." },
|
||||
{ "path": "components/AppTabbar.vue", "selector": ".tab-icon", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
|
||||
|
||||
{ "path": "components/GenealogyCard.vue", "selector": ".surname-seal", "property": "fixed-height", "reason": "Fixed-ratio surname seal." },
|
||||
{ "path": "components/GenealogyCard.vue", "selector": ".surname-seal-copy", "property": "fixed-height", "reason": "Text overlay follows fixed seal artwork." },
|
||||
{ "path": "components/GenealogyCard.vue", "selector": ".card-meta-icon", "property": "fixed-height", "reason": "Fixed-ratio metadata icon." },
|
||||
{ "path": "components/GenealogyCard.vue", "selector": ".card-chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
|
||||
|
||||
{ "path": "components/PageHeader.vue", "selector": ".header-hall", "property": "fixed-height", "reason": "Fixed-ratio header artwork." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".header-icon-button", "property": "fixed-height", "reason": "Minimum header touch target." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".header-logo", "property": "fixed-height", "reason": "Fixed-ratio brand mark." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".header-notice-icon", "property": "fixed-height", "reason": "Fixed-ratio notice icon." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".notice-dot", "property": "fixed-height", "reason": "Fixed notification indicator." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".header-back", "property": "fixed-height", "reason": "Minimum header touch target." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".header-back__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
|
||||
{ "path": "components/PageHeader.vue", "selector": ".page-header--root .header-logo", "property": "fixed-height", "reason": "Fixed-ratio root brand mark." },
|
||||
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".title-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".input-icon", "property": "fixed-height", "reason": "Fixed-ratio form icon." },
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".password-toggle__icon", "property": "fixed-height", "reason": "Fixed-ratio password icon." },
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".divider-line", "property": "fixed-height", "reason": "One-pixel ornamental rule." },
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".divider-knot", "property": "fixed-height", "reason": "Fixed-ratio divider ornament." },
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".wechat-icon", "property": "fixed-height", "reason": "Fixed-ratio provider icon." },
|
||||
{ "path": "pages/auth/a01-entry.vue", "selector": ".agreement-icon", "property": "fixed-height", "reason": "Fixed-ratio agreement control." },
|
||||
|
||||
{ "path": "pages/auth/a04-register.vue", "selector": ".back-button__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
|
||||
{ "path": "pages/auth/a04-register.vue", "selector": ".register-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
|
||||
{ "path": "pages/auth/a04-register.vue", "selector": ".agreement-icon", "property": "fixed-height", "reason": "Fixed-ratio agreement control." },
|
||||
{ "path": "pages/auth/a05-reset-password.vue", "selector": ".back-button__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
|
||||
{ "path": "pages/auth/a05-reset-password.vue", "selector": ".reset-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
|
||||
{ "path": "pages/auth/a05-reset-password.vue", "selector": ".success-mark", "property": "fixed-height", "reason": "Fixed-ratio success icon." },
|
||||
{ "path": "pages/auth/a06-auth-status.vue", "selector": ".back-button__icon", "property": "fixed-height", "reason": "Fixed-ratio back icon." },
|
||||
{ "path": "pages/auth/a06-auth-status.vue", "selector": ".status-divider", "property": "fixed-height", "reason": "Fixed-ratio title ornament." },
|
||||
{ "path": "pages/auth/a06-auth-status.vue", "selector": ".status-icon", "property": "fixed-height", "reason": "Fixed-ratio status icon." },
|
||||
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".current-seal", "property": "fixed-height", "reason": "Fixed-ratio genealogy seal." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".current-info-divider", "property": "fixed-height", "reason": "One-pixel ornamental rule." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".current-meta-icon", "property": "fixed-height", "reason": "Fixed-ratio metadata icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".shortcut-icon", "property": "fixed-height", "reason": "Fixed-ratio shortcut icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".section-divider", "property": "fixed-height", "reason": "Fixed-ratio section ornament." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".section-heading::before", "property": "fixed-height", "reason": "Fixed heading accent." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".application-record__chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".create-icon", "property": "fixed-height", "reason": "Fixed-ratio action icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".create-action .create-icon", "property": "fixed-height", "reason": "Responsive override for fixed-ratio action icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".create-cloud", "property": "fixed-height", "reason": "Fixed-ratio decorative cloud." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".error-panel__seal", "property": "fixed-height", "reason": "Fixed-ratio state icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".error-panel__divider", "property": "fixed-height", "reason": "Fixed-ratio state ornament." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".empty-seal", "property": "fixed-height", "reason": "Fixed-ratio empty-state seal." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".empty-divider", "property": "fixed-height", "reason": "Fixed-ratio empty-state ornament." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".add-dialog__close", "property": "fixed-height", "reason": "Minimum close touch target." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".add-dialog__close-icon", "property": "fixed-height", "reason": "Fixed-ratio close icon." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".genealogy-switcher__close", "property": "fixed-height", "reason": "Minimum close touch target." },
|
||||
{ "path": "pages/genealogy/g01-my-genealogies.vue", "selector": ".genealogy-switcher__close-icon", "property": "fixed-height", "reason": "Fixed-ratio close icon." },
|
||||
|
||||
{ "path": "pages/genealogy/g05-genealogy-overview.vue", "selector": ".overview-state__seal", "property": "fixed-height", "reason": "Fixed-ratio state icon." },
|
||||
{ "path": "pages/genealogy/g06-search-genealogies.vue", "selector": ".search-divider", "property": "fixed-height", "reason": "Fixed-ratio section ornament." },
|
||||
{ "path": "pages/genealogy/g06-search-genealogies.vue", "selector": ".search-status__cloud", "property": "fixed-height", "reason": "Fixed-ratio decorative cloud." },
|
||||
{ "path": "pages/genealogy/g06-search-genealogies.vue", "selector": ".search-status__hall", "property": "fixed-height", "reason": "Fixed-ratio state artwork." },
|
||||
|
||||
{ "path": "pages/family/f09-media-upload.vue", "selector": ".media-album-card__cover", "property": "fixed-height", "reason": "Fixed-ratio media thumbnail." },
|
||||
{ "path": "pages/family/f09-media-upload.vue", "selector": ".media-photo-editor__thumb", "property": "fixed-height", "reason": "Fixed-ratio media thumbnail." },
|
||||
|
||||
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-hero__hall", "property": "fixed-height", "reason": "Fixed-ratio header artwork." },
|
||||
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-hero__cloud", "property": "fixed-height", "reason": "Fixed-ratio decorative cloud." },
|
||||
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-identity__seal", "property": "fixed-height", "reason": "Fixed-ratio identity seal." },
|
||||
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-section-heading image", "property": "fixed-height", "reason": "Fixed-ratio heading ornament." },
|
||||
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-menu__icon", "property": "fixed-height", "reason": "Fixed-ratio menu icon." },
|
||||
{ "path": "pages/profile/m01-profile-home.vue", "selector": ".profile-menu__chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
|
||||
{ "path": "pages/profile/m03-security-settings.vue", "selector": ".security-row>image", "property": "fixed-height", "reason": "Fixed-ratio security icon." },
|
||||
{ "path": "pages/profile/m03-security-settings.vue", "selector": ".security-row .chevron", "property": "fixed-height", "reason": "Fixed-ratio navigation icon." },
|
||||
{ "path": "pages/profile/m10-about-settings.vue", "selector": ".brand-card image", "property": "fixed-height", "reason": "Fixed-ratio brand mark." },
|
||||
{ "path": "pages/profile/m10-about-settings.vue", "selector": ".settings-row image", "property": "fixed-height", "reason": "Fixed-ratio settings icon." },
|
||||
|
||||
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".generation-band", "property": "fixed-height", "reason": "Fixed-height generation lane in the scrollable tree canvas." },
|
||||
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node", "property": "fixed-height", "reason": "Fixed-ratio interactive tree node." },
|
||||
{ "path": "pages/tree/t01-tree-overview.vue", "selector": ".member-node--selected", "property": "fixed-height", "reason": "Selected state preserves the fixed-ratio tree node." },
|
||||
{ "path": "pages/tree/t03-member-profile.vue", "selector": ".member-heading__seal", "property": "fixed-height", "reason": "Fixed-ratio member seal." }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"covered": [
|
||||
"components/AppButton.vue",
|
||||
"components/AppDialog.vue",
|
||||
"components/AppLoading.vue",
|
||||
"components/AppTabbar.vue",
|
||||
"components/AppToast.vue",
|
||||
"components/AuthPageShell.vue",
|
||||
"components/GenealogyCard.vue",
|
||||
"components/GenealogyPageBackground.vue",
|
||||
"components/ModulePage.vue",
|
||||
"components/ModulePageBackground.vue",
|
||||
"components/PageHeader.vue",
|
||||
"pages/auth/a01-entry.vue",
|
||||
"pages/auth/a04-register.vue",
|
||||
"pages/auth/a05-reset-password.vue",
|
||||
"pages/auth/a06-auth-status.vue",
|
||||
"pages/genealogy/g01-my-genealogies.vue",
|
||||
"pages/genealogy/g03-create-genealogy.vue",
|
||||
"pages/genealogy/g05-genealogy-overview.vue",
|
||||
"pages/genealogy/g06-search-genealogies.vue",
|
||||
"pages/genealogy/g08-join-application.vue",
|
||||
"pages/genealogy/g09-my-applications.vue",
|
||||
"pages/genealogy/g10-application-review.vue",
|
||||
"pages/genealogy/g11-genealogy-settings.vue",
|
||||
"pages/genealogy/g12-generation-poems.vue",
|
||||
"App.vue",
|
||||
"components/tree/TreeMemberForm.vue",
|
||||
"pages/family/f01-family-feed.vue",
|
||||
"pages/family/f02-publish-feed.vue",
|
||||
"pages/family/f03-feed-detail.vue",
|
||||
"pages/family/f04-article-list.vue",
|
||||
"pages/family/f05-article-detail.vue",
|
||||
"pages/family/f06-article-editor.vue",
|
||||
"pages/family/f07-album-list.vue",
|
||||
"pages/family/f08-album-detail.vue",
|
||||
"pages/family/f09-media-upload.vue",
|
||||
"pages/family/f10-video-list.vue",
|
||||
"pages/notification/n01-message-center.vue",
|
||||
"pages/notification/n02-message-detail.vue",
|
||||
"pages/profile/m01-profile-home.vue",
|
||||
"pages/profile/m02-edit-profile.vue",
|
||||
"pages/profile/m03-security-settings.vue",
|
||||
"pages/profile/m04-change-password.vue",
|
||||
"pages/profile/m05-change-phone.vue",
|
||||
"pages/profile/m06-help-center.vue",
|
||||
"pages/profile/m07-feedback.vue",
|
||||
"pages/profile/m08-promotion.vue",
|
||||
"pages/profile/m09-vip-orders.vue",
|
||||
"pages/profile/m10-about-settings.vue",
|
||||
"pages/records/r01-people-list.vue",
|
||||
"pages/records/r02-person-detail.vue",
|
||||
"pages/records/r03-gift-list.vue",
|
||||
"pages/records/r04-gift-editor.vue",
|
||||
"pages/records/r05-ritual-list.vue",
|
||||
"pages/records/r06-ritual-detail.vue",
|
||||
"pages/records/r07-ritual-editor.vue",
|
||||
"pages/records/r08-growth-journal.vue",
|
||||
"pages/records/r09-life-events.vue",
|
||||
"pages/records/r10-memo-list.vue",
|
||||
"pages/records/r11-merit-records.vue",
|
||||
"pages/tree/t01-tree-overview.vue",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -60,7 +60,7 @@ foreach ($selector in @('card-meta', 'card-updated', 'card-role')) {
|
||||
if ($dialog -match '(?s)\.app-dialog\s*\{[^}]*position\s*:|(?s)\.app-dialog__content\s*\{[^}]*position\s*:') {
|
||||
throw 'AppDialog internal content must not use positioning'
|
||||
}
|
||||
Assert-Match $dialog '(?s)\.app-dialog\s*\{[^}]*background:\s*url\("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3\.png"\)' 'AppDialog must render its approved dialog asset as the dialog background'
|
||||
Assert-Match $dialog '(?s)\.app-dialog\s*\{[^}]*@include\s+adaptive\.adaptive-auth-dialog;' 'AppDialog must consume the approved adaptive dialog frame profile'
|
||||
if ($loading -match '(?im)(?<![-\w])position\s*:') { throw 'AppLoading must stay in its parent document flow' }
|
||||
if ($toast -match '(?s)\.app-toast__copy\s*\{[^}]*position\s*:') { throw 'AppToast copy must not create an unnecessary positioning context' }
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ foreach ($token in @(
|
||||
}
|
||||
|
||||
$dialog = Read-Utf8 'components/AppDialog.vue'
|
||||
foreach ($token in @('role="dialog"', 'aria-modal="true"', 'tabindex="-1"', '@keydown.esc.stop="cancel"', 'max-height: calc(100vh - 80rpx)', 'overflow-y: auto')) {
|
||||
foreach ($token in @('role="dialog"', 'aria-modal="true"', 'tabindex="-1"', '@keydown.esc.stop="cancel"', 'max-height: calc(var(--app-viewport-height, 100vh) - 160rpx)', 'overflow-y: auto')) {
|
||||
if (-not $dialog.Contains($token)) { throw "AppDialog accessibility contract missing: $token" }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ function Assert-Contains {
|
||||
}
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
$profiles = Get-Content -LiteralPath (Join-Path $root 'styles/adaptive-frame-profiles.scss') -Raw -Encoding utf8
|
||||
$paths = @(
|
||||
'pages/tree/t03-member-profile.vue',
|
||||
'pages/tree/t04-add-relative.vue',
|
||||
@@ -27,7 +28,7 @@ foreach ($path in @('pages/tree/t03-member-profile.vue', 'pages/tree/t07-member-
|
||||
}
|
||||
|
||||
$t03 = $pages['pages/tree/t03-member-profile.vue']
|
||||
foreach ($required in @('member-state--detail', 'member-state--error', 't01-state-panel.png', 't07-search-input-frame.png', '/pages/tree/t05-edit-member', 'query.personId')) {
|
||||
foreach ($required in @('member-state--detail', 'member-state--error', 'adaptive.adaptive-tree-panel', 'adaptive.adaptive-tree-field', '/pages/tree/t05-edit-member', 'query.personId')) {
|
||||
Assert-Contains $t03 $required "Missing T03 contract: $required"
|
||||
}
|
||||
|
||||
@@ -47,15 +48,19 @@ foreach ($entry in $specializedForms.GetEnumerator()) {
|
||||
}
|
||||
|
||||
$t07 = $pages['pages/tree/t07-member-directory.vue']
|
||||
foreach ($required in @('directory-state--list', 'directory-state--empty', 'directory-state--error', 'list-slip-frame.png', 'directory-card__status', '/pages/tree/t03-member-profile')) {
|
||||
foreach ($required in @('directory-state--list', 'directory-state--empty', 'directory-state--error', 'adaptive.adaptive-genealogy-list-card', 'directory-card__status', '/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', 't01-state-panel.png', 'query.state')) {
|
||||
foreach ($required in @('member-status--privacy', 'member-status--deceased', 'member-status--forbidden', 'adaptive.adaptive-tree-panel', 'query.state')) {
|
||||
Assert-Contains $t08 $required "Missing T08 contract: $required"
|
||||
}
|
||||
|
||||
foreach ($asset in @('t01-state-panel.png', 't07-search-input-frame.png', 'list-slip-frame.png')) {
|
||||
Assert-Contains $profiles $asset "Adaptive tree profile missing: $asset"
|
||||
}
|
||||
|
||||
$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" }
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = Split-Path $PSScriptRoot -Parent
|
||||
$component = [System.IO.File]::ReadAllText((Join-Path $root 'components/tree/TreeMemberForm.vue'), [System.Text.Encoding]::UTF8)
|
||||
$profiles = [System.IO.File]::ReadAllText((Join-Path $root 'styles/adaptive-frame-profiles.scss'), [System.Text.Encoding]::UTF8)
|
||||
|
||||
foreach ($expected in @(
|
||||
'grid-template-columns: auto minmax(0, 1fr)',
|
||||
'min-height: min(640px, calc((100vw - 16px) * 1.48))',
|
||||
'background: url("/static/assets/modules/tree/transparent/t01-state-panel.png")',
|
||||
'background: url("/static/assets/modules/tree/transparent/t07-search-input-frame.png")'
|
||||
'@include adaptive.adaptive-tree-panel;',
|
||||
'@include adaptive.adaptive-tree-field;'
|
||||
)) {
|
||||
if (-not $component.Contains($expected)) { throw "TreeMemberForm document-flow contract missing: $expected" }
|
||||
}
|
||||
foreach ($asset in @('t01-state-panel.png', 't07-search-input-frame.png')) {
|
||||
if (-not $profiles.Contains($asset)) { throw "Adaptive tree profile missing: $asset" }
|
||||
}
|
||||
|
||||
if ($component -match '(?m)\bposition\s*:') {
|
||||
throw 'TreeMemberForm must not retain positioning in ordinary forms, fields, actions, or result content'
|
||||
|
||||
Reference in New Issue
Block a user