@@ -227,6 +227,8 @@ interface BlockData {
227227 * Renders the ReactFlow canvas with blocks, edges, and all interactive features.
228228 */
229229const WorkflowContent = React . memo ( ( ) => {
230+ const workflowRootRef = useRef < HTMLDivElement | null > ( null )
231+ const viewportMoveLogCountRef = useRef ( 0 )
230232 const [ isCanvasReady , setIsCanvasReady ] = useState ( false )
231233 const [ potentialParentId , setPotentialParentId ] = useState < string | null > ( null )
232234 const [ selectedEdges , setSelectedEdges ] = useState < SelectedEdgesMap > ( new Map ( ) )
@@ -255,6 +257,52 @@ const WorkflowContent = React.memo(() => {
255257
256258 const addNotification = useNotificationStore ( ( state ) => state . addNotification )
257259
260+ const logWorkflowLayoutSnapshot = useCallback (
261+ (
262+ hypothesisId : 'H8' | 'H9' | 'H10' | 'H11' ,
263+ message : string ,
264+ extraData : Record < string , unknown >
265+ ) => {
266+ const workflowRoot = workflowRootRef . current
267+ const flowContainer = document . querySelector ( '.workflow-container' ) as HTMLElement | null
268+ const openDialogs = document . querySelectorAll ( '[role="dialog"][data-state="open"]' ) . length
269+ const bodyStyle = document . body . style
270+
271+ // #region agent log
272+ fetch ( 'http://127.0.0.1:7243/ingest/77a2b2bc-808d-4bfd-a366-739b0b04635d' , {
273+ method : 'POST' ,
274+ headers : { 'Content-Type' : 'application/json' } ,
275+ body : JSON . stringify ( {
276+ runId : 'initial' ,
277+ hypothesisId,
278+ location : 'workflow.tsx:WorkflowContent' ,
279+ message,
280+ data : {
281+ openDialogs,
282+ windowInnerHeight : window . innerHeight ,
283+ windowInnerWidth : window . innerWidth ,
284+ pageScrollY : window . scrollY ,
285+ visualViewportHeight : window . visualViewport ?. height ?? null ,
286+ visualViewportOffsetTop : window . visualViewport ?. offsetTop ?? null ,
287+ bodyOverflow : bodyStyle . overflow || null ,
288+ bodyPaddingRight : bodyStyle . paddingRight || null ,
289+ bodyPosition : bodyStyle . position || null ,
290+ bodyTop : bodyStyle . top || null ,
291+ workflowRootTop : workflowRoot ?. getBoundingClientRect ( ) . top ?? null ,
292+ workflowRootHeight : workflowRoot ?. getBoundingClientRect ( ) . height ?? null ,
293+ flowContainerTop : flowContainer ?. getBoundingClientRect ( ) . top ?? null ,
294+ flowContainerHeight : flowContainer ?. getBoundingClientRect ( ) . height ?? null ,
295+ reactFlowViewport : reactFlowInstance . getViewport ( ) ,
296+ ...extraData ,
297+ } ,
298+ timestamp : Date . now ( ) ,
299+ } ) ,
300+ } ) . catch ( ( ) => { } )
301+ // #endregion
302+ } ,
303+ [ reactFlowInstance ]
304+ )
305+
258306 const {
259307 workflows,
260308 activeWorkflowId,
@@ -1068,6 +1116,43 @@ const WorkflowContent = React.memo(() => {
10681116 isPointInLoopNode ,
10691117 ] )
10701118
1119+ useEffect ( ( ) => {
1120+ const observer = new MutationObserver ( ( ) => {
1121+ const openDialogs = document . querySelectorAll ( '[role="dialog"][data-state="open"]' ) . length
1122+ logWorkflowLayoutSnapshot ( 'H8' , 'Dialog/open-state mutation observed in workflow page' , {
1123+ openDialogs,
1124+ } )
1125+
1126+ requestAnimationFrame ( ( ) => {
1127+ logWorkflowLayoutSnapshot ( 'H11' , 'Next-frame workflow layout after dialog mutation' , {
1128+ openDialogs,
1129+ } )
1130+ } )
1131+ } )
1132+
1133+ observer . observe ( document . body , {
1134+ subtree : true ,
1135+ attributes : true ,
1136+ childList : true ,
1137+ attributeFilter : [ 'data-state' , 'style' ] ,
1138+ } )
1139+
1140+ return ( ) => observer . disconnect ( )
1141+ } , [ logWorkflowLayoutSnapshot ] )
1142+
1143+ useEffect ( ( ) => {
1144+ const viewport = window . visualViewport
1145+ if ( ! viewport ) return
1146+
1147+ const onViewportResize = ( ) => {
1148+ if ( document . querySelectorAll ( '[role="dialog"][data-state="open"]' ) . length === 0 ) return
1149+ logWorkflowLayoutSnapshot ( 'H10' , 'visualViewport resize while modal open' , { } )
1150+ }
1151+
1152+ viewport . addEventListener ( 'resize' , onViewportResize )
1153+ return ( ) => viewport . removeEventListener ( 'resize' , onViewportResize )
1154+ } , [ logWorkflowLayoutSnapshot ] )
1155+
10711156 const handleContextDuplicate = useCallback ( ( ) => {
10721157 copyBlocks ( contextMenuBlocks . map ( ( b ) => b . id ) )
10731158 executePasteOperation ( 'duplicate' , DEFAULT_PASTE_OFFSET )
@@ -3485,7 +3570,7 @@ const WorkflowContent = React.memo(() => {
34853570 ] )
34863571
34873572 return (
3488- < div className = 'flex h-full w-full flex-col overflow-hidden' >
3573+ < div ref = { workflowRootRef } className = 'flex h-full w-full flex-col overflow-hidden' >
34893574 < div className = 'relative h-full w-full flex-1' >
34903575 { /* Loading spinner - always mounted, animation paused when hidden to avoid overhead */ }
34913576 < div
@@ -3569,6 +3654,18 @@ const WorkflowContent = React.memo(() => {
35693654 elevateNodesOnSelect = { true }
35703655 autoPanOnConnect = { effectivePermissions . canEdit }
35713656 autoPanOnNodeDrag = { effectivePermissions . canEdit }
3657+ onMoveEnd = { ( _ , viewport ) => {
3658+ if ( viewportMoveLogCountRef . current >= 8 ) return
3659+ viewportMoveLogCountRef . current += 1
3660+ const openDialogs = document . querySelectorAll (
3661+ '[role="dialog"][data-state="open"]'
3662+ ) . length
3663+ logWorkflowLayoutSnapshot ( 'H9' , 'Main workflow ReactFlow viewport moved' , {
3664+ viewport,
3665+ moveLogCount : viewportMoveLogCountRef . current ,
3666+ openDialogs,
3667+ } )
3668+ } }
35723669 />
35733670
35743671 < Cursors />
0 commit comments