Add @workflowbuilder/ui + ui-tokens packages (1/3: packages) - #44
Open
librowski wants to merge 42 commits into
Open
Add @workflowbuilder/ui + ui-tokens packages (1/3: packages)#44librowski wants to merge 42 commits into
librowski wants to merge 42 commits into
Conversation
This was referenced Jun 24, 2026
librowski
marked this pull request as ready for review
June 24, 2026 12:31
librowski
requested review from
lukasz-jazwa,
piotrblaszczyk and
szymon-t-sc
as code owners
June 24, 2026 12:31
- SegmentPicker: type onChange/onSelect/onClick event as React.MouseEvent instead of MouseEventHandler (types vs runtime mismatch in public API) - Modal: apply className and ...rest to the same root element with ref, not split across the modal and content divs - Button: log a clear error for children that match no variant instead of silently rendering null - tokens: drop the swallowing try/catch in ejectTokens so a failed write aborts the build instead of feeding empty/stale JSON to the CSS step - tokens: lift tsconfig lib to es2021 (String.replaceAll) - add per-package lint-staged configs so in-package tsc runs on commit
- remove empty output.json and stale TODO in src/index.ts - rebrand vite lib + combine-css plugin names away from Overflow UI - Shape: replace '' sentinel with explicit 'default' - Snackbar/NodePanel JSDoc match actual behavior; check-built-css comment no longer references a non-existent stylelint rule - Switch: drop redundant styles prop (keep className) - Tooltip: inline the constant open/close delays, drop the speculative delay context (placement context kept) - tokens: scope codeChunks to generateCSSBundle; fix double-slash token dir
Set up Vitest in @workflowbuilder/ui (jsdom) and @workflowbuilder/ui-tokens (node) and wire both into the root pnpm test. Extract the pure helpers that were buried in component files into sibling, non-barrel modules so they can be unit-tested without rendering: date-picker date-utils (parseDateValue/normalizeInitialValue/dayjsTokenToDateFns), and the menu/tooltip placement mappers. Public types are re-exported so the package API is unchanged. Coverage: button structural guards, rangeBetween, getValidShape, the date parsing/timezone logic, placement->side/align + offset mapping, and toFileName. 50 ui + 4 tokens tests. Exclude specs from the dts build so they don't ship.
- narrow @base-ui/react peer range to ~1.4.0 (1.5/1.6 regressed transitions) - resolve the bundle-vs-dependency split: date-fns, react-day-picker, clsx and Phosphor are bundled into dist, so move them to devDependencies; only the external react-textarea-autosize stays a runtime dependency (peers unchanged) - rewrite npm description/keywords away from the diagram-library boilerplate - fix the README dev section (real pnpm --filter commands; no preview-page) and drop leftover 'Overflow UI' branding - correct CLAUDE.md: ui-tokens is private (not in changeset ignore)
Retire the single-package v* tag scheme now that the repo publishes two packages. Each package releases via its own scoped tag and workflow: - @workflowbuilder/sdk@X.Y.Z -> release-sdk.yml (trigger + version-parse updated) - @workflowbuilder/ui@X.Y.Z -> release-ui.yml (new; OIDC trusted publisher, provenance, build:ui, version/idempotency checks, GitHub Release) Add a UI lint/typecheck/test/build job to pr-check. Update RELEASE.md and CLAUDE.md for the scoped-tag two-package flow and correct the stale changeset ignore claim. Each package needs its own npm trusted publisher (manual, noted in RELEASE.md).
ui is the sole consumer of Base UI and is version-locked to 1.4.x (later versions regressed transitions), so it should own that pinned version rather than delegate it to every consumer via a peer range. Move @base-ui/react to dependencies (catalog: -> 1.4.1 at publish); react / react-dom stay peers as true singletons. Consistent with react-textarea-autosize, the other external.
react-day-picker v9 ships no styles of its own; the component never imported its stylesheet, so the calendar grid, month caption and nav rendered with browser defaults (vertical nav, bordered day buttons). Import react-day-picker/style.css so libInjectCss bundles it into dist (and the docs combined index.css), giving the calendar its real layout.
react-day-picker ships a default blue accent and no hover state. Re-theme it
to the design tokens: selected days get a filled --ax selected background
instead of the accent ring, today uses a themed accent color, and day buttons
gain a hover background (react-day-picker has none). New tokens:
--ax-public-date-picker-date-{hover-background,selected,today}-color.
react-day-picker paints the prev/next chevrons with --rdp-accent-color, which defaulted to a raw blue and clashed with the rest of the themed calendar. Paint them with a neutral nav token instead, and retheme --rdp-accent-color (focus rings, range endpoints) to the design-token accent so no raw blue remains.
- IconSwitch picked the thumb icon from the controlled `checked` prop, so in uncontrolled mode the icon never swapped. Render both icons and swap them via the switch's `data-checked` state in CSS (works controlled + uncontrolled). - Accordion fired `onToggleOpen` twice when the chevron was clicked: the Collapsible button's toggle and the header's onClick both ran. The header is the single click target now (Collapsible is display-only, controlled via isExpanded); also fixed aria-expanded to reflect isExpanded.
- @base-ui/react is a regular dependency now, not a peer (the entry still claimed peer); react / react-dom are the only peers. - document the review-driven breaking changes (Switch styles prop removed, Shape '' -> 'default', SegmentPicker event type, Modal prop target) and add a Fixed section (calendar styling, Accordion double-fire, IconSwitch, Button).
First release under the @workflowbuilder/ui name, versioned 2.0.0 to sit on the Workflow Builder 2.x line. Rewrite CHANGELOG.md from scratch: a short note that the library moved from @synergycodes/overflow-ui (with a link to its old changelog) plus a single 2.0.0 entry covering the highlights and the migration deltas. Update the README technology note to match.
…t regressions Several component stylesheets (node ports, radio buttons, edge labels, snackbar, node icon/description, base typography) shipped rule bodies outside the ui.base/ui.component layers, letting unlayered CSS beat layered CSS regardless of specificity - the same bug class that shipped collapsed decision-node ports (WB-190). Extend check-built-css.ts with a second, independent check that fails the build if any dist CSS rule sits outside an @layer block, with documented exceptions for :root custom-property blocks, the deliberate react-day-picker override, and verbatim ui-tokens output.
…ort barrel gaps - License: Apache-2.0 (was MIT), LICENSE file copied from root and added to the published files array, publishConfig.access set to public - matches the packages/sdk precedent. - @phosphor-icons/react moves from devDependencies to a regular dependency and joins the Vite externalPackages list, so it's imported rather than bundled - same treatment as @base-ui/react. - Export CollapsibleProps and IconSwitchProps, which were already reachable through the barrel but not exported as types. - Extract tooltip's open/close delay constants and placement context into tooltip-context.ts so the barrel export of tooltip.tsx no longer leaks internals. - Fix a stale "Overflow UI" reference in css-layers.md.
packages/tokens had no eslint config, no lint/typecheck scripts, and no README, and its checks weren't wired into pr-check.yml despite feeding packages/ui's build. Add the non-React eslint re-export used by packages/types and packages/execution-core, document the tokens.json -> Style Dictionary -> --ax-* CSS pipeline, and fold lint/typecheck/test into the existing UI CI job.
Stale generated artifact from an earlier TypeDoc setup; unreferenced anywhere in the repo and not produced by the current docs build.
Replaces the hand-rolled CSS scanner with postcss (already a devDependency). Also tightens the :root exemption: a top-level :root block now passes only when every declaration is a custom property.
- config.ts names sets exactly as tokens.json exports them; buildManifest()
validates the config up front and lists the available keys on mismatch
- Style Dictionary runs with brokenReferences: 'throw' - a dangling token
reference fails the build instead of shipping literal {token.path} values
- dist/tokens.css inlines the primitives (no relative @import), so the file
survives being copied out of the package alone
In the built barrel, libInjectCss evaluates per-chunk CSS before the entry CSS, so the order statement loaded last and the cascade inverted (ui.base beat ui.component) for every consumer following the README import path - secondary/ghost buttons rendered white-on-transparent. Duplicate statements are no-ops, so stamping the statement into each dist stylesheet makes any load order safe: barrel, subpath imports, and files copied out alone. combine-css-bundle now also throws when dist/assets is missing instead of silently skipping the published entrypoints.
The calendar was the one component whose styles lived outside the layer contract: react-day-picker's stylesheet shipped unlayered and our theming had to stay unlayered too, winning by specificity. The stylesheet now joins ui.base at import time (@import ... layer(ui.base)) and the theming moved into @layer ui.component, winning by layer order alone.
The plugin visited every rule, so @Keyframes steps got box-sizing too and the declaration became part of the animation (animated values override normal author styles while running). Injection now allowlists plain styling contexts (media/supports/container/layer) and skips anything unknown - a missed redundant declaration is easy to spot, a wrongly injected one is not. vite-plugin-dts excludes root-level *.spec.mts: a spec escaping the filter drags vitest's type graph into the ts program and stalls the build.
…ecks Three new checks close the guard's blind spots: - every dist stylesheet must lead with the @layer order statement (the inverted-cascade class was invisible to the old checks) - only layer names declared in src/styles/layers.css may appear - a typo creates an undeclared layer that silently wins the cascade - every *.css entry in package.json exports must exist in dist, and no dist stylesheet may use @import (self-containment) The rdp- substring exemption is gone: react-day-picker styles are layered now, so the guard runs with no exceptions. Shared FailureReport type, all parsing through postcss.
…ename Two latent traps from adversarial review: a set listed twice (or in both groups) made two builds race for one output file, and the theme filter matched primitive names as substrings of the whole source path - a set whose kebab name appears in the directory prefix (e.g. 'Tokens' vs ./dist/tokens/) would have silently emptied every theme.
Guard (check-built-css.ts): - allowlist matches exact dist paths, not basenames - a component entry named 'tokens' no longer inherits the token files' exemption - exports check walks conditional-export objects, not just string targets - layers.css is parsed for the @layer statement with a clear error instead of an unchecked .first cast that crashed on a leading comment - comments inside :root token blocks are no longer flagged Plugin (postcss-box-sizing.mts): - ancestor walk is properly typed (Document in the parent chain surfaced TS2322 under strict) and the :root skip covers grouped/qualified selectors - dropped the inert vendor-prefix stripper; spec grew to 10 cases tsconfig now includes the root build .mts files and scripts/ so these files leave the typecheck blind spot; specs and vite.config.mts stay out - with allowImportingTsExtensions (needed for the config's .mts imports) or a vitest-importing spec in the program, tsc stalls for minutes.
Decision log for keeping build-time per-rule injection over lint-based and selector-based alternatives (box-sizing does not inherit; portals escape subtree selectors; a global reset leaks onto consumer elements). css-layers.md points at the mechanism; CHANGELOG no longer claims styles.css establishes the layer order - every stylesheet carries it now.
…dding The padding sits on the wrapper, not the form element, so clicks in that area never focused the field. Forward pointerdown to the element; adornment clicks keep their own behavior.
ease-out on the exit transition drops most opacity in the first frames, which reads as an abrupt vanish; the pre-migration modal used symmetric ease. Applied to popup and backdrop in both directions.
Extending tsconfig.json's include with root build files made every tool that reads the package tsconfig (TypeDoc in the docs generator, and vite-plugin-dts before it) pull them into its program - TypeDoc hung indefinitely. The component program is src-only again; build files and scripts typecheck through tsconfig.node.json, chained in pnpm typecheck.
The build injects border-box per rule, so hover/connecting states of the opted-out handle silently shrank from 1rem to 0.75rem. State rules now repeat the opt-out; the plugin spec documents the contract.
Unlayered :root defaults raced consumer overrides by load order: a lazily loaded component stylesheet silently restored the default. The build now wraps top-level :root blocks in @layer ui.base (new postcss plugin for component CSS, copy transform for the generated token files), so any unlayered consumer override wins by layer rules. check-built-css drops its :root and token-file exemptions and also rejects var() calls whose first argument is not a dashed ident (var(), var(no-dashes)).
Base UI itself supports 17-19 and no React-19-only API is used; the ^19 peer was declared from the build target, not an actual requirement.
piotrblaszczyk
approved these changes
Aug 12, 2026
Dialog.Popup carried a full-screen wrapper, so every click landed inside the popup and Base UI never saw an outside press - the modal could only be dismissed with the close button or Esc. The popup is now the dialog window itself (centered, capped at the viewport with its own scrolling content area), and the orphan .backdrop-close class is gone.
Its Positioner was the only one without the shared popup class, so a calendar opened inside a modal rendered behind it - which the SDK papered over with a global z-index override. The 4px sideOffset goes away with it: the shared class already carries that gap.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part 1 of 3. Stacked series for the
@workflowbuilder/uiwork:Summary
Brings the overflow-ui Base UI rewrite into the monorepo as the publishable
@workflowbuilder/uipackage (+ private@workflowbuilder/ui-tokens). Nothing consumes it on this branch -sdk/demo/ai-studiostill build against the published@synergycodes/overflow-ui@1.0.0-beta.27; the switch-over is #45.How to review
Most of the file count is the vendored library (
packages/ui/src/**) - low review effort. The real surface, in suggested reading order:packages/ui/combine-css-bundle.mts- post-build CSS steps; stamps the@layer ui.base, ui.component;order statement into every dist stylesheet (duplicates are no-ops), so any import order and any bundler is safe. This closes the inverted-cascade bug where barrel consumers gotui.basebeatingui.component(invisible secondary buttons).packages/ui/scripts/check-built-css.ts- the built-CSS guard, 5 checks: novar(var()), every rule layered (no exceptions), order statement leads every file, only known layer names, exported entrypoints exist + no@importin dist.packages/ui/src/components/date-picker/- react-day-picker's stylesheet joinsui.basevia@import ... layer(); calendar theming lives inui.componentand wins by layer order (the old unlayered-specificity hack is gone).packages/tokens/src/- manifest-driven pipeline:config.tsusestokens.jsonkeys verbatim,manifest.tsvalidates them at build start and derives every path once;brokenReferences: 'throw';dist/tokens.cssis fully inlined (survives being copied out alone).packages/ui/postcss-box-sizing.mts(+ spec) - injectsbox-sizingonly inside an allowlist of styling contexts;@keyframessteps are never touched. Rationale + rejected alternatives:packages/ui/postcss-box-sizing.decision-log.md.packages/ui/tsconfig.json+tsconfig.node.json- component program vs build-tooling program. Mixing them made TypeDoc (docs generator) and vite-plugin-dts hang;pnpm typecheckruns both.release-ui.yml, scoped tags (@workflowbuilder/ui@X.Y.Z); plainv*tags no longer trigger releases after this merges.Review history
@phosphor-icons/reactexternalized).easemodal timing (exit fade read as abrupt), tsconfig split after the TypeDoc hang surfaced in UI Library docs: page-per-component + source-generated tables (3/3) #46.border-boxinjection overrode the base rule'scontent-boxopt-out) - state rules now repeat the opt-out, plugin spec documents the contract;--ax-public-*andtokens.css) moved into@layer ui.base- a consumer's unlayered:rootoverride now wins regardless of stylesheet load order;check-built-cssdropped its:rootand token-file exemptions;var()guard widened (empty/undashed first argument), combiner fails on an empty assets dir, manifest rejects file-name collisions after normalization, tokens build cleans dist and the UI build fails fast when tokens dist is missing;reactpeer widened to^18.0.0 || ^19.0.0(Base UI supports 17-19; no React-19-only API in src);acc7-100token in the Figma export and manifest-as-source-of-truth for token file names tracked as backlog tasks.Verification
pnpm build(guard runs inside) + tests (ui 60, tokens 8) + typecheck (both programs) + lint + knip - green.ui.base- a single top-level layer, so load order cannot invert the cascade.Notes
2.0.0is the pending first release of the package.releasetogether, with the@workflowbuilder/ui@2.0.0tag pushed no later than the docs deploy (docs instructnpm install @workflowbuilder/ui).