docs(09): add task 2 plan 09-05 (migration UI repair) and mark progress

This commit is contained in:
unanmed 2026-10-10 14:59:31 +08:00
parent 6f3095cb9b
commit cdfe7ffc76
3 changed files with 442 additions and 2 deletions

View File

@ -481,7 +481,7 @@ Plans:
**Goal**: 在已完成的数据端(可在 Node 环境独立运行/回放验证)之上完成渲染端框架,产出可在浏览器中运行并验证的最小版本
**Requirements**: TBD
**Depends on**: Phase 8
**Plans**: 4/4 plans executed (任务 1·UI 接口适配,D-10..D-14;本阶段增量派发,其余任务待用户再派发)
**Plans**: 5/5 plans executed (任务 1·UI 接口适配,D-10..D-14 已闭环;任务 2·迁移后 UI 修复,D-15..D-17,09-05 待执行;本阶段增量派发,其余任务待用户再派发)
Plans:
**Wave 1**
@ -500,6 +500,10 @@ Plans:
- [x] 09-04-PLAN.md — 任务 1·D-12(定向重设计):把 `leftStatus` / `rightStatus` 迁入 `statusBar.tsx` 由状态栏组件自持 + 属性 9 字段与 `lv` 走 `hero.attribute` 钩子、`floor` 走 `hero.location` 钩子、无钩子字段 `// TODO` + 清理 `main.tsx`
**Wave 5** *(blocked on Wave 4 completion)*
- [x] 09-05-PLAN.md — 任务 2·A + B + C:prettier 格式化 `background.tsx`/`statusBar.tsx`(D-15)+ `IUIController`/`UIController` 补 `<IGameUIPropsBase>`(14 处调用点 + 2 构造点,D-16)+ `src/ui/**` 迁移遗留类型错误修复(`main.tsx` `p`→`props`;`statusBar.tsx`/`toolbar.tsx` props 基类型归位;`settings.tsx` `loc` 窄化,D-17)+ 定向 `vue-tsc`/prettier 门禁
## Progress
**Execution Order:**
@ -515,4 +519,4 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 →
| 6. 单元测试 | 18/18 | Obsolete(由 Phase 9 承接) | - |
| 7. 数据端缺陷修复 | 15/16 | Complete(07-16 superseded) | - |
| 8. 测试重构与接口对齐 | 11/11 | Complete | 2026-10-08 |
| 9. 最小浏览器可运行版本 | 4/4 | In Progress| |
| 9. 最小浏览器可运行版本 | 5/5 | In Progress| |

View File

