Skip to content

fix(extractor): resolve imported css composition chains statically - #773

Draft
owjs3901 wants to merge 9 commits into
mainfrom
fix/compose-imported-chains
Draft

owjs3901 wants to merge 9 commits into
mainfrom
fix/compose-imported-chains

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Refs #688, #682 (W47 후속). #706 위에 쌓은 PR이며 #706이 먼저 병합되어야 합니다. 기준 커밋은 #706의 회귀 검증 완료 head 08a04858ba1538a62b47ad79f28a3a494d75b17a입니다. GitHub base는 CI 실행을 위해 main으로 둡니다.

요약

#706은 다른 모듈이 단일 정적 객체로 만든 css 결과를 합성합니다. 그 결과를 B 모듈에서 여러 css 인자로 다시 합성하고 C에서 가져오면, B의 선언 정보가 사라져 C가 두 경쟁 클래스를 붙이는 버그를 수정합니다. 기존 모듈 해석과 Composition을 그대로 사용해 알려진 객체·css 결과를 인자 순서대로 합치며, 알 수 없는 체인은 위치 포함 오류로 거부합니다.

  • A → B → C → D처럼 합성된 export를 다시 가져와도 앞 단계만 지정한 속성은 유지하고 같은 충돌 키의 뒤 선언만 남깁니다.
  • 가져온 결과의 최상위 const alias와 로컬 재합성을 지원합니다. 이름이 같은 중첩 shadow는 실제 semantic binding/scope로 구분합니다.
  • 알려진 null·undefined·false 조각과 빈 css()는 선언을 추가하지 않습니다.
  • 실패는 해당 native 정의에 붙여 두고 실제 소비한 binding만 보고합니다. 쓰지 않는 실패한 export 때문에 모듈 전체를 거부하지 않으며, 일반 상수 계산의 기존 fallback은 유지합니다.
  • 이 소비 판정은 spelling-only 목록이 아니라 기존 semantic reference와 실제 member 선택을 쓰는 Inline 경로에서 수행합니다. 실패한 import와 이름이 같은 중첩 외부 alias, 읽지 않은 실패 sibling은 거부하지 않고, const holder의 실패한 static/computed/nested member를 실제 읽으면 정의 위치 오류를 유지합니다.

동작

// A
export const base = css({ color: 'red', p: 2 })
// B
import { base as child } from './a'
export const base = css(child, { color: 'orange', margin: 3 })
// C
import { base as child } from './b'
export const base = css(child, { color: 'blue' })

수정 전 fresh #706 WASM에서 C는 e ${child}를 내보냈고 공유 CSS에는 .e{color:blue}.d{color:orange}가 모두 남았습니다. 실제 Chromium은 blue 대신 rgb(255, 165, 0)를 계산했습니다. 파일별 CSS도 경쟁 클래스를 모두 유지해 stylesheet 순서에 따라 결과가 정해졌습니다.

수정 후 실제 fresh WASM 출력:

// singleCss: C
export const base = "b c e";
// sheet: .e{color:blue}.d{color:orange}.a{color:red}.c{margin:12px}.b{padding:8px}

// per-file CSS: C
export const base = "c-a c-b c-c";
// C sheet: .c-c{color:blue}.c-b{margin:12px}.c-a{padding:8px}

두 모드에서 실제 Chromium 계산값은 color rgb(0, 0, 255), padding 8px, margin 12px입니다. 공유 sheet의 blue-before-orange 순서를 그대로 두고, C의 적용 클래스에서 orange atom만 제거합니다. B와 A 자체를 렌더링하면 각자 원래 orange/red와 padding을 유지합니다. 충돌 키는 기존처럼 속성·선택자·반응형 단계·레이어이고, styleOrder는 키에 넣지 않고 살아남은 선언의 원래 값을 유지합니다.

같은 모듈의 기존 합성 규칙과 styleOrder 처리, atomic sheet의 정렬, selector specificity는 바꾸지 않습니다. 런타임 styling helper나 새 의존성을 추가하지 않습니다.

새로 생기는 오류

정적으로 선언을 알 수 없는 imported native css 합성 체인은 기존처럼 클래스를 붙이지 않고, 파일:줄:열, 문제가 된 코드와 정적 선언이 필요한 이유를 포함한 빌드 오류로 거부합니다. 상수 evaluator의 재실행으로 이 거부를 우회하지 않습니다.

  • 모듈 또는 로컬 정의의 순환: native css chain must not be cyclic
  • 동적 규칙/인자: native css rules must be constant at build time
  • 해석할 수 없는 모듈 또는 export: native css chain needs module/export
  • native 체인 안의 선언을 알 수 없는 외부 클래스: native css chain needs complete static rule metadata

실제 public WASM 프로브의 오류 예시(두 CSS 모드에서 같은 정의 위치):

/src/a.ts:1:93: `css()` cannot use `child` at build time: native css chain must not be cyclic
/src/b.ts:1:59: `css()` cannot use `{color:globalThis.tone}` at build time: native css rules must be constant at build time
/src/b.ts:1:99: `css()` cannot use `child` at build time: native css chain needs module `./missing`

남는 한계

원래 정적 선언을 알 수 없는 외부 클래스와 다른 styling API의 범위는 #706 및 각 API 담당 PR의 계약을 따릅니다. 이 PR은 native css의 정적 합성 import 체인만 다룹니다.

