11 KiB
G01 Fixed Header And Independent List Scroll Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. The user forbids subagents, worktrees, git add, commit, push, reset, and checkout; execute inline in the existing workspace and preserve every pre-existing change.
Goal: Make G01's normal genealogy state keep its header, current genealogy card, shortcut grid, divider, and bottom tabbar fixed while only the three list groups and add action scroll vertically.
Architecture: Add one G01-only split-layout modifier for the normal hasGenealogies state. Keep the existing header/background/tabbar outside a single vertical scroll-view, place the current card/shortcuts/divider in a fixed flex section, and allocate all remaining viewport height to the list scroller. Loading, error, and empty states retain the existing normal-flow layout.
Tech Stack: Vue 3 <script setup>, uni-app scroll-view, SCSS, PowerShell static contracts, Node Chrome DevTools Protocol runtime smoke.
Global Constraints
- Modify only
pages/genealogy/g01-my-genealogies.vue, directly related G01 tests, G01 design/acceptance documentation, and new runtime screenshots. - Do not modify
PageHeader.vue,AppTabbar.vue,GenealogyPageBackground.vue, or another G page. - Do not add APIs, global state, pull-to-refresh, sticky group headings, collapse animations, or native UniApp prompts.
- Preserve the approved
12rpxspacing between adjacent application cards. - Verify 320×568, 360×640, 360×800, 412×915, and 412×1000.
- H5 evidence remains internal; Android/HBuilderX is still required.
- Do not use subagents, worktrees, git add, commit, push, reset, or checkout.
Task 1: Lock the split-scroll contract with failing tests
Files:
- Modify:
tests/g01-empty-state-contract.ps1 - Modify:
tests/g01-empty-state-runtime-smoke.js - Test:
tests/g01-empty-state-contract.ps1 - Test:
tests/g01-empty-state-runtime-smoke.js
Interfaces:
-
Consumes: the existing G01 selectors
.current-slip,.shortcut-grid,.section-divider,.genealogy-lower,.create-action. -
Produces: required selectors
.genealogy-fixed-zone,.genealogy-list-scroll,.genealogy-index--split, plus the runtime behavior contract for fixed and moving regions. -
Step 1: Extend the PowerShell static contract
Require one normal-state vertical scroll view, keep the fixed zone before it, keep .genealogy-lower inside it, and require the reset handlers:
foreach ($required in @(
'class="genealogy-fixed-zone"',
'class="genealogy-list-scroll"',
'scroll-y',
':scroll-top="listScrollCommand"',
'@scroll="handleListScroll"',
'const isListLayout = computed(',
'const resetListScroll = async () =>'
)) {
if ($g01 -notmatch [regex]::Escape($required)) { throw "Missing G01 split-scroll contract: $required" }
}
Assert-Match -Content $g01 -Pattern '(?s)<view class="genealogy-fixed-zone">.*class="current-slip".*class="shortcut-grid".*class="section-divider".*</view>\s*<scroll-view[^>]*class="genealogy-list-scroll"[^>]*>.*class="genealogy-lower".*</scroll-view>' -Message 'G01 fixed and scrolling regions are not separated correctly'
- Step 2: Extend the runtime smoke
For each required viewport, record fixed element rectangles, scroll the inner region, and assert only the lower heading moves:
const before = await valueOf(send, `(() => {
const selectors = ['.page-header', '.current-slip', '.shortcut-grid', '.section-divider', '.app-tabbar']
return Object.fromEntries(selectors.map((selector) => [selector, document.querySelector(selector)?.getBoundingClientRect().top]))
})()`)
await valueOf(send, `(() => {
const host = document.querySelector('.genealogy-list-scroll')
const scroller = host?.querySelector('.uni-scroll-view') || host
scroller.scrollTop = Math.min(180, scroller.scrollHeight - scroller.clientHeight)
scroller.dispatchEvent(new Event('scroll', { bubbles: true }))
})()`)
Wait until .section-heading moves upward, then assert every fixed selector changes by no more than one pixel. Also assert the inner region has positive height, scrollHeight > clientHeight, and the document has no horizontal overflow.
- Step 3: Run the static contract and observe RED
Run:
powershell -ExecutionPolicy Bypass -File tests/g01-empty-state-contract.ps1
Expected: FAIL because .genealogy-fixed-zone and .genealogy-list-scroll do not exist.
- Step 4: Run the runtime smoke and observe RED
Run:
node tests/g01-empty-state-runtime-smoke.js http://localhost:5173
Expected: FAIL because the normal state has no independent list scroller.
Task 2: Implement the G01-only split layout and position rules
Files:
- Modify:
pages/genealogy/g01-my-genealogies.vue:3-165 - Modify:
pages/genealogy/g01-my-genealogies.vue:270-393 - Modify:
pages/genealogy/g01-my-genealogies.vue:395-563 - Test:
tests/g01-empty-state-contract.ps1 - Test:
tests/g01-empty-state-runtime-smoke.js
Interfaces:
-
Consumes:
isLoading,hasError,hasGenealogies,selectGenealogy(genealogy)and existing page components. -
Produces:
isListLayout: ComputedRef<boolean>,listScrollCommand: Ref<number>,currentListScrollTop: Ref<number>,handleListScroll(event): void, andresetListScroll(): Promise<void>. -
Step 1: Add the normal-state layout modifier
Update the page shell and script imports:
<view
class="page-shell genealogy-index"
:class="{ 'genealogy-index--split': isListLayout }"
>
import { computed, nextTick, ref } from "vue";
const isListLayout = computed(
() => !isLoading.value && !hasError.value && hasGenealogies.value,
);
- Step 2: Separate fixed and scrolling regions
Inside the existing v-else-if="hasGenealogies" branch, wrap only the card, shortcuts, and divider in the fixed zone, and wrap .genealogy-lower in the single scroll view:
<view class="genealogy-fixed-zone">
<view class="current-slip" @click="openSwitcher">...</view>
<view class="shortcut-grid">...</view>
<image class="section-divider" ... />
</view>
<scroll-view
class="genealogy-list-scroll"
scroll-y
:scroll-top="listScrollCommand"
@scroll="handleListScroll"
>
<view class="genealogy-lower">...</view>
</scroll-view>
Do not move the background, title bar, tabbar, dialog layers, or non-normal states into the scroll view.
- Step 3: Add scroll tracking and explicit reset after genealogy switching
const listScrollCommand = ref(0);
const currentListScrollTop = ref(0);
const handleListScroll = (event) => {
currentListScrollTop.value = Number(event?.detail?.scrollTop || 0);
};
const resetListScroll = async () => {
listScrollCommand.value = currentListScrollTop.value;
await nextTick();
listScrollCommand.value = 0;
currentListScrollTop.value = 0;
};
const selectGenealogy = async (genealogy) => {
selectedGenealogyId.value = genealogy.id;
closeSwitcher();
await resetListScroll();
};
Opening/closing dialogs and returning from a pushed child page do not call resetListScroll, so the existing component instance retains its scroll position.
- Step 4: Add the split-only flex sizing
Keep the current default styles for loading/error/empty, and scope viewport locking to the modifier:
.genealogy-index--split {
display: flex;
height: 100vh;
min-height: 0;
flex-direction: column;
padding-bottom: calc(112rpx + env(safe-area-inset-bottom));
box-sizing: border-box;
}
.genealogy-index--split .genealogy-content {
display: flex;
min-height: 0;
flex: 1;
flex-direction: column;
padding-bottom: 0;
}
.genealogy-fixed-zone { flex: 0 0 auto; }
.genealogy-list-scroll {
width: 100%;
height: 0;
min-height: 0;
flex: 1;
}
Do not add a height media query or hide the system scroll indicator as a product contract.
- Step 5: Run the focused tests and reach GREEN
Run:
powershell -ExecutionPolicy Bypass -File tests/g01-empty-state-contract.ps1
node tests/g01-empty-state-runtime-smoke.js http://localhost:5173
powershell -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1
powershell -ExecutionPolicy Bypass -File tests/genealogy-shared-background-contract.ps1
Expected: all four commands exit 0.
Task 3: Capture evidence, update QA, and protect adjacent G behavior
Files:
- Create:
docs/design/screens/runtime/2026-07-16/G01-fixed-list-scroll-320x568.png - Create:
docs/design/screens/runtime/2026-07-16/G01-fixed-list-scroll-360x640.png - Create:
docs/design/screens/runtime/2026-07-16/G01-fixed-list-scroll-360x800.png - Create:
docs/design/screens/runtime/2026-07-16/G01-fixed-list-scroll-412x915.png - Create:
docs/design/screens/runtime/2026-07-16/G01-fixed-list-scroll-412x1000.png - Modify:
design-qa.md - Modify:
docs/验收规划.md - Modify:
docs/交接记录.md - Test: G01 and adjacent G runtime/static contracts
Interfaces:
-
Consumes: the implemented
.genealogy-list-scrollbehavior and existing Chrome debug server on port 9222. -
Produces: five internal H5 screenshots, a current QA record, and an accurate handoff state that does not claim Android or whole-page acceptance.
-
Step 1: Capture five real H5 viewports
Use Chrome DevTools Protocol to navigate to G01, set each required viewport, scroll the inner list to a representative position, and save the five named PNGs. Capture with captureBeyondViewport: false and check browser exceptions plus console.error.
- Step 2: Visually compare fixed and moving regions
Open the 320×568, 412×915, and 412×1000 screenshots together. Confirm the fixed area remains complete, the first visible list row is not covered by the divider, the tabbar does not cover the last reachable action, and the long background remains continuous.
- Step 3: Update QA and authoritative status documents
Prepend a G01 split-scroll section to design-qa.md with source spec, implementation screenshots, five required fidelity surfaces, comparison history, evidence limits, and final result: passed only if no H5 P0/P1/P2 remains.
Update docs/验收规划.md and docs/交接记录.md to say the fixed/scroll structure is implemented as an internal H5 candidate. Keep G01 [~], explicitly retain Android/HBuilderX and 4GB-device verification, and do not mark the page frozen or accepted.
- Step 4: Run final verification
Run:
powershell -ExecutionPolicy Bypass -File tests/g01-empty-state-contract.ps1
powershell -ExecutionPolicy Bypass -File tests/g01-visual-contract.ps1
powershell -ExecutionPolicy Bypass -File tests/genealogy-shared-background-contract.ps1
node tests/g01-empty-state-runtime-smoke.js http://localhost:5173
node tests/g03-create-flow-runtime-smoke.js http://localhost:5173
node tests/g05-overview-runtime-smoke.js http://localhost:5173
node tests/g06-search-flow-runtime-smoke.js http://localhost:5173
node tests/g08-g10-application-flow-runtime-smoke.js http://localhost:5173
node tests/g11-g12-settings-poems-runtime-smoke.js http://localhost:5173
git diff --check
Expected: every command exits 0. Report Android/HBuilderX as still pending.