Skip to content

fix(extractor): compile Emotion's css prop at build time - #727

Merged
owjs3901 merged 8 commits into
mainfrom
fix/emotion-css-prop
Oct 19, 2026
Merged

owjs3901 merged 8 commits into
mainfrom
fix/emotion-css-prop

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Refs #690 (D 필수 — EMO-01, EMO-05 일부). #722 위에 쌓은 브랜치입니다(#706→#707→#710→#711→#722가 병합되면 차이는 커밋 하나).

문제

  • EMO-01: Emotion의 css prop이 어떤 형태로도 컴파일되지 않았습니다. <div css={{ color: 'red' }} />는 그대로 남고 CSS가 나오지 않았습니다. @emotion/react/jsx-runtime으로 컴파일된 코드의 jsx('div', { css })와 css() 결과를 넘긴 css={style}도 마찬가지였습니다.
  • EMO-05(일부): @emotion/react의 jsx가 런타임 의존성으로 남았습니다.

수정

  • 컴파일 대상 (@emotion/react alias가 켜져 있을 때)
    • 태그와 Devup UI 컴포넌트의 css prop은 항상 컴파일합니다.
    • 파일이 @emotion/react나 @emotion/styled(하위 경로 포함)를 import하거나 @jsxImportSource @emotion/react pragma가 있으면 모든 요소의 css prop을 컴파일합니다.
    • @emotion/react/jsx-runtime·jsx-dev-runtime의 jsx/jsxs/jsxDEV와 @emotion/react의 jsx/createElement 호출도 같은 규칙을 따릅니다. include 라이브러리의 컴파일된 코드도 포함됩니다.
  • 값 해석 (Emotion과 같은 의미)
    • 배열, 조건, &&/||/??는 합성입니다. 뒤의 부분이 같은 속성·선택자·breakpoint·layer를 덮어씁니다(D1 정적 합성).
    • 파일이 아는 css() 클래스는 스타일로 합성합니다. 그 밖의 식별자와 멤버는 런타임 클래스로 붙입니다.
    • 문자열과 템플릿은 CSS 텍스트입니다. css 태그 템플릿의 mixin(${base};)은 그 자리에서 텍스트를 나눠 합성하고, 인라인 css(...) 호출은 인자를 합성 부분으로 펼칩니다.
    • 테마 함수 theme => ({ ... })는 값 위치의 theme.a.b를 var(--a-b)로 읽습니다. 구조 분해(({ colors }) => ...)와 theme.space[2]도 읽습니다.
    • 단위 없는 숫자는 Emotion처럼 px입니다. 최상위 상수에서 읽은 숫자도 같고, lineHeight 같은 단위 없는 속성은 숫자로 둡니다.
    • 런타임 값은 CSS 변수로 style에 넣습니다.
  • 출력
    • 결과 클래스는 요소의 className에 붙고, 변수는 기존 style 아래에 병합됩니다. 스프레드 위치에 따른 className/style 최종값(JSX-01 규칙)을 따릅니다.
    • className에 파일이 아는 css() 클래스가 있으면 Emotion의 registered class처럼 css prop을 덮어씁니다.
    • Devup UI 컴포넌트는 자신의 스타일 prop 위에 css prop을 합성합니다.
    • 파일이 정의한 styled 컴포넌트는 그 태그를 요소 자리에 렌더링하고 자신의 스타일 위에 css prop을 합성합니다. 조건은 태그를 렌더링하고 attrs·props 읽기·런타임 값이 없으며, 요소에 스프레드·as·forwardedAs가 없는 것입니다. 그 밖에는 스타일이 겹치지 않을 때 className으로 넘깁니다.
  • 런타임 제거
    • @emotion/react/jsx-runtime(dev 포함) import는 react/jsx-runtime으로, @jsxImportSource @emotion/react pragma는 react로 바뀝니다.
    • @emotion/react의 jsx/createElement는 @devup-ui/react/compat의 jsx(React createElement)로 바뀝니다.
  • 타입
    • @devup-ui/react/compat/css-prop이 React.Attributes에 css를 추가하고, compat/emotion이 이를 참조합니다.
    • @emotion/react 선언에 jsx와 createElement를 추가했습니다.
  • 함께 고친 버그: 합성 부분을 복제할 때 oxc의 clone_in이 바인딩 정보를 잃었습니다. 그래서 css(a, b)의 부분이 읽는 바인딩(예: keyframes 이름)이 읽히지 않았습니다. clone_in_with_semantic_ids로 고쳤습니다.

동작 변화

  • <div css={...}>가 이제 클래스로 컴파일됩니다. Emotion 신호가 없는 파일의 사용자 컴포넌트(<Custom css={...}>)는 이전처럼 그대로 둡니다.
  • <Box css>는 이전에 객체만 우연히 중첩 스타일로 처리되었습니다. 이제 위 규칙(합성, px, CSS 텍스트)을 따릅니다.
  • css(yellow, fade)처럼 합성 부분이 아는 바인딩을 읽으면 그 값이 클래스 문자열에 들어갑니다. 런타임 결과는 같으며, 기존 테스트 1건의 기대값을 갱신했습니다.

새로 생기는 오류

모두 파일:줄:열, 코드, 원인, 해결 방법을 함께 보여 줍니다.

  • 빌드가 읽을 수 없는 css prop 부분(호출, JSX 요소, 스프레드 등).
  • 함수 안이나 let/var로 선언한 스타일 객체·배열·함수·텍스트를 부분으로 사용. 인라인으로 쓰거나 최상위 const로 선언하도록 안내합니다.
  • 모듈을 실행해야만 아는 바인딩이나 코드가 변경하는 객체를 사용.
  • 테마 함수가 규칙을 바로 반환하지 않거나, 값 위치가 아닌 곳에서 theme을 읽음(theme.spacing(2), theme.colors[key], 조건 등).
  • CSS 텍스트에서 배치할 수 없는 보간, 중첩 규칙 안의 mixin.
  • styled 컴포넌트의 스타일을 덮어쓰지만 태그를 요소 자리에 렌더링할 수 없음. styled(Component)(...)로 옮기도록 안내합니다.

남는 한계

  • 런타임에만 정해지는 클래스(props.className 등)와의 우선순위는 CSS 특성을 따릅니다(K 문서화).
  • 사용자 컴포넌트가 동적 값을 받으려면 className과 style을 모두 전달해야 합니다(D8, K 문서화).
  • tsconfig jsxImportSource만으로 Emotion을 쓰는 파일은 신호가 없어 사용자 컴포넌트를 컴파일하지 않고, 번들러가 Emotion JSX 런타임을 import합니다. tsconfig를 읽는 후속 PR에서 처리합니다.
  • bun 플러그인의 hasDevupUI 게이트는 alias를 모릅니다(H 범위).
  • fix(extractor): read property arrays in aliased library rules as fallbacks #717(배열 fallback)과 함께 병합되면 css prop 객체의 속성 배열 처리를 확인해야 합니다.

확인

  • cargo clippy -D warnings(Rust 1.99), cargo test --workspace(extractor 1463건) 통과.
  • WASM 재빌드 후 bun test 5475건 통과, 커버리지 100%. bun lint 통과.
  • 조사 fixture css-prop-* 12개를 probe로 확인했습니다.
  • 소비자 설치 형태(단일 @types/react)에서 css prop 타입 검사를 확인했습니다.

2026-10-08 main 11790bef를 병합하여 업데이트했습니다. 충돌 없이 갱신된 부모 PR의 변경과 main의 변경을 모두 보존했습니다.

2026-10-15 갱신: 부모 #722 5ef8d41 및 포함된 main 76b1a15059d3d1b26c8ac20bb622a36cc937a7a4을 병합했습니다. 충돌 파일 lib.rs, visit.rs, packages/react/src/compat/index.ts는 CSS prop 기능과 부모의 namespace 변환·실제 target·forwardRef·필터링을 함께 보존했고, 캡처가 남는 고정 native 컴포넌트는 원래 render/closure를 유지한 채 빌드가 완성한 class만 비공개 prop으로 전달하도록 통합했습니다. 키 예약은 원본 식별자·JSX 이름·정적 객체/멤버 키 및 문법 래퍼까지 회피하며 비공개 prop은 DOM 전달 전에 제거됩니다. emotion_css_prop_on_every_element.snap은 부모의 TS용 JSX helper 출력만 다시 생성해 수용했으며 CSS/스타일 집합은 그대로입니다. import_alias_visit.rs의 불필요한 borrow도 제거했습니다. 새로 생기는 오류 없음.

병합 검증 추가

  • 기존 캡처 회귀 4개를 변경하지 않고 RED를 확인한 뒤, 겹침/비겹침·혼합 호출·클로저 shadowing·빈 class·등록 class 우선순위·동적 변수·ref/children·키 충돌을 실행 검증했습니다.
  • DEFAULT Rust stack에서 workspace 테스트, Rust 1.99 all-target Clippy, fresh WASM/root 빌드와 전체 commit hook을 통과했습니다. 로컬 Windows Rust coverage 수치는 Linux CI의 100% 증거로 간주하지 않으며 최신 CI 결과를 별도로 확인합니다.
  • 실제 public WASM 및 React/SSR의 TSX/TS/CTS/CJS 40개 행렬이 모두 통과했고 아래는 해당 추출 경로에서 얻은 실제 코드와 CSS입니다. 호출자 inline style이 생성 변수보다 나중에 적용되는 기존 순서도 유지합니다.
  • Rust LSP는 기존 shared daemon timeout으로 사용할 수 없었으며 clean으로 기록하지 않았습니다.

겹치는 color를 CSS prop의 blue로 정적으로 교체해도 원래 캡처 callback은 매 render마다 실행됩니다:

import "@devup-ui/react/devup-ui.css";
import { forwardRef as __devupForwardRef } from "react";
import { jsx } from "@devup-ui/react/compat";
function make(selected, tint) {
	const __devupCss0 = "reserved";
	const trace = [];
	const Card = __devupForwardRef((__devupRefProps, __devupRef) => (({ style, className, as: DevupAs = "article", forwardedAs, ...rest }) => (() => {
		const __devupRecord0 = (0, trace.push("controller"), selected);
		const __devupRecord1 = (0, __devupRecord0 ? (trace.push("leaf"), "red") : undefined);
		return <DevupAs {...(({ "theme": __devupOmit0, "__devupCss1": __devupOmit1, ...__devupDom }) => __devupDom)(rest)} as={forwardedAs} className={[rest.__devupCss1 ?? "a", className].filter(Boolean).join(" ")} style={style} />;
	})())({
		...__devupRefProps,
		ref: __devupRef
	}));
	const App = (ref) => <Card ref={ref} className={"caller"} style={{
		color: "inline",
		"--authored": "kept"
	}} __devupCss1={"b c"}>child</Card>;
	const Plain = (ref) => <Card ref={ref}>plain</Card>;
	return {
		Card,
		App,
		Plain,
		trace
	};
}


/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.c{color:blue}.a{color:green}.b{padding:4px}

등록된 호출자 class가 전체 color atom을 덮는 경우에도 빈 비공개 class를 명시적으로 전달하므로 green 기본값이 되살아나지 않습니다:

import "@devup-ui/react/devup-ui.css";
import { jsx as __devupJsx0 } from "react/jsx-runtime";
import { forwardRef as __devupForwardRef } from "react";
import { jsx } from "@devup-ui/react/compat";
function make(selected, tint) {
	const __devupCss0 = "reserved";
	const trace = [];
	const Card = __devupForwardRef((__devupRefProps, __devupRef) => (({ style, className, as: DevupAs = "article", forwardedAs, ...rest }) => (() => {
		const __devupRecord0 = (0, trace.push("controller"), selected);
		const __devupRecord1 = (0, __devupRecord0 ? (trace.push("leaf"), "red") : undefined);
		return __devupJsx0(DevupAs, {
			...(({ "theme": __devupOmit0, "__devupCss1": __devupOmit1, ...__devupDom }) => __devupDom)(rest),
			className: [rest.__devupCss1 ?? "a", className].filter(Boolean).join(" "),
			style,
			as: forwardedAs
		});
	})())({
		...__devupRefProps,
		ref: __devupRef
	}));
	const caller = "b";
	const App = (ref) => jsx(Card, {
		ref,
		className: caller,
		style: {
			color: "inline",
			"--authored": "kept"
		},
		children: "child",
		__devupCss1: ""
	});
	const Plain = (ref) => jsx(Card, {
		ref,
		children: "plain"
	});
	return {
		Card,
		App,
		Plain,
		trace
	};
}


/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a{color:green}.b{color:purple}

2026-10-16 갱신: 확정 main a6ecc125를 포함한 갱신된 #722 50c98563를 병합했습니다. 충돌 파일 visit.rs/emotion.d.ts는 CSS-prop/private-slot 관련 기존 helper와 main compile_cva를 합치고 CacheProvider 및 jsx/createElement를 모두 보존하여 해결했습니다. 자동 병합된 vanilla_extract.rs/dead_properties/origin.rs 두 호출부는 기존 alias 결과의 code/edits 필드를 그대로 받도록 Aliased 구조체 API에 기계적으로 맞췄습니다. 두 부모 2002f72/50c98563 확인, 새로 생기는 오류 없음.

main #717의 raw Emotion 배열은 CSS fallback이며 Devup responsive 배열과 다릅니다. coordinator 승인 후 fresh 공개 WASM의 TSX/TS × raw/등록된 Devup css 네 경우를 먼저 검증했습니다. 기존 responsive 테스트는 등록된 direct-Devup css 결과를 입력하도록 고치고 관찰 코드와 모든 기존 assertion은 바이트 비교로 그대로 보존했으며, 원래 raw 입력은 별도 fallback 회귀로 추가했습니다. 이 과정은 production 정책/renderer/guard를 바꾸지 않습니다. 생성된 emotion_css_prop_on_every_element 스냅샷 한 개만 재생성 결과 그대로 승인했습니다(assertion_line만 제거): Wide의 aliased margin:[1,2]가 level0 margin:1px;margin:2px fallback이 되고 기존 level1 margin8px가 없어지며 class 번호만 결정론적으로 이동합니다. forwardRef/props 필터 구조와 나머지 스냅샷은 그대로입니다.

기본 스택/CARGO_BUILD_JOBS=2/전체 트리 6 GiB에서 focused 17개, 정상 Rust workspace 4,441개/실패 0개, fmt 및 Rust 1.99 all-target Clippy(-D warnings)가 통과했습니다. fresh WASM/root build 후 실제 WASM/React 82개 검증(부모 20개+CSS-prop 40개+wrapped-key 18개+배열 의미 4개)이 통과했습니다. 이 중 부모 20개와 CSS-prop 40개는 실제 SSR을 포함하며, ref는 값/identity 전달을 확인한 것이고 DOM 부착을 주장하지 않습니다. 혼합 렌더의 캡처 평가·빈 문자열 slot 대체·등록 class 우선순위·동적 CSS 변수/inline style·원래 key 보존·private slot 비누출을 확인했습니다.

전체 commit hook은 기본 스택/12 GiB 한도로 통과했습니다(460.422초, 전체 Job 피크 3.379 GiB). 기존 React 마이그레이션 경고 9개/새 오류 0개, Bun 5,910개/실패 0개/함수·라인 커버리지 100%입니다. Windows Rust coverage 97.87%(19381/19803)는 Linux 100% 증거로 쓰지 않으며 새 head CI에서 별도 확인합니다. 최초 hook의 export 정렬/CRLF 오류는 emotion.d.ts만 ESLint 정리 후 새 commit 시도로 해결했습니다. LSP는 기존 미설치/daemon timeout으로 사용 불가이며 clean으로 주장하지 않습니다. 커밋 후에도 WASM/root build를 다시 통과했습니다. 새 head 76a1796a의 Linux CI 확인은 아래 별도 덧붙입니다.

실제 WASM/공개 API 검증 출력:

// Actual representative module/CSS followed by all 58 CSS-prop/wrapped-key results.
import "@devup-ui/react/devup-ui.css";
import { forwardRef as __devupForwardRef } from "react";
import { jsx } from "@devup-ui/react/compat";
function make(selected, tint) {
	const __devupCss0 = "reserved";
	const trace = [];
	const Card = __devupForwardRef((__devupRefProps, __devupRef) => (({ style, className, as: DevupAs = "article", forwardedAs, ...rest }) => (() => {
		const __devupRecord0 = (0, trace.push("controller"), selected);
		const __devupRecord1 = (0, __devupRecord0 ? (trace.push("leaf"), "red") : undefined);
		return <DevupAs {...(({ "theme": __devupOmit0, "__devupCss1": __devupOmit1, ...__devupDom }) => __devupDom)(rest)} as={forwardedAs} className={[rest.__devupCss1 ?? "a", className].filter(Boolean).join(" ")} style={style} />;
	})())({
		...__devupRefProps,
		ref: __devupRef
	}));
	const App = (ref) => <Card ref={ref} className={"caller"} style={{
		color: "inline",
		"--authored": "kept"
	}} __devupCss1={"b c"}>child</Card>;
	const Plain = (ref) => <Card ref={ref}>plain</Card>;
	return {
		Card,
		App,
		Plain,
		trace
	};
}


