新增 Verification-Checklist 功能验证清单 + Phase 1-2 验证步骤

按用户指示:功能验证写到专门的文档用来说明指导,等功能开发完毕统一
验证,不在 Phase 进行中频繁跑(避免反复重装 app 浪费时间)。

新增 docs/Verification-Checklist.md:
  - 文档定位:开发期累积 / 统一验收的操作手册,与 Contract §10
    契约边界保持独立
  - 通用准备:环境工具(Xcode console / Safari Web Inspector / 模拟器
    Features 菜单)+ 重装 app 清 launch snapshot + xcrun simctl
    get_app_container 沙盒访问
  - Phase 1 章节(已实现):
    A. 启动图 + LaunchScreen 方向(3 项)
    B. 启动流水线时序(含 Xcode console 期望输出 + 黑边 + 总耗时)
    C. app_*.js 落盘 + 命名(含 cat 命令)— 6 项硬约束
    D. window.app_* H5 console 对账(15 项变量 + 撤销变量验证)
  - Phase 2 章节(已实现):
    E. H5→Native handler 响应(13 项验证步骤)
    F. Native→H5 反向 callback(5 项触发场景含模拟器手动操作)
    G. ExternalSubscriptions 生命周期(Phase 6 才能完整验证)
  - Phase 3-10 占位章节:实施后陆续填充
  - 附录:与 Contract §10 26 项验收清单的章节映射表
  - 维护规则:每完成 Phase 子项补勾、不复制到 Contract §10、bug 修复
    时在 commit message 引用本文档 checkbox 编号

CLAUDE.md 文档索引表新增一行指向 Verification-Checklist.md。