검증

  • RED: fix(extractor): let a later css() part replace an earlier one's declarations #706 동작에서 34개 중 계약 회귀 22개 실패, 쓰지 않는 실패한 export/외부 클래스 control 12개 통과.
  • 수정 후 기존 import 체인 회귀 42개를 그대로 유지하고 소비 정밀도 18개, 함수 값 규칙 객체 거부·상수 배열 opaque control 4개를 추가한 64개 모두 통과; cargo test --workspace 통과, 기존 snapshot 추가 변경 없음. cargo fmt 및 Rust 1.99 clippy -D warnings 통과.
  • fresh WASM과 root bun run build 통과; bun test 5,498 pass / 0 fail, 100% 함수·행 커버리지; strict TypeScript 7 검사 통과.
  • 실제 Chromium·public WASM 테스트 26개 통과: 3/4모듈, 혼합 alias/로컬 레벨·hover·반응형, 원래 fix(extractor): let a later css() part replace an earlier one's declarations #706 6개 회귀, 순환·동적·누락 import 거부를 두 모드에서 검증. 소비 정밀도 8개는 실제 WASM에서 수정 전 4개 실패·4개 control 통과를 먼저 확인한 뒤, 수정 후 모두 통과했습니다.
  • bun lint 통과(기존 경고 2개 외 없음). 모든 local compiler/browser 실행에 CARGO_BUILD_JOBS=2와 전체 프로세스 트리 메모리 watchdog 적용; 메모리 초과 중단 없음.
  • 기존 snapshot은 이 후속 변경에서 모두 byte-identical입니다. main 대비 보이는 compose_rules_the_module_computes, composed_css_keeps_composed_classes, evaluate_build_time_values 3개 snapshot 변경은 선행 #706의 변경이며, 이 PR에서 추가 변경하지 않았습니다.
  • 기존 imported_classes 테스트의 의도된 기대 변화: 여러 객체 인자를 가진 twice는 알려진 atom으로 합쳐지고, runtime 소비는 별도 위치 포함 오류 회귀로 검증합니다. 기존 namespace/배열/tagged/keyframes opaque control은 유지합니다.
  • .changepacks/changepack_log_compose_imported_chains.json: bindings/devup-ui-wasm Patch.
  • 최종 소비 정밀도 gate review APPROVE(HIGH). 정확히 테스트한 다섯 파일을 한 atomic unit으로 staging했고 첫 실제 commit hook(lint, tarpaulin, Bun)을 실행해 통과했습니다(2e26c28). 첫 Linux 실행 37941509318은 다른 세 job이 모두 통과했고 Rust 커버리지만 99.97%(10219/10222)였습니다. 누락된 세 줄의 실제 동작 두 가지를 검증하는 4개 회귀만 추가했으며 구현·snapshot·워크플로는 바꾸지 않았습니다.
  • 테스트 전용 추가 커밋도 훅을 생략하지 않고 통과했습니다. 최종 head: f1d61e4, 두 번째 훅 watchdog peak 약 3.23 GB, 메모리 초과 중단 없음.
  • 최종 Linux CI 37945633664 전체 success: publish, benchmark, landing-next-e2e, vinext-rsc-css-e2e. 실제 publish 로그에서 Rust 100.00%(10222/10222), Bun 함수·행 100%를 확인했습니다. 두 CSS 모드의 landing/Next 실제 E2E, Codecov 업로드와 changepack 단계까지 모두 통과했습니다. 이 PR은 draft이며 fix(extractor): let a later css() part replace an earlier one's declarations #706 뒤에 사용자가 직접 검토·병합합니다.

2026-10-14 업데이트: 최신 main 76b1a15059d3d1b26c8ac20bb622a36cc937a7a4를 포함한 갱신된 #706 head 4450b6041a56ccf95645cdad80827430038d8862를 두 부모 merge commit으로 병합하고 main 재병합이 already up to date임을 확인했습니다; 충돌 파일 libs/extractor/src/lib.rs에서 native css 체인 실패의 정렬·중복 제거·즉시 거부와 main의 Emotion namespace 숫자 pixelify 변환을 각각 유지했습니다; 기존 snapshot 추가 변경 없음, 추가 build error 없음(기존 정적 체인 거부 계약 유지), fresh WASM·root build·Rust 3198개·Bun 5905개/0 fail/100%·정상 CI-unset 훅 통과(Windows Rust 98.16%, whole-job peak 4.222 GB), 기존 Chromium/public WASM 26개 통과, draft 상태 유지.

2026-10-15 갱신: 갱신된 선행 #706 03f266cd18800a0893266c4313ea283d6fc72145를 실제 두 부모 merge commit b995eaecdd06c0279cd343bff84094a3e3b83988로 병합했습니다(부모 e80410c8 + 03f266cd). 선행 head가 승인된 main a6ecc125를 이미 포함함을 ancestry로 확인했고, 충돌 없이 병합됐습니다. 기존 native chain 실패의 정렬·중복 제거·즉시 거부, semantic 소비 판정과 main 숫자/Emotion/const class-string 동작을 모두 유지했습니다. 기존 snapshot은 갱신된 #706과 동일하고 새 후보·추가 build error·runtime·dependency는 없습니다(기존 위치 포함 정적 체인 거부 계약 유지). 기본 stack/jobs2/6GB 감시로 Rust 4,076개·fmt/clippy, fresh WASM/root, Bun 5,909/0/함수·행 100%/lint, 기존 실제 Chromium/public WASM 26개와 두 모드 출력이 통과했습니다. 독립 gate APPROVE 뒤 정상 전체 hook도 통과했으며(12GB cap, peak 4.541GB, Windows Rust 98.24% 17,646/17,962은 Linux와 별도), 현재 head CI 38759791729 확인 중입니다. 이 PR은 draft를 그대로 유지하며 #706 뒤에 사용자가 직접 검토·병합합니다.

