Skip to content

fix(components): apply the css cascade before painters read style - #264

Closed
LeadcodeDev wants to merge 1 commit into
fix/default-size-font-contextfrom
fix/cascade-reaches-painters
Closed

LeadcodeDev wants to merge 1 commit into
fix/default-size-font-contextfrom
fix/cascade-reaches-painters

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

Severity Medium, category correctness. Location: crates/rustmotion-components/src/legacy_dispatch.rs:68

Impact

box_builder.rs:496 calls rustmotion_core::css::cascade::inherit_from(parent_css, &mut css) and paint_pass.rs:479 passes &node.css into dispatch. LegacyPaintDispatcher (the only real PaintDispatcher; the other is NoopDispatcher) binds it to _css and drops it, and Painter::paint_content (painter.rs:60-66) has no CssStyle parameter at all — so a painter structurally cannot see the cascaded style. Every painter resolves typography from its own declared struct field instead (text.rs:523 self.style.color_str_or("#FFFFFF")). All twelve properties inherit_from propagates (color, font-family, font-size, font-weight, font-style, line-height, letter-spacing, text-align, white-space, overflow-wrap, visibility, text-decoration) are paint-only: taffy_bridge.rs reads none of them (its own header says "Paint properties (color, background, transform...)" are excluded), paint_pass.rs contains zero references to visibility/text_decoration/text_align/font_family/white_space, and component_intrinsic (box_builder.rs:639) builds measurers from &child.component, not from the cascaded css. Net effect: {"type":"card", "style":{"color":"#ff0000"}} wrapping a text with no colour of its own renders white, not red — the documented CLAUDE.md feature "cascade.rs — Héritage color/font-* parent → enfant" has no observable effect. The regression test card_color_cascades_to_text_child_with_no_color_of_its_own (box_builder.rs:3066) passes because it only asserts on the intermediate text_box.css.color field, never on painted output.

Fix

Either thread the cascaded style to the painters — pass _css through PaintCtx (or add a css: &CssStyle parameter to paint_content) and have text-like painters prefer it over self.style — or apply inherit_from to the component's own style field before the painter reads it. Then add a pixel-level test asserting a card's color actually tints a child text, since the existing box-tree assertion cannot catch this class of regression.

Evidence the audit read

fn dispatch(
        &self,
        canvas: &Canvas,
        payload: &(dyn std::any::Any + Send + Sync),
        _css: &rustmotion_core::css::CssStyle,
        layout: &BoxLayout,
        frame: &PaintFrame,
    ) {

Stacked on fix/default-size-font-context, which carries the previous finding of this workstream. GitHub shows only this finding's diff; merge in order.

Part of the September 2026 audit remediation chantier. Refs #220 (RM-07).

@LeadcodeDev LeadcodeDev added the bug Something isn't working label Sep 21, 2026
@LeadcodeDev LeadcodeDev self-assigned this Sep 21, 2026
@LeadcodeDev
LeadcodeDev force-pushed the fix/default-size-font-context branch from 4b122f6 to f08bbe7 Compare September 22, 2026 06:11
@LeadcodeDev
LeadcodeDev force-pushed the fix/cascade-reaches-painters branch from 6dc5764 to dc957fe Compare September 22, 2026 06:11
@LeadcodeDev
LeadcodeDev force-pushed the fix/default-size-font-context branch from f08bbe7 to 2351a9f Compare September 22, 2026 08:36
@LeadcodeDev
LeadcodeDev force-pushed the fix/cascade-reaches-painters branch from dc957fe to 42c4881 Compare September 22, 2026 08:36
@LeadcodeDev
LeadcodeDev force-pushed the fix/default-size-font-context branch from 2351a9f to 067ad3a Compare September 22, 2026 08:46
box_builder.rs:496 calls
rustmotion_core::css::cascade::inherit_from(parent_css, &mut css) and
paint_pass.rs:479 passes &node.css into dispatch. LegacyPaintDispatcher (the
only real PaintDispatcher; the other is NoopDispatcher) binds it to _css and
drops it, and Painter::paint_content (painter.rs:60-66) has no CssStyle
parameter at all — so a painter structurally cannot see the cascaded style.
Every painter resolves typography from its own declared struct field instead
(text.rs:523 self.style.color_str_or("#FFFFFF")). All twelve properties
inherit_from propagates (color, font-family, font-size, font-weight, font-
style, line-height, letter-spacing, text-align, white-space, overflow-wrap,
visibility, text-decoration) are paint-only: taffy_bridge.rs reads none of
them (its own header says "Paint properties (color, background,
transform...)" are excluded), paint_pass.rs contains zero references to
visibility/text_decoration/text_align/font_family/white_space, and
component_intrinsic (box_builder.rs:639) builds measurers from
&child.component, not from the cascaded css. Net effect: {"type":"card",
"style":{"color":"#ff0000"}} wrapping a text with no colour of its own
renders white, not red — the documented CLAUDE.md feature "cascade.rs —
Héritage color/font-* parent → enfant" has no observable effect. The
regression test card_color_cascades_to_text_child_with_no_color_of_its_own
(box_builder.rs:3066) passes because it only asserts on the intermediate
text_box.css.color field, never on painted output.

Fix: Either thread the cascaded style to the painters — pass _css through
PaintCtx (or add a css: &CssStyle parameter to paint_content) and have text-
like painters prefer it over self.style — or apply inherit_from to the
component's own style field before the painter reads it. Then add a pixel-
level test asserting a card's color actually tints a child text, since the
existing box-tree assertion cannot catch this class of regression.

Refs #220
@LeadcodeDev
LeadcodeDev force-pushed the fix/cascade-reaches-painters branch from 42c4881 to 5fba6d1 Compare September 22, 2026 08:46
@LeadcodeDev
LeadcodeDev deleted the branch fix/default-size-font-context September 22, 2026 08:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant