Skip to content

fix(scripts): 把 slider 交付补丁的锚点重定到 upstream 实际提供的那一行 (#4976) - #4995

Merged
yinlianghui merged 2 commits into
mainfrom
claude/issue-4976-shadcn-slider-patch-anchor
Aug 17, 2026
Merged

fix(scripts): 把 slider 交付补丁的锚点重定到 upstream 实际提供的那一行 (#4976)#4995
yinlianghui merged 2 commits into
mainfrom
claude/issue-4976-shadcn-slider-patch-anchor

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#4976

病因:既不是本地丢标记,也不是 upstream 移锚

周检报的是 declared local patch slider-thumb-aria-delivery 不再适用于当前 upstream。issue 给的两种预设病因都不成立,实测是第三种:这个锚点当初是照着本地文件写的,从来没有对上过 upstream,所以从声明落地那一刻就是死的。

三条证据:

  1. 盘上标记齐全。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
  2. 被钉住的那一行是 objectui 自己加的。 commit a014bc00c(2026-04-13,"fix Slider accessibility")把 upstream 的单行自闭合 Thumb 展开成三行,并加入了把宿主 accessible name 转发到 thumb 的那一行 —— 锚点钉的正是这一行。它是本地编辑,不是 registry 内容。
  3. upstream 全文根本没有 aria-labelpackages/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 清洗,真实代码里没有):

find: 「 < SliderPrimitive.Thumb className="block h-5 w-5 … disabled:opacity-50" />」
replace: 展开成 Thumb / className / {...splitSliderThumbProps(props).thumb} / 自闭合 四行
  • payload 一个字节没动,packages/components/src/ui/slider.tsx完全没有出现在 diff 里 —— 29 个注册 field widget 在校验失败后从不携带 aria-invalid —— #3306 全注册表守卫实测的账本 #3318 的三通道交付行为原样保留。
  • 那串很长的 class list 只拼写一次(UPSTREAM_THUMB_CLASSNAME),锚点与替换文本不可能互相写歧 —— 与 sidebar 补丁把 SIDEBAR_COOKIE_NAME 传进去的理由相同。
  • 锚点正确之后,slider 与 upstream 的唯一分歧就只剩这一族声明式补丁本身,因此它按 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 0
  • changeset 空 frontmatter(只碰 scripts/,无发布物变化),check-changeset-no-major / check-changeset-fixed 均 exit 0

反向验证(预判写在前,变异在 commit 之后做,git checkout HEAD -- 还原):

变异预判实测
还原修前锚点check exit 1 / found 0x / slider 回到 ⚠ 4 local line(s) / Modified 17;测试恰好 2 红完全一致(2 failed | 20 passed)
find 只多插一个空格同上形状,found 0x完全一致(2 failed | 20 passed)
occurrences 1→2锚点在但计数不符 → expected 2x, found 1x,check exit 1完全一致(2 failed | 20 passed)

三次变异下红的都是那两条正向应用断言;两条 refuses … 断言保持绿,因为坏锚点走的也是拒绝路径 —— 这一点如实记下:能区分「锚点对不对」的是新增那条逐字节往返断言,不是那两条拒绝断言。这正是加它的理由。

upstream 坐标(下周它再动是新事件)

  • 源:https://ui.shadcn.com/r/styles/default/slider.json
  • 仓内逐字节副本:shadcn-ui/ui apps/v4/public/r/styles/default/slider.json,读取于 HEAD 8a7701ec27eb9cb8e0377db769fbe6d744113c52
  • files[0].content sha256:48bd0ba32cc7f341ecca995374be73111da2f761694cfcf91dbf8d4d9e632c06(1091 bytes / 29 lines)
  • 锚定行:Thumb 元素,单行自闭合,只带 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-label

Generated by Claude Code

…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>
@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM 验收 ✅ ACCEPT(#4976,批次 20)

实物核验(merge-base 7d1017790 与报告一致):4 files,+205/−58 —— 只动锚、fixture 与 docstring 规则;packages/components/src/ui/slider.tsx零触碰已亲验(#3318 已验收落地件的行为逐字节保全);第四个文件 shadcn-sync.js 仅扩测试导出(OBJECTUI_HEADER),理由成立。模型标识 msg/diff 双 0;releases/ 零触碰。

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

根因裁定采信:卡面两个假设(本地丢标记 / upstream 移锚)都不是 —— 锚在 PR #4933 里是照着本地已打补丁文件写的,声明当天就是死锚;五点证据链(锚行出自本仓 a014bc00c 的本地编辑、upstream 全文无 aria-label、shadcn-components.json:299 自己的散文、CI 首次调度即失败的时序、fixture 与锚同源自本地文件所以测试一直绿)完整闭合。新增的 registry 逐字节 round-trip 断言把这一失败类从「下周 weekly 才知道」变成「每个 PR 离线即抓」—— 三个变异里唯一在全部方向翻红的正是它,而两条 refuses 断言被诚实记录为不能区分死锚(不冒充钉子)。

离线验证边界的处置正确:ui.shadcn.com 被沙箱 egress 拒绝(403),dev 未绕策略、未 stub 脚本 —— 用真 CLI + 从 shadcn-ui/ui 仓内登记的 registry JSON 灌缓存,并与 CI 实测的 16 个组件计数逐位对上。活体最终确认由 PM 接手:本 PR 落 main 后我会手动触发一次 Shadcn Components Check workflow 作为线上复核;下一次 weekly 报告 Identical 30 / Modified 16 是修复生效,不是记账回退。

finding #4996(sheet/sidebar 补丁族零 application 覆盖,已实测休眠)入池。undraft + auto-merge(squash)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 17, 2026 13:24
@yinlianghui
yinlianghui added this pull request to the merge queueAug 17, 2026
Merged via the queue into main with commit d73e44cAug 17, 2026
20 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-4976-shadcn-slider-patch-anchor branch August 17, 2026 13:24
@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

活体复核完成(验收评论承诺项):PM 于 13:48Z 手动触发 shadcn-check.yml(run 32036758977,head 85fdb0612,含本 PR)—— completed success,与 09:13Z 立案那次失败(schedule,patch 类 exit 1)同一 workflow、真实网络取 registry。离线替代验证的结论被线上复现证实,#4976 闭环。下一次 weekly 报告 Identical 30 / Modified 16 即为本修复的常态读数。


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shadcn sync: declared local patches are failing

1 participant

@yinlianghui