Skip to content

fix(extractor): compile or reject function-local style objects, keep ClassNames cx calls, read style functions of props - #745

Open
owjs3901 wants to merge 8 commits into
mainfrom
fix/local-style-objects
Open

owjs3901 wants to merge 8 commits into
mainfrom
fix/local-style-objects

Conversation

@owjs3901

@owjs3901 owjs3901 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Refs #688, #690, #695 (함수 안의 스타일 객체, ClassNames cx, Emotion 스타일 함수). 기존 부모 #740과 선행 composition 체인은 병합되었고 main 770aaac5를 실제 merge했습니다. 이제 이 PR이 체인의 맨 아래이며 #754는 최종 green HEAD 58de843 뒤에 병합합니다. 최신 검증과 새 오류·크기/시간 수치는 마지막 W62n 최종 갱신에 기록했습니다.

요약

다른 작업에서 발견된 다섯 가지를 고칩니다.

  1. 조용한 오동작. 함수 안에서 const inner = { color: 'red' }로 선언한 객체를 css(inner)에 넘기면 클래스도 오류도 없이 return inner로 컴파일되었습니다. 인라인되지 않은 식별자가 "알 수 없는 클래스"로 읽혀 그대로 남았기 때문입니다.
  2. 보류: <ClassNames>의 opaque cx 호출 결과 허용은 부모의 정적 조합 계약과 충돌하므로 이번 갱신에서 제외합니다. 나머지 기능은 모두 유지합니다.
  3. css(cond && [{ ... }, { ... }])처럼 조건 아래의 규칙 객체 배열이 Cannot compose 오류였습니다.
  4. styled.div({ ... }).attrs({ ... })가 런타임 호출로 남아 던졌습니다.
  5. styled.div((props) => ({ ... })), styled.div({ ... }, (props) => ({ ... })) 스타일 함수가 Cannot compose 오류였습니다.

동작

1. 함수 안의 스타일 객체. 함수 안 const가 전부 리터럴로 쓰인 객체/배열이면, 최상위 상수와 똑같이 스타일 위치에서 인라인합니다(css(), styled(), globalCss(), keyframes(), 스타일 prop과 펼침, Emotion·styled-components·vanilla-extract 별칭, Global styles, ClassNames의 css). 그 바인딩이 스타일에서만 읽힐 때만 컴파일하고, 다른 곳에서도 읽히면(바뀔 수 있으므로) 바인딩 위치에 오류를 냅니다. 리터럴이 아닌 값을 가진 객체({ color: c })는 호출 위치에 LOCAL_STYLES 오류를 냅니다. 객체를 그대로 돌려주는 경우는 없습니다.

export function f() { const inner = { color: 'red' }; return css(inner) }
export function f() {
	const inner = { color: "red" };
	return "a";
}
.a{color:red}
export function f() { const inner = { color: 'red' }; console.log(inner); return css(inner) }
loc2.tsx:2:29: Cannot read `inner` as styles at build time: it is also read elsewhere, where code may change it, so use it only as styles, or declare it with `const` at the top level of the module

2. ClassNames의 cx. 알 수 없는 함수·멤버 호출은 Devup 스타일 클래스를 반환할 수 있으므로, 배열·조건·숫자 혼합 및 단독 호출 모두 부모의 위치가 표시되는 빌드 오류를 유지합니다. 해당 값이 Devup 스타일 클래스를 포함할 수 없다는 증명이 있을 때까지 opaque 호출 허용은 보류합니다. @emotion/css의 별도 계약은 변경하지 않습니다.

3. 조건 아래의 배열. css()는 known_parts 조합 경로로, styled()는 조건을 각 요소에 분배해(cond && [a, b] → [cond && a, cond && b], 삼항도 같은 방식, 조건이 같은 값을 내야 하므로 순수한 조건만) 조합합니다. 순수하지 않은 조건(get() && [...])은 이전처럼 Cannot compose 오류입니다.

4. .attrs 뒤에 붙이기. 위치가 표시되는 빌드 오류입니다.

at.tsx:2:18: `styled()` cannot use `styled.div({ color: "red" }).attrs({ id: "x" })` at build time: chain `.attrs` before the styles, as `styled.div.attrs({ ... })({ ... })`, which neither Emotion nor styled-components take after them

