Files
Jyotisha/docs/tasks/TASK-settings-dialog-and-billing-pane-20260906.md
T

18 KiB
Raw Blame History

TASK · 设置弹窗重排(固定尺寸、列表→详情)与「账户与点数」入弹窗、删除 /membership 整页(2026-09-06

  • 基线:origin/staging @ 985c3258
  • 分支:codex/settings-dialog-and-billing-pane-20260906
  • 执行方:coding agent;验收:Claude
  • 涉及文件:frontend/src/components/account-dialog-overlay.tsxfrontend/src/components/chart-library-panel.tsx、新建 frontend/src/components/profile-panel.tsx、新建 frontend/src/components/billing-panel.tsx+ frontend/src/hooks/use-billing-panel.tsfrontend/src/lib/billing-order-polling.ts)、新建 frontend/src/lib/settings-url.tsfrontend/src/lib/home-types.tsfrontend/src/lib/membership.tsfrontend/src/app/page.tsx(只改传参与调用,必须净减行renderProfile 的 JSX 整段搬出)、frontend/src/components/app-sidebar.tsxfrontend/src/components/onboarding-redeem-paywall.tsxfrontend/src/components/rectification-agentic-chat.tsxfrontend/src/hooks/use-consultation-run.tsfrontend/src/app/globals.cssfrontend/next.config.ts删除 frontend/src/app/membership/page.tsxfrontend/src/app/membership/orders/page.tsxtests/test_api_server_security.py(路由集合)、frontend/DESIGN.mdfrontend/docs/VOICE.md
  • BUG 编号起点:BUG-554BUG-553 由 TASK-session-list-title-and-order-20260906.md 预留;开工时复核最大号)
  • 串行:同日顺序 composer551/552)→ 会话列表(553)→ 本单。本单大改 page.tsxapp-sidebar.tsxuse-consultation-run.tsrectification-agentic-chat.tsx,必须在前两单合入 staging 后再开分支。
  • 删除页面属于路由集合变更:本单必须在同一变更里跑 .venv/bin/python -m pytest tests/test_api_server_security.py -k capability_audit(BUG-143 的防复发要求),不得只跑前端矩阵。

1. 事故实证(staging2026-09-06,桌面 Chrome,产品截图三张)

# 用户看到 期望(对标 Claude 设置弹窗)
1 「星盘资料」格:本人资料条与「编辑本人资料」按钮之后,空态一句「还没有其他星盘,先添加一份资料」紧接一整张「添加其他人的星盘」表单(关系下拉、称呼、出生日期、「你对出生时间了解多少」单选组……)铺在同一个滚动区里,弹窗撑到接近整屏 列表是列表,表单是点了「添加」之后才出现的二级视图
2 三个分区弹窗尺寸各不相同:「通用设置」是一个只装得下主题三选的小盒子,「个人资料」「星盘资料」是宽而高的大盒子,切换分区整个弹窗跳变 一个固定尺寸的设置窗,左侧导航不动,右侧内容区自己滚
3 「个人资料」格:96px 头像预览 + 8 个配色块 + 「换一个形象」占了上半屏,账户信息两行,底部又一个「管理星盘资料」按钮(与左侧导航重复) 头像一行搞定;重复入口删除
4 左侧导航第四项「账户与点数」点下去离开首页/membership 整页,弹窗关闭,返回后要重新加载 它就是第四个分区
5 「星盘资料」里每一条其他人资料一行塞下 关系下拉 + 用于合盘 + 编辑 + 设为默认 + 删除;合盘结果卡和合盘历史平铺在列表下面 列表条只显示人;操作在这个人的详情里

2. 根因(行号以基线为准)

  1. chart-library-panel.tsx L170282:一个 <div className="chart-library-panel"> 顺序渲染本人组 → 本人编辑表单(editingSelfChart)→ 其他人组 → 合盘卡 → 合盘历史 → 无条件渲染的「添加 / 编辑其他人」表单(L263)。没有视图状态,只有堆叠。
  2. globals.css L16371639.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 L16201670 renderProfile 把头像编辑区与账户信息 JSX 直接写在 page.tsx 里(约 50 行),末尾 L1668 <button …>管理星盘资料</button> 只是 openAccountDialog("chart-library")——与导航项重复。
  4. account-dialog-overlay.tsx L88 第四个导航项没有 dialog 值,直接 model.openRedeempage.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/createwindow.open(payUrl) → 每 3 秒轮询 /api/payment/epay/status(可见性感知),这套完全能装进弹窗。入口共 7 处:page.tsx L1618/L1782/L1810、onboarding-redeem-paywall.tsx L129<Link>)、rectification-agentic-chat.tsx L825window.location.assign,硬跳转)、use-consultation-run.ts L527/L722router.push)。