현재 draft head b995eaecdd06c0279cd343bff84094a3e3b83988의 CI 38759791729가 전체 SUCCESS입니다. publish·benchmark·landing-next-e2e·vinext-rsc-css-e2e가 모두 통과했고 Linux Rust 100% (12,713/12,713), Bun 5,909 pass / 0 fail 및 함수·라인 100%, preload regression 8 pass / 0 fail을 확인했습니다. CI 뒤에도 draft=true / OPEN / CLEAN임을 재확인했으며 상태 변경은 하지 않았습니다.

2026-10-18 갱신: 갱신된 선행 #706 435edf61b491c9e47fe7d9df4ec6e86d2949d5f5를 두 부모 merge commit 627e6c4a6c2ce4921bc1838631b5027c24734d24로 충돌 없이 병합했습니다; 그 parent가 main 3ca6182f를 이미 포함하므로 불필요한 main 재병합은 하지 않았고 기존 정적 체인의 실패 정렬·중복 제거·즉시 위치 포함 거부와 semantic 소비 판정을 보존했습니다; 새 snapshot 후보·수동 변경·추가 build error·runtime·dependency는 없고 기존 알려지지 않은 imported chain 오류 계약은 유지합니다; 기본 stack/jobs=2/native 6GiB 제한에서 Rust workspace·fmt/clippy·fresh WASM/root build가 통과했고 정식 전체 hook은 12GiB 제한에서 464.0초/peak 4,147,904,512 bytes로 통과했습니다(Bun 5,930개/0 fail/함수·라인 100%); 3모듈 public WASM은 singleCss의 base = "a b c"/.c{color:purple}.b{margin:12px}.a{padding:8px}와 파일별 모드의 base = "a-a a-b a-c"/.a-c{color:purple}.a-b{margin:12px}.a-a{padding:8px}를 출력했고 draft 상태를 그대로 유지합니다.; 최종 head 627e6c4a6c2ce4921bc1838631b5027c24734d24의 CI 39056153622는 5개 job 전체 SUCCESS이고 Linux Rust 100%(13521/13521), Bun 5930 pass/0 fail/함수·라인 100% 및 별도 preload 9 pass/0 fail을 확인했습니다.

owjs3901 and others added 3 commits October 9, 2026 18:17
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Changepacks

@devup-ui/wasm@1.0.83 → 1.0.84 - bindings/devup-ui-wasm/package.json