5. props의 함수. 규칙 객체 하나를 한 번에 돌려주는 callback은 원래 invocation scope와 인수 평가를 유지한 채 한 번 호출하고, authored dynamic value를 순서대로 고정 vector에 캡처합니다. 컴포넌트는 그 값을 CSS 변수로 전달하며, nonzero 숫자는 기존 px 변환을 유지하고 unitless 값은 그대로 둡니다. 리터럴과 증명된 theme 경로는 정적 CSS에 남습니다. ordinary property callback과 상속·ref·attrs·slot 경로는 기존 계약을 유지합니다. 아래의 최신 실제 public WASM 출력과 Callback Scope 보완을 함께 보십시오.

export const A = styled.div((p) => ({ color: p.color, padding: 8, '&:hover': { color: p.hover } }))
export const B = styled.div(({ theme }) => ({ color: theme.colors.primary, margin: theme.space[2] }))

최신 생성 코드와 CSS 원문은 아래 실제 Public WASM 출력에 기록했습니다.

새로 생기는 오류

  • 함수 안에서 선언한 스타일 객체가 스타일 밖에서도 읽힐 때: Cannot read inner as styles at build time: ... (바인딩 위치).
  • 함수 안에서 선언한 객체의 값이 빌드 시점에 알려지지 않을 때: ``css()cannot useinner` at build time: a style object it composes must be written in it, or declared with `const` at the top level of the module, where the build reads it`.
  • styled(...)(...) 뒤에 .attrs(...): 위의 chain .attrs before the styles 오류.
  • 규칙 객체 하나를 한 번에 돌려주지 않는 props 스타일 함수(본문에 문장이 있거나, 펼침·계산된 키·async가 있을 때): ``styled()cannot use<함수>` at build time: a style function of the props must give one rule object at once, with every entry written out, ...`.
  • 기존의 ClassNames cx(getClass()) 및 숫자·배열·조건 내부 unknown-call 오류는 유지합니다. 알려진 규칙 배열의 일부 중복 Cannot compose 오류만 해소합니다.

남는 한계

  • props 스타일 함수가 p.on && { ... }처럼 조건으로 규칙 객체 자체를 고르면 오류입니다(값마다 조건은 지원).
  • 최상위 const 객체와 달리, 함수 안 객체는 값이 전부 리터럴이어야 컴파일됩니다.
  • 순수하지 않은 조건(get() && [...])이 고르는 배열은 오류입니다.
  • {...inner}처럼 런타임 값이 있는 객체를 Box에 펼치는 동작은 이 PR의 범위 밖입니다(이전과 같음).

검증

최신 공유 Rust stable과 새 WASM/root build의 전체 검증 결과는 아래 W62g 실제 재검증에 기록했습니다. 이전 head의 역사적 성공을 이번 변경의 검증으로 계산하지 않습니다.

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

2026-10-18 W62g 갱신: 갱신된 #740 93fe24a4877d11a14f30a7a1fd914eefe6f3b8e4를 실제 두 부모 merge commit으로 반영하고 main a6ecc125 및 정적 fallback 26개 단언·capture/slot/marker·부모 오류 가드를 보존했습니다. ClassNames 호출 허용에 관한 자체 테스트 test_emotion_class_names_keeps_calls_as_classes는 located error를 명시적으로 검증하도록 바꾸고 test_emotion_class_names_reports_what_it_cannot_compile에는 부모의 단독 cx(getClass()) 거절을 복구했습니다. ClassNames cx의 opaque 호출은 Devup 스타일 클래스를 포함할 수 없다는 증명이 있기 전까지 위치가 표시되는 오류로 유지하며, 해당 허용 하나만 보류하고 다른 #745 기능은 유지합니다. test_style_objects_declared_in_functions 스냅샷은 부모 globalCss/keyframes의 인수 위치·오류 순서만 반영하고 test_styled_functions_of_the_props_and_arrays_under_conditions 스냅샷의 규칙 객체가 아닌 반환은 부모의 호출 위치 Cannot compose 거절을 보존합니다. test_styled_props_rules_keep_unmapped_theme_guards와 test_styled_function_leaves_map_literal_theme_paths는 정규화 전 theme 거절·일반 함수 리터럴 경로를 검증하고, conditional_rule_arrays_keep_one_getter_read_and_consistent_branches는 기존 capture lowering으로 getter를 한 번만 읽어 분기가 섞이지 않음을 두 CSS 모드에서 검증합니다. 동일 배열 입력은 정확한 main a6ecc125와 부모 93fe24a4에서는 기존 located Cannot compose 오류였고, 이 PR에서 새로 허용한 경로의 중복 읽기를 수정한 것입니다.

Theme 추가 회귀 test_styled_renamed_theme_roots_keep_located_errors는 별칭 root 우회를 독립 RED로 재현한 뒤 검증합니다. 승인된 기존 theme 경로 정책을 재사용하여 p['theme'] 및 ({theme: themeValue})의 리터럴 경로는 같은 CSS 변수로 매핑하고, 동적 키·호출은 기존 located error를 유지합니다. 정확한 main a6ecc125와 부모 93fe24a4는 해당 whole-rule/value-callback 네 입력을 모두 기존 오류로 거절했으며 런타임 theme 접근을 내보내지 않았습니다.

별도 수정 예정인 기존 오류: 일반 class-name 경로에서 literal-table 키 객체의 Symbol.toPrimitive와 뒤쪽 opaque 클래스 getter의 순서가 뒤바뀔 수 있습니다. 정확한 main a6ecc125와 부모 #740 93fe24a4의 public WASM에서 두 CSS 모드 모두 on,key,after,coerce 및 blue를 재현했습니다(올바른 순서는 on,key,coerce,after 및 red). coordinator 결정 msg_fb5ec7b4a02b에 따라 이 PR은 해당 부모 동작과 동등하게 유지하고 일반 경로 수정은 별도 PR로 분리합니다. 실험 회귀 lookup_key_coercion_precedes_later_opaque_reads의 올바른 red 기대값과 실제 RED 결과·30개 origin probe는 W62g 별도 재현 자료에 변경 없이 보존했으며 기존 부모·원래 #745 테스트는 제거하거나 완화하지 않았습니다.

W62g 실제 재검증

2026-10-18 현재 부모 검증은 로컬 실제 merge ffc8ff6a의 두 부모 d846365d + 93fe24a4 위에서 수행했습니다. 원격 검증은 마지막 CI 기록의 실제 head/run만 근거로 삼으며, 이전 head의 성공 run을 새 수정의 검증으로 사용하지 않습니다.

  • 공유 최신 Rust stable 1.99.1, CARGO_BUILD_JOBS=2, 기본 Rust stack으로 fmt check / workspace·all-target strict Clippy / 전체 Rust 테스트 4,990 pass, 0 fail, 0 ignored. INSTA_FORCE_PASS와 snapshot update를 해제한 최종 재실행도 성공했습니다.
  • snapshot 후보는 하나뿐이며 48개 row 전체를 비교했습니다. 10개 row는 compiler-owned render IIFE 제거와 이미 캡처한 값에 대한 동일 숫자 정규화 이동이고, 나머지 38개 row와 기존 거절은 그대로입니다. 승인 후 .snap.new는 남기지 않았습니다.
  • 새 bun install → bindings WASM build → root build 성공, Bun 5,922 pass, 0 fail, 함수·라인 100%, lint 0 errors / 기존 warnings 9개. 로컬 Bun 1.4.3 / Node 24.19.0 / wasm-pack 0.15.0을 관찰했으며 Rust나 도구 버전을 낮추거나 고정하지 않았습니다.
  • 실제 public WASM에서 원래 callback 회귀 14 GREEN, 추가 계약 22 PASS, 암묵적 theme root 10 GREEN, catch/loop/switch/var 및 named self-reference 추가 84 PASS, member-fold 거절 16 GREEN, 14개 API의 member guard 84 PASS, 기존 SDK 182 PASS. 실제 Chromium의 desktop/mobile 계산 스타일 100 PASS는 기존 88개와 추가 12개를 모두 포함합니다.
  • 파서 stack 오류는 native Boa Script::parse에서 재현했습니다. 실행하지 않은 callback과 catch를 없앤 control도 넘쳤고, 단순 catch는 성공했습니다. 숫자 변환을 캡처한 고정 slot으로 옮기고 중복된 compiler-owned IIFE만 제거한 뒤, 원본 catch/더 깊은 loop 입력을 바꾸지 않고 두 CSS 모드의 기본 stack에서 성공했습니다. stack/profile 조정, authored callback body wrapper 변경, 런타임 helper·프로토콜·규칙 해석기는 추가하지 않았습니다.
  • 생성물을 읽는 최종 public WASM SHA256: 207443f07750a5a6a57267783510177c8508dca6de051bd039f4b8cc597ce148. 빌드·테스트 job은 모두 owned process를 0까지 정리했습니다. Rust LSP daemon은 timeout이므로 LSP clean이라고 주장하지 않으며 실제 컴파일·Clippy·테스트 결과로 검증했습니다.

