diff --git a/CHANGELOG.md b/CHANGELOG.md index 2818a9df..03fd4027 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # 印度占星 Skill 更新日志 +## 2026-11-01 — 生时校正可以给档案里的其他人做 + +- 「生时校正」页标题旁可以换人,页面按当前选中的人显示:状态卡第一行写称呼和关系,核对一步列出称呼、关系、出生时间、出生城市,「改这份」打开这个人的资料。 +- 进行中的校正按人算:本人一个、每位家人各一个,可以同时进行(产品 2026-11-01 定)。 +- 采用或确认的时间只写回这个人的档案;本人的资料和本人的校正都不动。之后改了这个人的出生日期、时间或地点,采用的时间作废,和本人一样。 +- 删除一个人时,TA 的校正记录和校正对话一起删除,扣过的点不退,和删除对话一样。删除前的「将同时删除 N 个对话」也算上这些对话。 +- 对话里给家人也会出校正提议,明确说要校正时同样开始核对,不再说「给家人校正暂时还不支持」。别人的星盘上,上升提示也带「出生时间校正」链接;起步三问下面的「去校正」看的是当前这个人的出生时间。 +- 扣点不变:每次校正在第一条真实消息时扣一次,给谁做都一样。 +- 数据库迁移 `20261101010000_rectification_chart_subject.sql`,只加不删,以前的校正都算本人。 +- 还没做:校正对话里仍按「你」称呼,给别人做时怎么称呼待定。 +- Skill 版本不变(issue #160)。 + ## 2026-11-01 — 新增「印度占星知识」栏目(/learn) - 新页面 `/learn`:不登录也能看,可以被搜索引擎收录。左边是七个栏目加「全部」,右边按栏目列出「问句标题 + 一行摘要」;手机上栏目是一排可以横滑的按钮。 @@ -21,6 +33,7 @@ - 原来不带 `mode` 的 `/api/annual` 返回不变。说明见 `docs/engine/annual-package-v1.md`。 - Skill 版本不变(issue #152、#153)。 + ## 2026-10-30 — 星盘页:盘旁「盘面数据」卡,表格和盘联动 - 盘的右边(手机上是盘的下面)新增「盘面数据」卡,九个页签:行星、虚星、特殊点、映射、八分点、星力、大运、敌友、瑜伽。原来散在「九曜」「Vimshottari 大运」两张卡和深读「命性格局」「排盘底册」里的表都搬到这里,不再重复。 diff --git a/docs/research/pre_work_error_ledger.md b/docs/research/pre_work_error_ledger.md index 7387ea17..b2e6d6e0 100644 --- a/docs/research/pre_work_error_ledger.md +++ b/docs/research/pre_work_error_ledger.md @@ -2,6 +2,12 @@ Purpose: read this file before substantial project work. It exists to stop repeat mistakes caused by multiple Codex windows, WorkBuddy mirrors, local drafts, backup folders, and partial cloud-git visibility. +## 2026-11-01 · issue #160 生时校正给他人(worktree `/workspace/wt-160`) + +- `pre_work_check.py` exit 0,但五个 Python 步骤(运行时、碎片扫描、外部引擎适配器、远端可见性、focused)全部是「No usable project Python found」:本机没有项目 `.venv`,按约定不新建。外部引擎诊断与远端可见性这次都没有跑,不得写成预检通过。 +- Python 测试改用 `uv run --no-project --with pytest --with pyswisseph` 的临时环境跑,不写进仓库。 +- 没有 Docker,`tests/database-*.test.ts` 全部 skip;想在本机解包 Debian PostgreSQL 17 服务端二进制来跑 DB 测试,被会话权限拒绝,已删掉下载物,没有换别的办法绕过。新迁移只做了 libpg_query(pglast)语法和 PL/pgSQL 解析检查,没有在真库上执行过。 + ## 2026-09-30 · 盘型口径实现的预检与逐格平台诊断 - 开工 pre_work_check exit 1:remote verified、适配器/碎片步骤正常,focused 历史 `.workbuddy/skills/jyotish-vedic-astrology` 镜像路径断言失败;不制造镜像、不改断言。主会话保留完整 Node / Python 不同容器基线,最终分别逐名比对,不混用 npm 的 54 项与 quick 内 npm 的 56 项。 diff --git a/docs/tasks/TASK-onboarding-journey-20261022.md b/docs/tasks/TASK-onboarding-journey-20261022.md index f2709c85..46c3d7fe 100644 --- a/docs/tasks/TASK-onboarding-journey-20261022.md +++ b/docs/tasks/TASK-onboarding-journey-20261022.md @@ -71,7 +71,7 @@ 8. 不引第三方引导库或动画库。动效用 CSS 和 Web Animations API,复用 `TASK-site-motion-20261022.md` M1 的 `lib/motion/` 底座;提示用现有的 `@base-ui/react` Popover。 9. 系统开了 `prefers-reduced-motion` 时,D6 的全部动效退成淡入或直接出现,星点和闪烁去掉。 10. 清单的“做没做”**一律从真实数据推导**。唯一例外是“进过星盘页”(没有数据可推),见 T2。 -11. 生时校正仍只支持本人。帮别人看时,任何地方都不出现校正提议。 +11. ~~生时校正仍只支持本人。帮别人看时,任何地方都不出现校正提议。~~ 2026-11-01 解除(issue #160,产品定 1A 2A):档案里的人都能做生时校正,进行中的校正按人物分别算;帮别人看时,校正提议和起步三问下面的「去校正」看的是 TA 的出生时间。启程清单里的「做一次生时校正」仍只看本人。 12. 迁移只加不删(AGENTS.md §7.6):三列废列本单不删,关系列可为空。必须真跑 `npm run test:db`;本机没有 Docker 时,按已记录的无 Docker PG17 做法跑;仍做不了就写进 `BLOCKED.md`,不得写成通过。 13. 测试、fixture、文档里不得出现任何真实用户资料。 @@ -131,7 +131,7 @@ - `missingProfileStep()` 改为:本人资料完整,**或**档案里至少有一位资料完整的人,就算建完档。中途退出的,下次从缺的那一步接着问。 - 逐个核对事故实证 4 列出的调用点,按“当前人物”而不是“本人”判断: - 今日星语:本人没有出生资料时,首页不显示「今日星语」按钮。 - - 生时校正:当前人物不是本人时,不出现校正提议(红线 11)。 + - 生时校正:~~当前人物不是本人时,不出现校正提议(红线 11)。~~ 2026-11-01 起按当前人物判断(issue #160,红线 11 已解除)。 - 「和我合盘」:本人没有出生资料时,按钮位置改为文字「加上你自己后可合盘」。 - 顺手清理事故实证 6 的死代码,并修正 DESIGN.md §9 那一段。 diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 2658a126..38d23d11 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -1393,12 +1393,24 @@ Yogini 还在取时,右栏用和 Chara 相同的轨道等待(`OrbitWaiting` 推翻 2026-10-07「校正只从对话里的提议卡进入」(BUG-1247):产品要求校正单独有入口。 - **侧栏**:导航里「星盘」下面一项「生时校正」(Lucide `Clock3`),链接 `/rectification`。账户的 `rectification` 开关为 true 才出现;开关没到之前不出现,和「婚恋合盘」同一规则。 -- **`/rectification`**:沿用二级页外壳(`SecondaryPageShell`),标题旁不放人物切换(校正只支持本人,他人见 #160)。正文一栏宽 `min(100%, 680px)` 居中,上边距 32px(手机 20px,左右 16px)。一句说明,下面一张状态卡(`.rectification-page-card`:细边、`--radius-lg`、24px 内边距,手机 20px)。状态卡分没做过 / 进行中 / 已完成三种,状态只来自服务端 `entry-summary`(含会话 id、时间窗、确认过的大事数、采用时间)。进行中是两列数字(时间窗、已收大事);已完成时刻 28px 衬线数字。主按钮是组件库默认按钮,次按钮描边,都是真链接(`AppLink`)。「开始」「再校正一次」先进核对:两行资料表(手机上标签和值上下排),「开始校正」+ 链接「改这份」(`/people?self=1`)。开始后进首页校正面(`/?rectify=1` 续上或新开;做完之后再校正用 `/?rectify=new`),「继续」「查看结果」打开那次校正的会话(`/?c=`)。页面上不写价格。读取中用二级页现有的等待文案,不另做骨架。 +- **`/rectification`**:沿用二级页外壳(`SecondaryPageShell`),标题旁放人物切换(2026-11-01 起,见下节 #160;之前不放)。正文一栏宽 `min(100%, 680px)` 居中,上边距 32px(手机 20px,左右 16px)。一句说明,下面一张状态卡(`.rectification-page-card`:细边、`--radius-lg`、24px 内边距,手机 20px)。状态卡分没做过 / 进行中 / 已完成三种,状态只来自服务端 `entry-summary`(含会话 id、时间窗、确认过的大事数、采用时间)。进行中是两列数字(时间窗、已收大事);已完成时刻 28px 衬线数字。主按钮是组件库默认按钮,次按钮描边,都是真链接(`AppLink`)。「开始」「再校正一次」先进核对:两行资料表(手机上标签和值上下排),「开始校正」+ 链接「改这份」(`/people?self=1`)。开始后进首页校正面(`/?rectify=1` 续上或新开;做完之后再校正用 `/?rectify=new`),「继续」「查看结果」打开那次校正的会话(`/?c=`)。页面上不写价格。读取中用二级页现有的等待文案,不另做骨架。 - **对话回答下面**(`.rectification-offer-card`):改成一行,不再是带底色的卡片:灰字一句 + 主色下划线链接「去做生时校正」和 Lucide `ArrowRight`。点链接去 `/rectification`,不从这段对话开案例;对话进行中链接置灰不可点。回来后的分隔线、「按校正前的时间」不变(旧的已绑定案例照常显示)。 - **明确要求**:过渡句之后跳到 `/rectification`,不再直接打开校正面。 -- **建完档的首页**:起步三问下面一行浅底圆角条(`.starter-rectify-hint`,`--color-canvas-soft`),一句 + 描边胶囊「去校正」。只在本人出生时间是估的、首问还没用掉时出现,按启程进场依次浮出。 +- **建完档的首页**:起步三问下面一行浅底圆角条(`.starter-rectify-hint`,`--color-canvas-soft`),一句 + 描边胶囊「去校正」。只在当前人物出生时间是估的、首问还没用掉时出现(#160 之前只看本人),按启程进场依次浮出。 - **启程清单**:本人时间是估的账户在「问第一个问题」后多一项「做一次生时校正」,清单变成 8 项;其他账户仍是 7 项。 +## 生时校正给档案里的其他人(2026-11-01,issue #160) + +产品定 1A 2A:进行中的校正按人物分别算,可以同时进行;删人时 TA 的校正记录一起删除,扣过的点不退。 + +- **选人**:`/rectification` 用二级页默认的人物切换(`CurrentSubjectSwitcher`),页面跟着当前人物走;未登录时标题旁空着。换人后状态卡按新的人重读 `entry-summary?subject=`,上一个人的结果不会顶着显示,读取中仍用等待文案。 +- **状态卡第一行**(`.rectification-page-person`):称呼(正文字号、500)+ 关系小标签(`.rectification-page-person-relation`:细边、`--radius-sm`、小字、次要墨色)。一行不换行,称呼太长省略号截断,标签不挤到下一行。本人的标签写「本人」。不加说明句。 +- **核对**:资料表四行「称呼」「关系」「出生时间」「出生城市」,「改这份」打开这个人的编辑(`/people?edit=`;本人仍是 `/people?self=1`)。 +- **开始**:`/?rectify=1&rectifyFor=`(做完再校正 `/?rectify=new&rectifyFor=`);本人仍是不带参数的老地址。校正会话绑到那个人(`chat_sessions.chart_profile_id`),侧栏在那个人下面列出。 +- **星盘页上升提示**:别人的盘也带「出生时间校正」链接(以前只有本人带)。 +- **采用之后**:时间只写回那个人的档案;本人的资料和本人的校正不动。页面刷新人物列表和星盘、星历缓存。 +- **数据**:迁移 `20261101010000_rectification_chart_subject.sql`,只加不删:案例加 `chart_profile_id`(空 = 本人,老案例都是空),`chart_profiles` 加 `active_birth_provenance`。 + ## 用量页分段计时(2026-10-05,在途) `/admin/usage` 在原表格上多一列「分段」。格子里四行:分类耗时、第 0 步、第 1 步、第 1 步开始到第一个正文字符。每步带推理、输出、输入、缓存输入 token。供应商没返回的数显示「—」。不新开页面,不改表,数字来自 `usage_ledger.metadata` 里已有的 JSON。没有新的等待动画或输入框。 diff --git a/frontend/docs/VOICE.md b/frontend/docs/VOICE.md index 6ed59777..71a885ae 100644 --- a/frontend/docs/VOICE.md +++ b/frontend/docs/VOICE.md @@ -48,7 +48,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 - 有了你们两个人,就能看你们俩的关系画像。价格写在按钮上。 - 每天的天象和行运在这里,可以按日期翻。 -首页开场语下面可以有一行今日趋势(今日星语卡片的 trend,每日生成);还没生成时写「今天的星语还没写出来。」,没有出生分钟时不写。本人没有出生资料时,这一行和「今日星语」入口都不出现。入口按钮下面平时不写字,只在用户需要动手时写一句:有没做完的校正写「上次那次校正还没完成,可以在历史对话里接着做。」;当前人物不是本人写「生时校正暂时只支持本人。」。不再写「不确定出生时间时,用记得住的经历一步步缩小范围」「上次已经校正完,可以拿最新资料再来一次」,也不要用「·」把两句不相干的提示拼成一行。 +首页开场语下面可以有一行今日趋势(今日星语卡片的 trend,每日生成);还没生成时写「今天的星语还没写出来。」,没有出生分钟时不写。本人没有出生资料时,这一行和「今日星语」入口都不出现。入口按钮下面平时不写字,只在用户需要动手时写一句:有没做完的校正写「上次那次校正还没完成,可以在历史对话里接着做。」。2026-11-01 起(issue #160)档案里的人都能做校正,不再写「生时校正暂时只支持本人。」。不再写「不确定出生时间时,用记得住的经历一步步缩小范围」「上次已经校正完,可以拿最新资料再来一次」,也不要用「·」把两句不相干的提示拼成一行。 ## 婚恋合盘报告(2026-10-19) @@ -88,14 +88,27 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 - 读不到:「校正记录这会儿读不到。」按钮「再试一次」。未登录:「登录后可以做生时校正。」「去登录」。开关关着:「生时校正这会儿还做不了。」本人资料不全:「先把你自己的出生日期、时间和地点填好,才能做生时校正。」「去填资料」。 - **对话回答下面**:一句「出生时间还不确定。」加链接「去做生时校正」,指向 `/rectification`。不写题数和点数,开没开过案都是这一句。 - **明确要求**:过渡句仍是「好,我们开始核对出生时间。」/「核对还没做完,我们接着做。」,之后跳到 `/rectification`,由页面给「开始」或「继续」。 -- **建完档的首页**:本人出生时间是估的(家人记得、时段、不知道),且首问还没用掉时,起步三问下面一行「出生时间还不确定,可以先做生时校正。」加按钮「去校正」。帮别人看时不出现。 +- **建完档的首页**:当前人物的出生时间是估的(家人记得、时段、不知道),且首问还没用掉时,起步三问下面一行「出生时间还不确定,可以先做生时校正。」加按钮「去校正」。2026-11-01 起帮别人看时也出现(issue #160),看的是 TA 的时间。 - **启程清单**:同样只对本人时间是估的账户多一项「做一次生时校正」,采用过时间就打勾。 +## 生时校正给档案里的其他人(2026-11-01,issue #160) + +产品 2026-11-01 定 1A 2A:进行中的校正按人物分别算(本人一个、每位家人各一个,可以同时进行);删除人物时 TA 的校正记录一起删除,扣过的点不退。 + +- **`/rectification`**:标题旁是人物切换,页面跟着当前人物走。状态卡第一行写这个人的称呼,后面一个小标签写关系(本人写「本人」,没填关系写「其他」)。不加说明句。 +- **状态卡**:别人没做过时小字是「TA 现在用的时间」;本人仍是「你现在用的时间」。进行中、已完成的写法不变,数的是这个人自己的那次。 +- **核对**:四行「称呼」「关系」「出生时间」「出生城市」,按钮「开始校正」,链接「改这份」(打开这个人的编辑)。 +- **资料不全**:本人仍是「先把你自己的出生日期、时间和地点填好,才能做生时校正。」;别人是「先把 TA 的出生日期、时间和地点填好,才能做生时校正。」按钮都是「去填资料」。 +- **首页校正面**:资料不全时一句「请先把 TA 的出生资料填完整,再开始生时校正。」 +- **对话里**:给家人和本人一样出提议、一样能「好,我们开始核对出生时间。」,有没有进行中的那次只看这个人自己的。 +- 扣点不变:每次校正在第一条真实消息时扣一次,给谁做都一样。 +- 校正对话里 agent 仍按「你」称呼(「我们来把你的出生时间缩小……」);给别人做时怎么称呼还没定,这次不改。 + ## 生时校正从对话进入(2026-10-07,BUG-1247~1251;2026-10-12,BUG-1306) - **何时提议**:校正就在这个对话里能开。用户说推得不准时先分清读法问题和时间问题。只是不认同某个解释,按改读规则认下、改读,不提校正。不承诺能收窄出生时间,不在对话里收集经历做校正。说出过去的具体事情和盘对不上、说出生时间是估的或记不准、但没有明确要做校正,要先看过盘再提议。同一次对话提议过、用户没接,不再主动提;用户自己再要求时传 `userAsked=true`。还没看盘就顺口提议,不算已经提议过。规则只定义在 `consultation-thinking-plan.ts` 的 `RECTIFICATION_OFFER_RULE`。不用关键词判断。 - **明确要求则直接进入**:用户这一轮明确要做校正(校正、纠正、核对出生时间都算),由模型判断,不用关键词。第一步就可以打开,不用先排盘写解读。服务端放行后直接打开校正,不用点卡。过渡只有一句「好,我们开始核对出生时间。」已经采用过时间的,不算还在进行,明说要校正就新开一次。真没做完的,直接接着做那一次。这次如果接不回原来的会话,也直接新开。开案还没成功时,回答下面仍是普通提议卡,不说「已经开始」;打开失败可以再点「开始核对出生时间」。开案并绑定来源之后才改成「已经开始」。 -- **做不了必须说一句**:开关关着,或暂时没有价格,都说「出生时间核对这会儿还做不了。」不写内部原因,不写价格,不得沉默。家人档案说「给家人校正暂时还不支持」,不开案。不再说「可在历史对话里接着做。」 +- **做不了必须说一句**:开关关着,或暂时没有价格,都说「出生时间核对这会儿还做不了。」不写内部原因,不写价格,不得沉默。家人档案和本人一样能开(issue #160),不再说「给家人校正暂时还不支持」。不再说「可在历史对话里接着做。」 - **正文怎么提**(没有明确要求、只是时间可能不准):一句,放在最后一段(例:「如果想确认出生时间准不准,可以用下面的卡片,答几道过去经历的题核对一下。」),不写价格和题数——卡片上写。 - **卡片**:「可以用几道过去经历的题,核对一下你的出生时间。大约 N 题,会用掉 M 点;开始答第一题时才扣。」按钮「开始核对出生时间」;已开始(开案并绑定来源之后,不是结算当时)「出生时间核对已经开始。」/「继续核对出生时间」。点数来自 `feature_pricing`,不写死。 - **回来**:分隔线「出生时间改为 HH:MM(生时校正)」/「出生时间没有改」;之前的回答上方「按校正前的时间」;按钮「按新时间重新看:「原问题」」。校正面按钮「回到刚才的对话」。不再说「新建对话」。 @@ -509,7 +522,7 @@ Jyotisha 的可见文案是产品的一部分。正确性红线(真实性、 账户与点数的页签只用:会员套餐 / 点数包 / 兑换码 / 订单记录。购买中按钮写「跳转收银台…」,轮询中写「等待支付结果」,不要写「正在加载」。 -星盘档案不写关系。人数到顶时只说「最多保存 20 个人的星盘」。当前人物不是本人时,生时校正入口禁用,提示「生时校正暂时只支持本人。」删除一个人之前必须写出「将同时删除 N 个对话和 M 份报告,无法恢复。」本人不能删除,不要给删除按钮配一句绕过去的确认。 +星盘档案不写关系。人数到顶时只说「最多保存 20 个人的星盘」。生时校正对档案里的每个人都能用(issue #160),不再禁用入口,也不再写「生时校正暂时只支持本人。」删除一个人之前必须写出「将同时删除 N 个对话和 M 份报告,无法恢复。」这个数里也算上 TA 的生时校正对话:删人时 TA 的校正记录一起删除,扣过的点不退,和删除对话一样,不另写一句。本人不能删除,不要给删除按钮配一句绕过去的确认。 余额不足时输入框占位写「余额不足,可在账户与点数中补充」,不要叫用户去会员页。 diff --git a/frontend/src/app/(app)/page.tsx b/frontend/src/app/(app)/page.tsx index fbde4805..59257d95 100644 --- a/frontend/src/app/(app)/page.tsx +++ b/frontend/src/app/(app)/page.tsx @@ -26,7 +26,6 @@ import { rectificationOrdinaryComposerReopen, } from "@/lib/rectification-session-composer-guard"; import { ConversationalBirthTimeRectification } from "@/components/conversational-birth-time-rectification-lazy"; -import { declaredBirthTime } from "@/lib/rectification-surface-state"; import { toggleChatMessageFeedback } from "@/components/chat-message-actions"; import type { ChatTranscriptActions } from "@/components/chat-transcript"; import { ChatStreamingTranscript, useStreamingReplyHasText } from "@/components/chat-streaming-transcript"; @@ -431,7 +430,6 @@ export default function Home() { : activeReplyOutcome?.phase ?? "idle"; const replyAnnouncement = chatReplyAnnouncement(replyPhase, activeReplyOutcome?.replyOrdinal ?? 0); const accountId = account?.user.id; - const rectificationDeclaration = { declaredTime: declaredBirthTime(profile), birthDate: profile.date || null }; const rectificationErrorMessage = rectificationError === "profile_incomplete" ? "服务端未能读取完整出生资料,请重新确认并保存后再开始生时校正。" : rectificationError; @@ -466,7 +464,7 @@ export default function Home() { }); const { - setRectificationHeaderSlot, rectificationPanel, resumeRectificationSession, + setRectificationHeaderSlot, rectificationPanel, rectificationDeclaration, handleRectificationSaved, resumeRectificationSession, openRectificationSession, reopenRectificationSession, } = useRectificationSurface({ account, activeSessionIdRef, chartLibrary, creatingSession, modelCatalog, @@ -1339,7 +1337,7 @@ export default function Home() { selectedModelId={activeSession?.modelId ?? ""} onSelectModel={(modelId) => void selectSessionModel(modelId)} onOpenBilling={(options) => openAccountDialog("billing", { source: options?.source ?? "rectification" })} - onSaved={() => void refreshAccount()} + onSaved={handleRectificationSaved} panel={rectificationPanel} /> )} @@ -1355,7 +1353,7 @@ export default function Home() { chartSignsFallback={dailyStarlanguage.kind === "unavailable"} onAsk={(text) => void startSuggestedConsultation(text)} onNudge={nudgeComposer} - rectifyHint={currentSubjectId === "self" && account.rectification === true && birthTimeNeedsRectification(profile)} + rectifyHint={account.rectification === true && Boolean(dailyProfile) && birthTimeNeedsRectification(dailyProfile ?? profile)} /> ) : null} {jumpToLatestVisible && ( diff --git a/frontend/src/app/api/consult/route.ts b/frontend/src/app/api/consult/route.ts index 60b9d985..851eee74 100644 --- a/frontend/src/app/api/consult/route.ts +++ b/frontend/src/app/api/consult/route.ts @@ -1035,17 +1035,19 @@ export async function POST(request: Request) { const state = createConsultationRuntimeState({ plannedSteps: AGENT_MAX_STEPS }); latencyState = state; // C2/C3 (BUG-1248): the offer tool asks the server, never decides itself. + // Issue #160: the person this conversation is about, owner or not. + const rectificationSubjectId = prepared.subject?.role === "other" ? prepared.subject.chartProfileId : null; const rectificationOffer: RectificationOfferHook = { decide: () => decideRectificationOffer({ accounting, userId, - subjectRole: prepared.subject?.role === "other" ? "other" : "self", + subjectId: rectificationSubjectId, modelId: selectedModel.id, }), alreadyOffered: sessionHasRectificationOffer(chatSession?.messages), revisitQuestion: revisitQuestionForOffer(chatSession?.messages, visibleQuestion, requestId), sourceSessionId: sessionId, - loadResumableSessionId: () => loadResumableRectificationSessionId(accounting, userId), + loadResumableSessionId: () => loadResumableRectificationSessionId(accounting, userId, rectificationSubjectId), }; const hooks = createConsultationRuntimeHooks(state); const usages: Promise[] = []; diff --git a/frontend/src/app/api/rectification/cases/entry-summary/route.ts b/frontend/src/app/api/rectification/cases/entry-summary/route.ts index a5e62016..39468cf8 100644 --- a/frontend/src/app/api/rectification/cases/entry-summary/route.ts +++ b/frontend/src/app/api/rectification/cases/entry-summary/route.ts @@ -7,15 +7,18 @@ import { getRectificationEntrySummary, mapRectificationRpcError, } from "@/lib/rectification-agentic/v9/case-service"; +import { SELF_SUBJECT_ID, isChartSubjectId, subjectIdFromQuery } from "@/lib/subject-birth"; export const runtime = "nodejs"; /** - * GET /api/rectification/cases/entry-summary + * GET /api/rectification/cases/entry-summary[?subject=] * - * Server-truth context for homepage copy; the primary homepage action always creates. + * Server-truth context for the /rectification card, one person at a time + * (issue #160). No `subject` or `self` is the account owner; an id that is not + * this user's reads as "nothing on record" (the RPC filters by user). */ -export async function GET() { +export async function GET(request: Request) { let supabase; let accounting; try { @@ -32,8 +35,17 @@ export async function GET() { return NextResponse.json({ error: "请先登录" }, { status: 401 }); } + const subject = subjectIdFromQuery(new URL(request.url).searchParams.get("subject")); + if (subject !== SELF_SUBJECT_ID && !isChartSubjectId(subject)) { + return NextResponse.json({ error: "请求内容不正确", code: "invalid_subject" }, { status: 400 }); + } + try { - return NextResponse.json(await getRectificationEntrySummary(accounting, user.id)); + return NextResponse.json(await getRectificationEntrySummary( + accounting, + user.id, + subject === SELF_SUBJECT_ID ? null : subject, + )); } catch (error) { if (error instanceof RectificationCaseServiceError) { const view = mapRectificationRpcError( diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 432652e0..27a0e2be 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -7091,6 +7091,32 @@ button.synastry-dd-add { border-radius: 0 0 var(--radius-md) var(--radius-md); } color: var(--color-ink-tertiary); font-size: var(--type-body-sm); } +/* Whose chart (issue #160): the name, then the relation as a quiet tag. + One line; a long name is cut, the tag never wraps under it. */ +.rectification-page-person { + display: flex; + align-items: center; + gap: var(--space-2); + min-width: 0; + margin: 0 0 var(--space-1); + white-space: nowrap; +} +.rectification-page-person-name { + overflow: hidden; + color: var(--color-ink); + font-size: var(--type-body-md); + font-weight: 500; + text-overflow: ellipsis; +} +.rectification-page-person-relation { + flex: none; + padding: 1px var(--space-2); + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + color: var(--color-ink-secondary); + font-size: var(--type-body-sm); + line-height: 1.5; +} .rectification-page-label--done { color: var(--color-success); } @@ -7197,8 +7223,9 @@ button.synastry-dd-add { border-radius: 0 0 var(--radius-md) var(--radius-md); } } } -/* Onboarding prompt under the starter questions (issue #145): self chart with an - estimated time only. A quiet row, not a card; the link goes to /rectification. */ +/* Onboarding prompt under the starter questions (issue #145): the current + person's time is an estimate (anyone in the list since #160). A quiet row, + not a card; the link goes to /rectification. */ .starter-rectify-hint { display: flex; flex-wrap: wrap; diff --git a/frontend/src/components/chart-page/chart-page-view.tsx b/frontend/src/components/chart-page/chart-page-view.tsx index 3dab565d..128e641d 100644 --- a/frontend/src/components/chart-page/chart-page-view.tsx +++ b/frontend/src/components/chart-page/chart-page-view.tsx @@ -86,8 +86,8 @@ export function ChartPageRoute({ learnLinks }: { learnLinks?: ChartLearnLinks }) ); } -function ChartLagnaPrecheckNote({ value, self, learnLinks }: { value: ChartViewLagnaPrecheck; self: boolean; learnLinks?: ChartLearnLinks }) { - const line = lagnaPrecheckLine(value, { self }); +function ChartLagnaPrecheckNote({ value, learnLinks }: { value: ChartViewLagnaPrecheck; learnLinks?: ChartLearnLinks }) { + const line = lagnaPrecheckLine(value); // 「这个怎么看」 (issue #157): only while the ascendant can still change, and only when the article exists. const learn = value.state === "fixed" ? null : chartLearnLink(learnLinks, "birth-time-uncertain"); return ( @@ -362,7 +362,7 @@ export function ChartPageView({