review(panels+size): 面板结构收敛收尾、审查清理、新增平行数组护栏

一、审查清理
* `PlotView::FreeMarkerValue` 的 `const Lane&` 形参从未使用(函数体里只有一句
  `(void)lane;`)⇒ 删除形参与该语句,更新 2 个调用点。
* `EdgePanelLayoutFor` 里「选区截取是否就绪(双光标)」算了 3 遍 ⇒ 收敛为函数顶部
  一次 `clipReady`。它同时决定面板高度 footer、条目行可见性、按钮几何,
  三处各算一遍时只要有一处条件写错,就是"底部多一段空白"或"没双光标却能滚动"。

二、新增护栏(离屏检查)
* `panels`:新增「平行数组长度一致」不变式 —— `EdgePanelLayout` 的 13 个按行平行
  vector 必须与行数一致;4 个「重采样」专属数组允许为空、非空则必须对齐。
  这是"某列读到别人矩形"这类错位缺陷的唯一低成本护栏(O(1))。
* `panelscroll`:同样加一份(大文件、566 行时才会暴露的问题)。
* `panels` 输出增加 rows 数,便于定位。

三、文档
* `Documents/Code_Review_Plan.md` 新增 §七(第 35 轮节点与硬约束)。
* `Documents/Code_Review_Progress.md` 新增第 35 轮(面板架构评估 + 三次重构记录)。
* `Documents/Code_Review_Report.md` 新增 §12(评估结论 + gdtoa 最终结论 + 护栏价值)。
* `Documents/agent_mem.md` 新增第 35 轮六条规则。
* `Tools/README.md`:补 `panelscroll`、`integer_replacement_matches_std`,
  断言数更新为 260 / 108,最小集表补充面板相关条目。

验证:
* `make release`(删 .o 全量)零 error / 零 warning,产物 **786,944 B**。
* `make -C Tools` 零 error / 零 warning。
* `UnitTests` → checks=260 failed=0 cases=19。
* `OffscreenChecks -all` → 6/6 检查 PASS,checks=108 failed=0。
* `panels -mode compare` → 7 个面板像素哈希与几何逐项不变。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-11-04 14:37:59 +08:00
1 parent 34365b3c1c
commit b58b427f62
8 files changed
+408 -15

No files matched your search

