Post-audit reliability hardening — wave 2 (medium + deferred) - #112
Merged
Conversation
Wave 1 deleted LayerStyle itself but could not reach the types it dragged
along, because they sat behind a re-export chain outside that wave's file
partition.
schema::GridTrack was the worst of them: a three-variant, externally-tagged
shadow of the live css::style::GridTrack that actually drives grid layout. Two
incompatible types sharing one name is what led the documentation to teach
{"fr": 1}, a form the engine rejects. Its re-export chain through
traits::container turned out to have no consumers at all.
Spacing and GridPlacement, and five more orphans that were exclusively fields
of the deleted LayerStyle — CardBorder, CardShadow, FilterConfig, DropShadow
and TextGradient — go with it.
Pure removal: 111 deletions, one re-export line changed, no logic touched. The
exported schema keeps its 178 definitions, since none of these were ever
reachable from Component or Scenario.
Refs #111
…ema promises Three properties the schema accepted and the engine ignored. white-space was read in exactly two places repo-wide — cascade inheritance and the geometry validator — while the intrinsic hardcoded wrap: true and the painter wrapped unconditionally. So the validator's unwrappable_text_overflow fired on a state the renderer could not produce. Derive wrap from the property in both measurement and painting, for text, gradient_text and caption. gradient_text had no line-breaking path at all and gained one, with the gradient computed across the whole multi-line box so wrapping does not fragment the colour transition. Nowrap text also gets min-width: 0 so its box stays clamped while the line bleeds past it, which is what CSS does — without it, automatic minimum size lets the box grow and silently resize siblings. rich_text had no entry in component_intrinsic, so it measured 0x0 and rendered nothing unless the author guessed a width — for the component whose whole purpose is accent-coloured words inside a sentence. It now measures itself, through the same word-level line-breaker the painter uses, so measure and paint cannot disagree. Line breaking was previously only possible at span boundaries; a long single span now wraps internally. glow was stored by extract_effects and never read by resolve_props_for_effects: renders with and without it were byte-identical. It now builds a correctly coloured drop shadow. Note the separate keyframe path (glow_radius / glow_intensity as raw animated properties) still produces a black halo because GlowConfig.color is not plumbed there; that lives in css/animation.rs and is left for a follow-up. Refs #109
Four ways the tool could mislead an author, closed together. A misplaced attribute — color or font_size at component root instead of inside style — was a warning, and render exited 0 having silently used defaults. Both now fail. The checker also only ever inspected the top level of each component, so a misspelled scene key like "durration" passed silently; Scenario, Scene, View, VideoConfig, SceneLayout, Transition and Camera now reject unknown fields too. SceneEntry was serde(untagged), so the complete message for a missing duration or a misspelled transition type was "data did not match any variant of untagged enum SceneEntry" — no line, no scene index, no field name. Errors now name themselves. Text below roughly 1.2% of the output height is unreadable in a video even though it fits the frame perfectly, so validation said nothing. Threshold chosen by rendering a line at sizes from 8 to 28px and downscaling 50%, the way video is actually watched: everything below 14px degraded into a grey smear. Expressed as a fraction of height so it holds on 4K and vertical canvases, and it clears every shipped component default. Advisory, not blocking. Unresolvable colours now fail validation. Wave 1 made them loud at render time by painting magenta instead of black; catching them before anyone renders is the other half. --strict-attrs is left accepted so scripts keep working, but it now announces that it does nothing. A flag with no observable effect is the same defect as a schema property with no observable effect, which is what this whole change is about. Refs #110
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wave 2 of the post-audit chantier — the medium findings, plus three items wave 1
deliberately deferred. Parent: #102 · Sub-issues: #109 #110 #111
One commit per workstream.
Theme
Wave 1 fixed the things that produced wrong output. Wave 2 fixes the things that
produced no output and no complaint — properties the schema accepts and the engine
silently ignores. Every item here is the same defect wearing a different hat.
white-spaceunwrappable_text_overflowfired on a state the renderer could not producetext,gradient_textandcaptionrich_textglowcolor/font_sizerenderexited 0 with default styling{"durration": 3.0}on a scenedurationdata did not match any variant of untagged enum SceneEntryscenes[1]: missing field \duration``color: "ligth-blue"schema::GridTrackand 7 more dead typesVerification
cargo test --workspace→ 580 passed, 0 failed.cargo clippy --workspace --all-targetsclean. All 7
examples/*.jsonstill validate under the stricter rules — none neededweakening to pass.
Independent acceptance harness (fixtures written from the audit findings, not borrowed
from the implementing agents; pixel-measured where the claim is visual): 13/13 green,
including wave-1 non-regression checks.
Notes for review
(~13px on 1080p), chosen by rendering a line at 8–28px and downscaling 50% — the way
video is actually watched. Everything below 14px became a grey smear. Expressed as a
fraction so it holds on 4K and vertical canvases, and it clears every shipped
component default so it does not fire on already-clean scenarios.
--strict-attrsis now inert (unknown attributes error by default, so it hasnothing left to promote). Rather than leave a flag that silently does nothing — the
same defect this PR is about — it is kept for script compatibility and prints a
deprecation notice.
nowraptext getsmin-width: 0. Without it, CSS automatic minimum size lets anowrap text grow its own box past its flex container and silently resize siblings.
With it, the box stays clamped and the line bleeds past it, which is what a browser
does.
Known follow-ups, deliberately not in this PR
glow_radius/glow_intensityas raw keyframe properties still produces ablack halo:
GlowConfig.coloris not plumbed into that path, which lives incss/animation.rs.cmd_validate's blocking check never accounts forunresolved_vars, so a scenariowith only unresolved
$variablerefs reports valid whilerenderrefuses it.Badge's size→font-size table is duplicated ingeometry.rsbecauseBadgeSize::params()is private to the components crate. It will drift silently.CssStyle'sColor-typed fields only. Component-specificplain-
Stringcolour fields (Table.header_color,Marquee.color, chart series, …)still go through the lenient path.