Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
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
b5f5ad0
merge: integrate settled main into remaining type and lint fixes
owjs3901 Oct 20, 2026
92629ff
merge: integrate #750 and settled main into remaining type and lint f…
owjs3901 Oct 21, 2026
6cad867
merge: integrate main d1219765 into PR #763
owjs3901 Oct 23, 2026
366e4a3
test(eslint-plugin): cover remaining rule branches under the latest Bun
owjs3901 Oct 23, 2026
8aa95e8
merge: update PR #763 with settled main
owjs3901 Oct 24, 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"
}
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