docs(04-14): register UI adaptation implementation plan (step 2)

This commit is contained in:
unanmed 2026-09-30 16:10:47 +08:00
parent 425ff28297
commit cab6fe2e5a
4 changed files with 534 additions and 10 deletions

View File

@ -181,12 +181,13 @@ Plans:
3. 移动端(窄屏)布局下,同一场景正常显示且可操作
4. 数据端与渲染端保持双端分离——数据端无 DOM,仍可在 Node 环境跑回放验证
**Plans**: 12 已执行 + 1 已规划待执行(04-13) — 04-01..04-07 已执行(04-06 = material 接口影响范围清点·只读;04-07 = material 接口适应实施·第二步);04-08(TextureManager 新接口影响范围清点·只读,D-38/D-39/D-40/D-41/D-42 第一步)已执行;04-09(TextureManager 新接口适应实施·第二步·代码改动,D-43..D-50)已执行;04-10(渲染端结构性重构影响范围清点·只读,D-51..D-57 第一步)已执行;04-11(渲染端结构性重构·第二步·移植实施·代码改动,D-58..D-68)已执行;04-12(04-11 落地修正·桶导出边界 + 消费者直连 + shared 单文件·代码改动,D-69/D-70)已执行(异地执行,commit 81e4e98;`8b114c7 docs(04-12): sync tracking`);**04-13(部分 UI 的进一步接口适配·第一步·只读收集,D-71/D-72/D-73)已规划待执行**(执行前须 Task 0 汇报关卡等待用户「可以执行」);渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
**Plans**: 13 已执行 + 1 已规划待执行(04-14) — 04-01..04-07 已执行(04-06 = material 接口影响范围清点·只读;04-07 = material 接口适应实施·第二步);04-08(TextureManager 新接口影响范围清点·只读,D-38/D-39/D-40/D-41/D-42 第一步)已执行;04-09(TextureManager 新接口适应实施·第二步·代码改动,D-43..D-50)已执行;04-10(渲染端结构性重构影响范围清点·只读,D-51..D-57 第一步)已执行;04-11(渲染端结构性重构·第二步·移植实施·代码改动,D-58..D-68)已执行;04-12(04-11 落地修正·桶导出边界 + 消费者直连 + shared 单文件·代码改动,D-69/D-70)已执行(异地执行,commit 81e4e98;`8b114c7 docs(04-12): sync tracking`);**04-13(部分 UI 的进一步接口适配·第一步·只读收集,D-71/D-72/D-73)已执行**(交付 `04-UI-ADAPTATION-IMPACT.md`;A7/B5/C2/D5/E4/F5 + 8 行修改点汇总);**04-14(部分 UI 的进一步接口适配·第二步·适配实施,D-74..D-78)已规划待执行**(执行前须 Task 0 汇报关卡等待用户「可以执行」);渲染适配与移动端/桌面端双布局(REND-01/REND-02)整体仍未完成
**Plan register note (2026-09-21):** 第五个增量 `04-05`(收口 `#04-01-M-06` / `#04-01-M-09`:状态栏 9 个数值属性改经 `client.hero.attribute.getFinalAttribute(...)` 读取 + 逐图层钩子类 `RendererLayerHook`,2026-09-20 执行)此前未登记进本路线图,本次一并补登;第六个增量 `04-06` = material 接口适应(D-30/D-31/D-32)**第一步·只读影响清点**,产出 `04-MATERIAL-INTERFACE-IMPACT.md`,第二步(实施)待用户审阅后另行规划。第七个增量 `04-07` = material 接口适应实施(**第二步·代码改动**,D-33..D-37),2026-09-22 已执行(10 文件原子提交)。**第八个增量 `04-08` = TextureManager 新接口适应(D-38..D-42)第一步·只读影响清点**,产出 `04-TEXTURE-INTERFACE-IMPACT.md`;第二步(实施)待用户审阅后另行规划。
**Plan register note (2026-09-24):** 第十个增量 `04-10` = 渲染端结构性重构(用户裁定 2026-09-23 · D-51..D-57)**第一步·只读影响清点**:把 `packages-user/client-modules/src/render/` 下的通用内容(`render/components`、`render/elements`、`render/map` 三文件夹 + `render/utils/layout.ts`,规划日实测 **39** 文件)迁往渲染系统层 `packages-user/client-base/src/`(D-51/D-52;目标布局按 D-53:`client-base/src/{components,elements,map}/` + `client-base/src/layout/`,**不新建 `render/` 目录**);不移植 `render/utils/` 其余(`index.ts`/`saves.ts`/`use.ts`)与 `ui`/`fx`/`weather`/`render` 顶层文件(D-54);legacy 一律只报告(D-55);用户并发改动零触碰(D-56)。台账按 A 被移集合内部交叉引用 / B 外部导入点(含 `entry-client` / `entry-data` / `packages/legacy-ui` 经 `@user/client-modules` 公共面的间接依赖)/ C barrel 与导出面 / D 分层与循环依赖风险(`client-base` 不得反向依赖 `client-modules`,含 `package.json` 依赖现状)/ E legacy 命中(仅报告)/ F 未确定 六类分组,每行精确到文件或符号 + `file:line` 且 ≥2 锚点;产出 `04-STRUCTURE-MIGRATION-IMPACT.md`,生产代码零改动且**零命名变更**;**已规划待执行**,执行前须 Task 0 汇报关卡等待用户「可以执行」;第二步(移植实施)待用户审阅后另行规划;`REND-01` / `REND-02` 仍 Pending。
**Plan register note (2026-09-24 · 04-11 第二步):** 第十一个增量 `04-11` = 渲染端结构性重构**第二步·移植实施(代码改动)**(用户裁定 2026-09-24 · D-58..D-68)。**移植按 MOVE 落地**:把 D-52 的 **39** 文件(`render/components` 13 / `render/elements` 5 / `render/map` 20 / `render/utils/layout.ts` 1)按 D-53 放进 `packages-user/client-base/src/{components,elements,map}/` 与 `client-base/src/layout/layout.ts`(并新增 `layout/index.ts`,D-61),**并删除 `client-modules/src/render/` 下的原件**;D-58 另新建 `client-base/src/shared/`(从 `client-modules/src/shared.ts` 摘取被移 `map` 文件所需的 10 个常量,原文件保留);D-63 `client-base/src/index.ts` 新增 `./components` / `./elements` / `./map` / `./layout` / `./shared` 五条桶导出,实现层消费者(`render/ui` 8 文件 11 处含 `toolbar.tsx` 两处深层导入、`fallback/ui.ts:2`、`render/index.tsx:5,45,46`、`client.ts:25`、`types.ts:2`)改指 `@user/client-base`;被移 `map` 文件的 shared 说明符改指 4 处(D-58);D-59 / D-68 的架构耦合(`render/renderer` 3 处 + `render/use.ts` 5 处)与 D-60 的 `render/utils/` 断链(`index.ts:1` / `saves.ts:2`)共 **10** 处以带原因的 `// @ts-expect-error` 暂记,**不反向引用、不解耦**;D-66 `client-base/package.json` 新增 5 条 `workspace:*` 依赖(`@motajs/animate` / `@motajs/common` / `@motajs/render-vue` / `@motajs/system` / `@user/data-common`)并执行 `pnpm i`;D-64 `?raw` 不处理、D-65 legacy 一律不管、D-62 命名一律不变;门禁 = 目标落位 / 移动已发生 / 桶导出 / 依赖与 lockfile / 消费者改指 / 反向引用零命中 / `@ts-expect-error` 落点(10 处新增 + 既有 7 条保留)/ 39 文件允许差异 / 实现层差异 / 范围与用户改动 / 无 `import type` / CRLF 共 12 道 + 计划文件范围门禁 + 人工复核(**D-68 明示不要求可运行、不要求零类型错误,故不设 `check:type` / `build` / TS 诊断数类门禁**);`autonomous: false`(Task 0 汇报关卡);第二步的后续余留(自包引用未验证 / 两行同源星导出 / 8 处反向耦合接线 / D-60 断链)由用户接手;`REND-01` / `REND-02` 仍 Pending。
**Plan register note (2026-09-24 · 04-12 落地修正):** 第十二个增量 `04-12` = **04-11 落地修正·桶导出边界 + 消费者直连 + `shared` 单文件(代码改动)**(用户裁定 2026-09-24 · **D-69 / D-70**)。**D-69(桶导出边界原则,硬约束)**:一个 barrel(`index.ts` / `index.tsx`)**只允许 `export * from './<同目录下的文件或子文件夹>'`**、不得导出本文件夹之外的任何内容;反过来,任何需要被移内容的**消费端必须直接 `import ... from '@user/client-base'`**、**不得经任何 barrel 转发导出**。**D-70 的 6 条修正**:① `client-modules/src/render/utils/index.ts` **删除**指向 `./layout` 的越界转发 `export *` 及其 `@ts-expect-error`(保留 `./saves` / `./use`);② `render/ui/save.tsx` **拆分** `../utils` 导入(`adjustGrid` / `IGridLayoutData` → `@user/client-base`,`getSave` / `SaveData` 仍留 `../utils`)、`render/ui/title.tsx` 的 `adjustCover` → `@user/client-base`;③ `render/utils/saves.ts` 的 `getConfirm` / `waitbox` → `@user/client-base` 并**删除**其 `@ts-expect-error`;④ `client-base/src/shared/{shared.ts,index.ts}` **折成单文件** `client-base/src/shared.ts` 并删除 `shared/` 目录(`client-base/src/index.ts` 的 `export * from './shared'` 与 `map/*` 的 `'../shared'` / `'../../shared'` 说明符**不受影响**,无需改动);⑤ **逐字保留** `client-base/src/{components/{choices,input,misc,scroll,textboxTyper,tip}.tsx,elements/index.ts}` 内 **8** 处引用 `render/use.ts` / `render/renderer` 的架构耦合 `@ts-expect-error`(属 D-59 / D-67,留待用户收尾;`textboxTyper.ts` 另有 1 处既有 `无法推导` 标注同属保留,故这 7 文件合计 9 处);⑥ **不得**新增任何转发导出,`client-modules/src/render/index.tsx` **维持现状**(用户已删除越界两行,现合规)。旁注(仅报告):`packages/client/src/index.ts:1` 的 `export * from '@motajs/client-base'` 亦违反 D-69,但在 `packages/`(D-33 只报告)。改动面 = `client-base/src/shared.ts` + `client-modules/src/render/{utils/index.ts,utils/saves.ts,ui/save.tsx,ui/title.tsx}` 五个生产文件 + `files_deleted` 的 `client-base/src/shared/{shared.ts,index.ts}`;门禁 = 桶边界(8 个 in-scope barrel 的 `export * from` 全为 `./` 前缀 + `render/utils/index.ts` 不再转发 `./layout`)/ F1..F4 收口自检 / F5 的 8 处标注逐字保留 + porcelain 零命中 / 范围与用户改动 / CRLF + 人工复核(**不设 `check:type` / `build` / TS 诊断数类门禁**,D-68 的延续);`autonomous: false`(Task 0 汇报关卡等待用户「可以执行」);无依赖变更、不执行 `pnpm i`;本步不引入任何新命名;`REND-01` / `REND-02` 仍 Pending。
**Plan register note (2026-09-30 · 04-13 第一步):** 第十三个增量 `04-13` = **部分 UI 的进一步接口适配·第一步·只读收集需要修改的点(只读)**(用户裁定 2026-09-25 · **D-71 / D-72 / D-73**)。**要解决的问题**:数据端经 `d8fa4b8 refactor: Delete global loading & hook object` 与加载系统重构后,渲染端部分 UI 仍以旧接口编写——① 全局 `loading` / `hook` 对象已删除,组件不得再依赖它们监听全局钩子、**必须在对应对象上监听**(残留:`render/ui/main.tsx:146-151` 的 `hook.on('statusBarUpdate', ...)`、`render/index.tsx:35-38` 的 `hook.on('restart', ...)`,及 `main.tsx:140-143` 的手工单次调用);② 加载系统重构,旧加载 UI(`render/ui/load.tsx` 的 `LoadScene` / `LoadSceneUI`)已不能用,仍调用 `loader.initSystemLoadTask()` / `loader.load()` / `loader.progress`(`:71,72,76-79,91-92,118-119,139`),须重新适配新加载系统(`@motajs/loader` 的 `LoadManager` + `packages-user/data-state/src/loader/**` 的 `MotaDataLoader` / `IMotaDataLoader`)。**D-72**:本目标至少两个计划——第一个只读收集(生产代码零改动),第二个及后续才执行;第一个计划的产出是后续执行计划的输入。**D-73**:沿用 D-33 / D-42 / D-48 / D-49 / D-55 / D-65 / D-56 / D-68,**D-69 桶导出边界继续适用**。**交付物** = `.planning/phases/04-render-adaptation/04-UI-ADAPTATION-IMPACT.md`(只读影响台账):元信息块 + `只读起始基线` / `背景` / `方法` / `基准变更(d8fa4b8)` / A 全局 loading / hook 使用点(UI 面)/ B 目标对象与钩子 API 证据 / C 旧加载 UI 清单与新加载系统 / D 分层与 barrel(D-69)检查 / E legacy 命中(仅报告)/ F 未能从阅读确定(未猜测)/ 修改点汇总(供后续执行计划消费)/ 处置;A–E 固定列序表 `| ID | 项目 | 命中处 file:line | 目标锚点 / 证据 file:line | 问题描述 | 影响 | 置信 |`,事实行每行 ≥2 个 `file:line` 锚点、证据行带 `[证据行]` 标记;每个 A 点给出「应改监听 / 使用的对象」+ 现行源码证据,或显式 undetermined(进 F 类)。**边界**:被查 = 渲染端部分 UI(`client-modules/src/render/ui/**` + `render/index.tsx`);`client.ts` / `client-modules/src/index.ts` / `entry-client/src/create.ts` 的 `loading` / `hook` 使用点**只报告**(非 UI,是否纳入由用户裁定);`packages/` 与 `src/` 仅报告(D-33)。**门禁**:只读基线逐条一致 + 台账结构 / 内容 / 证据纪律(≥2 锚点)+ 范围与用户改动 + 既有 `04-01`..`04-12` 产物保护 + CRLF + 人工复核(**不设 `check:type` / `build` / TS 诊断数类门禁**,D-68 的延续);`autonomous: false`(Task 0 汇报关卡等待用户「可以执行」);**本步为只读、不引入任何命名变更**;`REND-01` / `REND-02` 仍 Pending。
**Plan register note (2026-09-30 · 04-14 第二步):** 第十四个增量 `04-14` = **部分 UI 的进一步接口适配·第二步·适配实施(代码改动)**(用户裁定 2026-09-30 · **D-74 / D-75 / D-76 / D-77 / D-78**)。**要解决的问题**:`04-13` 只读台账确认,数据端 `d8fa4b8 refactor: Delete global loading & hook object` 与加载系统重构后,渲染端部分 UI 仍以已删全局 `loading` / `hook` 与旧加载接口编写。**D-74..D-78 逐条落地**:① **D-78(tracer)** `render/ui/main.tsx` 删除全局 `hook` 状态栏监听残留(`:145-152`)与手工单次调用垫片(`:140-143`),改为在**勇士属性对象** `client.hero.attribute` 上 `addHook({ onUpdateAttribute })` 并在 `onUnmounted` 注销(接口 `packages-user/data-base/src/hero/types.ts:95-106`;触发点 `packages-user/data-base/src/hero/attribute.ts:89`),新增局部常量 `attributeHook`;② **D-74 / D-75** `render/ui/load.tsx` 以 `client.loader.start()`(`AsyncIterable`)取代旧「系统加载任务初始化 + 加载 + `progress` 迭代」三件套,进度 / 字节 / 任务读数一律取自 `client.loader.manager`(`ILoadManager`)的 `getLoadedTasks` / `getAddedTasks` / `getTotalByte` / `getLoadedByte` / `getByteRatio`,并移除 `:75` 失效 `@ts-expect-error`;③ **D-76** 删除 `render/index.tsx:35-38` 被注释 `restart` 监听块;④ **D-77** 删除 `client.ts:88-92,124-127` 两段被注释兼容层(含「兼容层」标签)与 `entry-client/src/create.ts:16,31,34-41` 被注释接线及模块创建函数块(`client-modules/src/index.ts` 经 `git grep` 实证**零残留、不编辑**)。**沿用约束** D-33 / D-42 / D-48 / D-49 / D-55 / D-56 / D-65 / D-68 / **D-69(本步零新增转发、不改任何 barrel)**。**开点(未裁定、默认不处置)**:旧加载 UI 的装配 / 可达性 —— `createGameRenderer`(`render/index.tsx:13`)无调用者、`LoadSceneUI` 仅在 `:25` 被 `sceneController.open`,本计划默认**不重新接线**,须用户另行裁定。**命名**:仅新增局部常量 `attributeHook`(Task 0 获批);不新增 / 改名任何公共 / 受保护 / 私有成员。**门禁**:编辑集最终形状 + 残留清零 + 范围(`material/` / `packages/` / `src/` / 数据端 / `packages/common` 零改动)+ 用户改动保留(`packages/common/src/{hook.ts,types.ts}` 与 `04-CONTEXT.md`,D-56)+ 白名单提交 + 既有 `04-01`..`04-13` 产物保护 + CRLF + 人工复核(**不设 `check:type` / `build` / TS 诊断数类门禁**,D-68 的延续);`autonomous: false`(Task 0 汇报关卡等待用户「可以执行」);无依赖变更、不执行 `pnpm i`;`REND-01` / `REND-02` 仍 Pending。
**Plan register note (2026-09-23):** 第九个增量 `04-09` = TextureManager 新接口适应**第二步(实施·代码改动)**(用户裁定 D-43..D-50:`renderWithoutCheck` 三处调用点改新 `render(tile, connection)`;`BlockCls` / `cls` 判定改为 `tileType === TileType.Autotile`、不做映射表;`extension/hero.ts` 勇士字面量填 `tileType: TileType.Unknown`;`MapRenderer` 不再自建 `AutotileProcessor` 而取 `manager.autotile`、保留 `readonly autotile` 字段与 `IMapRenderer` 签名;不改 `material/**` 与加载面;不考虑 legacy 兼容)。对象 = `packages-user/client-modules/src/render/map/{vertex.ts,renderer.ts,extension/hero.ts}` 的 **13 条** texture 归属 TS 诊断(vertex 6 / renderer 5 / hero 2),目标归零;`moving.ts` 与 `render/map/types.ts` 经核实无需改动。**已规划待执行**,执行前须 Task 0 汇报关卡等待用户「可以执行」;`REND-01` / `REND-02` 仍 Pending(本步仅为 REND-01 的 texture 子切片)。
Plans:
@ -239,6 +240,10 @@ Plans:
- [x] 04-13-PLAN.md — 部分 UI 的进一步接口适配·**第一步·只读收集需要修改的点(只读,D-71 / D-72 / D-73)**:以 `d8fa4b8 refactor: Delete global loading & hook object` 后的工作树为基准,收集渲染端**部分 UI** 对已删全局 `loading` / `hook` 与旧加载系统的使用点,产出 `04-UI-ADAPTATION-IMPACT.md`(元信息块 + `只读起始基线` / `背景` / `方法` / `基准变更(d8fa4b8)` / A 全局 loading / hook 使用点(UI 面)/ B 目标对象与钩子 API 证据 / C 旧加载 UI 清单与新加载系统 / D 分层与 barrel(D-69)检查 / E legacy 命中(仅报告)/ F 未能从阅读确定(未猜测)/ 修改点汇总 / 处置;A 类 = `render/ui/load.tsx` 旧加载接口(`:71,72,76-79,91-92,118-119,139`)+ `render/ui/main.tsx` `statusBarUpdate`(`:140-143,146-151`)+ `render/index.tsx` `restart`(`:35-38`),边界非 UI 面(`client.ts:88-92,124-127` / `client-modules/src/index.ts` / `entry-client/src/create.ts:16,31,34-41`)只报告;B 类逐处给目标对象证据(`IMotaDataLoader` / `ILoadManager` / 数据端可 hook 对象接口)或 undetermined;C 类登记旧 `LoadScene` / `LoadSceneUI` 与已删 `client-base/src/load/**` + 新 `@motajs/loader` / `data-state/src/loader/**` / `CoreState.loader` / `loadManager`;D 类 4 个 barrel 节点(只报告);E 类 legacy 只报告;F 类 ≥ 5 条未确定项;事实行每行 ≥2 个 `file:line` 锚点),**本步生产代码零改动、不引入任何命名变更**;门禁 = 只读基线一致 + 台账结构 / 内容 / 证据纪律 + 范围与用户改动 + 既有 `04-01`..`04-12` 产物保护 + CRLF + 人工复核(**不设 `check:type` / `build` / TS 诊断数类门禁**,D-68 的延续);`autonomous: false`(Task 0 汇报关卡等待用户「可以执行」);第二步(适配实施)待用户审阅后另行规划(D-72);`REND-01` / `REND-02` 仍 Pending
**Wave 13** *(blocked on Wave 12 completion)*
- [ ] 04-14-PLAN.md — 部分 UI 的进一步接口适配·**第二步·适配实施(代码改动,D-74 / D-75 / D-76 / D-77 / D-78)**:`render/ui/main.tsx` 状态栏改在勇士属性对象 `client.hero.attribute` 上注册 `onUpdateAttribute` 钩子(tracer,删除全局 `hook` 残留与手工调用垫片,新增局部 `attributeHook`);`render/ui/load.tsx` 改依 `client.loader.start()` + `client.loader.manager.get*`;删除 `render/index.tsx:35-38` 的 `restart` 注释块与 `client.ts:88-92,124-127` / `entry-client/src/create.ts:16,31,34-41` 的非 UI 残留(`client-modules/src/index.ts` 零残留不编辑);默认不重新接线旧加载 UI 装配点(开点,待用户裁定);`autonomous: false`(Task 0 汇报关卡等待用户「可以执行」);门禁全静态(不设 `check:type` / `build` / TS 诊断数类门禁,D-68 的延续);`REND-01` / `REND-02` 仍 Pending
**UI hint**: yes
### Phase 5: Legacy 移植

View File

@ -5,14 +5,14 @@ current_phase: 04
current_phase_name: 渲染适配与双布局
status: executing
stopped_at: Completed 04-13-PLAN.md(部分 UI 进一步接口适配·第一步只读收集:交付 04-UI-ADAPTATION-IMPACT.md;A7/B5/C2/D5/E4/F5 + 8 行修改点汇总;A-01 目标可确定=IMotaDataLoader/ILoadManager,其余多 undetermined;生产代码零改动;REND-01/REND-02 保持 Pending,第二步未规划)
last_updated: "2026-09-30T07:10:00.000Z"
last_updated: "2026-09-30T08:10:04.336Z"
last_activity: 2026-09-30
last_activity_desc: Completed 04-13-PLAN.md(部分 UI 进一步接口适配·第一步只读收集)
state_head: 26e2fb7
last_activity_desc: Planned 04-14-PLAN.md(部分 UI 进一步接口适配·第二步适配实施)
state_head: 425ff2829785decfa6dcfdfb072e9f68aae2135f
progress:
total_phases: 7
completed_phases: 1
total_plans: 84
total_plans: 85
completed_plans: 83
milestone_name: milestone
---
@ -28,10 +28,10 @@ See: .planning/PROJECT.md (updated 2026-09-17)
## Current Position
Phase: 04 (渲染适配与双布局) — IN PROGRESS
Plan: 13/13 已执行(04-01..04-13)
Status: 04-13 完成(部分 UI 适配·第一步只读收集);下一步 = 第二步适配实施(待你审阅台账后另行规划,D-72);Phase 4 整体仍未完成 — REND-01/REND-02 保持 Pending
Last activity: 2026-09-30 — Completed 04-13-PLAN.md(部分 UI 进一步接口适配·第一步只读收集)
Phase: 04 (渲染适配与双布局) — READY TO EXECUTE
Plan: 13/14 已执行(04-01..04-13);04-14(部分 UI 进一步接口适配·第二步适配实施)已规划待执行(Wave 13)
Status: 04-14 已规划并通过 checker(0 blocker / 0 warning / 4 info-advisory);待用户回复「可以执行」后执行;Phase 4 整体仍未完成 — REND-01/REND-02 保持 Pending
Last activity: 2026-09-30 — Planned 04-14-PLAN.md(部分 UI 进一步接口适配·第二步适配实施)
Progress: [█████░░░░░] 50%

View File

@ -0,0 +1,519 @@
---
phase: 04-render-adaptation
plan: 14
type: execute
wave: 13
depends_on:
- 04-01
- 04-02
- 04-03
- 04-04
- 04-05
- 04-06
- 04-07
- 04-08
- 04-09
- 04-10
- 04-11
- 04-12
- 04-13
files_modified:
- packages-user/client-modules/src/render/ui/main.tsx
- packages-user/client-modules/src/render/ui/load.tsx
- packages-user/client-modules/src/render/index.tsx
- packages-user/client-modules/src/client.ts
- packages-user/entry-client/src/create.ts
files_deleted: []
autonomous: false
requirements:
- REND-01
- REND-02
estimate:
tokens: 60000
raw_tokens: 60000
tasks: 5
confidence: low
must_haves:
truths:
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求)**:Task 0 必须先向用户汇报「要解决的问题(数据端 `d8fa4b8 refactor: Delete global loading & hook object` 与加载系统重构后,部分 UI 仍以已删全局 `loading` / `hook` 与旧加载接口编写)」「逐条裁定 **D-74(旧加载 UI 进度数据来源 = 新加载接口提供的方法)/ D-75(旧 `loader.progress` 异步迭代的等价 = 新接口 `start()` 返回的 `AsyncIterable`)/ D-76(`restart` 事件不再需要,删除相关注释内容)/ D-77(三处非 UI 旧接线残留直接删除)/ D-78(状态栏改在勇士属性对象上注册 `onUpdateAttribute` 钩子)**,以及沿用约束 D-33 / D-42 / D-48 / D-49 / D-55 / D-56 / D-65 / D-68 / D-69」「**待批准的精确编辑集**(含将要删除的既有注释行与被删除的手工单次调用垫片)」「**将新增的局部标识符命名**(`main.tsx` 内局部常量 `attributeHook`)」「**开点:旧加载 UI 的装配 / 可达性(`render/index.tsx:11,13,25`)与 `client-modules/src/index.ts` 在 D-77 下无可删残留的事实,D-74..D-78 未裁定**」,然后必须**停下等待**用户明确回复「可以执行」四个字;收到前不得执行任何任务、不得派发任何执行子代理。无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理/执行器派发)或配置模式(`.planning/config.json` 的 `mode: yolo`、`workflow.auto_advance`)如何,均不得自动放行。"
- "**D-78 端到端落地(tracer,`main.tsx`)**:状态栏更新不再依赖已删全局 `hook`——改为在勇士属性对象上注册钩子:`client.hero.attribute.addHook({ onUpdateAttribute })`(`IReadonlyHeroAttribute<THero> extends IHookable<IHeroAttributeHooks<THero>>`,`packages-user/data-base/src/hero/types.ts:95-106`;触发点 `packages-user/data-base/src/hero/attribute.ts:89`);`main.tsx` 内不再出现任何全局 `hook` 监听 / 注销残留;挂载期手工单次调用垫片(原 `:140-143`)被移除,改为由注册的控制器 `unload()` 在 `onUnmounted` 中清理。"
- "**D-74 / D-75 落地(旧加载 UI 重新适配,`load.tsx`)**:`LoadScene` 改依新加载系统——`const loader = client.loader`(`IMotaDataLoader`)保留;`loader.start()`(`AsyncIterable<number>`)取代旧 `initSystemLoadTask()` + `load()` + `progress` 异步迭代;进度 / 字节 / 任务数据一律取自 `loader.manager`(`ILoadManager`)的现有方法 `getLoadedTasks` / `getAddedTasks` / `getTotalByte` / `getLoadedByte` / `getByteRatio`;`load.tsx` 内不再出现旧接口成员;`:75` 的 `@ts-expect-error 需要重构` 随代码修正一并移除(该注解不再需要)。"
- "**D-76 / D-77 落地(删除旧内容)**:`render/index.tsx` 的被注释 `restart` 监听块被删除(D-76);`client.ts:88-92,124-127` 的被注释 `loading` 兼容层块、`entry-client/src/create.ts:16,31,34-41` 的被注释 `loading` / `hook` 接线与 `createModule` 函数被删除(D-77);`client-modules/src/index.ts` 经 `git grep` 实证**零残留**(无需删除)并以事实登记(D-77 列出该文件但现行文件已无 `loading` / `hook` 文本);生产树内再无 `hook.on` / `hook.off` 全局监听残留。"
- "**范围与边界零外溢**:`packages-user/client-base/src/material/**`(D-42 / D-48)、`packages/**`、`src/**`、任何测试文件、legacy 面(D-49 / D-55 / D-65)**零触碰**;无依赖新增、不改 `package.json` / `pnpm-lock.yaml`、不执行 `pnpm i`;不改写 `04-01`..`04-13` 既有 `*-PLAN.md` / `*-SUMMARY.md`;不产出 `04-15+`;不重新设计任何数据端契约。"
- "**用户并发改动零触碰(D-56)**:`.planning/phases/04-render-adaptation/04-CONTEXT.md`(规划日实测 ` M`)一律不修改、不暂存、不提交;`packages/common/src/hook.ts` 与 `packages/common/src/types.ts` 在本规划会话期间被用户提交(`425ff28 refactor: 删除 hook 的 loadHook 接口`;规划日起测时仍为 ` M`)——**无论其提交与否,本计划只读其现行 `IHookable.addHook` / `IHookController.unload` 形状,绝不修改 / 暂存 / 提交**;白名单提交前以 `git status` 逐条核对暂存面。"
- "**命名先获批(AGENTS.md)**:本步不新增 / 改名 / 删除任何公共、受保护、私有成员、方法、字段;如确需新增标识符,仅限任务已列明的**局部**标识符(`main.tsx` 内局部常量 `attributeHook`),且已在 Task 0 汇报并经用户同意;否则不得自行命名。"
- "**无运行时门禁(D-68 的延续)**:全部验证为**静态**(文件内容断言 + `git status --porcelain` 范围断言 + CRLF + 既有产物保护),每条 `<automated>` 都带 `<fails_when>`;**不得**出现任何以「`pnpm check:type` 通过」「`pnpm build` 成功」「TS 诊断数归零」为条件的门禁或验收项;`REND-01` / `REND-02` 在本 run 后仍为 **Pending**,`.planning/REQUIREMENTS.md` 零改动。"
artifacts:
- path: packages-user/client-modules/src/render/ui/main.tsx
provides: "D-78:移除全局 `hook` 状态栏监听残留与手工单次调用垫片,改为在 `client.hero.attribute` 上 `addHook({ onUpdateAttribute })` 注册并在 `onUnmounted` 注销"
- path: packages-user/client-modules/src/render/ui/load.tsx
provides: "D-74 / D-75:旧加载 UI 改依新加载系统(`client.loader.start()` + `client.loader.manager.get*`),移除旧接口成员与失效的 `@ts-expect-error`"
- path: packages-user/client-modules/src/render/index.tsx
provides: "D-76:删除被注释的 `restart` 全局 hook 监听块"
- path: packages-user/client-modules/src/client.ts
provides: "D-77:删除被注释的 `loading` 兼容层残留块"
- path: packages-user/entry-client/src/create.ts
provides: "D-77:删除被注释的 `loading` / `hook` 接线与 `createModule` 函数残留"
- path: .planning/phases/04-render-adaptation/04-14-SUMMARY.md
provides: "本增量执行记录:编辑集 / 门禁实测 / 人工复核 / 无运行时验证声明 / 开点(装配可达性)余留"
- path: .planning/ROADMAP.md
provides: "Phase 4 的 `Wave 13` + `04-14` register note(计划阶段登记)"
key_links:
- "`packages-user/client-modules/src/render/ui/main.tsx:145-152`(被注释的全局状态栏监听块)↔ `packages-user/data-base/src/hero/types.ts:95-106`(`IHeroAttributeHooks<THero>.onUpdateAttribute` / `IReadonlyHeroAttribute extends IHookable`)↔ `packages-user/data-base/src/hero/attribute.ts:89`(`forEachHook(hook => hook.onUpdateAttribute?.(name, value))` 触发点)↔ `packages/common/src/types.ts:84` / `packages/common/src/hook.ts:23,44`(`addHook` / `removeHookByController`)——D-78 的端到端接线依据"
- "`packages-user/client-modules/src/render/ui/main.tsx:140-143`(手工单次调用 + 「后续修改后需要删除」注释垫片)↔ 本任务新增的 `addHook` 注册 —— 垫片被其自身注释所述「修改后删除」取代"
- "`packages-user/client-modules/src/render/ui/load.tsx:36,71,72,76-79,91-92,118-119,139`(旧接口 `client.loader` / `initSystemLoadTask` / `load` / `progress.get*`)↔ `packages-user/data-state/src/loader/types.ts:21,26,31`(`IMotaDataLoader.manager` / `start()` / `loaded()`)↔ `packages/loader/src/types.ts:288-335`(`ILoadManager.getLoadedTasks` / `getAddedTasks` / `getTotalByte` / `getLoadedByte` / `getByteRatio`)↔ `packages/loader/src/manager.ts:88-111`(实现)——D-74 / D-75 的改指依据"
- "`packages-user/client-modules/src/render/index.tsx:35-38`(被注释 `restart` 监听)—— D-76 删除对象;`packages-user/client-modules/src/client.ts:88-92,124-127` 与 `packages-user/entry-client/src/create.ts:16,31,34-41` —— D-77 删除对象"
- "`packages-user/client-modules/src/index.ts:1-16`(`create()` 现只 `patchAll()` + `createRender()`,无 `loading` / `hook` 文本)↔ D-77 名单 —— **零残留事实**;与 `render/index.tsx:13`(`createGameRenderer` 无调用者)共同构成本计划的**开点(装配 / 可达性)**"
- "`packages/common/src/hook.ts` / `packages/common/src/types.ts`(用户并发改动,D-56;规划日起测为 ` M`,规划会话期间由用户提交 `425ff28`)—— 本计划只**读**其现行 `IHookable.addHook` / `IHookController.unload` 形状,绝不修改 / 暂存 / 提交"
- "`git status --porcelain -- packages packages-user src`(Task 1 步骤 (0a) 记录;规划日实测为空)↔ Task 1 / 2 / 3 / 4 的范围断言 + `packages/common/src/{hook.ts,types.ts}` 与 `.planning/.../04-CONTEXT.md` 的 ` M` 仍在的断言 —— D-56 与范围零外溢证明"
assumptions:
- "**FLAGGED ASSUMPTION(specless fallback)— REND-01 edge unresolved:** 未提供 REND-01(渲染端适配新数据层接口)的验收边界;本计划**不发明**阈值,只交付「部分 UI 进一步接口适配」的**第二步代码改动**(D-74..D-78)。用户须在推进 REND-01 余下部分前补齐边界判据。"
- "**FLAGGED ASSUMPTION(specless fallback)— REND-02 edge unresolved:** 未提供 REND-02(移动端与桌面端双布局)的验收边界;本计划不涉及双布局,也不发明其验收阈值。"
- "**开点 — 旧加载 UI 装配 / 可达性(D-74..D-78 未裁定):** 规划日 `git grep -n \"createGameRenderer\" -- packages-user packages src` 仅命中 `render/index.tsx:13` 定义行(**无调用者**),`LoadSceneUI` 只在 `createGameRenderer`(`render/index.tsx:25`)内被 `sceneController.open`;`client-modules/src/index.ts` 的 `create()` 已不再调用 `createGameRenderer`(`d8fa4b8` 删除了 `loading.once('coreInit')` 接线)。**本计划默认只做 `load.tsx` 的接口适配(D-74 / D-75),不重新接线装配点、不改 `render/index.tsx:11,13,25`**;若用户要求重新接线,退出本计划、修订后重新汇报(Task 0 的 `rewire` 选项)。"
- "**D-77 对 `packages-user/client-modules/src/index.ts` 的名单与现行事实不一致:** 该文件现行(规划日)**零** `loading` / `hook` 文本(`git grep` 实证);故本计划对该文件**无可删除内容**,仅以 `git grep` 证据登记「零残留」,不做任何编辑。Task 0 必须原样告知用户此事实并征得其同意(若用户指认其它意图,退出本计划、修订后重新汇报)。"
- "**规划日实测行号(须用户在 Task 0 确认):** `main.tsx:140-143`(垫片)+ `:145-152`(被注释监听块);`load.tsx:36,71,72,75,76-79,91-92,118-119,139`;`render/index.tsx:35-38`;`client.ts:88-92,124-127`;`entry-client/src/create.ts:16,31,34-41`。**执行日一律以当日实测按符号名 / 文本检索定位**;任一行号漂移按当日实测改写并在 SUMMARY 说明。"
- "**用户并发改动与 hook API 依赖:** 本计划依赖 `packages/common/src/hook.ts` / `types.ts` 的 `IHookable.addHook` / `IHookController.unload` 形状(规划日起测时为 ` M` 未提交,规划会话期间被用户提交为 `425ff28 refactor: 删除 hook 的 loadHook 接口`;现行工作树已含该 API,`git grep` 实测 `addHook` 与 `removeHookByController` 均在);若执行日该形状变化,按当日实测改写并停下向用户说明,不得臆造替代 API。"
- "**无运行时验证可得(D-68 的延续):** 本步不要求「保证能运行」,不启动渲染、不运行任何运行时用例;承载体为静态门禁 + 人工复核;**不得**以 `pnpm check:type` / `pnpm build` / TS 诊断数冒充门禁。"
- "**`client.ts` 的 `initMapExtensions`(`:137-148`)在删除注释块后无调用者:** 按 D-77 的名单(仅 `:88-92,124-127`)**不列入删除集,予以保留**;此事实在 Task 0 一并告知用户。"
prohibitions:
- "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理(AGENTS.md 的第一个 block 点约束;不因触发方式或 `yolo` / `auto_advance` 模式而放宽)。"
- "不得修改任何既有 jsDoc / 注释文本超出门禁批准的编辑集(AGENTS.md):本计划允许删除的既有注释**仅限**被删除的旧接线块自身携带的注释行(`main.tsx:140-143` 的两行说明、`client.ts` 两处 `// 兼容层` 标签、`entry-client/src/create.ts` 被注释接线与 `createModule` 块、`render/index.tsx:35-38` 的 `restart` 注释块),以及 `load.tsx:75` 随代码修正失效的 `@ts-expect-error 需要重构`;**这些删除项须在 Task 0 逐条列出并经用户同意后方可执行**,其余任何既有注释一律不得改动。"
- "不得新增 / 改名 / 删除任何公共、受保护、私有成员、方法、字段、接口或文件;新增标识符仅限局部常量 `attributeHook`(`main.tsx`),且须经 Task 0 获批。"
- "不得触碰、暂存、提交用户的并发改动(D-56):`.planning/phases/04-render-adaptation/04-CONTEXT.md`(仍为 ` M`)与 `packages/common/src/hook.ts` / `packages/common/src/types.ts`(规划会话期间已被用户提交为 `425ff28`,仍不得再改)一律不修改、不暂存、不提交。"
- "不得触碰 `packages-user/client-base/src/material/**`(D-42 / D-48)、`packages/**`、`src/**`、任何测试文件、任何 legacy 面(D-49 / D-55 / D-65)。"
- "不得改动 `package.json` / `pnpm-lock.yaml` / `tsconfig.json` / 任何 lint 配置;不得新增依赖;不得执行 `pnpm i`。"
- "不得重新接线旧加载 UI 的装配点(`render/index.tsx:11,13,25`)或为 `createGameRenderer` 增加调用者——该点属开点,须用户另行裁定。"
- "不得改写 / 覆盖 / 改号 `04-01`..`04-13` 的既有 `*-PLAN.md` / `*-SUMMARY.md`;不得改写既有 `04-CONTEXT.md` / `04-UI-ADAPTATION-IMPACT.md`;不得产出 `04-15+` 或任何新计划文件。除 `files_modified` 列出的五个生产文件、本 `04-14-PLAN.md`、`04-14-SUMMARY.md` 与 `.planning/ROADMAP.md` 的登记外,不得写任何其它文件(含 `.planning/STATE.md` / `WINDOWS.md` / `REQUIREMENTS.md`)。"
- "不得使用 `import type`(dev.md 项目规则);不得新增、删除或修改任何测试文件与既有测试。"
- "不得把任何门禁建立在「全仓 / 范围内 TS 诊断数」「`pnpm check:type`」「`pnpm build`」之上,也不得因这类检查失败而阻塞交付(D-68)。"
- "不得以「无运行时」为由弱化验证:每条 `<automated>` 必须有 `<fails_when>`;确实无法运行的门禁必须显式声明为不可得并降级为静态 + 人工,不得伪造绿。"
- "不得把 `loader.manager` 的 `getTotalByte` 实现细节(`packages/loader/src/manage.ts` 中返回 `this.loaded`)当作缺陷登记或自行修正——数据端语义归用户(D-33 / D-42 精神),本步只做接口适应。"
---
<objective>
Phase 4 第十四个增量(用户裁定 2026-09-30 · **D-74 / D-75 / D-76 / D-77 / D-78**)—— **部分 UI 的进一步接口适配·第二步(适配实施,代码改动)**。
**要解决的问题:**
`04-13` 的第一步只读台账(`.planning/phases/04-render-adaptation/04-UI-ADAPTATION-IMPACT.md`,A7 / B5 / C2 / D5 / E4 / F5 + 8 行修改点汇总)确认:数据端经 `d8fa4b8 refactor: Delete global loading & hook object` 与加载系统重构后,渲染端**部分 UI** 仍以已失效的旧接口编写。用户已对全部 F 类未决项作出裁定(D-74..D-78),本计划据其落地:
| 裁定 | 内容 | 落点 |
|---|---|---|
| **D-74(F-01)** | 旧加载 UI 的「任务进度 / 字节进度 / 总字节」数据来源 = **新加载接口提供的方法**(`IMotaDataLoader` / `ILoadManager`) | `render/ui/load.tsx` |
| **D-75(F-02)** | 旧 `loader.progress` 异步迭代的等价 = 新加载接口的 **`start()`(返回 `AsyncIterable`)** | `render/ui/load.tsx` |
| **D-76(F-04)** | `restart` 事件不再需要 → 删除相关(被注释)内容 | `render/index.tsx:35-38` |
| **D-77(F-05)** | 三处非 UI 旧加载 / 钩子接线残留**直接删除**(「重构即删旧内容」) | `client.ts:88-92,124-127`、`client-modules/src/index.ts`、`entry-client/src/create.ts:16,31,34-41` |
| **D-78(F-03)** | 状态栏更新改为在**勇士属性对象**(`client.hero.attribute`)上注册钩子 `IHeroAttributeHooks<THero>.onUpdateAttribute?(name, value)` | `render/ui/main.tsx:145-152`(+ 移除 `:140-143` 垫片) |
**沿用约束(D-73 延续):** D-33(范围外一律不改、不追平全部类型错误);D-42 / D-48(`packages-user/client-base/src/material/**` 归用户、AI 不得改);D-49 / D-55 / D-65(不考虑任何 legacy);D-56(不得触碰 / 提交用户并发改动);D-68(不要求「保证能运行」、不得以 `check:type` / `build` / TS 诊断数作为门禁);**D-69(桶导出边界)继续适用**——本步不新增任何转发导出、不改任何 barrel。
**本计划相对 04-13 的关系:** 04-13 是第一步只读收集(生产代码零改动);本计划是第二步适配实施(代码改动),输入 = `04-UI-ADAPTATION-IMPACT.md` 的 `## 修改点汇总` + D-74..D-78 的逐条裁定。
**任务切分(Task 0 汇报关卡 + Task 1 tracer + 2 个铺开任务 + Task 4 收口):** 落在**单一计划文件**内。
- **Task 0** = `checkpoint:decision`(`gate="blocking-human"`):汇报问题 / 裁定 / **精确编辑集**(含将被删除的既有注释行与垫片)/ **将新增的局部命名** / **开点(装配可达性)与 D-77 的 `client-modules/src/index.ts` 零残留事实** / 验证方式,等待「可以执行」。
- **Task 1(tracer)= D-78 的状态栏属性钩子**这一条线**端到端**落地:UI 组件(`main.tsx`)→ 数据端可 hook 属性对象(`client.hero.attribute`)→ 钩子触发点(`attribute.ts:89`),先用一个真实适配点证明「在对应对象上监听」的接线方式,并移除垫片与全局 hook 残留。
- **Task 2** = D-74 / D-75:旧加载 UI(`load.tsx`)改依新加载系统(`client.loader.start()` + `client.loader.manager.get*`)。
- **Task 3** = D-76 / D-77:删除 `restart` 注释块与三处非 UI 残留(`client-modules/src/index.ts` 以零残留证据登记)。
- **Task 4** = 收口(编辑集静态门禁 + 范围 / 用户改动门禁 + 既有产物保护 + CRLF + 人工复核 + `04-14-SUMMARY.md` + 白名单提交)。
## Source Audit(多源覆盖审计)
| SOURCE | ID | Feature/Requirement | Plan | Status | Notes |
|--------|----|---------------------|------|--------|-------|
| GOAL | — | Phase 4:「渲染端通过新数据层接口驱动,并同时支持移动端与桌面端布局」 | 04-14 | COVERED(增量·代码改动子切片) | 本 run 落地「部分 UI 进一步接口适配」的第二步(D-74..D-78);双布局属后续增量(见两条 FLAGGED ASSUMPTION) |
| REQ | REND-01 | 渲染端适配新数据层接口 | 04-14 | COVERED(UI 切片·代码改动;需求保持 Pending) | 「组件改在对应对象上监听 + 旧加载 UI 改依新加载系统」即 REND-01 的 UI 切片;验收边界未提供 → FLAGGED ASSUMPTION |
| REQ | REND-02 | 渲染端同时支持移动端与桌面端布局 | 04-14 | COVERED(不涉及) | 本 run 范围不含双布局;验收边界未提供 → FLAGGED ASSUMPTION |
| RESEARCH | — | (本阶段无 `04-RESEARCH.md`) | — | N/A | Phase 4 未产出 RESEARCH.md;等价的权威输入 = `04-UI-ADAPTATION-IMPACT.md`(04-13 交付物)+ `04-CONTEXT.md` 的 D-74..D-78 |
| CONTEXT | D-74 | 旧加载 UI 进度数据来源 = 新加载接口方法 | 04-14 | COVERED | Task 2(`loader.manager.get*`) |
| CONTEXT | D-75 | `loader.progress` 异步迭代等价 = `start()`(AsyncIterable) | 04-14 | COVERED | Task 2(`for await (... of loader.start())`) |
| CONTEXT | D-76 | `restart` 不再需要,删除相关注释内容 | 04-14 | COVERED | Task 3(`render/index.tsx:35-38`) |
| CONTEXT | D-77 | 三处非 UI 旧接线残留直接删除 | 04-14 | COVERED | Task 3(`client.ts` / `entry-client/src/create.ts` 删除;`client-modules/src/index.ts` 零残留证据登记) |
| CONTEXT | D-78 | 状态栏改在勇士属性对象上注册 `onUpdateAttribute` | 04-14 | COVERED(tracer) | Task 1 |
| CONTEXT | D-71 / D-72 | 目标:全局 `loading` / `hook` 须改在对应对象上监听;旧加载 UI 须重新适配;两步走(本步 = 第二步) | 04-14 | COVERED | Task 1(D-78)+ Task 2(D-74/75)+ Task 3(D-76/77) |
| CONTEXT | D-73 / D-33 | 范围外一律不改、不追平全部类型错误 | 04-14 | COVERED(边界) | `packages/` 与 `src/` 零改动;无「诊断归零」验收项 |
| CONTEXT | D-42 / D-48 | `client-base/src/material/` 归用户、AI 不得改 | 04-14 | COVERED(禁用路径) | Task 4 范围门禁断言 `material/` porcelain 为空 |
| CONTEXT | D-49 / D-55 / D-65 | 不考虑任何 legacy | 04-14 | COVERED(零触碰) | 无 legacy 任务 / 门禁 / 待办;`core.*` 存量使用不触碰 |
| CONTEXT | D-56 | 用户并发改动零触碰 | 04-14 | COVERED | Task 1 (0a) 记录基线;Task 4 断言用户 `04-CONTEXT.md` 与 `packages/common/src/{hook.ts,types.ts}` 改动仍在且未被暂存 |
| CONTEXT | D-68 | 不要求可运行、不以 TS 诊断数 / `check:type` / `build` 为门禁 | 04-14 | COVERED(延续) | 全部门禁为静态;prohibitions 明列 |
| CONTEXT | D-69 | 桶导出边界原则(硬约束) | 04-14 | COVERED(零新增转发) | 本步不改任何 barrel;Task 4 断言无 `index.ts` / `index.tsx` 被改 |
| LEDGER | `#04-13-A-01` / `B-01` | `load.tsx` 旧加载接口(`:36,71,72,76-79,91-92,118-119,139`)→ `IMotaDataLoader` / `ILoadManager` | 04-14 | COVERED | Task 2 |
| LEDGER | `#04-13-A-03` / `F-03`→D-78 | `main.tsx:140-143,145-152` 全局 hook 监听点 | 04-14 | COVERED | Task 1 |
| LEDGER | `#04-13-A-04` / `F-04`→D-76 | `render/index.tsx:35-38` `restart` 监听点 | 04-14 | COVERED | Task 3 |
| LEDGER | `#04-13-A-05/06/07` / `F-05`→D-77 | 边界非 UI 面 `client.ts` / `client-modules/src/index.ts` / `entry-client/src/create.ts` | 04-14 | COVERED | Task 3 |
| LEDGER | `#04-13-A-02` / `#04-13-C-01` | 装配链不可达(`createGameRenderer` 无调用者) | 04-14 | DEFERRED(开点) | D-74..D-78 未裁定;Task 0 显式呈报,默认不接线(见 assumptions / Task 0 `rewire` 选项) |
| Deferred Ideas | — | 阶段 4 后续步骤(双布局);`packages` 全部、`legacy-plugin-*`、`data-fallback` | — | DEFERRED | 未写入任何任务 |
## Estimate Basis(估算依据)
`estimate-calibration` 无历史样本(`factor: 1`、`applied: false`、`sample_count: 0`、`confidence: low`),故 `tokens == raw_tokens` 并如实标注 `confidence: low`。按「执行者实际触及量」估算(`estimate.tasks: 5` = Task 0 汇报关卡 + 4 个工作任务):Task 0 只读汇报并等待用户确认(约 6k,不写文件);Task 1(tracer)起始基线 + `main.tsx` 状态栏属性钩子端到端 + 门禁(约 11k);Task 2 `load.tsx` 改依新加载系统 + 门禁(约 13k);Task 3 D-76 / D-77 删除 + 证据 + 门禁(约 12k);Task 4 收口门禁 + 人工复核 + `04-14-SUMMARY.md` + 白名单提交(约 18k);合计约 60k。**本计划刻意不做多计划拆分**:委托明确要求只产出 `04-14` 一份代码改动计划。
</objective>
<execution_context>
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/REQUIREMENTS.md
@.planning/phases/04-render-adaptation/04-CONTEXT.md
@.planning/phases/04-render-adaptation/04-UI-ADAPTATION-IMPACT.md
@.planning/phases/04-render-adaptation/04-13-PLAN.md
@.planning/phases/04-render-adaptation/04-12-PLAN.md
@AGENTS.md
@dev.md
</context>
<tasks>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 0: 预执行汇报关卡 —— 汇报「部分 UI 的进一步接口适配·第二步(适配实施)」的问题、D-74..D-78 逐条裁定、**精确编辑集(含将删除的既有注释行与手工调用垫片)**、**将新增的局部命名**、**开点(旧加载 UI 装配 / 可达性)与 D-77 对 `client-modules/src/index.ts` 的零残留事实**、范围与硬边界、验证方式,等待用户回复「可以执行」</name>
<precondition>用户裁定 D-74 / D-75 / D-76 / D-77 / D-78 已写入 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(以文件内实际存在为准);`04-13` 的第一步只读台账 `.planning/phases/04-render-adaptation/04-UI-ADAPTATION-IMPACT.md` 已在场(含 `## 修改点汇总`);`client-modules/src/index.ts` 现行不含 `loading` / `hook` 文本。</precondition>
<files>(只读汇报,不修改任何文件)</files>
<read_first>
- AGENTS.md(「任何计划的执行,其第一个 block 点必须是向我汇报计划内容并等待我确认」;只有明确回复「可以执行」四个字之后才允许开始执行;「任何公共、受保护、私有方法、字段等内容的命名必须先向我反馈」;「不得擅自修改所有 jsDoc 注释,不得擅自修改任何已有注释」;「除非我明确要求,否则任何修改必须经由 GSD 标准流程」)
- .planning/phases/04-render-adaptation/04-CONTEXT.md(**D-74 / D-75 / D-76 / D-77 / D-78 逐条裁定原文**;D-71 / D-72 / D-73;D-33 / D-42 / D-48 / D-49 / D-55 / D-65 / D-56 / D-68 / D-69)
- .planning/phases/04-render-adaptation/04-UI-ADAPTATION-IMPACT.md(`## 修改点汇总` 8 行 + `#04-13-A-01..A-07` / `F-01..F-05`)
- .planning/phases/04-render-adaptation/04-13-PLAN.md(上一增量 Task 0 汇报关卡的结构与口径)
- .planning/phases/04-render-adaptation/04-14-PLAN.md(本计划全文:objective 的裁定表、tasks、must_haves、禁令、门禁)
- dev.md(「双端分离」系统层 / 实现层;「其他要求」的 CRLF、不得 `import type`)
</read_first>
<action>
**停下并向用户汇报,等待明确回复后才继续;汇报本身只读,不写任何文件、不改任何生产代码、不派发子代理。**
① **要解决的问题**:`04-13` 只读台账确认,数据端经 `d8fa4b8 refactor: Delete global loading & hook object` 与加载系统重构后,渲染端部分 UI 仍以已失效的旧接口编写。用户对全部 F 类未决项已裁定,本计划据其做**第二步的代码改动**。
② **逐条汇报裁定原文要点(D-74..D-78)**:
- **D-74(F-01)**:旧加载 UI 的「任务进度 / 字节进度 / 总字节」数据来源 = **新加载接口提供的方法**(`IMotaDataLoader.manager` → `ILoadManager` 的 `getLoadedTasks` / `getAddedTasks` / `getTotalByte` / `getLoadedByte` / `getByteRatio`)。
- **D-75(F-02)**:旧 `loader.progress` 异步迭代的等价 = 新加载接口的 **`start()`(返回 `AsyncIterable`)**。
- **D-76(F-04)**:`restart` 事件不再需要 → **删除**被注释的相关内容。
- **D-77(F-05)**:三处**非 UI** 的旧加载 / 钩子接线残留**直接删除**(「重构即删旧内容」,不保留、不适配)。
- **D-78(F-03)**:状态栏更新改为在**勇士属性对象**(`client.hero.attribute`)上注册钩子 `IHeroAttributeHooks<THero>.onUpdateAttribute?(name, value)`(`packages-user/data-base/src/hero/types.ts:95-106`;触发点 `packages-user/data-base/src/hero/attribute.ts:89`)。
- 沿用约束:D-33 / D-42 / D-48 / D-49 / D-55 / D-65 / D-56 / D-68 / **D-69(桶导出边界,本步零新增转发)**。
③ **精确编辑集(须逐条原样呈报,供用户批准;这是 AGENTS.md「不得擅自修改既有注释」的批准入口)**:
- **A. `packages-user/client-modules/src/render/ui/main.tsx`** —— 新增:在 `MainScene` 内注册 `client.hero.attribute.addHook({ onUpdateAttribute })` 且处理器调用既有 `updateStatus()` 与 `updateDataFallback()`,控制器存为**局部常量 `attributeHook`**,并在既有 `onUnmounted` 内调用 `attributeHook.unload()`;**删除**:`:140-143` 的手工单次调用垫片(含两行既有说明注释「保留函数调用…」与「后续修改后需要删除」,以及 `updateDataFallback();` / `updateStatus();`);**删除**:`:145-152` 中被注释的全局 hook 监听 / 注销行(其上方既有注释「监听状态栏更新事件」按用户意见决定保留或改写)。
- **B. `packages-user/client-modules/src/render/ui/load.tsx`** —— 改:`startLoad` 以内层 `client.loader.start()` 的异步迭代取代旧的「系统加载任务初始化 + 加载 + progress 迭代」三件套,迭代体内从 `client.loader.manager` 的现有方法取任务数 / 字节数,迭代结束后调用既有 `loadEnd()`;改:`renderTaskList` / `renderByteList` 中的旧 `progress` 成员访问改为 `client.loader.manager` 的对应方法;**删除**:`:75` 的失效 `@ts-expect-error`(其理由文本为「需要重构」,代码修正后不再需要);**保留** `const loader = client.loader;` 与既有 `loadEnd`。
- **C. `packages-user/client-modules/src/render/index.tsx`** —— **删除**:`:35-38` 被注释的 `restart` 监听块(D-76)。
- **D. `packages-user/client-modules/src/client.ts`** —— **删除**:`:88-92` 与 `:124-127` 两段被注释的兼容层块(含两处既有「兼容层」标签);**保留**:`:137-148` 的 `initMapExtensions`(注:删除上述注释后它已无调用者,但不在 D-77 的名单内,故不删)。
- **E. `packages-user/entry-client/src/create.ts`** —— **删除**:`:16`、`:31` 两行被注释接线,以及 `:34-41` 被注释的模块创建函数块。
- **F. `packages-user/client-modules/src/index.ts`** —— **无可删除内容**:该文件现行零残留(无任何被删全局对象的文本);D-77 虽列出该文件,但 `d8fa4b8` 已删除其接线,故本计划仅以核对命令登记「零残留」事实,**不做任何编辑**。若用户认为该文件另有意图(例如应删除 `createGameRenderer` 或重新接线),请选 `revise` 并说明。
- **将新增的标识符(命名预批)**:仅一个**局部常量 `attributeHook`**(`main.tsx` 的 `MainScene` 内);不新增 / 改名 / 删除任何公共、受保护、私有成员、方法、字段、接口或文件。
④ **开点(D-74..D-78 未裁定,须原样呈报;本计划默认不处置)**:旧加载 UI 的**装配 / 可达性**——规划日 `createGameRenderer`(`packages-user/client-modules/src/render/index.tsx:13`)无调用者,`LoadSceneUI` 仅在 `:25` 被 `sceneController.open`;`client-modules/src/index.ts` 的 `create()` 已不调用 `createGameRenderer`。**本计划默认只做 `load.tsx` 的接口适配(D-74 / D-75),不重新接线装配点、不改 `render/index.tsx:11,13,25`**。如用户要求一并重新接线,请在关卡中选择相应选项,本计划将退出并修订后重新汇报。
⑤ **范围与硬边界(须原样呈报)**:改动面 = 上列五个生产文件;**零触碰** = `packages-user/client-base/src/material/**`(D-42 / D-48)、`packages/**`、`src/**`、任何测试文件、任何 barrel(`index.ts` / `index.tsx` 的导出面,D-69)、任何 legacy 面(D-49 / D-55 / D-65)、用户并发改动 `packages/common/src/{hook.ts,types.ts}` 与 `.planning/phases/04-render-adaptation/04-CONTEXT.md`(D-56)。**无依赖变更、不改 `package.json` / `pnpm-lock.yaml`、不执行 `pnpm i`;不得使用 `import type`。**
⑥ **验证方式(须原样呈报,并声明本步无运行时要求)**:D-68 的延续——本步**没有「保证能运行」的要求**。证明方式全部为**静态**:① 各任务的**文件内容断言**(目标文件含新接线形状、不含被删旧形状);② **范围门禁**:`git status --porcelain -- packages-user/client-base/src/material packages packages/ src` 为空;③ **用户改动门禁**:`packages/common/src/{hook.ts,types.ts}` 与 `04-CONTEXT.md` 的 ` M` 仍在、未被暂存;④ **既有产物保护**:`04-01`..`04-13` 的 `*-PLAN.md` / `*-SUMMARY.md` porcelain 为空、无 `04-15+`;⑤ **CRLF**:被编辑的生产文件与 `04-14-SUMMARY.md`;⑥ **人工复核**。**不得使用** `pnpm check:type` / `pnpm build` / TS 诊断数。
⑦ **编号与 wave(须原样告知)**:本计划为 **04-14**(第十四个增量),`wave: 13`,`depends_on` 列 `04-01`..`04-13`;既有 `04-01`..`04-13` 产物一律不得改写 / 覆盖 / 改号,也不得产出 `04-15+`。估算 **60k tokens**(`estimate.tasks: 5`)。
⑧ **关卡性质与失败语义(必须原样告知用户)**:无论触发方式(`/gsd-execute-phase`、`/gsd-manager`、`/gsd-autonomous`、子代理 / 执行器派发)或配置模式(`mode: yolo`、`workflow.auto_advance`)如何,**执行者 / 子代理都必须在此停下并等待**用户明确回复「可以执行」四个字;未收到前不得执行任何任务、不得派发执行子代理,也不得以「自动化模式」为由自动放行。用户未回复「可以执行」即继续,或回复的是其它内容(含修订意见、沉默、自动化模式下的自动推进),一律视为**未通过本关卡**。
⑨ **若用户给出修订意见**(含要求重新接线装配点 / 不同意某条删除 / 要求保留手工单次调用 / 反对局部命名 `attributeHook`):退出本计划、修订 `04-14-PLAN.md` 后重新汇报,不得自行改道执行。
</action>
<decision>是否按本计划(04-14:部分 UI 进一步接口适配·第二步·适配实施——D-78 状态栏属性钩子(tracer)+ D-74/D-75 旧加载 UI 改依新加载系统 + D-76 restart 注释删除 + D-77 三处非 UI 残留删除,改动五个生产文件;默认不重新接线旧加载 UI 装配点)开始执行</decision>
<context>
AGENTS.md 要求任何计划的第一个 block 点必须是向用户汇报计划内容并等待用户确认。`.planning/config.json` 为 `mode: yolo`,故该关卡必须显式声明为 `blocking-human` 并置 `autonomous: false`。本计划含三类必须让用户明确知晓的判断:① **会删除既有注释行**(`main.tsx:140-143` 的两行说明、两处「兼容层」标签、`restart` / `createModule` 注释块、`load.tsx:75` 的失效 `@ts-expect-error`)——AGENTS.md 禁止擅自修改既有注释,故须在关卡获批;② **会移除手工单次调用垫片**(状态栏初始填充方式随之改变)——影响行为,须用户知晓;③ **一条开点未裁定**(旧加载 UI 装配 / 可达性)与 **一条名单 / 事实差异**(D-77 列出 `client-modules/src/index.ts` 但该文件现行零残留)。规划日起测的用户并发未提交改动为 `packages/common/src/hook.ts`、`packages/common/src/types.ts`、`.planning/phases/04-render-adaptation/04-CONTEXT.md`(均 ` M`);规划会话期间用户已将前两者提交为 `425ff28 refactor: 删除 hook 的 loadHook 接口`,`04-CONTEXT.md` 仍为 ` M`。本计划对三者一律不触碰、不暂存、不提交。
</context>
<options>
<option id="execute">
<name>可以执行</name>
<pros>按本计划落地:D-78 状态栏属性钩子(tracer)+ D-74/D-75 旧加载 UI 重新适配 + D-76/D-77 删除旧残留;默认不接线装配点(开点保留待裁)</pros>
<cons>无(改动可整体 git revert;正确性由静态门禁 + 人工复核共同证明)</cons>
</option>
<option id="rewire">
<name>可以执行,且一并重新接线旧加载 UI 的装配 / 可达性</name>
<pros>让已重新适配的旧加载 UI 重新可达(例如由 `client-modules/src/index.ts` 的 `create()` 调用 `createGameRenderer()`)</pros>
<cons>超出 D-74..D-78 的裁定范围,属新增设计(接线位置 / 时机需用户拍板);需修订本计划并重新汇报</cons>
</option>
<option id="revise">
<name>修订编辑集 / 删除项 / 局部命名后再执行</name>
<pros>避免带着错误的删除范围或错误命名落地后再返工</pros>
<cons>退出本计划并修订 PLAN.md 后重新汇报,增加一轮往返</cons>
</option>
</options>
<resume-signal>回复「可以执行」四个字(修订意见请按 action ⑨ 处理)</resume-signal>
<verify>
<human-check>用户在对话中明确回复「可以执行」四个字(人工核对用户回复原文;本关卡为人工关卡,无自动命令,失败语义写在 action ⑧ 与 acceptance_criteria 中)</human-check>
</verify>
<acceptance_criteria>
- 用户回复中明确包含「可以执行」四个字
- 汇报涵盖:要解决的问题、**D-74..D-78 逐条裁定要点**与沿用约束、**精确编辑集 A..F**(含将删除的既有注释行与手工调用垫片)、**将新增的局部命名 `attributeHook`**、**开点(旧加载 UI 装配 / 可达性)**与 **D-77 `client-modules/src/index.ts` 零残留事实**、范围与硬边界、验证方式
- 汇报中已原样告知 **D-68 的延续**(没有「保证能运行」的要求;不设 `check:type` / `build` / TS 诊断数类门禁)
- 汇报中已原样告知 **编号与 wave**(04-14 / wave 13 / `depends_on` 04-01..04-13)以及既有 `04-01`..`04-13` 产物不得改写 / 覆盖 / 改号、不得产出 `04-15+`
- 汇报中已原样告知**关卡性质**:执行者 / 子代理必须停下等待,不因触发方式或 `yolo` / `auto_advance` 模式自动放行
</acceptance_criteria>
<done>用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理</done>
</task>
<task type="tracer">
<name>Task 1(tracer):一条线端到端 —— 状态栏更新改在勇士属性对象上注册钩子(D-78):`main.tsx` UI 组件 → `client.hero.attribute`(`IHookable`)→ `onUpdateAttribute` 触发点,同时移除全局 hook 残留与手工调用垫片</name>
<precondition>Task 0 已闭环:用户在对话中明确回复「可以执行」四个字(未收到则不得开始本任务);且用户已批准编辑集 A(含 `:140-143` 垫片与 `:145-152` 注释块的删除)与局部命名 `attributeHook`。</precondition>
<files>packages-user/client-modules/src/render/ui/main.tsx</files>
<read_first>
- packages-user/client-modules/src/render/ui/main.tsx(整文件 314 行:`:8` `import { defineComponent, onUnmounted, reactive, ref } from 'vue'`;`:32` `import { client } from '../../core'`;`:96-133` `updateStatus`;`:135-138` `updateDataFallback`;`:140-143` 手工单次调用垫片 + 两行既有说明注释;`:145-152` 被注释的全局 hook 监听 / 注销块——**本任务的全部改动面**)
- packages-user/data-base/src/hero/types.ts:95-106(`IHeroAttributeHooks<THero>` 的 `onUpdateAttribute?<K extends keyof THero>(name: K, value: THero[K]): void`;`IReadonlyHeroAttribute<THero> extends IHookable<IHeroAttributeHooks<THero>>`)与 :921-932(`IHeroState<THero>.attribute: IReadonlyHeroAttribute<THero>`)
- packages-user/data-base/src/hero/attribute.ts:63-90(`recalculateAttribute` 内 `:89` `this.forEachHook(hook => hook.onUpdateAttribute?.(name, value))`——触发点)
- packages/common/src/types.ts:76-104(`IHookable.addHook(hook: Partial<H>): C` / `removeHookByController(hook: C): void`)与 :44-60(`IHookController.unload(): void`)与 packages/common/src/hook.ts:23-32,63-72(`addHook` 实现 / `HookController.unload`)
- packages-user/data-state/src/core.ts:89,145-147(`readonly hero: IHeroState<IHeroAttr>` 装配)与 packages-user/client-modules/src/client.ts:41(`ClientCore extends CoreState`)
- AGENTS.md(不得擅自修改既有注释;命名需先获批)与 dev.md(CRLF;不得 `import type`)
</read_first>
<action>
(0) **复验前置(只读,不改任何文件)**:`git grep -n "createGameRenderer" -- packages-user packages src`(规划日仅命中 `render/index.tsx:13`);`git status --porcelain -- packages-user/client-modules/src/render/ui/main.tsx`(本任务开始前应为空);确认 `packages/common/src/{hook.ts,types.ts}` 的 `IHookable.addHook` / `IHookController.unload` 形状仍如 read_first 所列——若已变化,**停下向用户说明,不得臆造替代 API**。
(1) **替换状态栏监听接线(D-78,tracer 的正题)**:把 `main.tsx` 中被注释的全局 hook 监听 / 注销块所在区域(`:145-152`)改为在勇士属性对象上注册钩子:
- 取对象:`client.hero`(`CoreState.hero`)→ `client.hero.attribute`(`IReadonlyHeroAttribute<IHeroAttr>`,`IHookable<IHeroAttributeHooks<IHeroAttr>>`);
- 注册:`const attributeHook = client.hero.attribute.addHook({ onUpdateAttribute: () => { updateStatus(); updateDataFallback(); } });`(局部常量名 **`attributeHook`**,已经 Task 0 获批);
- 注销:在既有 `onUnmounted` 回调内调用 `attributeHook.unload()`(`IHookController.unload` → `removeHookByController`),替换原被注释的全局 hook 注销行;
- 注意:`onUpdateAttribute` 的参数名是属性名、值是属性值(`attribute.ts:89` 处 `name` / `value`);本接线**无需**消费这两个参数即可照常更新状态栏(处理器按现值整体刷新),**不得**为此新增任何接口或改动数据端。
- 既有注释 `// 监听状态栏更新事件`(`:145`)**保留**(不得改动既有注释);如需为新代码加说明注释,新增即可。
(2) **移除手工单次调用垫片(`:140-143`)**:删除该区块的两行既有说明注释(「保留函数调用,避免被识别为未使用函数而报错」与「后续修改后需要删除」)与 `updateDataFallback();`、`updateStatus();` 两行——注册钩子后两函数均被使用,垫片按其自身注释所述「修改后删除」不再需要。**这是本任务被批准删除的仅有两处既有注释。**
(3) **确认零残留**:`main.tsx` 内不得再出现全局 hook 的监听 / 注销调用,也不得再出现全局 hook 标识符(`attributeHook.unload()` 内的 `Hook.` 为局部常量,不是全局 hook 引用)。`updateStatus` / `updateDataFallback` 的函数体(`:96-138`)**一律不动**(其中的 `core.*` 存量使用属 legacy,D-49 / D-55 / D-65 不管)。
(4) **CRLF 与收口**:确认 `main.tsx` 换行为 CRLF;本任务只允许编辑 `main.tsx`,**不得**改任何其它文件、不得新增计划文件、不得派发会写文件的子代理;**不得**执行 `pnpm check:type` / `pnpm build`。
</action>
<reversibility rating="reversible">本任务只改一个 UI 组件的钩子接线(在已存在的 `IHookable` 对象上注册 / 注销),无契约 / 命名 / 依赖变更,可用 `git revert` 或重跑本任务回退。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const f='packages-user/client-modules/src/render/ui/main.tsx';const t=fs.readFileSync(f,'utf8');const need=['client.hero.attribute','addHook(','onUpdateAttribute','attributeHook','unload()'];const miss=need.filter(k=>t.indexOf(k)<0);if(miss.length>0){console.error('D78 WIRING MISSING :: '+miss.join(' | '));process.exit(1)}console.log('OK D-78 attribute hook wired');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "D78 WIRING MISSING"(`main.tsx` 未在 `client.hero.attribute` 上注册 `onUpdateAttribute` 钩子,或未保留控制器 / 未注销)</fails_when>
<automated>node -e "const fs=require('fs');const f='packages-user/client-modules/src/render/ui/main.tsx';const t=fs.readFileSync(f,'utf8');const bad=[];if(t.indexOf('hook.on(')>=0)bad.push('GLOBAL HOOK LISTEN LEFT');if(t.indexOf('hook.off(')>=0)bad.push('GLOBAL HOOK OFF LEFT');if(t.indexOf('\u540e\u7eed\u4fee\u6539\u540e\u9700\u8981\u5220\u9664')>=0)bad.push('SHIM COMMENT LEFT');if(bad.length>0){console.error('RESIDUE LEFT :: '+bad.join(' ; '));process.exit(1)}console.log('OK no global hook residue, no shim');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "RESIDUE LEFT" / "GLOBAL HOOK LISTEN LEFT" / "GLOBAL HOOK OFF LEFT" / "SHIM COMMENT LEFT"(全局 hook 监听 / 注销残留未清,或手工调用垫片及其说明注释仍在)</fails_when>
<automated>node -e "const{execSync}=require('child_process');const o=execSync('git status --porcelain -- packages-user/client-base/src/material packages packages/ src packages-user/data-base packages-user/data-state packages/loader packages/common',{encoding:'utf8'}).replace(/[ \t\r\n]+$/,'');if(o.length>0){console.error('FORBIDDEN/MATERIAL/DATA TREE TOUCHED :: '+o);process.exit(1)}console.log('OK forbidden paths clean');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "FORBIDDEN/MATERIAL/DATA TREE TOUCHED"(触碰了 `material/` / `packages/` / `src/` / 数据端 / `packages/common` —— 违反 D-42 / D-48 / D-33 / D-56)</fails_when>
<automated>node -e "const fs=require('fs');const f='packages-user/client-modules/src/render/ui/main.tsx';const s=fs.readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF '+f);process.exit(1)}console.log('OK CRLF main.tsx');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF"(被编辑文件出现非 CRLF 换行,违反 dev.md)</fails_when>
</verify>
<acceptance_criteria>
- `main.tsx` 在 `client.hero.attribute` 上以 `addHook({ onUpdateAttribute: ... })` 注册钩子,控制器存为局部常量 `attributeHook`,并在 `onUnmounted` 中 `unload()`
- `main.tsx` 内不再出现全局 hook 的监听 / 注销调用;`:140-143` 的手工单次调用垫片及其两行既有说明注释已移除
- `updateStatus` / `updateDataFallback` 的函数体未被改动;`client.hero.attribute.getFinalAttribute(...)` 等既有读取保持
- 未触碰 `material/` / `packages/` / `src/` / 数据端 / `packages/common`;`main.tsx` 为 CRLF;未执行 `check:type` / `build`
</acceptance_criteria>
<done>端到端一条线走通:状态栏更新已从「全局 hook 监听」改为「在勇士属性对象上注册 `onUpdateAttribute` 钩子」并在卸载时注销;垫片与全局 hook 残留清除;门禁(接线 / 残留 / 禁用路径 / CRLF)全绿;后续任务只需按各自裁定横向落地其余改动</done>
</task>
<task type="auto">
<name>Task 2:旧加载 UI 改依新加载系统(D-74 / D-75)—— `load.tsx` 以 `client.loader.start()` 的 `AsyncIterable` 取代旧加载三件套,进度数据一律取自 `client.loader.manager` 的既有方法</name>
<precondition>Task 0 已闭环(用户已回复「可以执行」)且用户已批准编辑集 B(含 `:75` 失效 `@ts-expect-error` 的删除)。</precondition>
<files>packages-user/client-modules/src/render/ui/load.tsx</files>
<read_first>
- packages-user/client-modules/src/render/ui/load.tsx(整文件 165 行:`:36` `const loader = client.loader;`;`:63-68` `loadEnd`;`:70-83` `startLoad`(`:71` 旧系统加载任务初始化调用、`:72` 旧加载调用 + `.then(loadEnd)`、`:75` 失效 `@ts-expect-error`、`:76-79` 旧 `progress` 异步迭代与 `getLoadedTasks` / `getLoadedByte`);`:86-110` `renderTaskList`(`:91-92`);`:112-145` `renderByteList`(`:118-119,139`)——**本任务的全部改动面**)
- packages-user/data-state/src/loader/types.ts:19-31,84-142(`IMotaDataLoader`:`readonly manager: ILoadManager`、`start(): AsyncIterable<number>`、`loaded(): Promise<void>`;`MotaDataLoader.start()` 三段 `yield* this.manager.load()`)
- packages/loader/src/types.ts:288-335(`ILoadManager`:`getLoadedByte` / `getTotalByte` / `getByteRatio` / `getLoadedTasks` / `getAddedTasks` / `getTaskRatio`)与 packages/loader/src/manager.ts:88-111(实现)
- packages-user/data-state/src/types.ts:29(`ICoreState.loader: IMotaDataLoader`)与 packages-user/client-modules/src/client.ts:131(`this.loader.addCoreConfig('client', config.clientURL)`)
- AGENTS.md(不得擅自修改既有注释)与 dev.md(CRLF;不得 `import type`)
</read_first>
<action>
(0) **复验前置(只读)**:确认 `client.loader` 类型为 `IMotaDataLoader`(`packages-user/data-state/src/types.ts:29`)且 `IMotaDataLoader` 上存在 `start()` 与 `manager`(`packages-user/data-state/src/loader/types.ts:21,26`);确认 `start()` 的迭代值语义为加载进度数值、迭代结束即全部加载完成,故**迭代结束后**调用既有 `loadEnd()`(原 `.then(loadEnd)` 的等价)。
(1) **重写 `startLoad`(`:70-83`)**:删除旧的系统加载任务初始化调用(`:71`)与旧的加载调用 + `.then(loadEnd)`(`:72`);改为 `for await (const _ of loader.start()) { taskProgress.set(loader.manager.getLoadedTasks()); byteProgress.set(loader.manager.getLoadedByte()); }`,循环后调用 `loadEnd()`。删除 `:75` 的失效 `@ts-expect-error`(其理由文本为「需要重构」——代码修正后该注解不再需要;**这是本任务唯一被批准的既有注释删除**)。循环变量沿用原形态 `_`(不引入新命名)。
(2) **`renderTaskList`(`:86-110`)**:把旧的 `progress` 成员访问(`:91` 的任务数、`:92` 的已添加任务数)改为 `loader.manager` 的对应方法(`getLoadedTasks` / `getAddedTasks`);其余绘制逻辑与既有注释**一律不动**。
(3) **`renderByteList`(`:112-145`)**:把旧的 `progress` 成员访问(`:118` 的总字节、`:119` 的已加载字节、`:139` 的字节比)改为 `loader.manager` 的对应方法(`getTotalByte` / `getLoadedByte` / `getByteRatio`);其余绘制逻辑与既有注释**一律不动**。
(4) **不得做的事**:不得改动数据端(`packages/loader/**`、`packages-user/data-state/**`);不得因 `LoadManager.getTotalByte` 实现返回 `this.loaded` 而自行「修正」语义(数据端语义归用户);不得为 `loader.manager` 的取值另行缓存 / 包装 / 新增成员;不得使用 `import type`;不得触碰 `render/index.tsx`(装配点属开点,另见 Task 0)。
(5) **CRLF 与收口**:确认 `load.tsx` 为 CRLF;本任务只允许编辑 `load.tsx`,**不得**改任何其它文件、不得新增计划文件、不得派发会写文件的子代理;**不得**执行 `pnpm check:type` / `pnpm build`。
</action>
<reversibility rating="reversible">本任务只把 UI 的加载读数来源从已删旧接口改到新加载接口的现有方法,无契约 / 命名 / 依赖变更,可用 `git revert` 或重跑本任务回退。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const f='packages-user/client-modules/src/render/ui/load.tsx';const t=fs.readFileSync(f,'utf8');const need=['client.loader','loader.start()','loader.manager.getLoadedTasks','loader.manager.getAddedTasks','loader.manager.getTotalByte','loader.manager.getLoadedByte','loader.manager.getByteRatio','loadEnd()'];const miss=need.filter(k=>t.indexOf(k)<0);if(miss.length>0){console.error('LOADER ADAPT MISSING :: '+miss.join(' | '));process.exit(1)}console.log('OK load UI adapted to new loader');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "LOADER ADAPT MISSING"(`load.tsx` 未以 `start()` + `manager.get*` 完成改指,或 `client.loader` / `loadEnd()` 丢失)</fails_when>
<automated>node -e "const fs=require('fs');const f='packages-user/client-modules/src/render/ui/load.tsx';const t=fs.readFileSync(f,'utf8');const bad=[];if(t.indexOf('loader.progress')>=0)bad.push('OLD PROGRESS MEMBER LEFT');if(t.indexOf('initSystemLoadTask')>=0)bad.push('OLD INIT LEFT');if(t.indexOf('@ts-expect-error \u9700\u8981\u91cd\u6784')>=0)bad.push('STALE TS-EXPECT-ERROR LEFT');if(bad.length>0){console.error('OLD INTERFACE RESIDUE :: '+bad.join(' ; '));process.exit(1)}console.log('OK no old loader interface residue');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "OLD INTERFACE RESIDUE" / "OLD PROGRESS MEMBER LEFT" / "OLD INIT LEFT" / "STALE TS-EXPECT-ERROR LEFT"(旧加载接口成员或失效 `@ts-expect-error` 仍在)</fails_when>
<automated>node -e "const{execSync}=require('child_process');const o=execSync('git status --porcelain -- packages-user/client-base/src/material packages packages/ src packages-user/data-state packages/loader packages-user/data-base packages/common',{encoding:'utf8'}).replace(/[ \t\r\n]+$/,'');if(o.length>0){console.error('FORBIDDEN PATH TOUCHED :: '+o);process.exit(1)}console.log('OK forbidden paths clean');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "FORBIDDEN PATH TOUCHED"(触碰 `material/` / `packages/` / `src/` / 数据端 / `packages/common` —— 违反 D-42 / D-48 / D-33 / D-56)</fails_when>
<automated>node -e "const fs=require('fs');const f='packages-user/client-modules/src/render/ui/load.tsx';const s=fs.readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf){console.error('NON-CRLF '+f);process.exit(1)}console.log('OK CRLF load.tsx');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "NON-CRLF"(`load.tsx` 出现非 CRLF 换行)</fails_when>
</verify>
<acceptance_criteria>
- `load.tsx` 以 `client.loader.start()` 的异步迭代取代旧的系统加载任务初始化 + 加载调用 + `progress` 迭代三件套,循环后调用 `loadEnd()`
- `renderTaskList` / `renderByteList` 的进度读数一律取自 `client.loader.manager` 的既有方法;不再出现旧接口成员
- `:75` 的失效 `@ts-expect-error`(理由「需要重构」)已移除;其余既有注释与绘制逻辑未改动
- 未触碰数据端 / `material/` / `packages/` / `src/` / `packages/common`;`load.tsx` 为 CRLF;未执行 `check:type` / `build`
</acceptance_criteria>
<done>旧加载 UI 已按 D-74 / D-75 重新适配:进度 / 字节 / 任务数据全部来自新加载接口的现有方法,异步迭代走 `start()`,旧接口成员与失效 `@ts-expect-error` 清零;门禁(改指 / 残留 / 禁用路径 / CRLF)全绿</done>
</task>
<task type="auto">
<name>Task 3:删除旧内容(D-76 / D-77)—— `render/index.tsx` 的 `restart` 注释块、`client.ts` 的两段被注释兼容层、`entry-client/src/create.ts` 的被注释接线与模块创建函数块;并以核对命令登记 `client-modules/src/index.ts` 零残留</name>
<precondition>Task 0 已闭环(用户已回复「可以执行」)且用户已批准编辑集 C / D / E(含 `restart` 注释块、两处「兼容层」标签、被注释接线与模块创建函数块的删除)。</precondition>
<files>packages-user/client-modules/src/render/index.tsx, packages-user/client-modules/src/client.ts, packages-user/entry-client/src/create.ts</files>
<read_first>
- packages-user/client-modules/src/render/index.tsx(整文件 49 行:`:29-41` `createRender` 内 `:35-38` 被注释的 `restart` 监听块——**删除对象 C**;`:43-49` 7 条同目录 `export *` **一律不动**)
- packages-user/client-modules/src/client.ts(整文件 149 行:`:88-92` 与 `:124-127` 两段被注释的兼容层块(含两处「兼容层」标签)——**删除对象 D**;`:137-148` `initMapExtensions` **保留**;`:39,60,131` 新加载系统接线 **一律不动**)
- packages-user/entry-client/src/create.ts(整文件 41 行:`:16`、`:31` 两行被注释接线与 `:34-41` 被注释的模块创建函数块——**删除对象 E**;`:18-29` 的 `Mota.register(...)` 与 `:1-13` 的 import **一律不动**)
- packages-user/client-modules/src/index.ts(16 行:`create()` 现只 `patchAll()` + `createRender()`;规划日实测无任何被删全局对象的文本——**零残留证据对象 F,不编辑**)
- .planning/phases/04-render-adaptation/04-CONTEXT.md(D-76 / D-77 裁定原文)与 04-UI-ADAPTATION-IMPACT.md 的 `#04-13-A-04` / `#04-13-A-05/06/07`
- AGENTS.md(不得擅自修改既有注释)与 dev.md(CRLF;不得 `import type`)
</read_first>
<action>
(0) **复验前置(只读)**:`git grep -n "createGameRenderer" -- packages-user packages src`(记录当日结果,供 SUMMARY 开点事实);逐文件确认待删块的现行行号与形态(执行日以当日实测为准)。
(1) **删除对象 C(D-76)**:删除 `packages-user/client-modules/src/render/index.tsx` 中被注释的 `restart` 监听块(`:35-38`;该块原为「全局 hook 监听 → `sceneController.closeAll()` + 开启标题界面」)。**不得**改动 `createRender` 的其余调用、`:43-49` 的导出面、任何 import。删除后的 `createRender` 不得留下空注释占位。
(2) **删除对象 D(D-77)**:删除 `packages-user/client-modules/src/client.ts` 中 `:88-92` 与 `:124-127` 两段被注释的兼容层块,**连同两处既有的「兼容层」标签注释**(标签所指内容整体被删,标签本身即残留的一部分)。**保留** `:137-148` 的 `initMapExtensions`(不在名单内,虽已无调用者亦不删);`this.loader.addCoreConfig('client', config.clientURL)`(`:131`)与其余构造逻辑 **一律不动**。
(3) **删除对象 E(D-77)**:删除 `packages-user/entry-client/src/create.ts` 中 `:16`、`:31` 两行被注释接线,以及 `:34-41` 被注释的模块创建函数块。**保留**全部 import 与 `:18-29` 的 `Mota.register(...)`;删除后 `create()` 只保留注册逻辑,不得留下空注释占位。
(4) **零残留证据 F(D-77)**:对 `packages-user/client-modules/src/index.ts` 执行核对命令 `git grep -n -E "hook\.|loading" -- packages-user/client-modules/src/index.ts`,**原样记录输出**(规划日实测为空,退出码 1);本计划对该文件**不做任何编辑**,并在 SUMMARY 中登记「D-77 名单 / 现行事实差异 + 零残留证据」。
(5) **全局零残留复核**:执行 `git grep -n -E "hook\.on\(|hook\.off\(" -- packages-user/client-modules/src packages-user/entry-client/src`,断言为空(`render/use.ts` 内的 `hook` 是函数参数名,不属全局对象,不在命中范围)。
(6) **CRLF 与收口**:确认三个被编辑文件为 CRLF;本任务只允许编辑上述三个文件,**不得**改任何其它文件、不得新增计划文件、不得派发会写文件的子代理;**不得**触碰 barrel 导出面(D-69);**不得**执行 `pnpm check:type` / `pnpm build`。
</action>
<reversibility rating="reversible">本任务仅删除已失效的被注释旧接线(连同其标签注释),无契约 / 命名 / 依赖变更,可用 `git revert` 或重跑本任务回退。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('packages-user/client-modules/src/render/index.tsx','utf8');const bad=[];if(t.indexOf('// hook.')>=0)bad.push('RESTART COMMENT LEFT');if(t.indexOf('export function createRender')<0)bad.push('createRender MISSING');if(t.indexOf(\"'./ui'\")<0)bad.push('BARREL EXPORT DAMAGED');if(bad.length>0){console.error('D76 GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK D-76 restart comment removed');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "D76 GATE FAILED" / "RESTART COMMENT LEFT" / "createRender MISSING" / "BARREL EXPORT DAMAGED"(`restart` 注释块未删、误删 `createRender`,或导出面被破坏 —— 违反 D-76 / D-69)</fails_when>
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('packages-user/client-modules/src/client.ts','utf8');const bad=[];if(t.indexOf('// loading.')>=0)bad.push('CLIENT COMPAT BLOCK LEFT');if(t.indexOf('class ClientCore')<0)bad.push('ClientCore MISSING');if(t.indexOf('initMapExtensions')<0)bad.push('initMapExtensions LOST');if(bad.length>0){console.error('D77-CLIENT GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK D-77 client.ts compat blocks removed');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "D77-CLIENT GATE FAILED" / "CLIENT COMPAT BLOCK LEFT" / "ClientCore MISSING" / "initMapExtensions LOST"(`client.ts` 兼容层注释块未删、误删类定义,或误删不在名单内的 `initMapExtensions`)</fails_when>
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('packages-user/entry-client/src/create.ts','utf8');const bad=[];if(t.indexOf('// loading.')>=0)bad.push('CREATE LOADING LEFT');if(t.indexOf('// async function')>=0)bad.push('CREATEMODULE BLOCK LEFT');if(t.indexOf('export function create')<0)bad.push('create MISSING');if(t.indexOf(\"Mota.register('@user/client-modules'\")<0)bad.push('REGISTER DAMAGED');if(bad.length>0){console.error('D77-CREATE GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK D-77 entry-client/create.ts residues removed');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "D77-CREATE GATE FAILED" / "CREATE LOADING LEFT" / "CREATEMODULE BLOCK LEFT" / "create MISSING" / "REGISTER DAMAGED"(`create.ts` 注释残留未清、误删 `create` 或 `Mota.register` 面)</fails_when>
<automated>node -e "const{execSync}=require('child_process');let status=0,out='';try{out=execSync('git grep -n -E \"hook\\\\.on\\\\(|hook\\\\.off\\\\(\" -- packages-user/client-modules/src packages-user/entry-client/src',{encoding:'utf8'})}catch(e){status=(typeof e.status==='number')?e.status:1;out=String(e.stdout||'')}if(status!==0&&status!==1){console.error('GIT GREP FAILED status='+status);process.exit(1)}if(status===0&&out.trim().length>0){console.error('GLOBAL HOOK RESIDUE :: '+out.trim());process.exit(1)}console.log('OK zero global hook call sites across client-modules + entry-client (git status='+status+')');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "GLOBAL HOOK RESIDUE" / "GIT GREP FAILED"(`client-modules` / `entry-client` 源码面仍有全局 hook 监听 / 注销调用 —— D-76 / D-77 未清干净;或核对命令本身未正常执行)</fails_when>
<automated>node -e "const{execSync}=require('child_process');const o=execSync('git status --porcelain -- packages-user/client-base/src/material packages packages/ src packages-user/data-state packages-user/data-base packages/common',{encoding:'utf8'}).replace(/[ \t\r\n]+$/,'');if(o.length>0){console.error('FORBIDDEN PATH TOUCHED :: '+o);process.exit(1)}const files=['packages-user/client-modules/src/render/index.tsx','packages-user/client-modules/src/client.ts','packages-user/entry-client/src/create.ts'];const bad=[];for(const f of files){const s=require('fs').readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length;const crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf)bad.push('NON-CRLF '+f)}if(bad.length>0){console.error('CRLF FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK forbidden paths clean + CRLF');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "FORBIDDEN PATH TOUCHED" / "CRLF FAILED" / "NON-CRLF"(触碰禁用路径,或被编辑文件出现非 CRLF 换行)</fails_when>
</verify>
<acceptance_criteria>
- `render/index.tsx` 的 `restart` 注释块已删除,`createRender` 与 `:43-49` 导出面完好(D-76)
- `client.ts` 的两段被注释兼容层块(含两处「兼容层」标签)已删除;`initMapExtensions` 与新加载接线保留(D-77)
- `entry-client/src/create.ts` 的被注释接线与模块创建函数块已删除;import 与 `Mota.register(...)` 面完好(D-77)
- `client-modules/src/index.ts` 经核对命令实证零残留且**未被编辑**(D-77 名单 / 事实差异已登记)
- `client-modules` / `entry-client` 源码面零全局 hook 监听 / 注销调用;未触碰禁用路径;被编辑文件为 CRLF
</acceptance_criteria>
<done>旧内容删除完成:`restart` 注释块、两段兼容层注释、`create.ts` 的被注释接线与模块创建函数块清零,`client-modules/src/index.ts` 零残留已取证;barrel 导出面与保留项(`initMapExtensions` / `Mota.register`)未受损;门禁(D-76 / D-77 / 全局零残留 / 禁用路径 / CRLF)全绿</done>
</task>
<task type="auto">
<name>Task 4:收口 —— 编辑集与残留静态门禁 + 范围 / 数据端 / 用户改动门禁 + 既有产物保护 + CRLF + 人工复核 + `04-14-SUMMARY.md` + 白名单提交</name>
<precondition>Task 0 已闭环(用户已回复「可以执行」)且 Task 1 / 2 / 3 均已交付(D-78 接线 + D-74/D-75 改动 + D-76/D-77 删除)。</precondition>
<files>packages-user/client-modules/src/render/ui/main.tsx, packages-user/client-modules/src/render/ui/load.tsx, packages-user/client-modules/src/render/index.tsx, packages-user/client-modules/src/client.ts, packages-user/entry-client/src/create.ts, .planning/phases/04-render-adaptation/04-14-SUMMARY.md</files>
<read_first>
- .planning/phases/04-render-adaptation/04-12-SUMMARY.md 与 04-11-SUMMARY.md(代码改动增量的 SUMMARY 版式:编辑集 / 门禁实测 / 人工复核 / 无运行时验证声明 / Known Stubs / Next Phase Readiness)
- .planning/phases/04-render-adaptation/04-14-PLAN.md(本计划的 must_haves(truths / artifacts / key_links / assumptions / prohibitions)与逐条门禁)
- .planning/ROADMAP.md(Phase 4 的 `**Plans:**` 行、`**Wave 13**` 条目与 04-14 register note —— 计划阶段登记;本任务不重复改 ROADMAP,除非计划阶段未登记)
- packages-user/client-modules/src/render/ui/main.tsx(Task 1 成品)与 load.tsx(Task 2 成品)与 render/index.tsx / client.ts(Task 3 成品)与 packages-user/entry-client/src/create.ts(Task 3 成品)
- dev.md(「其他要求」的 CRLF;不得 `import type`)
- AGENTS.md(提交前核对暂存面;不得触碰用户并发改动)
</read_first>
<action>
(1) **编辑集静态门禁(合并复核五个生产文件的最终形状)**:断言 `main.tsx` 含 `client.hero.attribute` / `addHook(` / `onUpdateAttribute` / `attributeHook` / `unload()`;断言 `load.tsx` 含 `loader.start()` 与 `loader.manager.get*` 五个方法名且不含旧接口成员;断言 `render/index.tsx` / `client.ts` / `create.ts` 不含被删注释形状。**不得**执行 `pnpm check:type` / `pnpm build` / 任何 TS 诊断统计。
(2) **范围门禁**:断言 `git status --porcelain -- packages-user/client-base/src/material packages packages/ src ` 为空;断言本 run 触及的生产文件**仅**为 `files_modified` 列出的五个(`git status --porcelain -- packages-user/client-modules packages-user/entry-client` 的条目集合 ⊆ 五个;提交后为空亦通过);断言 `.planning/REQUIREMENTS.md` porcelain 为空。
(3) **数据端与用户改动门禁(D-56 / D-33)**:断言 `packages-user/data-state` / `packages-user/data-base` / `packages/loader` / `packages/common` porcelain 为空(本 run 未触碰);断言用户并发改动仍在且未被回退 —— `.planning/phases/04-render-adaptation/04-CONTEXT.md` 含 `D-74` 与 `D-78`、`packages/common/src/types.ts` 含 `addHook` 与 `IHookController`、`packages/common/src/hook.ts` 含 `removeHookByController`。
(4) **既有产物保护门禁**:断言 `.planning/phases/04-render-adaptation/` 下 `04-01`..`04-13` 的 `*-PLAN.md` / `*-SUMMARY.md` porcelain 为空;断言目录内不存在 `04-15+` 计划 / SUMMARY 文件。
(5) **CRLF 门禁**:断言五个被编辑生产文件与 `04-14-SUMMARY.md` 均为 CRLF。
(6) **人工复核(不可自动化,如实记录结论)**:逐条打开当日源码核对 —— ① `main.tsx` 的 `addHook({ onUpdateAttribute })` 挂在 `client.hero.attribute`(`IHookable`)上,处理器调用 `updateStatus` / `updateDataFallback`,`onUnmounted` 调 `attributeHook.unload()`;② `load.tsx` 的每个 `loader.manager.get*` 调用与 `ILoadManager`(`packages/loader/src/types.ts:288-335`)成员一一对应,`start()` 的迭代结束即加载完成;③ 三个删除文件的保留项(`createRender`、`initMapExtensions`、`Mota.register(...)`、导出面)完好;④ `client-modules/src/index.ts` 零残留核对命令输出与登记一致。发现偏差即回到相应任务修正,并把结论写入 SUMMARY。
(7) **写 `04-14-SUMMARY.md`**:按 04-11 / 04-12 的代码改动增量版式写 —— frontmatter(`phase` / `plan: 14` / `tags` / `requires` / `provides` / `affects` / `actuals` / `key-files`(created / modified / deleted)/ `key-decisions` / `requirements-completed: []` / `duration` / `completed` / `status`)+ 「要解决的问题」+ 编辑集(A..F 逐条,含删除项与新增局部命名)+ 门禁实测结果表 + 人工复核结论 + Decisions Made + Deviations(如有)+ **Known Stubs / 开点(旧加载 UI 装配 / 可达性未接线;`client-modules/src/index.ts` 名单 / 事实差异;`client.ts` 的 `initMapExtensions` 删除注释后无调用者)** + 无运行时验证声明(D-68 的延续)+ Next Phase Readiness(`REND-01` / `REND-02` 保持 Pending)。
(8) **白名单提交(单次)**:核对 `git status`,**只**暂存 `files_modified` 列出的五个生产文件与 `04-14-SUMMARY.md`(若 `.planning/ROADMAP.md` 的 04-14 登记在计划阶段未完成,则一并只暂存 `.planning/ROADMAP.md`)——**绝不**暂存 `packages/common/src/hook.ts`、`packages/common/src/types.ts`、`.planning/phases/04-render-adaptation/04-CONTEXT.md` 或任何其它文件;提交信息形如 `feat(04-14): 部分 UI 进一步接口适配·第二步(D-74..D-78)`。**不得**借提交顺带格式化 / 重排无关文件;**不得** amend 任何既有提交。
(9) **不写其它 `.planning/` 文件**:本任务除 `04-14-SUMMARY.md` 与(如需)`ROADMAP.md` 登记外**不得**写 `.planning/STATE.md` / `WINDOWS.md` / `REQUIREMENTS.md`。
</action>
<reversibility rating="reversible">本任务是收口(静态门禁 + 文档 + 一次白名单提交);提交内容为五个生产文件的接口适配与 SUMMARY,可用 `git revert` 整体回退。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const t=fs.readFileSync('packages-user/client-modules/src/render/ui/main.tsx','utf8');const l=fs.readFileSync('packages-user/client-modules/src/render/ui/load.tsx','utf8');const bad=[];['client.hero.attribute','addHook(','onUpdateAttribute','attributeHook','unload()'].forEach(k=>{if(t.indexOf(k)<0)bad.push('main:'+k)});['loader.start()','loader.manager.getLoadedTasks','loader.manager.getAddedTasks','loader.manager.getTotalByte','loader.manager.getLoadedByte','loader.manager.getByteRatio','loadEnd()'].forEach(k=>{if(l.indexOf(k)<0)bad.push('load:'+k)});if(l.indexOf('loader.progress')>=0)bad.push('load:OLD PROGRESS STILL THERE');if(t.indexOf('hook.on(')>=0||t.indexOf('hook.off(')>=0)bad.push('main:GLOBAL HOOK STILL THERE');if(bad.length>0){console.error('EDIT SET GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK edit set: D-78 wiring + D-74/D-75 adaptation');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "EDIT SET GATE FAILED" / "main:" / "load:" / "OLD PROGRESS STILL THERE" / "GLOBAL HOOK STILL THERE"(五个生产文件的最终形状与 D-74 / D-75 / D-78 不符)</fails_when>
<automated>node -e "const{execSync}=require('child_process');const q=p=>execSync('git status --porcelain -- '+p,{encoding:'utf8'}).replace(/[ \t\r\n]+$/,'');const bad=[];if(q('packages-user/client-base/src/material packages packages/ src').length>0)bad.push('MATERIAL/PKGS/SRC TOUCHED');if(q('packages-user/data-state packages-user/data-base packages/loader packages/common').length>0)bad.push('DATA SIDE TOUCHED');if(q('.planning/REQUIREMENTS.md').length>0)bad.push('REQUIREMENTS TOUCHED');if(bad.length>0){console.error('SCOPE GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK scope: material/packages/src + data side + REQUIREMENTS untouched');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "SCOPE GATE FAILED" / "MATERIAL/PKGS/SRC TOUCHED" / "DATA SIDE TOUCHED" / "REQUIREMENTS TOUCHED"(触碰禁用路径 / 数据端 / 需求文件 —— 违反 D-42 / D-48 / D-33 / D-56)</fails_when>
<automated>node -e "const fs=require('fs');const bad=[];const ctx=fs.readFileSync('.planning/phases/04-render-adaptation/04-CONTEXT.md','utf8');if(ctx.indexOf('D-74')<0||ctx.indexOf('D-78')<0)bad.push('USER CONTEXT EDIT LOST');const ty=fs.readFileSync('packages/common/src/types.ts','utf8');if(ty.indexOf('addHook')<0||ty.indexOf('IHookController')<0)bad.push('USER COMMON/TYPES EDIT LOST');const hk=fs.readFileSync('packages/common/src/hook.ts','utf8');if(hk.indexOf('removeHookByController')<0)bad.push('USER COMMON/HOOK EDIT LOST');if(bad.length>0){console.error('USER EDITS GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK user concurrent edits preserved');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "USER EDITS GATE FAILED" / "USER CONTEXT EDIT LOST" / "USER COMMON"(用户并发改动被回退 / 覆盖 —— 违反 D-56)</fails_when>
<automated>node -e "const{execSync}=require('child_process');const subj=execSync('git log -1 --pretty=format:%s',{encoding:'utf8'}).trim();const bad=[];if(subj.indexOf('04-14')<0)bad.push('LAST COMMIT NOT 04-14 :: '+subj);const names=execSync('git log -1 --name-only --pretty=format:',{encoding:'utf8'}).split(/\r?\n/).filter(Boolean);const allow=new Set(['packages-user/client-modules/src/render/ui/main.tsx','packages-user/client-modules/src/render/ui/load.tsx','packages-user/client-modules/src/render/index.tsx','packages-user/client-modules/src/client.ts','packages-user/entry-client/src/create.ts','.planning/phases/04-render-adaptation/04-14-SUMMARY.md','.planning/phases/04-render-adaptation/04-14-PLAN.md','.planning/ROADMAP.md']);const extra=names.filter(n=>!allow.has(n));if(extra.length>0)bad.push('NON-WHITELIST STAGED :: '+extra.join(' ; '));if(bad.length>0){console.error('WHITELIST COMMIT GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK whitelist commit only');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "WHITELIST COMMIT GATE FAILED" / "LAST COMMIT NOT 04-14" / "NON-WHITELIST STAGED"(提交混入用户并发改动或白名单外文件)</fails_when>
<automated>node -e "const fs=require('fs');const path=require('path');const dir='.planning/phases/04-render-adaptation';const files=fs.readdirSync(dir);const newPlan=files.filter(f=>/^04-(1[5-9]|[2-9][0-9])-/.test(f));const bad=[];if(newPlan.length>0)bad.push('NEW PLAN BEYOND 04-14 :: '+newPlan.join(','));const summ='.planning/phases/04-render-adaptation/04-14-SUMMARY.md';if(!fs.existsSync(summ))bad.push('SUMMARY MISSING');else{const s=fs.readFileSync(summ,'utf8');['04-14','D-74','D-75','D-76','D-77','D-78','Pending'].forEach(k=>{if(s.indexOf(k)<0)bad.push('SUMMARY MISSING '+k)});const lf=(s.match(/\n/g)||[]).length,crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf)bad.push('SUMMARY NON-CRLF')}if(bad.length>0){console.error('CLOSURE GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK closure: no 04-15+, SUMMARY present and keyed');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "CLOSURE GATE FAILED" / "NEW PLAN BEYOND 04-14" / "SUMMARY MISSING" / "SUMMARY NON-CRLF"(产出了 `04-15+`,或 `04-14-SUMMARY.md` 缺失 / 缺关键标识 / 非 CRLF)</fails_when>
<automated>node -e "const fs=require('fs');const files=['packages-user/client-modules/src/render/ui/main.tsx','packages-user/client-modules/src/render/ui/load.tsx','packages-user/client-modules/src/render/index.tsx','packages-user/client-modules/src/client.ts','packages-user/entry-client/src/create.ts'];const bad=[];for(const f of files){const s=fs.readFileSync(f,'utf8');const lf=(s.match(/\n/g)||[]).length,crlf=(s.match(/\r\n/g)||[]).length;if(lf!==crlf)bad.push('NON-CRLF '+f)}if(bad.length>0){console.error('CRLF GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK CRLF five edited files');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "CRLF GATE FAILED" / "NON-CRLF"(被编辑生产文件出现非 CRLF 换行,违反 dev.md)</fails_when>
<automated>node -e "const{execSync}=require('child_process');const o=execSync('git status --porcelain -- .planning/phases/04-render-adaptation',{encoding:'utf8'}).replace(/\s+$/,'');const rows=o.length?o.split(/\r?\n/):[];const existing=rows.filter(r=>/04-(0[1-9]|1[0-3])-(PLAN|SUMMARY)\.md$/.test(r));if(existing.length>0){console.error('ARTIFACT PROTECTION FAILED :: '+existing.join(' ; '));process.exit(1)}console.log('OK existing 04-01..04-13 artifacts untouched');"</automated>
<fails_when>non-zero exit, 或 stderr 含 "ARTIFACT PROTECTION FAILED"(改写 / 覆盖了既有 `04-01`..`04-13` 产物)</fails_when>
</verify>
<acceptance_criteria>
- **编辑集门禁**:五个生产文件最终形状符合 D-74 / D-75 / D-78,D-76 / D-77 删除项清零
- **范围门禁**:`material/` / `packages/` / `src/` / 数据端 / `packages/common` / `.planning/REQUIREMENTS.md` porcelain 为空;本 run 生产改动 ⊆ `files_modified`
- **用户改动门禁**:`04-CONTEXT.md`(D-74..D-78)与 `packages/common/src/{hook.ts,types.ts}` 的并发改动内容未被回退 / 覆盖(内容判据;两处 common 文件已被用户自行提交为 `425ff28`)
- **白名单提交**:单次提交只含五个生产文件 + `04-14-SUMMARY.md`(及必要的 ROADMAP 登记)
- **既有产物保护**:`04-01`..`04-13` 未被改写;无 `04-15+`
- **CRLF**:五个被编辑文件与 SUMMARY 均为 CRLF
- `04-14-SUMMARY.md` 已写(含编辑集 / 门禁实测 / 人工复核 / 开点 / 无运行时验证声明);未执行 `check:type` / `build`;未写其它 `.planning/` 文件
</acceptance_criteria>
<done>收口完成:编辑集与残留门禁全绿、范围 / 数据端 / 用户改动门禁通过、既有 `04-01`..`04-13` 产物零触碰、白名单单次提交完成;`04-14-SUMMARY.md` 落盘;开点(旧加载 UI 装配 / 可达性)与名单 / 事实差异如实登记;`REND-01` / `REND-02` 保持 Pending</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| (无新增外部边界) | 本计划只改渲染端 UI 组件的内部接线(读取数据端已存在的 `IHookable` 对象 / 新加载接口成员),不引入外部输入 / 网络 / 进程边界;不新增依赖、不安装包、不执行 `pnpm i` |
| 仓库工作树 → git 提交 | 用户的并发未提交改动(`packages/common/src/hook.ts`、`packages/common/src/types.ts`、`.planning/.../04-CONTEXT.md`,均 ` M`)与本次改动共处同一工作树;若暂存面把控不当,会把用户改动混入本计划提交(D-56) |
| 本计划 → 数据端契约 | 本步只做渲染端接口适应;若执行者顺带「修正」数据端实现(如 `LoadManager.getTotalByte` 的返回值)或新增数据端接口,会越出 D-33 / D-42 的边界 |
| 既有注释 → 编辑集 | AGENTS.md 禁止擅自修改既有注释;本计划批准的删除项若被扩大(顺手改别的注释 / 重排注释),会破坏用户既有表达 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-04-14-01 | Tampering | 数据端 / `material/` / `packages/` / `src/` 越界改动 | high | mitigate | 每个任务的 `<verify>` 均含禁用路径 porcelain 断言(`material` / `packages` / `src` / `data-state` / `data-base` / `loader` / `common`);prohibitions 明列;Task 4 范围门禁 |
| T-04-14-02 | Tampering | 提交暂存面(`git add`)混入用户并发改动 | high | mitigate | Task 4 步骤 (8) 明示只暂存五个生产文件 + SUMMARY(及必要 ROADMAP);`<verify>` 断言 HEAD 提交名 ⊆ 白名单且提交信息含 `04-14`;prohibitions 明列 D-56 |
| T-04-14-03 | Tampering | 既有注释被超范围修改 | medium | mitigate | Task 0 逐条列出被批准删除的注释行并要求用户同意;Task 4 人工复核保留项完好;prohibitions 明限删除项 |
| T-04-14-04 | Tampering | 既有 `04-01`..`04-13` 产物 / 数据端契约被改写 | medium | mitigate | Task 4 既有产物保护门禁(porcelain + 无 `04-15+`);prohibitions 明禁改写 / 新增数据端接口 |
| T-04-14-05 | Spoofing | 「无运行时」被用来弱化验证(伪造绿) | medium | mitigate | 每条 `<automated>` 必带 `<fails_when>`;D-68 明禁 `check:type` / `build` / TS 诊断数类门禁;Task 4 人工复核并写结论 |
| T-04-14-06 | Denial of Service | 引入运行时回归却无测试设施可测(渲染端无测试) | low | accept | 用户明确不要求「保证能运行」(D-68);本步为接口适应,正确性由静态门禁 + 人工复核 + 后续用户实测承担;`REND-01` / `REND-02` 保持 Pending |
| T-04-14-SC | Tampering | npm/pip/cargo 安装 | low | accept | 本计划无任何依赖新增 / 变更、不改 `package.json` / `pnpm-lock.yaml`、不执行 `pnpm i`,故无供应链安装面;`T-04-14-SC` 保留为「本 run 不适用」的显式登记 |
</threat_model>
<verification>
**Static-only(D-68 的延续:不设 `check:type` / `build` / TS 诊断数类门禁)**
- [ ] Task 0:用户明确回复「可以执行」四个字(`blocking-human` 关卡;含编辑集 / 局部命名 / 开点获批)
- [ ] Task 1 门禁:`main.tsx` 在 `client.hero.attribute` 上注册 `onUpdateAttribute` 钩子 + 局部 `attributeHook` + `unload()`;无全局 hook 残留与垫片;禁用路径 clean;CRLF
- [ ] Task 2 门禁:`load.tsx` 用 `start()` + `loader.manager.get*` 改指;无旧接口成员与失效 `@ts-expect-error`;禁用路径 clean;CRLF
- [ ] Task 3 门禁:`restart` 注释块 / 两段兼容层 / `create.ts` 被注释接线与模块创建块删除;保留项完好;`client-modules` + `entry-client` 零全局 hook 调用;禁用路径 clean;CRLF
- [ ] Task 4 门禁:编辑集最终形状 + 范围 / 数据端 / 用户改动 + 白名单提交 + 既有产物保护 + CRLF + `04-14-SUMMARY.md` 落盘
- [ ] 人工代码复核结论写入 SUMMARY
- [ ] 开点(旧加载 UI 装配 / 可达性)与 D-77 名单 / 事实差异如实登记;`REND-01` / `REND-02` 保持 Pending
</verification>
<success_criteria>
- **D-78 落地(tracer)**:状态栏更新改为在 `client.hero.attribute` 上 `addHook({ onUpdateAttribute })`,并在 `onUnmounted` 注销;`main.tsx` 无全局 hook 残留、无手工调用垫片
- **D-74 / D-75 落地**:旧加载 UI 的进度 / 字节 / 任务数据全部取自 `client.loader.manager` 的现有方法,异步迭代走 `client.loader.start()`;`load.tsx` 无旧接口成员、无失效 `@ts-expect-error`
- **D-76 / D-77 落地**:`restart` 注释块、`client.ts` 两段兼容层注释、`entry-client/src/create.ts` 被注释接线与模块创建函数块全部删除;`client-modules/src/index.ts` 零残留已取证
- **范围零外溢**:`material/` / `packages/` / `src/` / 数据端 / `packages/common` / 测试文件 / 任何 barrel 导出面全部零触碰;无依赖变更、不执行 `pnpm i`
- **用户改动保留**:`packages/common/src/{hook.ts,types.ts}` 与 `04-CONTEXT.md` 的并发改动未被修改 / 暂存 / 提交(D-56)
- **命名受控**:只新增局部常量 `attributeHook`(Task 0 获批);不新增 / 改名任何公共 / 受保护 / 私有成员
- **无运行时要求**:全部证明为静态门禁 + 人工复核;`REND-01` / `REND-02` 保持 Pending,`.planning/REQUIREMENTS.md` 零改动
- **开点如实保留**:旧加载 UI 的装配 / 可达性未被私自接线;`client-modules/src/index.ts` 的名单 / 事实差异已登记待裁
</success_criteria>
## Artifacts this phase produces
| Artifact | Kind | Purpose |
|---|---|---|
| `packages-user/client-modules/src/render/ui/main.tsx`(修改) | 生产代码 | D-78:删除全局 hook 状态栏监听残留与手工调用垫片,改为在勇士属性对象上注册 `onUpdateAttribute` 钩子并在卸载时注销 |
| `packages-user/client-modules/src/render/ui/load.tsx`(修改) | 生产代码 | D-74 / D-75:旧加载 UI 改依 `client.loader.start()` + `client.loader.manager.get*`;移除旧接口成员与失效 `@ts-expect-error` |
| `packages-user/client-modules/src/render/index.tsx`(修改) | 生产代码 | D-76:删除被注释的 `restart` 监听块 |
| `packages-user/client-modules/src/client.ts`(修改) | 生产代码 | D-77:删除两段被注释的兼容层残留(含标签) |
| `packages-user/entry-client/src/create.ts`(修改) | 生产代码 | D-77:删除被注释的接线与模块创建函数块 |
| `.planning/phases/04-render-adaptation/04-14-SUMMARY.md`(新增) | 文档 | 编辑集 / 门禁实测 / 人工复核 / 开点 / 无运行时验证声明 |
| `.planning/ROADMAP.md`(Wave 13 + 04-14 register note,计划阶段完成) | 文档 | `04-14` 的增量登记 |
| 未改动(显式保留) | — | `packages-user/client-modules/src/index.ts`(零残留,不编辑)、`packages-user/client-modules/src/render/ui/index.ts` 与其它 barrel、`packages-user/client-base/src/**`(含 `material/**`)、`packages-user/data-base/src/**`、`packages-user/data-state/src/**`、`packages/loader/src/**`、`packages/**`、`src/**`、`packages/common/src/{hook.ts,types.ts}`(用户并发改动)、`.planning/REQUIREMENTS.md` |
<output>
Create `.planning/phases/04-render-adaptation/04-14-SUMMARY.md` when done
</output>

View File

@ -44,5 +44,5 @@
"label": "Advance to the next step",
"reason": "Phase 04 of 7 · executing"
},
"updated_at": "2026-09-30T06:35:14.551Z"
"updated_at": "2026-09-30T08:10:04.411Z"
}