docs(05): 锚点补记四十三 —— 行内标签对齐契约 v0.89(W68 两轮:大小写不敏感 + 属性丢弃 + 不平衡整段降级 + 白名单收窄五项)+ br 改气泡分隔符(与 p 同级、三种写法等价、流式同源扣留)+ 真机八组样本逐条验收 + 三条待后端确认的口径

This commit is contained in:
AskaEth committed 2026-10-07 17:39:38 +08:00
1 parent 591f298dc0
commit 11602c0138
1 file changed
+56
+56
View File
@@ -2344,3 +2344,59 @@ infinity maximum height constraints, which is disallowed.
### 6. 环境
- HEAD = **`3d4a9f2`** = `origin/android`;worktree / 分支 / 构建锁**全清**;主仓唯一脏 = `devdocs/02`。
- 模拟器装最新包(含 W58~W67),`tcp:80` 已切回**真后端**。
## ★★ 锚点补记四十三 · 行内标签对齐契约 v0.89(W68,两轮)+ `<br>` 语义变更 + 真机验收(2026-10-07 晚)
### 1. 后端把这件事**升成了契约**(v0.89,推库 `2dae469` / `b989f0b`)
`回执二十九` + `回执三十` + `回执三十一` 三条连着来,**第三条是更正**:
- **白名单**:`b` / `strong` · `i` / `em` · `code`(**五项**,`u`/`s` 被收窄掉、`br` 移出)—— 成对;**标签名大小写不敏感**;
- **三条硬口径**:① 白名单外一律按纯文本原样(**字符一个不丢**);② **属性一律不解析**(`<b class=x onclick=y>` 只取标签名);③ **结构不平衡 ⇒ 不渲染**(粒度算实现细节);
- **🔴 `回执三十一`(主人的新指令)**:**`<br>` 不是行内标签,而是「气泡/消息分隔符」,与 `<p>` 同级** —— `<p>甲<br>乙</p>` ⇒ **两个气泡**、无 `<p>` 时同理、`<br><br>` 中间空气泡照旧丢弃、三种写法等价;
- **安全红线**(双方都成立):助手文本是**不可信内容** ⇒ **不许把整段当 HTML 注入**;
- **`<b>` 是模型的自然输出**(产出侧 grep 零命中),但**契约已承认**它 ⇒ 不再是「容错、不保证」;
- **IM 出站那个是他们的真 bug**(`sanitize.go` 的 `stripNarration` 没剥行内标签 ⇒ 发到微信/QQ 对方看到字面标签),**已派修**;
- 我们那三处差异,他们**主动让了两条**:白名单收窄到五项(我们不用做)、不平衡粒度算实现细节;**大小写留给我们定**。
### 2. 我们的定案(已写回 `02`)
| 项 | 定案 |
|---|---|
| 大小写 | **统一到不敏感**(+ **同时**改流式扣留侧,否则中间态把 `<B>` 吐上屏、收口再吃掉 = 字改口) |
| `u`/`s` | **撤掉**(多支持会造成跨端不一致:Web 不渲染、我们渲染 ⇒ 同一条消息两端显示不同) |
| 不平衡 | 改成**整段不渲染**(原口径是「局部降级」) |
| **`<br>`** | **改成气泡分隔符**(我们下午正好做成了「气泡内换行」,那是错的) |
### 3. W68(两轮):`4b19278`(首轮)+ `9dee236`(纠正)→ 合并 **`591f298`**
**首轮**(按我当时的认知):大小写不敏感(**消费侧与流式扣留侧同源一份判据**,避免分叉)、属性一律丢弃、结构不平衡整段降级、补了 `u`/`s` 与 `</br>`。
**纠正**:把 `<br>` 从**行内层**整体移到**切分层**(`BR_TAG_RE` + 新函数 `appendRegion`:每个文本区先 `stripLooseTags`、再按 `<br>` 切开补 `Tok.Break`,与 `<p>` 走**同一套机制、同一个粒度**);流式侧新增 `couldBeLineBreakNamePrefix`(残缺的 `<br`/`<BR`/`</br`/`<br/`/`<br class=x` 一律扣住;**完整的 `<br>` 不入栈**,一出现前缀就可产出);`u`/`s` 连枚举、渲染分支、import 一起撤净。
**跨分隔符标签的口径**(先按 `<p>`/`<br>` 切分、再各自行内解析)—— 三条硬理由:①「与 `<p>` 同级」本身规定了层序;②「一个气泡 = 一条消息」,而 Web 与 IM 出站的平衡判定都是**按单条消息**做的,跨分隔符的标签结构上不可能成立 ⇒ 两端结论天然一致;③ **反着做会让 `runs` 拼起来不再等于 `body`**,那条既有契约当场就没了。
**测试**:`ChatContentParserTest` 57 → **71**(14 条新增,`<br>` 契约表四行逐个断言**气泡数 + 各段文本**);`InlineMarkupTest` 删 6 条与 v0.89 冲突的、新增 3 条。全量 **126 suites / 1564 tests**。
### 4. 真机验收(`emulator-5554` + 假后端夹具,八组样本)
| 输入 | 屏幕实测 |
|---|---|
| `<p>甲<br>乙</p>` | **两个气泡**「甲」「乙」 |
| `丙<br>丁`(无 `<p>`) | **两个气泡** |
| `<p>戊<br><br>己</p>` | 两气泡(**空气泡丢弃**) |
| `甲<br/>乙</br>丙<BR class=x>丁` | **四个气泡**(三种写法 + 大小写 + 属性**全等价**) |
| `<p><b>庚<br>辛</b></p>` | 两气泡、**两条都是原文**(跨分隔符 ⇒ 不平衡 ⇒ 不渲染) |
| `<p><u>壬</u>与<b>粗</b></p>` | 「`<u>壬</u>`与**粗**」—— `u` 已撤掉显示字面、「粗」加粗 |
| `<p>甲<br2>乙</p>` | 一气泡、**字面** |
| `<p><B>大写粗</B>加<b class=z>属性粗</b></p>` | 「大写粗加属性粗」—— 大小写不敏感 + 属性丢弃 |
### 5. 三条**待后端确认**的口径(我们已写进 `02`,都是我们自定的)
1. **`<br>` 的大小写**:我们按**不敏感**做(依据:v0.89「标签名大小写不敏感」当时罩着 `br` + 他们 Go 侧 `(?is)`);若他们只认小写,我们收窄两处。
2. **`<br>` 在 `<code>` / `<action>` 内部也分隔**(按「**始终**」的字面读)⇒ 两半的标签因未配对回落字面;若他们在 Web/IM 有作用域保护,说一声。
3. **已知边界(真实产出不存在,仅登记)**:`<br>` 出现在**别的标签的属性里**也会分隔(我们不解析外层属性结构);另有一条**先前就存在**的怪相(本次未改、改前改后同结果):`<p class="a<br>b">甲</p>` 里 `<br>` 的 `>` 会提前闭合 `<p …>`。
### 6. 环境
- HEAD = **`591f298`** = `origin/android`;worktree / 分支 / 构建锁**全清**;主仓唯一脏 = `devdocs/02`。
- 全量真跑:**126 suites / 1564 tests / 0 failures**。模拟器装最新包,`tcp:80` 已切回**真后端**。