From 26af30cfe6216bf521e475c4b57446da26964613 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 26 Aug 2026 12:47:21 +0530 Subject: [PATCH] fix(compiler): preserve server-only component props during hydration --- bun.lock | 2 +- packages/compiler/package.json | 2 +- packages/compiler/src/codegen.ts | 9 ++++++++- packages/compiler/test/server-load-attributes.test.ts | 5 ++++- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/bun.lock b/bun.lock index e677decd..729bffd7 100644 --- a/bun.lock +++ b/bun.lock @@ -320,7 +320,7 @@ }, "packages/compiler": { "name": "@wrnexus/compiler", - "version": "0.8.24", + "version": "0.8.25", "dependencies": { "@wrnexus/csr": "workspace:*", "@wrnexus/store": "workspace:*", diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 1160ad97..008dd253 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.8.24", + "version": "0.8.25", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index a26268ef..21ef53bc 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -3196,7 +3196,7 @@ function __wrnProp(v: unknown): string { function renderPageComponentAttr( attr: Attr, dynamicExpressions: string[], - _reactive: PageReactive | null, + reactive: PageReactive | null, ): string { if (attr.event) { return ` ${componentEventAttribute(attr.name)}="${attrEscape(attr.value)}"`; @@ -3215,6 +3215,13 @@ function renderPageComponentAttr( dynamicExpressions.push(`\${__wrnexusPropAttr(${expression})}`); const marker = `\x00WRNEACH${dynamicExpressions.length - 1}\x00`; + if ( + reactive && + exprRefsState(expression, reactive.runtimeStateNames) && + !exprRefsState(expression, reactive.stateNames) + ) { + return ` ${attr.name}="${marker}"`; + } // The mount disappears during SSR. Preserve every dynamic component prop // as a parent-owned binding; server-only expressions simply fail closed in // the browser, while page state can continue driving the mounted child. diff --git a/packages/compiler/test/server-load-attributes.test.ts b/packages/compiler/test/server-load-attributes.test.ts index 46f54797..edd4be87 100644 --- a/packages/compiler/test/server-load-attributes.test.ts +++ b/packages/compiler/test/server-load-attributes.test.ts @@ -3,10 +3,12 @@ import { generate, parse } from "../src/index.ts"; test("server-only loader data renders native input attributes without client state", async () => { const code = generate( - parse(`page Profile { + parse(`import UserSelect from "./UserSelect.wrn" +page Profile { load server account { return {} } view {

{account.name}

+ {#if account.readonly}

Read only

{:else}

Editable

{/if} {#each account.items as item}{item}{/each} @@ -34,6 +36,7 @@ test("server-only loader data renders native input attributes without client sta expect(html).toContain(''); expect(html).toContain('identity="{"name":"Alice"}"'); expect(html).not.toContain("data-wrn-bind"); + expect(html).not.toContain("data-wrn-prop-bind"); expect(html).not.toContain("{account."); expect(html).toContain("

A "<User>"

"); expect(html).toContain("

Editable

");