Skip to content

Post-audit reliability hardening — wave 2 (medium + deferred) - #112

Merged
LeadcodeDev merged 3 commits into
mainfrom
chantier/audit-fiabilisation-v2
Aug 1, 2026
Merged

Post-audit reliability hardening — wave 2 (medium + deferred)#112
LeadcodeDev merged 3 commits into
mainfrom
chantier/audit-fiabilisation-v2

Conversation

@LeadcodeDev

Copy link
Copy Markdown
Owner

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.

Before After
white-space read in 2 places repo-wide, neither of them the painter — so unwrappable_text_overflow fired on a state the renderer could not produce honoured in measurement and painting, on text, gradient_text and caption
rich_text no intrinsic → measured 0×0 → rendered nothing without an explicit width measures itself; a long single span now wraps internally instead of only at span boundaries
glow stored, never read; renders with and without were byte-identical renders, in the configured colour
Misplaced color / font_size warning; render exited 0 with default styling both fail
{"durration": 3.0} on a scene silently ignored rejected, names the field
Missing duration data did not match any variant of untagged enum SceneEntry scenes[1]: missing field \duration``
11px text on a 1080p frame validates clean advisory warning
color: "ligth-blue" rendered magenta at render time (wave 1) fails validation
schema::GridTrack and 7 more dead types exported, shadowing the live CSS types removed

Verification

cargo test --workspace → 580 passed, 0 failed. cargo clippy --workspace --all-targets
clean. All 7 examples/*.json still validate under the stricter rules — none needed
weakening 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

  • The legibility threshold is evidence-based, not a guess. 1.2% of output height
    (~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-attrs is now inert (unknown attributes error by default, so it has
    nothing 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.
  • nowrap text gets min-width: 0. Without it, CSS automatic minimum size lets a
    nowrap 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

  • Animating glow_radius / glow_intensity as raw keyframe properties still produces a
    black halo: GlowConfig.color is not plumbed into that path, which lives in
    css/animation.rs.
  • cmd_validate's blocking check never accounts for unresolved_vars, so a scenario
    with only unresolved $variable refs reports valid while render refuses it.
  • Badge's size→font-size table is duplicated in geometry.rs because
    BadgeSize::params() is private to the components crate. It will drift silently.
  • Colour validation covers CssStyle's Color-typed fields only. Component-specific
    plain-String colour fields (Table.header_color, Marquee.color, chart series, …)
    still go through the lenient path.

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
@LeadcodeDev
LeadcodeDev merged commit 2ec0659 into main Aug 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant