AskaEth
9da25b0895
feat(W71): 按契约 v0.89 最终版返工 —— u 加回白名单(六项)+ <br> 只在普通文本层分隔
后端 `回执三十三`(v0.89 收口)更正了 W68/W69 的两处实现,本端照改。
一、`u` **加回**白名单(最终**六项**)
`回执三十三 §一` 明说:`回执三十一` 那句「收窄到五项」**是他们的疏漏** —— 主人随后裁定
下划线保留,后端已把 `u` 加回契约(`1107b2a`)与 IM 出站(`74c9f2d`),只是**忘了发回执登记**。
最终白名单 = `b`/`strong`/`i`/`em`/`code`/**`u`**,`s` 仍不在内(逐字原样)。
1. `InlineMarkup`:`PAIRED_TAG_NAMES` 加回 `"u"`、`InlineStyle` 加回 `Underline`、
`inlineStyleOf` 落到它。**只加回 `Underline`**:`s` 依旧不做(枚举里没有 `Strikethrough`,
不留走不到的渲染分支)。加回词表同时让流式扣留侧重新认 `<u` / `<U`(两边同一份判据)。
2. `MessageBubble.inlineBody`:`textDecoration = TextDecoration.Underline`(重新引入
`androidx.compose.ui.text.style.TextDecoration`;仍然只是 `AnnotatedString` + `SpanStyle`)。
3. 渲染仍是「样式只给有的那几项」,非 `<u>` 的片段逐像素未变。
二、`<br>` **不再「始终分隔」**:只有**普通文本层**的 `<br>` 才分隔(`回执三十三 §三②`),
`<code>` 与 `<action>` / `<narration>` **内部**的不分隔。后端给的两条理由我们同样成立:
① `<code>` 是**成对**元素,从中间切开必然两半不平衡 ⇒ 按硬口径「不平衡 ⇒ 不渲染」整段降级为
字面量(用户看到裸标签);② `<action>她<br>点点头</action>` 切开就是「她」「点点头」两条**聊天
消息** = 把注记正文当正文发了出去(信息泄露级)。
1. 新增 `protectedSpans`(保护区扫描)+ `appendRegion` 里跳过落在区间内的 `<br>`:
一趟线性扫描、**两个独立状态** —— `code` 一支用行内层的判据(`inlineTagEnd` +
`openInlineTagName`/`closeInlineTagName`:大小写不敏感、可带属性),角色注记一支用 `ROLE_RE`
的判据(只认小写、不带属性、找**同名**闭合)。两支各算各的,因为那本来就是两个消费侧各自的
尺子(行内层在 `<code>` 里只认自己的闭合;`ROLE_RE` 完全不看 `<code>`)。区间按**起点**升序,
切分处用游标 O(1) 判定 ⇒ 整体仍然线性(新增一条 20k 作用域的复杂度预算用例)。为此把
行内层的 `inlineTagEnd` 与 `CODE` 提为 `internal`:**标签边界与 `code` 这个名字只该有一把尺子**。
2. 作用域里的 `<br>` 算什么(**我们定的口径,写进 KDoc**):**普通字符、逐字原样** —— 不换行、
也不消掉。`<code>` 是它既有口径(里面连白名单标签都是内容,`<code><b>甲</b></code>` 显示字面
`<b>甲</b>`,由 `codeContentIsNotParsed` 钉着);注记那侧则是**强迫**的:旁白那一行画的正是
`parts` 的逐字原文(编辑框/搜索/通知同源,`parts` 是红线),换 `\n` 或删掉就是静默改用户内容。
两个作用域取同一条规矩,免得多出一种读法。「改成换行」因此被否:要么在这里改掉片段文本
(`parts` 当场不再是原文),要么在行内层给 `<br>` 单开一条规则(W68 刚从那里整体删掉)。
3. **未闭合的作用域不算作用域**(里面的 `<br>` 照旧分隔):判据只看「成对」,而消费侧本来就不认
未闭合的那个(行内层整段降级、`ROLE_RE` 配不出旁白);且「未闭合 ⇒ 保护到结尾」会让一个漏掉
`</code>` 的笔误把后面**所有** `<br>` 一并吞掉——一个畸形标签的爆炸半径不该放大到整段。
两种读法都不渲染、都不丢字,差别只在气泡怎么切,我们选与消费侧一致的那种。
4. 其余一律未动:`<br>` 大小写不敏感(两端口径一致,`回执三十三 §三①`)、`<br>` 出现在别的标签
属性里照样分隔(`§三③` 登记不修)、`<p>` 语义、星号(旁白/动作)语义、`stripLooseTags` /
`ContentAfterQuery` / `P_BLOCK_RE` / `ROLE_RE`、W63 的线性特性、`parts` 逐字原文。
测试:`InlineMarkupTest` 41→43 条(`<u>` 六项之一渲染下划线且大小写/属性/叠加/未闭合各断言、
`s` 每种形态都仍逐字字面且枚举里没有 Strikethrough、`<code>` 里那个 `<br>` 是内容);
`ChatContentParserTest` 71→77 条:W69 那条「`<code>`/`<action>` 内也分隔」换成 7 条新口径用例
(code 作用域存活且无裸标签、注记不产生气泡且 `parts` 逐字、只有 code/注记算保护区、嵌套、
未闭合、两支各算各的、流式扣留残缺 `<br`),另加一条保护区扫描的复杂度预算用例;
流式一致性用例的样本补上 `<u>` 与保护区两形状。
其余既有断言(含 W68 那批 `<br>` 用例、复杂度预算)一条都没放松。
验收:`assembleDebug` 通过(app-debug.apk 47 330 538 B);`testDebugUnitTest` 全量
**127 suites / 1589 tests / 0 failures / 0 errors**(基线 1580,+9 条本次新增)。
2026-10-07 19:10:42 +08:00
..
2026-10-07 19:10:42 +08:00
2026-09-08 21:48:40 +08:00
2026-09-08 20:40:58 +08:00