diff --git a/packages/runtime-tests/integration/components/static.test.tsx b/packages/runtime-tests/integration/components/static.test.tsx index 01713c7..e01bb82 100644 --- a/packages/runtime-tests/integration/components/static.test.tsx +++ b/packages/runtime-tests/integration/components/static.test.tsx @@ -130,3 +130,28 @@ test("Static flush clears the dynamic frame first (non-debug mode)", async () => app.unmount(); }); + +test("Static items do not add blank lines to the dynamic frame", async () => { + const items = ref([]); + + const App = defineComponent(() => () => ( + + + {{ + default: ({ item }: { item: string }) => {item}, + }} + + [live] + + )); + + const { lastFrame } = await render(App); + expect(lastFrame()).toBe("[live]"); + + items.value = ["a", "b", "c"]; + await nextTick(); + + // The dynamic frame should ONLY contain [live], no blank lines from Static + const frame = lastFrame()!; + expect(frame).toBe("[live]"); +}); diff --git a/packages/runtime/src/host/yoga.ts b/packages/runtime/src/host/yoga.ts index 9b6c996..721e7a7 100644 --- a/packages/runtime/src/host/yoga.ts +++ b/packages/runtime/src/host/yoga.ts @@ -43,6 +43,11 @@ function hasYoga(node: TuiNode): node is YogaCarrier { export function attachYoga(node: YogaCarrier): void { node.yoga = createYogaNode(); + // Static nodes are painted via a separate channel (paintIsolated), so they + // must not occupy space in the dynamic frame's yoga layout. + if (node.type === "static") { + (node.yoga as YogaNode).setDisplay(Yoga.DISPLAY_NONE); + } } export function detachYoga(node: YogaCarrier): void {