Skip to content

fix(components): span 废弃提示改为每模块加载一条,且只对 JSON 作者面开火 (#4917) - #5029

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-4917-span-deprecation-parity
Aug 17, 2026
Merged

fix(components): span 废弃提示改为每模块加载一条,且只对 JSON 作者面开火 (#4917)#5029
yinlianghui merged 1 commit into
mainfrom
claude/issue-4917-span-deprecation-parity

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#4917

背景:同一个渲染器落后 div 两步

span 的废弃提示身上压着两条已经裁定过的纪律,而 div 两条都补上了,span 一条没补:

两条叠加的后果与 div 当初一致:提示对引擎自己解析器的输出开火,而且不去重。basic/html-elements.tsx 的 TAGS 列表故意把这个标签排除在外(注释写明「已在别处注册」),正是因为它由 basic/span.tsx 注册 —— 所以 html tier 页面里写这个内联标签,编译出的节点必然落到这个废弃渲染器上,作者收到一条建议换 badge / text 的提示,而那两个都不是 html tier 里的等价标签,作者写什么都消不掉。

本 PR 就是那个「后续」:照抄 div.tsx 现在的形状,不另发明一套。

改动

packages/components/src/renderers/basic/span.tsx:

  • 模块级 _warnedDeprecations Set + warnDeprecatedOnce() —— 提示是类型的属性而不是节点的属性,每次渲染重复一遍只会把页面真正的报错埋掉。dev 构建照旧报,恰好一条。
  • 豁免判断 isHtmlTierNode(schema),provenance 由生产者建立(parser 给自己产出的节点打标),不是在这里猜节点形状。
  • 顺序是承重的:豁免判断放在 Set 标记之前。html-tier 节点先渲染不得 latch 守卫,否则会吞掉同一页上之后 JSON 作者节点应得的那一条 —— 正好把这条提示唯一的目标读者静音。与 warnDeprecatedOnce 内部「production 提前 return 在标记之前」是同一条纪律。
  • 文案加一句写明它针对哪个作者面。迁移建议逐字未动 —— 这一单收窄的是「告诉谁」,不是「告诉什么」。

content/docs/components/basic/span.mdx 补一段作用域说明,与 div.mdx 同形。

测试

两个新钉子,照抄 div-deprecation-*.test.tsx 的形状:

  • span-deprecation-warn-once.test.tsx —— production 静默用例必须跑在最前(Set 会 latch 整个模块实例的生命周期),它同时钉住守卫的顺序:production 渲染不得吞掉之后的 dev 提示。七次 SpanRenderer 调用(含三层嵌套)只收一条。
  • span-deprecation-provenance.test.tsx —— html-tier 用例跑在最前、对着一个未被污染的 Set,所以「零提示」不能用「之前某次渲染已经 latch 了守卫」解释;紧接着的 JSON 作者面用例观察到恰好一条,只有在前一个用例没标记 Set 时才可能。两个用例互相钉住。

一处刻意的偏离,连带发现 #5027

provenance 测试没有照抄 div 那条 textContent 对照断言。原因是这个内联标签在 html tier 里的文字压根不会渲染:该渲染器读 schema.body,而 parser 产出的是 children(div.tsx 读的是 schema.children || schema.body,所以 div 没事)。实测:

源码 < div className="outer" >< span className="inner" >hello html tier< /span >< p >page rendered< /p >< /div >
渲染 < div class="outer" >< span class="inner" >< /span >< p >page rendered< /p >< /div >

这是渲染路径的独立缺陷,不是提示作用域的缺陷,而且它需要单独裁定「规范的子节点键是 children 还是 body」—— 按 Commandment #0.1 contract-first,这该在类型/规范侧定,不该由渲染器顺手加一个宽容的 || 糊掉。已单独立 #5027,本 PR 不碰。

对照断言因此改用「同页 p 标签的文字」+「内联元素本身存在(带 html 源码里写的 class)」:两者合起来证明页面确实编译并渲染了、且节点确实落到了这个废弃渲染器上,所以观察到的静默是豁免生效,而不是节点根本没出现。两条断言在 #5027 修好之后依然成立。

反向验证(先书面预判,再变异)

变异预判实测
① 撤掉 isHtmlTierNode 豁免判断provenance 用例 1 红(html-tier 节点收到 1 条,期望 0);用例 2 连带红(Set 被用例 1 latch,变成 0 条)完全一致:expected [ Array(1) ] to have a length of +0 but got 1 / expected [] to have a length of 1 but got +0
② 只撤去重(has/add),保留 production 提前 returnwarn-once 用例 2、3 红(条数 > 1);用例 1「production 静默」应保持绿 —— 这是区分两道守卫彼此独立的判别项完全一致:4 红(4 / 2 / 9 / 1 条),3 绿 = production 静默 + html-tier 静默 + provenance 不上 DOM
③ 把 html fixture 改成编译不过的源码(塞一个未注册标签)③a 同时去掉三条对照断言 → 绿(演示空绿危险:toHaveLength(0) 在页面根本没渲染时免费通过);③b 保留对照断言 → 红在 not.toContain('failed to compile')完全一致:③a Tests 4 passed(绿,且是错误的绿);③b expected 'HTML page failed to compile (2)…' not to contain 'failed to compile'

变异 ① 的第二处失败是顺序钉子在响:没有豁免时,html-tier 节点会把 JSON 作者应得的那一条吞掉 —— 这恰好是把豁免放在 Set 标记之前所要防的事。

变异 ② 的判别项(用例 1 保持绿)证明 production 提前 return 与去重是两道独立的守卫,不是一道。

变异 ③ 是对对照断言本身的验证,回答「这条 toHaveLength(0) 会不会因为整页编译失败而免费通过」:③a 证明危险真实存在,③b 证明本 PR 的对照断言确实拦得住。

还原一律 git checkout -- 指定路径,未使用 git stash(共享栈)。


Generated by Claude Code

… authors (#4917)
`SpanRenderer` was two rulings behind `div`. It still `console.warn`ed on every
render, and it still fired at nodes the `kind:'html'` tier's own parser had
emitted -- the two defects ruled on for `div` in #3965 (PR #3998, which named
`span` as the follow-up and left this shape untouched) and #4000 (PR #4916,
which built the provenance mechanism and exported `isHtmlTierNode`). This is
that follow-up; it copies the shape now in `basic/div.tsx` rather than
inventing a second one.
The notice is a property of the deprecated TYPE, not of each node, so repeating
it per render only buries the page's real console errors. And an author writing
the plain inline tag in a `kind:'html'` page gets a node this deprecated
renderer serves, then is told to migrate to `badge` / `text` -- neither of
which exists in that tier's vocabulary, with nothing they could write to make
it stop. `basic/html-elements.tsx` deliberately leaves the tag out of its own
TAGS list precisely because this module registers it.
Order is load-bearing and pinned: the html-tier exemption is checked BEFORE the
warn-once set is marked, so an html-tier node rendering first cannot swallow
the notice a JSON-authored node earns later on the same page. Provenance is
established by the producer (the parser stamps what it emits), not guessed from
the node's shape here. The migration guidance is byte-for-byte unchanged; the
notice only gained the sentence naming which surface it means.
The provenance test deliberately does NOT copy the sibling `div` test's
`textContent` control assertion. The inline tag's own text never arrives,
because this renderer reads `schema.body` while the parser emits `children` --
a separate rendering-path defect, filed as #5027 with its own ruling to make
on which key is canonical, and deliberately not patched here behind a lenient
`||`. The control instead reads a sibling paragraph's text plus the presence of
the inline element itself: together they prove the page compiled AND that the
node reached this renderer, so the observed silence is an exemption rather than
a missing node. Both assertions still hold once #5027 is fixed.
Co-authored-by: Claude <noreply@anthropic.com>
@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM 验收 ✅ ACCEPT(#4917,批次 21)

实物核验(merge-base e71c854ce 与报告一致):5 files,+362/−9 —— span.tsx 照抄 div 的 post-#4916 形状(warn-once Set + isHtmlTierNode 豁免置于 seen-set 标记之前,顺序钉在位),提示文案补作者面指名、迁移建议逐字不变;两钉测试 + span.mdx 作用域段 + changeset patch。模型标识 msg/diff 双 0;releases/ 零触碰。

CI 亲读:20/20 check runs completed,零失败(两项 path-filter skipped 计绿)。

反向验证读数:①撤豁免 → 双红且第二条正是顺序钉开火(豁免位置承重的直接证据);②只撤去重 → 4 红 3 绿的分池证明两个守卫独立而非一体;③空绿检查按 #4916 家族陷阱做成配对(删对照→假绿实测坐实、留对照→红)—— 对照承重被实测而非声称。

deviation 1 的判断是本单亮点:不照抄 div 的 textContent 对照 —— 因为 span.tsx 读 schema.body 而 parser 产 children,html-tier span 自身文本根本不渲染,照抄会为无关原因翻红;替代对照(兄弟 p 文本 + querySelector 节点在场)证明同一件事且在底层缺陷修复后依然成立。没有用一个 || 把缺陷藏起来(#0.1),而是立 #5027(三个声明面互相矛盾、类型规范拼法渲染为空,#4631 的具体实例;因 #4631 on-hold 且是机制卡,独立立卡不做其子卡的理由成立)。probe 先行实证基线(两条重复提示实测)而非读码推断,#4994 前缀纪律 + 提示文案沿用 div 已绿文本规避 #4902 门,全部到位。

#5027 已由 PM 定级 pm:queue。undraft + auto-merge(squash)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 17, 2026 18:10
@yinlianghui
yinlianghui added this pull request to the merge queueAug 17, 2026
Merged via the queue into main with commit 1c9c342Aug 17, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4917-span-deprecation-parity branch August 17, 2026 18:11
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

span 的废弃提示落后 div 两步:仍是逐次渲染告警,且照旧对 kind:'html' tier 自产节点开火

2 participants

@yinlianghui@claude