# 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 的对话中授权: 1. 新建一个**不消耗点数、不调模型、打开即有**的只读星盘页。 2. Tab 顺序定为 **星盘 / 基础信息 / 大运 / 西洋盘 / 七政四余**。 3. **体系分在 Tab 这一层**;分盘 chip(D1–D30)只在印度体系内部使用。不得把西洋盘或七政四余做成第 13 个 chip。 4. **盘的中宫放「排盘参数卡」**:钟表时间与校正状态、出生地与经纬、岁差与交点模式、上升与月亮星宿、当前大运分运、引擎名。手机上放不下,只留岁差两行,完整参数改成盘下方一张卡。 5. **三套坐标系必须各自在中宫写明自己是什么,并显式点名不能互相换算**,也不得把两边结论叠加成「双重印证」。 6. 「传统象征」词条层可以做,但必须带边界句「是词条式释义,不是对你个人的判断」,且**不得出现任何运势判断**。 ### 本单推翻的一处早期说法 立单核对时发现:`VedicChartSvg` 并非「只被 personal-report 引用」,`rectification-board.tsx` 也在用。**因此本单不搬这个文件。** 新页面直接从现有路径 `@/components/personal-report/vedic-chart-svg` 引入。搬家会同时碰校正热点文件,与并行的校正轮次冲突;若将来要搬,单独一轮只做搬家。 ## 硬红线 1. `frontend/src/app/page.tsx` 在 `origin/staging` 上是 **1951 行**,AGENTS.md §6 定了 2000 行上限且「不得再增长」。**本页必须是独立 route,一行都不许加进 `page.tsx`。** 2. **不得搬动 `vedic-chart-svg.tsx`**,见上。也不得改动它的导出签名——三个既有调用点必须零改动。 3. **不得扣点、不得调模型。** 新增的 BFF 路由**不许 import** `@/lib/consultation-billing` 或任何 mastra agent。这一页出现 spinner / 骨架 / 「正在加载」即为不通过(AGENTS.md §6:揭幕后不得出现加载动画,流式生成中除外)。 4. `./node_modules/.bin/tsc --noEmit` 通过;`npm run lint` **0 error**。 5. `next build` 后 `/` 仍是 `○ Static`;首屏 gzip 变化在 ±2% 内,超出要在进度记录里给出原因。**新 route 自身不要求 Static**(它读账户数据)。 6. 测试总数不得低于开工时 `origin/staging` 的实测。改任何既有断言必须写「原值 / 新值 / 原因」三栏。 7. 不改数据库结构;本单不得顺带动迁移。 8. 改 UI 的同一提交内更新 `frontend/DESIGN.md`;新文案先对照 `frontend/docs/VOICE.md`。 9. **一行后端代码都不许改**,特别是 `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)若从引擎拿不到,可以按标准比例从大运边界推,但**必须在界面上注明「按标准比例推出,不是引擎单独返回的字段」**。 - **西洋盘**:圆盘(外圈黄道十二宫、Placidus 不等宽宫位、上升在左、度数逆时针)+ 元素模式分布 + 主要相位表。右栏第一张必须是边界卡。 - **七政四余**:十二地支宫方格(巳午未申在上,寅丑子亥在下)+ 人事宫 + 所辖宿 + 十一曜宿度表。必须出现三条口径:宿度自角宿起算、计都派别、庙旺未闭合。 **中宫排盘参数卡**:桌面放六行;**手机上盘宽只有约 318px,中宫塞不下**,只留岁差两行,完整参数改成盘下方一张卡。原型里这两种形态都画了。 **验收标准** - `tsc --noEmit` 0 错;`npm run lint` 0 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. 先保证 任务 1 + 任务 2 的前三个 Tab(星盘 / 基础信息 / 大运)。这三个不依赖任何新端点,**可以完全独立交付**。 2. 西洋盘、七政四余两个 Tab 可以只落 `unavailable` 形态,等对方端点上线再点亮。 3. 「传统象征」词条层可以延后;延后时基础信息卡只保留事实三行。 4. **不可让步**:不碰 `page.tsx`、不搬 `vedic-chart-svg.tsx`、不扣点不调模型、无 spinner、三套坐标系的边界标注、`/` 仍 Static。 ## 开工前置命令 ```bash 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` 追加本单。