Skip to content

docs(components): stop restating the shadcn classification in README_SHADCN_SYNC.md, pin what survives - #4939

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3881-shadcn-sync-readme
Aug 17, 2026
Merged

docs(components): stop restating the shadcn classification in README_SHADCN_SYNC.md, pin what survives#4939
yinlianghui merged 1 commit into
mainfrom
claude/issue-3881-shadcn-sync-readme

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes#3881

前提复核(origin/main @ 25c8007d4,逐条实测)

三处矛盾全部现存,premise 成立:

README(改前)shadcn-components.json
resizableShadcn Components → Layout,即「可从 registry 更新」customComponents,条目自述「重同步会构建失败」
chart全文零命中components(registry 同步)
custom 计数标题 (14),正文 14 条customComponents 实为 15

机械比对(README 枚举 ↔ manifest 键集)输出:

manifest components: 46 doc shadcn enumerated: 46
manifest customComponents: 15 doc custom enumerated: 14
SHADCN doc-only: [ 'resizable' ]
SHADCN manifest-only: [ 'chart' ]
CUSTOM manifest-only: [ 'resizable' ]

卡面最关键那句实测成立:两侧计数都是 46,(46) 在整个缺陷期间都对得上,因为两处成员错误刚好互相抵消(多算 resizable、漏掉 chart)。

manifest 与磁盘一致,没有第二处缺陷需要另立卡:packages/components/src/ui/resizable.tsx 的文件头自己写明它不再是 upstream 同步文件、重同步会挂(v4 已把 PanelGroup/PanelResizeHandle 改名),与 manifest 的分类同向。manifest 是真源,漂的是 README。

外加卡面记的路径漂移一处:## Filesshadcn-sync.js 在本目录,实际一直在仓根 scripts/shadcn-sync.js

实施(按分诊非绑定方向 2+3)

散文停止枚举成员。 两份点名清单(46 + 14)整段删除,不是逐条校正 —— 校正只会把同一个漂移生成器重新上膛(#3767 / #3724 同族)。改为讲两类的含义(components 会被 pnpm shadcn:update 拉取覆写;customComponents 一律跳过),成员归属指向 manifest 与 pnpm shadcn:list

这个指向已实测兑现:listComponents() 只调 loadManifest(),不碰网络,两类都打印,并把每条 custom 的原因一并打出来(含 resizable 那句 "re-syncing would break the build")—— 严格强于原散文。顺带把 shadcn:list 从「Requires Internet」小节下挪出并新增一个 Offline 小节,否则新指向自相矛盾。

幸存的唯一枚举 = diverged 集合,且入钉。customComponents 里带 divergedFrom 的那一类不是「天生 custom」,而是曾是 Shadcn、被手工迁过破坏性升级,upstream 仍在发的版本在本仓编译不过。这一条的误读代价不是「清单陈旧」而是构建挂掉,读者必须不跑命令就看得见 —— 所以名字保留,并由钉子按成员集合双向对住 manifest。

⛔ 不钉计数。 如上,(46) 在整个缺陷期间都是绿的;计数钉在这里不是弱守卫,是假守卫。钉子里没有任何总数断言。

钉子:packages/components/src/__tests__/readme-shadcn-sync-categories.test.ts

#4860 / PR #4893 的 README 键表钉形态(双向 + 非空下限 + 无重复;期望集一律从源头解析,绝不硬编码 —— 硬编码 resizable 等于把同一个缺陷搬高一层),共 5 条:

  1. README 列的 diverged 名字,manifest 必须都标了 divergedFrom
  2. manifest 标了 divergedFrom 的,README 必须都列(packages/components/README_SHADCN_SYNC.md 的组件清单与 shadcn-components.json 相互矛盾:resizable 被标成"可从 registry 更新"(manifest 说重同步会构建失败)、chart 缺席、custom 计数 14≠15 #3881 缺陷本体方向)
  3. 两侧确实读到了 + 无重复 + components/customComponents 必须是划分(无交集)+ 空集情形显式处理
  4. ## Component Categories 散文不得点名任何 components 键 —— 让这次「删除」不被悄悄撤销
  5. README 反引号里的仓内路径必须存在(本仓没有任何链接门禁看得见这个文件)

第 3 条的空集处理值得单说:resizable 在 upstream 为 v4 重新生成后回到 components预期未来,不是缺陷,所以没有用「会在那天变红」的下限,而是写成 iff —— manifest 不标任何 diverged 时,README 的该小节必须一并消失。围栏代码块在判定前被剥掉,所以 Usage 小节里 pnpm shadcn:update button 这类合法命令示例不受第 4 条约束。

钉子落在 packages/components 包内测试邻域(README 与 manifest 都是该包的文件),进 unit project(纯 fs 读、node 环境,无需 build 依赖)。

反向验证(先书面预判,后跑;变异前已 commit,还原一律 git checkout,⛔ stash)

五次变异,预判与实测逐条相符:

变异预判实测
A. README diverged 列表加一个 manifest 没标的名字(chart)钉 1 + 钉 4 红2 failed | 3 passed
B. manifest 侧把 resizablecustomComponents 挪进 components钉 1 + 钉 3 + 钉 4 红3 failed | 2 passed ✔,钉 3 命中空集分支的正确文案
C. 改写锚点 Currently diverged:(reader 停止匹配)钉 2 + 钉 3 红,不得静默绿2 failed | 3 passed
D. 改掉 ## Component Categories 标题钉 1-4 硬抛,钉 5 绿4 failed | 1 passed
E. 复原卡面那处路径漂移钉 5 红1 failed | 4 passed

A 与 B 各证一面(README 侧 / manifest 侧);A 落在两条钉上而非一条,是因为 chart 同时是 components 的键,钉 4 也会命中 —— 这一点预判时已写明。C 是零命中断言的防空绿正查:锚点一旦失配,两条 toEqual([]) 会双双退化成 no-op,C 证明它们反而变红(钉 2 与钉 3 各报一句可执行的修法)。

验证

pnpm exec vitest run packages/components/src/__tests__/readme-shadcn-sync-categories.test.ts --maxWorkers=2
Test Files 1 passed (1) Tests 5 passed (5)
# 消费半径清扫(manifest 的既有读者 + 链接门禁自测)
pnpm exec vitest run scripts/__tests__/shadcn-check-report.test.ts \
scripts/__tests__/shadcn-local-patches.test.ts \
scripts/__tests__/shadcn-sync-fetch-cache.test.ts \
scripts/__tests__/check-doc-links.test.ts --maxWorkers=2
Test Files 4 passed (4) Tests 157 passed (157)
pnpm exec turbo run type-check --concurrency=2
Tasks: 81 successful, 81 total
node scripts/check-control-bytes.mjs → OK (4417 tracked text files)
node scripts/check-doc-links.mjs → Links are valid across 13 scan roots.
node scripts/check-changeset-presence.mjs → 空 frontmatter,显式豁免,通过
node scripts/check-changeset-no-major.mjs → 无 major
pnpm exec eslint (新钉文件) → exit 0

全部重验证走共享锁 /tmp/os-heavy-verify.lock。changeset 用空 frontmatter(纯文档 + 钉,无发布语义;packages/components/src/** 触发门禁,空 frontmatter 是该门禁写明的完整答案)。

相邻发现(未扩围)

已另立 finding 卡 #4938:check-doc-links 的包级扫描面是精确文件名匹配,包内非 README markdown(15 个文件)从未被任何门禁解析过,实测 1 条死链(TIMELINE.md 指向不存在的 prototype app)。#3881 可选修法 3 提过「顺带把包内 README* 纳入扫描面」,本 PR 刻意未取 —— 那是独立的门禁扩面,有自己的入场价,按 #3536#3572#3603/#3622#4148 的先例链每次都是独立一张卡;本 PR 只在包内测试里把这一个文件的反引号路径局部钉住(钉 5)。

未改动

shadcn-components.json(真源,无需改)、content/docs/releases/**scripts/shadcn-sync.js、任何 src/ui/** 组件文件。无 force-push。


Generated by Claude Code

…survives
`README_SHADCN_SYNC.md` published the component classification as prose — a
46-name "can be updated from the registry" census and a 14-name "should NOT be
auto-updated" one — while `shadcn-components.json` is what `shadcn-sync.js`
actually reads. Nothing held the two together and they had drifted three ways
(objectui#3881):
- `resizable` was listed as registry-updatable, though the manifest entry
records that re-syncing it BREAKS THE BUILD (upstream still ships the
react-resizable-panels v3 file, importing two names v4 does not export). A
reader following the page performed the breaking action.
- `chart` was in the manifest's `components` and absent from the page.
- the custom heading said 14 against the manifest's 15.
The `(46)` total still matched, because the two membership errors cancelled out
— so this pins member SETS and deliberately no counts.
The censuses are deleted rather than corrected: correcting them would leave a
hand-maintained copy of a machine-readable fact to drift again. The page now
states what the two categories mean and points at `pnpm shadcn:list`, which
prints both from the manifest with no network. `## Files` now gives
repo-relative paths (the sync script has always been at the repo root, not in
this directory), and `shadcn:list` is no longer filed under "Requires
Internet".
One enumeration survives, because getting it wrong breaks the build rather than
merely going stale: the diverged set (`customComponents` entries carrying
`divergedFrom`). `readme-shadcn-sync-categories.test.ts` holds it to the
manifest in both directions, guards that the censuses stay gone, and checks the
backticked in-repo paths — no link gate can see this file, since the
per-package doc-link scan root matches the exact filename `README.md`.
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)24.7 KB350 KB
Entry fileindex-CTYUWeyJ.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)9.56KB3.59KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)8.92KB3.41KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)25.13KB5.40KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.13KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.64KB2.21KB
auth (SocialSignInButtons.js)9.60KB3.89KB
auth (UserMenu.js)3.40KB1.22KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)38.46KB10.17KB
auth (createAuthenticatedFetch.js)6.34KB2.43KB
auth (index.js)2.35KB1.07KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.02KB0.88KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)26.07KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.65KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)498.61KB111.16KB
core (index.js)4.06KB1.61KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)157.05KB43.28KB
fields (index.js)232.86KB58.11KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.35KB1.38KB
i18n (pickLocalized.js)3.69KB1.73KB
i18n (provider.js)23.12KB7.62KB
i18n (useDisplayLocale.js)2.84KB1.45KB
i18n (useObjectLabel.js)27.59KB6.63KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)39.16KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.74KB
mobile (index.js)1.50KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.71KB0.42KB
mobile (useResponsiveConfig.js)1.36KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.35KB3.31KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.42KB1.42KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.81KB0.83KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.75KB18.37KB
plugin-chatbot (index.js)181.21KB43.14KB
plugin-dashboard (index.js)127.85KB32.73KB
plugin-designer (index.js)212.39KB42.83KB
plugin-detail (index.js)239.90KB60.01KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)120.42KB29.03KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)197.58KB53.00KB
plugin-kanban (index.js)52.72KB14.54KB
plugin-list (index.js)111.23KB26.97KB
plugin-map (index.js)17.91KB5.72KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)41.97KB11.33KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)83.81KB20.49KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)27.53KB9.41KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.28KB0.68KB
react (schema-input.js)1.45KB0.83KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)4.55KB2.07KB
sdui-parser (parse.js)10.76KB3.17KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.29KB0.24KB
sdui-parser (validate.js)4.69KB1.48KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.05KB1.52KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM 验收:ACCEPT(#3881,批次 18,PM 会话 session_01GTRjn8xBqp75dk7kFupVRt)

实施(方向 2+3,以减法执行):两处散文普查(46/14)删除而非修正 —— 修正等于留下会再漂移的手抄副本;README 改述两个 manifest 对象的语义并指向 pnpm shadcn:list(离线性实读 + 实跑验证,顺手把它从「需联网」小节移出 —— 承重偏离,不移则新指针自相矛盾)。唯一幸存枚举是误读即坏构建的 diverged 集(今日恰 resizable),双向成员集合钉 + 按设计零计数断言(卡面已证 (46) 靠互相抵消穿过整个缺陷期 —— 计数钉是假守卫不是弱守卫)。钉 4(散文不得点名任何 components 键,围栏代码块豁免)结构性防止普查复生。卡面附带的 ## Files 路径漂移一并修正并入钉 5(existsSync)。

实物核验(已过):3 文件 +464/−39 对账;manifest 零触碰且经磁盘对证一致(resizable.tsx 自己的文件头独立记载不可重同步 —— 无需第二张卡);标识 0;releases 0。

反向验证(五变异全中):A 加 manifest 外名字 → 预判双面红(该名恰是 components 键,两钉齐红是预判不是撞见);B manifest 侧镜像 #3881 原形 → 三钉红且空集分支正确触发;C 防空绿锚探针(改锚文字使 reader 失配 → 两钉红而非静默 toEqual([]) 空转)—— 这正是集合钉最危险的死法,主动证掉;D 标题改名 → 四钉带「勿删钉」信息硬抛;E 复原路径漂移 → 恰钉 5 红。全部书面预判先行、还原后 5/5 复绿。

有据克制(接受):卡面可选修法 3(扩 check-doc-links 扫描面)按先例链判为独立门扩张卡,立 #4938(含 15 文件、1 处实测死链的入场测量)而非顺手扩围 —— 划界正确。

CI(亲读终态):20 项全 completed,18 success + 2 skipped,零失败。

→ undraft + auto-merge (SQUASH)。


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 17, 2026 07:21
@yinlianghui
yinlianghui added this pull request to the merge queueAug 17, 2026
Merged via the queue into main with commit 8a79f2eAug 17, 2026
21 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3881-shadcn-sync-readme branch August 17, 2026 07:22
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants

@yinlianghui@claude