Skip to content

Migrate consumers to @workflowbuilder/ui (2/3: consumers) - #45

Open
librowski wants to merge 22 commits into
ui-packagefrom
ui-consumers
Open

Migrate consumers to @workflowbuilder/ui (2/3: consumers)#45
librowski wants to merge 22 commits into
ui-packagefrom
ui-consumers

Conversation

@librowski

@librowski librowski commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Part 2 of 3 of the @workflowbuilder/ui series (#44 packages → this PR consumers → #46 docs).

Summary

Switches every consumer - packages/sdk, apps/demo, apps/ai-studio - from the published @synergycodes/overflow-ui@1.0.0-beta.27 to the in-repo @workflowbuilder/ui, and removes the overflow-ui dependency tree (~700 lockfile lines). Also bumps @base-ui/react to the 1.7 line (see history below - the 1.4.1 pin's rationale turned out to be a bug on our side).

How to review

  1. Packaging (packages/sdk/package.json, vite.config.mts): @workflowbuilder/ui is a workspace:* devDependency, bundled into dist (not external), listed in dts bundledPackages - a published SDK has zero runtime references to the unpublished package (the historical publish-404 trap is closed; verified against built artifacts).
  2. CSS integration (packages/sdk/src/index.css): ui styles arrive through the JS module graph only (the explicit @import of ui's index.css double-bundled ~91 KB and is gone); xyflow's stylesheet now joins the declared ext-lib layer (@import ... layer(ext-lib)) and its duplicate JS-side import is removed - this fixes node ports collapsing to raw 6px xyflow defaults once ui port rules became layered. Three SDK module stylesheets that open @layer ui.component now declare the layer order first.
  3. Modal transitions (packages/sdk/src/features/modals/providers/modal-provider.tsx): Dialog.Root stays mounted and open drives it; previously the conditional mount skipped Base UI's enter/exit lifecycle entirely - on every Base UI version. This disproved the "1.6 broke the modal backdrop fade" note that justified the old exact 1.4.1 pin, so the catalog now holds ^1.7.0 (workspace stays locked via lockfile).
  4. Dependency hygiene: react-is pinned in the catalog next to react (recharts' peer resolution drifted to a React-17-era copy after the MUI subtree vanished - isFragment() silently false under React 19); app react deps resolve from the catalog (React 19 requires the react/react-dom pair to match).
  5. Docs edits: DECISION-LOG archival banner now covers the UI-library swap (sections 3-4 describe removed machinery); token-editing doc points at packages/ui/dist/tokens.css instead of a node_modules symlink; root CLAUDE.md notes that app builds consume the prebuilt ui dist (Turborepo adoption tracked separately).

Review history

Three-reviewer adversarial pass (SDK migration / apps + tooling / cross-branch stack coherence) after the external #44 review; all findings above landed as individual commits. Import swap verified complete (zero overflow-ui references outside changelogs); all 34 ui symbols the SDK imports exist; Select.onChange and DatePicker.valueFormat signature changes verified compatible.

2026-08-11 - OpenCode (GPT-5.6) adversarial round. 8 findings, all verified on code; the two CRITICALs are fixed here:

  • Layer-order race (CRITICAL): UI chunk CSS arriving through the JS module graph could register the top-level ui layer before index.css's reset, ext-lib, ui declaration, flipping the effective order so xyflow beat every component style. There is now a single top-level layer: xyflow and the SDK resets join ui.base, and index.css opens with the same @layer ui.base, ui.component; statement every UI stylesheet carries - load order can no longer invert the cascade. Verified live in demo (one order statement registers; an unlayered :root override prepended before all component CSS still wins).
  • SSR crash (CRITICAL): the always-mounted modal portal evaluated createPortal(..., document.body) during render, so SSR of a closed editor threw ReferenceError - the portal now mounts after hydration.
  • Also: the dead .date-picker > div > button override (trigger height restored), the stale "pinned to 1.4.x" claims across README/CHANGELOG (actual dependency: ^1.7.0, recorded in a ui changeset), the README's nonexistent scroll token and unshipped 100vh reset, an npm-consumer path in the token guide, and the missing @workflowbuilder/ui install step in the custom-node guide (on UI Library docs: page-per-component + source-generated tables (3/3) #46).
  • Tracked as tasks: de-bundling ui from the SDK (double-copy problem), Next.js/'use client' verification, React 18 smoke test.

Verification

  • SDK: 202 tests, typecheck, lint, build:lib - green after every rebase.
  • Demo, measured in-browser: styled node ports (was: 6px dark dots), node-as-port connection target, modal enter/exit fades running the full 200ms lifecycle, menus/tooltips on Base UI 1.7.0.
  • Changeset: minor on @workflowbuilder/sdk, covering the bundling swap, derived type shapes, DOM/class-name churn, and the restored modal fade.

Notes

librowski-synergy and others added 5 commits August 11, 2026 11:56
The SDK bundles @workflowbuilder/ui and @base-ui/react into its dist, but
declared them as runtime dependencies - so a published @workflowbuilder/sdk
would 404 on @workflowbuilder/ui (not published) at npm install. Move both to
devDependencies (they are inlined, consumers don't install them).

Add @workflowbuilder/ui to the dts plugin's bundledPackages so its types are
inlined into dist/index.d.ts instead of leaking unresolvable
import('@workflowbuilder/ui') references into the public type surface. Verified:
no real ui/base-ui imports remain in dist/index.d.ts.

Remove the obsolete docs/overflow-ui.md (described the old external
@synergycodes/overflow-ui local-dev flow this migration replaces).
… branch point

Rebasing onto current main pulled in commits landed after this branch
diverged (WB-339's use-on-connect.tsx, PR #48's language-selector spec,
PR #48's ai-studio undo-redo buttons) that still imported the retired
@synergycodes/overflow-ui package. Point them at @workflowbuilder/ui
like every other already-migrated call site, and regenerate the
lockfile for the fully rebased dependency graph.
…undle

@workflowbuilder/ui is bundled (not externalized) into the SDK, so
@base-ui/react was being inlined transitively into dist/index.js even
though the SDK never imports it directly. @phosphor-icons/react was
already a direct SDK dependency but was likewise getting bundled a
second time on top of the copy demo/ai-studio install directly.

Add both to the SDK's Vite external list and move @base-ui/react from
devDependencies to dependencies, mirroring the treatment already used
for i18next/jsonforms/immer/zustand and matching how packages/ui
declares them.
The datepicker z-index fix (index.css) and the variable-suggestions
backdrop suppression (variable-text.module.css) both keyed off
`.base-Modal-root` and `.mantine-Popover-dropdown`, class names from
the retired MUI Base + Mantine modal/popover. Neither selector matched
anything after the Base UI migration, silently reintroducing both bugs:
a datepicker opened inside a modal rendered behind it, and the
variable-suggestions backdrop no longer disappeared when a modal opened
on top of it.

ModalProvider now toggles a `wb-modal-open` class on <body> whenever the
SDK's modal store has a modal open - a stable signal owned by the SDK
rather than coupled to @workflowbuilder/ui's internal markup. Both
selectors are repointed at it; the datepicker fix additionally keys off
Base UI's own `[data-open]` popup attribute, since the DatePicker's
popover class is a hashed CSS module class internal to
@workflowbuilder/ui.
librowski-synergy and others added 17 commits August 11, 2026 11:56
Update the move-ui-library-in-repo changeset: @base-ui/react is now a
regular dependency of the SDK rather than an inlined implementation
detail, and note explicitly that internal DOM structure and class
names of the bundled UI changed (MUI Base + Mantine -> Base UI), so
consumer styles or tests written against internal class names may need
updating. Stays a minor bump.
Three SDK stylesheets open @layer ui.component without declaring the
ui.base/ui.component order first. Module-graph order currently loads the
declaration earlier by luck; an import reorder or dynamic import would
register ui.component first and invert the cascade inside the ui layer -
react-day-picker's raw defaults would then beat the calendar theming.
Duplicate order statements are no-ops, so the prepends are free.
Removing the overflow-ui/MUI subtree left react-is resolvable only from
stale transitive copies, and pnpm rewired recharts' react-is peer to
17.0.2 (supplied by a vitest transitive). react-is 17 does not know
React 19 element symbols, so isFragment() is always false and fragment
children inside charts would be silently dropped. Pinned in the catalog
next to react/react-dom (must move in lockstep) and declared by
ai-studio to steer the peer resolution.
ModalProvider mounted Dialog.Root conditionally: it appeared with open
already true and unmounted on close, so Base UI's transition lifecycle
(data-starting-style / data-ending-style) never ran in either direction -
on every Base UI version. The root now stays mounted with open driving
it, and the last modal content is retained through the exit transition
because the store clears it on close.
The 1.4.1 pin guarded against a modal-fade regression attributed to Base
UI 1.6; the fade was actually broken by the SDK's own conditional Dialog
mounting (fixed in the previous commit) on every version. Caret range in
the catalog restores dependency dedupe for npm consumers of the published
packages; the workspace stays locked to an exact version via the lockfile.
dist/style.css carried every ui rule twice: once through the explicit
@import of ui's index.css and once through the JS module graph (each
bundled ui chunk carries a libInjectCss-injected CSS import). The JS
graph vector stays - it also tree-shakes CSS of components the SDK does
not use; the redundant @import goes. Verified by marker counts halving
in the rebuilt bundle (typography 24 -> 12) and a visual pass on demo.
…x token paths

The decision log's sections 3-4 describe the overflow-ui Vite alias and
import layout as current; the banner now covers the swap to the in-repo
library and corrects the disproven CSS-extraction claim. The token doc
pointed readers into a node_modules symlink instead of packages/ui/dist.
A caret react next to catalog react-dom is the exact drift the catalog
comment warns about - React 19 requires the pair to match (React error 527).
xyflow CSS arrived through two vectors (a bare css @import and a JS-side
import in diagram.tsx), both unlayered. Once the ui library's port rules
moved into @layer ui.component, unlayered xyflow beat them and node ports
rendered as raw 6px dark dots. The stylesheet now joins the declared (and
previously unused) ext-lib layer, which the ui layer outranks; the
duplicate JS import goes away. Verified in demo: styled ports and the
node-as-port connection target both render from the layered rules.
UI chunk CSS arrives through the JS module graph and can parse before
index.css; its stamped statement then registered top-level ui first and
the SDK's reset/ext-lib/ui declaration could only append, flipping the
effective order to [ui, reset, ext-lib] - xyflow beat every component
style. Now every stylesheet opens with the identical
@layer ui.base, ui.component; statement, xyflow and the SDK resets join
ui.base, and no second top-level layer exists to race against.

Verified live in demo: only one order statement registers, xyflow's
handle defaults sit in ui.base and lose to ui.component, and an
unlayered :root override prepended before all component CSS still wins.
The always-mounted portal evaluated createPortal(..., document.body) on
every render, so SSR of a closed editor threw ReferenceError.
DatePicker renders Popover.Trigger as the button itself, so the
.date-picker > div > button override matched nothing and date/datetime
fields lost their 2.5rem trigger height.
README and CHANGELOG promised a validated 1.4.x pin and blamed later
versions for transition regressions; the regression was our conditional
mounting, and the actual dependency is ^1.7.0. Changeset records the
dependency-contract change.
README advertised a scroll-thumb-hover token that does not exist and
claimed the html/body/root 100vh sizing ships in the package. The token
guide only described monorepo paths and suggested editing generated dist
CSS. Both now describe what actually ships and how an npm consumer
overrides tokens.
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.

3 participants