Repository: marko-js/marko

Issue:
Incorrect flush order with promise rejection
### Marko Version: 6.0.86

When `<await>` is wrapped in another `<server-await>` tag to share common server-side logic, the flush order appears to be incorrect when there is a promise rejection.

In this reproduction, when we have 2 promise, the first resolves, and the second rejects.

When the rejection happens first, the browser crashes because Marko runtime `<script>`s get flushed before the necessary HTML.

Conversely, when the resolution happens first, the browser does NOT crash because the HTML gets flushed first.

Reproduction: https://github.com/vwong/marko6-promise-order


Discussion:
Update: Even without wrapping `<await>`, ie, when directly using `<try><await>`, the problem still occurs.



Patch:
diff --git a/.changeset/ripe-pandas-do.md b/.changeset/ripe-pandas-do.md
new file mode 100644
index 0000000000..7a7b4e4094
--- /dev/null
+++ b/.changeset/ripe-pandas-do.md
@@ -0,0 +1,5 @@
+---
+"@marko/runtime-tags": patch
+---
+
+Fix issue where client side placeholders were not clearing batched effects, causing extra execution.
diff --git a/.changeset/spotty-trees-design.md b/.changeset/spotty-trees-design.md
new file mode 100644
index 0000000000..150713d8bb
--- /dev/null
+++ b/.changeset/spotty-trees-design.md
@@ -0,0 +1,5 @@
+---
+"@marko/runtime-tags": patch
+---
+
+Fix issue moving @placeholder/@catch content when the replacement range was not yet flushed.
diff --git a/.sizes.json b/.sizes.json
index 7c09363474..79e7949574 100644
--- a/.sizes.json
+++ b/.sizes.json
@@ -7,8 +7,8 @@
     {
       "name": "*",
       "total": {
-        "min": 19362,
-        "brotli": 7336
+        "min": 19377,
+        "brotli": 7348
       }
     },
     {
diff --git a/.sizes/dom.js b/.sizes/dom.js
index 379f7f97fc..9ac9d902bd 100644
--- a/.sizes/dom.js
+++ b/.sizes/dom.js
@@ -1,4 +1,4 @@
-// size: 19362 (min) 7336 (brotli)
+// size: 19377 (min) 7348 (brotli)
 var empty = [],
   rest = Symbol();
 function attrTag(attrs) {
@@ -1206,8 +1206,11 @@ function _await(nodeAccessor, renderer) {
                       tryWithPlaceholder.h.parentNode,
                       placeholderBranch.h,
                     ),
-                    removeAndDestroyBranch(placeholderBranch)),
-                  tryWithPlaceholder.H && runEffects(tryWithPlaceholder.H, !0));
+                    removeAndDestroyBranch(placeholderBranch)));
+                let pendingEffects2 = tryWithPlaceholder.H;
+                pendingEffects2 &&
+                  ((tryWithPlaceholder.H = []),
+                  runEffects(pendingEffects2, !0));
               }
             },
             -1,
diff --git a/packages/runtime-tags/src/dom/control-flow.ts b/packages/runtime-tags/src/dom/control-flow.ts
index 34a36893cd..71af3dcd3e 100644
--- a/packages/runtime-tags/src/dom/control-flow.ts
+++ b/packages/runtime-tags/src/dom/control-flow.ts
@@ -141,8 +141,11 @@ export function _await(nodeAccessor: Accessor, renderer: Renderer) {
                     );
                     removeAndDestroyBranch(placeholderBranch);
                   }
-                  if (tryWithPlaceholder.___effects) {
-                    (runEffects as any)(tryWithPlaceholder.___effects, true);
+
+                  const pendingEffects = tryWithPlaceholder.___effects;
+                  if (pendingEffects) {
+                    tryWithPlaceholder.___effects = [];
+                    runEffects(pendingEffects, true);
                   }
                 }
               }
diff --git a/packages/runtime-tags/src/dom/queue.ts b/packages/runtime-tags/src/dom/queue.ts
index 99efb597ad..41aa8ac7ec 100644
--- a/packages/runtime-tags/src/dom/queue.ts
+++ b/packages/runtime-tags/src/dom/queue.ts
@@ -94,14 +94,14 @@ export function prepareEffects(fn: () => void): unknown[] {
   return preparedEffects;
 }
 
