Skip to content

fix(wasm): align deterministic atom hoist names - #751

Merged
owjs3901 merged 33 commits into
mainfrom
fix/atom-hoist-names
Oct 18, 2026
Merged

owjs3901 merged 33 commits into
mainfrom
fix/atom-hoist-names

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Refs #694, #686 (W66). #782 (fix/order-independent-class-slots) 위에 쌓은 PR이며 #782를 먼저 병합해야 합니다. PR base는 main입니다.

요약

2026-10-15 사용자 결정에 따라 기존 #751의 긴 hex/content 클래스 이름을 짧은 카운터로 바꿉니다. 해시·콘텐츠·fingerprint 이름이나 스타일링 런타임은 추가하지 않습니다.

  • 예약된 공유 슬롯이 있는 webpack·Rsbuild·Next webpack은 atomHoist 기본 threshold가 2입니다. 양수 threshold의 기존 최소 2 보정과 atomHoist: 0 opt-out을 유지합니다. Bun은 기존대로 항상 singleCss이며 새 atom 옵션을 추가하지 않습니다.
  • 사전에 확정한 canonical bucket의 route reach가 threshold 이상이면 공유 이름 공간의 a, b, c를 사용하여 devup-ui.css에 한 번 정의합니다. 한 route 전용 bucket은 a-a 같은 파일별 이름과 해당 파일 CSS를 유지합니다.
  • 실제 singleCss: true에서는 atomHoist가 코드·CSS·계획·adapter ID/import/chunk 정책에 영향을 주지 않습니다.
  • !important 충돌, length/shadow token 첫 값, 공유 규칙의 updatedBaseStyle, sheet에 저장한 frozen plan을 보존하고 naming cache generation을 2로 올립니다.

동작

route reach와 threshold를 첫 scratch/live 추출 전에 설정합니다. #782의 실제 추출기 기반 예약 패스가 같은 물리 inventory를 경로 순서로 처리해 공유·파일별 카운터 슬롯을 미리 예약하며, 실제 변환 순서는 이름을 다시 정하지 않습니다. 내부 원자 identity는 충돌 없이 구분하지만 출력 이름에 identity 내용을 붙이지 않습니다.

keyframes는 기존 파일별 위치를 유지하고 생성 변수는 공유 카운터 이름 공간을 사용합니다. prefix는 한 번만 적용합니다. atom debug 모드도 짧은 카운터이며, atom과 무관한 기존 debug 동작은 유지합니다.

sheet만 복원한 CSS 렌더링은 유지합니다. 추출을 이어 갈 때는 같은 generation의 sheet/class map/file map을 함께 복원합니다. 새 빌드에서 카운터를 완전히 비운 뒤 첫 이름이 다시 a가 되는 것은 정상입니다. 변경된 값이 새 빌드마다 반드시 다른 문자열 이름을 얻는다고 주장하지 않습니다.

Adapter 지원 경계

2026-10-16 coordinator 결정에 따라 Vite와 Next Turbopack에는 두 번째 예약 구현을 추가하지 않습니다. Vite의 PR B, Next Turbopack의 #752가 해당 연동을 소유합니다.

  • 예약이 들어오기 전까지 두 adapter의 atomHoist는 유효하게 OFF이며 파일별 이름을 유지합니다. 명시적 finite 양수 요청에는 예약 미지원 한계와 fallback을 알리는 빌드 시 경고를 한 번 출력합니다. 생략·undefined·0·기존 비활성 숫자·singleCss는 해당 경고 없이 유지합니다.
  • 실제 두 공유 파일을 두 페이지가 함께 import하는 AB/BA/BA/AB 회귀에서 원래의 공유 클래스 교환을 RED로 확인한 뒤, OFF 상태의 추출 JS/maps와 실제 로드·배포 CSS 전체 바이트 동일성을 검증합니다. 이름·CSS·map·경로를 정규화하지 않습니다. Next의 timestamp가 붙은 디스크 watch 신호는 실제 CSS-loader 출력과 구분해 원본 진단 자료로 보존합니다.
  • Vite와 Next Turbopack의 공유 CSS 변수 및 공유 클래스 슬롯은 PR B와 #752가 들어오기 전까지 기존 main과 동일하게 arrival-order 기반입니다. 예를 들어 Vite atomHoist: 0에서도 동적 color/background 변수의 --a/--b가 AB/BA에 따라 바뀌는 원본 재현을 보존했습니다. 이는 통과한 테스트로 바꾸지 않았으며 이 PR에서 수정하거나 숨기지 않습니다. coordinator가 기존 소유 범위로 분류하여 #751을 기다리지 않고 게시하도록 결정했습니다 (msg_2ee7000a2bb8).
  • Vite는 PR B head가 공지되면 true-merge 후 활성화하며, Next Turbopack은 #752의 예약 연동 후 활성화합니다. hash/content fallback은 없습니다.

CSS / JS 바이트

#751의 같은 fixture(8 route, 공유 모듈 32개, 모듈당 공통 원자 80개, route당 전용 원자 25개)를 같은 측정 스크립트로 비교했습니다. header와 import-only CSS 파일을 포함하고 파일별 gzip을 합산했습니다. JS/JSX는 추출 후·bundler/minify 전 코드이며 landing 표는 실제 client JS 산출물입니다. 공유 상수 하나가 바뀌는 새 배포 빌드의 CSS 무효화도 비교합니다.

지표 정상 파일별 CSS (atom OFF) 기존 #751 hex atom ON W66 카운터 atom ON
전체 CSS raw bytes 69,604 30,352 10,230
전체 CSS gzip bytes 19,113 7,312 5,747
전체 추출 JS/JSX raw bytes 68,518 208,446 62,278
전체 추출 JS/JSX gzip bytes 10,221 17,722 10,033
정의 없는 생성 클래스 참조 0 0 0
route가 읽는 CSS raw bytes 64,816 11,515 5,442
route가 읽는 CSS gzip bytes 17,262–17,264 4,262–4,269 3,896–3,898
route가 읽는 추출 JS/JSX raw bytes 64,115 189,994 57,875
route가 읽는 CSS 파일 수 34 34 34
배포 후 변경 CSS 파일 수 32 1 1
다시 받는 CSS raw bytes 64,064 5,848 1,782
다시 받는 CSS gzip bytes 16,968 700 499

hex ON 대비 CSS raw 20,122 bytes, 추출 JS/JSX raw 146,168 bytes를 줄이고 참조 누락과 residual은 0입니다. 같은 예약 inventory에 대한 역순 추출의 전체 JS/CSS도 같습니다. CSS 요청 수가 줄었다고 주장하지 않습니다.

landing은 config를 바꾸지 않고 양쪽 모두 같은 bun run build(vinext)로 생성한 dist/client의 .css/.js만 측정했습니다. 최종 Vite 정책은 OFF이므로 이 앱에서 atom 절감이 있다고 주장하지 않습니다.

landing client asset 변경 전 (Vite 기본 OFF) 최종 변경 후 (예약 전 OFF)
CSS 파일 수 103 103
CSS raw / gzip bytes 60,282 / 28,848 60,282 / 28,849
JS 파일 수 69 69
JS raw / gzip bytes 688,247 / 218,096 688,247 / 218,086

raw 합계는 같고 gzip의 CSS +1 / JS -10 bytes 차이를 feature 효과나 일반적인 성능 개선으로 해석하지 않습니다. 중간의 예약 없는 Vite ON 후보 측정은 최종 표에 사용하지 않았습니다. 최종 #782 prerequisite 통합 후 같은 command로 다시 측정한 값입니다.

새로 생기는 오류

새로 의도한 소스 빌드 오류는 없습니다. Vite/Next Turbopack에서 예약 전 명시적으로 atomHoist 양수를 요청하면 위 한계를 알리는 경고 한 번이 생깁니다. 기존 위치 포함 오류와 reservation의 실제 transform 진단 경계를 보존합니다. #783 선행의 authoritative production prewarm 읽기/추출 실패 전파도 그대로 유지합니다. 새 의존성·스타일링 런타임·큰 stack 설정은 없습니다.

남는 한계

  • atom plan은 canonical bucket의 사전 reach를 사용합니다. 서로 다른 전용 bucket에서 우연히 같은 원자를 사용한 경우 내용을 나중에 합쳐 이미 출력한 이름을 바꾸지 않습니다. 늦은 reach로 기존 bucket을 승격·재번호화하지 않습니다.
  • #782의 물리 inventory 밖 virtual/generated 입력과 watch-history 경계는 유지하며 residual로 관측합니다. clean build와 누적 watch history의 같은 번호를 약속하지 않습니다.
  • Vite/Next Turbopack의 기존 공유 슬롯 arrival-order 한계 및 소유 PR은 위에 명시했습니다. 해당 atom 옵션을 지원한다고 주장하지 않습니다.
  • #764는 수정하지 않았습니다. 후속 true-merge 때 짧은 예약 카운터, generation 2, 호환 sheet/class/file 복원, singleCss 무효화, adapter별 ON/OFF·경고 경계를 반영해야 합니다. content-name 경로를 다시 도입하면 안 됩니다.

