fix(spec): 参考文档的联合单元格每种拼写只印一次,重复变体计入既有标记 (#6569) - #6738
Conversation
同一个联合的多个变体如果渲染成逐字相同的字符串,现在只印一次,被折叠掉的 变体数计进 #6226 既有的 `… +N more` 标记。全语料实测 14 格改变(7 个页面), 类型文本合计 3682 → 2469 字符;字面的 `object | object | object | object` 从 9 格降到 0 格。 裁决落在 issue 正文的 B 与 C 之间:折叠(重复拼写不携带第一份之外的信息), 但自报元数(#6226:省略必须说出自己的大小),并且复用表格已有的标记词汇而不 发明第四种省略记法。对读者成立的一句话:印出的拼写数 + 计数 = 联合的元数, 无论变体是被上限藏起来的还是被折叠掉的。 三条有意的边界:判等在渲染出的字符串上(`object | object[]` 是两种拼写, 永不折叠);不限于相邻(语料里唯一分歧处正是 #6226 立案的 `App.navigation`, 分隔符排第七位,相邻规则会把第八份导航项再印一遍);共享的守卫原样适用, 两三个重复仍逐字保留,四个才是第一个划算的宽度。 #6226 的 `string | string | string | string | string` pin 因此翻转 —— 改的是 候选而不是守卫,同一条未修改的守卫对新候选给出相反结论。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
…ntical-variant-dedupe
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckNo hand-written docs reference the 0 changed package(s). ✅ |
去重注释沿用了 issue 正文的「11 格 / 6 个页面」,而同一个 PR 的实测把它否掉了: 带相同 `object` 重复的是 **9 格 / 5 个页面**。11 是子串统计带进来的 —— `object \| object` 也会命中 `object \| object[]` 那两格(`ui/page.mdx` 的 `slots`、`automation/state-machine.mdx` 的 `states`),那是两种不同拼写,规则 有意不折叠。 注释是长期物,PR 正文不是;下一个读这个函数的人会把注释里的数字当实测事实。 差因一并写进注释,免得后来者 grep 出 11 之后误判「去重漏了两格」。 仅注释文本,逻辑、夹具、生成物均未改动。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AZgRyPVwi1jLb1mNNuUQ9o
|
座位验收( 承重命题:守卫未动 —— 独立复核通过本 PR 敢翻 #6226 那条 pin,全部理由压在一句话上:改的是候选,不是守卫。这句若不成立,整件事就从「重新测量」变成「放松闸门」。逐字核过:
于是旧 pin 写在注释里的理由(标记比省下的还长)正是新 pin 给出相反结论的理由 —— 42 字符 → 18 字符,同一条未修改的守卫因此接受。这是重新测量,不是放松。 开发者指出这一条与它自己的反向验证是独立到达的同一结论,同意:若日后有人质疑这次翻转,论据是这两条独立确认,而不是 PR 散文。 派单硬约束逐条兑现
两处数字更正与一次范围披露 —— 判定
一处修正(已推,已复核)初版代码注释仍写着 issue 正文的「11 格 / 6 个页面」—— 即本 PR 自己在正文里推翻的数字。注释是长效物,下一个读者不会读 PR 正文。已修: 复核方式为机械验证而非采信: 反向验证的那一处未命中预判红、实际绿的「拼写数 + 计数 = 元数」一条,不可能红:基线渲染器同样满足它。开发者就地重新归类进绿组并写明理由,而不是改掉预判假装命中 —— 这条钉的是「裁决保住了这个不变式」(正文选项 B 会破坏的正是它),仍然有价值,只是价值不在它原本被安排的位置。这种报法比一份干净的故事更可信。 另记前一次死亡运行留下的 CI 新一轮 8 项完成、零失败, Generated by Claude Code |
Fixes #6569
同一个联合的多个变体如果渲染成逐字相同的字符串,单元格现在只印一次,被折叠掉的变体数计进 #6226 既有的
… +N more标记。裁决口径按派单说明取语义(正文 A/B/C 与分诊 A/B/C 编号不一致):折叠(非正文 A 的维持现状)、自报元数(非正文 B 的静默丢弃)、复用既有标记词汇(非正文 C 的新记法)。维护者否决窗口仍开在 issue 线程上。
落地形状
变体数上限和去重合成一个标记,而不是叠成两次省略,于是对读者成立的仍是一句话:印出的拼写数 + 计数 = 联合的元数,无论某个变体是被上限藏起来的,还是因为这一格已经逐字印过它而被折叠掉的。无重复且未超上限时
hidden === 0,走的还是原来那条return full,渲染逐字不变。全语料实测(本次运行测得,post-merge 复测)
origin/main在实现期间前移三次(#6695 / #6688 / #6702,当前61282f906);已 merge 到本分支并在合并后的树上重跑gen:schema && gen:docs(.gitattributes的merge=os-regen不会文本合并生成物)。重跑产出零 diff,下列数字合并前后完全一致,无 delta:object | object | object | objectobject重复宽度本来就不是这张卡的目标(#6374 已经把形状宽度收掉了),这里只是顺带。
逐格
kernel/manifest.mdx·navigationContributionsitems: (object | object | object | object | … +5 more)[]items: (object | … +8 more)[]ai/conversation.mdx·messagescontent: (object | object | object | object)[]content: (object | … +3 more)[]ui/view.mdx·list/listViews/config×2,api/protocol.mdx·list/listViews,data/object.mdx·listViews(共 7 格)data?: object | object | object | objectdata?: object | … +3 moreui/app.mdx·navigation×2 /items(共 3 格)… +5 more(385 字符){ type: 'separator'; … }+… +7 more(160 字符)data/validation.mdx·then/otherwise… +2 more(325 字符)[ConditionalValidation](#conditionalvalidation)+… +4 more(139 字符)object重复的是 9 格。 另外 2 格(
ui/page.mdx·slots、automation/state-machine.mdx·states)命中的是object \| object[]—— 那是两种不同拼写,不是同形变体,本次有意不动。正文的 11 应当是子串统计带进来的。
(
ui/app.mdx×3、data/validation.mdx×2),不是object拼写。第 2 点是本 PR 的范围披露:裁决的措辞是「渲染成逐字相同的字符串的变体」,这 5 格正在这个措辞里;正文的现象表只列了
object那一类。把去重限定成只对字面object生效会是一条没有原则的特例,而且会把 #6226 自己立案的那一格(App.navigation)继续留着重复 —— 见下一节。三条有意的边界
object | object[](ui/page.mdx的Page.slots)是两种拼写,永不折叠。这个渲染器判的是读者看到的东西。PageSlots.slots1538 字符,枚举已省略后仍如此) #6226 立案的那一格:App.navigation的九个变体里八个是同一种导航项拼写、一个是{ type: 'separator'; … },而分隔符排在第七位。uniq式的相邻规则会折叠前面的连续段、遇到分隔符、然后把第八份导航项再印一遍 —— 等于把缺陷留在这一格里。554 处联合渲染中只有这 3 处(该格在它的三个页面上)分歧。object | object加标记反而变长 3 字符,object | object | object只省 6 字符而标记占 12 字符,两者都保留重复;四个重复才是第一个划算的宽度(省 15)。四这个数字是守卫算出来的,去重规则本身不认识它。卡片预判的「6 个
object会被守卫拒绝」在改前成立(上限只藏 2 个,省 6 < 12);去重后候选变成「1 种拼写 +… +5 more」,省 33,同一条未修改的守卫接受。预判描述的是改前的算术,不是改后的。#6226 既有 pin 翻转了 —— 逐条交代
format-type.test.ts里 #6226 把string | string | string | string | string逐字钉住,注释写的理由是算术而不是原则:按上限印四个再加 12 字符标记会更长,守卫拒绝。string | string | string | string | string(42 字符)string | … +4 more(18 字符,省 24)改的是候选而不是守卫 —— 同一条一行未动的
elideWithMarker因此接受。旧 pin 给出的理由,正是新 pin 给出相反结论的理由,所以这是一次重新测量而不是一次放松。守卫的拒绝区间仍然有人占着,只是往下挪了一档(两个/三个重复),并由新的单测钉住。夹具分诊(逐条,不是批量重拼)
PageSlots.slots1538 字符,枚举已省略后仍如此) #6226 那个variant(id)helper 的区分键原本是第五个,落在INLINE_KEY_LIMIT之后,于是它造出的每个变体渲染成同一个字符串。这忠实于App.navigation,也正是… +5 more当初在那里可达的原因;但一旦同形变体折叠,这样的夹具考的就是去重而不是上限,那个 describe 块的每一条都会在答错题。把区分键移进上限之内,该块回到只钉变体数上限。Manifest.navigationContributions那格(gen:docs 第三种残留宽度:嵌套形状深度 ——INLINE_KEY_LIMIT只管一层,摘要沿数组/Record/联合无预算下钻(Page.slots1538 字符) #6374 已经替换过一次的夹具):摘要之下九个变体都印object,改后由去重决定,1 + 8 = 9。保留在 gen:docs 联合类型的每个对象变体都印一遍完整摘要,一个单元格里出现近乎相同的形状 N 次(PageSlots.slots1538 字符,枚举已省略后仍如此) #6226 块里并保留它指向的语料格,因为两条规则共用一个标记,这里正是读者检查它们如何合成的地方 —— 但要按合成读,不要按上限读:今天已经没有任何语料格是由嵌套上限单独决定的。INLINE_KEY_LIMIT只管一层,摘要沿数组/Record/联合无预算下钻(Page.slots1538 字符) #6374 那条prints an identical variant once per variant(PR 里明写「待裁决」的那条)改成折叠后的形状,并把注释从「有意保留重复」改写成「裁决落地」。App.navigation同形变体的语料真相没有随夹具消失:它以忠实的九变体夹具(8 同形 + 第七位分隔符)钉在新的 gen:docs 深度预算落地后,11 个单元格出现object | object | object | object—— 同形变体是否该去重,落在 #6226 的裁决面上 #6569 块里,并断言那种拼写只出现一次。反向验证(先写预判,再跑;含一处未命中)
正向(新实现 vs 未改夹具):预判 7 红,实测 7 failed | 8824 passed,7 条正是点名的 7 条,零偏差。
反向(删掉去重、恢复
if (rendered.length <= VARIANT_LIMIT) return full;):预判 8 红 / 2 绿,实测 7 failed | 79 passed,即 7 红 / 3 绿。PageSlots.slots1538 字符,枚举已省略后仍如此) #6226 上限块七条全绿(如预判)—— 这正是夹具分诊成立的检验:那里再红就说明它还在答去重的题;验证
pnpm --filter @objectstack/spec test→ 345 files / 8839 tests passedpnpm --filter @objectstack/spec typecheck→ 通过(tsc --noEmit+check:scripts-typecheck+check:test-typecheck)pnpm --filter @objectstack/spec check:docs→✅ 230 generated files in sync with packages/spec(合并后的树上)node scripts/check-nul-bytes.mjs→ OK;并按纪律对改动文件做了越界自查([\x00-\x08\x0b\x0c\x0e-\x1f\x7f]),无命中Changeset
写了真 frontmatter 的
.changeset/identical-union-variant-dedupe.md(@objectstack/spec: patch),不打skip-changeset。判据取自本仓先例而非感觉:#6374(同一文件、同一类改动、今天落地)带的是@objectstack/spec: patch的真 changeset;本 PR 同样改变已发布的content/docs/references/**,那是 ADR-0033 的 AI 作者权威输入。Generated by Claude Code