$ErrorActionPreference = 'Stop' function Assert-Contains { param([string]$Content, [string]$Expected, [string]$Message) if ($Content -notmatch [regex]::Escape($Expected)) { throw $Message } } $root = Split-Path -Parent $PSScriptRoot $pagePath = Join-Path $root 'pages/tree/t01-tree-overview.vue' $t02Path = Join-Path $root 'pages/tree/t02-pedigree-overview.vue' $page = Get-Content -LiteralPath $pagePath -Raw -Encoding utf8 $pedigree = Get-Content -LiteralPath $t02Path -Raw -Encoding utf8 $pages = Get-Content -LiteralPath (Join-Path $root 'pages.json') -Raw -Encoding utf8 if (-not (Test-Path -LiteralPath $t02Path)) { throw 'T02 pedigree route file must exist' } if ($pages -notmatch 'pages/tree/t02-pedigree-overview') { throw 'T02 pedigree route must be registered in pages.json' } foreach ($required in @( 'appApi.getTree', 'createRequestController', 'isRequestCancelled', 'treeRequestController.abort()', 'onShow(() =>', 'requestController: treeRequestController', 'genealogyContext.isCurrentGenealogyInvalidated()', 'genealogyContext.setCurrentGenealogyId(genealogyId.value)' )) { Assert-Contains $page $required "Missing T01 remote data contract: $required" } foreach ($required in @( '@action="toPedigree"', 'const treeState = ref("loading")', 'tree-state--tree', 'tree-state--landscape', 'tree-state--empty', 'tree-state--error', 'tree-canvas--state', 'v-for="member in layoutMembers"', 'class="member-node__portrait"', 'class="lineage-pan-cue"', 'scroll-x', ':scroll-left="treeState === ''tree'' ? treeScrollLeft : 0"', '@scroll="handleTreeScroll"', ':style="nodeGridStyle(member)"', 'const nodeGridStyle = (member)', 'class="generation-rail"', 'class="generation-band"', 'v-for="row in generationRows"', ':style="generationBandStyle(row)"', 'const treeMetrics = computed(', 'const NODE_HALF_HEIGHT = 112;', 'const GENERATION_GAP = 296;', ':style="treeState === ''tree'' ? treeMetricsStyle : undefined"', 'const recenterSelectedMember = async () =>', 'class="tree-recenter"', 'class="member-action-profile"', 'const openMemberPanel = (member) =>', '"T03",', 'routeKey: "T04"', '"T06",', 'openPage("T07"' )) { Assert-Contains $page $required "Missing T01 tree contract: $required" } foreach ($required in @( '@action="toTree"', 'const pedigreePages = computed(() =>', 'class="pedigree-sheet"', 'class="pedigree-column pedigree-column--legend"', 'v-for="(page, pageIndex) in pedigreePages"', 'v-for="(member, memberIndex) in page.members"', 'class="pedigree-swiper"', ':disable-touch="false"', 'scroll-y', 'const handlePedigreePageChange = (event)', 'const toTree = () =>' )) { Assert-Contains $pedigree $required "Missing T02 pedigree contract: $required" } foreach ($fixtureToken in @('getGenealogyFixtureAccess', 'treeMembers', 'mockTree')) { if ($page -match [regex]::Escape($fixtureToken)) { throw "T01 must not use a fixture as its remote tree source: $fixtureToken" } } $invalidatedGuardIndex = $page.IndexOf('genealogyContext.isCurrentGenealogyInvalidated()') $contextWriteIndex = $page.IndexOf('genealogyContext.setCurrentGenealogyId(genealogyId.value)') if ($invalidatedGuardIndex -lt 0 -or $contextWriteIndex -le $invalidatedGuardIndex) { throw 'T01 must reject an invalidated genealogy before any route can rewrite the current context' } if ($page -match '