Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0155nFCgCHtoA7jhSDGmZmMu
103 lines
18 KiB
Markdown
103 lines
18 KiB
Markdown
# TASK · 设置弹窗重排(固定尺寸、列表→详情)与「账户与点数」入弹窗、删除 `/membership` 整页(2026-09-06)
|
||
|
||
- 基线:`origin/staging` @ `985c3258`
|
||
- 分支:`codex/settings-dialog-and-billing-pane-20260906`
|
||
- 执行方:coding agent;验收:Claude
|
||
- 涉及文件:`frontend/src/components/account-dialog-overlay.tsx`、`frontend/src/components/chart-library-panel.tsx`、新建 `frontend/src/components/profile-panel.tsx`、新建 `frontend/src/components/billing-panel.tsx`(+ `frontend/src/hooks/use-billing-panel.ts` 或 `frontend/src/lib/billing-order-polling.ts`)、新建 `frontend/src/lib/settings-url.ts`、`frontend/src/lib/home-types.ts`、`frontend/src/lib/membership.ts`、`frontend/src/app/page.tsx`(只改传参与调用,**必须净减行**:`renderProfile` 的 JSX 整段搬出)、`frontend/src/components/app-sidebar.tsx`、`frontend/src/components/onboarding-redeem-paywall.tsx`、`frontend/src/components/rectification-agentic-chat.tsx`、`frontend/src/hooks/use-consultation-run.ts`、`frontend/src/app/globals.css`、`frontend/next.config.ts`、**删除** `frontend/src/app/membership/page.tsx` 与 `frontend/src/app/membership/orders/page.tsx`、`tests/test_api_server_security.py`(路由集合)、`frontend/DESIGN.md`、`frontend/docs/VOICE.md`
|
||
- BUG 编号起点:**BUG-554**(BUG-553 由 `TASK-session-list-title-and-order-20260906.md` 预留;开工时复核最大号)
|
||
- 串行:同日顺序 **composer(551/552)→ 会话列表(553)→ 本单**。本单大改 `page.tsx`、`app-sidebar.tsx`、`use-consultation-run.ts`、`rectification-agentic-chat.tsx`,必须在前两单合入 staging 后再开分支。
|
||
- 删除页面属于路由集合变更:本单必须在同一变更里跑 `.venv/bin/python -m pytest tests/test_api_server_security.py -k capability_audit`(BUG-143 的防复发要求),不得只跑前端矩阵。
|
||
|
||
## 1. 事故实证(staging,2026-09-06,桌面 Chrome,产品截图三张)
|
||
|
||
| # | 用户看到 | 期望(对标 Claude 设置弹窗) |
|
||
| --- | --- | --- |
|
||
| 1 | 「星盘资料」格:本人资料条与「编辑本人资料」按钮之后,空态一句「还没有其他星盘,先添加一份资料」紧接**一整张**「添加其他人的星盘」表单(关系下拉、称呼、出生日期、「你对出生时间了解多少」单选组……)铺在同一个滚动区里,弹窗撑到接近整屏 | 列表是列表,表单是点了「添加」之后才出现的二级视图 |
|
||
| 2 | 三个分区弹窗尺寸各不相同:「通用设置」是一个只装得下主题三选的小盒子,「个人资料」「星盘资料」是宽而高的大盒子,切换分区整个弹窗跳变 | 一个固定尺寸的设置窗,左侧导航不动,右侧内容区自己滚 |
|
||
| 3 | 「个人资料」格:96px 头像预览 + 8 个配色块 + 「换一个形象」占了上半屏,账户信息两行,底部又一个「管理星盘资料」按钮(与左侧导航重复) | 头像一行搞定;重复入口删除 |
|
||
| 4 | 左侧导航第四项「账户与点数」点下去**离开首页**跳 `/membership` 整页,弹窗关闭,返回后要重新加载 | 它就是第四个分区 |
|
||
| 5 | 「星盘资料」里每一条其他人资料一行塞下 关系下拉 + 用于合盘 + 编辑 + 设为默认 + 删除;合盘结果卡和合盘历史平铺在列表下面 | 列表条只显示人;操作在这个人的详情里 |
|
||
|
||
## 2. 根因(行号以基线为准)
|
||
|
||
1. `chart-library-panel.tsx` L170–282:一个 `<div className="chart-library-panel">` 顺序渲染本人组 → 本人编辑表单(`editingSelfChart`)→ 其他人组 → 合盘卡 → 合盘历史 → **无条件渲染**的「添加 / 编辑其他人」表单(L263)。没有视图状态,只有堆叠。
|
||
2. `globals.css` L1637–1639:`.account-modal { width: min(100%, 560px); max-height: min(84dvh, 760px) }`,`.chart-library-modal { width: min(100%, 760px) }`,`profile-modal` 另有 sticky 头;高度由内容决定,所以主题格是小盒、资料格是大盒。`home-types.ts` L191 `accountDialogClasses` 给每个分区一个不同的 modal class,是尺寸分裂的源头。
|
||
3. `page.tsx` L1620–1670 `renderProfile` 把头像编辑区与账户信息 JSX 直接写在 `page.tsx` 里(约 50 行),末尾 L1668 `<button …>管理星盘资料</button>` 只是 `openAccountDialog("chart-library")`——与导航项重复。
|
||
4. `account-dialog-overlay.tsx` L88 第四个导航项没有 `dialog` 值,直接 `model.openRedeem` → `page.tsx` L1618 `router.push(membershipHref("account-menu"))`。`AccountDialog` 类型(`home-types.ts` L135)只有 `profile | chart-library | general | logout`,没有 billing 分区。
|
||
5. `/membership` 是独立页面(`membership/page.tsx` 583 行 + `orders/page.tsx` 127 行):套餐两页签、兑换码子弹窗、订单入口;支付走 `/api/payment/epay/create` → `window.open(payUrl)` → 每 3 秒轮询 `/api/payment/epay/status`(可见性感知),这套完全能装进弹窗。入口共 7 处:`page.tsx` L1618/L1782/L1810、`onboarding-redeem-paywall.tsx` L129(`<Link>`)、`rectification-agentic-chat.tsx` L825(`window.location.assign`,硬跳转)、`use-consultation-run.ts` L527/L722(`router.push`)。
|
||
|
||
## 3. 决策记录(产品已授权,2026-09-06)
|
||
|
||
1. **一个固定尺寸的设置弹窗**:桌面 `width: min(100vw - 32px, 880px); height: min(84dvh, 640px)`;左栏导航 176px 固定不滚,右栏内容独立 `overflow-y: auto`;标题栏固定在顶部。四个分区共享同一尺寸,切换分区不改变外框。`accountDialogClasses` 里 `profile-modal / chart-library-modal / general-modal` 三个 class 合并为一个 `settings-modal`(`logout-modal` 保留,退出确认仍是 400px 小弹窗)。≤767px 保持现有全屏 + 顶部四格页签的布局,只把内容区改成独立滚动。
|
||
2. **分区顺序与内容**:个人资料 → 星盘资料 → 账户与点数 → 通用设置。退出登录仍在头像菜单,不进导航。
|
||
3. **个人资料格**:一行头像编辑(当前头像 48px + 8 个配色块 + 「换一个形象」文字按钮),账户信息(昵称、登录邮箱)两行。删除「管理星盘资料」按钮。整段 JSX 搬进 `profile-panel.tsx`,`page.tsx` 只传 props。
|
||
4. **星盘资料格改为列表 → 详情**:
|
||
- 列表视图:「我的星盘」一条(名字、出生摘要、「当前默认」标签);「其他人」若干条(名字、关系标签、出生摘要);底部一个「添加其他人」按钮。列表条整条可点,右侧一个 chevron,不再一行五个按钮。
|
||
- 详情视图(同一内容区内切换,顶部一个「← 星盘资料」返回):本人详情 = 编辑表单;其他人详情 = 编辑表单 + 操作行(设为默认 / 删除)+ 「合盘」小节(关系类型 + 「用于合盘」)+ 该人的合盘记录(现有 `synastryHistory` 按 `partnerName` 归属,新记录保存时顺手带上 `partnerChartId`,执行方可以只加字段不做迁移;无记录不渲染小节)。
|
||
- 添加视图:点「添加其他人」进入,与其他人详情共用同一表单组件,保存后回到列表。
|
||
- 全局的「合盘结果摘要」卡与「合盘历史」列表从列表视图删除(决策 4 的详情视图接管);合盘触发后弹窗关闭、问题进入输入框的既有行为不变。
|
||
- 视图状态(`list | self | other:<id> | add`)放在 `chart-library-panel.tsx` 内部或 `frontend/src/lib/chart-library-view.ts` 纯函数里,不进 `page.tsx`。
|
||
5. **「账户与点数」成为第四分区**(`AccountDialog` 增加 `"billing"`):顶部一行摘要(余额 N 点 · 会员状态 + 到期);下面四个页签:会员套餐 / 点数包 / 兑换码 / 订单记录。套餐与点数包卡片、购买按钮、支付轮询、失败重试语义**原样搬**(`membership-page.test.ts` 锁住的所有行为都要在新组件里成立);兑换码不再是子弹窗,是页签内的行内表单;订单记录页签内直接列 `/api/payment/orders`。`plan` 高亮参数保留(`membershipHref(source, { plan })` 传来的推荐套餐卡片高亮并滚到可见)。
|
||
6. **删除 `/membership` 与 `/membership/orders` 两个页面**(产品明确要求,不是重定向壳)。`next.config.ts` 加 `redirects()`:`/membership` → `/?settings=billing&source=legacy`,`/membership/orders` → `/?settings=billing&tab=orders`(`permanent: false`)。`membershipHref()` 改为生成 `/?settings=billing&source=…[&plan=…]`,只给深链接与重定向用。
|
||
7. **入口统一走回调,不走路由**:`page.tsx` 三处、`app-sidebar.tsx` 头像菜单、`onboarding-redeem-paywall.tsx`(`<Link>` 改为按钮回调,付费墙关闭后打开设置弹窗 billing 格)、`rectification-agentic-chat.tsx` 的 402、`use-consultation-run.ts` 的两处余额不足,全部改为调用 `openAccountDialog("billing", { source, plan })`(hooks 与组件通过参数接收该回调,遵守参数式 hook 内 0 个 React hook 的既定模式)。`?settings=billing` 参数只在首页启动与 popstate 时由 `lib/settings-url.ts::parseSettingsQuery(search)` 读取一次,打开弹窗后 `history.replaceState` 抹掉参数——与既有 `chat-session-url.ts` 的做法一致,`/` 保持 Static。
|
||
8. **等待态**:弹窗打开时账户余额已在内存里(`account`),摘要行直接渲染;套餐卡片来自 `/api/payment/packages`,到达前该区域留空,不得出现 spinner / 骨架 / 「正在加载」(AGENTS §6)。购买中按钮文案「跳转收银台…」与轮询中的「等待支付结果」作为按钮/行内状态文字保留(这是流程中的状态,不是加载动画)。
|
||
9. `billing-panel.tsx` 通过 `next/dynamic`(`ssr: false`)按需加载,保证首屏 gzip 不因搬入而增长。
|
||
|
||
## 4. 硬红线
|
||
|
||
- `page.tsx` 不得增行,本单必须**净减**(`renderProfile` JSX 搬走);进度记录写开工 / 收尾行数。
|
||
- 不改支付、兑换、订单三组 API 与其 zod 契约;不改管理端;不动数据库迁移。
|
||
- 硬跳转清单(`chat-navigation-a11y-contract.test.ts` 锁定)只能缩小,不得新增 `window.location.assign`;`/login` 认证重定向保持硬跳转。
|
||
- 收银台仍用 `window.open` 新标签打开(BUG-251 与导航合同测试都要求原生行为)。
|
||
- 焦点陷阱、Escape 关闭、关闭后焦点回到触发按钮、`aria-modal` 等既有可达性行为不变(`account-dialog-overlay.test.ts`)。
|
||
- 删除页面后 `git grep -n "/membership" frontend/src` 只允许命中 `next.config.ts` 的重定向与 `membership.ts` 里生成 `?settings=billing` 的注释;`membership-page.test.ts`、`membership-navigation-contract.test.ts` 的 26 条测试**迁移**到新组件文件而不是删除,总数不降,每条改动在三栏表里说明。
|
||
- `tests/test_api_server_security.py` 的精确路由集合去掉 `membership` 与 `membership/orders`,同一提交里跑通。
|
||
- 任务书 / 进度 / Bug 历史 / 测试 fixture 不得出现产品截图里的昵称与邮箱;示例只用虚构数据。
|
||
- 改 UI 的同一提交更新 `frontend/DESIGN.md`:「Profile dialog」改写为「Settings dialog」(固定尺寸、四分区、列表→详情),删除「Redeem dialog」「Membership」两节,新增「Billing pane」;「Account popover」的 redeem 行改指向 billing 分区。
|
||
|
||
## 5. 任务分解
|
||
|
||
### 5.1 弹窗壳:固定尺寸 + 四分区(BUG-554 的一半)
|
||
- `home-types.ts`:`AccountDialog = "profile" | "chart-library" | "billing" | "general" | "logout"`;`accountDialogTitles` 加「账户与点数」;`accountDialogClasses` 收敛为 `settings-modal`(四个分区)+ `logout-modal`。
|
||
- `account-dialog-overlay.tsx`:导航数组加 billing 项(图标沿用 `WalletCards`),删 `openRedeem`;`AccountOverlayModel` 加 `renderBilling`;`openAccountDialog` 支持 `{ source, plan, tab }` 附加参数(放进 model 传给 billing 面板)。
|
||
- `globals.css`:`.settings-modal` 固定尺寸、`.account-settings-shell` 改为 `grid-template-rows: auto 1fr` + 内容区 `min-height: 0; overflow-y: auto`;删除 `.chart-library-modal` / `.profile-modal` 专属 sticky 头规则;移动端规则只改内容区滚动。
|
||
- 验收:`tests/account-dialog-overlay.test.ts` 加——四个分区导航项齐全且 `billing` 有 `aria-current`;不存在 `openRedeem`;`globals.css` 中 `.settings-modal` 同时声明 `width` 与 `height`,且 `chart-library-modal` / `profile-modal` 不再出现。
|
||
|
||
### 5.2 个人资料格搬出 `page.tsx`
|
||
- 新建 `profile-panel.tsx`(props:`account`、`profile`、`avatarSaving`、`avatarNotice`、`accountError`、`persistAvatar`);头像一行布局;删除「管理星盘资料」按钮。
|
||
- 验收:`page.tsx` 行数净减 ≥ 40;`tests/account-dialog-overlay.test.ts` 或新建 `tests/profile-panel.test.ts`:不含「管理星盘资料」;头像配色 radiogroup 与「换一个形象」仍在。
|
||
|
||
### 5.3 星盘资料格:列表 → 详情(BUG-554 的另一半)
|
||
- 按决策 4 重做 `chart-library-panel.tsx`;表单组件抽成 `chart-profile-form.tsx`(本人 / 其他人共用,`ProfileFields` + `AyanamsaPreferenceField` 不动)。
|
||
- 验收:`tests/chart-library-other-profile.test.ts`、`tests/chart-library-session.test.ts` 按三栏表改;新增用例——初始视图为列表且源码中不存在无条件渲染的 `<form`;「添加其他人」进入 add 视图、保存后回到列表;其他人详情包含 设为默认 / 删除 / 用于合盘;本人详情无删除;合盘记录按人归属、无记录不渲染。
|
||
|
||
### 5.4 「账户与点数」分区(CHANGELOG 项)
|
||
- 新建 `billing-panel.tsx` + `use-billing-panel.ts`(账户 / 套餐并发拉取、下单、`window.open`、可见性感知轮询与清理、失败态、兑换、订单列表;逻辑从 `membership/page.tsx` 搬,删除 `goBack`、`membership-loading`、子弹窗焦点陷阱);`page.tsx` 的 `renderBilling` 用 `next/dynamic` 挂载。
|
||
- 删除两个页面文件及其专属 CSS(`membership-page / membership-shell / membership-header* / membership-back / membership-hero*` 等只服务整页的规则),卡片、页签、按钮样式在 `.billing-panel` 下复用。
|
||
- `next.config.ts` 加两条重定向;`membership.ts::membershipHref` 改生成 `?settings=billing`;新建 `lib/settings-url.ts`(`parseSettingsQuery` / `stripSettingsQuery` 纯函数)。
|
||
- 七处入口改回调(决策 7)。
|
||
- 验收:`tests/billing-panel.test.ts` 承接 `membership-page.test.ts` 与 `membership-navigation-contract.test.ts` 的全部 26 条(逐条对照:下单带 `productId` 并安全打开收银台;终态失败停轮询并可用同一商品重试;`failed` 优先于 `paid`、非终态继续轮询;支付成功只刷新余额并 `notifyBalanceChanged`;兑换码去空白不改大小写;兑换成功报点数与最新余额;`plan` 参数高亮对应卡;隐藏页签暂停轮询、回来刷新;清理释放计时器与监听;账户与套餐并发请求;一个请求失败不隐藏另一区;401 仍硬跳 `/login`);`tests/settings-url.test.ts` 新建(`?settings=billing&source=x&plan=monthly&tab=orders` 解析、无参数返回 null、strip 后其他参数保留);`chat-navigation-a11y-contract.test.ts` 三栏表更新:软导航清单中 4 条 `membershipHref` 改为回调断言,硬跳转清单减少 1 条(`rectification-agentic-chat` 的 402);`tests/test_api_server_security.py -k capability_audit` 通过;`next build` 输出中不再有 `/membership`。
|
||
|
||
### 5.5 记录
|
||
- `docs/BUG_HISTORY.md`:BUG-554(设置弹窗尺寸随分区跳变 + 星盘资料格无条件铺开整张表单;关联 BUG-434 弹窗样式记录、BUG-251 导航记录)。
|
||
- `CHANGELOG.md`:设置弹窗固定尺寸与四分区、星盘资料列表→详情、账户与点数入弹窗、`/membership` 页面删除并重定向。
|
||
- `frontend/DESIGN.md`(§4 硬红线最后一条);`frontend/docs/VOICE.md`:分区名「账户与点数」、页签名「会员套餐 / 点数包 / 兑换码 / 订单记录」、列表条与详情返回的文案。
|
||
- `docs/tasks/PROGRESS-settings-dialog-and-billing-pane-20260906.md`;`docs/testing/settings-dialog-and-billing-pane-20260906.md`(真实环境:四个分区切换外框不动;星盘资料默认只有列表;添加→保存→回列表;点余额、头像菜单、余额不足 402、付费墙四个入口都在当前页打开弹窗且对话状态不丢;下单打开新标签、回来后余额刷新;旧链接 `/membership` 落到首页并自动打开分区;手机端全屏四页签内容可滚)。
|
||
|
||
## 6. 让步顺序
|
||
|
||
5.1 → 5.2 → 5.4 → 5.3 → 5.5。若 5.3 的详情视图在本轮做不完,允许先只做「添加表单默认收起、点按钮展开」并在进度记录写明,但 5.1、5.2、5.4 与页面删除不可拆、不可省。
|
||
|
||
## 7. 开工前置命令
|
||
|
||
```bash
|
||
git fetch origin --prune
|
||
git worktree add -b codex/settings-dialog-and-billing-pane-20260906 .worktrees/settings-dialog-and-billing-pane-20260906 origin/staging
|
||
cd .worktrees/settings-dialog-and-billing-pane-20260906
|
||
ln -s /workspace/Jyotisha/frontend/node_modules frontend/node_modules
|
||
wc -l frontend/src/app/page.tsx
|
||
cd frontend && npx tsx --test tests/account-dialog-overlay.test.ts tests/chart-library-*.test.ts tests/membership-*.test.ts tests/chat-navigation-a11y-contract.test.ts tests/epay-*.test.ts tests/redeem-orders-contract.test.ts 2>&1 | grep -E "^# (tests|pass|fail)|^not ok"
|
||
```
|
||
|
||
收尾:同一条命令 fail=0 且总数不低于开工;`tsc --noEmit`、`npm run lint`(0 error)、`npm test`、`npm run build`(`/` Static、gzip ±2%、无 `/membership` 路由);`cd .. && .venv/bin/python -m pytest tests/test_api_server_security.py -k capability_audit`。
|