Skip to content

Onboarding v4: design feedback round 2#2440

Draft
noisysocks wants to merge 8 commits intomainfrom
randerson/onboarding-v4-design-feedback-2
Draft

Onboarding v4: design feedback round 2#2440
noisysocks wants to merge 8 commits intomainfrom
randerson/onboarding-v4-design-feedback-2

Conversation

@noisysocks
Copy link
Contributor

@noisysocks noisysocks commented Mar 12, 2026

Asana Task/Github Issue:
https://app.asana.com/1/137249556945/project/1209121419454298/task/1213627872900762?focus=true
https://app.asana.com/1/137249556945/project/1142021229838617/task/1213508111481481?focus=true

Description

Address design feedback on the v4 onboarding flow:

  • Left align StepHeader text — title and subtitle now align left instead of center
  • Fix video flash when toggling DuckPlayer — use requestVideoFrameCallback to wait for the first frame before switching visibility, preventing a flash of the previous frame
  • Stagger bubble fade-in — top bubble (title/subtitle) fades in 100ms before the bottom bubble (body content), sequencing the load-in to guide the user's eye

Testing Steps

https://rawcdn.githack.com/duckduckgo/content-scope-scripts/1eecb0e5b9e282f4932c80ba63a7f79968f05830/build/integration/pages/onboarding/index.html?order=v4

  • Navigate through the onboarding steps and observe that titles appear slightly before body content on each transition
  • On the Duck Player step, toggle between "with" and "without" and verify no flash of the previous video frame
  • Verify StepHeader text is left-aligned

Checklist

  • I have tested this change locally
  • I have tested this change locally in all supported browsers
  • This change will be visible to users
  • I have added automated tests that cover this change
  • I have ensured the change is gated by config
  • This change was covered by a ship review
  • This change was covered by a tech design
  • Any dependent config has been merged

🤖 Generated with Claude Code


Note

Low Risk
Low risk UI/animation tweaks confined to onboarding v4 components; main risk is minor timing/visibility regressions in bubble transitions or video toggling across browsers.

Overview
Improves onboarding v4 polish by staggering bubble entry animations, left-aligning StepHeader text, and fixing Duck Player video flashing during toggles.

Bubble now accepts an optional fadeInDelay (via a CSS var) and SingleStep uses it to delay the bottom bubble when a top bubble is present. DuckPlayerContent now waits for the first rendered video frame (requestVideoFrameCallback) before swapping visibility, switches hidden videos to opacity: 0/no pointer events, and updates the Playwright test to assert opacity instead of not.toBeVisible().

Written by Cursor Bugbot for commit b59639f. This will update automatically on new commits. Configure here.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 12, 2026

Build Branch

Branch pr-releases/randerson/onboarding-v4-design-feedback-2
Commit 693139ddb5
Updated March 12, 2026 at 11:52:42 PM UTC

Static preview entry points

QR codes (mobile preview)
Entry point QR code
Docs QR for docs preview
Static pages QR for static pages preview
Integration pages QR for integration pages preview

Integration commands

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#pr-releases/randerson/onboarding-v4-design-feedback-2

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", branch: "pr-releases/randerson/onboarding-v4-design-feedback-2")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/randerson/onboarding-v4-design-feedback-2
git -C submodules/content-scope-scripts checkout origin/pr-releases/randerson/onboarding-v4-design-feedback-2
Pin to exact commit

npm (Android / Extension):

npm i github:duckduckgo/content-scope-scripts#693139ddb5fdd849f3efe161333ac64f06de9d81

Swift Package Manager (Apple):

.package(url: "https://github.com/duckduckgo/content-scope-scripts.git", revision: "693139ddb5fdd849f3efe161333ac64f06de9d81")

git submodule (Windows):

git -C submodules/content-scope-scripts fetch origin pr-releases/randerson/onboarding-v4-design-feedback-2
git -C submodules/content-scope-scripts checkout 693139ddb5fdd849f3efe161333ac64f06de9d81

@github-actions github-actions bot added the semver-patch Bug fix / internal — no release needed label Mar 12, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 12, 2026

[Beta] Generated file diff

Time updated: Thu, 12 Mar 2026 23:53:17 GMT

Apple
    - apple/pages/onboarding/dist/index.css
  • apple/pages/onboarding/dist/index.js

File has changed

Integration
    - integration/pages/onboarding/dist/index.css
  • integration/pages/onboarding/dist/index.js

File has changed

Windows
    - windows/pages/onboarding/dist/index.css
  • windows/pages/onboarding/dist/index.js

File has changed

@github-actions
Copy link
Contributor

⚠️ Cursor review was not successful.

This PR requires a manual review and approval from a member of one of the following teams:

  • @duckduckgo/content-scope-scripts-owners
  • @duckduckgo/apple-devs
  • @duckduckgo/android-devs
  • @duckduckgo/team-windows-development
  • @duckduckgo/extension-owners
  • @duckduckgo/config-aor
  • @duckduckgo/breakage-aor
  • @duckduckgo/breakage

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility Assessment

  1. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (L95-L101) — severity: error
    video.requestVideoFrameCallback(...) is called unguarded. If this API is unavailable in any supported runtime/WebView build, play() rejects before state transition logic runs, causing toggle/ended handlers to fail (and likely emit unhandled promise rejections). This maps to Platform-Specific Breakage / Missing APIs.

  2. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (L95-L101, L133-L144) — severity: warning
    frameReady is awaited even when video.play() throws. In failure modes where playback never starts (decode/network/autoplay edge cases), the frame callback may never fire, leaving toggle()/end() suspended and UI state stuck. This is a Timing/Race compatibility risk because transition completion now depends on a callback that may not resolve.

  3. special-pages/pages/onboarding/app/v4/components/StepHeader.module.css (L19, L32), special-pages/pages/onboarding/app/v4/components/Bubble.js (L147), special-pages/pages/onboarding/app/v4/components/Bubble.module.css (L40) — severity: info
    These visual-only changes (left alignment and staggered fade delay via CSS custom property) are low compatibility risk and do not alter browser API contracts.

Security Assessment

No direct security findings in this diff.

  • No changes to captured globals, wrapper utilities, message bridge, origin validation, or transport code.
  • No new postMessage, network fetch/XHR, dynamic code execution, or config-trust boundary changes.
  • Changed logic is isolated to onboarding special-page UI behavior.

Risk Level

Medium Risk — UI-only scope, but the new requestVideoFrameCallback dependency introduces a real cross-runtime compatibility failure mode that can block step transitions.

Recommendations

  1. Add capability/error hardening around first-frame wait: guard requestVideoFrameCallback and ensure play() always resolves/rejects deterministically (do not await an unresolvable frame promise).
  2. Add an integration test path that simulates missing requestVideoFrameCallback and a video.play() rejection, and assert the toggle flow still reaches a settled state.
  3. Catch/handle async handler failures in toggle()/end() call paths to prevent unhandled promise rejection noise and silent stuck states.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

Base automatically changed from randerson/onboarding-v4-polish to main March 12, 2026 09:18
noisysocks and others added 4 commits March 12, 2026 20:24
Use requestVideoFrameCallback to delay visibility switch until the
incoming video's first frame is painted. Use a state ref so the
onEnded handler always reads fresh state. Hide inactive video with
opacity: 0 instead of visibility: hidden so the compositor keeps
decoding frames.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When transitioning between steps, the top bubble (title/subtitle)
now fades in 100ms before the bottom bubble (body content). This
sequences the load-in to guide the user's eye and improve
comprehension.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@noisysocks noisysocks force-pushed the randerson/onboarding-v4-design-feedback-2 branch from a7a4953 to b59639f Compare March 12, 2026 09:25
@noisysocks noisysocks marked this pull request as ready for review March 12, 2026 09:27
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility Assessment

  1. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (approx. lines 95-101) — severity: error
    video.requestVideoFrameCallback(...) is called unconditionally. This API is not universally available across all embedded WebView/Safari baselines, so play() can reject via a TypeError path and break the toggle flow on unsupported runtimes. This is a direct platform-specific missing API regression risk.

  2. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (approx. lines 95-101, 131-145) — severity: warning
    frameReady is awaited even when video.play() fails. In failure/no-decode paths, the frame callback may never fire, so toggle() / end() can stall and leave UI state stuck. This is a timing/race contract issue: state transitions depend on an await that may never resolve.

Security Assessment

No security findings in this diff. Changes are isolated to onboarding special-page UI/CSS and do not modify message bridge, origin validation, captured globals, wrapper utilities, or injected feature shims.

Risk Level

High Risk — while scope is limited to onboarding, this introduces a new platform API dependency in the video transition path without capability checks, which can cause user-visible functional breakage on unsupported WebViews.