Plan §6.4 里程碑加 3 行(前面遗漏的 4986f36 / 09f071e + 本次 commit)。

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-22 19:08:32 +08:00
co-authored by Claude Opus 4.7
parent 09f071e38f
commit b2ee1b38eb
3 changed files with 219 additions and 1 deletions
+1
View File
@@ -117,6 +117,7 @@ daoqi 仓库当前并存两条工作线:
| `docs/H5-Native-Contract.md` | H5 ↔ 原生 桥接**契约**(黑盒可观察行为) | 涉及桥接接口必读 |
| `docs/H5-Native-Implementation-Design.md` | 新外壳实施蓝图(架构、模块、代码骨架) | 实现新外壳必读 |
| `docs/Development-Plan.md` | 开发执行计划(Phase 0–10、依赖图、进度追踪) | 排期 / 进度更新必读 |
| `docs/Verification-Checklist.md` | 功能验证清单(开发期累积、统一验收的操作手册) | 每完成 Phase 子项时补充 / 勾选;不要在 Phase 进行中频繁跑,等多个 Phase 累积后统一验证 |
| `../daoqi/`(仓库同级目录) | 原项目 msext 工作参照实现 | 遇到 iOS 行为差异 / 渠道注入 / 桥接 / SDK 初始化等不确定问题时,**优先参考此处**而非凭推测,详见下文「原项目 daoqi:遇到问题时的参考来源」 |
---
+3 -1
View File
@@ -776,7 +776,9 @@ H5 调 `OpenurlTitleData` 打开弹层 WebView,弹层内 H5 用 `window.settin
| 2026-06-22 | **AppDataWriter 代码落地**:新增 `Source/WebView/AppDataWriter.swift` + `Source/Resource/NetworkMonitor.swift`WebContainer.runBootPipelineSteps 在 step 5 后、step 6 loadFileURL 前调 `writeAppDataFiles(resolved:)` 写 4 个 app_*.js 文件 + 挂 battery/network 变化重写监听。BuildProject 通过 | `829cc8f` |
| 2026-06-22 | **`app_appversion` / `app_gameconfig` 业务语义纠正**grep `daoqi/msext/NewRootVC.m:1190-1208` 发现 `app_appversion` 不是版本号、是审核切换标志(`'0'` 正常 / `'1'` 审核期);`app_gameconfig` 也跟着 result 在 `BundleConfig.gameConfig` / `appleConfig` 切换。AppDataWriter.writeAppData 加 result 计算逻辑;Contract §4.2 + Design §7.5.1 同步纠正;Contract §4.2 修订记录加一行 | `0a313af` |
| 2026-06-22 | **Phase 2.A 8 个大厅 handler 落地** + **Phase 2.B 5 项反向 callback 落地** + BridgeData 加 nonisolated 解 Swift 6 actor 隔离 | `d38ff0f` + `23b3a1f` |
| 2026-06-22 | **业务期 `app_getbattery` / `app_getnetwork` 改为 evaluateJavaScript 重新赋值(不重写文件)**:启动期保持写文件,业务期变化时改为 `webView.evaluateJavaScript("window.app_xxx=N;")` 直接重新赋值(H5 已加载完 `<script src>` 不会再 fetch,重写文件无效)。与 §3.2 反向 callback 双轨同时执行。Design §7.5.2 / §7.5.4 + Contract §4.2 同步 | 本次 commit(用户指示) |
| 2026-06-22 | **业务期 `app_getbattery` / `app_getnetwork` 改为 evaluateJavaScript 重新赋值(不重写文件)**:启动期保持写文件,业务期变化时改为 `webView.evaluateJavaScript("window.app_xxx=N;")` 直接重新赋值(H5 已加载完 `<script src>` 不会再 fetch,重写文件无效)。与 §3.2 反向 callback 双轨同时执行。Design §7.5.2 / §7.5.4 + Contract §4.2 同步 | `4986f36` |
| 2026-06-22 | **Phase 2.C ExternalSubscriptions 生命周期管理**setup/teardown 配对挂 viewWillAppear/viewWillDisappearPhase 6 子游戏 push 时防双发 | `09f071e` |
| 2026-06-22 | 新增 `docs/Verification-Checklist.md` 功能验证清单:累积各 Phase 验证步骤、不在 Phase 进行中频繁验证、完成后统一跑。与 Contract §10 契约边界保持独立 | 本次 commit |
> **§3.4.1 撤销说明**:早期 commit `82bad8a` 实现的 `window.settings.getXxx()` polyfill 已撤销 — Contract §附录 A 自身明示「iOS<9 路径,新外壳如最低系统 ≥ iOS 14 可不实现」,本项目最低 iOS 15.6 → polyfill 路径未启用,H5 用 §7.5 的 `app_*.js` 文件路径作为唯一主路径。详细缘由见 Design §3.4.1(撤销说明)和 CLAUDE.md「典型案例」段。
+215
View File
@@ -0,0 +1,215 @@
# 功能验证清单(开发期累积,统一验收)
> **定位**:各 Phase 实施期间陆续累积的验证项目,本文档**不是契约**(契约在 `H5-Native-Contract.md` §10),而是面向开发者的**指导步骤**——告诉验证者"应该做什么操作、看到什么现象、用什么工具"。
>
> **使用方式**
> - 每完成一个 Phase 子项 → 把对应验证步骤勾掉
> - **不要在 Phase 进行中频繁手动验证**,避免反复重装 app 浪费时间
> - 多个 Phase 累积后**统一跑一次**完整验证清单
> - 与 Contract §10 契约验收清单**并行**Contract §10 是黑盒契约边界("必须长这样"),本文档是**怎么验证它长这样**的操作手册
---
## 通用准备
### 环境
| 工具 | 用途 | 启用方式 |
|------|------|---------|
| Xcode console | 看原生 os.log 输出 | Xcode 底部输出窗口 |
| Safari Web Inspector | 看 H5 端 JS console / DOM / Network | macOS Safari → 开发菜单 → Simulator / 真机 → 当前 H5 页面 |
| 模拟器 Features 菜单 | 切换 Battery State / Network Link Conditioner / Toggle In-Call Status | Simulator → Features |
| 真机 Safari 调试 | 真机 H5 网页检查器 | iPhone → 设置 → Safari → 高级 → 网页检查器 = ON |
### 重装 app(每次跑完整验证前都做一遍)
按 CLAUDE.md「typical case 1launch snapshot 缓存粘滞」教训:
- 模拟器/真机:**长按 app → 删除** → Xcode 重新 Run
- 或:`xcrun simctl uninstall booted com.skyapp.ylgamehall` 然后 Xcode Run
- 这样可以清掉 iOS sandbox 内的 launch snapshot 缓存,避免改了 Info.plist / LaunchScreen 后还是看到旧版本
### 沙盒访问
```bash
xcrun simctl get_app_container booted com.skyapp.ylgamehall data
# 输出形如 /Users/.../Containers/Data/Application/<UUID>/
```
---
## Phase 1 — 启动闭环
### A. 启动图 + LaunchScreen
- [ ] **A.1 启动图方向正确**:横屏设备启动瞬间看到完整 logo + "快乐游戏 / 健康生活" 文字,画面正向(不是横躺)
- [ ] **A.2 状态栏方向**:电池/时间在画面顶部居中横向,**不在**画面左侧竖向(竖向 = iOS 错误地按 portrait 渲染 LaunchScreen,参 CLAUDE.md「典型案例 1」)
- [ ] **A.3 黑边合理**:比 16:9 更宽的 iPhone(如 16 Pro)启动图两侧补黑边可接受;iPad 4:3 上下补黑边可接受
### B. 启动流水线时序
预期 Xcode console 顺序:
```
[BundleConfig] 渠道注入读取结果(11 项)
[SandboxPaths] 路径计算结果
[ResourceUnzipper] ensureReady 完成
[RemoteConfigClient] 拉取成功 .parsed
[VersionResolver] chulishengji 双子树合并结果
[LocalVersionReader] 本地 vs 远端对比
[LobbyZipUpgrader] 无需升级 / 升级完成 N → M
[lobby] write app_data.js → ... ← Phase 1.7 落地(AppDataWriter
[lobby] write app_gamesname.js → ...
[lobby] write app_battery.js: app_getbattery=0.XX → ...
[lobby] write app_network.js: app_getnetwork=2 → ...
```
- [ ] **B.1 启动一气呵成**splash 显示 → "拉取配置中" → ("下载更新中 XX%") → "加载大厅..." → splash 0.3s 淡出,看到 H5 大厅
- [ ] **B.2 16:9 letterbox 黑边**:H5 大厅在屏幕中间,左右(或上下)有黑边,画面比例 16:9
- [ ] **B.3 启动总耗时 < 2s**(无升级路径),有升级时进度条实时更新
### C. `app_*.js` 文件落盘 + 全局变量命名
```bash
# 进入沙盒看 4 个文件
xcrun simctl get_app_container booted com.skyapp.ylgamehall data
# 然后 cat 4 个文件
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_data.js
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_battery.js
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_network.js
cat <容器>/Library/Caches/<gamedir>/<gamestart>/app_gamesname.js
```
- [ ] **C.1 4 个文件全部存在**且内容非空
- [ ] **C.2 app_data.js 内 12 个 var 命名 100% 正确**(特别注意大小写:`app_Launchtype` L 大写、`app_getwifisignalLevel` wifi 小写 + signal/Level 区分)
- [ ] **C.3 app_battery.js 变量名带 get**`var app_getbattery=N;`(不是 `app_battery`
- [ ] **C.4 app_network.js 变量名带 get**`var app_getnetwork=N;`
- [ ] **C.5 app_gamesname.js 沿用 msext 字面**`var app_gamesname=new Array('xxx');`var 后两个空格 + new Array(...)
- [ ] **C.6 不存在已撤销变量**`app_data.js` 内没有 `app_gameid` / `app_compareCode`(早期 Contract 误列,原 msext 不写)
### D. `window.app_*` H5 console 对账
Safari Web Inspector → Console
```js
console.log("app_gameconfig =", app_gameconfig); // 期望 ChannelConfig.plist gameconfig 实际值
console.log("app_market =", app_market); // 期望 "2"
console.log("app_agent =", app_agent); // 期望 ChannelConfig.plist agent 实际值
console.log("app_channel =", app_channel);
console.log("app_Launchtype =", app_Launchtype); // 大厅期望 0
console.log("app_appversion =", app_appversion); // 期望 '0' 字符串(审核切换标志非版本号)
console.log("app_getbattery =", app_getbattery); // 期望 0.XX
console.log("app_getnetwork =", app_getnetwork); // 期望 1/2/3
console.log("app_gamesname =", app_gamesname); // 期望 Array
console.log("app_gameid =", app_gameid); // 期望 undefined ✓
console.log("app_compareCode =", app_compareCode); // 期望 undefined ✓
```
- [ ] **D.1 所有 15 个 app_* 全局变量都 `!== undefined`**
- [ ] **D.2 关键字段都是 ChannelConfig.plist 的实际值**(不是 H5 zip 默认占位)
- [ ] **D.3 `app_appversion === '0'`**(审核标志,正常业务);`app_gameconfig` 取 BundleConfig.gameConfig
---
## Phase 2 — 大厅 handler + 反向 callback
### E. H5 → Native handler 响应
每项的验证方式:在 H5 console 调对应 `bridge.callHandler('xxx', data, function(resp){console.log(resp);})`,看 Xcode console 有无副作用 + H5 收到 cb。
- [ ] **E.1 vibrator** → 真机震动 + H5 cb "vibrator"
- [ ] **E.2 repeatvibrator** → 真机震动 + cb "repeatvibrator"
- [ ] **E.3 canclevibrator** → cb "canclevibrator"(无可感知副作用,正常)
- [ ] **E.4 gameCopytext('test123')** → 系统剪贴板含 "test123";其他 app 粘贴可验证;iOS 14+ 会出"已粘贴自 ylgamehall" banner(平台行为非 bug
- [ ] **E.5 gamepastetext** → cb 是当前剪贴板内容(不是字面 "gamepastetext"
- [ ] **E.6 SwitchShake(1) + SwitchShake(0)** → 内部 canVoice 状态切换(无可视化副作用,cb "SwitchShake"
- [ ] **E.7 startshake → 摇一摇手机 → H5 收到 `shakeEnd` 反向 callback**`bridge.registerHandler('shakeEnd', fn)` 先注册)
- [ ] **E.8 stopshake → 摇一摇 → H5 不再收到 shakeEnd**
- [ ] **E.9 voicePlaying(1) / voicePlaying(0)** → 内部 actor VoiceCenter 开关位(cb "Response from voicePlaying");mediaTypeAudio 实现后效果可验证
- [ ] **E.10 getphoneInfo** → cb "getphoneInfo"**大写 I**+ 反向 callback `getphoneinfo`**小写 i**)含 6 字段(PhoneAdresseMAC / PhoneDeviceBrand / PhoneIMEI / PhoneModel / PhoneProvidersName / PhoneVersion
- [ ] **E.11 browser('https://www.apple.com')** → Safari 打开苹果首页
- [ ] **E.12 opensaoma** → cb "opensaoma"(空实现,无可感知副作用,符合契约)
- [ ] **E.13 startlocation** → cb "startlocation"Phase 2 stub,无定位真实业务;Phase 5 升级后再验真实定位)
### F. Native → H5 反向 callback
H5 需先用 `bridge.registerHandler('callbackName', fn)` 注册:
- [ ] **F.1 模拟器 Features → Battery State 切换** → H5 收到 `getBattery` "0.XX" + `window.app_getbattery` 实时跟随
- [ ] **F.2 模拟器 Features → Network Link Conditioner 切换 100% Loss** → H5 收到 `getnetwork` "1" + `window.app_getnetwork === 1`;切回 WiFi → 收 "2";切 3G → 收 "3"
- [ ] **F.3 App 切后台**(Home 键 / 上滑)→ H5 收到 `appservice` "1";回前台 → 收 "2"
- [ ] **F.4 摇一摇** → shakeEnd(见 E.7
- [ ] **F.5 getphoneInfo 触发 getphoneinfo**(见 E.10
### G. ExternalSubscriptions 生命周期
> Phase 2.C 已实现 setup/teardown 配对,但 Phase 6 子游戏容器才能完整验证"防双发"。当前只验证基本生命周期。
- [ ] **G.1 viewWillAppear setup**app 启动后 onChange 闭包都已挂钩(F.1-F.4 都生效)
- [ ] **G.2 viewWillDisappear teardown**:手动触发场景待 Phase 6 子游戏 push 上来时验证
---
## Phase 3 — 音频体系(待实现)
> Phase 3 实施后填充。预备验证项:
> - srcIsloop 背景音循环 / 单次按钮音 / 停止
> - mediaTypeAudio 远端语音回放 + gameui_play_voice / gameui_stop_voice
> - prepareaudio 录音 + AMR 转码 + 七牛上传 + getaudiourl
---
## Phase 4 — 分享 + 登录(待实现)
> Phase 4 实施后填充。预备验证项:
> - accreditlogin 微信授权 + sharelogin 7 字段(含大写 Province
> - friendsSharetypeUrlToptitleDescript 分享微信 / 朋友圈 + sharesuccess
---
## Phase 5 — 定位(待实现)
> Phase 5 实施后填充。预备验证项:
> - startlocation 一次性 + 持续 + getlocationinfo 9 字段(含小写 province
---
## Phase 6 — 子游戏(待实现)
> Phase 6 实施后填充。预备验证项:
> - SwitchOverGameData push 子游戏
> - 子游戏 backgameData 回大厅 + getWebdata
> - 子游戏 push 上来后大厅 ExternalSubscriptions teardown(验证不会双发)
---
## Phase 7-10(待实现)
> 各 Phase 实施后陆续填充。
---
## 附:契约 §10 26 项验收清单映射
本文档每个章节对应 Contract §10 的 A/B/C/D/E 五节:
| 本文档章节 | Contract §10 节 | 状态 |
|---|---|---|
| A. 启动图 + LaunchScreen | §A 启动(启动相关) | Phase 1 实现 |
| B. 启动流水线时序 | §A 启动 | Phase 1 实现 |
| C. app_*.js 落盘 + 命名 | §A 启动 | Phase 1 实现 |
| D. window.app_* H5 console | §A 启动 + §B 桥接 | Phase 1 实现 |
| E. H5→Native handler | §B 桥接 | Phase 2 实现 |
| F. 反向 callback | §C 系统事件 + §B 桥接 | Phase 2 实现 |
| G. 生命周期 | Design §2.4.2 内部约束) | Phase 2 实现 |
| 音频 / 录音 / 远端语音 | §B 桥接(音频相关) | Phase 3 待实现 |
| 分享 / 登录 | §B 桥接 + §E 回归 | Phase 4 待实现 |
| 定位 | §B 桥接 | Phase 5 待实现 |
| 子游戏 / 视频房间 | §B + §D 视频房间 | Phase 6 + 8 待实现 |
---
## 维护规则
- 每完成一个 Phase 子项 → 在对应章节补 / 勾验证步骤
- **不要把验证步骤复制到 Contract §10**Contract §10 是契约边界("必须长这样"),本文档是验证操作手册("怎么验证它长这样"),两份保持独立
- 验证发现的 bug → 修复后 commit 时在 message 引用本文档对应 checkbox 编号(如 "修 E.10 getphoneInfo 大小写错"