Shape type changes do not propagate to other open viewers of the same board until they reload #91
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
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lhumina_code/hero_whiteboard#91
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
Changing the Shape type of an existing shape (e.g. Rectangle → Ellipse)
in the Properties panel does not propagate to other clients viewing the same
board in real time. The other client keeps rendering the previous shape type
until the page is reloaded.
Steps to reproduce
Strokecolor via theProperties panel — observe that tab B updates immediately.
ShapefromRectangleto
Ellipse(or any other shape type) in the Properties panel.Expected
Tab B updates the rendered shape to an Ellipse in real time, the same way
it received the stroke/fill color changes.
Actual
Ellipse after manually reloading tab B.
Implementation Spec — Issue #91: Shape type changes must propagate live
Objective
Make Shape-type changes (e.g. Rectangle → Ellipse via the Properties panel) propagate live to all other open viewers of the same board, replacing the underlying Konva node so the rendered shape actually changes — not just its style attributes.
Root Cause
properties.js:678-686wires#prop-shape-typetoWhiteboardObjects.changeShapeType(...), which (inobjects.js:699-783) destroys the old.bgKonva node, creates a new one of the correct class, and callsWhiteboardSync.onUpdate(group).serializeForServeralready serializesdata.shapeType, so the new type is persisted server-side AND broadcast over WebSocket asobject.updated.sync.jsapplySyncUpdatethetype === 'shape'branch (lines 546-564) only patchesstyle.fill/style.stroke/style.strokeWidthand rerenders text. It never readsdata.shapeTypeand never swaps the underlying Konva node. Because aKonva.Rectcannot morph into aKonva.Ellipse(different classes), the remote viewer keeps rendering the old shape until full reload, whereloadSingleObject→WhiteboardApp.renderSingleObjectrebuilds the node fromshapeType.Requirements
shapeTypemust propagate live to all other open viewers within the existing debounce window.loadSingleObject).WhiteboardSync/object.updatedtransport. No new transports, no new dependencies.Files to Modify
crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js— extendchangeShapeTypeto accept anoptsargument so the receiver can call it without re-broadcasting or pushing history.crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js— inapplySyncUpdate'stype === 'shape'branch, detect adata.shapeTypemismatch and callWhiteboardObjects.changeShapeTypewith the no-broadcast option before applying style/text patches.Files to Leave Alone
properties.js— sender already works.app.js— initial render path (renderSingleObject) already handles shape kinds correctly.shapeTypeindata. No backend change required.applySyncUpdate(sticky, text, document, frame, kanban, mindmap, etc.) — out of scope.Step-by-Step Plan
Step 1 — Extend
changeShapeTypeinobjects.jsto support a no-broadcast modeFile:
crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js— functionchangeShapeType(group, newShapeType)at line 699.changeShapeType(group, newShapeType, opts). Treatoptsas optional{}.WhiteboardHistory.snapshotBefore(group.id())so it only runs when!opts._fromSync.WhiteboardSync.onUpdate(group)andWhiteboardHistory.commitUpdate(group.id())(lines 781-782) the same way.objData.shapeType === newShapeType,return.The change is backward-compatible: the existing single-arg call site in
properties.js:683still works.Dependencies: none.
Step 2 — Handle
data.shapeTypechanges inapplySyncUpdateFile:
crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.js— functionapplySyncUpdate(obj), branchtype === 'shape'(around line 546).Insert at the very top of the
type === 'shape'branch, before the existing style/text patch:Then let the existing style/text patch logic run on the (refreshed)
bg.Notes:
existing.shapeTypeis set inobjects.jscreateShapeand updated bychangeShapeType, so the comparison is reliable.bgis captured earlier inapplySyncUpdateand used by the dimensions block and style block. Reassigning is required becausechangeShapeTypedestroys and recreates the.bgnode.changeShapeTypereads dimensions from the (already resized) old bg before destroying it, so the new shape inherits the correct width/height._applyingSyncguard already preventsWhiteboardSync.onUpdatefrom re-broadcasting, but the explicit_fromSyncflag is cleaner and also blocks history pollution.Dependencies: Step 1.
Acceptance Criteria
rect,rounded_rect,circle,ellipse,diamond,triangle,pentagon,hexagon,star,cloud,callout,cylinder,parallelogram,cross.Notes / Caveats / Out of Scope
data.shapeTypeis already on the wire; the fix is purely receiver-side rendering plus a small refactor ofchangeShapeType.Konva.Rectcannot becomeKonva.Ellipsein place — different classes with different geometry primitives. Destroy + recreate is the correct approach, mirroring what the localchangeShapeTypealready does._fromSyncguard ensures that.onUpdatealready debounces and batches viaflushUpdates— no change needed.changeShapeTypealready handles aspect-ratio stretching for these.hero_whiteboard_serveralready round-tripsdataopaquely; no schema/migration change required.Test Results
Implementation Summary
Two files changed, JS-only — no server / SDK / OpenRPC / DB changes. Reuses the existing
object.updatedbroadcast; the wire format is unchanged because the sender already serializesdata.shapeType.Root cause
The receiver path in
sync.jsapplySyncUpdatepatched style fields on the existing.bgKonva node but never swapped the node whenshapeTypechanged. AKonva.RectandKonva.Ellipseare different classes, so until a full reload the remote viewer kept rendering the old shape.crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.jschangeShapeType(group, newShapeType)is nowchangeShapeType(group, newShapeType, opts)withopts = opts || {}. The function is unchanged for local callers (optsis optional and falsy means "behave as before").returnifobjData.shapeType === newShapeType— no churn when the type already matches.WhiteboardHistory.snapshotBefore(group.id())and the trailingWhiteboardSync.onUpdate(group)+WhiteboardHistory.commitUpdate(group.id())inif (!opts._fromSync) { ... }, so a sync-driven swap doesn't push a local history entry or re-broadcast.crates/hero_whiteboard_ui/static/web/js/whiteboard/sync.jsapplySyncUpdate, at the top of thetype === 'shape'branch, before the existing style/text patch:bgbecausechangeShapeTypedestroys the old.bgand creates a new one of the right class. The existing style/text patches then run on the newbg.Verification
cargo fmt --all -- --check: cleancargo check --workspace: cleancargo clippy --workspace -- -D warnings: cleancargo test --workspace --lib: passnode --checkon both modified JS modules: cleanManual smoke test
Notes
data.shapeTypewas already on the wire; the bug was purely that the receiver never acted on it._fromSyncguard is defense-in-depth on top of the existing_applyingSyncflag — it specifically blocks history pollution and is symmetric with the same pattern used elsewhere insync.js/themes.js/connectors.js.