Local Member 정적 가드 보완

최종 리뷰가 const holder={rules:{color:'red'}}; holder.rules.color='blue'; css(holder.rules)의 stale red 컴파일을 발견했습니다. 기존 가드가 bare identifier만 세고 member/fold/조건 선택으로 지워진 참조를 놓친 원인입니다. 두 모드에서 native 16 FAIL / safe 12 PASS, public 16 RED, 14개 API baseline의 safe 28 PASS / unsafe 56 RED를 먼저 실행한 후 수정했습니다. 원래 RED 기대값과 부모 negative는 그대로입니다.

성공한 AST 치환에서 지워지는 semantic reference ID를 원래 symbol에 한 번만 계산합니다. 조건의 test/버려진 branch는 계산하되, 선택되어 런타임에 남는 subtree와 실패한 fold probe를 미리 계산하지 않습니다. 원래 reference 총수·binding 위치·Cannot read ... as styles ... it is also read elsewhere 오류와 safe style-only member 동작은 유지합니다. 같은 참조를 다시 방문해도 중복 가산하지 않으며 numeric/fallback/ClassNames 경로와 별도 generic key-coercion 정책은 바꾸지 않았습니다. 새 native accounting 90개를 포함한 최종 4,990개와 public 16+84개가 모두 성공했습니다.

Callback Scope 보완

Whole-rule callback은 원래 arrow/일반 함수, 이름, 인수·중첩 destructuring·default·rest, strict directive, this/arguments 소유권을 유지하며 실제 props를 한 번만 받습니다. 모든 authored dynamic leaf는 사라진 선언까지 순서대로 한 번 캡처하고, empty/static-only callback의 인수 평가도 render마다 보존합니다. function-valued leaf는 (0, slot)(rest)로 호출하여 배열을 receiver로 주지 않습니다. 상속·attrs·ref·children·caller style·CSS slot·marker·retargeting 및 부모 static fallback 26개 단언을 유지했습니다.

암묵적인 실제 props theme 접근은 own arguments[0] 또는 첫 번째 rest formal의 [0]를 검사하되, catch/loop/switch lexical binding은 해당 scope 안에만 적용하고 nested block의 var는 함수 scope로 처리합니다. 기존 정적 theme 경로 매핑을 확대하지 않았습니다.

새로 생기는 오류 (이번 보완)

이름 있는 whole-rule callback이 자신의 실제 함수 binding을 참조하면 위치가 표시되는 build error가 나며 callback 이름과 recursion is not compiled 이유를 제공합니다. .name/.length, default와 nested capture도 포함하며, 같은 철자의 다른 lexical binding과 property 이름 또는 일반 property callback은 이 거절에 포함하지 않습니다. 정확한 main 3ca6182f와 부모 #740 93fe24a4도 원래 재귀 입력을 두 모드 모두 located error로 거절했으므로, 이 PR의 새 경로가 만든 runtime ReferenceError/undefined를 방지하는 승인된 수정입니다.

자체 신규 draft normal_callback_identity_and_direct_scope_survive_when_it_is_named의 metadata-positive 기대는 승인된 정책에 맞는 명시적 거절과 normal_callback_direct_scope_survives_when_named_without_self_references로 분리했습니다. strict this / own arguments / 실제 인수 한 개의 positive 검증과 원래 부모 negative는 유지했습니다. 추가 인수나 arguments/rest로 우회한 unprovable theme read도 기존 located error를 유지합니다.

남는 한계는 승인된 기존 범위와 같습니다. ClassNames cx의 opaque 호출 허용 하나만 보류하며, 일반 object key coercion 순서 오류는 기존 main/부모 origin과 현재 head 관찰 30건을 남겨 별도 수정합니다. 재귀적으로 반환한 rule object의 런타임 해석은 지원하지 않으며 스타일링 런타임을 추가하지 않습니다. Linux Rust 100%는 새 head의 실제 CI 결과를 확인한 후 별도로 기록합니다.

실제 Public WASM 출력

입력:

import styled from '@emotion/styled';export const A=styled.div(function(p){return{color:arguments[0].color,margin:arguments[0].m};});

생성 코드:

import "@devup-ui/react/devup-ui.css";
import { jsx as __devupJsx0 } from "react/jsx-runtime";
import { forwardRef as __devupForwardRef } from "react";
export const A = __devupForwardRef((__devupRefProps, __devupRef) => (({ style, className, as: DevupAs = "div", forwardedAs, ...rest }) => {
	const __devupRecord0 = (0, function(p) {
		return [arguments[0].color, arguments[0].m];
	}(rest));
	return __devupJsx0(DevupAs, {
		...(({ "theme": __devupOmit0, ...__devupDom }) => __devupDom)(rest),
		className: ["a c", className].filter(Boolean).join(" "),
		style: {
			...{
				"--b": (__devupValue=>typeof __devupValue===`number`&&__devupValue!==0?__devupValue+`px`:__devupValue)(__devupRecord0[`0`]),
				"--d": (__devupValue=>typeof __devupValue===`number`&&__devupValue!==0?__devupValue+`px`:__devupValue)(__devupRecord0[`1`])
			},
			...style
		},
		as: forwardedAs
	});
})({
	...__devupRefProps,
	ref: __devupRef
}));

생성 CSS (singleCss의 base CSS 원문):

/*! devup-ui v1.0.83, | Apache License 2.0 | https://devup-ui.com */.a{color:var(--b)}.c{margin:var(--d)}

이름 있는 callback의 실제 오류:

/src/props.ts:2:27: `styled()` cannot use `(function rules(p) { return { color: p.done ? "red" : rules({ done: true }).color }; })` at build time: callback `rules` refers to itself; recursion is not compiled

Member mutation의 실제 입력과 오류:

import {css} from '@devup-ui/react';
export function f(){const holder={rules:{color:'red'}};holder.rules.color='blue';return css(holder.rules);}
/src/props.ts:2:27: Cannot read `holder` as styles at build time: it is also read elsewhere, where code may change it, so use it only as styles, or declare it with `const` at the top level of the module

W62n 최종 갱신 (2026-10-21)

최종 HEAD는 58de84303cf30fc5023d59a50734db6e8df1f5e4이며, CI 39538680688이 첫 시도에 성공했습니다. main 770aaac5를 실제 merge한 587a11c1 및 후속 a2cbceaa의 기존 수정과 원래 단언을 모두 보존했습니다. 실제 CI merge ref 180acda94b71435368038e3e1952fbf17b94935b의 트리는 로컬에서 검증·훅·커밋한 트리 a4e8441ea7b066d1c52d53a81cfad73f26c0ce11과 같습니다. 이 PR은 이제 병합된 부모들을 포함한 main 위의 맨 아래 PR이며, #754가 이 최종 green HEAD를 뒤이어 반영합니다.

요약 / 동작

  • 타입 이름이 값 자기참조를 가리는 whole-rule callback 결함을 고쳤습니다. Oxc 분석용 복사본에서만 최상위 타입 매개변수를 지우고 실제 함수 값 바인딩을 확인합니다. 원본·생성 callback의 generic, 이름, 인수·default·rest·this·arguments와 실제 값 shadow는 바꾸지 않습니다.
  • 원래 테스트·단언·스냅샷을 수정하거나 완화하지 않고, wrapper guard 12건과 실제 extractor의 generic/값 shadow 32건을 추가했습니다. 수정 전 확정 RED는 90 pass / 16 fail이며, 최종 전체 Rust 테스트는 6,674 pass / 0 fail / 0 ignored입니다.
  • 한 번의 제한된 크기 패스에서 신규 코드의 중복 predicate·조건/인자 순회·정확히 같은 오류 생성·임시 오류 벡터만 줄였습니다. Preparation::value의 마지막 경로는 Evaluations::capture를 직접 호출합니다. 생성 이름·CSS/코드·기존 오류의 문구/위치/순서·크기 정책·도구 버전·stack/profile은 바꾸지 않았고 런타임이나 의존성을 추가하지 않았습니다.

새로 생기는 오류 / 원인 귀속

다음 입력은 타입 이름이 같아도 값 자기참조이므로 기존의 위치 오류가 납니다.

import {styled} from '@devup-ui/react';export const X=styled('div')(function rules<rules>(p:rules){return {color:rules.name};});

실제 공개 WASM 출력:

W62n-generic-same-type-name.tsx:1:69: `styled()` cannot use `(function rules<rules>(p: rules) { return { color: rules.name }; })` at build time: callback `rules` refers to itself; recursion is not compiled

정확한 공개 엔진으로 main770 / parent587 / a2를 먼저 비교했습니다. main770은 해당 whole-rule callback 입력 세 개 모두를 위치 포함 Cannot compose로 거부하므로 같은 허용 경로의 우회가 노출되지 않습니다. parent587과 a2는 다른 타입 이름의 자기참조는 거부하지만 같은 이름이면 허용했습니다. 따라서 a2에서 새로 생긴 결함이라고 하거나, main에도 같은 허용 경로의 결함이 있다고 추정하지 않습니다. function rules<rules>(p:rules){return {color:'red'};}처럼 타입으로만 쓰는 대조군은 generic을 유지하고 .a{color:red}로 컴파일되며, 실제 값 매개변수·중첩 값 shadow도 유지합니다. 기존 오류 문구를 바꾼 것이 아니라 놓치던 입력에 같은 진단을 적용합니다.

크기 / 추출 시간

1% WASM 정책은 그대로이며 실제 Linux CI wasm-size가 통과했습니다.

측정 main bytes 최종 bytes 기존 한도 결과
CI WASM raw 5,993,584 6,053,877 6,053,888 PASS (11 bytes 여유)
CI WASM gzip level 9 2,183,348 2,202,397 2,205,696 PASS
CI glue raw / gzip 43,165 / 7,327 43,165 / 7,327 44,032 / 8,192 PASS

직전 a2의 실제 Linux raw 6,054,095에서 218 bytes 감소해 207 bytes 초과를 해결했습니다. Linux gzip은 a2의 2,202,702에서 305 bytes 감소했습니다. 동일 로컬 도구의 별도 비교는 고정 a2 raw 6,059,994 / gzip 2,203,856 → 최종 raw 6,059,776 / gzip 2,203,417(−218 / −439)이며, 로컬 수치와 Linux 수치를 섞지 않습니다. 각 중간 후보의 raw/gzip과 paired 시간도 별도 보존했습니다.

기존 Criterion의 14개 원문 코퍼스와 고정 a2 공개 WASM을 그대로 사용했습니다. 런타임 raw literal의 CRLF→LF는 Rust lexical 처리와 같게만 적용하고 출력 코드/CSS/오류는 정규화하지 않았습니다. 한 번의 추출은 reset·theme/debug/prefix·공개 extract/getters/getCss/free를 포함하고 모듈 로드·warmup·비교 assertion은 제외합니다. 다음 값은 각 case 한 번의 추출 시간을 동일 가중치로 더한 것이며 앱 빌드 시간이나 native Criterion 수치가 아닙니다.

최종 paired 실행 (각 100 AB/BA rounds × case당 512회) a2 중앙값(ms) 최종 중앙값(ms) 중앙값 비율 paired 비율 중앙값
1 3.706716 3.721631 1.004024 1.003076
2 3.364934 3.343436 0.993611 0.995149

두 실행 모두 14 accepted / 0 rejected이고 원문 출력이 정확히 일치했습니다. 통합 200 paired 비율 중앙값은 0.997736, 105/200쌍이 더 낮았으며, 독립 쌍을 가정한 중앙값 95% order-statistic 구간은 0.989279–1.003869입니다. 이는 새 합격 허용치를 만든 것이 아니며 속도 향상이나 수학적인 무감속 증명이 아닙니다. 전체 수치는 방향이 바뀌어 반복되는 전체 추출 감속이 확인되지 않았지만, CSS-template 두 case의 pooled paired 중앙값 1.01624 / 1.01948 및 보정하지 않은 양의 구간도 숨기지 않습니다. 다중 비교·공유 머신 잡음·쌍의 독립성에는 불확실성이 남습니다. 같은 아티팩트 smoke와 다른 후보 반복에서도 방향이 바뀌었으며, CI benchmark는 기존 정책 그대로 첫 시도에 성공했습니다. 유리한 중앙값만 골라 성능 향상을 주장하지 않습니다.

검증 / 남는 한계

  • 새 install → 명시적 binding WASM → root build → fmt / workspace·all-target·all-feature strict Clippy / Rust 6,674/0/0 / Bun 6,649/0 및 함수·라인 100% / lint 0 errors·기존 warnings 20개 / verify:dist 모두 성공했습니다. 최신 공유 stable 1.99.1, Bun 1.4.3, 시스템 Node 26.12.0, wasm-pack 0.15.0을 사용했습니다.
  • 같은 최종 트리에 정상 훅 한 번을 통과했습니다. Windows Rust 97.85%(23,999/24,527)는 Linux 합격으로 세지 않습니다. **최종 Linux tarpaulin은 100.00%(17,059/17,059), Bun은 6,649/0·함수/라인 100%**입니다. 여덟 검증 job이 모두 첫 시도에 green이며 배포 전용 두 job만 PR에서 정상 skip입니다. landing 두 빌드는 각각 71 routes / 0 skipped였습니다.
  • 실제 기본 aliases를 켠 공개 비교에서 기존 인접 사례와 숫자·unary·boolean·null·TS wrapper 48건이 고정 a2와 정확히 같고 반복 결정론적입니다. 그중 네 건은 실제 위치 오류이며, 긍정 alias 사례는 실제 변환을 확인했습니다. 별도 eight-fixture 공개 probe와 generic 자기참조/대조군도 성공했습니다. 초기 {} aliases 비교는 Emotion pass-through였으므로 해당 오류의 근거로 세지 않고, 리뷰 지적 뒤 수정한 alias-enabled 증거만 사용합니다.
  • 최종 단일 gate review는 APPROVE(MEDIUM)입니다. Rust LSP daemon timeout 및 미설치 TS/JSON LSP는 clean이라고 주장하지 않고 실제 컴파일·Clippy·테스트로 검증했습니다. 커버리지 제외나 단언 완화는 없습니다. changepack은 관리 대상인 bindings/devup-ui-wasm/package.json만 Patch입니다.
  • opaque ClassNames cx unknown-call 허용 하나는 계속 보류하며 두 원래 guard 테스트와 기존 located error를 유지합니다. callback-form admission 자체가 거부하는 wrapper 구문을 새 지원이라고 주장하지 않습니다. 동적으로만 알 수 있는 전체 규칙 키·재귀 rule-object 해석은 계속 빌드 오류이며 스타일링 런타임을 넣지 않습니다. 별도 소유였던 일반 key-coercion 문제를 이 갱신에 몰래 포함하거나 현재 main 상태를 추정하지 않았습니다.

owjs3901 and others added 2 commits October 3, 2026 22:22
…ClassNames cx calls, read style functions of props

Refs #690, #695

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

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

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

Copy link
Copy Markdown
Contributor

Changepacks

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

Patch

  • 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
  • Emotion's now compiles at build time with no runtime: the element becomes what its child function ({ css, cx, theme }) => ... renders, each css and cx call becomes the classes it composes (later styles replace earlier ones; css reads strings as CSS text and its tagged templates split around mixins; cx keeps unknown classes, reads { name: condition } objects as conditional classes and composes nested css and cx calls), and theme.a.b in their values becomes var(--a-b). Numbers in css rules are px lengths, as Emotion reads them, and top-level constants are inlined. New build errors, each at file:line:col: a without exactly one child function taking { css, cx, theme } that gives what it renders at once (attributes, other children, rest or default parameters, async or generator functions, several statements); reading the theme other than as theme.a.b in a css value; using css or cx other than by calling them; a cx object with a spread, getter or method; parts css and cx cannot compose (runtime calls, spreads, a condition choosing between styles and classes). @devup-ui/react/compat exports ClassNames with the ClassNamesContent and ClassNamesArg types, and the @emotion/react declarations include them
  • Emotion and styled-components component selectors now compile at build time: a styled component the file defines that another style selects (${Child} { ... } in CSS text, [Child] or [&:hover ${Child}] as a rule key) gets a short marker class, which only such components get, and the selector reads it as .marker; a component extending it, or an element inlining it for its css prop, keeps the marker. Selectors written without & now nest under the component as stylis nests them (ul { ... } is & ul, a selector list nests each selector, :hover applies to the component itself) instead of becoming global rules, and rule-object keys starting with ., #, [, *, >, + or ~ are read as nested selectors instead of properties
  • 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
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source
  • Numbers in rules written against Emotion, styled-components or vanilla-extract now read as px lengths when a constant stands in for the literal, as they do for a literal written in place: css({ padding: gap }) with const gap = 8, styled.div({ margin: SIZE }), <Global styles={{ body: { margin: GAP } }} /> and the same with imported constants and constant style objects give 8px, while unitless properties keep bare numbers, 0 stays 0 and Devup shorthands such as p keep the spacing scale. css${mixin}; color: red; outside the css prop composes the mixin by styles, so a later declaration replaces the mixin's, and a css... template is a known style a later css() composes. Inline css() calls inside css() arguments (css(css({ ... }), { ... }), css(cond && css({ ... }), base)) are composed with the other parts, the later winning, instead of being compiled to a class of their own. 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.
  • A style object declared with const inside a function and passed to css(), styled(), globalCss(), keyframes(), a style prop or a spread (also through the Emotion, styled-components and vanilla-extract aliases) now compiles when every entry is written out and the object is only read as styles, instead of leaving the object where the class belongs; the build errors, at the binding, when the object is also read elsewhere (where code may change it), and at the call when it holds values only the runtime gives. Emotion's ClassNames cx keeps a function call's result as a class that stays as written, as @emotion/css cx does. css() and styled() compose an array of rule objects under a condition. A style function of the props (styled.div((props) => ({ ... })), and as a later argument) compiles: each value reading the props becomes a CSS variable the component sets (a number is a px length unless the property is unitless), theme reads (including theme.a[2]) become var(--a-2), and a condition chooses per value. New build errors: styled(...).attrs(...) chained after the styles (chain .attrs before them), a style function of the props that does not give one rule object at once with every entry written out, and a style object declared in a function that is also read elsewhere
  • 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
  • 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

@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
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@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

  • 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

  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@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
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

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

Patch

  • Emotion's now compiles at build time with no runtime: the element becomes what its child function ({ css, cx, theme }) => ... renders, each css and cx call becomes the classes it composes (later styles replace earlier ones; css reads strings as CSS text and its tagged templates split around mixins; cx keeps unknown classes, reads { name: condition } objects as conditional classes and composes nested css and cx calls), and theme.a.b in their values becomes var(--a-b). Numbers in css rules are px lengths, as Emotion reads them, and top-level constants are inlined. New build errors, each at file:line:col: a without exactly one child function taking { css, cx, theme } that gives what it renders at once (attributes, other children, rest or default parameters, async or generator functions, several statements); reading the theme other than as theme.a.b in a css value; using css or cx other than by calling them; a cx object with a spread, getter or method; parts css and cx cannot compose (runtime calls, spreads, a condition choosing between styles and classes). @devup-ui/react/compat exports ClassNames with the ClassNamesContent and ClassNamesArg types, and the @emotion/react declarations include them
  • 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
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@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
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

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

Patch

  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@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
  • A project whose tsconfig.json (or jsconfig.json) builds JSX with Emotion (compilerOptions.jsxImportSource of @emotion/react, through extends and project references too) now compiles the css prop on every element of every file, and its .tsx and .jsx files are built with React's JSX runtime through a /** @jsxImportSource react */ pragma, which a file's own JSX pragma overrides, so the bundler no longer imports Emotion's runtime. The plugins read the setting into a new @emotion/react/jsx-runtime alias (importAliases can turn it off with false, and it is left out when @emotion/react is not aliased); @devup-ui/react/compat/emotion-jsx-runtime declares Emotion's JSX runtimes as React's so the project type-checks without Emotion installed. plugin-utils exports readJsxImportSource, and mergeImportAliases takes the JSX import source

@codecov

codecov Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

Files with missing lines Coverage Δ
libs/extractor/src/consumed_references.rs 100.00% <100.00%> (ø)
libs/extractor/src/css_utils.rs 100.00% <100.00%> (ø)
...tor/src/extractor/extract_style_from_expression.rs 100.00% <100.00%> (ø)
...tractor/src/extractor/extract_style_from_styled.rs 100.00% <100.00%> (ø)
...s/extractor/src/extractor/rule_array_evaluation.rs 100.00% <100.00%> (ø)
libs/extractor/src/extractor/styled_arguments.rs 100.00% <100.00%> (ø)
libs/extractor/src/extractor/styled_props.rs 100.00% <100.00%> (ø)
...ibs/extractor/src/extractor/styled_props_leaves.rs 100.00% <100.00%> (ø)
...actor/src/extractor/styled_props_self_reference.rs 100.00% <100.00%> (ø)
...ibs/extractor/src/extractor/styled_props_values.rs 100.00% <100.00%> (ø)
... and 12 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.

No conflicted files; retain PR changes and merge updated parent #740, which contains main 11790be.
owjs3901 added a commit that referenced this pull request Oct 8, 2026
No conflicted files; retain PR changes and merge updated parent #745, which contains main 11790be.
owjs3901 and others added 2 commits October 17, 2026 23:18
Preserve the updated parent guards and local-style features while capturing
conditional rule controllers once and retaining static theme path checks.

Refs #707

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

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

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

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
owjs3901 and others added 3 commits October 20, 2026 22:54
…e objects

Refs #688

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

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

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

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

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