Web frame: programmatic zoom (toolbar / keyboard / fit) leaves the iframe overlay at the old size #106
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#106
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
Zooming the canvas via any path other than the mouse wheel leaves the iframe DOM overlay at its old size and screen position. The Konva placeholder card rescales correctly; the iframe content stays at the original 1.0 scale, often stranded inside a now-smaller-or-larger placeholder until a full page reload.
Steps to reproduce
-/ use the zoom slider).Expected
The iframe overlay tracks the new stage scale and position on every zoom change, just like it does for mouse-wheel zoom.
Actual
The iframe stays at the previous screen rect; only the placeholder is rescaled. A page reload — or any subsequent mouse-wheel zoom — brings them back into alignment.
Root cause
WhiteboardCanvas.setZoom(newScale)(incanvas.js) callsstage.scale({...})andstage.position({...})programmatically. Neither call fires Konva'swheelevent, which is the only zoom hookwebframe.jsinstalls:Every programmatic zoom path therefore bypasses the overlay refresh:
app.js:621/626/636)+/-shortcuts (per CLAUDE.md)focusFrame(frames.js:164) — already worked around in #102 by hiding all overlays during presentation, but the underlying bug is the same#101 fixed the receiver-side overlay tracking on remote position/size updates, but the local programmatic-zoom path was never wired.
Suggested fix
Smallest reliable fix: at the end of
setZoomincanvas.js, call a newWhiteboardWebframe.refreshAllOverlays()helper that walks every registered overlay and re-runsupdateOverlayPosition(essentiallyObject.keys(overlays).forEach(refreshOverlay)).Add the helper to
webframe.jsnext tohideAllOverlays/showAllOverlays:…and export it. Then in
canvas.js::setZoom, aftersaveView(), call:refreshOverlaydoesn't toggle_hiddenForInteraction, so any overlay that's intentionally hidden (e.g. during a remote drag in flight) stays hidden — only its target screen rect is refreshed for when it's next shown.Optional follow-up: have
setZoomstage.fire('zoomchange')and let webframe.js install astage.on('zoomchange.wf_<id>')listener, decoupling the modules. Out of scope for this issue.Acceptance criteria
+/-zoom keeps the overlay tracked.Spec — Issue #106: Programmatic zoom must refresh webframe iframe overlays
Objective
Make every programmatic zoom path (
WhiteboardCanvas.setZoom) update the webframe iframe DOM overlay so it tracks the new stage transform, just like wheel zoom does today.Root cause
canvas.js::setZoom(newScale)callsstage.scale(...)andstage.position(...)directly — neither fires Konva'swheelevent, which is the only zoom hook installed bywebframe.js. Every non-wheel zoom path (toolbar buttons, keyboard+/-, presentationfocusFrame, etc.) therefore leaves overlays at their old screen rect.Fix — add
refreshAllOverlays()and call it fromsetZoomSmallest reliable approach.
webframe.jsalready exposes a per-idrefreshOverlay(id)that callsupdateOverlayPositionwithout toggling_hiddenForInteraction. Add a sibling that walks all overlays.Files to Modify
crates/hero_whiteboard_ui/static/web/js/whiteboard/webframe.js— addrefreshAllOverlays()helper and export it.crates/hero_whiteboard_ui/static/web/js/whiteboard/canvas.js— call it at the end ofsetZoom.Step-by-Step Plan
Step 1 — Add
refreshAllOverlaystowebframe.jsNext to
hideAllOverlays/showAllOverlays:Add
refreshAllOverlays: refreshAllOverlays,to the public API export block.Step 2 — Call it from
canvas.js::setZoomAfter
saveView()at the end ofsetZoom:Acceptance Criteria
+/-zoom keeps overlays tracked.focusFramestill works correctly — overlays inside the focused frame land at the new viewport coordinates (composes with #102).Notes
refreshOverlaydoes not toggle_hiddenForInteraction, so an overlay that's intentionally hidden (drag in flight, presentation hide) stays hidden — only its underlying screen rect is refreshed for when it's next shown.stage.fire('zoomchange')would be cleaner long-term but is out of scope for this fix.Test Results
Implementation Summary
Two files changed, +8/-0.
crates/hero_whiteboard_ui/static/web/js/whiteboard/webframe.jsrefreshAllOverlays(): walks every registered overlay id and calls the existing per-idrefreshOverlay(id). LikerefreshOverlay, it does not toggle_hiddenForInteraction, so overlays that are intentionally hidden (drag in flight, presentation hide) stay hidden — only their underlying screen rect is refreshed for when they're next shown.crates/hero_whiteboard_ui/static/web/js/whiteboard/canvas.jssetZoom, aftersaveView(): Covers every programmatic zoom path (toolbar buttons, keyboard+/-, presentationfocusFrame, fit-to-content, minimap navigation).Verification
cargo fmt --all -- --check: cleancargo check --workspace: cleannode --check webframe.js: cleannode --check canvas.js: cleanManual smoke test
+/-keyboard shortcuts — same.Notes
refreshOverlayis a no-op for ids without a registered overlay, so callingrefreshAllOverlayson a board with zero webframes is free.stage.fire('zoomchange')event would be cleaner long-term but is out of scope for this fix.