Recommendations

  1. Guard requestVideoFrameCallback with capability detection before use, and keep state transition logic functional when unavailable.
  2. Do not await frameReady after failed play(); ensure toggle/end always reach a deterministic state transition.
  3. Add integration coverage for: missing requestVideoFrameCallback and rejected video.play() to verify no stuck phase/reverse state.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 3 potential issues.

Bugbot Autofix prepared fixes for all 3 issues found in the latest run.

  • ✅ Fixed: Play failure causes indefinite hang via unresolved promise
    • Added playSucceeded flag to only await frameReady if video.play() succeeds, preventing indefinite hang when play fails.
  • ✅ Fixed: Stale closure in async toggle after await
    • Updated toggle function to use stateRef.current instead of closure state variable, matching the pattern used in end function.
  • ✅ Fixed: Auto-play timer mismatched with new fade-in delay
    • Updated timeout from 667ms to 767ms and comment to reflect the new 500ms fade-in delay (500ms + 267ms = 767ms).

Create PR

Or push these changes by commenting:

@cursor push 6134e28588
Preview (6134e28588)
diff --git a/special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js b/special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js
--- a/special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js
+++ b/special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js
@@ -93,12 +93,16 @@
         video.currentTime = 0;
         /** @type {Promise<void>} */
         const frameReady = new Promise((resolve) => video.requestVideoFrameCallback(() => resolve()));
+        let playSucceeded = false;
         try {
             await video.play();
+            playSucceeded = true;
         } catch (error) {
             console.error(error);
         }
-        await frameReady;
+        if (playSucceeded) {
+            await frameReady;
+        }
     };
 
     /** @param {HTMLVideoElement | null} video */
@@ -106,20 +110,20 @@
         if (video) video.currentTime = 0;
     };
 
-    // Auto-play after bubble entry animation (400ms delay + 267ms duration = 667ms)
+    // Auto-play after bubble entry animation (500ms delay + 267ms duration = 767ms)
     useEffect(() => {
         const id = setTimeout(
             () => {
                 play(videoFor('with'));
                 setState((prev) => ({ ...prev, phase: isReducedMotion ? 'settled' : 'playing' }));
             },
-            isReducedMotion ? 0 : 667,
+            isReducedMotion ? 0 : 767,
         );
         return () => clearTimeout(id);
     }, []); // exclude isReducedMotion from deps — must not re-fire if reduced motion changes after mount
 
     const toggle = async () => {
-        const { target, phase, reverse } = state;
+        const { target, phase, reverse } = stateRef.current;
         if (phase === 'initial') {
             // Queue or cancel a reverse so auto-play will switch to "without" once the "with" video ends
             setState({ target, phase, reverse: !reverse });

// Ignore errors - we can assume that our browsers support playback
console.error(error);
}
await frameReady;
Copy link
Contributor

Choose a reason for hiding this comment

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

Play failure causes indefinite hang via unresolved promise

Medium Severity

The play function registers requestVideoFrameCallback before calling video.play(), then unconditionally awaits the resulting frameReady promise after the try-catch. If video.play() rejects (caught and logged), and the seek to time 0 doesn't produce a new frame (e.g., already at position 0), requestVideoFrameCallback never fires and frameReady never resolves. This causes play() to hang indefinitely, blocking both toggle and end from ever calling setState, effectively freezing the DuckPlayer UI.

Additional Locations (2)
Fix in Cursor Fix in Web


const end = () => {
if (state.reverse) {
// A reverse was queued — play the other video now
Copy link
Contributor

Choose a reason for hiding this comment

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

Stale closure in async toggle after await

Low Severity

The toggle function reads state from its closure at the top, then awaits play() in the 'settled' branch before calling setState with absolute values. Meanwhile, end correctly reads from stateRef.current to avoid stale closures. The inconsistency means toggle could overwrite state changes that occurred during the await, though the practical window is small since no videos are playing in the 'settled' phase.

Additional Locations (1)
Fix in Cursor Fix in Web

return () => clearTimeout(id);
}, []); // exclude isReducedMotion from deps — must not re-fire if reduced motion changes after mount

const toggle = () => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Auto-play timer mismatched with new fade-in delay

Low Severity

The auto-play timeout of 667ms was computed from the original 400ms fade-in delay + 267ms animation duration, as the comment states. This PR changes the bottom bubble's fadeInDelay to 500ms (for the DuckPlayer step, which always has a topBubble), so the fade-in now runs from 500–767ms. But the hardcoded 667ms timer was not updated, causing the video to start playing ~100ms before the bubble is fully opaque — the exact kind of visual flash this PR aims to fix.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility Assessment

  • special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (lines 95-101, 113, 133-134, 143-144) — warning: video.requestVideoFrameCallback(...) is called unconditionally and outside the try/catch around video.play(). On engines/WebViews where this API is unavailable, play() rejects via thrown TypeError, and the callers (useEffect fire-and-forget + async UI handlers) can end up with unhandled promise rejection and broken video-state transitions. This maps to Missing APIs and Timing/Race risk categories.
  • special-pages/pages/onboarding/app/v4/components/StepHeader.module.css and special-pages/pages/onboarding/app/v4/components/Bubble*info: visual-only alignment/timing changes; no browser API override or prototype-surface regression found.

Security Assessment

  • No direct security vulnerabilities found in changed lines (no message bridge/origin validation changes, no new network exfil paths, no dynamic code execution, no privileged API boundary changes).
  • Residual note: the unguarded requestVideoFrameCallback path can be used as a reliability DoS vector for the step UI on unsupported runtimes (functional breakage, not data exposure).

Risk Level

Medium Risk — PR is mostly UI/CSS, but it introduces a runtime dependency on requestVideoFrameCallback without capability checks/fallback, which can regress onboarding flow behavior on unsupported or edge WebView versions.

Recommendations

  1. Add feature detection and fallback in play(video):
    • Gate on typeof video.requestVideoFrameCallback === 'function'.
    • Fallback to loadeddata/canplay + requestAnimationFrame, or bounded timeout, before swapping visibility.
  2. Ensure no unhandled async failures from event handlers/effects:
    • In useEffect, call void play(...).catch(...).
    • Keep onEnded/onClick handlers resilient if play() fails.
  3. Add an integration test path that stubs missing requestVideoFrameCallback and verifies toggle/ended transitions still complete.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

}
video.currentTime = 0;
/** @type {Promise<void>} */
const frameReady = new Promise((resolve) => video.requestVideoFrameCallback(() => resolve()));
Copy link
Contributor

