mirror of
https://github.com/motajs/template.git
synced 2026-10-21 14:50:26 +08:00
docs(09): finalize shop plan (09-09) - GAP-S4 resolved, OI-S6 confirmed, drop hotkey name caveat
This commit is contained in:
parent
5bace98025
commit
7f0c90e3af
@ -481,7 +481,7 @@ Plans:
|
||||
**Goal**: 在已完成的数据端(可在 Node 环境独立运行/回放验证)之上完成渲染端框架,产出可在浏览器中运行并验证的最小版本
|
||||
**Requirements**: TBD
|
||||
**Depends on**: Phase 8
|
||||
**Plans**: 5/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),消费用户提供的 `ShopState` 接口(实现待补),价格/交易/支付资源/公共面导出为待用户裁定的缺口;本阶段增量派发,其余任务待用户再派发)
|
||||
**Plans**: 5/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:
|
||||
**Wave 1**
|
||||
@ -509,7 +509,7 @@ Plans:
|
||||
- [ ] 09-06-PLAN.md — 任务 3·book 怪物手册(D-23..D-27):列表 `book.tsx`(绑定 `props.state.enemyContext`、按 `code` 去重、`baseAttribute`、9 项数值含「1 防」、滚动、点击开详情)+ 独立详情 `bookDetail.tsx`(`IEnemy|IEnemyView`、基础↔计算后切换、仅特殊列表、临界走系统二分法接口、左下切换/右下返回)
|
||||
- [ ] 09-07-PLAN.md — 任务 3·equipbox 装备界面(D-28..D-32):`equipbox.tsx`(左列状态区域 + 右侧 装备描述/装备槽横滚/拥有装备纵滚;选中增减;点击选中/再点穿上/拖拽装脱/右下退出;入参 `IHeroState<IHeroAttr>` + 属性 id+中文名列表)
|
||||
- [ ] 09-08-PLAN.md — 任务 3·toolbox 道具栏(D-34..D-36):`toolbox.tsx`(三列 永久/可消耗/名称+说明;数量降序同数量按 id;单击选中/再点使用 `useItem`;无丢弃拖拽;入参 `IHeroState<IHeroAttr>`)
|
||||
- [ ] 09-09-PLAN.md — 任务 4·shop 商店界面(D-38..D-41):`shop.tsx`(顶部 购买/售出 切换;左 商店列表 id 排序/库存右对齐/显滚动条;右 名称·描述·购入量选择(按钮上方 买价/卖价/存货/拥有/总价)·购买按钮;四按钮 `<< < {量} > >` ±10/±1、按物品分别记忆;右下退出;入参 `IShopState`(用户提供的接口)+ `IHeroState<IHeroAttr>`;价格/交易/支付资源/导出为待裁定缺口)
|
||||
- [ ] 09-09-PLAN.md — 任务 4·shop 商店界面(D-38..D-41,已按用户补齐接口修订):`shop.tsx`(顶部 购买/售出 切换;左 商店列表 id 排序/库存右对齐/显滚动条;右 名称·描述·购入量选择(按钮上方 买价/卖价/存货/拥有/总价,买价/卖价经 `IShopState.getPrice`)·购买/售出按钮;四按钮 `<< < {量} > >` ±10/±1、按物品分别记忆;右下退出;入参 `IShopState` + `IHeroState<IHeroAttr>` + `IShopSystem`(交易经 `buyItem`/`sellItem`);**GAP-S4 已闭合:`IShopSystem` / `IShopHeroAttr` 已从 `@user/data-system` 公共面导出**)
|
||||
|
||||
## Progress
|
||||
|
||||
|
||||
@ -17,63 +17,68 @@ estimate:
|
||||
confidence: low
|
||||
must_haves:
|
||||
truths:
|
||||
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-22)**:Task 0 必须先汇报「本计划 = 任务 4 · shop 商店界面」的要做什么 / 为什么 / 大致怎么做 / 影响范围,**逐一列出所有新增命名(文件名、组件名、`GameUI` 名、props 接口名、行类型名)请求批准**,并**明确列出接口缺口(GAP-S0..GAP-S3)与开放项(OI-S1..OI-S5)**,然后必须**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行本计划任何一步、不得派发执行子代理,且不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行。"
|
||||
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03 / D-22)**:Task 0 必须先汇报「本计划 = 任务 4 · shop 商店界面」的要做什么 / 为什么 / 大致怎么做 / 影响范围,**逐一列出所有新增命名(文件名、组件名、`GameUI` 名、props 接口名、props 数组、行类型名;均已获用户批准,此处呈报确认)**,并**明确列出接口现状(GAP-S0..GAP-S4 已由用户全部补齐)与开放项(OI-S1..OI-S6)**,然后必须**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行本计划任何一步、不得派发执行子代理,且不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行。"
|
||||
- "**D-38(布局):** 顶部「**购买选项 | 售出选项**」切换;左「**商店列表**」;右列自上而下 = 「**选中物品名称**」/「**选中物品描述**」/「**购入量选择**」(其**按钮上方**显示 **买价 / 卖价 / 存货 / 拥有 / 总价** 等价格信息)/「**购买按钮**」。占满整个游戏画面(`FULL_LOC`,D-33);退出按钮右下(D-37)。参考图 `.planning/images/shop.png` 仅布局参考。"
|
||||
- "**D-39(模式 / 列表):** 可在**购入 / 售出**间切换;**售出也用「商店列表」**(商店不卖的,玩家也不能从背包售出);列表**按 id 排序**(等价身份键取 `num`);**库存显示在物品最右侧、右对齐**;列表**可滚动、显示滚动条**(`<Scroll loc={...}>` 不加 `noscroll`)。"
|
||||
- "**D-40(数量语义):** 数量调整四个按钮 `<< < {当前购入/售出量} > >>`,步进**同 legacy**(`<<`/`>>` = ±10,`<`/`>` = ±1);上限 = **购入:商店库存**(`shop.getCount(num)`)、**售出:玩家拥有数量**(`heroState.items.itemCount(num)`);**数量按物品分别记忆**(切换物品时保存各自数量);「购买按钮」买**当前选中物品**的数量。"
|
||||
- "**D-41(边界 / 依赖):** 本任务**只负责 UI 编写,不负责消费者如何调用**(**打开方式不管**);商店数据由数据端 **`IShopState`**(`packages-user/data-base/src/shop/types.ts:12-43`,**用户已提供 · 实现待补**)承载;入参经 props 传入(不自建单例、不接线打开方式)。"
|
||||
- "**接口现状(须 Task 0 呈报用户确认,含缺口):** 已提供 `IShopState`(`enabled` / `addItem(token,count)` / `getCount(token)`(不存在返回 `-1`)/ `iterateItems(): Iterable<[num,count]>` / `enable()` / `disable()`,`shop/types.ts:12-43`)与 `IShopManager`(`shop/types.ts:50-95`)。**缺口:** GAP-S0(`IShopState` 未从 `@user/data-base` 公共面导出,`data-base/src/index.ts:1-6` 无 `./shop`,且无 `shop/index.ts`);GAP-S1(**买价 / 卖价**缺来源——`IShopState` / `IItemRawData` 均无价格字段,legacy 价格在 `ItemShopChoice.money`/`.sell`,`src/types/declaration/eventDec.d.ts:110,115`);GAP-S2(**购买 / 售出交易**缺方法——`IShopState` 仅 `addItem` 调库存,无金钱 / 物品转移+扣库存的整体交易);GAP-S3(**支付资源** `money` vs `exp` 缺字段——legacy `ItemShopEvent.use`,`eventDec.d.ts:135`)。**一律 flag,不 invent。**"
|
||||
- "**范围零外溢 + 静态门禁:** 仅新增 `shop.tsx`;**不修改** 09-01..09-08 任何文件、不修改任何既有 UI / `src/render/**` / `packages/system` / `packages/render*` / 数据端(`packages-user/data-*`,**含 `shop/types.ts` 与 `index.ts`**)/ `legacy-ui`(**不实现数据端 API、不补 barrel 导出**);验证一律为**静态源码断言**(文件存在、关键符号存在、props 数组含 `controller/instance/state/shop/heroState`、无 `client` 单例、无 `import type`、无 `?.`、CRLF),**不使用** `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数作门禁。"
|
||||
- "**不改既有注释、不引入未批准命名:** 新建文件内所有注释按 `dev.md:72-86` 新写;所有新增公共命名在 Task 0 呈报用户批准后方可使用。"
|
||||
- "**D-41(边界 / 依赖):** 本任务**只负责 UI 编写,不负责消费者如何调用**(**打开方式不管**);商店数据由数据端 **`IShopState`** 承载(`packages-user/data-base/src/shop/types.ts:14-59`),交易由数据端 **`IShopSystem`**(`packages-user/data-system/src/shop/types.ts:8-45`)执行;两者均经 props 传入(不自建单例、不接线打开方式)。"
|
||||
- "**接口现状(用户已补齐 GAP-S0..GAP-S4,须 Task 0 呈报确认):** 已提供 `IShopState`(`enabled` `:16` / `addItem(token,count)` `:23` / `getCount(token)`(不存在返回 `-1`)`:29` / `iterateItems(): Iterable<[num,count]>` `:34` / **`setPrice(item,buy,sell)` `:42`** / **`getPrice(item): [buy,sell]` `:48`** / `enable()` `:53` / `disable()` `:58`,`shop/types.ts:14-59`);**价格字段已补齐**(GAP-S1 闭合,`IShopStateSave.prices: ReadonlyMap<number,[buy,sell]>` `:11`);`IShopManager`(`shop/types.ts:66-117`);**交易接口已补齐**(GAP-S2 闭合:`IShopSystem.buyItem(item,count)` `data-system/src/shop/types.ts:37` / `sellItem(item,count)` `:44`);**支付资源已明确为 `money`**(GAP-S3 闭合:`IShopHeroAttr.money` `data-system/src/shop/types.ts:3-6`)。**GAP-S4 亦已闭合**(用户已补齐 `@user/data-system` 的 `shop` 导出:`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`(`export * from './types'`),与 `./combat` / `./event` / `./path` 的既有 barrel 模式一致;`IShopSystem` / `IShopHeroAttr` 现可从 `@user/data-system` 导入)。**所有接口缺口(GAP-S0..GAP-S4)均已由用户补齐;一律不 invent。**"
|
||||
- "**范围零外溢 + 静态门禁:** 仅新增 `shop.tsx`;**不修改** 09-01..09-08 任何文件、不修改任何既有 UI / `src/render/**` / `packages/system` / `packages/render*` / 数据端(`packages-user/data-*`,**含 `shop/types.ts`、`data-system/src/shop/types.ts`、`data-base/src/index.ts`、`data-system/src/index.ts`**)/ `legacy-ui`(**不实现数据端 API、不补任何 barrel 导出**);验证一律为**静态源码断言**(文件存在、关键符号存在、props 数组含 `controller/instance/state/shop/heroState/shopSystem`、无 `client` 单例、无 `import type`、无 `?.`、CRLF),**不使用** `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数作门禁。"
|
||||
- "**不改既有注释、不引入未批准命名:** 新建文件内所有注释按 `dev.md:72-86` 新写;所有新增公共命名均已获用户批准(见 Task 0 清单)。"
|
||||
artifacts:
|
||||
- path: packages-user/client-modules/src/ui/shop.tsx
|
||||
provides: "D-38..D-41 的商店 UI:顶部 购买/售出 切换;左侧 商店列表(id 排序 / 库存右对齐 / 可滚动显滚动条);右侧 物品名称 / 描述(纵滚无滚动条)/ 购入量选择(`<< < {量} > >>`,按物品分别记忆,上限 库存/拥有,按钮上方 买价/卖价/存货/拥有/总价)/ 购买按钮;右下退出;入参 `IShopState` + `IHeroState<IHeroAttr>`。"
|
||||
provides: "D-38..D-41 的商店 UI:顶部 购买/售出 切换;左侧 商店列表(id 排序 / 库存右对齐 / 可滚动显滚动条);右侧 物品名称 / 描述(纵滚无滚动条)/ 购入量选择(`<< < {量} > >>`,按物品分别记忆,上限 库存/拥有,按钮上方 买价/卖价/存货/拥有/总价)/ 购买按钮;右下退出;入参 `IShopState` + `IHeroState<IHeroAttr>` + `IShopSystem`。"
|
||||
key_links:
|
||||
- "`packages-user/client-modules/src/ui/shop.tsx` ↔ `IShopState`(`packages-user/data-base/src/shop/types.ts:12-43`)—— 商店数据来源(`iterateItems` / `getCount` / `enabled`;**当前未从包公共面导出,GAP-S0 待用户补齐**)"
|
||||
- "`shop.tsx` ↔ `IShopState.iterateItems(): Iterable<[num: number, count: number]>`(`shop/types.ts:32`)—— 商店列表来源(购入与**售出共用**同一列表,D-39);`IShopState.getCount(token)`(`:27`)—— 商店库存(不存在返回 `-1`)"
|
||||
- "`packages-user/client-modules/src/ui/shop.tsx` ↔ `IShopState`(`packages-user/data-base/src/shop/types.ts:14-59`)—— 商店数据来源(`iterateItems` `:34` / `getCount` `:29` / `enabled` `:16` / **`getPrice` `:48`**);**已从 `@user/data-base` 公共面导出**(`data-base/src/index.ts:5` `export * from './shop'` + `shop/index.ts:1`,GAP-S0 已闭合)"
|
||||
- "`shop.tsx` ↔ `IShopState.iterateItems(): Iterable<[num: number, count: number]>`(`shop/types.ts:34`)—— 商店列表来源(购入与**售出共用**同一列表,D-39);`IShopState.getCount(token)`(`:29`)—— 商店库存(不存在返回 `-1`)"
|
||||
- "`shop.tsx` ↔ `IShopState.getPrice(item): [buy: number, sell: number]`(`shop/types.ts:48`)+ `IShopStateSave.prices: ReadonlyMap<number, [buy: number, sell: number]>`(`:11`)—— **买价 / 卖价**(GAP-S1 已闭合;不使用 legacy `ItemShopChoice.money`/`.sell`)"
|
||||
- "`shop.tsx` ↔ `IShopSystem.buyItem(item, count)`(`packages-user/data-system/src/shop/types.ts:37`)/ `sellItem(item, count)`(`:44`)—— **购买 / 售出交易**(GAP-S2 已闭合);`IShopSystem.openShop(shop)`(`:25`)/ `closeShop()`(`:30`)—— 交易所针对的当前商店(OI-S6 已确认:UI 于 setup 调用 `shopSystem.openShop(props.shop)`);`IShopHeroAttr.money`(`:5`)—— **支付资源 = money**(GAP-S3 已闭合)"
|
||||
- "`shop.tsx` ↔ `props.state.itemStore.getData(token)` → `IItemRawData`(`name` / `text` / `category`,`data-common/src/store/types.ts:176-200`)—— 物品名称 / 描述"
|
||||
- "`shop.tsx` ↔ `props.state.tileStore.num(token)` / `id(token)`(`store/types.ts:106,112`)—— num ↔ id 归一(列表按 id 排序、图标取 num)"
|
||||
- "`shop.tsx` ↔ `IHeroState.items.itemCount(num)`(`data-base/src/hero/types.ts:645`)/ `getItemState(num)`(`:627`)/ `addItem(num,count)`(`:621`)—— 玩家拥有数量 / 交易物品转移"
|
||||
- "`shop.tsx` ↔ `IHeroState.attribute.getFinalAttribute('money')`(`hero/types.ts:117,933`)+ `IHeroState.getModifiableAttribute().add('money', delta)`(`hero/types.ts:198,947`)—— 当前金币 / 交易金额变更"
|
||||
- "`shop.tsx` ↔ `IHeroState.items.itemCount(num)`(`data-base/src/hero/types.ts:645`)—— 玩家拥有数量(售出上限 / 「拥有」显示);`IHeroState.attribute.getFinalAttribute('money')`(`hero/types.ts:117,933`)—— 当前金币(可选显示)"
|
||||
- "`shop.tsx` ↔ `props.state`(`IClientBase`,`client-base/src/types.ts:15-50`)+ `IUIPropsBase`(`client-modules/src/ui/types.ts:1-5`)+ `props.controller.close(props.instance)`(D-37)—— 主对象经 props 获取 / 退出"
|
||||
- "`shop.tsx` ↔ `client-base/src/components/scroll.tsx`(`<Scroll>`(显滚动条)/ `<Scroll noscroll>`(描述),`:109`)+ `components/misc.tsx`(`<Selection>`,`:304`)+ `elements/props.ts`(`<icon>`,`:6-15`)—— 渲染骨架"
|
||||
assumptions:
|
||||
- "**`IShopState` 可导入(GAP-S0)**:假定用户在 Task 0 前补齐 `data-base` 公共面导出(`shop/index.ts` + `index.ts` `export * from './shop'`,或等价);若未补齐,Task 1 因无法命名 `shop: IShopState` 而**阻塞**(不 invent 类型、不改数据端)。"
|
||||
- "**`IShopState` 可导入(GAP-S0 已闭合)**:`data-base/src/index.ts:5` 已 `export * from './shop'`,`shop/index.ts` 已 `export * from './types'`,故 `import { IShopState } from '@user/data-base'` 成立。"
|
||||
- "**`IShopSystem` 可导入(GAP-S4 已闭合)**:`IShopSystem` 定义于 `data-system/src/shop/types.ts:8-45`,且用户已补齐 `@user/data-system` 公共面导出(`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`(`export * from './types'`)),故 `import { IShopSystem } from '@user/data-system'` 成立。不做数据端改动。"
|
||||
- "**「列表按 id 排序」等价身**:列表项为 `[num, count]`;排序键用 `num` 升序(数值稳定,与字符串 id 一一对应,需 id 时经 `tileStore.id(num)`)。"
|
||||
- "**「拥有数量」**:默认 `heroState.items.itemCount(num)`(`hero/types.ts:645`);若用户要求显示装备等其它口径,据其指定。"
|
||||
- "**「当前金币」**:默认 `heroState.attribute.getFinalAttribute('money')`。若 GAP-S3 确认商店使用 `exp`,支付资源改为 `exp`(据用户裁定)。"
|
||||
- "**买价 / 卖价 / 交易(GAP-S1 / GAP-S2)默认处置待 Task 0 裁定**:默认方案 = UI 仅消费既有公开接口,价格与交易在数据侧接口补齐前以**明确标记的 `// TODO` 占位**(不 invent 价格 / 交易 / 支付资源接口)。若用户选择「由 UI 以既有公开接口组装交易」(`shop.addItem` + `hero.items.addItem` + `hero.getModifiableAttribute().add('money', …)`),则据其指定实现。"
|
||||
- "**买价 / 卖价 / 交易来源(GAP-S1 / GAP-S2 已闭合)**:买价 = `props.shop.getPrice(num)[0]`,卖价 = `props.shop.getPrice(num)[1]`;购买 = `props.shopSystem.buyItem(num, count)`,售出 = `props.shopSystem.sellItem(num, count)`。**不再以 `// TODO` 占位、不再由 UI 组装金钱/物品转移。**"
|
||||
- "**支付资源(GAP-S3 已闭合)**:默认 `money`(`IShopHeroAttr.money`,`data-system/src/shop/types.ts:5`),由 `IShopSystem` 内部经 `bindHero` 的勇士对象扣付;UI 不直接改金钱。若日后改为 `exp`,由数据端调整(本 UI 不感知)。"
|
||||
- "**当前商店(OI-S6 已确认)**:`buyItem` / `sellItem` 作用于「当前打开的商店」;UI 在 setup 时调用 `shopSystem.openShop(props.shop)`(纯数据端选择,不打开 UI,符合 D-41「打开界面方式不管」)。此为确认后的行为。"
|
||||
- "**执行日行号可能漂移**:接口锚点为 2026-10-10 规划日;门禁按**符号文本**匹配,不硬编码行号。"
|
||||
prohibitions:
|
||||
- "不得在用户明确回复「可以执行」四个字之前执行本计划的任何任务、或派发任何执行子代理。"
|
||||
- "本计划**只做任务 4 · shop**:不得实现 / 修改 book / equipbox / toolbox;不得接线「如何打开 shop」(打开方式不管);不得实现消费者调用流程。"
|
||||
- "**不得臆造接口 / 命名**:不得新增数据端 API(含价格 / 交易 / 支付资源);不得为 `IShopState` 补价格字段或 barrel 导出;不得新增未在 Task 0 呈报获批的任何公共 / 受保护 / 私有命名。"
|
||||
- "不得修改 `files_modified` 之外的任何**生产**文件;不得改 09-01..09-08 及其产物;不得改 `packages/system` / `packages/render` / `packages/render-vue`;不得改数据端(`packages-user/data-*`,含 `shop/types.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`)与 `packages/**`;不得移除 / 改名任何既有单例。"
|
||||
- "**不得臆造接口 / 命名**:不得新增数据端 API;不得为 `IShopState` 补价格 / 交易字段;不得为数据端补任何 barrel 导出(数据端 barrel 由用户维护,GAP-S0..GAP-S4 已全部闭合);不得新增未在 Task 0 呈报获批的任何公共 / 受保护 / 私有命名。"
|
||||
- "不得修改 `files_modified` 之外的任何**生产**文件;不得改 09-01..09-08 及其产物;不得改 `packages/system` / `packages/render` / `packages/render-vue`;不得改数据端(`packages-user/data-*`,含 `shop/types.ts`、`data-system/src/shop/types.ts`、`data-base/src/index.ts`、`data-system/src/index.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`)与 `packages/**`;不得移除 / 改名任何既有单例。"
|
||||
- "不得修改任何既有 `jsDoc` 与注释;不得引入 `import type`(`dev.md:54`);不得使用 `?.`(`dev.md:107`);CRLF;不得遗留未使用 import;不得引入连续 `as`(`dev.md:96`);所有 `class` 必须先 `interface` 后 `implements`(`dev.md:100`)。"
|
||||
- "不得把门禁建立在「全仓 TS 诊断数」「`pnpm check:type`」「`pnpm build`」之上。"
|
||||
- "不得触碰、暂存、提交用户的并发改动(D-56):提交前 `git status` 逐条核对暂存面,只暂存本计划 `files_modified` 的条目(当前并发改动含 `shop/types.ts`(未跟踪)、`data-state/src/common.ts`、`src/content/core.jsonc`——一律不碰)。"
|
||||
- "不得触碰、暂存、提交用户的并发改动(D-56):提交前 `git status` 逐条核对暂存面,只暂存本计划 `files_modified` 的条目(当前并发改动含 `shop/types.ts`(未跟踪)、`data-system/src/shop/types.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`——一律不碰)。"
|
||||
open_items:
|
||||
- "**GAP-S0(类型导出)**:`IShopState` / `IShopManager` 未从 `@user/data-base` 公共面导出(`data-base/src/index.ts:1-6` 无 `./shop`,且无 `shop/index.ts`)。**须用户补齐**(数据侧);未补齐前 Task 1 阻塞。"
|
||||
- "**GAP-S1(买价 / 卖价)**:`IShopState` / `IItemRawData` 无价格字段;legacy 价格在 `ItemShopChoice.money`/`.sell`(`eventDec.d.ts:110,115`)。**须用户提供价格来源**,否则 买价/卖价/总价 以 `// TODO` 占位(D-38 价格信息部分受阻)。"
|
||||
- "**GAP-S2(交易)**:`IShopState` 无 购买 / 售出 交易方法。**须用户裁定**:由数据侧提供交易方法,或由 UI 以既有公开接口组装(默认:UI 组装,见 assumptions)。"
|
||||
- "**GAP-S3(支付资源)**:商店支付 `money` 还是 `exp` 缺字段(legacy `ItemShopEvent.use`,`eventDec.d.ts:135`)。默认按 `money`;请确认。"
|
||||
- "**GAP-S0(类型导出):已闭合** —— `IShopState` / `IShopManager` 已从 `@user/data-base` 公共面导出(`data-base/src/index.ts:5` + `shop/index.ts:1`)。"
|
||||
- "**GAP-S1(买价 / 卖价):已闭合** —— `IShopState.getPrice(item): [buy, sell]`(`shop/types.ts:48`)+ `IShopStateSave.prices`(`:11`)。"
|
||||
- "**GAP-S2(交易):已闭合** —— `IShopSystem.buyItem` / `sellItem`(`data-system/src/shop/types.ts:37,44`)。"
|
||||
- "**GAP-S3(支付资源):已闭合** —— `money`(`IShopHeroAttr.money`,`data-system/src/shop/types.ts:5`);若日后改 `exp` 由数据端调整。"
|
||||
- "**GAP-S4(系统层公共面导出):已闭合** —— 用户已补齐:`packages-user/data-system/src/shop/index.ts`(`export * from './types'`)+ `data-system/src/index.ts` `export * from './shop'`;`IShopSystem` / `IShopHeroAttr` 现可从 `@user/data-system` 导入。Task 1 无阻塞。"
|
||||
- "**OI-S1(列表范围)**:售出列表是否严格 = 商店列表(D-39 已决「是」,商店不卖的不能售出);确认无「玩家背包中商店不卖的道具」入列。"
|
||||
- "**OI-S2(列表过滤)**:是否过滤 `enabled === false` 的商店 / 无库存物品?默认:单商店界面(入参即一个 `IShopState`),列表 = `iterateItems()` 原样,不做 enabled 过滤。"
|
||||
- "**OI-S3(数量记忆粒度)**:数量按「物品」分别记忆(D-40);默认记忆于组件内 `Map<num, count>`,切换 购买/售出 时是否共用同一记忆?默认**按期模式分别记忆**(购买量与售出量各记),待确认。"
|
||||
- "**OI-S4(数字格式化)**:价格 / 数量格式化默认 `core.formatBigNumber`(沿用仓库新 UI 约定);可否接受?"
|
||||
- "**OI-S5(命名)**:见 Task 0 命名清单。全部待批准(注意 `GameUI` 名 `'shop'` 与既有热键 id `'shop'`(`packages-user/client-modules/src/action/hotkey.ts:75,492`)是否冲突需确认)。"
|
||||
- "**OI-S5(命名):已确认** —— 全部新增命名按提案采纳(见 Task 0 清单):文件 `shop.tsx`、组件 `Shop`、`GameUI` 名 `'shop'`、`ShopProps`、props 数组含 `shopSystem`、行类型 `IShopItemEntry`、导出 `ShopUI`。"
|
||||
- "**OI-S6(当前商店 / openShop):已确认** —— `IShopSystem.buyItem` / `sellItem` 作用于「当前打开的商店」;UI 于 setup 调用 `shopSystem.openShop(props.shop)`(纯数据端选择,不打开 UI,符合 D-41)。此为确认后的行为。"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
|
||||
**要解决的问题(D-38..D-41):** 把 legacy `packages/legacy-ui` 的商店界面(`src/ui/shop.vue`)移植到新 UI 接口上,并按用户已定形态实现:顶部「购买选项 | 售出选项」切换;左「商店列表」;右列自上而下 = 物品名称 / 物品描述 / 购入量选择(按钮上方显示 买价/卖价/存货/拥有/总价)/ 购买按钮;占满游戏画面;购入/售出切换、售出复用商店列表、id 排序、库存最右右对齐、列表滚动带滚动条;数量四按钮 `<< < {量} > >>`(±10 / ±1),数量按物品分别记忆;退出右下。
|
||||
**要解决的问题(D-38..D-41):** 把 legacy `packages/legacy-ui` 的商店界面(`src/ui/shop.vue`)移植到新 UI 接口上,并按用户已定形态实现:顶部「购买选项 | 售出选项」切换;左「商店列表」;右列自上而下 = 物品名称 / 物品描述 / 购入量选择(按钮上方显示 买价/卖价/存货/拥有/总价)/ 购买按钮;占满游戏画面;购入/售出切换、售出复用商店列表、id 排序、库存最右右对齐、列表滚动带滚动条;数量四按钮 `<< < {量} > >`(±10 / ±1),数量按物品分别记忆;退出右下。
|
||||
|
||||
**大致解决方案(本计划 = 09-09):** Task 0(`blocking-human`)= 汇报范围、命名清单、**用户提供的 `IShopState` 接口成员**与**接口缺口(GAP-S0..GAP-S3)**、开放项(OI-S1..OI-S5)并等待「可以执行」;Task 1 = 实现 `shop.tsx` 的**布局与列表**(顶栏模式切换、左商店列表、右 名称/描述/信息区,库存右对齐、id 排序、列表显滚动条);Task 2 = 实现**数量选择 + 购买按钮 + 退出**并跑最终静态门禁。
|
||||
**大致解决方案(本计划 = 09-09,已按用户补齐的接口修订):** Task 0(`blocking-human`)= 汇报范围、命名清单、**用户已补齐的全部接口(`IShopState` + 价格 `getPrice`/`setPrice` + `IShopSystem` 交易 + `money` 支付资源 + `@user/data-system` 的 `shop` 导出,GAP-S0..GAP-S4 均已闭合)、开放项(OI-S1..OI-S6)**并等待「可以执行」;Task 1 = 实现 `shop.tsx` 的**布局与列表**(顶栏模式切换、左商店列表、右 名称/描述/信息区,库存右对齐、id 排序、列表显滚动条、**买价/卖价 经 `getPrice`**);Task 2 = 实现**数量选择 + 购买按钮(经 `IShopSystem.buyItem`/`sellItem`)+ 退出**并跑最终静态门禁。
|
||||
|
||||
**接口适配性(本计划关键):** 用户已提供数据端 `IShopState`(`shop/types.ts`,**实现待补**)——**接口成员** = `enabled` / `addItem` / `getCount` / `iterateItems` / `enable` / `disable`。**买价/卖价(GAP-S1)、交易(GAP-S2)、支付资源(GAP-S3)、公共面导出(GAP-S0)缺失**,一律 **flag 不 invent**;由 Task 0 呈报用户裁定后据其落地。**标准。**
|
||||
**接口适配性(本计划关键,2026-10-10 修订):** 用户已补齐数据端接口 —— `IShopState`(含 **`getPrice`/`setPrice`**,GAP-S1 闭合;已从 `@user/data-base` 导出,GAP-S0 闭合);**`IShopSystem`(`buyItem`/`sellItem`,GAP-S2 闭合)**;**`money` 支付资源(GAP-S3 闭合)**。**GAP-S4 亦已闭合**:用户已补齐 `@user/data-system` 的 `shop` 导出(`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`),`IShopSystem` / `IShopHeroAttr` 现可从 `@user/data-system` 导入。**全部接口缺口(GAP-S0..GAP-S4)已闭合;不 invent。标准。**
|
||||
|
||||
**API coverage:** `detected:false`。**`No external API integration: 本计划仅为渲染端 UI 移植,数据来自数据端接口(`IShopState` / `IHeroState`),不涉及任何外部 API。`**
|
||||
**API coverage:** `detected:false`。**`No external API integration: 本计划仅为渲染端 UI 移植,数据来自数据端接口(`IShopState` / `IShopSystem` / `IHeroState`),不涉及任何外部 API。`**
|
||||
</objective>
|
||||
<execution_context>
|
||||
@C:/Users/13194/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||
@ -95,96 +100,99 @@ Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
<tasks>
|
||||
|
||||
<task type="checkpoint:decision" gate="blocking-human">
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报「任务 4 · shop 商店界面」范围、命名清单、用户提供的 IShopState 接口与接口缺口(GAP-S0..GAP-S3)与开放项,等待用户回复「可以执行」</name>
|
||||
<precondition>`09-05` 已闭环;仓库中 `shop.tsx` **尚不存在**;数据端 `shop/types.ts`(用户未跟踪新增)**已存在**(`IShopState` / `IShopManager`),但 **`IShopState` 尚未从 `@user/data-base` 公共面导出**(GAP-S0)。</precondition>
|
||||
<name>Task 0: 预执行汇报关卡 —— 汇报「任务 4 · shop 商店界面」范围、命名清单、用户已补齐的全部接口(IShopState 价格 + IShopSystem 交易 + money + @user/data-system 的 shop 导出,GAP-S0..GAP-S4 均已闭合)及开放项,等待用户回复「可以执行」</name>
|
||||
<precondition>`09-05` 已闭环;仓库中 `shop.tsx` **尚不存在**;数据端 `shop/types.ts`(`IShopState` / `IShopManager`,**含 `getPrice` / `setPrice`**)**已存在且已从 `@user/data-base` 导出**(GAP-S0 / GAP-S1 已闭合);数据端 `data-system/src/shop/types.ts`(`IShopSystem` / `IShopHeroAttr`)**已存在**,且 **已从 `@user/data-system` 公共面导出**(`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`;GAP-S4 已闭合)。</precondition>
|
||||
<files>(只读汇报,不修改任何文件)</files>
|
||||
<read_first>
|
||||
- .planning/phases/09-minimum-browser-runnable/09-CONTEXT.md(D-03 / D-04 / D-06 / D-18 / D-21 / D-22 / D-33 / D-37 / D-38 / D-39 / D-40 / D-41;任务 3 · 数据端接口补充)
|
||||
- .planning/phases/09-minimum-browser-runnable/09-09-PLAN.md(本计划全文,含命名清单、`IShopState` 成员、GAP-S0..GAP-S3 与 OI-S1..OI-S5)
|
||||
- packages-user/data-base/src/shop/types.ts:1-95(`IShopStateSave` / `IShopState`(`enabled`/`addItem`/`getCount`/`iterateItems`/`enable`/`disable`)/ `IShopManager`)
|
||||
- packages-user/data-base/src/index.ts(现有公共面,**无 `./shop`** → GAP-S0)
|
||||
- packages-user/data-common/src/store/types.ts:176-213(`IItemRawData`(`name`/`text`/`category`;**无价格字段** → GAP-S1)/ `IItemStore.getData` / `ITileStore`(`num`/`id`))
|
||||
- .planning/phases/09-minimum-browser-runnable/09-09-PLAN.md(本计划全文,含命名清单、接口成员与 OI-S1..OI-S6)
|
||||
- packages-user/data-base/src/shop/types.ts:1-117(`IShopStateSave`(含 `prices`)/ `IShopState`(`enabled`/`addItem`/`getCount`/`iterateItems`/**`setPrice`**/**`getPrice`**/`enable`/`disable`)/ `IShopManager`)
|
||||
- packages-user/data-base/src/shop/index.ts(`export * from './types'`)+ packages-user/data-base/src/index.ts(`export * from './shop'` → GAP-S0 已闭合)
|
||||
- packages-user/data-system/src/shop/types.ts:1-45(`IShopHeroAttr`(`money`)/ `IShopSystem`(`bindManager`/`bindHero`/`openShop`/`closeShop`/`buyItem`/`sellItem`)→ GAP-S2 / GAP-S3 已闭合)
|
||||
- packages-user/data-system/src/index.ts(**`export * from './shop'`** → GAP-S4 已闭合)+ packages-user/data-system/src/shop/index.ts(`export * from './types'`)+ packages-user/data-system/src/combat/index.ts(既有 barrel 模式先例)
|
||||
- packages-user/data-common/src/types.ts:25-48(`IHeroAttr`,含 `money`)
|
||||
- packages-user/data-common/src/store/types.ts:100-213(`IItemRawData`(`name`/`text`/`category`)/ `IItemStore.getData` / `ITileStore`(`num`/`id`))
|
||||
- packages-user/data-base/src/hero/types.ts:598-646(`IHeroItemState` / `IHeroItems`(`itemCount`/`getItemState`/`addItem`))、:104-198(`IReadonlyHeroAttribute.getFinalAttribute` / `IHeroAttribute.add`)、:926-957(`IHeroState`)
|
||||
- packages-user/client-base/src/types.ts:15-50(`IGameUIPropsBase` / `IClientBase extends ICoreState`);packages-user/client-modules/src/ui/types.ts:1-5(`IUIPropsBase`)
|
||||
- packages-user/client-base/src/components/scroll.tsx:55-109(`<Scroll>` props:`hor`/`noscroll`/`loc`)、components/misc.tsx:282-304(`<Selection>`)、elements/props.ts:6-15(`<icon>`)
|
||||
- packages-user/client-modules/src/shared.ts:76-98(`MAIN_WIDTH` / `MAIN_HEIGHT` / `FULL_LOC`)
|
||||
- .planning/images/shop.png(**布局参考**)
|
||||
- packages/legacy-ui/src/ui/shop.vue(布局 / 交互参考;D-20:不要求视觉一致)
|
||||
- src/types/declaration/eventDec.d.ts:96-141(legacy `ItemShopChoice`(`money`/`sell`/`number`)/ `ItemShopEvent`(`use: 'money' | 'exp'`)→ GAP-S1 / GAP-S3 证据)
|
||||
- dev.md(import 置首无空行/注释;无 `import type`;避免 `as`;无 `?.`;CRLF;interface 后 implements;不改既有注释)
|
||||
</read_first>
|
||||
<action>
|
||||
**停下并向用户汇报,等待明确回复「可以执行」后才继续;汇报本身只读。**
|
||||
|
||||
① **要解决的问题:** 任务 4 · shop 商店界面。把 legacy 商店移植到新接口;顶部 购买/售出 切换;左 商店列表;右 物品名称 / 描述 / 购入量选择(按钮上方 买价/卖价/存货/拥有/总价)/ 购买按钮;占满游戏画面;列表 id 排序、库存右对齐、滚动带滚动条;数量 `<< < {量} > >>`(±10/±1)按物品分别记忆;右下退出;打开方式不管。
|
||||
① **要解决的问题:** 任务 4 · shop 商店界面。把 legacy 商店移植到新接口;顶部 购买/售出 切换;左 商店列表;右 物品名称 / 描述 / 购入量选择(按钮上方 买价/卖价/存货/拥有/总价)/ 购买按钮;占满游戏画面;列表 id 排序、库存右对齐、滚动带滚动条;数量 `<< < {量} > >`(±10/±1)按物品分别记忆;右下退出;打开方式不管。
|
||||
|
||||
② **锁定裁定原文要点:** D-38(布局:顶切换 / 左列表 / 右 名称·描述·购入量选择·购买按钮;价格信息在按钮上方);D-39(购买/售出切换、售出复用商店列表、按 id 排序、库存最右右对齐、列表滚动显滚动条);D-40(四按钮 ±10/±1、上限 库存/拥有、数量按物品分别记忆、购买按钮买当前选中物品数量);D-41(只做 UI、打开方式不管、数据由 `ShopState` 承载);D-33 / D-37(占满游戏画面 root renderer、退出右下);D-03 / D-04 / D-06(执行前获批 / 命名获批 / 不扩范围)。
|
||||
|
||||
③ **用户提供的接口成员(`IShopState`,须用户确认):** 逐条列出——`enabled`(`:14`)、`addItem(token, count)`(`:21`)、`getCount(token)`(`:27`,不存在返回 `-1`)、`iterateItems(): Iterable<[num, count]>`(`:32`)、`enable()`(`:37`)、`disable()`(`:42`);另有 `IShopManager`(`:50-95`)。**须用户确认 UI 仅消费这些成员。**
|
||||
③ **用户已补齐的接口成员(须用户确认):**
|
||||
- `IShopState`(`shop/types.ts:14-59`):`enabled`(`:16`)、`addItem(token, count)`(`:23`)、`getCount(token)`(`:29`,不存在返回 `-1`)、`iterateItems(): Iterable<[num, count]>`(`:34`)、**`setPrice(item, buy, sell)`(`:42`)**、**`getPrice(item): [buy, sell]`(`:48`)**、`enable()`(`:53`)、`disable()`(`:58`)。
|
||||
- `IShopStateSave.prices: ReadonlyMap<number, [buy, sell]>`(`:11`)—— 买价/卖价来源(GAP-S1 闭合)。
|
||||
- `IShopSystem`(`data-system/src/shop/types.ts:8-45`):`bindManager`(`:13`)、`bindHero(hero: IHeroState<IShopHeroAttr> | null)`(`:19`)、`openShop(shop)`(`:25`)、`closeShop()`(`:30`)、**`buyItem(item, count)`(`:37`)**、**`sellItem(item, count)`(`:44`)**(GAP-S2 闭合)。
|
||||
- `IShopHeroAttr.money`(`data-system/src/shop/types.ts:5`)—— 支付资源 = `money`(GAP-S3 闭合)。
|
||||
- `IShopManager`(`shop/types.ts:66-117`)。**须用户确认 UI 仅消费上述成员。**
|
||||
|
||||
④ **接口缺口(须用户逐一裁定,一律 flag 不 invent):**
|
||||
- **GAP-S0(导出):** `IShopState` 未从 `@user/data-base` 导出(`data-base/src/index.ts` 无 `./shop`,且无 `shop/index.ts`)。**须用户补齐**(数据侧);未补齐前 Task 1 无法命名 `shop: IShopState` 而阻塞。
|
||||
- **GAP-S1(买价 / 卖价):** 无价格来源(`IShopState` / `IItemRawData` 均无价格字段)。legacy 在 `ItemShopChoice.money`/`.sell`(`eventDec.d.ts:110,115`)。**须用户提供价格来源**,否则 买价/卖价/总价 以 `// TODO` 占位。
|
||||
- **GAP-S2(交易):** `IShopState` 无 购买/售出 交易方法(仅 `addItem` 调库存)。**须裁定**:数据侧提供,或 UI 以既有公开接口组装(`shop.addItem` + `hero.items.addItem` + `hero.getModifiableAttribute().add('money', …)`)。
|
||||
- **GAP-S3(支付资源):** `money` vs `exp` 缺字段(legacy `ItemShopEvent.use`,`eventDec.d.ts:135`)。默认 `money`。
|
||||
④ **接口缺口状态(已全部闭合,呈报确认,无需再裁定):**
|
||||
- **GAP-S4(系统层公共面导出):已闭合** —— 用户已补齐 `@user/data-system` 的 `shop` 导出:`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`(`export * from './types'`),与 `./combat` / `./event` / `./path` 的既有 barrel 模式一致;`IShopSystem` / `IShopHeroAttr` 现可从 `@user/data-system` 导入。Task 1 无阻塞。
|
||||
- 历史缺口 GAP-S0 / GAP-S1 / GAP-S2 / GAP-S3 **亦已由用户补齐**。全部接口缺口闭合,无待裁定缺口。
|
||||
|
||||
⑤ **命名清单(须用户逐一批准,D-04):**
|
||||
⑤ **命名清单(已获用户批准,D-04,此处呈报确认):**
|
||||
- 新增文件:`packages-user/client-modules/src/ui/shop.tsx`。
|
||||
- 新增组件:`Shop`。
|
||||
- 新增 `GameUI` 名称字符串:`'shop'`(**注意**:与既有热键 id `'shop'`(`action/hotkey.ts:75,492`)是否同命名空间 / 冲突,请确认)。
|
||||
- 新增 props 接口:`ShopProps`(含 `shop: IShopState`、`heroState: IHeroState<IHeroAttr>`,及 `IUIPropsBase` 的 `controller/instance/state`)。
|
||||
- 新增行类型:`IShopItemEntry`(默认名,`{ readonly num: number; readonly id: string; readonly name: string; readonly text: string; readonly stock: number; readonly owned: number }`;如用户指定别名据其指定)。
|
||||
- 新增 `GameUI` 名称字符串:`'shop'`(已获批准)。
|
||||
- 新增 props 接口:`ShopProps`(含 `shop: IShopState`、`heroState: IHeroState<IHeroAttr>`、**`shopSystem: IShopSystem`**,及 `IUIPropsBase` 的 `controller/instance/state`)。
|
||||
- 新增 props 数组:`['controller','instance','state','shop','heroState','shopSystem']`(新 prop 名 `shopSystem`,已获批准)。
|
||||
- 新增行类型:`IShopItemEntry`(已获批准,`{ readonly num: number; readonly id: string; readonly name: string; readonly text: string; readonly stock: number; readonly owned: number; readonly buyPrice: number; readonly sellPrice: number }`)。
|
||||
- 新增导出:`ShopUI`(`new GameUI(...)`)。
|
||||
- **不修改**任何既有命名 / 单例;不向 `src/ui/index.ts` 添加导出(保持与同 wave 计划互不冲突)。
|
||||
|
||||
⑥ **验证方式(须用户确认):** 静态源码断言(文件存在 / 关键符号 / props 数组含 `controller,instance,state,shop,heroState` / 无 `client` 单例 / 无 `import type` / 无 `?.` / CRLF / 范围守卫)。**不设全仓类型 / 构建门禁。**
|
||||
⑥ **验证方式(须用户确认):** 静态源码断言(文件存在 / 关键符号 / props 数组含 `controller,instance,state,shop,heroState,shopSystem` / 无 `client` 单例 / 无 `import type` / 无 `?.` / CRLF / 范围守卫)。**不设全仓类型 / 构建门禁。**
|
||||
|
||||
⑦ **范围与硬边界:** 仅新增 `shop.tsx`;**零触碰** = 09-01..09-08 产物、`src/render/**`、`packages/system`、`packages/render*`、`packages-user/data-*`(含 `shop/types.ts`、`index.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`)、`legacy-ui`、book/equipbox/toolbox、用户并发改动(D-56)。
|
||||
⑦ **范围与硬边界:** 仅新增 `shop.tsx`;**零触碰** = 09-01..09-08 产物、`src/render/**`、`packages/system`、`packages/render*`、`packages-user/data-*`(含 `shop/types.ts`、`data-system/src/shop/types.ts`、`data-base/src/index.ts`、`data-system/src/index.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`)、`legacy-ui`、book/equipbox/toolbox、用户并发改动(D-56)。
|
||||
|
||||
⑧ **关卡性质与失败语义:** 无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户明确回复「可以执行」四个字;回复其它内容视为未通过。若用户给出修订意见或补齐缺口(GAP-S0..GAP-S3),退出本计划、据其裁定修订后重新汇报。
|
||||
⑧ **关卡性质与失败语义:** 无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户明确回复「可以执行」四个字;回复其它内容视为未通过。若用户给出修订意见,退出本计划、据其裁定修订后重新汇报。
|
||||
</action>
|
||||
<decision>是否按本计划实现任务 4 · shop 商店界面,并按用户裁定处置接口缺口:GAP-S0(用户补齐 `IShopState` 公共面导出);GAP-S1(用户提供买价/卖价来源,或接受 `// TODO` 占位);GAP-S2(数据侧提供交易方法,或 UI 以既有公开接口组装);GAP-S3(支付资源默认 money);OI-S1..OI-S5 照准;命名清单照准</decision>
|
||||
<decision>是否按本计划实现任务 4 · shop 商店界面,并按用户已补齐的接口处置:价格经 `IShopState.getPrice`(GAP-S1 已闭合);交易经 `IShopSystem.buyItem`/`sellItem`(GAP-S2 已闭合);支付资源 `money`(GAP-S3 已闭合);`IShopState` 已从 `@user/data-base` 导出(GAP-S0 已闭合);`IShopSystem` / `IShopHeroAttr` 已从 `@user/data-system` 导出(**GAP-S4 已闭合**);OI-S1..OI-S6 照准;命名清单照准</decision>
|
||||
<context>
|
||||
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。本次核心 = 命名清单 + 确认用户提供的 `IShopState` 成员 + 裁定四处接口缺口(GAP-S0..S3)。行号为规划日实测、可能漂移,门禁按符号文本匹配。**本计划的 UI 契约依赖用户对 GAP-S0..GAP-S3 的裁定:GAP-S0 未补齐则 Task 1 阻塞;GAP-S1 未提供则价格字段以 `// TODO` 占位。**
|
||||
AGENTS.md / D-03 / D-04 要求执行前与命名先获批;`.planning/config.json` 为 `mode: yolo`,故本关卡显式 `blocking-human` 且 `autonomous: false`。本次核心 = 命名清单 + 确认用户已补齐的全部接口(`IShopState` 价格 `getPrice`/`setPrice` + `IShopSystem` 交易 + `money` 支付 + `@user/data-system` 的 `shop` 导出,GAP-S0..GAP-S4 均已闭合)+ OI-S6(当前商店 / openShop,已确认)。行号为规划日实测、可能漂移,门禁按符号文本匹配。**本计划的 UI 契约所需接口缺口(含 GAP-S4)均已闭合,无阻塞。**
|
||||
</context>
|
||||
<options>
|
||||
<option id="execute-default">
|
||||
<name>可以执行(用户补齐 GAP-S0 并裁定 S1/S2/S3 后采纳默认处置)</name>
|
||||
<pros>一次落地布局 + 列表 + 数量 + 购买;UI-only 不越界;缺口按用户裁定落地</pros>
|
||||
<cons>须用户先补齐 `IShopState` 导出;价格 / 交易按裁定可能先占位</cons>
|
||||
</option>
|
||||
<option id="execute-ui-compose">
|
||||
<name>可以执行 + GAP-S2 选「UI 以既有公开接口组装交易」</name>
|
||||
<pros>无需数据侧新增交易方法;用既有 `shop.addItem`/`hero.items.addItem`/`hero.getModifiableAttribute().add('money',…)`</pros>
|
||||
<cons>交易语义进入 UI 层;GAP-S1(价格)仍需来源</cons>
|
||||
<name>可以执行(接口已全部补齐,按确认的处置落地)</name>
|
||||
<pros>一次落地布局 + 列表 + 数量 + 购买;价格经 `getPrice`、交易经 `IShopSystem`,UI-only 不越界</pros>
|
||||
<cons>无(GAP-S0..GAP-S4 均已闭合;OI-S6 由 UI 于 setup 调 `openShop`)</cons>
|
||||
</option>
|
||||
<option id="revise">
|
||||
<name>修订清单 / 命名 / 范围 / 缺口处置后重新汇报</name>
|
||||
<name>修订清单 / 命名 / 范围后重新汇报</name>
|
||||
<pros>避免带着错误契约落地</pros>
|
||||
<cons>须修订 PLAN.md 后重新汇报</cons>
|
||||
</option>
|
||||
</options>
|
||||
<resume-signal>回复「可以执行」四个字(如需调整 GAP-S0..GAP-S3 的处置、OI-S1..OI-S5 或命名方案,请在回复中指明;修订意见请按 action ⑧ 处理)</resume-signal>
|
||||
<resume-signal>回复「可以执行」四个字(如需调整 OI-S1..OI-S6 或命名方案,请在回复中指明;修订意见请按 action ⑧ 处理)</resume-signal>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs');const has=p=>fs.existsSync(p);const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const bad=[];if(has('packages-user/client-modules/src/ui/shop.tsx'))bad.push('post-state: shop.tsx already exists');if(!has('packages-user/data-base/src/shop/types.ts'))bad.push('pre-state changed: shop/types.ts missing');else{const s=read('packages-user/data-base/src/shop/types.ts');for(const [re,label] of [[/interface IShopState\b/,'IShopState'],[/getCount\(token/,'getCount'],[/iterateItems\(\): Iterable/,'iterateItems'],[/addItem\(token/,'addItem'],[/interface IShopManager\b/,'IShopManager']]){if(!re.test(s))bad.push('pre-state changed: '+label+' missing')}}const idx=read('packages-user/data-base/src/index.ts');if(/'\.\/shop'/.test(idx))bad.push('note: GAP-S0 already resolved (shop barrel export present) - re-review Task 1 precondition');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state intact (shop.tsx absent; IShopState present)')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "PRESTATE FAILED"(汇报基线不成立,或 `shop.tsx` 已存在 / `IShopState` 已漂移)</fails_when>
|
||||
<human-check>用户在对话中明确回复「可以执行」四个字(人工核对回复原文;用户须就 GAP-S0..GAP-S3 的处置与命名清单给出裁定;若用户指定非默认处置,须在汇报与后续任务中据其指定执行)</human-check>
|
||||
<automated>node -e "const fs=require('fs');const has=p=>fs.existsSync(p);const read=p=>fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const bad=[];if(has('packages-user/client-modules/src/ui/shop.tsx'))bad.push('post-state: shop.tsx already exists');const bt=read('packages-user/data-base/src/shop/types.ts');for(const [re,label] of [[/interface IShopState\b/,'IShopState'],[/getCount\(token/,'getCount'],[/iterateItems\(\): Iterable/,'iterateItems'],[/addItem\(token/,'addItem'],[/getPrice\(item/,'getPrice'],[/setPrice\(item/,'setPrice'],[/interface IShopManager\b/,'IShopManager'],[/prices: ReadonlyMap<number, \[buy: number, sell: number\]>/,'prices map']]){if(!re.test(bt))bad.push('pre-state changed: '+label+' missing')}const bidx=read('packages-user/data-base/src/index.ts');if(!/'\.\/shop'/.test(bidx))bad.push('pre-state changed: data-base index no /shop (GAP-S0 regressed)');const st=read('packages-user/data-system/src/shop/types.ts');for(const [re,label] of [[/interface IShopSystem\b/,'IShopSystem'],[/interface IShopHeroAttr\b/,'IShopHeroAttr'],[/buyItem\(item/,'buyItem'],[/sellItem\(item/,'sellItem'],[/openShop\(shop/,'openShop'],[/closeShop\(\)/,'closeShop']]){if(!re.test(st))bad.push('pre-state changed: '+label+' missing')}const sidx=read('packages-user/data-system/src/index.ts');if(!/'\.\/shop'/.test(sidx))bad.push('pre-state changed: data-system index no /shop (GAP-S4 regressed)');if(!has('packages-user/data-system/src/shop/index.ts'))bad.push('pre-state changed: data-system/src/shop/index.ts missing (GAP-S4 regressed)');if(bad.length>0){console.error('PRESTATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK pre-execution state intact (shop.tsx absent; IShopState+prices present+exported; IShopSystem present+exported)')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "PRESTATE FAILED"(汇报基线不成立,或 `shop.tsx` 已存在 / `IShopState` / `getPrice` / `IShopSystem` 已漂移)</fails_when>
|
||||
<human-check>用户在对话中明确回复「可以执行」四个字(人工核对回复原文;用户须就 OI-S1..OI-S6 与命名清单给出裁定(GAP-S0..GAP-S4 均已闭合且命名已获批准);若用户指定非默认处置,须在汇报与后续任务中据其指定执行)</human-check>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 用户回复中明确包含「可以执行」四个字
|
||||
- 汇报内容涵盖:D-38..D-41 / D-33 / D-37 范围、用户提供的 `IShopState` 成员清单、接口缺口 GAP-S0..GAP-S3(含 GAP-S0 未补齐则 Task 1 阻塞)、命名清单、OI-S1..OI-S5、验证方式(静态断言,无全仓门禁)、范围与硬边界
|
||||
- 汇报中已原样告知:**不新增未获批命名、不改既有注释、不改数据端 / 系统层 / 渲染系统、不补数据端 barrel 导出、不 invent 价格/交易/支付资源接口、不接线打开方式**
|
||||
- 汇报内容涵盖:D-38..D-41 / D-33 / D-37 范围、用户已补齐的全部接口成员(`IShopState` 含价格 / `IShopSystem` 交易 / `money` 支付资源 / `@user/data-system` 的 `shop` 导出,GAP-S0..GAP-S4 均已闭合)、命名清单、OI-S1..OI-S6、验证方式(静态断言,无全仓门禁)、范围与硬边界
|
||||
- 汇报中已原样告知:**不引入清单外命名、不改既有注释、不改数据端 / 系统层 / 渲染系统、不动数据端 barrel 导出(GAP-S0..GAP-S4 已由用户补齐)、不 invent 交易/价格接口、不接线打开方式**
|
||||
- 汇报中已原样告知**关卡性质**(必须停下等待,不因触发方式或 `yolo` / `auto_advance` 自动放行)
|
||||
</acceptance_criteria>
|
||||
<done>用户明确回复「可以执行」;在此之前未执行任何任务、未修改任何文件、未派发任何执行子代理</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1(布局 + 列表):新建 `shop.tsx` —— 顶部 购买/售出 切换 + 左侧 商店列表(id 排序 / 库存右对齐 / 显滚动条)+ 右侧 名称 / 描述 / 信息区</name>
|
||||
<precondition>Task 0 已闭环:用户明确回复「可以执行」四个字,且 **GAP-S0 已由用户补齐**(`IShopState` 可从 `@user/data-base` 导入;若未补齐则本任务阻塞,不得 invent 类型或改数据端)。若用户就 GAP-S1..GAP-S3 / OI-S1..OI-S5 另有指定,按指定执行。</precondition>
|
||||
<name>Task 1(布局 + 列表):新建 `shop.tsx` —— 顶部 购买/售出 切换 + 左侧 商店列表(id 排序 / 库存右对齐 / 显滚动条)+ 右侧 名称 / 描述 / 信息区(买价/卖价 经 getPrice)</name>
|
||||
<precondition>Task 0 已闭环:用户明确回复「可以执行」四个字,且 **GAP-S0 与 GAP-S4 均已闭合**(`IShopState` 可从 `@user/data-base` 导入;`IShopSystem` / `IShopHeroAttr` 可从 `@user/data-system` 导入——`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`)。不得 invent 类型或改数据端。若用户就 OI-S1..OI-S6 另有指定,按指定执行。</precondition>
|
||||
<files>packages-user/client-modules/src/ui/shop.tsx</files>
|
||||
<read_first>
|
||||
- packages-user/data-base/src/shop/types.ts:1-95(`IShopState` / `IShopManager`)
|
||||
- packages-user/data-base/src/shop/types.ts:1-117(`IShopState`(`enabled`/`addItem`/`getCount`/`iterateItems`/`getPrice`/`setPrice`/`enable`/`disable`)/ `IShopManager`)
|
||||
- packages-user/data-system/src/shop/types.ts:1-45(`IShopHeroAttr` / `IShopSystem`)
|
||||
- packages-user/data-common/src/types.ts:25-48(`IHeroAttr`,含 `money`)
|
||||
- packages-user/data-common/src/store/types.ts:176-213(`IItemRawData` / `IItemStore.getData`;`ITileStore` 的 `num`/`id`)
|
||||
- packages-user/data-base/src/hero/types.ts:598-646(`IHeroItemState` / `IHeroItems`)
|
||||
- packages-user/client-modules/src/ui/types.ts:1-5(`IUIPropsBase`)
|
||||
@ -196,43 +204,44 @@ Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
- dev.md(import 置首;无 `import type`;无 `?.`;CRLF)
|
||||
</read_first>
|
||||
<action>
|
||||
(1) **新建 `shop.tsx`(仅此文件)。** 命名须已在 Task 0 获批:`export interface ShopProps extends IUIPropsBase { shop: IShopState; heroState: IHeroState<IHeroAttr>; }`;`const shopProps = { props: ['controller','instance','state','shop','heroState'] } satisfies SetupComponentOptions<ShopProps>;`;`export const Shop = defineComponent<ShopProps>(props => { ... }, shopProps);`;`export const ShopUI = new GameUI('shop', Shop);`。根容器 `FULL_LOC`(D-33)。
|
||||
(1) **新建 `shop.tsx`(仅此文件)。** 命名须已在 Task 0 获批:`export interface ShopProps extends IUIPropsBase { shop: IShopState; heroState: IHeroState<IHeroAttr>; shopSystem: IShopSystem; }`(`IShopState` 来自 `@user/data-base`,`IShopSystem` 来自 `@user/data-system`,`IHeroState` 来自 `@user/data-base`,`IHeroAttr` 来自 `@user/data-common`);`const shopProps = { props: ['controller','instance','state','shop','heroState','shopSystem'] } satisfies SetupComponentOptions<ShopProps>;`;`export const Shop = defineComponent<ShopProps>(props => { ... }, shopProps);`;`export const ShopUI = new GameUI('shop', Shop);`。根容器 `FULL_LOC`(D-33)。
|
||||
|
||||
(2) **行类型与列表数据(D-39):** 定义 `export interface IShopItemEntry { readonly num: number; readonly id: string; readonly name: string; readonly text: string; readonly stock: number; readonly owned: number; }`(命名待批准)。以响应式 `computed` 由 `props.shop.iterateItems()`(`IShopState`,`shop/types.ts:32`,`Iterable<[num, count]>`)构建条目:`num` / `id`(`props.state.tileStore.id(num)`,`store/types.ts:112`)/ `name`、`text`(`props.state.itemStore.getData(num)` → `IItemRawData.name`/`.text`,`store/types.ts:182,184`)/ `stock`(`props.shop.getCount(num)`,`:27`)/ `owned`(`props.heroState.items.itemCount(num)`,`hero/types.ts:645`)。**列表按 `num` 升序排序**(D-39「按 id 排序」的等价身份键;先 `slice()` 再 `sort`,不就地改动)。
|
||||
(2) **行类型与列表数据(D-39):** 定义 `export interface IShopItemEntry { readonly num: number; readonly id: string; readonly name: string; readonly text: string; readonly stock: number; readonly owned: number; readonly buyPrice: number; readonly sellPrice: number; }`(命名已批准)。以响应式 `computed` 由 `props.shop.iterateItems()`(`IShopState`,`shop/types.ts:34`,`Iterable<[num, count]>`)构建条目:`num` / `id`(`props.state.tileStore.id(num)`,`store/types.ts:112`)/ `name`、`text`(`props.state.itemStore.getData(num)` → `IItemRawData.name`/`.text`,`store/types.ts:182,184`)/ `stock`(`props.shop.getCount(num)`,`:29`)/ `owned`(`props.heroState.items.itemCount(num)`,`hero/types.ts:645`)/ **`buyPrice` / `sellPrice`(`props.shop.getPrice(num)` → `[buy, sell]`,`shop/types.ts:48`)**。**列表按 `num` 升序排序**(D-39「按 id 排序」的等价身份键;先 `slice()` 再 `sort`,不就地改动)。
|
||||
|
||||
(3) **顶栏(D-38 / D-39):** 顶部「购买选项 | 售出选项」切换,用 `ref<'buy' | 'sell'>` 记录模式;售出模式**复用同一商店列表**(D-39,商店不卖的玩家也不能售出)。
|
||||
|
||||
(4) **左侧商店列表(D-39):** 纵向 `<Scroll loc={[...]}>`(**不加 `noscroll`**,显示滚动条)平铺 `props.shop` 条目;每行 = `<icon icon={entry.num} />` + 名称(`entry.name`)+ **库存(`entry.stock`)显示在物品最右侧、右对齐**;点击行 → 选中(`selected`)。选中项以 `<Selection>` 或高亮样式标识。
|
||||
|
||||
(5) **右侧信息区(D-38,自上而下):** 「选中物品名称」(`entry.name`)→「选中物品描述」(`entry.text`,纵向 `<Scroll noscroll>`,**不显示滚动条**)→「购入量选择」(本任务先占位于底部,具体见 Task 2)→「购买按钮」(Task 2)。价格信息行(买价/卖价/存货/拥有/总价)按 Task 0 裁定的来源实现;**GAP-S1 未提供时,买价/卖价/总价 以明确标记的 `// TODO` 占位(不 invent 价格接口)**,存货(`stock`)/拥有(`owned`)照常显示。
|
||||
(5) **右侧信息区(D-38,自上而下):** 「选中物品名称」(`entry.name`)→「选中物品描述」(`entry.text`,纵向 `<Scroll noscroll>`,**不显示滚动条**)→「购入量选择」(本任务先占位于底部,具体见 Task 2)→「购买按钮」(Task 2)。价格信息行(买价/卖价/存货/拥有/总价)**全部按用户已补齐的接口显示**:买价 = `entry.buyPrice`(`getPrice()[0]`)、卖价 = `entry.sellPrice`(`getPrice()[1]`)、存货 = `entry.stock`、拥有 = `entry.owned`;总价见 Task 2(数量 × 单价)。**不再有价格 `// TODO`。**
|
||||
|
||||
(6) **收口:** `import` 置首、块内无空行/注释(`dev.md:55`);无 `import type`;无 `?.`;不改既有注释;CRLF;无未使用 import;不改 `files_modified` 外文件。
|
||||
</action>
|
||||
<reversibility rating="reversible">纯新增文件;删除 / `git revert` 可回退。</reversibility>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs');const p='packages-user/client-modules/src/ui/shop.tsx';const bad=[];if(!fs.existsSync(p)){bad.push('shop.tsx missing')}else{const t=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const need=[['interface ShopProps','props interface'],['IShopState','shop state type'],['IHeroState','hero state type'],['shop','shop prop'],['heroState','heroState prop'],['IShopItemEntry','row type'],['.iterateItems(','enumerate shop items'],['.getCount(','stock lookup'],['itemCount(','owned count'],['itemStore','item store'],['getData(','item raw'],['.text','description'],['.name','name'],['<Scroll','scroll'],['noscroll','description no-scroll'],['<icon','icon'],['new GameUI(','GameUI export'],['props: [','props array']];for(const [s,label] of need){if(t.indexOf(s)<0)bad.push('missing '+label+': '+s)}if(/import type/.test(t))bad.push('has import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('has optional chaining');if(!/ShopUI\s*=\s*new GameUI\(/.test(t))bad.push('ShopUI not exported');if(!/props:\s*\[\s*'controller'\s*,\s*'instance'\s*,\s*'state'\s*,\s*'shop'\s*,\s*'heroState'\s*\]/.test(t))bad.push('props array incomplete');}if(bad.length>0){console.error('SHOP LAYOUT GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK shop: layout + shop list (sorted, stock right-aligned, scrollable) present')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SHOP LAYOUT GATE FAILED"(文件缺失 / 关键符号缺失 / props 数组不含 `shop`/`heroState` / 引入 `import type` 或 `?.`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const p='packages-user/client-modules/src/ui/shop.tsx';const bad=[];if(!fs.existsSync(p)){bad.push('shop.tsx missing')}else{const t=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const need=[['interface ShopProps','props interface'],['IShopState','shop state type'],['IShopSystem','shop system type'],['IHeroState','hero state type'],['shopSystem','shopSystem prop'],['shop','shop prop'],['heroState','heroState prop'],['IShopItemEntry','row type'],['.iterateItems(','enumerate shop items'],['.getCount(','stock lookup'],['.getPrice(','price lookup'],['itemCount(','owned count'],['itemStore','item store'],['getData(','item raw'],['.text','description'],['.name','name'],['<Scroll','scroll'],['noscroll','description no-scroll'],['<icon','icon'],['new GameUI(','GameUI export'],['props: [','props array']];for(const [s,label] of need){if(t.indexOf(s)<0)bad.push('missing '+label+': '+s)}if(/import type/.test(t))bad.push('has import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('has optional chaining');if(!/ShopUI\s*=\s*new GameUI\(/.test(t))bad.push('ShopUI not exported');if(!/props:\s*\[\s*'controller'\s*,\s*'instance'\s*,\s*'state'\s*,\s*'shop'\s*,\s*'heroState'\s*,\s*'shopSystem'\s*\]/.test(t))bad.push('props array incomplete');}if(bad.length>0){console.error('SHOP LAYOUT GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK shop: layout + shop list (sorted, stock right-aligned, scrollable) + prices via getPrice present')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SHOP LAYOUT GATE FAILED"(文件缺失 / 关键符号缺失 / props 数组不含 `shop`/`heroState`/`shopSystem` / 引入 `import type` 或 `?.`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const raw=fs.readFileSync('packages-user/client-modules/src/ui/shop.tsx','utf8');const bad=[];if(/(?<!\r)\n/.test(raw))bad.push('LF without CR');if(raw.includes('\r')&&!/\r\n/.test(raw))bad.push('stray CR');if(bad.length){console.error('CRLF FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK CRLF: shop.tsx')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "CRLF FAILED"(换行非 CRLF)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- `shop.tsx` 存在,导出 `Shop` / `ShopUI` / `IShopItemEntry`;props 数组含 `controller,instance,state,shop,heroState`
|
||||
- `shop.tsx` 存在,导出 `Shop` / `ShopUI` / `IShopItemEntry`;props 数组含 `controller,instance,state,shop,heroState,shopSystem`
|
||||
- 顶部 购买/售出 切换;售出复用同一商店列表;列表来自 `props.shop.iterateItems()` 按 `num` 升序,库存最右右对齐,纵向 `<Scroll>`(**不加 `noscroll`**,显示滚动条)
|
||||
- 右侧 名称(`name`)+ 描述(`text`,`<Scroll noscroll>` 不显示滚动条)+ 信息区占位 + 购买按钮位;存货(`getCount`)/拥有(`itemCount`)显示
|
||||
- 买价/卖价/总价:GAP-S1 已提供则按来源显示;未提供则以明确 `// TODO` 占位(**不 invent 价格接口**)
|
||||
- 右侧 名称(`name`)+ 描述(`text`,`<Scroll noscroll>` 不显示滚动条)+ 信息区 + 购买按钮位;存货(`getCount`)/拥有(`itemCount`)/**买价·卖价(`getPrice`)**显示
|
||||
- 价格字段取自 `props.shop.getPrice(num)` 的 `[buy, sell]`;**无价格 `// TODO` 占位**
|
||||
- 无 `import type`、无 `?.`、CRLF、无未使用 import;未改既有注释;未改 `files_modified` 外文件
|
||||
</acceptance_criteria>
|
||||
<done>布局与列表完成:顶栏切换 + 商店列表(排序/库存右对齐/显滚动条)+ 名称 / 描述区,静态门禁全绿</done>
|
||||
<done>布局与列表完成:顶栏切换 + 商店列表(排序/库存右对齐/显滚动条)+ 名称 / 描述区 + 买价/卖价(经 `getPrice`),静态门禁全绿</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2(数量 + 购买 + 退出 + 门禁):实现四按钮数量选择(按物品分别记忆 / 上限 库存·拥有)+ 购买按钮 + 右下退出,并跑最终静态门禁</name>
|
||||
<name>Task 2(数量 + 购买 + 退出 + 门禁):实现四按钮数量选择(按物品分别记忆 / 上限 库存·拥有)+ 购买按钮(经 IShopSystem 交易)+ 右下退出,并跑最终静态门禁</name>
|
||||
<precondition>Task 1 已完成(`shop.tsx` 布局与列表已落地)。</precondition>
|
||||
<files>packages-user/client-modules/src/ui/shop.tsx</files>
|
||||
<read_first>
|
||||
- packages-user/client-modules/src/ui/shop.tsx(Task 1 产物)
|
||||
- packages-user/data-base/src/shop/types.ts:12-43(`IShopState.addItem` / `getCount` / `iterateItems`)
|
||||
- packages-user/data-base/src/hero/types.ts:621-645(`IHeroItems.addItem` / `itemCount`)、:117,198,933,947(`getFinalAttribute('money')` / `IHeroAttribute.add` / `getModifiableAttribute`)
|
||||
- packages-user/data-base/src/shop/types.ts:14-59(`IShopState.getCount` / `iterateItems` / `getPrice`)
|
||||
- packages-user/data-system/src/shop/types.ts:1-45(`IShopSystem.openShop` / `closeShop` / `buyItem` / `sellItem`)
|
||||
- packages-user/data-base/src/hero/types.ts:621-645(`IHeroItems.addItem` / `itemCount`)、:117,933(`getFinalAttribute('money')`)
|
||||
- packages/legacy-ui/src/ui/shop.vue:113-158,205-275(数量四按钮 / 上限 watch / `confirm` 交易参考)
|
||||
- packages-user/client-modules/src/ui/title.tsx:194-253,397-412(`onClick` 事件写法)
|
||||
- dev.md(无 `?.`;避免连续 `as`;CRLF)
|
||||
@ -242,9 +251,9 @@ Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
|
||||
(2) **四按钮(D-40):** `<< < {当前购入/售出量} > >>`,步进同 legacy(`<<`/`>>` = ±10,`<`/`>` = ±1);点击后 `clamp` 至 `[0, 上限]`。
|
||||
|
||||
(3) **信息区(D-38):** 在四个按钮**上方**显示 买价 / 卖价 / 存货 / 拥有 / 总价(存货 = `stock`、拥有 = `owned`、总价 = 数量 × 单价)。买价 / 卖价 / 总价按 Task 0 裁定处置(GAP-S1:来源提供则显示,否则明确 `// TODO` 占位,**不 invent**)。
|
||||
(3) **信息区(D-38):** 在四个按钮**上方**显示 买价 / 卖价 / 存货 / 拥有 / 总价(买价 = `props.shop.getPrice(num)[0]`、卖价 = `props.shop.getPrice(num)[1]`、存货 = `stock`、拥有 = `owned`、总价 = 数量 × 单价,单价按当前模式取 买价 或 卖价)。**全部经用户已补齐的接口取得,无 `// TODO`。**
|
||||
|
||||
(4) **购买按钮(D-40 / GAP-S2 / GAP-S3):** 「购买按钮」购买**当前选中物品**的数量;购买 / 售出按当前模式执行。交易按 Task 0 裁定处置:数据侧提供交易方法则调用之;否则按默认(UI 以既有公开接口组装:购入 = `heroState.items.addItem(num, count)` + `heroState.getModifiableAttribute().add('money', -总价)` + `props.shop.addItem(num, -count)`;售出 = 反向)——**不得 invent 新接口**。数量为 0 时不执行(同 legacy)。
|
||||
(4) **购买 / 售出按钮(D-40 / GAP-S2 / GAP-S3):** 「购买按钮」交易**当前选中物品**的数量,按当前模式执行:购入 = `props.shopSystem.buyItem(num, count)`;售出 = `props.shopSystem.sellItem(num, count)`。**交易一律经 `IShopSystem`,不得由 UI 组装金钱 / 物品转移,不得 invent 新接口。** 数量为 0 时不执行(同 legacy)。当前商店(OI-S6 已确认):在 setup 时调用 `props.shopSystem.openShop(props.shop)`(纯数据端选择,不打开 UI,符合 D-41)。
|
||||
|
||||
(5) **退出(D-37):** 右下角按钮 → `props.controller.close(props.instance)`。
|
||||
|
||||
@ -252,21 +261,21 @@ Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
</action>
|
||||
<reversibility rating="reversible">纯新增文件内交互逻辑;`git revert` 可回退。</reversibility>
|
||||
<verify>
|
||||
<automated>node -e "const fs=require('fs');const p='packages-user/client-modules/src/ui/shop.tsx';const bad=[];if(!fs.existsSync(p)){bad.push('shop.tsx missing')}else{const t=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const need=[['购买','buy label'],['售出','sell label'],['买价','buy price label'],['卖价','sell price label'],['存货','stock label'],['拥有','owned label'],['总价','total label'],['count','quantity state'],['onClick','click handlers'],['controller.close(','exit']];for(const [s,label] of need){if(t.indexOf(s)<0)bad.push('missing '+label+': '+s)}if(/import type/.test(t))bad.push('has import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('has optional chaining');if(/as\s+\w+\s+as\s+/.test(t))bad.push('chained as');}if(bad.length>0){console.error('SHOP ACTION GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK shop: mode toggle + quantity + price info + buy + exit present')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SHOP ACTION GATE FAILED"(数量 / 价格标签 / 交互符号缺失 / 引入 `import type` / `?.` / 连续 `as`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const p='packages-user/client-modules/src/ui/shop.tsx';const bad=[];if(!fs.existsSync(p)){bad.push('shop.tsx missing')}else{const t=fs.readFileSync(p,'utf8').replace(/\r\n/g,'\n');const need=[['购买','buy label'],['售出','sell label'],['买价','buy price label'],['卖价','sell price label'],['存货','stock label'],['拥有','owned label'],['总价','total label'],['.getPrice(','price lookup'],['.buyItem(','buy transaction'],['.sellItem(','sell transaction'],['count','quantity state'],['onClick','click handlers'],['controller.close(','exit']];for(const [s,label] of need){if(t.indexOf(s)<0)bad.push('missing '+label+': '+s)}if(/import type/.test(t))bad.push('has import type');if(/\?\.[a-zA-Z]/.test(t))bad.push('has optional chaining');if(/as\s+\w+\s+as\s+/.test(t))bad.push('chained as');}if(bad.length>0){console.error('SHOP ACTION GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK shop: mode toggle + quantity + price info + buy/sell via IShopSystem + exit present')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "SHOP ACTION GATE FAILED"(数量 / 价格标签 / 交易或交互符号缺失 / 引入 `import type` / `?.` / 连续 `as`)</fails_when>
|
||||
<automated>node -e "const fs=require('fs');const raw=fs.readFileSync('packages-user/client-modules/src/ui/shop.tsx','utf8');const bad=[];if(/(?<!\r)\n/.test(raw))bad.push('LF without CR');if(raw.includes('\r')&&!/\r\n/.test(raw))bad.push('stray CR');if(bad.length){console.error('CRLF FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK CRLF: shop.tsx')"</automated>
|
||||
<fails_when>non-zero exit, 或 stderr 含 "CRLF FAILED"(换行非 CRLF)</fails_when>
|
||||
<automated>git -C . status --porcelain</automated>
|
||||
<fails_when>输出包含本计划 `files_modified` 之外的生产文件改动(如 `packages/system/`、`packages/render`、`packages-user/data-`、`shop/types.ts`、`legacy-ui/` 或 09-01..09-08 的既有文件)</fails_when>
|
||||
<fails_when>输出包含本计划 `files_modified` 之外的生产文件改动(如 `packages/system/`、`packages/render`、`packages-user/data-`、`shop/types.ts`、`data-system/src/shop/types.ts`、`legacy-ui/` 或 09-01..09-08 的既有文件)</fails_when>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 四按钮 `<< < {量} > >>`:步进 ±10 / ±1,`clamp` 至 `[0, 上限]`;上限 = 购入 库存 / 售出 拥有;数量按物品分别记忆
|
||||
- 信息区在按钮上方显示 买价/卖价/存货/拥有/总价;购买按钮购买当前选中物品数量;数量 0 不执行
|
||||
- 交易:数据侧提供则调用之,否则按默认以既有公开接口组装(`addItem` / `getModifiableAttribute().add('money', …)`);**未 invent 任何新接口**
|
||||
- 信息区在按钮上方显示 买价/卖价/存货/拥有/总价(买价/卖价 经 `getPrice`);购买按钮交易当前选中物品数量;数量 0 不执行
|
||||
- 交易:购入 `props.shopSystem.buyItem(num, count)`、售出 `props.shopSystem.sellItem(num, count)`;**UI 未自行组装金钱 / 物品转移,未 invent 任何新接口**
|
||||
- 右下退出调用 `controller.close(props.instance)`
|
||||
- 无 `import type`、无 `?.`、无连续 `as`、CRLF、无未使用 import;范围守卫无越界改动
|
||||
</acceptance_criteria>
|
||||
<done>数量选择 + 信息区 + 购买 + 退出完成;最终静态门禁 + CRLF + 范围守卫全绿</done>
|
||||
<done>数量选择 + 信息区 + 购买/售出(经 `IShopSystem`)+ 退出完成;最终静态门禁 + CRLF + 范围守卫全绿</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
@ -277,50 +286,51 @@ Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| 数据读取边界 | 经 `props.shop`(`IShopState`)/ `props.heroState`(`IHeroState`)/ `props.state`(itemStore/tileStore)读取商店、玩家与物品数据 |
|
||||
| 数据写入边界 | 交易写 `IShopState.addItem` / `IHeroItems.addItem` / `IHeroAttribute.add`;均经既有公开接口,不自建状态、不 invent 新接口 |
|
||||
| 命名边界 | 所有新增公共命名(文件 / 组件 / GameUI 名 / props / 行类型 / 导出)须 Task 0 获批后方可使用 |
|
||||
| 范围边界 | 只新增 `shop.tsx`;不碰既有文件、其它界面与数据端(含 `shop/types.ts`、barrel) |
|
||||
| 提交边界 | 不得把用户并发改动(D-56:`shop/types.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`)纳入暂存/提交 |
|
||||
| 数据写入边界 | 交易经 `props.shopSystem`(`IShopSystem.buyItem`/`sellItem`)触发;均经既有公开接口,不自建状态、不 invent 新接口、不直接改金钱 |
|
||||
| 命名边界 | 所有新增公共命名(文件 / 组件 / GameUI 名 / props / props 数组 / 行类型 / 导出)须 Task 0 获批后方可使用 |
|
||||
| 范围边界 | 只新增 `shop.tsx`;不碰既有文件、其它界面与数据端(含 `shop/types.ts`、`data-system/src/shop/types.ts`、两处 barrel) |
|
||||
| 提交边界 | 不得把用户并发改动(D-56:`shop/types.ts`、`data-system/src/shop/types.ts`、`data-state/src/common.ts`、`src/content/core.jsonc`)纳入暂存/提交 |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||
| T-09-09-01 | Tampering | 借新增之机修改既有 UI / 数据端 / 渲染系统(含 `shop/types.ts`、barrel) | high | mitigate | `files_modified` 白名单 + prohibitions + Task 2 `git status` 范围守卫 |
|
||||
| T-09-09-01 | Tampering | 借新增之机修改既有 UI / 数据端 / 渲染系统(含 `shop/types.ts`、`data-system/src/shop/types.ts`、两处 barrel) | high | mitigate | `files_modified` 白名单 + prohibitions + Task 2 `git status` 范围守卫 |
|
||||
| T-09-09-02 | Elevation of Privilege | 未经批准即执行 / 使用未批准命名 | high | mitigate | Task 0 `blocking-human`;命名清单呈报;prohibitions |
|
||||
| T-09-09-03 | Tampering | **invent** 价格 / 交易 / 支付资源接口或为 `IShopState` 补字段 / 补 barrel 导出 | high | mitigate | `<action>` / prohibitions 明列「flag 不 invent」;缺口 GAP-S0..S3 由 Task 0 呈报裁定 |
|
||||
| T-09-09-04 | Tampering | 交易金额 / 物品转移错误(未按裁定口径) | medium | mitigate | `<action>` 明列默认组装口径;acceptance 明列;数据侧提供交易时优先调用 |
|
||||
| T-09-09-03 | Tampering | **invent** 交易 / 价格接口,或为 `IShopState` / `@user/data-system` 补字段 / 补 barrel 导出 | high | mitigate | `<action>` / prohibitions 明列「flag 不 invent」;**GAP-S4(`@user/data-system` 的 `shop` 导出)已由用户补齐,UI 不动数据端 barrel** |
|
||||
| T-09-09-04 | Tampering | 交易金额 / 物品转移绕过 `IShopSystem`(由 UI 自组装 `addItem` + `add('money', …)` 致与数据端不一致) | medium | mitigate | `<action>` 明列交易一律经 `props.shopSystem.buyItem`/`sellItem`;acceptance 明列;Task 2 门禁断言 `.buyItem(` / `.sellItem(` |
|
||||
| T-09-09-05 | Tampering | 引入 `client` 单例 / `import type` / `?.` / 连续 `as` / 非 CRLF | medium | mitigate | prohibitions + Task 1/2 静态门禁 + CRLF 门禁 |
|
||||
| T-09-09-06 | Tampering | 售出列表被扩大为「玩家背包全部道具」(D-39 要求售出复用商店列表) | low | mitigate | `<action>` 明列售出复用 `iterateItems()`;acceptance 明列 |
|
||||
| T-09-09-07 | Repudiation | 未跑静态门禁即提交 | medium | mitigate | Task 1/2 各有自动化门禁 + SUMMARY |
|
||||
| T-09-09-08 | Tampering | 提交时混入用户并发改动(D-56) | medium | mitigate | 提交前 `git status` 逐条核对,只暂存本计划条目 |
|
||||
| T-09-09-09 | Spoofing | `IShopSystem` 交易的「当前商店」与界面显示商店不一致(未 `openShop(props.shop)`,交易作用于别的商店) | medium | mitigate | `<action>` 明列 setup 时 `shopSystem.openShop(props.shop)`(OI-S6 已确认);acceptance 明列交易对象 = 当前界面商店 |
|
||||
| T-09-09-SC | Tampering | npm/pip/cargo installs | low | accept | 无依赖安装 |
|
||||
</threat_model>
|
||||
|
||||
<verification>
|
||||
- **Task 1**:`shop.tsx` 存在且导出 `Shop` / `ShopUI` / `IShopItemEntry`;含 `interface ShopProps` / `IShopState` / `IHeroState` / `.iterateItems(` / `.getCount(` / `itemCount(` / `itemStore` / `getData(` / `.text` / `.name` / `<Scroll` / `noscroll` / `<icon` / `new GameUI(`;props 数组含 `controller,instance,state,shop,heroState`;无 `import type`、无 `?.`;CRLF。
|
||||
- **Task 2**:含 `购买` / `售出` / `买价` / `卖价` / `存货` / `拥有` / `总价` / `count` / `onClick` / `controller.close(`;无 `import type` / `?.` / 连续 `as`;CRLF。
|
||||
- **范围守卫**:`git status --porcelain` 仅本计划 `files_modified` 条目被新增(不含用户并发改动 `shop/types.ts` / `data-state/src/common.ts` / `src/content/core.jsonc`)。
|
||||
- **Task 1**:`shop.tsx` 存在且导出 `Shop` / `ShopUI` / `IShopItemEntry`;含 `interface ShopProps` / `IShopState` / `IShopSystem` / `IHeroState` / `.iterateItems(` / `.getCount(` / **`.getPrice(`** / `itemCount(` / `itemStore` / `getData(` / `.text` / `.name` / `<Scroll` / `noscroll` / `<icon` / `new GameUI(`;props 数组含 `controller,instance,state,shop,heroState,shopSystem`;无 `import type`、无 `?.`;CRLF。
|
||||
- **Task 2**:含 `购买` / `售出` / `买价` / `卖价` / `存货` / `拥有` / `总价` / **`.getPrice(`** / **`.buyItem(`** / **`.sellItem(`** / `count` / `onClick` / `controller.close(`;无 `import type` / `?.` / 连续 `as`;CRLF。
|
||||
- **范围守卫**:`git status --porcelain` 仅本计划 `files_modified` 条目被新增(不含用户并发改动 `shop/types.ts` / `data-system/src/shop/types.ts` / `data-state/src/common.ts` / `src/content/core.jsonc`)。
|
||||
- **无全仓门禁**:全程不使用 `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。
|
||||
- **人工复核**:结论写入 `09-09-SUMMARY.md`(含 GAP-S0..GAP-S3 与 OI-S1..OI-S5 的最终裁定)。
|
||||
- **人工复核**:结论写入 `09-09-SUMMARY.md`(含 GAP-S0..GAP-S4 闭合与 OI-S1..OI-S6 的确认记录)。
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 布局:顶部 购买/售出 切换;左 商店列表;右自上而下 名称 / 描述 / 购入量选择(按钮上方 买价/卖价/存货/拥有/总价)/ 购买按钮;占满游戏画面;右下退出。
|
||||
- 列表:售出复用商店列表;按 id(`num`)排序;库存最右右对齐;纵向滚动并显示滚动条;描述纵滚不显示滚动条。
|
||||
- 数量:四按钮 `<< < {量} > >>`(±10/±1);上限 购入 库存 / 售出 拥有;按物品分别记忆;购买按钮买当前选中物品数量。
|
||||
- 数据:消费用户提供的 `IShopState`(`iterateItems`/`getCount`/`addItem`)+ `IHeroState`(`items`/`attribute`);价格 / 交易 / 支付资源缺口按 Task 0 裁定处置,**未 invent 接口**。
|
||||
- 数量:四按钮 `<< < {量} > >>`(±10/±1);上限 购入 库存 / 售出 拥有;按物品分别记忆;购买按钮交易当前选中物品数量。
|
||||
- 数据:消费用户已补齐的 `IShopState`(`iterateItems`/`getCount`/`getPrice`)+ `IShopSystem`(`buyItem`/`sellItem`)+ `IHeroState`(`items`);价格经 `getPrice`、交易经 `IShopSystem`、支付资源 `money`;**未 invent 接口**。
|
||||
- 范围零外溢;零未获批命名;零既有注释改动;CRLF;无 `client` 单例;无 `import type` / `?.` / 连续 `as`。
|
||||
- GAP-S0..GAP-S3 与 OI-S1..OI-S5 已按用户裁定落地并入本计划。
|
||||
- GAP-S0..GAP-S4 已全部闭合、OI-S1..OI-S6 已按用户确认落地并入本计划。
|
||||
</success_criteria>
|
||||
|
||||
## Artifacts this phase produces
|
||||
|
||||
| 符号 / 路径 | 类型 | 说明 |
|
||||
|---|---|---|
|
||||
| `packages-user/client-modules/src/ui/shop.tsx`(新增) | 组件 / UI | 商店界面:`Shop` / `ShopUI` / `ShopProps` / `IShopItemEntry`(命名待批准) |
|
||||
| `packages-user/client-modules/src/ui/shop.tsx`(新增) | 组件 / UI | 商店界面:`Shop` / `ShopUI` / `ShopProps` / `IShopItemEntry`(命名已批准) |
|
||||
|
||||
**新增导出命名(全部须 Task 0 批准):** 文件 `shop.tsx`;组件 `Shop`;`GameUI` 名 `'shop'`;props 接口 `ShopProps`;行类型 `IShopItemEntry`(默认);导出 `ShopUI`。**不向 `src/ui/index.ts` 添加导出**(保持与同 wave 计划互不冲突)。
|
||||
**新增导出命名(已获用户批准):** 文件 `shop.tsx`;组件 `Shop`;`GameUI` 名 `'shop'`;props 接口 `ShopProps`;props 数组 `['controller','instance','state','shop','heroState','shopSystem']`(新 prop `shopSystem`);行类型 `IShopItemEntry`;导出 `ShopUI`。**不向 `src/ui/index.ts` 添加导出**(保持与同 wave 计划互不冲突)。
|
||||
|
||||
## Source Coverage Audit
|
||||
|
||||
@ -331,16 +341,17 @@ Phase 9 · 任务 3 · 第 9 个计划 —— **shop 商店界面**。
|
||||
| RESEARCH | 无 RESEARCH.md | N/A |
|
||||
| CONTEXT | D-38(布局:顶切换 / 左列表 / 右 名称·描述·购入量选择·购买按钮;价格信息在按钮上方) | COVERED(Task 1/2) |
|
||||
| CONTEXT | D-39(购买/售出切换 / 售出复用商店列表 / id 排序 / 库存最右右对齐 / 列表滚动显滚动条) | COVERED(Task 1) |
|
||||
| CONTEXT | D-40(四按钮 ±10/±1 / 上限 库存·拥有 / 数量按物品分别记忆 / 购买按钮买当前选中数量) | COVERED(Task 2) |
|
||||
| CONTEXT | D-40(四按钮 ±10/±1 / 上限 库存·拥有 / 数量按物品分别记忆 / 购买按钮交易当前选中数量) | COVERED(Task 2) |
|
||||
| CONTEXT | D-41(只做 UI / 打开方式不管 / 数据由 `ShopState` 承载) | COVERED(Task 0 边界 + prohibitions + 入参 `IShopState`) |
|
||||
| CONTEXT | D-33 / D-37(占满游戏画面 root renderer / 退出按钮右下) | COVERED(`FULL_LOC` + 右下按钮) |
|
||||
| CONTEXT | D-03 / D-04 / D-06 / D-22(执行前获批 / 命名获批 / 不扩范围 / 逐界面讨论后规划) | COVERED(Task 0 + prohibitions + 范围门禁) |
|
||||
| CONTEXT | 任务 3 · 数据端接口补充(shop:`ShopState` 接口已提供、实现待补) | COVERED(消费 `IShopState` 成员) |
|
||||
| CONTEXT | 任务 3 · 数据端接口补充(shop:`ShopState` 接口已提供) | COVERED(消费 `IShopState` 成员) |
|
||||
| CONTEXT | D-18 其余界面(book/equipbox/toolbox) | OUT OF SCOPE(各自计划) |
|
||||
| 缺口 | `IShopState` 未从 `@user/data-base` 公共面导出(无 `shop/index.ts`,`index.ts` 无 `./shop`) | **OPEN(GAP-S0,须用户补齐;未补齐 Task 1 阻塞)** |
|
||||
| 缺口 | 买价 / 卖价 无来源(`IShopState` / `IItemRawData` 均无价格字段;legacy 在 `ItemShopChoice.money`/`.sell`) | **OPEN(GAP-S1,须用户提供来源,否则 `// TODO` 占位)** |
|
||||
| 缺口 | 购买 / 售出 交易无方法(`IShopState` 仅 `addItem` 调库存) | **OPEN(GAP-S2,须裁定:数据侧提供 或 UI 以既有公开接口组装)** |
|
||||
| 缺口 | 支付资源 `money` vs `exp` 缺字段(legacy `ItemShopEvent.use`) | **OPEN(GAP-S3,默认 money,须确认)** |
|
||||
| 缺口 | `IShopState` 未从 `@user/data-base` 公共面导出 | **RESOLVED(GAP-S0;`data-base/src/index.ts:5` + `shop/index.ts:1`)** |
|
||||
| 缺口 | 买价 / 卖价 无来源 | **RESOLVED(GAP-S1;`IShopState.getPrice` `:48` / `setPrice` `:42` / `IShopStateSave.prices` `:11`)** |
|
||||
| 缺口 | 购买 / 售出 交易无方法 | **RESOLVED(GAP-S2;`IShopSystem.buyItem`/`sellItem`,`data-system/src/shop/types.ts:37,44`)** |
|
||||
| 缺口 | 支付资源 `money` vs `exp` 缺字段 | **RESOLVED(GAP-S3;`IShopHeroAttr.money`,`data-system/src/shop/types.ts:5`,默认 money)** |
|
||||
| 缺口 | `IShopSystem` / `IShopHeroAttr` 未从 `@user/data-system` 公共面导出 | **RESOLVED(GAP-S4;`data-system/src/index.ts` `export * from './shop'` + `data-system/src/shop/index.ts`)** |
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/09-minimum-browser-runnable/09-09-SUMMARY.md` when done
|
||||
|
||||
Loading…
Reference in New Issue
Block a user