Frontmatter
| id | 8156 |
| title | Implement Cross-Window Drag Coordinator |
| state | Closed |
| labels | enhancementaiarchitecture |
| assignees | tobiu |
| createdAt | Dec 26, 2025, 4:58 PM |
| updatedAt | Dec 27, 2025, 11:53 PM |
| githubUrl | https://github.com/neomjs/neo/issues/8156 |
| author | tobiu |
| commentsCount | 5 |
| parentIssue | null |
| subIssues | [] |
| subIssuesCompleted | 0 |
| subIssuesTotal | 0 |
| blockedBy | [x] 8159 Refactor Cross-Window Drag Logic to Dashboard SortZone |
| blocking | [] |
| closedAt | Dec 27, 2025, 11:53 PM |
Implement Cross-Window Drag Coordinator

Input from Gemini 3:
✦ ## Context & Prerequisites Update
We have successfully completed the foundational refactoring required for this ticket (see #8155 and #8157).
Current State:
Neo.dashboard.Container: Now natively supports the detached item lifecycle (detach-to-window, re-integrate). It tracks detached items via adetachedItemsMap and listens toNeo.currentWorkerfor window connection events.Neo.dashboard.Panel: A new class created to encapsulate dashboard item configs, specificallypopupUrl, enabling different app shells for different widgets.- App Refactoring: Both
AgentOSandColorsapps have been refactored to use this new architecture, removing their manual window management logic.Architectural Plan for #8156: With the "Popup Proxy" mechanism now standardized, we can proceed with the Cross-Window Drag Coordinator.
Neo.manager.DragCoordinator(Singleton in App Worker):
- Registry: Dashboards will register themselves with a
sortGroupidentifier.- Translation: It will use
Neo.manager.Window(the "God View") to hit-test global screen coordinates against registered window rectangles.- Delegation: When a popup (controlled by the main thread drag) hovers over a different dashboard window, the Coordinator will signal that target dashboard's
SortZoneto render a placeholder.Flow:
- Source: Dashboard A (Window 1) -> Item detached -> Popup Proxy.
- Transit: Popup Proxy dragged over Window 2.
- Target: Dashboard B (Window 2) detects "Remote Drag" via Coordinator.
- Drop: Item component is reparented from Dashboard A to Dashboard B. VDOM engine handles the cross-window DOM move.
Next Step: Start implementation of
Neo.manager.DragCoordinator.

Input from Gemini 3:
✦ # Session Handoff: Cross-Window Drag Implementation Status
We have made significant architectural progress but introduced regressions in the core drag logic. This comment serves as the recovery plan for the next session.
1. Implemented Architecture (Do Not Discard)
We established a robust "Handover Protocol" for dragging items between windows:
Neo.manager.DragCoordinator: Implemented. Acts as the traffic controller.
- Detects when a drag leaves the Source Window and enters a Target Window.
- Orchestrates the handover.
Neo.dashboard.Container:
- Added
suspendWindowDrag(widgetName): Closes the popup and stops the Source's window-drag mode.- Added
resumeWindowDrag(widgetName, proxyRect): Re-opens the popup if the drag leaves the Target back into the void.- Sort Groups: Both
AgentOSandColorsapps now usesortGroup: 'neo-connected-dashboard'.Neo.draggable.container.SortZone(New Logic Only):
- Registers/Unregisters with Coordinator.
startRemoteDrag: Designed to create a visible local proxy in the Target, simulating a native drag.onRemoteDragMove/Drop: Handles the target-side lifecycle.2. Resolved Concepts
- The "Visible Proxy" Handover: We agreed that the Source "Popup Window" should vanish upon entering the Target, replaced instantly by a local, visible
DragProxyin the Target. This provides the best UX (looks like a native in-app drag).- State Ownership: The Source Dashboard MUST be the one to call
suspendWindowDragbecause it owns thedetachedItemsmap and the context to talk to the correctDragDropaddon instance.3. Critical Regressions & Open Issues (Immediate Actions Required)
The current state of
src/draggable/container/SortZone.mjsis broken.
- Local Drag Broken: In our rush, we modified core methods like
applyAbsolutePositioningandsetupDragState, breaking standard local drag operations (positioning drift,adjustItemRectsToParentissues).
- Recovery: Compare
SortZone.mjswithtmpSortZone.mjs(the pre-session backup). Revert the core positioning logic to the backup version. Only re-apply the Coordinator hooks (register,suspend,resume) and the new remote methods (startRemoteDrag, etc.).onDragEndErrors: We are seeingTypeError: Cannot read properties of null (reading 'indexOf')inonDragEnd. This indicates state corruption (likelysortableItemsordragComponentbecoming null/invalid).- Code Quality: There are unused parameters (e.g., in
applyAbsolutePositioning) and messy logic. Needs a strict cleanup.- Window Manager Initialization:
Neo.manager.Window.getWindowAt()fails for new windows becauseWindowmanager doesn't fetch initial DOM rects onconnect.
- Action: Update
Neo.manager.Windowto query window bounds immediately upon registration.Next Steps
- Restore SortZone: Use
tmpSortZone.mjsto fix the regressions inSortZone.mjs.- Fix Window Manager: Ensure
Neo.manager.Windowhas valid rects for new windows.- Test Handover: Verify the
Coordinator->suspend->startRemoteDragflow with working positioning.

Input from gemini:
✦ ## Implementation Update & Architectural Refinement
We have successfully refactored the Drag & Drop architecture to support cross-window operations while maintaining a clean separation of concerns.
Key Changes from Original Design:
Refactored
container/SortZone.mjs(Base Class):
- Role: Now acts as a lightweight "detector" for drag operations.
- Window Detachment: It detects when an item is dragged out of bounds (if
enableProxyToPopupis true) and fires thedragBoundaryExitevent. It explicitly does not implement the window opening or layout calculation logic (startWindowDrag,calculateExpandedLayouthave been removed).- Cleanup: All dependencies on
DragCoordinatorand remote drag handling were removed.Enhanced
dashboard/SortZone.mjs(Subclass):
- Role: Provides the concrete implementation for dashboard-specific drag behaviors.
- Window Drag: Implements
startWindowDragandcalculateExpandedLayoutto handle the "detach to window" workflow triggered by the base class events.- Cross-Window Coordination: Registers with the
DragCoordinatorand implements theonRemote*methods (startRemoteDrag,onRemoteDragMove, etc.) to handle drags entering from other windows.- Duplication Strategy: Some layout logic (
calculateExpandedLayout) was intentionally placed here to isolate the complex dashboard behavior from the generic container.Result: This architecture allows standard containers to remain simple while giving Dashboards the specialized ability to handle window detachment and cross-application coordination. The
DragCoordinatornow interacts exclusively with theDashboardSortZone.

Input from Gemini 2.5:
✦ ✦ ## Cross-Window Drag Proxy & Coordination Logic Fixed
We have resolved several critical issues impacting the positioning, sizing, and movement of the cross-window drag proxy.
Key Fixes Implemented:
Proxy Sizing & Layout Thrashing:
- Issue: The drag proxy was defaulting to the full width of the source window/container (775px) instead of the target item width (716px), causing layout distortion.
- Fix: Updated
DashboardSortZone.startRemoteDragto create thedragPlaceholderwithflex: 'none'and no fixed width. This allows it to naturally adopt the target container's layout dimensions (e.g., 716px) beforesetupDragStatemeasures it. The proxy size is then explicitly set to match this measured placeholder size.Vertical Positioning (Browser Chrome Gap):
- Issue: The proxy appeared vertically offset (lower) than the mouse cursor.
- Root Cause:
DragCoordinatorwas calculating coordinates relative to the window frame (screenTop), while the proxy (indocument.body) is positioned relative to the viewport. The difference is the browser chrome height.- Fix: Updated
Neo.manager.Windowto calculate and storeheaderHeight(outerHeight - innerHeight). UpdatedDragCoordinator.onDragMoveto subtract thisheaderHeightfromlocalY, correctly mapping screen coordinates to the viewport.Horizontal Positioning & Sorting Logic:
- Issue: Adjusting the proxy's visual position (
left) independently of the logical drag offset (offsetX) caused the sorting logic (onDragMove) to miscalculate the item's position relative to slots, triggering re-ordering too early or late.- Fix: Synchronized the visual and logical coordinates. In
startRemoteDrag, we now add the dashboard's offset (ownerRect) tome.offsetXandme.offsetY. This ensures that both the visual style calculation (localX - me.offsetX) and the sorting delta (clientX - me.offsetX - itemRect.left) operate on the same consistent coordinate basis, incorporating the "divergence" between the viewport and the dashboard container.Remote Drag Movement:
- Issue: The proxy was not updating its position during the drag.
- Root Cause:
DragZone.dragMovedefaults to skipping updates ifmoveInMainThreadis true (which is the default forDashboardSortZone).- Fix: Enhanced
DragZone.dragMoveto accept aforceparameter. UpdatedDashboardSortZone.onRemoteDragMoveto callme.dragMove(..., true), forcing the worker-side update for remote drags without needing to toggle instance configuration state.Current Status: The cross-window drag operation now correctly creates a proxy that:
- Matches the target dashboard's column width.
- Aligns perfectly with the mouse cursor (accounting for window chrome and offsets).
- Updates position smoothly during the drag.
- Triggers sorting re-orders at the expected visual thresholds.

closing the ticket, since we reached the poc state.
Objective Enable seamless drag-and-drop operations between different browser windows (e.g., dragging a widget from the AgentOS window into the Colors window). This requires a coordinator to map global screen coordinates to specific dashboard instances across the shared App Worker.
Architecture
Neo.manager.DragCoordinator(New Singleton):sortGroupidentifier.Coordinate Mapping:
Neo.manager.Window.getWindowAt(screenX, screenY)to identify the target browser window under the cursor.Remote Drag Handling:
SortZone.SortZonerenders a placeholder as if it were a local drag.Drop Handling: