fix(synastry): 补上 issue #85 第 1 次验收打回的三处

G1 路由清单在 synastry 与 terms 之间加入 synastry/[reportId]。G2 把「同一对已经有报告时,仍打开原来那份」改成「同一对正在生成时,仍打开那份」。G3 删掉永远成立的侧栏断言,改为渲染 /synastry/<id> 并核对当前项是婚恋合盘。

Co-Authored-By: Grok (agent loop)
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jesse-ux
2026-10-22 01:19:45 +08:00
co-authored by Grok (agent loop) Cursor
parent 2579fa14e5
commit 413140b40c
6 changed files with 95 additions and 4 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
## 2026-10-21 — 合盘两人顺序跟选人页一致
- 先选的人写在前面。等待页、报告标题、报告中心卡片和追问对话标题用同一个顺序。同一对已经有报告时,仍打开原来那份。Skill 版本不变(BUG-1407)。
- 先选的人写在前面。等待页、报告标题、报告中心卡片和追问对话标题用同一个顺序。同一对正在生成时,仍打开那份。Skill 版本不变(BUG-1407)。
## 2026-10-21 — 合盘正文不再重复节标题
@@ -69,7 +69,7 @@
## T10 两人顺序(BUG-1407)
- 根因:去重把两个人按编号从小到大存放,生成又把较小的编号当成甲。等待页用的是选人页当场记下的顺序,所以只有等待页没反。
- 创建时把选人顺序另存一节。生成按这个顺序加载两个人,标题、卡片和追问标题跟着走。去重用的编号排序不动。同一对已经有报告时仍打开原来那份,顺序是当时保存的。
- 创建时把选人顺序另存一节。生成按这个顺序加载两个人,标题、卡片和追问标题跟着走。去重用的编号排序不动。同一对正在生成时,仍打开那份,顺序是当时保存的。
- 真机(执行方未做,不得写成已通过):先选甲、再选乙。等待页、揭幕后的标题、报告中心卡片、从报告进的对话标题都是甲 × 乙。
## 这一轮检查
@@ -0,0 +1,42 @@
# 进度:婚恋合盘 T7~T11 第 1 次打回(issue #85)
- 修复单:`docs/tasks/TASK-synastry-staging-fix-fix-20261022.md`
- 被验提交:`57ee79cc5`
- 分支:`codex/synastry-staging-fix-20261021`
- 工作树:`.worktrees/synastry-staging-fix-20261021`
- 开工:`git fetch origin --prune` 后 `git merge origin/staging`(不 rebase)。无冲突。合入提交 `2579fa14e`,只带进任务书和验收清单。
- 没有新 BUG 号。没有改数据库、`.gitea`、引擎。没有 push。
## 做了什么
- G1:`tests/test_api_server_security.py` 的路由表在 `synastry` 和 `terms` 之间加入 `synastry/[reportId]`,旁边写了三栏。
- G2:`CHANGELOG.md` 的 BUG-1407 和 `PROGRESS-synastry-staging-fix-20261021.md` 的 T10,把「同一对已经有报告时,仍打开原来那份」改成「同一对正在生成时,仍打开那份」。其余句子没动。
- G3:删掉 `frontend/tests/synastry-report-surface.test.ts` 里永远成立的 `assert.equal("/synastry/abc".startsWith("/synastry/"), true)`。改为按现有侧栏渲染写法(`SidebarProvider` + `AppSidebar`)渲染侧栏,`pathname` 为 `/synastry/abc`,当前项 `data-active="true"` 是「婚恋合盘」,「我的报告」不是。合盘开关按外壳传入打开,否则这一项不会画出来。
## 断言三栏
| 位置 | 原值 | 新值 | 原因 |
| --- | --- | --- | --- |
| `tests/test_api_server_security.py` 路由表 | `synastry` 后面直接是 `terms` | 加入 `synastry/[reportId]` | issue #85 T7 合盘报告独立地址 |
| `frontend/tests/synastry-report-surface.test.ts` 侧栏当前项 | `assert.equal("/synastry/abc".startsWith("/synastry/"), true)` | `pathname` 为 `/synastry/abc` 时渲染侧栏,`data-active="true"` 的是「婚恋合盘」,「我的报告」不是 | 原断言永远成立,没有渲染侧栏 |
## 命令与数字
| 检查 | 结果 |
| --- | --- |
| 快速门 | 退出码 0。pytest 1099 条:通过 1097,失败 0,错误 0,跳过 2。G1 `test_capability_audit_scans_registry_and_local_sources` 通过。失败名单空。跳过的两条不是失败:`test_dig_bala_jupiter_synthetic_north_china_case_needs_better_than_house_only_linear_model`、`test_slice_glyphs_cover_their_table_characters` |
| `tsc --noEmit -p .` | 退出码 0,0 错 |
| `npm run lint` | 退出码 0。0 error,122 warning,与上一轮进度记录的基线相同 |
| `synastry-report-surface.test.ts` | 退出码 0。2 通过,0 失败,0 cancelled |
工作树没有 `.venv`,解释器用 `.worktrees/agent-loop/.venv/Scripts/python.exe`。
`tools/agent-loop/windows-known-failures.txt` 列的是前端 `test:db` 的 13 条,不是这次 pytest。这次 pytest 没有失败,没有多出来的名字。
## 偏离
- 快速门加了 `--skip-frontend-runtime`。`quick` 默认还会跑前端全量 `npm test`、lint 和 `next build`。修复单写明全量测试和 build 由验收方复跑,本轮不跑。Python 编译、审计和 pytest 目标与 `--profile quick` 相同。
- 第一次快速门退出码 1,发生在 pytest 之前:控制台编码是 GBK,`scripts/validate_bphs_invariants.py` 打印符号时 `UnicodeEncodeError`。没有改这个脚本。重跑设置 `PYTHONUTF8=1` 后,上面的退出码 0 才是结果。
- 为了在门禁只印一行时仍能数 pytest,设置了 `PYTEST_ADDOPTS` 把 junit 写到 `gate-logs/`(已被忽略,未提交)。
- `docs/tasks/README.md` 里这份修复单的进度改指 `PROGRESS-synastry-staging-fix-fix-20261022.md`,状态从待领取改为待验收。修复单「不用改的」那一节没有动。
- 快速门把 5 个 `references/oracle/artifacts/pending_packets/*.json` 改成了 CRLF,内容无差。已还原,没有提交。
+1 -1
View File
@@ -520,4 +520,4 @@
| `TASK-goel-jaimini-strength-20261022.md` | — | #94 搬上游公开的 Goel Jaimini 强度与 Brahma / Maheshwara / Rudra(4 个上游原样文件,按 #81 决策 A 先例根目录预算 448→452;来源串改书目引用);只做计算、不改报告;冻结身份不变 | 待领取(可与 #89 并行;须在 #91 印章前合入) | `codex/goel-jaimini-strength-20261022` |
| `TASK-chart-subject-table-fix-20261022.md` | `PROGRESS-chart-subject-table-20261021.md` | **#86 第 1 次打回**:报告所有表被强制单行(打印跑出纸面);保存他人复制了档案页代码且已分叉;点编辑框内边距会丢修改 | 待领取 | `codex/chart-subject-table-20261021` |
| `TASK-full-data-parity-3-20261022.md` | — | #93 完整数据版补齐(三):「独立年度方法参考」正式环境调 PyJHora(子进程隔离;装上后其余输出逐字节不变,7 个换路径开关钉死);输入默认用本地时间与正确年份(D6 待产品确认) | 待领取(等 #91 合入) | `codex/full-data-parity-3-20261022` |
| `TASK-synastry-staging-fix-fix-20261022.md` | `PROGRESS-synastry-staging-fix-20261021.md` | **#85 第 1 次打回**(T7~T11 主体通过):新地址没登记进路由清单致快速门红;CHANGELOG 与 T11 矛盾;侧栏点亮测试是空断言 | 待领取 | `codex/synastry-staging-fix-20261021` |
| `TASK-synastry-staging-fix-fix-20261022.md` | `PROGRESS-synastry-staging-fix-fix-20261022.md` | **#85 第 1 次打回**(T7~T11 主体通过):新地址没登记进路由清单致快速门红;CHANGELOG 与 T11 矛盾;侧栏点亮测试是空断言 | 待验收 | `codex/synastry-staging-fix-20261021` |
+46 -1
View File
@@ -1,9 +1,46 @@
import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import { createElement } from "react";
import { renderToStaticMarkup } from "react-dom/server";
import { PathnameContext } from "next/dist/shared/lib/hooks-client-context.shared-runtime";
import { AppSidebar } from "../src/components/app-sidebar.tsx";
import { SidebarProvider } from "../src/components/ui/sidebar.tsx";
import { reportSurfaceRedirect } from "../src/lib/synastry-report-href.ts";
function renderSidebar(pathname: string) {
return renderToStaticMarkup(
createElement(
PathnameContext.Provider,
{ value: pathname },
createElement(
SidebarProvider,
null,
createElement(AppSidebar, {
sessions: [],
activeSessionId: null,
compatibilityEnabled: true,
account: {
name: "示例甲",
email: "fictional@example.invalid",
credits: 12,
initial: "示",
avatar: null,
},
}),
),
),
);
}
function navAnchor(markup: string, label: string) {
const anchors = markup.match(/<a\b[^>]*\breport-nav-button\b[^>]*>[\s\S]*?<\/a>/g) ?? [];
const hit = anchors.find((anchor) => anchor.includes(`>${label}<`));
assert.ok(hit, `sidebar nav link missing: ${label}`);
return hit;
}
const read = (path: string) => readFileSync(new URL(path, import.meta.url), "utf8");
test("an old synastry url moves to the synastry route and keeps the language", () => {
@@ -37,7 +74,15 @@ test("the synastry route reuses the reader and the sidebar stays on 婚恋合盘
assert.match(reader, /window\.location\.replace\(redirectTo\)/);
assert.match(sidebar, /\{ href: "\/synastry", label: "婚恋合盘"/);
assert.match(sidebar, /pathname === href \|\| pathname\.startsWith\(`\$\{href\}\/`\)/);
assert.equal("/synastry/abc".startsWith("/synastry/"), true);
// 原值: assert.equal("/synastry/abc".startsWith("/synastry/"), true)
// 新值: pathname 为 /synastry/<id> 时渲染侧栏,当前项(data-active="true")是「婚恋合盘」,不是「我的报告」
// 原因: 原断言永远成立,没有渲染侧栏(issue #85 G3)
const markup = renderSidebar("/synastry/abc");
const synastry = navAnchor(markup, "婚恋合盘");
const reports = navAnchor(markup, "我的报告");
assert.match(synastry, /\bdata-active="true"/);
assert.doesNotMatch(reports, /\bdata-active="true"/);
assert.doesNotMatch(reports, /aria-current="page"/);
assert.match(picker, /window\.location\.assign\(`\/synastry\/\$\{payload\.report\.id\}`\)/);
assert.match(picker, /href=\{`\/synastry\/\$\{report\.id\}`\}/);
assert.doesNotMatch(picker, /\/reports\/\$\{/);
+4
View File
@@ -1643,6 +1643,10 @@ def test_capability_audit_scans_registry_and_local_sources() -> None:
# 新值: 中间加入 synastry
# 原因: 婚恋合盘页 app/(app)/synastry 进入页面扫描(issue 82,2026-10-19)
'synastry',
# 原值: synastry 后面直接是 terms
# 新值: 加入 synastry/[reportId]
# 原因: issue #85 T7 合盘报告独立地址
'synastry/[reportId]',
'terms',
]
# 原值: 期望集合含 'Case Validation'