把三处功能要求也变成可复跑的检查(structcheck 93 条 + A/B 反证)

背景
----
「点组名不折起」「组名改名框不吞项数」「SignalData 去掉 displayName」
三处用户要求此前只有散文描述与已压缩的历史记录。本提交给它们补上
(a) 源码级断言、(b) A/B 反证、(c) 运行时断言三类证据。

新增 / 修改
-----------
- Tools/structcheck.ps1 新增第 [3] 组「行为与注释不变量」(31 条断言):
  * 折起热区:RowHitPart 产出专用 RowPart::GROUP_TITLE,其矩形正好是「组N」标签矩形;
    单击门限 `if (part != RowPart::DISCLOSURE && part != RowPart::GROUP_TITLE)` 全文件恰好 1 处;
    ToggleGroupFromRow 恰好 2 个入口(单击门限 + 双击折叠三角);
    双击非名称本体(`if (part != RowPart::BODY) return false;`)不进入改名。
  * 改名框:右边界必须是 `statsRight - statsWidth - 6*scale`,且全文件不得出现 `right = statsRight;`;
    项数宽度必须来自 GroupStatsText。
  * 数据模型:SignalData / SignalGroup 各只声明 1 个字符串字段且名为 name;
    RenameSignal / RenameGroup 存在;全仓 displayName / Title() / Set*DisplayName 残留 0 处。
  * 注释核验:过时措辞(文件下标 / 所属文件下标 / 原信号所属文件下标 / 文件分组 / 原文件下标 /
    文件序号)全仓 0 处;已修正注释确实存在(例如 DerivedSignalRequest::groupIndex →
    「原信号所属分组下标」)。
- Tools/structcheck_phrases.txt(新增,UTF-8):中文短语表,供 .ps1 以 UTF8Encoding($false)
  读入后逐条断言(forbid / require)。⚠️ 中文不能直接写进 .ps1 —— PowerShell 5.1 按 ANSI 读脚本。
- 断言总数 62 → 93;退出码仍为 0/1,可直接接 CI。

A/B 反证(判别力)
------------------
回退两处 UI 修复(`if (part != DISCLOSURE && part != GROUP_TITLE)` → `if (false)`;
`right = statsRight - statsWidth - 6*scale;` → `right = statsRight;`):
  * panelanim.exe   147/156(9 项如实失败)→ 恢复后 156/156
    失败文案直接点名需求:the group rename box stops before the item counter /
    clicking the group name does not start collapsing / does not collapse the group ...
  * structcheck     90/93(3 项如实失败)→ 恢复后 93/93
    single-click toggle gate accepts only DISCLOSURE / GROUP_TITLE (found 0) /
    rename box right edge excludes the item counter / rename box never extends over the counter

运行时断言(数据层,datacheck 69/69)
-------------------------------------
  Check(set.RenameSignal(groupIndex, 0, L"改名") && set.Resolve(SignalRef{0,0})->name == L"改名",
        "signal rename writes the single name field");
  Check(set.RenameGroup(groupIndex, L"我的组") && set.GroupAt(0)->name == L"我的组",
        "group rename writes the single name field");
  Check(!set.RenameSignal(groupIndex, 0, L"") && ..., "empty rename is rejected (name is never blanked)");
即重命名只写一个 name 字段(编译期已无 displayName 可写),空名被拒。

文档
----
- Documents/refactor_report_2026-10-16.md:新增 §9.7(三处功能要求的源码断言 + A/B + 运行时断言);
  §1.3 补完整 A/B 实测;§9.4 验证表与 §9.5 变更清单同步(62 → 93)。
- Documents/agent_mem.md:§9.37 开头补可复跑证据块;§11.2 第 96 条扩写为三组 + 中文短语数据文件的做法;
  变更日志 d2e6fdd 行的结构检查描述更新为 93 条与 A/B 数据。

验证
----
make release 零警告,Target.exe 764 416 B;
structcheck 93/93;verify 49/49;datacheck 69/69;edgecheck 46/46;
panelanim 156/156;errcheck 19/19。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-16 04:56:35 +08:00
1 parent 695b73a366
commit 9cf6b2fedc
4 files changed
+181 -13

No files matched your search

