@@ -77,14 +77,21 @@ export const useDragDropHandlers = (taskId: string) => {
7777
7878 const { tabId, panelId : sourcePanelId } = sourceData ;
7979
80+ // Defer structural tree changes to the next frame so @dnd -kit can finish
81+ // its DOM cleanup first. Without this, React tries to removeChild on nodes
82+ // that @dnd -kit has already repositioned, causing a DOM exception.
83+ const applyMove = ( fn : ( ) => void ) => requestAnimationFrame ( fn ) ;
84+
8085 // Handle drop on tab bar or on a tab in a different panel -> move tab
8186 if (
8287 ( targetData ?. type === "tab-bar" || targetData ?. type === "tab" ) &&
8388 targetData . panelId &&
8489 targetData . panelId !== sourcePanelId
8590 ) {
86- moveTab ( taskId , tabId , sourcePanelId , targetData . panelId ) ;
87- setFocusedPanel ( taskId , targetData . panelId ) ;
91+ applyMove ( ( ) => {
92+ moveTab ( taskId , tabId , sourcePanelId , targetData . panelId ) ;
93+ setFocusedPanel ( taskId , targetData . panelId ) ;
94+ } ) ;
8895 return ;
8996 }
9097
@@ -100,13 +107,15 @@ export const useDragDropHandlers = (taskId: string) => {
100107 const { panelId : targetPanelId , zone } = targetData ;
101108
102109 if ( zone === "center" ) {
103- moveTab ( taskId , tabId , sourcePanelId , targetPanelId ) ;
104- setFocusedPanel ( taskId , targetPanelId ) ;
110+ applyMove ( ( ) => {
111+ moveTab ( taskId , tabId , sourcePanelId , targetPanelId ) ;
112+ setFocusedPanel ( taskId , targetPanelId ) ;
113+ } ) ;
105114 } else if ( isSplitDirection ( zone ) ) {
106- splitPanel ( taskId , tabId , sourcePanelId , targetPanelId , zone ) ;
107- // For splits, the new panel gets a generated ID, so we can't easily focus it here
108- // The target panel remains focused which is reasonable behavior
109- setFocusedPanel ( taskId , targetPanelId ) ;
115+ applyMove ( ( ) => {
116+ splitPanel ( taskId , tabId , sourcePanelId , targetPanelId , zone ) ;
117+ setFocusedPanel ( taskId , targetPanelId ) ;
118+ } ) ;
110119 }
111120 } ;
112121
0 commit comments