Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e0a97d4
fix(extractor): scope build-time style callee detection
owjs3901 Oct 3, 2026
f091ff1
fix(extractor): resolve compiled APIs and lowered reads by lexical bi…
owjs3901 Oct 3, 2026
39ef954
fix(extractor): scope aliased library number rewrites by binding
owjs3901 Oct 3, 2026
5c23118
fix(extractor): follow barrel exports and aliases by symbol
owjs3901 Oct 3, 2026
b54606a
chore: add scoped compiled names patch changepack
owjs3901 Oct 3, 2026
29812f3
test(extractor): cover ClassNames whitespace and non-function children
owjs3901 Oct 3, 2026
87777b7
Merge remote-tracking branch 'origin/fix/eslint-alias-rules' into owj…
owjs3901 Oct 5, 2026
11e3f1c
fix(eslint-plugin): keep composition arrays out of responsive rules
owjs3901 Oct 5, 2026
72781be
fix(react): align StyleX and supported CSS property contracts
owjs3901 Oct 5, 2026
d7e7c63
fix(extractor): enforce the shared static styleOrder contract
owjs3901 Oct 5, 2026
809f8e5
fix(config): validate custom shorthand targets atomically
owjs3901 Oct 5, 2026
8931734
fix(plugins): validate shorthand options before plugin setup
owjs3901 Oct 5, 2026
d69f519
fix(extractor): validate order metadata in composed css props
owjs3901 Oct 5, 2026
74afb76
merge: update PR #750 with PR #742
owjs3901 Oct 8, 2026
4473674
merge: update PR #763 with b711ed1f
owjs3901 Oct 8, 2026
b6545c2
merge: update PR #763 with main
owjs3901 Oct 8, 2026
8b5b4d9
merge: update PR #763 with main
owjs3901 Oct 8, 2026
4825e4b
fix(packages): restore main release baseline in types and lint stack
owjs3901 Oct 8, 2026
baabf69
fix(vite-plugin): restore main RSC CSS behavior and regression gate
owjs3901 Oct 8, 2026
575401f
fix: merge updated type parent into type and lint followups
owjs3901 Oct 16, 2026
7f98689
merge: update PR #750 with PR #742
owjs3901 Oct 16, 2026
1742bb5
merge: update PR #750 with PR #742
owjs3901 Oct 20, 2026
588b1ff
merge: update PR #750 with main
owjs3901 Oct 20, 2026
0807cc8
test(extractor): cover Linux coverage edge paths
owjs3901 Oct 20, 2026
b5f5ad0
merge: integrate settled main into remaining type and lint fixes
owjs3901 Oct 20, 2026
6300750
fix(extractor): merge scoped names with constant css tags
sisyphus-dev-ai Oct 21, 2026
92629ff
merge: integrate #750 and settled main into remaining type and lint f…
owjs3901 Oct 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_dead_css_property_types.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/react/package.json": "Patch"
},
"note": "Reject the 18 unprefixed legacy flexbox and scroll-snap properties rejected by the compiler in component and StyleX property types. Keep vendor-prefixed compatibility properties, stroke-color and ime-mode.",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_eslint_composition_arrays.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/eslint-plugin/package.json": "Patch"
},
"note": "Do not treat composition arrays as responsive property values in the duplicate, trailing-null or single-value array rules. Preserve composition arrays and their nested responsive property diagnostics.",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_scoped_compiled_names.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch"
},
"note": "Resolve compiled styling APIs and components by lexical binding so shadowed local names remain ordinary JavaScript and JSX, and preserve StyleX conditions exactly once in argument order. Conditions needing capture storage now report located build errors in parameter defaults, class field initializers, and statement frames visible to direct eval when safe local storage is unavailable; move the value into a variable in a safe statement body.",
"date": "2026-10-01T00:00:00.000Z"
}
13 changes: 13 additions & 0 deletions .changepacks/changepack_log_shorthand_target_validation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"packages/plugin-utils/package.json": "Patch",
"packages/vite-plugin/package.json": "Patch",
"packages/next-plugin/package.json": "Patch",
"packages/webpack-plugin/package.json": "Patch",
"packages/rsbuild-plugin/package.json": "Patch",
"packages/bun-plugin/package.json": "Patch"
},
"note": "Custom shorthand targets are constrained to supported camelCase/kebab-case CSS properties, built-in aliases and valid custom properties. All five build adapters normalize options at creation before registration or extraction, including Next.js production cache reuse. Unsupported targets fail registration atomically with the original shorthand name, target and zero-based index; previously registered shorthands remain unchanged. New devup-ui option errors expose structured logical shorthands paths without inventing source coordinates. Eighteen dead unprefixed property names are rejected.",
"date": "2026-10-05T00:00:00.000Z"
}
8 changes: 8 additions & 0 deletions .changepacks/changepack_log_style_order_contract.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"changes": {
"bindings/devup-ui-wasm/package.json": "Patch",
"packages/eslint-plugin/package.json": "Patch"
},
"note": "Validate explicit static styleOrder as an integer from 1 to 254 or its canonical decimal digit string, consistently in extraction and lint, including composed Emotion css props. Keep order metadata out of library px conversion. Invalid static values and conditional branches now produce located build errors instead of silently changing cascade order.",
"date": "2026-10-01T00:00:00.000Z"
}
7 changes: 7 additions & 0 deletions .changepacks/changepack_log_stylex_public_contracts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"changes": {
"packages/react/package.json": "Patch"
},
"note": "Type every compiled StyleX API with opaque styles, dynamic function arguments, conditional variables, partial themes and static null/string theme contract placeholders. Unsupported runtime shapes are rejected by TypeScript; no new build errors in this change.",
"date": "2026-10-01T00:00:00.000Z"
}
13 changes: 8 additions & 5 deletions bindings/devup-ui-wasm/src/css_output_edge_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,13 +176,16 @@ fn escaped_string_css_dead_names_fail_before_emission(
#[serial]
fn configured_shorthand_names_keep_their_valid_targets() -> Result<(), String> {
let _scope = super::TestStateGuard::new();
css::set_custom_shorthands(BTreeMap::from([(
"boxAlign".to_string(),
vec!["align-items".to_string()],
)]));
assert_eq!(
css::set_custom_shorthands(BTreeMap::from([(
"boxAlign".to_string(),
vec!["align-items".to_string()],
)])),
Ok(())
);
let source = "import { Box } from '@devup-ui/react';\nconst e = <Box boxAlign='center' />;";
let output = compile(source);
css::set_custom_shorthands(BTreeMap::new());
assert_eq!(css::set_custom_shorthands(BTreeMap::new()), Ok(()));
let css = output?.css().ok_or("expected emitted CSS")?;
assert!(css.contains("{align-items:center}"), "{css}");
assert!(!css.contains("{box-align:"), "{css}");
Expand Down
59 changes: 50 additions & 9 deletions bindings/devup-ui-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -678,16 +678,17 @@ pub fn register_theme(theme_object: JsValue) -> Result<(), JsValue> {
}

/// Internal function to register custom style-property shorthands.
pub fn register_shorthands_internal(shorthands: BTreeMap<String, Vec<String>>) {
css::set_custom_shorthands(shorthands);
pub fn register_shorthands_internal(
shorthands: BTreeMap<String, Vec<String>>,
) -> Result<(), css::shorthand::InvalidShorthandTarget> {
css::set_custom_shorthands(shorthands)
}

#[wasm_bindgen(js_name = "registerShorthands")]
#[cfg(not(tarpaulin_include))]
pub fn register_shorthands(shorthands: JsValue) -> Result<(), JsValue> {
let shorthands = serde_wasm_bindgen::from_value(shorthands).map_err(js_error)?;
register_shorthands_internal(shorthands);
Ok(())
register_shorthands_internal(shorthands).map_err(js_error)
}

#[wasm_bindgen(js_name = "getDefaultTheme")]
Expand Down Expand Up @@ -1209,7 +1210,7 @@ mod tests {
#[serial]
fn test_get_theme_interface() {
let _state = TestStateGuard::new();
register_shorthands_internal(BTreeMap::new());
assert_eq!(register_shorthands_internal(BTreeMap::new()), Ok(()));
let sheet = StyleSheet::default();
assert_eq!(
sheet.create_interface(
Expand All @@ -1231,7 +1232,8 @@ mod tests {
register_shorthands_internal(BTreeMap::from([(
"insetX".to_string(),
vec!["left".to_string(), "right".to_string()],
)]));
)]))
.unwrap();
assert_eq!(
get_theme_interface(
"package",
Expand All @@ -1245,7 +1247,7 @@ mod tests {
);

// test wrong case
register_shorthands_internal(BTreeMap::new());
register_shorthands_internal(BTreeMap::new()).unwrap();
let mut sheet = StyleSheet::default();
let mut theme = Theme::default();
let mut color_theme = ColorTheme::default();
Expand Down Expand Up @@ -1904,7 +1906,7 @@ mod tests {
)]);

register_theme_internal(theme);
register_shorthands_internal(shorthands);
register_shorthands_internal(shorthands).unwrap();

// Verify the theme was registered
let default_theme = GLOBAL_STYLE_SHEET.lock().unwrap().theme.get_default_theme();
Expand All @@ -1913,6 +1915,45 @@ mod tests {
css::disassemble_property("insetX").collect::<Vec<_>>(),
["left", "right"]
);
register_shorthands_internal(BTreeMap::new());
register_shorthands_internal(BTreeMap::new()).unwrap();
}

#[test]
#[serial]
fn test_register_shorthands_internal_rejects_targets_atomically() {
register_shorthands_internal(BTreeMap::from([("old".into(), vec!["py".into()])])).unwrap();
let error = register_shorthands_internal(BTreeMap::from([
("aValid".into(), vec!["width".into()]),
("bad".into(), vec!["height".into(), "widht".into()]),
]))
.unwrap_err();
assert_eq!(error.alias, "bad");
assert_eq!(error.target, "widht");
assert_eq!(error.index, 1);
assert_eq!(css::get_custom_shorthand_names(), ["old"]);
assert_eq!(
css::disassemble_property("old").collect::<Vec<_>>(),
["padding-top", "padding-bottom"]
);
register_shorthands_internal(BTreeMap::new()).unwrap();
}

#[test]
#[serial]
fn test_register_shorthands_internal_normalizes_vendor_targets() {
register_shorthands_internal(BTreeMap::from([(
"maskAlias".into(),
vec![
"WebkitMaskImage".into(),
"-webkit-mask-image".into(),
"--Mask".into(),
],
)]))
.unwrap();
assert_eq!(
css::disassemble_property("maskAlias").collect::<Vec<_>>(),
["-webkit-mask-image", "-webkit-mask-image", "--Mask"]
);
register_shorthands_internal(BTreeMap::new()).unwrap();
}
}
4 changes: 2 additions & 2 deletions bindings/devup-ui-wasm/src/reservation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ pub(super) fn transform(
)
}

const A: &str = "import {Box,keyframes,globalCss} from '@devup-ui/react'; const spin=keyframes({from:{opacity:0},to:{opacity:1}}); globalCss({body:{margin:0}}); export const A=(value:string)=><Box color='red' gap={value} styleOrder={0} animation={`${spin} 1s`} />;";
const A: &str = "import {Box,keyframes,globalCss} from '@devup-ui/react'; const spin=keyframes({from:{opacity:0},to:{opacity:1}}); globalCss({body:{margin:0}}); export const A=(value:string)=><Box color='red' gap={value} styleOrder={1} animation={`${spin} 1s`} />;";
const B: &str = "import {Box,keyframes} from '@devup-ui/react'; const fade=keyframes({from:{opacity:1},to:{opacity:0}}); export const B=(value:string)=><Box bg='blue' w={value} animation={`${fade} 2s`} />;";

#[derive(Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -123,7 +123,7 @@ fn reservation_preserves_nonempty_sheet_theme_and_unchanged_registries() -> Test
)?;
theme.tailwind_css = "@theme { --color-custom: #123; }".into();
register_theme_internal(theme);
css::set_custom_shorthands(BTreeMap::from([("customGap".into(), vec!["gap".into()])]));
css::set_custom_shorthands(BTreeMap::from([("customGap".into(), vec!["gap".into()])]))?;
css::set_prefix(Some("du-".into()));
transform(&input("live.tsx", A), &option)?;
with_style_sheet_mut(|sheet| {
Expand Down
10 changes: 5 additions & 5 deletions bindings/devup-ui-wasm/src/state/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ fn reset_returns_fresh_extraction_when_engine_is_dirty() {
set_prefix(Some("dirty".into()));
set_debug(true);
set_atom_hoist(Some(2));
register_shorthands_internal(BTreeMap::from([("alias".into(), vec!["left".into()])]));
assert_eq!(
register_shorthands_internal(BTreeMap::from([("alias".into(), vec!["left".into()])])),
Ok(())
);
register_theme_internal(
serde_json::from_str(
r#"{
Expand All @@ -47,10 +50,7 @@ fn reset_returns_fresh_extraction_when_engine_is_dirty() {
)]));
extract_fixture();
reset_state_for_testing();
assert_eq!(
export_sheet_internal().unwrap(),
empty_sheet
);
assert_eq!(export_sheet_internal().unwrap(), empty_sheet);
assert_eq!(get_default_theme().unwrap(), None);
assert!(MODULE_RESOLVER.with_borrow(Option::is_none));
assert_eq!(get_prefix(), None);
Expand Down
41 changes: 18 additions & 23 deletions libs/css/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ pub mod is_special_property;
mod num_to_nm_base;
pub mod optimize_multi_css_value;
pub mod optimize_value;
mod property_names;
pub mod quoted;
pub mod rm_css_comment;
mod selector_separator;
pub mod shorthand;
pub mod slot_reservation;
pub mod style_selector;
pub mod tailwind_definitions;
Expand Down Expand Up @@ -268,26 +270,18 @@ pub fn reset_custom_shorthands() {
}

/// Replace the custom shorthand registry used by style extraction.
pub fn set_custom_shorthands(shorthands: BTreeMap<String, Vec<String>>) {
if let Ok(mut registry) = CUSTOM_SHORTHANDS.write() {
let shorthands: BTreeMap<String, Vec<String>> = shorthands
.into_iter()
.map(|(name, properties)| {
let properties = properties
.into_iter()
.flat_map(|property| {
GLOBAL_STYLE_PROPERTY.get(property.as_str()).map_or_else(
|| vec![to_kebab_case(&property).into_owned()],
|mapped| mapped.iter().map(|value| (*value).to_string()).collect(),
)
})
.collect();
(name, properties)
})
.collect();
HAS_CUSTOM_SHORTHANDS.store(!shorthands.is_empty(), Ordering::Relaxed);
*registry = shorthands;
}
pub fn set_custom_shorthands(
shorthands: BTreeMap<String, Vec<String>>,
) -> Result<(), shorthand::InvalidShorthandTarget> {
let shorthands = shorthand::normalize_shorthands(shorthands)?;
let mut registry = CUSTOM_SHORTHANDS.write().unwrap_or_else(|poisoned| {
CUSTOM_SHORTHANDS.clear_poison();
poisoned.into_inner()
});
HAS_CUSTOM_SHORTHANDS.store(!shorthands.is_empty(), Ordering::Relaxed);
*registry = shorthands;
drop(registry);
Ok(())
}

#[must_use]
Expand Down Expand Up @@ -1567,7 +1561,7 @@ mod tests {

#[test]
#[serial]
fn test_custom_shorthand() {
fn test_custom_shorthand() -> Result<(), shorthand::InvalidShorthandTarget> {
let _state = test_state::TestStateGuard::new();
set_custom_shorthands(BTreeMap::from([(
"insetX".to_string(),
Expand All @@ -1576,17 +1570,18 @@ mod tests {
"marginRight".to_string(),
"py".to_string(),
],
)]));
)]))?;

assert_eq!(
disassemble_property("insetX").collect::<Vec<_>>(),
["left", "margin-right", "padding-top", "padding-bottom"]
);

set_custom_shorthands(BTreeMap::new());
set_custom_shorthands(BTreeMap::new())?;
assert_eq!(
disassemble_property("insetX").collect::<Vec<_>>(),
["inset-x"]
);
Ok(())
}
}
Loading
Loading