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:

syntax compiles to valid?
origin-(--x) — v4 shorthand, ours transform-origin: var(--x)
origin-[--x] — v3 bracket, upstream transform-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

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Jul 30, 2026 2:23pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 27.9 KB 350 KB
Entry file index-BM6rqya0.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 458.88KB 100.29KB
core (index.js) 2.16KB 0.78KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 134.67KB 34.24KB
fields (index.js) 222.07KB 54.35KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 60.52KB 17.11KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 111.59KB 28.74KB
plugin-designer (index.js) 210.51KB 42.50KB
plugin-detail (index.js) 221.42KB 54.12KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 106.54KB 25.88KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 184.01KB 48.28KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 102.39KB 24.18KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 40.32KB 10.53KB
plugin-timeline (index.js) 25.75KB 7.32KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.95KB 21.02KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 3.47KB 1.54KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 2.07KB 0.99KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 1.08KB 0.64KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.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 main Jul 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 free to 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