Splits the remaining work into three briefs that own disjoint files so agents can run them in parallel. The qizheng brief is widened to own scripts/jyotish_api_server.py outright and register all three read-only endpoints (/api/qizheng, /api/western, /api/ephemeris_events), because the chart page needs a tropical natal route that no path exposes today and the ephemeris page needs ingress/station scanning that /api/transit does not provide. The chart-page brief owns app-sidebar.tsx and adds both nav entries; the ephemeris-page brief owns only its own route. Checking the tree changed one plan: vedic-chart-svg.tsx is also imported by rectification-board, so the component stays where it is instead of moving to a shared layer. BUG ranges are pre-allocated per brief to keep parallel work from colliding, and the panchanga Lahiri/Raman split found while prototyping is filed as investigating rather than fixed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
12 KiB
TASK-chart-page-20260915 · P0 星盘事实页(五个 Tab)
基线
- 代码基线:
origin/staging=2d7698ea(文档树基线b3ccef4c,纯文档提交,代码未变)。 - 并行关系:本单与
TASK-qizheng-native-chart-20260915(后端)、TASK-ephemeris-page-20260915(前端星历页)同期并行。三份单没有共享代码文件,见「文件归属」。 - 原型:会话内已交付可点原型(五个 Tab 的完整形态、中宫排盘参数卡、三套坐标系边界文案、桌面 1440 与手机 390 两套)。原型里的数据全部是实跑引擎输出,不是编的;实现时以本单文字为准,原型作为视觉与信息层级参照。
- Skill 版本
6.9.16,本单不 bump(不新增解读口径,只把已算出的事实露出来)。
为什么要有这一页
当前产品里,用户填完出生资料看不见自己的盘:
VedicChartSvg在frontend/src/components/personal-report/vedic-chart-svg.tsx,只被三处引用——personal-report-document-view.tsx、personal-report-markdown-view.tsx(用其中的NorthIndianChartSvg)、rectification-board.tsx。也就是说,盘面只在已生成报告或校正进行中才出现。- 侧边栏「星盘资料」是
ChartLibraryPanel(frontend/src/components/chart-library-panel.tsx),只管出生资料的增删改查,不画盘。 - 结果:第一次看见自己的盘必须先花点数、等模型。
而引擎侧 /api/chart、/api/dasha/chara、/api/nakshatra_full 早就在;星宿与 Pada 在 scripts/dasha_analyzer.py 已经算出来。这一页不新增任何计算,只是把已有结果摆出来。
决策记录
产品负责人在 2026-09-15 的对话中授权:
- 新建一个不消耗点数、不调模型、打开即有的只读星盘页。
- Tab 顺序定为 星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余。
- 体系分在 Tab 这一层;分盘 chip(D1–D30)只在印度体系内部使用。不得把西洋盘或七政四余做成第 13 个 chip。
- 盘的中宫放「排盘参数卡」:钟表时间与校正状态、出生地与经纬、岁差与交点模式、上升与月亮星宿、当前大运分运、引擎名。手机上放不下,只留岁差两行,完整参数改成盘下方一张卡。
- 三套坐标系必须各自在中宫写明自己是什么,并显式点名不能互相换算,也不得把两边结论叠加成「双重印证」。
- 「传统象征」词条层可以做,但必须带边界句「是词条式释义,不是对你个人的判断」,且不得出现任何运势判断。
本单推翻的一处早期说法
立单核对时发现:VedicChartSvg 并非「只被 personal-report 引用」,rectification-board.tsx 也在用。因此本单不搬这个文件。 新页面直接从现有路径 @/components/personal-report/vedic-chart-svg 引入。搬家会同时碰校正热点文件,与并行的校正轮次冲突;若将来要搬,单独一轮只做搬家。
硬红线
frontend/src/app/page.tsx在origin/staging上是 1951 行,AGENTS.md §6 定了 2000 行上限且「不得再增长」。本页必须是独立 route,一行都不许加进page.tsx。- 不得搬动
vedic-chart-svg.tsx,见上。也不得改动它的导出签名——三个既有调用点必须零改动。 - 不得扣点、不得调模型。 新增的 BFF 路由不许 import
@/lib/consultation-billing或任何 mastra agent。这一页出现 spinner / 骨架 / 「正在加载」即为不通过(AGENTS.md §6:揭幕后不得出现加载动画,流式生成中除外)。 ./node_modules/.bin/tsc --noEmit通过;npm run lint0 error。next build后/仍是○ Static;首屏 gzip 变化在 ±2% 内,超出要在进度记录里给出原因。新 route 自身不要求 Static(它读账户数据)。- 测试总数不得低于开工时
origin/staging的实测。改任何既有断言必须写「原值 / 新值 / 原因」三栏。 - 不改数据库结构;本单不得顺带动迁移。
- 改 UI 的同一提交内更新
frontend/DESIGN.md;新文案先对照frontend/docs/VOICE.md。 - 一行后端代码都不许改,特别是
scripts/jyotish_api_server.py——它归TASK-qizheng-native-chart-20260915独占。
文件归属(并行前提)
本单拥有:
frontend/src/app/chart/**(新 route)frontend/src/app/api/chart-view/**(新 BFF)frontend/src/components/chart-page/**(新组件目录)frontend/src/components/app-sidebar.tsx—— 本单独占。本单同时加「星盘」与「星历」两个入口,星历页那一单不碰这个文件。frontend/src/lib/chart-view-*.ts
本单不许碰:scripts/**、deploy/**、vendor/**、frontend/src/app/ephemeris/**、frontend/src/app/api/ephemeris/**、frontend/src/lib/ephemeris-*.ts、frontend/src/components/personal-report/**、frontend/src/components/rectification-*。
三方共享、只许追加各自小节:docs/BUG_HISTORY.md、CHANGELOG.md、frontend/DESIGN.md。合入顺序 qizheng → chart-page → ephemeris-page;rebase 时按各自小节重放,不得覆盖对方。
任务分解
任务 1 · BFF 路由 frontend/src/app/api/chart-view/route.ts
按既有路由的写法(参照 frontend/src/app/api/daily-starlanguage/route.ts 的骨架:export const runtime = "nodejs"、maxDuration、jyotishApiBase = process.env.JYOTISH_API_BASE ?? "http://127.0.0.1:5200"、createServerSupabaseClient、ACCOUNT_BIRTH_SELECT + globalBirthProfileFromAccountRow、consumeUserRequestRateLimit),但:
- 不 import
consultation-billing、不 import@/mastra。 - 出生资料只从服务端自有资料读,不接受前端传入的出生参数(沿用
server-owned-birth-profile的既有边界)。 - 未登录返回
401。 - 上游调用:
/api/chart(主)+/api/dasha/chara(Chara 大运)。- 坑,必须照做:
/api/dasha单独调会忽略月亮黄经、回默认 Ketu balance;大运要从/api/chart响应的dasha段取。/api/dasha/chara与/api/varga_full必须把planets、ascendant、houses一起传进去,否则返回planets must include longitude data。
- 坑,必须照做:
- 西洋盘与七政四余走
/api/western、/api/qizheng(由 qizheng 单交付)。这两个端点未上线时,本路由对应字段返回unavailable,不得抛错、不得让整页失败。 - 响应是一个窄合同(新增
frontend/src/lib/chart-view-contract.ts,zod 校验),只含页面要用的字段,不透传引擎全量响应。
验收标准
- 新增
frontend/tests/chart-view-route.test.ts:未登录 401;资料不全返回结构化提示而非 500;/api/western或/api/qizheng不可用时该字段为unavailable且其余 Tab 正常;响应通过 zod 校验。 - fixture 必须来自真实引擎响应(golden),不得手造形状(AGENTS.md §7.4)。
- 全程零扣点:测试里断言未调用任何计费路径。
任务 2 · 页面与五个 Tab frontend/src/app/chart/
页面结构按原型:页眉(眉标「直接计算 · 打开即有 · 不消耗点数」+ 标题 + 出生资料一行)→ 五个 Tab → 内容区。
- 星盘:D1–D30 分盘 chip(一行可换行)+ 盘面 + 右侧行星位置表(星体 / 星座与度数 / 宫位 / 状态)。盘面复用
VedicChartSvg(从现有路径引入,不搬文件)。 - 基础信息:每个星体一张卡——D1 落座与宫位、本命星宿 + 第几足 + 星宿主、D9 落座、传统象征一行(灰字 + 顶部边界卡)。
- 大运:Vimshottari 与 Chara 两条轨并列,当前段高亮。底部固定一句:只有两条同时指向同一段时间才算证据,单轨命中降一级置信度。
- Chara 的正式名称是 Chara Dasha(kn_rao 变体),引擎
method字段就这么写的,不要写成 Narayana。 - 分运(antardasha)若从引擎拿不到,可以按标准比例从大运边界推,但必须在界面上注明「按标准比例推出,不是引擎单独返回的字段」。
- Chara 的正式名称是 Chara Dasha(kn_rao 变体),引擎
- 西洋盘:圆盘(外圈黄道十二宫、Placidus 不等宽宫位、上升在左、度数逆时针)+ 元素模式分布 + 主要相位表。右栏第一张必须是边界卡。
- 七政四余:十二地支宫方格(巳午未申在上,寅丑子亥在下)+ 人事宫 + 所辖宿 + 十一曜宿度表。必须出现三条口径:宿度自角宿起算、计都派别、庙旺未闭合。
中宫排盘参数卡:桌面放六行;手机上盘宽只有约 318px,中宫塞不下,只留岁差两行,完整参数改成盘下方一张卡。原型里这两种形态都画了。
验收标准
tsc --noEmit0 错;npm run lint0 error。- 新增组件测试:五个 Tab 都能切换;
unavailable的 Tab 渲染静态说明而不是报错;手机宽度下中宫只有两行且完整参数卡出现在盘下方。 - 视觉:
frontend/DESIGN.md追加「只读星盘页」小节,写清 Tab 体系边界、中宫参数卡、三套坐标系的各自标注。 - 文案对照
frontend/docs/VOICE.md。 - 触摸目标 ≥ 44px;正文 ≥ 14px,12–13px 只给短标签与元信息(DESIGN.md §3)。盘内 SVG 文字按缩放后实际像素计,不得低于 12px。
任务 3 · 侧边栏入口(本单独占)
frontend/src/components/app-sidebar.tsx(367 行)在「新建对话」之后、「我的报告」之前新增两个入口:星盘、星历。- 两个入口都要有折叠态图标(DESIGN.md「Sidebar shell · Collapsed content」要求折叠时仍可达)。
- 当前项使用既有的白玻璃面 + 2px 深棕标记,不得新造一套选中态。
- 星历入口现在会指向一个尚未存在的 route(由
TASK-ephemeris-page-20260915交付)。本单先建一个最小占位页frontend/src/app/ephemeris/page.tsx?不。 本单不创建 ephemeris 目录(归属对方)。做法:星历入口在对方 route 落地前指向/ephemeris,本地开发会 404,这是预期的;在进度记录里写明,并在 PR 描述里注明合入顺序。
验收标准
- 侧边栏三态(桌面展开 288px / 平板折叠 64px / 手机抽屉)下两个入口都可达。
frontend/tests/touch-target-contract.test.ts通过。- 不得改动既有入口的顺序与文案。
让步顺序
- 先保证 任务 1 + 任务 2 的前三个 Tab(星盘 / 基础信息 / 大运)。这三个不依赖任何新端点,可以完全独立交付。
- 西洋盘、七政四余两个 Tab 可以只落
unavailable形态,等对方端点上线再点亮。 - 「传统象征」词条层可以延后;延后时基础信息卡只保留事实三行。
- 不可让步:不碰
page.tsx、不搬vedic-chart-svg.tsx、不扣点不调模型、无 spinner、三套坐标系的边界标注、/仍 Static。
开工前置命令
cd /workspace/Jyotisha
git status -sb
git fetch origin --prune
git worktree add -b codex/chart-page-20260915 .worktrees/chart-page-20260915 origin/staging
cd .worktrees/chart-page-20260915/frontend
npm ci
./node_modules/.bin/tsc --noEmit # 记下开工基线
npm run lint
npm test 2>&1 | tail -20 # 记下测试总数,验收时不得低于此数
npx next build 2>&1 | grep -E "^[│├└]| / " | head # 记下 `/` 的 Static 标记与首屏 gzip
wc -l src/app/page.tsx # 应为 1951
无 Docker 时 npm run test:db 的失败清单要与基线逐条比对,写进 BLOCKED.md,不得写成「通过」。
BUG 编号起点
origin/staging 上 docs/BUG_HISTORY.md 当前最大号 BUG-699。三份并行单预分配:qizheng 单 700–703、本单 704–706、ephemeris-page 单 707–709。本单是新功能,正常情况下不需要开 BUG 号;只有实现中发现既有缺陷才用,用满要通知另外两单整体后移。
进度与记录
- 进度记录:
docs/tasks/PROGRESS-chart-page-20260915.md,本单状态板一行。 - 环境缺口写
BLOCKED.md(预期:无 Docker →test:db阻塞;无登录态与 Chrome → 浏览器级走查留给docs/testing/)。 - 真人验收清单写进
docs/testing/:五个 Tab 切换、手机宽度中宫两行、折叠侧边栏两个入口可达、未登录跳转。 CHANGELOG.md写用户可感知的变化,注明 Skill 版本未 bump。- 索引:
docs/tasks/README.md追加本单。