feat/dataset-detail-chart #5

Merged
gaozheng merged 74 commits from feat/dataset-detail-chart into main 2026-06-13 17:30:37 +08:00
1 changed files with 124 additions and 71 deletions
Showing only changes of commit 66869a1e2e - Show all commits

View File

@ -1,97 +1,150 @@
# 交接文档:数据集详情图表dataset-detail-chart
# 交接文档:数据集详情图表 + 全 App 网络层异步化
> 给下一个会话:读完本文件 + 文末"立即要做的事"即可无缝接手。日期 2026-06-11。
> 给下一个会话:读完本文件即可无缝接手。最后更新 2026-06-12。
> 分支 **`feat/dataset-detail-chart`**,领先 main **68 commits**,工作区干净,**测试 116/116 全绿**。
---
## 0. 一句话现状
geoproQt6/C++ 离线桌面客户端1:1 复刻赛盈地空 web已完成两大块
1. **数据集详情图表**(仅 ERT 反演 `dd_inversion_data`QwtPlot 落地,用户已验收)。
2. **全 App 网络层 100% 异步化**(详情/导航/登录/项目列表全异步,同步 `QEventLoop` 阻塞路径已彻底删除,无技术债)。
均**未合并入 main**,分支挂起等收尾。
---
## 1. 背景
geopro 是 **Qt6 / C++ 离线桌面客户端**,目标是**像素级 1:1 复刻** web 系统
`http://tenant.geomative.cn/#/projectSpace/datasetMange/datasetInfo`(赛盈地空)的「数据集详情」视图。
本轮专做 ERT 反演数据(`ddCode=dd_inversion_data`)的详情视图。
- geopro = **Qt6 / C++ 离线桌面客户端**,目标**像素级 1:1 复刻** web 系统 `http://tenant.geomative.cn/#/projectSpace/datasetMange/datasetInfo`(赛盈地空)。
- **硬约束**:禁 QWebEngine/Chromium离线。图表用本地 **QwtPlot轴/交互/图例)+ VTK 算法层(等值线几何)+ 连续/离散色阶**。验收 = **视觉等价 + 交互一致**(非字节级 diff
- **标准测试 ds**`id=1458990939709440`ddCode=dd_inversion_data
- 站点 baseUrl`http://tenant.geomative.cn/pop-api`。Auth header`geomativeauthorization: Geomative <token>`。
**硬约束**:禁止 QWebEngine/Chromium离线要求。图表用本地 **QwtPlot轴/交互/图例)+ VTK 算法层(等值线几何)+ 连续/离散色阶**,不是 web 渲染。验收标准是**视觉等价 + 交互一致**(非字节级像素 diff
---
**标准测试 ds**`id=1458990939709440`ddCode=dd_inversion_data。原版网格视图参考截图仓库根 `web-grid-E3b.png`(未入库,可重新用 Playwright 截)。
## 2. 技术栈与构建/测试
## 2. 目标
数据详情含两个页签,均已完成:
- **原数据**Plotly scattergl 风格散点(方形点/白描边/连续色阶/x 轴顶部)。
- **网格数据**:填充等值面 + 黑色等值线 + 沿线数值标注 + 不规则白边NaN 裁剪)+ 底部异常表/描述。
## 3. 技术栈与构建
- Qt6 Widgets**Qwt 6.2**(源码在 `external/qwt-src/`**用 `cmake/qwt.cmake` 以 CMake 构建**静态库,不要用 qmake——本机 VS2026 缺 vswhereqmake 不可用)。
- 关键qwt.cmake 必须 `target_compile_definitions(qwt PRIVATE QWT_MOC_INCLUDE=1)`,否则 61 个链接错误;需链接 Qt6 Widgets/Concurrent/PrintSupport/Svg。
- **VTK 9.6 仅用算法层**`vtkBandedPolyDataContourFilter` / `vtkStripper` / `vtkSplineFilter` / `vtkDataSetSurfaceFilter`),不做 VTK 渲染。
- CMakeVS 自带 4.2.3+ Ninja + MSVC 14.51GoogleTest/CTestvcpkg仅非 Qt 依赖)。
- Qt6 Widgets + **Qwt 6.2**(源码 `external/qwt-src/`**用 `cmake/qwt.cmake` 以 CMake 构建**静态库,**不要用 qmake**——本机 VS2026 缺 vswhere
- **VTK 9.6 仅算法层**`vtkBandedPolyDataContourFilter`/`vtkStripper`/`vtkSplineFilter`/`vtkDataSetSurfaceFilter`),不做 VTK 渲染。
- CMakeVS 自带)+ Ninja + MSVC 14.51GoogleTest/CTestvcpkg仅非 Qt 依赖)。
- **构建**`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-build.ps1`
- ⚠️ 若报 `LNK1104 无法打开 geopro_desktop.exe`,是 exe 正在运行:先 `Get-Process geopro_desktop | Stop-Process -Force` 再构建。
- **测试**`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-test.ps1`(当前 **75/75 绿**)。
- 单测过滤:`build/release/tests/geopro_tests.exe --gtest_filter=ContourBands.*`
- ⚠️ `LNK1104 无法打开 geopro_desktop.exe` = exe 在运行:先 `Get-Process geopro_desktop -ErrorAction SilentlyContinue | Stop-Process -Force` 再构建。
- ⚠️ **改头文件后偶发** ninja 增量陈旧导致链接报「符号在 main.cpp.obj 重复定义」:删 `build/release/src/app/CMakeFiles/geopro_desktop.dir/main.cpp.obj` 后重建。
- **测试**`powershell.exe -ExecutionPolicy Bypass -File scripts/dev-test.ps1`**只跑 ctest 不构建——必须先 dev-build**)。当前 **116/116 绿**
- 单测过滤:`build/release/tests/geopro_tests.exe --gtest_filter=ApiBatch.*`
- ⚠️ 直接跑 exe 时 4 个 `CrsTransform/VoxelRegister/Terrain` 失败是缺 `PROJ_DATA` 环境项;**ctestdev-test会注入环境、全绿**——以 dev-test 的 "X/X passed" 为准。
- **网络/署名**AuthLiveTest 联网真打站点;提交信息**全局禁用署名**(勿加 Co-Authored-By
## 4. 数据 API均经 Playwright 实测)
---
Auth header`geomativeauthorization: Geomative <token>`。ApiClient 把非 object 的 `data` 包成 `{"value": <data>}`
**原版也是分页懒加载**(客户端已对齐):
## 3. 第一块:数据集详情图表(已完成,仅 dd_inversion_data
| 页 | 接口 | 说明 |
|---|---|---|
| 原数据 | `lvl/colorGradation/getDetail`(type1) + `dd/ert/inversion/getErtRawDataScatterGraph/{id}` | 2 个请求,~0.8s |
| 网格数据 | `dd/ert/inversion/rows/{id}` + `lvl/colorGradation/getDetail`(type2) + `exception/queryException/{id}` | 3 个rows 服务端网格化**波动 14s** |
详情含两页签,均经用户逐项验收:
- **原数据**Plotly scattergl 风格散点(方形点/白描边/连续色阶/x 轴顶部)。
- **网格数据**:填充等值面(栅格 QImage+ 黑色等值线vtkStripper+vtkSplineFilter 样条平滑,**每条线只标一个**数值)+ NaN 白边裁剪 + 底部异常表/描述。
- `rows``x[nx]`、`y[ny]`、`v[ny][nx]`(电阻率,含 NaN=无数据)、`z[ny][nx]`(高程)、vmin/vmax。标准 ds 为 nx=300,ny=100。
- `colorGradation``properties.colorBar=[[值,"rgba()"],…]`type1 散点连续/type2 网格离散,~17 段)+ `lineConfig{showLines,color,lineType}` + `labelConfig{showLabels,color}`
- **配色机制(实测纠正过)**:不是 log**colorBar 值线性归一化到 [0,1] + 非均匀色阶停靠点 + 连续插值(散点)/离散分带(网格)**
数据 API均经 Playwright 实测):
| 页 | 接口 |
|---|---|
| 原数据 | `lvl/colorGradation/getDetail`(type1) + `dd/ert/inversion/getErtRawDataScatterGraph/{id}` |
| 网格 | `dd/ert/inversion/rows/{id}`(服务端网格化,**波动 14s**) + `colorGradation/getDetail`(type2) + `exception/queryException/{id}` |
## 5. 架构与关键文件
> ⚠️ 架构偏离:原 spec 定 QGraphicsView**实际落地用 QwtPlot**(见 `plans/2026-06-11-dataset-detail-chart-v2-qwt.md`,权威)。
数据流:`DatasetListPanel` 双击 → `DatasetDetailController.openDataset`(同步阻塞拉原数据 2 请求)→ `chartReady(ChartData)``DatasetDetailPanel`(多 ds 的 QTabWidget) → `DatasetDetailPage`(buildTabbedPanel 原数据/网格 两页签) → 两个 View。切到网格页签 → `gridDataNeeded` 冒泡 → `loadGridData`(懒加载 3 请求)→ `gridReady(GridData)``GridDataChartView.setGridData`
**策略分派已打通**(本次会话):控制器从硬编码 `dd_inversion_data` 改为走 `ChartStrategyRegistry`(在 **controller 层** `src/controller/IDatasetChartStrategy.hpp`,含 `hasGridPhase()`),未注册类型优雅降级「暂不支持」。`ErtInversionStrategy`app 层)已注册。**这是接其余 dd 类型的地基**
- `src/controller/DatasetDetailController.{hpp,cpp}` — openDataset / **loadGridData**懒加载busy_ 重入守卫 + catch(...) 防死锁。signal: chartReady/gridReady/loadFailed/focusRequested。
- `src/app/panels/chart/`
- `RawDataChartView.*` — 散点视图模板(白底浅 palette、QwtPlotGrid 网格线、过原点 QwtPlotMarker 零线、**QwtPlotRescaler 锁定 x:y 真实比尺(aspect=1, ref=xTop)**、**LivePanner**、x 轴在 **xTop**、独立 ColorBarWidget。析构 delete colorSvc_。
- `GridDataChartView.*` — 网格视图。x 轴在 **xBottom**。布局toolbar(固定) + **QScrollArea(setWidgetResizable)** 内含 **QSplitter(竖直)**{ chartArea(plot+colorBar, minH280) | 异常表/描述(minH160) }。→ 页签内滚动 + 可拖分割条。
- `ContourPlotItem.*`QwtPlotItem**填充走栅格**QImage(ARGB32, K=4 上采样, 双线性插值 + `colorAtDiscrete` 离散着色 + 任一邻格 NaN→像素透明=白边裁剪)draw 时 blit**等值线走矢量**`buildContourBands` 取 lines标注`resolveLineLevels` 采样吸附级别,**每条线只标一个**(弧长中点,旋转)。异常叠加(点/线/面)。
- `ScatterPlotItem.*` — 散点项xTop/yLeft7px 方块白描边连续配色n=min(x,y) 防越界)。
- `ColorMapService.*` — colorAtContinuous(线性插值)/colorAtDiscrete(阶梯)from core::ColorScale。
- `ColorBarWidget.*` — 独立色阶条,**居中约 74% 宽**(非满宽)、等宽色带、白底深字。
- `LivePanner.*` — canvas 事件过滤器:**左键实时平移**(连续平移两轴+replot) + **滚轮缩放**(以光标为中心,上滚=放大,**消费事件**避免冒泡触发外层滚动条)。取代了 QwtPlotPanner/QwtPlotMagnifier。
- `src/render/ContourBands.{hpp,cpp}``buildContourBands(Grid, ColorScale, ContourOptions{upsample=2,smooth=0.3,makeLines})``{bands, lines}`。VTK网格→上采样→平滑→toCellGrid(剔 NaN 格)→surface→banded→(lines: **vtkStripper 连段 + vtkSplineFilter 样条平滑**, 不再 DP 简化)。
- `src/data/dto/DatasetChartDto.*`、`src/data/api/ApiDatasetRepository.*` — JSON 解析v 行数校验 qWarning、markType 钳制)。
- `src/app/main.cpp` — 装配detailDock `setWidget(..., ads::CDockWidget::ForceNoScrollArea)`**禁 ADS 把标题/页签卷入整体滚动条**gridDataNeeded→loadGridData、gridReady→setGridData 接线。
---
## 6. 关键设计决策(及为什么
## 4. 第二块:全 App 网络层异步化(本次会话完成,无技术债)
- **真实比尺锁定**用户选定区别于原版的响应式填充QwtPlotRescaler aspect=1剖面呈真实"宽扁"。
- **填充用栅格而非多边形**300×100 网格 banded 多边形约 3 万个,逐帧+实时拖动会卡QImage 一次成图 + blit 流畅。
- **等值线样条平滑**banded 边是大量 2 点短线段→ vtkStripper 连接 + vtkSplineFilter 拟合平滑曲线(贴近原版圆滑)。
- **滚轮事件必须消费**:否则冒泡触发 ADS/外层滚动条。
- **页签内滚动**ForceNoScrollArea(dock) + 视图内 QScrollArea(裹 splitter),使标题/页签/工具条固定、仅内容区滚动。
- **同步阻塞是全 App 共性问题**ApiClient 用 QEventLoop 死等→每次请求冻 UI。已定**单独立项异步化**(见下)。
**动机**:原 `ApiClient``QEventLoop` 死等每个请求 → 全 App 冻 UI网格 rows 14s 最痛)。
## 7. 当前进度
**核心安全不变量spec §5.0,务必遵守)**「abort 后绝不回灌」靠三件套——
1. 每层 `aborted_` 入口守卫(`disconnect` 只是尽力而为,挡不住已入队的迟到信号);
2. 控制器**句柄身份比对**`if (load != current_) return;` 丢弃迟到信号);
3. **一律 `deleteLater`**,禁止同步 delete。
错误判定口径:业务 `code != 200 || !rawError.isEmpty()`HTTP 200 也可能 code=500
- 分支 **`feat/dataset-detail-chart`**,领先 main **37 commits**,工作区干净(除未入库的 `web-grid-E3b.png`)。
- 两个视图 + 交互 + 懒加载 + 布局**全部完成并经用户逐项验收通过**。
- **cpp-reviewer 审查已做**HIGH 全修(散点越界 / colorSvc 析构泄漏 / QwtPlot autoDelete 注释 / 控制器 catch(...) 防 busy 死锁)+ 值得的 MEDIUM/LOW清死代码、填充等比限幅、DTO 校验/枚举钳制、ContourLine.level 默认 NaN。提交 `78f96db`。75/75 测试绿。
**net 层原语**`src/net/`AUTOMOC 已 ON
- `IApiCall`/`ApiCall`:单请求句柄(包 QNetworkReply`finished(ApiResponse)` + `abort()`,自管理 deleteLater。
- `ApiBatch`**并发汇聚** N 个 IApiCall全成功 `succeeded(QList)` / 任一失败 **fail-fast** `failed(i,resp)`+abort 其余。
- `ApiChain`**串行依赖链**(上步结果喂下步工厂,工厂可抛转 failed。**契约:首个 step 工厂不得同步抛/同步 fire**(否则信号在调用方连接前丢失;生产路径都发异步请求,满足)。
- `ApiResponseParse::buildResponse`sync/async 共用解析(已无 sync 调用方,仅 ApiCall 用)。
- `ApiClient`:仅 `getAsync/postJsonAsync`**同步 `get/postJson`/`await`/QEventLoop 已删除**)。
## 8. 立即要做的事(接手第一步)
**data 层**
- 详情:`ChartLoad`/`GridLoad`(抽象基 + `ApiChartLoad`/`ApiGridLoad` 实现,包 ApiBatch + 注入 parse、`IAsyncDatasetRepository`、`ApiDatasetRepository.loadChartAsync/loadGridAsync`、`DatasetLoads.hpp`(ChartParts/GridParts)。
- 导航:`NavRequest`(单非模板句柄,`done(QVariant)`/`failed(QString)``ApiNavRequest` 包 IApiCall + 解析器)、`NavLoads.hpp`(各类型 `Q_DECLARE_METATYPE`)、`IAsyncProjectRepository`9 方法 `...Async` 后缀,返回 `NavRequest*`,薄封装;汇聚/链编排放控制器)、`ApiProjectRepository` 仅实现异步接口。
**收尾本分支**——已问用户"如何收尾"4 选项待其回答:①合并回 main(本地) ②推送建 PR(origin=gitea) ③保持现状 ④丢弃。
合并/PR 前先清掉未入库的 `web-grid-E3b.png`(保持工作区干净)。**等用户给出选择后执行**(用 superpowers:finishing-a-development-branch
**controller 层**
- `DatasetDetailController`abort-and-replace + 句柄身份比对 + `loadStarted(dsId,Phase)` + 析构 abort`ChartStrategyRegistry` 分派。
- `WorkbenchNavController`全异步NavRequest 续延依赖链 + 控制器内并发计数 + abort-and-replace + 身份比对);**删除 `busy_`/`BusyGuard`/`drainPendingCheckedTms`**`busyChanged(bool)` 语义改为「有在飞句柄」(去抖);`setCheckedTms` 入口去重 + 「最后一次为准」由 abort-and-replace 承接 + `tmExceptionCache_` 缓存命中不发请求。对外信号面**零改动**main.cpp 接线没动)。
## 9. 后续计划(本分支之后)
**app 层**
- `AuthService`net 层):`fetchCaptchaAsync()→CaptchaLoad`、`loginAsync()→LoginLoad`(内用 ApiChainverifyCodeCheck→RSA(step2 工厂内)→login2。`LoginWindow`:不冻 + 可取消(析构 abort`repaint()` hack。
- `ProjectListDialog`:改用 `IAsyncProjectRepository`NavRequest + abort-and-replace + 身份比对 + 析构 abort过滤/分页/选项目行为等价。
- `LoadingOverlay`:网格懒加载「加载中」遮罩,接 `loadStarted`、就绪/失败隐藏。
- `main.cpp``qRegisterMetaType<ApiResponse>()`;装配注入 registry/异步 repo引用绑定接线信号面不变
1. **工具条编辑类功能**(网格页占位按钮):白化 / 滤波处理 / 色阶配置 / 异常框注 / 自动标注 / 网格 / 另存为 / 导出。交互较重,建议先 brainstorm 拆解。
2. **ApiClient 异步化(已单独立项)**:把全局同步阻塞 ApiClient 改异步QNetworkReply 信号,去 QEventLoop+ Repository/控制器信号化→全 App 不冻 UI、可并发。架构改动建议单独 spec+plan。
3. **更广项目**:数据详情只是一个特性;原型/`D:\Projects\GEOPRO\Geopro3.0 菜单.xlsx`(客户端 tab) 里还有其他数据类型、三维视图、其他菜单。
**执行方式**:全程 subagent-driven每块 implementer + spec 符合度评审 + 代码质量评审 opus + follow-up 加固)。测试 75 → 116+41含 abort 回灌防护回归用例)。
## 10. 相关文档与记忆
---
- 设计/计划:`docs/superpowers/specs/2026-06-11-dataset-detail-view-design.md`、`docs/superpowers/plans/2026-06-11-dataset-detail-chart-v2-qwt.md`v2 QwtPlot 返工方案,权威)、`docs/superpowers/plans/2026-06-11-dataset-detail-chart.md`。
- 外部返工方案:`docs/Geopro3.0_二维图表返工技术方案.md`。
## 5. 关键文件地图
- net`src/net/{IApiCall.hpp,ApiCall.*,ApiBatch.*,ApiChain.*,ApiResponseParse.*,ApiClient.*,AuthService.*,AuthLoads.*}`
- data`src/data/api/{DatasetLoads.hpp,DatasetLoadHandles.*,ApiDatasetRepository.*,NavRequest.*,NavLoads.hpp,ApiProjectRepository.*,DatasetChartDto.*}`、`src/data/repo/{IAsyncDatasetRepository.hpp,IAsyncProjectRepository.hpp,IDatasetRepository.hpp(本地样例同步,保留),RepoTypes.hpp}`
- controller`src/controller/{DatasetDetailController.*,WorkbenchNavController.*,IDatasetChartStrategy.hpp}`
- app`src/app/main.cpp`、`src/app/panels/{DatasetDetailPanel.*,DatasetDetailPage.*,LoadingOverlay.*,chart/*}`、`src/app/login/LoginWindow.*`、`src/app/ProjectListDialog.*`、`src/app/panels/chart/ErtInversionStrategy.hpp`
- 测试:`tests/net/{FakeApiCall.hpp,test_api_batch.cpp,test_api_chain.cpp,test_auth.cpp(live),test_auth_loads.cpp}`、`tests/data/{test_nav_request.cpp,test_dataset_load_handles.cpp}`、`tests/controller/{test_dataset_detail_controller.cpp,test_workbench_nav_controller.cpp}`、`tests/app/test_chart_strategy_registry.cpp`
---
## 6. 尚未完成 / 下一步(按优先级)
### A. 收尾本分支(最先)
分支领先 main 68 commits、116/116 绿、工作区干净。用户多次选「保持现状」未合并。下一步可问用户:①合并回 main(本地) ②推送建 PR(origin=gitea `https://gitea.geomative.cn/gaozheng/geopro.git`) ③保持现状。用 `superpowers:finishing-a-development-branch`
### B. 其余 dd 类型详情图(计划已写,多数 BLOCKED
计划:`plans/2026-06-11-dataset-detail-other-dd-types.md`。
- **Phase 1策略分派打通✅ 已完成**(本次会话)。
- **Phase 0样本探查需做**——用 Playwright 登录原版 web对每类 dd 找有数据对象、抓真实响应存 fixtures + 渲染规格。**这是后续所有类型的前置。**
- **Phase 2ERT 测量散点)/ Phase 3TEM 折线,新 LineChartView**:仅当 Phase 0 确认有样本才解锁。
- **Phase 4BLOCKED**——dd_grid/轨迹/测井/GPR**当前租户无活数据样本**GPR 对象无数据、无测井数据),按 1:1 复刻铁律不能凭空实现。
- 矩阵详见计划文件。
### C. 工具条编辑类功能(网格页占位按钮,未做,单独立项)
白化 / 滤波处理 / 色阶配置 / 异常框注 / 自动标注 / 网格化 / 另存为 / 导出 / 描述富文本 / 大视图(Esc)全屏。当前是占位按钮。交互较重,建议先 brainstorm 拆解。spec §2.3 列为范围外。
### D. 纯整洁 follow-up非债、非阻断
- 删 `DatasetDetailController::ChartData.grid/gridScale` 死字段(从未填充,预存)。
- 若未来引入 cross-thread/QueuedConnection再补 `qRegisterMetaType<QList<ApiResponse>>()`
---
## 7. 相关文档
- **spec**`docs/superpowers/specs/2026-06-11-apiclient-async-design.md`(异步设计 + §5.0 安全不变量 + §7 错误判定/退出契约 + 顶部「状态更新」=已完成)、`docs/superpowers/specs/2026-06-11-dataset-detail-view-design.md`(详情视图,顶部状态=仅 ERT 反演完成 + QwtPlot 偏离)。
- **plan**`docs/superpowers/plans/2026-06-11-apiclient-async-datasetdetail.md`(详情试点)、`2026-06-11-apiclient-async-rollout.md`(导航 Part A + 登录 Part B均落地、`2026-06-11-dataset-detail-other-dd-types.md`(其余 dd 类型Phase 1 done、`2026-06-11-dataset-detail-chart-v2-qwt.md`QwtPlot 返工,权威)。
- API 文档:`docs/apis`business_OpenAPI.json
- **记忆(务必遵守)**`~/.claude/projects/D--Git-lanbingtech-geopro/memory/`
- 外部方案:`docs/Geopro3.0_二维图表返工技术方案.md`。
---
## 8. 记忆(务必遵守,`~/.claude/projects/D--Git-lanbingtech-geopro/memory/`
- `reply-in-chinese` — 全程中文回复。
- `study-original-via-playwright`**任何不确定必须用 Playwright 实地看原版,禁止联想猜测**
- `no-embellishment-replicate-exactly` — 严格 1:1不加原版没有的特性曾因等值线"周期重复标注"被纠正,原版每条线只标一个)。
- `study-original-via-playwright`**任何不确定必须用 Playwright 实地看原版,禁止臆测**(尤其做新 dd 类型详情图前抓样本/规格)。
- `no-embellishment-replicate-exactly` — 严格 1:1不加原版没有的特性曾因等值线「周期重复标注」被纠正
---
## 9. 工作方式备注(本次会话沿用,效果好)
- 用户偏好 **subagent-driven** 执行 + 实现后 **code review + spec 符合度** 双评审opus 评审);「非必要不停,一口气做完」。
- **真并行构建在本项目不安全**dev-build 硬编码单一 build 目录 + 多任务共改 main.cpp/CMakeLists→ 用顺序执行worktree 隔离不了构建(硬编码路径 + 冷配置开销)。
- 破坏性接口改形需**原子落地**(同批提交)保持构建绿(详情试点 Task5+6 合并、Part A A4+A5、本次清债 都是此教训)。
- 评审 follow-up 多为小加固直接在新代码上补「不为不可能的场景写错误处理」CLAUDE.md §2——评审若建议为构造保证不会发生的情况加防御可不采纳。
</content>