docs(09): tighten 09-15 C:ui gate/D-54 boundary; refresh 09-14 as deferral record

This commit is contained in:
unanmed 2026-10-12 18:50:24 +08:00
parent b577bb1ef7
commit acf20f548d
3 changed files with 80 additions and 101 deletions

View File

@ -22,6 +22,7 @@ must_haves:
- "**A-2-1(umbrella,仅记录):** 全局 `core`(`src/types/declaration/core.d.ts:1302`)的替代为 `CoreState`(`packages-user/data-state/src/core.ts:76`);本计划**仅记录**,**不删除 `core` 全局声明**、**不编辑** `client-modules/src/core.ts:6` 的 `core` 单例定义、不做源码改造。"
- "**A-6(用户自行处理):** `Mota.require('@user/data-base')` 三处(A-6-1 cache.ts:491 / A-6-2 misc.ts:89 / A-6-3 misc.ts:219)及其 `loading` 承载**按用户决定 DEFERRED,用户自行处理**;本计划**不修改** `cache.ts` / `misc.ts`。"
- "**范围零外溢 + 静态门禁:** 本计划**不修改任何源码**(`files_modified` 仅本计划 SUMMARY);不改既有注释 / JSDoc;不用 `import type`;验证一律静态断言(范围 / `git status` 守卫),**不使用** `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。"
- "**可观测终态(延后记录):** 本计划执行后**无任何源码改动**——`git status --porcelain` 仅含本计划 SUMMARY(若已写)与用户既有并发改动(`packages-user/data-state/**`);`hotkey.ts` / `cache.ts` / `misc.ts` 等保持原状;A-1-1 与 7 条存档簇保持 **DEFERRED(D-54)**;A-6 用户自行处理;A-2-1 仅记录;40 条 ALT 覆盖承接可审计。"
artifacts:
- path: .planning/phases/09-minimum-browser-runnable/09-14-SUMMARY.md
provides: "本计划延后记录:A-1-1 DEFERRED(D-54)、A-2-1 umbrella 记录、A-6 用户自行处理、40 条 ALT 整体覆盖的可审计承接关系。"

View File