Patch

  • Rewriting styled-components, Emotion and vanilla-extract imports keeps type-only imports type-only: import { type DefaultTheme, ThemeProvider } now leaves import type { DefaultTheme } on the package instead of a value import that made it a runtime dependency, and an import type declaration is left as written. spin.getName() on a keyframes() binding becomes the generated keyframes name instead of a call on a string that threw at runtime
  • The styled-components and Emotion compat ThemeProvider now gives its theme through React context: useTheme, withTheme and the new ThemeConsumer return the real theme object (an object merged over the outer theme, or what a theme function makes of it) instead of an empty Proxy of var() references, withTheme forwards refs, lets a caller's theme win and sets a displayName. Styles keep reading the CSS variables the provider declares. styled-components v6.4 createTheme is available from the compat entry, and ThemeConsumer and createTheme imports are redirected there instead of staying a runtime dependency on styled-components
  • Resolve native css() definitions composed from known rule objects and css() results through imported module chains. Later declarations replace earlier declarations at the same property, selector, breakpoint and layer, while earlier-only declarations remain. Imported composition chains with cycles, dynamic parts or unresolved imports now produce located build errors instead of silently deferring precedence to stylesheet order.
  • css(a, b) composing classes whose styles the build knows (a const bound to css() in the file, or a css() result another module exports with a rule object every value of which is known) merges their styles: a later part's declaration replaces an earlier one's for the same property, selector, breakpoint and layer, also under conditions (css(base, cond && danger)) and for ||/?? parts, where the classes used to be joined and the stylesheet order picked the winner. vanilla-extract style([a, b]) passes each composed style as its own argument, so the later one wins, and a style composed again later (style([a, b, a])) is kept. Classes the build does not know (strings, props, CSS Modules) are kept as they are
  • styled(Base) extending a styled component the file binds to a const renders Base's tag directly with Base's styles composed under its own, so the extension's declarations replace Base's for the same property, selector, breakpoint and layer instead of losing to them by stylesheet order; Base's attrs apply before the extension's. attrs merge as styled-components merges them: className is joined with the caller's, style is merged and other props replace earlier ones. On an element, a className or style spread after the explicit prop now wins, as React merges props in the order they are written
  • stylex.props() and stylex.attrs() merge namespaces this file creates key by key as StyleX does: a later namespace's key replaces an earlier one's, so props(red, blue) is blue and props(blue, red) is red, and a key set to null removes the earlier value; conditions (cond && styles.x, cond ? a : b) choose per key. Arguments the build cannot read key by key (namespaces from other modules, props, dynamic namespaces, include()) keep the previous joined behavior
  • Respect source-order property replacement across conditional spreads in styled rule objects, and document that styled callbacks cannot return runtime class names. No new build errors; existing located Cannot compose errors remain unchanged.
  • Preserve unitless legacy vendor numbers, classify vendor time values as milliseconds and emit the Opera vendor prefix correctly; reject nested responsive arrays and known dead unprefixed CSS property names with source-located build errors, while preserving supported experimental CSS, unknown properties and documented shorthands.
  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • Typography documents the emitted default breakpoints, and source-backed integration tests keep the public breakpoint lists and range bounds synchronized with the production defaults. No runtime changes or new build errors.
  • @emotion/css is aliased by default: css and keyframes compile like Devup UI's own (numbers in px, as Emotion reads them), injectGlobal declares global CSS. cx takes class names, as in Emotion: strings and runtime values are classes kept as written (falsy values are skipped), an object is a { name: condition } class map, arrays nest, and inline css() calls and css() classes compose with the rules css(a, b) uses. merge(className) splits what the build can read (string tokens, template literal parts) and composes the css() classes in it, keeping a runtime string it cannot split as it is. Namespace members, constant-string bracket access, const destructuring and const alias chains compile through these same named APIs with scope-aware binding resolution; numeric values retain Emotion units after constant inlining and build-time evaluation. The Emotion import disappears when no runtime-only member is used. Ambient declarations type the styling imports (cx(...args: ClassNamesArg[]), merge(className: string)). New located build errors: cx() cannot use a spread, a getter, a setter or a method in a class map, or a template literal call; merge() takes exactly one class string, not a spread or a template literal call; the root module has no default export, namespace member keys must be constant strings naming supported exports, and styling functions cannot be passed, mutated or re-exported. Optional styling reads/calls, namespace rest destructuring, styling defaults or nested patterns, non-const aliases, exported styling bindings and reads whose initialization order cannot be established are build errors instead of runtime dependencies. Styling loaded through require, import-equals or dynamic import must use static ES module imports; an unawaited dynamic namespace that the build cannot follow is also a located error. Its runtime-only APIs (cache, flush, hydrate, sheet, getRegisteredStyles) stay on @emotion/css, including their nested/default destructuring, optional reads and recognized runtime-loader access
  • Emotion's CacheProvider is available from the compat entry as a no-op that renders its children, and import { CacheProvider } from '@emotion/react' is redirected there instead of keeping @emotion/react as a runtime dependency; the cache it configures holds styles Emotion inserts at runtime, which Devup UI writes at build time
  • In rule objects written against Emotion, styled-components or vanilla-extract, a property whose value is an array of literals (background: ['red', 'linear-gradient(...)']) declares each value in order as a browser fallback, as those libraries do, instead of being read as Devup UI breakpoints; numbers get px as the library gives them. Arrays holding runtime values stay as written
  • style-order-range only checks styleOrder where the build reads it: a prop of a Devup UI component and a top-level key of a style object passed to css/globalCss/keyframes. A styleOrder key in nested data, in a prop value such as an element passed as a prop, or in a call of a function sharing a name with an Object.prototype member is no longer reported. The array, typography and media rules already skip non-style data, and css-utils-literal-only accepts a css()/keyframes() result held in a const inside a function. No new build errors.
  • The extractor guide shows reproducible native-element output, generated CSS-variable bindings, and actual atomic CSS rather than obsolete class names. Documentation only: no new build errors.
  • Global CSS keeps the order it is written in: globalCss rules, @import and @font-face now appear in source order within each file (the later of two equal rules wins as written, repeated imports and font faces count where they are written last), object globalCss declarations on the same selector keep their written order, and custom cascade layers are declared in the order files first use them (nested layers after their parent) instead of alphabetical order
  • Keep leading JSX compiler pragmas through extraction, styled lowering, namespace rewrites and generated stylesheets with correct source maps. Enabled Emotion aliases keep the React JSX import-source compatibility bridge; other sources and opted-out Emotion are preserved. No new build errors.
  • Accept JSX in .js, .mjs and .cjs throughout extraction without changing TypeScript dialects or plain JavaScript output, and pass parsed source-map objects to webpack callbacks for Next SWC compatibility. Malformed relevant source now fails with located parser diagnostics instead of unlocated parser-panic messages or silently recovered output.
  • Preserve complete template interpolations when extracting style values: fold exact pure template text and keep dynamic element values in CSS variables without dropping side effects. Standalone css() values that cannot be known at build time now reach the existing located build error instead of silently losing their interpolations.
  • Selectors: a nested rule without & in a styled or css template is scoped to the component instead of becoming a global rule (.child {} and > p {} select descendants, :hover {} and ::before {} the component), and a rule nested in a selector list applies under each of its selectors; content keeps the quotes of its strings (content: 'x', content: ""); params go on the pseudo-class that takes them, so _groupNthChild gives :is(...):nth-child(2n) .a also when nested; a bare selectors key is a pseudo-class name, an attribute of the element or a descendant selector ('div p', '> p'), under a parent too, and keeps the case of class names; ::part(), ::slotted(), ::highlight(), ::picker() and the view-transition pseudo-elements get two colons. New build errors: a _name prop or selector key that names nothing (_notASelector, selectors={{ div: ... }}), and a string under a selector that is not CSS text (_hover="external-class")
  • Styled components the build generates are wrapped in React's forwardRef, so a ref passed to them reaches the element they render on React 18 as well as React 19; the file imports forwardRef from react when it defines one
  • Styled components no longer pass every prop to the tag they render: $ props, theme and the props their style functions or attrs read are kept away from a tag unless it takes them as attributes, and shouldForwardProp (Emotion options or styled-components withConfig) is evaluated at build time. A shouldForwardProp the build cannot evaluate is a build error naming the file, line and code, with the forms it accepts. An element using a styled component the file defines drops props the component neither reads nor passes on; spread props are passed as written
  • Styled components render what their as prop names, defaulting to the tag or component they were defined with, and pass forwardedAs on as as, as styled-components and Emotion do; as used to reach the rendered tag as an attribute. Component.withComponent(target) on a styled component the file binds to a const builds a component rendering the same styles and attrs as target (a tag name or a component JSX can name), where it used to call a method the generated function does not have and throw
  • stylex.attrs() gives the dynamic CSS variables it sets as style attribute text ("--a:" + value), as StyleX does for spreading onto non-React targets, instead of a style object; stylex.props() keeps the object React takes. The attrs() return type declares style as a string
  • Tailwind classes in a className: each class compiles on its own and every class Tailwind does not define (custom, plugin and analytics classes) stays as written, where any recognized class used to rebuild the whole className without them and could rewrite part of another class; a class touching a template interpolation (icon-${name}) is left as written, and template classNames no longer get spaces inserted around interpolations. A class whose variant is not understood stays as written instead of applying unconditionally; variants follow Tailwind v4: stacked variants compose (hover:focus: matches the element itself), hover is guarded by @media (hover: hover), group-/peer- use the group/peer marker classes (and group-hover/name), and data-, aria-, has-, nth-, , **, supports-[…] and [&…]/[@media …] variants compile. Utilities emit all their declarations (size-, rounded-t-, truncate, sr-only, not-sr-only, text-sm with its line height, text-sm/6, leading-), translate/rotate/scale/skew compose through Tailwind's individual properties and registered variables (@Property) with negative values kept negative, and arbitrary values follow Tailwind's grammar: spaces for underscores except in url() and var() names, _ for an underscore, spaces around math operators, type hints, [property:value] and (--variable) shorthands. Words that only share a utility's prefix (select-wrapper, order-summary, cursor-foo) are no longer compiled
  • Order standalone nonresponsive hover-capability guards within their selector group so active, focus and disabled styles can override hover. Keep responsive Tailwind hover and Devup selector prop ordering unchanged. No new build errors.
  • More of Tailwind CSS v4 compiles: container queries (@sm:, @max-md:, @min-[400px]:, @sm/main:) and @container utilities, mask-* (all but the gradient masks), text-shadow-, inset-shadow-, font-variant-numeric utilities (tabular-nums and the like), 3D transforms (rotate-x/y/z, translate-z, scale-z, translate-3d, scale-3d, transform-3d, backface-, perspective-, perspective-origin-), contain- and outline-hidden (with its forced-colors fallback). The project's Tailwind CSS is read further: functional @Utility with --value() and --modifier(), @apply in @Utility, and theme(--name) in arbitrary values; classes of @plugin entries stay as written. A className now compiles the classes of cn()/twMerge() calls whose arguments are all static (merged as tailwind-merge does, the later class wins), of consts bound to class strings, and of cva() calls (base, variants, compound variants). The Rsbuild plugin reads the Tailwind CSS again and compiles every module again when it changes, and the Bun plugin reads it again. No new build errors
  • Tailwind classes in a className follow Tailwind CSS v4: colors are the v4 default palette (oklch) and the colors and shadows of devup.json theme are usable as Tailwind theme values (bg-primary, text-text, shadow-card, as var(--token)); the opacity modifier (bg-red-500/50, /[71.37%], /(--alpha)) mixes with color-mix as v4 does; a trailing or leading ! makes every declaration important; blur, brightness, contrast, grayscale, hue-rotate, invert, saturate, sepia and their backdrop-* forms compose through Tailwind's custom properties instead of overwriting filter and backdrop-filter; gradients use bg-linear-, bg-conic- and bg-radial-* with from-, via- and to-* composing through the gradient variables (bg-gradient-to-* is not defined by v4 and stays as written); animate-* brings the @Keyframes it names and transition, duration, delay and ease follow v4; space-* and divide-* apply to the children like v4 (:where(& > :not(:last-child))); @Property rules are emitted only for the custom properties a class uses. Anything the build cannot reproduce exactly (drop-shadow-*) stays as written in the className. No new build errors
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors
  • A styled-components or Emotion style function reading the theme by literal keys, such as p => p.theme.space[2] or ({ theme }) => theme.colors['brand'], now reads the CSS variable ThemeProvider declares (var(--space-2)). A theme read the build cannot turn into a CSS variable, such as p => p.theme, p => p.theme.brand() or theme.space[i], is a build error naming the file, line, column and code, where it used to read a theme object that does not exist at runtime and give undefined or throw
  • Generate React JSX-runtime calls for styled components in non-JSX source modules while preserving JSX and TSX output. No new build errors.
  • While @vanilla-extract/css is aliased, importing @vanilla-extract/recipes or @vanilla-extract/sprinkles is a build error naming the file, line and column: those packages generate CSS Devup UI does not compile, so their rules used to be silently missing from the stylesheet. The error says to write the styles with css(), or to disable the alias and build with the vanilla-extract plugin. Type-only imports are allowed
  • Skip source-map generation in the codeExtractWithoutSourceMap WASM export while preserving extracted code, CSS and dependencies. Add real WASM and Next loader regressions for map-enabled and map-disabled extraction. No new build errors.

