Web frame outside the focused frame leaks into presentation mode #102
Labels
No labels
prio_critical
prio_low
type_bug
type_contact
type_issue
type_lead
type_question
type_story
type_task
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#102
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Starting presentation mode while a webframe exists OUTSIDE all frames leaves the iframe DOM overlay parked on top of the presented slide instead of being hidden / re-positioned. The presented slide ends up with an unrelated live website rendered over its content even though the webframe is not a member of the focused frame.
Steps to reproduce
Expected
In presentation mode, only the focused frame's contents are visible inside the spotlight. Objects outside the frame (including webframes) are masked by the dark backdrop, the same way other objects are.
Actual
The iframe overlay stays rendered at its pre-presentation screen coordinates — which, after the stage zoom/pan that focusFrame applies, often happen to land inside the spotlight area. The result is a live website appearing inside the slide, on top of whatever the slide's actual contents are.
Root cause (likely)
Two compounding issues in
webframe.js:Stage transforms during presentation are not propagated to the iframe overlay.
WhiteboardFrames.focusFramecallsWhiteboardCanvas.setZoom(...)andstage.position({ x, y })programmatically. The webframe's only position-update hooks listen forstage.on('dragstart' | 'dragend' | 'wheel'), which don't fire for programmatic transform changes. So the overlay stays at its old screen position while the canvas viewport has moved underneath it.The presentation spotlight does not hide non-member webframe overlays. The
#pres-spotlightelement usesbox-shadow: 0 0 0 9999px rgba(0,0,0,0.92)to dim everything outside the focused frame's screen rect. That CSS shadow paints over the Konva canvas (z-index lower than 9999) but the iframe overlay has its own stacking context — depending on z-index, the box-shadow may not visually mask it. The cleanest behaviour is to actively hide every iframe overlay whose underlying Konva object is not a child of the focused frame for the duration of the presentation.Suggested fix
WhiteboardFrames.startPresentation/focusFrame, iterate the webframe overlays and hide every overlay whose owning Konva group is not contained in the currently-focused frame's bounds (or use the new explicitparent_frame_idfrom issue #96 if applicable).WhiteboardFrames.stopPresentation, restore visibility of all overlays.WhiteboardWebframe.updateOverlayPosition(id, group)after every programmatic stage transform (focusFrame,setZoom, …) so any webframe inside the focused frame stays in sync with the new viewport. This shares the same fix surface with the related drag-desync bug.Acceptance criteria
Spec — Issue #102: Web frame outside the focused frame leaks into presentation mode
Objective
During presentation mode, the iframe DOM overlay attached to a webframe object that lives outside the focused frame must not be visible inside the spotlight. The webframe should be masked exactly like every other non-focused-slide object on the canvas.
Root Cause
WhiteboardFrames.focusFramereframes the stage by callingWhiteboardCanvas.setZoom(...)andstage.position({...})— both programmatic mutations that do NOT fire Konva'sdragstart/dragend/wheelevents.WhiteboardWebframe.createWebframeonly refreshes the iframe overlay's screen position in response to those three stage events. So when presentation reframes the canvas, every webframe's<div id="wf-overlay-*">keeps its pre-presentation coordinates.box-shadow: 0 0 0 9999px rgba(0,0,0,0.92)painted over#whiteboard-container. The iframe overlay div has its own stacking context (z-index: 10) and lives outside the Konva paint surface, so the box-shadow mask cannot dim it. The existingbody.wb-presenting #whiteboard-container *rule disables clicks but not visibility.Net effect: the iframe stays where the user last saw it on screen, fully visible on top of (or beside) the spotlight.
Recommended Fix — Option A (smallest, lowest risk)
Hide ALL webframe overlays for the entire duration of presentation mode and restore them on exit. The presentation lifecycle in
frames.jsowns the overlay-visibility behaviour.webframe.jsalready exportshideAllOverlaysandshowAllOverlays— no new helper needed.Trade-off: webframes that happen to be inside the focused slide will appear as their static Konva placeholder (dark rect with header + URL label) rather than as a live iframe. Per the user's bug report this is acceptable; Option B (per-slide containment test) is deferred as a follow-up if/when live web content during presentation becomes a requirement.
Requirements
startPresentation, every webframe iframe overlay DOM element is hidden before the firstfocusFrameruns.stopPresentation, every webframe iframe overlay is repositioned and shown again, matching the restored zoom/pan.nextFrame/prevFrameneed no extra plumbing — overlays remain hidden for the whole presentation; only entry and exit toggle visibility.Files to Modify
crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js— only file touched.Files to Leave Alone
crates/hero_whiteboard_ui/static/web/js/whiteboard/webframe.js—hideAllOverlays/showAllOverlaysare already exported and do exactly what we need.crates/hero_whiteboard_ui/templates/web/board.html— no CSS or markup changes.Step-by-Step Plan
Step 1 — Hide all webframe overlays at presentation start
File:
frames.js, functionstartPresentation.After
document.body.classList.add('wb-presenting')and BEFOREfocusFrame(frames[0]), add:Hiding before
focusFrameensures the iframe never paints at the new (now-incorrect) coordinates between the zoom change and the restore.Step 2 — Restore all webframe overlays at presentation end
File:
frames.js, functionstopPresentation.After
WhiteboardCanvas.drawGrid()(which finishes restoring the previous view) and BEFORE clearingpreviousView, add:showAllOverlayswalks every overlay id and callsshowOverlay(id, group)→updateOverlayPosition(id, group), which reads the currentstage.scaleX()andstage.position()— which we just restored topreviousView. Overlays land at the correct restored screen coordinates without any extra arithmetic.Step 3 — No changes for
nextFrame/prevFrameOverlays are hidden for the whole presentation, so frame-to-frame navigation needs no extra hide/show cycle.
Acceptance Criteria
refreshOverlaypath.Notes / Caveats
parent_frame_idthe same way other object types do post-#96, then iterate webframes infocusFrameand selectively hide/show + refresh based on whether theirparent_frame_idmatches the focused frame's id. Separate, larger change.body.wb-presenting [id^="wf-overlay-"] { visibility: hidden }. It would visually mask the overlay but leave the iframe loaded and laid out at stale coordinates, papering over the underlying transform-tracking issue.setZoom/stage.positionshape is also used by minimap navigation and "fit to content"; making them fire syntheticdragendevents would have non-local consequences.Test Results
Implementation Summary
One file changed (
frames.js), +33/-0. Option B chosen: webframes inside the focused slide stay live; webframes outside are hidden for the duration of presentation mode.Root cause
WhiteboardFrames.focusFramereframes the stage withWhiteboardCanvas.setZoom(...)andstage.position({...})— both programmatic mutations. Webframe's iframe-overlay listeners only react tostage.dragstart/dragend/wheel, so they never repositioned the iframe during presentation. The presentation spotlight'sbox-shadowmask paints over the Konva canvas but the iframe overlay has its own stacking context (z-index: 10), so the dimming doesn't reach it.crates/hero_whiteboard_ui/static/web/js/whiteboard/frames.js_applyWebframePresentationVisibility(frame)private helper. It iteratesWhiteboardObjects.getAllObjects(), picks entries withtype === 'webframe', computes the webframe group's canvas-coord box from its.bgrect, and tests fully-inside containment against the focused frame's.bgrect (canvas coords).WhiteboardWebframe.showOverlay(id, group)thenrefreshOverlay(id)(refresh runs after the new stage transform is applied, so the iframe lands at the correct screen position).WhiteboardWebframe.hideOverlay(id).focusFrame: at the end, whenpresentationModeis true, calls_applyWebframePresentationVisibility(frame). CoversstartPresentation(which setspresentationMode = truethen callsfocusFrame(frames[0])) and thenextFrame/prevFramepaths.stopPresentation: after restoring the previous zoom/pan, callsWhiteboardWebframe.showAllOverlays()which re-shows + repositions every overlay against the restored stage transform.Verification
cargo fmt --all -- --check: cleancargo check --workspace: cleancargo clippy --workspace -- -D warnings: cleancargo test --workspace: passnode --check frames.js: cleanManual smoke test
Notes / scope
parent_frame_idfrom issue #96, so it works for webframes that pre-date that change. If the team later wants the explicit-membership semantics from #96 to govern presentation visibility too, swap the geometric test for aparent_frame_id === Number(frame.id())check (and extend the webframe registry to trackparent_frame_id, which is currently a follow-up).stage.scaleX()/stage.position(), which are exactly whatfocusFramejust set. No extra arithmetic needed.refreshOverlaycall — both fixes share the same overlay-tracking infrastructure.