-export let runEffects = (effects: unknown[]) => {
+export let runEffects = ((effects) => {
   for (let i = 0, scope: Scope; i < effects.length; ) {
     (effects[i++] as (a: Scope, b: Scope) => void)(
       (scope = effects[i++] as Scope),
       scope,
     );
   }
-};
+}) as (effects: unknown[], checkPending?: boolean) => void;
 
 function runRenders() {
   while (pendingRenders.length) {
diff --git a/packages/runtime-tags/src/html/inlined-runtimes.ts b/packages/runtime-tags/src/html/inlined-runtimes.ts
index 7fabbc75b5..8c5a1ddb48 100644
--- a/packages/runtime-tags/src/html/inlined-runtimes.ts
+++ b/packages/runtime-tags/src/html/inlined-runtimes.ts
@@ -35,61 +35,60 @@ export const WALKER_RUNTIME_CODE = MARKO_DEBUG
     },
   })
 ), self[runtimeId]))`
-  : `(e=>(self[e]||(self[e]=(l,s=e+l,f=s.length,o={},d=[],n=document,t=n.createTreeWalker(n,129))=>n=self[e][l]={i:s,d:n,l:o,v:d,x(){},w(e,l,a){for(;e=t.nextNode();)n.x(l=(l=e.data)&&!l.indexOf(s)&&(o[a=l.slice(f+1)]=e,l[f]),a,e),l>"#"&&d.push(e)}}),self[e]))`;
+  : `(e=>(self[e]||(self[e]=(l,f=e+l,s=f.length,a={},d=[],t=document,n=t.createTreeWalker(t,129))=>t=self[e][l]={i:f,d:t,l:a,v:d,x(){},w(e,l,r){for(;e=n.nextNode();)t.x(l=(l=e.data)&&!l.indexOf(f)&&(a[r=l.slice(s+1)]=e,l[s]),r,e),l>"#"&&d.push(e)}}),self[e]))`;
 export const REORDER_RUNTIME_CODE = MARKO_DEBUG
   ? /* js */ `((runtime) => {
-if (runtime.j) return;
-let onNextSibling,
-  placeholder,
-  nextSibling,
-  placeholders = {},
-  replace = (marker, container) => {
-    marker.replaceWith(...container.childNodes);
-    container.remove();
-  };
-runtime.d.head.append(
-  runtime.d.querySelector("style[" + runtime.i + "]") || "",
-);
-runtime.j = {};
-runtime.x = (op, id, node, start, placeholderCallback) => {
-  if (op == "#") {
-    (placeholders[id] = placeholder).i++;
-  } else if (node == nextSibling) {
-    onNextSibling();
-  }
-
-  if (node.tagName == "T" && (id = node.getAttribute(runtime.i))) {
-    start = runtime.l["^" + id];
-
-    if (start) {
-      placeholders[id] = {
-        i: 1,
-        c(end = runtime.l[id] || node) {
-          for (
-            ;
-            (nextSibling = end.previousSibling || start).remove(),
-            start != nextSibling;
-          );
-          replace(end, node);
-        },
-      };
+  if (runtime.j) return;
+  let onNextSibling,
+    placeholder,
+    nextSibling,
+    placeholders = {},
+    replace = (id, container) => runtime.l[id].replaceWith(...container.childNodes);
+  runtime.d.head.append(
+    runtime.d.querySelector("style[" + runtime.i + "]") || ""
+  );
+  runtime.j = {};
+  runtime.x = (op, id, node, placeholderRoot, placeholderCb) => {
+    if (node == nextSibling) {
+      onNextSibling();
     }
 
-    nextSibling = node.nextSibling;
-    placeholder = placeholders[id];
-    onNextSibling = () => {
-      start || replace(runtime.l[id], node);
-      if (!--placeholder.i) {
-        placeholder.c();
+    if (op == "#") {
+      (placeholders[id] = placeholder).i++;
+    } else if (op == "!") {
+      if (runtime.l[id] && placeholders[id]) {
+        placeholders[id].c();
       }
-    };
+    } else if (node.tagName == "T" && (id = node.getAttribute(runtime.i))) {
+      nextSibling = node.nextSibling;
+      onNextSibling = () => {
+        node.remove();
+        placeholderRoot || replace(id, node);
+        placeholder.c();
+      };
+      placeholder =
+        placeholders[id] ||
+        (placeholderRoot = placeholders[id] =
+          {
+            i: runtime.l[id] ? 1 : 2,
+            c(start = runtime.l["^" + id]) {
+              if (--placeholderRoot.i) return 1;
+              for (
+                ;
+                (nextSibling =
+                  runtime.l[id].previousSibling || start).remove(),
+                  start != nextSibling;
 
-    // repurpose "op" for callbacks ...carefully
-    if (op = runtime.j[id]) {
-      placeholderCallback = placeholder.c;
-      placeholder.c = () => placeholderCallback() + op(runtime.r);
+              );
+              replace(id, node);
+            },
+          });
+      // repurpose "op" for callbacks ...carefully
+      if ((op = runtime.j[id])) {
+        placeholderCb = placeholder.c;
+        placeholder.c = () => placeholderCb() || op(runtime.r);
+      }
     }
-  }
-};
+  };
 })`
-  : `(e=>{if(e.j)return;let i,o,l,r={},t=(e,i)=>{e.replaceWith(...i.childNodes),i.remove()};e.d.head.append(e.d.querySelector("style["+e.i+"]")||""),e.j={},e.x=(c,d,n,a,g)=>{"#"==c?(r[d]=o).i++:n==l&&i(),"T"==n.tagName&&(d=n.getAttribute(e.i))&&((a=e.l["^"+d])&&(r[d]={i:1,c(i=e.l[d]||n){for(;(l=i.previousSibling||a).remove(),a!=l;);t(i,n)}}),l=n.nextSibling,o=r[d],i=()=>{a||t(e.l[d],n),--o.i||o.c()},(c=e.j[d])&&(g=o.c,o.c=()=>g()+c(e.r)))}})`;
+  : `(e=>{if(e.j)return;let i,r,l,t={},c=(i,r)=>e.l[i].replaceWith(...r.childNodes);e.d.head.append(e.d.querySelector("style["+e.i+"]")||""),e.j={},e.x=(a,d,n,o,u)=>{n==l&&i(),"#"==a?(t[d]=r).i++:"!"==a?e.l[d]&&t[d]&&t[d].c():"T"==n.tagName&&(d=n.getAttribute(e.i))&&(l=n.nextSibling,i=()=>{n.remove(),o||c(d,n),r.c()},r=t[d]||(o=t[d]={i:e.l[d]?1:2,c(i=e.l["^"+d]){if(--o.i)return 1;for(;(l=e.l[d].previousSibling||i).remove(),i!=l;);c(d,n)}}),(a=e.j[d])&&(u=r.c,r.c=()=>u()||a(e.r)))}})`;
diff --git a/packages/runtime-tags/src/html/writer.ts b/packages/runtime-tags/src/html/writer.ts
index b3b55c6ad8..c3670b5781 100644
--- a/packages/runtime-tags/src/html/writer.ts
+++ b/packages/runtime-tags/src/html/writer.ts
@@ -819,6 +819,7 @@ function tryCatch(content: () => void, catchContent: (err: unknown) => void) {
             writeMarker = false;
             cur.async = false;
             cur.next = bodyNext;
+            cur.needsWalk = true;
             cur.html = endMarker;
             cur.scripts = cur.effects = cur.lastEffect = "";
             cur.placeholderBody = cur.placeholderRender = cur.reorderId = null;
@@ -962,6 +963,7 @@ export class Chunk {
   public lastEffect = "";
   public async = false;
   public consumed = false;
+  public needsWalk = false;
   public reorderId: string | null = null;
   public placeholderBody: Chunk | null = null;
   public placeholderRender: (() => void) | null = null;
@@ -1030,6 +1032,7 @@ export class Chunk {
 
   consume() {
     let cur: Chunk = this;
+    let needsWalk = cur.needsWalk;
 
     if (cur.next && !cur.async) {
       let html = "";
@@ -1038,6 +1041,7 @@ export class Chunk {
       let lastEffect = "";
       do {
         cur.flushPlaceholder();
+        needsWalk ||= cur.needsWalk;
         html += cur.html;
         effects = concatEffects(effects, cur.effects);
         scripts = concatScripts(scripts, cur.scripts);
@@ -1046,6 +1050,7 @@ export class Chunk {
         cur = cur.next;
       } while (cur.next && !cur.async);
 
+      cur.needsWalk = needsWalk;
       cur.html = html + cur.html;
       cur.effects = concatEffects(effects, cur.effects);
       cur.scripts = concatScripts(scripts, cur.scripts);
@@ -1076,8 +1081,8 @@ export class Chunk {
     const { state } = boundary;
     const { $global, runtimePrefix, nonceAttr } = state;
     let { html, scripts } = this;
-    let hasWalk = state.walkOnNextFlush;
-    if (hasWalk) state.walkOnNextFlush = false;
+    let needsWalk = state.walkOnNextFlush;
+    if (needsWalk) state.walkOnNextFlush = false;
 
     if (state.needsMainRuntime && !state.hasMainRuntime) {
       state.hasMainRuntime = true;
@@ -1093,7 +1098,7 @@ export class Chunk {
     }
 
     if (effects) {
-      hasWalk = true;
+      needsWalk = true;
       state.resumes = state.resumes ? state.resumes + "," + effects : effects;
     }
 
@@ -1113,7 +1118,7 @@ export class Chunk {
     }
 
     if (state.writeReorders) {
-      hasWalk = true;
+      needsWalk = true;
 
       if (!state.hasReorderRuntime) {
         state.hasReorderRuntime = true;
@@ -1200,7 +1205,7 @@ export class Chunk {
       state.writeReorders = null;
     }
 
-    if (hasWalk) {
+    if (needsWalk) {
       scripts = concatScripts(scripts, runtimePrefix + RuntimeKey.Walk + "()");
     }
 
@@ -1211,9 +1216,15 @@ export class Chunk {
   }
 
   flushHTML() {
-    this.flushScript();
-    const { boundary, scripts } = this;
+    const { boundary } = this;
     const { state } = boundary;
+    if (this.needsWalk) {
+      this.needsWalk = false;
+      state.walkOnNextFlush = true;
+    }
+
+    this.flushScript();
+    const { scripts } = this;
     const { $global, nonceAttr } = state;
     const { __flush__ } = $global;
     let { html } = this;


Test patch:
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-deep-recursive/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-deep-recursive/__snapshots__/ssr.expected.md
index a720f41ebc..802a583c58 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/async-deep-recursive/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-deep-recursive/__snapshots__/ssr.expected.md
@@ -111,6 +111,7 @@ INSERT html/body/script0
 INSERT html/body/script0/#text
 REMOVE html/head/style after html/body/div
 INSERT html/head/style
+REMOVE t after html/body/div
 INSERT t
 INSERT html/body/div/div/div
 INSERT html/body/div/div/div/#comment0
@@ -125,23 +126,17 @@ INSERT t
 INSERT #comment
 INSERT html/body/script1
 INSERT html/body/script1/#text
-REMOVE html/body/div/div/div in t
-REMOVE #comment in t
-INSERT html/body/div/div/div
 REMOVE t after html/body/script0
 REMOVE #text after #comment
 REMOVE #comment after html/body/div/div/#comment0
-REMOVE html/body/div/div/div in t
 REMOVE #comment after html/body/div/div/#comment0
 INSERT html/body/div/div/div
-REMOVE t after html/body/div
+REMOVE t after html/body/script0
 INSERT t
 INSERT html/body/script2
 INSERT html/body/script2/#text
-REMOVE #comment in t
 REMOVE t after html/body/script1
 REMOVE #text after #comment
 REMOVE #comment after html/body/div/div/div/div/#comment0
 REMOVE #comment after html/body/div/div/div/div/#comment0
-REMOVE t after html/body/script0
 ```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/.name-cache.json b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/.name-cache.json
new file mode 100644
index 0000000000..d965f64e0d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/.name-cache.json
@@ -0,0 +1,7 @@
+{
+  "vars": {
+    "props": {
+      "$_": "e"
+    }
+  }
+}
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/csr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/csr-sanitized.expected.md
new file mode 100644
index 0000000000..478d1a6922
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/csr-sanitized.expected.md
@@ -0,0 +1,16 @@
+# Render
+
+
+# Render ASYNC
+```html
+Rejected B
+```
+
+
+# Render ASYNC
+```html
+<div>
+  Resolved A: A Value
+</div>
+Rejected B
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/csr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/csr.expected.md
new file mode 100644
index 0000000000..311268f2e9
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/csr.expected.md
@@ -0,0 +1,50 @@
+# Render
+```html
+<!---->
+<!---->
+<!---->
+<!---->
+<!---->
+<!---->
+```
+
+# Mutations
+```
+INSERT #comment0, #comment1, #text0, #comment2, #comment3, #text1, #comment4, #comment5
+```
+
+# Render ASYNC
+```html
+<!---->
+<!---->
+<!---->
+Rejected B
+<!---->
+```
+
+# Mutations
+```
+INSERT #text1
+REMOVE #comment after #text1
+REMOVE #text after #text1
+REMOVE #comment after #text1
+```
+
+# Render ASYNC
+```html
+<!---->
+<!---->
+<div>
+  Resolved A: A Value
+</div>
+<!---->
+Rejected B
+<!---->
+```
+
+# Mutations
+```
+INSERT div
+REMOVE #text after div
+UPDATE div/#text1 "" => "A Value"
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/dom.expected/template.hydrate.js b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/dom.expected/template.hydrate.js
new file mode 100644
index 0000000000..56f0c60005
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/dom.expected/template.hydrate.js
@@ -0,0 +1,5 @@
+// size: 118 (min) 67 (brotli)
+(_._enable_catch(),
+  _._enable_catch(),
+  _._content_resume("a0", "Rejected B", "b"),
+  _._content_resume("a1", "Rejected A", "b"));
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/dom.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/dom.expected/template.js
new file mode 100644
index 0000000000..5db729680d
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/dom.expected/template.js
@@ -0,0 +1,39 @@
+export const $template = "<!><!><!><!>";
+export const $walks = /* over(1), replace, over(1), replace, over(2) */"b%b%c";
+import { resolveAfter, rejectAfter } from "../../utils/resolve";
+import * as _ from "@marko/runtime-tags/debug/dom";
+_._enable_catch();
+_._enable_catch();
+const $await_content2__v = /* @__PURE__ */_._const("v", ($scope, v) => _._text($scope["#text/0"], v));
+const $await_content2__$params = /* @__PURE__ */_._const("$params3", ($scope, $params3) => $await_content2__v($scope, $params3[0]));
+const $await_content2 = /* @__PURE__ */_._content_branch("<div>Resolved B: <!></div>", /* next(1), over(1), replace, out(1) */"Db%l", 0, $await_content2__$params);
+const $catch_content2 = _._content_resume("__tests__/template.marko_5_content", "Rejected B", /* over(1) */"b");
+const $try_content2__await = /* @__PURE__ */_._await("#text/0", $await_content2);
+const $try_content2__setup = $scope => {
+  $try_content2__await($scope, rejectAfter(new Error("rejected b"), 1));
+};
+const $try_content2 = /* @__PURE__ */_._content_branch("<!><!><!>", /* over(1), replace, over(2) */"b%c", $try_content2__setup);
+const $await_content__v = /* @__PURE__ */_._const("v", ($scope, v) => _._text($scope["#text/0"], v));
+const $await_content__$params = /* @__PURE__ */_._const("$params2", ($scope, $params2) => $await_content__v($scope, $params2[0]));
+const $await_content = /* @__PURE__ */_._content_branch("<div>Resolved A: <!></div>", /* next(1), over(1), replace, out(1) */"Db%l", 0, $await_content__$params);
+const $catch_content = _._content_resume("__tests__/template.marko_2_content", "Rejected A", /* over(1) */"b");
+const $try_content__await = /* @__PURE__ */_._await("#text/0", $await_content);
+const $try_content__setup = $scope => {
+  $try_content__await($scope, resolveAfter("A Value", 2));
+};
+const $try_content = /* @__PURE__ */_._content_branch("<!><!><!>", /* over(1), replace, over(2) */"b%c", $try_content__setup);
+const $try = /* @__PURE__ */_._try("#text/0", $try_content);
+const $try2 = /* @__PURE__ */_._try("#text/1", $try_content2);
+export function $setup($scope) {
+  $try($scope, {
+    catch: _.attrTag({
+      content: $catch_content($scope)
+    })
+  });
+  $try2($scope, {
+    catch: _.attrTag({
+      content: $catch_content2($scope)
+    })
+  });
+}
+export default /* @__PURE__ */_._template("__tests__/template.marko", $template, $walks, $setup);
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/html.expected/template.js b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/html.expected/template.js
new file mode 100644
index 0000000000..29ddbd7d0b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/html.expected/template.js
@@ -0,0 +1,33 @@
+import { resolveAfter, rejectAfter } from "../../utils/resolve";
+import * as _ from "@marko/runtime-tags/debug/html";
+export default _._template("__tests__/template.marko", input => {
+  const $scope0_id = _._scope_id();
+  _._try($scope0_id, "#text/0", _._content_resume("__tests__/template.marko_1_content", () => {
+    const $scope1_id = _._scope_id();
+    _._await($scope1_id, "#text/0", resolveAfter("A Value", 2), v => {
+      const $scope3_id = _._scope_id();
+      _._html(`<div>Resolved A: ${_._escape(v)}</div>`);
+    }, 0);
+  }, $scope0_id), {
+    catch: _.attrTag({
+      content: _._content_resume("__tests__/template.marko_2_content", () => {
+        const $scope2_id = _._scope_id();
+        _._html("Rejected A");
+      }, $scope0_id)
+    })
+  });
+  _._try($scope0_id, "#text/1", _._content_resume("__tests__/template.marko_4_content", () => {
+    const $scope4_id = _._scope_id();
+    _._await($scope4_id, "#text/0", rejectAfter(new Error("rejected b"), 1), v => {
+      const $scope6_id = _._scope_id();
+      _._html(`<div>Resolved B: ${_._escape(v)}</div>`);
+    }, 0);
+  }, $scope0_id), {
+    catch: _.attrTag({
+      content: _._content_resume("__tests__/template.marko_5_content", () => {
+        const $scope5_id = _._scope_id();
+        _._html("Rejected B");
+      }, $scope0_id)
+    })
+  });
+});
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/resume-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/resume-sanitized.expected.md
new file mode 100644
index 0000000000..3040d6220f
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/resume-sanitized.expected.md
@@ -0,0 +1,7 @@
+# Render
+```html
+<div>
+  Resolved A: A Value
+</div>
+Rejected B
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/resume.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/resume.expected.md
new file mode 100644
index 0000000000..5efb9e5c37
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/resume.expected.md
@@ -0,0 +1,53 @@
+# Render
+```html
+<!--M_!^b-->
+<html>
+  <head>
+    <script>
+      WALKER_RUNTIME("M")("_");
+      M._.r = [_ =&gt; (_.d = [0, _.a = {
+        "ConditionalScope:#text/0": _.b = {
+          "#BranchAccessor": "#text/0"
+        },
+        "ConditionalScope:#text/1": _.c = {
+          "#BranchAccessor": "#text/1"
+        }
+      }, _.b, _.c], _.b["#CatchContent"] = _._[
+        "__tests__/template.marko_2_content"
+        ](_.a), _.c["#CatchContent"] = _._[
+        "__tests__/template.marko_5_content"
+        ](_.a), _.d)]
+    </script>
+    <style
+      m_=""
+    >
+      t{display:none}
+    </style>
+  </head>
+  <body>
+    <!--M_[-->
+    <script>
+      REORDER_RUNTIME(M._);
+      M._.w()
+    </script>
+    <div>
+      Resolved A: A Value
+    </div>
+    <!--M_!b-->
+    <!--M_]1 #text/0 2-->
+    <!--M_[-->
+    Rejected B
+    <!--M_]1 #text/1 3-->
+    <script>
+      M._.w()
+    </script>
+  </body>
+</html>
+```
+
+# Mutations
+```
+REMOVE html/body/#comment0 before #comment
+INSERT html/body/#comment0
+INSERT html/body/#text0
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/ssr-sanitized.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/ssr-sanitized.expected.md
new file mode 100644
index 0000000000..496835d371
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/ssr-sanitized.expected.md
@@ -0,0 +1,7 @@
+# Render End
+```html
+<div>
+  Resolved A: A Value
+</div>
+Rejected B
+```
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/ssr.expected.md
new file mode 100644
index 0000000000..26ed22bb5b
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/__snapshots__/ssr.expected.md
@@ -0,0 +1,94 @@
+# Write
+```html
+  <!--M_[--><!--M_!^b--><script>WALKER_RUNTIME("M")("_");M._.r=[_=>(_.d=[0,_.a={"ConditionalScope:#text/0":_.b={"#BranchAccessor":"#text/0"},"ConditionalScope:#text/1":_.c={"#BranchAccessor":"#text/1"}},_.b,_.c],_.b["#CatchContent"]=_._["__tests__/template.marko_2_content"](_.a),_.c["#CatchContent"]=_._["__tests__/template.marko_5_content"](_.a),_.d)]</script>
+```
+
+# Write
+```html
+  <style M_>t{display:none}</style><t M_=c>Rejected B</t><script>REORDER_RUNTIME(M._);M._.w()</script>
+```
+
+# Write
+```html
+  <div>Resolved A: A Value</div><!--M_!b--><!--M_]1 #text/0 2--><!--M_[--><!--M_!^c--><!--M_!c--><!--M_]1 #text/1 3--><script>M._.w()</script>
+```
+
+# Render End
+```html
+<!--M_[-->
+<!--M_!^b-->
+<html>
+  <head>
+    <script>
+      WALKER_RUNTIME("M")("_");
+      M._.r = [_ =&gt; (_.d = [0, _.a = {
+        "ConditionalScope:#text/0": _.b = {
+          "#BranchAccessor": "#text/0"
+        },
+        "ConditionalScope:#text/1": _.c = {
+          "#BranchAccessor": "#text/1"
+        }
+      }, _.b, _.c], _.b["#CatchContent"] = _._[
+        "__tests__/template.marko_2_content"
+        ](_.a), _.c["#CatchContent"] = _._[
+        "__tests__/template.marko_5_content"
+        ](_.a), _.d)]
+    </script>
+    <style
+      m_=""
+    >
+      t{display:none}
+    </style>
+  </head>
+  <body>
+    <script>
+      REORDER_RUNTIME(M._);
+      M._.w()
+    </script>
+    <div>
+      Resolved A: A Value
+    </div>
+    <!--M_!b-->
+    <!--M_]1 #text/0 2-->
+    <!--M_[-->
+    Rejected B
+    <!--M_]1 #text/1 3-->
+    <script>
+      M._.w()
+    </script>
+  </body>
+</html>
+```
+
+# Mutations
+```
+INSERT #comment0
+INSERT #comment1
+INSERT html
+INSERT html/head
+INSERT html/head/script
+INSERT html/head/script/#text
+INSERT html/head/style
+INSERT html/head/style/#text
+INSERT html/body
+INSERT t
+INSERT html/body/#text
+INSERT html/body/script0
+INSERT html/body/script0/#text
+REMOVE html/head/style after html/head/script
+INSERT html/head/style
+REMOVE t before html/body/script0
+INSERT html/body/div
+INSERT html/body/div/#text
+INSERT html/body/#comment0
+INSERT html/body/#comment1
+INSERT html/body/#comment2
+INSERT #comment
+INSERT #comment
+INSERT html/body/#comment3
+INSERT html/body/script1
+INSERT html/body/script1/#text
+REMOVE #comment after html/body/#comment2
+REMOVE #comment after html/body/#comment2
+INSERT html/body/#text
+```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/template.marko b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/template.marko
new file mode 100644
index 0000000000..8ba1a71a67
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/template.marko
@@ -0,0 +1,18 @@
+import { resolveAfter, rejectAfter } from "../../utils/resolve";
+<try>
+  <@catch>
+    Rejected A
+  </>
+  <await|v|=resolveAfter("A Value", 2)>
+    <div>Resolved A: ${v}</div>
+  </await>
+</try>
+
+<try>
+  <@catch>
+    Rejected B
+  </>
+  <await|v|=rejectAfter(new Error("rejected b"), 1)>
+    <div>Resolved B: ${v}</div>
+  </await>
+</try>
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/test.ts b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/test.ts
new file mode 100644
index 0000000000..67ce6536c0
--- /dev/null
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reject-then-resolve-isolated-boundaries/test.ts
@@ -0,0 +1,3 @@
+import { wait } from "../../utils/resolve";
+
+export const steps = [{}, wait(4)];
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-reorder-nested-batched-resolve/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-reorder-nested-batched-resolve/__snapshots__/ssr.expected.md
index 9769f661bd..dca2294adc 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/async-reorder-nested-batched-resolve/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-reorder-nested-batched-resolve/__snapshots__/ssr.expected.md
@@ -111,6 +111,7 @@ INSERT html/body/script0
 INSERT html/body/script0/#text
 REMOVE html/head/style after html/body/#comment
 INSERT html/head/style
+REMOVE t after html/body/#comment
 INSERT t
 INSERT html/body/div
 INSERT html/body/div/#comment0
@@ -125,16 +126,12 @@ INSERT t
 INSERT #comment
 INSERT html/body/script1
 INSERT html/body/script1/#text
-REMOVE html/body/div in t
-REMOVE #comment in t
-INSERT html/body/div
 REMOVE t after html/body/script0
 REMOVE #text before #comment
 REMOVE #comment after #comment
-REMOVE html/body/div in t
 REMOVE #comment before html/body/#comment
 INSERT html/body/div
-REMOVE t after html/body/#comment
+REMOVE t after html/body/script0
 INSERT t
 INSERT html/body/div/div/div
 INSERT html/body/div/div/div/#comment0
@@ -142,14 +139,9 @@ INSERT html/body/div/div/div/div
 INSERT html/body/div/div/div/#comment1
 INSERT html/body/script2
 INSERT html/body/script2/#text
-REMOVE html/body/div/div/div in t
-REMOVE #comment in t
-INSERT html/body/div/div/div
 REMOVE t after html/body/script1
 REMOVE #text after #comment
 REMOVE #comment after html/body/div/div/#comment0
-REMOVE html/body/div/div/div in t
 REMOVE #comment after html/body/div/div/#comment0
 INSERT html/body/div/div/div
-REMOVE t after html/body/script0
 ```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/async-state/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/async-state/__snapshots__/ssr.expected.md
index 5c0577cc9e..a7ae2c5ca4 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/async-state/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/async-state/__snapshots__/ssr.expected.md
@@ -77,6 +77,7 @@ INSERT html/body/script0
 INSERT html/body/script0/#text
 REMOVE html/head/style after html/body/#comment5
 INSERT html/head/style
+REMOVE t after html/body/#comment5
 INSERT t
 INSERT html/body/#comment2
 INSERT html/body/#text
@@ -84,20 +85,9 @@ INSERT html/body/#comment3
 INSERT html/body/#comment4
 INSERT html/body/script1
 INSERT html/body/script1/#text
-REMOVE html/body/#comment2 before html/body/#text
-REMOVE html/body/#text before html/body/#comment3
-REMOVE html/body/#comment3 before html/body/#comment4
-REMOVE html/body/#comment4 in t
-REMOVE #comment in t
-INSERT html/body/#comment2, html/body/#text, html/body/#comment3, html/body/#comment4
 REMOVE t after html/body/script0
 REMOVE #text after #comment
 REMOVE #comment after html/body/#comment1
-REMOVE html/body/#comment2 before html/body/#text
-REMOVE html/body/#text before html/body/#comment3
-REMOVE html/body/#comment3 before html/body/#comment4
-REMOVE html/body/#comment4 in t
 REMOVE #comment after html/body/#comment1
 INSERT html/body/#comment2, html/body/#text, html/body/#comment3, html/body/#comment4
-REMOVE t after html/body/#comment5
 ```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md
index 67be21d4af..53474cebbc 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/catch-single-reject-async/__snapshots__/ssr.expected.md
@@ -54,11 +54,10 @@ INSERT html/body/script
 INSERT html/body/script/#text
 REMOVE html/head/style after html/body/#text2
 INSERT html/head/style
+REMOVE t after html/body/#text2
 REMOVE script after #text
 REMOVE #text after #comment
 REMOVE #comment after html/body/#comment0
-REMOVE html/body/#text1 in t
 REMOVE #comment after html/body/#comment0
 INSERT html/body/#text1
-REMOVE t after html/body/#text2
 ```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md
index 9aacb414d7..1baca41aad 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholder-single/__snapshots__/ssr.expected.md
@@ -71,21 +71,15 @@ INSERT html/body/script0
 INSERT html/body/script0/#text
 REMOVE html/head/style after html/body/#text4
 INSERT html/head/style
+REMOVE t after html/body/#text4
 INSERT html/body/#text5
 INSERT t
 INSERT html/body/#text2
 INSERT html/body/script1
 INSERT html/body/script1/#text
-REMOVE html/body/#text2 in t
-REMOVE #comment after html/body/#text1
-INSERT html/body/#text2
 REMOVE t after html/body/#text5
 REMOVE #text after #comment
 REMOVE #comment after html/body/#comment0
-REMOVE html/body/#text1 before html/body/#text2
-REMOVE html/body/#text2 before html/body/#text3
-REMOVE html/body/#text3 in t
 REMOVE #comment after html/body/#comment0
 INSERT html/body/#text1, html/body/#text2, html/body/#text3
-REMOVE t after html/body/#text4
 ```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md
index 10f62ee0e8..182ad3a6b7 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/placeholders-nested/__snapshots__/ssr.expected.md
@@ -96,42 +96,25 @@ INSERT html/body/script0
 INSERT html/body/script0/#text
 REMOVE html/head/style after html/body/#text7
 INSERT html/head/style
+REMOVE t after html/body/#text7
+REMOVE t after html/body/#text7
 INSERT html/body/#text8
 INSERT t
 INSERT html/body/#text2
 INSERT html/body/script1
 INSERT html/body/script1/#text
-REMOVE html/body/#text2 in t
-REMOVE #comment after html/body/#text1
-INSERT html/body/#text2
 REMOVE t after html/body/#text8
 REMOVE #text after #comment
 REMOVE #comment after html/body/#comment0
-REMOVE html/body/#text1 before html/body/#text2
-REMOVE html/body/#text2 before html/body/#text3
-REMOVE html/body/#text3 before html/body/#comment1
-REMOVE html/body/#comment1 before #comment
-REMOVE #comment before #text
-REMOVE #text before #comment
-REMOVE #comment before html/body/#comment2
-REMOVE html/body/#comment2 in t
 REMOVE #comment after html/body/#comment0
 INSERT html/body/#text1, html/body/#text2, html/body/#text3, html/body/#comment1, #comment, #text, #comment, html/body/#comment2
-REMOVE t after html/body/#text7
 INSERT t
 INSERT html/body/#text5
 INSERT html/body/script2
 INSERT html/body/script2/#text
-REMOVE html/body/#text5 in t
-REMOVE #comment after html/body/#text4
-INSERT html/body/#text5
 REMOVE t after html/body/script1
 REMOVE #text after #comment
 REMOVE #comment after html/body/#comment1
-REMOVE html/body/#text4 before html/body/#text5
-REMOVE html/body/#text5 before html/body/#text6
-REMOVE html/body/#text6 in t
 REMOVE #comment after html/body/#comment1
 INSERT html/body/#text4, html/body/#text5, html/body/#text6
-REMOVE t after html/body/#text7
 ```
\ No newline at end of file
diff --git a/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/csr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/csr.expected.md
index 7583c17cab..0088217d23 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/csr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/csr.expected.md
@@ -111,8 +111,6 @@ Async: 1
 INSERT #comment0, #text0, #text1, #comment1
 REMOVE #text after #comment1
 REMOVE #text in div
-INSERT #text
-REMOVE #text in div
 INSERT div/#text
 ```
 
diff --git a/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/ssr.expected.md b/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/ssr.expected.md
index d6d9f73203..004c852ec2 100644
--- a/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/ssr.expected.md
+++ b/packages/runtime-tags/src/__tests__/fixtures/try-effects-async/__snapshots__/ssr.expected.md
@@ -95,6 +95,7 @@ INSERT html/body/script0
 INSERT html/body/script0/#text
 REMOVE html/head/style after html/body/#comment9
 INSERT html/head/style
+REMOVE t after html/body/#comment9
 INSERT t
 INSERT html/body/#comment4
 INSERT html/body/#text0
@@ -104,24 +105,9 @@ INSERT html/body/#comment6
 INSERT html/body/#comment7
 INSERT html/body/script1
 INSERT html/body/script1/#text
-REMOVE html/body/#comment4 before html/body/#text0
-REMOVE html/body/#text0 before html/body/#comment5
-REMOVE html/body/#comment5 before html/body/#text1
-REMOVE html/body/#text1 before html/body/#comment6
-REMOVE html/body/#comment6 before html/body/#comment7
-REMOVE html/body/#comment7 in t
-REMOVE #comment in t
-INSERT html/body/#comment4, html/body/#text0, html/body/#comment5, html/body/#text1, html/body/#comment6, html/body/#comment7
 REMOVE t after html/body/script0
 REMOVE #text after #comment
 REMOVE #comment after html/body/#comment3
-REMOVE html/body/#comment4 before html/body/#text0
-REMOVE html/body/#text0 before html/body/#comment5
-REMOVE html/body/#comment5 before html/body/#text1
-REMOVE html/body/#text1 before html/body/#comment6
-REMOVE html/body/#comment6 before html/body/#comment7
-REMOVE html/body/#comment7 in t
 REMOVE #comment after html/body/#comment3
 INSERT html/body/#comment4, html/body/#text0, html/body/#comment5, html/body/#text1, html/body/#comment6, html/body/#comment7
-REMOVE t after html/body/#comment9
 ```
\ No newline at end of file