收尾:LanePanelState 抽取 + 错误提示非模态化(清空上一轮的“未完成”)

一、Lane 的内聚性(上一轮建议 5,本轮完成)
- 新增 struct LanePanelState { bool open = true; double x = -1.0; double y = -1.0; };
- Lane 的 12 个平铺面板字段(edgePanelOpen/X/Y 等 × 4 类实验性功能)
  → 4 个聚合成员 edgePanel / edgeCenterPanel / meanFilterPanel / weakPeakPanel;
- 访问点全部改为 lane.edgePanel.open 形式(DataModelTab.cpp 快照拷贝、
  PlotViewInteraction.cpp 面板拖拽、PlotViewPaint.cpp 面板层序绘制);
- RebuildLanes 内部的 LaneCarry 同步收敛为 4 个 LanePanelState,
  12 行逐字段赋值简化为 4 行整体赋值。
- 教训:批量改名不能对“声明”和“使用”跑同一组替换对,
  否则声明行会变成 `bool edgePanel.open = true;` 这类非法代码。

二、错误提示与模态阻塞解耦(上一轮建议 1,本轮完成)
- Application 新增 SetModalErrorsEnabled(bool) / ModalErrorsEnabled(),
  成员 m_modalErrorsEnabled 默认 true(交互场景行为不变);
- Initialize 最先执行 SetModalErrorsEnabled(showCommand != SW_HIDE):
  隐藏窗口(自动化/离屏)下没人能点掉模态框,等于把进程钉死;
- SetStatusMessage(text, isError=true):允许模态时原样 MessageBoxW,
  否则改走非模态通道 m_plot.ShowPersistentHint(text)(绘图区底部浮层);
  普通提示与空文本依旧被忽略;
- PlotView 新增公开接口 ShowPersistentHint(text)(内部转 SetHintSticky),
  不向宿主暴露粘滞/淡出等内部细节。

三、顺带修掉一个真实缺陷
- PlotView::Paint 在 m_lanes.empty() 时提前 return,而提示浮层在其后的
  叠加层绘制 ⇒ 一个信号都没勾选时任何错误都显示不出来,恰恰是
  “文件加载失败”最需要提示的场景;该分支补一次 PaintHint(g)。

四、验证(全部离屏:SW_HIDE + 不注入输入 + BELOW_NORMAL)
- make release / make debug:通过,零警告;Build/Release/Target.exe = 764416 B;
- verify 49/49、datacheck 69/69、edgecheck 46/46、panelanim 156/156;
- 新增探针 .preflight/perf/errcheck 19/19:默认值 / 隐藏窗口自动降级 / 宿主显式开关;
  工作线程 + 5s 超时判定“不阻塞”(旧行为会永久阻塞,判失败并 ExitProcess,
  探针不会挂住);EnumWindows 统计本进程 #32770 顶层窗口数量前后不变;
  错误文本进入 m_hintText / m_hintSticky / HintNeedsAnimation / m_hintShownAt;
  像素级:有提示帧与无提示帧在绘图区内的差异像素 3781,且全帧差异同为 3781
  (证明差异只来自提示)。

五、文档
- Documents/agent_mem.md:§9.37 新增第八节(本轮补齐)、第九节(仅剩 MF4 项)、
  §11.2 新增第 93–95 条硬约束、变更日志新增一行;
- Documents/refactor_report_2026-10-16.md:§7.3 / §7.4 标记完成,新增 §九 收尾补记
  (含 9.1–9.5:聚合化、解耦、探针、验证结果、变更清单)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-16 04:38:12 +08:00
1 parent e6b7bf66ec
commit d2e6fdd085
11 files changed
+296 -132

No files matched your search

+13 -8
View File
@@ -509,6 +509,11 @@ namespace jdi
m_hintShownAt = (text.empty()) ? 0 : GetTickCount64();
}
void PlotView::ShowPersistentHint(const std::wstring& text)
{
SetHintSticky(text);
}
void PlotView::ReleaseHint()
{
// 粘滞结束:从这里开始重新计时(先停留再淡出)。
@@ -1125,11 +1130,11 @@ namespace jdi
lane.edgeValue = (yMin + yMax) * 0.5;
lane.edgeValueValid = true;
}
lane.edgePanelOpen = true;
lane.edgePanel.open = true;
}
else
{
lane.edgePanelOpen = false;
lane.edgePanel.open = false;
}
return true;
}
@@ -1177,11 +1182,11 @@ namespace jdi
}
}
}
lane.edgeCenterPanelOpen = true;
lane.edgeCenterPanel.open = true;
}
else
{
lane.edgeCenterPanelOpen = false;
lane.edgeCenterPanel.open = false;
}
// 关闭后若本标签页**已无任何预览曲线功能**,立即释放派生曲线数据(缓存 + 后台线程的
// thread_local 工作缓冲)—— 否则关掉面板后内存不会下降(用户报告)。
@@ -1271,11 +1276,11 @@ namespace jdi
}
}
}
lane.weakPeakPanelOpen = true;
lane.weakPeakPanel.open = true;
}
else
{
lane.weakPeakPanelOpen = false;
lane.weakPeakPanel.open = false;
}
// 关闭后若本标签页**已无任何预览曲线功能**,立即释放派生曲线数据(缓存 + 后台线程的
// thread_local 工作缓冲)—— 否则关掉面板后内存不会下降(用户报告)。
@@ -1360,11 +1365,11 @@ namespace jdi
}
}
}
lane.meanFilterPanelOpen = true;
lane.meanFilterPanel.open = true;
}
else
{
lane.meanFilterPanelOpen = false;
lane.meanFilterPanel.open = false;
}
// 关闭后若本标签页**已无任何预览曲线功能**,立即释放派生曲线数据(缓存 + 后台线程的
// thread_local 工作缓冲)—— 否则关掉面板后内存不会下降(用户报告)。
@@ -1282,21 +1282,21 @@ namespace jdi
switch (m_edgePanelDragKind)
{
case EdgePanelKind::CENTER:
lane.edgeCenterPanelX = left;
lane.edgeCenterPanelY = top;
lane.edgeCenterPanel.x = left;
lane.edgeCenterPanel.y = top;
break;
case EdgePanelKind::MEAN:
lane.meanFilterPanelX = left;
lane.meanFilterPanelY = top;
lane.meanFilterPanel.x = left;
lane.meanFilterPanel.y = top;
break;
case EdgePanelKind::PEAK:
lane.weakPeakPanelX = left;
lane.weakPeakPanelY = top;
lane.weakPeakPanel.x = left;
lane.weakPeakPanel.y = top;
break;
case EdgePanelKind::COUNT:
default:
lane.edgePanelX = left;
lane.edgePanelY = top;
lane.edgePanel.x = left;
lane.edgePanel.y = top;
break;
}
m_dragMoved = true;
+24 -21
View File
@@ -693,6 +693,9 @@ namespace jdi
{
InvalidateSceneCache();
PaintEmptyState(g);
// ⚠️ 空状态(尚未勾选任何信号)也必须绘制提示浮层:加载失败 / 解析异常正是
// **最需要**非模态提示的场景,若在此提前返回,宿主的错误信息将永远不可见。
PaintHint(g);
return;
}
@@ -4667,9 +4670,9 @@ namespace jdi
const bool enabled = meanFilter ? lane.meanFilter
: (weakPeak ? lane.weakPeak
: (centerAxis ? lane.edgeCenter : lane.edgeCount));
const bool open = meanFilter ? lane.meanFilterPanelOpen
: (weakPeak ? lane.weakPeakPanelOpen
: (centerAxis ? lane.edgeCenterPanelOpen : lane.edgePanelOpen));
const bool open = meanFilter ? lane.meanFilterPanel.open
: (weakPeak ? lane.weakPeakPanel.open
: (centerAxis ? lane.edgeCenterPanel.open : lane.edgePanel.open));
if (!enabled || !open)
{
return layout;
@@ -4748,12 +4751,12 @@ namespace jdi
}
// 首次使用(位置为负)时自动放到绘图区右侧,并各网格依次错开,避免完全重叠。
const double storedX = meanFilter ? lane.meanFilterPanelX
: (weakPeak ? lane.weakPeakPanelX
: (centerAxis ? lane.edgeCenterPanelX : lane.edgePanelX));
const double storedY = meanFilter ? lane.meanFilterPanelY
: (weakPeak ? lane.weakPeakPanelY
: (centerAxis ? lane.edgeCenterPanelY : lane.edgePanelY));
const double storedX = meanFilter ? lane.meanFilterPanel.x
: (weakPeak ? lane.weakPeakPanel.x
: (centerAxis ? lane.edgeCenterPanel.x : lane.edgePanel.x));
const double storedY = meanFilter ? lane.meanFilterPanel.y
: (weakPeak ? lane.weakPeakPanel.y
: (centerAxis ? lane.edgeCenterPanel.y : lane.edgePanel.y));
float left = (storedX >= 0.0)
? static_cast<float>(storedX)
: (m_plotRect.X + m_plotRect.Width - width - 16.0f * m_scale);
@@ -5320,18 +5323,18 @@ namespace jdi
const std::vector<Lane>& lanes = m_tab->Lanes();
for (const Lane& lane : lanes)
{
mixFloat(static_cast<float>(lane.edgePanelX));
mixFloat(static_cast<float>(lane.edgePanelY));
mixFloat(static_cast<float>(lane.edgeCenterPanelX));
mixFloat(static_cast<float>(lane.edgeCenterPanelY));
mixFloat(static_cast<float>(lane.meanFilterPanelX));
mixFloat(static_cast<float>(lane.meanFilterPanelY));
mixFloat(static_cast<float>(lane.weakPeakPanelX));
mixFloat(static_cast<float>(lane.weakPeakPanelY));
mix(lane.edgePanelOpen ? 1ull : 0ull);
mix(lane.edgeCenterPanelOpen ? 1ull : 0ull);
mix(lane.meanFilterPanelOpen ? 1ull : 0ull);
mix(lane.weakPeakPanelOpen ? 1ull : 0ull);
mixFloat(static_cast<float>(lane.edgePanel.x));
mixFloat(static_cast<float>(lane.edgePanel.y));
mixFloat(static_cast<float>(lane.edgeCenterPanel.x));
mixFloat(static_cast<float>(lane.edgeCenterPanel.y));
mixFloat(static_cast<float>(lane.meanFilterPanel.x));
mixFloat(static_cast<float>(lane.meanFilterPanel.y));
mixFloat(static_cast<float>(lane.weakPeakPanel.x));
mixFloat(static_cast<float>(lane.weakPeakPanel.y));
mix(lane.edgePanel.open ? 1ull : 0ull);
mix(lane.edgeCenterPanel.open ? 1ull : 0ull);
mix(lane.meanFilterPanel.open ? 1ull : 0ull);
mix(lane.weakPeakPanel.open ? 1ull : 0ull);
mix(lane.edgeValueValid ? 1ull : 0ull);
mixDouble(lane.edgeValue);
}