@ -0,0 +1,428 @@
---
phase: 09-minimum-browser-runnable
plan: 5
type: execute
wave: 5
depends_on:
- 09-04
files_modified:
- packages-user/client-modules/src/ui/background.tsx
- packages-user/client-modules/src/ui/statusBar.tsx
- packages-user/client-modules/src/ui/main.tsx
- packages-user/client-modules/src/ui/toolbar.tsx
- packages-user/client-modules/src/ui/settings.tsx
- packages-user/client-modules/src/ui/save.tsx
- packages-user/client-modules/src/ui/statistics.tsx
- packages-user/client-modules/src/ui/viewmap.tsx
- packages-user/client-modules/src/ui/controller.tsx
- packages-user/client-modules/src/render/scene.ts
- packages-user/client-modules/src/render/utils/saves.ts
- packages-user/client-base/src/components/choices.tsx
- packages-user/client-base/src/components/input.tsx
- packages-user/client-base/src/components/misc.tsx
- .planning/phases/09-minimum-browser-runnable/09-05-SUMMARY.md
autonomous: false
requirements: []
estimate:
tokens: 62000
raw_tokens: 62000
tasks: 3
confidence: low
must_haves:
truths:
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03)**:Task 0 必须先汇报「任务 2 = A(prettier 格式化)/ B(`IUIController`/`UIController` 泛型适配)/ C(`src/ui/**` 迁移遗留类型错误)」三项的范围、**逐条 `文件:行 → 错误码 → 拟修法` 清单(本计划核心产出)**、验证方式(定向 `vue-tsc` + `prettier --check`,非全仓零错误门禁)、硬边界与开放项,然后必须**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行本计划任何一步、不得派发执行子代理,且不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行。"
- "**D-15(prettier):`prettier --check` 对 `packages-user/client-modules/src/ui/background.tsx` 与 `statusBar.tsx` 通过**(执行前实测二者均 warn;`prettier --write` 后 `--check` 零 warn)。纯格式改动,不改逻辑、不改语义。"
- "**D-16(IUIController 泛型适配):`@motajs/system` 的 `IUIController<PB>` / `UIController<PB>` 为必填泛型,本计划为所有未适配使用点补 `<IGameUIPropsBase>`**:`ui/save.tsx`(495/545/569/593)、`ui/settings.tsx`(680/691)、`ui/statistics.tsx`(283)、`ui/viewmap.tsx`(559)、`render/utils/saves.ts`(125);`client-base` 侧 `components/choices.tsx`(598/651/710/765)、`components/input.tsx`(547/584)、`components/misc.tsx`(571)。两类模块的 `IGameUIPropsBase` 引入来源分别为 `@user/client-base`(client-modules)与 `../types`(client-base 组件)。"
- "**D-16 构造点(实测为 TS2554,非仅缺泛型):`render/scene.ts:3` 与 `ui/controller.tsx:4` 现为 `new UIController('main-scene')` / `new UIController('main-ui')`;`UIController` 构造器 `constructor()` 零参且 `PB` 必填**。修法 = 改为 `new UIController<IGameUIPropsBase>()`(补泛型 + 去掉无害的悬空字符串实参——字符串在旧构造器被忽略、运行时无行为差异)。**模块级单例 `sceneController` / `mainUIController` 的存废/改名属 Phase 4 遗留未决项,本计划不删、不改名、不迁移**。"
- "**D-17(`packages-user/client-modules/src/ui/**` 迁移遗留类型错误)**:`main.tsx`(56 `p` 未定义;138/184 `<LeftStatusBar>`/`<RightStatusBar>` props 缺 `controller`/`instance`)、`settings.tsx`(81/86/90/94/371/385/393 `loc` 不存在于 `UIRawProps<..., MainSettingsProps>`)、`statusBar.tsx`(521 `<MixedToolbar>` props 缺 `controller`/`instance`)、`toolbar.tsx`(397/403/409 内嵌工具栏 props 缺 `controller`/`instance`)。修复以**定向 `vue-tsc` 断言这些具体错误消失**为门禁,**不**设全仓「零类型错误」门禁。"
- "**C 的三处根因与修法(实测):** ① `main.tsx:56` 是 09-04 上次迁移遗留——setup 内主对象变量名为 `props`,写成了 `p.state.flags...`;改 `props.state.flags...`。② `statusBar.tsx`/`toolbar.tsx` 的 `StatusBarProps`/`ToolbarProps` 误以 `IUIPropsBase`(含必填 `controller`/`instance`/`state`)为基类型,而这两者仅被**直接内嵌**使用、只消费 `state`(及 `loc`/`hidden`/`status`),并非经 UI 控制器打开;改为以现有名 `IClientBaseExtended`(仅 `state`)+ `DefaultProps` 为基(**不新增命名**)。③ `settings.tsx` 的 `loc` 报错根因是 `props.controller` 的类型来自 `IUIDefaultPropsBase<this>` ⇒ `IUIController<MainSettingsProps>`,使 `UIRawProps<C, MainSettingsProps>` 把 `loc` 一并 `Omit` 掉;修法 = 在 `MainSettings`/`SyncSave` 的 setup 内引入一个基类型控制器的窄化引用(`IUIController<IGameUIPropsBase>`,实测赋值成立、无需 `as`),仅用于开子 UI 的 `.open(...)` 调用。"
- "**范围零外溢 + 定向门禁**:仅改 `files_modified` 白名单;`client-base` 其余错误(`choices.tsx` 665、`input.tsx` 224/561、`misc.tsx` 583、`floorSelect.tsx`、`elements/**`)、渲染系统错误(`render/index.tsx:25`、`client-base/src/map/renderer.ts:463`)、`data-*`、`legacy-ui`、`src/App.vue`、以及 `client-base/src/map/vertex.ts` 的 prettier **一律不碰**。门禁只断言本计划列出的错误消失、且被改动文件无新增非白名单错误。"
- "**静态/工具门禁 + 守卫**:Task 1 用静态脚本断言所有 `IUIController` 类型标注均带 `<IGameUIPropsBase>`、两处构造点已修;Task 2 用定向 `vue-tsc`(读取输出、按「文件 + 错误码」断言在册错误消失、无回归)+ `prettier --check` 两文件通过 + CRLF 守卫。全程**不**以全仓诊断数 / `pnpm build` 为门禁。"
artifacts:
- path: packages-user/client-modules/src/ui/background.tsx
provides: "A:prettier 格式化后(纯格式,逻辑零改)。"
- path: packages-user/client-modules/src/ui/statusBar.tsx
provides: "A + C:prettier 格式化;`StatusBarProps` 基类型由 `IUIPropsBase` 改为 `DefaultProps + IClientBaseExtended`(去必填 `controller`/`instance`),`<MixedToolbar>` 内嵌不再报缺 props。"
- path: packages-user/client-modules/src/ui/toolbar.tsx
provides: "C:`ToolbarProps` 基类型由 `IUIPropsBase` 改为 `DefaultProps + IClientBaseExtended`;`<NumpadToolbar>`/`<ReplayingToolbar>`/`<PlayingToolbar>` 内嵌不再报缺 props。"
- path: packages-user/client-modules/src/ui/main.tsx
provides: "C:`main.tsx:56` `p.state` → `props.state`。"
- path: packages-user/client-modules/src/ui/settings.tsx
provides: "B + C:`openSettings`/`openReplay` 的 `IUIController` 补 `<IGameUIPropsBase>`;`MainSettings`/`SyncSave` setup 引入基类型控制器窄化引用,7 处 `.open(...)` 的 `loc` 报错消除。"
- path: packages-user/client-modules/src/ui/save.tsx
provides: "B:4 处函数参数 `IUIController<IGameUIPropsBase>`。"
- path: packages-user/client-modules/src/ui/statistics.tsx
provides: "B:`openStatistics` 参数 `IUIController<IGameUIPropsBase>`。"
- path: packages-user/client-modules/src/ui/viewmap.tsx
provides: "B:`openViewMap` 参数 `IUIController<IGameUIPropsBase>`。"
- path: packages-user/client-modules/src/ui/controller.tsx
provides: "B:`new UIController<IGameUIPropsBase>()`(去悬空字符串实参)。"
- path: packages-user/client-modules/src/render/scene.ts
provides: "B:`new UIController<IGameUIPropsBase>()`(去悬空字符串实参)。"
- path: packages-user/client-modules/src/render/utils/saves.ts
provides: "B:`syncFromServer` 参数 `IUIController<IGameUIPropsBase>`。"
- path: packages-user/client-base/src/components/choices.tsx
provides: "B:`getConfirm`/`getChoice`/`routedConfirm`/`routedChoices` 参数 `IUIController<IGameUIPropsBase>`。"
- path: packages-user/client-base/src/components/input.tsx
provides: "B:`getInput`/`getInputNumber` 参数 `IUIController<IGameUIPropsBase>`。"
- path: packages-user/client-base/src/components/misc.tsx
provides: "B:`waitbox` 参数 `IUIController<IGameUIPropsBase>`。"
key_links:
- "`packages-user/client-modules/src/client.ts:126-127`(`new UIController<IGameUIPropsBase>()`)↔ `ui/controller.tsx:4` / `render/scene.ts:3`(同型构造点)—— PB 类型参数一致性证据"
- "`packages-user/client-base/src/types.ts:15-19`(`IGameUIPropsBase`)/ `:52-55`(`IClientBaseExtended`)—— 两类模块的引入来源"
- "`packages/system/src/ui/types.ts:110-113`(`UIRawProps<C, PB> = Omit<Props<C>, keyof PB>`)↔ `settings.tsx` `props.controller` 的 `this` 泛型 —— `loc` 被误 Omit 的根因"
- "`packages-user/client-modules/src/ui/settings.tsx`(窄化控制器引用)↔ `IUIController<IGameUIPropsBase>` —— D-17 `loc` 修复链"
- "`packages-user/client-modules/src/ui/statusBar.tsx` / `toolbar.tsx` 的 `IClientBaseExtended` 基类型 ↔ `main.tsx`/`statusBar.tsx`/`toolbar.tsx` 的内嵌 JSX —— 去必填 `controller`/`instance` 链"
assumptions:
- "**行号为规划日(2026-10-09)实测,执行时可能漂移**:修正定位以「文件 + 错误码 + 报错文本」为准;门禁按「文件 + 错误码」匹配,不硬编码行号(白名单排除项除外)。"
- "**`new UIController('...')` 的字符串实参为无害悬空量**:`packages/system/src/ui/controller.ts:49` 构造器零参,字符串在运行时不产生任何效果;移除它不构成行为变更。若用户欲将其变为「控制器名」属功能新增(需改 `packages/system`),超出本计划范围。"
- "**settings 窄化引用为组件内局部 `const`(非公共/受保护/私有成员、非导出符号)**:本计划在 `MainSettings` / `SyncSave` 的 setup 内新增一个基类型控制器局部引用(默认名 `controller`)。参照 09-04 先例(局部 `refreshAttribute`/`locationHook` 视为无需单独批准),仍于 Task 0 呈报用户;若用户要求改内联 `as` 断言(无新命名,但 `dev.md:96` 不鼓励 `as`)或另指定名,据其指定执行。"
- "**`statusBar.tsx` / `toolbar.tsx` 基类型改 `IClientBaseExtended` 为「复用现有名」而非新增**:`IClientBaseExtended`(`client-base/src/types.ts:52`)已导出且语义正是「携带主对象 `state`」,改基类型不新增任何接口名、不改组件导出面。`StatusBarProps`/`ToolbarProps` 仅被这两文件与 `main.tsx` 内嵌消费(已验证全仓引用仅此三文件)。"
- "**既有注释零改动**:prettier 的格式化(含可能的行内注释缩进/换行归一)视为「格式」,不计为「改注释」;除 prettier 的纯格式化外,不触碰任何既有 `jsDoc`/`//` 文本(含 `statusBar.tsx:534` 提及 `p.status` 的既有注释)。"
prohibitions:
- "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理。"
- "本计划**只做 A(prettier 两文件)+ B(`IUIController`/`UIController` 泛型适配)+ C(`src/ui/**` 迁移遗留类型错误)**:不得修改 `packages/system` / `packages/render` / `packages/render-vue` 的接口定义;不得修复任何**明确排除**的错误(`client-base` 其余、`floorSelect.tsx`、`elements/**`、`render/index.tsx`、`client-base/src/map/renderer.ts`、`data-*`、`legacy-ui`、`src/App.vue`、`vertex.ts` 的 prettier)。"
- "不得新增任何公共/受保护/私有成员命名、不得新增导出接口;不得删除或改名任何模块级单例(`sceneController` / `mainUIController`);不得引入名为 `IHeroAttr` 的任何对象。"
- "不得触碰 `files_modified` 之外的任何生产文件;不得改 `render/` 内除 `scene.ts` 与 `utils/saves.ts` 外的内容;不得改数据端(`packages-user/data-*`)与 `packages/**`。"
- "不得修改任何既有 `jsDoc` 与注释(AGENTS.md)——prettier 的纯格式化除外;不得引入 `import type`(`dev.md:54`);不得使用 `?.`(`dev.md:107`);CRLF;移除后不得遗留未使用 import(`eslint` 零报错,`dev.md:106`);不得引入连续 `as`(`dev.md:96`)。"
- "不得把门禁建立在「全仓 TS 诊断数」「`pnpm build`」之上;验证为定向 `vue-tsc`(按文件 + 错误码)+ `prettier --check` + 静态断言。"
- "不得触碰、暂存、提交用户的并发改动(D-56):提交前 `git status` 逐条核对暂存面,只暂存本计划 `files_modified` 的条目。"
---
<objective>
Phase 9 · 任务 2 · 第 5 个计划 —— **迁移后 UI 修复:A(prettier 格式化)+ B(`IUIController`/`UIController` 泛型适配)+ C(`src/ui/**` 迁移遗留类型错误)**。
**要解决的问题(三项,实测清单,行号为规划日):**
**A. prettier 格式化(D-15)** —— `prettier --check` 实测对以下两文件报 warn(纯格式,逻辑零改):
- `packages-user/client-modules/src/ui/background.tsx`
- `packages-user/client-modules/src/ui/statusBar.tsx`
**B. `IUIController` / `UIController` 泛型适配(D-16)** —— `@motajs/system` 的 `IUIController<PB extends IUIDefaultPropsBase<any>>`(`types.ts:123`)与 `class UIController<PB ...>`(`controller.ts:26`)现为**必填泛型**。`vue-tsc` 实测 TS2314「Generic type 'IUIController<PB>' requires 1 type argument(s)」于:
| 文件 | 行 |
|---|---|
| `ui/save.tsx` | 495 / 545 / 569 / 593 |
| `ui/settings.tsx` | 680 / 691 |
| `ui/statistics.tsx` | 283 |
| `ui/viewmap.tsx` | 559 |
| `render/utils/saves.ts` | 125 |
| `client-base/src/components/choices.tsx` | 598 / 651 / 710 / 765 |
| `client-base/src/components/input.tsx` | 547 / 584 |
| `client-base/src/components/misc.tsx` | 571 |
构造点(实测为 **TS2554「Expected 0 arguments, but got 1」**,非仅缺泛型):
- `render/scene.ts:3` → `new UIController('main-scene')`
- `ui/controller.tsx:4` → `new UIController('main-ui')`
**C. `src/ui/**` 迁移遗留类型错误(D-17)** —— 09-01..09-04 迁移/props 工作的后继:
- `ui/main.tsx:56` TS2304「Cannot find name 'p'」(应为 `props.state`)。
- `ui/main.tsx:138/184` TS2322:`<LeftStatusBar>`/`<RightStatusBar>` props 缺 `controller`/`instance`。
- `ui/statusBar.tsx:521` TS2322:`<MixedToolbar>` props 缺 `controller`/`instance`。
- `ui/toolbar.tsx:397/403/409` TS2322:`<NumpadToolbar>`/`<ReplayingToolbar>`/`<PlayingToolbar>` props 缺 `controller`/`instance`。
- `ui/settings.tsx:81/86/90/94/371/385/393` TS2353:`'loc' does not exist in UIRawProps<..., MainSettingsProps>`。
**大致解决方案(本计划 = 09-05):** Task 0(`blocking-human`)= 汇报 A/B/C 范围与逐条修法清单并等待「可以执行」;Task 1 = B(为 16 处调用点补 `IUIController<IGameUIPropsBase>` / 为 2 处构造点补泛型并去悬空字符串实参);Task 2 = C(`main.tsx` 的 `p`→`props`;`statusBar.tsx`/`toolbar.tsx` 的 props 基类型由 `IUIPropsBase` 改 `IClientBaseExtended`;`settings.tsx` 引入基类型控制器窄化引用)**并** A(对 background.tsx / statusBar.tsx 执行 `prettier --write`)**并**最终定向门禁(`vue-tsc` 按文件 + 错误码断言 + `prettier --check` + CRLF/范围守卫)。
**为何 A 并入 Task 2 末尾:** `statusBar.tsx` 同时是 A 与 C 的对象;先做 C 的代码改动、再 `prettier --write` 归一,可避免「格式化后再改代码导致 `--check` 复失」。
**API coverage:** `detected:false`。**`No external API integration: 本计划仅为渲染端 UI 的类型/格式修复与 UI 系统泛型适配,不涉及任何外部 API。`**
</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/phases/09-minimum-browser-runnable/09-CONTEXT.md
@.planning/phases/09-minimum-browser-runnable/09-PATTERNS.md
@.planning/phases/09-minimum-browser-runnable/09-04-PLAN.md
@AGENTS.md
@dev.md
</context>
<tasks>
<task type="checkpoint:decision" gate="blocking-human">
<name>Task 0: 预执行汇报关卡 —— 汇报「任务 2 = A(prettier) + B(IUIController 泛型适配) + C(src/ui 迁移遗留类型错误)」清单,等待用户回复「可以执行」</name>
<precondition>`09-04` 已闭环(状态归属迁移完成);仓库仍存本计划 A/B/C 三类问题(见 `<objective>` 实测清单)。`vue-tsc` 与 `prettier` 可用(`node_modules/.bin` 已装)。</precondition>
<files>(只读汇报,不修改任何文件)</files>
<read_first>
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-03 / D-04 / D-06 / D-15 / D-16 / D-17)
- .planning/phases/09-minimum-browser-runnable/09-05-PLAN.md(本计划全文,含 A/B/C 逐条清单)
- packages-user/client-modules/src/ui/main.tsx:55-57(`p.state` 遗留)、:138-142 / :184-188(内嵌状态栏)
- packages-user/client-modules/src/ui/statusBar.tsx:103-110(`StatusBarProps`)、:521-525(`<MixedToolbar>`)
- packages-user/client-modules/src/ui/toolbar.tsx:30-45(`ToolbarProps`/`toolbarProps`)、:388-415(内嵌工具栏)
- packages-user/client-modules/src/ui/settings.tsx:25-31(`MainSettingsProps`)、:80-96 / :371-393(`props.controller.open` 的 `loc`)、:679-698(`openSettings`/`openReplay`)
- packages/system/src/ui/types.ts:110-113 / :118-123(`UIRawProps<C, PB>` / `IUIController<PB>` 泛型约束)、:27-32(`IUIDefaultPropsBase.controller: IUIController<this>`)
- packages/system/src/ui/controller.ts:26-28,49(`UIController<PB>` 与零参构造器)
- packages-user/client-base/src/types.ts:15-19(`IGameUIPropsBase`)、:52-55(`IClientBaseExtended`)
- packages-user/client-modules/src/client.ts:126-127(既有 `new UIController<IGameUIPropsBase>()` 范本)
- dev.md(import 置首无空行/注释;无 `import type`;避免 `as` 与连续 `as`;无 `?.`;CRLF;eslint 零报错;不改既有注释)
</read_first>
<action>
**停下并向用户汇报,等待明确回复「可以执行」后才继续;汇报本身只读。**
① **要解决的问题:** Phase 9 任务 2 = 迁移后 UI 修复,含 A(prettier 格式化两文件)、B(`IUIController`/`UIController` 必填泛型适配)、C(`src/ui/**` 迁移遗留类型错误)。均为 09-01..09-04 迁移/props 工作的后继。
② **锁定裁定原文要点:** **D-15**(prettier 两文件,纯格式);**D-16**(补 `IGameUIPropsBase` 泛型,范围见清单);**D-17**(`src/ui/**` 类型错误,排除 client-base 其余 / 渲染系统 / 数据端 / legacy)。**D-03 / D-04 / D-06**(执行前须获批;命名须先获批;不得扩大范围)。
③ **逐条「文件:行 → 错误码 → 拟修法」清单(须用户确认):** 原样呈报 `<objective>` 中 A/B/C 三张清单,并注明:B 的构造点实测为 **TS2554(多余字符串实参)**、C 的三处根因(`p` 笔误 / props 基类型误挂 `IUIPropsBase` / `IUIDefaultPropsBase<this>` 致 `loc` 被 `Omit`)。
④ **拟实现契约(须用户确认):**
- **B:** 为 16 处 `IUIController` 标注补 `<IGameUIPropsBase>`(client-modules 从 `@user/client-base` 引入;client-base 组件从 `../types` 引入);两处构造点改 `new UIController<IGameUIPropsBase>()`(去悬空字符串实参)。
- **C-①** `main.tsx:56` `p.state.flags...` → `props.state.flags...`。
- **C-②** `statusBar.tsx` 的 `StatusBarProps`、`toolbar.tsx` 的 `ToolbarProps` 基类型由 `IUIPropsBase` 改为 `DefaultProps, IClientBaseExtended`(去必填 `controller`/`instance`,仅保留 `state`);删除两文件不再使用的 `./types` 引入,改引入 `IClientBaseExtended`。
- **C-③** `settings.tsx`:`openSettings`/`openReplay` 参数 `IUIController` → `IUIController<IGameUIPropsBase>`;`MainSettings`/`SyncSave` 的 setup 内新增基类型控制器局部引用(默认 `const controller: IUIController<IGameUIPropsBase> = props.controller;`),仅将 7 处开子 UI 的 `props.controller.open(...)` 改为 `controller.open(...)`;`props.controller.close(...)`/`closeAll()` 不动。
- **A:** 对 `background.tsx`、`statusBar.tsx` 执行 `prettier --write`(在 C 之后)。
⑤ **验证方式(须用户确认):** 定向 `vue-tsc`(读输出,按「文件 + 错误码」断言 `<objective>` 在册错误消失、被改文件无新增非白名单错误)+ `prettier --check` 两文件通过 + CRLF/范围守卫。**不设全仓「零类型错误」门禁、不用 `pnpm build`。**
⑥ **待用户裁定的开放项:**
- **构造点字符串实参:** `new UIController('main-scene')` / `('main-ui')` 的字符串在零参构造器下无效果;默认**移除**并以 `<IGameUIPropsBase>` 补泛型。若用户欲保留为「控制器名」,属改 `packages/system` 的功能新增,须另行界定(本计划不含)。
- **settings 窄化引用命名:** 默认用组件局部 `const controller`;若用户要求改内联 `as`(无新命名)或另指定名,据其指定执行。
- **statusBar/toolbar 基类型改法:** 默认改 `IClientBaseExtended`(复用现有名,零新增命名);请用户确认「内嵌组件只消费 `state`、不携带 `controller`/`instance`」这一设计归位。
⑦ **命名:** 本计划**不新增任何公共/受保护/私有成员命名、不新增导出接口**;`settings.tsx` 新增一个组件内局部 `const`(默认名 `controller`),请用户知悉/批准。**不引入 `IHeroAttr`。**
⑧ **范围与硬边界:** 仅改 `files_modified` 白名单;**零触碰** = client-base 其余错误、`floorSelect.tsx`、`elements/**`、渲染系统(`render/index.tsx:25`、`map/renderer.ts:463`)、`data-*`、`legacy-ui`、`src/App.vue`、`vertex.ts` 的 prettier、用户并发改动(D-56)。不删/不改名模块级单例;**无依赖变更、不执行 `pnpm i`。**
⑨ **关卡性质与失败语义:** 无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者/子代理必须停下等待用户明确回复「可以执行」四个字;回复其它内容视为未通过。若用户给出修订意见,退出本计划、修订后重新汇报。
</action>
<decision>是否按本计划执行 A(prettier 两文件)+ B(`IUIController`/`UIController` 补 `<IGameUIPropsBase>`、两处构造点去悬空字符串实参)+ C(`main.tsx` `p`→`props`;`statusBar.tsx`/`toolbar.tsx` 基类型改 `IClientBaseExtended`;`settings.tsx` 基类型控制器窄化);门禁为定向 `vue-tsc` + `prettier --check`;不新增导出命名、不改单例、无范围外改动</decision>
<context>
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。本次核心 = A/B/C 三项清单 + 两处开放项(构造字符串实参、settings 局部命名)。本计划无新接口命名未知项。行号为规划日实测、可能漂移,门禁按「文件 + 错误码」匹配。
</context>
<options>
<option id="execute-all">
<name>可以执行(采纳全部默认处置)</name>
<pros>一次补齐 A/B/C;定向门禁可验证</pros>
<cons>无</cons>
</option>
<option id="execute-cast">
<name>可以执行 + settings 改用内联 `as`(不新增局部命名)</name>
<pros>零新增命名</pros>
<cons>7 处 `as` 断言,`dev.md:96` 不鼓励</cons>
</option>
<option id="execute-alt-ctor">
<name>可以执行 + 构造点另行处置(如保留控制器名,改 `packages/system`)</name>
<pros>若确需控制器名</pros>
<cons>须扩范围到 `packages/system`,本计划不含</cons>
</option>
<option id="revise">
<name>修订清单/修法后重新汇报</name>
<pros>避免带着错误契约落地</pros>
<cons>须修订 PLAN.md 后重新汇报</cons>
</option>
</options>
<resume-signal>回复「可以执行」四个字(如需调整构造点处置或 settings 命名方案请在回复中指明;修订意见请按 action ⑨ 处理)</resume-signal>
<verify>
<automated>node -e "const fs=require('fs');const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const bad=[];const main=read('packages-user/client-modules/src/ui/main.tsx');if(!/const hideStatus = ref\(\s*p\.state\.flags/.test(main))bad.push('pre-state changed: main.tsx p.state.hideStatus not found');const sb=read('packages-user/client-modules/src/ui/statusBar.tsx');if(!/interface StatusBarProps extends DefaultProps, IUIPropsBase/.test(sb))bad.push('pre-state changed: StatusBarProps base not IUIPropsBase');const tb=read('packages-user/client-modules/src/ui/toolbar.tsx');if(!/interface ToolbarProps extends DefaultProps, IUIPropsBase/.test(tb))bad.push('pre-state changed: ToolbarProps base not IUIPropsBase');const sc=read('packages-user/client-modules/src/render/scene.ts');if(!/new UIController\('main-scene'\)/.test(sc))bad.push('pre-state changed: scene ctor');const settings=read('packages-user/client-modules/src/ui/settings.tsx');if(!/controller: IUIController,/.test(settings))bad.push('pre-state changed: settings IUIController bare missing');if(/const controller[^=]*=\s*props\.controller/.test(settings))bad.push('post-state: settings controller alias already added');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state intact (A/B/C all still unresolved)')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "PRESTATE FAILED"(汇报基线不成立,或部分修复已先行落地)</fails_when>
<human-check>用户在对话中明确回复「可以执行」四个字(人工核对回复原文;用户若指定构造点/命名/基类型的非默认处置,须在汇报与后续任务中据其指定执行)</human-check>
</verify>
<acceptance_criteria>
- 用户回复中明确包含「可以执行」四个字
- 汇报内容涵盖:A/B/C 三张清单(**逐条 文件:行 → 错误码 → 拟修法**)、C 的三处根因、B 构造点实为 TS2554、验证方式(定向 `vue-tsc` + `prettier --check`,无全仓零错误门禁)、范围与硬边界
- 汇报中已原样告知两处开放项:**构造点字符串实参默认移除**、**settings 默认新增组件局部 `const controller`(可改内联 `as`)**
- 汇报中已原样告知:**不新增导出命名、不删/不改名单例、不引入 `IHeroAttr`、无依赖变更**
- 汇报中已原样告知**关卡性质**(必须停下等待,不因触发方式或 `yolo` / `auto_advance` 自动放行)
</acceptance_criteria>
<done>用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理</done>
</task>
<task type="auto">
<name>Task 1(B):为 16 处 `IUIController` 标注补 `<IGameUIPropsBase>`,并修正 2 处 `new UIController(...)` 构造点</name>
<precondition>Task 0 已闭环:用户明确回复「可以执行」四个字(若用户就构造点字符串实参另有指定,按指定执行)。</precondition>
<files>packages-user/client-modules/src/ui/save.tsx, packages-user/client-modules/src/ui/settings.tsx, packages-user/client-modules/src/ui/statistics.tsx, packages-user/client-modules/src/ui/viewmap.tsx, packages-user/client-modules/src/render/utils/saves.ts, packages-user/client-modules/src/render/scene.ts, packages-user/client-modules/src/ui/controller.tsx, packages-user/client-base/src/components/choices.tsx, packages-user/client-base/src/components/input.tsx, packages-user/client-base/src/components/misc.tsx</files>
<read_first>
- packages/system/src/ui/types.ts:118-123(`IUIController<PB extends IUIDefaultPropsBase<any>>`)、:110-113(`UIRawProps<C, PB>`)
- packages/system/src/ui/controller.ts:26-28,49(`class UIController<PB>`、零参构造器)
- packages-user/client-base/src/types.ts:15-19(`IGameUIPropsBase`)、:52-55(`IClientBaseExtended`)
- packages-user/client-modules/src/client.ts:126-127(既有 `new UIController<IGameUIPropsBase>()` 范本)
- packages-user/client-modules/src/ui/func.ts:7,15-18(client-modules 侧引入 `IGameUIPropsBase` 的既有范本:`import { ... IGameUIPropsBase } from '@user/client-base'`)
- packages-user/client-base/src/map/extension/door.ts:7(client-base 同包内 `import { IMapRenderer } from '../types'` 范本)
- 目标各文件(见 `<files>`)中的 `IUIController` 与 `new UIController` 出现处(save.tsx:495/545/569/593;settings.tsx:680/691;statistics.tsx:283;viewmap.tsx:559;render/utils/saves.ts:125;render/scene.ts:3;ui/controller.tsx:4;components/choices.tsx:598/651/710/765;components/input.tsx:547/584;components/misc.tsx:571)
- dev.md(import 置首无空行/注释;无 `import type`;无 `?.`;CRLF)
</read_first>
<action>
(1) **client-modules 侧 9 处标注:** 将下列函数参数的 `IUIController` 改为 `IUIController<IGameUIPropsBase>`(**只改类型标注,不改函数体/签名其余部分**):
- `ui/save.tsx`:`:495` `selectSave`、`:545` `saveSave`、`:569` `saveLoad`、`:593` `saveWithExist`。
- `ui/settings.tsx`:`:680` `openSettings`、`:691` `openReplay`。
- `ui/statistics.tsx`:`:283` `openStatistics`。
- `ui/viewmap.tsx`:`:559` `openViewMap`。
- `render/utils/saves.ts`:`:125` `syncFromServer`。
在所需文件的 import 中**补 `IGameUIPropsBase`(自 `@user/client-base`)**;不改动既有 import 顺序/主题(`import` 置首、块内无空行/注释,`dev.md:55`)。
(2) **client-base 侧 7 处标注:** 将下列函数参数的 `IUIController` 改为 `IUIController<IGameUIPropsBase>`:
- `components/choices.tsx`:`:598` `getConfirm`、`:651` `getChoice`、`:710` `routedConfirm`、`:765` `routedChoices`。
- `components/input.tsx`:`:547` `getInput`、`:584` `getInputNumber`。
- `components/misc.tsx`:`:571` `waitbox`。
在所需文件的 import 中**补 `IGameUIPropsBase`(自同包 `../types`,与 `map/extension/door.ts:7` 同范式)**。
(3) **构造点 2 处:**
- `render/scene.ts:3`:`new UIController('main-scene')` → `new UIController<IGameUIPropsBase>()`(补泛型 + 去悬空字符串实参);自 `@user/client-base` 补引入 `IGameUIPropsBase`。
- `ui/controller.tsx:4`:`new UIController('main-ui')` → `new UIController<IGameUIPropsBase>()`(同上);自 `@user/client-base` 补引入 `IGameUIPropsBase`。
- **不改**模块级单例名(`sceneController` / `mainUIController`)、不改其导出、不改 `createMainController`/`setBackground` 调用。
(4) **命名零新增:** 本任务只补泛型实参与调整 import,**不新增任何标识符**;`IGameUIPropsBase` 为既有导出名。
(5) **CRLF 与收口:** 改动文件须保持 CRLF;不得残留未使用 import(`eslint` 零报错,`dev.md:106`);不得改任何既有注释。
</action>
<reversibility rating="reversible">纯类型标注/泛型实参补充 + import 增补;`git revert` 可整体回退;不改运行时行为(构造点去掉的字符串在零参构造器下无效果)。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const exec=t=>t.split('\n').filter(l=>!/^\s*(\/\/|\*|\/\*)/.test(l)).join('\n');const files=['packages-user/client-modules/src/ui/save.tsx','packages-user/client-modules/src/ui/settings.tsx','packages-user/client-modules/src/ui/statistics.tsx','packages-user/client-modules/src/ui/viewmap.tsx','packages-user/client-modules/src/render/utils/saves.ts','packages-user/client-base/src/components/choices.tsx','packages-user/client-base/src/components/input.tsx','packages-user/client-base/src/components/misc.tsx'];const bad=[];for(const f of files){const e=exec(read(f));const bare=e.match(/:\s*IUIController(?!<)/g);if(bare)bad.push(f+' bare : IUIController x'+bare.length);if(!/IGameUIPropsBase/.test(e))bad.push(f+' IGameUIPropsBase not referenced');}for(const f of ['packages-user/client-modules/src/render/scene.ts','packages-user/client-modules/src/ui/controller.tsx']){const t=read(f);if(!/new UIController<IGameUIPropsBase>\(\)/.test(t))bad.push(f+' missing new UIController<IGameUIPropsBase>()');if(/new UIController\s*\(\s*['\"]/.test(t))bad.push(f+' stray string arg remains');if(!/IGameUIPropsBase/.test(t))bad.push(f+' IGameUIPropsBase not imported/used');}if(bad.length>0){console.error('B GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK B: all IUIController annotations carry IGameUIPropsBase; ctor sites fixed')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "B GATE FAILED"(仍存裸 `IUIController` 标注 / 构造点未补泛型或仍带字符串实参 / 未引入 `IGameUIPropsBase`)</fails_when>
</verify>
<acceptance_criteria>
- 16 处 `IUIController` 参数标注均为 `IUIController<IGameUIPropsBase>`
- `render/scene.ts` 与 `ui/controller.tsx` 均为 `new UIController<IGameUIPropsBase>()`,无悬空字符串实参
- `IGameUIPropsBase` 的引入来源正确:client-modules 自 `@user/client-base`;client-base 组件自 `../types`
- 未新增任何标识符;未删/改名模块级单例;无未使用 import;CRLF
</acceptance_criteria>
<done>B 完成:`IUIController`/`UIController` 全部使用点完成必填泛型适配,`new UIController(...)` 构造点修正</done>
</task>
<task type="auto">
<name>Task 2(C + A + 门禁):修复 `src/ui/**` 迁移遗留类型错误(`p`→`props`、props 基类型归位、settings `loc` 窄化),并对两文件执行 prettier,最后跑定向门禁</name>
<precondition>Task 1 已完成(B 的 16 处标注与 2 处构造点已修)。</precondition>
<files>packages-user/client-modules/src/ui/main.tsx, packages-user/client-modules/src/ui/statusBar.tsx, packages-user/client-modules/src/ui/toolbar.tsx, packages-user/client-modules/src/ui/settings.tsx, packages-user/client-modules/src/ui/background.tsx</files>
<read_first>
- packages-user/client-modules/src/ui/main.tsx:28(`MainSceneProps extends IUIPropsBase`)、:34(`props` 形参)、:55-57(`p.state` 遗留)、:138-142 / :184-188(内嵌状态栏)
- packages-user/client-modules/src/ui/statusBar.tsx:21-22(import)、:103-110(`StatusBarProps` + `statusBarProps`)、:521-525(`<MixedToolbar status state>`)
- packages-user/client-modules/src/ui/toolbar.tsx:27-45(`ToolbarProps` + `toolbarProps`)、:163-170(`ReplayingProps` + `replayingProps`)、:388-415(内嵌工具栏)
- packages-user/client-modules/src/ui/settings.tsx:1-31(import + `MainSettingsProps`)、:80-96 / :355-401(`MainSettings` / `SyncSave` 的 `props.controller.open`)、:679-698(`openSettings`/`openReplay`)
- packages-user/client-base/src/types.ts:52-55(`IClientBaseExtended`)、:15-19(`IGameUIPropsBase`)
- packages/system/src/ui/types.ts:27-32(`IUIDefaultPropsBase.controller: IUIController<this>` 根因)、:110-113(`UIRawProps`)
- packages/render-vue/src/props.ts:24-81(`BaseProps`,含可选 `loc`/`hidden`)与 packages/render-vue/src/types.ts:58-61(`DefaultProps`)——确认 `DefaultProps` 不带 `controller`/`instance`
- packages-user/client-modules/src/ui/main.tsx / statusBar.tsx / toolbar.tsx 内 `<LeftStatusBar>`/`<RightStatusBar>`/`<MixedToolbar>`/`<NumpadToolbar>`/`<ReplayingToolbar>`/`<PlayingToolbar>` 的全部使用点(已验证全仓仅此三文件)
- dev.md(不改既有注释;无 `import type`;避免 `as`;无 `?.`;CRLF;eslint 零报错)
</read_first>
<action>
(1) **C-① `main.tsx`:** 将 `:55-57` 的 `const hideStatus = ref(p.state.flags.getFieldValueDefaults('hideStatusBar', false));` 中 `p.state` 改为 `props.state`(setup 形参名为 `props`,`p` 为笔误)。其余(`hideStatus` 消费点、`// TODO` 注释、地图渲染、交互监听)**不动**。
(2) **C-② `statusBar.tsx` / `toolbar.tsx` props 基类型归位:**
- `statusBar.tsx`:将 `interface StatusBarProps extends DefaultProps, IUIPropsBase { loc; hidden }` 改为 `interface StatusBarProps extends DefaultProps, IClientBaseExtended { loc; hidden }`;删除 `import { IUIPropsBase } from './types';`,在既有的 `@user/client-base` 引入中补 `IClientBaseExtended`(如 `import { TextContent, IClientBaseExtended } from '@user/client-base';`)。`statusBarProps.props = ['loc','hidden','state']` 保持(`state` 仍为合法键)。
- `toolbar.tsx`:将 `interface ToolbarProps extends DefaultProps, IUIPropsBase { loc?: ElementLocator }` 改为 `interface ToolbarProps extends DefaultProps, IClientBaseExtended { loc?: ElementLocator }`;删除 `import { IUIPropsBase } from './types';`,在既有的 `@user/client-base` 引入中补 `IClientBaseExtended`。`toolbarProps`(`props: ['loc','state']`)与 `replayingProps`(`props: ['status','loc','state']`)保持。
- 组件导出面(`LeftStatusBar`/`RightStatusBar`/`PlayingToolbar`/`ReplayingToolbar`/`NumpadToolbar`/`MixedToolbar`)与 `ReplayingStatus` 接口**不变**。
(3) **C-③ `settings.tsx` 的 `loc` 修复:**
- 自 `@user/client-base` 补引入 `IGameUIPropsBase`。
- 在 `MainSettings` 的 setup 内、`const [key, scope] = useKey();` 之前,新增基类型控制器局部引用:`const controller: IUIController<IGameUIPropsBase> = props.controller;`(**实测 `IUIController<MainSettingsProps>` 可赋值给 `IUIController<IGameUIPropsBase>`,无需 `as`**)。
- 将 `MainSettings` 内 4 处开子 UI 调用 `props.controller.open(...)`(`:80` ViewMapUI / `:86` ReplaySettingsUI / `:90` SyncSaveUI / `:94` GameInfoUI)的接收者改为 `controller.open(...)`。
- 在 `SyncSave` 的 setup 内新增同样的局部引用 `const controller: IUIController<IGameUIPropsBase> = props.controller;`,并将 `:371` SyncSaveSelectUI / `:385` DownloadSaveSelectUI / `:393` ClearSaveSelectUI 的 `props.controller.open(...)` 改为 `controller.open(...)`。
- **不动** `props.controller.close(props.instance)`(`:60` / `:366` / `:397`)、`props.controller.closeAll()`(`:97` 区)、`getConfirm(props.controller, ...)`(`:98` 区)、`getInput(props.controller, ...)`、`syncFromServer(props.controller, ...)` 等既有调用(实测无报错)。
- (若 Task 0 用户选用内联 `as` 方案,则以 `(props.controller as IUIController<IGameUIPropsBase>).open(...)` 替换上述 7 处,不新增局部命名。)
(4) **A(prettier,C 之后):** 对本文件集执行 `prettier --write`:`ui/background.tsx`、`ui/statusBar.tsx`(`--config` 走仓库 `.prettierrc`)。纯格式;若 prettier 对既有注释仅作缩进/换行归一,视为格式(不改注释文本)。
(5) **收口:** `import` 置首、块内无空行/注释(`dev.md:55`);不得残留未使用 import;不得引入 `import type`(`dev.md:54`);不得使用 `?.`(`dev.md:107`);不改任何既有注释;CRLF;本任务只允许改 `<files>` 白名单文件。
(6) **最终门禁(本任务 `<verify>`):** 运行定向 `vue-tsc`(读输出),按「文件 + 错误码」断言 A/B/C 在册错误消失、被改文件无新增非白名单错误;`prettier --check` 两文件通过;CRLF 守卫。**不使用全仓诊断数 / `pnpm build`。**
</action>
<reversibility rating="reversible">组件 props 基类型归位 + 局部引用 + 一处变量名笔误修复 + 格式化;`git revert` 可整体回退;未改组件导出公共契约与运行时行为。</reversibility>
<verify>
<automated>& node_modules\.bin\vue-tsc.CMD --noEmit 2>&1 | Out-File -Encoding utf8 "$env:TEMP\tscheck-0905.txt"; node -e "const fs=require('fs');const f=process.argv[1];const txt=fs.readFileSync(f,'utf8').replace(/\r\n/g,'\n').replace(/\\\\/g,'/');const errs=[];for(const l of txt.split('\n')){if(/^\s/.test(l))continue;const m=l.match(/^(.+?)\((\d+),(\d+)\): error (TS\d+): (.*)$/);if(m)errs.push({file:m[1],line:+m[2],code:m[4],msg:m[5]});}const has=(e,s)=>e.file.endsWith(s);const bad=[];for(const e of errs){if((has(e,'ui/controller.tsx')||has(e,'render/scene.ts'))&&e.code==='TS2554')bad.push('B ctor: '+e.file+':'+e.line);if(e.code==='TS2314'&&/IUIController/.test(e.msg))bad.push('B leftover: '+e.file+':'+e.line);if(has(e,'ui/main.tsx')&&(e.code==='TS2304'||e.code==='TS2322'))bad.push('C main: '+e.file+':'+e.line+': '+e.msg);if((has(e,'ui/statusBar.tsx')||has(e,'ui/toolbar.tsx'))&&e.code==='TS2322')bad.push('C bar/tool: '+e.file+':'+e.line+': '+e.msg);if(has(e,'ui/settings.tsx')&&(e.code==='TS2353'||e.code==='TS2314'))bad.push('C settings: '+e.file+':'+e.line+': '+e.msg);}const touched=['ui/save.tsx','ui/settings.tsx','ui/statistics.tsx','ui/viewmap.tsx','ui/controller.tsx','render/utils/saves.ts','render/scene.ts','ui/main.tsx','ui/statusBar.tsx','ui/toolbar.tsx','components/choices.tsx','components/input.tsx','components/misc.tsx'];const allow=e=>((has(e,'components/choices.tsx')||has(e,'components/input.tsx')||has(e,'components/misc.tsx'))&&e.code==='TS7006');for(const e of errs){if(touched.some(t=>has(e,t))&&!allow(e))bad.push('unexpected in touched file: '+e.file+':'+e.line+': '+e.code+': '+e.msg);}if(bad.length>0){console.error('C GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK C: in-scope type errors gone; only allowlisted excluded errors remain in touched files')" "$env:TEMP\tscheck-0905.txt"</automated>
<fails_when>non-zero exit, 或 stderr 含 "C GATE FAILED"(A/B/C 在册错误未消失,或被改文件出现白名单外新错误)</fails_when>
<automated>& node_modules\.bin\prettier.CMD --check packages-user/client-modules/src/ui/background.tsx packages-user/client-modules/src/ui/statusBar.tsx</automated>
<fails_when>non-zero exit(prettier `--check` 报 warn/error,两文件未格式化)</fails_when>
<automated>node -e "const fs=require('fs');const files=['packages-user/client-modules/src/ui/main.tsx','packages-user/client-modules/src/ui/statusBar.tsx','packages-user/client-modules/src/ui/toolbar.tsx','packages-user/client-modules/src/ui/settings.tsx','packages-user/client-modules/src/ui/background.tsx'];const bad=[];for(const f of files){const raw=fs.readFileSync(f,'utf8');if(/(?<!\r)\n/.test(raw))bad.push(f+': LF without CR');if(raw.includes('\r')&&!/\r\n/.test(raw))bad.push(f+': stray CR')}if(bad.length>0){console.error('CRLF FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK CRLF: changed files are CRLF-consistent')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "CRLF FAILED"(被改文件出现非 CRLF 换行)</fails_when>
</verify>
<acceptance_criteria>
- `main.tsx` 无 `p.state`(已改 `props.state`);`hideStatus` 及其消费点、地图渲染、交互监听零破坏
- `statusBar.tsx` 的 `StatusBarProps` 与 `toolbar.tsx` 的 `ToolbarProps` 基类型为 `DefaultProps, IClientBaseExtended`;两文件不再引用 `IUIPropsBase`;组件导出面不变
- `settings.tsx` 的 7 处开子 UI 调用不再因 `loc` 报错(改用基类型控制器窄化引用,或按用户指定的内联 `as`);`openSettings`/`openReplay` 参数为 `IUIController<IGameUIPropsBase>`
- 定向 `vue-tsc`:A/B/C 在册错误消失;被改文件仅余白名单排除错误(`choices`/`input`/`misc` 的 `TS7006`)
- `prettier --check` 对 `background.tsx`、`statusBar.tsx` 通过
- 未新增导出命名;未改既有注释;CRLF;本任务只改 `<files>` 白名单
</acceptance_criteria>
<done>C + A 完成:`src/ui/**` 迁移遗留类型错误修复,两文件 prettier 通过;定向门禁(`vue-tsc` + `prettier` + CRLF)全绿</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| 泛型适配边界 | `IUIController<PB>` 的 `PB` 必须与运行时 `createPropsBase` 产出(`IGameUIPropsBase`)一致;类型实参错配会掩盖真实类型错误 |
| props 基类型边界 | 内嵌组件(statusBar/toolbar)与控制器打开组件(MainScene/settings)的 props 基类型职责不同,不可混用 |
| 范围边界 | 仅改白名单文件;排除的错误(client-base 其余 / 渲染系统 / 数据端 / legacy / vite prettier)不得被顺带修改 |
| 提交边界 | 不得把用户并发改动(D-56)纳入暂存/提交 |
## STRIDE Threat Register
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|-----------|----------|-----------|----------|-------------|-----------------|
| T-09-05-01 | Tampering | 借修类型之机扩大范围改动被排除文件 | high | mitigate | `files_modified` 白名单 + prohibitions + Task 2 门禁按「触碰文件 + 白名单排除项」断言无越界改动 |
| T-09-05-02 | Tampering | 泛型实参错配(如误用 `IUIPropsBase` 或旧类型)掩盖错误 | medium | mitigate | 统一取 `IGameUIPropsBase`(`client.ts` 既有范本);Task 1 静态断言所有标注带 `<IGameUIPropsBase>` |
| T-09-05-03 | Tampering | 误改 `statusBar.tsx`/`toolbar.tsx` 的组件导出面或 `ReplayingStatus` 契约 | medium | mitigate | 仅改 props 基类型与 import;acceptance 明列导出面不变;门禁断言 `vue-tsc` 无新增错误 |
| T-09-05-04 | Tampering | `settings.tsx` 误改 `close`/`getConfirm` 等非报错调用导致行为变更 | medium | mitigate | action 明列仅改 7 处 `.open(...)`;其余调用不动 |
| T-09-05-05 | Tampering | 修改既有注释 / 引入 `import type` / `?.` / 未使用 import | medium | mitigate | prohibitions + CRLF/静态门禁;prettier 仅格式化 |
| T-09-05-06 | Repudiation | 未跑定向门禁即提交 | medium | mitigate | Task 1/2 各有自动化门禁 + SUMMARY;验证未通过不得提交 |
| T-09-05-07 | Elevation of Privilege | 未经批准即执行 / 引入未批准命名 | high | mitigate | Task 0 `blocking-human`;新增局部 `controller` 于 Task 0 呈报;prohibitions 禁新增导出命名 |
| T-09-05-SC | Tampering | npm/pip/cargo installs | low | accept | 无依赖安装、不执行 `pnpm i` |
| T-09-05-08 | Tampering | 提交时混入用户并发改动(D-56) | medium | mitigate | 提交前 `git status` 逐条核对,只暂存本计划 `files_modified` 条目 |
</threat_model>
<verification>
- **Task 1(B)**:`IUIController` 全部标注带 `<IGameUIPropsBase>`(无裸标注);`render/scene.ts` / `ui/controller.tsx` 为 `new UIController<IGameUIPropsBase>()` 且无字符串实参;引入来源正确。
- **Task 2(C + A)**:定向 `vue-tsc` 断言——`client-modules/src/ui/{main,settings,statusBar,toolbar}.tsx` 的在册错误消失、`IUIController` TS2314 全消、构造点 TS2554 消失;被改文件仅余白名单排除错误(`choices`/`input`/`misc` 的 `TS7006`)。`prettier --check` 对 `background.tsx`/`statusBar.tsx` 通过。CRLF 守卫通过。
- **范围守卫**:`git status` 仅本计划 `files_modified` 条目被改;未触碰 `packages/system`、`packages/render*`、数据端、legacy、`src/App.vue`、`vertex.ts`。
- **无全仓门禁**:全程不使用「全仓 TS 诊断数」「`pnpm build`」作为通过条件。
- **人工复核**:结论写入 `09-05-SUMMARY.md`。
</verification>
<success_criteria>
- A:`prettier --check` 对 `background.tsx`、`statusBar.tsx` 通过(纯格式,逻辑零改)。
- B:`IUIController`/`UIController` 全部使用点补 `<IGameUIPropsBase>`;两处构造点修正。
- C:`main.tsx` `p` 笔误修复;`statusBar.tsx`/`toolbar.tsx` props 基类型归位;`settings.tsx` 7 处 `loc` 报错消除。
- 定向 `vue-tsc` 在册错误消失、无白名单外新增错误;`prettier --check` 通过。
- 零范围外改动、零新增导出命名、零既有注释改动、CRLF、无依赖变更。
</success_criteria>
## Artifacts this phase produces
**修改的文件与符号(无新增导出命名):**
| 符号 / 路径 | 类型 | 说明 |
|---|---|---|
| `packages-user/client-modules/src/ui/background.tsx`(改) | 组件 | A:prettier 格式化 |
| `packages-user/client-modules/src/ui/statusBar.tsx`(改) | 组件 / 契约 | A + C:prettier;`StatusBarProps` 基类型 → `DefaultProps + IClientBaseExtended` |
| `packages-user/client-modules/src/ui/toolbar.tsx`(改) | 组件 / 契约 | C:`ToolbarProps` 基类型 → `DefaultProps + IClientBaseExtended` |
| `packages-user/client-modules/src/ui/main.tsx`(改) | 组件 | C:`p.state` → `props.state` |
| `packages-user/client-modules/src/ui/settings.tsx`(改) | 组件 | B + C:`openSettings`/`openReplay` 补泛型;`MainSettings`/`SyncSave` 基类型控制器窄化(新增 1 个组件局部 `const`) |
| `packages-user/client-modules/src/ui/save.tsx`(改) | 模块 | B:4 处 `IUIController<IGameUIPropsBase>` |
| `packages-user/client-modules/src/ui/statistics.tsx`(改) | 模块 | B:`IUIController<IGameUIPropsBase>` |
| `packages-user/client-modules/src/ui/viewmap.tsx`(改) | 模块 | B:`IUIController<IGameUIPropsBase>` |
| `packages-user/client-modules/src/ui/controller.tsx`(改) | 组合根 | B:`new UIController<IGameUIPropsBase>()`(单例名不变) |
| `packages-user/client-modules/src/render/scene.ts`(改) | 组合根 | B:`new UIController<IGameUIPropsBase>()`(单例名不变) |
| `packages-user/client-modules/src/render/utils/saves.ts`(改) | 模块 | B:`IUIController<IGameUIPropsBase>` |
| `packages-user/client-base/src/components/choices.tsx`(改) | 组件 / 工具 | B:4 处 `IUIController<IGameUIPropsBase>` |
| `packages-user/client-base/src/components/input.tsx`(改) | 组件 / 工具 | B:2 处 `IUIController<IGameUIPropsBase>` |
| `packages-user/client-base/src/components/misc.tsx`(改) | 组件 / 工具 | B:1 处 `IUIController<IGameUIPropsBase>` |
**无新增导出接口命名:** 本计划不新增公共/受保护/私有成员命名、不新增导出接口;`settings.tsx` 新增 1 个组件局部 `const`(默认名 `controller`,非成员/非导出),已列入 Task 0 呈报用户。不引入 `IHeroAttr`。
## Source Coverage Audit
| 来源 | 项 | 处置 |
|------|----|------|
| GOAL | Phase 9(ROADMAP)「完成渲染端框架,产出可在浏览器运行并验证的最小版本」 | COVERED(增量·任务 2 子切片:迁移后 UI 类型/格式修复,为可运行版本扫清编译错误) |
| REQ | Phase 9 ROADMAP「Requirements: TBD」,无需求 ID | N/A(`requirements: []` 如实标注) |
| RESEARCH | 无 RESEARCH.md(本阶段为增量派发,无独立研究产物) | N/A |
| CONTEXT | D-15(prettier 两文件) | COVERED(Task 2 的 A) |
| CONTEXT | D-16(`IUIController`/`UIController` 泛型适配,16 处 + 2 构造点) | COVERED(Task 1) |
| CONTEXT | D-17(`src/ui/**` 迁移遗留类型错误) | COVERED(Task 2 的 C) |
| CONTEXT | D-03 / D-04 / D-06(执行前获批 / 命名获批 / 不扩范围) | COVERED(Task 0 + prohibitions + 范围门禁) |
| CONTEXT | Deferred Ideas(其余杂项对齐由用户后续增量派发) | DEFERRED(未纳入任何任务) |
| 排除项 | client-base 其余错误 / 渲染系统 / 数据端 / legacy / `src/App.vue` / `vertex.ts` prettier | EXCLUDED(prohibitions 明列不碰) |
<output>
Create `.planning/phases/09-minimum-browser-runnable/09-05-SUMMARY.md` when done
</output>

View File

@ -48,6 +48,14 @@
- **D-13(flags):** flags 尚无监听系统,本任务不处理 flags 的更新,仅在代码中标注 `TODO`。
- **D-14(纯适配):** 本任务不引入 UI 形态/视觉改动;不做 `@motajs/legacy-ui` 移植(属后续任务)。
### 任务 2(当前规划单元:迁移后 UI 修复)
> 本节界定**本次规划运行**的范围:仅规划任务 2,不规划 Phase 9 的其余内容。
- **D-15(prettier 修复):** 对本次迁移后未格式化的 UI 文件运行 prettier 修复格式:`packages-user/client-modules/src/ui/background.tsx`、`packages-user/client-modules/src/ui/statusBar.tsx`。纯格式改动,不改逻辑。
- **D-16(IUIController 泛型适配):** `@motajs/system` 的 `IUIController<PB>` / `UIController<PB>` 现为必填泛型;为所有未适配的使用点补上类型实参(`IGameUIPropsBase`)。范围:`client-modules` 侧 `ui/save.tsx`(495/545/569/593)、`ui/settings.tsx`(680/691)、`ui/statistics.tsx`(283)、`ui/viewmap.tsx`(559)、`render/utils/saves.ts`(125)、`render/scene.ts`(3 构造)、`ui/controller.tsx`(4 构造);`client-base` 侧 `components/choices.tsx`(598/651/710/765)、`components/input.tsx`(547/584)、`components/misc.tsx`(571)。
- **D-17(UI 实现迁移遗留类型错误):** 修复 `packages-user/client-modules/src/ui/**` 的类型错误:`main.tsx`(56 `p` 未定义;138/184 props 不匹配)、`settings.tsx`(81/86/90/94/371/385/393 `loc` 不存在)、`statusBar.tsx`(521)、`toolbar.tsx`(397/403/409)。**不含** `client-base` 其余错误、渲染系统(`render/`、`client-base/map`)、数据端、legacy。
### the agent's Discretion
- 无。用户明确未授权 AI 自行决定 UI 形态或任务范围。