3. 决策记录(产品已授权,2026-09-06)

  1. 一个固定尺寸的设置弹窗:桌面 width: min(100vw - 32px, 880px); height: min(84dvh, 640px);左栏导航 176px 固定不滚,右栏内容独立 overflow-y: auto;标题栏固定在顶部。四个分区共享同一尺寸,切换分区不改变外框。accountDialogClassesprofile-modal / chart-library-modal / general-modal 三个 class 合并为一个 settings-modallogout-modal 保留,退出确认仍是 400px 小弹窗)。≤767px 保持现有全屏 + 顶部四格页签的布局,只把内容区改成独立滚动。
  2. 分区顺序与内容:个人资料 → 星盘资料 → 账户与点数 → 通用设置。退出登录仍在头像菜单,不进导航。
  3. 个人资料格:一行头像编辑(当前头像 48px + 8 个配色块 + 「换一个形象」文字按钮),账户信息(昵称、登录邮箱)两行。删除「管理星盘资料」按钮。整段 JSX 搬进 profile-panel.tsxpage.tsx 只传 props。
  4. 星盘资料格改为列表 → 详情
    • 列表视图:「我的星盘」一条(名字、出生摘要、「当前默认」标签);「其他人」若干条(名字、关系标签、出生摘要);底部一个「添加其他人」按钮。列表条整条可点,右侧一个 chevron,不再一行五个按钮。
    • 详情视图(同一内容区内切换,顶部一个「← 星盘资料」返回):本人详情 = 编辑表单;其他人详情 = 编辑表单 + 操作行(设为默认 / 删除)+ 「合盘」小节(关系类型 + 「用于合盘」)+ 该人的合盘记录(现有 synastryHistorypartnerName 归属,新记录保存时顺手带上 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/ordersplan 高亮参数保留(membershipHref(source, { plan }) 传来的推荐套餐卡片高亮并滚到可见)。
  6. 删除 /membership/membership/orders 两个页面(产品明确要求,不是重定向壳)。next.config.tsredirects()/membership/?settings=billing&source=legacy/membership/orders/?settings=billing&tab=orderspermanent: 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/dynamicssr: 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.tsmembership-navigation-contract.test.ts 的 26 条测试迁移到新组件文件而不是删除,总数不降,每条改动在三栏表里说明。
  • tests/test_api_server_security.py 的精确路由集合去掉 membershipmembership/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.tsAccountDialog = "profile" | "chart-library" | "billing" | "general" | "logout"accountDialogTitles 加「账户与点数」;accountDialogClasses 收敛为 settings-modal(四个分区)+ logout-modal
  • account-dialog-overlay.tsx:导航数组加 billing 项(图标沿用 WalletCards),删 openRedeemAccountOverlayModelrenderBillingopenAccountDialog 支持 { 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 加——四个分区导航项齐全且 billingaria-current;不存在 openRedeemglobals.css.settings-modal 同时声明 widthheight,且 chart-library-modal / profile-modal 不再出现。

5.2 个人资料格搬出 page.tsx

  • 新建 profile-panel.tsxpropsaccountprofileavatarSavingavatarNoticeaccountErrorpersistAvatar);头像一行布局;删除「管理星盘资料」按钮。
  • 验收:page.tsx 行数净减 ≥ 40tests/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.tstests/chart-library-session.test.ts 按三栏表改;新增用例——初始视图为列表且源码中不存在无条件渲染的 <form;「添加其他人」进入 add 视图、保存后回到列表;其他人详情包含 设为默认 / 删除 / 用于合盘;本人详情无删除;合盘记录按人归属、无记录不渲染。

5.4 「账户与点数」分区(CHANGELOG 项)

  • 新建 billing-panel.tsx + use-billing-panel.ts(账户 / 套餐并发拉取、下单、window.open、可见性感知轮询与清理、失败态、兑换、订单列表;逻辑从 membership/page.tsx 搬,删除 goBackmembership-loading、子弹窗焦点陷阱);page.tsxrenderBillingnext/dynamic 挂载。
  • 删除两个页面文件及其专属 CSSmembership-page / membership-shell / membership-header* / membership-back / membership-hero* 等只服务整页的规则),卡片、页签、按钮样式在 .billing-panel 下复用。
  • next.config.ts 加两条重定向;membership.ts::membershipHref 改生成 ?settings=billing;新建 lib/settings-url.tsparseSettingsQuery / stripSettingsQuery 纯函数)。
  • 七处入口改回调(决策 7)。
  • 验收:tests/billing-panel.test.ts 承接 membership-page.test.tsmembership-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.mddocs/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. 开工前置命令

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 --noEmitnpm run lint0 error)、npm testnpm run build/ Static、gzip ±2%、无 /membership 路由);cd .. && .venv/bin/python -m pytest tests/test_api_server_security.py -k capability_audit