+63 -2
View File
@@ -44,6 +44,15 @@
但不失去判别力);**A/B**(把 `OnLButtonDown` 的热区判断改回“任意行内区域都折起”
+ 把 `RowNameRect` 右边界改回“吞掉项数”)→ **10 项如实失败**,恢复后全绿。
> ✅ **完整的 A/B 实测(后续补记,见 §9.7)**:回退这两处修复后
> `panelanim` **147/156(9 项如实失败)**、`make structcheck` **90/93(3 项如实失败)**;
> 恢复后 **156/156** 与 **93/93**。失败文案直接点名需求:
> `the group rename box stops before the item counter`、
> `clicking the group name does not start collapsing` / `does not collapse the group`、
> `single-click toggle gate accepts only DISCLOSURE / GROUP_TITLE (found 0)`。
> 这两条要求现在还被 **`make structcheck` 第 [3] 组**的源码级断言长期守着(见 §9.7),
> 不依赖人来重读散文。
---
## 二、数据模型去冗余(用户指出的重点)
@@ -400,7 +409,7 @@ struct LanePanelState
| `.preflight/perf/edgecheck`(边沿统计) | **46/46** |
| `.preflight/perf/panelanim`(分组折起动画 + 两处 UI 修复) | **156/156** |
| `.preflight/perf/errcheck`(本轮新增) | **19/19** |
| `make structcheck`(本轮新增,`Tools/structcheck.ps1`) | **62/62**(结构约束:拆分 + 共享控件 + 分层,见 §9.6) |
| `make structcheck`(本轮新增,`Tools/structcheck.ps1`) | **93/93**(结构约束 + 本轮行为/注释不变量,见 §9.6 / §9.7) |
| 上一遍的 A/B(回退两处 UI 修复) | 如实失败 **9** 项(失败文案逐条点名「点组名不应折起」「改名框不应包住条目计数」),恢复后 156/156 |
### 9.5 本轮变更清单
@@ -415,7 +424,8 @@ struct LanePanelState
修改 Components/PlotView/Src/PlotViewFeatures.cpp 实现 ShowPersistentHint
修改 Components/Application/Inc/Application.hpp SetModalErrorsEnabled / ModalErrorsEnabled / m_modalErrorsEnabled
修改 Components/Application/Src/Application.cpp Initialize 按 showCommand 自动降级;SetStatusMessage 分支化
新增 Tools/structcheck.ps1 结构检查(拆分 + 共享控件 + 分层),62 条断言
新增 Tools/structcheck.ps1 结构检查(拆分 + 共享控件 + 分层 + 本轮行为/注释不变量),93 条断言
新增 Tools/structcheck_phrases.txt 注释核验用中文短语表(UTF-8,供 .ps1 读取)
修改 Makefile 新增 structcheck 目标
新增 .preflight/perf/errcheck_main.cpp 模态/非模态解耦探针(gitignored,不随产物提交)
```
@@ -445,3 +455,54 @@ make structcheck # = powershell -File Tools/structcheck.ps1
> 不变量是「**`Shared` 不依赖任何组件、底层 `PaintUtil` 不依赖 `Shared`**」,而不是「`Utilities` 一律不得
> 依赖 `Shared`」——脚本按前者断言,避免把既有合理结构误判为违规(这一条是脚本第一版报出 13 个假
> 失败后修正的:最初用「所有 `Ui*` 函数」判定,把 `UiFillRect`/`UiText` 这类**底层原语**也算成了控件)。
### 9.7 三处功能要求也变成可复跑的检查(`structcheck` 第 [3] 组,总计 93 条)
本轮另外三处用户要求(不折起热区、改名框不吞项数、`SignalData` 去冗余 + 注释核验)此前只有
散文描述。现在它们同时有**源码级断言**与 **A/B 反证**:
**(a) 源码级断言(`Tools/structcheck.ps1` 第 [3] 组,31 条)**
| 断言 | 实测 |
| --- | --- |
| `RowHitPart` 产出专用热区 `RowPart::GROUP_TITLE`,且其矩形**正好**是「组N」标签矩形 | OK |
| 单击折起门限只接受 `DISCLOSURE` / `GROUP_TITLE`(全文件**恰好 1 处**) | OK |
| `ToggleGroupFromRow` 只有 **2 个入口**(单击门限 + 双击折叠三角) | OK |
| 双击**三角**仍折起;双击**非名称本体**一律 `return false`(不进入改名) | OK |
| 改名框右边界 = 项数左缘(`statsRight - statsWidth - 6*scale`),且**不存在** `right = statsRight;` | OK |
| `SignalData` / `SignalGroup` **各只声明 1 个字符串字段且名为 `name`** | 各 1 个 |
| `RenameSignal` / `RenameGroup` 存在;全仓 `displayName` / `Title()` / `Set*DisplayName` 残留 **0 处** | 0 |
| 过时措辞(`文件下标` / `所属文件下标` / `原信号所属文件下标` / `文件分组` / `原文件下标` / `文件序号`)**全仓 0 处** | 0 |
| 已修正注释**确实存在**,例如 `DerivedSignalRequest::groupIndex` → 「原信号所属分组下标」 | OK |
> ⚠️ 中文短语不能写在 `.ps1` 里(PowerShell 5.1 按 ANSI 读脚本),因此它们放在 UTF-8 数据文件
> `Tools/structcheck_phrases.txt` 中,由脚本以 `UTF8Encoding($false)` 读入后逐条断言(`forbid` / `require`)。
**(b) A/B 反证(判别力,两处 UI 修复)**
把两处修复回退(`if (part != DISCLOSURE && part != GROUP_TITLE)` → `if (false)`;
`right = statsRight - statsWidth - 6*scale;` → `right = statsRight;`)后:
| 验证手段 | 回退后 | 恢复后 |
| --- | --- | --- |
| `panelanim.exe`(真实 `Application` + 消息注入 + 逐像素断言) | **147/156,9 项如实失败** | **156/156** |
| `make structcheck` | **90/93,3 项如实失败** | **93/93** |
回退后 `panelanim` 报出的前两条失败文案**直接点名需求**:
`FAIL: the group rename box stops before the item counter`、
`FAIL: clicking the group name does not start collapsing` / `does not collapse the group`;
`structcheck` 报出的三条:`single-click toggle gate accepts only DISCLOSURE / GROUP_TITLE (found 0)`、
`rename box right edge excludes the item counter`、`rename box never extends over the counter`。
**(c) 运行时行为断言(数据层,`datacheck` 69/69)**
```
Check(set.RenameSignal(groupIndex, 0, L"改名") && set.Resolve(SignalRef{0, 0})->name == L"改名",
"signal rename writes the single name field");
Check(set.RenameGroup(groupIndex, L"我的组") && set.GroupAt(0)->name == L"我的组",
"group rename writes the single name field");
Check(!set.RenameSignal(groupIndex, 0, L"") && set.Resolve(SignalRef{0, 0})->name == L"改名",
"empty rename is rejected (name is never blanked)");
```
即:重命名**只写一个 `name` 字段**(编译期已无 `displayName` 可写),空名被拒。