Files
jiapuapp/docs/superpowers/plans/2026-07-16-g01-fixed-header-independent-list-scroll.md
T
2026-07-20 06:52:26 +08:00

11 KiB
Raw Blame History

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:

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, and resetListScroll(): 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-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 -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.