feat(api): 完善家谱系统API客户端契约

- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
This commit is contained in:
fizzleaf
2026-07-29 16:57:27 +08:00
parent 59a72fb22b
commit fb1743aa2a
74 changed files with 13166 additions and 1320 deletions
@@ -33,7 +33,7 @@
- `auditGenealogyJoinApply(genealogyId, applyId, body)`
- `cancelGenealogyJoinApply(applyId)`
- [ ] **Step 1: 写失败的路径和 body 白名单测试**
- [x] **Step 1: 写失败的路径和 body 白名单测试**
测试固定以下请求:
@@ -69,7 +69,7 @@ await client.auditGenealogyJoinApply(genealogyId, applyId, {
- 审核 body 只保留 `status/auditRemark`
- 所有 path ID 拒绝不安全数字。
- [ ] **Step 2: 运行 RED**
- [x] **Step 2: 运行 RED**
Run:
@@ -79,11 +79,11 @@ node --test --test-name-pattern "genealogy lifecycle" tests/api-client-contract.
Expected: FAIL,新方法尚不存在。
- [ ] **Step 3: 最小实现方法和白名单**
- [x] **Step 3: 最小实现方法和白名单**
使用现有 `request()``pickDefined()``toRequiredPathId()`;不增加兼容别名或 APP fallback。
- [ ] **Step 4: 运行 GREEN**
- [x] **Step 4: 运行 GREEN**
Run:
@@ -110,7 +110,7 @@ Expected: PASS。
- `normalizeCreatedGenealogy(item)`
- `buildCreatedGenealogyUrl(item)`
- [ ] **Step 1: 写失败的创建行为测试**
- [x] **Step 1: 写失败的创建行为测试**
```js
assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({
@@ -141,7 +141,7 @@ assert.deepEqual(GenealogyEntryPages.buildGenealogyCreateBody({
- 创建响应必须有稳定 `genealogyId`、谱名和姓氏。
- 跳转地址只能由响应 ID 产生:`profile-family-home.html?genealogyId=...`
- [ ] **Step 2: 运行 RED**
- [x] **Step 2: 运行 RED**
Run:
@@ -151,11 +151,11 @@ node --test tests/genealogy-entry-pages.test.js
Expected: FAIL,纯函数不存在。
- [ ] **Step 3: 最小实现纯函数**
- [x] **Step 3: 最小实现纯函数**
严格构造 `AppGenealogyCreateBody`,可选空值省略,不读取页面外字段。
- [ ] **Step 4: 运行 GREEN**
- [x] **Step 4: 运行 GREEN**
Run:
@@ -182,7 +182,7 @@ Expected: PASS。
- Consumes: Task 1 和 Task 2、`RegionPages``UploadPages`
- Produces: `initGenealogyCreatePage()`
- [ ] **Step 1: 写失败的页面测试**
- [x] **Step 1: 写失败的页面测试**
断言页面:
@@ -193,7 +193,7 @@ Expected: PASS。
- 表单只包含后端创建 DTO 字段和地区选择辅助字段。
- 创建按钮、状态区域和额度区域可由脚本控制。
- [ ] **Step 2: 运行 RED**
- [x] **Step 2: 运行 RED**
Run:
@@ -203,7 +203,7 @@ node --test tests/genealogy-entry-pages.test.js tests/pending-pages.test.js test
Expected: FAIL,页面仍 pending 且缺上传/初始化行为。
- [ ] **Step 3: 实现页面初始化和提交**
- [x] **Step 3: 实现页面初始化和提交**
初始化顺序:
@@ -218,7 +218,7 @@ Expected: FAIL,页面仍 pending 且缺上传/初始化行为。
401 清登录态;403 保留登录态;业务错误显示在表单状态区域。
- [ ] **Step 4: 运行 GREEN**
- [x] **Step 4: 运行 GREEN**
Run:
@@ -237,11 +237,11 @@ Expected: PASS。
- Modify: `docs/PC接口对接规划.md`
- Modify: `docs/superpowers/plans/2026-07-29-genealogy-create-first-batch.md`
- [ ] **Step 1: 更新契约和阶段状态**
- [x] **Step 1: 更新契约和阶段状态**
记录 `AppGenealogyCreateBody``AppGenealogyVo`、额度、权限、上传派生字段、加入申请 ApiClient 契约及仍未开放的加入 UI。
- [ ] **Step 2: 聚焦和全量验证**
- [x] **Step 2: 聚焦和全量验证**
Run:
@@ -253,6 +253,6 @@ git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check
Expected: 全部 PASS。
- [ ] **Step 3: 浏览器验证并报告**
- [x] **Step 3: 浏览器验证并报告**
使用真实账号验证创建页登录态、额度、地区加载、无手填 ID、控制台错误和提交前校验。不得为测试消耗真实创建额度;除非用户明确授权,不执行最终创建写操作。
@@ -0,0 +1,509 @@
# 阶段 6 PC 功能页面 Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Project rules reserve code edits for the primary agent; subagents may only perform read-only exploration and review.
**Goal:** 实现所有当前 PC 接口已具备闭环但前端缺失或未开放的谱文、相册/照片、祭祀/祭品、管理员邀约、指定账号绑定和家谱成员管理页面。
**Architecture:** `utils/ApiClient.js` 唯一拥有 PC method/path/query/body;每个业务使用独立 UMD 页面脚本,页面只消费脚本导出的规范化、校验、渲染和初始化接口。`profile-common.js` 提供家谱上下文,`upload-pages.js` 产生 OSS ID,所有写操作使用稳定字符串 ID、防重复提交并在成功后重读。
**Tech Stack:** 静态 HTML、原生 JavaScript UMD、Axios 请求封装、Node `node:test`、现有 profile CSS、wangEditor v5、统一分片上传。
## Global Constraints
- 后端目录 `D:/WorkSpace/Java/Genealogy` 严格只读。
- 只使用 `/genealogy/pc/**`,不得使用 APP 接口或旧路径 fallback。
- 不允许手填业务 ID、用户 ID 或 OSS ID;int64 在浏览器边界保存为十进制字符串。
- 普通列表无法重读停用记录的模块固定提交 `status=0`
- 每个模块严格执行 RED → GREEN → 聚焦测试 → 真实账号非破坏性验证 → 规划更新。
- 当前工作树已有前几阶段改动;不得重置、覆盖或提交无关文件。除非用户另行要求,本计划不创建 Git commit。
---
### Task 1: 扩展 PC ApiClient 契约
**Files:**
- Modify: `utils/ApiClient.js`
- Modify: `tests/api-client-contract.test.js`
**Interfaces:**
- Produces:
- `articles(genealogyId)`, `articleDetail(genealogyId, articleId)`, `createArticle(genealogyId, body)`, `updateArticle(genealogyId, articleId, body)`
- `albums(genealogyId)`, `createAlbum(genealogyId, body)`, `updateAlbum(genealogyId, albumId, body)`, `albumPhotos(genealogyId, albumId)`, `createAlbumPhoto(genealogyId, albumId, body)`
- `ceremonies(genealogyId)`, `ceremonyDetail(genealogyId, ceremonyId)`, `createCeremony`, `updateCeremony`, `ceremonyGifts`, `createCeremonyGift`
- `replaceCeremonyInvitees(genealogyId, ceremonyId, inviteeUserIds)`
- `genealogyMembers(genealogyId)`, `genealogyMemberOptions(genealogyId)`, `updateGenealogyMember`, `removeGenealogyMember`, `leaveGenealogy`, `transferGenealogyOwner`
- [x] **Step 1: 写失败的路径和 body 白名单测试**
```js
await client.createArticle('9007199254740993001', {
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
appUserId: 'must-drop',
status: '0'
});
assert.deepEqual(calls[0].data, {
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
status: '0'
});
await client.replaceCeremonyInvitees(
'9007199254740993001',
'9007199254740993003',
['9007199254740993004']
);
assert.deepEqual(calls.at(-1).data, {
inviteeUserIds: ['9007199254740993004']
});
```
- [x] **Step 2: 运行 RED**
Run: `node --test --test-name-pattern "article|album|ceremony admin|genealogy member" tests/api-client-contract.test.js`
Expected: FAIL,原因是新方法不存在或仍只有删除方法。
- [x] **Step 3: 最小实现所有方法和白名单**
```js
var ARTICLE_FIELDS = [
'categoryId', 'articleTitle', 'articleSummary', 'coverOssId',
'articleContent', 'authorName', 'sortOrder', 'status'
];
var ALBUM_FIELDS = ['albumName', 'albumDesc', 'coverOssId', 'sortOrder', 'status'];
var ALBUM_PHOTO_FIELDS = [
'ossId', 'photoTitle', 'photoDesc', 'photographer',
'shootTime', 'sortOrder', 'status'
];
var CEREMONY_FIELDS = [
'ceremonyType', 'ceremonyTitle', 'ceremonyDesc', 'ceremonyTime',
'location', 'locationAddress', 'longitude', 'latitude',
'coverOssId', 'sortOrder', 'status'
];
var CEREMONY_GIFT_FIELDS = ['giverName', 'giftAmount', 'giftMessage'];
var MEMBER_UPDATE_FIELDS = ['memberName', 'relationName', 'roleType', 'lineagePersonId'];
```
每个方法只通过既有 `request()``pickDefined()``toRequiredPathId()` 和路径 builder 发请求;成员 options 按 YAML 不发送 Query。
- [x] **Step 4: 运行 GREEN 和语法检查**
Run: `node --test tests/api-client-contract.test.js`
Run: `node --check utils/ApiClient.js`
Expected: PASS。
---
### Task 2: 谱文列表、详情、创建、修改和删除
**Files:**
- Create: `public/js/article-pages.js`
- Create: `tests/article-pages.test.js`
- Modify: `profile-article.html`
- Modify: `profile-article-edit.html`
- Modify: `tests/pending-pages.test.js`
- Modify: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: Task 1 article ApiClient methods、`ProfileUI.requireGenealogyContext()``ProfileUpload.bindUploadField()``RichEditorPages`
- Produces: `buildArticleBody`, `normalizeArticle`, `normalizeArticles`, `matchesArticle`, `renderArticleList`, `renderArticleDetail`, `initArticleListPage`, `initArticleEditPage`
- [x] **Step 1: 写失败的谱文行为测试**
```js
assert.deepEqual(ArticlePages.buildArticleBody({
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
categoryId: '',
status: '1'
}), {
articleTitle: '族史',
articleContent: '<p>正文</p>',
coverOssId: '9007199254740993002',
status: '0'
});
assert.equal(
ArticlePages.normalizeArticle({ articleId: Number.MAX_SAFE_INTEGER + 1 }),
null
);
assert.doesNotMatch(
ArticlePages.renderArticleDetail({
articleId: '9007199254740993003',
articleTitle: '<img src=x onerror=alert(1)>',
articleContent: '<script>alert(1)</script>',
status: '0'
}),
/<script|onerror/
);
```
页面测试必须断言两个 HTML 不再 pending、加载 `article-pages.js`、没有手填 `articleId/categoryId/coverOssId/status`,列表页存在详情区域,编辑页存在标题/摘要/正文/作者/封面上传。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/article-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js`
Expected: FAIL,原因是模块不存在且页面仍为 pending。
- [x] **Step 3: 实现谱文脚本和页面**
```js
function buildArticleBody(values) {
return compact({
articleTitle: requiredText(values.articleTitle, '请输入谱文标题'),
articleSummary: optionalText(values.articleSummary),
coverOssId: normalizeNullableId(values.coverOssId),
articleContent: requiredText(values.articleContent, '请输入谱文正文'),
authorName: optionalText(values.authorName),
sortOrder: normalizeOptionalSafeInteger(values.sortOrder),
status: '0'
});
}
```
列表从直接数组读取;详情按 `articleId` 匹配;列表没有独立分类选项源时不渲染 `categoryId` 输入。新增/修改成功后用返回 ID 或 URL ID 重读详情,删除后重读列表。所有可见文本转义,正文使用现有安全富文本展示规则。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/article-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js`
Run: `node --check public/js/article-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告谱文阶段**
`docs/PC接口对接规划.md` 记录 ArticleBody/ArticleVo 字段、C20 谱文状态、`categoryId` query 差异、封面 URL 和停用记录阻断。
---
### Task 3: 相册列表、相册编辑和照片管理
**Files:**
- Create: `profile-album-edit.html`
- Create: `profile-album-detail.html`
- Create: `public/js/album-pages.js`
- Create: `tests/album-pages.test.js`
- Modify: `profile-album.html`
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: Task 1 album ApiClient methods、ProfileUI、ProfileUpload
- Produces: `buildAlbumBody`, `buildAlbumPhotoBody`, `normalizeAlbum`, `normalizeAlbumPhoto`, `findAlbumById`, `renderAlbumList`, `renderPhotoList`, three page initializers
- [x] **Step 1: 写失败的相册/照片测试**
```js
assert.deepEqual(AlbumPages.buildAlbumPhotoBody({
ossId: '9007199254740993010',
photoTitle: '祠堂合影',
shootTime: '2026-07-29T10:30',
status: '1'
}), {
ossId: '9007199254740993010',
photoTitle: '祠堂合影',
shootTime: '2026-07-29 10:30:00',
status: '0'
});
assert.equal(AlbumPages.findAlbumById(
[{ albumId: '9007199254740993011', albumName: '旧影', status: '0' }],
'9007199254740993011'
).albumName, '旧影');
```
页面测试断言 OSS ID 只能是 hidden/upload target,列表链接传播 `genealogyId + albumId`,详情页具有照片上传和真实删除入口。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/album-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Expected: FAIL,原因是脚本和新增页面不存在。
- [x] **Step 3: 实现三个页面和脚本**
```js
function buildAlbumBody(values) {
return compact({
albumName: requiredText(values.albumName, '请输入相册名称'),
albumDesc: optionalText(values.albumDesc),
coverOssId: normalizeNullableId(values.coverOssId),
sortOrder: normalizeOptionalSafeInteger(values.sortOrder),
status: '0'
});
}
```
相册编辑/详情通过重新读取相册列表并按稳定 `albumId` 匹配;照片列表调用 `/photos`。照片新增后重读照片列表;删除照片后重读并更新相册计数;删除相册后返回并重读相册列表。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/album-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Run: `node --check public/js/album-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告相册阶段**
记录 AlbumBody、AlbumPhotoBody、AlbumVo、AlbumPhotoVo、文件 URL 缺口和停用记录阻断。
---
### Task 4: 祭祀活动、祭品和管理员邀约
**Files:**
- Create: `profile-ceremony.html`
- Create: `profile-ceremony-detail.html`
- Create: `public/js/ceremony-admin-pages.js`
- Create: `tests/ceremony-admin-pages.test.js`
- Modify: `profile-gift-edit.html`
- Modify: `profile-gift.html`
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: Task 1 ceremony/member ApiClient methods、ProfileUI、ProfileUpload
- Produces: `buildCeremonyBody`, `buildCeremonyGiftBody`, `normalizeCeremony`, `normalizeCeremonyGift`, `normalizeInviteeOption`, `buildInviteeBody`, list/detail/edit initializers
- [x] **Step 1: 写失败的祭祀测试**
```js
assert.deepEqual(CeremonyAdminPages.buildCeremonyGiftBody({
giverName: '宗亲',
giftAmount: '88.50',
giftMessage: '敬献'
}), {
giverName: '宗亲',
giftAmount: 88.5,
giftMessage: '敬献'
});
assert.throws(() => CeremonyAdminPages.buildCeremonyGiftBody({
giftAmount: '-1'
}));
assert.deepEqual(CeremonyAdminPages.buildInviteeBody([
'9007199254740993020',
'9007199254740993020',
'9007199254740993021'
]), {
inviteeUserIds: ['9007199254740993020', '9007199254740993021']
});
```
页面测试断言个人邀请脚本和管理员脚本职责分离;活动编辑没有手填封面 ID;详情页有祭品和受邀成员选择器。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/ceremony-admin-pages.test.js tests/ceremony-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Expected: FAIL,原因是管理模块和页面不存在。
- [x] **Step 3: 实现活动、祭品和邀约管理**
```js
function buildInviteeBody(ids) {
var values = Array.from(new Set(ids.map(normalizeId).filter(Boolean)));
return { inviteeUserIds: values };
}
```
活动固定 `status=0`;经纬度必须成对且范围合法;祭品金额非负并通过精确 JSON 数值检查。管理员候选过滤空 `appUserId`,提交空数组时显示取消全部待响应邀请确认。各写操作完成后重读活动详情、祭品或邀请名单。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/ceremony-admin-pages.test.js tests/ceremony-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Run: `node --check public/js/ceremony-admin-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告祭祀阶段**
记录 CeremonyBody、CeremonyGiftBody、CeremonyInviteesBody、权限与金额/文件/停用阻断。
---
### Task 5: 启用 SPECIFIED 世系账号绑定
**Files:**
- Modify: `public/js/lineage-pages.js`
- Modify: `profile-tree.html`
- Modify: `tests/lineage-pages.test.js`
**Interfaces:**
- Consumes: Task 1 `genealogyMemberOptions(genealogyId)`
- Produces: `normalizeBindingMemberOption`, `renderBindingMemberOptions`;扩展现有表单初始化和 `buildLineagePersonBody`
- [x] **Step 1: 写失败的绑定测试**
```js
assert.deepEqual(LineagePages.normalizeBindingMemberOption({
memberId: '9007199254740993030',
appUserId: '9007199254740993031',
memberName: '族员甲',
appUserNickName: '账号甲',
status: '0'
}), {
value: '9007199254740993031',
label: '族员甲(账号甲)'
});
assert.equal(LineagePages.normalizeBindingMemberOption({
memberId: '9007199254740993032',
appUserId: null,
status: '0'
}), null);
```
页面测试断言 `SPECIFIED` 不再 disabled、只显示成员选择器且不存在 appUserId 文本输入。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/lineage-pages.test.js tests/api-client-contract.test.js`
Expected: FAIL,原因是选项仍禁用且规范化函数不存在。
- [x] **Step 3: 最小启用成员选项绑定**
只在 `canManage` 时显示 `SPECIFIED`;选择后内部保存字符串 `appUserId``NONE/SELF` 必须删除 body 中的 `appUserId``SPECIFIED` 必须选择有效 option;403 作为权限错误处理,不清登录态。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/lineage-pages.test.js tests/api-client-contract.test.js`
Run: `node --check public/js/lineage-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告绑定阶段**
把 C15 更新为“由同家谱正常成员 options 解决;未绑定账号的成员不可选”,保留没有任意 AppUser 搜索能力的边界。
---
### Task 6: 家谱成员管理、退出和所有权转移
**Files:**
- Create: `public/js/member-admin-pages.js`
- Create: `tests/member-admin-pages.test.js`
- Modify: `profile-family-admin.html`
- Modify: `tests/pending-pages.test.js`
**Interfaces:**
- Consumes: Task 1 member ApiClient methods、`lineagePersonOptions`、ProfileUI
- Produces: `normalizeMember`, `normalizeMemberOptions`, `buildMemberUpdateBody`, `canEditMember`, `canRemoveMember`, `canTransferOwner`, `renderMemberList`, `initMemberAdminPage`
- [x] **Step 1: 写失败的成员管理测试**
```js
assert.deepEqual(MemberAdminPages.buildMemberUpdateBody({
memberName: '族员甲',
relationName: '侄',
roleType: 'editor',
lineagePersonId: '9007199254740993040',
appUserId: 'must-drop'
}), {
memberName: '族员甲',
relationName: '侄',
roleType: 'editor',
lineagePersonId: '9007199254740993040'
});
assert.throws(() => MemberAdminPages.buildMemberUpdateBody({
roleType: 'owner'
}));
```
渲染测试断言手机号和内部 ID 不可见;owner 不显示移除按钮;当前 owner 不显示退出按钮而显示所有权转移。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/member-admin-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Expected: FAIL,原因是脚本不存在且页面 pending。
- [x] **Step 3: 实现成员管理页面**
成员列表支持关键词刷新;编辑只提交 `memberName/relationName/roleType/lineagePersonId`。角色选择只有 `admin/editor/member`。移除、退出、转让都二次确认并使用响应中的 memberId;成功后重读成员列表,退出成功后清除当前家谱上下文并返回家谱选择页。
- [x] **Step 4: 运行 GREEN**
Run: `node --test tests/member-admin-pages.test.js tests/api-client-contract.test.js tests/pending-pages.test.js`
Run: `node --check public/js/member-admin-pages.js`
Expected: PASS。
- [x] **Step 5: 更新规划并报告成员阶段**
记录成员更新字段、角色差异、退出/移除/转让权限与世系关系解除继续阻断。
---
### Task 7: 导航开放、真实验证和阶段收尾
**Files:**
- Modify: `profile.html`
- Modify: `profile-content.html`
- Modify: `profile-families.html`
- Modify: `docs/PC接口对接规划.md`
- Modify: `tests/pending-pages.test.js`
- Modify: `tests/pc-scope.test.js`
**Interfaces:**
- Consumes: Tasks 16 completed pages
- Produces: 所有功能的稳定导航入口和阶段 6 完成记录
- [x] **Step 1: 写失败的入口行为测试**
```js
for (const page of [
'profile-article.html',
'profile-album.html',
'profile-ceremony.html',
'profile-family-admin.html'
]) {
assert.match(navigationHtml, new RegExp(page.replace('.', '\\.')));
}
```
测试同时断言新增/开放页面不加载 `pending-pages.js`,不存在 APP path、手填 ID 或原始 JSON。
- [x] **Step 2: 运行 RED**
Run: `node --test tests/pending-pages.test.js tests/pc-scope.test.js`
Expected: FAIL,原因是导航尚未开放。
- [x] **Step 3: 开放入口并更新规划**
个人中心和家谱内容入口链接到谱文、相册和祭祀;家谱管理入口链接到成员管理。规划逐模块记录字段表、验证结果、契约差异和唯一未实现的世系关系解除。
- [x] **Step 4: 聚焦和全量自动验证**
Run:
```powershell
node --test tests/article-pages.test.js tests/album-pages.test.js
node --test tests/ceremony-admin-pages.test.js tests/ceremony-pages.test.js
node --test tests/lineage-pages.test.js tests/member-admin-pages.test.js
node --test tests/api-client-contract.test.js tests/pending-pages.test.js tests/pc-scope.test.js
npm test
node --check public/js/article-pages.js
node --check public/js/album-pages.js
node --check public/js/ceremony-admin-pages.js
node --check public/js/member-admin-pages.js
git -c safe.directory=D:/WorkSpace/Web/jiapu diff --check
```
Expected: 全部 PASS。
- [x] **Step 5: 真实账号浏览器验证**
逐页验证登录态、家谱上下文、无数据空状态、筛选、隐私隐藏和控制台错误。真实账号没有家谱时,确认业务请求被阻止且所有入口回到 `profile-families.html?next=...`;不得制造家谱或业务记录。
- [x] **Step 6: 独立只读复核和最终报告**
安排默认子代理只读检查所有阶段 6 文件,主代理按 file:line 抽查。报告使用 `Changed / Verified / Blocked / Next`,明确世系关系解除、文件 URL、停用记录和缺真实数据写入验证的剩余风险。