Choose a reason for hiding this comment

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

requestVideoFrameCallback is invoked unguarded here and outside the try/catch around video.play(). On runtimes lacking this API, this throws and can break state transitions via unhandled async rejection. Please add a capability check + fallback frame-readiness strategy.

@noisysocks noisysocks marked this pull request as draft March 12, 2026 10:11
@noisysocks
Copy link
Contributor Author

@cursor Please make the 100ms delay between top bubble fade in and bottom bubble fade in configurable via url param. Similar to what we do for ?bubbleWidth.

@cursor
Copy link
Contributor

cursor bot commented Mar 12, 2026

Summary

Added a URL parameter bubbleFadeInDelay to configure the delay offset between the top and bottom bubble fade-in animations, following the same pattern as ?bubbleWidth.

Changes made:

  • Added bubbleFadeInDelay URL parameter parsing at the top of SingleStep.js
  • Updated the bottom bubble's fadeInDelay calculation to use: 400ms (default top delay) + configurable offset (default 100ms)

How it works:

  • Default behavior: Top bubble fades in at 400ms, bottom bubble at 500ms (100ms offset)
  • With ?bubbleFadeInDelay=200: Bottom bubble fades in at 600ms (200ms offset)
  • With ?bubbleFadeInDelay=0: Both bubbles fade in simultaneously at 400ms
  • Invalid values fall back to the default 100ms offset

The implementation matches the bubbleWidth override pattern and handles invalid input by falling back to the default.

Open in Web Open in Cursor 

Add ?bubbleFadeInDelay URL parameter to configure the delay offset between
top and bottom bubble fade-in animations. Default remains 100ms offset
(top: 400ms, bottom: 500ms). Follows the same pattern as ?bubbleWidth.

Co-authored-by: Robert Anderson <robert@noisysocks.com>
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility Assessment

  1. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (play() around requestVideoFrameCallback and await frameReady) — warning
    Unconditional video.requestVideoFrameCallback(...) use is a cross-platform compatibility risk (Missing APIs category). If this API is absent in any supported WebView/Safari baseline, it throws synchronously before the try/catch, causing toggle/end async handlers to reject.

  2. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (play() error path) — warning
    frameReady is awaited even when video.play() rejects. In rejection paths, no frame callback is guaranteed, so the promise can remain pending and stall state transitions (Timing and Race Conditions + return contract expectations for async handlers).

  3. special-pages/pages/onboarding/app/v4/components/Bubble.js + Bubble.module.css + SingleStep.js (fade delay plumbing) — info
    The staggered fade change is scoped to CSS custom-property timing and preserves component contracts. I do not see a site-compat regression pattern here.

  4. special-pages/pages/onboarding/app/v4/components/StepHeader.module.css (text alignment) — info
    Pure presentation change (text-align: left), no API/runtime compatibility concerns.

Security Assessment

No direct security findings in this diff.

The PR does not touch injected API shims, captured globals, messaging bridge, origin checks, or any network/data-exfiltration path. Changes are localized to onboarding special-page UI behavior.

Risk Level

Medium Risk — primarily due to the new first-frame synchronization path introducing potential runtime hangs/handler rejection on unsupported API or failed playback paths.

Recommendations

  1. Gate requestVideoFrameCallback with feature detection and provide a bounded fallback path (for example, resolve on loadeddata/canplay or immediate resolve after successful play()).
  2. Do not await frame readiness when play() fails; short-circuit to existing state transition logic so UI cannot deadlock.
  3. Add an automated test case that stubs missing requestVideoFrameCallback and a play() rejection to verify toggle/end still settle correctly.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

The "Hi there" and "Protections activated!" steps had centered
headings and body copy while the rest of the steps were left-aligned.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Stale comment

Web Compatibility Assessment

  1. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (lines 95-101) — error
    requestVideoFrameCallback is called unguarded (video.requestVideoFrameCallback(...)). On engines where this API is absent, this throws synchronously before video.play() runs, causing the toggle/end async handlers to reject and leaving the step stuck. This maps to the Missing APIs / platform-specific breakage risk.
  2. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (lines 95-101) — warning
    await frameReady runs even when video.play() rejects. If playback fails (decode/policy/media error), no frame callback is guaranteed, so the promise can remain pending and block state transitions. This creates a latent UI deadlock in the toggle flow.

Security Assessment

No security vulnerabilities found in this diff.
The changes are isolated to onboarding special-page UI/animation code and tests; no message bridge, transport/origin validation, captured-globals, network, or injected API-shimming paths were modified.

Risk Level

Medium Risk — scoped UI changes, but the new video-frame sync path introduces a real compatibility failure mode on unsupported or failed playback paths.

Recommendations

  1. Guard requestVideoFrameCallback with feature detection and fall back to immediate resolve or a loadeddata/canplay-based wait.
  2. Do not await frame readiness after failed play(); short-circuit to preserve state progress. A pragmatic alternative is Promise.race([frameReady, timeout]) with a short timeout.
  3. Add an integration/unit test path that stubs HTMLVideoElement.prototype.requestVideoFrameCallback as undefined and asserts toggle still progresses without hanging.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

noisysocks and others added 2 commits March 13, 2026 10:51
The comparison table and actions now fade in 100ms after the title,
configurable via the bubbleFadeInDelay URL param.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Web Compatibility Assessment

  1. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (95-101) — error
    video.requestVideoFrameCallback(...) is called unconditionally. On platforms/engines where this API is missing, this throws and breaks toggle/end transitions for the Duck Player step. This is a Missing APIs / platform-specific breakage risk.

  2. special-pages/pages/onboarding/app/v4/components/DuckPlayerContent.js (96-101, 133, 143) — error
    play() catches video.play() rejection but still awaits frameReady. If playback fails (or first frame is never produced), requestVideoFrameCallback may never resolve, causing state transitions to hang in async handlers (toggle/end). This is a timing/race/hang compatibility issue.

Security Assessment

No direct security vulnerabilities identified in this diff.

  • No changes to message bridge, origin validation, captured globals, wrapper utilities, or privileged transports.
  • New URL param usage (bubbleFadeInDelay) is parsed numerically before writing to CSS custom properties, so no direct script/CSS injection vector is introduced by these lines.

Risk Level

Medium Risk — UI-only scope, but the new video-frame synchronization path introduces cross-platform API availability and unresolved-promise failure modes that can break onboarding progression.

Recommendations

  1. Guard requestVideoFrameCallback with feature detection and provide a fallback (loadeddata/canplay/requestAnimationFrame) before visibility swap.
  2. Do not await frame readiness after video.play() rejection; fail fast to previous behavior (switch state without frame wait) or use a bounded timeout.
  3. Add tests for degraded environments: missing requestVideoFrameCallback and rejected video.play() to ensure toggle/end never deadlock.
Open in Web View Automation 

Sent by Cursor Automation: Web compat and sec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-patch Bug fix / internal — no release needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants