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

+75
View File
@@ -480,3 +480,78 @@ Tools/README.md 目录结构、作用、测试项、验收标准、A/B
机械提取为 3 个成员函数,判据 = `panels -mode compare` 全绿。
3. **`EdgeScanFor` 状态查找**:维持线性扫描(第 34 轮实测 198 状态 0.097 µs/次,
哈希化需 +5,632 B,不划算)。
---
## 13. 第 36 轮(2026-11-04):§12.7 两项建议的落地结果
> 计划 `Code_Review_Plan.md` §八,逐节点 `Code_Review_Progress.md` 第 36 轮。
### 13.1 一句话结论
§12.7 的两项都做完了,并且**都用像素基线证明是纯结构改动**:
`PaintEdgeCursor` 从约 1900 行降到约 900 行(内容绘制拆到 `PlotViewPanelPaint.cpp` 1016 行),
平行数组的"漏推一列"隐患用 `AppendRow()` 唯一入口消除。
产物 786,432 → **785,408 B**(−1,024),7 个面板的像素哈希与几何**逐项不变**。
| §12.7 建议 | 结果 |
| --- | --- |
| 1. `EdgePanelLayout` 平行数组隐患 | ✅ 用 `AppendRow()` 消除真实隐患;**完整结构合并**按其风险(悬垂视图)暂不做,理由见 §13.3 |
| 2. 拆分 `PaintEdgeCursor` | ✅ 完成,新建 `PlotViewPanelPaint.cpp` |
### 13.2 拆分后的文件职责
| 文件 | 行数 | 职责 |
| --- | --- | --- |
| `PlotViewPaint.cpp` | 5575 | 面板**骨架**(边框 / 标题栏 / 折起三角 / GX 徽标 / ✕)+ 底部按钮 + 其余全部绘制 |
| `PlotViewPanelPaint.cpp` | 1016 | 面板**内容**:`PaintPanelCountTableRows` / `PaintPanelListRows` / `PaintPanelMarkerGroups` |
⚠️ 骨架与内容的划分依据:**对所有面板都是同一份**的留在骨架;
**按排版类别不同**的进内容函数。新增一种面板 = `PanelStyleOf` 加一支 +
在对应内容函数里加一支(不会碰到骨架)。
### 13.3 为什么没做"完整合并成 `std::vector<PanelRow>`"
技术上可行:`PanelColumn<T>` 持有 `std::vector<PanelRow>*` + 成员指针,
读点只需 `layout.rowXxx` → `layout.rowXxx()`(纯正则,314 处可信)。
**但**:`EdgePanelLayout` 必须自定义拷贝构造 / 拷贝赋值来重绑视图,
而它是 `EdgePanelLayoutFor()` 的返回值、又被存进 `std::vector<EdgePanelLayout>` ——
拷贝在热路径上。视图一旦在拷贝后忘记重绑,行为是**悬垂指针**:
可能读出垃圾矩形、也可能"看起来正常"直到某次重排才崩。
对比两个方案的**失败可见性**:
* 现在的平行数组 + `AppendRow()`:漏一列 ⇒ **长度不变式当场报错**;
* 列视图:忘了重绑 ⇒ **没有检查能抓到**。
因此本轮只做"消除真实隐患"(`AppendRow`),完整合并推迟到
"逐行几何基线"(把每行 18 个矩形都写进基线文件、逐项比对)就绪之后。
### 13.4 本轮"机械搬移"踩到的三个真问题(值得记住)
1. **局部 lambda 无法跨函数复用**:`paintCounts` 只服务列表段 ⇒ 下沉为该函数的局部 lambda;
`paintPanelScrollBar` 服务三段 ⇒ 提升为 `pvdetail::PaintPanelScrollBar` 内联函数
(参数显式化,不再隐式捕获 `m_theme`)。
2. **提取块依赖外层局部变量**:「标记透明度」滑块用 `panel` / `pad` ⇒ 改为从 `layout.panel` 派生。
这类隐式依赖正是"整个函数都看得见"的害处,提取时必须逐个查。
3. **const 正确性**:`tabLanes` 在 marker 段里是**被写**的(回写各列实测宽度)⇒
参数不能是 `const std::vector<Lane>&`。提取时"看起来很只读"的参数要核对写入点。
### 13.5 验证与现状
| 项 | 结果 |
| --- | --- |
| `make release`(全量) | 零 error / 零 warning;**785,408 B**(距 1 MB 预警线 ≈260 KB) |
| `UnitTests` | `checks=260 failed=0 cases=19` |
| `OffscreenChecks -all` | 6/6 PASS,`checks=101 failed=0` |
| `panels -mode compare` | 7 个面板像素哈希 + 几何逐项不变 |
| `panelscroll`(566 行) | 18 个行数组全部等于 566 |
### 13.6 后续建议(更新)
1. **逐行几何基线**:把每行 18 个矩形写进 `Temp/tools/rows-baseline.txt`,逐项比对。
这是"完整结构合并"与"重写布局算式"的前置护栏,成本低(导出 + 字符串比对)。
2. **`rowPoints` / `rowIterations` 已无绘制方使用**(旧式输入框排版已删除):
可在 `AppendRow` 里保留但标注"仅为兼容保留",或在下一轮与文档一起清掉。
3. **`PaintPanelListRows` 仍约 470 行**:可再按"条目栏 / 参数行 / 重采样行"拆三段,
但当前已不在"改一次就出错"的路径上,优先级低于第 1 项。