검증

  • 최종 head b2ce080d065962e59770be61b3d8d6b84f10018e. main a6ecc125와 fix(plugins): reserve shared counter slots before extraction #782 초기 af54ab8c를 실제 두 부모 merge로 순서대로 통합한 뒤, 공지된 최종 fix(plugins): reserve shared counter slots before extraction #782 b019767b를 다시 true-merge했습니다. 마지막 두 부모는 046b2a94와 b019767b이며 watch 수정 9f2ad995, fix(next): deliver complete webpack CSS across compilers and cached builds #783 42cb0a4e, #781을 포함합니다. 유일한 constructor 충돌은 atomHoist = 2와 productionPrewarmFiles를 모두 보존했고, 다섯 Next 위임 테스트는 실제 RED 후 선행의 안정된 production CSS 경로/complete set 계약에 맞췄습니다. 모든 독자 snapshot 변경은 0입니다.
  • regression FIRST: Rust intended RED 4개, 이전 기본값/singleCss RED 13개, 최종 availability RED 13개를 확인한 뒤 구현했습니다. 기존 #751의 우선순위·토큰·keyframe·공유 갱신·persisted plan 회귀와 예약 소비자의 실제 양수 hoist 검증을 유지했습니다.
  • cargo fmt --all, Rust 1.99 workspace/all-target Clippy -D warnings, 기본 stack workspace test 통과. 새 WASM와 root build 및 landing의 같은 command build 통과.
  • 정상 전체 hook 최종 통과: Bun 6,212 pass / 0 fail / 함수·라인 100%, lint 오류 0/기존 경고 9, Rust 테스트 통과. CI unset·jobs2·기본 stack·12GB hook watchdog, 840.070s, peak 3,615,350,784 bytes. 선행 통합 후 install/WASM/root 및 Rust 게이트를 다시 실행했습니다. bypass·amend·force-push 없음.
  • 부모가 최종 실제 Next/Vite AB/BA/BA/AB를 재실행하여 JS/maps/실제 CSS 바이트 및 정확한 파일별 선언 일치를 독립 확인했습니다. eight-route fixture도 최종 WASM로 다시 측정했습니다.
  • Windows native Rust 98.38%(17,659/17,949)는 전체 100%가 아닙니다. 측정된 추가 실행 줄 65/65 covered·추가 미커버 0이며 일부 test-only module은 XML 매핑이 없어 미측정입니다. Linux CI의 100% 판정과 혼동하지 않습니다.
  • Rust LSP daemon timeout, TypeScript/Biome LSP 미설치 및 기존 설치 거절로 clean LSP를 주장하지 않습니다. 실제 compiler/Clippy/Bun 검증을 기록합니다.
  • 최초 CI 38891331379는 Linux Rust 100.00%(12,698/12,698) 및 Bun 함수·라인 100%였지만, #782에서 이미 조사하던 native webpack watch AB/BA 두 회귀로 publish만 실패했습니다. 나머지 benchmark/landing-next-e2e/vinext-rsc-css-e2e는 통과했습니다. 테스트를 약화하거나 watch 수정을 복제하지 않고 공지된 b019767b의 수정·선행을 통합했습니다.
  • 최종 CI run 38896227143 (실행) 전체 GREEN: publish / benchmark / landing-next-e2e / vinext-rsc-css-e2e 모두 success입니다. 최종 Linux Rust 100.00%(12,698/12,698), Bun 6,212 pass / 0 fail / 함수·라인 100%, preload race 별도 8 pass / 0 fail을 확인했습니다. 실패했던 native webpack watch AB/BA 두 행도 최종 run에서 모두 통과했습니다. PR은 MERGEABLE/CLEAN이며 normal push만 했고 병합·승인·draft 변경은 하지 않았습니다.
  • 마지막 독립 leaf gate 검토는 APPROVE / HIGH, 차단 항목 없음입니다. 실제 부모 QA와 원본 CI·출력 자료를 감사한 내부 검토이며 GitHub PR 승인이나 병합 동작이 아닙니다. 알려진 PR B/fix(next-plugin): isolate coordinator sessions and enforce complete CSS #752 소유 한계는 위에 명시한 그대로 유지합니다.

