From 25a421c72704d3020c04114e89c610c2af977586 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Wed, 26 Aug 2026 15:11:27 +0530 Subject: [PATCH] fix(compiler): preserve native boolean semantics in page branches --- bun.lock | 2 +- packages/compiler/package.json | 2 +- packages/compiler/src/codegen.ts | 13 +++++++++++ .../conditional-boolean-attributes.test.ts | 23 +++++++++++++++++++ 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 packages/compiler/test/conditional-boolean-attributes.test.ts diff --git a/bun.lock b/bun.lock index 373a7d58..64887ff4 100644 --- a/bun.lock +++ b/bun.lock @@ -320,7 +320,7 @@ }, "packages/compiler": { "name": "@wrnexus/compiler", - "version": "0.8.25", + "version": "0.8.26", "dependencies": { "@wrnexus/csr": "workspace:*", "@wrnexus/store": "workspace:*", diff --git a/packages/compiler/package.json b/packages/compiler/package.json index 008dd253..e80eefda 100644 --- a/packages/compiler/package.json +++ b/packages/compiler/package.json @@ -1,6 +1,6 @@ { "name": "@wrnexus/compiler", - "version": "0.8.25", + "version": "0.8.26", "type": "module", "main": "src/index.ts", "exports": { diff --git a/packages/compiler/src/codegen.ts b/packages/compiler/src/codegen.ts index 21ef53bc..992f47af 100644 --- a/packages/compiler/src/codegen.ts +++ b/packages/compiler/src/codegen.ts @@ -511,6 +511,19 @@ function renderLoopBody(node: ViewNode, locals: string[] = []): string { return escLit(` ${name}`); } + // Native boolean attributes inside #if/#each must use presence semantics, + // just like attributes on the page root. disabled="false" still disables + // an HTML input. Component props retain their serialized boolean value. + if ( + !componentTag && + !node.attrs.some((a) => a.name === "data-component") && + isHtmlBooleanAttribute(name) + ) { + const expression = wholeAttributeExpression(attr.value); + if (expression) return "${(" + expression + ") ? " + JSON.stringify(` ${name}`) + ' : ""}'; + if (attr.value === "false") return ""; + } + return escLit(` ${name}="`) + bakeLoopAttr(attr.value, componentTag) + escLit(`"`); }) .join(""); diff --git a/packages/compiler/test/conditional-boolean-attributes.test.ts b/packages/compiler/test/conditional-boolean-attributes.test.ts new file mode 100644 index 00000000..61ad2c49 --- /dev/null +++ b/packages/compiler/test/conditional-boolean-attributes.test.ts @@ -0,0 +1,23 @@ +import { expect, test } from "bun:test"; +import { parse, generate } from "../src/index.ts"; + +test("native boolean attributes inside conditional and loop bodies use presence semantics", async () => { + const source = `page Example { + load server account { return {} } + view { + {#if account.visible}{/if} + {#each account.rows as row}{/each} + } + }`; + const code = new Bun.Transpiler({ loader: "ts" }).transformSync(generate(parse(source))); + const mod = await import("data:text/javascript;base64," + Buffer.from(code).toString("base64")); + const html = await mod.default({ + account: { visible: true, edit: true, rows: [{ checked: false }, { checked: true }] }, + }); + expect(html).not.toContain("disabled"); + expect(html).not.toContain('checked="false"'); + expect(html.match(/ checked/g)).toHaveLength(1); + expect(html).toContain("required"); + const denied = await mod.default({ account: { visible: true, edit: false, rows: [] } }); + expect(denied).toContain(" disabled"); +});