What to build: 接入地图摘要分页契约,渐进加载监测点和未关联设备。同一批加载使用相同快照标记,刷新时切换到新快照。
Blocked by: 05
Status: resolved Labels: ready-for-agent
已完成并关闭(2026-09-18)。 逻辑闸门 44/44、真机走查 16/16,回归 t01/t03/t04/t05(179/179)/t06/t21/t28(7/7)/t29/t30 全部通过、t05 走查 10/10。
领域层(domain/mapSummary.js 追加,纯函数 + 注入取数,可脱浏览器断言)
MAP_SUMMARY_PHASE 六态:idle / loading / complete / empty / failed / expired——验收第 5 条的四态全部可演示,EMPTY(后端说总数 0)与 COMPLETE(确实加载完)严格分开。mergeMapPage(prev, page):跨页合并的核心。三条语义照抄后端实现(CockpitMapSnapshotService)而不是演示页的乐观假设:分页窗口是「监测点 + 未关联设备」一条总序列(先排满监测点再排设备,跨页横跨两类);按 kind+code 去重(重放/重叠切片不得在地图上画两遍,不同 kind 同名编码不合并、无编码脏对象不吞掉);prev = null 即换快照,旧对象不残留。createMapSummaryLoader({ fetchPage, pageSize }):渐进加载状态机。第 1 页不带 snapshotAt、第 2 页起带第 1 页锁定的标记(后端 validatePagination 的硬要求);响应快照与锁定值不一致立即进 expired,绝不把两个快照的点拼在一张图上;失败保持 page 不动 = 重试同一页不跳页;完成态是终态;取数函数抛错收敛成 failed 不卡 loading。summaryView(state):六态 → 中文文案 + 色键,内部原因码不上界面(票 05 同一条纪律)。toMapBatch 补 objects 字段:批次对外就是一条「监测点在前、设备在后」的合并序列,地图不必各写一套分支。服务层(services/cockpitApi.js、services/mapApi.js 不动语义)
REQUEST_REASON.SNAPSHOT_EXPIRED(snapshot-expired):后端把快照失效放在 400 响应体 AjaxResult.data.category(CockpitErrorCategory 大写枚举),不在 HTTP 状态码里——只看状态码会把它和「分页参数不合法」混成一个原因。readBusinessReason() 做枚举→词汇映射,只认白名单这一个取值,其它(含 INVALID_REQUEST)一律回笼统 unavailable。stores/mapStore.js 一处:fetchMapSnapshot 回的是票 05 的 section()(没有 ok 字段),装载器吃 {ok, data, reason}——直接塞会恒判失败(逻辑闸门第一轮抓到的接口形状坑),翻译时 success/empty → ok:true,其余失败原因原样上提。store 与界面
stores/mapStore.js(ADR 0004 的 mapStore):装载器唯一实例。对外只有 advance()(取下一批)/ refresh()(换新快照、从第 1 页重来)/ view(文案色键)/ settled(完成或空)——组件与页面不出现 page/pageSize/snapshotAt(静态断言守着)。演示页大小取 2(mock 三页翻完 5 个对象,渐进可感)。CockpitShell.vue:筛选条右区新增「地图摘要」指示器——实时文案(加载中·第 N 页·已上图 M 个 / 加载完成·N/M / 快照已失效·请刷新…)+ 一个动作按钮(未完 = 「加载下一页」,完成 = 「刷新快照」),data-map-summary-phase 供走查断言。MapCanvas.vue:装载状态写进宿主 dataset(data-map-summary[-loaded|-total|-snapshot|-page]),底部状态灯随态变色(加载中橙呼吸/完成绿/失败失效红);DEV 把手新增 __cockpitMap.mapSummary.{advance,refresh,getState} 供人工演示与走查驱动。detailStore.js:地图摘要分区改由 mapStore 供货(同一端点只拉一份、只持一个快照)。分区映射刻意不把「还没翻完」写成 STALE——票 16 词汇里 STALE 是「有值但过期」,分页未完的数据是新鲜的,说成"已过期"是对值班人员撒谎;取到一部分就是 SUCCESS,「翻到第几页」由地图摘要指示器与地图点位数体现。真异常三种如实报:FAILED / STALE+retained(快照作废)/ EMPTY。mock(mocks/contractMocks.js、mocks/index.js)
/map-snapshot 从「只给一页假分页」改成按后端真实语义分页:窗口横跨两类对象、第 2 页起不带快照或快照不一致抛 SNAPSHOT_EXPIRED、complete 只在末尾为真。走查开关新增 window.__COCKPIT_MOCK_MAP__ = { pageSize, snapshotAt, expireAfterPage }——expireAfterPage: 1 即演示「取完第 1 页后后端换快照」。complete:false),并补「第 3 页带着第 1 页快照请求、结果一致且 complete」的断言。node .scratch/cockpit-shell-panel-layout/verify/t08-map-summary.mjs → 44/44。八段:A 批次合并(跨页拼接/去重/跨 kind 不合并/换快照清空/长跑护栏 maxBatches+maxObjects);B 演示态词(六态中文文案、零原因码泄漏);C 装载器状态机(同批同快照、渐进、完成即终态、刷新换快照、过期不拼接、失败重试同页、空快照、参数由调用方给);D 适配层(查询串带 page/pageSize/snapshotAt、400 里的 SNAPSHOT_EXPIRED 与 INVALID_REQUEST 分别归类);E 取数抛错收敛;F mock 真实分页(切片、跨两类、缺快照即失效);G 装载器→store→分区状态(部分翻完 = success 而非 stale、未取到不改写分区、刷新回第 1 页、过期保留已上图);H 静态守卫(8 个界面文件零分页/快照参数、参数只在 mapStore 适配处拼、外壳不直接翻页)。node .scratch/cockpit-shell-panel-layout/verify/t08-map-summary-walkthrough.mjs → 16/16(yarn dev @ localhost:8000,服务由维护者开启)。逐条:首屏引擎 ready + 第 1 批已在图上且 loaded < total(框架先于全量);逐批推进 2→4→5、终态 complete、批次数 3;整个加载过程快照标记唯一;完成后按钮切「刷新快照」;刷新回第 1 页(loaded 5→2,旧快照的点不残留);失败场景分区板 failed 而地图宿主与引擎保留;快照过期场景显示「已失效·请刷新」且保留已上图 2 个;空快照显示「没有可上图对象」不误报失败;1680/1280 两种宽度下筛选条零横向溢出、零页面级报错。check() 签名多了一个 desc 参数,调用点却是三参——证据字符串被当成"通过"吃掉,所有断言恒真。修签名后立刻暴露三个真失败(起点判定、按钮态时序、刷新等待),逐个修脚本并复核产品行为后才得真 16/16。教训:走查脚本也要对得了账,绿不够,得知道绿的是什么。STALE,走查与复盘发现这既违反票 16 的词汇(STALE=有值但过期),又与地图上的真实进度自相矛盾。改为部分翻完 = SUCCESS,进度归地图摘要指示器。__scope-left/right 补 min-width:0、摘要块与分区板显式 flex-shrink:1,走查守卫 F1 实测两种宽度 scopeOverflow: 0。% 格式串吞证据:模板串里的 % 走了 console 格式化分支,证据被吞——改用字符串拼接输出。coordinate 字段已随对象带出,summarizeCoordinateValidation 即其计数依据)。refresh() 是它可复用的入口。mapStore.MAP_SUMMARY_PAGE_SIZE 一处可调)。