Skip to content

rtl: opt-in logical-class transform, shadcn's model (init --rtl, migrate rtl) - #34

Open
jackielii wants to merge 18 commits into
mainfrom
feat/rtl-transform
Open

jackielii wants to merge 18 commits into
mainfrom
feat/rtl-transform

Conversation

@jackielii

Copy link
Copy Markdown
Contributor

Closes #32

What

Adopts shadcn/ui's RTL model. The registry stays physical; a project opts in and gets logical direction classes in what it vendors.

  • "rtl": true in gsxui.json (gsxui init --rtl). With it, gsxui add and gsxui apply pass every vendored .gsx (and init the composed style.css) through internal/rtl. Without it, vendoring is byte-identical to today.
  • gsxui migrate rtl rewrites components vendored before opting in, in place, keeping your edits, idempotently, through the journaled transaction so managed hashes move with the content.
  • internal/rtl carries upstream's transform-rtl.ts table verbatim (Classes), rewrites class literals in gsx source by byte offset then formats (GSX), and rewrites @apply lists (CSS). internal/srcedit is the shared edit applier, extracted from stylegen.
  • Side-keyed content stays physical in full: Sheet switch side and Drawer switch direction arms, Sidebar's [data-side=…] rail border, Drawer's data-[vaul-drawer-direction=…] variants. This is wider than upstream's positioning-only skip and matches the existing physical-side ruling rtl.spec.ts pins.
  • The docs site renders its RTL demos from a generated site/uirtl package (upstream does the same with ui-rtl), so the RTL page shows the transform's real output. The login demo gained a NativeSelect with a Playwright assertion that its chevron sits at the logical end, the symptom RTL: physical direction classes remain in ~15 components (RTL page says logical) #32 reported.
  • foundation.css gains origin-top-start and five siblings, which the transform emits and Tailwind lacks.
  • Docs: the RTL page now describes the opt-in model (it previously claimed logical classes by default); ## rtl in docs/jsx-parity.md; CHANGELOG; README; getting-started.

An rtl: true project renders both directions: the document's dir decides at render time.

Not transformed

side/direction prop values (our floating positioning already resolves placement per direction in JS); behaviour JS (carries no classes); input-otp stays dir="ltr". tw-animate-css's sized logical slides have an upstream bug (Wombosvideo/tw-animate-css#67); gsxui emits none.

Verification

  • go test ./... -count=1 including the network e2e tests: green. TestE2ERTL vendors RTL: physical direction classes remain in ~15 components (RTL page says logical) #32's components into a scratch consumer with the flag and asserts the issue's physical classes are gone.
  • Registry sweep: every ui/*.gsx and all 440 registry/generated/*/*.gsx transform, reparse, are a fixed point, and carry no physical class outside side-keyed arms and variants; a guard test pins that no shipped file uses a construct the walker skips.
  • stylegen --check, --check-authoring, --check-layers, make audit, test-css-audit, test-theme-state, verify-generated, verify-generated-styles, gofmt -l: clean. Playwright 566 passed (one known home-showcase flake, green alone).

Spec and plan: docs/superpowers/specs/2026-09-15-rtl-opt-in-transform-design.md (read both addenda), docs/superpowers/plans/2026-09-16-rtl-opt-in-transform.md.

https://claude.ai/code/session_019u7BSQ2Kt9bkwzAXN9hGwv

Spec addendum: side-keyed classes stay physical in full; centering needs
no exception; SidebarInset margins map like upstream; shared srcedit.

Claude-Session: https://claude.ai/code/session_019u7BSQ2Kt9bkwzAXN9hGwv
Adds the logical transform-origin utilities the transform emits, and a
NativeSelect to the Arabic login demo with a Playwright assertion that its
chevron sits at the logical end.

Claude-Session: https://claude.ai/code/session_019u7BSQ2Kt9bkwzAXN9hGwv
Drawer names its physical-placement prop `direction`, after vaul's API, so
its class-list arms were being logicalised while Sheet's and Sidebar's stayed
physical. Both identifiers now count as placement.

Claude-Session: https://claude.ai/code/session_019u7BSQ2Kt9bkwzAXN9hGwv
vaul spells the drawer's physical side as its own data attribute, the role
data-[side=…] plays elsewhere, so tokens carrying that variant stay physical
in full rather than having their borders and radii logicalised.

Claude-Session: https://claude.ai/code/session_019u7BSQ2Kt9bkwzAXN9hGwv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RTL: physical direction classes remain in ~15 components (RTL page says logical)

1 participant