@devup-ui/bun-plugin@1.0.22 → 1.0.23 - packages/bun-plugin/package.json

Patch

  • Bun plugin works with Bun.build: DevupUI() (from @devup-ui/bun-plugin/register) serves the generated stylesheet to the bundler's CSS loader once every other module is loaded, so the build emits a CSS output with the styles of the whole bundle instead of an empty module; class names are short there and readable under the runtime (debug option). Files importing the packages Devup UI takes the place of (@emotion/react, @emotion/styled, styled-components, @vanilla-extract/css), @stylexjs/stylex, or a subpath or re-export of these or @devup-ui/react, are compiled instead of skipped
  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • Keep leading JSX compiler pragmas through extraction, styled lowering, namespace rewrites and generated stylesheets with correct source maps. Enabled Emotion aliases keep the React JSX import-source compatibility bridge; other sources and opted-out Emotion are preserved. No new build errors.
  • More of Tailwind CSS v4 compiles: container queries (@sm:, @max-md:, @min-[400px]:, @sm/main:) and @container utilities, mask-* (all but the gradient masks), text-shadow-, inset-shadow-, font-variant-numeric utilities (tabular-nums and the like), 3D transforms (rotate-x/y/z, translate-z, scale-z, translate-3d, scale-3d, transform-3d, backface-, perspective-, perspective-origin-), contain- and outline-hidden (with its forced-colors fallback). The project's Tailwind CSS is read further: functional @Utility with --value() and --modifier(), @apply in @Utility, and theme(--name) in arbitrary values; classes of @plugin entries stay as written. A className now compiles the classes of cn()/twMerge() calls whose arguments are all static (merged as tailwind-merge does, the later class wins), of consts bound to class strings, and of cva() calls (base, variants, compound variants). The Rsbuild plugin reads the Tailwind CSS again and compiles every module again when it changes, and the Bun plugin reads it again. No new build errors
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors

@devup-ui/components@0.1.60 → 0.1.61 - packages/components/package.json

Patch

  • @devup-ui/react and @devup-ui/components take React as a peer of ^18 || ^19 instead of * and no longer depend on React 19 themselves, so a React 18 app no longer gets a second React. Select and Stepper read their context with useContext instead of React 19's use() and provide it with Context.Provider instead of React 19's (which crashes React 18), and Select repositions its container from an effect instead of a callback ref returning cleanup, which React 18 ignores
  • Button, Checkbox, Input, Radio and Textarea forward their ref to the button, input or textarea they render, so a ref reaches the form element on React 18 as well as React 19

@devup-ui/eslint-plugin@1.0.22 → 1.0.23 - packages/eslint-plugin/package.json

Patch

  • The @devup-ui/react README and the css-utils-literal-only rule docs link their mention of build errors to the new Build Errors reference (https://devup-ui.com/docs/build-errors), which lists the build errors grouped by API; the docs sidebar lists the page after Supported Syntax & Limitations, whose Build errors section links to it. Documentation only: no new build errors
  • ESLint rules now check exactly what the build reads. The array, typography, media and styleOrder rules and css-utils-literal-only follow the import aliases the build compiles by default (@emotion/react, @emotion/styled, styled-components, @vanilla-extract/css) and the rules of styled (styled.div({...}), styled("div", {...}), styled(Base)({...}, {...}); not .attrs()), createGlobalStyle({...}) and the styles of Emotion Global, and check and fix a module-level const object that only styles read. Vanilla-extract stylesheets (.css.ts/.css.js) are no longer read as Devup UI. New rule no-runtime-read reports css/keyframes/styled/Box read as a value ("X is read at runtime, where it does not exist"), and css-utils-literal-only reports globals that do not exist where a .css.ts is evaluated (window, document, process, ...). No new build errors.
  • style-order-range only checks styleOrder where the build reads it: a prop of a Devup UI component and a top-level key of a style object passed to css/globalCss/keyframes. A styleOrder key in nested data, in a prop value such as an element passed as a prop, or in a call of a function sharing a name with an Object.prototype member is no longer reported. The array, typography and media rules already skip non-style data, and css-utils-literal-only accepts a css()/keyframes() result held in a const inside a function. No new build errors.

@devup-ui/next-plugin@1.0.90 → 1.0.91 - packages/next-plugin/package.json

Patch

  • Isolate the coordinator port file per owning process when another live next dev/build holds it, record the owner pid, and report port, pid and recovery when the coordinator is missing or unreachable
  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • Reconstruct the complete deterministic Next webpack stylesheet after each server, edge and client compiler reset, including accepted external package dependency closures, so delivered CSS matches rendered classes in per-file and single-CSS modes. Keep production CSS request paths stable across build IDs so persistent webpack cache cannot bypass the CSS loader. Genuine production prewarm collection, read and extraction failures now fail the build instead of silently dropping styles; failed initialization releases its build session without leaking styles into unrelated builds.
  • Accept JSX in .js, .mjs and .cjs throughout extraction without changing TypeScript dialects or plain JavaScript output, and pass parsed source-map objects to webpack callbacks for Next SWC compatibility. Malformed relevant source now fails with located parser diagnostics instead of unlocated parser-panic messages or silently recovered output.
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors
  • Skip source-map generation in the codeExtractWithoutSourceMap WASM export while preserving extracted code, CSS and dependencies. Add real WASM and Next loader regressions for map-enabled and map-disabled extraction. No new build errors.

@devup-ui/plugin-utils@1.0.17 → 1.0.18 - packages/plugin-utils/package.json

Patch

  • Webpack and Rsbuild builds hold every style in their stylesheets: a stylesheet module is built on its first import, so styles extracted later (the global styles of files webpack or Rsbuild reached late, often outside src) were missing from the output. The files the entries reach under src are now extracted before the build, the shared base on disk is kept current, and when a stylesheet still changed during the build it is compiled once more, writing only the complete result. Rsbuild serves each stylesheet from the current sheet instead of a module-wide string kept only when it grew, which left edits that shortened the CSS and later builds in the same process stale. plugin-utils adds computeReachableFiles
  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • @emotion/css is aliased by default: css and keyframes compile like Devup UI's own (numbers in px, as Emotion reads them), injectGlobal declares global CSS. cx takes class names, as in Emotion: strings and runtime values are classes kept as written (falsy values are skipped), an object is a { name: condition } class map, arrays nest, and inline css() calls and css() classes compose with the rules css(a, b) uses. merge(className) splits what the build can read (string tokens, template literal parts) and composes the css() classes in it, keeping a runtime string it cannot split as it is. Namespace members, constant-string bracket access, const destructuring and const alias chains compile through these same named APIs with scope-aware binding resolution; numeric values retain Emotion units after constant inlining and build-time evaluation. The Emotion import disappears when no runtime-only member is used. Ambient declarations type the styling imports (cx(...args: ClassNamesArg[]), merge(className: string)). New located build errors: cx() cannot use a spread, a getter, a setter or a method in a class map, or a template literal call; merge() takes exactly one class string, not a spread or a template literal call; the root module has no default export, namespace member keys must be constant strings naming supported exports, and styling functions cannot be passed, mutated or re-exported. Optional styling reads/calls, namespace rest destructuring, styling defaults or nested patterns, non-const aliases, exported styling bindings and reads whose initialization order cannot be established are build errors instead of runtime dependencies. Styling loaded through require, import-equals or dynamic import must use static ES module imports; an unawaited dynamic namespace that the build cannot follow is also a located error. Its runtime-only APIs (cache, flush, hydrate, sheet, getRegisteredStyles) stay on @emotion/css, including their nested/default destructuring, optional reads and recognized runtime-loader access
  • Reconstruct the complete deterministic Next webpack stylesheet after each server, edge and client compiler reset, including accepted external package dependency closures, so delivered CSS matches rendered classes in per-file and single-CSS modes. Keep production CSS request paths stable across build IDs so persistent webpack cache cannot bypass the CSS loader. Genuine production prewarm collection, read and extraction failures now fail the build instead of silently dropping styles; failed initialization releases its build session without leaking styles into unrelated builds.
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors

@devup-ui/react@1.0.44 → 1.0.45 - packages/react/package.json

Patch

  • The @devup-ui/react README and the css-utils-literal-only rule docs link their mention of build errors to the new Build Errors reference (https://devup-ui.com/docs/build-errors), which lists the build errors grouped by API; the docs sidebar lists the page after Supported Syntax & Limitations, whose Build errors section links to it. Documentation only: no new build errors
  • The styled-components and Emotion compat ThemeProvider now gives its theme through React context: useTheme, withTheme and the new ThemeConsumer return the real theme object (an object merged over the outer theme, or what a theme function makes of it) instead of an empty Proxy of var() references, withTheme forwards refs, lets a caller's theme win and sets a displayName. Styles keep reading the CSS variables the provider declares. styled-components v6.4 createTheme is available from the compat entry, and ThemeConsumer and createTheme imports are redirected there instead of staying a runtime dependency on styled-components
  • @emotion/css is aliased by default: css and keyframes compile like Devup UI's own (numbers in px, as Emotion reads them), injectGlobal declares global CSS. cx takes class names, as in Emotion: strings and runtime values are classes kept as written (falsy values are skipped), an object is a { name: condition } class map, arrays nest, and inline css() calls and css() classes compose with the rules css(a, b) uses. merge(className) splits what the build can read (string tokens, template literal parts) and composes the css() classes in it, keeping a runtime string it cannot split as it is. Namespace members, constant-string bracket access, const destructuring and const alias chains compile through these same named APIs with scope-aware binding resolution; numeric values retain Emotion units after constant inlining and build-time evaluation. The Emotion import disappears when no runtime-only member is used. Ambient declarations type the styling imports (cx(...args: ClassNamesArg[]), merge(className: string)). New located build errors: cx() cannot use a spread, a getter, a setter or a method in a class map, or a template literal call; merge() takes exactly one class string, not a spread or a template literal call; the root module has no default export, namespace member keys must be constant strings naming supported exports, and styling functions cannot be passed, mutated or re-exported. Optional styling reads/calls, namespace rest destructuring, styling defaults or nested patterns, non-const aliases, exported styling bindings and reads whose initialization order cannot be established are build errors instead of runtime dependencies. Styling loaded through require, import-equals or dynamic import must use static ES module imports; an unawaited dynamic namespace that the build cannot follow is also a located error. Its runtime-only APIs (cache, flush, hydrate, sheet, getRegisteredStyles) stay on @emotion/css, including their nested/default destructuring, optional reads and recognized runtime-loader access
  • Emotion's CacheProvider is available from the compat entry as a no-op that renders its children, and import { CacheProvider } from '@emotion/react' is redirected there instead of keeping @emotion/react as a runtime dependency; the cache it configures holds styles Emotion inserts at runtime, which Devup UI writes at build time
  • The README no longer claims every CSS-in-JS pattern compiles: it describes what the build compiles and that anything it cannot know is a CSS variable or a located build error, and links the new Supported Syntax & Limitations page, which lists what compiles, the style override rules (where a later part wins and where the CSS cascade decides), Baseline 2024 browser support, that a component taking the css prop must pass className and style on, the runtime-only APIs and how each is handled, and the known limitations
  • @devup-ui/react and @devup-ui/components take React as a peer of ^18 || ^19 instead of * and no longer depend on React 19 themselves, so a React 18 app no longer gets a second React. Select and Stepper read their context with useContext instead of React 19's use() and provide it with Context.Provider instead of React 19's (which crashes React 18), and Select repositions its container from an effect instead of a callback ref returning cleanup, which React 18 ignores
  • stylex.attrs() gives the dynamic CSS variables it sets as style attribute text ("--a:" + value), as StyleX does for spreading onto non-React targets, instead of a style object; stylex.props() keeps the object React takes. The attrs() return type declares style as a string

@devup-ui/reset-css@1.0.31 → 1.0.32 - packages/reset-css/package.json

Patch

  • Auto-update: depends on '@devup-ui/react' via a local workspace dependency

@devup-ui/rsbuild-plugin@1.0.67 → 1.0.68 - packages/rsbuild-plugin/package.json

Patch

  • Webpack and Rsbuild builds hold every style in their stylesheets: a stylesheet module is built on its first import, so styles extracted later (the global styles of files webpack or Rsbuild reached late, often outside src) were missing from the output. The files the entries reach under src are now extracted before the build, the shared base on disk is kept current, and when a stylesheet still changed during the build it is compiled once more, writing only the complete result. Rsbuild serves each stylesheet from the current sheet instead of a module-wide string kept only when it grew, which left edits that shortened the CSS and later builds in the same process stale. plugin-utils adds computeReachableFiles
  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • More of Tailwind CSS v4 compiles: container queries (@sm:, @max-md:, @min-[400px]:, @sm/main:) and @container utilities, mask-* (all but the gradient masks), text-shadow-, inset-shadow-, font-variant-numeric utilities (tabular-nums and the like), 3D transforms (rotate-x/y/z, translate-z, scale-z, translate-3d, scale-3d, transform-3d, backface-, perspective-, perspective-origin-), contain- and outline-hidden (with its forced-colors fallback). The project's Tailwind CSS is read further: functional @Utility with --value() and --modifier(), @apply in @Utility, and theme(--name) in arbitrary values; classes of @plugin entries stay as written. A className now compiles the classes of cn()/twMerge() calls whose arguments are all static (merged as tailwind-merge does, the later class wins), of consts bound to class strings, and of cva() calls (base, variants, compound variants). The Rsbuild plugin reads the Tailwind CSS again and compiles every module again when it changes, and the Bun plugin reads it again. No new build errors
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors

@devup-ui/vite-plugin@1.0.73 → 1.0.74 - packages/vite-plugin/package.json

Patch

  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors

@devup-ui/webpack-plugin@1.0.71 → 1.0.72 - packages/webpack-plugin/package.json

Patch

  • Webpack and Rsbuild builds hold every style in their stylesheets: a stylesheet module is built on its first import, so styles extracted later (the global styles of files webpack or Rsbuild reached late, often outside src) were missing from the output. The files the entries reach under src are now extracted before the build, the shared base on disk is kept current, and when a stylesheet still changed during the build it is compiled once more, writing only the complete result. Rsbuild serves each stylesheet from the current sheet instead of a module-wide string kept only when it grew, which left edits that shortened the CSS and later builds in the same process stale. plugin-utils adds computeReachableFiles
  • Class names and file numbers no longer depend on the order files are processed in. Every plugin walks the files it can extract (the project's source and the included packages) before the first transform and numbers them in path order (a file collapsed into a bucket is numbered as the bucket, and only files that mention the package, StyleX or an aliased package take numbers) through the new seedFileMap, so identical builds give identical class prefixes, CSS file names and output hashes; in development, files that appear later are numbered after the existing ones. Styles are given class names in a fixed order, not the order of a hash set. The exported class, file and canonical maps are canonical JSON (sorted keys). Stylesheet evaluation (vanilla-extract) refuses Math.random(), Date.now(), new Date() and Date(), which differ between builds, with a build error; fixed dates such as new Date(0) are unchanged. resetBuildState starts a build from its own options: the prefix, atom hoisting, routes, buckets, names, numbers and styles an earlier build in the process left are cleared, and the prefix is set on every setup even when it is absent. Engine snapshots from concurrent transforms are written one after another per file, so an older snapshot is never written last. File lists are ordered by code point rather than the locale
  • Reconstruct the complete deterministic Next webpack stylesheet after each server, edge and client compiler reset, including accepted external package dependency closures, so delivered CSS matches rendered classes in per-file and single-CSS modes. Keep production CSS request paths stable across build IDs so persistent webpack cache cannot bypass the CSS loader. Genuine production prewarm collection, read and extraction failures now fail the build instead of silently dropping styles; failed initialization releases its build session without leaking styles into unrelated builds.
  • Accept JSX in .js, .mjs and .cjs throughout extraction without changing TypeScript dialects or plain JavaScript output, and pass parsed source-map objects to webpack callbacks for Next SWC compatibility. Malformed relevant source now fails with located parser diagnostics instead of unlocated parser-panic messages or silently recovered output.
  • Tailwind classes in a className read the Tailwind CSS of the project: the file that imports tailwindcss (the tailwind.css option of devup.json, else a conventional path) gives @theme variables (colors, spacing, shadows, blurs, radii, easings, animations with their @Keyframes, default transitions, breakpoints), @Utility utilities and @custom-variant variants, and the Vite, webpack and Next.js plugins read it again when it or a CSS file it imports changes. Anything that cannot be read exactly, and the classes of namespaces the build does not read, stay as written. More of Tailwind CSS v4 compiles: ring-, inset-ring-, ring-offset-* and drop-shadow-* compose with shadows and filters, borders and outlines draw in --tw-border-style and --tw-outline-style (a border now shows its style), line-clamp, text decoration, border-spacing, logical and scroll spacing, tables, perspective, font-stretch and more; the arguments of clsx(), classnames() and classNames() and [...].join(' ') compile in place. Radii follow v4 (rounded-sm is 0.25rem, rounded-full is calc(infinity * 1px)). No new build errors

@codecov

codecov Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
libs/extractor/src/imported_constants.rs 100.00% <100.00%> (ø)
libs/extractor/src/lib.rs 100.00% <100.00%> (ø)
libs/extractor/src/style_values.rs 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

owjs3901 and others added 6 commits October 15, 2026 00:37
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Keep imported style metadata and main constant class-string reads together.

Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Preserve imported-chain refusals and main constant class-string behavior.

Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Refs #688

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
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.

1 participant