Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bridgeDidIgnoreMessage triggered after FormSubmission on modal #83

Open
williamkennedy opened this issue Feb 14, 2025 · 2 comments
Open

Comments

@williamkennedy
Copy link

williamkennedy commented Feb 14, 2025

Intro

Great work on hotwire-native-ios. The evolution from turbo-ios has been a joy to work with.

I'm unsure if this problem is a stimulus or a hotwire-native-ios issue. I have not tried to replicate this on Android just yet.

Update

After further debugging, I believe it has to do with the destination lifecycle in the BridgeDelegate.

Behavior

When submitting a form with success and redirect, the bridge component raises the error message bridgeDidIgnoreMessage. It seems that the message is rejected and called from this line

This means the bridge does not render on the show page after redirect.

Expected Behavior

The bridge component should be rendered.

Video

https://share.cleanshot.com/L16qxynS

Source code

Branch is here and PR to show file change is here

Logs

[Session] visit ["reload": false, "location": http://localhost:45678, "options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.advance, response: nil)]
[ColdBootVisit] startVisit http://localhost:45678
[Bridge] bridgeDestinationViewDidLoad: http://localhost:45678/
[Bridge] bridgeDestinationViewWillAppear: http://localhost:45678/
Failed to send CA Event for app launch measurements for ca_event_type: 0 event_name: com.apple.app_launch_measurement.FirstFramePresentationMetric
[Bridge] bridgeDestinationViewDidAppear: http://localhost:45678/
Failed to send CA Event for app launch measurements for ca_event_type: 1 event_name: com.apple.app_launch_measurement.ExtendedLaunchMetrics
Failed to resolve host network app id to config: bundleID: com.apple.WebKit.Networking instance ID: Optional([_EXExtensionInstanceIdentifier: 0BAF1C8C-E894-4F1F-939E-9582A58E41FB])
[ColdBootVisit] Same-origin redirect detected: http://localhost:45678 -> http://localhost:45678/. http://localhost:45678
[Bridge] bridgeDidReceiveMessage Message(id: "1", component: "overflow-menu", event: "connect", metadata: Optional(HotwireNative.Message.Metadata(url: "http://localhost:45678/")), jsonData: "{\"label\":\"Options\",\"metadata\":{\"url\":\"http:\\/\\/localhost:45678\\/\"}}")
[Bridge] ← pageLoaded ["timestamp": 1739548243785, "restorationIdentifier": 21d2c7ce-704b-43a1-b760-5c2d5133775b]
[ColdBootVisit] completeVisit http://localhost:45678
[Bridge] ← visitProposed ["location": http://localhost:45678/new, "options": {
    acceptsStreamResponse = 0;
    action = advance;
}, "timestamp": 1739548250576]
[Session] visit ["location": http://localhost:45678/new, "reload": false, "options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.advance, response: nil)]
[ColdBootVisit] startVisit http://localhost:45678/new
[Bridge] bridgeDestinationViewDidLoad: http://localhost:45678/new
[Bridge] bridgeDestinationViewWillAppear: http://localhost:45678/new
[Bridge] ← pageLoaded ["restorationIdentifier": d752c7a7-54b9-4280-83b4-89782e7a1836, "timestamp": 1739548250701]
[ColdBootVisit] completeVisit http://localhost:45678/new
[Bridge] bridgeDestinationViewDidAppear: http://localhost:45678/new
[Bridge] ← formSubmissionStarted ["timestamp": 1739548251681, "location": http://localhost:45678/new]
[Bridge] ← formSubmissionFinished ["timestamp": 1739548251706, "location": http://localhost:45678/new]
[Bridge] ← visitProposed ["location": http://localhost:45678/success, "options": {
    action = advance;
    response =     {
        redirected = 1;
        responseHTML = "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"turbo-refresh-method\" content=\"morph\">\n    <meta name=\"turbo-refresh-scroll\" content=\"preserve\">\n\n    <title>It Worked!</title>\n\n    <link rel=\"stylesheet\" href=\"/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"/styles/utilities.css\">\n    <link rel=\"stylesheet\" href=\"/styles/app.css\">\n\n    \n      <link rel=\"stylesheet\" href=\"/styles/native.css\">\n      <link rel=\"stylesheet\" href=\"/styles/bridge.css\">\n    \n\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"@hotwired/turbo\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/stimulus\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/hotwire-native-bridge\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\"\n        }\n      }\n    </script>\n\n    <script type=\"module\" src=\"/application.js\"></script>\n  </head>\n  <body class=\"\">\n    <main id=\"content\" class=\"grid pad --bottom-soft\">\n      <h1 class=\"page-title\">It Worked!</h1>\n\n<p>\n  You have successfully submitted a form. What a ride.\n</p>\n\n<div data-controller=\"menu bridge--menu\">\n\n  <button\n    class=\"button\"\n    data-controller=\"bridge--overflow-menu\"\n    data-action=\"click->bridge--menu#show click->menu#show\"\n    data-bridge-title=\"Options\">\n    Open Menu\n  </button>\n\n  <div class=\"dialog\"\n    data-menu-target=\"dialog\"\n    data-action=\"click@window->menu#hideOnClickOutside\">\n\n    <div class=\"dialog-content\">\n\n      <span class=\"dialog-close\" data-action=\"click->menu#hide\">&times;</span>\n      <p data-bridge--menu-target=\"title\">Select an option</p>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option One\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Two\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Three\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Four\n      </button>\n\n    </div>\n\n  </div>\n\n\n\n    </main>\n  </body>\n</html>\n";
        statusCode = 200;
    };
    shouldCacheSnapshot = 0;
}, "timestamp": 1739548251707]
[Session] visit ["options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.advance, response: Optional(HotwireNative.VisitResponse(statusCode: 200, responseHTML: Optional("<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"turbo-refresh-method\" content=\"morph\">\n    <meta name=\"turbo-refresh-scroll\" content=\"preserve\">\n\n    <title>It Worked!</title>\n\n    <link rel=\"stylesheet\" href=\"/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"/styles/utilities.css\">\n    <link rel=\"stylesheet\" href=\"/styles/app.css\">\n\n    \n      <link rel=\"stylesheet\" href=\"/styles/native.css\">\n      <link rel=\"stylesheet\" href=\"/styles/bridge.css\">\n    \n\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"@hotwired/turbo\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/stimulus\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/hotwire-native-bridge\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\"\n        }\n      }\n    </script>\n\n    <script type=\"module\" src=\"/application.js\"></script>\n  </head>\n  <body class=\"\">\n    <main id=\"content\" class=\"grid pad --bottom-soft\">\n      <h1 class=\"page-title\">It Worked!</h1>\n\n<p>\n  You have successfully submitted a form. What a ride.\n</p>\n\n<div data-controller=\"menu bridge--menu\">\n\n  <button\n    class=\"button\"\n    data-controller=\"bridge--overflow-menu\"\n    data-action=\"click->bridge--menu#show click->menu#show\"\n    data-bridge-title=\"Options\">\n    Open Menu\n  </button>\n\n  <div class=\"dialog\"\n    data-menu-target=\"dialog\"\n    data-action=\"click@window->menu#hideOnClickOutside\">\n\n    <div class=\"dialog-content\">\n\n      <span class=\"dialog-close\" data-action=\"click->menu#hide\">&times;</span>\n      <p data-bridge--menu-target=\"title\">Select an option</p>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option One\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Two\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Three\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Four\n      </button>\n\n    </div>\n\n  </div>\n\n\n\n    </main>\n  </body>\n</html>\n")))), "reload": false, "location": http://localhost:45678/success]
[JavascriptVisit] startVisit http://localhost:45678/success, [:]
[Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier [Optional("http://localhost:45678/success"), Optional({
    action = advance;
    response =     {
        responseHTML = "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"turbo-refresh-method\" content=\"morph\">\n    <meta name=\"turbo-refresh-scroll\" content=\"preserve\">\n\n    <title>It Worked!</title>\n\n    <link rel=\"stylesheet\" href=\"/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"/styles/utilities.css\">\n    <link rel=\"stylesheet\" href=\"/styles/app.css\">\n\n    \n      <link rel=\"stylesheet\" href=\"/styles/native.css\">\n      <link rel=\"stylesheet\" href=\"/styles/bridge.css\">\n    \n\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"@hotwired/turbo\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/stimulus\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/hotwire-native-bridge\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\"\n        }\n      }\n    </script>\n\n    <script type=\"module\" src=\"/application.js\"></script>\n  </head>\n  <body class=\"\">\n    <main id=\"content\" class=\"grid pad --bottom-soft\">\n      <h1 class=\"page-title\">It Worked!</h1>\n\n<p>\n  You have successfully submitted a form. What a ride.\n</p>\n\n<div data-controller=\"menu bridge--menu\">\n\n  <button\n    class=\"button\"\n    data-controller=\"bridge--overflow-menu\"\n    data-action=\"click->bridge--menu#show click->menu#show\"\n    data-bridge-title=\"Options\">\n    Open Menu\n  </button>\n\n  <div class=\"dialog\"\n    data-menu-target=\"dialog\"\n    data-action=\"click@window->menu#hideOnClickOutside\">\n\n    <div class=\"dialog-content\">\n\n      <span class=\"dialog-close\" data-action=\"click->menu#hide\">&times;</span>\n      <p data-bridge--menu-target=\"title\">Select an option</p>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option One\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Two\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Three\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Four\n      </button>\n\n    </div>\n\n  </div>\n\n\n\n    </main>\n  </body>\n</html>\n";
        statusCode = 200;
    };
}), nil]
[Bridge] bridgeDestinationViewWillDisappear: http://localhost:45678/new
[Bridge] ← visitStarted ["identifier": ad846139-4a71-426e-b325-23700c78b4ad, "timestamp": 1739548251734, "isPageRefresh": 0, "hasCachedSnapshot": 0]
[JavascriptVisit] didStartVisitWithIdentifier http://localhost:45678/success, ["isPageRefresh": false, "identifier": "ad846139-4a71-426e-b325-23700c78b4ad", "hasCachedSnapshot": false]
[Bridge] ← visitRequestStarted ["timestamp": 1739548251734, "identifier": ad846139-4a71-426e-b325-23700c78b4ad]
[JavascriptVisit] didStartRequestForVisitWithIdentifier http://localhost:45678/success, ["identifier": "ad846139-4a71-426e-b325-23700c78b4ad", "date": 2025-02-14 15:50:51 +0000]
[Bridge] ← visitRequestCompleted ["identifier": ad846139-4a71-426e-b325-23700c78b4ad, "timestamp": 1739548251735]
[JavascriptVisit] didCompleteRequestForVisitWithIdentifier http://localhost:45678/success, ["identifier": "ad846139-4a71-426e-b325-23700c78b4ad"]
[Bridge] ← visitRequestFinished ["identifier": ad846139-4a71-426e-b325-23700c78b4ad, "timestamp": 1739548251735]
[JavascriptVisit] didFinishRequestForVisitWithIdentifier http://localhost:45678/success, ["identifier": "ad846139-4a71-426e-b325-23700c78b4ad", "date": 2025-02-14 15:50:51 +0000]
[Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete
bridgeDidIgnoreMessage: Message(id: "2", component: "overflow-menu", event: "connect", metadata: Optional(HotwireNative.Message.Metadata(url: "http://localhost:45678/success")), jsonData: "{\"metadata\":{\"url\":\"http:\\/\\/localhost:45678\\/success\"},\"label\":\"Options\"}")
[Bridge] ← visitCompleted ["timestamp": 1739548251741, "identifier": ad846139-4a71-426e-b325-23700c78b4ad, "restorationIdentifier": 66411cee-15a1-40bd-8106-d2a37844640b]
[JavascriptVisit] didCompleteVisitWithIdentifier http://localhost:45678/success, ["restorationIdentifier": "66411cee-15a1-40bd-8106-d2a37844640b", "identifier": "ad846139-4a71-426e-b325-23700c78b4ad"]
[Bridge] → window.turboNative.cacheSnapshot []
[Bridge] bridgeDestinationViewDidDisappear: http://localhost:45678/new
[Bridge] bridgeDestinationViewDidLoad: http://localhost:45678/success
[Bridge] bridgeDestinationViewWillDisappear: http://localhost:45678
[Bridge] → window.turboNative.clearSnapshotCache []
[Bridge] bridgeDestinationViewWillAppear: http://localhost:45678/success
[Bridge] = window.turboNative.cacheSnapshot evaluation complete
[Bridge] = window.turboNative.clearSnapshotCache evaluation complete
[Bridge] ← visitRendered ["timestamp": 1739548252285, "identifier": ad846139-4a71-426e-b325-23700c78b4ad]
[JavascriptVisit] didRenderForVisitWithIdentifier http://localhost:45678/success, ["identifier": "ad846139-4a71-426e-b325-23700c78b4ad"]
[Bridge] bridgeDestinationViewDidDisappear: http://localhost:45678
[Bridge] bridgeDestinationViewDidAppear: http://localhost:45678/success
[Session] visit ["location": http://localhost:45678/success, "options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.advance, response: nil), "reload": false]
[ColdBootVisit] startVisit http://localhost:45678/success
[Bridge] bridgeDidReceiveMessage Message(id: "1", component: "overflow-menu", event: "connect", metadata: Optional(HotwireNative.Message.Metadata(url: "http://localhost:45678/success")), jsonData: "{\"label\":\"Options\",\"metadata\":{\"url\":\"http:\\/\\/localhost:45678\\/success\"}}")
[Bridge] ← pageLoaded ["timestamp": 1739548254303, "restorationIdentifier": 74a53284-4c1a-4dee-86e6-077d443bcd79]
[ColdBootVisit] completeVisit http://localhost:45678/success
[Bridge] bridgeDestinationViewWillDisappear: http://localhost:45678/success
[Session] visit ["options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.restore, response: nil), "location": http://localhost:45678, "reload": false]
[JavascriptVisit] startVisit http://localhost:45678, [:]
[Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier [Optional("http://localhost:45678"), Optional({
    action = restore;
}), Optional("21d2c7ce-704b-43a1-b760-5c2d5133775b")]
[Bridge] bridgeDestinationViewWillAppear: http://localhost:45678/success
[Bridge] ← visitStarted ["isPageRefresh": 0, "hasCachedSnapshot": 0, "timestamp": 1739548259203, "identifier": a16eedb9-3d34-45dd-9968-183db33b75e4]
[JavascriptVisit] didStartVisitWithIdentifier http://localhost:45678, ["identifier": "a16eedb9-3d34-45dd-9968-183db33b75e4", "hasCachedSnapshot": false, "isPageRefresh": false]
[Bridge] ← visitRequestStarted ["identifier": a16eedb9-3d34-45dd-9968-183db33b75e4, "timestamp": 1739548259206]
[JavascriptVisit] didStartRequestForVisitWithIdentifier http://localhost:45678, ["identifier": "a16eedb9-3d34-45dd-9968-183db33b75e4", "date": 2025-02-14 15:50:59 +0000]
[Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete
[Bridge] ← visitRequestFinished ["identifier": a16eedb9-3d34-45dd-9968-183db33b75e4, "timestamp": 1739548259217]
[JavascriptVisit] didFinishRequestForVisitWithIdentifier http://localhost:45678, ["identifier": "a16eedb9-3d34-45dd-9968-183db33b75e4", "date": 2025-02-14 15:50:59 +0000]
[Bridge] ← visitRequestCompleted ["identifier": a16eedb9-3d34-45dd-9968-183db33b75e4, "timestamp": 1739548259218]
[JavascriptVisit] didCompleteRequestForVisitWithIdentifier http://localhost:45678, ["identifier": "a16eedb9-3d34-45dd-9968-183db33b75e4"]
[Bridge] bridgeDidReceiveMessage Message(id: "2", component: "overflow-menu", event: "connect", metadata: Optional(HotwireNative.Message.Metadata(url: "http://localhost:45678/")), jsonData: "{\"label\":\"Options\",\"metadata\":{\"url\":\"http:\\/\\/localhost:45678\\/\"}}")
[Bridge] ← visitCompleted ["identifier": a16eedb9-3d34-45dd-9968-183db33b75e4, "restorationIdentifier": 21d2c7ce-704b-43a1-b760-5c2d5133775b, "timestamp": 1739548259227]
[JavascriptVisit] didCompleteVisitWithIdentifier http://localhost:45678, ["restorationIdentifier": "21d2c7ce-704b-43a1-b760-5c2d5133775b", "identifier": "a16eedb9-3d34-45dd-9968-183db33b75e4"]
[Bridge] ← visitRendered ["identifier": a16eedb9-3d34-45dd-9968-183db33b75e4, "timestamp": 1739548259285]
[JavascriptVisit] didRenderForVisitWithIdentifier http://localhost:45678, ["identifier": "a16eedb9-3d34-45dd-9968-183db33b75e4"]
[Bridge] bridgeDestinationViewDidDisappear: http://localhost:45678/success
[Bridge] bridgeDestinationViewDidAppear: http://localhost:45678/
[Bridge] ← visitProposed ["location": http://localhost:45678/new, "options": {
    acceptsStreamResponse = 0;
    action = advance;
}, "timestamp": 1739548260561]
[Session] visit ["reload": false, "options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.advance, response: nil), "location": http://localhost:45678/new]
[JavascriptVisit] startVisit http://localhost:45678/new, [:]
[Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier [Optional("http://localhost:45678/new"), Optional({
    action = advance;
}), nil]
[Bridge] ← visitStarted ["isPageRefresh": 0, "identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "timestamp": 1739548260567, "hasCachedSnapshot": 1]
[JavascriptVisit] didStartVisitWithIdentifier http://localhost:45678/new, ["isPageRefresh": false, "identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d", "hasCachedSnapshot": true]
[Bridge] bridgeDestinationViewDidLoad: http://localhost:45678/new
[Bridge] bridgeDestinationViewWillAppear: http://localhost:45678/new
[Bridge] ← visitRequestStarted ["identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "timestamp": 1739548260569]
[JavascriptVisit] didStartRequestForVisitWithIdentifier http://localhost:45678/new, ["identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d", "date": 2025-02-14 15:51:00 +0000]
[Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete
[Bridge] ← visitRendered ["identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "timestamp": 1739548260572]
[JavascriptVisit] didRenderForVisitWithIdentifier http://localhost:45678/new, ["identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d"]
[Bridge] ← visitRequestFinished ["identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "timestamp": 1739548260576]
[JavascriptVisit] didFinishRequestForVisitWithIdentifier http://localhost:45678/new, ["identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d", "date": 2025-02-14 15:51:00 +0000]
[Bridge] ← visitRequestCompleted ["identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "timestamp": 1739548260577]
[JavascriptVisit] didCompleteRequestForVisitWithIdentifier http://localhost:45678/new, ["identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d"]
[Bridge] ← visitRendered ["identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "timestamp": 1739548260578]
[JavascriptVisit] didRenderForVisitWithIdentifier http://localhost:45678/new, ["identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d"]
[Bridge] ← visitCompleted ["identifier": cd09b3c8-eccd-40be-8219-a9ae54dad85d, "restorationIdentifier": 15014992-7938-4c59-9b5a-a501b09a04cd, "timestamp": 1739548260578]
[JavascriptVisit] didCompleteVisitWithIdentifier http://localhost:45678/new, ["identifier": "cd09b3c8-eccd-40be-8219-a9ae54dad85d", "restorationIdentifier": "15014992-7938-4c59-9b5a-a501b09a04cd"]
[Bridge] bridgeDestinationViewDidAppear: http://localhost:45678/new
[Bridge] ← formSubmissionStarted ["location": http://localhost:45678/new, "timestamp": 1739548261478]
[Bridge] ← formSubmissionFinished ["location": http://localhost:45678/new, "timestamp": 1739548261487]
[Bridge] ← visitProposed ["location": http://localhost:45678/success, "options": {
    action = advance;
    response =     {
        redirected = 1;
        responseHTML = "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"turbo-refresh-method\" content=\"morph\">\n    <meta name=\"turbo-refresh-scroll\" content=\"preserve\">\n\n    <title>It Worked!</title>\n\n    <link rel=\"stylesheet\" href=\"/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"/styles/utilities.css\">\n    <link rel=\"stylesheet\" href=\"/styles/app.css\">\n\n    \n      <link rel=\"stylesheet\" href=\"/styles/native.css\">\n      <link rel=\"stylesheet\" href=\"/styles/bridge.css\">\n    \n\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"@hotwired/turbo\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/stimulus\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/hotwire-native-bridge\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\"\n        }\n      }\n    </script>\n\n    <script type=\"module\" src=\"/application.js\"></script>\n  </head>\n  <body class=\"\">\n    <main id=\"content\" class=\"grid pad --bottom-soft\">\n      <h1 class=\"page-title\">It Worked!</h1>\n\n<p>\n  You have successfully submitted a form. What a ride.\n</p>\n\n<div data-controller=\"menu bridge--menu\">\n\n  <button\n    class=\"button\"\n    data-controller=\"bridge--overflow-menu\"\n    data-action=\"click->bridge--menu#show click->menu#show\"\n    data-bridge-title=\"Options\">\n    Open Menu\n  </button>\n\n  <div class=\"dialog\"\n    data-menu-target=\"dialog\"\n    data-action=\"click@window->menu#hideOnClickOutside\">\n\n    <div class=\"dialog-content\">\n\n      <span class=\"dialog-close\" data-action=\"click->menu#hide\">&times;</span>\n      <p data-bridge--menu-target=\"title\">Select an option</p>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option One\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Two\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Three\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Four\n      </button>\n\n    </div>\n\n  </div>\n\n\n\n    </main>\n  </body>\n</html>\n";
        statusCode = 200;
    };
    shouldCacheSnapshot = 0;
}, "timestamp": 1739548261487]
[Session] visit ["options": HotwireNative.VisitOptions(action: HotwireNative.VisitAction.advance, response: Optional(HotwireNative.VisitResponse(statusCode: 200, responseHTML: Optional("<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"turbo-refresh-method\" content=\"morph\">\n    <meta name=\"turbo-refresh-scroll\" content=\"preserve\">\n\n    <title>It Worked!</title>\n\n    <link rel=\"stylesheet\" href=\"/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"/styles/utilities.css\">\n    <link rel=\"stylesheet\" href=\"/styles/app.css\">\n\n    \n      <link rel=\"stylesheet\" href=\"/styles/native.css\">\n      <link rel=\"stylesheet\" href=\"/styles/bridge.css\">\n    \n\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"@hotwired/turbo\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/stimulus\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/hotwire-native-bridge\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\"\n        }\n      }\n    </script>\n\n    <script type=\"module\" src=\"/application.js\"></script>\n  </head>\n  <body class=\"\">\n    <main id=\"content\" class=\"grid pad --bottom-soft\">\n      <h1 class=\"page-title\">It Worked!</h1>\n\n<p>\n  You have successfully submitted a form. What a ride.\n</p>\n\n<div data-controller=\"menu bridge--menu\">\n\n  <button\n    class=\"button\"\n    data-controller=\"bridge--overflow-menu\"\n    data-action=\"click->bridge--menu#show click->menu#show\"\n    data-bridge-title=\"Options\">\n    Open Menu\n  </button>\n\n  <div class=\"dialog\"\n    data-menu-target=\"dialog\"\n    data-action=\"click@window->menu#hideOnClickOutside\">\n\n    <div class=\"dialog-content\">\n\n      <span class=\"dialog-close\" data-action=\"click->menu#hide\">&times;</span>\n      <p data-bridge--menu-target=\"title\">Select an option</p>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option One\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Two\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Three\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Four\n      </button>\n\n    </div>\n\n  </div>\n\n\n\n    </main>\n  </body>\n</html>\n")))), "location": http://localhost:45678/success, "reload": false]
[JavascriptVisit] startVisit http://localhost:45678/success, [:]
[Bridge] → window.turboNative.visitLocationWithOptionsAndRestorationIdentifier [Optional("http://localhost:45678/success"), Optional({
    action = advance;
    response =     {
        responseHTML = "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\">\n    <meta name=\"turbo-refresh-method\" content=\"morph\">\n    <meta name=\"turbo-refresh-scroll\" content=\"preserve\">\n\n    <title>It Worked!</title>\n\n    <link rel=\"stylesheet\" href=\"/styles/base.css\">\n    <link rel=\"stylesheet\" href=\"/styles/utilities.css\">\n    <link rel=\"stylesheet\" href=\"/styles/app.css\">\n\n    \n      <link rel=\"stylesheet\" href=\"/styles/native.css\">\n      <link rel=\"stylesheet\" href=\"/styles/bridge.css\">\n    \n\n    <script type=\"importmap\">\n      {\n        \"imports\": {\n          \"@hotwired/turbo\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/stimulus\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\",\n          \"@hotwired/hotwire-native-bridge\": \"https://cdn.jsdelivr.net/npm/@hotwired/[email protected]/+esm\"\n        }\n      }\n    </script>\n\n    <script type=\"module\" src=\"/application.js\"></script>\n  </head>\n  <body class=\"\">\n    <main id=\"content\" class=\"grid pad --bottom-soft\">\n      <h1 class=\"page-title\">It Worked!</h1>\n\n<p>\n  You have successfully submitted a form. What a ride.\n</p>\n\n<div data-controller=\"menu bridge--menu\">\n\n  <button\n    class=\"button\"\n    data-controller=\"bridge--overflow-menu\"\n    data-action=\"click->bridge--menu#show click->menu#show\"\n    data-bridge-title=\"Options\">\n    Open Menu\n  </button>\n\n  <div class=\"dialog\"\n    data-menu-target=\"dialog\"\n    data-action=\"click@window->menu#hideOnClickOutside\">\n\n    <div class=\"dialog-content\">\n\n      <span class=\"dialog-close\" data-action=\"click->menu#hide\">&times;</span>\n      <p data-bridge--menu-target=\"title\">Select an option</p>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option One\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Two\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Three\n      </button>\n\n      <button\n        class=\"button\"\n        data-menu-target=\"item\"\n        data-bridge--menu-target=\"item\"\n        data-action=\"click->menu#itemSelected\">\n        Option Four\n      </button>\n\n    </div>\n\n  </div>\n\n\n\n    </main>\n  </body>\n</html>\n";
        statusCode = 200;
    };
}), nil]
[Bridge] bridgeDestinationViewWillDisappear: http://localhost:45678/new
[Bridge] ← visitStarted ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "hasCachedSnapshot": 1, "timestamp": 1739548261501, "isPageRefresh": 0]
[JavascriptVisit] didStartVisitWithIdentifier http://localhost:45678/success, ["isPageRefresh": false, "identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781", "hasCachedSnapshot": true]
[Bridge] ← visitRequestStarted ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "timestamp": 1739548261501]
[JavascriptVisit] didStartRequestForVisitWithIdentifier http://localhost:45678/success, ["date": 2025-02-14 15:51:01 +0000, "identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781"]
[Bridge] ← visitRequestCompleted ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "timestamp": 1739548261501]
[JavascriptVisit] didCompleteRequestForVisitWithIdentifier http://localhost:45678/success, ["identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781"]
[Bridge] ← visitRequestFinished ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "timestamp": 1739548261501]
[JavascriptVisit] didFinishRequestForVisitWithIdentifier http://localhost:45678/success, ["identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781", "date": 2025-02-14 15:51:01 +0000]
[Bridge] = window.turboNative.visitLocationWithOptionsAndRestorationIdentifier evaluation complete
bridgeDidIgnoreMessage: Message(id: "3", component: "overflow-menu", event: "connect", metadata: Optional(HotwireNative.Message.Metadata(url: "http://localhost:45678/success")), jsonData: "{\"label\":\"Options\",\"metadata\":{\"url\":\"http:\\/\\/localhost:45678\\/success\"}}")
[Bridge] ← visitCompleted ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "timestamp": 1739548261505, "restorationIdentifier": 067beb42-234b-465a-b81b-ed137cd49d28]
[JavascriptVisit] didCompleteVisitWithIdentifier http://localhost:45678/success, ["identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781", "restorationIdentifier": "067beb42-234b-465a-b81b-ed137cd49d28"]
bridgeDidIgnoreMessage: Message(id: "4", component: "overflow-menu", event: "connect", metadata: Optional(HotwireNative.Message.Metadata(url: "http://localhost:45678/success")), jsonData: "{\"label\":\"Options\",\"metadata\":{\"url\":\"http:\\/\\/localhost:45678\\/success\"}}")
[Bridge] → window.turboNative.cacheSnapshot []
[Bridge] bridgeDestinationViewDidDisappear: http://localhost:45678/new
[Bridge] = window.turboNative.cacheSnapshot evaluation complete
[Bridge] bridgeDestinationViewDidLoad: http://localhost:45678/success
[Bridge] bridgeDestinationViewWillDisappear: http://localhost:45678
[Bridge] → window.turboNative.clearSnapshotCache []
[Bridge] bridgeDestinationViewWillAppear: http://localhost:45678/success
[Bridge] = window.turboNative.clearSnapshotCache evaluation complete
[Bridge] ← visitRendered ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "timestamp": 1739548262085]
[JavascriptVisit] didRenderForVisitWithIdentifier http://localhost:45678/success, ["identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781"]
[Bridge] ← visitRendered ["identifier": 7e8c3e27-d967-4934-b1de-8aa25c7eb781, "timestamp": 1739548262085]
[JavascriptVisit] didRenderForVisitWithIdentifier http://localhost:45678/success, ["identifier": "7e8c3e27-d967-4934-b1de-8aa25c7eb781"]
[Bridge] bridgeDestinationViewDidDisappear: http://localhost:45678
[Bridge] bridgeDestinationViewDidAppear: http://localhost:45678/success
@williamkennedy
Copy link
Author

For anyone that comes across this issue, for now, I have added an event listener to listen for the turbo:load event and resend the message. However, this is just a workaround for now.

@leonvogt
Copy link
Contributor

Hi @williamkennedy 👋
I've stumbled upon a similar issue where only the first tab receives the bridge component message, while the other tabs ignore it.
This happens because the viewController has not yet called any of the necessary Destination lifecycle methods yet. (They are called when the tab is selected.)

Steps to reproduce (with the upcoming demo app that includes tabs, which is awesome btw! 🎉):

  • Use the rails-demo-site branch for iOS
  • Use the rails branch for the demo site
  • Add the following code to app/views/layouts/application.html.erb, to add a overflow bridge component to every page
<body>
  <%= render "shared/nav" %>
  <div class="my-3">
    <%= render "shared/flash" %>

+    <button data-controller="bridge--overflow-menu">
+      Overflow Menu
+    </button>

    <%= yield %>
  </div>
</body>

Result:
The bridge component message is only received in the first tab. The other tabs ignore the message and therefore do not display the overflow menu.

Your PR #84 solved the issue for me—thanks for that! 🙏

Here's a diff of the behavior:
Current behavior With PR #84
Only the first tab displays the overflow menu. The others only show it once the tab is reloaded. All tabs display the overflow menu.
Image Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants