test(tools): 行数组不变式收紧为"18 个全部等于行数"

背景:`AppendRow()`(提交 85aaed5)已经成为新增面板行的唯一入口,
18 个平行数组由它一次补齐。因此旧的宽松判据("13 个必须等长 +
4 个重采样专属列允许为空")不再需要 —— 任何一列短于行数,
就是"有人绕过 AppendRow 手写 push"的直接证据。

改动:
* `panels` 检查:改为断言 18 个数组长度**全部**等于 `rowRefs.size()`,
  失败时打印全部 18 列的长度便于定位。
* `panelscroll` 检查:同样收紧(大文件 566 行时才会暴露的问题)。
* `Tools/README.md`:同步不变式描述。

文档:
* `Documents/Code_Review_Plan.md` 新增 §八(第 36 轮节点与硬约束)。
* `Documents/Code_Review_Progress.md` 新增第 36 轮(两次重构 + 三个搬移陷阱)。
* `Documents/Code_Review_Report.md` 新增 §13(拆分职责表、为何不做完整合并、
  失败可见性对比、后续建议)。
* `Documents/agent_mem.md` 新增第 36 轮规则。

验证:
* `make release` 全量零 error / 零 warning,产物 **785,408 B**。
* `UnitTests` → checks=260 failed=0;`OffscreenChecks -all` → 6/6 PASS,checks=101 failed=0。
* `panels -mode compare` → 7 个面板像素哈希与几何逐项不变。
* `panelscroll`(MF4 566 行)→ 18 个行数组全部 = 566。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 15:20:07 +08:00
1 parent 85aaed55f6
commit 221f9d2410
6 files changed
+260 -35

No files matched your search

+65
View File
@@ -10499,3 +10499,68 @@ release/debug **零警告**;Release `TargetCheck.exe` **890,880 B**(< 1 MB
是"改布局就出错"的最后一块机制隐患;已先用长度检查兜住。
* `PaintEdgeCursor`(约 1600 行)把三段内容绘制机械提取为 3 个成员函数,
判据 = `panels -mode compare` 全绿。
---
## 2026-11-04 · 第 36 轮(`Code_Review` 分支):§12.7 两项建议落地
> 提交:`38fbd4b`(拆分 PaintEdgeCursor)、`85aaed5`(AppendRow 唯一入口)、本提交(不变式收紧+文档)。
> 规则原文:`Documents/agent_mem.md` 第 36 轮、`Code_Review_Report.md` §13。
### 1. ✅ `PaintEdgeCursor` 已拆分(1900 → 约 900 行)
| 文件 | 行数 | 职责 |
| --- | --- | --- |
| `PlotViewPaint.cpp` | 5575 | 面板**骨架**(边框/标题栏/折起三角/GX 徽标/✕)+ 底部按钮 + 其余绘制 |
| `PlotViewPanelPaint.cpp` | 1016 | 面板**内容**:`PaintPanelCountTableRows` / `PaintPanelListRows` / `PaintPanelMarkerGroups` |
划分依据:**对所有面板都是同一份**的留骨架;**按排版类别不同**的进内容函数。
新增一种面板 = `PanelStyleOf` 加一支 + 对应内容函数加一支(不碰骨架)。
滚动条外观统一为 `pvdetail::PaintPanelScrollBar(g, layout, theme)`。
### 2. ✅ 平行数组错位隐患用 `AppendRow()` 消除
**新硬规则**:新增面板行**只能**走 `EdgePanelLayout::AppendRow(ref, header, nameRect, triangle, check)`
(18 个平行数组一次补齐);参数行 / 重采样行一律 `rowXxx.back() = …` **赋值**。
**禁止**在任何布局分支里手写 `rowXxx.push_back(...)`。
⇒ "新增一列"只需改 `AppendRow` 一处;"新增一行"必须走它(结构上不会漏列)。
改前有 **4 个**地方各自手写 push(表格 13 次 / 列表基行 13 次 / 未勾选分支 6+4 次空 push /
展开分支 6+4 次),任何一处漏推都会让该列之后的所有行读到别人的矩形。
### 3. ⚠️ 为什么**不**把平行数组合并成 `std::vector<PanelRow>` + 列视图
技术上可行(读点只需正则加括号),但 `EdgePanelLayout` 必须自定义拷贝构造/赋值来重绑视图,
而它是 `EdgePanelLayoutFor()` 的返回值、又被存进 `std::vector` —— 拷贝是热路径。
**失败可见性对比**:
* 现在(平行数组 + AppendRow):漏一列 ⇒ **长度不变式当场报错**;
* 列视图:忘了重绑 ⇒ **悬垂指针,没有检查能抓到**。
⇒ 先消除真实隐患,完整合并推迟到"逐行几何基线"就绪后。
### 4. ✅ 不变式收紧为"全等"
`panels` / `panelscroll` 的长度检查从"13 个等长 + 4 个可空"改为
**18 个全部必须等于行数** —— `AppendRow()` 成为唯一入口后,
"某列短了"就是"有人绕过它"的直接证据。
### 5. ⚠️ 机械搬移踩到的三个真问题(提取代码时必查)
1. **局部 lambda 不能跨函数复用**:只服务一段的下沉为该函数局部;多段共用的提升为
pvdetail 内联函数(参数显式化)。
2. **提取块会隐式依赖外层局部变量**(如「标记透明度」滑块的 `panel` / `pad`)⇒
改为从 `layout` 派生。这是"整个函数都看得见"的害处。
3. **const 正确性**:`tabLanes` 在 marker 段里是**被写**的(回写各列实测宽度)⇒
"看起来只读"的参数要核对写入点。
### 6. 本轮数字
* Release **785,408 B**(786,432 → −1,024);零 warning。
* `UnitTests` 260 断言 / 19 用例;`OffscreenChecks -all` 6 检查 / 101 断言;全部 PASS。
* `panels -mode compare` 7 个面板像素哈希 + 几何**逐项不变**(两次重构全程守住)。
* `panelscroll`(MF4 566 行)18 个行数组全部 = 566。
### 7. 遗留(下一步候选)
* **逐行几何基线**(导出每行 18 个矩形做逐项比对)—— 完整结构合并与布局算式重写的前置护栏。
* `rowPoints` / `rowIterations` 已无绘制方使用(旧式输入框排版早已删除),可标注/清理。
* `PaintPanelListRows` 仍约 470 行,可再拆三段,但已不在"改一次就出错"的路径上。