Skip to content

fix(scripts): release 页 Console 段改按破坏性判据分节,Breaking changes 不再结构性永不渲染 (#6294) - #6392

Merged
hotlong merged 1 commit into
mainfrom
claude/issue-6294-release-section-by-breaking
Aug 7, 2026
Merged

fix(scripts): release 页 Console 段改按破坏性判据分节,Breaking changes 不再结构性永不渲染 (#6294)#6392
hotlong merged 1 commit into
mainfrom
claude/issue-6294-release-section-by-breaking

Conversation

@hotlong

Copy link
Copy Markdown
Contributor

Fixes#6294

scripts/objectui-range.mjs 的分节表按声明级别分节,首项 ['major', 'Breaking changes']。objectui 在发布窗内不声明 major(破坏性一律 minor + 作者正文标注,scripts/check-changeset-no-major.mjs 刻意守这条线),因此 ### Breaking changes 这个节头结构性永不渲染,而升级者最该先读的那一类被呈现在 ### Features 标题下。

本 PR 把首节的分节判据从「声明级别 == major」改为共享判据 r.breaking判据一字未改 —— 它在 scripts/objectui-changeset-digest.mjs 里,本 PR 只消费。

1. 前提复核(在 origin/main 上)

SECTIONS 现行形状,scripts/objectui-range.mjs:119-124(改前):

/** Declared level → the release-page heading it belongs under. */constSECTIONS=[['major','Breaking changes'],['minor','Features'],['patch','Fixes'],];

r.breaking / r.breakingSource 确实可用 —— classifyRange 已经为每条 releasing 条目算出(objectui-changeset-digest.mjs:531-539,PR #6289 落下),而本文件第 80 行本来就在 import 并调用 classifyRange:

constannotated=hasBreakingAnnotation(body);releasing.push({breaking: level==='major'||annotated,breakingSource: level==='major' ? 'declared-major' : annotated ? 'annotation' : null,});

真实区间实测(改前,f5bc4c78be76..f995a452d2ca,与 #6099 同一现场):64 条 releasing、0 条 major,只渲染出 ### Features / ### Fixes 两节,作者亲手标注的 4 条破坏性条目全部落在 Features 下。前提成立。

2. ⚠️ 节序断言的空绿审计(本单的硬要求)

分诊的假设是::363-364 那条节序断言「今天之所以通过,很可能正是因为那个节从不渲染」。实测把这个假设证伪了 —— 如实报告,不套模板。

改前它并不是空绿。 两条独立证据:

  1. 断言本身是肯定式的。 它的第一个合取项是存在性检查indexOf('### Breaking changes') > -1,节不存在时直接判假;不存在时也不会退化成「-1 小于 indexOf(Features)」这种平凡真。
  2. fixture 里本来就有一条 major self-test 的形状 2(refactor(layout)!)显式声明 "@object-ui/layout": major,所以该节在 fixture 中确实渲染。用探针在改前的代码上跑同一 fixture:
--- headings (BEFORE) ---
### Breaking changes
### Features
### Fixes

所以「结构性永不渲染」是生产窗口的事实(objectui 从不声明 major),不是 self-test fixture 的事实。fixture 用的是 declared-major 这条在真实发布窗内不存在的路径。

真正的缺口不是空绿,而是覆盖错位: 改前这条断言只被 declared-major 承载,而真实世界唯一会走的 annotation 路径完全无覆盖。改后如果只换判据不动 fixture,断言仍会被同一条 major 承载,新代码路径一条断言也不碰 —— 那才是这一单真正的空绿风险。

改后靠什么承载(三层,逐层加严):

  • fixture 新增形状 6:minor + 作者 **BREAKING (v17)** 标注(仿真实提交 042e09d77),于是该节同时declared-majorannotation 两条来源承载;
  • 节序断言本身加严成三节全序 —— Breaking changes 先于 Features 先于 Fixes,每个合取项都是对本 fixture 真实渲染的节的存在性检查(新增形状 7 让 ### Fixes 也首次真的渲染,改前 fixture 里根本没有 patch 条目);
  • 新增一条只有 annotation 破坏性的节序断言:把 declared-major 条目从区间里滤掉后重渲染,### Breaking changes 仍须渲染且仍须领先。这条就是真实发布窗的形状,也是判据一旦退回 major 就必然转红的那条(见下方 B)。

3. 真实区间重放(f5bc4c78be76..f995a452d2ca)

 改前 改后
### Breaking changes (缺席) 4 条
### Features 14 条 10 条
### Fixes 50 条 50 条
条目总数 64 64

4 条作者标注的破坏性条目逐条归位:

042e09d77 ### Features -> ### Breaking changes
6e794a19e ### Features -> ### Breaking changes
8ec406728 ### Features -> ### Breaking changes
d22ae31ce ### Features -> ### Breaking changes

总数 64 -> 64:无丢失、无重复,只是换了标题。

4. 反向验证(先申报后执行)

声明 A —— 节活过来

申报:改前 ### Breaking changes 不出现,改后出现且恰好收进那 4 条。

实测:符合。 见上表(真实区间)与探针 fixture:改后 level=minor breaking=true src=annotation 的条目落到 ### Breaking changes(改前落在 ### Features)。

声明 B —— 变异体

申报(执行前写下):把首节判据退回 r.level === 'major'、保留全部断言,预测 3 条转红(两条归属断言 + annotation-only 节序断言),而三节全序断言不会红,因为 fixture 里仍有一条 major,该节照样渲染、照样领先。

实测:与申报逐条吻合,3 红。

 ✓ the three sections render in page order: Breaking changes, Features, Fixes
✗ a `minor` entry the AUTHOR annotated breaking is under Breaking changes
✗ that annotated entry is NOT left under Features
✓ sectioning consumes the verdict, it does not restate the level: still `minor`/annotation
✓ a `patch` merely MENTIONING "breaking" mid-sentence stays under Fixes
✓ grouping never filters: every releasing entry appears exactly once
✗ with NO declared `major` in range, Breaking changes still renders and still leads
⛔ objectui-range --self-test: 3 failure(s)

断言极性逐条标明:

断言极性变异体下为什么
三节全序(Breaking 先于 Features 先于 Fixes)肯定式(三个存在性合取)绿fixture 仍有一条 major,节照样渲染并领先。它守的是节序,不是判据 —— 单靠它抓不到本回归。这是本 PR 最该说清的一点。
标注条目 Breaking changes 节内肯定式(成员归属)该条目退回 Features,节内找不到。
标注条目不在 Features 节内否定式,但有内容、非结构性条目真实存在且真的会被渲染到 Features,所以否定式在此有判别力 —— 不是「因为什么都没产生所以通过」。
annotation-only 区间下节仍渲染且领先肯定式(存在性优先)滤掉 declared-major 后,级别键控下该节一条都取不到,indexOf 返回 -1,第一个合取项即判假。这条是本次改动的真正守卫。
「分组永不过滤:每条恰好出现一次」肯定式(计数)绿变异体下 major/minor/patch 仍完备,不丢条目。
「消费判据而不复述级别:仍是 minor/annotation」肯定式绿级别与 breakingSource 未被本 PR 触碰,本就该保持绿 —— 它守的是「判据没被改写」。

变异体已完全还原(grep -n MUTANT 无命中,还原后 self-test 21/21 绿)。

声明 C —— 非 breaking 条目不误入

申报:普通 minor/patch 仍进各自节,不因改判而漂到 Breaking 节。

实测:符合。 真实区间 Fixes 50 条纹丝不动,Features 恰好少掉那 4 条。fixture 侧新增形状 7 是近似样本:一条 patch,正文只在句中提到 "breaking"(Reordering here is technically breaking for anyone who relied on the churn…,仿真实的 9e9e9a92f),既不是强调标签也不在块首 —— 断言它必须留在 ### Fixes 且不得出现在 Breaking 节。这条从消费端钉住了判据的精度,而没有改判据。

声明 D —— 下游未破

申报:objectui-range.mjs 的既有消费者行为不变,至少跑它们的 self-test 取证。

实测:符合。 消费面已枚举:package.jsoncheck:objectui-changeset(digest + range 双自测)、scripts/check-objectui-pin-fresh.mjs(仅在帮助文本里提到本脚本)、docs/releases-maintenance.md(用法文档,用法未变)。scripts/bump-objectui.sh 走的是 buildDigest,不经过 renderRange。digest self-test 与 pin-fresh self-test 均全绿(见门禁表)。CLI 的 --json 形状未变(字段直出 classifyRange),只是 markdown 的标题归属变了。

5. 门禁 EXIT 表(均在 git add 之后跑)

门禁EXIT结果
pnpm check:objectui-changeset0digest self-test 全绿;objectui-range self-test 21/21 绿(改前 14 条)
pnpm exec eslint scripts/objectui-range.mjs --no-inline-config0无输出
pnpm check:nul-bytes0自测 56 断言 + 扫描 6046 个跟踪文本文件,无裸控制字节
控制字节自扫 grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'grep=1无命中(本 PR 未书写任何控制字符)
node scripts/check-objectui-pin-fresh.mjs --self-test011 条全绿(下游)

6. 不在本 PR 里

  • scripts/objectui-changeset-digest.mjs —— 判据所在地,本 PR 只消费,一字未动。
  • content/docs/releases/** —— CLAUDE.md 铁律,发布页集中编写,绝不在代码 PR 里改。
  • v17.mdx —— v17.mdx 缺 rc.1 之后两次 console pin 移动的窗口段落(页面自述「that range needs its own section」未兑现) #6115 另有其单。
  • ⛔ 判据本身与收录集(哪些条目算 releasing)—— 均未触碰。
  • ⛔ 未加 .changeset/:scripts/ 工具链、不发布任何包,走 skip-changeset 路线。
  • 未给 Breaking 节内条目再加 **BREAKING** 前缀标记(digest 侧 markBreaking 的做法)—— 节头已经说明了这件事,且超出本单范围。

Generated by Claude Code

…#6294)
`scripts/objectui-range.mjs` 的分节表按**声明级别**分节,首项是
`['major', 'Breaking changes']`。而 objectui 在发布窗内不声明 `major` ——
破坏性一律 `minor` 加作者正文标注,`scripts/check-changeset-no-major.mjs`
刻意守着这条线。于是该节头结构性永不渲染,破坏性条目被呈现在 `### Features`
标题下,恰好是升级者最需要先读的那一类。
改为:首节按 `classifyRange` 已经算出的共享判据 `r.breaking` 抽取(#6099 /
PR #6289 落下的那半边),其余条目仍按声明级别分入 Features / Fixes。
判据一字未改 —— 本文件只消费,不复述;这正是 #6289 把判据放进共享实现
而不是放进 `buildDigest` 的原因。分节表对剩余条目做了完备性兜底:
任何级别表放不下的条目仍会被打印,分组是呈现,永不再变成过滤器(#4843)。
实测区间 `f5bc4c78be76..f995a452d2ca`(与 #6099 同一现场):
改前 `### Breaking changes` 缺席,4 条作者标注的破坏性条目全部落在 Features;
改后该节渲染并恰好收进这 4 条,Features 14 -> 10,Fixes 50 不变,
总条目 64 -> 64(无丢失、无重复)。
self-test 从 14 条增至 21 条,fixture 增加两个真实形状:`minor` + 作者
`**BREAKING (v17)**` 标注(仿 `042e09d77`),以及正文只在句中提到 "breaking"
的 `patch` 近似样本(仿 `9e9e9a92f`),后者必须留在 Fixes。
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BDmDsu2575gDxeMCxXhDE3
@vercel

vercelBot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 7, 2026 4:31pm

Request Review

@hotlonghotlong added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 7, 2026 — with Claude
@hotlong
hotlong marked this pull request as ready for review August 7, 2026 16:52
@hotlong
hotlong added this pull request to the merge queueAug 7, 2026
Merged via the queue into main with commit 9766d4fAug 7, 2026
25 of 26 checks passed
@hotlong
hotlong deleted the claude/issue-6294-release-section-by-breaking branch August 7, 2026 17:05
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectui-range 的 release 页 Console 段按声明级别分节,### Breaking changes 在发布窗内结构性永不渲染(#6099 的同源另一半)

2 participants

@hotlong@claude