276 lines
11 KiB
Markdown
276 lines
11 KiB
Markdown
# 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 `12rpx` spacing 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:
|
||
|
||
```powershell
|
||
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:
|
||
|
||
```js
|
||
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
|
||
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:
|
||
|
||
```powershell
|
||
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`, and `resetListScroll(): Promise<void>`.
|
||
|
||
- [ ] **Step 1: Add the normal-state layout modifier**
|
||
|
||
Update the page shell and script imports:
|
||
|
||
```vue
|
||
<view
|
||
class="page-shell genealogy-index"
|
||
:class="{ 'genealogy-index--split': isListLayout }"
|
||
>
|
||
```
|
||
|
||
```js
|
||
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:
|
||
|
||
```vue
|
||
<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**
|
||
|
||
```js
|
||
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:
|
||
|
||
```scss
|
||
.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
|
||
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-scroll` behavior 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
|
||
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.
|