 joywayerandClaude Opus 4.7
|
f4307ccc80
|
WebView 改全屏铺满(与 daoqi msext 同款),撤销 16:9 letterbox
用户反馈:现代 iPhone 横屏(19.5:9 比例)下 16:9 letterbox 出现明显
左右大黑边,与原项目视觉不一致。
调研 daoqi msext NewRootVC.m:256:实际用 CGRectMake(0, 0, DEVW, DEVH)
直接全屏 WKWebView,没有任何 letterbox。H5 viewport 自身处理设计稿
(1280×720)→ 设备实际宽高的伸缩。msext 上线多年用户已习惯此视觉。
改 WebContainerViewController + SubGameViewController 的 setupBridgedWebView:
- 去掉 16:9 aspect 约束 + 居中 + width/height max/fill 优先级方案
- 改为 top/bottom/leading/trailing 四 edge 直接撑满 view
文件头注释同步:从「16:9 letterbox」改为「全屏铺满」。
原 Design §6.3.5 的 letterbox 决策已被现网实测推翻,未来如某些 H5
页面需要恢复 letterbox 再单独抽 LetterboxLayout(YAGNI)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 07:23:34 +08:00 |
|
 joywayerandClaude Opus 4.7
|
4ca4459ad5
|
启动期错误态精确分类:NWPath 区分真断网/权限拒,按钮语义动态切换
用户反馈:之前的文案统一映射 NSURLError code,没区分"真没网"和"权限
被拒"——两类 iOS 14+ 上都报 -1009,但 NWPath.unsatisfiedReason 里有
wifiDenied / cellularDenied 标记可精确区分。点击重试时也没有针对权限
被拒场景做特殊处理(重发请求被同样的权限决定拒绝,原地循环)。
改动:
1. **NWPath snapshot 精确分类**
- 新增 BootErrorKind: noNetworkAccess / wifiDenied / cellularDenied /
networkTimeout / networkCannotReach / localResourceMissing / unknown
- 新增 currentPathSnapshot() async:临时 NWPathMonitor 等首次 update
拿当前 path
- classifyBootError 接 path 参数:先看 NWPath.unsatisfiedReason
(iOS 14.2+)区分权限拒/真断网;fallback 才按 NSURLError code
- runBootPipeline 的 catch 内 await snapshot,传入精确 kind
2. **按钮语义按 kind 切换**(同一个按钮承担双语义,UX 一致)
- 权限拒(wifiDenied / cellularDenied)→ 按钮显示"前往设置",
点击跳 UIApplication.openSettingsURLString
- 其他(noNetworkAccess / timeout / unreachable / localResource)→
按钮显示"重试",点击重跑 runBootPipeline
- SplashOverlay.showError 加 actionTitle 参数,通过 UIButton.Configuration
动态改 title
3. **didBecomeActive 自动重试**(行业最佳做法)
- 权限拒分支跳设置时挂 UIApplication.didBecomeActiveNotification 监听
- 用户在设置改完权限切回 app → 自动 runBootPipeline → 进入大厅
无需用户再点任何按钮
- 一次性 observer:触发后自动取消,避免重复
- runBootPipeline 入口同时 stop NWPathMonitor + stop settingsReturnObserver
保证多次重试干净
文案精确化:
- noNetworkAccess:"当前未联网 / 请检查 Wi-Fi 或蜂窝数据后重试"
- wifiDenied:"未授权使用无线局域网 / 请前往 iOS 设置 → 本应用 → 打开
「无线数据」,授权后将自动重试"
- cellularDenied:"未授权使用蜂窝数据 / 请前往 iOS 设置 → 本应用 → 打开
「无线数据」,或连接 Wi-Fi 后重试"
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 07:19:50 +08:00 |
|
 joywayerandClaude Opus 4.7
|
0e446df97a
|
splash 进度条配色:systemBlue 填充 + .label 15% alpha 轨道
跟错误态"重试"主按钮 systemBlue 视觉一致;轨道用 .label 系统语义色
的 15% alpha,浅深模式自适应(浅模式淡灰、深模式淡白),在浅色启动图
上比默认 UIProgressView 灰条更显眼。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 07:13:13 +08:00 |
|
 joywayerandClaude Opus 4.7
|
34f76e8824
|
启动期错误态 UX 调整:浅背景深色字,仅保留"重试"按钮
按用户反馈精简错误态 UI:
- splash 启动图是浅色背景(米白底 + logo + 蓝色文字),白色文字在上面
看不清。错误态显示时改为 backgroundColor = .systemBackground(浅模式
白、深模式黑),同时隐藏启动图 imageView,文字用 .label / .secondaryLabel
系统语义色确保两种 appearance 下都对比清晰
- 去掉"前往系统设置"次按钮:仅保留蓝色填充主按钮"重试"
- "无法连接服务器"文案删去"或前往系统设置"尾句
"权限弹窗"诉求的实际实现:
重试按钮直接重跑 runBootPipeline → RemoteConfigClient.fetch →
URLSession.dataTask 重新发起请求。这正是 Apple 推荐的"让网络请求
自然触发权限弹窗"模式:
- 首次启动若系统网络权限弹窗未响应/被拒,重新发请求会再次触发系统
对权限状态的评估,可能复弹
- 用户在 iOS 隐私管理里改了权限后回 app,点重试就能成功
- NWPathMonitor 也在静默监听,切到正常网络后无需点击即自动重试
不需要额外的"手动申请权限" API 代码(iOS 没有公开的 API 强制弹起
被拒绝的网络权限弹窗);现有「重新发请求」即是教科书做法。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 07:10:23 +08:00 |
|
 joywayerandClaude Opus 4.7
|
5731588d33
|
启动期错误处理 v2:撤回降级,splash 错误态 + NWPathMonitor 自动重试
撤回上一 commit 74f6925 的"远端 config 失败就降级进大厅"路径——错过升级
判定 = 用户用旧 IPA 不被强制升级 = 业务风险。新策略:
**远端 config 失败 → 停留 splash 显示错误态**(不弹 modal alert / 不暴露原始 NSError)
行业主流方案:
1. SplashOverlay 扩展错误态 UI(隐藏 label / progress,显示 title +
message + "重试" 主按钮 + "前往系统设置" 次按钮),通过 onRetry /
onOpenSettings closure 与 controller 解耦
2. WebContainerViewController.presentBootError 按 BootErrorKind 分类给出
友好文案(networkOffline / networkTimeout / networkCannotReach /
localResourceMissing / unknown)
3. 启动期独立 NWPathMonitor,仅在"从无网变有网"时静默自动重试一次
(初始 nil 状态不触发,避免死循环;用户切到 Wi-Fi 后无需手动操作)
4. runBootPipeline 重新进入时 stopBootRetryWaiting + splash.hideError
保证多次重试干净(防止 monitor 泄漏)
进大厅的必要条件(恢复严格):
- ResourceUnzipper.ensureReady 成功(本地 H5 zip 就位)
- RemoteConfigClient.fetch 拿到正确 config(升级判定可执行)
- 本地 appVersion >= 远端(否则走 IPA 升级 modal alert)
- showmessage 为空(否则走运营 modal alert)
- LobbyZipUpgrader 完成(含跳过升级 / 真升级两路径)
业务流程信号(operationalMessage / ipaUpgradeRequired)继续用 modal alert
(这两类不是错误,是产品决策的弹窗永停);技术错误(网络/解压/写文件等)
统一进 splash 错误态 + 自动重试。
raw error 仍 print 到 Xcode console(含 NSURLErrorDomain code 等开发期
排查信息),用户视角永不暴露技术细节。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 06:47:04 +08:00 |
|
 joywayerandClaude Opus 4.7
