Skip to content

docs(components): record why the eight small Shadcn divergences exist, and split --check into documented vs undocumented - #3054

Merged
os-zhuang merged 1 commit into
mainfrom
claude/shadcn-small-divergence-triage
Jul 30, 2026
Merged

docs(components): record why the eight small Shadcn divergences exist, and split --check into documented vs undocumented#3054
os-zhuang merged 1 commit into
mainfrom
claude/shadcn-small-divergence-triage

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

I proposed triaging the eight components diverging from upstream by 1–2 lines, on the theory they'd be stale cruft worth dropping to widen the safe-to-sync set.

The theory was wrong. All eight are load-bearing; none can be synced away. The safe-to-sync count cannot go up, so the deliverable inverts: instead of removing divergence, this records why each one exists.

What they actually are

Six are the Tailwind v4 arbitrary-value migrationpopover, tooltip, hover-card, menubar, dropdown-menu, context-menu.

Upstream still ships v3's origin-[--radix-…]. I compiled both forms against the installed tailwindcss 4.3.3:

syntaxcompiles tovalid?
origin-(--x) — v4 shorthand, ourstransform-origin: var(--x)
origin-[--x] — v3 bracket, upstreamtransform-origin: --x❌ browsers drop it

A bare custom-property name isn't a value, so syncing would leave every one of these popovers animating from the default origin instead of the one Radix computes. Applied repo-wide in 925051db6, with zero v3-form occurrences left.

form adds Record<\data-${string}`, string | undefined>toFormItemso callers can attach a stabledata-testidto the field wrapper — the ADR-0054 testability contract, same family ascommand's contentProps`.

sonner re-exports toast so consumers take it from @object-ui/components rather than depending on sonner directly. plugin-form, plugin-kanban and others import it that way; syncing breaks those imports.

What ships instead

A localEdits field on each manifest entry, surfaced by the script:

  • printed in full when --update refuses, so the reason arrives at the moment someone is about to override it
  • reduced to a [documented] / [UNDOCUMENTED] marker in --check
⚠ popover 1 local line(s) upstream lacks — --update would refuse, 1 upstream line(s) pending [documented]
⚠ command 35 local line(s) upstream lacks — --update would refuse, 3 upstream line(s) pending [UNDOCUMENTED]
These carry local edits a sync would delete:
documented (8): context-menu(2), dropdown-menu(2), form(1), hover-card(1), menubar(2), popover(1), sonner(2), tooltip(1)
UNDOCUMENTED (9): badge(4), calendar(7), chart(11), command(35), select(32), sheet(4), sidebar(24), slider(4), table(17)
→ nobody has written down why these diverge. Until someone does,
there is no way to tell a deliberate fix from stale drift.

