docs(tasks): report chart layout fix brief — overlapping charts + scroll remount jank (BUG-616/617)
This commit is contained in:
@@ -121,6 +121,7 @@
|
||||
| `TASK-report-list-500-20260907.md`(仓库根) | `PROGRESS-report-list-500-20260907.md` | 列表 PostgREST JSON 路径 500 | 待验收 | `b466a6fc`(BUG-574) |
|
||||
| — | `PROGRESS-report-progress-20260909.md` | 生成等待屏只有 spinner 与秒表:后端 `progressPercent` / `progressPhase` 与分章行已产出,前端解析后一字未渲染,且分章行在 `generating` 时根本不出服务端。改为按章分格进度条 + 章节清单,停滞 90 秒改「用时较长,仍在写」;不画百分比条、不做插值动画、不报预计剩余 | 已验收 | `848e39e6`、`5565b632`(BUG-601) |
|
||||
| `TASK-report-chart-render-20260909.md` | `PROGRESS-report-chart-render-20260909.md` | 报告页所有分盘标题下空白:引擎 22 张 `<svg>` 被 `skipHtml` 丢弃(09-06 Markdown 直渲回归)。引擎旁加 ```jyotish-chart 围栏 JSON,前端 zod 校验后用自绘北印式组件全部渲染;不放开原始 HTML | 已验收(2fdcb14f):tsc/lint 0 error、chart 测试 29/29、`/` Static、gzip +0.96%;围栏构造在 try 外的 P2 并入 `TASK-upstream-sync2-fix-20260909.md` 任务 3 | `codex/report-chart-render-20260909`(BUG-607) |
|
||||
| `TASK-report-chart-layout-fix-20260909.md` | — | 星盘渲染回归修复:22 张北印盘全部重叠(浮动 + 负 margin 凑两栏对连续多对无效)+ 滚动时 `components` 每渲染新建导致整篇报告卸载重建、页面卡;改 rehype 成组进既有两栏 grid、文章树 `useMemo`、目录状态下沉 | 待领取 | 分支 `codex/report-chart-layout-fix-20260909`(BUG-616/617) |
|
||||
|
||||
### 前端基础与工程
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
# TASK · 报告页星盘全部叠在一起 + 页面卡顿 — 2026-09-09
|
||||
|
||||
- 基线:`origin/staging` @ `17b36f3a`(staging 已部署 `365ad799`,含 `2fdcb14f` 星盘渲染与 `7d3bb0c5` BUG-609~611 修复)。
|
||||
- 分支:`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 段 D2~D60 共 19 个 `####` 标题以约 2.4em 间距堆在左上角,19 张盘全部叠在一块区域上,"Shodashvarga 星座总表" 标题和后面的表格也压在盘上。
|
||||
|
||||
根因在 `frontend/src/app/globals.css` L3835–L3860(`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`:
|
||||
|
||||
| 位置 | 事实 |
|
||||
| --- | --- |
|
||||
| L133–L134 | `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` L4187,document 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`:删 L3835–L3860 浮动块;`.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 }`;打印样式 L4420–L4440 一并核对,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**,开工时重新核对。
|
||||
Reference in New Issue
Block a user