|
74f6925be3
|
启动期错误处理 UX 改造:优雅降级 + 友好文案,不暴露技术细节
修复用户反馈的体验问题:网络权限未同意 / 长期不响应权限弹窗时,
启动会弹出"启动失败" + 原始 NSError("Error Domain=NSURLErrorDomain
Code=-1009 ...大量技术细节..."),用户被吓懵。
按行业主流做法(参考微信/支付宝/网易云音乐启动期错误处理)改两件事:
1. **优雅降级:远端 config 失败 ≠ 启动失败**
runBootPipelineSteps 把"拉远端 config + 升级判定"整段包 do/catch:
- 业务流程信号(operationalMessage / ipaUpgradeRequired)正常抛出弹窗
- 网络 / 解析 / H5 zip 下载任何失败 → 用 LocalVersionReader 构造
fallback ResolvedVersion,跳过升级判定,直接加载本地 H5 大厅
- splash 文案区分在线 "加载大厅..." / 离线 "离线加载..."
- raw error 仍 print 到 Xcode console 便于排查
理论依据:ResourceUnzipper.ensureReady 之后本地 H5 zip 已就绪,
远端 config 仅用于升级判定,失败时本地大厅完全可用。
2. **友好文案 + 多按钮 alert(showRetryableAlert)**
- 新增 BootErrorKind 分类:剥洋葱看 NSURLError code 映射到
networkOffline (-1009) / networkTimeout (-1001) / networkCannotReach
(-1003/-1004/-1005/-1009/-1018/-1019/-1020) / localResourceMissing /
unknown
- 每类配友好标题 + 正文("网络未连接"/"网络较慢"/"暂时无法连接服务器"
而非 raw "Error Domain=...")
- 按钮组合按类别:网络类含"去设置"(跳 UIApplication.openSettingsURLString)
+ 重试 + 稍后再说;本地资源类只重试 + 稍后再说
- "重试" 不强制("稍后再说" 让用户自由退出,避免单按钮卡死)
结果:用户在断网 / 未授权 Wi-Fi / 测试域名不可达等场景下,splash 多停
约 7s(3 次重试 + 退避)后自动降级进入大厅,**不弹任何 alert**。仅当
本地资源也失效(极罕见)时才弹友好提示,永不暴露原始 NSError。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 03:12:33 +08:00 |
|
 joywayerandClaude Opus 4.7
|
323fb9074b
|
修复 AMap 真机链接错:补 3 个系统 framework + 永久文档化
真机首次跑 ylgamehall 时 linker 报 7 个 Undefined symbol:
- _CNCopyCurrentNetworkInfo / _CNCopySupportedInterfaces / _SCNetworkReachability*
→ SystemConfiguration.framework
- _OBJC_CLASS_$_CTTelephonyNetworkInfo → CoreTelephony.framework
- _OBJC_CLASS_$_EAAccessoryManager → ExternalAccessory.framework
根因:AMap 是 fat 静态库(`Vendor/AMap/*.framework`),它内部引用的系统 API
不会被自动带入 transitive 依赖,必须项目方手动 link。CocoaPods 时代由
podspec 自动声明(daoqi msext 走 Pods 故未在 pbxproj 显式 link
ExternalAccessory);本项目走 Vendor 手动接入路线,必须显式补齐。
工程:pbxproj 加 3 个 PBXBuildFile + PBXFileReference(Xcode UI 操作自动写入)
文档(永久记录避免重蹈):
- Vendor/AMap/README.md「工程接入步骤」第 5 步补齐 3 个系统 framework
+ 列出每个 framework 对应的 symbol
- Vendor/AMap/README.md 末尾加「如果未来真机链接报 C++ symbol 缺失」
备用方案(加 libc++.tbd,daoqi 已加;当前 AMap 2.12.0 未触发)
- docs/SDK-Integration-Guide.md §A.2 第 5 步同步
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 02:50:29 +08:00 |
|
 joywayerandClaude Opus 4.7
|
e6bcb32820
|
Verification-Checklist:覆盖 Phase 3.6 / 4.B-F / 5 / 6 / 7 / 8.4 / 9.2
之前 Verification-Checklist 只覆盖到 Phase 1 / 2 / 3.A / 4 stub;后续
Phase 全部留 "待实现" 占位。一次性补齐,并:
- §I.5 七牛 token 自签 + 上传单元验证(Phase 3.6 落地后的真机/单测项)
- §J 微信授权 → sharelogin 7 字段(Province 大写 P)
- §K 微信链接分享 type=1 → sharesuccess 反向
- §L SharePanel 三选一(动画 / 半透明背景 / 平台未装行为)
- §M QQ URL Scheme 分享(中文 encode / 朋友圈 to_qzone / 乐观策略)
- §N 抖音相册 + URL Scheme 4 候选
- §O 微信截图 type=2 / 远端图 type=3(thumb 0.4x / errCode -2 cancelled)
- §P 高德定位 9 字段(latitude/longitude string、province 小写 p、errorCode 12)
- §Q 子游戏 push + 节流 + 栈深 + backgameData + ExternalSubscriptions 防双发 + SubGameDownloader 缓存/升级
- §R 弹层 OpenurlTitleData + finishweb/browser/backgameData 三 settings + Cookie 隔离 + overlay→sub-game 链路
- §S 视频房间 stub + 大厅不注册边界
- §T phonestate(CXCallObserver 真机;仅子游戏订阅;pop 后停止)
- §U H5ErrorRelay 三类异常源 + Overlay 不挂 + 原始 console.error 链未断
新增「通用准备 → 真机 vs 模拟器跑哪些」清单,明示 M Mac 模拟器
不能跑的章节(§J/K/L/M/N/O/P/T/I.5.3)+ 建议路径(先模拟器跑大头,
再真机跑特定章节)。
末尾「契约 §10 映射表」从 11 行扩到 23 行,每行带 Phase 状态。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 02:37:04 +08:00 |
|
 joywayerandClaude Opus 4.7
|
f7d472d91c
|
Phase 9.3-9.6 决策落定:极光/闲聊/Agora/Bugly 全部不集成
四个 SDK 的「集成与否」决策对齐当前项目状态:
- 9.3 极光 JAnalytics:YAGNI,H5 业务不调埋点 handler,建空协议反而
misleading;未来真要启用按 Vendor → 抽 Tracker 协议 → Noop fallback
- 9.4 闲聊:SDK 已停更 + 业务方未启用;SharePanel 三按钮已只含
微信/QQ/抖音,连 stub 都不需要
- 9.5 Agora:用户明确视频房暂不需要;VideoRoomHandlers stub 已满足
契约边界(H5 调 3 个 handler 不报 not found),未来重启按 8.6 蓝图
- 9.6 Bugly:沿用 msext CLAUDE.md「账号已废弃」决策,崩溃监控完全
依赖 Sentry(如 9.1 启用)+ 用户/运营反馈链
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 02:05:37 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a1c5883cc5
|
Phase 9.2:H5ErrorRelay 把 H5 异常中继到原生 print
零修改 H5(原则 A):WKUserScript .atDocumentStart 在业务 JS 跑前 hook
三类异常源:
- `console.error(...)`:保留原函数链式调用 + 转发 args
- `window.onerror`:onmessage / filename / lineno / colno / error.stack
- `unhandledrejection`:reason + reason.stack
走独立的 webkit.messageHandlers.h5error 通道(与 WVJB 不同 channel,不冲突)。
原生侧 MessageProxy weak target 切断 retain cycle。仅 BridgedWebView 安装
(大厅+子游戏),OverlayViewController 是第三方外链,不挂。
开发期价值:H5 出错时 Xcode console 直接能看到 "[H5 onerror] ... at xxx.js:42:8",
减少调试盲点。生产期 print 走 OSLog,对体积/性能影响极小。
新增:
- Source/Bridge/H5ErrorRelay.swift(@MainActor singleton)
接入:
- BridgedWebView.init:H5ErrorRelay.shared.install(into:) 在 WVJB 之后
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 02:04:46 +08:00 |
|
 joywayerandClaude Opus 4.7
|
5885f97715
|
Plan §8 进度补漏:Phase 8.1-8.3 视频房间 stub 全勾
8.1/8.2/8.3 视频房间 3 件套 stub 实际在 Phase 6 commit C(VideoRoomHandlers.swift)
就已落地,§5 已勾,§8 当时遗漏,一并补上并明确「不抽 protocol/Noop 类
避免过度设计」的选择。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 02:02:05 +08:00 |
|
 joywayerandClaude Opus 4.7
|
ded6b5d86b
|
Phase 8.4:PhoneStateMonitor → phonestate 反向 callback
契约 §3.1 [14]phonestate 落地。msext 用已废弃的 CTCallCenter(iOS 10+
deprecated);新外壳改用 CallKit CXCallObserver,对 H5 输出字符串一致:
- "2" = 来电响铃中(hasConnected=false / isOutgoing=false / hasEnded=false)
或已连接(hasConnected=true)
- "0" = 挂断(hasEnded=true)
- 其它(拨出未连接、保持)不发
新增:
- Source/Resource/PhoneStateMonitor.swift(@MainActor singleton,与 BatteryMonitor /
NetworkMonitor 同款 pattern;onChange closure + start/stop 生命周期)
接入:
- SubGameViewController.setupExternalSubscriptions:start + onChange
- SubGameViewController.teardownExternalSubscriptions:onChange=nil + stop
- 大厅 WebContainerViewController 不订阅(msext gameController 同款边界,
契约表注「已注释」表示大厅不发;H5 子游戏才依赖此事件做哑麦/暂停)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 02:01:29 +08:00 |
|
 joywayerandClaude Opus 4.7
|
8fc37b0827
|
Plan §8 进度补漏:Phase 2 / 4.11-4.12 / 7.6-7.7 全勾
Phase 2 大厅简单 handler 实际在 Phase 1 / 4.B 顺带都已落地(13 个文件全
存在于 Source/Bridge/Handlers/ + Source/Resource/),只是 §8 进度表
长期未同步。一次性补勾,并附实际实现文件路径方便检索。
Phase 4.11 / 4.12(截图 / 远端图分享)由 commit 8e4c36d 落地,§5 已勾,
§8 这里也勾上。
Phase 7.6 finishweb / 7.7 browser overlay 实际在 OverlayViewController
首版(commit 0427bb9)就已实现 handleMessage 三分支,与 7.5 同时落地,
但 §8 当时只勾了 7.1-7.5,遗漏 7.6/7.7。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 01:59:01 +08:00 |
|
 joywayerandClaude Opus 4.7
|
8e4c36d1c8
|
Phase 4.F:微信截图分享 type=2 / 远端图分享 type=其它
按 msext gameController.m:637-678 字面行为,WechatShare 按 content.type
分发:
- type=1 → shareLink(已有)
- type=2 → captureScreenshot → JPEG 0.6 + thumb 0.4x → shareImage
- type=其它 → downloadRemote(URL) → JPEG 0.9 + thumb 0.4x → shareImage
新增:
- ImageProvider.scaledThumb(_:scale:):等价 msext FuncPublic
imageByScalingAndCroppingForSize 0.4x 缩放;微信 SDK setThumbImage:
内部再压到 32KB
- WeChatManager.ShareImage 结构 + shareImage(_:scene:) 异步包装
(WXImageObject + WXMediaMessage.setThumbImage,FIFO 串行复用 sharePending)
契约:与 type=1 一致,调成功 → sharesuccess 反向 callback(success/cancelled
都发);errCode=-2 → cancelled;其它 errCode → failed。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 01:33:17 +08:00 |
|
 joywayerandClaude Opus 4.7
|
d9d9554d9f
|
Plan 进度同步:Phase 3.6 / 4.1 / 4.3-4.10 / 5.1-5.6 勾选
按 CLAUDE.md「进度同步规范」要求,把已落地的子项在 Development-Plan.md
§5 / §8 中同步勾选。同时修正几处描述与当前实现的差异:
- 4.1 微信 SDK:xcframework 2.0.5 入库 Vendor(不是 Embed Frameworks),
Universal Links 不接(企业签分发不需要)
- 4.3 WeChatSDK:新 SDK 2.x 无 MMAPP_SUPPORT_* flag,简单 registerApp
- 4.6 WeChatAuth:客户端直拼 sns/oauth2/access_token + sns/userinfo,
接受 msext 同款 secret 路径(不走后台 /wechat/login)
- 5.1 AMap:高德官方不出 xcframework,回退 fat .framework,M Mac 模拟器
链接失败是接受的现状(详见 Vendor/AMap/README.md「已知限制」)
- 5.3 AMapWrapper:updatePrivacyShow/Agree 是 AMapLocationManager 类方法
- 5.4 LocationService:仅 requestOnce + stop,未实现持续模式(msext 也
只是单次,H5 未要求持续)
- 3.6 Qiniu:CryptoKit HMAC-SHA1 自签 token + QNUploadManager 走
initWithConfiguration + defaultConfigurationV2
Phase 4.11 / 4.12(截图 / 远端图分享)下个 commit 实现。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 01:33:05 +08:00 |
|
 joywayerandClaude Opus 4.7
|
f137618070
|
入库微信 OpenSDK xcframework(2.0.5 NoPay,12 MB)
WechatOpenSDK-NoPay.xcframework 跟随仓库分发,与 Vendor/AMap/*.framework
同样跟踪到 git,新机 clone 后直接可链接,无需额外下载步骤。
- xcframework 含 ios-arm64 + ios-arm64_x86_64-simulator 两 slice
- 模块名 WechatOpenSDK(外层目录名 NoPay 是 zip 包形态,内部 framework 仍叫
WechatOpenSDK,canImport(WechatOpenSDK) 已生效)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 01:07:22 +08:00 |
|
 joywayerandClaude Opus 4.7
|
01780704cd
|
docs:明确 AMap fat framework 不含 simulator arm64 是项目接受的现状
高德官方至今不提供 xcframework,只发布 fat .framework(含 x86_64 + device
arm64)。M 芯片 Mac iOS-simulator target 链接失败是 fat 库的物理限制,与
daoqi msext 维护实践一致——开发期跑真机即可。
不动 Build Settings、不自造 xcframework、不切 CocoaPods 三条「不要做」也
一并明示,避免未来维护者花时间在这条死路上:
- Vendor/AMap/README.md 新增「已知限制」章节,含决策原因 + 三条「不要做」
- docs/SDK-Integration-Guide.md §A.2 简明提示 + 链回 README
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 01:07:02 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a3679aba7d
|
修复 SDK 真实接入后的 Swift 编译错误 + Xcode 链接配置
实测三个 SDK(微信 / 高德 / 七牛)链接进 Target 后,暴露出 9 个 Swift 编译错误,逐一修正:
代码侧(5 个 Swift 文件):
- AMapWrapper:updatePrivacyShow / updatePrivacyAgree 是 AMapLocationManager
类方法(since 2.8.0),不在 AMapServices 上;改为 AMapLocationManager.*
并补 import AMapLocationKit
- BackGameDataHandler:删 `WXApi.delegate = nil`。新版 SDK 2.x 无 static
delegate API,delegate 由调用方逐次传给 handleOpenURL/sendAuthReq 并被
SDK 弱引用;WeChatManager singleton 长生命周期不析构,无需 cleanup
- QiniuConfig / QiniuTokenSigner:所有 static 成员标 nonisolated,让 actor
QiniuUploader 可直接调,无需 await MainActor.run
- QiniuUploader:QNUploadManager() 在七牛 SDK 8.x 已 kQNDeprecated,改用
initWithConfiguration: + defaultConfigurationV2
工程侧(pbxproj,Xcode UI 自动写入):
- 新 Frameworks group 容纳 Vendor/WechatSDK / Vendor/AMap 引用
- WechatOpenSDK-NoPay.xcframework:Embed & Sign(动态库)
- AMapFoundationKit / AMapLocationKit:Do Not Embed(静态 fat .framework)
BuildProject 验证:Swift 全部编译通过;剩余 AMap fat framework arch 冲突
(M Mac iOS-simulator)是工程配置选择,下个 commit 处理。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 01:03:29 +08:00 |
|
 joywayerandClaude Opus 4.7
|
52ad421d22
|
SDK 选型修正:微信回 Vendor 路径(Tencent 无官方 SPM)
之前文档把 https://github.com/Tencent/WechatOpenSDK-XCFramework 当作
SPM 入口,实际该仓库不存在(404)。Tencent 官方至今未维护 SPM;社区
wrapper(yanyin1986 / anotheren)非官方,按 daoqi CLAUDE.md ADR-006
「闭源 SDK 走 Vendor」否决。微信改回与 AMap 同款 Vendor .xcframework
处置:
- Vendor/WechatSDK/README.md 重写为腾讯开放平台下载页 + Embed & Sign 说明
- docs/SDK-Integration-Guide.md §A / §A.1 / §1.3 同步修正
- §A 总览:SPM × 1(七牛)+ Vendor × 2(微信 / 高德)
- §A.1:重写为 Vendor Add Files 流程,强调 Embed & Sign 与 AMap
静态库 Do Not Embed 的区别
- §1.3:微信 xcframework 状态标记 ⏳ 等用户从官方下载放置
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 00:02:45 +08:00 |
|
 joywayerandClaude Opus 4.7
|
4e5600a9ee
|
Phase 3.C:Xcode 加入七牛 SPM 依赖 objc-sdk 8.9.2
按 SDK-Integration-Guide.md §A.3 操作,加入 https://github.com/qiniu/objc-sdk
(传递依赖 happy-dns-objc 1.0.4 自动解析)。canImport(QiniuSDK) 现在
返回 true,QiniuUploader.upload(...) 真实路径激活。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-23 00:02:31 +08:00 |
|
 joywayerandClaude Opus 4.7
|
1d7e258130
|
Phase 4.E + Phase 5 + Phase 3.C 七牛上传:代码侧 SDK 接入(SPM 优先 / Vendor 兜底)
选型(CLAUDE.md ADR-006「SPM 优先 + Vendor xcframework 兜底」):
- 微信 → SPM(Tencent 官方 https://github.com/Tencent/WechatOpenSDK-XCFramework);
项目方下载的 WechatOpenSDK-NoPay.xcframework 已撤出 Vendor,docs/res 内副本作离线备份
- 高德 → Vendor 手动(官方未提供 SPM;Vendor/AMap/{AMapFoundationKit,AMapLocationKit}.framework
随仓库分发,fat .framework 同时含 x86_64 + arm64,Do Not Embed)
- 七牛 → SPM(https://github.com/qiniu/objc-sdk v8.9.x)
代码侧(全部 #if canImport 守卫,Xcode 加 SDK 前编译为 no-op):
- Source/SDK/WeChat/WeChatSDK.swift:WXApi.registerApp + handleOpenURL(沿用 msext AppID
wx586a9b321e56efb7,universalLink 空字符串走非 ULAPI 路径)
- Source/SDK/WeChat/WeChatManager.swift:WXApiDelegate + state UUID 配对的 authorize +
FIFO 串行 share async/await wrapper
- Source/Login/WeChatAuth.swift:客户端直拼 sns/oauth2/access_token + sns/userinfo →
7 字段(Province 大写 P / city 经 danbian 去单引号),沿用 msext 同款路径
- Source/SDK/AMap/AMapWrapper.swift:iOS 14+ 隐私合规 3 步 + apiKey 注入
- Source/Location/LocationService.swift:actor + AMapLocationManager 异步包装 + 9 字段
- Source/Network/QiniuConfig.swift:4 项常量沿用 msext(AccessKey/SecretKey/Bucket/Domain)
- Source/Network/QiniuTokenSigner.swift:纯 Swift CryptoKit HMAC-SHA1 + Base64URL 自签
token(与 msext QiniuManager.m:200-230 等价,不依赖 Qiniu SDK 工作)
- Source/Network/QiniuUploader.swift:actor 包 QNUploadManager async/await
Handler 升级:
- AccreditLoginHandler:拉起授权 → 反向 callback sharelogin 7 字段
- WechatShare:真实链接分享(type=2/3 截图待 Phase 4.F)
- StartLocationHandler:真实定位 → 9 字段(latitude/longitude string, province 小写 p)
生命周期:
- AppDelegate.didFinishLaunchingWithOptions:WeChatSDK.register + AMapWrapper.bootstrap
- SceneDelegate.openURLContexts:WeChatSDK.handleOpenURL 接入回调
- BackGameDataHandler:子游戏 backgameData pop 时 WXApi.delegate=nil + LocationService.stop()
Info.plist:CFBundleURLTypes 加 wx586a9b321e56efb7;LSApplicationQueriesSchemes 追加
weixin/weixinULAPI/weixinURLParamsAPI;NSLocationWhenInUseUsageDescription +
NSMicrophoneUsageDescription(gamehallname 中文文案)。
.gitignore:排除 docs/res/{AMap_iOS_Loc_ALL,objc-sdk-8.9.2}/(235MB 项目方下载副本,
已走 SPM/Vendor 接入不需要副本)。
文档:docs/SDK-Integration-Guide.md 重写 §A 用户手动 Xcode UI 三步(SPM × 2 +
Add Files × 2);Vendor/{AMap,WechatSDK}/README.md 记录各自接入路径选型。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 22:22:23 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a2baf742b6
|
docs:新增 SDK-Integration-Guide.md(微信 / 高德定位 / opencore-amr / 七牛)
按 CLAUDE.md ADR-006「禁 CocoaPods,SPM 优先 + Vendor xcframework」组织,
每个 SDK 章节给出:凭证清单 / 工程接入 / 代码触点 / BackGameData 清理钩子 / 验收 /
风险,便于项目方发凭证后开发者按表落地。
关键约束(写入指南):
- 微信:不复用 msext AppID;AppSecret 客户端泄露风险,fallback 路径上线前必须切后台
- 高德:APIKey 与 Bundle ID 绑死,必须重新申请(msext 的 key 不可用);
iOS 14+ 隐私合规三步(updatePrivacyShow → updatePrivacyAgree → apiKey)
- opencore-amr:复用 msext 已 segalign 8 修复版 .a,免重编源码;MRC wrapper 加
-fno-objc-arc 文件级标志
- 七牛:SPM 拉官方 objc-sdk v8.9.x;token 缓存 + 失效前 30s 预刷新
- 每个 SDK 都标明在 BackGameDataHandler 必须加的清理钩子(WXApi.delegate=nil /
LocationService.stop / AudioRecorder.cancel / QiniuUploader.cancelInFlight)
CLAUDE.md 文档索引追加一行指向新文件。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 21:36:51 +08:00 |
|
 joywayerandClaude Opus 4.7
|
0427bb9542
|
Phase 7 完整实现:OpenurlTitleData 弹层(OverlayViewController + 3 个 settings)
- AppCoordinator 扩 OverlayRequest + lastOverlayAt(3s 节流 / 栈深 < 3 守门) +
showOverlay / popOverlay(data==nil 仅 pop 即 finishweb 路径;
data!=nil 同时发 .subGameDidReturn 即 backgameData 路径)
- 新增 Source/WebView/OverlayViewController.swift:独立 WKWebView,
WKWebViewConfiguration.websiteDataStore = .nonPersistent() 隔离 Cookie / 缓存;
.atDocumentEnd 注入 polyfill:window.settings = {browser/finishweb/backgameData}
→ webkit.messageHandlers.<name>(与 WVJB 不同 channel 可同名共存);
ScriptMessageProxy weak target 切断 retain cycle
- OpenurlTitleDataHandler 升级为真实 push:解 url / "title "(末尾空格契约)/
data / orientation → AppCoordinator.showOverlay,cb 字面始终回 "OpenurlTitleData"
- SubGameViewController.setupExternalSubscriptions 也订阅 .subGameDidReturn
(overlay 可从子游戏 push,pop 回子游戏时需要 callback 子游戏 H5 的 getWebdata;
与 viewWillAppear/Disappear 生命周期对齐,栈顶才挂钩避免双发)
- orientation 字段保留契约但 landscape-only app 暂不实施旋转(msext 旋转 transform
在我们项目里无意义,记注释,如有 H5 反馈再补)
- Plan §5.7.1-7.7 / §8 进度已勾选
至此 SwitchOverGameData → push 子游戏 / OpenurlTitleData → push 弹层 / backgameData ↔
finishweb → pop + getWebdata 反向闭环全部接通。栈深永远 ≤ 3。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 21:30:03 +08:00 |
|
 joywayerandClaude Opus 4.7
|
5b67e414b3
|
BackGameDataHandler:补 SDK 接入 TODO 清单(防止未来漏清理)
记录 Phase 4.E 微信 / Phase 5 定位 / Phase 3.B-D 录音上传 / Phase 8 Agora
真实接入时必须在此处补的对应清理钩子(msext 在 backgameData / cleanUpAction
里逐项清理;我们当前 stub 状态无需要清的状态,但落地任一 SDK 后必须回到此处补 1 行)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 21:23:56 +08:00 |
|
 joywayerandClaude Opus 4.7
|
c77037f407
|
Phase 8 视频房间 3 件套 stub:createRoom / getVideoinfo / exitRoom
业务暂未启用视频功能,按运营确认仅维持桥契约,让 H5 子游戏调用不报 "no handler"。
- 新增 Source/Bridge/Handlers/VideoRoomHandlers.swift:单一 enum.register 3 个 stub,
各自 callback 字面回包名(与 OpenSaomaHandler 同款 stub 模式)
- SubGameViewController.registerBridgeHandlers 挂上 VideoRoomHandlers
- 不抽 VideoRoom protocol / NoopVideoRoom 类(违反"不为假设的未来需求设计"原则);
未来重新接入 Agora 时把 3 个 stub 展开实现即可,注册点不变
- Plan §5.6.3 / §5.8.1-8.3 进度已勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 21:19:55 +08:00 |
|
 joywayerandClaude Opus 4.7
|
5f3a435180
|
Phase 6 commit C:backgameData pop + getWebdata 反向 callback
- 新增 Source/Bridge/Handlers/BackGameDataHandler.swift(仅 SubGameViewController 注册):
- 调 AudioPlayer.stopAllBackground 停背景音
- AppCoordinator.popSubGame(returningData:) → popViewController + 发 .subGameDidReturn
- data 兼容 string / object(非字符串走 JSONSerialization 序列化透传)
- 字面 cb "backgameData"(msext gameController.m:691-709 等价,
WXApi 清理跳过 — 微信 SDK 待 Phase 4.E)
- AudioPlayer.stopAllBackground:无条件停背景音(msext 不看 type 直接 nil 行为)
- SubGameViewController.registerBridgeHandlers:挂上 BackGameDataHandler,
注释更新 exitRoom/getVideoinfo/createRoom 推迟到 Phase 8
- WebContainerViewController:在 setupExternalSubscriptions 挂 .subGameDidReturn
观察者 → bridge.call("getWebdata", .string(data)),teardown 时 removeObserver
(生命周期与 battery/network/appservice 同一对,子游戏栈顶时大厅已 teardown
避免双发)
- Plan §5.6.3 / 6.5 / 6.6 / §8 进度已勾选
至此 SwitchOverGameData → push 子游戏 → backgameData → pop + getWebdata
完整链路接通;子游戏视频房间(exitRoom/getVideoinfo/createRoom)留待 Phase 8。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 21:05:23 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a9eaf86c63
|
Phase 6 commit B:SubGameDownloader zip 下载 + 解压
- 新增 Source/Resource/SubGameDownloader.swift(actor):URLSession + ZIPFoundation,
缓存命中直接复用(看 {Caches}/{gameDir}/{gameStart}/index.html 是否存在),
未命中下载 → staging 解压 → 原子 rename(与 LobbyZipUpgrader 同思路;
msext gameController.m:1340-1401 等价,以 staging 替代 msext 直接覆盖避免半残留)
- SubGameViewController.runBootPipelineSteps:接入 ensureReady,splash 实时更新下载进度,
移除 commit A 占位的 SubGameBootError
- DownloadProgressDelegate 与 LobbyZipUpgrader 同款就地复制(两处用不抽公共,
三处出现时再合并)
- Plan §5.6.7 / §8 进度已勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 21:01:53 +08:00 |
|
 joywayerandClaude Opus 4.7
|
7a5644053d
|
Phase 6 commit A:AppCoordinator + SubGameViewController 框架 + SwitchOverGameData push
- AppCoordinator:2s 节流 + 栈深 < 2 守门 + .subGameDidReturn 通知名(Design §2.4.3)
- SubGameViewController:复用 BridgedWebView/Splash/16:9 letterbox/ExternalSubscriptions,
AppDataWriter(.subGame) 写 4 个 app_*.js;H5 未安装时抛错(待 commit B 接入下载)
- SwitchOverGameHandler:解 Gamedirectory/gamedownloadurl/data → AppCoordinator.showSubGame
(msext NewRootVC.m:541-566 等价,节流与栈深由 Coordinator 守门,cb 字面始终回)
- SceneDelegate:UINavigationController 承载大厅,nav 引用交给 AppCoordinator
- Plan 进度已勾选 §5 / §8(commit B/C 留待 SubGameDownloader 与 backgameData/getWebdata 链路)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 20:59:00 +08:00 |
|
 joywayerandClaude Opus 4.7
|
68cf7140d7
|
Phase 6/7 stub:SwitchOverGameData + OpenurlTitleData
补齐 H5 桥 §3.1 [15][17]两个 stub handler,让 H5 调子游戏切换 / 弹层
入口时不再报 "no handler registered",避免业务流程卡住。完整流程留
Phase 6 / Phase 7 实施。
新增:
- Source/Bridge/Handlers/SwitchOverGameHandler.swift §3.1 [17]stub
cb "SwitchOverGameData"(驼峰首字母大写沿用 msext 字面)
完整实现需 AppCoordinator + SubGameViewController + SubGameDownloader
(Phase 6.4 落地)
- Source/Bridge/Handlers/OpenurlTitleDataHandler.swift §3.1 [15]stub
cb "OpenurlTitleData"
完整实现需 OverlayViewController + window.settings polyfill + 3s 节流
(Phase 7 落地);注意契约硬约束 "title " 末尾空格已在注释提示
WebContainer.registerBridgeHandlers 接入。
至此 H5 桥 §3.1 大厅可调的全部 16 项 handler(除 backgameData / createRoom
等子游戏专属 §H 4 项)都已注册(含完整实现 / stub 两种状态),
H5 启动 / 业务期任何 handler 调用都不会报 "no handler"。
BuildProject 通过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 20:14:31 +08:00 |
|
 joywayerandClaude Opus 4.7
|
39c1b5164d
|
Phase 4.D:DouyinShare URL Scheme + Photos 真实落地(不依赖 SDK)
移植 msext DouyinShareManager.m:201-269 shareImageToDouyin 完整流程到 Swift。
新增基础设施(Phase 4.F 截图 / 远端图也共用):
Source/Share/ImageProvider.swift:
- captureScreenshot() throws -> UIImage:UIGraphicsImageRenderer 截
keyWindow(含状态栏 + letterbox 黑边 + WebView),与 msext
FuncPublic.getImageWithFullScreenshot 等价
- downloadRemote(_:) async throws -> UIImage:URLSession.data(from:)
下载远端 URL → UIImage 解码
- ImageProviderError 错误分类(noKeyWindow / downloadFailed / decodeFailed /
badURL)
Source/Share/PhotoLibrarySaver.swift:
- save(_:) async throws:使用 PHAccessLevel.addOnly(iOS 14+ 推荐,仅写
相册不读,弹窗更友好)+ PHAssetChangeRequest.creationRequestForAsset
- 自动处理权限三态(authorized/notDetermined 请求 + denied/restricted 抛错)
DouyinShare.share 完整实现:
- 拿图片源:
* type == "2" 截图 / type == "1" 链接(退化为当前屏截屏,msext 行为)
→ ImageProvider.captureScreenshot
* type == 其它(远端图 URL)→ ImageProvider.downloadRemote(webpageUrl)
- PhotoLibrarySaver.save 保存到相册(含 .permissionDenied 错误回 .failed)
- UIPasteboard.general.image = image 兜底(抖音可能从剪贴板读图)
- 按 msext 顺序尝试 4 个 URL Scheme:
snssdk1128://camera / publish / share/image / 基础 scheme
canOpenURL 命中即 await UIApplication.open + 调起即 success
- 全部失败返回 .failed("所有抖音 URL Scheme 都调起失败")
Info.plist 加 NSPhotoLibraryAddUsageDescription("需要将分享内容保存到
相册,以便您在抖音中选取分享"),addOnly 权限弹窗用此文案。
BuildProject 通过
Plan §5 Phase 4.4 勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 20:07:45 +08:00 |
|
 joywayerandClaude Opus 4.7
|
aff5ee7633
|
Phase 4.C:QQShare URL Scheme 真实落地(不依赖 SDK)
移植 msext QQShareManager.m:716-771 simpleShareToQQFriend 简化版到 Swift。
QQShare.share 完整实现:
- canOpenURL("mqqapi://") 检测 QQ 安装;未装返回 .notInstalled
- 仅链接分享走完整 URL Scheme(type == "1");type == "2"/其它(截图 /
远端图)暂返回 .success 避免 H5 业务卡住,Phase 4.F 接入
- URL 构造(与 msext 等价):
* scene == .timeline → mqqapi://share/to_qzone? (QQ 空间)
* scene == .friend → mqqapi://share/to_fri? (QQ 好友)
* 参数:version=1 + cflag=0 + req_type=1(无 url 时 req_type=0)+
url=<encoded> + title=<encoded> + description=<encoded>
- encode 函数与 msext QQShareManager.m:1262-1268 等价:仅保留 RFC 3986
unreserved 字符(alphanumeric + "-._~"),其它全部 percent encode
- canOpenURL 二次检查 + await UIApplication.open
- 调起即视为 success(msext 同款乐观策略,URL Scheme 单向无返回)
Info.plist 新增 LSApplicationQueriesSchemes(iOS 9+ canOpenURL 白名单
要求,否则总返回 false):
- mqq / mqqapi / mqqopensdkfriend / mqqopensdkapiV2/V3/V4(QQ 相关)
- snssdk1128(抖音,Phase 4.D 用)
- 微信相关待 Phase 4.E 接入 SDK 时再加(weixin / weixinULAPI 等)
BuildProject 通过
Plan §5 Phase 4.3 勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 20:04:36 +08:00 |
|
 joywayerandClaude Opus 4.7
|
2ad4563d16
|
Phase 4.B:SharePlatform 框架 + SharePanel 三选一 UI + ShareCenter 分发
按 msext gameController.m:575-597 / SharePanel.m 模式落地分享框架。
新增 Source/Share/:
- SharePlatform.swift:
* SharePlatform 协议(name / isInstalled / share async → ShareResult)
* ShareContent struct(nonisolated Sendable,6 字段 sharefriend/sharetype/
type/webpageUrl/title/desc)
* ShareResult enum (success / cancelled / notInstalled / failed)
* ShareScene enum (friend / timeline)
- SharePanel.swift:
* @MainActor UIView,半透明黑底覆盖全屏 + 底部圆角内容视图滑入
* 三按钮(微信绿 / QQ 蓝 / 抖音黑)+ label 等间距居中布局
* 已安装平台按钮高亮,未装置灰
* 点击空白区 dismiss + completion(.cancelled)
* 点击按钮 dismiss + 触发对应 SharePlatform.share + completion(result)
* 0.25s 滑入/滑出动画
* 与 msext SharePanel.m 等价行为
- ShareCenter.swift:
* sharefriend == "1" → SharePanel.show 三选一
* sharefriend == "2" → WechatShare.share scene: .timeline 朋友圈
* 与 msext gameController.m:585 行为 1:1
- WechatShare.swift:stub(Phase 4.E 等微信 SDK 接入)
- QQShare.swift:stub + canOpenURL("mqqapi://") 检测(Phase 4.C 升级
URL Scheme 真实调起)
- DouyinShare.swift:stub + canOpenURL("snssdk1128://") 检测(Phase 4.D 升级
URL Scheme + Photos)
FriendsShareHandler 升级:
- 入参解析为 ShareContent
- 调 ShareCenter.dispatch
- completion 内根据 result 触发 sharesuccess 反向 callback:
* success / cancelled → bridge.call("sharesuccess", {success:"2", type:sharefriend})
* notInstalled / failed → 不发(H5 业务期 timeout 后自行 fallback,msext 乐观策略)
ShareContent 标 nonisolated 解 Swift 6 actor 隔离(项目默认 MainActor isolation
让 struct 跨 actor 受限)。
BuildProject 通过
Plan §5 Phase 4.2 勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 20:02:34 +08:00 |
|
 joywayerandClaude Opus 4.7
|
b071ced657
|
Phase 4 方向再次纠正:撤销 A,QQ + 抖音 SharePanel 三选一不依 SDK
用户提示"原项目还有 QQ 分享、抖音分享"。深度调研发现:
上一轮 A 方向决策 (a6095ef) 错了 — grep 范围之前仅看 NewRootVC.m,
漏了 gameController.m。msext gameController.m:575-597 显示 H5 调
friendsShare 时原生**弹 SharePanel 三选一面板**:
sharefriend == 1 → SharePanel.showWithDictionary(微信好友 / QQ / 抖音)
sharefriend == 2 → WechatShareManager.shareWithContent(朋友圈)
Contract §3.1 [2]旧描述(sharetype=3 闲聊)已被 msext 新代码覆盖;
sharetype 字段在新代码里读了但不用。
调研 QQ + 抖音是否需要 SDK:
- QQShareManager.m:14: __has_include(<TencentOpenAPI/QQApiInterface.h>)
条件编译 + line 716-771 simpleShareToQQFriend URL Scheme fallback
(mqqapi://share/to_fri?...) — **不依赖 SDK**
- DouyinShareManager.m: 只 import Photos + MobileCoreServices;
全程 URL Scheme(snssdk1128://share/video / camera)+ 保存截图到
相册让用户在抖音里选 — **不依赖 SDK**
撤销 A 决策。新方向(Plan §5 4.2-4.4):
- 4.2 SharePanel 三选一面板(msext gameController.m:585 等价行为)
- 4.3 QQShare URL Scheme(移植 simpleShareToQQFriend)
- 4.4 DouyinShare URL Scheme + Photos
- 4.5(原 4.B)微信 SDK 接入(仍需项目方提供 framework + AppID + UL)
文档同步:
- Plan §2.3 阻塞表:QQ 改为"不需要 SDK,走 URL Scheme";抖音同款
- Plan §5 Phase 4:4.2 重写为 SharePanel 设计 + 4.3 QQ + 4.4 抖音 + 4.5 微信
- CLAUDE.md 依赖管理段:"不接入 QQ SDK / 也不接入抖音 SDK",
含 LSApplicationQueriesSchemes + NSPhotoLibraryAddUsageDescription 说明
- Verification-Checklist L/M/N 章节:SharePanel + QQ + 抖音验证项
- Plan §6.4 里程碑加纠正记录(标 a6095ef 已撤销)
下一步:代码实施 4.B SharePlatform 协议 + SharePanel UI + ShareCenter 分发。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 19:59:00 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a6095ef4c8
|
Phase 4 方向最终决策 A:QQ 不在 H5 桥范围,不实施
深度 grep msext NewRootVC.m / gameController.m 未发现 QQShareManager
引用 — QQ 完全不接 H5 桥。Contract §3.1 [2] sharetype 取值仅 "1"/"2"=
微信 / "3"=闲聊,无 QQ 选项。
按 CLAUDE.md 原则 A(H5 端零修改不可妥协):契约不变 → H5 桥不实现
QQ 分享。msext 里 QQShareManager 只服务于原生 SharePanel(独立功能),
新外壳无原生 SharePanel-like 入口需求 → 也不需要 QQShareManager。
文档同步调整为"不实施":
- Plan §2.3 阻塞表:QQ OpenSDK 改写为详细理由(H5 桥契约无 QQ
选项 + msext QQShareManager 不接 H5 桥的 grep 证据)
- Plan §5 Phase 4.2:从"QQ 改走 URL Scheme"改为"不实施",但保留
URL Scheme 备用知识库(未来如需原生入口可参 msext fallback)
- CLAUDE.md 依赖管理段:"不接入 QQ SDK 也不实现 QQ 分享",明示
原因(契约无 QQ + msext 行为)
- Verification-Checklist L 章节:QQ 验证项删除(不实施无需验证)
Phase 4 至此剩 4.B 微信 SDK 接入(项目方阻塞)+ 4.D 截图/远端图分享
(依赖 4.B)。当前 stub 已让 H5 调登录/分享不报 "no handler",
等微信 SDK 到位后升级。
Plan §6.4 里程碑加一行决策记录。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 19:45:12 +08:00 |
|
 joywayerandClaude Opus 4.7
|
aa80aec18c
|
Phase 4.A:登录+分享 stub + QQ 改走 URL Scheme 不依 SDK
按用户约定调整 Phase 4 方向:微信继续走 SDK(等项目方提供),QQ 分享
改用 URL Scheme 直接调起 QQ app,**不依赖 SDK**。
调研 daoqi/msext/Class/Utils/QQShareManager.m 发现已有完整的 URL Scheme
fallback 路径:
- line 14: __has_include(<TencentOpenAPI/QQApiInterface.h>) 条件编译
- line 231 注释: "Fallback: 使用 URL Scheme (mqqapi://) - 无需 SDK,
直接调起 (Legacy)"
- mqqapi://share/to_fri / mqqapi://share/to_qzone / mqqopensdkfriend://share
等 schema 实现见 line 733/786/789
新增 handler stub(让 H5 启动 / 业务期调登录 / 分享不报 "no handler"):
- Source/Bridge/Handlers/AccreditLoginHandler.swift
accreditlogin cb "Response from accreditlogin"
完整实现需 Phase 4.B 微信 SDK + WeChatManager.authorize() + WeChatAuth.exchangeForUser
+ bridge.call("sharelogin", 7 字段含大写 Province) 反向 callback
- Source/Bridge/Handlers/FriendsShareHandler.swift
friendsSharetypeUrlToptitleDescript cb "sharefriend"
完整实现需 ShareCenter 策略分发 + sharesuccess 反向 callback
WebContainer.registerBridgeHandlers 接入。
文档调整:
- Plan §2.3 阻塞表:QQ OpenSDK 标"不需要",改 URL Scheme 路径
- Plan §5 Phase 4 前置:去掉 QQ OpenSDK 一行
- Plan §5 Phase 4.2:完整改写为 URL Scheme 方案,含 LSApplicationQueriesSchemes
清单 + URL 构造样例 + "调起即 success" 乐观策略说明
- Plan §8 进度追踪:4.2 QQ SDK Vendor 标"不需要"
- Plan §6.4 里程碑加 3 行(前两轮 commit hash 补 + 本次)
- CLAUDE.md 依赖管理段加 "不接入 QQ SDK" 约定(与"不引入 CocoaPods"
并列的负面清单)
- Verification-Checklist Phase 4 章节:E.19/E.20 stub + K/L 待 4.B/4.C
完整后填充验证项
BuildProject 通过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 19:19:23 +08:00 |
|
 joywayerandClaude Opus 4.7
|
0602b9d4b6
|
Phase 3.A:srcIsloop 本地音频 + 3.B/3.C/3.D handler stub
3.A 本地音频完整落地(与 msext NewRootVC initJSdata 等价):
Source/Audio/AudioPlayer.swift(@MainActor 状态机):
- playOnce(url) 单次按钮音;buttonPlayers 数组保活池避免多次点击
互相打断(duration + 0.5s 后清理)
- loopBackground(url, type:) 循环背景音;numberOfLoops = -1 +
backgroundType 记录用于同名停止
- stopBackground(type:) 同名校验后才停止(msext 同款语义)
Source/Bridge/Handlers/LocalAudioHandler.swift:
- 注册 srcIsloop handler(@MainActor enum)
- 入参 src + isloop,按 0/1/-1 分支调 AudioPlayer.shared 对应方法
- audioFileURL 路径:{Caches}/{gamedir}/{gamestart}/assets/wav/{src}
与 msext NewRootVC 路径约定 1:1(H5 zip 内 assets/wav 子目录)
- audioFileURL 标 nonisolated 因 SandboxPaths 是 nonisolated 静态
- cb 字面 "Response from srcIsloop" 严格保持
Source/Bridge/Handlers/RemoteAudioHandler.swift(3.B/3.C/3.D stub):
- prepareaudio + mediaTypeAudio 仅 cb 维持契约,无真实业务
- 待 Phase 3.B(opencore-amr .a 接入)+ 3.C(七牛 SPM + token)+
3.D(远端播放)完整后升级
- 按 CLAUDE.md 原则 A:H5 调啥就得有人接,即使是 stub
WebContainerViewController.registerBridgeHandlers 接入:
- LocalAudioHandler.register(完整实现)
- RemoteAudioHandler.register(stub)
Plan §5 Phase 3.1 勾选;3.2 真机验证留待 H5 提供音频文件后做。
Verification-Checklist.md 新增 Phase 3 章节:
- H. 本地音频(E.14-E.16 srcIsloop 三个 isloop 值)
- I. prepareaudio / mediaTypeAudio stub(E.17 E.18)
BuildProject 通过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 19:11:34 +08:00 |
|
 joywayerandClaude Opus 4.7
|
b2ee1b38eb
|
新增 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>
|
2026-06-22 19:08:32 +08:00 |
|
 joywayerandClaude Opus 4.7
|
09f071e38f
|
Phase 2.C:ExternalSubscriptions 生命周期管理(防双发预备)
按 Design §2.4.2 把 battery / network / appservice 三组外部事件源的订阅
挂钩与 VC 生命周期配对:viewWillAppear → setup,viewWillDisappear →
teardown。配套约束:未来 Phase 6 子游戏 push 上来时大厅自动 teardown,
避免桥事件双发(栈深 ≤ 2,§2.4.3)。
WebContainerViewController 改造:
- 新增 setupExternalSubscriptions():
* 幂等 start 3 个 monitor(NetworkMonitor / BatteryMonitor /
AppLifecycleObserver)
* 设 BatteryMonitor.onChange → evaluateJavaScript("window.app_getbattery=N")
+ bridge.call("getBattery", "%.2f")
* 设 NetworkMonitor.onChange → evaluateJavaScript("window.app_getnetwork=N")
+ bridge.call("getnetwork", "1"/"2"/"3")
* 设 AppLifecycleObserver.onBackground/Foreground → bridge.call("appservice",
"1"/"2")
- 新增 teardownExternalSubscriptions():
* 解绑 onChange / on{Background,Foreground} = nil
* 释放对 bridge/webView 的 closure 引用避免子游戏 push 后双发
* monitor 不停(保持后台 currentXxx 状态新鲜),下次 setup 直接 resume
- viewWillAppear / viewWillDisappear override 调 setup / teardown 配对
- writeAppDataFiles 精简:只写首次值 + 启动 NetworkMonitor/BatteryMonitor
(需要 currentXxx 读首次值);AppLifecycleObserver 启动挪到 setup
至此 Phase 2 全部完成(2.A 8 个 handler + 2.B 5 项反向 callback + 2.C
生命周期管理)。
BuildProject 通过
Plan §5 Phase 2.13 勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 19:02:35 +08:00 |
|
 joywayerandClaude Opus 4.7
|
4986f36967
|
业务期 app_getbattery / app_getnetwork 改为 evaluateJavaScript 重新赋值
按用户指示调整变化时机制:
启动期(保持):loadFileURL 之前写 4 个 app_*.js 文件,H5 同步
<script src> 引入读到实际值
业务期(调整):变化时**不重写文件**,改为
webView.evaluateJavaScript("window.app_getbattery=N;") 直接重新赋值
全局变量。原因:H5 已加载完后 <script src> 不会再 fetch,重写文件
对当前页面无影响(仅影响下次 reload),改 evaluateJavaScript 即时
生效 + 无文件 IO 开销。
与 §3.2 反向 callback 双轨同时执行:
- evaluateJavaScript 让 H5 业务期同步读 app_xxx 永远拿到最新值
- bridge.call 让 H5 注册的 callback handler 收到事件主动响应
WebContainerViewController.writeAppDataFiles 改动:
- BatteryMonitor.onChange:Task @MainActor await
webView.evaluateJavaScript("window.app_getbattery=N;") + bridge.call
- NetworkMonitor.onChange:同款 evaluateJavaScript + bridge.call
- AppLifecycleObserver:appservice 无对应 app_* 全局变量,只走 bridge.call
文档同步:
- Design §7.5.2 改为「两阶段」表述:启动期写文件 + 业务期 evaluateJavaScript
- Design §7.5.4 接入点代码示例同步
- Contract §4.2 app_battery.js / app_network.js 子节更新时机段
- Contract §4.2 修订记录加一行
- Plan §6.4 里程碑加一行 + 前两轮 commit hash 补充
BuildProject 通过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 18:50:53 +08:00 |
|
 joywayerandClaude Opus 4.7
|
23b3a1fdad
|
Phase 2.B:3 项反向 callback 落地(getBattery / getnetwork / appservice)
完成大厅业务期实时状态推送给 H5 的事件驱动链路。每次变化时同时(1)
重写对应 app_*.js 文件供下次 reload,(2)bridge.call 反向推送给当前
页 H5 — 双轨与原 msext 行为等价。
新增 Source/Resource/BatteryMonitor.swift:
- @MainActor public final class BatteryMonitor,shared 单例 + 幂等 start()
- 监听 UIDevice.batteryLevelDidChangeNotification + 启动期读首次值
- currentLevel 同步快照(max(level, 0) 兜底模拟器 -1)+ onChange 回调
- notification block 内 MainActor.assumeIsolated 安全跨到 main actor
新增 Source/Resource/AppLifecycleObserver.swift:
- @MainActor public final class AppLifecycleObserver,shared 单例 + 幂等 start()
- 监听 didEnterBackgroundNotification + willEnterForegroundNotification
- onBackground / onForeground 双钩子;assumeIsolated 同上
WebContainerViewController.writeAppDataFiles 接入(替换 inline addObserver):
- 3 个 monitor 启动统一聚合(NetworkMonitor + BatteryMonitor + AppLifecycle)
- BatteryMonitor.onChange:writeBattery + bridge.call("getBattery", "%.2f")
- NetworkMonitor.onChange:writeNetwork + bridge.call("getnetwork", "1"/"2"/"3")
- AppLifecycle.onBackground/Foreground:bridge.call("appservice", "1"/"2")
- 首次值改读 BatteryMonitor.currentLevel 而非 UIDevice 原始值
至此 Phase 2.B 全部完成(5 项反向 callback:getphoneinfo / getBattery /
getnetwork / appservice / shakeEnd 全部接好)。
剩 Phase 2.C ExternalSubscriptions 生命周期管理(防双发,栈深 ≥ 2 时
下层不发桥事件)。
BuildProject 通过
Plan §5 Phase 2.B 全部勾选
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 18:45:03 +08:00 |
|
 joywayerandClaude Opus 4.7
|
d38ff0f8c4
|
Phase 2.A:大厅 8 个简单 handler 落地 + BridgeData 加 nonisolated
闭合 Phase 1.17 联调最后一公里(解决 Xcode 日志 4 个 "no handler registered"
+ 提前落地 4 个无外部依赖的简单 handler)。
新增 7 个 handler 模块(Source/Bridge/Handlers/):
- ClipboardHandler.swift §3.1 [13][14]剪贴板 UIPasteboard.general
- ShakeHandler.swift §3.1 [7][8][9]摇一摇 + §3.2 [5]shakeEnd 反向
canShake / canVoice 状态 + motionEnded 转发钩子
- VoicePlayingHandler.swift §3.1 [6]actor VoiceCenter.shared 开关位
- DeviceInfoHandler.swift §3.1 [21]+ §3.2 [1]getphoneInfo 大写 I 入、
getphoneinfo 小写 i 出反向 callback
DeviceInfoSnapshot 6 字段(IDFA 用 idfv 兜底,
CLAUDE.md 不接 ATT/IDFA)
- BrowserHandler.swift §3.1 [16]UIApplication.open async(Task @MainActor
异步打开,闭包先 cb 不等 open 完成)
- OpenSaomaHandler.swift §3.1 [22]空 stub(契约要求注册)
- StartLocationHandler.swift §3.1 [20]Phase 5 完整实现,Phase 2 stub
扩展 VibratorHandler.swift:
- §3.1 [10]vibrator(原有)+ [11]repeatvibrator(msext RootVC.m:1820 沿用:
实际单次 vibrate 不真循环)+ [12]canclevibrator
BridgeProtocol.swift BridgeData 加 nonisolated:
- 项目默认 SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor 让 BridgeData 跨 actor
访问被拦;asString / asDouble / asInt / asBool / asArray / asObject /
subscript 全部标 nonisolated(纯值类型本就该是 isolation-free)
- 影响所有 @Sendable async handler 闭包内的 data?.asXxx 访问
WebContainerViewController 接入:
- 加 private let shakeHandler = ShakeHandler() 持有
- registerBridgeHandlers() 注册 8 个 handler 模块
- 加 canBecomeFirstResponder = true + viewDidAppear becomeFirstResponder
- 加 motionEnded(_:with:) 转发到 shakeHandler.handleMotionEnded
(motion 检测必需 first responder,否则不触发)
BuildProject 通过。重装 app 后 H5 启动应不再报 "no handler registered" 错误。
Plan §5 Phase 2.A 全部勾选(含 startlocation bonus stub + BridgeData
nonisolated 重构)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 18:38:00 +08:00 |
|
 joywayerandClaude Opus 4.7
|
0a313afe8e
|
app_appversion 业务语义纠正:审核切换标志 0/1(非版本号)
调研 daoqi/msext NewRootVC.m:1190-1208 initJSdata 发现 app_appversion
真实业务语义是审核切换标志,**不是 App 的版本号**:
if (远端 ResolvedVersion.appVersion >= 本地 BundleConfig.appVersion) {
app_gameconfig = BundleConfig.gameConfig; // 正常业务接口
app_appversion = '0'; // 标志:正常
} else { // 远端 < 本地(罕见,审核期 / IPA 已升远端未跟上)
app_gameconfig = BundleConfig.appleConfig; // 苹果审核期接口
app_appversion = '1'; // 标志:审核期
}
H5 端 if (app_appversion === '1') 切苹果审核期分支;99% 时间下都是 '0'
+ gameconfig。
当前代码两处错误:
❌ app_appversion 写成 "43"(版本号字符串)
❌ app_gameconfig 永远是 BundleConfig.gameConfig 硬编码
不符合契约(H5 拿到错误标志会走错分支)。按 CLAUDE.md 原则 A 第一准则
必须 1:1 等价 msext。
修复(Source/WebView/AppDataWriter.swift writeAppData):
- 加 result 计算:localAppVer = Int(bc.appVersion), remoteAppVer =
resolvedVersion?.appVersion ?? localAppVer;result = remoteAppVer >=
localAppVer ? 0 : 1
- 加 gameConfigStr 切换:result == 0 ? bc.gameConfig : bc.appleConfig
- app_appversion 写字面 '\(result)' 单引号包裹(与 msext "var
app_appversion='%d';" 等价)
- Logger 输出加 result 说明 + local/remote 版本号便于调试
Contract §4.2:新增详细业务语义说明段(含 msext 原 ObjC 代码 + result
0/1 含义解读 + app_gameconfig 动态切换说明),修订记录加一行。
Design §7.5.1 表格:#2 app_gameconfig 数据源改为"动态切换",#6
app_appversion 数据源改为"审核切换标志"。
Plan §6.4 里程碑加一行。
BuildProject 通过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 18:28:08 +08:00 |
|
 joywayerandClaude Opus 4.7
|
7c856d0d66
|
AppDataWriter:app_data.js 内 5 个关键字段提到声明顺序最前
按用户要求调整 app_data.js 内 var 声明顺序,把 H5 调试时最关注的 5 个
核心字段提到最前:
1. app_gameconfig 远端配置接口
2. app_market 渠道市场标识
3. app_agent 登录态 agent
4. app_channel 渠道 ID
5. app_Launchtype 大厅(0) / 子游戏(1) 标识
之后按原 msext 顺序:app_version / app_gamedir / app_gamestart /
app_appversion / app_getwifisignalLevel / app_gamename / app_invitationcode。
JS 内 var 声明的顺序对最终读取值无影响(var 提升 + 整段 script 同步
执行完才退出),契约不破,与 msext 行为等价。仅便于 H5 console 一眼
看到核心字段。
Logger.debug 输出顺序同步调整保持与文件内容顺序一致,便于联调对账。
BuildProject 通过
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 18:14:10 +08:00 |
|
 joywayerandClaude Opus 4.7
|
829cc8f825
|
代码层落地 AppDataWriter + NetworkMonitor + WebContainer 接入
按 Design §7.5 蓝图落地,让 H5 启动时能从沙盒读到实际渠道/启动/设备
值,闭合 Phase 1.17 联调"app_gameconfig 不正确"现象。
新增 Source/WebView/AppDataWriter.swift:
- public struct AppDataWriter(@MainActor)
- ContainerRole enum:.lobby / .subGame(name:, dir:)
- writeInitial():写 app_data.js(12 项)+ app_gamesname.js(暂只写当前
gameStart 一项,Phase 6 子游戏完整时扩为扫描已装列表)
- writeBattery(level:):写 app_battery.js(var app_getbattery=N;)
- writeNetwork(code:):写 app_network.js(var app_getnetwork=N;)
- 字面严格对齐 msext:
* 字符串单引号包裹 'value'
* 数值无引号(version=1 / Launchtype=0 / getwifisignalLevel=1)
* app_gamesname 用 "var app_gamesname=new Array(...)"(var 后两空格)
* 大小写硬约束:Launchtype L 大、getwifisignalLevel wifi 小 + signal/Level 区分
- escape 转义反斜杠 / 单引号 / 换行(避免渠道字段含单引号导致 H5 JS 解析错)
- Logger debug 输出文件路径 + 每个 key=value 一行,便于 §7.5.8 联调对账
新增 Source/Resource/NetworkMonitor.swift:
- @MainActor public final class NetworkMonitor
- NWPathMonitor 最简 wrap:currentCode 同步快照(默认 2 WiFi,启动后被
首次 path 更新)+ onChange MainActor 回调
- shared 单例 + 幂等 start()
WebContainer.runBootPipelineSteps 接入:
- switch outcome 前提取 let resolved: ResolvedVersion,避免变量作用域
限制 case 内
- 新增 writeAppDataFiles(resolved:):启动 NetworkMonitor + 开 battery
监控 + 写 4 个文件首次值 + 挂 addObserver(battery) + onChange(network)
- 调用位置:step 5 LobbyZipUpgrader 之后、step 6 loadFileURL 之前
(与 msext NewRootVC.initJSdata 等价时序)
BuildProject 通过
Plan §6.4 里程碑加一行。
Phase 1.17 联调验证:启动后 H5 console 应读到实际 app_channel /
app_gameconfig 等值,不再是 H5 zip 包内默认占位。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 12:44:45 +08:00 |
|
 joywayerandClaude Opus 4.7
|
13cc24b463
|
§7.5 路径回退到 writeToFile:撤销 evaluateJavaScript 路径
CLAUDE.md 原则 A 升级为第一准则(H5 端零修改不可妥协)后,重新评估
上一轮 commit 246f215 引入的 evaluateJavaScript 路径:
- WKUserScript(.atDocumentStart) 注入会被 H5 自带 var app_xxx 声明
覆盖回退
- WKUserScript(.atDocumentEnd) 又太晚,H5 业务顶层 <script> 已经
读过默认值
- 没有"在 <script src> 之后、业务 <script> 之前"的精确时机
- 任何"让 H5 改一行 / 改一个文件"的妥协方案违反原则 A 第一准则
- 详细分析见 CLAUDE.md「典型案例 3」
撤销方案:回到原 msext writeToFile 路径,原生在 loadFileURL 之前写
4 个 app_*.js 文件到沙盒,H5 启动时 <script src> 同步引入即可读到
实际值,时序 100% 等价 msext。
Design §7.5 全面回退(-285 / +126 行):
- §7.5 头部:标题 / 简介改回写文件路径;中间走过两次弯路的完整决策
链记录在头部(commit f68b0db → 246f215 → 本次 commit)
- §7.5.2 注入时机表 → 4 个文件写入时机表,附"为什么不要照搬
WKUserScript 注入"警告段
- §7.5.3 删除 AppDataInjector + .legacy 标注;AppDataWriter 升回主路径
(保留 logger debug 输出 / msext 字面对齐细节 / 转义 / 大小写)
- §7.5.4 接入点:writeInitial + writeBattery + writeNetwork 三阶段,
addObserver 持续监听
- §7.5.5 大厅 / 子游戏 / 弹层差异表回到 "app_data.js / app_gamesname.js
/ app_battery.js / app_network.js" 维度
- §7.5.6 与 msext 差异表回到"writeToFile 等价化"对比
- §7.5.7 与 §3.4.1 polyfill 关系:§7.5 文件预写为主路径;
WKUserScript + evaluateJavaScript "探索后撤回"
- §7.5.8 联调路径恢复 cat 文件路径,加 4 种现象诊断表
Contract §4.2 同步:
- 头部说明改回 "原生 writeToFile 写文件" + 撤销中间 evaluateJavaScript
路径的复盘段
- 子节标题回到 "写到 {gamedir}/{gamestart}/app_*.js"
- 子节"更新时机"段回到 loadFileURL 前 / addObserver / 前台切换
- 删除"H5 团队 zip 自带 + 默认占位"约定(不再适用,原生重新负责写入)
- 修订记录新增一行"路径再次调整(最终)"
Contract §10 验收清单同步:
- "H5 zip 自带 4 个 .js" → "沙盒下 4 个 .js 存在(xcrun simctl + cat 验证)"
- 删"原生 WKUserScript 覆盖生效"项 → 加"值是实际渠道值而非占位"
- "阶段 B 动态覆盖" → "battery/network 变化重写文件"
Plan §6.4 里程碑加两行:CLAUDE.md A 升级 + §7.5 最终回退。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 12:40:07 +08:00 |
|
 joywayerandClaude Opus 4.7
|
3039daf903
|
CLAUDE.md:原则 A 升级为第一准则(H5 端零修改不可妥协)+ 加典型案例 3
早期 CLAUDE.md 把原则 A / B 描述为"地位对等",本次升级:原则 A 是
**第一准则**,地位高于原则 B。两者冲突时无条件选 A、压力让原生 Swift
端承担,哪怕代码看起来过时。
原则 A 新增三个子节:
A.1 已经被否决的 / 永远不要再提出的方案:
- "H5 改一行"(哪怕只是把读 app_xxx 挪到 DOMContentLoaded 之后)
- "H5 改一个文件"(哪怕只是改成 if-undefined 守卫)
- "H5 加一个 polyfill"
- "H5 调整调用时机"
- "新桥接口"
唯一例外:项目方主动要求改 H5 时
A.2 替代方案 = 原生 Swift 端硬扛:
- 回到原 msext 老路(哪怕是写文件 / 轮询等"现代不推荐"模式)
- 接受技术不优雅:B 对 A 让步
- 不要"等 H5 改了再优雅化"自我安慰
A.3 判定流程:写代码前问"方案要 H5 改任何东西吗?" → 是则立即否决
「两条原则的交叉判定」段同步修订:A > B(不再地位对等)。
新增典型案例 3「app_* 注入时序问题」记录本轮:
- 调研发现 WKUserScript 无法 1:1 等价 msext 的"loadFileURL 前写文件"
- 我错误地提出 A/B/C 三方案,其中 A/B 要求 H5 配合改动 → 违反原则 A
- 正确做法:方案 C,原生 Swift 回到 msext 老路 writeToFile
- 教训:WKUserScript / evaluateJavaScript 不是万能药;遇时序冲突
回到旧路径不要为了"现代"而牺牲契约
下一步等用户确认后:撤回 §7.5 路径调整(commit 246f215),把 Design
重新指回 AppDataWriter 写文件方案 + 代码层落地实施。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 12:17:37 +08:00 |
|
 joywayerandClaude Opus 4.7
|
246f215d6f
|
§7.5 重大路径调整:app_* 改为 evaluateJavaScript 覆盖 window.app_xxx
H5 团队新约定:app_*.js 4 个文件由 H5 团队自带在 gamehall.zip 内(含
默认占位值),原生**不再写文件**,只用 WKUserScript(.atDocumentEnd) +
evaluateJavaScript 覆盖 window.app_xxx 全局变量的实际值。
Design 三处大改 (+339/-91 净 +248 行):
§7.5 标题 / 头部:
- 标题:H5 app_*.js 预注入文件机制 → 修改 H5 端 app_*.js 全局变量值
(evaluateJavaScript 路径)
- 新增「2026-06-22 重大方向调整」说明段:3 条理由 + 旧路径降级为
§7.5.3.legacy
§7.5.2 注入时机:
- 旧:4 个文件写入时机表
- 新:两阶段注入表(A 首次 documentEnd UserScript / B 动态
evaluateJavaScript)+ 为什么必须 documentEnd 不能 documentStart
(H5 的 var 声明会回退我们的值)+ 为什么不直接走异步 callHandler
(违反契约原则 A)
§7.5.3 Swift 骨架:
- AppDataWriter (writeToFile) → AppDataInjector (UserScript +
evaluateJavaScript)
- 阶段 A makeInitialUserScript:拼装 (function(){window.app_xxx=...})()
documentEnd 注入;包含 13 项启动期已知字段
- 阶段 B injectBattery / injectNetwork:async evaluateJavaScript 单条覆盖
- Logger.debug 保留所有 key=value 打印(联调可观察性)
- 旧 AppDataWriter 保留为 §7.5.3.legacy(明示不要实施)
§7.5.4 接入点:
- configuration.userContentController.addUserScript(阶段 A)
- addObserver(batteryLevelDidChange) → injectBattery
- networkMonitor.stateDidChange → injectNetwork
- bridgedWebView.didFinishOnce 首次补一次 battery + network
§7.5.5 / .6 / .7 / .8 同步调整:差异表 / msext 对比 / 联调路径
- §7.5.8 路径 2 从「沙盒 cat .js 文件」改为「Safari Web Inspector 看
window.app_xxx 实时值」(不再写文件无文件可 cat)
- 加阶段 B 手动触发验证(模拟器 Battery State / Network Link Conditioner)
Contract §4.2 同步:
- 新增 msext 旧契约 vs 新契约对比表(3 维度:文件来源 / 赋值机制 / 路径感知)
- 子节标题从「app_battery.js(写到 ...)」改为「H5 zip 包内,含
app_getbattery 默认占位」+ 更新时机改写为 evaluateJavaScript 时机
- 「H5 团队保证 zip 内 4 个 app_*.js 存在 + 合理默认占位」约定
- 修订记录加一行路径调整
Contract §10 验收清单调整:
- 文件验收:4 个 .js 由 H5 zip 自带(不是原生生成)
- 新增「原生覆盖生效」验收:Web Inspector 读 window.app_channel 必须
是 ChannelConfig.plist 实际值,不是 H5 默认占位
- app_Launchtype 类型修正为数字 0/1(不是字符串)
- 新增「阶段 B 动态覆盖」验收:模拟器手动触发 battery/network 变化
Plan §6.4 里程碑加新一行记录本次路径调整。
至此 H5↔Native 通讯 5 条路径全部按新契约对齐:
1. WVJB 异步 callback(22 项)
2. Native→H5 反向 callback(15 项)
3. **window.app_xxx evaluateJavaScript 覆盖(15 项)** ← 本次重写
4. 弹层 settings.* polyfill(4 项)
5. WKUIDelegate alert/confirm(2 项)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 08:19:53 +08:00 |
|
 joywayerandClaude Opus 4.7
|
9f4ccb762e
|
Design §7.5.3/.8:AppDataWriter 加 os.log 调试输出 + 联调可观察性
用户在 Phase 1.17 联调准备时反馈:app_*.js 写入时应该有控制台输出,
方便调试。本 commit 在 §7.5 蓝图层落实可观察性,Phase 2 实施按此骨架
就有完整 debug log。
§7.5.3 AppDataWriter 改动(同时修正若干与 msext 字面不一致的细节):
- 引入 os.Logger(subsystem: ylgamehall, category: AppDataWriter)
- writeAppData / writeGamesName / writeBattery / writeNetwork 全部打印
{ContainerRole} + 文件路径 + 每个 key=value 一行,便于和 H5
console.log(app_xxx) 逐项对账
- ContainerRole 实现 CustomStringConvertible("lobby" / "subGame(name)")
- Logger.debug 级别:Debug 出 Xcode 控制台,Release 自动过滤
- privacy: .public 标注(确保字符串值不被 Logger 模糊化为 <private>)
**字面修正**(对照 daoqi/msext 实际代码二次校对):
- 字符串包裹符 " → '(msext 用 "var x='%@';" 单引号)
- escape 转义对象从 \" 改为 \'(与单引号包裹配套)
- 数值字段(version / Launchtype / getwifisignalLevel / appversion)
去掉引号,沿用 msext "var app_version=1;" / "var app_appversion='%d';"
- app_gamesname 从 JS array literal [...] 改为 new Array(...),
var 后两空格,严格 AppDelegate.m:259 字面
§7.5.8 新增「联调时的可观察性(Phase 1.17 调试手段)」:
- 路径 1:Xcode 控制台 — 原生 os.log 输出格式样例
- 路径 2:沙盒文件直接 cat — xcrun simctl get_app_container 命令 +
4 个 .js 文件 cat 验证
- 路径 3:H5 console — Safari Web Inspector 逐项 console.log(app_xxx)
样例,标注 ⚠️ 大小写硬约束(Launchtype L 大写 / battery 带 get 前缀)
- 路径 4:真机 Safari 调试启用方法
- Release 注意事项:os.log .debug 自动过滤避免性能开销和敏感字段外泄
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 08:09:57 +08:00 |
|
 joywayerandClaude Opus 4.7
|
0cdd709e66
|
中优 3 项收尾:Plan §6.4 里程碑 + Design §20.1 拆分 + Contract §10 验收清单
补完本轮 audit 与 Design 大改后的文档边界,避免后续 Phase 实施者
按陈旧描述走弯路。
1. Plan §6.4 文档同步段新增「Design 接口骨架覆盖里程碑」表:
- 4 行时间线记录 Phase 1 闭环 / Design 全 51 项接口骨架 / Contract §4.2
按 daoqi 原项目修订 / CLAUDE.md 加典型案例的 commit hash
- 末尾澄清「§3.4.1 撤销说明」与 §7.5 主路径关系,避免新人疑惑
2. Design §20.1 LobbyHandlers 加架构说明段:
- 明示实际拆为 AudioHandlers / DeviceHandlers / LocationHandlers /
ShakeHandlers / OpenurlTitleDataHandler 5 个 sub-struct(§8.1.1 /
§8.4.1 / §8.6 / §8.7 / §3.4.2 各自归属)
- LobbyHandlers 成为聚合点,依次调子 .register()
- 保留 monolithic register 速查表作为与 Contract §3.1 编号对账用
- 每行注释 §3.1 [N] 编号便于追溯
3. Contract §10 验收清单加 5 项硬约束:
§A 启动新增:
- app_*.js 4 个文件存在
- 15 个 app_* 全局变量命名 100% 正确(大小写硬约束)
- 大厅 vs 子游戏 app_Launchtype = "0"/"1" 字面差异
- app_gameid / app_compareCode 必须 undefined(早期 Contract 误列)
§B 桥接新增:
- H5 alert(msg) → UIAlertController 单按钮
- H5 confirm(msg) → 取消/确定 双按钮 + 正确返回 bool
至此 Plan / Design / Contract 三文档完全对齐 51 项接口(含 §7.5 app_*.js
15 个、§3.7 WKUIDelegate 2 个)。Phase 2 实施可直接对照 Contract §10
验收清单做契约测试。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 08:02:24 +08:00 |
|
 joywayerandClaude Opus 4.7
|
fae7b3da82
|
CLAUDE.md:新增第 2 个典型案例「H5 与原生通讯接口的真实路径」
补在 LaunchScreen 案例后,记录本轮发现:
- 症状:基于 Contract §附录 A 实现 window.settings.getXxx() polyfill 后
H5 行为不对,user 反馈"读不到数据"
- 错误做法:把附录 A 9 项 getter 实现为 polyfill,认为是 H5 主路径
- 原项目真相:grep "var app_" 在 daoqi/msext NewRootVC.m:1204 /
gameController.m:2160 / AppDelegate.m:259,发现 H5 主路径是写 .js 文件
+ <script src> 同步引入预生成的全局变量(不是 polyfill 函数)
- 同时发现 Contract §4.2 本身也写错(漏 6 项 / 多 2 项 / 文件名变量名混淆)
- 修复:撤销 §3.4.1 polyfill / 新增 §7.5 app_*.js 预注入 / 全面修订
Contract §4.2
教训 3 条:
1. 文档与代码冲突时原项目代码是真理。Contract 也可能写错(10 年前
写就的文档遗漏/误传是常态),必须用 grep 验证字面字符串
2. 优先验证字面字符串、不要依赖语义猜测:app_getbattery vs app_battery
一字之差,凭"应该就是 app_battery 吧"100% 犯错
3. iOS 最低系统版本是契约边界的天然过滤器:契约明示可不实现的旧接口
新项目应主动剔除,不要"出于完备性"反而做一份用不到的实现
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:59:17 +08:00 |
|
 joywayerandClaude Opus 4.7
|
47a89aa49c
|
Contract §4.2:按原项目代码全面修订 app_*.js JS 注入清单
Phase 1.17 联调准备时回归原项目(CLAUDE.md「先看原项目」规则),
grep daoqi/msext 全部 "var app_" 字面字符串,对照本文档 §4.2 早期记录
发现 4 类硬错误:
1. 多列了不存在的变量 — 原项目代码字面字符串里**根本不写**:
- app_gameid grep 0 命中 → 删除
- app_compareCode grep 0 命中 → 删除
2. 文件名与变量名混淆 — msext 文件名不带 get、变量名带 get:
- 文件 app_battery.js → 内容 "var app_getbattery=%lf;" (gameController.m:1191 / NewRootVC.m:1706)
- 文件 app_network.js → 内容 "var app_getnetwork=%d;" (gameController.m:1198 / NewRootVC.m:1714)
早期文档统一写成 app_battery / app_network 容易让实施者把变量名也
去掉 get,导致 H5 业务读到 undefined → 修正
3. 漏列 6 项真实存在的变量(gameController.m:2160 / NewRootVC.m:1204
/ AppDelegate.m:259 字面字符串里都有):
- app_version 硬编码 "1"
- app_Launchtype 大厅 0 / 子游戏 1(⚠️ L 大写)
- app_getwifisignalLevel 硬编码 1(⚠️ wifi 小写、signal/Level 大小写区分)
- app_gamename 大厅 = gameStart / 子游戏 = game_name
- app_invitationcode tuiguang_id 推广码 / 邀请码
- app_gamesname 独立 app_gamesname.js 文件 (var<两空格>new Array)
4. 大厅 vs 子游戏字面差异之前未注明:
- app_Launchtype = 0/1
- app_gamedir:gamedir vs gamefilepath
- app_gamestart + app_gamename:gamestart vs game_name
新增"修订记录"小节明示三处修订的代码依据行号,方便未来回溯。
至此 Contract §4.2 与 Design §7.5 完全对齐(15 项变量,全部对应到
daoqi 原项目真实代码行)。Phase 2 实施 AppDataWriter 时按 Contract §4.2
为准,H5 端验收按 Contract 不会再有差异。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:58:02 +08:00 |
|
 joywayerandClaude Opus 4.7
|
f68b0dba93
|
Design §7.5:补 H5 app_*.js 预注入文件机制(15 个变量),§3.4.1 polyfill 撤销
按 CLAUDE.md「先看原项目」规则深度审查 daoqi/msext NewRootVC.initJSdata /
gameController.initJSdata / AppDelegate 写入逻辑后发现:
**关键纠正**:H5 在 iOS 9+ 路径下的真实读值机制是 §4.2「写 app_*.js 文件
+ H5 <script src> 同步引入」,不是早期 commit 82bad8a 实现的
§3.4.1 window.settings.getXxx() polyfill(那是 §附录 A 旧桥 JSExport
路径,契约明示「新外壳如最低系统 ≥ iOS 14 可不实现」,本项目最低
iOS 15.6 → 无需实现)。
§3.4.1 处理:标题改为「⚠️ 撤销」+ 完整说明撤销原因;旧 polyfill 代码
骨架保留为 §3.4.1.legacy 作为数据源映射参考(不删除以保 git log 整洁,
也方便 Phase 2 实施者了解 9 项 getter 的等价数据来源)。
§7.5 新章节覆盖 15 个变量(用户列 14 + audit 发现 app_invitationcode):
- §7.5.1 完整映射表:变量名 / 大厅值 / 子游戏值 / 数据源 / 文件
- §7.5.2 4 个文件写入时机(一次性 vs 事件驱动)+ 为什么写文件不
evaluateJavaScript(保证 <script src> 同步引入时机)
- §7.5.3 AppDataWriter struct Swift 骨架(escape 4 种特殊字符避免
渠道字段含引号导致 H5 JS 解析错)
- §7.5.4 WebContainerViewController 接入点:loadFileURL 前 writeInitial
+ writeBattery + writeNetwork;持续监听 batteryLevelDidChange /
NWPathMonitor 触发增量重写
- §7.5.5 大厅 / 子游戏 / 弹层 4 维度差异(弹层不写 app_*)
- §7.5.6 与 msext 6 维度差异表(单一真相 / 转义 / 大小写严格)
- §7.5.7 §7.5 vs §3.4.1 vs §3.4 OverlayBridge vs §3.2 callback 四条
路径关系澄清
大小写严格契约:app_Launchtype(L 大写)/ app_getwifisignalLevel(wifi
小写、signal/Level 区分大小写)— 任何拼写错误会让 H5 业务读到 undefined。
至此 H5 端 JS 与原生通讯的完整 5 条路径全部覆盖:
1. WebViewJavascriptBridge 异步 callback(§5/§8/§3.1,22 项 handler)
2. Native→H5 反向 callback(§3.2,15 项)
3. app_*.js 文件预注入全局变量(§7.5,15 项)
4. 弹层 window.settings 同步 polyfill(§3.4,3 项 + §3.4.2 OpenurlTitleData)
5. WKUIDelegate alert / confirm(§3.7,2 项)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:51:13 +08:00 |
|
 joywayerandClaude Opus 4.7
|
33574a1cc2
|
Design §3.7:补 WKUIDelegate alert / confirm 2 项接口
覆盖契约 §4.3 H5 alert(msg) / confirm(msg) 触发原生弹窗的硬约束。
WKWebView 默认行为是直接吞掉 H5 的 alert/confirm 不弹任何窗口,必须实现
WKUIDelegate 才有响应。不实现的代价:H5 业务里 alert("登录失败")
之类的提示用户完全看不见,业务哑火无可视化报错。
§3.7 实现骨架:
- runJavaScriptAlertPanelWithMessage → UIAlertController .alert,
标题 appDisplayName,单"确定"按钮,completionHandler() 必须调一次
- runJavaScriptConfirmPanelWithMessage → "取消"/"确定" 两按钮,
completionHandler(false/true) 必须调一次
- 接入点:bridgedWebView.webView.uiDelegate = self 在 viewDidLoad
与 navigationDelegate 一起设置
§3.7.1 与 msext 4 维度差异表(标题来源 / 按钮顺序 / 样式 / 并发)+
明示"未实现导致的现象"提醒,方便 1.17 真机联调时排查。
至此契约 §3 + §附录 A + §4.3 共 51 项接口(22 异步 handler + 15 反向 callback
+ 3 弹层 polyfill + 9 同步 getter polyfill + 2 WKUIDelegate)的 Design 实现
骨架全部覆盖完整。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:33:33 +08:00 |
|
 joywayerandClaude Opus 4.7
|
5f5f6a9ff9
|
Design §3.4.2:补 OpenurlTitleData handler — 大厅打开 threeView 弹层
覆盖 §3.1 [15]OpenurlTitleData,是大厅 WVJB 桥的异步 handler,作用是
触发新的 OverlayViewController push 到导航栈。补在 §3.4 弹层桥章节内
(与 §3.4 settings polyfill 一脉相承),叫 §3.4.2。
§3.4.2 OpenurlTitleDataHandler:
- 节流:throttle final class 持 lastOpen Date,3 秒内重复调用被吞掉但
cb 仍要回(否则 H5 会等)
- 入参解析:url 必填;"title " 末尾有空格(msext 历史契约硬约束,必须
obj["title "]);data 透传;orientation 0/1 但本项目固定横屏
- 调 LobbyCoordinator.pushOverlay 打开 OverlayViewController
OverlayViewController 接入:
- WKWebViewConfiguration + UserScript 注入:
1. §3.4 OverlayBridge.polyfill(settings.backgameData/browser/finishweb)
2. window.app_data = "<escaped data>" 把业务数据透传到 H5
- 等价于 msext threeView 的 JSContext setObject:forKey:@"settings" 行为
§3.4.3 与 msext 5 维度差异表:UIWebView → WKWebView / "title " 末尾空格
严格保持 / 节流类替代静态变量 / UserScript atDocumentStart 注入避免 race
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:32:30 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a06a18c3aa
|
Design §8.7:新增 ShakeKit + 4 项接口实现骨架
覆盖 §3.1 [7]startshake / [8]stopshake / [9]SwitchShake +
§3.2 [5]shakeEnd 反向 callback 共 4 项接口。
§8.7.1 ShakeKit module 骨架:
- canShake / canVoice 开关位(@MainActor 实例属性,取代 msext 全局变量)
- handleMotionEnded(.motionShake) 集中处理:判 canShake → 播音效 → 触发 callback
- shake_sound_male.mp3 复用 AudioKit.AudioPlayer.playOnce 统一路径
§8.7.1 接入 VC:
- WebContainerViewController.canBecomeFirstResponder = true
- viewDidAppear 调 becomeFirstResponder(必要,否则 motionEnded 不触发)
- motionEnded(_:with:) 转发到 shakeKit.handleMotionEnded
§8.7.2 H5 handler 实现:
- 【7】startshake : canshake=YES + cb "startshake from accreditlogin"
(字面字符串带 "from accreditlogin" 后缀沿用历史)
- 【8】stopshake : canshake=NO + cb 同上
- 【9】SwitchShake : data int → canvoice = (1==YES) + cb "SwitchShake"
- 反向 [5]shakeEnd: data=nil,由 motionEnded → onShakeEnded 触发
§8.7.3 与 msext 6 维度差异表(全局变量 → 实例属性 / 音效统一 /
motionEnded 集中处理 / canBecomeFirstResponder 基类统一 / responseCallback 字面保留)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:31:08 +08:00 |
|
 joywayerandClaude Opus 4.7
|
d48ecc0473
|
Design §8.6:新增 LocationKit + 2 项接口实现骨架
覆盖 §3.1 [20]startlocation + §3.2 [6]getlocationinfo 反向 callback。
§8.6.1 LocationService module 骨架:
- AMapServices.bootstrap:privacy + key 启动期完成
- locateOnce() async/await 包装 AMapLocationManager.requestLocation
- startContinuous / stopContinuous + onContinuousUpdate 闭包钩子
- LocationInfo struct 转契约 §3.2 表 B 9 字段(latitude/longitude string
化用 String(format: "%f"),province 小写 p)
- LocationError.permissionDenied(code: 12)
§8.6.2 H5 handler(LocationHandlers.startLocation):
- 入参 data: int 1=持续 / 其他=一次性,与 msext RootVC 沿用
- 持续模式:onContinuousUpdate 挂钩 → 每次位置变化推 getlocationinfo
- 一次性:await locateOnce → 单次推
- responseCallback: "startlocation"
- 失败 callback 数据结构 errorCode 数字 12 不是 string(契约硬约束)
§8.6.3 与 msext 6 维度差异表(SDK 接入 / 隐私合规集中 / async wrapper /
入参解析 / latitude string / province 小写 p)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:30:06 +08:00 |
|
 joywayerandClaude Opus 4.7
|
c6221d30ca
|
Design §8.4.1:补 DeviceKit 9 项接口实现骨架
覆盖 §3.1 异步 handler 6 项 + §3.2 反向 callback 3 项,含本轮全文 audit
新发现的 3 项反向 callback(前一轮 audit 漏列):
§3.1 异步 handler 6 项(DeviceHandlers.register):
- 【10】vibrator — AudioServicesPlaySystemSound + cb "vibrator"
- 【11】repeatvibrator — 同 vibrator(msext RootVC.m:1820 沿用历史)
- 【12】canclevibrator — AudioServicesDisposeSystemSoundID + cb "canclevibrator"
- 【13】gamepastetext — UIPasteboard.general.string → cb 返回内容
- 【14】gameCopytext — UIPasteboard.general.string = data + cb "gameCopytext"
- 【21】getphoneInfo — cb "getphoneInfo"(大写 I)+ bridge.call("getphoneinfo"
小写 i!,data: 表 A 6 字段)
※ 大小写 i 不一致是 msext 历史契约,严格保持
§3.2 反向 callback 3 项(DeviceHandlers.bindReverseCallbacks):
- 【2】 getBattery — UIDevice.batteryLevelDidChangeNotification
data: String(format: "%.2f", level) 0~1
- 【3】 getnetwork — NWPath 变化 data: "1"无网/"2"WiFi/"3"蜂窝
- 【4】 appservice — App 前后台 data: "1"=后台/"2"=前台(命名错位沿用)
辨析 polyfill 与 callback 同名问题:契约 §3.4.1 polyfill 的同步 getter
`getbattery()` / `getnetwork()` 与 §3.2 反向 `getBattery` / `getnetwork`
名字重合但语义不同(前者 H5 同步问当前值,后者 Native 事件推变化),
两条路径并存互不矛盾。在 §8.4.1 新增辨析段澄清。
支撑组件骨架:BatteryMonitor / AppLifecycleMonitor(NetworkMonitor 已在
§8.4 现有);§8.4.2 与 msext 6 维度差异表。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:28:34 +08:00 |
|
 joywayerandClaude Opus 4.7
|
82bad8a85d
|
Design §3.4.1:补 window.settings 同步 getter polyfill(9 项含 getothername)
audit 第二轮发现:Contract §附录 A 旧桥 JSExport 共 28 方法,与 §3.1
异步主桥去重后多出 9 项 H5 同步只读 getter。前一轮 audit 只覆盖 §3.1
主表的 22 项异步 handler,这 9 项 getter 在 Design 完全无覆盖:
- getchannelName / getmarketname / getOther / getothername(name)
- getcompareCode / getbattery / getnetwork
- getGameinstall(name) / getGameplay(jsondata)
这些接口 H5 端形式是同步表达式(var ch = window.settings.getchannelName()),
WKWebView 无法同步返回 JS 值。唯一不破契约(H5 端零修改)的路径:
documentStart 注入数据快照 + 纯 JS getter polyfill,本地查询零延迟。
§3.4.1 新增覆盖:
- 9 项 getter 业务语义 + 数据源映射表(驳到 BundleConfig / DeviceKit /
SandboxPaths.installedSubGames)
- 数据快照时机:静态字段(11 项渠道注入)启动期一次;动态字段
(battery / network / installedGames)每次 loadFileURL 前重新快照
- SettingsBridgePolyfill.makeUserScript 完整 JS 注入骨架
- WebContainerViewController 接入点(runBootPipelineSteps 末尾、
loadFileURL 前调 installSettingsPolyfill)
- 与原 msext JSExport 5 维度差异表
待 Phase 2 实施时查清:getcompareCode 在 msext RootVC.m:1560 附近的真实
返回值语义(业务校验码,可能与 zip 版本号或固定值相关)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:24:01 +08:00 |
|
 joywayerandClaude Opus 4.7
|
92298b657e
|
Design §8.1:补 AudioKit 4 项 handler 完整实现骨架(方案 A 示范)
H5↔Native 接口实现层缺口补全启动(用户选 A:分散到各 module 章节补)。
本 commit 作为风格示范,后续 commit 按相同模板补 DeviceKit / Location /
Shake / OpenurlTitleData 共 11 项 handler。
§8.1 末尾新增两小节:
§8.1.1 — H5 audio handlers 实现骨架(185 行)
- AudioHandlers struct 集中注册 4 项 handler,便于发现 / 单测
- 【3】srcIsloop 入参解析 src/isloop → SandboxPaths.lobbyAssets/wav
+ AudioPlayer.playOnce / loopBackground / stopBackground 三态
+ responseCallback "Response from srcIsloop"
- 【4】prepareaudio 权限三态分支(denied → bridge.call alertMessage
而非原生弹窗,契约 §6.2 ifauth 语义)+ recordAndUpload
+ getaudiourl 反向 callback(time 字段必须 string,契约硬约束)
- 【5】mediaTypeAudio voiceCenter 总开关短路 + 下载 → AMR→WAV
→ playVoice with onStart/onEnd 桥到 gameui_play_voice/
gameui_stop_voice;失败也补 stop_voice 避免 H5 UI 卡"播放中"
- 【6】voicePlaying actor VoiceCenter 隔离 isEnabled 状态
§8.1.2 — 与原 msext 的差异(不要照抄的部分)
- 录音 UI / 上传后端 / 状态共享 / 总开关 / 路径拼接 5 维度对照表
骨架级别(结构 + 关键调用 + 注释 + 契约引用),不是产品代码 — 实施
Phase 2 时按此骨架落地真实代码。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 07:14:15 +08:00 |
|
 joywayerandClaude Opus 4.7
|
c95e80df9c
|
Phase 1.16:SceneDelegate 接 WebContainer + 退役 RootViewController
- SceneDelegate.scene(_:willConnectTo:options:) 中 rootViewController
从 M0 占位 RootViewController() 改为 WebContainerViewController()
- 删除 ylgamehall/RootViewController.swift(M0 + Phase 1.2–1.13 烟雾测试
整体退役);所有 print 验证记录在 git log,留着是死代码
- File System Synchronized Group 自动适应文件删除,无需改 pbxproj
- BuildProject 通过
至此 Phase 1.16 完成;启动链路:
SceneDelegate → WebContainerViewController.viewDidLoad
→ setupBridgedWebView + setupSplash + registerBridgeHandlers
→ runBootPipeline (ensureReady → fetch → resolve → upgrade → loadFileURL)
→ didFinish 淡出 splash
Plan 进度已勾选(§5 Phase 1.16 + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 04:23:52 +08:00 |
|
 joywayerandClaude Opus 4.7
|
6c943e2924
|
Phase 1.15:VibratorHandler 单次振动 handler
新增 ylgamehall/Source/Bridge/Handlers/VibratorHandler.swift:
- 无状态 public enum VibratorHandler,static func register(on:)
- 入参忽略(参考原 msext RootVC.m:1816-1818:time 参数实际不读取)
- 副作用:AudioServicesPlaySystemSound(kSystemSoundID_Vibrate)
- responseCallback:.string("vibrator"),与契约 §3.1 [10]一致
- 模拟器无振动硬件、调用静默忽略;真机才能感知
WebContainerViewController.viewDidLoad 新增 registerBridgeHandlers()
单一聚合点,Phase 2+ 在此追加更多 handler,便于发现 / 调试。
repeatvibrator / canclevibrator 严格守住 Plan §5 Phase 2.2 范围,不
提前实现。
BuildProject 通过
Plan 进度已勾选(§5 Phase 1.15 + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 04:02:33 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a20498f898
|
CLAUDE.md:新增「参考原项目 daoqi」规则 + LaunchScreen 典型案例
新增「原项目 daoqi:遇到问题时的参考来源」段:
- 明确原 daoqi 项目路径 ../daoqi/(与 ylgamehall 同级目录)
- 列出何时主动去读原项目(iOS 行为不符预期 / Info.plist 不确定 / 桥接
字段 / SDK 时序 / 渠道注入 / 老 hack 等)
- 怎么用原项目(先查关键文件 → grep 同名 key → 看 git log → 对照差异)
- 与「原则 B:内部自由重构」不冲突:参考是"必须做的清单"和"已踩过的坑"
备忘,不是"实现细节照抄"
新增 LaunchScreen orientation 典型案例:
- 症状:启动图横躺被旋转 90°
- 错误做法:凭文档推测、纯调 storyboard / contentMode / 反复换素材
- 去原项目找到 msext 用 LaunchImage Asset Catalog(iOS 8 时代)的"按
orientation 自动旋转 portrait 资源"魔法 — 这套机制 iOS 14+ 已 deprecated
- 新项目用 LaunchScreen.storyboard 没有自动旋转,必须三步组合:
1. 素材物理像素就是横屏(sips -r -90 一次性旋转 msext 残留 portrait 素材)
2. Info.plist 显式补无后缀 UISupportedInterfaceOrientations
3. UIImageView scaleAspectFit + 黑底
- iOS launch snapshot 缓存粘滞:开发期改启动相关任意一项后必须卸载重装
才能看到新效果;终端用户不受影响
文档索引表加入 ../daoqi/ 一行,加突出可见性。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 03:59:29 +08:00 |
|
 joywayerandClaude Opus 4.7
|
a6433d9e95
|
修复启动图方向:Info.plist 补顶层 UISupportedInterfaceOrientations
Xcode 26 General → Deployment Info UI 只把 orientation 写入带后缀的
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone / _iPad,**没有**
顶层无后缀的 UISupportedInterfaceOrientations。LaunchScreen 阶段 iOS 处于
device idiom 尚未识别的早期窗口,只看顶层无后缀 key —— 缺失时 fallback
到 portrait 渲染再被旋转 90° 适配横屏设备,视觉表现是 splash 内容横躺
在中间一个竖向矩形里。
修复:在 Info.plist 显式补一份无后缀 UISupportedInterfaceOrientations =
landscape。Build 后 .app 内 Info.plist 三个 key(无后缀 + ~iphone +
~ipad)齐全,LaunchScreen 正确按 landscape 渲染。
参考:原 daoqi/msext 用的也是顶层无后缀 key(iOS 9 时代写法)+
ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME 自动旋转 portrait 资源;新项目
LaunchScreen.storyboard 没有自动旋转魔法,必须显式补 key。
注:开发期间改 Info.plist orientation 后 Xcode 覆盖安装不会刷 iOS
launch snapshot 缓存,需长按删除 app 重装才能看到新效果;终端用户
不受影响(IPA 首次安装即按当前 Info.plist 渲染)。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 03:59:14 +08:00 |
|
 joywayerandClaude Opus 4.7
|
0ecfa4fb69
|
修复 SplashImage 方向:素材逆时针旋转 90° 改为真正横屏
源素材 docs/res/Res/Default-568h@2x~iphone.png 是 CgBI PNG,物理 640×1136
但画面内容是"横躺"在竖向容器里 —— msext 旧 LaunchImage 系统依赖 device
orientation 自动旋转,现代 LaunchScreen.storyboard 没有这个魔法,直接显示
会看到躺倒的画面(用户反馈:启动图的横竖方向错误)。
处理:
- 用 sips -r -90 -s format png 一次性把素材逆时针旋转 90° 输出为标准
PNG(顺便去掉 CgBI 优化标志),物理像素 1136×640,覆盖到
ylgamehall/Assets.xcassets/SplashImage.imageset/SplashImage@2x.png
- LaunchScreen.storyboard 中 <image> 的 width/height 从 640/1136 改为
1136/640;UIImageView 的 contentMode 从 scaleAspectFill 改为
scaleAspectFit。aspectFit 在比 16:9 更宽的现代横屏 iPhone(如 16 Pro
≈2.17:1)上左右补黑边(黑底无视觉违和),保画面完整不裁切 logo
- WebContainer 的 SplashOverlay 用同款 scaleAspectFit,避免启动 → 容器
视觉过渡时出现裁切方式跳变
- BuildProject 通过
Plan 进度已勾选(§5 Phase 1.14.b + §8 同步修订)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 03:10:53 +08:00 |
|
 joywayerandClaude Opus 4.7
|
f795957eb1
|
Phase 1.14.e:webView didFinish 后 0.3s 淡出 splash
把 WKNavigationDelegate.didFinish 里的 splash.isHidden = true 换成
UIView.animate(withDuration: 0.3) 渐变 alpha=0 + completion 内
removeFromSuperview,避免渲染瞬间硬切,也让 view 层级在大厅起来后
完全干净(无空持有的 splash 占位)。
BuildProject 通过
Plan 进度已勾选(§5 Phase 1.14.e + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 02:51:25 +08:00 |
|
 joywayerandClaude Opus 4.7
|
f247b81d6d
|
Phase 1.14.d:WebContainerViewController(16:9 letterbox + Splash + 启动流水线)
新增 ylgamehall/Source/WebView/WebContainerViewController.swift:
- 持有 BridgedWebView(间接持 BridgeBus)+ 文件内私有 SplashOverlay
- 16:9 letterbox 布局:required(aspect 16:9 + width/height ≤ superview)
+ low(width/height = superview) + center{X,Y} 居中。屏幕比 < 16:9 → 上下
黑边,> 16:9 → 左右黑边。Auto Layout 自动选短边贴边、长边居中
- SplashOverlay:SplashImage(scaleAspectFill) + UIProgressView + UILabel;
update(text:progress:) 切换状态机
- runBootPipeline 串:ensureReady → "拉取配置中..." → RemoteConfigClient.fetch
→ 分支(.shortText / .parsed → showmessage / IPA 升级 / LobbyZipUpgrader
with onProgress hop 到 MainActor) → "加载大厅..." → loadFileURL(lobbyIndex,
allowingReadAccessTo: lobbyRoot)
- 三种 alert:showBlockingAlert(短文本/showmessage,永停)/
showIPAUpgradeAlert(确定→openURL,永停)/ showFatalAlert(重试→重跑 pipeline)
- WKNavigationDelegate.didFinish 暂用 splash.isHidden = true(1.14.e 改为
0.3s 淡出动画)
- 横屏锁定 + statusBar 显示(与 RootViewController 一致)
不接 SceneDelegate,1.16 才换 rootViewController;本次仅落地容器代码。
BuildProject 通过
Plan 进度已勾选(§5 Phase 1.14.d + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 02:50:16 +08:00 |
|
 joywayerandClaude Opus 4.7
|
f3c66b670d
|
Phase 1.14.c:LobbyZipUpgrader 加 0…1 progress 回调
- upgradeIfNeeded 新增 onProgress: @escaping @Sendable (Double) -> Void 参数,
默认 no-op 兼容仅做版本对比的烟雾测试
- 新增文件内私有 DownloadProgressDelegate(URLSessionDownloadDelegate
+ @unchecked Sendable),在 didWriteData 回调里把 totalBytesWritten /
totalBytesExpectedToWrite 换算成 0…1 上报,clamp 到 [0,1]
- 通过 session.download(from:delegate:) 注入;下载完成后兜底报 1.0
(部分 server 不发 Content-Length 或末尾片段晚到,避免进度条停在 99%)
- RootViewController 烟雾测试用 ProgressTicker 把回调节流到 10% 阶梯打印,
避免几百行刷屏(NSLock 保护单 Int 状态,@unchecked Sendable)
- BuildProject 通过
Plan 进度已勾选(§5 Phase 1.14.c + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 02:46:11 +08:00 |
|
 joywayerandClaude Opus 4.7
|
ba34854133
|
Phase 1.14.b:LaunchScreen 替换为启动图(SplashImage)
- 将 docs/res/Res/Default-568h@2x~iphone.png(msext 时代竖图 640×1136
CgBI 压缩,29708 字节)拷入 ylgamehall/Assets.xcassets/SplashImage.imageset/,
Contents.json 声明 universal idiom @1x/@2x/@3x(仅 @2x 提供物理文件)
- 改写 LaunchScreen.storyboard:黑底 + 单个 UIImageView(image="SplashImage",
contentMode=scaleAspectFill),四边约束到 superview 完成全屏铺满
- 启动瞬间用户看到的是启动图而非黑屏;竖图在横屏设备上会被 aspectFill
上下裁剪,启动只展示 0.x s,接受此折衷,Phase 10 polish 时再考虑做
专门的横屏素材
- BuildProject 通过(55s)
Plan 进度已勾选(§5 Phase 1.14.b + §8)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
2026-06-22 02:43:14 +08:00 |
|
joywayer
|
45a8f483bc
|
Phase 1.14.a:替换 AppIcon 为 docs/res 内 msext 旧版图标
从 docs/res/Images.xcassets/AppIcon-1.appiconset 拷贝 9 个 png +
Contents.json 到 ylgamehall/Assets.xcassets/AppIcon.appiconset,替换
Xcode 默认空 universal 1024 模板。
- 9 个 png 覆盖 iPhone 29x29 / 40x40 / 57x57 / 60x60 三档分辨率 ×
@1x/@2x/@3x 组合(msext 历史素材,分辨率名 "129x29" 等是文件名
artifact,实际对应规格表里的 29x29)
- Contents.json 沿用旧版 iPhone-specific idiom,含 iOS marketing
1024 占位(无文件名)
- BuildProject 通过,actool 3 个 warning 暂不处理(iPad 76/83.5
缺失 → iOS 自动用 iPhone 缩放;1024 marketing 缺失 → App Store
上架要求,企业签不强制);Phase 10 上线前 polish 时补齐
- 顺手发现 BridgedWebView.swift:88 用 WKProcessPool 在 iOS 15+ 被弃用,
Phase 1.14.d 实施 WebContainer 时一并清理
Plan §5 1.14.a / §8 进度已勾选
|
2026-06-22 02:36:59 +08:00 |
|
joywayer
|
2280462a22
|
docs:Plan + Design 扩展 Phase 1.14 为 5 子项(含 AppIcon / LaunchScreen / progress / splash UI)
原 Phase 1.14 仅含"WebContainer 16:9 letterbox + 串接整链路",
漏掉了用户实际能看到的三件事:AppIcon / LaunchScreen 启动图 / zip
下载进度条。msext 上线时这三件都有,新外壳上线必须对齐。按用户偏好
方案 A 合并到 Phase 1.14,避免做两遍 WebContainer。
- Plan §5 Phase 1.14 拆为 1.14.a–e 五个子项
- 1.14.a AppIcon:拷贝 docs/res/Images.xcassets/AppIcon-1.appiconset
(9 个 png + Contents.json) 到 ylgamehall/Assets.xcassets
- 1.14.b LaunchScreen:Default-568h@2x~iphone.png → SplashImage
imageset;改写 LaunchScreen.storyboard 横屏铺满 UIImageView
- 1.14.c LobbyZipUpgrader 加 onProgress 回调(@Sendable (Double) -> Void)
- 1.14.d WebContainerViewController:splash 覆盖层(启动图 +
UIProgressView + 状态文字)+ 16:9 letterbox + 启动流水线串接
- 1.14.e webView didFinish 淡出 splash(0.3s 动画 + removeFromSuperview)
- Plan §8 进度追踪同步:5 个子项独立勾选条目
- Design §6.3.5 WebContainer 调用串完整重写:
- Splash UI 6 状态切换表(拼命启动中... / 拉取配置中... /
下载更新中 XX% / 加载大厅... / 淡出)
- runBootPipeline 完整 swift 骨架:含 .shortText 短文本响应处理 /
showmessage 阻塞处理 / IPA 升级 alert / zip 升级 onProgress 回调
- 所有失败 / 阻塞终态都用 UIAlertController(非 splash 文字):
短文本 / showmessage / IPA 升级 / 网络错误 / zip 下载失败
各场景的按钮文案 + tap 行为表
|
2026-06-22 02:35:37 +08:00 |
|
joywayer
|
87304552c3
|
docs:勾选 Plan §5 / §8 Phase 1.10-1.13 进度 + CLAUDE.md 新增「进度同步规范」
把 Plan 任务清单和进度追踪 checklist 同步到当前实际进度(1.10-1.13
均已完成 + 实测验证),并在 CLAUDE.md 写入"每完成 Phase 子项必须立即
更新 Plan 进度"的硬约束。
- Plan §5 Phase 1.10-1.13 任务清单 [ ] → [x]
- Plan §8 进度追踪同步:标注算法名 / 端到端实测结果(260→261 验证)
- CLAUDE.md 新增「进度同步规范」节,规定:
- Plan §5 + §8 每完成子项立即勾选,与 commit 同 commit 内完成
- 任务描述与实现有差异时同步修订(不允许 Plan 与代码长期不一致)
- commit message 末尾备注"Plan 进度已勾选"
- 理由:Plan 是项目记忆的唯一权威进度视图,新人 clone 后看 Plan
§8 即可知整体进度;与 commit log 双写互为校验
Plan 进度已勾选
|
2026-06-22 02:30:31 +08:00 |
|
joywayer
|
0887088bb8
|
Phase 1.13:LobbyZipUpgrader 完成 H5 zip 升级端到端链路
Phase 1 远程配置链路(1.10-1.13)最后一环:把 VersionResolver 解出的
gameZip URL 下载 + 解压 + 原子 rename 到 lobbyRoot,触发条件是远端
gameVersion > 本地 version.xml /game/version@value。
- 新增 ylgamehall/Source/Resource/LobbyZipUpgrader.swift
- public actor,单例 LobbyZipUpgrader.shared
- upgradeIfNeeded(resolved:) async throws -> Outcome
- .noop:远端版本 ≤ 本地,或 gameZip nil / 非法 URL,直接返回
- .upgraded(from:to:):完成升级,附前后版本号
- URLSession.download(from:) 异步下载(60s 请求 / 300s 资源超时),
HTTP 状态码非 2xx 抛 .httpStatus
- 解压到 caches/lobby-staging-{UUID}/ 隔离目录(不直接覆盖 lobbyRoot,
msext "removeItem + ZipArchive overWrite:YES" 半途崩溃会留半残;
我们 staging 完整解压成功后才 rename)
- 原子 rename:fm.removeItem(lobbyRoot) → fm.moveItem(staging,
to: lobbyRoot);任一步失败抛 .stagingMoveFailed,调用方决定回退
- 类型化 UpgradeError 5 种:badGameZipURL / downloadFailed /
httpStatus / unzipFailed / stagingMoveFailed
- 实现照搬 msext NewRootVC.m:1789-1869 downFileFromServer + unzip
+ unzipDone 的语义,但用现代 async + 原子 rename 替代 ASIHTTPRequest
+ detachNewThreadSelector + ZipArchive overWrite:YES
- RootViewController 烟雾测试串接 LobbyZipUpgrader:
调 upgradeIfNeeded → switch outcome → 升级后重新读 version.xml 验证
- 真机实测端到端链路:
第一次:本地 260 vs 远端 261 → 升级完成 261,耗时 2.007s
(下载 11.5 MB HTTP zip + ZIPFoundation 解压 + 原子 rename)
重新读 version.xml = 261 ✓ lobbyIndex 仍存在 ✓
第二次:本地 261 vs 远端 261 → .noop,耗时 0.001s(幂等验证)
Phase 1 远程配置链路(1.10-1.13)完整闭环。下一步 Phase 1.14
WebContainer 把这条链路插到 viewDidLoad 之前 + 16:9 letterbox 加载
lobbyIndex,第一次让真实大厅 H5 渲染出来。
|
2026-06-22 02:27:48 +08:00 |
|
joywayer
|
9e50c38323
|
Phase 1.12:LocalVersionReader 读 version.xml + ATS 全局放行
新增 LocalVersionReader 读两个本地版本号 + Info.plist 加
NSAllowsArbitraryLoads 全局放行 HTTP(为 Phase 1.13 zip 下载链路准备)。
- 新增 ylgamehall/Source/Resource/LocalVersionReader.swift
- public enum LocalVersionReader,nonisolated static 命名空间
- localAppVersion: Int 从 BundleConfig.shared.appVersion 转 Int
(沿用 ChannelConfig.plist 注入),解析失败返回 0
- localGameVersion: Int 用 Foundation XMLParser SAX 风格解析
SandboxPaths.lobbyVersionXML 的 /game/version@value 节点,
缺失 / 损坏返回 0(msext NewRootVC.m:664-682 `[nil intValue]=0`
兜底语义,是首装后首次升级的关键机制)
- subGameVersion(dir:start:) 子游戏版本号读取,Phase 6 用
- 内部 VersionXMLHandler 标 nonisolated(Swift 6 严格并发下
XMLParserDelegate 跨边界),SAX 风格只在 didStartElement 抓
version 节点的 value 属性
- Info.plist 加 NSAppTransportSecurity / NSAllowsArbitraryLoads = true
- 企业签 / 超签分发场景,后端域名易变(zip CDN / 配置服 /
七牛 / 错误日志上报等),白名单维护成本高于安全收益
- 全局放行的副作用主要影响第三方 SDK 流量;本项目无意外引入未审计
SDK,且第三方 SDK 自身一般也走 HTTPS,不受影响
- 详见 ADR-008 决策上下文与 CLAUDE.md「不上架 App Store,企业签 /
超签 / TF 分发」契约
- RootViewController 烟雾测试串接 LocalVersionReader:打印本地 vs 远端
对比 + IPA / zip 升级判定
- 真机实测(现网 .txt 本地 + 当前 IPA 内 gamehall.zip 解压物):
- 本地 appVersion = 43, 远端 = 43 → 不升级
- 本地 gameVersion = 260(IPA 内 2023-12 zip 的 version.xml),
远端 = 261 → 需要 zip 升级。Phase 1.13 将实现下载 +
解压 + 替换链路
|
2026-06-22 02:24:22 +08:00 |
|
joywayer
|
dfbc26a024
|
Phase 1.11:VersionResolver chulishengji 双子树合并算法
实现 msext NewRootVC.m:1372-1538 的 chulishengji 双子树合并算法的纯
函数 Swift 版本,把远端 RemoteConfig 化简为 5 个目标字段决策结果
(appVersion / appDownload / gameVersion / gameZip / showmessage)。
- 新增 ylgamehall/Source/Network/VersionResolver.swift
- public enum VersionResolver + 纯函数 static func resolve
- public struct ResolvedVersion: Sendable 含 5 字段
- 内部 SubtreeExtract 中转 4 字段累加结果(每层非空才覆盖)
- extractAgentSubtree 4 层(agent → channel → market → game 嵌
market 命中后)按 NewRootVC.m:885-1013 复刻
- extractGameSubtree 4 层(game → game-self → channel → market)
按 NewRootVC.m:1015-1180 复刻
- mergeIPA 默认 agent 赢;game.appVersion > agent.appVersion 时
game 赢(NewRootVC.m:1416)
- mergeZip 默认 game 赢;agent.gameVersion > game.gameVersion 时
agent 赢(NewRootVC.m:1456)
- showmessage 任意子树非空就用,最后写赢;回退到 config.showmessage
- 字段名 game_download vs game_zip 由 SubtreeExtract.gameZip 统一吸收,
Codable 模型已用 gameZip 命名,故无需重复查两个 key
- RootViewController 烟雾测试串接 RemoteConfigClient + VersionResolver,
打印 chulishengji 合并后的 5 字段
- 真机实测(现网 .txt JSON):
- appVersion = 43 → 等于本地 BundleConfig.appVersion,不触发 IPA 升级
- gameVersion = 261 → 远端最新版(本地 version.xml 待 Phase 1.12 读取
后对比;首装后 version.xml 实测 261 等于本地)
- gameZip = http://tsgames.daoqi88.cn/zip2/gamehall.zip
(HTTP,Phase 1.13 下载需 ATS NSExceptionDomains daoqi88.cn)
- appDownload = itms-services://... 企业签 manifest URL
- showmessage = ""(无运营公告)
- 单测目前未写(等 Phase 0.2 单测 target 建立后补齐),但生产数据
已验证算法正确
|
2026-06-22 02:18:27 +08:00 |
|
joywayer
|
1bc287bafc
|
Phase 1.10:RemoteConfigClient 完整版(HTTPS + cache-busting + 短文本 + FlexibleString)
第二轮调研 daoqi/NewRootVC.m 全文核对后,把对原项目远程配置流程的精确
理解固化进 ADR-008 + Design §6.3.2,并补齐 RemoteConfigClient 缺失的
3 个契约(cache-busting query、短文本响应识别、字段类型混乱兼容)。
核心修正:线上热路径是 chulishengji 双子树合并算法(NewRootVC.m:1372-1538),
不是 onnet 简单 4 层覆盖。viewWillAppear 与 gonet 都会强制把当前 agent 的
gamelist 提到 self.gamelist,只要服务器 JSON 含 agent.gamelist(线上 100% 都有),
self.gamelist 就非 nil,走 else 分支 → chulishengji。本次烟雾测试实测:
agent[0].gamelist = 7 个,完美印证 chulishengji 必要性。
- RemoteConfigClient 新增 cache-busting query:?vXXXXXXXXYYYYYYYY(两段 8 位 hex
连写,无 =,照搬 msext NewRootVC.m:1226 契约),每次重试都重新生成
- 新增 RemoteConfigOutcome enum:.parsed(RemoteConfig) 与 .shortText(String)
两种成功状态。短文本响应(trim 后 utf16.count ≤ 100)是服务端运营杀手锏 #1,
msext 当 alert 文本弹窗 + 停止重试(NewRootVC.m:1239-1243)
- UTF-8 解码 + trim 跟 msext NewRootVC.m:1237-1238 严格对齐
- 所有 String? 字段改用 decodeFlexibleStringIfPresent 扩展(兼容 number-as-string):
agentid / channelid / marketid / gameid / showmessage / appVersion / appDownload
/ gameVersion / gameZip 全部 9 字段。msext 用 NSNumber.intValue/description 静默
吞下 number,新外壳 Codable 严格类型必须显式处理
- 远端 URL 从 http 改为 https(后台已切,无需 ATS 例外)
- Plan ADR-008 修订为含 9 个子节(A-I)的精确决策记录,含 chulishengji
双子树算法、agent 子树结构、game 子树结构、字段级合并优先级、决策顺序、
与 msext 差异表
- Design §6.3.2 VersionResolver 重写:从"简单 4 层 reduce"改为"chulishengji
双子树合并 + 字段级合并",附 13 项单测覆盖矩阵
- RootViewController 烟雾测试切换为 outcome enum switch case,覆盖 .parsed
与 .shortText 两条路径
- 实测烟雾:HTTPS 拉到 30 KB JSON 解析 .parsed 成功(耗时 2.7s 含 TLS 握手),
agent[0].gamelist 含 7 个 game 节点,证明 chulishengji 路径是热路径
|
2026-06-22 02:14:57 +08:00 |
|
joywayer
|
5f032342e7
|
docs:Plan / Design 补 Phase 1 远程配置 + zip 升级流水线(ADR-008)
调研 daoqi/msext 原项目 NewRootVC.m:1551-1621 viewWillAppear、1372-1492
chulishengji、1789-1869 downFileFromServer 三段核心代码后发现 Plan 初版
Phase 1 漏掉了启动流程里最关键的一环——从 gameconfig 拼远端 .txt 拉
真实配置、做 agent → channel → market 4 级覆盖、对比本地版本、必要时
下载并替换 H5 zip。不做的代价是:上线后用户永远停留在 IPA 内打包瞬间
的 H5 旧版本,H5 团队任何更新都到达不了。
- Plan Phase 1 任务清单从 13 项扩为 17 项,插入 4 个新子项:
- 1.10 RemoteConfigClient(actor,URLSession async + 指数退避)
- 1.11 VersionResolver(纯函数 + 单测,reduce 实现 4 级覆盖)
- 1.12 LocalVersionReader(解析 version.xml)
- 1.13 LobbyZipUpgrader(actor,原子 rename)
原 1.10-1.13 后移为 1.14-1.17。已完成的 1.1-1.9 标记为 ✅
- Plan 新增 ADR-008 完整记录决策背景 / 触发事件 / 与 msext 实现差异
对照表 / 守护条款 / 子游戏复用规划
- Design §6.3 整节重写:从原"ConfigService 一锅端"扩为完整 4 模块流水线
(§6.3.1-6.3.7),含 Codable RemoteConfig 模型、纯函数 VersionResolver、
nonisolated LocalVersionReader、actor LobbyZipUpgrader、WebContainer
调用串、与 msext 差异表、子游戏 Phase 6 复用规划
- 关键设计纠正:
- 远端 URL 不带 SERVERNew 前缀,仅 gameconfig.replace("-","/") + ".txt"
- 4 级覆盖:顶层 → agent → channel → market 深层胜出 + game 子树覆盖
agent 子树
- 解压策略:staging-{uuid}/ 临时目录 + 原子 moveItem rename,不学
msext "目录名 +1" 累积 hack
|
2026-06-22 00:42:30 +08:00 |
|
joywayer
|
65505b0a79
|
Phase 1.9:BridgedWebView 封装 WKWebView + JS 注入 + BridgeBus
WebContainerViewController(Phase 1.10)的视图层依赖项:把 WKWebView 配置 /
WVJB.js 注入 / 桥总线 / scrollView 契约配置 4 件事一锅端打包成单一 UIView,
后续大厅 / 子游戏 / 弹层 VC 直接 add 进自己的内容区即可。
- 新增 ylgamehall/Source/WebView/BridgedWebView.swift
- @MainActor final class,继承 UIView
- 内含 public let webView: WKWebView 与 public let bridge: BridgeBus
- init() 全自动:
- WKWebViewConfiguration 按契约 §4.1:
defaultWebpagePreferences.allowsContentJavaScript = true(iOS 14+
现代 API,替代已弃用的 preferences.javaScriptEnabled)、
javaScriptCanOpenWindowsAutomatically = NO、minimumFontSize = 10、
allowsInlineMediaPlayback = true、processPool = SharedProcessPool.shared
- 用 WKUserScript(.atDocumentStart) 把 WebViewJavascriptBridge.js 注入
所有加载的 H5 页面,确保 H5 业务代码运行前 bridge 已就绪
- 创建 BridgeBus,自动向 controller 注册 WVJBHandler 通道
- scrollView:bounces=NO / isScrollEnabled=NO(契约硬约束,漏则 H5
上下滑动失控)/ contentInsetAdjustmentBehavior=.never / 双向无指示器
- WebView 撑满 self,自动布局
- SharedProcessPool 命名空间:跨 WebView 共享 WKProcessPool,Cookie /
资源缓存复用,启动加速(Design §3.5)
- BuildProject 通过,下一步 Phase 1.10 WebContainerViewController 用
BridgedWebView 嵌入 + 16:9 letterbox + 加载 lobbyIndex
|
2026-06-22 00:19:51 +08:00 |
|
joywayer
|
af1a2028e5
|
Phase 1.8:WebViewJavascriptBridge.js 协议源码入 Bundle
H5 端桥协议实现,与 BridgeBus 的消息格式 1:1 对齐:
H5 → Native 走 window.webkit.messageHandlers.WVJBHandler.postMessage(...),
Native → H5 走 window.WebViewJavascriptBridge._handleMessageFromObjC(base64)。
- 新增 ylgamehall/Resources/JS/WebViewJavascriptBridge.js(~130 行)
- bridge.registerHandler(name, fn):H5 注册等待 Native callHandler
- bridge.callHandler(name, data, responseCallback):H5 主动调 Native
- bridge._handleMessageFromObjC(b64):Native 入口,base64 → JSON parse
→ 派发到 H5 handler 或匹配响应 responseCallbacks
- base64 解码用 decodeURIComponent + atob 处理 UTF-8 多字节字符
- 兼容 marcuswestin 旧式 window.WVJBCallbacks 队列握手:注入后
立即 flush 队列(H5 业务代码无需修改)
- RootViewController 加 Phase 1.8 烟雾测试:Bundle.main.url 找到 JS 文件
并打印 bytes / head;synchronized group 会把 Resources/JS/ 子目录
扁平化到 bundle 根,注释里说明此约束
- BuildProject 通过,bundle/WebViewJavascriptBridge.js 实际存在
- 下一步 Phase 1.9 BridgedWebView 用 WKUserScript(.atDocumentStart) 注入
本文件到所有 H5 页面,桥真正生效
|
2026-06-22 00:16:55 +08:00 |
|
joywayer
|
4a6b1e1631
|
Phase 1.7:BridgeBus 实现 H5↔Native 消息总线 + WKScriptMessageHandler
桥核心的运行时层:把 BridgeProtocol 落到 WKWebView 上,承担 JS → Native
消息分发 + Native → H5 主动调用 + 双向 callback 配对。
- 新增 ylgamehall/Source/Bridge/BridgeBus.swift
- @MainActor final class,BridgeProtocol + WKScriptMessageHandler 双重
conform;init(webView:, controller:) 自动向 controller 注册 WVJBHandler
通道
- JS → Native(userContentController:didReceive:)
- body 兼容单条 dict 与 batch [[dict]]
- 优先识 responseId → 查 pendingCallbacks 派给 Native 端 callback
- 否则按 handlerName 查 handlers,构造异步 responseCallback 闭包,
handler 是 @Sendable async 用独立 Task 执行
- Native → H5(call(_:data:callback:))
- 拼 payload {handlerName, data, callbackId?},base64 后注入
WebViewJavascriptBridge._handleMessageFromObjC('<base64>')
- callback 用 nativeCallbackCounter 递增成 objc_cb_<n> 入
pendingCallbacks,等 JS 响应回收
- sendResponseToJS:拼 {responseId, responseData} 走同一通道
- makeResponseCallback 辅助方法:把多层嵌套闭包拆出来,避免
Swift 6 类型推断在深嵌套 + actor 隔离上 ICE
- 资源生命周期:webView 弱引用避免循环;BridgeBus 由 controller.add(self)
retains,WebView 释放后 controller 一并清空 handler 列表自然 release
- BuildProject 通过,Swift 6 严格并发 0 warning;下一步 Phase 1.8 注入
marcuswestin/WVJB JS 端配套协议即可双向消息打通
|
2026-06-22 00:11:54 +08:00 |
|
joywayer
|
f740274d59
|
Phase 1.6:BridgeProtocol + BridgeData 协议骨架
桥核心的纯类型层,不依赖 WKWebView,便于 Phase 1.7 BridgeBus 实现与
单测注入 mock 都基于此抽象。
- 新增 ylgamehall/Source/Bridge/BridgeProtocol.swift
- protocol BridgeProtocol: AnyObject, Sendable
- register(_:handler:) 注册 H5 → Native handler,重复名覆盖
- call(_:data:callback:) Native → H5 主动调用,callback 可选
- typealias BridgeHandler = @Sendable (BridgeData?, BridgeCallback?)
async -> Void,允许 handler 内部 await
- typealias BridgeCallback = @Sendable (BridgeData?) -> Void
- enum BridgeData: Sendable 六态(string/number/bool/null/array/object),
与 JSON 值同构
- BridgeData JSON 互转:
- init?(jsonObject:) 从 JSONSerialization 输出递归构造;NSNumber
Bool/Double 鉴别用 CFGetTypeID 避免 NSNumber(value:true) 被当 1.0
- var jsonObject: Any 反向,供 evaluateJavaScript 拼 JSON 字符串
- BridgeData 访问语法糖:asString/asInt/asDouble/asBool/asArray/asObject
+ subscript(key:) 对象字段访问 + subscript(index:) 数组访问
- BridgeData 字面量构造:ExpressibleByStringLiteral/IntegerLiteral/
FloatLiteral/BooleanLiteral/ArrayLiteral/DictionaryLiteral,
fixture / handler 代码 ["k": 1, "v": true] 直接构造 .object
- BuildProject 通过,Sendable 全自动派生
|
2026-06-22 00:05:26 +08:00 |
|
joywayer
|
463f0fc6fe
|
Phase 1.5:ResourceUnzipper 解压 gamehall.zip 到 Caches 全链路打通
启动期完成「ChannelConfig.plist 读 → SandboxPaths 算路径 →
gamehall.zip 解压」三步,lobbyIndex 真实存在于沙盒 Caches,
为 Phase 1.10 WebContainer file:// 加载就绪。
- 新增 ylgamehall/Source/Resource/ResourceUnzipper.swift
- public actor,全局单例 ResourceUnzipper.shared
- ensureReady() async throws:检查 lobbyVersionXML 不存在则调
FileManager.unzipItem (ZIPFoundation 扩展) 解压 Bundle 内
gamehall.zip 到 SandboxPaths.lobbyRoot
- UnzipError 类型化错误(bundleZipNotFound / unzipFailed)
- 幂等:已就绪直接 return,并发调用安全(actor 串行)
- 新增 ylgamehall/Resources/gamehall.zip:从 docs/res 拷一份入
Bundle(11.5 MB,2023-12 旧版;上线前由 H5 团队替换最新版,
ADR-002 已记录)
- Swift 6 严格并发适配:
- SandboxPaths:所有 static 成员标 nonisolated(无状态命名空间,
从 actor / 非 MainActor 上下文都能直接读 caches/documents/bundle
及 lobby 路径计算)
- BundleConfig:class 整体 nonisolated(Sendable + all-let 属性,
跨 actor 边界安全),允许 ResourceUnzipper actor 读 channel 配置
- RootViewController 补 ResourceUnzipper 烟雾测试 Task:
await ensureReady() 后打印耗时 + lobbyIndex 是否真实存在
- 真机验证(iOS Simulator):首次 1.266s 解压成功,二次启动 0s 跳过
|
2026-06-22 00:01:08 +08:00 |
|
joywayer
|
b866ee212c
|
Phase 1.4:引入 ZIPFoundation SPM 依赖(v0.9.20)
ResourceUnzipper(Phase 1.5)的前置:用 ZIPFoundation 替代旧 ZipArchive
解压 gamehall.zip。ADR-006 纯 SPM + Vendor 策略下的首个 SPM 依赖。
- 引入:https://github.com/weichsel/ZIPFoundation
- 版本 0.9.20(Up to Next Major,commit 22787ffb59de)
- License: MIT;纯 Swift 实现,Xcode 26 原生兼容
- 链接到 target ylgamehall
- pbxproj:XCRemoteSwiftPackageReference + XCSwiftPackageProductDependency
写入;Package.resolved 锁定版本,多机 / CI 复现
- BuildProject 通过;ZIPFoundation 全部 .swift 由 SwiftCompile 实际
参与构建,与 ylgamehall target 链接完成
- 附带:ChannelConfig.plist 移除内联说明注释(Phase 1.1 commit 后由
维护者整理),不影响 plist 数据结构
|
2026-06-21 23:56:32 +08:00 |
|
joywayer
|
730574a2e2
|
Phase 1.3:SandboxPaths 统一沙盒 / Bundle / H5 路径入口
集中管理 NSSearchPathFor... 调用,避免散落,方便后续 ResourceUnzipper
(Phase 1.5)与 WebView 加载(Phase 1.10)复用。
- 新增 ylgamehall/Source/Resource/SandboxPaths.swift
- 基础:caches / documents / bundle
- 大厅 H5:lobbyRoot / lobbyIndex / lobbyVersionXML(基于 BundleConfig
当前渠道值动态拼接)
- 子游戏 H5:subGameRoot(_:) / subGameIndex(_:_:)(Phase 6 调用,
入参传 SwitchOverGameData 解出的 dir/start)
- 全部 enum + static,无状态,Swift 6 严格并发零负担
- RootViewController.viewDidLoad 补 SandboxPaths 烟雾打印 6 项路径,
便于首次启动 console 校验:
- lobbyIndex 应为 {Caches}/FtJf073.../gamehall/index.html
- lobbyVersionXML 应为 {Caches}/FtJf073.../gamehall/version.xml
- BuildProject 通过,synchronized group 自动收编新文件
|
2026-06-21 23:52:23 +08:00 |
|
joywayer
|
dcc12bb76d
|
Phase 1.2:BundleConfig 读 ChannelConfig.plist 完成 11 项渠道注入加载
- 新增 ylgamehall/Source/Resource/BundleConfig.swift
- 11 个 public let 属性(qiniuDomain / gameId / channel / gameDir
/ gameStart / gameConfig / market / agent / appVersion / other
/ appleConfig)
- public init(bundle: Bundle = .main) —— 默认走 main bundle,单测
可注入 mock bundle 验证不同 plist fixture
- PropertyListSerialization 反序列化 ChannelConfig.plist
- final class + Sendable(all-let,String 是 Sendable,Swift 6
严格并发下编译器静态校验通过)
- 单例 BundleConfig.shared,业务统一通过此访问
- RootViewController.viewDidLoad 加烟雾测试 print 11 项值,首次运行
真机/模拟器即可在 console 验证读取正确
- BuildProject 通过,synchronized group 自动收编 BundleConfig.swift
为 source(.swift 文件类型走 SwiftCompile,不再卡 plist 那样的
resource 扁平化问题)
- 单元测试待 Phase 0.2 单测 target 建立后补齐
|
2026-06-21 23:47:23 +08:00 |
|
joywayer
|
c25a7514c7
|
fix(ChannelConfig.plist):注释去掉 -- 双连字符避免 XML parse 错误
XML 注释规范禁止 -- 出现在注释内,原注释包含的 codesign 命令示例
(--force / --sign / --entitlements)触发解析错误,Xcode 直接打开
plist 报 "XML Parse Error: Comment must not contain '--'"。
把详细命令示例移除,只保留单行简短指引,命令示例已在 Design
§7.0.3 写过,不在 plist 重复。
|
2026-06-21 23:45:38 +08:00 |
|
joywayer
|
8c2ffa3106
|
Phase 1.1:渠道注入用 ChannelConfig.plist 替代 msext 目录树(ADR-007)
契约 §0.3 描述 msext 把 11 个渠道值编码为 Bundle 根的 11 个空目录
子文件夹名(目录名本身就是值)。在 Xcode 26.5 默认的 synchronized
group 下,此机制不兼容(子目录被扁平化、11 个 .gitkeep 撞名;
folder reference 拖入流程失效;Run Script Phase 受 sandbox 阻碍且
默认 Based on dependency analysis 让 clean build 也不跑)。
CLAUDE.md 原则 B 落地——原生内部自由重构,不照搬 msext。改用单一
plist 存储等价语义,H5 端通过 app_data.js 看到的 11 个 JS 全局变量
行为完全不变(契约边界在 BundleConfig.shared.xxx,与底层无关)。
多渠道分发用 plutil -replace + 重签 + 重打包,工作量与 mv 目录名
重签完全相同。
- 新增 ylgamehall/Resources/ChannelConfig.plist:11 个 string key
含母包默认值(沿用 msext 现网值);synchronized group 自动入 Bundle
- Design §7.0 / §7.2 重写为 plist 方案;BundleConfig 代码骨架改为
PropertyListSerialization + init(bundle:) 可注入式
- Plan §5 Phase 1.1 任务清单从 4 个子项简化为 1 项;ADR-004 文字
同步;新增 ADR-007 完整记录决策背景 / 触发事件 / 工程兼容性分析
/ 理由 / 守护条款
- pbxproj:ENABLE_USER_SCRIPT_SANDBOXING 残留为 NO(前期 Run Script
方案探索时关闭,plist 方案下不再需要,但未恢复以避免再次 UI 操作;
无 Run Script 故无实际安全暴露面,未来可随时改回 YES)
- BuildProject 验证:plist 已落在 .app 根,plutil -p 输出 11 个键值
完整
|
2026-06-21 23:41:25 +08:00 |
|
joywayer
|
cabdc1ed42
|
docs:剥离 docs/res/ 为私人素材池,废止仓库根 Resources/ 假设
明确 docs/res/ 是项目维护者的私人原始素材池,与项目架构无关;项目
代码 / 构建脚本 / Xcode 工程都不感知它的存在,需要时从中拷一份到
项目按现代实践规划的位置。原 ADR-004「Resources/ 在仓库根 +
Build Phase 引入」语义已崩塌(gamehall.zip / Images.xcassets / Res/
均被挪入 docs/res/),修订为「项目内资源目录由各 Phase 按需落地」:
静态 Bundle 资源走 ylgamehall/Resources/、原生 Asset Catalog 走
ylgamehall/Assets.xcassets/、渠道注入产物走 ylgamehall/ChannelInjection/
(.gitignore + Scripts/inject_channel.sh 生成)、闭源 SDK 走 Vendor/。
- CLAUDE.md 原「Resources/ 目录约定」节改写为「docs/res/ 与项目
资源的关系」,禁止把 docs/res 当项目资源目录使用
- Design §7.0 整节重写为「项目资源目录约定」三小节(docs/res
与项目无关 / 项目内资源目录在 Phase 实施时按需落地 / 渠道注入
运行时路径)
- Plan ADR-004 修订为「项目资源目录由各 Phase 按需落地」;ADR-002
/ §2.2 / Phase 1.1.a-d / 1.5 同步调整为 docs/res → ylgamehall/Resources
的拷贝模型;Phase 1 任务清单细化渠道注入由 Scripts/inject_channel.sh
生成
- 物理迁移:仓库根 Resources/gamehall.zip / Images.xcassets / Res/
全部 rename 到 docs/res/(git rename detection 自动识别)
|
2026-06-21 22:32:38 +08:00 |
|
joywayer
|
17a8b96cf8
|
docs:依赖管理改为纯 SPM + Vendor,移除 CocoaPods(ADR-006)
调研发现 Qiniu SDK 已官方支持 SPM(https://github.com/qiniu/objc-sdk
v8.9.x),AMap 仍仅支持 CocoaPods 或手动 XCFramework;CocoaPods 1.15.2
不兼容 Xcode 26 的 PBXFileSystemSynchronizedRootGroup,需绕道 Bundler
才能升到 1.16+。综合性价比决定不引入 CocoaPods 工具链,所有闭源 SDK
(含 AMap)统一走 Vendor .xcframework 手动接入。
- Design §14.1 三层策略改写为两层(SPM + Vendor),新增 Vendor 接入
标准流程 6 步法
- Design §14.2 AMap 接入方式从 CocoaPods 改为 Vendor,Qiniu 标注 SPM
URL 与版本
- Plan §2.3 / §5 Phase 5.1 / §6.1 / §8 同步 AMap Vendor 化
- Plan 新增 ADR-006 完整决策记录
- CLAUDE.md 新增「依赖管理约定」节,禁止引入 CocoaPods
|
2026-06-21 21:46:19 +08:00 |
|
joywayer
|
2a3d985dd7
|
调整极光
|
2026-06-21 20:48:41 +08:00 |
|
joywayer
|
d18ea4f1c5
|
新增所需资源,修改优化文档
|
2026-06-21 20:40:46 +08:00 |
|
joywayer
|
4e4290ded6
|
建立 .gitignore,清理误入库的 xcuserdata
- 标准 iOS/Swift 模板:忽略 build/DerivedData/xcuserdata、
SPM .build、CocoaPods Pods/、签名证书、本地敏感配置
- 保留 Package.resolved / Podfile.lock / Vendor/ 入库
以保证多人 + CI 依赖锁定与闭源 SDK 可分发
- 同时把误入库的 xcuserdata/xcschememanagement.plist 从
版本控制移除(本地文件保留)
|
2026-06-21 20:05:21 +08:00 |
|
joywayer
|
7a84223841
|
M0:项目基础配置就绪(横屏锁定、Swift 6、代码启动)
- 锁定 iPhone/iPad 仅横屏(LandscapeLeft/Right),契合 H5 端
1280×720 设计分辨率;UIRequiresFullScreen=true 防 iPad
多任务下方向不锁
- Swift Language Version 升到 6.0(配合已启用的
Approachable Concurrency + MainActor 默认隔离)
- 删除默认 Main.storyboard / ViewController.swift 模板,
改用 SceneDelegate 代码创建 RootViewController 启动,
Build Settings 同步清空 INFOPLIST_KEY_UIMainStoryboardFile
- Info.plist 补齐契约项 UIStatusBarHidden=NO、
UIViewControllerBasedStatusBarAppearance=YES,配合
AppDelegate 启动时设置 applicationSupportsShakeToEdit=true
覆盖契约 §4.2.1 摇一摇前置
- BuildProject 通过验证
|
2026-06-21 20:03:33 +08:00 |
|
joywayer
|
1da2884358
|
CLAUDE.md:新增「及时提交」规则到提交规范
每完成一个可独立验证的工作单元(bug 修复 / 配置变更 /
文档更新 / 依赖升级 / M 里程碑子项)就主动提议提交,
BuildProject 通过 / 关键路径手测通过即立即提议。
理由:项目处于 greenfield 重写阶段,commit 颗粒越小
回滚成本越低;CLAUDE.md / docs 与 git log 一起构成
项目记忆,commit message 是"为什么"的第一现场。
|
2026-06-21 20:03:14 +08:00 |
|
joywayer
|
beb05ab451
|
添加文档和CLAUDE.md
|
2026-06-21 19:39:22 +08:00 |
|
joywayer
|
108b359f96
|
Initial Commit
|
2026-06-21 19:37:33 +08:00 |
|