Skip to content

fix(cli): check judges SVG gradient text by its gradient stops, not as black - #7258

Merged
miguel-heygen merged 1 commit into
mainfrom
fix/contrast-gradient-text
Nov 5, 2026
Merged

miguel-heygen merged 1 commit into
mainfrom
fix/contrast-gradient-text

Conversation

@miguel-heygen

Copy link
Copy Markdown
Collaborator

What changed

hyperframes check now judges SVG text filled with a gradient (fill="url(#id)") by the gradient's own stop colours, and stops reading it as black. Near-white gradient titles on a dark backdrop no longer fail contrast_aa_failure at about 1:1.

The rule: a gradient fill is judged by its weakest stop, the one with the lowest contrast against the measured backdrop, since some glyph pixels really are that colour. Stops come from the gradient itself or, when it has none, from the gradient its href names. Stop opacity counts, and fully transparent stops are left out. A fill that points at a pattern or at nothing has no colour to judge, so that text is skipped. HTML gradient text (background-clip: text with transparent color) is already skipped in the same way.

Why

The audit read SVG text's colour from fill only when fill was a solid colour; for url(#...) it fell back to CSS color. That is not the paint SVG text uses, and it is usually the initial black. Every gradient title was judged as black text. Reported from the field on animated multicolour gradient text that was visibly white.

What I measured

  • Reproduced on the released CLI first: a fixture with near-white gradient SVG text (stops #fff, #ffe3f1, #e3f6ff, two animated with GSAP) on #0b0b14 fails hyperframes@0.9.16 check with contrast_aa_failure, severity error, fg rgb(0,0,0), 1.07:1.
  • fails without the fix: contrast-svg-gradient.browser.test.ts runs the audit in Chromium on four SVG texts and expects the light gradient and the href-borrowed gradient to pass, judged on their weakest stop (#e3f6ff), a gradient with one dark stop (#202030) to fail on that stop, and the pattern fill to be skipped. With main's audit every one of the four is read as rgb(0,0,0). With the fix the contrast tests (this file, the clip, solid, occlusion and fg tests) pass 45/45 three runs in a row on Linux Chromium.
  • contrast-fg.test.ts: the TypeScript mirror of the foreground rule returns null for a url(...) fill instead of color.

What I did NOT exercise

  • Gradients whose visible part of the text covers only some stops (userSpaceOnUse spans wider than the text, gradientTransform, spreadMethod): every stop is judged, so such text can be held to a stop it never shows.
  • A url(#missing) <colour> fallback paint: skipped rather than judged by the fallback colour.
  • The whole hyperframes check command built from this branch: the fixture ran on released 0.9.16 only; the fix is proven through the audit script in Chromium.

Size

About 40 lines in the browser audit, the mirror's rule and test, and one browser test file.

@heygengenesis heygengenesis Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving at 8cfe7350. I found no blockers.

  • Fix: url(#id) fills on SVG text no longer fall back to CSS color, which was the black at 1.07:1. They resolve through the gradient's stops, following href/xlink:href for at most 8 hops. Stops are scaled by stop-opacity, and transparent stops are dropped. The ratio comes from the stop with the lowest contrast, and the reported fg is that stop. Pattern fills and missing references are skipped, which matches the existing background-clip:text skip. The stroke override still compares against the weakest-stop ratio, as it should.
  • fails without the fix: contrast-svg-gradient.browser.test.ts checks the four selectors with an exact toEqual. On main's audit, all four come back rgb(0,0,0) and #pattern-text is still emitted, so the test can't pass there. On this head it ran green in CI: the flake gate passed it 10/10 and the chrome vitest project passed it. CI, Comments, preview-regression, regression and CodeQL are green. Windows render was still running when I posted this.
  • The contrast-fg.ts mirror now returns Rgba | null. Its only caller is its own test, so nothing downstream breaks.

Non-blocking, and already listed under "did NOT exercise": judging every stop can be too strict for userSpaceOnUse or gradientTransform spans the glyphs never reach. A url(#missing) <colour> fallback is skipped rather than judged by its colour. Both are fine to leave as follow-ups.

@miguel-heygen
miguel-heygen added this pull request to the merge queue Nov 5, 2026
Merged via the queue into main with commit 478ef2f Nov 5, 2026
69 checks passed
@miguel-heygen
miguel-heygen deleted the fix/contrast-gradient-text branch November 5, 2026 19:21
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 6, 2026
…view

Adopts heygen-com#7240 (deferred frame extraction leaves the distributed code),
heygen-com#7241 (a capture segment extracts and deletes its own frames), heygen-com#7255
(segmented and sequential capture check drawElement samples), heygen-com#7254
(normalize-audio --reference stops short of the peak ceiling), heygen-com#7256
(engine names a failed script's origin first), heygen-com#7258 (SVG gradient text
judged by its stops) and heygen-com#7242 (windowed source frames, off unless
HF_VIDEO_FRAME_WINDOW=true). Release v0.9.17 is not synced wholesale;
the producer packed budget takes upstream's +12000 from this range.
Watermark advances to 32eb4c3.

Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SanHsien added a commit to SanHsien/hyperframes that referenced this pull request Nov 6, 2026
…view to heygen-com#7258

Adopts heygen-com#7306 (motion-blur composition counts in render_complete telemetry,
adapted to discoverMovers; readEvents stays false without heygen-com#7191) and heygen-com#7318
(a set that interrupts a tween holds at any worker count) together with its
merged prerequisite heygen-com#6851. PR axis advances to heygen-com#7258; heygen-com#7253 and heygen-com#7257 are open
and pinned as monitored (97->99).

Co-authored-by: Vance Ingalls <vance@heygen.com>
Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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