/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.c{color:blue}.a{color:green}.b{padding:4px}

[
  {
    "extension": "tsx",
    "selected": false,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": false,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": false,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": false,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": false,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": true,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": true,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": true,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": true,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "selected": true,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": false,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": false,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": false,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": false,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": false,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": true,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": true,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": true,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": true,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "ts",
    "selected": true,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": false,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": false,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": false,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": false,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": false,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": true,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": true,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": true,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": true,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cts",
    "selected": true,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": false,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": false,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": false,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": false,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": false,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": true,
    "case": "nonoverlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "green"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"a b caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": true,
    "case": "overlap",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "blue"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b c caller\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": true,
    "case": "empty",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": true,
    "case": "registered",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "color",
        "purple"
      ],
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": false,
    "ssr": "<article class=\"c b\" style=\"color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "cjs",
    "selected": true,
    "case": "dynamic",
    "mixedRenderCaptures": true,
    "refs": true,
    "children": true,
    "css": [
      [
        "padding",
        "4px"
      ]
    ],
    "dynamic": true,
    "ssr": "<article class=\"b c caller\" style=\"--d:tomato;color:inline;--authored:kept\">child</article>"
  },
  {
    "extension": "tsx",
    "key": "('__devupCss0')",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "tsx",
    "key": "(`\\u005f_devupCss0`)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "tsx",
    "key": "('__devupCss0' as const)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "tsx",
    "key": "'__devupCss0'!",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "tsx",
    "key": "('__devupCss0' satisfies string)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "ts",
    "key": "('__devupCss0')",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "ts",
    "key": "(`\\u005f_devupCss0`)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "ts",
    "key": "('__devupCss0' as const)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "ts",
    "key": "'__devupCss0'!",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "ts",
    "key": "('__devupCss0' satisfies string)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "ts",
    "key": "(<string>'__devupCss0')",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cts",
    "key": "('__devupCss0')",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cts",
    "key": "(`\\u005f_devupCss0`)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cts",
    "key": "('__devupCss0' as const)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cts",
    "key": "'__devupCss0'!",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cts",
    "key": "('__devupCss0' satisfies string)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cjs",
    "key": "('__devupCss0')",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  },
  {
    "extension": "cjs",
    "key": "(`\\u005f_devupCss0`)",
    "plain": [
      "green"
    ],
    "css": [
      "blue"
    ],
    "authoredPreserved": true,
    "privateConsumed": true,
    "captures": true
  }
]

2026-10-16 CI 확인: head 76a1796a4c2276a2e9865b49bdea518dd485d5c8의 run 38859790488은 모든 job 및 Codecov가 통과했습니다. publish job 116839231289 실제 로그에서 Linux Rust 100.00% coverage, 13856/13856 lines covered, Bun 5910개/실패 0개/전체 커버리지 100%를 확인했습니다. 확정 main a6ecc12 포함 및 현재 merge 상태는 최신 PR audit로 재확인했습니다.

2026-10-19 main/부모 계약 동기화 (W60k)

승인된 main 4baa1d7458e14aba30c5fa0c005b1011310fc365을 실제 두 부모 병합 2c127f81218297608b1e16b224c1e69790e262e9으로 반영했습니다. imported_constants.rs, lib.rs, visit.rs 충돌은 main의 #784 pragma bridge·#701 layer 수집·#719/#706 값 처리·#773 실패 전달과 이 PR의 css-prop/D1/Emotion px 경로를 모두 유지했습니다. 중복 pragma 범위 수정은 단일 실제 header 재작성 소유자로 정리했습니다.

검증 중 확인한 문제는 실제 RED 후 수정했습니다: 잘못된/본문 뒤 pragma의 Custom.css 오인, 상수 배열의 px 배율 손실, computed theme key의 문자열 정규화, 합성 후 레이어 최초 사용 순서 손실입니다. 특히 zz → aa → zz는 생존 원자 순서만 뒤집어서는 해결되지 않아, 원자가 교체되기 전에 레이어 메타데이터만 등록하며 잃는 red 원자는 출력하지 않습니다. 기존 pragma 단언 6개는 삭제하지 않고 실제 파서/프로덕션 header 인식기를 호출하는 어댑터로 보강했습니다. 최종 독립 source gate는 APPROVE입니다.

새로 생기는 오류: main의 엄격한 JSX 파서에 따라 유효하지 않은 css={}는 위치 포함 구문 오류입니다. 승인 msg_965f339643b5에 따라 그 토큰만 성공 입력에서 분리했고, 원래 혼합 입력 전체를 새 정확한 test.tsx:1:192 오류 회귀에 그대로 보존했습니다. 모든 유효 입력·기존 단언·깊이는 유지했으며 파서를 완화하지 않았습니다. 나머지 수정은 새 오류 범주를 추가하지 않습니다. 스냅샷 2개만 실제 재생성/검사하여 수용했습니다: Toggle 1회 조건 캡처, main #784의 header 위치 보존 및 승인된 잘못된 태그/빈 출력 제거입니다. 다른 879개는 그대로입니다.

최신 공유 stable 1.99.1/기본 스택/jobs=2에서 Rust 5,489 통과·0 실패·0 무시, fmt/Clippy 통과, Bun 6,320 통과·0 실패·함수/라인 100%, lint 0 오류·기존 20 경고입니다. 실제 WASM/SSR 106개(Emotion 62+부모 26+계약 18)가 통과했고, 전체 12 GiB 훅 562.269초·최대 3.455 GiB 뒤 새 WASM/루트 빌드와 106개를 다시 통과했습니다. 실제 layer 출력은 @layer zz,aa;이며 뒤의 green만 남습니다. Windows Rust 97.91%는 Linux 100% 증거가 아닙니다. 새 head CI 39142933024는 생성되어 실행 중이며 최종 결과를 이어서 확인합니다.

남는 한계: 새 동작 한계 없음, LSP 시간 초과로 검증 불가입니다. 자식의 raw index hash 변화는 숨기거나 복구하지 않았고, 모든 mode/OID/path와 충돌 stage 및 두 부모가 정확히 동일함을 별도로 확인했습니다(원인은 byte 수준 미확정, stat-cache 갱신과 일치). 새 Patch 로그는 관리 대상 bindings/react manifest만 명시합니다. 승인 msg_8d42bc531ec1에 따라 이미 진행 중인 4baa 병합을 끝냈으며 현재 main f014de09와의 실제 충돌 또는 main 원인 CI 실패가 있을 때만 최신 main을 추가 반영합니다.

2026-10-19 CI 실제 결과: head 2c127f81의 39142933024는 publish의 Rust snapshot 1개(test_emotion_css_prop_on_every_element)가 실패했고 다른 검증 job 4개는 통과했습니다. Linux 실행은 4,570 통과·1 실패이며 Linux 100%는 아직 증명되지 않았습니다. 실패 diff는 사용자 병합된 main #774가 Button/Wide/Toggle/JSX-call renderer의 전달 객체에 $ 필터를 추가한 것으로, 4baa 기준 새 css-prop 스냅샷의 기대값에 그 main 변경이 아직 없기 때문입니다. 외부 장애나 Codecov-only 실패가 아닙니다. 현재 MERGEABLE/UNSTABLE이며, 승인 msg_8d42bc531ec1의 실제 main 원인 CI 실패 조건이 충족되어 최신 main 추가 true merge와 스냅샷 재생성·검사·전체 게이트가 다음 단계입니다. 아직 그 추가 병합은 시작하지 않았고, 문맥 인계를 위해 작업 트리는 공개 head에서 깨끗하게 보존했습니다.

  • 2026-10-19: 최신 main 347baaf93(fix(extractor): filter every transient prop from styled rest spreads #774/fix(release): drop the unmanaged app entry from the transient props changepack #788/chore(deps): update all libraries including Oxc #768 포함)을 2c127f81에 실제 2부모 병합하여 7f04f7e2로 갱신했습니다. 텍스트 충돌은 없었고 emotion_css_prop_on_every_element.snap만 재생성하여 Button/Wide/Toggle/네이티브 호출의 전달 props $ 필터 4곳을 반영했으며 기존 CSS·클래스·1회 캡처·private slot·전체 테스트와 Oxc 0.154/sourcemap 9의 승인된 hybrid CommonJS 처리를 유지했습니다. 기본 스택/stable 1.99.1/jobs=2/6GiB에서 Rust 5,562/0, Bun 6,342/0 및 함수·라인 100%, lint 0오류(기존 20경고), 실제 WASM/React 106건을 통과했고 12GiB 전체 훅 1회(662.246초, peak 3.348GiB) 후 새 빌드와 106건도 재통과했습니다. 새로 생기는 오류: 이 통합 자체의 추가 범주는 없으며 최신 main의 엄격한 문법·중첩 선언 진단은 상속합니다. 남는 런타임: main #774의 전달 props 복사본에 대한 Object.fromEntries/Object.entries/filter는 요구된 동작 보존을 위해 유지합니다(새 스타일링 helper/attrs callable 없음). CI 39193539326은 진행 중이며 Linux Rust 100%/전체 작업 성공은 아직 주장하지 않습니다.

실제 훅 이후 WASM 출력 발췌(62 Emotion + 26 부모 + 18 계약 검증 중 overlap/false):

return <DevupAs {...Object.fromEntries(Object.entries((({ "theme": __devupOmit0, "__devupCss1": __devupOmit1, ...__devupDom }) => __devupDom)(rest)).filter(([k]) => k[0] !== "$"))} as={forwardedAs} className={[rest.__devupCss1 ?? "a", className].filter(Boolean).join(" ")} style={style} />;
.c{color:blue}.a{color:green}.b{padding:4px}
  • 2026-10-19 CI 정정: 7f04f7e2의 실제 실행 39193539326은 publish의 Linux Rust 커버리지 한 줄(style_collector.rs의 CI 포맷 기준 27행)만 미달하여 14,712/14,713=99.99%로 실패했습니다. 나머지 React 18/benchmark/Next/Vinext RSC 작업은 모두 성공했으며 Codecov 장애가 아닙니다. 제외·억제·스택/타임아웃 변경 없이 해당 실제 동작을 검증하는 테스트로 보완 중이고, 새 head의 Linux 100%가 확인되기 전에는 전체 성공을 주장하지 않습니다.

  • 2026-10-19 커버리지 보완: CI 포맷의 누락 27행이 실제 ExtractStyleProp::Expression.styles 순회임을 매핑하고, 정적·동적·중복 중첩 레이어가 zz → zz.inner → aa → bb의 최초 작성 순서 메타데이터만 남기는 테스트 1건을 추가했습니다. 해당 순회를 생략한 변이에서 실제 실패를 확인한 뒤 원래 제품 코드를 복원했으며 기존 테스트/스냅샷/제품 동작은 바꾸지 않았습니다. 새 head 3e3d5878(단일 부모 7f04f7e2)의 Rust 5,563/0, Bun 6,342/0 및 함수·라인 100%, 실제 WASM/React 106건, 전체 훅(587.510초/peak 4.365GiB), 훅 이후 새 빌드·106건을 통과했습니다. 새 오류/런타임/의존성/제외·억제는 없으며 새 CI 39197748342의 Linux Rust 100%와 전체 작업 성공은 확인 중입니다.

  • 2026-10-19 최종 확인: head 3e3d5878의 새 CI 39197748342가 SUCCESS이며 Linux Rust 14,713/14,713=100.00%, Bun 6,342 pass/0 fail·함수/라인 100%, preload 회귀 9/0, publish/React 18/benchmark/Next/Vinext RSC 전체 작업과 Codecov 업로드가 성공했습니다(release/deploy 정상 skip). PR은 OPEN/MERGEABLE/CLEAN이고 새 오류 범주·제품 런타임·제외/억제 없이 실제 Expression payload 테스트로 한 줄 누락을 닫았습니다.

owjs3901 and others added 2 commits October 3, 2026 02:02
Refs #690

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… and conditional styles

Refs #690

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 2, 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
  • 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
  • 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 css prop compiles at build time while @emotion/react is aliased: on tags and Devup UI components always, and on every element once the file imports @emotion/react or @emotion/styled or names @emotion/react in a @jsxImportSource pragma; jsx, jsxs and jsxDEV from @emotion/react/jsx-runtime (or jsx-dev-runtime) and jsx from @emotion/react compile it too, so libraries built with Emotion's JSX runtime do as well. The prop becomes the element's className, with CSS variables in its style for values only the runtime gives: arrays and conditions compose with a later part replacing what an earlier one sets, css() classes the file knows compose by their styles, strings and templates are CSS text, css`` mixins split the text around them, a function of the theme reads theme.a.b as var(--a-b), unitless numbers are px as in Emotion (constants included), and a className holding known css() classes overrides the prop as Emotion's registered classes do. A styled component the file defines renders its tag in the element's place when the prop overrides its styles and it renders a tag with no attrs or props read and the element has no spread, as or forwardedAs. Emotion's JSX runtime imports become react/jsx-runtime, the pragma names react, and jsx comes from @devup-ui/react/compat (React's createElement); @devup-ui/react/compat/css-prop types the prop on React.Attributes. New build errors, each with file:line:column and the code: a css prop part the build cannot read (a call, an element), a style object declared inside a function or with let, a binding only running the module gives or code changes, a theme function that does more than return rules or reads the theme other than as theme.a.b in a value, an interpolation CSS text cannot place or a mixin inside a nested rule, and a css prop overriding a styled component's styles where its tag cannot be rendered in place. Composing css() with a part that reads a known binding (such as a keyframes name) now reads its value
  • 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.
  • 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
  • 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

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

@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
  • 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
  • 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 css prop compiles at build time while @emotion/react is aliased: on tags and Devup UI components always, and on every element once the file imports @emotion/react or @emotion/styled or names @emotion/react in a @jsxImportSource pragma; jsx, jsxs and jsxDEV from @emotion/react/jsx-runtime (or jsx-dev-runtime) and jsx from @emotion/react compile it too, so libraries built with Emotion's JSX runtime do as well. The prop becomes the element's className, with CSS variables in its style for values only the runtime gives: arrays and conditions compose with a later part replacing what an earlier one sets, css() classes the file knows compose by their styles, strings and templates are CSS text, css`` mixins split the text around them, a function of the theme reads theme.a.b as var(--a-b), unitless numbers are px as in Emotion (constants included), and a className holding known css() classes overrides the prop as Emotion's registered classes do. A styled component the file defines renders its tag in the element's place when the prop overrides its styles and it renders a tag with no attrs or props read and the element has no spread, as or forwardedAs. Emotion's JSX runtime imports become react/jsx-runtime, the pragma names react, and jsx comes from @devup-ui/react/compat (React's createElement); @devup-ui/react/compat/css-prop types the prop on React.Attributes. New build errors, each with file:line:column and the code: a css prop part the build cannot read (a call, an element), a style object declared inside a function or with let, a binding only running the module gives or code changes, a theme function that does more than return rules or reads the theme other than as theme.a.b in a value, an interpolation CSS text cannot place or a mixin inside a nested rule, and a css prop overriding a styled component's styles where its tag cannot be rendered in place. Composing css() with a part that reads a known binding (such as a keyframes name) now reads its value
  • 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
  • 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
  • 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 2, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
libs/extractor/src/build_time_values.rs 100.00% <ø> (ø)
libs/extractor/src/composition.rs 100.00% <100.00%> (ø)
libs/extractor/src/css_prop.rs 100.00% <100.00%> (ø)
libs/extractor/src/css_utils.rs 100.00% <100.00%> (ø)
libs/extractor/src/dead_properties/origin.rs 100.00% <ø> (ø)
...tractor/src/extractor/extract_style_from_styled.rs 100.00% <100.00%> (ø)
libs/extractor/src/extractor/styled_css_slot.rs 100.00% <100.00%> (ø)
libs/extractor/src/import_alias_visit.rs 100.00% <100.00%> (ø)
libs/extractor/src/imported_constants.rs 100.00% <100.00%> (ø)
libs/extractor/src/jsx_import_source.rs 100.00% <ø> (ø)
... and 9 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 3 commits October 8, 2026 16:00
No conflicted files; retain PR changes and merge updated parent #722, which contains main 11790be.
Refs #690

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
owjs3901 and others added 3 commits October 19, 2026 00:46
Refs #682 #695 #688 #689

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

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

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@owjs3901
owjs3901 merged commit f015f14 into main Oct 19, 2026
8 checks passed
@owjs3901
owjs3901 deleted the fix/emotion-css-prop branch October 19, 2026 05:51
owjs3901 added a commit that referenced this pull request Oct 19, 2026
Refs #682 #695 #688 #689

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