Repository navigation
fix(engine): copy border-width/style/color onto the replacement video frame - #3993
Conversation
… frame injectVideoFramesBatch substitutes each <video> with a sibling <img> holding an extracted still frame, copying an allow-listed set of CSS properties from the video's computed style onto the img. border-width/border-style/ border-color were absent from that allow-list, so any border authored directly on a <video> never reached the replacement image and disappeared from render/snapshot output. border-radius and clip-path were already on the list and already clip a replaced element's content correctly without needing overflow:hidden, confirmed by a real-Chromium test that exercises the actual capture path and reads real screenshot pixels. Also corrected a stale comment in frameCapture.ts claiming detectCssEffectRisk can return "clip-path" as a risk value -- it can't; that string only ever comes from a separate, animation-only at-risk-props gate further down the same file. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
style-9-prod's video has an authored 8px border + 16px border-radius (design_review.md calls out the "floating" look) that never rendered before this fix, so its committed golden baked in the pre-fix (borderless) output. Regenerated via `tsx src/regression-harness.ts style-9-prod --update`; verified the diff is exactly the border/radius becoming visible (extracted frames before/after) and the fixture passes its own visual/audio checks again (100/100 checkpoints, correlation 1.000). Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
…rame injectVideoFramesBatch measured the video's used box (offsetLeft/Top/ Width/Height) after creating, inserting, and styling the replacement <img> sibling -- once that copy loop started carrying border-width (added for the border-visibility fix), the freshly-inserted, still in-flow, now-bordered <img> competed for space in a flex row before the video's own box was ever read. In a flex-centered layout (video width:100%, sharing a row with the img sibling), that shrank the measured video box by the exact width of the border, so the img ended up sized from the video's post-shrink box instead of its authored one. Moved the measurement to before the <img> is created/inserted/styled at all, and set img.style.boxSizing = "border-box" explicitly after the style-copy loop, since offsetWidth/offsetHeight (and the getBoundingClientRect fallback) are always a border-box measurement regardless of what box-sizing value that loop copies from the video's own computed style. Added a flex-centered regression test asserting the replacement img's box stays identical to the video's box; confirmed it fails with the old measurement order (a 16px border shrinks a 500px box to 484px) and passes with the fix. Regenerated the one fixture (style-9-prod) whose committed golden had baked in the shrunk-frame geometry. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
Follow-up to c888903: group the four measurement locals into a single videoBox object instead of four loose consts, and trim the surrounding comments down to their load-bearing content. No behavior change -- same measurement order, same fallback conditions, same applied style values; re-verified red (reverting the measurement order still reproduces the 500px -> 484px shrink) and green. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
terencecho
left a comment
There was a problem hiding this comment.
APPROVE at 213ecf08 — border-carry via shared allow-list; box-parity captured pre-substitute + box-sizing:border-box pin on the img
Head verified: 213ecf08091ba2545d245068a4b5f078d1e14d6a. Commit co-authors: miga-heygen (bot carrier) + miguel-heygen (trust-listed).
Attribute carry is via a shared allow-list, not ad-hoc local copy
MEDIA_VISUAL_STYLE_PROPERTIES in parityContract.ts now lists border-width/border-style/border-color alongside the already-present border-radius/clip-path/overflow/filter/mix-blend-mode/backdrop-filter. Narrow-but-principled: the three added properties are exactly what makes border: shorthand round-trip. Other visual companions (background-color, box-shadow, outline) aren't required by the reported symptom and are natural follow-ups if a future case surfaces them — using the same allow-list means any such addition is one line, not a rework.
Box-parity fix is correct at the invariant level
videoBox is captured from offsetWidth/offsetHeight (always a border-box measurement) before the <img> sibling is created, so the freshly-bordered in-flow img can no longer shrink the video's flex box. The explicit img.style.boxSizing = "border-box" after the copy loop is the right pin — it defends against the copy loop inheriting content-box from the video, which would otherwise push the img's content area past videoBox by border-width.
Test pins both axes at real pixels
videoFrameBorderClip.test.ts launches real headless Chromium, screenshots a border:8px solid red; border-radius:24px; clip-path:inset(0 round 24px) video, and reads border/center/corner pixel channels via an in-page canvas — confirms border paints, content fills, and the corner is transparent. The third case (flex-centered, width:100%, box-sizing:border-box reset) asserts imgBox equals videoBox via getBoundingClientRect() rounded, and the commit narrative documents the red/green (500px → 484px shrink reproduces without the fix, disappears with it).
No unintended surface widening
screenshotService.ts change is scoped to the injectVideoFramesBatch substitution path; frameCapture.ts diff is pure comment correction (documents that detectCssEffectRisk structurally can't return "clip-path"); test-classification.mjs adds the new file to the integration lane so it gets a real browser.
Golden regeneration is defensible
style-9-prod MP4 shows the previously-invisible 16px border ring re-appearing; compiler-output drift in compiled.html is unrelated timing-attributes churn (called out in commit body); harness reports 100/100 checkpoints, correlation 1.000.
CI
All non-skipped checks SUCCESS across CI, CodeQL, all 9 regression shards ×2 runs, Windows render, Player perf, Preview parity, Typecheck, Lint, Build, Test. Skipped jobs are path-scoped (Skills, Codex plugin, CLI npx shim, GCP BeginFrame — unrelated). mergeStateStatus: BLOCKED on REVIEW_REQUIRED only.
— Review by tai (pr-review)
…-paint # Conflicts: # packages/producer/scripts/test-classification.mjs
Edit accuracy: accurate 2061 (base branch 2061), smooth 1615 of thoseThe gate passes. Quarantined, measured but not gated (0) |
terencecho
left a comment
There was a problem hiding this comment.
Re-review at 9a5e423f01b0ba2d54a191acd00cd041f4c67d46; the earlier tai approval at 213ecf08 does not cover this revision.
Strengths: The shared border-style allow-list (packages/core/src/inline-scripts/parityContract.ts:17–19) reaches both replacement images and grading canvases. Measuring before sibling insertion and explicitly pinning the replacement to border-box (packages/engine/src/services/screenshotService.ts:738–745, 791) preserves the original outer box, including content-box and flex layouts.
[P2] Preserve the visible border when a previously drawn grading source loses its content area — packages/core/src/runtime/colorGrading.ts:2634–2644.
After the production grading runtime has successfully drawn a decoded image with ordinary exposure:0.5 grading and width:160px;height:90px;box-sizing:border-box;border:16px solid red, change its authored width/height to 24px. Production resize/style observers redraw it automatically; no manual redraw is required. Native Chromium still paints a 32×32 red border-only box (1,024 pixels), but current head paints zero nonwhite pixels. Shrinking only the width (24×90 authored, 32×90 used) likewise loses all 2,880 native border pixels.
The new border subtraction produces zero content dimensions and reaches canvas.style.display = "none"; return null. drawEntry returns without restoring the already-hidden source (opacity:0!important), so both representations disappear. I also independently replayed the explicit-redraw zero-size variant against the actual head and immutable-base runtime bundles. Removing only the canvas-hide statement restores all 1,024 border pixels.
Keep the CSS border-only surface while skipping the zero-sized GL draw, or restore the native source on this rejected-layout path. Add an observer-driven regression starting with a successful draw and shrinking below the combined border widths. Expansion automatically recovers, and an initially tiny image keeps its source visible: this is a history-dependent visibility transition, not permanent corruption. Base already rendered borders incorrectly but retained visible media pixels; the introduced behavior is the complete disappearance, not a regression from formerly correct red borders. The confirmed path is native-source grading, not every injected-video export path.
Validation: Focused core 70/70, engine 54/54, and real-Chromium injector 3/3 tests pass, along with scoped builds/typechecks/lint and classifier/reachability guards. Independent browser checks pass 21 current outer-box assertions, 14 grading-buffer assertions, and 18 native-border pixel comparisons; the consumed border-only mutant fails box/buffer invariants. These affirmative cases do not cover the transition above. Full render bank, native HDR compositor, and BeginFrame runs were not rerun. Initial collapse reproduction used direct CSS style writes and the real runtime redraw API, not a GSAP animation.
Verdict: REQUEST CHANGES
Reasoning: The main border-copy and box-parity fix is sound, but the new content-size calculation makes a legitimate border-only layout hide both the grading canvas and its native source.
— tai
terencecho
left a comment
There was a problem hiding this comment.
Re-review at b5d7230cff8344804b3e1ac9b4b135b6d9c0ef82: the post-draw disappearance from my previous review is fixed. Automatic production observers now preserve the 1,024/2,880 native border pixels after two-axis/one-axis shrink; true borderless zero outer boxes remain hidden and expansion resumes drawing.
[P2] Keep exactly one visible border surface before the first successful draw — packages/core/src/runtime/colorGrading.ts:2643–2653.
Start a decoded image with ordinary exposure:0.5 grading, authored width:24px;height:24px;box-sizing:border-box;border:16px solid rgba(255,0,0,0.5), on a white background. Its native used box is 32×32. At this head the native source stays at opacity 1, while the grading canvas is also display:block. The border is painted twice: all 1,024 pixels become [255,63,63,255] instead of the native [255,127,127,255].
The new empty-content return at line 2653 retains the canvas made visible at line 2643. drawEntry returns at lines 3116–3117 before hideSourceElement at line 3168, so initialization has two visible representations. The immutable prior head 9a5e423f and a narrowly consumed old-hide mutant render this initial case correctly (native source visible, canvas hidden). Expansion hides the native source and restores parity; shrinking again after a successful draw also stays correct. This is a new initialization regression, not the already-fixed post-draw disappearance or a claim that the original main baseline had correct borders.
This also persists through eight painted animation frames and runtime destroy/reinitialization at the collapsed size in an independent Chromium witness: native alpha 128 becomes 192 across all 1,024 pixels. The actual injector+grader composition also reproduces the initial overlap with both fresh and precreated replacement-image siblings.
Choose one visible source/canvas representation on the empty-content path, accounting for whether the runtime already owns source visibility. Preserve the repaired already-hidden-source case. A separately consumed !entry.hasDrawn canvas-hide branch restores initial/reinitialized alpha parity while retaining the prior-drawn one-/two-axis collapse fix. Add an initially border-only translucent-border regression: the new solid-red/display-only test cannot distinguish one border from two.
Validation: actual production-source Chromium/SwiftShader bundles and automatic observers, with 84 primary stages and 115 independently built/captured native/head/prior/base/mutant snapshots; parent verified both pixel differentials, source-bundle/mutation consumption, lifecycle recovery/disposal, and representative screenshots. Focused grading/parity 71, engine 54, and actual injector Chromium 3 tests pass, plus scoped builds/typechecks/lint/classification/reachability. The author's new test consumes the real branch and fails when the old hide is restored, but does not catch this overpaint. Full render bank, native Windows/HDR compositor, and BeginFrame were not independently rerun. The earlier replacement-image border-box pin remains correct; its coverage gap is nonblocking and is not the cause of this verdict.
Verdict: REQUEST CHANGES
Reasoning: The original visibility transition is repaired, but the replacement empty-content path now double-paints initially translucent borders. This decision is about the reproduced rendering regression, not pending CI.
— tai
jrusso1020
left a comment
There was a problem hiding this comment.
Approve at dea8311a. The border now reaches the replacement frame, the frame's box matches the video's, and the graded canvas keeps its border without stretching the picture. The commits since 9a5e423f fix both zero-content-box edges tai raised.
Checked
- Border copy (
parityContract.ts:17-19,screenshotService.ts:755-773): I tested this in real headless Chromium.- Different borders on each side copy through (
6px 0px 0px 2px | dashed none none solid), andcurrentColorresolves to an rgb value. - A video with no border copies
0px none, which changes nothing.
- Different borders on each side copy through (
- Measure before insert (
screenshotService.ts:738-745,:791):offsetWidth/offsetHeightare border-box values, andgetBoundingClientRectis still only the fallback when the width is 0. The forcedbox-sizing: border-boxruns after the copy loop, so it wins.- The video and image rects matched exactly for content-box, padded, inline, a
data-startvideo in a flex row, different borders per side, androtate(20deg) scale(1.3). offset*ignores transforms, so the transform isn't applied twice.- Calling it a second time on the same frame is stable.
- The video and image rects matched exactly for content-box, padded, inline, a
- Where the reorder matters: timed videos already get an absolute image up front (
frameCapture.ts:2519/2721), so the reorder mostly affects untimed videos and the CLI snapshot path. style-9-prod's video is timed, so its new golden comes from the border copy. - Color grading (
colorGrading.ts:2605-2617,:2640-2656):- The canvas is border-box at
offsetWidth. The buffer is (offsetWidth minus the side borders) × DPR, which equals the content box, so nothing stretches. - With no border, the buffer and viewport are unchanged.
- The other consumer of the list is the parity harness. It already copies
box-sizing, so nothing regresses there.
- The canvas is border-box at
- Golden: only
style-9-prod/output/output.mp4changed, and itscompiled.htmlis byte-identical to main. style-9-prod is the only fixture with a border on the<video>itself. Every regression shard passes. - Registration: the new test is in the integration list next to
coreRuntimeBrowser, its hash matchestest-reachability.json, and the reachability check reports zero orphan tests. frameCapture.ts: the change there is comment-only, and the comment is now accurate.
Should-fix: one guard is untested
- Deleting
img.style.boxSizing = "border-box"(screenshotService.ts:791) leaves every test green. The flex test sets* { box-sizing: border-box }, so the copy loop already gives the image border-box, and the content-box tests only check pixels. - With that line gone, a content-box video gives a 232x152 image against a 216x136 video. Assert image rect = video rect in a content-box case, or drop the
*reset from the flex test.
Nits
colorGrading.test.ts:239never checks that the canvas actually gets the border, so removing the three properties from the list still passes all of core. Anexpect(canvas.style.borderTopWidth).toBe("16px")would pin it.- Padding isn't copied to the image or the canvas, so a padded video's picture fills its padding box. That was already true before this PR, and the PR still improves it.
Since b5d7230c: the border-only double paint is fixed (colorGrading.ts:2619-2627, used at :2657)
- The bug: when the content box is 0 or less, the canvas carries only the copied border. Before the first draw (or after a context loss gives the source back), the source was still showing its own border, so a translucent border painted twice.
- The fix:
visibleLayoutBoxesnow hides such a canvas unless grading has hidden the source (!entry.sourceHidden). Once a draw has hidden the source, the canvas keeps the border, so it still shows exactly once. - Content box above 0:
updateCanvasLayoutruns only insidedrawEntry(:3126), right before the GL draw, andhideSourceElementruns in the same task (:3178). The canvas border and the source border never share a painted frame on the success path. - The one gap left: a never-drawn entry whose shader draw throws (the
catchat:3181) keeps a blank, bordered canvas over the visible source. That's a failure path and narrow, so it isn't blocking. - New tests: never-drawn border-only hides the canvas; context loss on a drawn border-only entry gives the source back and hides the canvas; a drawn entry whose content box empties keeps the canvas border (
colorGrading.test.ts:253-285).
Tests:
- At
dea8311aI read the 3-commit delta overb5d7230c(colorGrading.ts+11/−1 plus tests) and didn't re-run locally; this approval waits for the required checks at this head. - At
9a5e423f:videoFrameBorderClip3/3, core (colorGrading, parityContract) 96/96, engine (screenshotService, videoFrameInjector) 54/54,coreRuntimeBrowser53/53. - 3 of 4 mutants are caught: dropping the border properties, measuring after insert, and reverting the grading math. The survivor is the box-sizing line above.
— Rames
terencecho
left a comment
There was a problem hiding this comment.
Re-review at dea8311a4aec13d90f2738b487dba9477df8a50b, against actual merge-base 63a7ec692f4a6bbdbcf39b68561fb197b233df32 (9 files). Additive to Rames's at-head review: the outer-box fixes are sound, but the single-visible-border invariant still fails across two ownership transitions and a real first-upload fallback.
Prior concerns: The native-source post-draw disappearance in my 9a5e423f review is repaired. The initially border-only double paint in my b5d7230c review is also repaired for native sources and initially collapsed injected frames. Real context loss/restoration, never-drawn/decode, destroy/reinitialize, and ordinary one-/two-axis collapse/recovery preserve native border parity when source ownership remains intact. The new context-loss test genuinely distinguishes sourceHidden from historical hasDrawn.
Strengths: Measuring before sibling insertion and pinning the replacement image to border-box (packages/engine/src/services/screenshotService.ts:738–745,791) preserve used geometry. The grading canvas's border-box/content-buffer split (packages/core/src/runtime/colorGrading.ts:2605–2617,2650–2668) correctly preserves ordinary native borders without stretching the picture.
[P2] Require effective source ownership before retaining a border-only canvas
packages/core/src/runtime/colorGrading.ts:2619–2626, reached from :3126–3127.
After a successful decoded-image draw with exposure:0.5, seek a real paused GSAP timeline that sets width:24px;height:24px;opacity:1 on a border-box image with border:16px solid rgba(255,0,0,0.5). GSAP replaces grading's inline opacity hide; the source is now visible, but entry.sourceHidden remains true. The empty-content branch retains the canvas and returns before hideSourceElement can reassert the hide. Both borders paint: all 1,024 pixels of Chromium's used 32×32 box are [255,63,63,255], versus native [255,127,127,255]. Eight subsequent production redraws leave the overpaint unchanged.
This also reproduces with ordinary style writes. The production GSAP adapter seeks the authored timeline (packages/core/src/runtime/adapters/gsap.ts:144–161), and capture renderSeek seeks/syncs before grading redraw (packages/core/src/runtime/init.ts:3941–3947), so these writes are supported runtime inputs. Immutable b5d7230c reproduces it; immutable 9a5e423f does not. Positive-content expansion re-hides the source and restores border parity, so this is specifically the retained empty-content surface, not an unrepaired positive-content opacity path. drawEntry already computes whether grading's opacity:0!important is actually effective at :3094–3095; the new guard does not use that evidence. A consumed production-source mutation checking effective opacity ownership on this branch restores all 1,024 pixels while preserving the native/context lifecycle fixes. Reconcile live ownership before choosing the sole visible representation, and cover simultaneous size/opacity animation.
[P2] Account for the injected drawable, not just the hidden video
packages/core/src/runtime/colorGrading.ts:2626, together with drawable selection at :2498–2501 and source hiding at :2959–2961.
With actual injectVideoFramesBatch and attribute-based grading, start border-only, expand to a successful draw, then collapse again. sourceHidden describes the video, but the drawable is its __render_frame__ image. The injector deliberately keeps that image visible at opacity 1 (packages/engine/src/services/screenshotService.ts:810–815; active sync also does so at :888–890). Retaining the copied-border canvas therefore paints a second border over the visible image.
Fresh and precreated sibling paths both reproduce: two-axis collapse doubles all 1,024 border pixels; one-axis collapse doubles 2,880. The same collapsed case passes at 9a5e423f, fails at b5d7230c and current head, and passes with a consumed injected-drawable border-only gate. The initial case is fixed, but this later ownership transition remains broken. Ordinary-size injected grading also doubles 6,976 border-ring pixels at both 9a5e423f and current head; that is a PR-wide border-copy interaction, not a new regression from the latest guard, and the narrow collapse mutation does not repair it. Ensure only one actual drawable/canvas border paints across the injection/grading composition.
[P2] Hide the copied-border canvas when the first texture upload fails
packages/core/src/inline-scripts/parityContract.ts:17–19, together with packages/core/src/runtime/colorGrading.ts:2642,2653,3144,3182–3185.
Rames identified the first-shader-failure coverage gap; the additional evidence establishes a reachable rendering regression, not just mock hardening. A decoded cross-origin PNG without CORS can paint natively but cannot be uploaded to WebGL. With nonzero 160×90 geometry and a 16px 50%-alpha red border, real texImage2D throws SecurityError before the first successful draw. The canvas already has its copied border and display:block; the catch leaves it visible while the native source remains opacity 1. All 6,976 border-ring pixels double to [255,63,63,255] versus native [255,127,127,255]; the interior picture is unchanged. Eight actual redraws do not repair the fallback.
This input is supported by CLI/Studio preview: the actual createStudioServer response from /api/projects/:id/preview preserves an ordinary absolute PNG src, no crossorigin, active grading, and the authored border, and installs /api/runtime.js. The CLI preview adapter does not localize ordinary remote PNG/JPEG (packages/cli/src/server/studioServer.ts:490–529; packages/studio-server/src/routes/preview.ts:448–486,521–555). Separately, a real two-origin browser fixture running immutable production grading reproduces the actual decode/upload failure; no upload throw or dimensions are mocked. This is not a claim that every export is affected: successful plain-image localization in compileForRender (packages/producer/src/services/htmlCompiler.ts:2079–2084) avoids this particular tainted-input witness.
The same failure at full immutable merge-base 63a7ec69 and sampled main 6ae1af74 preserves native fallback exactly (zero differing pixels). Head, b5d7230c, and 9a5e423f all double the border. A narrowly consumed head mutation removing only the three new border-copy entries restores exact fallback under the identical upload error. Same-origin and properly CORS-enabled controls grade successfully with zero border differences. This is PR-wide, not introduced only by the latest guard. On first-draw failure, keep the surviving source as the sole border surface; add a real failure-path parity regression.
Validation: Restored focused core 73/73, engine 59/59, and actual producer Chromium 3/3 pass; scoped builds/typechecks and classifier/reachability checks pass (27 reachability tests, zero orphan tests). Production-source mutations fail genuine geometry/visibility assertions, including context restoration and measurement-after-insertion. Removing the injector's explicit border-box pin survives the current producer suite: a nonblocking coverage gap, not a current-code defect. Independent immutable transitive-source browser bundles, real GSAP/injector/CORS calls, labeled pixel captures, and narrow repair mutations establish the failures; I verified source/bundle hashes, all 1,263 final artifact pins, and independently recomputed pixel counts, including positive-content controls. The actual served-preview probe and the real browser pixel witness are separate checks, not a claimed full-app end-to-end run. Production source matches f1e6588be974d2be1098e99a03b3efc79db9cb6c; the later delta is grading tests only. All 11 required checks were observed successful at current head. Initial missing parser build and background-video readiness setup failures were corrected with real scoped builds and a foreground WebM replay; the golden MP4 was inspected as its LFS pointer only, and no full render-bank, native Windows/HDR, or BeginFrame rerun is claimed. Controlled base/main injector comparisons are not claimed as complete baseline exports.
Verdict: REQUEST CHANGES
Reasoning: Ordinary native initialization and context-loss fixes are real, but historical video/source hide state is insufficient after authored opacity writes or injected-frame draws, and first-upload failure now overpaints the surviving native border. These are reproduced rendering defects, not a CI-based verdict.
— tai
jrusso1020
left a comment
There was a problem hiding this comment.
Request changes at dea8311a. This replaces my approval at this head (review 5475366067). tai's three findings reproduce in real Chromium, and the injected-frame one is wider than stated. A graded video in the normal render path paints a see-through border twice on every frame, so two claims in my approval were wrong: "the border shows exactly once" and "the canvas border and the source border never share a painted frame".
How I checked: chrome-headless-shell 152 with software WebGL, using the head and merge-base (63a7ec69) builds of colorGrading.ts, parityContract.ts and the real injectVideoFramesBatch. I also used real GSAP 3.15 and a second origin serving an image without CORS. The element is a 192×122 border-box with border:16px solid rgba(255,0,0,.5) on white, graded with {adjust:{exposure:.5}}. A single border reads [255,127,127] and a doubled one [255,63,63]. I counted pixels around the whole ring.
| Scenario | Head | Main |
|---|---|---|
| Graded img, normal size | single | no border (the bug this PR fixes) |
| Graded img collapsed to 24 px | single | no border |
Graded img collapsed + GSAP opacity:1 |
double, still double after 8 more redraws | no border |
| Injected video frame, graded, normal size | double, 9024/9024 px | no border |
| Injected video frame, graded, collapsed | double | no border |
| Cross-origin img (no CORS), graded | double: texImage2D SecurityError, 9024 px differ from native |
0 px differ |
None of these is a regression against a correct border, since main shows no border at all. They are new defects in the new border copy.
1. Should-fix (main render path): an injected frame and the grading canvas both paint the border
parityContract.ts:17-19adds the border properties to the copied list. The injector copies them onto the__render_frame__img, and grading copies them from that img onto the canvas (copyMediaVisualStylesinupdateCanvasLayout).hideSourceElementhides only the<video>. The injector keeps the img visible at opacity 1 whenever the video carries grading's hidden marker, so two bordered layers are always showing together.- Every graded video in a render whose border colour has alpha below 1 gets a darker border on every frame. An opaque border is pixel-identical. With
border-radius:24px, 160 anti-aliased corner pixels darken by up to 64. videoFrameBorderClip.test.tshas no graded case, which is how this got through.- Fix: when the drawable is a render-frame image (
isRenderFrameImage(source)), set the canvasborderColortotransparentafter the style copy, and keep the width so the picture geometry stays. The img then paints the border once.
2. Nit or low should-fix: a GSAP opacity write while collapsed (colorGrading.ts:2619-2627)
visibleLayoutBoxeschecksentry.sourceHidden, which only remembers that grading once hid the source.- GSAP's
style.opacity = 1replaces grading'sopacity:0 !important, but the flag stays true. The border-only return inupdateCanvasLayoutthen exits beforehideSourceElementcan hide the source again. The normal-size path does hide it again on every draw. - Fix: when that branch returns with
sourceHiddenset, callhideSourceElement(entry), or passdrawEntry'shiddenByColorGrading(:3094-3095) into the check.
3. Nit or low should-fix: the first draw fails (the drawEntry catch, around :3182)
- This is the gap my approval called narrow.
updateCanvasLayoutsets the border anddisplay:blockbefore the draw, and a throw leaves a blank bordered canvas over the visible source. - A cross-origin image in CLI or Studio preview hits it, because no media gets
crossorigin. Renders localize images, so they're unaffected. - An opaque border on an element at
opacity:.5doubles too, because the canvas copies the opacity.
One fix for all three: paint the canvas border colour only while grading's hide is actually in effect and the drawable is not a visible injected frame; otherwise make it transparent. Set it after hideSourceElement runs in drawEntry. Setting it only in updateCanvasLayout, before the first hide, would leave the first successful frame without a border.
Everything else in my earlier review still holds: the measure-before-insert order, box sizing, the grading buffer math, golden scope, and the border-only and context-loss fixes since b5d7230c.
— Rames
jrusso1020
left a comment
There was a problem hiding this comment.
Request changes at fd124bdb, narrowly: one line. The narrowing clears all three double-border cases from my last review: I found no double border anywhere at this head. But "graded media look exactly as on main" doesn't hold wherever the grading canvas isn't fully opaque. There, the injected frame's border shows through on graded videos. I tested a one-line fix that brings every graded case to 0 px different from main.
Real-Chromium probes (chrome-headless-shell 152, software WebGL, each tree's real injectVideoFramesBatch and grading bundle, against merge-base 63a7ec69):
- Setup: a 192×122 border-box with
border:16px solid rgba(255,0,0,.5)on white, gradedexposure:.5. - Diff: pixels that differ from main across the whole 400×300 capture.
| case | head border ring | diff vs main |
|---|---|---|
| native img or video, ungraded | single | 0 |
| injected video, ungraded (fill, contain) | single: the fix this PR is for | ~23k, intended |
| graded img: normal, collapsed + GSAP opacity, redraws, re-expanded | none, as main | 0 |
| injected video, graded: normal, collapsed, opaque border | none, as main | 0 |
| cross-origin img, graded, first draw fails | single (source's own) | 0 |
injected video, graded, object-fit: contain |
2688 px single (top and bottom stripes) | 2688 |
| injected video, graded, frame with alpha (transparent left half) | 4480 px single | 4724 |
injected video, graded, border-radius: 24px |
none | 160 (anti-aliased corners, max Δ54) |
Why: colorGrading.ts:2615 sets borderStyle = "none" on the canvas, but the injected __render_frame__ img still gets the border from the copy loop (screenshotService.ts:756). It stays at opacity 1 while the video carries grading's hidden marker (:732). The canvas above it (colorGrading.ts:3070) covers the img's border only where its own pixels are opaque, so letterbox bands, alpha frames and rounded corners let it through.
Fix (tested): in drawEntry, after the successful hideSourceElement(entry) (around colorGrading.ts:3150), add:
if (injectedFrameSource) source.style.borderStyle = "none";- With that patched into the head bundle, every graded case above is 0 px different from main, including contain, alpha and radius. The ungraded injected border still shows once.
- A failed draw skips the line, so the img keeps its single border.
borderColor = "transparent"instead still leaves 154–318 px of difference, because the border width shifts the img's picture.- Test: please add one real-Chromium test of a graded, bordered, injected video with
object-fit: contain. Nothing pins "graded looks as on main" for the injected img today, which is how this got through.
Otherwise:
- Geometry: matches main, with the canvas at 192×122 and its buffer at 192×122.
- Cleanup:
visibleLayoutBoxesandborderOnlyare gone, with no dead code left. - Mutant: removing
borderStyle = "none"fails the new "draws a bordered source's grading canvas without a border" test. - Tests: core (colorGrading, parityContract) 107/107, engine (screenshotService, videoFrameInjector) 54/54, producer (videoFrameBorderClip, coreRuntimeBrowser) 57/57.
- CI: all required checks are green, including the
style-9-prodregression shard.
I'll approve a head with that line.
— Rames
jrusso1020
left a comment
There was a problem hiding this comment.
Approve at 6d1f4ade. This head adds the one line my fd124bdb review asked for, and nothing else changes.
The fix (colorGrading.ts:3151)
- In
drawEntry, right after the GL draw andhideSourceElement,if (injectedFrameSource) source.style.borderStyle = "none";takes the border off the injected render frame. - From then on, the graded canvas is the only layer painted for that video. That closes the leak from letterbox bands, transparent pixels and rounded corners.
- It runs only on the success path, after a draw. A frame that never gets graded keeps its copied border, so an ungraded video still shows its border once, which is the point of the PR.
injectedFrameSourceisisRenderFrameImage(source)(:3069), so a live<video>source never loses its own border.
Test: colorGrading.test.ts:813 draws over an injected frame bordered 16px solid and asserts the frame ends at borderStyle: none with the canvas showing. Deleting the new line fails exactly that test, and colorGrading.test.ts passes 58/58 at this head.
Earlier findings:
- The double borders from my
dea8311areview were resolved by the narrowing atfd124bdb. - The graded-frame leak from my
fd124bdbreview is resolved by this line. - The PR body's 0 px real-Chromium diffs against main for contain, alpha and radius match what I measured with this same line at
fd124bdb.
Nit: those real-Chromium probes aren't in CI. The unit test pins the style, not the pixels. A graded, bordered, object-fit: contain case in the browser suite would keep this from regressing quietly.
terencecho's CHANGES_REQUESTED is still live at dea8311a, so this approval alone doesn't open the gate. This approval waits for the required checks at this head.
— Rames
terencecho
left a comment
There was a problem hiding this comment.
Re-reviewed 6d1f4adec73339efcfb352d768ad333289a818b0 against merge-base 63a7ec692f4a6bbdbcf39b68561fb197b233df32, with separate dea8311a / fd124bdb controls. The narrowing resolves my three prior overpaint findings; one distinct grading-release lifecycle defect remains.
Strengths / prior findings resolved
- The shared border allow-list plus pre-insertion measurement and explicit image border-box keep the ungraded replacement geometry coherent (
packages/core/src/inline-scripts/parityContract.ts:17–19,packages/engine/src/services/screenshotService.ts:738–797). Fresh producer Chromium tests cover timed/untimed borders and flex layouts under both box-sizing modes. - Keeping the grading canvas borderless and suppressing the injected image's border only after a successful draw preserves the intended graded baseline (
packages/core/src/runtime/colorGrading.ts:2615,3149–3153). Removing just the new image-suppression line reproduces the preceding head's contain/alpha/radius mismatches, so deleting that line is not a complete fix. - My prior review is explicitly resolved for its three sampled failure families: real-GSAP native collapse/one-axis source-ownership overpaint, injected-source overpaint, and genuine first-upload cross-origin
SecurityError. Current head matches base with zero whole-screenshot pixel differences in 28 injected graded stages (fill, contain, alpha, radius, contain+radius, untimed contain, precreated contain × first/steady/collapse/expand), 12 native stages, and six upload/CORS-control stages. The olddea8311acontrols still reproduce 1,024 / 2,880 native and 6,976 upload-failure differing pixels. This also independently verifies the fix for Rames's preceding-head contain/alpha/radius finding.
Blocker — P2: restore the injected frame's border when grading releases it
packages/core/src/runtime/colorGrading.ts:3151 writes source.style.borderStyle = "none" to the persistent injected image. Removing grading through setGrading(video, null), removing the actual data-color-grading attribute, or destroying the grading runtime detaches the canvas and restores grading-owned native-video opacity/markers, but never restores that image's border (colorGrading.ts:3435–3445, 1950–1967, 3684–3696). The video remains hidden by the injector, so the visible, now-ungraded frame retains the suppression.
This survives the production before-capture hook when the decoded source-frame index is unchanged: packages/engine/src/services/videoFrameInjector.ts:223–224 skips reinjection and :240 only synchronizes visibility. Capture then waits for grading redraw (packages/engine/src/services/frameCapture.ts:2839–2849), but the removed entry is no longer tracked. That redraw cannot recopy the border.
Fresh real-Chromium witness, using the actual injector/hook and grading runtime, with exact source-extracted FrameLookupTable and its canonical timing dependencies:
| State on a 192×122 border-box video with a translucent 16px border | Head | Prior grading runtime | Consumed detach repair |
|---|---|---|---|
| Initially ungraded | 9,024 | 9,024 | 9,024 |
| Grading removed; same source-frame index; hook + LUT/redraw completed | 0 | 9,024 | 9,024 |
| Same decoded still explicitly reinjected | 9,024 | 9,024 | 9,024 |
The lower-level held-tail fixture has one extracted frame at 1 FPS for a one-second source in a ten-second clip: 0.25s selects index 0, and 2.25s clamps to that final index 0 while the clip remains active. Both live removal and real MutationObserver-driven attribute removal reproduce the loss; active/inactive status, canvas removal, browser errors, and screenshot pixels were checked. A narrowly consumed detach-time border restoration repairs these cases while retaining suppression during active grading.
A second independent Chromium probe uses the complete pinned core runtime, actual __player.renderSeek/GSAP, engine captureFrameToBuffer, and real parseVideoElements → createFrameLookupTable → injector. Both extracted and capture FPS are 30; authored data-playback-rate="0.1" propagates through the factory (videoFrameExtractor.ts:2807). Source selection floors rate-adjusted source time × extracted FPS (:2568–2589), so captures at 0, 1/30, 2/30, and 0.4s select indices 0, 0, 0, and 1. Grading successfully draws at 0; GSAP writes data-color-grading="{}" at 0.01s. The two subsequent cached captures have no grading canvas and an inactive runtime, yet differ from a never-graded control by 11,279 pixels. The fd124bdb core-runtime and omitted-suppression-line controls match it exactly. At 0.4s frame 1 forces injection and head matches the control again.
These controls keep the head engine/injector fixed while changing the grading/core runtime; they are not complete preceding-head exports. The extracted PNG bank/metadata are synthetic, and the engine seek bridge delegates to actual production renderSeek; no FFmpeg extraction or full session initialization is claimed. The independent held-tail probe additionally exercises real GSAP attribute removal with suppressed events, the actual cached hook, and exact source-extracted lookup code. Live removal, attribute removal, and runtime destroy were all browser-witnessed.
This is a regression introduced by the new grading-runtime suppression relative to fd124bdb under the same head engine, and a hole in this PR's new ungraded-border contract; base did not implement that contract, so I am not labeling it a newly lost base-rendered border. Preserve the successful-draw suppression, restore the transferred border when grading releases the image, and add a regression that removes grading between captures sharing a source-frame index. A test that always reinjects immediately after removal would mask it.
Verification / limits
Fresh restored-head tests passed: core grading/parity 71, engine screenshot/injector 54, producer Chromium 4. The suppression-line mutant fails the new jsdom test (colorGrading.test.ts:839) and reproduces real browser mismatch controls. The browser matrices above cover sampled states, not every renderer/transition combination; later draw failure/context-loss release and arbitrary authored-border changes are not independently covered here. The detach repair is a diagnostic control, not a certified final patch. The binary golden change is not independently re-rendered end-to-end in this review. No fix was committed or pushed, no CI rerun or settings change was made, and no merge was performed.
Verdict: REQUEST CHANGES
Reasoning: The prior overpaint findings are fixed by the narrower policy. Its new persistent-image style mutation lacks a release counterpart, leaving ungraded cached-frame captures borderless until a later injection repairs them.
— tai
jrusso1020
left a comment
There was a problem hiding this comment.
Approve at 51561530.
This fixes the release finding. The one line I suggested earlier (borderStyle = "none" on the injected frame) never undid itself, so removing grading left a reused frame without its border. The frame's inline border-style and its priority are now saved the first time grading hides it. restoreSourceElement puts them back, so every path that shows the source again (release through detachEntry, context loss, a failed context restore) also brings the border back.
Checked
- The new assertion catches the bug: when I removed the
restoreFrameBordercall fromrestoreSourceElement, the test failed. - A local probe passes on top of the PR's test:
- Three redraws in a row keep the original value, because the save happens only once per frame.
- Context loss restores
dashed !importantwith its priority. - After a restore, a new frame element is graded borderless, the old frame gets its border back, and release restores the new frame's own
dotted.
- When the injector copies the computed
border-styleonto the reused frame again, nothing is lost. The frame is the same, so nothing is re-saved, and the value it saved first is the one restored. colorGrading.test.ts: 58/58. All required checks pass at this head.
Not run as a full render on my side; this rests on the unit test, the probe and the PR's real-Chromium cached-frame numbers.
— Rames
jrusso1020
left a comment
There was a problem hiding this comment.
Correction to my approval at 51561530: tai's stale-style case is real, and one bullet in my review was wrong. I wrote that when the injector copies the computed border-style onto the same frame again, the first saved value is the right one to restore. That only holds if the authored border never changes while grading is on. If it goes from solid to dashed during grading, the injector writes dashed onto the reused frame, hideFrameBorder skips saving it (same frame) and overwrites it with none, and release then restores the old solid.
Suggested fix: in hideFrameBorder, re-save whenever the frame's current inline border-style isn't the none that grading wrote. That way, whatever the injector last wrote is what gets restored. A test for this: change the frame's border-style between two redraw() calls, then release and check that the frame gets the new value.
I'll re-review the head that fixes it.
— Rames
terencecho
left a comment
There was a problem hiding this comment.
Re-reviewed 515615309cae22f571869a28e792a74c404b0f9d against PR base/effective merge-base 63a7ec692f4a6bbdbcf39b68561fb197b233df32 (nine changed files). The ordinary cached-frame release defect is fixed; a distinct same-image style-ownership defect remains.
Strengths / prior concerns resolved
- The shared border allow-list, pre-insertion measurement and replacement-image border-box pin still preserve the ungraded outer box (
packages/core/src/inline-scripts/parityContract.ts:17–19,packages/engine/src/services/screenshotService.ts:738–797). Successful grading retains the borderless canvas and suppresses the replacement border only after drawing (packages/core/src/runtime/colorGrading.ts:3173–3174). - The new
restoreFrameBordercall precedes the native-source ownership early return (colorGrading.ts:1964–1974). It resolves my preceding-head release finding: real cached-hook probes now restore the ordinary border after livesetGrading(..., null), actual attribute removal, and runtime destroy without reinjection. A separately emitted no-restore control reproduces the old failure. Absent inline style,!importantpriority, image replacement, context loss/recovery and controlled context-restore failure were also exercised. - Sampled graded contain/alpha/rounded/clip and native-collapse controls retain the narrowed policy's parity; genuine first-upload failures retain the ungraded fallback. The earlier overpaint findings are not being reopened.
[P2] Restore the latest copied border style, not the first style on the image
packages/core/src/runtime/colorGrading.ts:1952–1959,1968 refreshes the saved value only when the image identity changes. The production injector reuses that image (packages/engine/src/services/screenshotService.ts:708–710) while recopying the video's current computed visual styles on later source frames (:756–775). Those are legitimate writes to the same element, not new element identities.
Concrete authored sequence: grade a video whose border starts solid; a real paused GSAP timeline sets its border to dashed at 0.1s. At capture 4/30s the next source frame is injected into the same image, copying dashed, and grading suppresses it again. Remove the actual data-color-grading attribute at 5/30s. Release restores the original saved solid, even though the source's computed border is dashed. The source-frame cache skips reinjection (packages/engine/src/services/videoFrameInjector.ts:223–224), so captures at 8/30s, including a repeated same-time capture, retain the wrong solid border.
This is verified with installed GSAP, the complete core entry.ts/init/renderSeek runtime, the unchanged exported production HF_BRIDGE_SCRIPT, actual captureFrameToBuffer, and the real lookup/injector/cache. The native H.264 video decodes; playback rate 0.25 and the two-frame bank yield indices 0,1,1,1,1 and resolver reads 1,2,2,2,2. The replacement identity remains unchanged, and player/timeline times and active→inactive grading state match the captures.
Under the same current engine/core consumer setup:
| Grader variant | After authored dashed + grading release |
|---|---|
Prior 6d1f4ade |
No border: the original release defect |
| Current head | Old solid border: 7,104 red pixels |
| Narrow saved-style refresh control | Dashed border: 5,280 red pixels |
The white dashed gap in the correction is not confused with the prior grader's missing border: the prior has zero red pixels. All sampled active graded screenshots are whole-image identical across the three variants; static-border current/correction release screenshots are also identical. The current change restores a border, but not necessarily the current authored border. This is a hole in the PR's new release contract, not a claim that main or the prior grader already rendered this sequence correctly.
Please reconcile the saved value with legitimate same-image style recopies while preserving suppression during grading, absent-value restoration and priority. Add a regression that changes border style between source-frame injections, then removes grading on a cached frame. A narrow control that refreshes a non-none overwrite proves causality here, but is not a complete fix: it cannot distinguish an intentional authored none from grading's own suppression without additional ownership information.
Rames's subsequent correction now acknowledges this stale-style case and retracts the reused-frame bullet in their at-head approval. The evidence above adds the actual core/GSAP capture-and-cache reproduction and a consumed correction control. A non-none refresh alone still needs the authored-none ownership distinction noted above.
Verification / limits
Restored focused tests pass: core grading/parity 71, engine screenshot/injector 54, producer Chromium 4, and classification/reachability 32. The additional causal matrix covers 30 real runtime captures; source/artifact hashes, pixel comparisons, cache/identity state and cleanup were independently checked. Prior/current/correction comparisons keep current consumers fixed; they are not complete preceding-head exports. The capture session and extracted PNG bank are constructed fixtures, not a full exporter/session initialization or FFmpeg extraction run. No full render bank, golden MP4 decode/re-render, native HDR/hardware-GPU or BeginFrame claim is made. These are sampled controls, not exhaustive transition coverage. Tracked source is clean; owned browser/server resources were closed. No fix, commit, push, CI rerun, settings change or merge was performed.
Verdict: REQUEST CHANGES
Reasoning: The preceding ordinary release blocker is resolved, but the new save-once ownership record restores stale authored style after a supported same-image recopy, producing the wrong border in cached captures. This is a reproduced rendering defect, not a CI-based verdict.
— tai
jrusso1020
left a comment
There was a problem hiding this comment.
Approve at 12e4c5a0.
The stale-style case from my last comment is fixed. On release, restoreFrameBorder now writes the video's current computed border-style onto the frame, instead of a value saved the first time grading drew. That is the same value, from the same source and with the same (empty) priority, that the injector writes on the next seek (screenshotService.ts: img.style.setProperty(property, computedStyle.getPropertyValue(property)) over MEDIA_VISUAL_STYLE_PROPERTIES). So a release can no longer bring back an older border than the injector would. Dropping the saved priority loses nothing, because the injector never writes !important on the frame either.
Checked
- The updated test changes the video to
16px dashedwhile grading and expectsdashedafter release. Two mutants fail it: putting back the51561530save/restore (expected 'solid' to be 'dashed'), and a restore that only removes the inline value (expected '' to be 'dashed'). - All three release paths still restore before anything else:
detachEntry,webglcontextlost, and a failedwebglcontextrestoredall go throughrestoreSourceElement, which callsrestoreFrameBorderfirst. A frame swap still restores the old frame before hiding the new one (hideFrameBorder,entry.borderlessFrame !== frame). colorGrading.test.ts: 58/58.
Nit (not blocking)
- When the video is released because it was removed from the DOM (
refresh()→!element.isConnected→removeElement), Chrome'sgetComputedStyleon the detached video returns"". The restore then clears the frame's inlineborder-style, and the frame ends up with no border. I checked this in headless Chrome:attached: 'dashed',detached: "", and the frame's computed style after the write wasnone. The old save/restore kept the border in this case. It only matters if the frame stays in the DOM and visible after its video is removed, which I didn't find a path for in a render. If you want to cover it anyway,if (!entry.element.isConnected)could leave the frame's style alone.
Not run as a full render; this is the unit suite plus a headless-Chrome probe.
— Rames
terencecho
left a comment
There was a problem hiding this comment.
Re-reviewed 12e4c5a0bd6a8315316ec4096ceb915e75af02e8 against PR base/effective merge-base 63a7ec692f4a6bbdbcf39b68561fb197b233df32 (nine changed files). My stale-style changes request is resolved at this head.
Strengths / prior concerns resolved
- Release now takes
borderStylefrom the video's current computed style, rather than the first snapshot on the persistent image (packages/core/src/runtime/colorGrading.ts:1951–1970). As Rames's at-head review explains, this agrees with the injector's source and ordinary inline priority (packages/engine/src/services/screenshotService.ts:756–775). Source-authored!importantrules resolve correctly; the injector does not transfer their priority to the replacement. Intentionalnoneand asymmetric per-side styles are not mistaken for grading's suppression. - The ordinary cached-frame release blocker remains fixed: border restoration precedes the native-source ownership early return. Detach, context loss, failed context restoration and destroy reach that restoration; a frame-identity swap restores the old image before suppressing the new one. Independent source diagnostics cover those paths.
- The shared border allow-list, pre-insertion measurement and replacement-image border-box pin remain sound (
packages/core/src/inline-scripts/parityContract.ts:17–19;packages/engine/src/services/screenshotService.ts:738–745,791). The narrowed grading policy still suppresses the injected border only after a successful draw (packages/core/src/runtime/colorGrading.ts:3166). The earlier native-collapse/opacity, injected-border overpaint and first-upload fallback findings remain resolved in the sampled controls; this revision does not reintroduce them.
Additional independent capture evidence
Real Chromium/SwiftShader runs consume the complete pinned core entry/init/renderSeek runtime, installed GSAP, unchanged exported production HF_BRIDGE_SCRIPT, actual captureFrameToBuffer, FrameLookupTable and injector/cache, with physical PNG-bank reads. The native H.264 video and injected images are decoded. For the solid→dashed sequence, captures select source indices 0,1,1,1,1, with cumulative file reads 1,2,2,2,2: release and both later/repeated captures genuinely reuse the same image without reinjection.
Released head screenshots equal never-graded screenshots across the whole image for dashed, asymmetric styles, intentional none, important class/stylesheet styles, live setGrading(..., null) and genuine live-only destroy. The immutable 51561530 grader under current consumers still produces the stale-style contrast (2,016 differing pixels in the representative dashed case); deleting only release restoration produces 8,304 differing pixels that persist through repeated cached captures. Active grading also visibly changes the picture, rather than merely reporting active status.
Real WebGL context loss restores the current dashed border without another file read; recovery suppresses it again and a subsequent release remains correct. A genuine cross-origin texture-upload SecurityError preserves the native fallback. Native shrink/authored-opacity/re-expansion comparisons equal base. Separate paired controls use both full immutable base core and a separately emitted base injector graph: active contain, radius+clip and per-side-border captures match base. The primary full-base-core/current-injector hybrid is not used to establish paired base parity.
Two observations are not new blockers: destroy with an active grading attribute can immediately re-upsert grading through full-runtime visibility synchronization at both prior and head, so those raw captures are not counted as ungraded-release proof; width/color changes after a source index is already cached remain stale at head/prior/never-graded alike and real reinjection repairs all. Rames's detached-video nit also remains conditional: no supported path with a surviving visible orphan replacement was established.
Validation / scope
Restored-head suites pass: core grading/parity/sibling 81, engine screenshot/injector 54, producer Chromium 57, classifier 5, reachability unit 27, and zero orphan tests. Twelve additional source lifecycle diagnostics pass; the actual prior grader fails 11 and the consumed release-call-deletion mutant fails 10. These diagnostics use mocked GL/decode/geometry; the browser evidence above does not. Across 209 captures, 107 whole-image comparisons include 87 equalities and 20 nonzero diagnostic/control contrasts, not 209 successful release cases. Source/input/bundle hashes, cache and lifecycle state, numerical pixel assertions, tracked-source restoration and owned-resource cleanup were independently verified.
The net textual changes and relevant consumers were audited; the giant frameCapture.ts was read at integration points, and CLI snapshot only at relevant excerpts. Capture sessions and extracted PNG banks are constructed fixtures, not full exporter/session initialization or FFmpeg extraction. Repeated grade→release→regrade cycles on one cached image and every per-side enable/disable history were not independently pixel-replayed; failed context restoration and identity-swap restoration are source-diagnostic coverage, not full-runtime browser proofs. The paired base graph's generated runtime getter is derived from the separately pinned base core bundle. No full render-bank/golden MP4 decode or re-render, BeginFrame, HDR or hardware-GPU proof is claimed. Setup/fixture failures were corrected before the retained passing runs. This is a code-merits verdict, not a claim that CI is green or the merge gate is clear; no CI rerun, fix commit/push, settings change or merge was performed.
Verdict: APPROVE
Reasoning: Current-source restoration closes the supported same-image stale-style failure while retaining cached release and the narrowed grading/fallback policy. Independent source and adversarial capture passes found no additional actionable production-reachable blocker in the reviewed scenarios.
— tai
Summary
injectVideoFramesBatchsubstitutes each<video>with a sibling<img>holding an extracted still frame, and copies an allow-listed set of CSS properties from the video's computed style onto that image.border-width,border-style, andborder-colorwere missing from the allow-list, so any border authored directly on a<video>never reached the replacement image and disappeared from render/snapshot output.border-radiusandclip-pathwere already on the allow-list. A real-Chromium test confirms they already clip a replaced element's content correctly without needingoverflow: hidden— the visible symptom traced entirely to the missing border properties, not a separate radius/clip-path bug.Copying a border onto the replacement image changed its geometry:
injectVideoFramesBatchmeasured the video after inserting the image as an in-flow sibling, so in flex-centred layouts the freshly bordered sibling shrank the video's box by the border width before it was read, and incontent-boxlayouts the border pushed the image outward. The video's used box is now measured before the image is created, and the image is forced tobox-sizing: border-boxsince that measurement is always a border-box value. With both changes the replacement image's rectangle equals the video's in flex and absolute layouts under either box-sizing.Also corrected a stale comment in
frameCapture.tsthat claimed a CSS-effect risk detector can return"clip-path"— it structurally cannot; that value only ever comes from a separate, animation-only gate elsewhere in the same file.The border properties live in the shared media style list, which color grading also copies onto its canvas. Decision: the grading canvas does not carry the border (
borderStyle: none) and keeps covering the source's whole box, so a graded bordered element looks as it does on main (border not shown while graded). Painting the border on the grading canvas was tried and reverted: the canvas and the source (or the injected frame, or a failed first upload) can each end up owning a visible border, so a translucent border painted twice in several reachable orderings. Showing the border on graded media needs one owner for that border across the injection and grading paths, which is a separate change. On the render path the injected frame also drops its copied border once grading draws over it, so letterboxed, alpha and rounded graded videos match main exactly. When grading releases the element (grading removed, attribute removed, runtime destroyed, or context lost), the frame gets its border back, so a cached frame that is not re-injected is not left borderless.Test plan
injectVideoFramesBatchfunction against a styled<video class="clip">(and adata-starttimed variant), takes a real screenshot, and reads real pixel values via an in-page canvas to confirm the border renders and the rounded/clipped corner stays clipped.border-style: none/border-width: 0pxis a no-op, verified with an additional pixel check.coreRuntimeBrowser.test.tssibling).getBoundingClientRect()equals the video's; it fails with the old measure-after-insert order (width 484 instead of 500) and passes with the fix.style-9-prodgolden video: its 16 px video border was previously invisible, so the committed frames were stale. The regenerated frames keep the video box where it was and show the border ring.compiled.htmlis unchanged from main (timings were never affected).parityContract,screenshotService, andvideoFrameInjectorsuites pass unchanged.box-sizing: border-boxpin.