瀏覽代碼

fix(runtime-vapor): preserve hydration cursor for nested insertions

daiwei 1 月之前
父節點
當前提交
ba998777f6
共有 29 個文件被更改,包括 386 次插入176 次删除
  1. 7 7
      packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap
  2. 1 1
      packages/compiler-vapor/__tests__/compile.spec.ts
  3. 1 1
      packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap
  4. 30 30
      packages/compiler-vapor/__tests__/transforms/__snapshots__/logicalIndex.spec.ts.snap
  5. 1 1
      packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap
  6. 1 1
      packages/compiler-vapor/__tests__/transforms/__snapshots__/transformKey.spec.ts.snap
  7. 1 1
      packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap
  8. 1 1
      packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap
  9. 2 2
      packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap
  10. 6 6
      packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap
  11. 29 29
      packages/compiler-vapor/__tests__/transforms/logicalIndex.spec.ts
  12. 1 1
      packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts
  13. 1 2
      packages/compiler-vapor/src/generators/operation.ts
  14. 0 5
      packages/compiler-vapor/src/ir/index.ts
  15. 2 10
      packages/compiler-vapor/src/transforms/transformChildren.ts
  16. 1 1
      packages/runtime-vapor/__tests__/componentSlots.spec.ts
  17. 2 2
      packages/runtime-vapor/__tests__/components/Teleport.spec.ts
  18. 3 3
      packages/runtime-vapor/__tests__/customElement.spec.ts
  19. 162 0
      packages/runtime-vapor/__tests__/hydration.spec.ts
  20. 3 3
      packages/runtime-vapor/__tests__/scopeId.spec.ts
  21. 7 9
      packages/runtime-vapor/src/apiCreateDynamicComponent.ts
  22. 10 6
      packages/runtime-vapor/src/apiCreateFor.ts
  23. 10 6
      packages/runtime-vapor/src/apiCreateFragment.ts
  24. 25 9
      packages/runtime-vapor/src/apiCreateIf.ts
  25. 12 19
      packages/runtime-vapor/src/component.ts
  26. 8 8
      packages/runtime-vapor/src/componentSlots.ts
  27. 36 0
      packages/runtime-vapor/src/dom/hydration.ts
  28. 22 0
      packages/runtime-vapor/src/fragment.ts
  29. 1 12
      packages/runtime-vapor/src/insertionState.ts

+ 7 - 7
packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap

@@ -38,7 +38,7 @@ export function render(_ctx) {
     "default": _withVaporCtx(() => {
       const n0 = _createIf(() => (true), () => {
         const n3 = t0()
-        _setInsertionState(n3, null, 0, true)
+        _setInsertionState(n3, null, 0)
         const n2 = _createComponentWithFallback(_component_Bar)
         _withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
         return n3
@@ -159,7 +159,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
   const n3 = t1()
   const n2 = _child(n3, 1)
   _renderEffect(() => _setProp(n3, "id", _ctx.foo))
-  _setInsertionState(n3, 0, 0, true)
+  _setInsertionState(n3, 0, 0)
   const n1 = _createComponentWithFallback(_component_Comp)
   _renderEffect(() => _setText(n2, _toDisplayString(_ctx.bar)))
   return [n0, n3]
@@ -223,7 +223,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
     const n5 = _next(n2, 1)
     const n4 = _next(n5, 2)
     const x2 = _txt(n2)
-    _setInsertionState(n6, n5, 1, true)
+    _setInsertionState(n6, n5, 1)
     const n3 = _createComponentWithFallback(_component_Child)
     const x4 = _txt(n4)
     _renderEffect(() => _setText(x4, _toDisplayString(_ctx.useId())))
@@ -246,7 +246,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
   const _component_Child = _resolveComponent("Child")
   const n1 = t0()
   _renderEffect(() => _setProp(n1, "id", _ctx.useId()))
-  _setInsertionState(n1, null, 0, true)
+  _setInsertionState(n1, null, 0)
   const n0 = _createComponentWithFallback(_component_Child)
   return n1
 }"
@@ -280,7 +280,7 @@ export function render(_ctx) {
   const n4 = _child(p0)
   _setInsertionState(n6, n5, 1)
   const n0 = _createComponentWithFallback(_component_Comp)
-  _setInsertionState(n6, n7, 3, true)
+  _setInsertionState(n6, n7, 3)
   const n1 = _createIf(() => (true), () => {
     const n3 = t0()
     return n3
@@ -298,9 +298,9 @@ export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n3 = t0()
   const n1 = _child(n3)
-  _setInsertionState(n1, null, 0, true)
+  _setInsertionState(n1, null, 0)
   const n0 = _createSlot("default", null)
-  _setInsertionState(n3, null, 1, true)
+  _setInsertionState(n3, null, 1)
   const n2 = _createComponentWithFallback(_component_Comp)
   return n3
 }"

+ 1 - 1
packages/compiler-vapor/__tests__/compile.spec.ts

@@ -266,7 +266,7 @@ describe('compile', () => {
       })
       expect(code).contains(
         `_renderEffect(() => _setProp(n1, "id", _ctx.useId()))
-  _setInsertionState(n1, null, 0, true)
+  _setInsertionState(n1, null, 0)
   const n0 = _createComponentWithFallback(_component_Child)`,
       )
       expect(code).matchSnapshot()

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/TransformTransition.spec.ts.snap

@@ -49,7 +49,7 @@ export function render(_ctx) {
         return n5
       }, () => {
         const n14 = t2()
-        _setInsertionState(n14, 0, 0, true)
+        _setInsertionState(n14, 0, 0)
         const n9 = _createIf(() => (_ctx.c), () => {
           const n11 = t1()
           return n11

+ 30 - 30
packages/compiler-vapor/__tests__/transforms/__snapshots__/logicalIndex.spec.ts.snap

@@ -11,7 +11,7 @@ export function render(_ctx) {
   const n3 = _next(_child(n2), 2)
   _setInsertionState(n2, 0, 0)
   const n0 = _createComponentWithFallback(_component_Comp1)
-  _setInsertionState(n2, n3, 2, true)
+  _setInsertionState(n2, n3, 2)
   const n1 = _createComponentWithFallback(_component_Comp2)
   return n2
 }"
@@ -31,7 +31,7 @@ export function render(_ctx) {
   const n0 = _createComponentWithFallback(_component_Comp1)
   _setInsertionState(n3, 0, 1)
   const n1 = _createComponentWithFallback(_component_Comp2)
-  _setInsertionState(n3, n4, 3, true)
+  _setInsertionState(n3, n4, 3)
   const n2 = _createComponentWithFallback(_component_Comp3)
   return n3
 }"
@@ -48,7 +48,7 @@ export function render(_ctx) {
   const n4 = _next(_child(n5), 1)
   _setInsertionState(n5, n4, 1)
   const n0 = _createComponentWithFallback(_component_Comp)
-  _setInsertionState(n5, null, 3, true)
+  _setInsertionState(n5, null, 3)
   const n1 = _createIf(() => (true), () => {
     const n3 = t0()
     return n3
@@ -69,7 +69,7 @@ export function render(_ctx) {
   const n6 = _nthChild(n5, 3, 3)
   _setInsertionState(n5, n4, 1)
   const n0 = _createComponentWithFallback(_component_Comp)
-  _setInsertionState(n5, n6, 3, true)
+  _setInsertionState(n5, n6, 3)
   const n1 = _createIf(() => (true), () => {
     const n3 = t0()
     return n3
@@ -88,7 +88,7 @@ export function render(_ctx) {
   const n2 = t0()
   _setInsertionState(n2, null, 1)
   const n0 = _createComponentWithFallback(_component_Comp1)
-  _setInsertionState(n2, null, 2, true)
+  _setInsertionState(n2, null, 2)
   const n1 = _createComponentWithFallback(_component_Comp2)
   return n2
 }"
@@ -101,7 +101,7 @@ const t0 = _template("<div>", true)
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n1 = t0()
-  _setInsertionState(n1, null, 0, true)
+  _setInsertionState(n1, null, 0)
   const n0 = _createComponentWithFallback(_component_Comp)
   return n1
 }"
@@ -114,7 +114,7 @@ const t0 = _template("<div><span>A", true)
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n1 = t0()
-  _setInsertionState(n1, null, 1, true)
+  _setInsertionState(n1, null, 1)
   const n0 = _createComponentWithFallback(_component_Comp)
   return n1
 }"
@@ -131,7 +131,7 @@ export function render(_ctx) {
   const n2 = _next(_child(n3), 1)
   _setInsertionState(n3, n2, 1)
   const n0 = _createComponentWithFallback(_component_Comp1)
-  _setInsertionState(n3, n2, 2, true)
+  _setInsertionState(n3, n2, 2)
   const n1 = _createComponentWithFallback(_component_Comp2)
   return n3
 }"
@@ -145,7 +145,7 @@ export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n2 = t0()
   const n1 = _next(_child(n2), 1)
-  _setInsertionState(n2, n1, 1, true)
+  _setInsertionState(n2, n1, 1)
   const n0 = _createComponentWithFallback(_component_Comp)
   return n2
 }"
@@ -158,7 +158,7 @@ const t1 = _template("<div><span>A</span>", true)
 
 export function render(_ctx) {
   const n2 = t1()
-  _setInsertionState(n2, null, 1, true)
+  _setInsertionState(n2, null, 1)
   const n0 = _createKeyedFragment(() => (_ctx.id), () => {
     const n1 = t0()
     return n1
@@ -175,7 +175,7 @@ const t1 = _template("<div><span>A</span><!><span>B", true)
 export function render(_ctx) {
   const n3 = t1()
   const n2 = _next(_child(n3), 1)
-  _setInsertionState(n3, n2, 1, true)
+  _setInsertionState(n3, n2, 1)
   const n0 = _createKeyedFragment(() => (_ctx.id), () => {
     const n1 = t0()
     return n1
@@ -191,7 +191,7 @@ const t1 = _template("<div><span>A", true)
 
 export function render(_ctx) {
   const n2 = t1()
-  _setInsertionState(n2, 0, 0, true)
+  _setInsertionState(n2, 0, 0)
   const n0 = _createKeyedFragment(() => (_ctx.id), () => {
     const n1 = t0()
     return n1
@@ -210,7 +210,7 @@ export function render(_ctx) {
   const n2 = t0()
   _setInsertionState(n2, 0, 0)
   const n0 = _createComponentWithFallback(_component_Comp1)
-  _setInsertionState(n2, null, 2, true)
+  _setInsertionState(n2, null, 2)
   const n1 = _createComponentWithFallback(_component_Comp2)
   return n2
 }"
@@ -230,7 +230,7 @@ export function render(_ctx) {
   const n0 = _createComponentWithFallback(_component_Comp1)
   _setInsertionState(n3, n4, 2)
   const n1 = _createComponentWithFallback(_component_Comp2)
-  _setInsertionState(n3, null, 4, true)
+  _setInsertionState(n3, null, 4)
   const n2 = _createComponentWithFallback(_component_Comp3)
   return n3
 }"
@@ -246,7 +246,7 @@ export function render(_ctx) {
   const n2 = t0()
   _setInsertionState(n2, 0, 0)
   const n0 = _createComponentWithFallback(_component_Comp1)
-  _setInsertionState(n2, 0, 1, true)
+  _setInsertionState(n2, 0, 1)
   const n1 = _createComponentWithFallback(_component_Comp2)
   return n2
 }"
@@ -259,7 +259,7 @@ const t0 = _template("<div><span>A", true)
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n1 = t0()
-  _setInsertionState(n1, 0, 0, true)
+  _setInsertionState(n1, 0, 0)
   const n0 = _createComponentWithFallback(_component_Comp)
   return n1
 }"
@@ -271,7 +271,7 @@ const t0 = _template("<div><span>A</span>", true)
 
 export function render(_ctx) {
   const n1 = t0()
-  _setInsertionState(n1, null, 1, true)
+  _setInsertionState(n1, null, 1)
   const n0 = _createSlot("default", null)
   return n1
 }"
@@ -283,7 +283,7 @@ const t0 = _template("<div><span>A", true)
 
 export function render(_ctx) {
   const n1 = t0()
-  _setInsertionState(n1, 0, 0, true)
+  _setInsertionState(n1, 0, 0)
   const n0 = _createSlot("default", null)
   return n1
 }"
@@ -296,7 +296,7 @@ const t1 = _template("<div><span>A</span>", true)
 
 export function render(_ctx) {
   const n3 = t1()
-  _setInsertionState(n3, null, 1, true)
+  _setInsertionState(n3, null, 1)
   const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
     const n2 = t0()
     return n2
@@ -312,7 +312,7 @@ const t1 = _template("<div><span>A", true)
 
 export function render(_ctx) {
   const n3 = t1()
-  _setInsertionState(n3, 0, 0, true)
+  _setInsertionState(n3, 0, 0)
   const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
     const n2 = t0()
     return n2
@@ -328,7 +328,7 @@ const t1 = _template("<div><span>A</span>", true)
 
 export function render(_ctx) {
   const n3 = t1()
-  _setInsertionState(n3, null, 1, true)
+  _setInsertionState(n3, null, 1)
   const n0 = _createIf(() => (_ctx.show), () => {
     const n2 = t0()
     return n2
@@ -345,7 +345,7 @@ const t1 = _template("<div><span>A</span><!><p>B", true)
 export function render(_ctx) {
   const n4 = t1()
   const n3 = _next(_child(n4), 1)
-  _setInsertionState(n4, n3, 1, true)
+  _setInsertionState(n4, n3, 1)
   const n0 = _createIf(() => (_ctx.show), () => {
     const n2 = t0()
     return n2
@@ -361,7 +361,7 @@ const t1 = _template("<div><span>A", true)
 
 export function render(_ctx) {
   const n3 = t1()
-  _setInsertionState(n3, 0, 0, true)
+  _setInsertionState(n3, 0, 0)
   const n0 = _createIf(() => (_ctx.show), () => {
     const n2 = t0()
     return n2
@@ -390,7 +390,7 @@ export function render(_ctx) {
     const n5 = t1()
     return n5
   }, 5, null, 0)
-  _setInsertionState(n7, null, 2, true)
+  _setInsertionState(n7, null, 2)
   const n6 = _createComponentWithFallback(_component_Comp2)
   return n7
 }"
@@ -407,7 +407,7 @@ export function render(_ctx) {
   const n6 = t2()
   _setInsertionState(n6, 0, 0)
   const n0 = _createComponentWithFallback(_component_Comp)
-  _setInsertionState(n6, 0, 1, true)
+  _setInsertionState(n6, 0, 1)
   const n1 = _createIf(() => (_ctx.show), () => {
     const n3 = t0()
     return n3
@@ -428,7 +428,7 @@ const t2 = _template("<div><span>A</span><!><p>B", true)
 export function render(_ctx) {
   const n6 = t2()
   const n5 = _next(_child(n6), 1)
-  _setInsertionState(n6, n5, 1, true)
+  _setInsertionState(n6, n5, 1)
   const n0 = _createIf(() => (_ctx.show), () => {
     const n2 = t0()
     return n2
@@ -450,7 +450,7 @@ const t3 = _template("<div><span>A</span><!><p>B", true)
 export function render(_ctx) {
   const n8 = t3()
   const n7 = _next(_child(n8), 1)
-  _setInsertionState(n8, n7, 1, true)
+  _setInsertionState(n8, n7, 1)
   const n0 = _createIf(() => (_ctx.a), () => {
     const n2 = t0()
     return n2
@@ -473,7 +473,7 @@ const t2 = _template("<div><span>A</span>", true)
 
 export function render(_ctx) {
   const n5 = t2()
-  _setInsertionState(n5, null, 1, true)
+  _setInsertionState(n5, null, 1)
   const n0 = _createIf(() => (_ctx.show), () => {
     const n2 = t0()
     return n2
@@ -502,7 +502,7 @@ export function render(_ctx) {
     const n4 = t1()
     return n4
   }, 5, null, 0)
-  _setInsertionState(n6, null, 2, true)
+  _setInsertionState(n6, null, 2)
   const n5 = _createComponentWithFallback(_component_Comp)
   return n6
 }"
@@ -516,7 +516,7 @@ const t2 = _template("<div><span>A", true)
 
 export function render(_ctx) {
   const n5 = t2()
-  _setInsertionState(n5, 0, 0, true)
+  _setInsertionState(n5, 0, 0)
   const n0 = _createIf(() => (_ctx.show), () => {
     const n2 = t0()
     return n2

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformChildren.spec.ts.snap

@@ -8,7 +8,7 @@ const t1 = _template("<div><div></div><!><div>", true)
 export function render(_ctx) {
   const n4 = t1()
   const n3 = _next(_child(n4), 1)
-  _setInsertionState(n4, n3, 1, true)
+  _setInsertionState(n4, n3, 1)
   const n0 = _createIf(() => (1), () => {
     const n2 = t0()
     return n2

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/transformKey.spec.ts.snap

@@ -92,7 +92,7 @@ const t0 = _template("<div>", true)
 export function render(_ctx) {
   const _component_Foo = _resolveComponent("Foo")
   const n2 = t0()
-  _setInsertionState(n2, null, 0, true)
+  _setInsertionState(n2, null, 0)
   const n0 = _createKeyedFragment(() => (_ctx.id), () => {
     const n1 = _createComponentWithFallback(_component_Foo)
     return n1

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/vFor.spec.ts.snap

@@ -87,7 +87,7 @@ const t1 = _template("<div>")
 export function render(_ctx) {
   const n0 = _createFor(() => (_ctx.list), (_for_item0) => {
     const n5 = t1()
-    _setInsertionState(n5, null, 0, true)
+    _setInsertionState(n5, null, 0)
     const n2 = _createFor(() => (_for_item0.value), (_for_item1) => {
       const n4 = t0()
       const x4 = _txt(n4)

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/__snapshots__/vIf.spec.ts.snap

@@ -281,7 +281,7 @@ export function render(_ctx) {
     const n2 = t0()
     return n2
   }, null, 1)
-  _setInsertionState(n8, null, 1, true)
+  _setInsertionState(n8, null, 1)
   const n3 = _createIf(() => (_ctx.bar), () => {
     const n5 = t1()
     return n5

+ 2 - 2
packages/compiler-vapor/__tests__/transforms/__snapshots__/vOnce.spec.ts.snap

@@ -42,7 +42,7 @@ const t0 = _template("<div>", true)
 export function render(_ctx) {
   const _component_Comp = _resolveComponent("Comp")
   const n1 = t0()
-  _setInsertionState(n1, null, 0, true)
+  _setInsertionState(n1, null, 0)
   const n0 = _createComponentWithFallback(_component_Comp, { id: () => (_ctx.foo) }, null, null, true)
   return n1
 }"
@@ -66,7 +66,7 @@ const t0 = _template("<div>", true)
 
 export function render(_ctx) {
   const n1 = t0()
-  _setInsertionState(n1, null, 0, true)
+  _setInsertionState(n1, null, 0)
   const n0 = _createSlot("default", null, null, null, true)
   return n1
 }"

+ 6 - 6
packages/compiler-vapor/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap

@@ -411,7 +411,7 @@ export function render(_ctx) {
   const n6 = t0()
   _setInsertionState(n6, null, 0)
   const n0 = _createSlot("foo", null)
-  _setInsertionState(n6, null, 1, true)
+  _setInsertionState(n6, null, 1)
   const n1 = _createIf(() => (true), () => {
     const n3 = _createComponentWithFallback(_component_Foo)
     return n3
@@ -635,7 +635,7 @@ export function render(_ctx) {
     "default": _withVaporCtx(() => {
       const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
         const n3 = t0()
-        _setInsertionState(n3, null, 0, true)
+        _setInsertionState(n3, null, 0)
         const n2 = _createComponentWithFallback(_component_ChildComp)
         return n3
       })
@@ -657,7 +657,7 @@ export function render(_ctx) {
     "default": _withVaporCtx(() => {
       const n0 = _createIf(() => (_ctx.show), () => {
         const n3 = t0()
-        _setInsertionState(n3, null, 0, true)
+        _setInsertionState(n3, null, 0)
         const n2 = _createComponentWithFallback(_component_ChildComp)
         return n3
       }, null, 1)
@@ -694,7 +694,7 @@ export function render(_ctx) {
     "default": _withVaporCtx(() => {
       const n0 = _createIf(() => (_ctx.show), () => {
         const n3 = t0()
-        _setInsertionState(n3, null, 0, true)
+        _setInsertionState(n3, null, 0)
         const n2 = _createPlainElement("my-element")
         return n3
       }, null, 1)
@@ -732,10 +732,10 @@ export function render(_ctx) {
     "default": _withVaporCtx(() => {
       const n0 = _createIf(() => (_ctx.a), () => {
         const n6 = t1()
-        _setInsertionState(n6, null, 0, true)
+        _setInsertionState(n6, null, 0)
         const n2 = _createIf(() => (_ctx.b), () => {
           const n5 = t0()
-          _setInsertionState(n5, null, 0, true)
+          _setInsertionState(n5, null, 0)
           const n4 = _createComponentWithFallback(_component_ChildComp)
           return n5
         }, null, 1)

+ 29 - 29
packages/compiler-vapor/__tests__/transforms/logicalIndex.spec.ts

@@ -106,8 +106,8 @@ describe('compiler: logicalIndex', () => {
             <span>A</span>
           </div>
         `)
-        // setInsertionState(parent, anchor=0(prepend), logicalIndex=0, last=true)
-        expect(code).toContain('_setInsertionState(n1, 0, 0, true)')
+        // setInsertionState(parent, anchor=0(prepend), logicalIndex=0)
+        expect(code).toContain('_setInsertionState(n1, 0, 0)')
         expect(code).toMatchSnapshot()
       })
 
@@ -122,7 +122,7 @@ describe('compiler: logicalIndex', () => {
         // Comp1
         expect(code).toContain('_setInsertionState(n2, 0, 0)')
         // Comp2
-        expect(code).toContain('_setInsertionState(n2, 0, 1, true)')
+        expect(code).toContain('_setInsertionState(n2, 0, 1)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -136,8 +136,8 @@ describe('compiler: logicalIndex', () => {
             <p>B</p>
           </div>
         `)
-        // setInsertionState(parent, anchor=n${id}, logicalIndex=1, last=true)
-        expect(code).toContain('_setInsertionState(n2, n1, 1, true)')
+        // setInsertionState(parent, anchor=n${id}, logicalIndex=1)
+        expect(code).toContain('_setInsertionState(n2, n1, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -153,7 +153,7 @@ describe('compiler: logicalIndex', () => {
         // Comp1
         expect(code).toContain('_setInsertionState(n3, n2, 1)')
         // Comp2
-        expect(code).toContain('_setInsertionState(n3, n2, 2, true)')
+        expect(code).toContain('_setInsertionState(n3, n2, 2)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -166,8 +166,8 @@ describe('compiler: logicalIndex', () => {
             <Comp />
           </div>
         `)
-        // setInsertionState(parent, null(append), logicalIndex=1, last=true)
-        expect(code).toContain('_setInsertionState(n1, null, 1, true)')
+        // setInsertionState(parent, null(append), logicalIndex=1)
+        expect(code).toContain('_setInsertionState(n1, null, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -182,7 +182,7 @@ describe('compiler: logicalIndex', () => {
         // Comp1
         expect(code).toContain('_setInsertionState(n2, null, 1)')
         // Comp2
-        expect(code).toContain('_setInsertionState(n2, null, 2, true)')
+        expect(code).toContain('_setInsertionState(n2, null, 2)')
         expect(code).toMatchSnapshot()
       })
 
@@ -192,7 +192,7 @@ describe('compiler: logicalIndex', () => {
             <Comp />
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n1, null, 0, true)')
+        expect(code).toContain('_setInsertionState(n1, null, 0)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -209,7 +209,7 @@ describe('compiler: logicalIndex', () => {
         // Comp1: prepend, logicalIndex=0
         expect(code).toContain('_setInsertionState(n2, 0, 0)')
         // Comp2: append, logicalIndex=2
-        expect(code).toContain('_setInsertionState(n2, null, 2, true)')
+        expect(code).toContain('_setInsertionState(n2, null, 2)')
         expect(code).toMatchSnapshot()
       })
 
@@ -228,7 +228,7 @@ describe('compiler: logicalIndex', () => {
         // Comp2: insert, logicalIndex=2
         expect(code).toContain('_setInsertionState(n3, n4, 2)')
         // Comp3: append, logicalIndex=4
-        expect(code).toContain('_setInsertionState(n3, null, 4, true)')
+        expect(code).toContain('_setInsertionState(n3, null, 4)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -241,7 +241,7 @@ describe('compiler: logicalIndex', () => {
             <span>A</span>
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n3, 0, 0, true)')
+        expect(code).toContain('_setInsertionState(n3, 0, 0)')
         expect(code).toMatchSnapshot()
       })
 
@@ -253,7 +253,7 @@ describe('compiler: logicalIndex', () => {
             <p>B</p>
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n4, n3, 1, true)')
+        expect(code).toContain('_setInsertionState(n4, n3, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -264,7 +264,7 @@ describe('compiler: logicalIndex', () => {
             <div v-if="show" />
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n3, null, 1, true)')
+        expect(code).toContain('_setInsertionState(n3, null, 1)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -277,7 +277,7 @@ describe('compiler: logicalIndex', () => {
             <span>A</span>
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n3, 0, 0, true)')
+        expect(code).toContain('_setInsertionState(n3, 0, 0)')
         expect(code).toMatchSnapshot()
       })
 
@@ -288,7 +288,7 @@ describe('compiler: logicalIndex', () => {
             <div v-for="i in list" :key="i" />
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n3, null, 1, true)')
+        expect(code).toContain('_setInsertionState(n3, null, 1)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -301,7 +301,7 @@ describe('compiler: logicalIndex', () => {
             <span>A</span>
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n1, 0, 0, true)')
+        expect(code).toContain('_setInsertionState(n1, 0, 0)')
         expect(code).toMatchSnapshot()
       })
 
@@ -312,7 +312,7 @@ describe('compiler: logicalIndex', () => {
             <slot />
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n1, null, 1, true)')
+        expect(code).toContain('_setInsertionState(n1, null, 1)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -329,7 +329,7 @@ describe('compiler: logicalIndex', () => {
           </div>
         `)
         // The entire if/else block is logicalIndex = 1
-        expect(code).toContain('_setInsertionState(n6, n5, 1, true)')
+        expect(code).toContain('_setInsertionState(n6, n5, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -344,7 +344,7 @@ describe('compiler: logicalIndex', () => {
           </div>
         `)
         // The entire if/else-if/else block is logicalIndex = 1
-        expect(code).toContain('_setInsertionState(n8, n7, 1, true)')
+        expect(code).toContain('_setInsertionState(n8, n7, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -357,7 +357,7 @@ describe('compiler: logicalIndex', () => {
           </div>
         `)
         // logicalIndex = 0 for prepend
-        expect(code).toContain('_setInsertionState(n5, 0, 0, true)')
+        expect(code).toContain('_setInsertionState(n5, 0, 0)')
         expect(code).toMatchSnapshot()
       })
 
@@ -370,7 +370,7 @@ describe('compiler: logicalIndex', () => {
           </div>
         `)
         // logicalIndex = 1 for append
-        expect(code).toContain('_setInsertionState(n5, null, 1, true)')
+        expect(code).toContain('_setInsertionState(n5, null, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -386,7 +386,7 @@ describe('compiler: logicalIndex', () => {
         // v-if/v-else: logicalIndex = 1
         // Comp: logicalIndex = 2
         expect(code).toContain('_setInsertionState(n6, null, 1)')
-        expect(code).toContain('_setInsertionState(n6, null, 2, true)')
+        expect(code).toContain('_setInsertionState(n6, null, 2)')
         expect(code).toMatchSnapshot()
       })
 
@@ -403,7 +403,7 @@ describe('compiler: logicalIndex', () => {
         // v-if/v-else: logicalIndex = 1
         // span: logicalIndex = 2
         expect(code).toContain('_setInsertionState(n6, 0, 0)')
-        expect(code).toContain('_setInsertionState(n6, 0, 1, true)')
+        expect(code).toContain('_setInsertionState(n6, 0, 1)')
         expect(code).toMatchSnapshot()
       })
 
@@ -421,7 +421,7 @@ describe('compiler: logicalIndex', () => {
         // Comp2: logicalIndex = 2
         expect(code).toContain('_setInsertionState(n7, null, 0)')
         expect(code).toContain('_setInsertionState(n7, null, 1)')
-        expect(code).toContain('_setInsertionState(n7, null, 2, true)')
+        expect(code).toContain('_setInsertionState(n7, null, 2)')
         expect(code).toMatchSnapshot()
       })
     })
@@ -434,7 +434,7 @@ describe('compiler: logicalIndex', () => {
             <span>A</span>
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n2, 0, 0, true)')
+        expect(code).toContain('_setInsertionState(n2, 0, 0)')
         expect(code).toMatchSnapshot()
       })
 
@@ -446,7 +446,7 @@ describe('compiler: logicalIndex', () => {
           </div>
         `)
         expect(code).toMatchSnapshot()
-        expect(code).toContain('_setInsertionState(n2, null, 1, true)')
+        expect(code).toContain('_setInsertionState(n2, null, 1)')
       })
 
       test('key in middle', () => {
@@ -457,7 +457,7 @@ describe('compiler: logicalIndex', () => {
             <span>B</span>
           </div>
         `)
-        expect(code).toContain('_setInsertionState(n3, n2, 1, true)')
+        expect(code).toContain('_setInsertionState(n3, n2, 1)')
         expect(code).toMatchSnapshot()
       })
     })

+ 1 - 1
packages/compiler-vapor/__tests__/transforms/transformChildren.spec.ts

@@ -70,7 +70,7 @@ describe('compiler: children transform', () => {
     )
     // ensure the insertion anchor is generated before the insertion statement
     expect(code).toMatch(`const n3 = _next(_child(n4), 1)`)
-    expect(code).toMatch(`_setInsertionState(n4, n3, 1, true)`)
+    expect(code).toMatch(`_setInsertionState(n4, n3, 1)`)
     expect(code).toMatchSnapshot()
   })
 })

+ 1 - 2
packages/compiler-vapor/src/generators/operation.ts

@@ -171,7 +171,7 @@ function genInsertionState(
   operation: InsertionStateTypes,
   context: CodegenContext,
 ): CodeFragment[] {
-  const { parent, anchor, logicalIndex, append, last } = operation
+  const { parent, anchor, logicalIndex, append } = operation
   return [
     NEWLINE,
     ...genCall(
@@ -186,7 +186,6 @@ function genInsertionState(
               'null'
             : `n${anchor}`,
       logicalIndex !== undefined ? String(logicalIndex) : undefined,
-      last && 'true',
     ),
   ]
 }

+ 0 - 5
packages/compiler-vapor/src/ir/index.ts

@@ -100,7 +100,6 @@ export interface IfIRNode extends BaseIRNode, EffectBoundary {
   anchor?: number
   logicalIndex?: number
   append?: boolean
-  last?: boolean
 }
 
 export interface IRFor {
@@ -122,7 +121,6 @@ export interface ForIRNode extends BaseIRNode, IRFor, EffectBoundary {
   anchor?: number
   logicalIndex?: number
   append?: boolean
-  last?: boolean
 }
 
 export interface KeyIRNode extends BaseIRNode, EffectBoundary {
@@ -134,7 +132,6 @@ export interface KeyIRNode extends BaseIRNode, EffectBoundary {
   anchor?: number
   logicalIndex?: number
   append?: boolean
-  last?: boolean
 }
 
 export interface SetBlockKeyIRNode extends BaseIRNode {
@@ -244,7 +241,6 @@ export interface CreateComponentIRNode extends BaseIRNode, EffectBoundary {
   anchor?: number
   logicalIndex?: number
   append?: boolean
-  last?: boolean
 }
 
 export interface SlotOutletIRNode extends BaseIRNode, EffectBoundary {
@@ -259,7 +255,6 @@ export interface SlotOutletIRNode extends BaseIRNode, EffectBoundary {
   anchor?: number
   logicalIndex?: number
   append?: boolean
-  last?: boolean
 }
 
 export interface GetTextChildIRNode extends BaseIRNode {

+ 2 - 10
packages/compiler-vapor/src/transforms/transformChildren.ts

@@ -8,7 +8,6 @@ import {
   DynamicFlag,
   type IRDynamicInfo,
   IRNodeTypes,
-  type InsertionStateTypes,
   isBlockOperation,
 } from '../ir'
 import { shouldUseCreateElement } from './transformElement'
@@ -81,8 +80,6 @@ export const transformChildren: NodeTransform = (node, context) => {
 function processDynamicChildren(context: TransformContext<ElementNode>) {
   let prevDynamics: IRDynamicInfo[] = []
   let staticCount = 0
-  let dynamicCount = 0
-  let lastInsertionChild: IRDynamicInfo | undefined
   const children = context.dynamic.children
 
   // Track logical index for each child.
@@ -94,7 +91,7 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
   for (const [index, child] of children.entries()) {
     if (child.flags & DynamicFlag.INSERT) {
       child.logicalIndex = logicalIndex
-      prevDynamics.push((lastInsertionChild = child))
+      prevDynamics.push(child)
       logicalIndex++
     }
 
@@ -109,7 +106,6 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
         } else {
           registerInsertion(prevDynamics, context, -1 /* prepend */)
         }
-        dynamicCount += prevDynamics.length
         prevDynamics = []
       }
       staticCount++
@@ -122,14 +118,10 @@ function processDynamicChildren(context: TransformContext<ElementNode>) {
       prevDynamics,
       context,
       // the logical index of append child
-      dynamicCount + staticCount,
+      prevDynamics[0].logicalIndex!,
       true,
     )
   }
-
-  if (lastInsertionChild && lastInsertionChild.operation) {
-    ;(lastInsertionChild.operation! as InsertionStateTypes).last = true
-  }
 }
 
 function registerInsertion(

+ 1 - 1
packages/runtime-vapor/__tests__/componentSlots.spec.ts

@@ -1676,7 +1676,7 @@ describe('component: slots', () => {
                   () => props.show,
                   () => {
                     const n5 = template('<div></div>')() as any
-                    setInsertionState(n5, null, 0, true)
+                    setInsertionState(n5, null, 0)
                     createSlot('header', null, () => {
                       const n4 = template('default header')()
                       return n4

+ 2 - 2
packages/runtime-vapor/__tests__/components/Teleport.spec.ts

@@ -1388,7 +1388,7 @@ function runSharedTests(deferMode: boolean): void {
           () => show.value,
           () => {
             const n0 = template('<div></div>')()
-            setInsertionState(n0 as any, null, 0, true)
+            setInsertionState(n0 as any, null, 0)
             createComponent(
               VaporTeleport,
               {
@@ -1585,7 +1585,7 @@ function runSharedTests(deferMode: boolean): void {
       setup() {
         const n0 = template('<div id="tt"></div>')()
         const n4 = template('<div></div>')() as any
-        setInsertionState(n4, null, 0, true)
+        setInsertionState(n4, null, 0)
         createComponent(
           VaporTeleport,
           { to: () => '#tt' },

+ 3 - 3
packages/runtime-vapor/__tests__/customElement.spec.ts

@@ -125,7 +125,7 @@ describe('defineVaporCustomElement', () => {
       const containerComp = defineVaporComponent({
         setup() {
           const n1 = template('<div><div id="move"></div></div>', true)() as any
-          setInsertionState(n1, 0, 0, true)
+          setInsertionState(n1, 0, 0)
           createPlainElement('my-el-input', {
             value: () => num.value,
             onInput: () => ($event: CustomEvent) => {
@@ -781,13 +781,13 @@ describe('defineVaporCustomElement', () => {
         const t0 = template('<div>fallback</div>')
         const t1 = template('<div></div>')
         const n3 = t1() as any
-        setInsertionState(n3, null, 0, true)
+        setInsertionState(n3, null, 0)
         createSlot('default', null, () => {
           const n2 = t0()
           return n2
         })
         const n5 = t1() as any
-        setInsertionState(n5, null, 0, true)
+        setInsertionState(n5, null, 0)
         createSlot('named', null)
         return [n3, n5]
       },

+ 162 - 0
packages/runtime-vapor/__tests__/hydration.spec.ts

@@ -5060,6 +5060,38 @@ describe('Vapor Mode hydration', () => {
       )
     })
 
+    test('enabled teleport hydration with empty v-if should preserve target anchors', async () => {
+      const data = ref({ ok: false, msg: 'foo' })
+
+      const teleportContainer = document.createElement('div')
+      teleportContainer.id = 'teleport-empty-if'
+      teleportContainer.innerHTML =
+        `<!--teleport start anchor-->` + `<!--teleport anchor-->`
+      document.body.appendChild(teleportContainer)
+
+      const { container } = await mountWithHydration(
+        '<!--teleport start--><!--teleport end-->',
+        `<teleport to="#teleport-empty-if">
+          <span v-if="data.ok">{{data.msg}}</span>
+        </teleport>`,
+        data,
+      )
+      await nextTick()
+
+      expect(container.innerHTML).toBe(
+        `<!--teleport start--><!--teleport end-->`,
+      )
+      expect(teleportContainer.innerHTML).toBe(
+        `<!--teleport start anchor--><!--if--><!--teleport anchor-->`,
+      )
+
+      data.value.ok = true
+      await nextTick()
+      expect(teleportContainer.innerHTML).toBe(
+        `<!--teleport start anchor--><span>foo</span><!--if--><!--teleport anchor-->`,
+      )
+    })
+
     test('disabled teleport hydration over empty main-view range should preserve teleport end anchor', async () => {
       const data = ref({ msg: 'foo' })
 
@@ -7359,6 +7391,136 @@ describe('mismatch handling', () => {
       expect(cloned.outerHTML).toBe(`<div>claimed</div>`)
     })
   })
+
+  test('nested insertion hydration preserves outer sibling cursor', async () => {
+    const { container, data } = await testWithVaporApp(
+      `
+      <template>
+        <section><div><components.Child /></div><span>static</span></section>
+        <section>{{ data }}</section>
+      </template>
+      `,
+      {
+        Child: '<template><a>child</a></template>',
+      },
+    )
+
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><a>child</a></div><span>static</span></section><section>foo</section><!--]-->`,
+    )
+
+    data.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><a>child</a></div><span>static</span></section><section>bar</section><!--]-->`,
+    )
+  })
+
+  test('nested v-if hydration preserves outer sibling cursor', async () => {
+    const { container, data } = await testWithVaporApp(`
+      <template>
+        <section><div><span v-if="true">child</span></div><span>static</span></section>
+        <section>{{ data }}</section>
+      </template>
+    `)
+
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><span>child</span></div><span>static</span></section><section>foo</section><!--]-->`,
+    )
+
+    data.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><span>child</span></div><span>static</span></section><section>bar</section><!--]-->`,
+    )
+  })
+
+  test('nested v-if hydration preserves same-root dynamic sibling and outer cursor', async () => {
+    const { container, data } = await testWithVaporApp(`
+      <template>
+        <section><div><span v-if="true">child</span></div><span>{{ data }}</span></section>
+        <section>{{ data }}</section>
+      </template>
+    `)
+
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><span>child</span></div><span>foo</span></section><section>foo</section><!--]-->`,
+    )
+
+    data.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><span>child</span></div><span>bar</span></section><section>bar</section><!--]-->`,
+    )
+  })
+
+  test('single-root nested v-if hydration keeps static siblings', async () => {
+    const { container, data } = await testWithVaporApp(`
+      <template>
+        <main>
+          <section><div><span v-if="true">child</span></div><span>static</span></section>
+          <section>{{ data }}</section>
+        </main>
+      </template>
+    `)
+
+    expect(container.innerHTML).toBe(
+      `<main><section><div><span>child</span></div><span>static</span></section><section>foo</section></main>`,
+    )
+
+    data.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<main><section><div><span>child</span></div><span>static</span></section><section>bar</section></main>`,
+    )
+  })
+
+  test('nested v-for hydration preserves outer sibling cursor', async () => {
+    const { container, data } = await testWithVaporApp(`
+      <template>
+        <section><div><span v-for="item in ['child']">{{ item }}</span></div><span>static</span></section>
+        <section>{{ data }}</section>
+      </template>
+    `)
+
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><!--[--><span>child</span><!--]--></div><span>static</span></section><section>foo</section><!--]-->`,
+    )
+
+    data.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><!--[--><span>child</span><!--]--></div><span>static</span></section><section>bar</section><!--]-->`,
+    )
+  })
+
+  test('nested slot hydration preserves outer sibling cursor', async () => {
+    const { container, data } = await testWithVaporApp(
+      `
+      <template>
+        <components.Wrapper><a>child</a></components.Wrapper>
+      </template>
+      `,
+      {
+        Wrapper: `
+          <template>
+            <section><div><slot /></div><span>static</span></section>
+            <section>{{ data }}</section>
+          </template>
+        `,
+      },
+    )
+
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><!--[--><a>child</a><!--]--></div><span>static</span></section><section>foo</section><!--]-->`,
+    )
+
+    data.value = 'bar'
+    await nextTick()
+    expect(container.innerHTML).toBe(
+      `<!--[--><section><div><!--[--><a>child</a><!--]--></div><span>static</span></section><section>bar</section><!--]-->`,
+    )
+  })
 })
 
 describe('data-allow-mismatch', () => {

+ 3 - 3
packages/runtime-vapor/__tests__/scopeId.spec.ts

@@ -316,7 +316,7 @@ describe('scopeId', () => {
     const Child = defineVaporComponent({
       setup() {
         const n0 = template('<div>')() as any
-        setInsertionState(n0, null, 0, true)
+        setInsertionState(n0, null, 0)
         createSlot('default')
         return n0
       },
@@ -380,7 +380,7 @@ describe('scopeId', () => {
     const Parent = defineVaporComponent({
       setup() {
         const n1 = template('<div>', true)() as any
-        setInsertionState(n1, null, 0, true)
+        setInsertionState(n1, null, 0)
         createSlot('default', null)
         return n1
       },
@@ -389,7 +389,7 @@ describe('scopeId', () => {
     const Child = defineVaporComponent({
       setup() {
         const n1 = template('<div>', true)() as any
-        setInsertionState(n1, null, 0, true)
+        setInsertionState(n1, null, 0)
         createSlot('default', null)
         return n1
       },

+ 7 - 9
packages/runtime-vapor/src/apiCreateDynamicComponent.ts

@@ -21,11 +21,12 @@ import { type RawSlots, getScopeOwner } from './componentSlots'
 import {
   insertionAnchor,
   insertionParent,
-  isLastInsertion,
   resetInsertionState,
 } from './insertionState'
 import {
-  advanceHydrationNode,
+  type HydrationCursor,
+  captureHydrationCursor,
+  exitHydrationCursor,
   isHydrating,
   locateHydrationNode,
 } from './dom/hydration'
@@ -43,8 +44,10 @@ export function createDynamicComponent(
 ): VaporFragment {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  const _isLastInsertion = isLastInsertion
   if (!isHydrating) resetInsertionState()
+  const hydrationCursor: HydrationCursor | null = isHydrating
+    ? captureHydrationCursor()
+    : null
 
   const frag =
     isHydrating || __DEV__
@@ -74,9 +77,6 @@ export function createDynamicComponent(
         if (isHydrating) {
           locateHydrationNode(shouldConsumeFragmentStart(value))
           frag.hydrate()
-          if (_isLastInsertion) {
-            advanceHydrationNode(_insertionParent!)
-          }
         }
         return frag
       }
@@ -98,9 +98,7 @@ export function createDynamicComponent(
   if (!isHydrating) {
     if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
   } else {
-    if (_isLastInsertion) {
-      advanceHydrationNode(_insertionParent!)
-    }
+    exitHydrationCursor(hydrationCursor)
   }
   return frag
 }

+ 10 - 6
packages/runtime-vapor/src/apiCreateFor.ts

@@ -24,13 +24,15 @@ import {
 import { renderEffect } from './renderEffect'
 import { VaporVForFlags } from '@vue/shared'
 import {
+  type HydrationCursor,
   advanceHydrationNode,
   currentHydrationNode,
   enterHydrationBoundary,
+  enterHydrationCursor,
+  exitHydrationCursor,
   isComment,
   isHydrating,
   locateHydrationBoundaryClose,
-  locateHydrationNode,
   locateNextNode,
   markHydrationAnchor,
   setCurrentHydrationNode,
@@ -46,7 +48,6 @@ import {
   insertionAnchor,
   insertionIndex,
   insertionParent,
-  isLastInsertion,
   resetInsertionState,
 } from './insertionState'
 import { applyTransitionHooks, isTransitionEnabled } from './transition'
@@ -89,9 +90,9 @@ export const createFor = (
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
   const _insertionIndex = insertionIndex
-  const _isLastInsertion = isLastInsertion
+  let hydrationCursor: HydrationCursor | null = null
   if (isHydrating) {
-    locateHydrationNode(true)
+    hydrationCursor = enterHydrationCursor(true)
   } else {
     resetInsertionState()
   }
@@ -556,8 +557,11 @@ export const createFor = (
 
   if (!isHydrating) {
     if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
-  } else if (!pendingHydrationAnchor) {
-    advanceHydrationNode(_isLastInsertion ? _insertionParent! : parentAnchor!)
+  } else {
+    if (!pendingHydrationAnchor && currentHydrationNode === parentAnchor!) {
+      advanceHydrationNode(parentAnchor!)
+    }
+    exitHydrationCursor(hydrationCursor)
   }
 
   return frag

+ 10 - 6
packages/runtime-vapor/src/apiCreateFragment.ts

@@ -1,10 +1,14 @@
 import { type Block, type BlockFn, insert } from './block'
-import { advanceHydrationNode, isHydrating } from './dom/hydration'
+import {
+  type HydrationCursor,
+  captureHydrationCursor,
+  exitHydrationCursor,
+  isHydrating,
+} from './dom/hydration'
 import { DynamicFragment } from './fragment'
 import {
   insertionAnchor,
   insertionParent,
-  isLastInsertion,
   resetInsertionState,
 } from './insertionState'
 import { renderEffect } from './renderEffect'
@@ -22,8 +26,10 @@ import { renderEffect } from './renderEffect'
 export function createKeyedFragment(key: () => any, render: BlockFn): Block {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  const _isLastInsertion = isLastInsertion
   if (!isHydrating) resetInsertionState()
+  const hydrationCursor: HydrationCursor | null = isHydrating
+    ? captureHydrationCursor()
+    : null
 
   const frag = __DEV__
     ? new DynamicFragment('keyed', true)
@@ -34,9 +40,7 @@ export function createKeyedFragment(key: () => any, render: BlockFn): Block {
   if (!isHydrating) {
     if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
   } else {
-    if (_isLastInsertion) {
-      advanceHydrationNode(_insertionParent!)
-    }
+    exitHydrationCursor(hydrationCursor)
   }
   return frag
 }

+ 25 - 9
packages/runtime-vapor/src/apiCreateIf.ts

@@ -1,13 +1,15 @@
 import { type Block, type BlockFn, insert } from './block'
 import {
+  type HydrationCursor,
   advanceHydrationNode,
+  currentHydrationNode,
+  enterHydrationCursor,
+  exitHydrationCursor,
   isHydrating,
-  locateHydrationNode,
 } from './dom/hydration'
 import {
   insertionAnchor,
   insertionParent,
-  isLastInsertion,
   resetInsertionState,
 } from './insertionState'
 import { renderEffect } from './renderEffect'
@@ -25,15 +27,17 @@ export function createIf(
 ): Block {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  const _isLastInsertion = isLastInsertion
   if (!isHydrating) resetInsertionState()
+  let hydrationCursor: HydrationCursor | null = null
+  let branchShape: VaporBlockShape | undefined
 
   let frag: Block
   if (once) {
     const ok = condition()
     if (isHydrating) {
-      locateHydrationNode(
-        decodeIfShape(blockShape!, ok) === VaporBlockShape.MULTI_ROOT,
+      branchShape = decodeIfShape(blockShape!, ok)
+      hydrationCursor = enterHydrationCursor(
+        branchShape === VaporBlockShape.MULTI_ROOT,
       )
     }
     frag = ok
@@ -51,8 +55,9 @@ export function createIf(
     renderEffect(() => {
       const ok = condition()
       if (isHydrating) {
-        locateHydrationNode(
-          decodeIfShape(blockShape!, ok) === VaporBlockShape.MULTI_ROOT,
+        branchShape = decodeIfShape(blockShape!, ok)
+        hydrationCursor = enterHydrationCursor(
+          branchShape === VaporBlockShape.MULTI_ROOT,
         )
       }
       ;(frag as DynamicFragment).update(
@@ -65,9 +70,20 @@ export function createIf(
   if (!isHydrating) {
     if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
   } else {
-    if (_isLastInsertion) {
-      advanceHydrationNode(_insertionParent!)
+    // SSR empty branches render as <!---->, and no template adoption consumes
+    // that comment. Claim it before restoring the outer cursor.
+    if (branchShape === VaporBlockShape.EMPTY && hydrationCursor) {
+      const start = hydrationCursor.start
+      if (
+        start &&
+        currentHydrationNode === start &&
+        start.nodeType === 8 &&
+        (start as Comment).data === ''
+      ) {
+        advanceHydrationNode(start)
+      }
     }
+    exitHydrationCursor(hydrationCursor)
   }
 
   return frag

+ 12 - 19
packages/runtime-vapor/src/component.ts

@@ -86,14 +86,16 @@ import {
 } from './componentSlots'
 import { hmrReload, hmrRerender } from './hmr'
 import {
+  type HydrationCursor,
   adoptTemplate,
   advanceHydrationNode,
   currentHydrationNode,
   enterHydrationBoundary,
+  enterHydrationCursor,
+  exitHydrationCursor,
   isComment,
   isHydrating,
   locateEndAnchor,
-  locateHydrationNode,
   locateNextNode,
   markHydrationAnchor,
   setCurrentHydrationNode,
@@ -114,7 +116,6 @@ import {
 import {
   insertionAnchor,
   insertionParent,
-  isLastInsertion,
   resetInsertionState,
 } from './insertionState'
 import type {
@@ -256,8 +257,8 @@ export function createComponent(
 ): VaporComponentInstance {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  const _isLastInsertion = isLastInsertion
   let hydrationClose: Node | null = null
+  let hydrationCursor: HydrationCursor | null = null
   let exitHydrationBoundary: (() => void) | undefined
   let deferHydrationBoundary = false
   const finalizeHydrationBoundary = () => {
@@ -267,7 +268,7 @@ export function createComponent(
     }
   }
   if (isHydrating) {
-    locateHydrationNode()
+    hydrationCursor = enterHydrationCursor()
     if (component.__multiRoot && isComment(currentHydrationNode!, '[')) {
       hydrationClose = locateEndAnchor(currentHydrationNode!)
       exitHydrationBoundary = enterHydrationBoundary(
@@ -338,9 +339,6 @@ export function createComponent(
         if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
       } else {
         frag.hydrate()
-        if (_isLastInsertion) {
-          advanceHydrationNode(_insertionParent!)
-        }
       }
       return frag
     }
@@ -357,9 +355,6 @@ export function createComponent(
         if (_insertionParent) insert(frag, _insertionParent, _insertionAnchor)
       } else {
         frag.hydrate()
-        if (_isLastInsertion) {
-          advanceHydrationNode(_insertionParent!)
-        }
       }
 
       return frag as any
@@ -441,10 +436,6 @@ export function createComponent(
       mountComponent(instance, _insertionParent!, _insertionAnchor)
     }
 
-    if (isHydrating && _isLastInsertion) {
-      advanceHydrationNode(_insertionParent!)
-    }
-
     if (
       __FEATURE_SUSPENSE__ &&
       isSuspenseEnabled &&
@@ -466,12 +457,16 @@ export function createComponent(
         }
         finalizeHydrationBoundary()
       }
+      exitHydrationCursor(hydrationCursor)
     }
 
     return instance
   } finally {
     if (isHydrating && !deferHydrationBoundary) {
+      // Boundary cleanup still needs the component-local cursor. Only after
+      // that do we restore the outer cursor's resume point.
       finalizeHydrationBoundary()
+      exitHydrationCursor(hydrationCursor)
     }
   }
 }
@@ -912,9 +907,9 @@ export function createPlainElement(
 ): HTMLElement {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  const _isLastInsertion = isLastInsertion
+  let hydrationCursor: HydrationCursor | null = null
   if (isHydrating) {
-    locateHydrationNode()
+    hydrationCursor = enterHydrationCursor()
   } else {
     resetInsertionState()
   }
@@ -968,9 +963,7 @@ export function createPlainElement(
   if (!isHydrating) {
     if (_insertionParent) insert(el, _insertionParent, _insertionAnchor)
   } else {
-    if (_isLastInsertion) {
-      advanceHydrationNode(_insertionParent!)
-    }
+    exitHydrationCursor(hydrationCursor)
   }
 
   return el

+ 8 - 8
packages/runtime-vapor/src/componentSlots.ts

@@ -12,13 +12,14 @@ import { renderEffect } from './renderEffect'
 import {
   insertionAnchor,
   insertionParent,
-  isLastInsertion,
   resetInsertionState,
 } from './insertionState'
 import {
-  advanceHydrationNode,
+  type HydrationCursor,
+  captureHydrationCursor,
+  enterHydrationCursor,
+  exitHydrationCursor,
   isHydrating,
-  locateHydrationNode,
 } from './dom/hydration'
 import {
   type DynamicFragment,
@@ -188,8 +189,8 @@ export function createSlot(
 ): Block {
   const _insertionParent = insertionParent
   const _insertionAnchor = insertionAnchor
-  const _isLastInsertion = isLastInsertion
   if (!isHydrating) resetInsertionState()
+  let hydrationCursor: HydrationCursor | null = null
 
   const instance = getScopeOwner()!
   const rawSlots = instance.rawSlots
@@ -199,7 +200,7 @@ export function createSlot(
 
   let fragment: VaporFragment
   if (isRef(rawSlots._) && isInteropEnabled) {
-    if (isHydrating) locateHydrationNode()
+    if (isHydrating) hydrationCursor = enterHydrationCursor()
     fragment = instance.appContext.vapor!.vdomSlot(
       rawSlots._,
       name,
@@ -208,6 +209,7 @@ export function createSlot(
       fallback,
     )
   } else {
+    if (isHydrating) hydrationCursor = captureHydrationCursor()
     const slotFragment = (fragment = new SlotFragment())
     // mark the slot as forwarded
     slotFragment.forwarded =
@@ -307,9 +309,7 @@ export function createSlot(
     if (fragment.insert) {
       ;(fragment as VaporFragment).hydrate!()
     }
-    if (_isLastInsertion) {
-      advanceHydrationNode(_insertionParent!)
-    }
+    exitHydrationCursor(hydrationCursor)
   }
 
   return fragment

+ 36 - 0
packages/runtime-vapor/src/dom/hydration.ts

@@ -148,6 +148,42 @@ export function advanceHydrationNode(node: Node): void {
   }
 }
 
+export type HydrationCursor = {
+  start: Node | null
+  // `undefined` means this scope follows the cursor advanced by its body.
+  // `null` is a real resume point: the outer scope has no next node.
+  resume: Node | null | undefined
+}
+
+export function enterHydrationCursor(
+  consumeFragmentStart = false,
+): HydrationCursor {
+  const resume = insertionParent ? currentHydrationNode : undefined
+  locateHydrationNode(consumeFragmentStart)
+  return {
+    start: currentHydrationNode,
+    resume,
+  }
+}
+
+/**
+ * Capture only the outer resume cursor for dynamic wrappers whose inner owner
+ * locates the local start later, after the selected inner path is known.
+ * This avoids consuming insertion state too early.
+ */
+export function captureHydrationCursor(): HydrationCursor {
+  return {
+    start: null,
+    resume: insertionParent ? currentHydrationNode : undefined,
+  }
+}
+
+export function exitHydrationCursor(cursor: HydrationCursor | null): void {
+  if (cursor && cursor.resume !== undefined) {
+    setCurrentHydrationNode(cursor.resume)
+  }
+}
+
 /**
  * Locate the first non-fragment-comment node and locate the next node
  * while handling potential fragments.

+ 22 - 0
packages/runtime-vapor/src/fragment.ts

@@ -431,6 +431,28 @@ export class DynamicFragment extends VaporFragment {
           advanceHydrationNode(currentHydrationNode)
           return
         }
+        if (
+          this.anchorLabel &&
+          currentHydrationNode &&
+          isComment(currentHydrationNode, 'teleport anchor')
+        ) {
+          const parentNode = getParentNode(currentHydrationNode)
+          const anchor = markHydrationAnchor(currentHydrationNode)
+          if (parentNode) {
+            // Target-side teleport anchors are structural. Empty dynamic
+            // fragments insert their own anchor before the target anchor
+            // instead of consuming it as mismatched SSR content.
+            queuePostFlushCb(() => {
+              parentNode.insertBefore(
+                (this.anchor = markHydrationAnchor(
+                  __DEV__ ? createComment(this.anchorLabel!) : createTextNode(),
+                )),
+                anchor.parentNode === parentNode ? anchor : null,
+              )
+            })
+            return
+          }
+        }
         if (
           !isSlot &&
           this.anchorLabel &&

+ 1 - 12
packages/runtime-vapor/src/insertionState.ts

@@ -16,11 +16,6 @@ export let insertionAnchor: Node | 0 | undefined | null
 // logical index for hydration
 export let insertionIndex: number | undefined
 
-// indicates whether the insertion is the last one in the parent.
-// if true, means no more nodes need to be hydrated after this insertion,
-// advancing current hydration node to parent nextSibling
-export let isLastInsertion: boolean | undefined
-
 /**
  * This function is called before a block type that requires insertion
  * (component, slot outlet, if, for) is created. The state is used for actual
@@ -30,10 +25,8 @@ export function setInsertionState(
   parent: ParentNode & { $fc?: Node | null },
   anchor?: Node | 0 | null,
   logicalIndex?: number,
-  last?: boolean,
 ): void {
   insertionParent = parent
-  isLastInsertion = last
   insertionIndex = logicalIndex
 
   if (anchor !== undefined) {
@@ -52,9 +45,5 @@ export function setInsertionState(
 }
 
 export function resetInsertionState(): void {
-  insertionParent =
-    insertionAnchor =
-    insertionIndex =
-    isLastInsertion =
-      undefined
+  insertionParent = insertionAnchor = insertionIndex = undefined
 }