fix(components,i18n): Sheet/Dialog 关闭按钮接入 common.close,并让 shadcn 同步机制携带该补丁 (objectstack#5505) - #3455
Merged
Merged
Conversation
…携带该补丁 (objectstack#5505) Sheet/Dialog 两个 primitive 自动渲染的关闭按钮是纯图标(lucide X), 其 sr-only span 就是该控件的可访问名称。上游 shadcn 把它硬编码成英文 字面量,因此在 zh/ja/es 下控制台里每一个抽屉与模态框都朗读英文 "Close" (~20 个 SheetContent 消费方,以及全部 DialogContent 消费方)。 改动把该 span 换成 <CloseSrLabel />,实现放在 packages/components/src/lib/close-label.tsx —— 用 createSafeTranslation 解析 common.close,未挂载 I18nProvider 时回退英文,以保证仓内大量按英文 名定位这些按钮的既有用例与 e2e 不受影响。 关键点:src/ui/** 由 registry 重新生成(AGENTS.md 第 7 条),直接手改会被 下一次 pnpm shadcn:update 静默还原,且因为上游会连同用法一起删掉, type-check 也发现不了。因此补丁以数据形式声明在 scripts/shadcn-local-patches.mjs,由同步流程在每次写入时自动重新施加 (包括 --force),并做双向强制: - 锚点在当前上游已失效 / 变得有歧义时,--update 拒绝写入而不是静默漏掉; - pnpm shadcn:check 在补丁从磁盘文件丢失、或已无法重新施加到上游时 以非零码退出并打印 id/issue/原因; - 一个纯离线测试在每个 PR 上守住同一不变量(registry 在 CI 不可达, 依赖网络的检查无法充当闸门)。 registry 返回无法解析的内容(离线、egress 403)时按 fetch 错误处理, 避免把网络故障误报成补丁失效。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
同一个组件可能同时命中两类失败(磁盘文件丢了补丁 + 上游挪了锚点), 相加会把它报成 2 个组件。改为按组件名去重计数。 已验证:仅还原 dialog.tsx 时输出 "1 component(s)" 并以 1 退出; 补丁齐全时退出 0。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
yinlianghui
marked this pull request as ready for review
August 6, 2026 06:45
This was referenced Aug 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes objectstack-ai/objectstack#5505
问题
Sheet/Dialog两个 primitive 自动渲染的关闭按钮是纯图标(lucide X),它的sr-onlyspan 不是装饰 —— 那就是该控件的可访问名称,屏幕阅读器朗读的是它,getByRole('button', { name })匹配的也是它。上游 shadcn 把这个名称硬编码成英文字面量,所以在 zh/ja/es 下,控制台里每一个抽屉和模态框都朗读英文 "Close":约 20 个SheetContent消费方,以及全部DialogContent消费方(ChatDock、ActivityFeed、metadata-admin、AiChatPage、BuildDebugDrawer、PeoplePicker、RecordDetailDrawer、ObjectChart、AiPendingActionsInbox 等)。唯一的例外是
NavigationOverlay,它用[&>button:last-of-type]:hidden把 primitive 自带的关闭按钮藏掉并自渲染一个 —— 这一点在 origin/main 上仍然成立,已复核。为什么不能直接改那两个文件
packages/components/src/ui/**是 No-Touch 区(AGENTS.md 第 7 条),由 registry 重新生成。就地手改会被下一次pnpm shadcn:update静默还原,而且type-check 抓不到:上游会连同用法一起删掉,重新生成后的文件照样编译通过。这正是shadcn-sync.js自己在command.tsx注释里记录过的失效模式。方案(PM 裁定的 Route B)
补丁以数据形式声明在
scripts/shadcn-local-patches.mjs,由同步流程本身负责携带,而不是靠人记得重做。载荷放在重新生成区之外。 真正的 i18n 实现落在
packages/components/src/lib/close-label.tsx(src/lib/不参与重新生成),primitive 里只留一行引用。这样每个文件的补丁只有两处单行锚点 —— 加一行 import、换一个元素 —— 对上游改动的抗性远高于把 hook 内联进去。翻译走
createSafeTranslation,解析common.close(自 objectstack#5430 起十个语言包都有),未挂载I18nProvider时回退英文。这一条是硬要求:仓内大量既有用例按英文名定位这些按钮且不挂 provider(plugin-form/src/discardGuard.test.tsx、plugin-detail/src/__tests__/InlineCreateRelated.closeButtonName.test.tsx等),渲染出原始 key 会连带打挂它们。declared = enforced
声明本身不值钱,能否大声失败才是。三处独立强制:
--update写入时重新施加,包括--force。补丁在本地改动拒绝逻辑之前施加,因此仅有已声明补丁这一处差异的组件会与上游比较为相等、正常同步,"拒绝覆盖"继续只对未声明的改动报警。occurrences固定为 1),--update报错退出而不是静默漏掉该补丁 —— 猜一个位置比停下来更糟。pnpm shadcn:check非零退出,当补丁从磁盘文件里丢失(被--force、手改或错误合并抹掉),或已无法重新施加到当前上游(上游挪了锚点,下次同步会失败 —— 提前预警)。另外:registry 返回无法解析的内容(离线、egress 403、schema 变更)时按 fetch 错误处理,不会把网络故障误报成补丁失效。
验证
方向在跑之前先写死,结果与预测一致。
反向验证 —— 把两个 primitive 还原成 origin/main 的英文字面量后重跑:
en 正例与 no-provider 回退两侧都绿(回退语义未变,这正是它们作为 pin 的意义);非 en 正例、英文字面量负例、源码级闸门在改动前全红、改动后全绿。
大声失败路径(补丁被还原时跑
pnpm shadcn:check):补丁齐全时同一命令退出码 0 —— 且此时 46 个 registry 请求全部失败(沙箱 egress 拦截),闸门没有误报,证明了上面那条防误报保护。
测试命令(均在仓根,加 flock 串行化):
pnpm exec vitest run四个新增/改动测试文件 → 29 passedpnpm exec vitest run packages/components→ 89 files / 648 tests passedpnpm exec vitest run packages/plugin-form packages/plugin-detail packages/plugin-view→ 91 files / 856 passedpnpm exec vitest run packages/app-shell packages/plugin-dashboard→ 305 files / 2632 passedpnpm --filter @object-ui/components type-check→ 通过eslint改动文件 → 0 error 0 warningnode scripts/check-control-bytes.mjs→ OK;另对本次全部改动文件做了控制字符自查(gate 扫不到的范围)需要 reviewer 注意的两点
packages/components/src/lib/close-label.tsx是新增文件,严格说超出了派发单列的文件清单。这是刻意的、也是 Route B 的必要条件:载荷若留在src/ui/**内,它自己就会被重新生成掉,补丁将不得不整段重新插入实现代码。放在src/lib/才能让补丁缩到两行锚点。请确认这个取舍。ui.shadcn.com,因此无法真跑一次线上--update。补丁引擎改为用忠实还原上游内容的 fixture 做确定性离线验证(施加 / 幂等 / 锚点消失 / 锚点有歧义 / 报错文案),并且仓库里这两个 primitive 的当前内容就是该引擎生成的输出。离线也是 CI 能真正设闸的唯一形态 —— 顺带说明:.github/workflows/shadcn-check.yml每周跑且带continue-on-error: true,吞掉退出码,所以 PR 级的实际闸门是那个离线测试(在ci.yml里跑),而不是该 workflow。Generated by Claude Code