@ -1,23 +1,23 @@
---
phase: 09-minimum-browser-runnable
plan: 14
subsystem: client-modules(跨包 legacy 依赖收口 / 全局 core umbrella 记录)
tags: [legacy-removal, GameStorage, SaveSystem, halted, R2, A-1-1, A-2-1]
subsystem: client-modules(跨包 legacy 依赖 / 存档簇的 D-54 延后登记)
tags: [legacy-removal, GameStorage, SaveSystem, deferred, D-54, A-1-1, A-2-1]
# Dependency graph
requires:
- phase: 09-minimum-browser-runnable
provides: 09-13 回退层去除 + 12 条 ALT 收口(ALT-COUNT: 40 / ENTRY-COUNT: 99)
provides:
- Task 0 关卡闭环记录(裁剪后范围:A-1-1 / A-2-1;A-6 DEFERRED 用户自行处理)
- A-2-1 umbrella 部分收口记录(REPLACED vs DEFERRED 逐条,40 条 ALT 可审计)
- R2 阻断记录(A-1-1 GameStorage→SaveSystem:模块级 hotkey.ts 无合规实例路径)
- Task 0 汇报关卡闭环记录(延后记录性质:A-1-1 / 存档簇 DEFERRED per D-54;A-6 用户自行处理;A-2-1 仅记录)
- A-1-1 与 7 条存档簇的 DEFERRED(D-54)登记
- 40 条 ALT 覆盖承接可审计(REPLACED vs DEFERRED 逐条)
affects: [09-15, 09-16]
# Actuals (#2632) — pairs with the plan's `estimate` to calibrate future estimates.
actuals:
tokens: 2637
tasks: 2
tokens: 0
tasks: 1
commits: 0
plan_head_before: 529bb5578dfec2fb052f3ce41d686183bb9a0de9
@ -25,7 +25,7 @@ actuals:
tech-stack:
added: []
patterns:
- "模块级(非组件)文件取得 SaveSystem 无合规路径时:记录 R2 阻断 + STOP + 标记待裁定,保留 legacy 用法,不 invent"
- "存档相关适配(GameStorage / SaveSystem / localForage 及存档簇)统一依 D-54 登记为 DEFERRED,不纳入第一批改造"
key-files:
created:
@ -33,82 +33,60 @@ key-files:
modified: []
key-decisions:
- "Task 0 关卡按用户决定闭环:裁剪后范围 = A-1-1(GameStorage→SaveSystem)+ A-2-1(umbrella 记录);A-6-1/2/3(Mota.require/loading)DEFERRED(用户自行处理)"
- "R2 成立:模块级 hotkey.ts 无合规取得 SaveSystem 实例的路径(D-11 禁 client 单例),A-1-1 记录 R2 阻断并 STOP(待裁定)"
- "未修改任何源码;未触碰 A-6(cache.ts / misc.ts);未引入 client 单例;未新增命名"
- "A-2-1 仅作 umbrella 记录:不删全局 core 声明(core.d.ts:1302)、不改 client-modules/src/core.ts:6 单例定义"
- "本计划为延后记录(DEFERRAL RECORD):无 active work task、不修改任何源码"
- "A-1-1(GameStorage → SaveSystem)依 D-54 属存档相关适配,DEFERRED,后续另行规划"
- "7 条存档簇(C:saves / C:getSave / C:getAllSaves / C:setLocalForage / C:removeLocalForage / C:removeSave / C:clearLocalForage)依 D-54 一并 DEFERRED(承接于 09-15 的延后组)"
- "A-6(Mota.require / loading,A-6-1/2/3)按用户决定 DEFERRED,用户自行处理"
- "A-2-1(全局 core → CoreState)仅作 umbrella 记录;不删 core.d.ts 声明、不改 core.ts:6 单例定义"
patterns-established:
- "替代不可合规落地时:记录 R2 阻断 + STOP + 标记待裁定,保留 legacy 用法(不冒充完成),保留全部 ALT 追踪"
- "存档相关适配一律登记为 DEFERRED(D-54),保留全部 ALT 追踪,不冒充完成"
requirements-completed: []
# Metrics
duration: 12min
duration: 0min
completed: 2026-10-12
status: halted
status: complete
---
# Phase 9 Plan 14: `GameStorage` → `SaveSystem` 收口(R2 阻断)+ A-2-1 umbrella 记录 Summary
# Phase 9 Plan 14: `GameStorage` / 存档簇的 D-54 延后登记 + A-2-1 umbrella 记录 Summary
**`hotkey.ts` 跨包 legacy 依赖 `GameStorage` → `SaveSystem`(A-1-1)因模块级文件无合规实例路径(D-11 禁 `client` 单例)触发 R2 阻断并按计划 STOP;A-2-1 全局 `core` umbrella 部分收口已逐条记录(40 条 ALT 可审计);A-6(`Mota.require`/`loading`)按用户决定保持 DEFERRED;未改动任何源码**
**本计划为延后记录(DEFERRAL RECORD):无 active work task、未修改任何源码;A-1-1(`GameStorage` → `SaveSystem`)与 7 条存档簇依 D-54 DEFERRED;A-6(`Mota.require` / `loading`)按用户决定 DEFERRED(用户自行处理);A-2-1(全局 `core` → `CoreState`)仅作 umbrella 记录;40 条 ALT 覆盖承接逐条可审计**
## Performance
- **Duration:** 12 min
- **Started:** 2026-10-12T08:43:51Z
- **Completed:** 2026-10-12T08:55:00Z
- **Tasks:** 2(Task 0 为 `blocking-human` 汇报关卡,已闭环;Task 1 触发 R2 STOP,无源码改动)
- **Duration:** 0 min(延后记录,无 active work task)
- **Tasks:** 1(Task 0 为 `blocking-human` 汇报关卡,已闭环;本计划无源码改造任务)
- **Files modified:** 0(仅新增本 SUMMARY)
## Task 0:预执行汇报关卡(已闭环)
**类型:** `checkpoint:decision` · `gate="blocking-human"` · **结论:用户已闭环。**
- 本计划 = 任务 7 · 第一批改造(ALT 条目)· **第 2 组 / 共 4 组** 的**裁剪后范围**:
- **A-1-1**:跨包 legacy 依赖 `GameStorage`(`@motajs/legacy-system`)→ `SaveSystem` 全局存储(`packages-user/client-base/src/save/system.ts:41`)。
- **A-2-1**:全局 `core`(`src/types/declaration/core.d.ts:1302`)→ `CoreState`(`packages-user/data-state/src/core.ts:76`)——**umbrella,仅记录部分收口**。
- **范围裁剪说明:** 原第 2 组的 **A-6-1/2/3(`Mota.require('@user/data-base')` / `loading`)已按用户决定 DEFERRED,由用户自行处理**;本计划**不修改** `packages-user/client-base/src/elements/cache.ts` / `misc.ts`,不核实 / 不改写 `loading`。
- **`files_modified` 精确为 1 个源文件**(`packages-user/client-modules/src/action/hotkey.ts`)+ 本 SUMMARY。
- 本计划 = 任务 7 · 第一批改造(ALT 条目)· **第 2 组 / 共 4 组** 的**延后记录**。
- 采纳处置:`execute-default`(确认本计划为延后记录,无源码改造)。
- 依据 `AGENTS.md`(执行前须获批)/ D-03 / D-04;`.planning/config.json` 为 `mode: yolo`,故关卡显式 `blocking-human` 且 `autonomous: false`。
- 采纳处置:`execute-default`(可替代部分如实落地;R2 触发时按 STOP 处置;A-6 保持 DEFERRED)。OI-1 / OI-3 / OI-4 见下。
- OI-1 / OI-2 / OI-3 / OI-4 见下。
## Task 1:`GameStorage` → `SaveSystem`(A-1-1)—— R2 阻断,STOP(无源码改动)
## 本计划性质:延后记录(DEFERRAL RECORD,D-54)
### R2 核实结论:无合规实例路径
**本计划无 active work task,不修改任何源码文件。** 逐条处置如下:
`hotkey.ts` 为**模块级副作用文件**(非组件),无 `props` / `IClientBase` 注入点。经全仓核实:
- `SaveSystem` 实例**仅**在 `ClientCore` 构造函数内创建(`packages-user/client-modules/src/client.ts:74`),并以实例成员 `readonly save: ISaveSystem`(`client.ts:49`)对外暴露;`IClientBase.save`(`packages-user/client-base/src/types.ts:23`)是唯一对外契约。
- 模块级代码可见的引用只有 legacy 全局(`core` / `main` 等)与 `client` 单例(`packages-user/client-modules/src/core.ts:6`)。**`client` 单例被 D-11 明确禁止**。
- 仓库无任何全局提供 `SaveSystem`(`src/types/declaration/*.d.ts` 中无相关 `declare`);无 props / context / provider 机制;`dev.md` 亦指出不应存在 `createXxx` 式初始化函数把主对象下传。
- 结论:模块级 `hotkey.ts` **无法在不引入 `client` 单例、不 invent 接口、不做结构性改动的前提下**合规取得 `SaveSystem` 实例。
### 语义差异(sync → async,进一步佐证不可无痛替换)
| 维度 | `GameStorage`(现状) | `SaveSystem`(审计替代) |
| ID | 对象 | 处置 |
| --- | --- | --- |
| 后端 | `localStorage`(键值) | Dexie / IndexedDB |
| 读取 | 构造器 `read()` 同步 `JSON.parse` | `getGlobal<T>(key)` **返回 `Promise<T \| null>`** |
| 写入 | `setValue()` 仅改内存;`beforeunload` / `blur` 时批量 `write()` | `setGlobal(key, value)` **返回 `Promise<void>`**,即时落库 |
| 键 | `GameStorage.fromAuthor('AncTe', 'gameKey')` | 全局存储 `key`(无 author 命名空间) |
| A-1-1 | `GameStorage`(`@motajs/legacy-system`,hotkey.ts:3)→ `SaveSystem`(save/system.ts:41) | **DEFERRED(D-54)** —— 存档相关适配暂时忽略 / 延后,后续另行规划;本计划不改 `hotkey.ts` |
| A-2-1 | 全局 `core`(core.d.ts:1302)→ `CoreState`(data-state/core.ts:76) | **仅记录(umbrella)** —— 不删 `core` 全局声明、不改 `core.ts:6` 单例定义;不做源码改造 |
| A-6-1/2/3 | `Mota.require('@user/data-base')`(cache.ts:491 / misc.ts:89 / misc.ts:219) | **DEFERRED(用户自行处理 `loading`)** —— 本计划不改 `cache.ts` / `misc.ts` |
| 7 条存档簇 | `C:saves` / `C:getSave` / `C:getAllSaves` / `C:setLocalForage` / `C:removeLocalForage` / `C:removeSave` / `C:clearLocalForage`(`core.*` → `SaveSystem`) | **DEFERRED(D-54)** —— 存档相关适配暂时忽略 / 延后;承接于 09-15 的延后组,本计划不改 |
`hotkey.ts` 现有行为为「`read()` → `fromJSON` 到 `gameKey` → 变更(`set` 事件)时 `setValue` 写内存 → 卸载时落盘」。改写为 `SaveSystem` 需将模块初始化的同步读取改为异步(`getGlobal().then(...)`)并把增量写入改为异步 read-modify-write —— 属**行为契约变化**,非机械替换。
> **依据(D-54):** 新存档系统与旧设计完全不同、适配难度大,故存档相关 legacy 适配暂时忽略 / 延后(含 `GameStorage`、`SaveSystem`、`localForage` 及 `core.saves` / `core.getSave` / `core.getAllSaves` / `core.setLocalForage` / `core.removeLocalForage` / `core.removeSave` / `core.clearLocalForage`)。本计划据此将 A-1-1 与 7 条存档簇登记为 DEFERRED,不纳入第一批改造。
### 阻断处置(按计划执行)
> **补充佐证(存档适配难度,支持 D-54):** A-1-1 的 `GameStorage` → `SaveSystem` 非机械替换——后端由 `localStorage` 键值改为 Dexie / IndexedDB,读取 / 写入契约由同步改为 `Promise`(`getGlobal` / `setGlobal`),且 `hotkey.ts` 为模块级副作用文件(无 `props` / `IClientBase` 注入点),在不引入 `client` 单例(D-11 禁)的前提下无合规实例路径。该难度与 D-54「适配难度大」一致,故 A-1-1 归入 DEFERRED。
- **未修改** `hotkey.ts`:`GameStorage` 用法(`:3` 导入、`:518-522` 实例化 / `read`、`:523-525` `set` 监听 `setValue`、`:526` `fromJSON(toJSON())`)保持原样。
- **未引入** `client` 单例;**未 invent** 任何接口 / 全局 / 命名。
- **未保留 `GameStorage` 冒充完成**:A-1-1 状态记为 **「待裁定」(R2 阻断)**,交用户裁定接入口径(**OI-3**)。
- 依据计划:`09-14-PLAN.md` Line 179「若无法合规取得:记录 R2 阻断 + STOP」;Line 126「若 R2 触发 STOP,则相应条目标记为「待裁定」,门禁对未改文件不作强制」。
## 40 条 ALT 覆盖总表(ALT-COUNT = 40)
## A-2-1 umbrella(部分收口)—— REPLACED vs DEFERRED 逐条(本计划记录)
> **A-2-1 性质:** 全局 `core` → `CoreState` 的 **umbrella** 记录。本计划**不删除** `src/types/declaration/core.d.ts:1302` 的全局 `core` 声明,**不编辑** `packages-user/client-modules/src/core.ts:6` 的 `core` 单例定义(该定义属 ADAPT 分类 / 其它批次)。全量收口须待其余分类(ADAPT / NONE / TBD)完成后另行规划。
**本计划实际源码改动:无。** A-1-1 因 R2 阻断未落地,故本计划**未新增任何 REPLACED**;下表使 40 条 ALT 的整体覆盖保持可审计(含 09-13 已收口部分与 09-15 / 09-16 承接部分)。
### 40 条 ALT 覆盖总表(ALT-COUNT = 40;五类之和 = ENTRY-COUNT 99)
> 本计划**未新增任何 REPLACED**;下表使 40 条 ALT 的整体覆盖保持可审计(含 09-13 已收口部分与 09-15 / 09-16 承接部分)。
**① 已 REPLACED(09-13 回退层去除组,12 条)**
@ -127,22 +105,26 @@ status: halted
| C:drawTip | `core.drawTip` | ✅ 09-13(→ `TipStore.drawTip`) |
| C:playSound | `core.playSound` | ✅ 09-13(→ `SoundPlayer.play`) |
**② 本计划(09-14,2 条)**
**② 本计划(09-14,延后登记 2 条)**
| ID | 对象 | 状态 |
| --- | --- | --- |
| A-1-1 | `GameStorage`(hotkey.ts:3,518-522) | ⏸ **待裁定(R2 阻断)** —— 未改源码 |
| A-2-1 | 全局 `core`(core.d.ts:1302)→ `CoreState`(data-state/core.ts:76) | 📝 **umbrella 部分收口(本记录)** —— 未删声明 / 未改 `core.ts:6` 单例 |
| A-1-1 | `GameStorage`(hotkey.ts:3)→ `SaveSystem`(save/system.ts:41) | ⏸ **DEFERRED(D-54)** —— 未改源码 |
| A-2-1 | 全局 `core`(core.d.ts:1302)→ `CoreState`(data-state/core.ts:76) | 📝 **仅记录(umbrella)** —— 未删声明 / 未改 `core.ts:6` 单例 |
**③ DEFERRED —— 承接于 09-15(15 条,per 09-15-PLAN.md ALT 引用)**
**③ DEFERRED(D-54)—— 存档簇 7 条(承接于 09-15 的延后组)**
`A-1-6`、`A-5-2`、`C:clearLocalForage`、`C:drawThumbnail`、`C:getAllSaves`、`C:getSave`、`C:openBook`、`C:openEquipbox`、`C:openQuickShop`、`C:openToolbox`、`C:removeLocalForage`、`C:removeSave`、`C:saves`、`C:setLocalForage`、`C:ui`
`C:clearLocalForage`、`C:getAllSaves`、`C:getSave`、`C:removeLocalForage`、`C:removeSave`、`C:saves`、`C:setLocalForage`
**④ DEFERRED —— 承接于 09-16(8 条,per 09-16-PLAN.md ALT 引用)**
**④ 承接于 09-15(active 7 条,per 09-15-PLAN.md)**
`A-1-7`、`A-1-9`、`A-5-3`、`A-5-7`、`C:debug`、`C:encodeRoute`、`C:getTilesetOffset`、`C:hasItem`
`A-1-6`、`A-5-2`、`C:openBook`、`C:openEquipbox`、`C:openQuickShop`、`C:openToolbox`、`C:ui`
**⑤ DEFERRED —— 用户自行处理(A-6,3 条)**
**⑤ 承接于 09-16(9 条,per 09-16-PLAN.md)**
`A-1-7`、`A-1-9`、`A-5-3`、`A-5-7`、`C:debug`、`C:drawThumbnail`、`C:encodeRoute`、`C:getTilesetOffset`、`C:hasItem`
**⑥ 用户自行处理(A-6,3 条)**
| ID | 对象 | 处置 |
| --- | --- | --- |
@ -150,7 +132,7 @@ status: halted
| A-6-2 | `Mota.require('@user/data-base')`(misc.ts:89) | DEFERRED(同上) |
| A-6-3 | `Mota.require('@user/data-base')`(misc.ts:219) | DEFERRED(同上) |
**计数核对:** 12 + 2 + 15 + 8 + 3 = **40** ✅(与 `09-LEGACY-REPLACEMENT-AUDIT.md` 的 `ALT-COUNT: 40` 一致)。
**计数核对:** 12(①)+ 2(②)+ 7(③)+ 7(④)+ 9(⑤)+ 3(⑥)= **40** ✅(与 `09-LEGACY-REPLACEMENT-AUDIT.md` 的 `ALT-COUNT: 40` 一致)。
## 命名影响(D-04 / OI-4)
@ -160,44 +142,38 @@ status: halted
| 门禁 | 结果 |
| --- | --- |
| Task 0 pre-state(`hotkey.ts` + `save/system.ts` + 审计文档存在) | ✅ OK(`OK prestate intact`) |
| Task 1 GAMESTORAGE gate(`hotkey.ts` 无 `GameStorage` / 无 `@motajs/legacy-system` / 有 `SaveSystem`) | ⏸ **按 R2 分支:不改文件,门禁不作强制**(计划 Line 126) |
| Task 1 CRLF / IMPORT gate(`hotkey.ts`) | ⏸ 未改文件(沿用原状:CRLF,无 `import type`) |
| Task 1 scope gate(`git status --porcelain`:仅本计划产物 + 用户既有并发改动) | ✅ OK(仅新增本 SUMMARY;`data-state/**`、`data-system/**` 为用户并发改动,未触碰 / 未暂存) |
| Task 0 pre-state(审计文档 + CONTEXT 存在) | ✅ OK(`OK prestate intact`) |
| 范围守卫(`git status --porcelain`:仅本计划产物 + 用户既有并发改动) | ✅ OK(仅新增本 SUMMARY;`packages-user/data-state/**` 为用户并发改动,未触碰 / 未暂存) |
| 源码改动 | ✅ **0**(`hotkey.ts` / `cache.ts` / `misc.ts` / `saves.ts` / `save.tsx` 等均未改) |
**门禁口径:** 全部为**静态断言**;未使用 `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。
**A-6 边界(T-09-14-01):** `packages-user/client-base/src/elements/cache.ts` / `misc.ts` **未被本计划修改**(`git status` 无其改动)。
## Deviations from Plan
**None(R2 STOP 为计划内既定分支,非偏离)。** 计划 Task 1 明确:无法合规取得实例路径时「记录 R2 阻断 + STOP」(Line 179)、「相应条目标记为「待裁定」,门禁对未改文件不作强制」(Line 126)。本计划严格按该分支执行。
**None。** 本计划严格按延后记录执行:无 active work task、未修改任何源码。
- 未 invent 替代方案;未引入 `client` 单例;未新增命名;未越界改其它分类。
- 未触碰 A-6(`cache.ts` / `misc.ts`)与 `09-CONTEXT.md` / `09-LEGACY-REPLACEMENT-AUDIT.md` / 09-01..09-13 任何文件。
## Issues Encountered
- **R2(A-1-1 接入口径,OI-3)—— 阻断,待用户裁定:** 模块级 `hotkey.ts` 取得 `SaveSystem` 的合规路径未定。可选方向(均需用户裁定 / 新命名批准 / 结构改动,故本计划不擅自实施):
1. 由拥有主对象的调用方在初始化时把 `ISaveSystem` 注入 hotkey 模块(需新增初始化入口 / 命名 → D-04);
2. 将热键配置持久化迁移到已有主对象上下文的位置(结构性改动 → Rule 4 / 影响面扩大);
3. 暂缓 A-1-1,待渲染端后续重构(如 hotkey 相关 UI 重构,D-19)时一并处理。
- **用户并发改动(非本计划产物,未触碰 / 未暂存):** `packages-user/data-state/**`(`enemy/{aura,calculator,final,legacy,special}`)与 `packages-user/data-system/src/combat/types.ts`。二者为 `git status` 守卫豁免的既有并发改动。
- **存档适配(A-1-1 + 7 条存档簇)—— DEFERRED(D-54):** 后续另行规划;本计划不实施、不核实接入口径(OI-2)。
## Next Phase Readiness
- **A-1-1 未收口,为 09-15 / 09-16 的悬置前置:** 因本计划 `status: halted`,依赖本计划的后续计划应视为**阻塞待裁定**,直至用户就 R2 / OI-3 给出接入口径。
- A-2-1 umbrella 已记录(全局 `core` 声明与 `core.ts:6` 单例定义均保持原状)。
- A-1-1 与 7 条存档簇保持 **DEFERRED(D-54)**,后续另行规划。
- A-6(`Mota.require` / `loading`)保持 DEFERRED(用户自行处理)。
- 40 条 ALT 覆盖已在本文档逐条可审计(见 A-2-1 节)。
- A-2-1 umbrella 已记录(全局 `core` 声明与 `core.ts:6` 单例定义均保持原状)。
- 40 条 ALT 覆盖已在本文档逐条可审计(见「40 条 ALT 覆盖总表」)。
---
## Self-Check: PASSED
- `hotkey.ts` **未修改**(`GameStorage` 用法保持原状;R2 阻断如实记录,未冒充完成)。
- 未触碰 `cache.ts` / `misc.ts`(A-6 DEFERRED);未触碰用户并发改动。
- A-2-1 部分收口记录完成,40 条 ALT 覆盖表逐条列明(REPLACED 12 / 本计划 2 / 09-15 承接 15 / 09-16 承接 8 / A-6 用户 3 = 40)。
- **未修改任何源码**(`hotkey.ts` / `cache.ts` / `misc.ts` 等保持原状;本计划无 active work task)。
- 未触碰 A-6(`cache.ts` / `misc.ts`);未触碰用户并发改动。
- A-1-1 + 7 条存档簇登记为 DEFERRED(D-54);A-2-1 仅记录;40 条 ALT 覆盖表逐条列明(12 + 2 + 7 + 7 + 9 + 3 = 40)。
- 提交:无任务提交(`commits: 0`,如实;本计划无源码改动);本 SUMMARY 由收尾 docs 提交承载。
- STATE.md / ROADMAP.md / REQUIREMENTS.md 未被本执行修改(由 orchestrator 负责)。
@ -205,4 +181,4 @@ status: halted
*Phase: 09-minimum-browser-runnable*
*Plan: 14*
*Completed: 2026-10-12*
*Status: halted (R2 STOP — A-1-1 待用户裁定,OI-3)*
*Status: complete (延后记录 — A-1-1 与 7 条存档簇 DEFERRED,D-54)*

View File

@ -22,12 +22,12 @@ must_haves:
- "**本计划的第一个 block 点是 Task 0 的 `blocking-human` 汇报关卡(AGENTS.md 硬要求,D-03)**:Task 0 必须先汇报「本计划 = 任务 7 · 第一批改造(ALT)· 4 个计划中的第 3 个(**UI 打开 / 控制替换**;存档簇依 D-54 DEFERRED)」的要做什么 / 为什么 / 大致怎么做 / 影响范围,**必须列明:逐条 ALT 条目表(本组 active 7 条;存档簇 7 条 DEFERRED);逐文件影响表(4 文件);须核实的点(R2:`core.openBook` 等打开 UI 的传参与 `UIController.open` / props 接入,及 `core.ui` 等价方法);存档簇 DEFERRED(D-54)说明;命名影响;开放项 OI-1..OI-4**,然后必须**停下等待**用户明确回复「可以执行」四个字;未收到前不得执行本计划任何一步、不得派发执行子代理,且不因触发方式或 `mode: yolo` / `workflow.auto_advance` 自动放行。"
- "**D-50 / D-52(范围 / 依据):** 实现本批 ALT 之**第 3 组 active 部分**(7 条:C:openBook / C:openToolbox / C:openEquipbox / C:openQuickShop / C:ui / A-1-6 / A-5-2),严格依据审计替代方案,**不 invent**。"
- "**D-53(其它分类不在本批):** 不触碰 `可直接替换` / `有替代但需适配` / `无替代` / `待定`;本组只改 ALT 条目。"
- "**D-54(存档相关适配延后):** 原第 3 组的**存档簇 7 条**(C:saves / C:getSave / C:getAllSaves / C:setLocalForage / C:removeLocalForage / C:removeSave / C:clearLocalForage)属**存档相关适配**,依 **D-54 暂时忽略 / 延后**,后续另行规划;本计划**不修改** `render/utils/saves.ts` / `ui/save.tsx`,也不改 `ui/settings.tsx` 的 `C:clearLocalForage` 用法(`:573`)。"
- "**D-54(存档相关适配延后):** 原第 3 组的**存档簇 7 条**(C:saves / C:getSave / C:getAllSaves / C:setLocalForage / C:removeLocalForage / C:removeSave / C:clearLocalForage)属**存档相关适配**,依 **D-54 暂时忽略 / 延后**,后续另行规划;本计划**不修改** `render/utils/saves.ts` / `ui/save.tsx`,也不改 `ui/settings.tsx` 的 `C:clearLocalForage` 用法(`:573`)。**同时 `ui/settings.tsx:621` 的 `core.ui.closePanel()`(位于 `core.clearLocalForage` / `core.removeLocalForage('autoSave')` 存档回调内)随所属存档块一并为 DEFERRED(D-54)——C:ui 的 active callsite 仅 `settings.tsx:160`,`:621` 不纳入本次替换、保持原状。**"
- "**R2(UI 打开 / 控制的传参与接入):** 审计将 `core.openBook` / `core.openToolbox` / `core.openEquipbox` / `core.openQuickShop` / `core.ui` / `mainUi` 的替代定位到 `BookUI` / `ToolBoxUI` / `EquipBoxUI` / `ShopUI`(`ui/*.tsx`)、`mainUIController`(`ui/controller.tsx:5`)与 `UIController.open`(`packages/system/src/ui/controller.ts:190`)。执行日须核实打开这些 UI 所需 props 的取得路径(D-11:经 props / 主对象,不得引入 `client` 单例)与 `core.ui.closePanel()` / `core.ui._drawViewMaps()` 的等价方法(审计述 `closePanel` 以 `closeAll` 等价;`_drawViewMaps` 等价物须核实);无法等价时**记录并 STOP**。"
- "**范围零外溢 + 静态门禁:** 只改 `files_modified`;不改既有注释 / JSDoc;不用 `import type`;源码 CRLF;验证一律静态断言(legacy 用法消失 + 替代符号出现 + CRLF + 范围 / `git status` 守卫),**不使用** `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。"
artifacts:
- path: packages-user/client-modules/src/ui/settings.tsx
provides: "`mainUi` / `mainUi.open` → `mainUIController` / `UIController.open`;`core.ui` → `mainUIController`。"
provides: "`mainUi` / `mainUi.open` → `mainUIController` / `UIController.open`;`core.ui`(active `:160`)→ `mainUIController`;`core.ui`(`:621`,存档回调内)DEFERRED(D-54)。"
- path: packages-user/client-modules/src/action/hotkey.ts
provides: "`core.openBook` / `core.openToolbox` / `core.openEquipbox` / `core.openQuickShop` → 对应 UI + `UIController.open`。"
- path: packages-user/client-modules/src/ui/toolbar.tsx
@ -38,7 +38,7 @@ must_haves:
provides: "本计划执行摘要:本组 active ALT(7 条 UI 打开 / 控制)、逐文件改动、R2 结论、**存档簇 7 条 DEFERRED(D-54)**、命名影响、门禁结果。"
key_links:
- "`action/hotkey.ts:481,484,487,493` 与 `ui/toolbar.tsx:89,90,98,99,192` 与 `ui/viewmap.tsx:130`(`core.open*`)↔ `ui/book.tsx:251` / `ui/toolbox.tsx:278` / `ui/equipbox.tsx:551` / `ui/shop.tsx:491`(`*UI`)—— C:openBook / C:openToolbox / C:openEquipbox / C:openQuickShop"
- "`ui/settings.tsx:160` 与 `ui/toolbar.tsx:198`(`core.ui`)↔ `ui/controller.tsx:5`(`mainUIController`)—— C:ui"
- "`ui/settings.tsx:160`(active)与 `ui/toolbar.tsx:198`(active)(`core.ui`)↔ `ui/controller.tsx:5`(`mainUIController`)—— C:ui;**`ui/settings.tsx:621`(`core.ui.closePanel()`,位于 `core.clearLocalForage` / `core.removeLocalForage('autoSave')` 存档回调内)为 DEFERRED(D-54)——随所属存档块延后,不纳入本次替换;UIOPEN 门禁据此仅校验 active 区,允许 `:621` 保留。**"
- "`ui/settings.tsx:13,67`(`mainUi` / `mainUi.open('settings')`)↔ `ui/controller.tsx:5`(`mainUIController`)/ `packages/system/src/ui/controller.ts:190`(`UIController.open`)—— A-1-6 / A-5-2"
assumptions:
- "**执行日行号可能漂移**:引用行号为 2026-10-11 规划日实测;门禁以执行日实测为准。"
@ -47,7 +47,7 @@ must_haves:
- "**Markdown 使用 LF,源码使用 CRLF**。"
prohibitions:
- "不得在用户明确回复「可以执行」四个字之前执行本计划任何一步、或派发任何执行子代理。"
- "**不得将存档簇作为 active 任务执行(DEFERRED,D-54)**:本计划**不得**修改 `render/utils/saves.ts` / `ui/save.tsx`,也不得改 `ui/settings.tsx` 的 `C:clearLocalForage` 用法(`core.saves` / `core.getSave` / `core.getAllSaves` / `core.setLocalForage` / `core.removeLocalForage` / `core.removeSave` / `core.clearLocalForage` 全部 DEFERRED)。"
- "**不得将存档簇作为 active 任务执行(DEFERRED,D-54)**:本计划**不得**修改 `render/utils/saves.ts` / `ui/save.tsx`,也不得改 `ui/settings.tsx` 的 `C:clearLocalForage` 用法(`core.saves` / `core.getSave` / `core.getAllSaves` / `core.setLocalForage` / `core.removeLocalForage` / `core.removeSave` / `core.clearLocalForage` 全部 DEFERRED);此外 `ui/settings.tsx:621` 的 `core.ui.closePanel()`(存档回调内)亦为 DEFERRED,不得改动。"
- "**不得 invent UI 替代**:`UIController` 无等价方法的 `core.ui.*`,须记录并 STOP,不得造符号或猜语义。"
- "**不得引入 `client` 单例**;不得为接入而新增未批准命名(D-04)。"
- "不得触碰非 ALT 分类用法(如 `core.doSL` / `core.startGame` / `core.chooseReplayFile` / `core.download` / `core.firstData` / `core.status` 等)—— 它们属其它分类或其它组。"
@ -97,17 +97,19 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
| C:openToolbox | `core.openToolbox`(hotkey.ts:484) | `ToolBoxUI`(toolbox.tsx:278) | hotkey.ts:484;toolbar.tsx:90 | 改经 `mainUIController.open(ToolBoxUI, …)`(R2) |
| C:openEquipbox | `core.openEquipbox`(hotkey.ts:487) | `EquipBoxUI`(equipbox.tsx:551) | hotkey.ts:487;toolbar.tsx:98 | 改经 `mainUIController.open(EquipBoxUI, …)`(R2) |
| C:openQuickShop | `core.openQuickShop`(hotkey.ts:493) | `ShopUI`(shop.tsx:491) | hotkey.ts:493;toolbar.tsx:99 | 改经 `mainUIController.open(ShopUI, …)`(R2) |
| C:ui | `core.ui`(settings.tsx:160) | `mainUIController`(ui/controller.tsx:5) | settings.tsx:160;toolbar.tsx:198 | `closePanel` → `closeAll`;`_drawViewMaps` 等价物核实(R2) |
| C:ui | `core.ui`(settings.tsx:160,active) | `mainUIController`(ui/controller.tsx:5) | settings.tsx:160(active);toolbar.tsx:198(active);**settings.tsx:621 DEFERRED(D-54,存档回调内)** | `closePanel` → `closeAll`;`_drawViewMaps` 等价物核实(R2);**`settings.tsx:621` 随存档块 DEFERRED,不改** |
| A-1-6 | `mainUi`(settings.tsx:13) | `mainUIController`(ui/controller.tsx:5) | ui/settings.tsx:13,67 | 移除 `@motajs/legacy-ui` 的 `mainUi` 导入,改用 `mainUIController` |
| A-5-2 | `mainUi.open('settings')`(settings.tsx:67) | `UIController.open`(system/ui/controller.ts:190) | ui/settings.tsx:67 | 改 `mainUIController.open(MainSettingsUI, …)` |
> **DEFERRED(D-54,本计划不实施;存档相关适配暂时忽略 / 延后):** `C:saves`(`core.saves`,saves.ts:173,185)/ `C:getSave`(saves.ts:21)/ `C:getAllSaves`(saves.ts:38,自指锚点)/ `C:setLocalForage`(saves.ts:172,187)/ `C:removeLocalForage`(saves.ts:174)/ `C:removeSave`(save.tsx:538)/ `C:clearLocalForage`(settings.tsx:573)—— `core.*` → `SaveSystem`(`save/system.ts:41` 等)。本计划**不修改** `render/utils/saves.ts` / `ui/save.tsx`,也不改 `ui/settings.tsx` 的 `C:clearLocalForage` 用法。
>
> **C:ui 的 DEFERRED 出现点:** `ui/settings.tsx:621` 的 `core.ui.closePanel()` 位于 `core.clearLocalForage` / `core.removeLocalForage('autoSave')` 存档回调内,随所属存档块一并为 **DEFERRED(D-54)**,**不属 C:ui 本次替换的 active callsite**(active 仅 `settings.tsx:160`)。门禁据此仅校验 active 区(见 Task 1 UIOPEN 门禁)。
## 逐文件影响表(本组)
| 文件 | 动作 | 改动内容 | 关联 ALT |
| --- | --- | --- | --- |
| `packages-user/client-modules/src/ui/settings.tsx` | 修改 | `mainUi` / `mainUi.open` → `mainUIController` / `UIController.open`;`core.ui` → `mainUIController` | C:ui / A-1-6 / A-5-2 |
| `packages-user/client-modules/src/ui/settings.tsx` | 修改 | `mainUi` / `mainUi.open` → `mainUIController` / `UIController.open`;`core.ui`(active `:160`)→ `mainUIController`;`core.ui`(`:621`,存档回调内)DEFERRED(D-54),不改 | C:ui / A-1-6 / A-5-2 |
| `packages-user/client-modules/src/action/hotkey.ts` | 修改 | `core.open*` → 对应 `*UI` + `UIController.open` | C:openBook / C:openToolbox / C:openEquipbox / C:openQuickShop |
| `packages-user/client-modules/src/ui/toolbar.tsx` | 修改 | `core.open*` / `core.ui` → 对应 `*UI` + `mainUIController` | C:openBook / C:openToolbox / C:openEquipbox / C:openQuickShop / C:ui |
| `packages-user/client-modules/src/ui/viewmap.tsx` | 修改 | `core.openBook` → `BookUI` + `UIController.open` | C:openBook |
@ -142,7 +144,7 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
⑦ **范围与禁止:** 只改 ALT 条目相关用法;不触碰其它分类(`core.doSL` / `core.startGame` / `core.status` 等);不改 09-01..09-14;不改既有注释;不用 `import type`;不引入 `client` 单例;源码 CRLF;**不触碰存档簇(DEFERRED,D-54)。**
⑧ **验证方式:** 静态断言——`hotkey.ts` / `toolbar.tsx` / `viewmap.tsx` 无 `core.open*` 且有 `*UI` / `UIController`;`settings.tsx` / `toolbar.tsx` 无 `core.ui` 且有 `mainUIController`;无 `@motajs/legacy-ui` 的 `mainUi` 导入;CRLF;范围 / `git status` 守卫。**不设全仓类型 / 构建门禁。**
⑧ **验证方式:** 静态断言——`hotkey.ts` / `toolbar.tsx` / `viewmap.tsx` 无 `core.open*` 且有 `*UI` / `UIController`(正向);`settings.tsx`(`:160`)/ `toolbar.tsx`(`:198`)的 **active `core.ui` 替换为 `mainUIController`**(正向),且 **`settings.tsx:621` 的 `core.ui.closePanel()`(存档回调内)依 D-54 保持 DEFERRED、允许保留**(UIOPEN 门禁仅校验 active 区);无 `@motajs/legacy-ui` 的 `mainUi` 导入;CRLF;范围 / `git status` 守卫。**不设全仓类型 / 构建门禁。**
⑨ **开放项:** OI-1(批次拆分)、OI-2(存档簇 DEFERRED,D-54)、OI-3(R2)、OI-4(命名)。**无论触发方式或 `mode: yolo` / `workflow.auto_advance`,执行者 / 子代理必须停下等待用户明确回复「可以执行」四个字**;回复其它内容视为未通过。
</action>
@ -190,7 +192,7 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
- packages-user/client-modules/src/action/hotkey.ts:475-515(`core.openBook` / `openToolbox` / `openEquipbox` / `openQuickShop`)
- packages-user/client-modules/src/ui/toolbar.tsx:1-30,85-205(`core.open*` / `core.ui._drawViewMaps` / `mainUIController` 既有用法)
- packages-user/client-modules/src/ui/viewmap.tsx:125-140(`core.openBook`)
- packages-user/client-modules/src/ui/settings.tsx:1-70,155-165,685-700(`mainUi` / `mainUi.open('settings')` / `core.ui.closePanel` / 既有 `controller.open(MainSettingsUI, …)`)
- packages-user/client-modules/src/ui/settings.tsx:1-70,155-165,610-630,685-700(`mainUi` / `mainUi.open('settings')` / active `core.ui.closePanel`(`:160`)/ **存档回调内 `core.ui.closePanel`(`:621`,DEFERRED,D-54)** / 既有 `controller.open(MainSettingsUI, …)`)
- packages-user/client-modules/src/ui/book.tsx:251、toolbox.tsx:278、equipbox.tsx:551、shop.tsx:491(`BookUI` / `ToolBoxUI` / `EquipBoxUI` / `ShopUI`)
- packages-user/client-modules/src/ui/controller.tsx:5(`mainUIController`)
- packages/system/src/ui/controller.ts(`UIController.open`)
@ -203,15 +205,15 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
- `action/hotkey.ts`:`core.openBook(true)` / `core.openToolbox(true)` / `core.openEquipbox(true)` / `core.openQuickShop(true)` → 对应 `*UI` 经 `mainUIController.open(…)`(参数按新契约;原 `true` 的语义按真实接口等价);
- `ui/toolbar.tsx`:两处 `core.openBook(true)` 与 `core.openToolbox(true)` / `core.openEquipbox(true)` / `core.openQuickShop(true)` 同上;`core.ui._drawViewMaps()` → 等价物;
- `ui/viewmap.tsx`:`core.openBook(true)` → `BookUI` + `mainUIController.open`;
- `ui/settings.tsx`:`mainUi.open('settings')` → `mainUIController.open(MainSettingsUI, …)`;`core.ui.closePanel()` → `mainUIController` 等价方法;移除 `import { mainUi } from '@motajs/legacy-ui'`(A-1-6 / A-5-2 / C:ui)。
- `ui/settings.tsx`:`mainUi.open('settings')` → `mainUIController.open(MainSettingsUI, …)`;**`core.ui.closePanel()`(active,`:160`,`ReplayChoice.Start`)→ `mainUIController` 等价方法**;移除 `import { mainUi } from '@motajs/legacy-ui'`(A-1-6 / A-5-2 / C:ui)。**`settings.tsx:621` 的 `core.ui.closePanel()` 位于 `core.clearLocalForage` / `core.removeLocalForage('autoSave')` 存档回调内,依 D-54 为 DEFERRED——本任务不改该调用(存档簇整体延后)。**
(3) **不 invent / 不越界:** 只改本组 ALT UI 打开 / 控制调用;`core.book` 之类不存在的符号不得造;`ui/settings.tsx` 内 `core.restart` / `core.doSL` / `core.startGame` / `core.chooseReplayFile` / `core.download` / `core.encodeRoute`(→09-16)等**本任务不改**;`ui/viewmap.tsx` 内 `core.hasItem` / `core.flyTo`(→09-16 / TBD)不改。
(3) **不 invent / 不越界:** 只改本组 ALT UI 打开 / 控制调用;`core.book` 之类不存在的符号不得造;`ui/settings.tsx` 内 `core.restart` / `core.doSL` / `core.startGame` / `core.chooseReplayFile` / `core.download` / `core.encodeRoute`(→09-16)等**本任务不改**;`ui/settings.tsx:621` 的存档回调内 `core.ui.closePanel()`(DEFERRED,D-54)**不改**;`ui/viewmap.tsx` 内 `core.hasItem` / `core.flyTo`(→09-16 / TBD)不改。
(4) **命名 / 行尾 / 注释:** 预期不新增命名;如必须新增须呈报批准(D-04)。保持 CRLF;不改既有注释 / JSDoc;不使用 `import type`;不新增 `?.`(dev.md 允许场景除外)。
</action>
<reversibility rating="reversible">四文件调用点替换;`git revert` 可回退。</reversibility>
<verify>
<automated>node -e "const fs=require('fs');const bad=[];const code=p=>fs.readFileSync(p,'utf8').split(/\r?\n/).filter(l=>!/^\s*(\/\/|\*|\/\*)/.test(l)).join('\n');const hk=code('packages-user/client-modules/src/action/hotkey.ts');for(const m of ['core.openBook','core.openToolbox','core.openEquipbox','core.openQuickShop']){if(new RegExp(m.replace('.','\\.')+'\\(',).test(hk))bad.push('hotkey.ts still uses '+m)}if(!/BookUI|ToolBoxUI|EquipBoxUI|ShopUI|UIController/.test(hk))bad.push('hotkey.ts: no UI replacement');const tb=code('packages-user/client-modules/src/ui/toolbar.tsx');for(const m of ['core.openBook','core.openToolbox','core.openEquipbox','core.openQuickShop','core.ui']){if(new RegExp(m.replace('.','\\.')).test(tb))bad.push('toolbar.tsx still uses '+m)}const vm=code('packages-user/client-modules/src/ui/viewmap.tsx');if(/core\.openBook\s*\(/.test(vm))bad.push('viewmap.tsx still uses core.openBook');const st=code('packages-user/client-modules/src/ui/settings.tsx');if(/core\.ui\./.test(st))bad.push('settings.tsx still uses core.ui');if(/from '@motajs\/legacy-ui'/.test(st)&&/mainUi/.test(st))bad.push('settings.tsx still imports mainUi');if(bad.length){console.error('UIOPEN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK UI open/control callsites replaced')"</automated>
<automated>node -e "const fs=require('fs');const bad=[];const code=p=>fs.readFileSync(p,'utf8').split(/\r?\n/).filter(l=>!/^\s*(\/\/|\*|\/\*)/.test(l)).join('\n');const hk=code('packages-user/client-modules/src/action/hotkey.ts');for(const m of ['core.openBook','core.openToolbox','core.openEquipbox','core.openQuickShop']){if(new RegExp(m.replace('.','\\.')+'\\(',).test(hk))bad.push('hotkey.ts still uses '+m)}if(!/BookUI|ToolBoxUI|EquipBoxUI|ShopUI|UIController/.test(hk))bad.push('hotkey.ts: no UI replacement');const tb=code('packages-user/client-modules/src/ui/toolbar.tsx');for(const m of ['core.openBook','core.openToolbox','core.openEquipbox','core.openQuickShop','core.ui']){if(new RegExp(m.replace('.','\\.')).test(tb))bad.push('toolbar.tsx still uses '+m)}if(!/mainUIController/.test(tb))bad.push('toolbar.tsx: no mainUIController replacement');if(!/BookUI|ToolBoxUI|EquipBoxUI|ShopUI/.test(tb))bad.push('toolbar.tsx: no *UI replacement');const vm=code('packages-user/client-modules/src/ui/viewmap.tsx');if(/core\.openBook\s*\(/.test(vm))bad.push('viewmap.tsx still uses core.openBook');if(!/BookUI/.test(vm))bad.push('viewmap.tsx: no BookUI replacement');const st=code('packages-user/client-modules/src/ui/settings.tsx');if(/props\.controller\.closeAll\(\);\s*core\.ui\.closePanel\(\)/.test(st))bad.push('settings.tsx: active core.ui.closePanel (ReplayChoice.Start :160) not replaced');const uiOcc=(st.match(/core\.ui\./g)||[]).length;if(uiOcc!==1)bad.push('settings.tsx: expected exactly 1 core.ui. occurrence (the DEFERRED save-callback one), found '+uiOcc);if(!/core\.saves\.autosave\.now\s*=\s*0;[\s\S]{0,80}core\.ui\.closePanel\(\)/.test(st))bad.push('settings.tsx: remaining core.ui.closePanel() is not the DEFERRED save-callback occurrence (:621)');if(!/mainUIController/.test(st))bad.push('settings.tsx: no mainUIController replacement');if(/from '@motajs\/legacy-ui'/.test(st)&&/mainUi/.test(st))bad.push('settings.tsx still imports mainUi');if(bad.length){console.error('UIOPEN GATE FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK UI open/control callsites replaced (settings.tsx:621 deferred save-callback core.ui retained per D-54)')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "UIOPEN GATE FAILED"(残留本组 UI 打开 / 控制 legacy 调用或 `mainUi` 导入,或缺少替代符号)</fails_when>
<automated>node -e "const fs=require('fs');const bad=[];for(const p of ['packages-user/client-modules/src/action/hotkey.ts','packages-user/client-modules/src/ui/toolbar.tsx','packages-user/client-modules/src/ui/viewmap.tsx','packages-user/client-modules/src/ui/settings.tsx']){const b=fs.readFileSync(p,'utf8');if(b.indexOf('\r\n')<0)bad.push('not CRLF: '+p);if(/\bimport\s+type\b/.test(b))bad.push('import type: '+p)}if(bad.length){console.error('CRLF/IMPORT FAILED :: '+bad.join(' ; '));process.exit(1)}console.log('OK files CRLF and no import type')"</automated>
<fails_when>non-zero exit, 或 stderr 含 "CRLF/IMPORT FAILED"(非 CRLF 或出现 `import type`)</fails_when>
@ -219,12 +221,12 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
<fails_when>输出包含本计划 `files_modified` 之外、且非用户既有并发改动(`packages-user/data-state/**`)的生产文件改动</fails_when>
</verify>
<acceptance_criteria>
- 7 条 UI ALT:`hotkey.ts` / `toolbar.tsx` / `viewmap.tsx` / `settings.tsx` 中 `core.open*` / `core.ui` / `mainUi` 改为 `*UI` / `mainUIController` / `UIController.open`;无残留本组 legacy 调用(除 STOP 分支记录者)
- 7 条 UI ALT:`hotkey.ts` / `toolbar.tsx` / `viewmap.tsx` / `settings.tsx` 中 active 的 `core.open*` / `core.ui`(`settings.tsx:160`、`toolbar.tsx:198`)/ `mainUi` 改为 `*UI` / `mainUIController` / `UIController.open`;**`settings.tsx:621` 的 `core.ui.closePanel()`(存档回调内)依 D-54 保持 DEFERRED、允许保留**;无其它残留本组 legacy 调用(除 STOP 分支记录者)
- R2 结论写入 SUMMARY;无法等价的条目记录并 STOP,未猜测
- 未触碰非 ALT 用法;未引入 `client` 单例;未新增未批准命名
- 被改文件 CRLF、无 `import type`;未改 `files_modified` 外文件(用户并发改动除外)
</acceptance_criteria>
<done>UI 打开 / 控制 7 条替换到位(或记录 STOP);UIOPEN / CRLF / scope 门禁全绿;本组 active 7 条全部落地(存档簇 7 条保持 DEFERRED,D-54)</done>
<done>UI 打开 / 控制 7 条替换到位(或记录 STOP);UIOPEN / CRLF / scope 门禁全绿;本组 active 7 条全部落地;`settings.tsx:621` 存档回调内的 `core.ui` 保持 DEFERRED(D-54);存档簇 7 条保持 DEFERRED(D-54)</done>
</task>
</tasks>
@ -235,7 +237,7 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
| Boundary | Description |
|----------|-------------|
| 范围边界 | 只改 ALT 条目相关用法;不触碰 DIRECT / ADAPT / NONE / TBD(D-53) |
| 存档边界 | **存档簇已 DEFERRED(D-54):不得修改 `render/utils/saves.ts` / `ui/save.tsx` / `ui/settings.tsx` 的 `C:clearLocalForage` 用法** |
| 存档边界 | **存档簇已 DEFERRED(D-54):不得修改 `render/utils/saves.ts` / `ui/save.tsx` / `ui/settings.tsx` 的 `C:clearLocalForage` 用法(`:573`)及存档回调内的 `core.ui.closePanel()`(`:621`)** |
| 替代依据边界 | 替代方案只取自审计;结构不等价处记录并 STOP,**不猜**(D-52 / R2) |
| 单例边界 | 不得引入 `client` 单例(D-11) |
| 命名边界 | 未经批准不得新增命名(D-04) |
@ -249,14 +251,14 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
| T-09-15-02 | Elevation of Privilege | 为接入而引入 `client` 单例 / 使用未批准命名 | high | mitigate | D-11 / D-04;prohibitions |
| T-09-15-03 | Tampering | 越界改动非 ALT 用法(`core.doSL` / `core.status` 等) | high | mitigate | 范围门禁 + `<action>` 明列不改项 + `git status` 守卫 |
| T-09-15-04 | Tampering | **将已 DEFERRED 的存档簇当作 active 任务执行(改 `saves.ts` / `save.tsx`)** | high | mitigate | prohibitions 明列存档簇 DEFERRED(D-54);`files_modified` 不含 `saves.ts` / `save.tsx`;`git status` 守卫 |
| T-09-15-05 | Repudiation | 保留 `core.open*` / `core.ui` 却视作完成 | medium | mitigate | UIOPEN 门禁强制 legacy 用法消失 |
| T-09-15-05 | Repudiation | 保留 active 的 `core.open*` / `core.ui` 却视作完成 | medium | mitigate | UIOPEN 门禁强制 active legacy 用法消失(`settings.tsx:621` 存档回调内 `core.ui` 依 D-54 豁免) |
| T-09-15-06 | Information Disclosure | 把用户并发改动(`data-state/**`)误纳入提交 | medium | mitigate | `git status` 守卫豁免既有并发改动 |
| T-09-15-07 | Repudiation | 未跑静态门禁即提交 | medium | mitigate | Task 1 有自动化门禁 + SUMMARY |
| T-09-15-SC | Tampering | npm/pip/cargo installs | low | accept | 无第三方包安装 |
</threat_model>
<verification>
- **Task 1**:`hotkey.ts` / `toolbar.tsx` / `viewmap.tsx` 无 `core.open*` 且有 `*UI` / `UIController`;`settings.tsx` / `toolbar.tsx` 无 `core.ui` 且有 `mainUIController`;无 `mainUi` 导入;R2 结论写入 SUMMARY;CRLF / import type 检查;范围门禁。
- **Task 1**:`hotkey.ts` / `toolbar.tsx` / `viewmap.tsx` 无 `core.open*` 且有 `*UI` / `UIController`(正向);`settings.tsx`(`:160`)/ `toolbar.tsx`(`:198`)的 active `core.ui` 替换为 `mainUIController`(正向);**`settings.tsx:621`(存档回调内 `core.ui.closePanel()`)依 D-54 保持 DEFERRED——UIOPEN 门禁仅校验 active 区,允许该 deferred 出现保留**;无 `mainUi` 导入;R2 结论写入 SUMMARY;CRLF / import type 检查;范围门禁。
- **存档簇(DEFERRED,D-54)**:`render/utils/saves.ts` / `ui/save.tsx` **不在本计划范围**,不修改;`ui/settings.tsx` 的 `C:clearLocalForage` 用法不改。
- **R2 触发时**:记录并 STOP,相应条目标记「待裁定」。
- **无全仓门禁**:不使用 `pnpm check:type` / `pnpm build` / 全仓 TS 诊断数。
@ -265,7 +267,7 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
<success_criteria>
- 本组 active 7 条 ALT:UI 打开 / 控制(→ `*UI` / `mainUIController` / `UIController.open`)按审计落地。
- 存档簇 7 条保持 **DEFERRED(D-54)**,本计划未触碰 `render/utils/saves.ts` / `ui/save.tsx`。
- 存档簇 7 条保持 **DEFERRED(D-54)**,本计划未触碰 `render/utils/saves.ts` / `ui/save.tsx`;`ui/settings.tsx:621` 的 `core.ui.closePanel()`(存档回调内)亦保持 DEFERRED,未触碰。
- R2 若成立,**如实记录并 STOP**,不猜测、不保留 legacy 用法冒充完成。
- 零越界:不触碰其它分类、不触碰存档簇、不引入 `client` 单例、不新增未批准命名。
- 静态门禁(UIOPEN / CRLF / 范围)全绿(或按 STOP 分支记录)。
@ -275,7 +277,7 @@ Phase 9 · 任务 7 · 第一批改造(ALT 条目)· **第 3 组 / 共 4 组
| 符号 / 路径 | 类型 | 说明 |
|---|---|---|
| `packages-user/client-modules/src/ui/settings.tsx`(修改) | 源码 | `mainUi` / `mainUi.open` → `mainUIController` / `UIController.open`;`core.ui` → `mainUIController` |
| `packages-user/client-modules/src/ui/settings.tsx`(修改) | 源码 | `mainUi` / `mainUi.open` → `mainUIController` / `UIController.open`;`core.ui`(active `:160`)→ `mainUIController`;`core.ui`(`:621`,存档回调内)DEFERRED(D-54) |
| `packages-user/client-modules/src/action/hotkey.ts`(修改) | 源码 | `core.open*` → `*UI` + `UIController.open` |
| `packages-user/client-modules/src/ui/toolbar.tsx`(修改) | 源码 | `core.open*` / `core.ui` → `*UI` / `mainUIController` |
| `packages-user/client-modules/src/ui/viewmap.tsx`(修改) | 源码 | `core.openBook` → `BookUI` |