That split is the point. Divergence with a recorded reason is a decision; divergence without one is an open question — and that ambiguity is exactly what let resizable rot for a major version (#3029).

Verification

No changeset.

🤖 Generated with Claude Code

…, and split --check into documented vs undocumented
Triaged the eight components diverging from upstream by 1–2 lines, on the
theory that small divergences would be stale cruft worth dropping to widen
the safe-to-sync set. That theory was wrong. **All eight are load-bearing;
none can be synced away.**
Six are the Tailwind v4 arbitrary-value migration (`popover`, `tooltip`,
`hover-card`, `menubar`, `dropdown-menu`, `context-menu`). Upstream still
ships v3's `origin-[--radix-…]`, which on Tailwind 4.x compiles to a bare
`transform-origin: --radix-…` instead of `var(--radix-…)` — invalid CSS
that browsers drop, leaving the open/close animation scaling from the
default origin rather than the one Radix computes. Verified by compiling
both forms against the installed tailwindcss 4.3.3: the paren form emits
`var()`, the bracket form does not. Applied repo-wide in 925051d, with
zero v3-form occurrences left.
`form` adds `Record<\`data-${string}\`, string | undefined>` to FormItem so
callers can attach a stable `data-testid` to the field wrapper — the
ADR-0054 testability contract again, same family as `command`'s
`contentProps`. `sonner` re-exports `toast` so consumers take it from
@object-ui/components rather than depending on `sonner` directly;
plugin-form and plugin-kanban import it that way.
So the deliverable inverts. Nothing gets synced; instead each reason is
recorded in a new `localEdits` field on the manifest entry, and the script
surfaces it — printed in full when `--update` refuses, and reduced to a
`[documented]` / `[UNDOCUMENTED]` marker in `--check`, which now splits the
Modified bucket:
documented (8): context-menu, dropdown-menu, form, hover-card,
menubar, popover, sonner, tooltip
UNDOCUMENTED (9): badge, calendar, chart, command, select, sheet,
sidebar, slider, table
That split is the point. Divergence with a recorded reason is a decision;
divergence without one is an open question, and until someone writes it
down there is no way to tell a deliberate fix from stale drift — which is
exactly the ambiguity that let `resizable` rot for a major version.
Manifest only; no component source changes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@vercel

vercelBot commented Jul 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectuiIgnoredIgnoredJul 30, 2026 2:23pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Main entry (gzip)27.9 KB350 KB
Entry fileindex-BM6rqya0.js
StatusPASS

📦 Bundle Size Report

PackageSizeGzipped
app-shell (index.js)8.20KB2.97KB
app-shell (runtime-config.js)7.42KB2.32KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)7.57KB2.97KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)1.17KB0.53KB
auth (AuthProvider.js)22.10KB4.37KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.12KB3.41KB
auth (LoginForm.js)17.86KB5.29KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.43KB2.09KB
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)35.76KB9.11KB
auth (createAuthenticatedFetch.js)4.37KB1.69KB
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)4.91KB0.87KB
auth (useIsWorkspaceAdmin.js)1.61KB0.85KB
collaboration (CommentThread.js)18.38KB4.49KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)3.65KB1.42KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.25KB0.53KB
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)458.88KB100.29KB
core (index.js)2.16KB0.78KB
create-plugin (index.js)9.28KB2.98KB
data-objectstack (index.js)134.67KB34.24KB
fields (index.js)222.07KB54.35KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.32KB1.77KB
i18n (index.js)2.46KB0.96KB
i18n (pickLocalized.js)1.70KB0.83KB
i18n (provider.js)5.37KB1.72KB
i18n (useObjectLabel.js)25.17KB5.80KB
i18n (useSafeTranslation.js)3.26KB1.44KB
layout (index.js)38.45KB10.67KB
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.05KB1.53KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)6.84KB2.42KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)3.67KB1.12KB
permissions (evaluator.js)4.41KB1.44KB
permissions (index.js)0.91KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.52KB
permissions (usePermissions.js)1.55KB0.71KB
plugin-ai (index.js)15.71KB3.79KB
plugin-calendar (index.js)44.90KB12.35KB
plugin-charts (index.js)60.52KB17.11KB
plugin-chatbot (index.js)180.09KB42.72KB
plugin-dashboard (index.js)111.59KB28.74KB
plugin-designer (index.js)210.51KB42.50KB
plugin-detail (index.js)221.42KB54.12KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)106.54KB25.88KB
plugin-gantt (index.js)162.26KB39.53KB
plugin-grid (index.js)184.01KB48.28KB
plugin-kanban (index.js)47.82KB13.18KB
plugin-list (index.js)102.39KB24.18KB
plugin-map (index.js)16.80KB5.24KB
plugin-markdown (index.js)13.65KB4.67KB
plugin-report (index.js)40.32KB10.53KB
plugin-timeline (index.js)25.75KB7.32KB
plugin-tree (index.js)8.36KB2.81KB
plugin-view (index.js)85.95KB21.02KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.71KB3.53KB
providers (index.js)0.44KB0.22KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.67KB2.37KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)19.28KB6.38KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.02KB0.55KB
sdui-parser (codegen.js)4.09KB1.74KB
sdui-parser (index.js)3.47KB1.54KB
sdui-parser (parse.js)10.04KB2.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 (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 (index.js)2.07KB0.99KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)0.20KB0.18KB
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.04KB1.93KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)0.20KB0.18KB
types (ui-action.js)1.08KB0.64KB
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

@os-zhuang
os-zhuang merged commit 4c2a465 into mainJul 30, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/shadcn-small-divergence-triage branch July 30, 2026 14:30
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-zhuang