Unexpected behavior with Turbo redirect/morph and Turbo frame

I have a Turbo modal setup in my application that opens when the inner turbo frame src is set and loaded. Here’s the important bits: (using Alpine)

<div x-data="{ open: false }" @turbo:frame-load="if ($event.target.id === 'turbo-frame-modal') open = true;">
  <%= turbo_frame_tag "turbo-frame-modal", target: "_top", "x-turbo-frame-src-observer":"", "@turbo:before-morph-attribute": "console.log($event.detail.attributeName, $event.detail.mutationType)" %>
</div>

I use it to load a form lets say a /edit page. In the controller on success, I do a redirect, and Turbo updates the document via a morph operation. The markup for the new page that is being morphed has the turbo frame without a src (since it hasn’t been triggered via a link). The expected behavior is that the morph gets applied and the Modal doesn’t open because nothing gets loaded in the frame.

Whats happening is the morph gets applied removing the frames src and then immediately after, the previous src gets set on the turbo frame, triggering the modal again. I have some logs setup to trace whats going on.

From the logs the morph operation removes the src attribute, the turbo:render event gets fired after the morph is completed, and then the src gets set back to the previous url before the morph.

frame src change: null
turbo:render renderMethod=morph
frame src change: https://app.local/meetings/b8c70d46-b953-4190-b4ee-a8b335617532/edit

Listening to events, logging and tracing isn’t showing me from where this attribute change is coming from. The logs from the turbo:before-morph-attribute event show the first changes that remove the src and nothing for when it gets set again. Really struggling to pin this down.

Wondering if anyone has any experience with this or has any ideas on how to keep it from happening. Hacking away on it I wrote some code that clones the turbo frame after the morph, removes it from the document, and then appends it back. That worked but doesn’t feel like a legit solution.

I am not sure what is going on from what you just wrote but maybe I have a hunch, depending on more details of what exactly you are doing.

When you return the new content to morph into, are you returning the full page with the HTML tag and everything or are you rendering just the content, without the layout?

I’m asking because the way idiomorph works is that it will try to find the best match for the new content in the old content and then morph that. If you return the full HTML document the match is trivial and it will morph the full content. If you return just the new body, without the content and it has an id. It will find that same id in the old content and then morph that. At the end, it will insert the old content that was around the old element around the new content.

The reason my mind went to that is because it’s a place where the algorithm will perform and insert and it happens after all the morphing is done. Also, modals are often rendered at the top level, as children of the body tag, alongside another div which is the full content.

It would be easier to tell if that is the case if I could see more of the markup: The markup of the page when the form is submitted and the new markup that is fetched after the redirect.

@Radan_Skoric thanks for the reply. I ended up opening an issue because I can’t think of any scenario where this would be wanted. I included a simple stimulus repro and quick screen recording Unexpected behavior when morphing a turbo frame src · Issue #1158 · hotwired/turbo · GitHub

As for your question, when you do a redirect from a controller with Turbo drive enabled, the ajax request returns the full document markup for the page you are redirecting to and uses it to apply either a replace or morph (depending on what you have configured)