From a85cbf240d90f731eb2f30c915e8cdd2ab998fb5 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 26 Aug 2026 13:48:48 +0530 Subject: [PATCH] fix(dev-server): preserve serialized object component props --- bun.lock | 2 +- packages/dev-server/package.json | 2 +- packages/dev-server/src/runtime.ts | 10 +++- .../test/structured-component-props.test.ts | 47 +++++++++++++++++++ 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 packages/dev-server/test/structured-component-props.test.ts diff --git a/bun.lock b/bun.lock index 729bffd7..cfb35c89 100644 --- a/bun.lock +++ b/bun.lock @@ -357,7 +357,7 @@ }, "packages/dev-server": { "name": "@wrnexus/dev-server", - "version": "0.8.56", + "version": "0.8.57", "dependencies": { "@wrnexus/authz": "workspace:*", "@wrnexus/cache": "workspace:*", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index 261c74fb..6e5ce518 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/dev-server", - "version": "0.8.56", + "version": "0.8.57", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/dev-server/src/runtime.ts b/packages/dev-server/src/runtime.ts index cdf712ec..537fa319 100644 --- a/packages/dev-server/src/runtime.ts +++ b/packages/dev-server/src/runtime.ts @@ -1746,7 +1746,15 @@ export function createHandlers(deps: RuntimeDeps): Handlers { // component defaults creates the real child boundary; the bridged prop // binding synchronizes the live value when the branch is instantiated. for (const [key, value] of Object.entries(props)) { - if (/^\{[\s\S]*\}$/.test(value) && !/^\{t:/.test(value)) delete props[key]; + if (/^\{[\s\S]*\}$/.test(value) && !/^\{t:/.test(value)) { + // SSR object props also start/end with braces. Preserve serialized + // JSON; only unresolved expressions should fall back to defaults. + try { + JSON.parse(value); + } catch { + delete props[key]; + } + } } if (normalizedName === "languageswitcher" && deps.i18n) { props.locales ??= JSON.stringify( diff --git a/packages/dev-server/test/structured-component-props.test.ts b/packages/dev-server/test/structured-component-props.test.ts new file mode 100644 index 00000000..608072e7 --- /dev/null +++ b/packages/dev-server/test/structured-component-props.test.ts @@ -0,0 +1,47 @@ +import { expect, test } from "bun:test"; +import { createProductionHandlers } from "../src/prod.ts"; + +test("component resolution preserves JSON user/permission props and drops unresolved bindings", async () => { + let received: Record = {}; + const handlers = createProductionHandlers( + { + pages: [ + { + raw: "/", + mod: { + default: () => + '

Account content

', + }, + }, + ], + api: [], + realtime: [], + middleware: [], + layouts: [], + components: [ + { + name: "AccountShell", + mod: { + render: (props: Record) => { + received = props; + const identity = JSON.parse(props.identity ?? "{}"); + const permissions = JSON.parse(props.permissions ?? "{}"); + return permissions.read + ? "
" + identity.displayName + "
" + : "
Denied
"; + }, + }, + }, + ], + }, + {}, + ); + const response = await handlers.fetch(new Request("http://localhost/"), { upgrade: () => false }); + expect(response?.status).toBe(200); + const html = await response!.text(); + expect(html).toContain("Alice"); + expect(html).toContain("Account content"); + expect(JSON.parse(received.permissions!)).toEqual({ read: true, update: false }); + expect(JSON.parse(received.preferences!)).toEqual({ theme: "dark" }); + expect(received.pending).toBeUndefined(); +});