From cb133685f2b3b6a386a6f288226445ccc989280b Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Sun, 11 Oct 2026 15:31:23 +0800 Subject: [PATCH] docs(09): add plan 09-10 for task 3 review fixes R-1..R-8 across five UIs --- .planning/ROADMAP.md | 6 +- .../09-minimum-browser-runnable/09-10-PLAN.md | 387 ++++++++++++++++++ 2 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 .planning/phases/09-minimum-browser-runnable/09-10-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index ab6323c..456ad07 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -481,7 +481,7 @@ Plans: **Goal**: 在已完成的数据端(可在 Node 环境独立运行/回放验证)之上完成渲染端框架,产出可在浏览器中运行并验证的最小版本 **Requirements**: TBD **Depends on**: Phase 8 -**Plans**: 9/9 plans executed (任务 1·UI 接口适配,D-10..D-14 已闭环;任务 2·迁移后 UI 修复,D-15..D-17 已闭环;任务 3·legacy UI 移植,book/equipbox/toolbox 已规划(09-06..09-08)并已按用户补齐的数据端接口修订——裸怪物临界 `calculateEnemyCritical`、与空装备对比 `compareEquip(-1)`、道具枚举 `iterateItems`、`@user/data-system` 依赖已声明;shop 已规划(09-09,D-38..D-41)并已按用户补齐的接口修订——价格 `IShopState.getPrice`/`setPrice`(GAP-S1 闭合)、交易 `IShopSystem.buyItem`/`sellItem`(GAP-S2 闭合)、支付资源 `money`(GAP-S3 闭合)、`IShopState` 已从 `@user/data-base` 导出(GAP-S0 闭合),**GAP-S4 已闭合(用户补齐 `@user/data-system` 的 `shop` 导出:`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`,`IShopSystem` / `IShopHeroAttr` 现可导入)**;本阶段增量派发,其余任务待用户再派发) +**Plans**: 10/10 plans executed (任务 1·UI 接口适配,D-10..D-14 已闭环;任务 2·迁移后 UI 修复,D-15..D-17 已闭环;任务 3·legacy UI 移植,book/equipbox/toolbox 已规划(09-06..09-08)并已按用户补齐的数据端接口修订——裸怪物临界 `calculateEnemyCritical`、与空装备对比 `compareEquip(-1)`、道具枚举 `iterateItems`、`@user/data-system` 依赖已声明;shop 已规划(09-09,D-38..D-41)并已按用户补齐的接口修订——价格 `IShopState.getPrice`/`setPrice`(GAP-S1 闭合)、交易 `IShopSystem.buyItem`/`sellItem`(GAP-S2 闭合)、支付资源 `money`(GAP-S3 闭合)、`IShopState` 已从 `@user/data-base` 导出(GAP-S0 闭合),**GAP-S4 已闭合(用户补齐 `@user/data-system` 的 `shop` 导出:`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`,`IShopSystem` / `IShopHeroAttr` 现可导入)**;本阶段增量派发,其余任务待用户再派发) Plans: **Wave 1** @@ -511,6 +511,10 @@ Plans: - [x] 09-08-PLAN.md — 任务 3·toolbox 道具栏(D-34..D-36):`toolbox.tsx`(三列 永久/可消耗/名称+说明;数量降序同数量按 id;单击选中/再点使用 `useItem`;无丢弃拖拽;入参 `IHeroState`) - [x] 09-09-PLAN.md — 任务 4·shop 商店界面(D-38..D-41,已按用户补齐接口修订):`shop.tsx`(顶部 购买/售出 切换;左 商店列表 id 排序/库存右对齐/显滚动条;右 名称·描述·购入量选择(按钮上方 买价/卖价/存货/拥有/总价,买价/卖价经 `IShopState.getPrice`)·购买/售出按钮;四按钮 `<< < {量} > >` ±10/±1、按物品分别记忆;右下退出;入参 `IShopState` + `IHeroState` + `IShopSystem`(交易经 `buyItem`/`sellItem`);**GAP-S4 已闭合:`IShopSystem` / `IShopHeroAttr` 已从 `@user/data-system` 公共面导出**) +**Wave 7** *(blocked on Wave 6 completion · 对四个界面(五文件)的复查修正,R-1..R-8)* + +- [ ] 09-10-PLAN.md — 任务 3·复查修正(R-1..R-8):对 `book.tsx`/`bookDetail.tsx`/`equipbox.tsx`/`toolbox.tsx`/`shop.tsx` 统一应用——R-1 移除自绘背景、R-2 长文本改 `TextContent`、R-3 顶层 jsDoc、R-4 JSX 分段注释、R-5 取消 `IShopItemEntry`/`IHeroAttrItem` 导出、R-6 非空判断(字面量 `isNil`/对象 `!`,去 `=== null`/`=== undefined`)、R-7 `shop` 的 `openShop` 移入 `onMounted`、R-8 `bookDetail` 改 `instanceof EnemyView`(已核实 `EnemyView` 为运行期类) + ## Progress **Execution Order:** diff --git a/.planning/phases/09-minimum-browser-runnable/09-10-PLAN.md b/.planning/phases/09-minimum-browser-runnable/09-10-PLAN.md new file mode 100644 index 0000000..d3c6af5 --- /dev/null +++ b/.planning/phases/09-minimum-browser-runnable/09-10-PLAN.md @@ -0,0 +1,387 @@ +--- +phase: 09-minimum-browser-runnable +plan: 10 +type: execute +wave: 7 +depends_on: + - 09-06 + - 09-07 + - 09-08 + - 09-09 +files_modified: + - packages-user/client-modules/src/ui/book.tsx + - packages-user/client-modules/src/ui/bookDetail.tsx + - packages-user/client-modules/src/ui/equipbox.tsx + - packages-user/client-modules/src/ui/toolbox.tsx + - packages-user/client-modules/src/ui/shop.tsx + - .planning/phases/09-minimum-browser-runnable/09-10-SUMMARY.md +autonomous: false +requirements: [] +estimate: + tokens: 70000 + raw_tokens: 70000 + tasks: 3 + confidence: low +must_haves: + truths: + - "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-22)**:Task 0 必须先汇报「本计划 = 任务 3 · 复查修正(对 book / bookDetail / equipbox / toolbox / shop 五个 UI 应用 R-1..R-8)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,**逐一列出每个 R 在各文件的实测命中情况**、**R-8 的可行性结论(`EnemyView` 为运行期类,可用 `instanceof`)**、**命名结论(本修正不引入任何新命名)**与**开放项(见下)**,然后必须**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行本计划任何一步、不得派发执行子代理,且不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行。" + - "**R-1(不写 UI 背景):** 移除 UI 内自绘的整屏背景。**实测**:仅 `book.tsx` 与 `bookDetail.tsx` 各绘有一处 ``(整屏黑底);`equipbox.tsx` / `toolbox.tsx` / `shop.tsx` 内的 `g-rectr` 均为行/单元高亮,**不是** UI 背景,**不命中** R-1。背景交由 UI 系统的背景控制器(`ui/background.tsx` 的 `MainBackgroundUI`)处理。" + - "**R-2(长文本必须用 `TextContent`):** 长文本改用 `TextContent` 组件渲染。**实测命中**:`bookDetail.tsx` 的特殊属性描述 `special.getDescription()`;`equipbox.tsx` 的装备描述 `equip.item.text`;`toolbox.tsx` 的道具说明 `current.raw.text`;`shop.tsx` 的道具描述 `entry.text`。**`book.tsx` 无长文本**(仅短名称/数值),不命中。" + - "**R-3(顶层定义 jsDoc):** 为每个组件**顶层定义的变量 / 常量 / 函数**补齐 jsDoc 注释(按 `dev.md:72-86`;**不得修改任何既有注释**)。已实测出各文件缺失 jsDoc 的顶层定义清单(见 Task 1 / Task 2)。**策略(默认,待 Task 0 确认):已有注释(`//` 或 `/** */`)的顶层定义视为已满足、不再叠加第二条注释**(避免与「不改既有注释」冲突,也避免重复注释)。" + - "**R-4(JSX 分段注释):** 在五个文件的 JSX 中加注释标明每段负责的内容(TSX 内用单行 `{/* … */}`,符合 `dev.md:79`「不允许非 jsDoc 的多行注释」)。" + - "**R-5(按需导出):** 不需要对外导出的类型取消 `export`。**实测**:`shop.tsx` 的 `IShopItemEntry` 与 `equipbox.tsx` 的 `IHeroAttrItem` 属本地行/项类型,取消导出;**导出保留**:各文件的 props 接口(`BookProps` / `BookDetailProps` / `EquipBoxProps` / `ToolBoxProps` / `ShopProps`)与组件 / `*UI`(`Book` / `BookUI`、`BookDetail` / `BookDetailUI`、`EquipBox` / `EquipBoxUI`、`ToolBox` / `ToolBoxUI`、`Shop` / `ShopUI`)。" + - "**R-6(非空判断约定):** 字面量用 `isNil`、对象用 `!`;消除全部 `=== undefined` / `=== null` / `!== null`(含 JSX 条件渲染中的 `x !== null &&`)。**实测命中**见 Task 1 / Task 2 逐处清单。" + - "**R-7(组件顶层无副作用):** 组件顶层不得有副作用。**实测命中**:仅 `shop.tsx` 顶层调用 `props.shopSystem.openShop(props.shop)`(第 95 行),须移入 `onMounted`。其余四文件顶层仅做引用赋值 / `ref` / `computed` / 函数声明,无副作用,不命中。" + - "**R-8(IEnemy / IEnemyView 判别):** 以 `instanceof EnemyView` 替代 `'getBaseEnemy' in props.enemy`。**可行性结论:可用。** `EnemyView` 是运行期类(`packages-user/data-system/src/combat/enemy.ts:4` `export class EnemyView implements IComputingEnemyView`),且经 `combat/index.ts`(`export * from './enemy'`)→ `data-system/src/index.ts`(`export * from './combat'`)**已从 `@user/data-system` 公共面导出**;`EnemyContext` 创建视图即 `new EnemyView(enemy, this)`(`combat/context.ts:307`),故对由上下文产出的视图 `instanceof` 成立。`bookDetail.tsx` 内 4 处结构判别(`getView` / `getBase` / `getComputed` / `toggleMode`)统一替换为 `props.enemy instanceof EnemyView`。" + - "**范围零外溢 + 静态门禁:** 仅修改 `book.tsx` / `bookDetail.tsx` / `equipbox.tsx` / `toolbox.tsx` / `shop.tsx` 五个文件;**不修改** 09-01..09-09 任何文件、不修改任何其它 UI / `src/render/**` / `packages/system` / `packages/render*` / `packages/legacy-ui` / 数据端(`packages-user/data-*`);验证一律为**静态源码断言**(背景移除 / `TextContent` 使用 + 无长文本 `text=` / `onMounted(` 包裹 `openShop(` / `isNil` 导入使用 / 无 `=== undefined`·`=== null` / 导出增减 / jsDoc 存在 / JSX 注释存在 / 无 `import type`·`?.` / CRLF / 范围守卫),**不使用** `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数作门禁。" + - "**不改既有注释、不引入未批准命名、不增新依赖:** 本修正**不引入任何新命名**(`TextContent` / `onMounted` / `isNil` / `EnemyView` 均为既有符号);采用 `import { TextContent } from '@user/client-base'`(`statusBar.tsx:10` 先例)、`import { onMounted } from 'vue'`、`import { isNil } from 'lodash-es'`(`main.tsx:24` 先例),**不改 `package.json`**;不得修改任何既有 `jsDoc` 与注释。" + artifacts: + - path: packages-user/client-modules/src/ui/book.tsx + provides: "R-1 移除整屏黑底;R-3 顶层 jsDoc;R-4 JSX 注释;R-6 `critical !== null` → 真值判断。" + - path: packages-user/client-modules/src/ui/bookDetail.tsx + provides: "R-1 移除整屏黑底;R-2 特殊属性描述改 `TextContent`;R-3 顶层 jsDoc;R-4 JSX 注释;R-6 `critical.value !== null` → 真值判断;R-8 `instanceof EnemyView` 判别。" + - path: packages-user/client-modules/src/ui/equipbox.tsx + provides: "R-2 装备描述改 `TextContent`;R-3 顶层 jsDoc;R-4 JSX 注释;R-5 `IHeroAttrItem` 取消导出;R-6 字面量 `isNil` / 对象 `!`。" + - path: packages-user/client-modules/src/ui/toolbox.tsx + provides: "R-2 道具说明改 `TextContent`;R-3 顶层 jsDoc;R-4 JSX 注释;R-6 对象 `!`。" + - path: packages-user/client-modules/src/ui/shop.tsx + provides: "R-2 道具描述改 `TextContent`;R-3 顶层 jsDoc;R-4 JSX 注释;R-5 `IShopItemEntry` 取消导出;R-6 对象 `!`;R-7 `openShop` 移入 `onMounted`。" + key_links: + - "R-1 ↔ `packages-user/client-modules/src/ui/background.tsx`(`MainBackgroundUI = new GameUI('main-background', MainBackground)`,`:28`)—— UI 系统自带的背景控制器;五个 UI 不再自绘背景" + - "R-2 ↔ `packages-user/client-base/src/components/textbox.tsx`(`TextContent` 组件,`:117`;props `text` / `width`(必填) / `loc` / `height` / `lineHeight` / `autoHeight` / `font` / `fillStyle`)—— 长文本多行渲染载体;`TextContent` 经 `@user/client-base` 导出(`statusBar.tsx:10` 先例)" + - "R-6 ↔ `lodash-es` 的 `isNil`(`main.tsx:24` 先例)—— 字面量非空判断;对象非空判断用 `!obj`(`dev.md:120`)" + - "R-7 ↔ `vue` 的 `onMounted`(`viewmap.tsx:356` / `title.tsx:347` 先例)—— 顶层次作用移入挂载回调" + - "R-8 ↔ `packages-user/data-system/src/combat/enemy.ts`(`export class EnemyView` `:4`)+ `combat/index.ts`(`export * from './enemy'`)+ `data-system/src/index.ts`(`export * from './combat'`)—— `instanceof EnemyView` 的运行期类来源;`combat/context.ts:307` 证实上下文创建的视图即 `EnemyView` 实例" + - "R-5 ↔ `packages-user/client-modules/src/ui/shop.tsx`(`IShopItemEntry` `:31`)/ `equipbox.tsx`(`IHeroAttrItem` `:10`)—— 本地行 / 项类型取消导出" + assumptions: + - "**`EnemyView` 可从 `@user/data-system` 导入(已核实)**:`data-system/src/combat/enemy.ts:4` 为运行期类,经 `combat/index.ts` → `data-system/src/index.ts` 公共面导出;`combat/context.ts:307` 以 `new EnemyView(...)` 创建视图。故 `instanceof EnemyView` 对上下文产出的视图成立。" + - "**`TextContent` 可从 `@user/client-base` 导入(已核实)**:`statusBar.tsx:10` 已 `import { TextContent } from '@user/client-base'`。" + - "**`isNil` 可从 `lodash-es` 导入(已核实先例)**:`main.tsx:24` 已 `import { isNil } from 'lodash-es'`(`lodash-es` 未直接列于 `client-modules/package.json`,但既有代码如此使用;若用户在 Task 0 要求改走其它来源,据其指定执行)。" + - "**`g-rect` 与 `g-rectr` 区分**:自绘整屏背景为 ``;`` 为行 / 单元高亮,非背景。门禁用 `/`,**不新增**打开 `MainBackgroundUI` 的接线。若用户要求 UI 主动打开背景控制器,请指明。" + - "**OI-R2(`IHeroAttrItem` 是否取消导出)**:`IHeroAttrItem` 被导出的 `EquipBoxProps.attributes` 引用;R-5 精神为「本地项类型不导出」,默认取消导出(消费者按结构类型传参)。若用户希望保留该导出以便消费者具名引用,请指明。" + - "**OI-R3(R-3 jsDoc 覆盖范围)**:默认覆盖各文件**模块级**(含 `xxxProps` 常量、组件常量、`*UI` 常量)与**组件 setup 内顶层**定义,**跳过** interface / type alias / enum(`dev.md:82`);且**已有注释的定义不叠加**。若用户要求覆盖更多 / 更少,请指明。" + - "**OI-R4(`TextContent` 尺寸参数)**:R-2 替换长文本为 `TextContent` 时,需为其提供必填 `width`;默认取所在区域可用宽度(如描述区宽 − 内边距),并保留外层 ``。具体像素由执行者按现有布局取近似值,不改布局结构。" +--- + + +Phase 9 · 任务 3 · 第 10 个计划 —— **复查修正(R-1..R-8)**,对已完成(09-06..09-09)的四个界面共五个文件统一应用用户复查要求。 + +**要解决的问题(R-1..R-8,源出 `09-CONTEXT.md`「任务 3 · 复查修正」):** 用户复查 `book.tsx` / `bookDetail.tsx` / `equipbox.tsx` / `toolbox.tsx` / `shop.tsx` 后提出 8 项修正——(R-1) UI 不自行绘制背景;(R-2) 长文本必须用 `TextContent`(`text` 不支持换行);(R-3) 顶层定义补 jsDoc;(R-4) JSX 加分段注释;(R-5) 不需要导出的类型不导出;(R-6) 非空判断字面量用 `isNil`、对象用 `!`,不写 `=== undefined` / `=== null`;(R-7) 组件顶层无副作用(`openShop` 移入 `onMounted`);(R-8) 以 `instanceof EnemyView` 替代 `'getBaseEnemy' in …`。 + +**大致解决方案(本计划 = 09-10):** +- **Task 0(`blocking-human`)= 预执行汇报关卡**:汇报问题、R-1..R-8 逐文件的**实测命中清单**、R-8 可行性结论(`EnemyView` 为运行期类,可用)、命名结论(**零新命名**)、开放项 OI-R1..OI-R4、验证方式(静态断言,无全仓门禁)与硬边界,并等待用户「可以执行」。 +- **Task 1 = 修正 `book.tsx` + `bookDetail.tsx`**(R-1 / R-2(仅 bookDetail 描述)/ R-3 / R-4 / R-6 / R-8),并跑该组静态门禁。 +- **Task 2 = 修正 `equipbox.tsx` + `toolbox.tsx` + `shop.tsx`**(R-2 / R-3 / R-4 / R-5 / R-6 / R-7),并跑**五文件最终**静态门禁(无 `=== undefined`·`=== null`、CRLF、范围守卫)。 + +**为何两文件 + 三文件分组:** 五文件无 `files_modified` 交叉冲突,但同一计划内顺序修改以避免门禁互相干扰;`bookDetail` 含 R-8(唯一需 `EnemyView`)与 R-2(描述),单独与该组的 `book` 同批;其余三文件共享 R-2(描述)+ R-5/R-7 特征,第二批收口并以全量门禁统一收束。 + +**API coverage:** `detected:false`。**`No external API integration: 本计划仅为渲染端 UI 源码的静态复查修正(注释 / 导入 / 非空判断 / 背景移除 / 文本载体),数据来自既有数据端接口,不涉及任何外部 API。`** + + +@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md +@C:/Users/13194/.config/opencode/gsd-core/templates/summary.md + + + +@.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-06-PLAN.md +@.planning/phases/09-minimum-browser-runnable/09-07-PLAN.md +@.planning/phases/09-minimum-browser-runnable/09-08-PLAN.md +@.planning/phases/09-minimum-browser-runnable/09-09-PLAN.md +@AGENTS.md +@dev.md + + + + + + Task 0: 预执行汇报关卡 —— 汇报「任务 3 · 复查修正(R-1..R-8,五文件)」的逐文件实测命中、R-8 可行性结论、命名结论与开放项,等待用户回复「可以执行」 + `09-06`..`09-09` 均已完成(`book.tsx` / `bookDetail.tsx` / `equipbox.tsx` / `toolbox.tsx` / `shop.tsx` 均已存在);`EnemyView` 仍为 `@user/data-system` 公共面上的运行期类(`combat/enemy.ts`),`TextContent` 仍从 `@user/client-base` 导出。 + (只读汇报,不修改任何文件) + + - .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-03 / D-04 / D-06 / D-21 / D-22 / D-23..D-41;**「任务 3 · 复查修正」R-1..R-8**) + - .planning/phases/09-minimum-browser-runnable/09-10-PLAN.md(本计划全文,含逐文件命中清单与 OI-R1..OI-R4) + - packages-user/client-modules/src/ui/book.tsx(R-1 背景 L110;R-3/R-4;R-6 `critical !== null` L182/191/200/209) + - packages-user/client-modules/src/ui/bookDetail.tsx(R-1 背景 L110;R-2 描述 L168;R-6 `critical.value !== null` L186/195;R-8 `'getBaseEnemy' in` L39/44/49/93) + - packages-user/client-modules/src/ui/equipbox.tsx(R-2 `equip.item.text` L344;R-5 `IHeroAttrItem` L10;R-6 L115/145/153/311/320/329/338/365/402) + - packages-user/client-modules/src/ui/toolbox.tsx(R-2 `current.raw.text` L247;R-6 L226/236/245) + - packages-user/client-modules/src/ui/shop.tsx(R-2 `entry.text` L304;R-5 `IShopItemEntry` L31;R-6 L114/218/293/304/328/346/364/382;R-7 `openShop` L95) + - packages-user/client-modules/src/ui/background.tsx:17-28(`MainBackground` / `MainBackgroundUI`,UI 系统背景控制器) + - packages-user/client-base/src/components/textbox.tsx:40-235(`TextContentProps`:`text` / `width`(必填) / `loc` / `height` / `lineHeight` / `autoHeight` / `font` / `fillStyle`) + - packages-user/data-system/src/combat/enemy.ts:1-39(`export class EnemyView`)、packages-user/data-system/src/combat/index.ts(`export * from './enemy'`)、packages-user/data-system/src/index.ts(`export * from './combat'`)、packages-user/data-system/src/combat/context.ts:307(`new EnemyView(enemy, this)`) + - packages-user/client-modules/src/ui/statusBar.tsx:10(`import { TextContent } from '@user/client-base'` 先例)、statistics.tsx:104-110(`` 用法)、main.tsx:24,82(`import { isNil } from 'lodash-es'` 先例)、viewmap.tsx:356(`onMounted` 先例) + - dev.md(import 置首无空行/注释;无 `import type`;避免 `as`;无 `?.`;CRLF;对象用 `!obj`、字面量 `!isNil`;不改既有注释) + + + **停下并向用户汇报,等待明确回复「可以执行」后才继续;汇报本身只读。** + + ① **要解决的问题:** 任务 3 · 复查修正。把用户对四个已交付界面的 8 项复查要求(R-1..R-8)统一应用到 `book.tsx` / `bookDetail.tsx` / `equipbox.tsx` / `toolbox.tsx` / `shop.tsx` 五个文件上。 + + ② **逐文件实测命中清单(须用户确认口径):** + - **R-1(移除自绘背景):** `book.tsx:110`、`bookDetail.tsx:110` 各有 ``(整屏黑底)→ 移除。`equipbox` / `toolbox` / `shop` 内的 `g-rectr` 为行 / 单元高亮,**不是**背景 → 不命中。 + - **R-2(长文本改 `TextContent`):** `bookDetail.tsx:168`(`special.getDescription()`)、`equipbox.tsx:344`(`equip.item.text`)、`toolbox.tsx:247`(`current.raw.text`)、`shop.tsx:304`(`entry.text`)→ 改 ``。`book.tsx` 无长文本 → 不命中。 + - **R-3(顶层 jsDoc):** 待补清单——`book.tsx`:`bookProps` / `Book` / `BookUI` / `ctx` / `format` / `specialText` / `statX` / `openDetail` / `exit`;`bookDetail.tsx`:`bookDetailProps` / `BookDetail` / `BookDetailUI` / `mode` / `getView` / `getBase` / `getComputed` / `current` / `format` / `statX` / `toggleMode` / `close` / `toggleText`;`equipbox.tsx`:`equipBoxProps` / `EquipBox` / `EquipBoxUI` / `hero`;`toolbox.tsx`:`toolBoxProps` / `ToolBox` / `ToolBoxUI` / `hero`;`shop.tsx`:`shopProps` / `Shop` / `ShopUI` / `shop` / `hero`。(已有注释的 `enemies` / `specials` / `diff` / `endPress` / `getLocator` 等**不叠加**。) + - **R-4(JSX 分段注释):** 五文件 JSX 均未分段 → 逐段补 `{/* … */}`。 + - **R-5(按需导出):** `shop.tsx:31` `IShopItemEntry`、`equipbox.tsx:10` `IHeroAttrItem` → 取消 `export`;props 接口与组件 / `*UI` 导出保留。 + - **R-6(非空判断):** `book.tsx` L182/191/200/209;`bookDetail.tsx` L186/195;`equipbox.tsx` L115(`=== undefined`)/145(`=== undefined`)/153/311/320/329/338/365/402;`toolbox.tsx` L226/236/245;`shop.tsx` L114/218/293/304/328/346/364/382 → 按「字面量 `isNil` / 对象 `!`」改写。 + - **R-7(顶层无副作用):** 仅 `shop.tsx:95` `props.shopSystem.openShop(props.shop);` → 移入 `onMounted(() => …)`。 + - **R-8(`instanceof EnemyView`):** `bookDetail.tsx` 的 `'getBaseEnemy' in props.enemy` 共 4 处(L39/44/49/93)→ 改 `props.enemy instanceof EnemyView`。 + + ③ **R-8 可行性结论(须用户确认):** **可用。** `EnemyView` 为运行期类(`combat/enemy.ts:4`),经 `combat/index.ts` → `data-system/src/index.ts` 从 `@user/data-system` 公共面导出;`combat/context.ts:307` 证实上下文创建视图即 `new EnemyView(…)`,故 `instanceof` 对上下文产出视图成立。**无须**引入类型守卫 / 断言 / 新命名。 + + ④ **命名结论(D-04):** **本修正不引入任何新命名**。仅使用既有符号 `TextContent`(`@user/client-base`)/ `onMounted`(`vue`)/ `isNil`(`lodash-es`)/ `EnemyView`(`@user/data-system`)。**不改 `package.json`**。 + + ⑤ **开放项(须用户裁定):** OI-R1(R-1 是否仅移除背景、不新增背景控制器接线,默认「仅移除」);OI-R2(`IHeroAttrItem` 是否随 R-5 取消导出,默认「取消」);OI-R3(R-3 jsDoc 覆盖范围,默认见清单,且已有注释定义不叠加);OI-R4(`TextContent` 的 `width` 尺寸取近似可用宽度、保留外层 `Scroll`,不改布局结构)。 + + ⑥ **验证方式(须用户确认):** 静态源码断言——背景移除(book/bookDetail 无 `/ + 是否按本计划对 `book.tsx` / `bookDetail.tsx` / `equipbox.tsx` / `toolbox.tsx` / `shop.tsx` 应用复查修正 R-1..R-8(R-8 采用 `instanceof EnemyView`,已确认可行;零新命名),并按 OI-R1..OI-R4 的默认处置执行 + + AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。本次核心 = 逐文件命中清单 + R-8 可行性(`EnemyView` 为运行期类,可用)+ 零新命名 + OI-R1..OI-R4。行号为规划日实测、可能漂移,门禁按符号文本匹配。 + + + + + + + 回复「可以执行」四个字(如需调整 OI-R1..OI-R4 的处置,请在回复中指明;修订意见请按 action ⑧ 处理) + + node -e "const fs=require('fs');const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const has=p=>fs.existsSync(p);const bad=[];const files=['packages-user/client-modules/src/ui/book.tsx','packages-user/client-modules/src/ui/bookDetail.tsx','packages-user/client-modules/src/ui/equipbox.tsx','packages-user/client-modules/src/ui/toolbox.tsx','packages-user/client-modules/src/ui/shop.tsx'];for(const f of files){if(!has(f))bad.push('missing '+f)}if(has(files[1])){const t=read(files[1]);if(!/'getBaseEnemy' in props\.enemy/.test(t))bad.push('pre-state changed: bookDetail structural check gone')}if(has('packages-user/data-system/src/combat/enemy.ts')){const e=read('packages-user/data-system/src/combat/enemy.ts');if(!/export class EnemyView/.test(e))bad.push('pre-state changed: EnemyView class missing')}else{bad.push('pre-state changed: combat/enemy.ts missing')}const ci=has('packages-user/data-system/src/combat/index.ts')?read('packages-user/data-system/src/combat/index.ts'):'';if(!/from '\.\/enemy'/.test(ci))bad.push('pre-state changed: combat barrel no ./enemy')}const idx=read('packages-user/data-system/src/index.ts');if(!/from '\.\/combat'/.test(idx))bad.push('pre-state changed: data-system index no ./combat');if(has('packages-user/client-modules/src/ui/shop.tsx')){const s=read('packages-user/client-modules/src/ui/shop.tsx');if(!/props\.shopSystem\.openShop\(shop\)/.test(s))bad.push('pre-state changed: shop openShop call gone')}if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state intact (5 files exist; EnemyView class present+exported; bookDetail structural check + shop openShop call present)')" + non-zero exit, 或 stderr 含 "PRESTATE FAILED"(五文件缺失 / `EnemyView` 类或其导出链漂移 / `bookDetail` 结构判别或 `shop` openShop 调用已不存在) + 用户在对话中明确回复「可以执行」四个字(人工核对回复原文;用户须就 OI-R1..OI-R4 给出裁定;若用户指定非默认处置,须在汇报与后续任务中据其指定执行) + + + - 用户回复中明确包含「可以执行」四个字 + - 汇报内容涵盖:R-1..R-8 的**逐文件实测命中清单**、R-8 可行性结论(`EnemyView` 为运行期类,可用 `instanceof`)、命名结论(零新命名)、OI-R1..OI-R4、验证方式(静态断言,无全仓门禁)、范围与硬边界 + - 汇报中已原样告知:**不引入任何新命名、不改既有注释、不改数据端 / 系统层 / 渲染系统 / 其它 UI / `package.json`、R-8 不强行落地(无须守卫 / 断言即可)** + - 汇报中已原样告知**关卡性质**(必须停下等待,不因触发方式或 `yolo` / `auto_advance` 自动放行) + + 用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理 + + + + Task 1(book + bookDetail):修正 `book.tsx` 与 `bookDetail.tsx` —— R-1 移除背景、R-2 描述改 `TextContent`、R-3/R-4、R-6、R-8 `instanceof EnemyView` + Task 0 已闭环:用户明确回复「可以执行」四个字(若用户就 OI-R1..OI-R4 另有指定,按指定执行)。 + packages-user/client-modules/src/ui/book.tsx, packages-user/client-modules/src/ui/bookDetail.tsx + + - packages-user/client-modules/src/ui/book.tsx(本文件现状:R-1 背景 L110;R-6 `critical !== null` L182/191/200/209;顶层定义) + - packages-user/client-modules/src/ui/bookDetail.tsx(本文件现状:R-1 背景 L110;R-2 描述 L168;R-6 `critical.value !== null` L186/195;R-8 结构判别 L39/44/49/93) + - packages-user/client-base/src/components/textbox.tsx:40-135(`TextContentProps`:`text` / `width`(必填) / `loc` / `height` / `lineHeight` / `autoHeight` / `font` / `fillStyle`) + - packages-user/client-modules/src/ui/statistics.tsx:104-110(`` 用法先例)、statusBar.tsx:10(从 `@user/client-base` 导入 `TextContent`) + - packages-user/data-system/src/combat/enemy.ts:4(`export class EnemyView`) + - packages-user/client-modules/src/ui/background.tsx:17-28(背景由 `MainBackgroundUI` 处理) + - dev.md(import 置首;无 `import type`;无 `?.`;对象用 `!obj`;CRLF;不改既有注释) + + + **(1) `book.tsx`:** + - **R-1**:删除第 110 行 ``(不再自绘整屏背景)。 + - **R-6**:将 4 处 JSX 条件 `{critical !== null && ( … )}`(L182/191/200)与 `{critical !== null && def1 !== null && ( … )}`(L209)改为对象真值判断 `{critical && ( … )}` / `{critical && def1 && ( … )}`(`critical` / `def1` 为对象或 `null`,用 `!` 约定的真值形式;不写 `!== null`)。 + - **R-3**:为缺 jsDoc 的顶层定义补 jsDoc(按 `dev.md:72-86`):模块级 `bookProps`(如「怪物手册组件的 props 声明」)、`Book`(如「怪物手册列表组件」)、`BookUI`(如「怪物手册列表的 UI 控制器」);setup 内 `ctx`(如「当前地图的怪物上下文」)、`format`、`specialText`、`statX`、`openDetail`、`exit`(各写一行说明用途)。**已有注释的 `enemies` 等不叠加。** + - **R-4**:在 JSX 中加 `{/* … */}` 分段注释(如标题、数值列表区、退出按钮段)。 + - **收口**:不改行为其余部分;不改既有注释;无 `import type` / `?.`;CRLF。 + + **(2) `bookDetail.tsx`:** + - **R-1**:删除第 110 行 ``。 + - **R-8**:`import { IEnemyCritical, IEnemyView, EnemyView } from '@user/data-system';`;将 4 处结构判别 `'getBaseEnemy' in props.enemy`(`getView` L39 / `getBase` L44 / `getComputed` L49 / `toggleMode` L93)替换为 `props.enemy instanceof EnemyView`(保持原逻辑:为视图时取 `getBaseEnemy()` / `getComputedEnemy()`、可切换;否则视为 plain `IEnemy`)。 + - **R-2**:`import { Scroll, TextContent } from '@user/client-base';`;将特殊属性描述 ``(L168)替换为 ``(保留所在容器与 `Scroll`;`width` 取该行可用宽度,OI-R4)。`getSpecialName()` 短文本保持 ``。 + - **R-6**:`{critical.value !== null && ( … )}`(L186/195)改为 `{critical.value && ( … )}`。 + - **R-3**:补 jsDoc:模块级 `bookDetailProps` / `BookDetail` / `BookDetailUI`;setup 内 `mode` / `getView` / `getBase` / `getComputed` / `current` / `format` / `statX` / `toggleMode` / `close` / `toggleText`。**已有注释的 `specials` / `getLocator` 等不叠加**(`critical` 已有 jsDoc)。 + - **R-4**:JSX 加 `{/* … */}` 分段注释(标题、属性数值、特殊列表、临界区、左下切换 / 右下返回)。 + - **收口**:不改既有注释;无 `import type` / `?.`;CRLF;无未使用 import。 + + 仅对既有文件做源码级修正(注释 / 导入 / 非空判断 / 背景删除 / 文本载体 / 判别方式);`git revert` 可整体回退。 + + node -e "const fs=require('fs');const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const bad=[];const doc=(t,n)=>new RegExp('/\\*\\*[\\s\\S]*?\\*/\\s*(?:export\\s+)?(?:const|async\\s+function|function)\\s+'+n+'\\b').test(t); +const bp='packages-user/client-modules/src/ui/book.tsx';if(!fs.existsSync(bp)){bad.push('book.tsx missing')}else{const t=read(bp);if(/ still present (R-1)');if(/!== null/.test(t))bad.push('book: still has !== null (R-6)');if(!/\{critical &&/.test(t))bad.push('book: critical truthy check missing (R-6)');if(!/critical && def1 &&/.test(t))bad.push('book: critical&&def1 truthy check missing (R-6)');if(t.indexOf('{/*')<0)bad.push('book: no JSX section comment (R-4)');for(const n of ['bookProps','Book','BookUI','ctx','format','specialText','statX','openDetail','exit']){if(!doc(t,n))bad.push('book: missing jsDoc on '+n+' (R-3)')}if(/import type/.test(t))bad.push('book: import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('book: optional chaining');} +const bd='packages-user/client-modules/src/ui/bookDetail.tsx';if(!fs.existsSync(bd)){bad.push('bookDetail.tsx missing')}else{const t=read(bd);if(/ still present (R-1)');if(!/instanceof EnemyView/.test(t))bad.push('bookDetail: instanceof EnemyView missing (R-8)');if(/'getBaseEnemy' in props\.enemy/.test(t))bad.push('bookDetail: structural check still present (R-8)');if(!/EnemyView/.test(t)||!/from '@user\/data-system'/.test(t))bad.push('bookDetail: EnemyView import missing');if(!/TextContent/.test(t))bad.push('bookDetail: TextContent missing (R-2)');if(/text=\{special\.getDescription\(\)\}/.test(t))bad.push('bookDetail: description still via text= (R-2)');if(/!== null/.test(t))bad.push('bookDetail: still has !== null (R-6)');if(t.indexOf('{/*')<0)bad.push('bookDetail: no JSX section comment (R-4)');for(const n of ['bookDetailProps','BookDetail','BookDetailUI','mode','getView','getBase','getComputed','current','format','statX','toggleMode','close','toggleText']){if(!doc(t,n))bad.push('bookDetail: missing jsDoc on '+n+' (R-3)')}if(/import type/.test(t))bad.push('bookDetail: import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('bookDetail: optional chaining');} +if(bad.length>0){console.error('REVIEWFIX-A GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK book+bookDetail: R-1/R-2/R-3/R-4/R-6/R-8 applied')" + non-zero exit, 或 stderr 含 "REVIEWFIX-A GATE FAILED"(背景未移除 / `instanceof EnemyView` 未落地或旧结构判别仍在 / `TextContent` 未用于描述 / 仍含 `!== null` / 缺 jsDoc 或 JSX 注释 / 引入 `import type` 或 `?.`) + node -e "const fs=require('fs');const files=['packages-user/client-modules/src/ui/book.tsx','packages-user/client-modules/src/ui/bookDetail.tsx'];const bad=[];for(const f of files){if(!fs.existsSync(f)){bad.push('missing '+f);continue}const raw=fs.readFileSync(f,'utf8');if(/(? + non-zero exit, 或 stderr 含 "CRLF FAILED"(换行非 CRLF) + + + - `book.tsx`:无 `` 整屏背景;`critical` / `def1` 条件改为对象真值判断;顶层定义补 jsDoc(清单齐全);JSX 有分段注释 + - `bookDetail.tsx`:无整屏背景;4 处结构判别改为 `props.enemy instanceof EnemyView` 且 `EnemyView` 自 `@user/data-system` 导入;特殊属性描述改用 `TextContent`;`critical.value` 条件改为真值判断;顶层定义补 jsDoc;JSX 有分段注释 + - 两文件:无 `import type`、无 `?.`、无未使用 import、CRLF;未改既有注释;未改 `files_modified` 外文件 + + book.tsx 与 bookDetail.tsx 完成 R-1/R-2/R-3/R-4/R-6/R-8 修正;该组静态门禁 + CRLF 全绿 + + + + Task 2(equipbox + toolbox + shop + 收口):修正三文件 —— R-2 描述改 `TextContent`、R-3/R-4/R-5、R-6、R-7 `openShop` 移入 `onMounted`,并跑五文件最终静态门禁 + Task 1 已完成(`book.tsx` / `bookDetail.tsx` 已修正,五文件最终门禁的可达性成立)。 + packages-user/client-modules/src/ui/equipbox.tsx, packages-user/client-modules/src/ui/toolbox.tsx, packages-user/client-modules/src/ui/shop.tsx + + - packages-user/client-modules/src/ui/equipbox.tsx(R-2 描述 L338-351;R-5 `IHeroAttrItem` L10;R-6 L115/145/153/311/320/329/338/365/402) + - packages-user/client-modules/src/ui/toolbox.tsx(R-2 说明 L245-253;R-6 L226/236/245) + - packages-user/client-modules/src/ui/shop.tsx(R-2 描述 L299-310;R-5 `IShopItemEntry` L31;R-6 L114/218/293/304/328/346/364/382;R-7 `openShop` L95) + - packages-user/client-base/src/components/textbox.tsx:40-135(`TextContentProps`) + - packages-user/client-modules/src/ui/main.tsx:24(`import { isNil } from 'lodash-es'` 先例)、viewmap.tsx:356 / title.tsx:347(`onMounted` 先例) + - dev.md(import 置首;无 `import type`;无 `?.`;字面量 `!isNil`、对象 `!obj`;CRLF;不改既有注释) + + + **(1) `equipbox.tsx`:** + - **R-2**:`import { Scroll, TextContent } from '@user/client-base';`;将装备描述 ``(L344)替换为 ``(保留外层 ``;`width` 取描述区可用宽度,OI-R4)。 + - **R-5**:`export interface IHeroAttrItem` → `interface IHeroAttrItem`(取消导出;`EquipBoxProps.attributes` 仍引用之)。 + - **R-6**:`if (uid === undefined) return null;`(L115)→ `if (isNil(uid)) return null;`;`const another = existing === undefined ? -1 : existing;`(L145)→ `const another = isNil(existing) ? -1 : existing;`;`return equip === null ? -1 : equip.item.num;`(L153)→ `return equip ? equip.item.num : -1;`;`{equip === null &&`(L311)→ `{!equip &&`;`{equip !== null &&`(L320/329/338)→ `{equip &&`;`slotEquip !== null && slotEquip.uid === selected.value`(L365)→ `!!slotEquip && slotEquip.uid === selected.value`;`{slotEquip !== null &&`(L402)→ `{slotEquip &&`。并新增 `import { isNil } from 'lodash-es';`(import 块内、置于合适位置、无空行/注释)。 + - **R-3**:补 jsDoc:`equipBoxProps` / `EquipBox` / `EquipBoxUI` / `hero`(其余常量与 setup 内定义已有注释,不叠加)。 + - **R-4**:JSX 加 `{/* … */}` 分段注释(状态区、描述区、装备槽区、拥有装备区、退出)。 + - **收口**:不改既有注释;无 `import type` / `?.`;CRLF。 + + **(2) `toolbox.tsx`:** + - **R-2**:`import { Scroll, TextContent } from '@user/client-base';`;将道具说明 ``(L247)替换为 ``(保留外层 ``)。 + - **R-6**:`text={current === null ? '未选择道具' : current.raw.name}`(L226)→ `text={current ? current.raw.name : '未选择道具'}`;`{current === null && (`(L236)→ `{!current && (`;`{current !== null && (`(L245)→ `{current && (`。 + - **R-3**:补 jsDoc:`toolBoxProps` / `ToolBox` / `ToolBoxUI` / `hero`(其余已有注释,不叠加)。 + - **R-4**:JSX 加 `{/* … */}` 分段注释(永久道具列、可消耗道具列、名称、说明、退出)。 + - **收口**:同上。 + + **(3) `shop.tsx`:** + - **R-7**:`import { computed, defineComponent, onMounted, ref } from 'vue';`;将顶层 `props.shopSystem.openShop(shop);`(L95)移入 `onMounted(() => props.shopSystem.openShop(shop));`(消除顶层副作用)。 + - **R-2**:`import { Scroll, TextContent } from '@user/client-base';`;将道具描述 ``(L304)替换为 ``(保留外层 ``)。 + - **R-5**:`export interface IShopItemEntry` → `interface IShopItemEntry`(取消导出)。 + - **R-6**:`if (data === null) continue;`(L114)→ `if (!data) continue;`;`if (entry !== null) {`(L218)→ `if (entry) {`;`text={entry === null ? '未选择道具' : entry.name}`(L293)→ `text={entry ? entry.name : '未选择道具'}`;`text={entry === null ? '' : entry.text}`(L304,见 R-2);四处 `entry === null ? '-' : core.formatBigNumber(entry.xxx)`(L328/346/364/382)→ `entry ? core.formatBigNumber(entry.xxx) : '-'`。 + - **R-3**:补 jsDoc:`shopProps` / `Shop` / `ShopUI` / `shop` / `hero`(其余已有注释,不叠加)。 + - **R-4**:JSX 加 `{/* … */}` 分段注释(顶栏切换、商店列表、名称 / 描述 / 价格信息、数量选择、购买按钮、退出)。 + - **收口**:不改既有注释;无 `import type` / `?.` / 连续 `as`;CRLF。 + + 仅对既有三文件做源码级修正;`git revert` 可整体回退。 + + node -e "const fs=require('fs');const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const bad=[];const doc=(t,n)=>new RegExp('/\\*\\*[\\s\\S]*?\\*/\\s*(?:export\\s+)?(?:const|async\\s+function|function)\\s+'+n+'\\b').test(t); +const eb='packages-user/client-modules/src/ui/equipbox.tsx';if(!fs.existsSync(eb)){bad.push('equipbox.tsx missing')}else{const t=read(eb);if(!/isNil\(/.test(t)||!/from 'lodash-es'/.test(t))bad.push('equipbox: isNil not imported/used (R-6)');if(/=== undefined|=== null|!== null/.test(t))bad.push('equipbox: still has ===/!== null/undefined (R-6)');if(!/TextContent/.test(t))bad.push('equipbox: TextContent missing (R-2)');if(/text=\{equip\.item\.text\}/.test(t))bad.push('equipbox: description still via text= (R-2)');if(/export interface IHeroAttrItem/.test(t))bad.push('equipbox: IHeroAttrItem still exported (R-5)');if(!/interface IHeroAttrItem/.test(t))bad.push('equipbox: IHeroAttrItem gone');if(!/export interface EquipBoxProps/.test(t))bad.push('equipbox: EquipBoxProps must stay exported');if(!/export const EquipBoxUI\s*=\s*new GameUI\(/.test(t))bad.push('equipbox: EquipBoxUI export missing');if(t.indexOf('{/*')<0)bad.push('equipbox: no JSX section comment (R-4)');for(const n of ['equipBoxProps','EquipBox','EquipBoxUI','hero']){if(!doc(t,n))bad.push('equipbox: missing jsDoc on '+n+' (R-3)')}if(/import type/.test(t))bad.push('equipbox: import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('equipbox: optional chaining');} +const tb='packages-user/client-modules/src/ui/toolbox.tsx';if(!fs.existsSync(tb)){bad.push('toolbox.tsx missing')}else{const t=read(tb);if(/=== null|!== null|=== undefined/.test(t))bad.push('toolbox: still has ===/!== null/undefined (R-6)');if(!/TextContent/.test(t))bad.push('toolbox: TextContent missing (R-2)');if(/text=\{current\.raw\.text\}/.test(t))bad.push('toolbox: description still via text= (R-2)');if(t.indexOf('{/*')<0)bad.push('toolbox: no JSX section comment (R-4)');for(const n of ['toolBoxProps','ToolBox','ToolBoxUI','hero']){if(!doc(t,n))bad.push('toolbox: missing jsDoc on '+n+' (R-3)')}if(/import type/.test(t))bad.push('toolbox: import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('toolbox: optional chaining');} +const sh='packages-user/client-modules/src/ui/shop.tsx';if(!fs.existsSync(sh)){bad.push('shop.tsx missing')}else{const t=read(sh);if(/=== null|!== null|=== undefined/.test(t))bad.push('shop: still has ===/!== null/undefined (R-6)');if(!/TextContent/.test(t))bad.push('shop: TextContent missing (R-2)');if(/text=\{entry === null/.test(t))bad.push('shop: description still via text= (R-2)');if(/export interface IShopItemEntry/.test(t))bad.push('shop: IShopItemEntry still exported (R-5)');if(!/export interface ShopProps/.test(t))bad.push('shop: ShopProps must stay exported');if(!/export const ShopUI\s*=\s*new GameUI\(/.test(t))bad.push('shop: ShopUI export missing');if(!/onMounted\(/.test(t))bad.push('shop: onMounted missing (R-7)');if(t.indexOf('openShop(') top-level side effect (R-7)');if(!/onMounted\(\(\)\s*=>[\s\S]{0,160}openShop\(/.test(t))bad.push('shop: openShop not wrapped in onMounted (R-7)');if(t.indexOf('{/*')<0)bad.push('shop: no JSX section comment (R-4)');for(const n of ['shopProps','Shop','ShopUI','shop','hero']){if(!doc(t,n))bad.push('shop: missing jsDoc on '+n+' (R-3)')}if(/import type/.test(t))bad.push('shop: import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('shop: optional chaining');if(/as\s+\w+\s+as\s+/.test(t))bad.push('shop: chained as');} +if(bad.length>0){console.error('REVIEWFIX-B GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK equipbox+toolbox+shop: R-2/R-3/R-4/R-5/R-6/R-7 applied')" + non-zero exit, 或 stderr 含 "REVIEWFIX-B GATE FAILED"(仍含 `=== undefined`·`=== null`·`!== null` / `isNil` 未用 / `TextContent` 未替换描述 / `IShopItemEntry`·`IHeroAttrItem` 仍导出或 props 导出丢失 / `openShop` 未包裹于 `onMounted` / 缺 jsDoc 或 JSX 注释 / 引入 `import type`·`?.`·连续 `as`) + node -e "const fs=require('fs');const files=['packages-user/client-modules/src/ui/book.tsx','packages-user/client-modules/src/ui/bookDetail.tsx','packages-user/client-modules/src/ui/equipbox.tsx','packages-user/client-modules/src/ui/toolbox.tsx','packages-user/client-modules/src/ui/shop.tsx'];const bad=[];for(const f of files){if(!fs.existsSync(f)){bad.push('missing '+f);continue}const raw=fs.readFileSync(f,'utf8');const t=raw.replace(/\r\n/g,'\n');if(/(? + non-zero exit, 或 stderr 含 "REVIEWFIX-FINAL GATE FAILED"(五文件存在残留 `=== undefined`·`=== null`·`!== null` / `import type` / `?.` / 非 CRLF) + git -C . status --porcelain + 输出包含本计划 `files_modified`(五个 UI 文件 + `09-10-SUMMARY.md`)之外的生产文件改动(如 `packages/system/`、`packages/render`、`packages/legacy-ui/`、`packages-user/data-*`、`package.json`、其它 UI 或 09-01..09-09 的既有文件) + + + - `equipbox.tsx`:装备描述改用 `TextContent`;字面量 `isNil`、对象 `!`(无 `=== undefined`·`=== null`·`!== null`);`IHeroAttrItem` 未导出、`EquipBoxProps` 与组件 / `EquipBoxUI` 仍导出;顶层定义补 jsDoc;JSX 有分段注释 + - `toolbox.tsx`:道具说明改用 `TextContent`;对象真值判断(无 `=== null`·`!== null`);顶层定义补 jsDoc;JSX 有分段注释 + - `shop.tsx`:道具描述改用 `TextContent`;`IShopItemEntry` 未导出、`ShopProps` 与组件 / `ShopUI` 仍导出;对象真值判断;`openShop` 由 `onMounted` 包裹(顶层无副作用);顶层定义补 jsDoc;JSX 有分段注释 + - 五文件:无 `=== undefined` / `=== null` / `!== null`、无 `import type`、无 `?.`、无连续 `as`、CRLF、无未使用 import;未改既有注释;范围守卫无越界改动 + + equipbox / toolbox / shop 完成 R-2/R-3/R-4/R-5/R-6/R-7 修正;五文件最终静态门禁(含 CRLF 与范围守卫)全绿 + + + + + +## Trust Boundaries + +| Boundary | Description | +|----------|-------------| +| 修改范围边界 | 仅五个 UI 源码文件;不得波及其它 UI / 渲染系统 / 数据端 / `package.json` | +| 导出契约边界 | 仅取消 `IShopItemEntry` / `IHeroAttrItem` 的导出;props 接口与组件 / `*UI` 导出保持不变(若需变更须先获批) | +| 命名边界 | 本修正不引入任何新命名;如需引入须 Task 0 获批 | +| R-8 判别边界 | `instanceof EnemyView` 依赖运行期类身份;不得退化为结构判别或引入新守卫 | +| 提交边界 | 不得把用户并发改动(D-56)纳入暂存/提交 | + +## STRIDE Threat Register + +| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan | +|-----------|----------|-----------|----------|-------------|-----------------| +| T-09-10-01 | Tampering | 借修正之机改动五文件之外的生产文件(其它 UI / 渲染系统 / 数据端 / `package.json`) | high | mitigate | `files_modified` 白名单 + prohibitions + Task 2 `git status` 范围守卫 | +| T-09-10-02 | Elevation of Privilege | 未经批准即执行 / 引入未批准命名 | high | mitigate | Task 0 `blocking-human`;命名结论呈报;prohibitions 禁未获批命名 | +| T-09-10-03 | Tampering | 误改既有注释 / jsDoc(违反 AGENTS.md) | high | mitigate | prohibitions 明列「新增允许、修改禁止」;R-3 策略「已有注释不叠加」;门禁仅断言新增 jsDoc 存在 | +| T-09-10-04 | Tampering | R-8 强行落地致需类型守卫 / 断言 / 新命名 | medium | mitigate | 已核实 `EnemyView` 为运行期类、无须守卫;prohibitions 明列「如需守卫则停下汇报」 | +| T-09-10-05 | Tampering | R-6 改写遗漏(残留 `=== undefined` / `=== null` / `!== null`) | medium | mitigate | 逐处清单 + Task 1/2 门禁断言五文件无残留 | +| T-09-10-06 | Tampering | R-2 遗漏(长文本仍用 `text=`)或误判(短文本也改) | medium | mitigate | `` 明列四处长文本;门禁断言 `TextContent` 存在且描述 `text=` 消失;`book.tsx` 不命中 | +| T-09-10-07 | Tampering | R-7 未真正移除顶层副作用(`openShop` 仍在顶层) | medium | mitigate | 门禁断言 `onMounted(` 存在且 `openShop(` 位于其后并被包裹 | +| T-09-10-08 | Elevation of Privilege | 误改导出契约(props / 组件 / `*UI` 导出被删) | medium | mitigate | 门禁断言 `EquipBoxProps` / `ShopProps` 仍导出、`*UI = new GameUI` 仍在 | +| T-09-10-09 | Repudiation | 未跑静态门禁即提交 | medium | mitigate | Task 1/2 各有自动化门禁 + SUMMARY | +| T-09-10-10 | Tampering | 引入 `client` 单例 / `import type` / `?.` / 连续 `as` / 非 CRLF | medium | mitigate | prohibitions + Task 1/2 门禁 + 五文件 CRLF 门禁 | +| T-09-10-11 | Tampering | 提交时混入用户并发改动(D-56) | medium | mitigate | 提交前 `git status` 逐条核对,只暂存本计划条目 | +| T-09-10-SC | Tampering | npm/pip/cargo installs | low | accept | 无第三方包安装、无依赖安装(`lodash-es` 沿用既有用法,不改 `package.json`) | + + + +- **Task 1**:`book.tsx` 无 ``、`critical` / `def1` 真值判断、jsDoc 存在、`{/*` 存在;`bookDetail.tsx` 无整屏背景、4 处判别为 `props.enemy instanceof EnemyView`、描述用 `TextContent`、`critical.value` 真值判断、jsDoc 存在、`{/*` 存在;两文件无 `import type` / `?.`、CRLF。 +- **Task 2**:`equipbox.tsx` 描述用 `TextContent`、`isNil` 已用、无 `=== null`·`=== undefined`、`IHeroAttrItem` 未导出、`EquipBoxProps` / `EquipBoxUI` 仍在;`toolbox.tsx` 说明用 `TextContent`、无 `=== null`、jsDoc 齐全;`shop.tsx` 描述用 `TextContent`、`IShopItemEntry` 未导出、`ShopProps` / `ShopUI` 仍在、`openShop` 被 `onMounted` 包裹、jsDoc 齐全;三文件 `{/*` 存在。 +- **最终(五文件)**:无 `=== undefined` / `=== null` / `!== null`;无 `import type` / `?.` / 连续 `as`;CRLF;`git status --porcelain` 仅本计划 `files_modified` 条目被改动。 +- **无全仓门禁**:全程不使用 `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。 +- **人工复核**:结论写入 `09-10-SUMMARY.md`(含 R-1..R-8 的逐文件处置与 OI-R1..OI-R4 的最终裁定)。 + + + +- R-1:`book.tsx` / `bookDetail.tsx` 移除自绘整屏背景;其余三文件无自绘背景(不命中)。 +- R-2:`bookDetail` 特殊属性描述、`equipbox` 装备描述、`toolbox` 道具说明、`shop` 道具描述均改用 `TextContent`;`book.tsx` 无长文本(不命中)。 +- R-3:五文件顶层定义(模块级 + setup 内)补齐 jsDoc;已有注释者不叠加。 +- R-4:五文件 JSX 均有分段注释。 +- R-5:`IShopItemEntry` / `IHeroAttrItem` 取消导出;props 接口与组件 / `*UI` 导出保留。 +- R-6:五文件无 `=== undefined` / `=== null` / `!== null`;字面量用 `isNil`、对象用 `!`。 +- R-7:`shop.tsx` 的 `openShop` 移入 `onMounted`;组件顶层无副作用。 +- R-8:`bookDetail.tsx` 以 `instanceof EnemyView` 替代 `'getBaseEnemy' in …`(已确认可行)。 +- 范围零外溢;零新命名;零既有注释改动;无 `client` 单例;无 `import type` / `?.` / 连续 `as`;CRLF;不改 `package.json`。 + + +## Artifacts this phase produces + +| 符号 / 路径 | 类型 | 说明 | +|---|---|---| +| `packages-user/client-modules/src/ui/book.tsx`(修改) | UI | 移除自绘背景(R-1);顶层 jsDoc(R-3);JSX 分段注释(R-4);对象真值判断(R-6) | +| `packages-user/client-modules/src/ui/bookDetail.tsx`(修改) | UI | 移除自绘背景(R-1);描述改 `TextContent`(R-2);jsDoc(R-3);JSX 注释(R-4);真值判断(R-6);`instanceof EnemyView`(R-8) | +| `packages-user/client-modules/src/ui/equipbox.tsx`(修改) | UI | 描述改 `TextContent`(R-2);jsDoc(R-3);JSX 注释(R-4);`IHeroAttrItem` 取消导出(R-5);`isNil` / `!`(R-6) | +| `packages-user/client-modules/src/ui/toolbox.tsx`(修改) | UI | 说明改 `TextContent`(R-2);jsDoc(R-3);JSX 注释(R-4);`!`(R-6) | +| `packages-user/client-modules/src/ui/shop.tsx`(修改) | UI | 描述改 `TextContent`(R-2);jsDoc(R-3);JSX 注释(R-4);`IShopItemEntry` 取消导出(R-5);`!`(R-6);`openShop` 移入 `onMounted`(R-7) | + +**命名变更:** 无新增命名;仅取消两处类型导出(`IShopItemEntry` / `IHeroAttrItem`)。**不向 `src/ui/index.ts` 添加导出。** + +## Source Coverage Audit + +| 来源 | 项 | 处置 | +|------|----|------| +| GOAL | Phase 9(ROADMAP)「完成渲染端框架,产出可在浏览器运行并验证的最小版本」 | COVERED(增量·任务 3 复查修正子切片) | +| REQ | Phase 9 ROADMAP「Requirements: TBD」,无需求 ID | N/A(`requirements: []` 如实标注) | +| RESEARCH | 无 RESEARCH.md(本阶段为增量派发,无独立研究产物) | N/A | +| CONTEXT | R-1(不写 UI 背景) | COVERED(Task 1:book/bookDetail 移除;其余不命中) | +| CONTEXT | R-2(长文本必须用 `TextContent`) | COVERED(Task 1 bookDetail 描述;Task 2 equipbox/toolbox/shop 描述) | +| CONTEXT | R-3(顶层定义的 jsDoc) | COVERED(Task 1 + Task 2 逐文件 jsDoc) | +| CONTEXT | R-4(JSX 分段注释) | COVERED(Task 1 + Task 2) | +| CONTEXT | R-5(按需导出) | COVERED(Task 2:`IShopItemEntry` / `IHeroAttrItem` 取消导出) | +| CONTEXT | R-6(非空判断约定) | COVERED(Task 1 + Task 2 逐处;门禁断言无残留) | +| CONTEXT | R-7(组件顶层无副作用) | COVERED(Task 2:shop `openShop` 移入 `onMounted`) | +| CONTEXT | R-8(IEnemy / IEnemyView 判别) | COVERED(Task 1:bookDetail `instanceof EnemyView`;可行性已核实) | +| CONTEXT | D-23..D-41(四个已实现 UI 的既有形态) | COVERED(本计划不改其形态,仅应用 R-1..R-8;D-23..D-41 由 09-06..09-09 承载) | +| CONTEXT | D-03 / D-04 / D-06 / D-22(执行前获批 / 命名获批 / 不扩范围 / 逐界面讨论后规划) | COVERED(Task 0 + prohibitions + 范围门禁) | +| CONTEXT | Deferred Ideas | DEFERRED(未纳入任何任务) | +| 缺口 | 无(R-1..R-8 均可落地;R-8 已确认 `EnemyView` 可用) | N/A | + + +Create `.planning/phases/09-minimum-browser-runnable/09-10-SUMMARY.md` when done +