+89
View File
@@ -391,3 +391,92 @@ Tools/README.md 目录结构、作用、测试项、验收标准、A/B
重写后 `-mode compare`;只要有一处像素/几何差异就说明改坏了。
5. **不要重复这两次实验**:`EdgeScanFor` 哈希化(+5.6 KB / −5 μs 不划算)、
仅切 `__USE_MINGW_ANSI_STDIO`(0 B)。数字见 §11.1 与 `Code_Review_Progress.md` 第 34 轮。
---
## 12. 第 35 轮(2026-11-04):浮动面板架构评估与收敛 + 整数格式化
> 计划见 `Code_Review_Plan.md` §七,逐节点记录见 `Code_Review_Progress.md` 第 35 轮。
### 12.1 一句话结论
「每个面板骨架一致、但每次改布局就出错」的机制**不是**"缺一个公共基类",
而是**同一套"按种类分支"被抄了 6 份**(分类布尔量 ×3、滚动几何 ×3、滚动条绘制 ×3、
滚动设置 ×7、按种类转发 ×2、标题 ×2)。本轮把这些抄本收敛成"唯一来源 + 编译期穷举检查",
并补了两条离屏护栏(`panelscroll`、平行数组长度一致),
用 786,944 B(−1,024)的产物体积换来"新增面板只改 3 个地方"。
### 12.2 评估:这些面板/菜单算不算"公共组件"?
| 对象 | 现状 | 结论 |
| --- | --- | --- |
| 右键菜单 / 二级菜单 | `Components/Shared/PopupMenu` + `Layout`/`Paint` 分片 | **已经是公共组件**(5 类菜单共用一份实现) |
| 拾色器浮层 | `Components/Shared/ColorPickerPopup` | **已经是公共组件** |
| 实验性功能浮动面板 | 全部在 `PlotView` 内,骨架一份、**按种类分支多份** | 骨架公共,分支需收敛(本轮已做) |
**为什么不把面板做成 `UiWidget` 树节点**(像 PopupMenu 那样):
1. 面板像素要进 `m_panelDc` 图层缓存(`PanelGlobalKey ⊕ PanelSlotKey`),
与 `UiWidget` 的 dirty/capture 体系叠加会互相失效(`SignalPanel` 行位图缓存
与控件树就是刻意分开的);
2. 面板几何**必须**由 `EdgePanelLayoutFor` 唯一给出(绘制 / 命中 / 光标 / 调试盒 /
缓存键五处共用),控件树会拆掉这条"几何同源";
3. 面板要往 `m_panelDc` `BitBlt` 抓像素、`AddOccluder` 挖洞、按 `m_panelOrder` 逐层设文本层号
—— 全是 `PlotView` 内部协议。
⇒ 结论:**公共化方向是"派发点收敛",不是"搬进控件树"**。本轮照此执行。
### 12.3 做了什么(提交 `abcf3e6`、`34365b3`、`本提交`)
| 项 | 内容 | 效果 |
| --- | --- | --- |
| 面板特征层 | `pvdetail::PanelStyle` + `PanelStyleOf(kind)`、`PanelScrollMetricsFor(...)`、`PlotView::PanelTitle(kind)`;宽度 / 布局 / 绘制三处调用点全部改读 | 分类 3 份 → 1 份;滚动几何 3 份 → 1 份 |
| 潜伏错误修复 | `EdgePanelWidth` 的标题链漏了 MARK/CLIP(按"边沿计数"量宽) | 标题与宽度从此同源 |
| 滚动收敛 | `SetLanePanelScroll` 唯一实现 + 7 个一行包装;滚轮/拖滚动条的 2 条 `switch` 链改直接调用 | 7 份 → 1 份,读写同源 |
| 滚动条绘制 | `paintPanelScrollBar(layout)`,3 处复用 | 3 份 → 1 份 |
| 整数格式化 | `Utf8Util::FormatInteger*` 替代 76 处 `std::to_wstring` + 2 处 `std::to_string` | **−1,024 B** |
| 审查清理 | 删 `FreeMarkerValue` 未使用形参与 `(void)lane;`;`clipReady` 3 处判定收敛为 1 处 | 减少分歧点 |
### 12.4 ⚠️ 一条建议的最终结论:gdtoa(≈33 KB)**去不掉**
* 只替换整数转换:**−1,024 B**。
* 叠加 `-D__USE_MINGW_ANSI_STDIO=0`(`swprintf` 让给 UCRT):**0 字节**。
* 根因定位到行:libstdc++ 的
`.../include/c++/x86_64-w64-mingw32/bits/os_defines.h:48-51` **无条件**
`#undef __USE_MINGW_ANSI_STDIO` + `#define … 1`("libstdc++ depends on
POSIX-definitions of those functions")。`g++ -E -dM` 实测最终值恒为 1
⇒ **任何 C++ 翻译单元**的 `swprintf` / `strtod` 都是 MinGW 版本,命令行无法覆盖。
* ⇒ 要省这 33 KB 只能自己重写「浮点 → 十进制(正确舍入)」与浮点解析的特殊分支
(等价于重写 gdtoa)。**判定:不做**,已在 `Makefile` 留下结论与证据,避免重复尝试。
### 12.5 新增的护栏(本轮最重要的产出之一)
1. **`panelscroll` 检查**:7 种面板逐一"滚到底 / 滚回顶",验证夹取、滑块端点、内容区不动、
不可滚时无滚动条。用 `MF4-Record.MF4`(566 信号并入同一网格)实测 6/7 可滚
(自由标记无固定点,不可滚是正确行为)。
2. **平行数组长度不变式**(`panels` + `panelscroll` 各一份):`EdgePanelLayout` 里
13 个按行平行的 `vector` 必须与行数一致;4 个「重采样」专属数组允许为空(非空则必须对齐)。
这条检查成本 O(1),却直接守住"某列读到别人矩形"这一整类错位缺陷。
3. **像素基线在真实事故中证明了价值**:本轮把 `hasButton` 映射到 `buildButton` 后,
「选区截取」底部按钮行高归零 ⇒ 面板高度 124 → 87 px,`panels -mode compare`
**当场报 MISMATCH**(另外 4 个面板的哈希也变了 —— 因为面板互相重叠,短掉的那块露出下层)。
如果没有这条基线,这个回归只会在用户那里以"截取面板底部怪怪的"形式出现。
### 12.6 体积与验证
| 项 | 结果 |
| --- | --- |
| Release 产物 | **786,944 B**(第 34 轮末 788,480 → −1,536;其中 −1,024 来自整数格式化,−512 来自面板收敛) |
| `make release` / `make -C Tools` | 零 error / 零 warning(全量重建) |
| `UnitTests` | `checks=260 failed=0 cases=19` |
| `OffscreenChecks -all` | 6 个检查全 PASS,`checks=108 failed=0` |
| `panels -mode compare` | 7 个面板像素哈希 + 几何**逐项不变** |
### 12.7 后续建议(按性价比)
1. **`EdgePanelLayout` 的平行数组合并为 `std::vector<PanelRow>`**(314 处引用)。
这是"改布局就出错"的**最后一块机制性隐患**:每个分支必须按同一顺序 push 恰好 13 项。
本轮先补了长度一致检查作为护栏;真正的合并仍需一次专门的重构窗口。
2. **`PaintEdgeCursor` 拆分**(约 1600 行):把三段内容绘制(表格 / 列表 / 自由标记)
机械提取为 3 个成员函数,判据 = `panels -mode compare` 全绿。
3. **`EdgeScanFor` 状态查找**:维持线性扫描(第 34 轮实测 198 状态 0.097 µs/次,
哈希化需 +5,632 B,不划算)。