Frame drag does not move external widgets (calendar, kanban, mindmap, webframe) inside it #105
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#105
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
After issue #96 introduced explicit
parent_frame_idfor frame membership, dragging a frame correctly moves the seven core object types (sticky, text, shape, document, image, emoji, drawing) that are members of it. External widgets — calendar, kanban, mindmap, webframe — do not move with their containing frame, even when they are visually inside it.This was called out as a follow-up in the #96 summary; this ticket tracks doing the work.
Steps to reproduce
Expected
The widget moves with the frame, the same way a sticky note does.
Actual
The frame moves; the widget stays where it was. The widget effectively detaches from its visual container.
Root cause
The four external widget modules (
calendar.js,kanban.js,mindmap.js,webframe.js) own their own registry construction:objStore[id] = { group: group, type: 'webframe', url: url };(webframe.js:93)objStore[id] = { group: group, type: 'mindmap' };(mindmap.js:63)objStore[id] = { group: group, type: 'kanban' };(kanban.js:84)objStore[id] = { group: group, type: 'calendar', viewMode: viewMode };(calendar.js:59)None of them carry a
parent_frame_idfield. Issue #96 only extended the seven core types' registry entries (inobjects.js) and only calledrecomputeParentFrame(group)in those types'dragendhandlers. As a result, external widgets never get aparent_frame_idset, andcreateFrame's id-based capture loop indragstartnever picks them up.Suggested fix
For each of
calendar.js,kanban.js,mindmap.js,webframe.js:opts.parent_frame_idin the create function and add it to the registry entry:parent_frame_id: opts.parent_frame_id != null ? Number(opts.parent_frame_id) : null.opts.idis present (i.e. local user creation, not sync hydration), callWhiteboardObjects.recomputeParentFrame(group)(which would need to be exposed as a public export) so a widget dropped inside a frame's bounds gets its membership set.dragendhandler, after the existingWhiteboardSync.onUpdate(group)call, callif (WhiteboardObjects.recomputeParentFrame(group)) WhiteboardSync.onUpdate(group);so dragging the widget into / out of a frame transitions membership.Then export
recomputeParentFrameon theWhiteboardObjectspublic API. The existing_fromSyncsemantics inapplySyncUpdatealready update the registry'sparent_frame_idfrom server-broadcast payloads; that path is shared with the seven core types and needs no change.app.js::createObjectFromDataalready passesparent_frame_id: obj.parent_frame_idto every external widget'screate*call (added in #96), so once each module accepts the opt and stores it, server-driven hydration on initial load and onobject.createdbroadcasts will round-trip correctly.Acceptance criteria
parent_frame_id(next frame drag doesn't pick it up).parent_frame_idpropagate viaobject.updatedand the receiver's local registry is updated.Spec — Issue #105: Frame drag must move external widgets inside it
Objective
Make the four external widget modules (
calendar.js,kanban.js,mindmap.js,webframe.js) participate in id-based frame capture the same way the seven core types inobjects.jsalready do (post-#96). After this change, dragging a frame carries calendar/kanban/mindmap/webframe children with it, and dragging one of these widgets into or out of a frame updates itsparent_frame_idand broadcasts it.Requirements
parent_frame_idfield, initialized fromopts.parent_frame_id(so server-loaded widgets keep their server-side membership) ornullfor fresh creates.dragendhandler must callWhiteboardObjects.recomputeParentFrame(group)so dropping inside/outside a frame updates membership and triggers a sync.!opts.id), runrecomputeParentFrame(group)BEFOREWhiteboardSync.onCreate(group)so the broadcasted create payload includes the freshly-set parent.recomputeParentFramemust be exported on theWhiteboardObjectspublic API.sync.js::serializeForServerandapp.js::createObjectFromData).Files to Modify
crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.js— exportrecomputeParentFrame.crates/hero_whiteboard_ui/static/web/js/whiteboard/calendar.js,kanban.js,mindmap.js,webframe.js— two surgical edits each (dragend tail + registry-entry line + conditional recompute on local create).Files to Leave Alone
sync.js,app.js, server crates — already wired post-#96.kanban.jshandlers (column dragend, card dragend) — intra-board moves, kanban group itself doesn't move.webframe.jsstage-scopeddragend.wf_<id>listeners — handle iframe overlay during canvas pan/zoom, unrelated to membership.Step-by-Step Plan
Step 1 — Export
recomputeParentFramefromWhiteboardObjectsAdd
recomputeParentFrame: recomputeParentFrame,to the public API return block inobjects.js(e.g. afterredrawAllFrameBadges). Blocks Steps 2–5.Step 2 —
calendar.jsIn
createCalendar:WhiteboardSync.onUpdate(group);in the outer-groupdragend, append:WhiteboardSync.onCreate(group).Step 3 —
kanban.jsSame pattern in
createKanban's outer-groupdragendand registry-entry write. Do NOT touchcolTitle/cardGroupdragend handlers.Step 4 —
mindmap.jsSame pattern in
createMindmap.Step 5 —
webframe.jsSame pattern in
createWebframe. Keep the existing dragend ordering:showOverlay→commitUpdate→onUpdate, then append the recompute branch. Do NOT touch the stage-scopeddragend.wf_<id>listeners.Parallelism
objects.jsbefore the widget files.Acceptance Criteria
parent_frame_idbecomes that frame's id; subsequently dragging the frame moves the calendar. Same for kanban, mindmap, webframe.parent_frame_idbecomesnulland a sync update is sent.parent_frame_id, and the broadcastobject.createpayload carries it (verified via network or another client).parent_frame_idkeeps the membership through reload — frame drag still captures them.frametype itself behave exactly as before — no regressions.Notes
objects.jsgets one new line in the export block.recomputeParentFrameearly-returns fortype === 'frame', so frames stay out of recursive parenting.parent_frame_idalready round-trips end-to-end.Test Results
Integration tests in hero_whiteboard_examples require a running hero_proc (per project CLAUDE.md) and were not run in this sandbox — unrelated to this JS-only change.
Implementation Summary
Five files changed, +21/-5. The seven core types in
objects.jsalready participate in id-based frame capture (post-#96); this change extends the same plumbing to the four external widget modules.crates/hero_whiteboard_ui/static/web/js/whiteboard/objects.jsAdded
recomputeParentFrame: recomputeParentFrame,to the public API export block so the widget modules can reach the helper.calendar.js,kanban.js,mindmap.js,webframe.jsSame two surgical edits in each, inside their respective create function (
createCalendar,createKanban,createMindmap,createWebframe):After
WhiteboardSync.onUpdate(group);in the outer-groupdragend, append:Persists and broadcasts a membership change when the user drags the widget into or out of a frame.
Extend the registry-entry write to include
parent_frame_id, then runrecomputeParentFrameon local creates beforeWhiteboardSync.onCreate(group):Sync-hydration paths (which always pass
opts.id) skip the recompute and trust the server-suppliedparent_frame_id. Fresh local creates inherit membership from whichever frame contains the new widget's bounding rect.webframe.js's outer-group dragend keeps its existing order (showOverlay → commitUpdate → onUpdate) and appends the recompute branch last. The stage-scopeddragend.wf_<id>listeners (used for the iframe overlay during canvas pan/zoom) are intentionally untouched.kanban.js's innercolTitleandcardGroupdragend handlers (intra-board moves) are intentionally untouched.Verification
cargo fmt --all -- --check: cleancargo check --workspace: cleancargo clippy --workspace -- -D warnings: cleancargo test -p hero_whiteboard_server: 3 passed, 0 failednode --checkonobjects.js,calendar.js,kanban.js,mindmap.js,webframe.js: all cleanThe two integration tests in
hero_whiteboard_examplesrequire a runninghero_proc(per project CLAUDE.md) and were not run in this sandbox — they are unrelated to this JS-only change.Manual smoke test
object.updated.Notes / scope
recomputeParentFrameearly-returns fortype === 'frame', preserving the no-recursive-parenting invariant.parent_frame_idalready round-trips end-to-end throughserializeForServer,applySyncUpdate, andapp.js::createObjectFromData.webframe.js's recent #101 (sync), #102 (presentation), #103 (rotation), and #104 (delete cleanup) fixes are unaffected.