$ErrorActionPreference = 'Stop' function Read-Page([string]$RelativePath) { return [System.IO.File]::ReadAllText( (Join-Path (Split-Path -Parent $PSScriptRoot) $RelativePath), [System.Text.Encoding]::UTF8 ) } function Assert-Contains([string]$Content, [string]$Expected, [string]$Message) { if (-not $Content.Contains($Expected)) { throw $Message } } $t03 = Read-Page 'pages/tree/t03-member-profile.vue' $t04 = Read-Page 'pages/tree/t04-add-relative.vue' $t05 = Read-Page 'pages/tree/t05-edit-member.vue' $t06 = Read-Page 'pages/tree/t06-edit-relationship.vue' $t08 = Read-Page 'pages/tree/t08-member-states.vue' Assert-Contains $t03 '{{ memberContextDescription }}' 'T03 must render its state-specific context description' if ($t03 -notmatch '(?s)const\s+memberContextDescription\s*=\s*computed\(\(\)\s*=>\s*\(\{\s*loading:\s*"\u6B63\u5728\u8BFB\u53D6\u6210\u5458\u8D44\u6599",\s*detail:\s*"\u6210\u5458\u8EAB\u4EFD\u4E0E\u4EB2\u5C5E\u5173\u7CFB",\s*restricted:\s*"\u9690\u79C1\u6210\u5458\u4EC5\u5C55\u793A\u57FA\u7840\u8EAB\u4EFD",\s*error:\s*"\u8BF7\u91CD\u65B0\u9009\u62E9\u6210\u5458"') { throw 'T03 must keep distinct loading, detail, restricted, and error context copy' } foreach ($entry in @( @{ Name = 'T04'; Content = $t04 }, @{ Name = 'T05'; Content = $t05 }, @{ Name = 'T06'; Content = $t06 } )) { if ($entry.Content -match ')|import\s+TreeMemberForm') { throw "$($entry.Name) must own its business workflow instead of rendering TreeMemberForm" } foreach ($nativeUi in @('uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet')) { if ($entry.Content.Contains($nativeUi)) { throw "$($entry.Name) must not use $nativeUi" } } } foreach ($required in @( 'const addState = ref("form")', 'const addForm = reactive({', 'const relationOptions = [', 'const genderOptions = [', 'const isFirstMember = computed(', 'query.personId', 'query.mode === "first"', 'fieldErrors.name', 'fieldErrors.relation', 'add-state--form', 'add-state--preview', 'add-state--error', 'class="add-relative-form"', ']*>(.*?)').Groups[1].Value $t04PanelStyle = [regex]::Match($t04Style, '(?ms)^\s*\.add-relative-panel\s*\{(?.*?)\}').Groups['body'].Value if ($t04PanelStyle -match '\bmin-height\s*:') { throw 'T04 add-relative panel height must be driven by its current content' } foreach ($required in @( 'const editState = ref("form")', 'const editForm = reactive({', 'findTreeMemberPresentationFixture(genealogyId.value, id)', 'const isDirty = computed(', 'const discardDialogVisible = ref(false)', 'query.personId', 'edit-state--form', 'edit-state--preview', 'edit-state--error', 'edit-state--no-permission', 'class="edit-member-form"', ']*>(.*?)').Groups[1].Value $t05PanelStyle = [regex]::Match($t05Style, '(?ms)^\s*\.edit-member-panel\s*\{(?.*?)\}').Groups['body'].Value if ($t05PanelStyle -match '\bmin-height\s*:') { throw 'T05 edit-member panel height must be driven by its current content' } foreach ($required in @( 'const relationshipState = ref("form")', 'const relationshipForm = reactive({', 'const memberOptions = computed(', 'const relationshipOptions = [', 'const relationshipPreview = computed(', 'const validateRelationship = () =>', 'query.personId', 'relationship-state--form', 'relationship-state--preview', 'relationship-state--conflict', 'relationship-state--error', 'class="relationship-form"', ']*>(.*?)').Groups[1].Value $t06PanelStyle = [regex]::Match($t06Style, '(?ms)^\s*\.relationship-panel\s*\{(?.*?)\}').Groups['body'].Value if ($t06PanelStyle -match '\bmin-height\s*:') { throw 'T06 relationship panel height must be driven by its current content' } foreach ($required in @( 'const genealogyId = ref("")', 'const personId = ref("")', 'const statusState = ref("loading")', 'findTreeMemberPresentationFixture(genealogyId.value, personId.value)', 'query.personId', 'query.state', 'member-status--privacy', 'member-status--deceased', 'member-status--forbidden', 'member-status--error', 'class="member-status-context"' )) { Assert-Contains $t08 $required "T08 missing member-driven status contract: $required" } if ($t08 -match 'class="status-tabs"|const\s+tabs\s*=|@click="statusState\s*=') { throw 'T08 must not let the user switch among demonstration states' } foreach ($required in @( 'findTreeMemberPresentationFixture(genealogyId.value, normalizedPersonId)', 'query.personId', 'const openMemberState = () =>', 'openPage("T08", { genealogyId: genealogyId.value, personId: personId.value }, "T03")' )) { Assert-Contains $t03 $required "T03 missing semantic member-state entry: $required" } Write-Output 'T03-T08-BUSINESS-SPECIALIZATION-CONTRACT PASS'