Files
Jyotisha/docs/tasks/TASK-report-chart-layout-fix-20260909.md
T

111 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK · 报告页星盘全部叠在一起 + 页面卡顿 — 2026-09-09
- 基线:`origin/staging` @ `17b36f3a`staging 已部署 `365ad799`,含 `2fdcb14f` 星盘渲染与 `7d3bb0c5` BUG-609611 修复)。
- 分支:`codex/report-chart-layout-fix-20260909`worktree `.worktrees/report-chart-layout-fix-20260909`
- 关联:`TASK-report-chart-render-20260909.md`(BUG-607,本单是它的回归修复)。改 `personal-report-markdown-view.tsx``globals.css`,与同日其他任务书无文件冲突。
## 1. 事故实证(产品负责人 staging 真机截图,2026-09-09
### P0 · 22 张北印盘互相重叠、标题被堆成一列
`/personal-report`(长报告页)"Birth Chart / Vargas" 段:D1 / D9 / Moon 三张盘叠在同一位置;Vargas I 段 D2D60 共 19 个 `####` 标题以约 2.4em 间距堆在左上角,19 张盘全部叠在一块区域上,"Shodashvarga 星座总表" 标题和后面的表格也压在盘上。
根因在 `frontend/src/app/globals.css` L3835L3860`2fdcb14f` 引入):
```css
h4.personal-report-chart-heading:has(+ .personal-report-chart-figure) { float: left; width: 50%; }
h4.personal-report-chart-heading + .personal-report-chart-figure { float: left; width: 50%; margin-left: -50%; padding-top: 2.4em; }
.personal-report-chart-figure + :not(h4):not(.personal-report-chart-figure) { clear: both; }
```
执行方想用"标题浮动 50%、图浮动 50% 再负 margin 拉回标题底下"来在 ReactMarkdown 的扁平兄弟节点上凑出两栏。`margin-left:-50%` 让每个 figure 的 margin box 宽度为 0,浮动算法认为它不占横向空间,于是所有 figure 堆在同一行同一位置;标题只按 50% 排两列,第三个起往下掉一个标题高度。只要连续出现两对以上"标题 + 盘"Vargas I 就是 19 对)就必然重叠;Moon Chart 的标题和盘之间还有一段说明文字,`:has(+)` 不匹配,那段文字直接被浮动块环绕。`personal-report-markdown-view.test.tsx``renderToStaticMarkup` 只看 DOM,看不到布局,所以 golden 全绿。
### P1 · 滚动时整篇报告反复重建,页面卡
`frontend/src/components/personal-report/personal-report-markdown-view.tsx`
| 位置 | 事实 |
| --- | --- |
| L133L134 | `activeId` 状态放在 `PersonalReportMarkdownView` 顶层;L136 的 IntersectionObserver 每次目录高亮切换都 `setActiveId` → 整个组件重渲染 |
| L34 `markdownComponents(headings)` / L88 `renderMarkdown` | 每次渲染都新建一套 `components` 函数(`a/img/table/h2/h3/h4/pre` 全是新的函数身份)。React 按元素 `type` 引用比较,type 变了就**卸载重建**整棵子树 |
| L167 | 摘要 lead(含 D1 / D9 / Moon 三张盘、基础资料表等)每次 `activeId` 变化都整段卸载重建 |
| L104 `LazyMarkdownSection` | 没有 `memo`,父组件重渲染就跟着重跑 `renderMarkdown` → 已揭开的 Vargas I(19 张盘,每张 12 个多边形 + 上百个 `<text>`)同样整段卸载重建 |
滚过一个标题 = 22 张 SVG + 所有表格全部销毁再创建一次。09-06 Markdown 直渲时就有这个结构,只是那时没有 SVG,代价没被感知;星盘一进来就卡。
## 2. 根因
- P0:用浮动 + 负 margin 模拟两栏,对连续多对无效;单元测试是静态标记,无法拦布局错误。
- P1`components` 映射每渲染新建 + 目录高亮状态放在文章树的父级,等于"每次目录变色重建整篇报告"。
## 3. 决策记录
| 决策 | 内容 |
| --- | --- |
| D1 | **两栏保留**,但不再用浮动/负 margin 凑。改在 Markdown → HTML 阶段用一个 rehype 插件把每对「`#### D… / Moon Chart` 标题 +(可选一段说明)+ ```jyotish-chart 围栏」包成 `.personal-report-chart-card`,连续的卡片包进既有的 `.personal-report-chart-grid``globals.css` L4187document view 已在用的两栏 grid,≤860px 自动单栏)。这是唯一能让扁平兄弟节点成组的确定性方案。 |
| D2 | 若 rehype 方案在时限内做不稳,**让步为单栏**:删掉浮动块,figure 在流中居中、`max-width: 360px`;宁可单栏也不允许任何重叠。 |
| D3 | 目录高亮与文章树解耦:`activeId` 与 observer 下沉到目录组件;文章树只随 `markdown` 变化重建。 |
| D4 | 不放开 `skipHtml`、不引入 `rehype-raw`(BUG-607 红线维持);不新增依赖(rehype 插件用普通循环遍历 hast,不装 `unist-util-visit`)。 |
不推翻 BUG-607 任何红线。
## 4. 硬红线
1. `tsc --noEmit` 0 错;`npm run lint` 0 error`npm test` 失败清单与无 Docker 基线(28 条)逐条一致;`next build` `/` Static;首屏 gzip ±2%。
2. `globals.css` 里 `margin-left: -50%` 与 `float` 用于 `.personal-report-chart-*` 的规则**全部删除**,不留 fallback。
3. `frontend/src/app/page.tsx` 不动;不新增 npm 依赖。
4. 下载的 `.md``personal-report-longform-download.ts`)行为不变:标题保留、围栏剥掉。
## 5. 任务分解
### 任务 1 · 两栏成组(P0)
- 新文件 `frontend/src/lib/report-chart-grid-rehype.ts`:导出一个 rehype 插件,遍历 `root.children`
- 识别「卡片单元」= `h4`(文本匹配 `/^(?:D\d{1,3}\b|Moon Chart)/`)→ 跳过空白文本节点 → 可选一个 `p` → `pre > code.language-jyotish-chart`。
- 把单元三个节点搬进 `div.personal-report-chart-card`;相邻卡片(中间只有空白)合并进同一个 `div.personal-report-chart-grid`;孤立一张卡也包 grid 并加 `is-single`。
- 不匹配的 `h4`/`pre` 原样保留("Shodashvarga 星座总表"等不受影响)。
- `personal-report-markdown-view.tsx` `renderMarkdown` 接 `rehypePlugins={[reportChartGrid]}``h4`/`pre` 渲染器不变。
- `globals.css`:删 L3835L3860 浮动块;`.personal-report-md-article .personal-report-chart-card h4` 复用 `h4.personal-report-chart-heading` 字号;`.personal-report-chart-card .personal-report-chart-figure { max-width: none; margin: var(--space-3) 0 0 }`;打印样式 L4420L4440 一并核对,grid 在打印下单栏。
- `frontend/DESIGN.md` 同提交更新"报告页星盘"条目。
验收(`frontend/tests/personal-report-markdown-view.test.tsx` 新增):
- 输入 3 对连续「标题 + 围栏」(第 3 对标题与围栏之间夹一段 `p`)→ 输出含 **1 个** `.personal-report-chart-grid`、**3 个** `.personal-report-chart-card`,每张卡内依次是 `h4` →(`p`)→ `figure`grid 后面的普通 `h4` 不在任何卡内。
- 输入 1 对 → grid 带 `is-single`。
- 新增一条 CSS 合同测试:`globals.css` 中不存在同时含 `personal-report-chart` 与 `float:`/`margin-left: -50%` 的规则。
- 真实 golden`frontend/tests/fixtures/report-chart-blocks-golden.json` 对应的 Markdown)渲染后 22 张 `figure` 全在 `.personal-report-chart-card` 内。
### 任务 2 · 滚动不重建(P1)
- `markdownComponents` 的 `cursor`(h2/h3 去重 id 用)改成每次 `renderMarkdown` 调用时新建,但**渲染结果整树 `useMemo`**lead 用 `useMemo(() => renderMarkdown(outline.leadMarkdown, outline.headings), [outline])``LazyMarkdownSection` 内 `useMemo(..., [visible, section])`,组件本身 `memo()`。
- `activeId` 状态与 IntersectionObserver 移到新的 `ReportToc` 组件(接 `headings`),`PersonalReportMarkdownView` 不再持有任何滚动态。
- 本仓测试无 DOM 运行时(`renderToStaticMarkup`),无法自动验证重渲染次数:进度记录里写明这一点,并给出改动前后 React 组件树结构说明;真机验证条目写进 `docs/testing/report-chart-render-20260909.md`(Vargas I 全部揭开后连续滚动 10 秒,Chrome Performance 无 >100ms 长帧;目录高亮切换时 D1 的 `<svg>` DOM 节点不变——Elements 面板选中后滚动,选中不丢)。
验收:`git diff` 中 `renderMarkdown(` 只在 `useMemo` 内被调用;`PersonalReportMarkdownView` 里没有 `useState`。
### 任务 3 · 记录
- `docs/BUG_HISTORY.md`**BUG-616**P0 重叠,关联 BUG-607,说明为什么静态标记测试没拦住)、**BUG-617**P1 重建卡顿,关联 09-06 Markdown 直渲)。
- `docs/tasks/PROGRESS-report-chart-layout-fix-20260909.md``CHANGELOG.md` 一条。
- `docs/testing/report-chart-render-20260909.md` 补任务 2 的两条真机条目。
## 6. 让步顺序
1. 任务 1 的 rehype 两栏 → 让步为 D2 单栏(仍必须删浮动块 + CSS 合同测试)。
2. 任务 2 的 `ReportToc` 下沉可后做,但 `useMemo` 整树与 `memo(LazyMarkdownSection)` 不可砍。
3. **不可砍**:删浮动块、任务 3。
## 7. 开工前置命令
```bash
git -C /workspace/Jyotisha status -sb | head -1
git fetch origin --prune
git worktree add -b codex/report-chart-layout-fix-20260909 .worktrees/report-chart-layout-fix-20260909 origin/staging
cd .worktrees/report-chart-layout-fix-20260909/frontend
npm test 2>&1 | tail -5 # 记录基线:总数 / 失败清单
./node_modules/.bin/tsc --noEmit && npm run lint
```
## 8. BUG 编号起点
`docs/BUG_HISTORY.md` 与已推任务书里最大为 `BUG-615`;本单 **BUG-616 / 617**,开工时重新核对。