Skip to content

Comments

BL-15721 show bubbles on top of videos#7597

Draft
nabalone wants to merge 1 commit intomasterfrom
BL-15721_put_bubbles_above_videos
Draft

BL-15721 show bubbles on top of videos#7597
nabalone wants to merge 1 commit intomasterfrom
BL-15721_put_bubbles_above_videos

Conversation

@nabalone
Copy link
Contributor

@nabalone nabalone commented Jan 20, 2026

This change is Reviewable


Open with Devin

@nabalone
Copy link
Contributor Author

Feels dangerous enough to me that maybe it should target 6.4 instead? What do you think?

}

// Detector above the canvas so we can detect hover and clicks of playback controls
.bloom-videoMouseDetector {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know what to call this. "videoInteractionDetector"? "videoMouseEventDetector"? If we decide not to preserve hover detection (see below) then we could call it "videoClickDetector"

&.bloom-videoReplayIcon {
display: flex;
.bloom-videoContainer.playing {
.bloom-videoMouseDetector:hover {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like we decided not to do hover behavior in BloomPlayer (e.g. BloomBooks/bloom-player@c31f0fc) but kept it in BloomDesktop. Do we still want this?

.filter(
(_, x) =>
!x.classList.contains("bloom-videoControlContainer"),
!x.classList.contains("bloom-videoControlContainer") &&
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure what's going on here, we have a "Not sure about keeping this" comment above, but now there's no point in keeping a bloom-videoControlContainer without a bloom-videoMouseDetector, it won't work

@andrew-polk andrew-polk marked this pull request as draft January 22, 2026 23:56
@andrew-polk
Copy link
Contributor

Please retarget to master

devin-ai-integration[bot]

This comment was marked as resolved.

@nabalone nabalone force-pushed the BL-15721_put_bubbles_above_videos branch from 3ddcf5d to 580c9ac Compare February 20, 2026 18:14
@nabalone nabalone changed the base branch from Version6.3 to master February 20, 2026 18:46
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 new potential issues.

View 6 additional findings in Devin Review.

Open in Devin Review

@nabalone
Copy link
Contributor Author

Ugh devin review is all messed up because I forgot to retarget before having it start. Trying to figure out how to fix it

@nabalone nabalone marked this pull request as ready for review February 20, 2026 18:53
@andrew-polk
Copy link
Contributor

Ugh devin review is all messed up because I forgot to retarget before having it start. Trying to figure out how to fix it

I told it to regen.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

🐛 2 issues in files not directly in the diff

⚠️ AddInlineScript computes typeAttr but never sets it on the script element (src/BloomExe/Book/HtmlDom.cs:379-385)

The new AddInlineScript method in HtmlDom.cs computes a typeAttr string on line 382 (var typeAttr = module ? "module" : "text/javascript";) but never actually sets it as an attribute on the <script> element. The script element is appended without a type attribute, so the browser will default to text/javascript.

Root Cause and Impact

The caller in Book.cs:579-586 passes module: true to request a module script for Vite dev hot reloading preamble code. Because the type attribute is never set on the element, the browser treats the script as classic text/javascript instead of module. While this specific inline script doesn't use ES module syntax (no import/export), the intent to mark it as a module is lost, and this is clearly unfinished code — the pattern used in the sibling AddScriptFile method (src/BloomExe/Book/HtmlDom.cs:362-374) correctly calls script.SetAttribute("type", typeAttr). The AddInlineScript method should do the same.


🐛 BloomReaderUDPListener.Dispose() dereferences potentially null _clientForBookRequestReceive (src/BloomExe/Publish/BloomPub/wifi/BloomReaderUDPListener.cs:130)

Dispose() at line 130 calls _clientForBookRequestReceive.Dispose() without a null check, but the field is initialized to null at src/BloomExe/Publish/BloomPub/wifi/BloomReaderUDPListener.cs:25 and only assigned inside the async ListenAsync method.

Root Cause and Impact

The constructor (BloomReaderUDPListener()) starts ListenAsync on a background task via Task.Run. If Dispose() is called before ListenAsync has reached line 71 where _clientForBookRequestReceive is assigned — or if ListenAsync returns early because the new UdpClient(...) constructor throws (caught at line 79) — then _clientForBookRequestReceive is still null. Calling .Dispose() on null throws a NullReferenceException, crashing the dispose path.

Additionally, even in the normal flow, ListenAsync wraps the client in a using block (line 85), which will dispose it when the loop exits. Then Dispose() disposes it again — a double-dispose. While UdpClient likely tolerates this, the null case is a real crash risk.

View 9 additional findings in Devin Review.

Open in Devin Review

@nabalone nabalone force-pushed the BL-15721_put_bubbles_above_videos branch from 580c9ac to 97d01c7 Compare February 20, 2026 23:34
@nabalone nabalone marked this pull request as draft February 20, 2026 23:35
@nabalone
Copy link
Contributor Author

I think we should wait until after BL-15935 and BL-15936. If we still think this is PR is even worth doing.

Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 4 new potential issues.

🐛 2 issues in files not directly in the diff

⚠️ AddInlineScript computes typeAttr but never sets it on the script element (src/BloomExe/Book/HtmlDom.cs:379-385)

The new AddInlineScript method in HtmlDom.cs computes a typeAttr string on line 382 (var typeAttr = module ? "module" : "text/javascript";) but never actually sets it as an attribute on the <script> element. The script element is appended without a type attribute, so the browser will default to text/javascript.

Root Cause and Impact

The caller in Book.cs:579-586 passes module: true to request a module script for Vite dev hot reloading preamble code. Because the type attribute is never set on the element, the browser treats the script as classic text/javascript instead of module. While this specific inline script doesn't use ES module syntax (no import/export), the intent to mark it as a module is lost, and this is clearly unfinished code — the pattern used in the sibling AddScriptFile method (src/BloomExe/Book/HtmlDom.cs:362-374) correctly calls script.SetAttribute("type", typeAttr). The AddInlineScript method should do the same.


🐛 BloomReaderUDPListener.Dispose() dereferences potentially null _clientForBookRequestReceive (src/BloomExe/Publish/BloomPub/wifi/BloomReaderUDPListener.cs:130)

Dispose() at line 130 calls _clientForBookRequestReceive.Dispose() without a null check, but the field is initialized to null at src/BloomExe/Publish/BloomPub/wifi/BloomReaderUDPListener.cs:25 and only assigned inside the async ListenAsync method.

Root Cause and Impact

The constructor (BloomReaderUDPListener()) starts ListenAsync on a background task via Task.Run. If Dispose() is called before ListenAsync has reached line 71 where _clientForBookRequestReceive is assigned — or if ListenAsync returns early because the new UdpClient(...) constructor throws (caught at line 79) — then _clientForBookRequestReceive is still null. Calling .Dispose() on null throws a NullReferenceException, crashing the dispose path.

Additionally, even in the normal flow, ListenAsync wraps the client in a using block (line 85), which will dispose it when the loop exits. Then Dispose() disposes it again — a double-dispose. While UdpClient likely tolerates this, the null case is a real crash risk.

View 11 additional findings in Devin Review.

Open in Devin Review

// can't move them if they aren't draggable, so it
// makes sense that a click anywhere on the video would play it; there's nothing else useful
// to do in response.
if (!video.closest(".drag-activity-play")) {

Choose a reason for hiding this comment

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

🟡 Potential null dereference on video in handleVideoClick after optional chaining

The handleVideoClick handler obtains video via optional chaining (lines 400-402), meaning video can be undefined. However, line 410 immediately accesses video.closest(...) without a null check, which would throw TypeError: Cannot read properties of undefined (reading 'closest'). Lines 414 and 417 also access video.paused / pass ev to handlers that dereference video without guarding.

Root Cause

Before this PR, video was assigned as ev.currentTarget as HTMLVideoElement (bloomVideo.ts:399 old code), which was always a valid HTMLVideoElement because the click listener was attached directly to the <video> element. After the change, the listener is on the bloom-videoMouseDetector div, and video is obtained through DOM traversal:

const video = (ev.currentTarget as HTMLElement)
    ?.closest(".bloom-videoContainer")
    ?.getElementsByTagName("video")[0];

The optional chaining (?.) acknowledges that the result can be undefined, but the very next usage at line 410 does not guard against it:

if (!video.closest(".drag-activity-play")) {

In normal operation this is unlikely to crash because the mouseDetector is only created when a <video> element exists inside the container. However, if the video element is removed after setup (e.g., by another editing operation), or if the DOM structure is unexpected, this will throw at runtime.

Impact: A crash in the click handler would prevent any further click processing on that video container, silently breaking play/pause behavior.

Suggested change
if (!video.closest(".drag-activity-play")) {
if (!video || !video.closest(".drag-activity-play")) {
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +1690 to +1695
// TODO copilot did this and it works but I'm not sure it's the right fix. Let's see if HandlePlayClick gets fixed in BL-15936
// For draggable videos in Play Mode, disable pointer events on the mouse detector
// so clicks can pass through to the drag system which handles playback
.drag-activity-play [data-draggable-id] .bloom-videoMouseDetector {
pointer-events: none;
}

Choose a reason for hiding this comment

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

🚩 TODO comment left in production code suggesting uncertainty about approach

Lines 1690-1694 contain a TODO comment: // TODO copilot did this and it works but I'm not sure it's the right fix. along with a CSS rule that disables pointer events on the mouse detector for draggable videos in Play Mode. The comment suggests this rule was generated by Copilot and the author isn't confident it's correct. This may need a follow-up investigation per BL-15936 to ensure draggable video playback works properly without this workaround.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

2 participants