测试画布补上对话框和滚动,门禁才能继续(BUG-1440)。
Independent Staging Quality Gate / validate (push) Failing after 15m41s
Independent Staging Quality Gate / publish (push) Skipped

run 5404 的 Python 已过,前端 29 条画不出新零件。页面不改。

Co-Authored-By: Grok (agent loop)
This commit is contained in:
jesse-ux
2026-10-25 16:21:42 +08:00
co-authored by Grok (agent loop)
parent 1c428d7b6e
commit 58e5516fa8
6 changed files with 78 additions and 13 deletions
+17
View File
@@ -19526,5 +19526,22 @@
- 复发自:BUG-1029。上次是组件搬家后文件清单没跟上;这次是入口删了,断言还锁着旧标签。
- 修复版本:分支 `codex/gate-birth-journey-contract-20261025`。
- 断言三栏:原值是扫描面必须出现 `<BirthTimeRectification`;新值是扫描面不得出现该标签,且 `page.tsx` 必须挂载 `<ConversationalBirthTimeRectification`;原因是 BUG-1431 去掉了到不了的建档校正卡,校正仍从对话进入。
- 后续:下一道门禁(run 5404)的 Python 检查已过。前端还有 29 条,另记 BUG-1440。测试网站仍未更新。
## BUG-1440 | 测试环境画不出组件库的对话框和滚动
- 状态:resolved(这 6 份检查已过;测试网站要等这次门禁通过才更新)
- 首次发现:2026-10-25
- 最近更新:2026-10-25
- 影响面:测试环境发布。页面行为没有新的改动。
- 用户现象:BUG-1439 合进测试分支之后,测试网站仍停在完整数据版(二)。
- 触发条件:run 5404 的快速门。Python 已过。前端 5188 条里 29 条失败,发布镜像被跳过。
- 根因:检查用的不是浏览器,是一套自己画的页面。组件库往页面上放对话框时会写入子节点、会读样式、会把确认框画到页面主体上;这套画布少了这三样,注销确认、滚动跟随和校正对话就中断。人物卡的名字改到「打开档案」上之后,点「另一位」点到了「更多操作」,新对话仍挂在本人名下。
- 修复:画布补上子节点赋值、样式读取,并在加载组件库之前放好文档,布局效果才不会被关掉。注销确认改到页面主体里核对。打开人物改认「某某 打开档案」,不再点到「更多操作」。页面代码没有改。
- 验证:`account-deletion.test.tsx`、`conversation-scroll-anchor-lifecycle.test.tsx`、`new-chat-from-people-lifecycle.test.tsx`、`rectification-dup-question-20260926.test.tsx`、`rectification-latency-20260926.test.tsx`、`rectification-opening-plain-20260926.test.tsx`,50 条通过,0 失败。没有登录浏览器,不把页面走查写成通过。
- 防复发:组件库对话框和滚动跟随仍走这套画布。画布再少方法时,先补画布,不要改页面来迁就检查。
- 相关记录:BUG-1439、BUG-1043、BUG-1044、BUG-1045、BUG-1046。issue #96。
- 修复版本:分支 `codex/gate-birth-journey-contract-20261025`。
- 断言三栏:原值是注销确认的字在挂载点里,人物按钮的可见文字里要有名字;新值是注销确认到页面主体里找,打开人物只认「某某 打开档案」;原因是确认框画在页面主体上,人物名字在卡片里,打开档案的按钮没有可见文字。
@@ -1,10 +1,10 @@
# PROGRESS · 出生时间合同跟上对话里的校正(2026-10-25)
没有新工单。测试分支已经含启程(二)到组件库收尾,测试网站仍停在完整数据版(二)。这一张只改一条检查,不改页面。
没有新工单。测试分支已经含启程(二)到组件库收尾,测试网站仍停在完整数据版(二)。页面不改,只让检查能画出已经换上的零件。
## 结论
建档里的旧校正卡已经不在。校正从对话进入。Python 门禁还在找旧标签,所以从启程(二)那一笔起没有发布。
建档里的旧校正卡已经不在。校正从对话进入。Python 那条已经跟上。下一道门禁卡在测试画布画不出新零件,画布已补上。测试网站要等这道门禁通过才更新。
## 断言改动(原值 / 新值 / 原因)
@@ -29,6 +29,14 @@
## 门禁事实
- 测试网站 `/api/health` 的 `deployment.gitCommit` 仍是 `0b8fcdb842499a2b38f4e618c8c190ffe92e9cf0`。
- 测试网站 `/api/health` 的 `deployment.gitCommit` 仍是 `0b8fcdb842499a2b38f4e618c8c190ffe92e9cf0`。这一轮没有再去问健康检查。
- #104 的检查通过,发布镜像时下载前端依赖超时,没有部署。
- #96 到 #111 的快速门都停在这条 Python 检查。#111 是 1 条失败,其余通过。
- BUG-1439 合入后的门禁是 run 5404。Python 已过。前端 5188 条里 29 条失败,镜像没有发布。失败都在测试画布:写不了子节点、读不了样式、注销确认不在挂载点里、人物卡点到了「更多操作」。画布补上之后,这 6 份检查 50 条通过。页面没有改。记 BUG-1440。
## 断言改动(BUG-1440,原值 / 新值 / 原因)
| 测试 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `account-deletion.test.tsx` 待注销账号 | 注销确认的字和两个按钮在挂载点里 | 到页面主体里找 | 确认框画在页面主体上,挂载点是空的 |
| `new-chat-from-people-lifecycle.test.tsx` 打开人物 | 按钮可见文字里要有名字 | 只认「某某 打开档案」 | 名字在卡片里。同名的「更多操作」不能当成点开这个人 |
+1 -1
View File
@@ -40,7 +40,7 @@
| `TASK-rectification-explicit-open-20261023.md` | `PROGRESS-rectification-explicit-open-20261023.md` | **明说要校正只回一句话(issue #103)**:找会话直接读一张对服务角色撤权的表、必然失败被吞(真正进行中的也续不上);`candidate_accepted` 被算作进行中挡住入口和提议卡;兜底只给一句话。产品选 C;只改入口口径、经 RPC 找会话、删兜底,不改共享状态集合、不动表。BUG-1423 | 已合入 staging | 分支 `codex/rectification-explicit-open-20261023` |
| `TASK-chart-table-span-20261023.md` | `PROGRESS-chart-table-span-20261023.md` | **星盘页本命 / 月亮行星表挤进左栏(issue #102)**:#87 给表包了 `ChartSymbolHelp` 后跨栏规则失配;根因修法是 `VedicLayout` 用固定整行容器承载表格,跨栏只认容器;加结构断言防复发。BUG-1422 | 已合入 staging | 分支 `codex/chart-table-span-20261023` |
| `TASK-ui-kit-migration-20261023.md` | `PROGRESS-ui-kit-0-base-20261023.md`;`PROGRESS-ui-kit-1-shell-20261023.md`;`PROGRESS-ui-kit-2-chat-20261023.md`;`PROGRESS-ui-kit-3-chart-20261023.md`;`PROGRESS-ui-kit-4-people-20261023.md`;`PROGRESS-ui-kit-5-pages-20261023.md`;`PROGRESS-ui-kit-6-rectification-20261023.md`;`PROGRESS-ui-kit-7-cleanup-20261023.md` | **界面统一换成组件库**:shadcn base-nova(补 `shadcn/tailwind.css`)· Lucide · Motion · react-resizable-panels · dnd-kit · Zustand;lint 拦手写控件(白名单只减不增);对话旁星盘面板;会话、档案拖动排序(动表);长相不变。产品 2026-10-23 拍板 D1~D8;8 个 issue(K0~K7)串行,取代全站动效 #98 #100 #101 | K0–K7 已在测试环境,待验收 | 原型 https://claude.ai/artifact/PGnWsjHhu38KJseEfEMNWG |
| —(门禁修复,无新任务书) | `PROGRESS-birth-journey-contract-20261025.md` | **出生时间合同仍要求建档里的旧校正卡(BUG-1439)**:#96 去掉到不了的卡片后,Python 门禁从那一笔起失败,测试网站停在完整数据版(二)。合同改为锁首页对话里的校正。页面不改 | 已合入 staging,等门禁 | `codex/gate-birth-journey-contract-20261025` |
| —(门禁修复,无新任务书) | `PROGRESS-birth-journey-contract-20261025.md` | **测试分支过不了门禁,网站停在完整数据版(二)**:BUG-1439 让出生时间合同改锁对话里的校正。下一道门禁 Python 已过,前端 29 条画不出新对话框和滚动(BUG-1440),只补测试画布,页面不改 | 已合入 staging,等门禁 | `codex/gate-birth-journey-contract-20261025` |
| `TASK-onboarding-journey-20261022.md` | `PROGRESS-onboarding-1-data-20261022.md`;`PROGRESS-onboarding-2-intake-20261022.md`;`PROGRESS-onboarding-4-motion-20261022.md` | **新用户启程**:建档加「先看谁的盘」(自己 / 帮别人看,本人资料可后补)、人物关系字段、首问免费(服务端原子)、起步三问、启程清单(七项从数据推导,收费项标价,老用户可见)、八条一次性提示(含星盘四页签)、启程流程专用动效。产品 2026-10-22 拍板 D1~D6;动表经产品同意进循环,拆四个 issue 串行。BUG-1420 已被文档去重占用,本轮首问免费记 BUG-1421。(一)T1+T2 在 `codex/onboarding-1-data-20261022`。(二)T3+T4 记 BUG-1431,在 `codex/onboarding-2-intake-20261022`。(四)T7 在 `codex/onboarding-4-motion-20261022` | (一)(二)(三)已在 staging;(四)本分支待验收 | 原型 https://claude.ai/artifact/PcTwMVb9RD611R3qStxv3U |
| `TASK-site-motion-20261022.md` | — | **全站动效**:换页按侧栏方向滑入、选中块跟随、页签、盘型切换星体移宫、换人、翻日期、列表补位、弹窗从触发处长出、抽屉、顶部提示、点数滚动、中英交叉淡入;重写 DESIGN §6(推翻 180 ms 上限与 Spatial Instant),不引动画库,先验证 View Transitions。产品 2026-10-22 拍板;三个 issue,排在启程(三)之后、启程(四)之前;**2026-10-23 被 ui-kit-migration 取代** | 已取代 | 原型 https://claude.ai/artifact/7u9goivnmTNSGTNWNeVMQd |
| `TASK-rectification-narrower-delivery-20261016.md` | `PROGRESS-rectification-narrower-delivery-fix-20261017.md` | **交付区间收窄**:显示改按活着的簇分段。第 1 次打回只补两处显示漏段(取整差 8 分、缺区间整段退回)。对半出题和门槛 8 改 6 仍不做。基线与对照各 924 条,12 格包含率都过红线。BUG-1367 起,本轮原记 BUG-1370,因与婚恋合盘保留号冲突改为 BUG-1392 | 待验收 | `codex/rectification-narrower-delivery-20261016` |
+15 -5
View File
@@ -3,6 +3,7 @@ import { readFileSync } from "node:fs";
import test from "node:test";
import React from "react";
import { createClientLifecycleHarness } from "./react-client-lifecycle-test-support";
import { AccountDeletionGate } from "../src/components/account-deletion-gate";
import { AccountDeletionSection } from "../src/components/account-deletion-section";
import {
@@ -19,13 +20,17 @@ import {
runAccountDeletionTick,
} from "../src/lib/account-deletion-worker-core";
import { LEGAL_ENTITY } from "../src/lib/legal-entity";
import { createClientLifecycleHarness } from "./react-client-lifecycle-test-support";
// Self-service account deletion with a 7-day cooling-off period (2026-09-30).
Object.assign(globalThis, { React });
const read = (path: string) => readFileSync(new URL(path, import.meta.url), "utf8");
const migration = read("../supabase/migrations/20260930020000_account_deletion_requests.sql");
type HostNode = { tagName: string; text: string; childNodes: HostNode[] };
function walkHost(node: HostNode): HostNode[] {
return node.childNodes.flatMap((child) => [child, ...walkHost(child)]);
}
test("status parsing, confirmation word, date and error codes", () => {
assert.deepEqual(parseAccountDeletionStatus(null), { status: "none" });
assert.deepEqual(parseAccountDeletionStatus({ status: "none" }), { status: "none" });
@@ -152,10 +157,15 @@ test("a pending account sees 「账号注销中」 with the date and a way back"
try {
await h.render(<AccountDeletionGate signedIn />);
await h.idle();
assert.match(h.container.text, /账号注销中/);
assert.match(h.container.text, /10 月 7 日 永久删除/);
assert.ok(h.elements().some((node) => node.tagName === "BUTTON" && node.text === "撤销注销"));
assert.ok(h.elements().some((node) => node.tagName === "BUTTON" && node.text === "退出登录"));
// 原值:这句话和两个按钮在挂载点里面。
// 新值:到文档体里找。挂载点本身是空的。
// 原因:注销确认改成组件库对话框,内容画在页面主体上,不留在原来的格子里。
const surface = h.document.body;
const surfaceNodes = [surface, ...walkHost(surface)];
assert.match(surface.text, /账号注销中/);
assert.match(surface.text, /10 月 7 日 永久删除/);
assert.ok(surfaceNodes.some((node) => node.tagName === "BUTTON" && node.text === "撤销注销"));
assert.ok(surfaceNodes.some((node) => node.tagName === "BUTTON" && node.text === "退出登录"));
await h.render(<AccountDeletionGate signedIn={false} />);
} finally {
globalThis.fetch = originalFetch;
@@ -275,7 +275,11 @@ async function openDocument(tab: Tab, href: string, options: { mobile?: boolean
// 原值:排除类名 `profile-trigger`
// 新值:排除类名 `sidebar-account-trigger`
// 原因:K1 账户按钮改名。页面上同文案的按钮仍可点,不被页脚账户按钮抢走
const found = nodes().find((node) => node.tagName === "BUTTON" && node.text.includes(label)
// 原值:按钮的可见文字里要有名字。
// 新值:打开档案的按钮没有可见文字,名字在 aria-label「某某 打开档案」上。
// 原因:K4 把名字放在卡片里。同名的「更多操作」不能被当成点开这个人。
const found = nodes().find((node) => node.tagName === "BUTTON"
&& (node.text.includes(label) || String(node.props["aria-label"] ?? "") === `${label} 打开档案`)
&& !String(node.props.className ?? "").split(/\s+/).includes("sidebar-account-trigger"));
assert.ok(found, `button ${label} is rendered`);
return found;
@@ -406,7 +410,7 @@ for (const person of [
let doc: Awaited<ReturnType<typeof openDocument>> | undefined;
try {
doc = await openRectificationThenGoTo(tab, api, "星盘档案");
await doc.settle(() => doc!.h.elements().some((node) => node.tagName === "BUTTON" && node.text.includes("阿澈")), "the people list");
await doc.settle(() => doc!.h.elements().some((node) => node.tagName === "BUTTON" && String(node.props["aria-label"] ?? node.text).includes("阿澈")), "the people list");
await doc.h.event(doc.button(person.row));
await doc.h.event(doc.button("和 TA 对话"));
const assigned = tab.location.assigned.at(-1);
@@ -445,7 +449,7 @@ test("a stashed rectification session of the person you left is not reopened as
doc = await openRectificationThenGoTo(tab, api, "星盘档案");
// 原值: === RECT(离开时存根)。新值: === null。原因: BUG-1052 D3 不再存根;落点断言不变。
assert.equal(tab.session.getItem("jyotisha.session-url-return"), null);
await doc.settle(() => doc!.h.elements().some((node) => node.tagName === "BUTTON" && node.text.includes("阿澈")), "the people list");
await doc.settle(() => doc!.h.elements().some((node) => node.tagName === "BUTTON" && String(node.props["aria-label"] ?? node.text).includes("阿澈")), "the people list");
await doc.h.event(doc.button("阿澈"));
await doc.h.event(doc.button("看星盘"));
const assigned = tab.location.assigned.at(-1);
@@ -73,7 +73,18 @@ class HostElement {
return this.parentNode?.closest(selector) ?? null;
}
get parentElement() { return this.parentNode; }
get children() { return this.childNodes.filter((child) => child.nodeType === 1); }
private hasAssignedChildren = false;
private assignedChildren: unknown = undefined;
get children() {
// A test or a library may replace `children`. Until that happens, the live
// element list is the child nodes.
if (this.hasAssignedChildren) return this.assignedChildren;
return this.childNodes.filter((child) => child.nodeType === 1);
}
set children(value: unknown) {
this.hasAssignedChildren = true;
this.assignedChildren = value;
}
matches() { return false; }
querySelector() { return null; }
querySelectorAll() { return []; }
@@ -158,6 +169,17 @@ class HostDocument {
createElementNS(_namespace: string, tag: string) { return this.createElement(tag); }
}
// Base UI chooses `useLayoutEffect` or a no-op once, the first time it loads,
// by reading `typeof document`. Tests import this harness before those
// components so the choice sticks. Each test still installs its own document.
if (typeof globalThis.document === "undefined") {
Object.defineProperty(globalThis, "document", {
value: new HostDocument(),
writable: true,
configurable: true,
});
}
export function createClientLifecycleHarness() {
const document = new HostDocument();
const frames = new Map<number, ReturnType<typeof setTimeout>>();
@@ -184,6 +206,10 @@ export function createClientLifecycleHarness() {
filter: "none",
willChange: "auto",
contain: "none",
getPropertyValue(name: string) {
if (name === "--space-4") return "16px";
return "";
},
};
class KeyboardEvent {}
class FocusEvent {}