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

+35
View File
@@ -236,3 +236,38 @@
`__USE_MINGW_ANSI_STDIO=1`,命令行关不掉 —— 见 Makefile 注释)。
4. **改面板几何/绘制前后必须跑** `OffscreenChecks -run panels -mode record/compare`;
本轮它当场抓到了"截取面板少了 37px 高度"的回归。
---
## 八、第 36 轮追加(2026-11-04):§12.7 两项建议的落地
> 触发:用户要求「继续按照上轮报告的后续建议推进」。§12.7 列了两项:
> ① `EdgePanelLayout` 平行数组合并;② `PaintEdgeCursor` 拆分。
| 节点 | 内容 | 结论 |
| --- | --- | --- |
| **P26** | 拆分 `PaintEdgeCursor` 三段内容绘制 | ✅ 完成(新建 `PlotViewPanelPaint.cpp`) |
| **P27** | 平行数组错位隐患 | ✅ 用 `AppendRow()` 唯一入口消除(**未做**完整结构合并,理由见下) |
| **P28** | 收紧不变式 | ✅ 18 个数组**全部**必须等于行数(不再允许"重采样专属列可为空") |
### 8.1 本轮新增硬约束
1. **新增一种面板行,只能走 `EdgePanelLayout::AppendRow(...)`**。
18 个平行数组由它一次补齐;参数行 / 重采样行一律 `rowXxx.back() = …` **赋值**。
**禁止**在任何布局分支里手写 `rowXxx.push_back(...)`。
2. **面板内容绘制按排版类别分文件**:骨架(边框/标题栏/✕/按钮)在 `PlotViewPaint.cpp`,
内容在 `PlotViewPanelPaint.cpp`。新增一种面板 = 加一个 `PanelStyleOf` 分支 +
在对应内容函数里加一支。
3. **`OffscreenChecks -run panels` 的长度不变式现在是"全等"判定**:
任何一列短于行数即失败(这条检查是"绕过 `AppendRow`"的唯一探测器)。
### 8.2 为什么没有做「完整合并成 `std::vector<PanelRow>` + 列视图」
技术上可行(`PanelColumn<T>` 持有 `std::vector<PanelRow>*` + 成员指针,
读点只需在 `layout.rowXxx` 后加一对括号),但 **`EdgePanelLayout` 需要自定义拷贝构造/赋值**
来重新绑定视图;而它是 `EdgePanelLayoutFor()` 的返回值、又被存进 `std::vector`,
拷贝是热路径 —— 拷贝语义一旦写错,会产生**比原缺陷更隐蔽的悬垂视图**(原缺陷至少会被
长度不变式抓到,悬垂指针不会)。
本轮先用 `AppendRow()` 把**真实隐患**("某分支漏推一列")消除,
完整结构合并留待"逐行几何基线"检查就绪后再做。