Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): 把 slider 交付补丁的锚点重定到 upstream 实际提供的那一行 (#4976) - #4995
Conversation
…m serves (#4976) `pnpm shadcn:check` has been reporting the declared local patch `slider-thumb-aria-delivery` as unappliable since the day it landed. The file on disk was never the problem: all four markers are present, and `verifyLocalPatches` reports nothing missing. The anchor was. It named the line that forwards the host's accessible name onto `SliderPrimitive.Thumb` — a line objectui added by hand in a014bc0 ("fix Slider accessibility", 2026-04-13), not a line the registry has ever served. Upstream renders the thumb with a class list and nothing else, which slider's own `localEdits` entry in `shadcn-components.json` had said all along. So the anchor matched the local file and could not match upstream: `found 0x`, on the first weekly run after the declaration merged. Re-targeted at upstream's single-line self-closing Thumb, expanding it into the multi-line form that carries the routed props. The payload is unchanged and `packages/components/src/ui/slider.tsx` is not touched, so no behaviour moves. With the anchor correct, the component's only divergence from upstream is the declared patch family, so it now reads as `Identical to upstream` instead of a permanent documented-modified row. The fixture that hid this is replaced with the registry's verbatim bytes, and the assertion that generalises is added: patching those bytes must reproduce the shipped primitive byte for byte. An anchor invented from the local file cannot satisfy that, so the whole class is caught offline on every PR — the module and its "Adding a patch" note now say plainly that `find` is written from registry bytes, never from `src/ui/**`. Fixes#4976 Co-authored-by: Claude <noreply@anthropic.com>
…ly array
The shared thumb class-list constant was inserted between the family docblock
and the array it annotates, so `@type {LocalPatch[]}` documented the string
instead. Moved above the docblock; no behaviour change.
Co-authored-by: Claude <noreply@anthropic.com>yinlianghui
commented
Aug 17, 2026
PM 验收 ✅ ACCEPT(#4976,批次 20) 实物核验(merge-base CI 亲读:19/19 check runs completed,零失败(两项 path-filter skipped 计绿)。 根因裁定采信:卡面两个假设(本地丢标记 / upstream 移锚)都不是 —— 锚在 PR #4933 里是照着本地已打补丁文件写的,声明当天就是死锚;五点证据链(锚行出自本仓 离线验证边界的处置正确: finding #4996(sheet/sidebar 补丁族零 application 覆盖,已实测休眠)入池。undraft + auto-merge(squash)。 Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
yinlianghui
commented
Aug 17, 2026
活体复核完成(验收评论承诺项):PM 于 13:48Z 手动触发 Generated by Claude Code |
Fixes#4976
病因:既不是本地丢标记,也不是 upstream 移锚
周检报的是 declared local patch
slider-thumb-aria-delivery不再适用于当前 upstream。issue 给的两种预设病因都不成立,实测是第三种:这个锚点当初是照着本地文件写的,从来没有对上过 upstream,所以从声明落地那一刻就是死的。三条证据:
packages/components/src/ui/slider.tsx四个 marker 全在(第 15/19/27/34 行),verifyLocalPatches('slider', …)返回[]。CI 日志自己就是这么说的:The file on disk is still correct — but the next --update cannot re-apply these。a014bc00c(2026-04-13,"fix Slider accessibility")把 upstream 的单行自闭合 Thumb 展开成三行,并加入了把宿主 accessible name 转发到 thumb 的那一行 —— 锚点钉的正是这一行。它是本地编辑,不是 registry 内容。aria-label。packages/components/shadcn-components.json里 slider 的localEdits一直写着这件事:"Upstream renders the Thumb with only a className, so the accessible name never reaches the element screen readers focus."PR #4933 的测试 fixture 也是从本地文件反推出来的(把 objectui 自己的转发行当成 upstream 的),所以它跟锚点自洽、跟 upstream 不符 —— 测试一路绿着,补丁一直是死的。这正是 #4984 那一族的失效方式。
修法:只动锚点
find重定到 upstream 实际提供的那一行(下面<后的空格是为了绕开 GitHub 正文的 HTML 清洗,真实代码里没有):packages/components/src/ui/slider.tsx完全没有出现在 diff 里 —— 29 个注册 field widget 在校验失败后从不携带aria-invalid—— #3306 全注册表守卫实测的账本 #3318 的三通道交付行为原样保留。UPSTREAM_THUMB_CLASSNAME),锚点与替换文本不可能互相写歧 —— 与 sidebar 补丁把SIDEBAR_COOKIE_NAME传进去的理由相同。shadcn-sync.js的既有设计从Modified变成Identical to upstream(Identical 29→30,Modified 17→16,documented 列表不再含 slider)。这是预期结果,不是漏统计。防复发:一条能离线判死的断言
新增
regenerates the shipped slider.tsx byte for byte from registry bytes:把声明的补丁作用在 registry 逐字节内容上,结果必须逐字节等于我们发布的那个文件。从本地文件臆造出来的锚点无法满足这条断言 —— 这就是它能在 PR 阶段离线抓住整个失效族的原因,不用等下一次周检。fixture 换成 registry 逐字节内容,provenance(URL / 仓内路径 / HEAD sha / content sha256)写在注释里,下一个读者可以复核。模块头的「Adding a patch」也补上了这条规矩:
find从 registry 字节写,永远不从src/ui/**写。原来那个「refuses when upstream restructures the thumb away」的 fixture 用的恰好是真实 upstream 形状,却断言它必须失败 —— 已按两种诚实的情形拆开重写:upstream 改 class list(现实中的 churn),和 upstream 整个删掉 thumb 元素(结构性)。后者加了 fixture 自身的守卫断言,防止 mutation 没生效而空跑成绿。
验证
registry 主机被本环境 egress 策略拒绝(
ui.shadcn.com:443→ CONNECT 403),所以线上取数不可得,如实记录。替代做法是真跑 CLI、只把它的磁盘缓存用真实 upstream 字节喂满:shadcn-ui/ui 把ui.shadcn.com/r/styles/default/*.json逐字节签入仓内apps/v4/public/r/styles/default/,按fetchRegistry的信封格式写进node_modules/.cache/shadcn-sync/即可。脚本本身没有任何 stub。可信度旁证:这样跑出来的 16 个 modified 组件计数与 CI 线上那次逐位相同(badge(4) calendar(7) chart(11) command(35) … tooltip(1)),只少了 slider(4) —— 缓存里的字节就是 CI 当时取到的字节。
node scripts/shadcn-sync.js --check→✓ slider Identical to upstream,DECLARED LOCAL PATCHES段整节消失,exit 0(修前:同一命令 exit 1,anchor expected 1x, found 0x)pnpm exec vitest run scripts/__tests__→ 50 files / 1173 tests 全绿pnpm run type-check:scripts→ exit 0;node scripts/check-control-bytes.mjs→ OK(4439 文件);pnpm exec eslint三个改动文件 → exit 0scripts/,无发布物变化),check-changeset-no-major/check-changeset-fixed均 exit 0反向验证(预判写在前,变异在 commit 之后做,
git checkout HEAD --还原):found 0x/ slider 回到⚠ 4 local line(s)/ Modified 17;测试恰好 2 红find只多插一个空格found 0xoccurrences1→2expected 2x, found 1x,check exit 1三次变异下红的都是那两条正向应用断言;两条
refuses …断言保持绿,因为坏锚点走的也是拒绝路径 —— 这一点如实记下:能区分「锚点对不对」的是新增那条逐字节往返断言,不是那两条拒绝断言。这正是加它的理由。upstream 坐标(下周它再动是新事件)
https://ui.shadcn.com/r/styles/default/slider.jsonapps/v4/public/r/styles/default/slider.json,读取于 HEAD8a7701ec27eb9cb8e0377db769fbe6d744113c52files[0].contentsha256:48bd0ba32cc7f341ecca995374be73111da2f761694cfcf91dbf8d4d9e632c06(1091 bytes / 29 lines)className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",全文无aria-labelGenerated by Claude Code