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>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
246f215d6f
commit
3039daf903
@@ -93,6 +93,20 @@ daoqi 仓库当前并存两条工作线:
|
|||||||
- **优先验证字面字符串、不要依赖语义猜测**:`app_getbattery` 与 `app_battery` 一字之差,AI 助手凭语义"应该就是 app_battery 吧" 100% 会犯错
|
- **优先验证字面字符串、不要依赖语义猜测**:`app_getbattery` 与 `app_battery` 一字之差,AI 助手凭语义"应该就是 app_battery 吧" 100% 会犯错
|
||||||
- **iOS 最低系统版本是契约边界的天然过滤器**:契约里 iOS<9 路径明示可不实现的接口(如 §附录 A 旧桥),新项目最低 iOS 15.6 应当主动剔除,不要"出于完备性"反而实现一份用不到的 polyfill
|
- **iOS 最低系统版本是契约边界的天然过滤器**:契约里 iOS<9 路径明示可不实现的接口(如 §附录 A 旧桥),新项目最低 iOS 15.6 应当主动剔除,不要"出于完备性"反而实现一份用不到的 polyfill
|
||||||
|
|
||||||
|
### 典型案例:`app_*` 注入时序问题(原则 A 不可妥协)
|
||||||
|
|
||||||
|
- **症状**:H5 端读到 `app_gameconfig` 等关键变量是 H5 zip 包内默认占位值,不是 ChannelConfig.plist 的实际渠道值
|
||||||
|
- **调研发现**(按 CLAUDE.md「先看原项目」规则):原 msext 在 `NewRootVC.viewDidLoad → initJSdata`(`loadFileURL` **之前**)用 `writeToFile:` 把 `app_data.js` 写入沙盒,H5 启动时通过 `<script src>` 同步引入就能直接读到实际值。新外壳改用现代化的 `WKUserScript(.atDocumentEnd)` + `evaluateJavaScript` 路径,**时序无法 1:1 等价**:
|
||||||
|
- `documentStart` 注入会被 H5 自带 `app_*.js` 内的 `var app_xxx = "默认值"` 声明覆盖回退
|
||||||
|
- `documentEnd` 注入又太晚(H5 业务顶层 `<script>` 在 documentEnd 之前已经跑过 `var x = app_xxx`,读到的是默认占位)
|
||||||
|
- WKUserScript 没有"在 `<script src="app_data.js">` 之后、业务 `<script>` 之前"的精确时机
|
||||||
|
- **错误做法**:提出 3 个方案,其中 A "H5 改一行:把 `app_xxx` 用法移到 `DOMContentLoaded` 之后"和 B "H5 改 4 个文件:用 `if(typeof xxx==='undefined')` 守卫" 都要求 H5 配合改动 — **违反原则 A 第一准则**,立即否决
|
||||||
|
- **正确做法**:方案 C — 原生 Swift 端**回到 msext 老路**,在 `loadFileURL` 前用 `String.write(to:)` 把 `app_*.js` 物理文件写到沙盒,覆盖 H5 zip 包内自带的版本。哪怕这是"现代 WKWebView 不推荐"的写法,但行为 100% 等价 msext,契约不破
|
||||||
|
- **教训**:
|
||||||
|
- **遇到契约边界时不要琢磨"H5 改一点点就行"**——再小的改动都不行,第一准则不可妥协
|
||||||
|
- **`WKUserScript` / `evaluateJavaScript` 不是万能药**:现代化 API 在某些时序场景下**无法**1:1 等价旧路径,遇到时序冲突就回到旧路径,不要为了"看起来现代"而牺牲契约
|
||||||
|
- **原则 B "现代化"对原则 A 让步**:Swift 写文件 / 看似过时的模式在新外壳里完全可以存在;不优雅是可接受的代价,契约破裂不是
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 文档索引
|
## 文档索引
|
||||||
@@ -139,13 +153,42 @@ daoqi 仓库当前并存两条工作线:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 核心约定:两条平行原则
|
## 核心约定:两条原则(原则 A 是第一准则,地位高于原则 B)
|
||||||
|
|
||||||
新项目(新外壳)开发同时遵循以下两条原则。两条**地位对等**,互为补充:
|
> 早期版本曾把 A / B 描述为"地位对等",**2026-06-22 升级**:原则 A 是**第一准则**,地位**高于**原则 B。两者冲突时无条件选 A、压力让原生 Swift 端承担。
|
||||||
|
|
||||||
### 原则 A:H5 端完美适配(外部契约)
|
### 原则 A:H5 端零修改(第一准则,不可妥协)
|
||||||
|
|
||||||
**H5 端零修改即可适配新外壳**。这是不可越界的硬约束,详见下方 §五类必须一致的内容。
|
**H5 端不做任何改动**——一行代码、一个字符、一个文件名、一个调用时机,都不允许 H5 团队配合调整。这是新外壳所有设计 / 实现的**最高约束**,地位高于原则 B、高于"技术优雅"、高于"现代化"、高于任何关于"性能 / 可维护性 / 可观察性"的考量。
|
||||||
|
|
||||||
|
#### A.1 已经被否决的 / 永远不要再提出的方案
|
||||||
|
|
||||||
|
下面这些方案在任何场景下都**不要**作为建议提给项目方,即便它们更简洁 / 更现代 / 工作量更小:
|
||||||
|
|
||||||
|
- ❌ "H5 改一行":哪怕只让 H5 把读 `app_xxx` 的代码挪到 `DOMContentLoaded` 之后
|
||||||
|
- ❌ "H5 改一个文件":哪怕只让 H5 把 `app_*.js` 内容换成 `if(typeof xxx==='undefined') var xxx=...`
|
||||||
|
- ❌ "H5 加一个 polyfill":哪怕只让 H5 引入一段 5 行的兼容垫片
|
||||||
|
- ❌ "H5 调整调用时机":哪怕只让 H5 把同步表达式改成 `setTimeout(..., 0)`
|
||||||
|
- ❌ "新桥接口":哪怕只让 H5 改成调一个新 handler 名
|
||||||
|
|
||||||
|
**唯一例外**:项目方主动要求改 H5 时(如 H5 团队自己提出重构),新外壳配合调整契约。其它一切场景下,H5 不可触碰。
|
||||||
|
|
||||||
|
#### A.2 替代方案:原生 Swift 端硬扛
|
||||||
|
|
||||||
|
遇到契约边界冲突时正确的应对:
|
||||||
|
|
||||||
|
1. **回到原 msext 老路**:原 msext 是上线 N 年的稳定实现,它怎么解决就照搬。哪怕老路是"现代 WKWebView 不推荐"的写法(如 `loadFileURL` 前 `writeToFile` 写 .js 文件、`UIWebView` 时代的 `JSContext` 注入约定),只要功能等价,照搬
|
||||||
|
2. **接受技术不优雅**:硬扛会让原生 Swift 代码出现"看似过时的写法"(写文件、轮询、特殊时机 hook 等)。**这是可以接受的**——原则 B "现代化"对原则 A 让步,不是相反
|
||||||
|
3. **不要用"虽然这是临时方案,等 H5 改了再优雅化"自我安慰**:H5 永远不会"等会儿改"。**今天的临时方案就是终身方案**
|
||||||
|
|
||||||
|
#### A.3 判定流程
|
||||||
|
|
||||||
|
写代码 / 写文档前问自己:
|
||||||
|
|
||||||
|
> "这个方案要 H5 端做任何动作吗?包括 H5 团队改一行、调整调用顺序、删一个文件、加一个守卫?"
|
||||||
|
>
|
||||||
|
> - 答"是" → **立即否决,重新设计**
|
||||||
|
> - 答"否" → 继续判定其它原则
|
||||||
|
|
||||||
### 原则 B:原生内部实现自由重构(内部架构)
|
### 原则 B:原生内部实现自由重构(内部架构)
|
||||||
|
|
||||||
@@ -158,10 +201,10 @@ daoqi 仓库当前并存两条工作线:
|
|||||||
|
|
||||||
→ 详细技术选型与架构设计见 `docs/H5-Native-Implementation-Design.md`。
|
→ 详细技术选型与架构设计见 `docs/H5-Native-Implementation-Design.md`。
|
||||||
|
|
||||||
**两条原则的交叉判定**:
|
**两条原则的交叉判定**(A 是第一准则):
|
||||||
- 如果某项决策影响 H5 可观察行为(接口名/字段/数据/JS 协议)→ 必须遵循原则 A,照搬旧项目
|
- 如果某项决策影响 H5 可观察行为(接口名/字段/数据/JS 协议/时序)→ 必须遵循原则 A,**照搬原 msext 实现,哪怕看起来过时**
|
||||||
- 如果某项决策只影响原生内部(技术栈/模块结构/状态管理/SDK 选型/代码风格)→ 必须遵循原则 B,按现代标准设计,**不要照搬旧项目**
|
- 如果某项决策**只**影响原生内部(技术栈/模块结构/状态管理/SDK 选型/代码风格)→ 必须遵循原则 B,按现代标准设计,不要照搬旧项目
|
||||||
- 当两者冲突时,**原则 A 优先**:宁可让原生内部多一层适配层去保住契约,也不能为了内部优雅破坏 H5 契约
|
- 当两者冲突时,**无条件选 A**:原则 A 是不可妥协的第一准则。原生内部多一层适配层 / 多一段"看似过时"的代码 / 多一份与原 msext 等价的实现,都可以接受;契约被破,**永远不可以接受**
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user