2026-10-18 갱신: 갱신된 선행 #782 6d7937b7505c49b021f6b367b32c7364d165a45d를 두 부모 merge commit 74238b2d99b09040f3e1ef55480c3d26defeb10f로 병합했고 main 3ca6182f 포함과 두 부모를 확인했습니다; 충돌 libs/sheet/src/lib.rs는 #751의 hoisted/고정 atom plan·짧은 counter 이름·전용/공유 bucket·토큰 첫 값·공유 갱신 판정과 main의 order/global 파일·선언 순서·레이어·import/font-face 정렬 배치를 모두 유지했습니다; 순서 있는 global owner는 정렬까지 보존하고 atom plan에서 정렬 후 인접한 동일 선언만 제거해 기존 dedup 계약과 red→blue→red cascade를 함께 보존했습니다(기존 유효 회귀 1개 RED 후 그대로 GREEN, 파일/offset 및 3파일 역순 회귀 4개 추가); 충돌 sheet__tests__deserialize.snap은 HEAD baseline에서 테스트로 재생성한 유일한 .snap.new를 검토해 order: 0과 layer_orders: {} 추가만 수용했고 atom_plan: None/hoisted: false는 유지했습니다; 기본 stack/jobs=2/native 6GiB에서 fmt/clippy·Rust 전체(582 CSS/117 WASM/3,943 extractor/225 sheet 및 통합·doctest)·fresh WASM/root·Bun 6,214개/0 fail/함수·라인 100%가 통과했고, 정식 hook도 12GiB/502.2초/peak 4,258,689,024 bytes로 통과했습니다(Windows Rust 98.02% 19,336/19,726은 Linux CI와 구분); 실제 public WASM의 AB/BA 공유 a b c·전용 a-a a-b 및 코드/CSS/map 동일성·residual0·사용하지 않는 scratch CSS 미게시와 ABC/CBA body{margin:0;color:red;color:blue;color:red}를 확인했고 독립 Oracle gate가 APPROVE/HIGH/차단 없음으로 검토했습니다; 새 build error·dependency·스타일링 runtime·해시/내용 기반 출력 이름은 없고 기존 Vite/Next Turbopack 예약 미지원 경계/경고는 담당 PR 범위 그대로 유지합니다.; 최종 head 74238b2d99b09040f3e1ef55480c3d26defeb10f의 CI 39069211577는 5개 job 전체 SUCCESS이고 Linux Rust 100%(13767/13767), Bun 6214 pass/0 fail/함수·라인 100% 및 별도 preload 9 pass/0 fail을 확인했습니다.

2026-10-18 W71b 갱신: 최신 main f16cd190·a26bb579를 포함한 선행 #782의 검증 완료 커밋 01d4fd1c를 실제 두 부모 병합으로 반영했습니다(최종 513c19bc); WASM ABI의 getUnreservedClassSlotCount·reserveClassSlots 내보내기 목록과 실제 예약·계수·상태 복구 테스트 3개를 그대로 포함하며 atomHoist의 짧은 counter 이름과 global 선언 순서를 보존했습니다; 최신 stable Rust 1.99.1 기본 스택의 fmt·Clippy·workspace 테스트, 새 WASM·전체 빌드, ABI 16/16, 정규 전체 훅과 실제 AB/BA 및 ABC/CBA 출력 검증 통과, CI 39116170220 전체 성공(Linux Rust 13859/13859 100%, Bun 6319 통과·0 실패 및 함수/라인 100%); 새로 생기는 오류 없음, 기존 한계 변경 없음.

owjs3901 and others added 9 commits October 4, 2026 09:38
Refs #694, #686

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 4, 2026 •

Copy link
Copy Markdown
Contributor

Changepacks

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

Patch

  • Enable atomHoist by default at two routes in Webpack, Rsbuild and Next Webpack, with numeric zero opting out and no effect in singleCss mode. Keep Vite and Next Turbopack in per-file mode until their shared-slot reservation lands, warning once for explicit positive atomHoist requests. Allocate shared and file-local short counter names through deterministic reserved slots instead of content names; preserve frozen route plans, dynamic !important, local keyframes, first-value responsive tokens, shared CSS updates and persisted placement. Bump the naming cache generation. No new build errors.
  • 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
  • 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.
  • Reserve existing short counter class, variable, keyframe and raw-file slots in physical input order before Bun, webpack, Rsbuild and Next webpack extraction. Watch revisions append sorted slots without renumbering. No new source build errors or styling runtime; unknown virtual, generated and opaque outside-inventory inputs retain observable residual allocation.
  • 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.
  • Reserve existing short counter class, variable, keyframe and raw-file slots in physical input order before Bun, webpack, Rsbuild and Next webpack extraction. Watch revisions append sorted slots without renumbering. No new source build errors or styling runtime; unknown virtual, generated and opaque outside-inventory inputs retain observable residual allocation.
  • 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

  • Enable atomHoist by default at two routes in Webpack, Rsbuild and Next Webpack, with numeric zero opting out and no effect in singleCss mode. Keep Vite and Next Turbopack in per-file mode until their shared-slot reservation lands, warning once for explicit positive atomHoist requests. Allocate shared and file-local short counter names through deterministic reserved slots instead of content names; preserve frozen route plans, dynamic !important, local keyframes, first-value responsive tokens, shared CSS updates and persisted placement. Bump the naming cache generation. No new build errors.
  • 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.
  • Reserve existing short counter class, variable, keyframe and raw-file slots in physical input order before Bun, webpack, Rsbuild and Next webpack extraction. Watch revisions append sorted slots without renumbering. No new source build errors or styling runtime; unknown virtual, generated and opaque outside-inventory inputs retain observable residual allocation.
  • 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.
  • Reserve existing short counter class, variable, keyframe and raw-file slots in physical input order before Bun, webpack, Rsbuild and Next webpack extraction. Watch revisions append sorted slots without renumbering. No new source build errors or styling runtime; unknown virtual, generated and opaque outside-inventory inputs retain observable residual allocation.
  • 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

  • Enable atomHoist by default at two routes in Webpack, Rsbuild and Next Webpack, with numeric zero opting out and no effect in singleCss mode. Keep Vite and Next Turbopack in per-file mode until their shared-slot reservation lands, warning once for explicit positive atomHoist requests. Allocate shared and file-local short counter names through deterministic reserved slots instead of content names; preserve frozen route plans, dynamic !important, local keyframes, first-value responsive tokens, shared CSS updates and persisted placement. Bump the naming cache generation. No new build errors.
  • 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
  • Reserve existing short counter class, variable, keyframe and raw-file slots in physical input order before Bun, webpack, Rsbuild and Next webpack extraction. Watch revisions append sorted slots without renumbering. No new source build errors or styling runtime; unknown virtual, generated and opaque outside-inventory inputs retain observable residual allocation.
  • 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

  • Enable atomHoist by default at two routes in Webpack, Rsbuild and Next Webpack, with numeric zero opting out and no effect in singleCss mode. Keep Vite and Next Turbopack in per-file mode until their shared-slot reservation lands, warning once for explicit positive atomHoist requests. Allocate shared and file-local short counter names through deterministic reserved slots instead of content names; preserve frozen route plans, dynamic !important, local keyframes, first-value responsive tokens, shared CSS updates and persisted placement. Bump the naming cache generation. No new build errors.
  • 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

  • Enable atomHoist by default at two routes in Webpack, Rsbuild and Next Webpack, with numeric zero opting out and no effect in singleCss mode. Keep Vite and Next Turbopack in per-file mode until their shared-slot reservation lands, warning once for explicit positive atomHoist requests. Allocate shared and file-local short counter names through deterministic reserved slots instead of content names; preserve frozen route plans, dynamic !important, local keyframes, first-value responsive tokens, shared CSS updates and persisted placement. Bump the naming cache generation. No new build errors.
  • 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.
  • Reserve existing short counter class, variable, keyframe and raw-file slots in physical input order before Bun, webpack, Rsbuild and Next webpack extraction. Watch revisions append sorted slots without renumbering. No new source build errors or styling runtime; unknown virtual, generated and opaque outside-inventory inputs retain observable residual allocation.
  • 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 4, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
...ndings/devup-ui-wasm/src/atom_hoist_measurement.rs 100.00% <100.00%> (ø)
bindings/devup-ui-wasm/src/atom_tests.rs 100.00% <100.00%> (ø)
bindings/devup-ui-wasm/src/lib.rs 100.00% <100.00%> (ø)
bindings/devup-ui-wasm/src/reservation.rs 100.00% <100.00%> (ø)
bindings/devup-ui-wasm/src/reservation/rows.rs 100.00% <100.00%> (ø)
libs/css/src/atom_hoist.rs 100.00% <100.00%> (ø)
libs/css/src/atom_name.rs 100.00% <100.00%> (ø)
libs/css/src/file_map.rs 100.00% <100.00%> (ø)
libs/css/src/file_routes.rs 100.00% <ø> (ø)
libs/css/src/lib.rs 100.00% <100.00%> (ø)
... and 33 more
🚀 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 15 commits October 8, 2026 15:13
Merge main history while preserving this PR implementation. No manual conflict resolutions.
Refs #694; merge updated PR #743 with main a2b74d0

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
owjs3901 and others added 8 commits October 16, 2026 18:57
Refs #694

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@owjs3901
owjs3901 merged commit 32238e4 into main Oct 18, 2026
8 checks passed
@owjs3901
owjs3901 deleted the fix/atom-hoist-names branch October 18, 2026 11:30
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