Skip to content

fix(components): resizable is a diverged file, not a synced one — stop the sync from breaking the build, and finish the v4 migration in it - #3029

Merged
os-zhuang merged 1 commit into
mainfrom
claude/resizable-shadcn-resync
Jul 30, 2026
Merged

fix(components): resizable is a diverged file, not a synced one — stop the sync from breaking the build, and finish the v4 migration in it#3029
os-zhuang merged 1 commit into
mainfrom
claude/resizable-shadcn-resync

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Follow-up to #3025. The plan was to re-sync ui/resizable.tsx from upstream Shadcn and delete the compensation wrapper #3024 added. That turned out to be impossible, and the sync script is actively dangerous today. This PR fixes the danger and gets the same cleanup a different way.

Re-syncing would break the build

#3024's note claimed upstream had been regenerated for v4. It has not. Both registry endpoints still serve the v3 file:

endpoint imports dead data-[panel-group-direction]
r/styles/default/resizable.json ResizablePrimitive.PanelGroup / PanelResizeHandle 8
r/styles/new-york/resizable.json ResizablePrimitive.PanelGroup / PanelResizeHandle 8

Neither name exists in react-resizable-panels@4.12.2 — it exports only Group, Panel, Separator; PanelGroup and PanelResizeHandle appear zero times in its declarations. So pnpm shadcn:update-all, an advertised script, overwrites this file with code that cannot compile.

This has already happened once. ui/resizable.tsx has been diverged since 00b61d6d8 — titled "fix: upgrade shadcn components and fix build issues" — which is the hand-fix converting import * as ResizablePrimitiveimport { Group, Panel, Separator } after a bulk sync ran.

Defusing it

resizable moves from components to customComponents in shadcn-components.json — the manifest's existing mechanism for exactly this. Verified against the script:

  • --update-all iterates Object.keys(manifest.components) (line 287), which no longer contains it
  • --update resizable now refuses: Component "resizable" not found in Shadcn registry (might be custom)

Finishing the migration in the file itself

With the file no longer synced, the fix belongs in it rather than in a wrapper built to avoid touching it. The 8 aria-[orientation=horizontal] classes #3024 put in custom/resizable.tsx move into ui/resizable.tsx, replacing the 8 dead data-[panel-group-direction=vertical] ones they were shadowing from one import away.

custom/resizable.tsx becomes a plain re-export. It stays as the sole public export site — src/index.ts star-exports ./ui and ./custom side by side, so exporting Resizable* from both would collide (TS2308). ResizableHandleProps is preserved rather than dropped: it has no in-repo consumers, but it is public API via the package root.

The group's data-[panel-group-direction=vertical]:flex-col is dropped outright — v4 writes flexDirection: c === "horizontal" ? "row" : "column" as an inline style, which outranks any class.

On AGENTS.md #7

This edits a file under packages/components/src/ui/. The rule scopes itself to "upstream 3rd-party files overwritten by sync scripts" — after this PR the file is neither. Its new header says so, loudly, along with how to undo the whole arrangement if upstream ever regenerates for v4.

Behaviour is unchanged, provably

Diffing the merged class string before vs. after: exactly the 8 dead selectors removed, 0 added, everything else byte-identical.

移除的类 (8):  data-[panel-group-direction=vertical]:h-px … [&[data-panel-group-direction=vertical]>div]:rotate-90
新增的类 (0):  (无)

The one real risk in merging base and variant classes into a single cn() was tailwind-merge eating a base utility. It does not — all 23 classes survive (variant-prefixed utilities don't conflict with bare ones).

Verification

  • pnpm --filter @object-ui/components type-check — clean
  • pnpm --filter @object-ui/app-shell type-check — clean
  • Full components suite — 53 files, 410 tests passed
  • resizable-orientation (15 tests) green unmoved, including the "only keys on attributes the installed library actually emits" guard, which proves the 8 variants still match the real DOM from their new home
  • Public surface confirmed intact in dist/custom/resizable.d.ts (all 4 names)
  • No browser pass: the class-string diff above is a stronger proof of no-op than a screenshot

No changeset — internal cleanup, no public API change.

🤖 Generated with Claude Code

…p the sync from breaking the build, and finish the v4 migration in it

`ui/resizable.tsx` looks like a Shadcn-synced no-touch file (AGENTS.md #7).
It is not one, and has not been since `00b61d6d8` ("upgrade shadcn
components and fix build issues") hand-migrated its imports from v3's
`import * as ResizablePrimitive` to v4's `{ Group, Panel, Separator }`.

Upstream never followed. Both registry endpoints — `styles/default` and
`styles/new-york` — still serve the v3 file, which reaches for
`ResizablePrimitive.PanelGroup` and `PanelResizeHandle`. Neither name
exists in react-resizable-panels v4.12.2: it exports only `Group`,
`Panel` and `Separator`. So `pnpm shadcn:update-all` — an advertised
script — overwrites this file with code that cannot compile. The commit
title above is what that looks like after the fact; it has already
happened once.

Defused by listing `resizable` under `customComponents` in
`shadcn-components.json`, which is the manifest's existing mechanism for
this: `--update-all` iterates `Object.keys(manifest.components)`, and
`--update resizable` now refuses with "not found in Shadcn registry".

With the file no longer synced, the v4 fix belongs in it rather than in a
wrapper built to avoid touching it. #3024 added
`custom/resizable.tsx` to re-key the stacked-divider styles off the dead
`data-[panel-group-direction=vertical]` and onto the `aria-orientation`
attribute v4 actually emits — 8 compensating classes shadowing 8 dead
ones in a file one import away. Those 8 now live in `ui/resizable.tsx`
directly and `custom/resizable.tsx` is a plain re-export, kept as the
sole public export site because `src/index.ts` star-exports `./ui` and
`./custom` side by side and exporting `Resizable*` from both collides
(TS2308).

The group's `data-[panel-group-direction=vertical]:flex-col` is dropped
outright: v4 writes `flex-direction: row | column` as an inline style off
`orientation`, which outranks any class.

Behaviour is unchanged, and provably so — diffing the merged class string
before against after yields exactly the 8 dead selectors removed and
nothing added, with `tailwind-merge` dropping none of the 23 survivors
now that base and variants share one `cn()` call.

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 11:37am

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-M31XW1RB.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.25KB 1.01KB
auth (org-roles.js) 6.72KB 2.85KB
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) 457.71KB 100.07KB
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.56KB 42.56KB
plugin-detail (index.js) 221.50KB 54.15KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 104.83KB 25.30KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 180.86KB 47.45KB
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.91KB 20.99KB
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) 0.77KB 0.41KB
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.00KB 0.96KB
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) 2.39KB 1.17KB
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 9969e9f into main Jul 30, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/resizable-shadcn-resync branch July 30, 2026 11:43
os-zhuang added a commit that referenced this pull request Jul 30, 2026
… serves, and refuses to write a file when it cannot (#3033)

`updateComponent` rewrote two alias shapes: `@/lib/utils` and
`@/components/ui/x`. Surveying all 46 registry entries this manifest
points at, the second one **never appears**. Shadcn moved to a different
layout, and what it serves today is:

    42 components   @/lib/utils                      ← rewritten
     8 components   @/registry/default/ui/<name>     ← missed
     2 components   @/registry/default/hooks/<name>  ← missed
     1 component    @/registry/new-york/ui/<name>    ← missed
     1 component    @/registry/default/lib/<name>    ← missed

So syncing `calendar`, `carousel`, `command`, `form`, `pagination`,
`sidebar`, `toggle-group` or `chart` left `@/registry/...` specifiers
that do not resolve from `src/ui/` — the script swapped working code for
code that cannot compile, and said `✓ Updated`. That is how `resizable`
ended up hand-patched and eventually reclassified (#3029).

Two changes:

**The rewrite table now covers what is served.** `ui/` maps to a sibling
(`./x`), `hooks/` and `lib/` step up a level (`../hooks/x`, `../lib/x`),
across any `<style>` segment. The older `@/components/ui/x` and `@/hooks/x`
forms stay in the table so a re-pointed source keeps working. Matching is
on the quoted specifier rather than on `from "…"`, so `export … from`,
dynamic `import()` and `vi.mock()` are covered.

**It fails closed.** Any `@/…` left after rewriting is now a hard refusal
to write, naming the specifier and pointing at the table. An enumerated
list of paths silently goes stale the next time Shadcn reorganises; the
guard is what makes that visible instead of catastrophic. Verified with a
hypothetical future layout (`@/registry/tokyo/blocks/button`): flagged,
while known aliases still rewrite.

Also: `--check` and `--diff` now rewrite before comparing, so import-path
style alone no longer reads as drift, and `--diff` warns when `--update`
would refuse. A registry entry shipping more than one file (`toast` ships
3) now says which files it is not writing rather than silently dropping
them.

Verified against all 46 live registry entries: every one rewrites to zero
unmapped aliases, and every rewritten relative specifier resolves on disk.
End-to-end `--update sidebar` — the entry exercising all three shapes —
now reproduces the import block already on main **exactly**, zero
difference; the remaining 21/-24 line delta is genuine upstream drift in
the component body. Restored afterwards; no component file changes here.

Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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