Audience loses the presented frame when the owner moves it during presentation (no re-focus until reload) #199
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#199
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
While an audience member is watching in presentation mode, if the owner/editor moves (or resizes/rotates) the currently presented frame, the audience loses sight of that frame — the canvas stays zoomed/positioned to where the frame used to be, and the spotlight cutout is in the wrong place — until the slide changes or the page is reloaded.
Confirmed root cause
focusFrame(frame)incrates/hero_whiteboard_admin/static/web/js/whiteboard/frames.js(~line 341) is what fits the audience's stage to the focused frame and computes_focusedScreenRect(the spotlight cutout consumed by the host template). It is invoked only on slide transitions:startPresentation()(first frame),nextFrame()/prevFrame(),applyRemoteSlide(index)(~line 329, remote slide change),When the presenter moves/resizes/rotates the focused frame, that change is broadcast as a normal
object.update. On the audience sidesync.js applySyncUpdateapplies it (thetype === 'frame'branches ~sync.js:324and ~735) and the frame's Konva node moves to its new coordinates — but nothing notifiesWhiteboardFrames, andfocusFrameis not re-run. There is no exported re-focus hook and no call site outsideframes.js(verified by grep). So the audience's stage stays fitted to the old frame rect: the frame is now off-screen / outside the spotlight, appearing "lost". A subsequent slide change or a page reload re-runsfocusFramefrom the frame's current position and it looks correct again.The presenter's own view has the same gap (dragging the focused frame during presentation does not re-fit their view either), but the audience impact is the visible bug.
Expected
While presenting, if the currently focused frame's geometry (position, size, or rotation) changes — whether from the local presenter or a remote editor — the view should re-fit to the frame and the spotlight cutout should track it, so the audience keeps seeing the frame without reloading.
Requirements
_applyingSync/ sync-suppression guards).Notes / pointers
frames.jsalready has the pattern for sync-driven re-focus:applyRemoteSlidewrapsfocusFramein_applyingSync = true/false. A new exported hook (e.g.WhiteboardFrames.notifyObjectUpdated(objectId)orrefocusIfPresenting(objectId)) called fromsync.js applySyncUpdateafter a frame update would mirror howsync.jsalready callsWhiteboardConnectors.reAnchorByObject(...)after applying object moves.frames.jstrackscurrentFrameIndexand the sortedframesarray (getFrames()); compare the updated object id againstframes[currentFrameIndex].id().object.updatemessages; re-fitting every tick is acceptable if it is cheap (it is the same math as a slide change) but coalescing per animation frame is preferable.applySyncUpdate).touch crates/hero_whiteboard_admin/src/assets.rsbeforecargo build --release -p hero_whiteboard_admin, and verify the served asset changed before testing.Affected files (expected)
crates/hero_whiteboard_admin/static/web/js/whiteboard/frames.js— exported re-focus hook; only act when presenting and the object is the focused frame.crates/hero_whiteboard_admin/static/web/js/whiteboard/sync.js— call the hook from the frame branch ofapplySyncUpdate(and wherever local frame moves are applied/broadcast).Implementation Spec for Issue #199
Objective
When the presentation owner moves, resizes, or rotates the currently focused frame during a live presentation, every audience client (and the presenter) must immediately re-fit the stage to the new frame geometry and reposition the spotlight cutout — no slide change, no reload, no sync echo loop, no server/schema change.
Requirements
Files to Modify/Create
frames.js- exportednotifyObjectUpdated(objectId)hook (rAF-coalesced) +notifyFrameRemoved()graceful fallback.sync.js- call the hook from thetype === 'frame'branch ofapplySyncUpdate; callnotifyFrameRemoved()from theobject.deletedframe branch.objects.js- call the hook from the local framedragendcommit.tools.js- call the hook from the sharedtransformendcommit (local presenter resize/rotate).crates/hero_whiteboard_admin/src/assets.rs- touch only (rust-embed cache-bust).Implementation Plan
Step 1: Add
notifyObjectUpdatedto WhiteboardFramesFiles:
crates/hero_whiteboard_admin/static/web/js/whiteboard/frames.jsvar _refocusRafScheduled = false;near_focusedScreenRect(~:339).function notifyObjectUpdated(objectId)(after focusFrame/_applyWebframePresentationVisibility, before the return ~:411):if (!presentationMode) return;;if (!frames.length) return;getFrames()(use the in-memory array like the resize observer ~:121):var focused = frames[currentFrameIndex]; if (!focused) return;group.id()is updated in place byWhiteboardObjects.remapId):var fid = String(focused.id()); if (String(objectId) !== fid) return;syncDrawScheduledpattern (sync.js:893-899): if_refocusRafScheduledreturn; set it;requestAnimationFrame(function(){ _refocusRafScheduled=false; if(!presentationMode||!frames.length) return; var f=frames[currentFrameIndex]; if(!f) return; focusFrame(f); _emit(); });_applyingSync— that guard (frames.js:326-333) exists only to suppress_broadcastSlidein applyRemoteSlide; this path never calls_broadcastSlide, and_emitMUST run so the host repositions the spotlight.notifyObjectUpdated(andnotifyFrameRemovedfrom Step 5) in the module return.Dependencies: none
Step 2: Call from the remote sync path
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/sync.jsapplySyncUpdate, at the end of thetype === 'frame'branch (~:735-748; generic x/y/size/rotation already applied earlier ~:614-649), add:if (typeof WhiteboardFrames !== 'undefined' && WhiteboardFrames.notifyObjectUpdated) { WhiteboardFrames.notifyObjectUpdated(strId); }Precedent: the sibling-module call
WhiteboardConnectors.reAnchorByObject(strId)sync.js:888-890 (mirror guard shape; keep it inside the frame branch so it doesn't run for every type). The branch already runs under:595) so no echo possible._applyingSync = true(Dependencies: Step 1
Step 3: Local presenter drag path
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/objects.jsdragendhandler (~:1198-1210): afterWhiteboardSync.onUpdate(group)/ children loop, beforecapturedChildren = [], addif (typeof WhiteboardFrames !== 'undefined' && WhiteboardFrames.notifyObjectUpdated) { WhiteboardFrames.notifyObjectUpdated(group.id()); }. Hook self-guards on presentationMode + focused-id so it's inert during normal editing. (Originator skips its own broadcast viamsg.sender === localUserIdsync.js:152, so the remote hook does NOT cover the local presenter.)Dependencies: Step 1
Step 4: Local presenter resize/rotate path
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/tools.jstransformer.on('transformend', ...)(:270-287): inside the:286), addnodes.forEach, afterWhiteboardHistory.commitUpdate(node.id());(if (typeof WhiteboardFrames !== 'undefined' && WhiteboardFrames.notifyObjectUpdated) { WhiteboardFrames.notifyObjectUpdated(node.id()); }. focusFrame re-fits size+rotation via getClientRect.Dependencies: Step 1. Steps 2/3/4 parallelizable after Step 1.
Step 5: Graceful focused-frame-deleted handling
Files:
crates/hero_whiteboard_admin/static/web/js/whiteboard/frames.js,sync.jsnotifyFrameRemoved()to WhiteboardFrames: ifpresentationMode,getFrames()(rebuild from live.find('.frame')); ifframes.length === 0→showNoFramesNotice()+_focusedScreenRect = null; _emit();; else clampcurrentFrameIndex = Math.max(0, Math.min(currentFrameIndex, frames.length - 1)); focusFrame(frames[currentFrameIndex]); _emit();(clamp pattern from applyRemoteSlide ~:324).sync.jsobject.deletedframe branch (~:167-188) afterobj.group.destroy()/delete allObj[delId]:if (obj.type === 'frame' && WhiteboardFrames.notifyFrameRemoved) WhiteboardFrames.notifyFrameRemoved();focused.getStage && !focused.getStage()(destroyed node) — focusFrame already returns early when.bgis missing.Dependencies: Step 1
Step 6: Rebuild + verify
Files:
crates/hero_whiteboard_admin/src/assets.rstouchit;cargo build --release -p hero_whiteboard_admin; restart; confirm servedframes.jscontainsnotifyObjectUpdatedbefore testing (rust-embed embeds at compile time).Dependencies: Steps 1-5
Acceptance Criteria
Notes
WhiteboardFrames.notifyObjectUpdated(objectId)+notifyFrameRemoved(). Called from applySyncUpdate frame branch; precedentWhiteboardConnectors.reAnchorByObject(strId)~sync.js:888-890._applyingSyncwrap: it only suppresses_broadcastSlide(not called here);_emitmust run so the host spotlight tracks. sync.js-side_applyingSync=true(~:595) already blankets the remote path (onUpdate guard ~:458).syncDrawScheduledrAF one-shot (sync.js:893-899) with a dedicated_refocusRafScheduled; resolve the focused frame fresh inside the rAF so last drag wins._focusedScreenRect(~:369-375);_emitpasses it asstate.screenRectto the host subscriber (board.html positionSpotlight ~:567-625 / board_view.html ~:188-208), refreshing#pres-spotlightincl. rotation.touch crates/hero_whiteboard_admin/src/assets.rs,cargo build --release -p hero_whiteboard_admin, restart, verify served frames.js has the new export before testing.Test Results
cargo test --workspace --lib: ok (0 passed; 0 failed; 0 ignored across all crates) - no Rust regression
node --check (frames, sync, objects, tools): ok (all 4 files pass syntax check)
Note: #199 is a JS-only change (re-focus the presentation view when the focused frame's geometry changes). No JS unit harness exists in this repo; the Rust suite is the regression gate and the audience re-focus / spotlight tracking is verified manually in-browser.
Implementation Summary
JS-only. A new self-guarding WhiteboardFrames hook re-fits the presentation view (and the spotlight) when the currently focused frame's geometry changes, so the audience no longer loses the frame until reload. No server/schema change.
Changes
frames.js
sync.js
objects.js
tools.js
The hook self-guards, so all four call sites are inert during normal (non-presenting) editing and for non-focused objects.
Behavior after change
Tests