Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 17 additions & 5 deletions crates/rustmotion-components/src/box_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1495,7 +1495,9 @@ fn apply_intrinsic_overrides(component: &Component, css: &mut CssStyle) {
css.width = Some(CSize::Length(CLP::Px(c.width)));
}
if css.height.is_none() {
let font_size = c.style.font_size_px_or(16.0);
let font_size = c
.style
.font_size_px_ctx(&crate::intrinsic::measure_time_font_size_ctx(0.0), 16.0);
let line_height = font_size * 1.3;
let n = c.items.len() as f32;
let h = n * line_height + (n - 1.0).max(0.0) * c.gap;
Expand Down Expand Up @@ -1640,7 +1642,9 @@ fn apply_intrinsic_overrides(component: &Component, css: &mut CssStyle) {
// box is fit exactly to the unwrapped text width, the painter's
// own `wrap_text(text, font, Some(text_area_w))` never has a
// reason to wrap, so painted output matches this box exactly.
let font_size = t.style.font_size_px_or(16.0);
let font_size = t
.style
.font_size_px_ctx(&crate::intrinsic::measure_time_font_size_ctx(0.0), 16.0);
let family = t.style.font_family_or("Inter");
let text_w = measure_text_line_width(&t.text, font_size, family, false);
let h_pad = 12.0; // callout.rs's own `let padding = 12.0;`
Expand All @@ -1660,7 +1664,10 @@ fn apply_intrinsic_overrides(component: &Component, css: &mut CssStyle) {
// Same shape as Callout above; padding value borrowed from
// callout.rs since tooltip.rs's own paint() centers text in the
// body with no defined constant of its own.
let font_size = t.style.font_size_px_or(t.font_size);
let font_size = t.style.font_size_px_ctx(
&crate::intrinsic::measure_time_font_size_ctx(0.0),
t.font_size,
);
let family = t.style.font_family_or("Inter");
let text_w = measure_text_line_width(&t.text, font_size, family, false);
let h_pad = 12.0;
Expand All @@ -1685,7 +1692,9 @@ fn apply_intrinsic_overrides(component: &Component, css: &mut CssStyle) {
// formula (h_pad = font_size*1.2 per side, `gap` before/after/
// between every pill) using the same public fields and the same
// `measure_text_with_fallback` call it makes internally.
let font_size = p.style.font_size_px_or(14.0);
let font_size = p
.style
.font_size_px_ctx(&crate::intrinsic::measure_time_font_size_ctx(0.0), 14.0);
let family = p.style.font_family_or("Inter");
let h_pad = font_size * 1.2;
let n = p.items.len() as f32;
Expand Down Expand Up @@ -1713,7 +1722,10 @@ fn apply_intrinsic_overrides(component: &Component, css: &mut CssStyle) {
// height ratio both of those same real usages share:
// `font_size: 24` paired with `style.height: 48`, i.e.
// `2 × font_size`.
let font_size = m.style.font_size_px_or(m.font_size);
let font_size = m.style.font_size_px_ctx(
&crate::intrinsic::measure_time_font_size_ctx(0.0),
m.font_size,
);
apply_default_size(css, 800.0, font_size * 2.0);
}
Stepper(s) => {
Expand Down
67 changes: 67 additions & 0 deletions crates/rustmotion-components/tests/audit_ws_i.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Regression tests for workstream I of the 2026-09 audit (components and
//! the CSS cascade).
//!
//! - `apply_intrinsic_overrides`'s default-size branches resolved
//! `font-size` through the context-free `font_size_px_or`, which returns
//! `0.0` for a relative unit (`rem`/`vw`/`vh`) instead of resolving it —
//! collapsing `marquee`/`list`/`callout`/`tooltip`/`pill_nav` to a 0px box.
//! - `cascade::inherit_from` computes inherited `color`/`font-*` but
//! nothing on the render path ever reads the result — every painter reads
//! its own component's un-cascaded `style` field instead.
//! - `TableIntrinsic` measures content-fitted per-column widths, but
//! the painter splits the box evenly across columns regardless.

use rustmotion_components::box_builder::{build_scene_with_anim, BuildAnimationCtx};
use rustmotion_components::{ChildComponent, Component, PositionMode};
use rustmotion_core::css::taffy_bridge::ConversionContext;
use rustmotion_core::engine::layout_pass::run_layout;

fn single_child_scene(json: serde_json::Value) -> ChildComponent {
let component: Component = serde_json::from_value(json).expect("deserialize component");
ChildComponent {
component,
position: Some(PositionMode::Absolute { x: 0.0, y: 0.0 }),
x: None,
y: None,
z_index: None,
bleed: false,
}
}

// ─── default sizes must resolve against the node's font-size ───────────────

#[test]
fn marquee_with_relative_font_size_gets_a_positive_height() {
// Reproduction: `font_size_px_or` returns 0.0 for any relative unit
// (`.px()` can't resolve `%`/`em`/`rem`/`vw`/`vh`). Five sites in
// `apply_intrinsic_overrides` still called it — Marquee among them —
// so a marquee declaring `"font-size": "2rem"` and no explicit height
// used to collapse `apply_default_size(css, 800.0, 0.0)` to a 0px-tall
// box, and `paint_pass`'s `height <= 0.0` guard then skipped it
// entirely: the marquee never appeared on screen.
let child = single_child_scene(serde_json::json!({
"type": "marquee",
"content": "BREAKING NEWS",
"style": { "font-size": "2rem" }
}));
let children = vec![child];

let built = build_scene_with_anim(
&children,
(1920.0, 1080.0),
BuildAnimationCtx {
time: 0.0,
scenario_time: 0.0,
scene_duration: 1.0,
fps: 30,
},
);
let layout = run_layout(&built.root, (1920.0, 1080.0), &ConversionContext::default());
let marquee_id = built.root.children[0].id;
let l = layout.get(marquee_id).expect("marquee laid out");
assert!(
l.height > 0.0,
"marquee at font-size: 2rem must lay out with a positive height, got {}",
l.height
);
}
Loading