This commit is contained in:
rain
2026-07-21 21:02:55 +08:00
parent 5d99bae3f2
commit a127280ce6
12 changed files with 1131 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
# SDD Progress
Plan: docs/superpowers/plans/2026-07-21-page-header-safe-area-g03.md
Branch: fix/page-header-safe-area
Base: 779a442
Task 1: complete (commits 779a442..2f22df6, review clean)
Task 2: complete (commits 2f22df6..a0d19f4, review clean after strict-regex fix)
Task 3: complete (verification only at a0d19f4, review clean; 8 contracts + 2 H5 smokes pass)
@@ -0,0 +1,143 @@
# Review package: 779a442..2f22df6
## Commits
2f22df6 fix: adapt shared page header to safe area
## Files changed
components/PageHeader.vue | 12 ++++++++++--
tests/shared-component-document-flow-contract.ps1 | 4 ++++
tests/shared-interaction-accessibility-contract.ps1 | 10 ++++++++++
3 files changed, 24 insertions(+), 2 deletions(-)
## Diff
diff --git a/components/PageHeader.vue b/components/PageHeader.vue
index 5ef28dd..17ab7ef 100644
--- a/components/PageHeader.vue
+++ b/components/PageHeader.vue
@@ -67,52 +67,60 @@
</view>
</template>
<script setup>
const props = defineProps({
title: { type: String, required: true },
action: { type: String, default: "" },
root: { type: Boolean, default: false },
unreadCount: { type: Number, default: 0 },
fallbackUrl: { type: String, default: "/pages/genealogy/g01-my-genealogies" },
+ customBack: { type: Boolean, default: false },
});
+const emit = defineEmits(["brand", "notice", "action", "back"]);
+
const goBack = () => {
+ if (props.customBack) {
+ emit("back");
+ return;
+ }
const stack = getCurrentPages();
if (stack.length > 1) {
uni.navigateBack();
return;
}
uni.reLaunch({ url: props.fallbackUrl });
};
</script>
<style scoped lang="scss">
.page-header-slot {
- height: 104rpx;
+ height: calc(104rpx + var(--status-bar-height, 0px));
flex: none;
}
.page-header-slot--root {
height: calc(124rpx + var(--status-bar-height, 0px));
}
.page-header {
position: fixed;
top: 0;
right: 0;
left: 0;
z-index: 30;
display: flex;
align-items: center;
- height: 104rpx;
+ height: calc(104rpx + var(--status-bar-height, 0px));
padding: 0 24rpx;
+ padding-top: var(--status-bar-height, 0px);
box-sizing: border-box;
background: $brand-red;
color: #fff9ed;
}
.page-header--root {
height: calc(124rpx + var(--status-bar-height, 0px));
padding-top: var(--status-bar-height, 0px);
background-color: #b52e22;
overflow: hidden;
diff --git a/tests/shared-component-document-flow-contract.ps1 b/tests/shared-component-document-flow-contract.ps1
index e7b88ae..71e6b5f 100644
--- a/tests/shared-component-document-flow-contract.ps1
+++ b/tests/shared-component-document-flow-contract.ps1
@@ -28,20 +28,24 @@ if ($header -match '(?s)\.page-header--root \.header-side,\s*\.page-header--root
throw 'PageHeader root content must not use positioning'
}
Assert-Match $header '(?s)\.header-icon-button\s*\{[^}]*display:\s*grid;[^}]*place-items:\s*center;' 'PageHeader icon button must use a grid overlay'
if ($header -match '(?s)\.header-icon-button\s*\{[^}]*position\s*:') {
throw 'PageHeader icon button must not be a positioning context'
}
Assert-Match $header '(?s)\.header-logo,\s*\.header-notice-icon,\s*\.notice-dot\s*\{[^}]*grid-area:\s*1 / 1;' 'PageHeader icon and notice dot must share one grid cell'
if ($header -match '(?s)\.notice-dot\s*\{[^}]*position\s*:') {
throw 'PageHeader notice dot must use grid alignment instead of positioning'
}
+Assert-Match $header '(?s)\.page-header-slot\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader secondary slot must reserve status bar plus 104rpx content height'
+Assert-Match $header '(?s)\.page-header\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);[^}]*padding-top:\s*var\(--status-bar-height, 0px\);' 'PageHeader secondary header must place its 104rpx content below the status bar'
+Assert-Match $header '(?s)\.page-header-slot--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root slot height must remain unchanged'
+Assert-Match $header '(?s)\.page-header--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root height must remain unchanged'
if ($card -match '(?im)(?<![-\w])position\s*:') {
throw 'GenealogyCard must use grid instead of positioning'
}
Assert-Match $card '(?s)\.genealogy-card\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*72rpx minmax\(0, 1fr\);[^}]*column-gap:\s*22rpx;' 'GenealogyCard must own the two-column document-flow grid'
Assert-Match $card '(?s)\.row-frame\s*\{[^}]*grid-area:\s*1 / 1 / 2 / -1;[^}]*z-index:\s*1;[^}]*width:\s*calc\(100% \+ var\(--card-padding-x\) \+ var\(--card-padding-x\)\);[^}]*height:\s*calc\(100% \+ var\(--card-padding-y\) \+ var\(--card-padding-y\)\);[^}]*margin:\s*calc\(-1 \* var\(--card-padding-y\)\) calc\(-1 \* var\(--card-padding-x\)\);' 'GenealogyCard row frame must span the padded card grid above the fixed page background'
Assert-Match $card '(?s)\.surname-seal\s*\{[^}]*display:\s*grid;[^}]*grid-row:\s*1;[^}]*z-index:\s*2;[^}]*place-items:\s*center;' 'GenealogyCard surname seal must share the frame row and stay above it'
Assert-Match $card '(?s)\.card-main\s*\{[^}]*grid-row:\s*1;[^}]*z-index:\s*2;' 'GenealogyCard content must share the frame row and stay above it'
Assert-Match $card '(?s)\.surname-seal-frame,\s*\.surname-seal-copy\s*\{[^}]*grid-area:\s*1 / 1;' 'GenealogyCard seal frame and copy must share one grid cell'
if ($card -match '(?s)\.card-name\s*\{[^}]*(?:overflow:\s*hidden|text-overflow:\s*ellipsis|white-space:\s*nowrap)') {
diff --git a/tests/shared-interaction-accessibility-contract.ps1 b/tests/shared-interaction-accessibility-contract.ps1
index 84ad70c..1059467 100644
--- a/tests/shared-interaction-accessibility-contract.ps1
+++ b/tests/shared-interaction-accessibility-contract.ps1
@@ -10,20 +10,30 @@ if ($button -match '<view\s+[^>]*class="app-button"') { throw 'AppButton must us
$header = Read-Utf8 'components/PageHeader.vue'
foreach ($pattern in @(
'(?s)<button[^>]+class="header-back"[^>]+aria-label=',
'(?s)<button[^>]+class="header-icon-button header-notice"[^>]+aria-label=',
'(?s)<button[^>]+class="header-icon-button"[^>]+aria-label='
)) {
if ($header -notmatch $pattern) { throw "PageHeader accessibility contract missing: $pattern" }
}
if (-not $header.Contains('fallbackUrl')) { throw 'PageHeader must expose a deep-link fallback route' }
+foreach ($token in @(
+ 'customBack: { type: Boolean, default: false }',
+ 'const emit = defineEmits(["brand", "notice", "action", "back"]);',
+ 'if (props.customBack) {',
+ 'emit("back");',
+ 'uni.navigateBack();',
+ 'uni.reLaunch({ url: props.fallbackUrl });'
+)) {
+ if (-not $header.Contains($token)) { throw "PageHeader back contract missing: $token" }
+}
$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')) {
if (-not $dialog.Contains($token)) { throw "AppDialog accessibility contract missing: $token" }
}
$toast = Read-Utf8 'components/AppToast.vue'
foreach ($token in @('v-show="visible"', 'role="status"', 'aria-live="polite"', 'aria-atomic="true"')) {
if (-not $toast.Contains($token)) { throw "AppToast accessibility contract missing: $token" }
}
@@ -0,0 +1,229 @@
# Review package: 2f22df6..9362c47
## Commits
9362c47 refactor: reuse shared header on G03
## Files changed
pages/genealogy/g03-create-genealogy.vue | 62 ++++----------------------------
tests/g03-create-flow-contract.ps1 | 9 +++--
tests/g03-document-flow-contract.ps1 | 4 ++-
tests/g03-visual-states-contract.ps1 | 4 +--
4 files changed, 17 insertions(+), 62 deletions(-)
## Diff
diff --git a/pages/genealogy/g03-create-genealogy.vue b/pages/genealogy/g03-create-genealogy.vue
index 2bc757d..f37cadc 100644
--- a/pages/genealogy/g03-create-genealogy.vue
+++ b/pages/genealogy/g03-create-genealogy.vue
@@ -1,30 +1,20 @@
<!-- 页面编号:G-03;用途:创建家谱与录入首代人物的同路由两步流程。 -->
<template>
<view class="flow-page">
<GenealogyPageBackground />
- <view class="flow-header">
- <view class="flow-header__content">
- <view class="flow-header__back" @click="goBack">
- <image
- class="flow-header__back-icon"
- src="/static/assets/foundation/transparent/chevron-right.png"
- mode="aspectFit"
- />
- </view>
- <text class="flow-header__title">{{
- isAncestorStep ? "录入首代人物" : "创建家谱"
- }}</text>
- <view class="flow-header__side" />
- </view>
- </view>
+ <PageHeader
+ :title="isAncestorStep ? '录入首代人物' : '创建家谱'"
+ custom-back
+ @back="goBack"
+ />
<view class="flow-content">
<view class="create-flow-panel">
<view v-if="!isAncestorStep" class="create-flow-panel__content">
<view class="flow-step-label"><text>第一步 · 立谱信息</text></view>
<text class="flow-heading">为家族立一部可传承的谱</text>
<text class="flow-note"
>家谱建立后可继续完善,名称与访问规则由创建者维护。</text
>
@@ -241,20 +231,21 @@
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
+import PageHeader from "@/components/PageHeader.vue";
const currentStep = ref("create");
const genealogyId = ref("");
const isSubmitting = ref(false);
const createState = ref("form");
const ancestorState = ref("form");
const duplicateReminderVisible = ref(false);
const fieldErrors = reactive({
surname: "",
name: "",
@@ -416,61 +407,20 @@ const enterOverview = () =>
</script>
<style scoped lang="scss">
.flow-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: #f9f6ef;
}
-.flow-header {
- z-index: 3;
- height: 112rpx;
- overflow: hidden;
- background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")
- center / 100% 100% no-repeat;
-}
-
-.flow-header__content {
- z-index: 1;
- display: flex;
- height: 100%;
- align-items: center;
- justify-content: space-between;
- padding: 0 26rpx;
- box-sizing: border-box;
-}
-
-.flow-header__back,
-.flow-header__side {
- display: flex;
- width: 72rpx;
- align-items: center;
-}
-
-.flow-header__back-icon {
- width: 34rpx;
- height: 34rpx;
- transform: rotate(180deg);
-}
-
-.flow-header__title {
- flex: 1;
- color: #ffe4a7;
- font-family: "STKaiti", "KaiTi", serif;
- font-size: 39rpx;
- font-weight: 700;
- letter-spacing: 4rpx;
- text-align: center;
-}
-
.flow-content {
z-index: 2;
padding: 24rpx 32rpx 48rpx;
}
.create-flow-panel {
min-height: 1000rpx;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
diff --git a/tests/g03-create-flow-contract.ps1 b/tests/g03-create-flow-contract.ps1
index 4accfd6..89923c2 100644
--- a/tests/g03-create-flow-contract.ps1
+++ b/tests/g03-create-flow-contract.ps1
@@ -30,29 +30,32 @@ foreach ($required in @(
'window.addEventListener("hashchange", syncFlowFromRoute)',
'step=ancestor&genealogyId=',
'const createState = ref("form");',
'const ancestorState = ref("form");',
'const fieldErrors = reactive({',
'const duplicateReminderVisible = ref(false);',
'class="duplicate-reminder-layer"',
'if (isSubmitting.value) return;',
'/pages/genealogy/g05-genealogy-overview?genealogyId=',
'g01-empty-panel-frame.png',
- 'root-header-cinnabar.jpg',
'a01-scroll-primary-v3.png',
'create-flow-panel',
- 'flow-primary-action'
+ 'flow-primary-action',
+ 'import PageHeader from "@/components/PageHeader.vue";',
+ '<PageHeader',
+ 'custom-back',
+ '@back="goBack"'
)) {
Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required"
}
-foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=')) {
+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" }
}
foreach ($className in @('create-flow-panel', 'flow-primary-action')) {
Assert-NoCssSurface -Content $g03 -ClassName $className
}
if (-not (Test-Path -LiteralPath $panelPath)) { throw 'G03 requires the accepted genealogy paper panel bitmap' }
Add-Type -AssemblyName System.Drawing
$panel = [System.Drawing.Bitmap]::FromFile($panelPath)
diff --git a/tests/g03-document-flow-contract.ps1 b/tests/g03-document-flow-contract.ps1
index 0249541..654dfa7 100644
--- a/tests/g03-document-flow-contract.ps1
+++ b/tests/g03-document-flow-contract.ps1
@@ -1,15 +1,17 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$page = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
$style = [regex]::Match($page, '(?s)<style[^>]*>(.*?)</style>').Groups[1].Value
$positions = [regex]::Matches([regex]::Replace($style, '(?s)/\*.*?\*/', ''), '(?m)\bposition\s*:\s*([^;]+);')
if ($positions.Count -ne 1 -or $positions[0].Groups[1].Value.Trim() -ne 'fixed') {
throw "G03 must retain only its combined fixed dialog layer rule; found $($positions.Count) position declarations"
}
foreach ($required in @(
- 'background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")',
'background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")',
'background: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png")',
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G03 is missing real asset background: $required" } }
+foreach ($forbidden in @('flow-header', 'root-header-cinnabar.jpg')) {
+ if ($page -match [regex]::Escape($forbidden)) { throw "G03 must not retain private header token: $forbidden" }
+}
Write-Output 'G03-DOCUMENT-FLOW-CONTRACT PASS'
diff --git a/tests/g03-visual-states-contract.ps1 b/tests/g03-visual-states-contract.ps1
index 5342acb..cfb7911 100644
--- a/tests/g03-visual-states-contract.ps1
+++ b/tests/g03-visual-states-contract.ps1
@@ -2,22 +2,22 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$g03 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
function Assert-Match {
param([string]$Content, [string]$Pattern, [string]$Message)
if ($Content -notmatch $Pattern) { throw $Message }
}
if ($g03 -match '<text>返回</text>') { throw 'G03 header must use only the approved image back arrow' }
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back,\s*\.flow-header__side\s*\{[^}]*width:\s*72rpx;' -Message 'G03 header side slots must preserve centered title after removing back text'
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back-icon\s*\{[^}]*width:\s*34rpx;[^}]*height:\s*34rpx;' -Message 'G03 back arrow must use the approved visible size'
+Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+:title="isAncestorStep \? .\u5F55\u5165\u9996\u4EE3\u4EBA\u7269. : .\u521B\u5EFA\u5BB6\u8C31."\s+custom-back\s+@back="goBack"\s*/>' -Message 'G03 must use PageHeader with its dynamic title and custom back handler'
+if ($g03 -match 'flow-header|flow-header__back|flow-header__title') { throw 'G03 must not retain a private header implementation' }
Assert-Match -Content $g03 -Pattern '(?s)\.flow-note,\s*\.flow-rule__note\s*\{[^}]*font-size:\s*25rpx;' -Message 'G03 guidance copy must use the approved readable size'
Assert-Match -Content $g03 -Pattern '(?s)\.flow-rule__option\s*\{[^}]*font-size:\s*23rpx;' -Message 'G03 visibility options must use the approved readable size'
Assert-Match -Content $g03 -Pattern '(?s)\.field-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*34rpx;' -Message 'G03 field errors must use the approved readable size'
Assert-Match -Content $g03 -Pattern '(?s)\.flow-error\s*\{[^}]*font-size:\s*25rpx;[^}]*line-height:\s*36rpx;' -Message 'G03 submit errors must use the approved readable size'
foreach ($asset in @('a01-scroll-dialog-v3.png', 'a01-scroll-primary-v3.png', 'a01-scroll-secondary-v3.png')) {
if ($g03 -notmatch [regex]::Escape($asset)) { throw "G03 must retain approved shared asset: $asset" }
}
Write-Output 'G03-VISUAL-STATES-CONTRACT PASS'
@@ -0,0 +1,230 @@
# Review package: 2f22df6..a0d19f4
## Commits
a0d19f4 test: tighten G03 shared header contract
9362c47 refactor: reuse shared header on G03
## Files changed
pages/genealogy/g03-create-genealogy.vue | 62 ++++----------------------------
tests/g03-create-flow-contract.ps1 | 9 +++--
tests/g03-document-flow-contract.ps1 | 4 ++-
tests/g03-visual-states-contract.ps1 | 4 +--
4 files changed, 17 insertions(+), 62 deletions(-)
## Diff
diff --git a/pages/genealogy/g03-create-genealogy.vue b/pages/genealogy/g03-create-genealogy.vue
index 2bc757d..f37cadc 100644
--- a/pages/genealogy/g03-create-genealogy.vue
+++ b/pages/genealogy/g03-create-genealogy.vue
@@ -1,30 +1,20 @@
<!-- 页面编号:G-03;用途:创建家谱与录入首代人物的同路由两步流程。 -->
<template>
<view class="flow-page">
<GenealogyPageBackground />
- <view class="flow-header">
- <view class="flow-header__content">
- <view class="flow-header__back" @click="goBack">
- <image
- class="flow-header__back-icon"
- src="/static/assets/foundation/transparent/chevron-right.png"
- mode="aspectFit"
- />
- </view>
- <text class="flow-header__title">{{
- isAncestorStep ? "录入首代人物" : "创建家谱"
- }}</text>
- <view class="flow-header__side" />
- </view>
- </view>
+ <PageHeader
+ :title="isAncestorStep ? '录入首代人物' : '创建家谱'"
+ custom-back
+ @back="goBack"
+ />
<view class="flow-content">
<view class="create-flow-panel">
<view v-if="!isAncestorStep" class="create-flow-panel__content">
<view class="flow-step-label"><text>第一步 · 立谱信息</text></view>
<text class="flow-heading">为家族立一部可传承的谱</text>
<text class="flow-note"
>家谱建立后可继续完善,名称与访问规则由创建者维护。</text
>
@@ -241,20 +231,21 @@
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, onMounted, onUnmounted, reactive, ref } from "vue";
import { onLoad, onShow } from "@dcloudio/uni-app";
import GenealogyPageBackground from "@/components/GenealogyPageBackground.vue";
+import PageHeader from "@/components/PageHeader.vue";
const currentStep = ref("create");
const genealogyId = ref("");
const isSubmitting = ref(false);
const createState = ref("form");
const ancestorState = ref("form");
const duplicateReminderVisible = ref(false);
const fieldErrors = reactive({
surname: "",
name: "",
@@ -416,61 +407,20 @@ const enterOverview = () =>
</script>
<style scoped lang="scss">
.flow-page {
display: flex;
min-height: 100vh;
flex-direction: column;
background: #f9f6ef;
}
-.flow-header {
- z-index: 3;
- height: 112rpx;
- overflow: hidden;
- background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")
- center / 100% 100% no-repeat;
-}
-
-.flow-header__content {
- z-index: 1;
- display: flex;
- height: 100%;
- align-items: center;
- justify-content: space-between;
- padding: 0 26rpx;
- box-sizing: border-box;
-}
-
-.flow-header__back,
-.flow-header__side {
- display: flex;
- width: 72rpx;
- align-items: center;
-}
-
-.flow-header__back-icon {
- width: 34rpx;
- height: 34rpx;
- transform: rotate(180deg);
-}
-
-.flow-header__title {
- flex: 1;
- color: #ffe4a7;
- font-family: "STKaiti", "KaiTi", serif;
- font-size: 39rpx;
- font-weight: 700;
- letter-spacing: 4rpx;
- text-align: center;
-}
-
.flow-content {
z-index: 2;
padding: 24rpx 32rpx 48rpx;
}
.create-flow-panel {
min-height: 1000rpx;
background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")
center / 100% 100% no-repeat;
}
diff --git a/tests/g03-create-flow-contract.ps1 b/tests/g03-create-flow-contract.ps1
index 4accfd6..89923c2 100644
--- a/tests/g03-create-flow-contract.ps1
+++ b/tests/g03-create-flow-contract.ps1
@@ -30,29 +30,32 @@ foreach ($required in @(
'window.addEventListener("hashchange", syncFlowFromRoute)',
'step=ancestor&genealogyId=',
'const createState = ref("form");',
'const ancestorState = ref("form");',
'const fieldErrors = reactive({',
'const duplicateReminderVisible = ref(false);',
'class="duplicate-reminder-layer"',
'if (isSubmitting.value) return;',
'/pages/genealogy/g05-genealogy-overview?genealogyId=',
'g01-empty-panel-frame.png',
- 'root-header-cinnabar.jpg',
'a01-scroll-primary-v3.png',
'create-flow-panel',
- 'flow-primary-action'
+ 'flow-primary-action',
+ 'import PageHeader from "@/components/PageHeader.vue";',
+ '<PageHeader',
+ 'custom-back',
+ '@back="goBack"'
)) {
Assert-Contains -Content $g03 -Expected $required -Message "Missing G03 flow contract: $required"
}
-foreach ($forbidden in @("from '@/utils/api.js'", 'appApi.', 'uni.showToast', 'uni.showModal', 'uni.showLoading', 'uni.showActionSheet', '/pages/tree/t01-tree-overview?genealogyId=')) {
+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" }
}
foreach ($className in @('create-flow-panel', 'flow-primary-action')) {
Assert-NoCssSurface -Content $g03 -ClassName $className
}
if (-not (Test-Path -LiteralPath $panelPath)) { throw 'G03 requires the accepted genealogy paper panel bitmap' }
Add-Type -AssemblyName System.Drawing
$panel = [System.Drawing.Bitmap]::FromFile($panelPath)
diff --git a/tests/g03-document-flow-contract.ps1 b/tests/g03-document-flow-contract.ps1
index 0249541..654dfa7 100644
--- a/tests/g03-document-flow-contract.ps1
+++ b/tests/g03-document-flow-contract.ps1
@@ -1,15 +1,17 @@
$ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$page = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
$style = [regex]::Match($page, '(?s)<style[^>]*>(.*?)</style>').Groups[1].Value
$positions = [regex]::Matches([regex]::Replace($style, '(?s)/\*.*?\*/', ''), '(?m)\bposition\s*:\s*([^;]+);')
if ($positions.Count -ne 1 -or $positions[0].Groups[1].Value.Trim() -ne 'fixed') {
throw "G03 must retain only its combined fixed dialog layer rule; found $($positions.Count) position declarations"
}
foreach ($required in @(
- 'background: url("/static/assets/foundation/opaque/root-header-cinnabar.jpg")',
'background: url("/static/assets/modules/genealogy/transparent/g01-empty-panel-frame.png")',
'background: url("/static/assets/modules/auth/transparent/a01-scroll-dialog-v3.png")',
'background: url("/static/assets/foundation/transparent/a01-scroll-primary-v3.png")'
)) { if ($page -notmatch [regex]::Escape($required)) { throw "G03 is missing real asset background: $required" } }
+foreach ($forbidden in @('flow-header', 'root-header-cinnabar.jpg')) {
+ if ($page -match [regex]::Escape($forbidden)) { throw "G03 must not retain private header token: $forbidden" }
+}
Write-Output 'G03-DOCUMENT-FLOW-CONTRACT PASS'
diff --git a/tests/g03-visual-states-contract.ps1 b/tests/g03-visual-states-contract.ps1
index 5342acb..d539bc5 100644
--- a/tests/g03-visual-states-contract.ps1
+++ b/tests/g03-visual-states-contract.ps1
@@ -2,22 +2,22 @@ $ErrorActionPreference = 'Stop'
$root = Split-Path -Parent $PSScriptRoot
$g03 = Get-Content -LiteralPath (Join-Path $root 'pages/genealogy/g03-create-genealogy.vue') -Raw -Encoding utf8
function Assert-Match {
param([string]$Content, [string]$Pattern, [string]$Message)
if ($Content -notmatch $Pattern) { throw $Message }
}
if ($g03 -match '<text>返回</text>') { throw 'G03 header must use only the approved image back arrow' }
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back,\s*\.flow-header__side\s*\{[^}]*width:\s*72rpx;' -Message 'G03 header side slots must preserve centered title after removing back text'
-Assert-Match -Content $g03 -Pattern '(?s)\.flow-header__back-icon\s*\{[^}]*width:\s*34rpx;[^}]*height:\s*34rpx;' -Message 'G03 back arrow must use the approved visible size'
+Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+:title="isAncestorStep \? ''\u5F55\u5165\u9996\u4EE3\u4EBA\u7269'' : ''\u521B\u5EFA\u5BB6\u8C31''"\s+custom-back\s+@back="goBack"\s*/>' -Message 'G03 must use PageHeader with its dynamic title and custom back handler'
+if ($g03 -match 'flow-header|flow-header__back|flow-header__title') { throw 'G03 must not retain a private header implementation' }
Assert-Match -Content $g03 -Pattern '(?s)\.flow-note,\s*\.flow-rule__note\s*\{[^}]*font-size:\s*25rpx;' -Message 'G03 guidance copy must use the approved readable size'
Assert-Match -Content $g03 -Pattern '(?s)\.flow-rule__option\s*\{[^}]*font-size:\s*23rpx;' -Message 'G03 visibility options must use the approved readable size'
Assert-Match -Content $g03 -Pattern '(?s)\.field-error\s*\{[^}]*font-size:\s*24rpx;[^}]*line-height:\s*34rpx;' -Message 'G03 field errors must use the approved readable size'
Assert-Match -Content $g03 -Pattern '(?s)\.flow-error\s*\{[^}]*font-size:\s*25rpx;[^}]*line-height:\s*36rpx;' -Message 'G03 submit errors must use the approved readable size'
foreach ($asset in @('a01-scroll-dialog-v3.png', 'a01-scroll-primary-v3.png', 'a01-scroll-secondary-v3.png')) {
if ($g03 -notmatch [regex]::Escape($asset)) { throw "G03 must retain approved shared asset: $asset" }
}
Write-Output 'G03-VISUAL-STATES-CONTRACT PASS'
@@ -0,0 +1,7 @@
# Review package: a0d19f4..a0d19f4
## Commits
## Files changed
## Diff
+116
View File
@@ -0,0 +1,116 @@
### Task 1: 为公共 PageHeader 建立安全区与返回契约
**Files:**
- Modify: `tests/shared-component-document-flow-contract.ps1`
- Modify: `tests/shared-interaction-accessibility-contract.ps1`
- Modify: `components/PageHeader.vue`
**Interfaces:**
- Consumes: uni-app 提供的 `var(--status-bar-height, 0px)``getCurrentPages()``uni.navigateBack()``uni.reLaunch()`
- Produces: `customBack: Boolean = false` 属性和 `back` 事件;普通页头及占位槽均使用安全区总高度。
- [ ] **Step 1: 写入普通页头安全区的失败契约**
`tests/shared-component-document-flow-contract.ps1` 的 PageHeader 断言中加入:
```powershell
Assert-Match $header '(?s)\.page-header-slot\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader secondary slot must reserve status bar plus 104rpx content height'
Assert-Match $header '(?s)\.page-header\s*\{[^}]*height:\s*calc\(104rpx \+ var\(--status-bar-height, 0px\)\);[^}]*padding-top:\s*var\(--status-bar-height, 0px\);' 'PageHeader secondary header must place its 104rpx content below the status bar'
Assert-Match $header '(?s)\.page-header-slot--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root slot height must remain unchanged'
Assert-Match $header '(?s)\.page-header--root\s*\{[^}]*height:\s*calc\(124rpx \+ var\(--status-bar-height, 0px\)\);' 'PageHeader root height must remain unchanged'
```
- [ ] **Step 2: 写入自定义返回的失败契约**
`tests/shared-interaction-accessibility-contract.ps1` 的 PageHeader 检查中加入:
```powershell
foreach ($token in @(
'customBack: { type: Boolean, default: false }',
'const emit = defineEmits(["brand", "notice", "action", "back"]);',
'if (props.customBack) {',
'emit("back");',
'uni.navigateBack();',
'uni.reLaunch({ url: props.fallbackUrl });'
)) {
if (-not $header.Contains($token)) { throw "PageHeader back contract missing: $token" }
}
```
- [ ] **Step 3: 运行聚焦契约并确认 RED**
Run:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
```
Expected: 两项均 FAIL,分别指出普通页头仍为固定 `104rpx``customBack` 合同不存在。
- [ ] **Step 4: 实现最小公共组件改动**
`components/PageHeader.vue` 中补充属性和事件:
```js
const props = defineProps({
title: { type: String, required: true },
action: { type: String, default: "" },
root: { type: Boolean, default: false },
unreadCount: { type: Number, default: 0 },
fallbackUrl: { type: String, default: "/pages/genealogy/g01-my-genealogies" },
customBack: { type: Boolean, default: false },
});
const emit = defineEmits(["brand", "notice", "action", "back"]);
const goBack = () => {
if (props.customBack) {
emit("back");
return;
}
const stack = getCurrentPages();
if (stack.length > 1) {
uni.navigateBack();
return;
}
uni.reLaunch({ url: props.fallbackUrl });
};
```
把普通变体的尺寸改为:
```scss
.page-header-slot {
height: calc(104rpx + var(--status-bar-height, 0px));
flex: none;
}
.page-header {
height: calc(104rpx + var(--status-bar-height, 0px));
padding: 0 24rpx;
padding-top: var(--status-bar-height, 0px);
}
```
保留 `.page-header-slot--root``.page-header--root` 的现有 `124rpx` 规则不变。
- [ ] **Step 5: 运行聚焦契约并确认 GREEN**
Run:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
git diff --check
```
Expected: 两项输出 `PASS``git diff --check` 退出码为 0。
- [ ] **Step 6: 提交公共组件改动**
```powershell
git add components/PageHeader.vue tests/shared-component-document-flow-contract.ps1 tests/shared-interaction-accessibility-contract.ps1
git commit -m "fix: adapt shared page header to safe area"
```
+56
View File
@@ -0,0 +1,56 @@
# Task 1 Report
Status: DONE
## Changed files
- `components/PageHeader.vue`
- `tests/shared-component-document-flow-contract.ps1`
- `tests/shared-interaction-accessibility-contract.ps1`
## RED
Command:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
```
Failure evidence:
- `PageHeader secondary slot must reserve status bar plus 104rpx content height`
- `PageHeader back contract missing: customBack: { type: Boolean, default: false }`
Both contract scripts exited with code `1`, for the expected missing behavior.
## GREEN
Command:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/shared-component-document-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/shared-interaction-accessibility-contract.ps1
git diff --check
```
Result summary:
- `SHARED-COMPONENT-DOCUMENT-FLOW-CONTRACT PASS`
- `SHARED-INTERACTION-ACCESSIBILITY-CONTRACT PASS`
- `git diff --check` exited `0`
## Commit
`2f22df6 fix: adapt shared page header to safe area`
## Self-review
- Normal PageHeader slot and header use `calc(104rpx + var(--status-bar-height, 0px))`; the header applies status-bar top padding.
- Root slot and header retain their `calc(124rpx + var(--status-bar-height, 0px))` rules.
- `customBack` emits `back` and returns before navigation; normal navigation retains stack-aware back and fallback relaunch behavior.
- The added contract tests cover the required exact public interface and layout declarations.
## Concerns
None. Git emitted existing line-ending conversion warnings during checks and commit, but all required checks passed.
+98
View File
@@ -0,0 +1,98 @@
### Task 2: 用公共 PageHeader 替换 G03 自建页头
**Files:**
- Modify: `tests/g03-visual-states-contract.ps1`
- Modify: `tests/g03-create-flow-contract.ps1`
- Modify: `tests/g03-document-flow-contract.ps1`
- Modify: `pages/genealogy/g03-create-genealogy.vue`
**Interfaces:**
- Consumes: Task 1 的 `<PageHeader :title="String" custom-back @back="Function" />`
- Produces: G03 动态标题 `isAncestorStep ? "录入首代人物" : "创建家谱"`,以及现有 `goBack()` 对第一、第二步的业务分流。
- [ ] **Step 1: 将 G03 契约改成公共页头要求**
`tests/g03-visual-states-contract.ps1` 删除 `.flow-header__back``.flow-header__side``.flow-header__back-icon` 尺寸断言,改为:
```powershell
Assert-Match -Content $g03 -Pattern '(?s)<PageHeader\s+:title="isAncestorStep \? .录入首代人物. : .创建家谱."\s+custom-back\s+@back="goBack"\s*/>' -Message 'G03 must use PageHeader with its dynamic title and custom back handler'
if ($g03 -match 'flow-header|flow-header__back|flow-header__title') { throw 'G03 must not retain a private header implementation' }
```
`tests/g03-create-flow-contract.ps1` 的 required 项中加入:
```powershell
'import PageHeader from "@/components/PageHeader.vue";',
'<PageHeader',
'custom-back',
'@back="goBack"'
```
并从 required 项删除 `'root-header-cinnabar.jpg'`;在 forbidden 项加入:
```powershell
'class="flow-header"',
'flow-header__back',
'root-header-cinnabar.jpg'
```
`tests/g03-document-flow-contract.ps1` 删除页头背景的 required 项,并加入:
```powershell
foreach ($forbidden in @('flow-header', 'root-header-cinnabar.jpg')) {
if ($page -match [regex]::Escape($forbidden)) { throw "G03 must not retain private header token: $forbidden" }
}
```
- [ ] **Step 2: 运行 G03 聚焦契约并确认 RED**
Run:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1
```
Expected: 契约因 G03 仍含私有 `flow-header` 且尚未导入 `PageHeader` 而 FAIL。
- [ ] **Step 3: 替换 G03 页头结构并删除私有样式**
`pages/genealogy/g03-create-genealogy.vue` 中把整个 `.flow-header` 模板替换为:
```vue
<PageHeader
:title="isAncestorStep ? '录入首代人物' : '创建家谱'"
custom-back
@back="goBack"
/>
```
`<script setup>` 中加入:
```js
import PageHeader from "@/components/PageHeader.vue";
```
完整删除 `.flow-header``.flow-header__content``.flow-header__back``.flow-header__side``.flow-header__back-icon``.flow-header__title` 六组样式。保留现有 `goBack()`:第二步 `redirectTo` 第一页,第一步 `navigateBack`
- [ ] **Step 4: 运行 G03 聚焦契约并确认 GREEN**
Run:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1
git diff --check
```
Expected: 三项输出 `PASS``git diff --check` 退出码为 0。
- [ ] **Step 5: 提交 G03 接入改动**
```powershell
git add pages/genealogy/g03-create-genealogy.vue tests/g03-visual-states-contract.ps1 tests/g03-create-flow-contract.ps1 tests/g03-document-flow-contract.ps1
git commit -m "refactor: reuse shared header on G03"
```
+77
View File
@@ -0,0 +1,77 @@
# Task 2 Report: Reuse shared PageHeader on G03
## Status
Complete. Commit: `9362c47` (`refactor: reuse shared header on G03`).
## Changed files
- `pages/genealogy/g03-create-genealogy.vue`
- `tests/g03-visual-states-contract.ps1`
- `tests/g03-create-flow-contract.ps1`
- `tests/g03-document-flow-contract.ps1`
## RED
Command:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1
```
Exit code: `1`.
Failure evidence:
- `G03 must use PageHeader with its dynamic title and custom back handler`
- `Missing G03 flow contract: import PageHeader from "@/components/PageHeader.vue";`
- `G03 must not retain private header token: flow-header`
## GREEN
Command:
```powershell
& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1
git diff --check
```
Result: exit code `0`; all three focused contracts emitted `PASS`, and `git diff --check` exited cleanly. Git emitted only LF-to-CRLF informational warnings for the four changed files.
## Self-review
- Replaced only the private G03 header with `PageHeader`, using the required dynamic title, `custom-back`, and existing `goBack` handler.
- Added the single shared-header import.
- Removed all six specified private header style groups and the obsolete header asset reference.
- Preserved G03 forms, dialogs, actions, backgrounds, data, and the existing two-step `goBack()` branching unchanged.
- Updated contracts to require the shared header and reject private header tokens.
## Concerns
- The known App-Plus `step=ancestor` route-parameter issue was deliberately not modified, as required.
- No remaining task-specific concern.
## Follow-up: strict dynamic-title quotes
Fixed `tests/g03-visual-states-contract.ps1` so its `PageHeader` title regex requires the Vue expression's internal single quotes. In the PowerShell single-quoted pattern, each required single quote is written as `''`.
Regression commands:
```powershell
# RED: old pattern incorrectly matched a candidate with double-quoted internal titles.
$ErrorActionPreference = 'Stop'; $pattern = '(?s)<PageHeader\s+:title="isAncestorStep \? .\u5F55\u5165\u9996\u4EE3\u4EBA\u7269. : .\u521B\u5EFA\u5BB6\u8C31."\s+custom-back\s+@back="goBack"\s*/>'; $title1 = -join [char[]]@(0x5F55, 0x5165, 0x9996, 0x4EE3, 0x4EBA, 0x7269); $title2 = -join [char[]]@(0x521B, 0x5EFA, 0x5BB6, 0x8C31); $invalid = '<PageHeader :title="isAncestorStep ? "' + $title1 + '" : "' + $title2 + '"" custom-back @back="goBack" />'; if ($invalid -match $pattern) { throw 'Dynamic-title regex incorrectly matches double-quoted internal titles' }
# GREEN and focused verification
& powershell -ExecutionPolicy Bypass -File tests/g03-visual-states-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-create-flow-contract.ps1
& powershell -ExecutionPolicy Bypass -File tests/g03-document-flow-contract.ps1
git diff --check
```
Output summary: the RED probe failed with `Dynamic-title regex incorrectly matches double-quoted internal titles`; after the fix, the strict-quote probe and all three G03 contracts emitted `PASS`, and `git diff --check` exited `0`.
Commit: `a0d19f4` (`test: tighten G03 shared header contract`).
+54
View File
@@ -0,0 +1,54 @@
### Task 3: 自动回归公共页头消费者与响应式边界
**Files:**
- Verify: `components/PageHeader.vue`
- Verify: `pages/genealogy/g03-create-genealogy.vue`
- Verify: `pages/genealogy/g06-search-genealogies.vue`
- Verify: `pages/genealogy/g01-my-genealogies.vue`
**Interfaces:**
- Consumes: Task 1 与 Task 2 的公共页头合同。
- Produces: 公共组件、G03、G06 与 G01 无静态合同回退的验证记录。
- [ ] **Step 1: 运行全部聚焦合同**
Run:
```powershell
$tests = @(
'tests/shared-component-document-flow-contract.ps1',
'tests/shared-interaction-accessibility-contract.ps1',
'tests/g03-visual-states-contract.ps1',
'tests/g03-create-flow-contract.ps1',
'tests/g03-document-flow-contract.ps1',
'tests/g06-search-flow-contract.ps1',
'tests/g06-document-flow-contract.ps1',
'tests/g01-visual-contract.ps1'
)
foreach ($test in $tests) { & powershell -ExecutionPolicy Bypass -File $test }
```
Expected: 八项均输出各自 `PASS`PowerShell 退出码为 0。
- [ ] **Step 2: 运行现有 H5 逻辑和宽度辅助检查**
在本地 H5 服务已运行时执行:
```powershell
node tests/g03-create-flow-runtime-smoke.js http://localhost:5173
node tests/g06-search-flow-runtime-smoke.js http://localhost:5173
```
Expected: 分别输出 `G03-CREATE-FLOW-RUNTIME-SMOKE PASS``G06-SEARCH-FLOW-RUNTIME-SMOKE PASS`320、360、412px 视口无横向溢出。若本地服务端口不同,只替换 URL,不改变测试内容。
- [ ] **Step 3: 检查最终差异**
Run:
```powershell
git diff --check
git status --short
```
Expected: `git diff --check` 退出码为 0;工作区只允许存在本计划明确列出的验证证据或尚未提交的计划文件。
+66
View File
@@ -0,0 +1,66 @@
# Task 3 Verification Report
## Status
PASS. All eight focused static contracts and both available H5 runtime smoke checks passed. No source or test files were modified by this task.
## Focused contract checks
Command executed:
```powershell
$tests = @(
'tests/shared-component-document-flow-contract.ps1',
'tests/shared-interaction-accessibility-contract.ps1',
'tests/g03-visual-states-contract.ps1',
'tests/g03-create-flow-contract.ps1',
'tests/g03-document-flow-contract.ps1',
'tests/g06-search-flow-contract.ps1',
'tests/g06-document-flow-contract.ps1',
'tests/g01-visual-contract.ps1'
)
foreach ($test in $tests) { & powershell -ExecutionPolicy Bypass -File $test }
```
| Test | Exit code | Output summary |
| --- | ---: | --- |
| `tests/shared-component-document-flow-contract.ps1` | 0 | `SHARED-COMPONENT-DOCUMENT-FLOW-CONTRACT PASS` |
| `tests/shared-interaction-accessibility-contract.ps1` | 0 | `SHARED-INTERACTION-ACCESSIBILITY-CONTRACT PASS` |
| `tests/g03-visual-states-contract.ps1` | 0 | `G03-VISUAL-STATES-CONTRACT PASS` |
| `tests/g03-create-flow-contract.ps1` | 0 | `G03-CREATE-FLOW-CONTRACT PASS` |
| `tests/g03-document-flow-contract.ps1` | 0 | `G03-DOCUMENT-FLOW-CONTRACT PASS` |
| `tests/g06-search-flow-contract.ps1` | 0 | `G06-SEARCH-FLOW-CONTRACT PASS` |
| `tests/g06-document-flow-contract.ps1` | 0 | `G06-DOCUMENT-FLOW-CONTRACT PASS` |
| `tests/g01-visual-contract.ps1` | 0 | `PASS G-01 visual contract` |
Aggregate command exit code: `0`.
## H5 runtime smoke checks
Availability probe command:
```powershell
Invoke-WebRequest -UseBasicParsing -Uri 'http://localhost:5173' -TimeoutSec 5
```
Result: HTTP `200`; therefore an existing local H5 service was available at `http://localhost:5173`. No service was started or installed. (`Get-NetTCPConnection -LocalPort 5173` did not list a listener, but the HTTP probe and both browser-driven smoke tests succeeded.)
| Command | Exit code | Output summary |
| --- | ---: | --- |
| `node tests/g03-create-flow-runtime-smoke.js http://localhost:5173` | 0 | `G03-CREATE-FLOW-RUNTIME-SMOKE PASS` |
| `node tests/g06-search-flow-runtime-smoke.js http://localhost:5173` | 0 | `G06-SEARCH-FLOW-RUNTIME-SMOKE PASS` |
## Final worktree checks
| Command | Exit code | Output summary |
| --- | ---: | --- |
| `git diff --check` | 0 | No output; no whitespace errors. |
| `git status --short` | 0 | `?? .superpowers/` only. |
`git status --short` also emitted warnings that `C:\\Users\\Administrator/.config/git/ignore` could not be accessed due to permission denial; this did not affect its exit code or the reported worktree status.
## Worktree classification and concerns
The only untracked tree is `.superpowers/`, containing task briefs, earlier task reports/review diffs, and this Task 3 report. It is verification scratch evidence rather than source or test changes. No source-code worktree changes were reported.
Concern: the port-listener probe did not enumerate `5173` despite a successful HTTP `200`; runtime results confirm the H5 target was nevertheless reachable and exercised successfully.
+46
View File
@@ -0,0 +1,46 @@
### Task 4: MuMu 原生视觉验收门
**Files:**
- Create: `tmp/G03-shared-header-mumu.png`
- Create: `tmp/G06-shared-header-mumu.png`
- Create: `tmp/G01-root-header-regression-mumu.png`
**Interfaces:**
- Consumes: HBuilderX 已同步到 MuMu 的最新 Android 包与设备 `emulator-5554`
- Produces: G03、G06、G01 三张原生截图及用户逐页结论。
- [ ] **Step 1: 确认设备尺寸与密度**
Run:
```powershell
$adb = 'C:\Users\Administrator\Desktop\HBuilderX\plugins\launcher-tools\tools\adbs\adb.exe'
& $adb devices
& $adb -s emulator-5554 shell wm size
& $adb -s emulator-5554 shell wm density
```
Expected: `emulator-5554``device`,并记录当前 MuMu 的物理尺寸与密度。
- [ ] **Step 2: 捕获 G03、G06、G01 原生截图**
每次人工导航到指定页面后执行同一组命令,仅替换文件名:
```powershell
& $adb -s emulator-5554 shell screencap -p /sdcard/G03-shared-header-mumu.png
& $adb -s emulator-5554 pull /sdcard/G03-shared-header-mumu.png tmp/G03-shared-header-mumu.png
```
G06 使用 `G06-shared-header-mumu.png`G01 使用 `G01-root-header-regression-mumu.png`
- [ ] **Step 3: 按页面验收并向用户展示**
检查:
```text
G03:返回按钮和“创建家谱”完整位于系统状态栏下方,正文紧随占位槽,无裁切或横向溢出。
G06:公共二级页头获得相同安全区,正文没有被推入页头或遮挡。
G01:根页头高度、logo、标题和操作区与已放行状态一致。
```
只有用户明确确认三页后,才能把本次公共页头改动报告为视觉通过。G03 原生第二步仍因既有路由参数问题单独记为未通过,不得用本次页头结论覆盖。