@barefootjs/cli 0.28.1 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1025 -473
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -3374,13 +3374,13 @@ function csrSubstituteOnce(value2, env) {
3374
3374
  const recordSubstitution = (start2, end2, sub2) => {
3375
3375
  splices.push({ start: start2 - OFFSET, end: end2 - OFFSET, text: `(${sub2.replacement})` });
3376
3376
  };
3377
- const collectBindingNames3 = (name2, out) => {
3377
+ const collectBindingNames4 = (name2, out) => {
3378
3378
  if (ts4.isIdentifier(name2)) out.add(name2.text);
3379
3379
  else if (ts4.isObjectBindingPattern(name2)) {
3380
- for (const el of name2.elements) collectBindingNames3(el.name, out);
3380
+ for (const el of name2.elements) collectBindingNames4(el.name, out);
3381
3381
  } else if (ts4.isArrayBindingPattern(name2)) {
3382
3382
  for (const el of name2.elements) {
3383
- if (!ts4.isOmittedExpression(el)) collectBindingNames3(el.name, out);
3383
+ if (!ts4.isOmittedExpression(el)) collectBindingNames4(el.name, out);
3384
3384
  }
3385
3385
  }
3386
3386
  };
@@ -3388,7 +3388,7 @@ function csrSubstituteOnce(value2, env) {
3388
3388
  for (const stmt2 of block.statements) {
3389
3389
  if (ts4.isVariableStatement(stmt2)) {
3390
3390
  for (const decl of stmt2.declarationList.declarations) {
3391
- collectBindingNames3(decl.name, out);
3391
+ collectBindingNames4(decl.name, out);
3392
3392
  }
3393
3393
  } else if (ts4.isFunctionDeclaration(stmt2) && stmt2.name) {
3394
3394
  out.add(stmt2.name.text);
@@ -3431,7 +3431,7 @@ function csrSubstituteOnce(value2, env) {
3431
3431
  }
3432
3432
  if (ts4.isArrowFunction(node) || ts4.isFunctionExpression(node)) {
3433
3433
  const bound = /* @__PURE__ */ new Set();
3434
- for (const p of node.parameters) collectBindingNames3(p.name, bound);
3434
+ for (const p of node.parameters) collectBindingNames4(p.name, bound);
3435
3435
  if (node.body && ts4.isBlock(node.body)) {
3436
3436
  collectBlockDeclarations(node.body, bound);
3437
3437
  }
@@ -3535,7 +3535,12 @@ function buildSignalMemoEnv(signals, memos, propsObjectName) {
3535
3535
  for (const m of memos) {
3536
3536
  substitutions.set(m.name, {
3537
3537
  kind: "call",
3538
- replacement: extractMemoBodyExpr(m.computation),
3538
+ // Destructured mode (#2468): `templateComputation` (when present) has
3539
+ // bare destructured prop refs already rewritten to `_p.X` — the
3540
+ // spliced body lands in the module-scope template arrow, which is not
3541
+ // a closure over init's `const value = _p.value` extraction. Mirrors
3542
+ // the signal `templateInitialValue` handling above (#2265).
3543
+ replacement: extractMemoBodyExpr(m.templateComputation ?? m.computation),
3539
3544
  freeIdentifiers: m.computationFreeIdentifiers ?? /* @__PURE__ */ new Set()
3540
3545
  });
3541
3546
  }
@@ -3558,6 +3563,16 @@ var init_csr_substitute = __esm({
3558
3563
  }
3559
3564
  });
3560
3565
 
3566
+ // ../jsx/src/adapters/child-scope.ts
3567
+ function derivesScopeFromSlot(comp) {
3568
+ return comp.slotId != null && comp.loopItemRoot !== true;
3569
+ }
3570
+ var init_child_scope = __esm({
3571
+ "../jsx/src/adapters/child-scope.ts"() {
3572
+ "use strict";
3573
+ }
3574
+ });
3575
+
3561
3576
  // ../jsx/src/ir-to-client-js/html-template.ts
3562
3577
  function createStringProtector() {
3563
3578
  const strings = [];
@@ -3848,7 +3863,7 @@ function renderFlatMapClientBody(cb, restSpreadNames) {
3848
3863
  rawLeaf: true,
3849
3864
  renderLeaf: (ir) => {
3850
3865
  const key = flatMapLeafKeyExpr(ir);
3851
- const html = irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, 1, void 0, void 0, true);
3866
+ const html = irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, 1, void 0, void 0);
3852
3867
  return `({ k: ${key ?? "undefined"}, h: \`${html}\` })`;
3853
3868
  }
3854
3869
  });
@@ -3860,7 +3875,7 @@ function renderFlatMapProjectionClientBody(inner, restSpreadNames) {
3860
3875
  const chained = applyLoopChain(inner);
3861
3876
  const params = inner.index ? `(${inner.param}, ${inner.index})` : `(${inner.param})`;
3862
3877
  const key = inner.key ? `(${inner.key})` : "undefined";
3863
- const html = inner.children.map((c) => irToHtmlTemplate(escapeLeafTextExpressions(c), restSpreadNames, 1, void 0, void 0, true)).join("");
3878
+ const html = inner.children.map((c) => irToHtmlTemplate(escapeLeafTextExpressions(c), restSpreadNames, 1, void 0, void 0)).join("");
3864
3879
  return `${chained}.map(${params} => ({ k: ${key}, h: \`${html}\` }))`;
3865
3880
  }
3866
3881
  function escapeLeafTextExpressions(ir) {
@@ -3884,8 +3899,8 @@ function escapeLeafTextExpressions(ir) {
3884
3899
  return ir;
3885
3900
  }
3886
3901
  }
3887
- function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, branchSlotsVar, insideLoop = false, inHoistedChildren = false) {
3888
- const recurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, insideLoop, inHoistedChildren);
3902
+ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, branchSlotsVar, inHoistedChildren = false) {
3903
+ const recurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, inHoistedChildren);
3889
3904
  const wrapExpr = (expr) => wrapExprWithLoopParams(expr, loopParams);
3890
3905
  const wrapInterpolation = (expr) => branchSlotsVar ? `__bfSlot(${expr}, ${branchSlotsVar})` : expr;
3891
3906
  switch (node.type) {
@@ -3915,7 +3930,7 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3915
3930
  attrParts.push(`bf="${node.slotId}"`);
3916
3931
  }
3917
3932
  const attrs = attrParts.join(" ");
3918
- const childrenRecurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, insideLoop, false);
3933
+ const childrenRecurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, false);
3919
3934
  const children2 = dangerouslyHtmlChildren(node.attrs, (v) => wrapExpr(v.expr)) ?? node.children.map(childrenRecurse).join("");
3920
3935
  if (children2 || !VOID_ELEMENTS.has(node.tag)) {
3921
3936
  return `<${node.tag}${attrs ? " " + attrs : ""}>${children2}</${node.tag}>`;
@@ -3951,11 +3966,11 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3951
3966
  if (node.name === "Portal") {
3952
3967
  return node.children.map(recurse).join("");
3953
3968
  }
3954
- const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
3969
+ const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => p.name !== "ref" && !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
3955
3970
  if (p.clientOnly) return null;
3956
3971
  switch (p.value.kind) {
3957
3972
  case "jsx-children": {
3958
- const hoistedRecurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, insideLoop, true);
3973
+ const hoistedRecurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth, loopParams, branchSlotsVar, true);
3959
3974
  const childHtml = p.value.children.map((c) => hoistedRecurse(c)).join("");
3960
3975
  return `${quotePropName(p.name)}: \`${childHtml}\``;
3961
3976
  }
@@ -3968,7 +3983,7 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3968
3983
  case "expression":
3969
3984
  case "template":
3970
3985
  case "spread": {
3971
- const expr = attrValueToString(p.value) ?? "undefined";
3986
+ const expr = attrValueToString(p.value, { useTemplate: true }) ?? "undefined";
3972
3987
  return `${quotePropName(p.name)}: ${expr}`;
3973
3988
  }
3974
3989
  }
@@ -3978,11 +3993,11 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3978
3993
  const propsExpr = propsEntries.length > 0 ? `{${propsEntries.join(", ")}}` : "{}";
3979
3994
  const keyProp = node.props.find((p) => p.name === "key");
3980
3995
  const keyArg = keyProp ? `, ${attrValueToString(keyProp.value) ?? "undefined"}` : "";
3981
- const slotArg = !insideLoop && node.slotId ? `, '${node.slotId}'` : "";
3996
+ const slotArg = derivesScopeFromSlot(node) ? `, '${node.slotId}'` : "";
3982
3997
  return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ", undefined" : "")}${slotArg})}`;
3983
3998
  }
3984
3999
  case "loop": {
3985
- const innerRecurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop);
4000
+ const innerRecurse = (n) => irToHtmlTemplate(n, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar);
3986
4001
  let childTemplate = node.children.map(innerRecurse).join("");
3987
4002
  if (node.bodyIsItemConditional && node.key) {
3988
4003
  childTemplate = `${itemAnchorTemplate(node.key)}${childTemplate}`;
@@ -3995,11 +4010,11 @@ function irToHtmlTemplate(node, restSpreadNames, loopDepth = 0, loopParams, bran
3995
4010
  let mapExpr;
3996
4011
  if (node.flatMapCallback) {
3997
4012
  const body2 = renderPreamble(node.flatMapCallback, {
3998
- renderLeaf: (ir) => irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop)
4013
+ renderLeaf: (ir) => irToHtmlTemplate(stripLeafKeyAttr(ir), restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar)
3999
4014
  });
4000
4015
  mapExpr = `\${${wrappedArray}.flatMap(${node.flatMapCallback.params} => ${body2}).join('')}`;
4001
4016
  } else if (node.preamble) {
4002
- const preamble = renderPreamble(node.preamble, { textVariant: "client", renderLeaf: (ir) => irToHtmlTemplate(ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar, insideLoop) });
4017
+ const preamble = renderPreamble(node.preamble, { textVariant: "client", renderLeaf: (ir) => irToHtmlTemplate(ir, restSpreadNames, loopDepth + 1, loopParams, branchSlotsVar) });
4003
4018
  mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => { ${preamble} return \`${childTemplate}\` }).join('')}`;
4004
4019
  } else {
4005
4020
  mapExpr = `\${${wrappedArray}.${iterMethod}(${callbackParam} => \`${childTemplate}\`).join('')}`;
@@ -4468,7 +4483,7 @@ function irToComponentTemplateWithOpts(node, opts) {
4468
4483
  if (node.name === "Portal") {
4469
4484
  return node.children.map(recurse).join("");
4470
4485
  }
4471
- const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
4486
+ const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => p.name !== "ref" && !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
4472
4487
  if (p.clientOnly) return null;
4473
4488
  switch (p.value.kind) {
4474
4489
  case "jsx-children": {
@@ -4578,7 +4593,7 @@ function canGenerateStaticTemplate(node, propNames, inlinableConstants, unsafeLo
4578
4593
  return assertNever(node);
4579
4594
  }
4580
4595
  }
4581
- function generateCsrTemplate(node, inlinableConstants, ctx2, insideLoop, restSpreadNames, propsObjectName, unsafeLocalNames, deferredChildSlots) {
4596
+ function generateCsrTemplate(node, inlinableConstants, ctx2, restSpreadNames, propsObjectName, unsafeLocalNames, deferredChildSlots) {
4582
4597
  const base = buildSignalMemoEnv(ctx2.signals, ctx2.memos, propsObjectName ?? null);
4583
4598
  const csrEnv = { substitutions: new Map(base.substitutions), propsObjectName: base.propsObjectName };
4584
4599
  if (inlinableConstants) {
@@ -4589,7 +4604,7 @@ function generateCsrTemplate(node, inlinableConstants, ctx2, insideLoop, restSpr
4589
4604
  }
4590
4605
  }
4591
4606
  const effectiveUnsafeLocalNames = mergeCsrNullUnsafe(ctx2, unsafeLocalNames);
4592
- return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames: effectiveUnsafeLocalNames, deferredChildSlots, loopDepth: -1 });
4607
+ return generateCsrTemplateWithOpts(node, { inlinableConstants, restSpreadNames, propsObjectName, csrEnv, unsafeLocalNames: effectiveUnsafeLocalNames, deferredChildSlots, loopDepth: -1 });
4593
4608
  }
4594
4609
  function mergeCsrNullUnsafe(ctx2, unsafeLocalNames) {
4595
4610
  let merged = null;
@@ -4647,6 +4662,7 @@ function computeDeferredChildSlots(node, ctx2, inlinableConstants, unsafeLocalNa
4647
4662
  if (n.slotId) {
4648
4663
  const dropped = n.props.some((p) => {
4649
4664
  if (p.name === "..." || p.name.startsWith("...") || p.name === "key") return false;
4665
+ if (p.name === "ref") return false;
4650
4666
  if (p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase()) return false;
4651
4667
  if (p.clientOnly) return false;
4652
4668
  return propResolvesUnsafe(p, env, unsafeLocalNames);
@@ -4673,7 +4689,7 @@ function computeDeferredChildSlots(node, ctx2, inlinableConstants, unsafeLocalNa
4673
4689
  return deferred;
4674
4690
  }
4675
4691
  function generateCsrTemplateWithOpts(node, opts) {
4676
- const { restSpreadNames, propsObjectName, csrEnv, insideLoop, unsafeLocalNames, loopDepth = 0 } = opts;
4692
+ const { restSpreadNames, propsObjectName, csrEnv, unsafeLocalNames, loopDepth = 0 } = opts;
4677
4693
  const env = csrEnv ?? { substitutions: /* @__PURE__ */ new Map(), propsObjectName: propsObjectName ?? null };
4678
4694
  const transformExpr = (expr, templateExpr) => {
4679
4695
  const source = templateExpr ?? expr;
@@ -4686,7 +4702,6 @@ function generateCsrTemplateWithOpts(node, opts) {
4686
4702
  };
4687
4703
  const recurse = (n) => generateCsrTemplateWithOpts(n, opts);
4688
4704
  const childrenRecurse = (n) => generateCsrTemplateWithOpts(n, { ...opts, inHoistedChildren: false });
4689
- const recurseInLoop = (n) => generateCsrTemplateWithOpts(n, { ...opts, insideLoop: true, loopDepth: loopDepth + 1, inHoistedChildren: false });
4690
4705
  switch (node.type) {
4691
4706
  case "element": {
4692
4707
  const mergeCtx = {
@@ -4775,7 +4790,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4775
4790
  if (node.slotId && opts.deferredChildSlots?.has(node.slotId)) {
4776
4791
  return `<div ${BF_PLACEHOLDER}="${node.slotId}"></div>`;
4777
4792
  }
4778
- const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
4793
+ const propsEntries = node.props.filter((p) => p.name !== "..." && !p.name.startsWith("...") && p.name !== "key").filter((p) => p.name !== "ref" && !(p.name.startsWith("on") && p.name.length > 2 && p.name[2] === p.name[2].toUpperCase())).map((p) => {
4779
4794
  if (p.clientOnly) return null;
4780
4795
  switch (p.value.kind) {
4781
4796
  case "jsx-children": {
@@ -4808,7 +4823,7 @@ function generateCsrTemplateWithOpts(node, opts) {
4808
4823
  const propsExpr = propsEntries.length > 0 ? `{${propsEntries.join(", ")}}` : "{}";
4809
4824
  const keyProp = node.props.find((p) => p.name === "key");
4810
4825
  const keyArg = keyProp ? `, ${transformKeyValue(keyProp.value, transformExpr)}` : "";
4811
- const slotArg = !insideLoop && node.slotId ? `, '${node.slotId}'` : "";
4826
+ const slotArg = derivesScopeFromSlot(node) ? `, '${node.slotId}'` : "";
4812
4827
  return `\${renderChild('${nameForRegistryRef(node.name)}', ${propsExpr}${keyArg || (slotArg ? ", undefined" : "")}${slotArg})}`;
4813
4828
  }
4814
4829
  case "loop": {
@@ -4825,7 +4840,6 @@ function generateCsrTemplateWithOpts(node, opts) {
4825
4840
  };
4826
4841
  const recurseInLoopBody = (n) => generateCsrTemplateWithOpts(n, {
4827
4842
  ...opts,
4828
- insideLoop: true,
4829
4843
  loopDepth: loopDepth + 1,
4830
4844
  inHoistedChildren: false,
4831
4845
  loopBoundNames: boundHere,
@@ -4897,6 +4911,7 @@ var init_html_template = __esm({
4897
4911
  init_csr_substitute();
4898
4912
  init_src();
4899
4913
  init_loop_chain();
4914
+ init_child_scope();
4900
4915
  VOID_ELEMENTS = /* @__PURE__ */ new Set([
4901
4916
  "area",
4902
4917
  "base",
@@ -5355,6 +5370,30 @@ function typeNodeToTypeInfo(typeNode, sourceFile, rawOf) {
5355
5370
  if (ts7.isArrayTypeNode(typeNode)) {
5356
5371
  return { kind: "array", raw, elementType: recurse(typeNode.elementType) };
5357
5372
  }
5373
+ if (ts7.isLiteralTypeNode(typeNode)) {
5374
+ const lit = typeNode.literal;
5375
+ if (ts7.isStringLiteral(lit) || ts7.isNoSubstitutionTemplateLiteral(lit)) {
5376
+ return { kind: "primitive", raw, primitive: "string", literalValue: lit.text };
5377
+ }
5378
+ if (ts7.isNumericLiteral(lit)) {
5379
+ return { kind: "primitive", raw, primitive: "number", literalValue: lit.text };
5380
+ }
5381
+ if (ts7.isPrefixUnaryExpression(lit) && lit.operator === ts7.SyntaxKind.MinusToken && ts7.isNumericLiteral(lit.operand)) {
5382
+ return { kind: "primitive", raw, primitive: "number", literalValue: `-${lit.operand.text}` };
5383
+ }
5384
+ if (lit.kind === ts7.SyntaxKind.TrueKeyword || lit.kind === ts7.SyntaxKind.FalseKeyword) {
5385
+ return {
5386
+ kind: "primitive",
5387
+ raw,
5388
+ primitive: "boolean",
5389
+ literalValue: lit.kind === ts7.SyntaxKind.TrueKeyword ? "true" : "false"
5390
+ };
5391
+ }
5392
+ if (lit.kind === ts7.SyntaxKind.NullKeyword) {
5393
+ return { kind: "primitive", raw, primitive: "null" };
5394
+ }
5395
+ return { kind: "unknown", raw };
5396
+ }
5358
5397
  if (ts7.isUnionTypeNode(typeNode)) {
5359
5398
  return { kind: "union", raw, unionTypes: typeNode.types.map(recurse) };
5360
5399
  }
@@ -5647,8 +5686,7 @@ function baseTypeName(raw) {
5647
5686
  return (idx === -1 ? raw : raw.slice(0, idx)).trim();
5648
5687
  }
5649
5688
  function isNullishArm(t) {
5650
- if (t.kind === "primitive" && (t.primitive === "null" || t.primitive === "undefined")) return true;
5651
- return t.kind === "unknown" && (t.raw === "null" || t.raw === "undefined");
5689
+ return t.kind === "primitive" && (t.primitive === "null" || t.primitive === "undefined");
5652
5690
  }
5653
5691
  function stripUnion(type2) {
5654
5692
  if (!type2 || type2.kind !== "union" || !type2.unionTypes) return type2;
@@ -6339,14 +6377,16 @@ function collectSignal(node, ctx2) {
6339
6377
  const pattern = node.name;
6340
6378
  const callExpr = node.initializer;
6341
6379
  const elements2 = pattern.elements;
6342
- if (elements2.length < 1 || elements2.length > 2 || !ts8.isBindingElement(elements2[0]) || !ts8.isIdentifier(elements2[0].name)) {
6380
+ const getterElided = elements2.length === 2 && ts8.isOmittedExpression(elements2[0]);
6381
+ if (elements2.length < 1 || elements2.length > 2 || !getterElided && (!ts8.isBindingElement(elements2[0]) || !ts8.isIdentifier(elements2[0].name))) {
6343
6382
  return;
6344
6383
  }
6345
6384
  if (elements2.length === 2 && (!ts8.isBindingElement(elements2[1]) || !ts8.isIdentifier(elements2[1].name))) {
6346
6385
  return;
6347
6386
  }
6348
- const getter = elements2[0].name.text;
6349
6387
  const setter = elements2.length === 2 && ts8.isBindingElement(elements2[1]) && ts8.isIdentifier(elements2[1].name) ? elements2[1].name.text : null;
6388
+ if (getterElided && !setter) return;
6389
+ const getter = getterElided ? `__bfGet_${setter}` : elements2[0].name.text;
6350
6390
  const initialValue = callExpr.arguments[0] ? ctx2.getJS(callExpr.arguments[0]) : "";
6351
6391
  const typedInitialValue = callExpr.arguments[0] ? callExpr.arguments[0].getText(ctx2.sourceFile) : void 0;
6352
6392
  let type2 = { kind: "unknown", raw: "unknown" };
@@ -6367,6 +6407,7 @@ function collectSignal(node, ctx2) {
6367
6407
  ctx2.signals.push({
6368
6408
  getter,
6369
6409
  setter,
6410
+ getterElided: getterElided || void 0,
6370
6411
  initialValue,
6371
6412
  typedInitialValue: typedInitialValue !== initialValue ? typedInitialValue : void 0,
6372
6413
  templateInitialValue,
@@ -6538,9 +6579,17 @@ function collectMemo(node, ctx2) {
6538
6579
  const blockBody = arrowNode && ts8.isArrowFunction(arrowNode) && ts8.isBlock(arrowNode.body) ? arrowNode.body : void 0;
6539
6580
  const parsedBlock = blockBody ? parseBlockBodyTolerant(blockBody, ctx2.sourceFile, (node2) => ctx2.getJS(node2)) : void 0;
6540
6581
  const parsedBlockComplete = parsedBlock && blockBody ? parsedBlock.length === blockBody.statements.length : void 0;
6582
+ let templateComputation;
6583
+ if (!ctx2.propsObjectName && callExpr.arguments[0]) {
6584
+ const propNames = new Set(ctx2.propsParams.map((p) => p.name));
6585
+ if (propNames.size > 0) {
6586
+ templateComputation = rewriteBarePropRefs(computation, callExpr.arguments[0], propNames);
6587
+ }
6588
+ }
6541
6589
  ctx2.memos.push({
6542
6590
  name: name2,
6543
6591
  computation,
6592
+ templateComputation,
6544
6593
  parsedBlock,
6545
6594
  parsedBlockComplete,
6546
6595
  typedComputation: typedComputation !== computation ? typedComputation : void 0,
@@ -9627,8 +9676,10 @@ function deriveFormat(locale, probeOptions) {
9627
9676
  return { pattern, names };
9628
9677
  }
9629
9678
  function unionMemberLiteral(member) {
9630
- const m = /^'([^'\\]*)'$|^"([^"\\]*)"$/.exec(member.raw.trim());
9631
- return m ? m[1] ?? m[2] : null;
9679
+ if (member.kind === "primitive" && member.primitive === "string" && member.literalValue !== void 0) {
9680
+ return member.literalValue;
9681
+ }
9682
+ return null;
9632
9683
  }
9633
9684
  function resolveLocaleUnionMembers(locale, metadata) {
9634
9685
  let sourcePropName = null;
@@ -10185,6 +10236,14 @@ function jsxToIR(analyzer) {
10185
10236
  function buildIRRoot(analyzer) {
10186
10237
  if (analyzer.conditionalReturns.length > 0) {
10187
10238
  const ctx3 = createTransformContext(analyzer);
10239
+ const allReactiveNoLocals = analyzer.jsxReturn != null && analyzer.conditionalReturns.every(
10240
+ (cr) => cr.scopeVariables.length === 0 && exprCallsReactiveGetters(cr.condition, ctx3)
10241
+ );
10242
+ if (allReactiveNoLocals) {
10243
+ const chain = buildIfStatementChain(analyzer, ctx3, { asConditional: true });
10244
+ if (!chain) return null;
10245
+ return chain.type === "conditional" ? wrapInScopeElement(chain) : chain;
10246
+ }
10188
10247
  return buildIfStatementChain(analyzer, ctx3);
10189
10248
  }
10190
10249
  if (!analyzer.jsxReturn) return null;
@@ -10336,6 +10395,50 @@ function transformJsxElement(node, ctx2) {
10336
10395
  }
10337
10396
  return transformHtmlElement(node, ctx2, tagName2);
10338
10397
  }
10398
+ function lowerFormControlValueSsr(tagName2, attrs, children2) {
10399
+ if (tagName2 !== "textarea" && tagName2 !== "select") return;
10400
+ const valueAttr = attrs.find((a) => a.name === "value");
10401
+ if (!valueAttr || valueAttr.clientOnly || valueAttr.value.kind !== "expression") return;
10402
+ const { expr, templateExpr } = valueAttr.value;
10403
+ valueAttr.clientOnly = true;
10404
+ if (tagName2 === "textarea") {
10405
+ if (children2.length > 0) return;
10406
+ children2.push({
10407
+ type: "expression",
10408
+ expr,
10409
+ // The client-side registration template interpolates slotless
10410
+ // expression children RAW (no text-slot `escapeText` wrapper), so a
10411
+ // value containing `</textarea>` would break out of the element on
10412
+ // CSR mount. Escape in the client-only template variant; `expr`
10413
+ // stays clean for the SSR adapters, whose template engines (and
10414
+ // hono/jsx) already escape text children natively.
10415
+ templateExpr: `escapeText(${templateExpr ?? expr})`,
10416
+ typeInfo: null,
10417
+ reactive: false,
10418
+ slotId: null,
10419
+ loc: valueAttr.loc,
10420
+ origin: { phase: "ssr", scope: "template", effect: "pure" }
10421
+ });
10422
+ return;
10423
+ }
10424
+ const selectedFor = (optValue) => AttrValueOf.expression(
10425
+ `(${expr}) === ${JSON.stringify(optValue)}`,
10426
+ templateExpr !== void 0 ? { templateExpr: `(${templateExpr}) === ${JSON.stringify(optValue)}` } : void 0
10427
+ );
10428
+ const distribute = (nodes) => {
10429
+ for (const n of nodes) {
10430
+ if (n.type === "element" && n.tag === "option") {
10431
+ if (n.attrs.some((a) => a.name === "selected")) continue;
10432
+ const optValue = n.attrs.find((a) => a.name === "value");
10433
+ if (!optValue || optValue.value.kind !== "literal") continue;
10434
+ n.attrs.push({ name: "selected", value: selectedFor(optValue.value.value), loc: n.loc });
10435
+ } else if (n.type === "fragment" || n.type === "element" && n.tag === "optgroup") {
10436
+ distribute(n.children);
10437
+ }
10438
+ }
10439
+ };
10440
+ distribute(children2);
10441
+ }
10339
10442
  function transformHtmlElement(node, ctx2, tagName2) {
10340
10443
  const { attrs, events, ref } = processAttributes(
10341
10444
  node.openingElement.attributes,
@@ -10344,6 +10447,7 @@ function transformHtmlElement(node, ctx2, tagName2) {
10344
10447
  const needsScope = ctx2.isRoot;
10345
10448
  ctx2.isRoot = false;
10346
10449
  const children2 = transformChildren(node.children, ctx2);
10450
+ lowerFormControlValueSsr(tagName2, attrs, children2);
10347
10451
  const needsSlot = events.length > 0 || hasDynamicContent(children2) || hasReactiveAttributes(attrs, ctx2) || ref !== null;
10348
10452
  const slotId = needsSlot ? generateSlotId(ctx2) : null;
10349
10453
  if (slotId) {
@@ -10380,6 +10484,8 @@ function transformSelfClosingElement(node, ctx2) {
10380
10484
  return transformSelfClosingComponent(node, ctx2, resolved ?? tagName2);
10381
10485
  }
10382
10486
  const { attrs, events, ref } = processAttributes(node.attributes, ctx2);
10487
+ const selfClosingChildren = [];
10488
+ lowerFormControlValueSsr(tagName2, attrs, selfClosingChildren);
10383
10489
  const needsSlot = events.length > 0 || hasReactiveAttributes(attrs, ctx2) || ref !== null;
10384
10490
  const slotId = needsSlot ? generateSlotId(ctx2) : null;
10385
10491
  const needsScope = ctx2.isRoot;
@@ -10390,7 +10496,7 @@ function transformSelfClosingElement(node, ctx2) {
10390
10496
  attrs,
10391
10497
  events,
10392
10498
  ref,
10393
- children: [],
10499
+ children: selfClosingChildren,
10394
10500
  slotId,
10395
10501
  needsScope,
10396
10502
  loc: getSourceLocation(node, ctx2.sourceFile, ctx2.filePath)
@@ -11747,6 +11853,15 @@ function branchHasNoElement(node) {
11747
11853
  }
11748
11854
  return true;
11749
11855
  }
11856
+ function tagLoopItemRootComponents(nodes) {
11857
+ for (const node of nodes) {
11858
+ if (node.type === "component") {
11859
+ node.loopItemRoot = true;
11860
+ } else if (node.type === "conditional") {
11861
+ tagLoopItemRootComponents([node.whenTrue, node.whenFalse]);
11862
+ }
11863
+ }
11864
+ }
11750
11865
  function loopBodyItemConditional(children2) {
11751
11866
  const real = children2.filter(
11752
11867
  (c) => !(c.type === "text" && typeof c.value === "string" && !c.value.trim())
@@ -12015,10 +12130,10 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
12015
12130
  const pre = multiReturn.preamble ?? [];
12016
12131
  if (pre.length > 0) {
12017
12132
  preamble = preambleFromValueStatements(pre, ctx2);
12018
- if (!isClientOnly && !(ctx2.analyzer.acceptsCallbackBody?.("map") ?? false)) {
12133
+ if (!preamble.declarations && !isClientOnly && !(ctx2.analyzer.acceptsCallbackBody?.("map") ?? false)) {
12019
12134
  ctx2.analyzer.errors.push(
12020
12135
  createError(ErrorCodes.UNSUPPORTED_JSX_PATTERN, loc, {
12021
- message: "A .map() callback body with a `const`/`let` preamble before its branches cannot be lowered to a template: the loop-local binding cannot be carried into a conditional branch on this backend.",
12136
+ message: "A .map() callback body with a preamble before its branches cannot be lowered to a template: the preamble is not a sequence of value declarations, so this backend has no per-row local to carry into the branches.",
12022
12137
  suggestion: {
12023
12138
  message: "Add /* @client */ to evaluate this expression on the client only"
12024
12139
  }
@@ -12104,6 +12219,20 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
12104
12219
  }
12105
12220
  if (valueStmts.length > 0) {
12106
12221
  preamble = preambleFromValueStatements(valueStmts, ctx2);
12222
+ if (!preamble.declarations && !isClientOnly && !(ctx2.analyzer.acceptsCallbackBody?.("map") ?? false)) {
12223
+ ctx2.analyzer.errors.push(
12224
+ createError(
12225
+ ErrorCodes.UNSUPPORTED_JSX_PATTERN,
12226
+ getSourceLocation(body2, ctx2.sourceFile, ctx2.filePath),
12227
+ {
12228
+ message: "A .map() callback preamble that is not a sequence of value declarations cannot be lowered to a template: this backend can declare a per-row local, but cannot run arbitrary statements per row.",
12229
+ suggestion: {
12230
+ message: "Add /* @client */ to evaluate this expression on the client only"
12231
+ }
12232
+ }
12233
+ )
12234
+ );
12235
+ }
12107
12236
  }
12108
12237
  }
12109
12238
  }
@@ -12202,6 +12331,7 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
12202
12331
  );
12203
12332
  preamble = void 0;
12204
12333
  }
12334
+ tagLoopItemRootComponents(children2);
12205
12335
  let childComponent;
12206
12336
  if (children2.length === 1 && children2[0].type === "component") {
12207
12337
  const comp = children2[0];
@@ -12222,6 +12352,9 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
12222
12352
  const isDirectPropArray = method2 !== "flatMap" && isArrayExprDirectPropRef(arrayExpr, ctx2);
12223
12353
  const isStaticArray = !isSignalOrMemoArray(array, ctx2) && !isDirectPropArray && !hasCalls && !objectIteration;
12224
12354
  const preambleRegions = preamble && !isStaticArray ? collectPreambleRegions(children2, new Set(preamble.declaredNames), ctx2) : void 0;
12355
+ if (preamble && !isStaticArray) {
12356
+ markPreambleAttrSlots(children2, new Set(preamble.declaredNames), ctx2);
12357
+ }
12225
12358
  const nestedComponents = collectNestedComponents(children2).filter((c) => c.name !== childComponent?.name);
12226
12359
  return {
12227
12360
  type: "loop",
@@ -12460,6 +12593,43 @@ function collectPreambleRegions(nodes, declared, ctx2) {
12460
12593
  visit3(nodes);
12461
12594
  return regions;
12462
12595
  }
12596
+ function markPreambleAttrSlots(nodes, declared, ctx2) {
12597
+ const readsDeclared = (attr) => {
12598
+ if (attr.name === "key") return false;
12599
+ const value2 = attr.value;
12600
+ if (value2.kind !== "expression" && value2.kind !== "template") return false;
12601
+ const refs = attr.freeIdentifiers ?? extractFreeIdentifiersFromText(attrValueText(value2));
12602
+ for (const r2 of refs) if (declared.has(r2)) return true;
12603
+ return false;
12604
+ };
12605
+ const visit3 = (list) => {
12606
+ for (const node of list) {
12607
+ switch (node.type) {
12608
+ case "element":
12609
+ if (!node.slotId && node.attrs.some(readsDeclared)) node.slotId = generateSlotId(ctx2);
12610
+ visit3(node.children);
12611
+ break;
12612
+ case "fragment":
12613
+ visit3(node.children);
12614
+ break;
12615
+ case "conditional":
12616
+ visit3([node.whenTrue, ...node.whenFalse ? [node.whenFalse] : []]);
12617
+ break;
12618
+ }
12619
+ }
12620
+ };
12621
+ visit3(nodes);
12622
+ }
12623
+ function attrValueText(value2) {
12624
+ if (value2.kind === "expression") return value2.expr;
12625
+ if (value2.kind !== "template") return "";
12626
+ const out = [];
12627
+ for (const p of value2.parts) {
12628
+ if (p.type === "ternary") out.push(p.condition, p.whenTrue, p.whenFalse);
12629
+ else if (p.type === "lookup") out.push(p.key);
12630
+ }
12631
+ return out.join(" ");
12632
+ }
12463
12633
  function collectBindingNames(name2, out) {
12464
12634
  if (ts11.isIdentifier(name2)) {
12465
12635
  out.add(name2.text);
@@ -12497,9 +12667,28 @@ function preambleFromValueStatements(statements, ctx2) {
12497
12667
  ssrText: tsxSourceText(typedParts.join(" ")),
12498
12668
  declaredNames: [...declared],
12499
12669
  // Value-only preambles accumulate no JSX, so no child needs the array join.
12500
- builderNames: []
12670
+ builderNames: [],
12671
+ declarations: neutralPreambleDeclarations(statements, ctx2) ?? void 0
12501
12672
  };
12502
12673
  }
12674
+ function neutralPreambleDeclarations(statements, ctx2) {
12675
+ const out = [];
12676
+ for (const stmt of statements) {
12677
+ if (!ts11.isVariableStatement(stmt)) return null;
12678
+ for (const decl of stmt.declarationList.declarations) {
12679
+ if (!ts11.isIdentifier(decl.name)) return null;
12680
+ if (!decl.initializer) return null;
12681
+ const valueParsed = tsNodeToParsedExpr(decl.initializer);
12682
+ if (!isSupported(valueParsed).supported) return null;
12683
+ out.push({
12684
+ name: decl.name.text,
12685
+ valueParsed,
12686
+ raw: decl.initializer.getText(ctx2.sourceFile)
12687
+ });
12688
+ }
12689
+ }
12690
+ return out.length > 0 ? out : null;
12691
+ }
12503
12692
  function trimPreambleSegments(segments) {
12504
12693
  const last = segments[segments.length - 1];
12505
12694
  if (last?.kind === "js") {
@@ -12930,6 +13119,9 @@ function tryResolveIdentifierAsTemplateLiteral(ident, ctx2) {
12930
13119
  if (ts11.isNoSubstitutionTemplateLiteral(ast) || ts11.isStringLiteral(ast)) {
12931
13120
  return [{ type: "string", value: ast.text }];
12932
13121
  }
13122
+ if (ts11.isElementAccessExpression(ast) && !ts11.isStringLiteralLike(ast.argumentExpression) && !ts11.isNumericLiteral(ast.argumentExpression)) {
13123
+ return tryResolveTemplateSpanFromConst(ast, ctx2);
13124
+ }
12933
13125
  if (!ts11.isTemplateExpression(ast)) return null;
12934
13126
  let resolvedAny = false;
12935
13127
  const parts = [];
@@ -13158,8 +13350,11 @@ function processComponentProps(attributes2, ctx2) {
13158
13350
  }
13159
13351
  let value2 = getAttributeValue(attr, ctx2);
13160
13352
  if (value2.kind === "template") {
13161
- value2 = AttrValueOf.expression(templatePartsToJsString(value2.parts), {
13162
- parts: value2.parts
13353
+ const collapsed = templatePartsToJsString(value2.parts);
13354
+ const collapsedTemplate = templatePartsToJsString(value2.parts, { useTemplate: true });
13355
+ value2 = AttrValueOf.expression(collapsed, {
13356
+ parts: value2.parts,
13357
+ ...collapsedTemplate !== collapsed && { templateExpr: collapsedTemplate }
13163
13358
  });
13164
13359
  } else if (value2.kind === "boolean-attr") {
13165
13360
  value2 = AttrValueOf.booleanShorthand();
@@ -13188,18 +13383,20 @@ function processComponentProps(attributes2, ctx2) {
13188
13383
  }
13189
13384
  return props;
13190
13385
  }
13191
- function templatePartsToJsString(parts) {
13386
+ function templatePartsToJsString(parts, opts) {
13192
13387
  let result2 = "`";
13193
13388
  for (const part of parts) {
13194
13389
  if (part.type === "string") {
13195
- result2 += part.value;
13390
+ result2 += opts?.useTemplate && part.templateValue ? part.templateValue : part.value;
13196
13391
  } else if (part.type === "ternary") {
13197
- result2 += `\${${part.condition} ? '${part.whenTrue}' : '${part.whenFalse}'}`;
13392
+ const cond = opts?.useTemplate && part.templateCondition ? part.templateCondition : part.condition;
13393
+ result2 += `\${${cond} ? '${part.whenTrue}' : '${part.whenFalse}'}`;
13198
13394
  } else if (part.type === "lookup") {
13395
+ const key = opts?.useTemplate && part.templateKey ? part.templateKey : part.key;
13199
13396
  const obj = "{" + Object.entries(part.cases).map(
13200
13397
  ([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`
13201
13398
  ).join(", ") + "}";
13202
- result2 += `\${(${obj})[${part.key}]}`;
13399
+ result2 += `\${(${obj})[${key}]}`;
13203
13400
  }
13204
13401
  }
13205
13402
  result2 += "`";
@@ -13400,11 +13597,12 @@ function replaceBranchLocalRefs(text, branchNames, resolve12) {
13400
13597
  const pattern = new RegExp(`(?<![\\w$])(${branchNames.join("|")})(?![\\w$])`, "g");
13401
13598
  return replaceInExprContexts(text, pattern, (_match, name2) => resolve12(name2));
13402
13599
  }
13403
- function buildIfStatementChain(analyzer, ctx2) {
13600
+ function buildIfStatementChain(analyzer, ctx2, opts) {
13404
13601
  const conditionalReturns = analyzer.conditionalReturns;
13602
+ const asConditional = opts?.asConditional === true;
13405
13603
  let alternate = null;
13406
13604
  if (analyzer.jsxReturn) {
13407
- ctx2.isRoot = true;
13605
+ ctx2.isRoot = !asConditional;
13408
13606
  alternate = transformNode(analyzer.jsxReturn, ctx2);
13409
13607
  }
13410
13608
  for (let i = conditionalReturns.length - 1; i >= 0; i--) {
@@ -13468,7 +13666,7 @@ function buildIfStatementChain(analyzer, ctx2) {
13468
13666
  ctx2.getJS = substitutedGetJS;
13469
13667
  ctx2.analyzer.getJS = substitutedGetJS;
13470
13668
  }
13471
- ctx2.isRoot = true;
13669
+ ctx2.isRoot = !asConditional;
13472
13670
  let consequent;
13473
13671
  try {
13474
13672
  consequent = transformNode(condReturn.jsxReturn, ctx2);
@@ -13502,6 +13700,27 @@ function buildIfStatementChain(analyzer, ctx2) {
13502
13700
  analyzer.sourceFile,
13503
13701
  analyzer.filePath
13504
13702
  );
13703
+ if (asConditional && alternate) {
13704
+ const conditional = {
13705
+ type: "conditional",
13706
+ condition,
13707
+ templateCondition,
13708
+ conditionType: null,
13709
+ reactive: true,
13710
+ whenTrue: consequent,
13711
+ whenFalse: alternate,
13712
+ slotId: generateSlotId(ctx2),
13713
+ loc,
13714
+ origin: {
13715
+ phase: "tick",
13716
+ scope: "template",
13717
+ effect: "pure",
13718
+ freeRefs: resolveFreeRefs(condReturn.condition, makeBindingEnv(ctx2))
13719
+ }
13720
+ };
13721
+ alternate = conditional;
13722
+ continue;
13723
+ }
13505
13724
  const ifStmt = {
13506
13725
  type: "if-statement",
13507
13726
  condition,
@@ -13846,7 +14065,11 @@ function collectLoopChildReactiveTexts(node, ctx2, loopParam, loopParamBindings,
13846
14065
  });
13847
14066
  return texts;
13848
14067
  }
13849
- function collectLoopChildReactiveAttrs(node, ctx2, loopParam, loopParamBindings, stopAtReactiveConditionals = false) {
14068
+ function anyNameIn(names, set) {
14069
+ for (const n of names) if (set.has(n)) return true;
14070
+ return false;
14071
+ }
14072
+ function collectLoopChildReactiveAttrs(node, ctx2, loopParam, loopParamBindings, stopAtReactiveConditionals = false, preambleNames) {
13850
14073
  const attrs = [];
13851
14074
  traverseElements(node, (el) => {
13852
14075
  if (el.slotId) {
@@ -13857,14 +14080,16 @@ function collectLoopChildReactiveAttrs(node, ctx2, loopParam, loopParamBindings,
13857
14080
  const valueStr = attrValueToString(attr.value);
13858
14081
  if (!valueStr) continue;
13859
14082
  const expanded = expandConstantForReactivity(valueStr, ctx2, attr.freeIdentifiers);
13860
- const reactive = classifyReactivity(expanded.expr, ctx2, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || attr.callsReactiveGetters || attr.hasFunctionCalls;
14083
+ const readsPreamble = preambleNames !== void 0 && preambleNames.size > 0 && anyNameIn(expanded.freeIds ?? extractFreeIdentifiersFromText(expanded.expr), preambleNames);
14084
+ const reactive = classifyReactivity(expanded.expr, ctx2, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || readsPreamble || attr.callsReactiveGetters || attr.hasFunctionCalls;
13861
14085
  if (!attr.clientOnly && !reactive) continue;
13862
14086
  attrs.push({
13863
14087
  childSlotId: el.slotId,
13864
14088
  attrName: attr.name,
13865
14089
  expression: expanded.expr,
13866
14090
  ...pickAttrMetaFromIR(attr),
13867
- ...expanded.freeIds !== void 0 && { freeIdentifiers: expanded.freeIds }
14091
+ ...expanded.freeIds !== void 0 && { freeIdentifiers: expanded.freeIds },
14092
+ ...readsPreamble && { readsPreamble: true }
13868
14093
  });
13869
14094
  }
13870
14095
  }
@@ -13877,6 +14102,7 @@ var init_reactivity = __esm({
13877
14102
  init_types();
13878
14103
  init_utils();
13879
14104
  init_prop_handling();
14105
+ init_csr_substitute();
13880
14106
  init_walker();
13881
14107
  }
13882
14108
  });
@@ -13947,7 +14173,7 @@ function emitTemplateCloneLines(template, indent) {
13947
14173
  ];
13948
14174
  }
13949
14175
  function emitLoopItemElementSetup(lines, opts) {
13950
- const { template, bodyIsMultiRoot, indent, singleRootLayout } = opts;
14176
+ const { template, bodyIsMultiRoot, indent, singleRootLayout, mountRow } = opts;
13951
14177
  const innerIndent = indent + " ";
13952
14178
  if (bodyIsMultiRoot) {
13953
14179
  lines.push(`${indent}let __el, __extras`);
@@ -13958,17 +14184,19 @@ function emitLoopItemElementSetup(lines, opts) {
13958
14184
  lines.push(ln);
13959
14185
  }
13960
14186
  lines.push(`${innerIndent}__el.__bfExtras = __extras`);
14187
+ if (mountRow) lines.push(`${innerIndent}mountRowRoot(__el)`);
13961
14188
  lines.push(`${indent}}`);
13962
14189
  return;
13963
14190
  }
13964
14191
  if (singleRootLayout === "inline") {
13965
14192
  const cloneExpr = emitTemplateCloneInline(template);
13966
- lines.push(`${indent}const __el = __existing ?? (() => { ${cloneExpr} })()`);
14193
+ const clone2 = `__existing ?? (() => { ${cloneExpr} })()`;
14194
+ lines.push(`${indent}const __el = ${mountRow ? `__existing ?? mountRowRoot((() => { ${cloneExpr} })())` : clone2}`);
13967
14195
  return;
13968
14196
  }
13969
- lines.push(`${indent}const __el = __existing ?? (() => {`);
14197
+ lines.push(`${indent}const __el = __existing ?? ${mountRow ? "mountRowRoot(" : ""}(() => {`);
13970
14198
  for (const ln of emitTemplateCloneLines(template, innerIndent)) lines.push(ln);
13971
- lines.push(`${indent}})()`);
14199
+ lines.push(`${indent}})()${mountRow ? ")" : ""}`);
13972
14200
  }
13973
14201
  function emitMultiRootTemplateCloneLines(template, indent, varEl, varExtras) {
13974
14202
  const isSvg = multiRootTemplateNeedsSvgWrap(template);
@@ -14150,10 +14378,11 @@ function collectInnerLoops(nodes, siblingOffsets, outerLoopParam, ctx2, options2
14150
14378
  const template = n.children.map((c) => irToPlaceholderTemplate(c, void 0, emitDepth, loopParamsForTemplate)).join("");
14151
14379
  const refsOuter = outerLoopParam ? new RegExp(`\\b${outerLoopParam}\\b`).test(n.array) : false;
14152
14380
  const bindings = emptyLoopChildBindings();
14381
+ const innerPreambleNames = preambleNamesOf(n);
14153
14382
  if (ctx2) {
14154
14383
  for (const child of n.children) {
14155
14384
  bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx2, n.param, n.paramBindings));
14156
- bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx2, n.param, n.paramBindings));
14385
+ bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx2, n.param, n.paramBindings, false, innerPreambleNames));
14157
14386
  bindings.refs.push(...collectLoopChildRefs(child));
14158
14387
  }
14159
14388
  }
@@ -14367,7 +14596,7 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14367
14596
  if (!l.slotId || inCond) return;
14368
14597
  const projectionInner = l.method === "flatMap" && l.children.length === 1 && l.children[0].type === "loop" ? l.children[0] : void 0;
14369
14598
  const childHandlers = [];
14370
- const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx2, siblingOffsets, l.param, l.paramBindings);
14599
+ const bindings = projectionInner ? emptyLoopChildBindings() : collectLoopChildBindings(l.children, ctx2, siblingOffsets, l.param, l.paramBindings, preambleNamesOf(l));
14371
14600
  if (!projectionInner) {
14372
14601
  for (const child of l.children) {
14373
14602
  childHandlers.push(...collectEventHandlersFromIR(child));
@@ -14389,15 +14618,7 @@ function collectElements(node, ctx2, siblingOffsets, insideConditional = false)
14389
14618
  if (l.childComponent) {
14390
14619
  template = "";
14391
14620
  if (l.isStaticArray && l.children[0]) {
14392
- staticItemTemplate = irToHtmlTemplate(
14393
- l.children[0],
14394
- buildRestSpreadNames(ctx2),
14395
- 0,
14396
- void 0,
14397
- void 0,
14398
- /* insideLoop */
14399
- true
14400
- );
14621
+ staticItemTemplate = irToHtmlTemplate(l.children[0], buildRestSpreadNames(ctx2), 0, void 0, void 0);
14401
14622
  }
14402
14623
  } else if (l.children[0] && !projectionInner) {
14403
14624
  const loopParamSpec = [{ param: l.param, bindings: l.paramBindings }];
@@ -14627,7 +14848,7 @@ function collectBranchLoops(node, ctx2, siblingOffsets) {
14627
14848
  } else {
14628
14849
  childTemplate = n.children.map((c) => irToHtmlTemplate(c, void 0, 0, branchLoopParamSpec)).join("");
14629
14850
  }
14630
- const branchBindings = ctx2 && !projectionInner ? collectLoopChildBindings(n.children, ctx2, siblingOffsets, n.param, n.paramBindings) : emptyLoopChildBindings();
14851
+ const branchBindings = ctx2 && !projectionInner ? collectLoopChildBindings(n.children, ctx2, siblingOffsets, n.param, n.paramBindings, preambleNamesOf(n)) : emptyLoopChildBindings();
14631
14852
  loops.push({
14632
14853
  kind: "branch",
14633
14854
  array: n.array,
@@ -14708,11 +14929,16 @@ function collectBranchConditionals(node, ctx2, siblingOffsets) {
14708
14929
  });
14709
14930
  return result2;
14710
14931
  }
14711
- function collectLoopChildBindings(children2, ctx2, siblingOffsets, loopParam, loopParamBindings) {
14932
+ function preambleNamesOf(loop) {
14933
+ if (loop.isStaticArray) return void 0;
14934
+ const declared = loop.preamble?.declaredNames;
14935
+ return declared && declared.length > 0 ? new Set(declared) : void 0;
14936
+ }
14937
+ function collectLoopChildBindings(children2, ctx2, siblingOffsets, loopParam, loopParamBindings, preambleNames) {
14712
14938
  const bindings = emptyLoopChildBindings();
14713
14939
  for (const child of children2) {
14714
14940
  bindings.events.push(...collectLoopChildEventsWithNesting(child));
14715
- bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx2, loopParam, loopParamBindings, true));
14941
+ bindings.reactiveAttrs.push(...collectLoopChildReactiveAttrs(child, ctx2, loopParam, loopParamBindings, true, preambleNames));
14716
14942
  bindings.reactiveTexts.push(...collectLoopChildReactiveTexts(child, ctx2, loopParam, loopParamBindings, true));
14717
14943
  bindings.refs.push(...collectLoopChildRefs(child));
14718
14944
  bindings.conditionals.push(...collectLoopChildConditionals(child, ctx2, siblingOffsets, loopParam, loopParamBindings));
@@ -15293,6 +15519,8 @@ function isValueReferenceIdentifier(id2) {
15293
15519
  if ((ts13.isMethodDeclaration(parent2) || ts13.isGetAccessorDeclaration(parent2) || ts13.isSetAccessorDeclaration(parent2)) && parent2.name === id2) {
15294
15520
  return false;
15295
15521
  }
15522
+ if (ts13.isPropertyDeclaration(parent2) && parent2.name === id2) return false;
15523
+ if (ts13.isMetaProperty(parent2) && parent2.name === id2) return false;
15296
15524
  if (ts13.isVariableDeclaration(parent2) && parent2.name === id2) return false;
15297
15525
  if (ts13.isFunctionDeclaration(parent2) && parent2.name === id2) return false;
15298
15526
  if (ts13.isFunctionExpression(parent2) && parent2.name === id2) return false;
@@ -15466,6 +15694,11 @@ var init_imports = __esm({
15466
15694
  "registerTemplate",
15467
15695
  "initChild",
15468
15696
  "upsertChild",
15697
+ // Connects a template-clone loop row before the body's tail runs, so a child
15698
+ // that inits inside it resolves context against real ancestors rather than
15699
+ // falling through to the global store. The clone-root counterpart of the
15700
+ // mount point `createComponent` consumes for component-root rows.
15701
+ "mountRowRoot",
15469
15702
  "createPortal",
15470
15703
  "provideContext",
15471
15704
  "createContext",
@@ -16387,7 +16620,6 @@ function emitRegistrationAndHydration(lines, ctx2, _ir, graph, inlinability) {
16387
16620
  _ir.root,
16388
16621
  csrInlinableConstants,
16389
16622
  ctx2,
16390
- void 0,
16391
16623
  restSpreadNames,
16392
16624
  ctx2.propsObjectName,
16393
16625
  unsafeLocalNames,
@@ -16791,6 +17023,7 @@ function buildSignalPlan(signal2, ctx2, lookups) {
16791
17023
  kind: "signal",
16792
17024
  getter: signal2.getter,
16793
17025
  setter: signal2.setter,
17026
+ getterElided: signal2.getterElided,
16794
17027
  initialValueExpr: "",
16795
17028
  controlledEffect: null,
16796
17029
  initializerOverride: `${factory}()`
@@ -16805,6 +17038,7 @@ function buildSignalPlan(signal2, ctx2, lookups) {
16805
17038
  kind: "signal",
16806
17039
  getter: signal2.getter,
16807
17040
  setter: signal2.setter,
17041
+ getterElided: signal2.getterElided,
16808
17042
  initialValueExpr: resolveSignalInitialValue(signal2, ctx2, lookups),
16809
17043
  controlledEffect,
16810
17044
  branchCondition: signal2.branchCondition,
@@ -16881,9 +17115,10 @@ function bfIdArg(bfId) {
16881
17115
  return bfId ? `, ${JSON.stringify(bfId)}` : "";
16882
17116
  }
16883
17117
  function emitSignal(lines, plan) {
17118
+ const getterSlot = plan.getterElided ? "" : plan.getter;
16884
17119
  if (plan.initializerOverride) {
16885
17120
  if (plan.setter) {
16886
- lines.push(` const [${plan.getter}, ${plan.setter}] = ${plan.initializerOverride}`);
17121
+ lines.push(` const [${getterSlot}, ${plan.setter}] = ${plan.initializerOverride}`);
16887
17122
  } else {
16888
17123
  lines.push(` const [${plan.getter}] = ${plan.initializerOverride}`);
16889
17124
  }
@@ -16892,9 +17127,9 @@ function emitSignal(lines, plan) {
16892
17127
  const id2 = bfIdArg(plan.bfId);
16893
17128
  if (plan.branchCondition) {
16894
17129
  if (plan.setter) {
16895
- lines.push(` let ${plan.getter}, ${plan.setter}`);
17130
+ lines.push(plan.getterElided ? ` let ${plan.setter}` : ` let ${plan.getter}, ${plan.setter}`);
16896
17131
  lines.push(` if (${plan.branchCondition}) {`);
16897
- lines.push(` ;[${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id2})`);
17132
+ lines.push(` ;[${getterSlot}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id2})`);
16898
17133
  lines.push(` }`);
16899
17134
  } else {
16900
17135
  lines.push(` let ${plan.getter}`);
@@ -16905,7 +17140,7 @@ function emitSignal(lines, plan) {
16905
17140
  return;
16906
17141
  }
16907
17142
  if (plan.setter) {
16908
- lines.push(` const [${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id2})`);
17143
+ lines.push(` const [${getterSlot}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id2})`);
16909
17144
  } else {
16910
17145
  lines.push(` const [${plan.getter}] = createSignal(${plan.initialValueExpr}${id2})`);
16911
17146
  }
@@ -17532,7 +17767,7 @@ var init_shared = __esm({
17532
17767
  // ../jsx/src/ir-to-client-js/plan/build-static-array-child-init.ts
17533
17768
  function staticPreludeStatements(preamble) {
17534
17769
  return preamble ? [renderPreamble(preamble, {
17535
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
17770
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0)
17536
17771
  })] : [];
17537
17772
  }
17538
17773
  function buildStaticArrayChildInitsPlan(ctx2) {
@@ -18125,7 +18360,8 @@ function buildReactiveEffectsPlan(args2) {
18125
18360
  attrs: slotAttrs.map((attr) => ({
18126
18361
  attrName: attr.attrName,
18127
18362
  wrappedExpression: wrap(attr.expression),
18128
- meta: pickAttrMeta(attr)
18363
+ meta: pickAttrMeta(attr),
18364
+ ...attr.readsPreamble && { readsPreamble: true }
18129
18365
  }))
18130
18366
  });
18131
18367
  }
@@ -18333,7 +18569,7 @@ function buildReactiveEmit(inner, level, wrapOuter, uidSuffix, outerLoopParam, o
18333
18569
  ] : [{ param: inner.param, bindings: inner.paramBindings }];
18334
18570
  preludeStatements.push(renderPreamble(inner.preamble, {
18335
18571
  transformJs: (t) => wrapInner(wrapOuter(t)),
18336
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, leafLoopParams, void 0, true)
18572
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, leafLoopParams, void 0)
18337
18573
  }));
18338
18574
  }
18339
18575
  const childRefs = buildChildRefBindings(inner.bindings.refs, inner.param, inner.paramBindings);
@@ -18358,7 +18594,7 @@ function buildStaticEmit(inner, level, uidSuffix) {
18358
18594
  if (indexAlias) preludeStatements.push(indexAlias);
18359
18595
  if (inner.preamble) {
18360
18596
  preludeStatements.push(renderPreamble(inner.preamble, {
18361
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18597
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0)
18362
18598
  }));
18363
18599
  }
18364
18600
  return {
@@ -18400,7 +18636,7 @@ function buildTopLevelCompositePlan(elem, profileComponentName) {
18400
18636
  indexParam: elem.index || "__idx",
18401
18637
  mapPreambleWrapped: elem.preamble ? renderPreamble(elem.preamble, {
18402
18638
  transformJs: wrap,
18403
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
18639
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0)
18404
18640
  }) : "",
18405
18641
  template: elem.template,
18406
18642
  outerComps: filterCondCompsOut(outerCompsByDepth, elem.bindings.conditionals),
@@ -18453,7 +18689,7 @@ function buildBranchCompositePlan(loop, cv, profileComponentName) {
18453
18689
  indexParam: loop.index || "__idx",
18454
18690
  mapPreambleWrapped: loop.preamble ? renderPreamble(loop.preamble, {
18455
18691
  transformJs: wrap,
18456
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0, true)
18692
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0)
18457
18693
  }) : "",
18458
18694
  template: loop.template,
18459
18695
  outerComps: filterCondCompsOut(outerCompsByDepth, loop.bindings.conditionals),
@@ -18535,7 +18771,7 @@ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
18535
18771
  // BUG-3: it rewrote leaf refs to accessor-call form (`t().name`),
18536
18772
  // which throws since `t` is a plain object in this scope.
18537
18773
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18538
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18774
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0)
18539
18775
  }) : null,
18540
18776
  mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? []
18541
18777
  })
@@ -18557,7 +18793,7 @@ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
18557
18793
  // See note in `buildDynamicLoopDelegationPlan` above (BUG-3): no
18558
18794
  // loopParams spec — leaf refs must stay in plain-object form here.
18559
18795
  mapPreamble: loop.preamble ? renderPreamble(loop.preamble, {
18560
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18796
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0)
18561
18797
  }) : null,
18562
18798
  mapPreambleDeclaredNames: loop.preamble?.declaredNames ?? []
18563
18799
  })
@@ -18579,7 +18815,7 @@ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
18579
18815
  // See note in `buildDynamicLoopDelegationPlan` above (BUG-3): no
18580
18816
  // loopParams spec — leaf refs must stay in plain-object form here.
18581
18817
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
18582
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0, true)
18818
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, void 0, void 0)
18583
18819
  }) : null,
18584
18820
  mapPreambleDeclaredNames: elem.preamble?.declaredNames ?? [],
18585
18821
  offset: elem.offset ?? null,
@@ -18621,36 +18857,219 @@ var init_build_event_delegation = __esm({
18621
18857
  }
18622
18858
  });
18623
18859
 
18860
+ // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts
18861
+ function wiringOn(branch) {
18862
+ const found = [];
18863
+ if (branch.childComponents.length > 0) found.push("child components");
18864
+ if (branch.innerLoops && branch.innerLoops.length > 0) found.push("an inner loop");
18865
+ if (branch.conditionals && branch.conditionals.length > 0) found.push("a nested conditional");
18866
+ if (branch.events && branch.events.length > 0) found.push("events");
18867
+ if (branch.reactiveAttrs && branch.reactiveAttrs.length > 0) found.push("reactive attrs");
18868
+ if (branch.reactiveTexts && branch.reactiveTexts.length > 0) found.push("reactive text");
18869
+ return found;
18870
+ }
18871
+ function analyzeLazyConditional(cond, indexParam, arms) {
18872
+ for (const [label2, branch] of [["true", cond.whenTrue], ["false", cond.whenFalse]]) {
18873
+ const wiring = wiringOn(branch);
18874
+ if (wiring.length > 0) {
18875
+ return NO(`conditional on slot ${cond.slotId}: its ${label2} arm owns ${wiring.join(" + ")}`);
18876
+ }
18877
+ }
18878
+ for (const [label2, html] of [["true", arms.whenTrueHtml], ["false", arms.whenFalseHtml]]) {
18879
+ if (html.includes("bf-cond-start:")) {
18880
+ return NO(`conditional on slot ${cond.slotId}: its ${label2} arm is a fragment conditional`);
18881
+ }
18882
+ if (!html.includes(`bf-c="${cond.slotId}"`)) {
18883
+ return NO(`conditional on slot ${cond.slotId}: its ${label2} arm has no single bf-c root`);
18884
+ }
18885
+ if (html.includes("${")) {
18886
+ return NO(`conditional on slot ${cond.slotId}: its ${label2} arm interpolates a value`);
18887
+ }
18888
+ }
18889
+ if (!cond.conditionFreeIdentifiers) {
18890
+ return NO(`conditional on slot ${cond.slotId}: condition has no analyzable identifier set`);
18891
+ }
18892
+ if (cond.conditionFreeIdentifiers.has(indexParam)) {
18893
+ return NO(`conditional on slot ${cond.slotId}: condition reads the loop index parameter '${indexParam}'`);
18894
+ }
18895
+ return {
18896
+ lazySafe: true,
18897
+ facts: {
18898
+ slotId: cond.slotId,
18899
+ condition: cond.condition,
18900
+ whenTrueHtml: arms.whenTrueHtml,
18901
+ whenFalseHtml: arms.whenFalseHtml
18902
+ }
18903
+ };
18904
+ }
18905
+ var NO;
18906
+ var init_lazy_conditional = __esm({
18907
+ "../jsx/src/ir-to-client-js/control-flow/plan/lazy-conditional.ts"() {
18908
+ "use strict";
18909
+ NO = (reason2) => ({ lazySafe: false, reason: reason2 });
18910
+ }
18911
+ });
18912
+
18913
+ // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts
18914
+ import ts15 from "typescript";
18915
+ function analyzeLazyPreamble(preamble, indexParam, primableNames) {
18916
+ if (!preamble) return NO_PREAMBLE;
18917
+ if (preamble.builderNames.length > 0) {
18918
+ return NO2(`map-callback preamble accumulates JSX leaves (${preamble.builderNames.join(", ")})`);
18919
+ }
18920
+ for (const seg of preamble.segments) {
18921
+ if (seg.kind !== "js") return NO2("map-callback preamble contains a JSX leaf");
18922
+ }
18923
+ const text = preambleAnalysisText(preamble);
18924
+ if (text.trim().length === 0) return NO_PREAMBLE;
18925
+ const declaredNames = /* @__PURE__ */ new Set();
18926
+ const sf = ts15.createSourceFile(
18927
+ "__lazy_preamble__.ts",
18928
+ text,
18929
+ ts15.ScriptTarget.Latest,
18930
+ /* setParentNodes */
18931
+ true,
18932
+ ts15.ScriptKind.TS
18933
+ );
18934
+ for (const stmt of sf.statements) {
18935
+ if (!ts15.isVariableStatement(stmt)) {
18936
+ return NO2(`map-callback preamble has a non-declaration statement (${ts15.SyntaxKind[stmt.kind]})`);
18937
+ }
18938
+ const isConst = (stmt.declarationList.flags & ts15.NodeFlags.Const) !== 0;
18939
+ if (!isConst) return NO2("map-callback preamble declares a mutable binding (let/var)");
18940
+ for (const decl of stmt.declarationList.declarations) {
18941
+ collectBindingNames2(decl.name, declaredNames);
18942
+ if (!decl.initializer) {
18943
+ return NO2("map-callback preamble has a declaration with no initializer");
18944
+ }
18945
+ const impure = findImpureNode(decl.initializer, primableNames);
18946
+ if (impure) {
18947
+ return NO2(`map-callback preamble initializer is not re-runnable (${impure})`);
18948
+ }
18949
+ }
18950
+ }
18951
+ for (const name2 of declaredNames) {
18952
+ if (primableNames.has(name2)) {
18953
+ return NO2(`map-callback preamble shadows the signal/memo getter '${name2}'`);
18954
+ }
18955
+ }
18956
+ const readNames = extractFreeIdentifiersFromStatementText(text);
18957
+ if (readNames.has(indexParam) && !declaredNames.has(indexParam)) {
18958
+ return NO2(`map-callback preamble reads the loop index parameter '${indexParam}'`);
18959
+ }
18960
+ const freeNames = new Set(readNames);
18961
+ for (const declared of declaredNames) freeNames.delete(declared);
18962
+ return { lazySafe: true, facts: { declaredNames, freeNames } };
18963
+ }
18964
+ function collectBindingNames2(name2, out) {
18965
+ if (ts15.isIdentifier(name2)) {
18966
+ out.add(name2.text);
18967
+ return;
18968
+ }
18969
+ for (const element of name2.elements) {
18970
+ if (ts15.isOmittedExpression(element)) continue;
18971
+ collectBindingNames2(element.name, out);
18972
+ }
18973
+ }
18974
+ function findImpureNode(root2, primableNames) {
18975
+ let found = null;
18976
+ const visit3 = (node) => {
18977
+ if (found) return;
18978
+ if (ts15.isCallExpression(node)) {
18979
+ const callee = node.expression;
18980
+ const isSignalRead = ts15.isIdentifier(callee) && primableNames.has(callee.text) && node.arguments.length === 0 && node.questionDotToken === void 0;
18981
+ if (!isSignalRead) {
18982
+ found = `call to ${callee.getText(callee.getSourceFile())}`;
18983
+ return;
18984
+ }
18985
+ }
18986
+ if (ts15.isNewExpression(node)) {
18987
+ found = "new expression";
18988
+ return;
18989
+ }
18990
+ if (ts15.isTaggedTemplateExpression(node)) {
18991
+ found = "tagged template";
18992
+ return;
18993
+ }
18994
+ if (ts15.isAwaitExpression(node)) {
18995
+ found = "await";
18996
+ return;
18997
+ }
18998
+ if (ts15.isYieldExpression(node)) {
18999
+ found = "yield";
19000
+ return;
19001
+ }
19002
+ if (ts15.isPrefixUnaryExpression(node) || ts15.isPostfixUnaryExpression(node)) {
19003
+ const op = node.operator;
19004
+ if (op === ts15.SyntaxKind.PlusPlusToken || op === ts15.SyntaxKind.MinusMinusToken) {
19005
+ found = "increment/decrement";
19006
+ return;
19007
+ }
19008
+ }
19009
+ if (ts15.isDeleteExpression(node)) {
19010
+ found = "delete";
19011
+ return;
19012
+ }
19013
+ if (ts15.isFunctionExpression(node) || ts15.isArrowFunction(node) || ts15.isClassExpression(node)) {
19014
+ found = "function or class expression";
19015
+ return;
19016
+ }
19017
+ if (ts15.isBinaryExpression(node) && isAssignmentOperator(node.operatorToken.kind)) {
19018
+ found = "assignment";
19019
+ return;
19020
+ }
19021
+ ts15.forEachChild(node, visit3);
19022
+ };
19023
+ visit3(root2);
19024
+ return found;
19025
+ }
19026
+ function isAssignmentOperator(kind2) {
19027
+ return kind2 >= ts15.SyntaxKind.FirstAssignment && kind2 <= ts15.SyntaxKind.LastAssignment;
19028
+ }
19029
+ var NO_PREAMBLE, NO2;
19030
+ var init_lazy_preamble = __esm({
19031
+ "../jsx/src/ir-to-client-js/control-flow/plan/lazy-preamble.ts"() {
19032
+ "use strict";
19033
+ init_types();
19034
+ init_csr_substitute();
19035
+ NO_PREAMBLE = {
19036
+ lazySafe: true,
19037
+ facts: { declaredNames: /* @__PURE__ */ new Set(), freeNames: /* @__PURE__ */ new Set() }
19038
+ };
19039
+ NO2 = (reason2) => ({ lazySafe: false, reason: reason2 });
19040
+ }
19041
+ });
19042
+
18624
19043
  // ../jsx/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts
18625
19044
  function lazyRowEligibility(args2) {
18626
19045
  const { shape, bindings, arraySourceIdentifiers, scope } = args2;
18627
- if (scope.profile) return NO("profile mode keeps the granular eager emission");
19046
+ if (scope.profile) return NO3("profile mode keeps the granular eager emission");
18628
19047
  if (shape.callSite !== "plain" && shape.callSite !== "branch-plain") {
18629
- return NO(`call site '${shape.callSite}' is not a plain loop row`);
18630
- }
18631
- if (shape.flatMapLeafItem) return NO("flatMap descriptor loop (build-or-patch renderItem)");
18632
- if (shape.anchored) return NO("anchored whole-item-conditional loop");
18633
- if (shape.bodyIsMultiRoot) return NO("multi-root (Fragment) row");
18634
- if (!shape.hasExplicitKey) return NO("index-keyed loop (no explicit key)");
18635
- if (shape.conditionalCount > 0) return NO("row contains a reactive conditional");
18636
- if (shape.childRefCount > 0) return NO("row has imperative child refs");
18637
- if (shape.hasChildComponent) return NO("row body is a child component");
18638
- if (shape.nestedComponentCount > 0) return NO("row contains nested child components");
18639
- if (shape.innerLoopCount > 0) return NO("row contains an inner loop");
18640
- if (shape.hasMapPreamble) return NO("row has a map-callback preamble (may declare row-local reactivity)");
18641
- if (shape.preambleRegionCount > 0) return NO("row has preamble-patched regions");
18642
- if (shape.hasParamUnwrap) return NO("destructured loop param without param bindings");
19048
+ return NO3(`call site '${shape.callSite}' is not a plain loop row`);
19049
+ }
19050
+ if (shape.flatMapLeafItem) return NO3("flatMap descriptor loop (build-or-patch renderItem)");
19051
+ if (shape.anchored) return NO3("anchored whole-item-conditional loop");
19052
+ if (shape.bodyIsMultiRoot) return NO3("multi-root (Fragment) row");
19053
+ if (!shape.hasExplicitKey) return NO3("index-keyed loop (no explicit key)");
19054
+ if (shape.conditionalRefusal) return NO3(shape.conditionalRefusal);
19055
+ if (shape.childRefCount > 0) return NO3("row has imperative child refs");
19056
+ if (shape.hasChildComponent) return NO3("row body is a child component");
19057
+ if (shape.nestedComponentCount > 0) return NO3("row contains nested child components");
19058
+ if (shape.innerLoopCount > 0) return NO3("row contains an inner loop");
19059
+ if (shape.mapPreambleRefusal) return NO3(shape.mapPreambleRefusal);
19060
+ if (shape.preambleRegionCount > 0) return NO3("row has preamble-patched regions");
19061
+ if (shape.hasParamUnwrap) return NO3("destructured loop param without param bindings");
18643
19062
  for (const b of bindings) {
18644
19063
  if (b.referencesIndex) {
18645
- return NO(`binding on slot ${b.slotId} references the loop index parameter`);
19064
+ return NO3(`binding on slot ${b.slotId} references the loop index parameter`);
18646
19065
  }
18647
19066
  if (b.opaqueOuterNames.includes(UNKNOWN_IDENTIFIERS)) {
18648
- return NO(`binding on slot ${b.slotId} has no analyzable identifier set`);
19067
+ return NO3(`binding on slot ${b.slotId} has no analyzable identifier set`);
18649
19068
  }
18650
19069
  }
18651
- if (!arraySourceIdentifiers) return NO("loop source free identifiers unavailable");
19070
+ if (!arraySourceIdentifiers) return NO3("loop source free identifiers unavailable");
18652
19071
  const sourceGate = checkSourceConsistency(arraySourceIdentifiers, scope);
18653
- if (sourceGate) return NO(`loop source is not provably hydration-consistent: ${sourceGate}`);
19072
+ if (sourceGate) return NO3(`loop source is not provably hydration-consistent: ${sourceGate}`);
18654
19073
  return { eligible: true };
18655
19074
  }
18656
19075
  function checkSourceConsistency(names, scope) {
@@ -18692,6 +19111,7 @@ function checkSourceConsistency(names, scope) {
18692
19111
  }
18693
19112
  function classifyLazyBinding(args2) {
18694
19113
  const { kind: kind2, slotId, free, rowLocalNames, indexParam, scope } = args2;
19114
+ const preamble = args2.preamble;
18695
19115
  if (free === null) {
18696
19116
  return {
18697
19117
  kind: kind2,
@@ -18704,30 +19124,43 @@ function classifyLazyBinding(args2) {
18704
19124
  // `UNKNOWN_IDENTIFIERS` must keep refusing the loop: with no
18705
19125
  // identifier set we cannot rule out an index read either, and
18706
19126
  // `applyItem` / `applyOuter` have no index parameter to give it.
18707
- referencesIndex: false
19127
+ referencesIndex: false,
19128
+ // An assumption like `referencesIndex` above, and the conservative
19129
+ // one: with no identifier set we cannot rule out a preamble read
19130
+ // either. The loop is refused for `UNKNOWN_IDENTIFIERS` regardless.
19131
+ readsPreamble: preamble != null && preamble.declaredNames.size > 0
18708
19132
  };
18709
19133
  }
18710
19134
  let readsItem = false;
18711
19135
  let referencesIndex = false;
19136
+ let readsPreamble = false;
18712
19137
  const reactiveOuterNames = [];
18713
19138
  const opaqueOuterNames = [];
18714
- for (const name2 of free) {
19139
+ const classifyName = (name2) => {
18715
19140
  if (rowLocalNames.has(name2)) {
18716
19141
  readsItem = true;
18717
- continue;
19142
+ return;
18718
19143
  }
18719
19144
  if (name2 === indexParam) {
18720
19145
  referencesIndex = true;
18721
- continue;
19146
+ return;
18722
19147
  }
18723
- if (INERT_BINDING_GLOBALS.has(name2)) continue;
19148
+ if (INERT_BINDING_GLOBALS.has(name2)) return;
18724
19149
  if (scope.signals.has(name2) || scope.memos.has(name2)) {
18725
19150
  if (!reactiveOuterNames.includes(name2)) reactiveOuterNames.push(name2);
18726
- continue;
19151
+ return;
18727
19152
  }
18728
19153
  const constFree = scope.constants.get(name2);
18729
- if (constFree !== void 0 && constFree !== null && constFree.size === 0) continue;
19154
+ if (constFree !== void 0 && constFree !== null && constFree.size === 0) return;
18730
19155
  opaqueOuterNames.push(name2);
19156
+ };
19157
+ for (const name2 of free) {
19158
+ if (preamble?.declaredNames.has(name2)) {
19159
+ readsPreamble = true;
19160
+ for (const dep of preamble.freeNames) classifyName(dep);
19161
+ continue;
19162
+ }
19163
+ classifyName(name2);
18731
19164
  }
18732
19165
  return {
18733
19166
  kind: kind2,
@@ -18736,10 +19169,11 @@ function classifyLazyBinding(args2) {
18736
19169
  readsOuter: reactiveOuterNames.length > 0 || opaqueOuterNames.length > 0,
18737
19170
  reactiveOuterNames,
18738
19171
  opaqueOuterNames,
18739
- referencesIndex
19172
+ referencesIndex,
19173
+ readsPreamble
18740
19174
  };
18741
19175
  }
18742
- var PURE_SOURCE_GLOBALS, INERT_BINDING_GLOBALS, UNKNOWN_IDENTIFIERS, NO;
19176
+ var PURE_SOURCE_GLOBALS, INERT_BINDING_GLOBALS, UNKNOWN_IDENTIFIERS, NO3;
18743
19177
  var init_lazy_row_eligibility = __esm({
18744
19178
  "../jsx/src/ir-to-client-js/control-flow/plan/lazy-row-eligibility.ts"() {
18745
19179
  "use strict";
@@ -18785,7 +19219,7 @@ var init_lazy_row_eligibility = __esm({
18785
19219
  "decodeURI"
18786
19220
  ]);
18787
19221
  UNKNOWN_IDENTIFIERS = "<unknown>";
18788
- NO = (reason2) => ({ eligible: false, reason: reason2 });
19222
+ NO3 = (reason2) => ({ eligible: false, reason: reason2 });
18789
19223
  }
18790
19224
  });
18791
19225
 
@@ -18801,6 +19235,23 @@ function decideLazyRow(args2) {
18801
19235
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, loop.param, loop.paramBindings);
18802
19236
  const rowLocalNames = /* @__PURE__ */ new Set([loop.param]);
18803
19237
  for (const b of loop.paramBindings ?? []) rowLocalNames.add(b.name);
19238
+ const primableNames = /* @__PURE__ */ new Set([...scope.signals.keys(), ...scope.memos]);
19239
+ const preambleAnalysis = analyzeLazyPreamble(loop.preamble, args2.indexParam, primableNames);
19240
+ const rawConditionals = loop.bindings.conditionals ?? [];
19241
+ const condFacts = [];
19242
+ let conditionalRefusal = null;
19243
+ for (const cond of rawConditionals) {
19244
+ const verdict = analyzeLazyConditional(cond, args2.indexParam, {
19245
+ whenTrueHtml: addCondAttrToTemplate(wrap(cond.whenTrueHtml), cond.slotId),
19246
+ whenFalseHtml: addCondAttrToTemplate(wrap(cond.whenFalseHtml), cond.slotId)
19247
+ });
19248
+ if (!verdict.lazySafe) {
19249
+ conditionalRefusal = verdict.reason;
19250
+ break;
19251
+ }
19252
+ condFacts.push(verdict.facts);
19253
+ }
19254
+ const preambleFacts = preambleAnalysis.lazySafe ? preambleAnalysis.facts : void 0;
18804
19255
  const classified = [];
18805
19256
  const attrClass = /* @__PURE__ */ new Map();
18806
19257
  loop.bindings.reactiveAttrs.forEach((attr, i) => {
@@ -18810,7 +19261,8 @@ function decideLazyRow(args2) {
18810
19261
  free: attrFreeIdentifiers2(attr.expression),
18811
19262
  rowLocalNames,
18812
19263
  indexParam: args2.indexParam,
18813
- scope
19264
+ scope,
19265
+ preamble: preambleFacts
18814
19266
  });
18815
19267
  attrClass.set(i, c);
18816
19268
  classified.push(c);
@@ -18823,23 +19275,38 @@ function decideLazyRow(args2) {
18823
19275
  free: text.freeIdentifiers ?? null,
18824
19276
  rowLocalNames,
18825
19277
  indexParam: args2.indexParam,
18826
- scope
19278
+ scope,
19279
+ preamble: preambleFacts
18827
19280
  });
18828
19281
  textClass.set(i, c);
18829
19282
  classified.push(c);
18830
19283
  });
19284
+ const condClass = /* @__PURE__ */ new Map();
19285
+ condFacts.forEach((c, i) => {
19286
+ const k = classifyLazyBinding({
19287
+ kind: "attr",
19288
+ slotId: c.slotId,
19289
+ free: rawConditionals[i].conditionFreeIdentifiers ?? null,
19290
+ rowLocalNames,
19291
+ indexParam: args2.indexParam,
19292
+ scope,
19293
+ preamble: preambleFacts
19294
+ });
19295
+ condClass.set(i, k);
19296
+ classified.push(k);
19297
+ });
18831
19298
  const shape = {
18832
19299
  callSite: args2.callSite,
18833
19300
  flatMapLeafItem: args2.flatMapLeafItem,
18834
19301
  anchored: args2.anchored,
18835
19302
  bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
18836
19303
  hasExplicitKey: loop.key != null,
18837
- conditionalCount: loop.bindings.conditionals?.length ?? 0,
19304
+ conditionalRefusal,
18838
19305
  childRefCount: loop.bindings.refs?.length ?? 0,
18839
19306
  nestedComponentCount: loop.nestedComponents?.length ?? 0,
18840
19307
  innerLoopCount: loop.innerLoops?.length ?? 0,
18841
19308
  hasChildComponent: "childComponent" in loop && loop.childComponent != null,
18842
- hasMapPreamble: args2.mapPreambleWrapped.length > 0,
19309
+ mapPreambleRefusal: preambleAnalysis.lazySafe ? null : preambleAnalysis.reason,
18843
19310
  preambleRegionCount: args2.preambleRegionCount,
18844
19311
  hasParamUnwrap: args2.paramUnwrap.length > 0
18845
19312
  };
@@ -18865,7 +19332,8 @@ function decideLazyRow(args2) {
18865
19332
  refIndex: attrSlotIds.indexOf(attr.childSlotId),
18866
19333
  ordinal: ordinal++,
18867
19334
  readsItem: c.readsItem,
18868
- readsOuter: c.readsOuter
19335
+ readsOuter: c.readsOuter,
19336
+ readsPreamble: c.readsPreamble
18869
19337
  };
18870
19338
  });
18871
19339
  const texts = loop.bindings.reactiveTexts.map((text, i) => {
@@ -18879,7 +19347,8 @@ function decideLazyRow(args2) {
18879
19347
  // silently never being written. Anything the classifier did place in
18880
19348
  // a list keeps exactly the classifier's answer.
18881
19349
  readsItem: c.readsItem || !c.readsOuter,
18882
- readsOuter: c.readsOuter
19350
+ readsOuter: c.readsOuter,
19351
+ readsPreamble: c.readsPreamble
18883
19352
  };
18884
19353
  });
18885
19354
  const outerPrimeGetters = [];
@@ -18888,6 +19357,25 @@ function decideLazyRow(args2) {
18888
19357
  if (!outerPrimeGetters.includes(name2)) outerPrimeGetters.push(name2);
18889
19358
  }
18890
19359
  }
19360
+ const condRefBase = attrSlotIds.length + (texts.length > 0 ? 1 : 0);
19361
+ const conditionals = condFacts.map((c, i) => {
19362
+ const klass = condClass.get(i);
19363
+ return {
19364
+ slotId: c.slotId,
19365
+ wrappedCondition: wrap(c.condition),
19366
+ whenTrueHtml: c.whenTrueHtml,
19367
+ whenFalseHtml: c.whenFalseHtml,
19368
+ refIndex: condRefBase + i,
19369
+ ordinal: ordinal++,
19370
+ // A condition reading neither the item nor a reactive outer name still
19371
+ // has to be applied somewhere; `applyItem` is the harmless choice (the
19372
+ // dedup makes a repeat a no-op), matching how a text that classified as
19373
+ // neither is handled above.
19374
+ readsItem: klass.readsItem || !klass.readsOuter,
19375
+ readsOuter: klass.readsOuter,
19376
+ readsPreamble: klass.readsPreamble
19377
+ };
19378
+ });
18891
19379
  return {
18892
19380
  plan: {
18893
19381
  attrSlotIds,
@@ -18897,7 +19385,14 @@ function decideLazyRow(args2) {
18897
19385
  textNeedsRead: texts.some((t) => t.readsOuter),
18898
19386
  lastCount: ordinal,
18899
19387
  outerPrimeGetters,
18900
- hasOuter: attrs.some((a) => a.readsOuter) || texts.some((t) => t.readsOuter)
19388
+ preambleStatements: args2.mapPreambleWrapped,
19389
+ // Which apply bodies must re-run the preamble — computed from the FINAL
19390
+ // binding lists, not the raw classification, so a text/conditional that
19391
+ // classified as neither item- nor outer-driven (and was therefore forced
19392
+ // into `applyItem` above) is counted in the body it actually lands in.
19393
+ itemNeedsPreamble: [...attrs, ...texts, ...conditionals].some((b) => b.readsItem && b.readsPreamble),
19394
+ outerNeedsPreamble: [...attrs, ...texts, ...conditionals].some((b) => b.readsOuter && b.readsPreamble),
19395
+ conditionals
18901
19396
  },
18902
19397
  decision
18903
19398
  };
@@ -18948,7 +19443,10 @@ var init_build_lazy_row = __esm({
18948
19443
  init_expression_parser();
18949
19444
  init_types();
18950
19445
  init_csr_substitute();
19446
+ init_html_template();
18951
19447
  init_utils();
19448
+ init_lazy_conditional();
19449
+ init_lazy_preamble();
18952
19450
  init_lazy_row_eligibility();
18953
19451
  }
18954
19452
  });
@@ -18974,7 +19472,7 @@ function buildBranchLoopPlan(loop, profileComponentName, lazyScope) {
18974
19472
  const indexParam = loop.index || "__idx";
18975
19473
  const mapPreambleWrapped = loop.preamble ? renderPreamble(loop.preamble, {
18976
19474
  transformJs: (t) => wrapLoopParamAsAccessor(t, loop.param, loop.paramBindings),
18977
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0, true)
19475
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: loop.param, bindings: loop.paramBindings }], void 0)
18978
19476
  }) : "";
18979
19477
  const preambleRegions = buildPreambleRegionPlans(loop.preambleRegions, loop.param, loop.paramBindings);
18980
19478
  const plan = {
@@ -19124,7 +19622,7 @@ var init_claim_plan = __esm({
19124
19622
  });
19125
19623
 
19126
19624
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
19127
- import ts15 from "typescript";
19625
+ import ts16 from "typescript";
19128
19626
  function bindingIdArg(ctx2, slotId) {
19129
19627
  if (!ctx2.profile || !slotId) return "";
19130
19628
  return `, ${JSON.stringify(`${ctx2.componentName}#binding:${slotId}`)}`;
@@ -19204,19 +19702,19 @@ function lowerToLocaleCallsInReactiveExpr(expr, matcher) {
19204
19702
  if (!matcher) return expr;
19205
19703
  let sourceFile;
19206
19704
  try {
19207
- sourceFile = ts15.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts15.ScriptTarget.Latest, true, ts15.ScriptKind.TS);
19705
+ sourceFile = ts16.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts16.ScriptTarget.Latest, true, ts16.ScriptKind.TS);
19208
19706
  } catch {
19209
19707
  return expr;
19210
19708
  }
19211
19709
  const stmt = sourceFile.statements[0];
19212
- if (!stmt || !ts15.isExpressionStatement(stmt)) return expr;
19213
- const root2 = ts15.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
19710
+ if (!stmt || !ts16.isExpressionStatement(stmt)) return expr;
19711
+ const root2 = ts16.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
19214
19712
  const candidates = [];
19215
19713
  const visit3 = (n) => {
19216
- if (ts15.isCallExpression(n) && n.arguments.length === 2 && ts15.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
19714
+ if (ts16.isCallExpression(n) && n.arguments.length === 2 && ts16.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && n.expression.name.text === "toLocaleDateString") {
19217
19715
  candidates.push(n);
19218
19716
  }
19219
- ts15.forEachChild(n, visit3);
19717
+ ts16.forEachChild(n, visit3);
19220
19718
  };
19221
19719
  visit3(root2);
19222
19720
  if (candidates.length === 0) return expr;
@@ -19253,19 +19751,19 @@ function lowerDateCallsInReactiveExpr(expr, matcher) {
19253
19751
  if (!matcher) return expr;
19254
19752
  let sourceFile;
19255
19753
  try {
19256
- sourceFile = ts15.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts15.ScriptTarget.Latest, true, ts15.ScriptKind.TS);
19754
+ sourceFile = ts16.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts16.ScriptTarget.Latest, true, ts16.ScriptKind.TS);
19257
19755
  } catch {
19258
19756
  return expr;
19259
19757
  }
19260
19758
  const stmt = sourceFile.statements[0];
19261
- if (!stmt || !ts15.isExpressionStatement(stmt)) return expr;
19262
- const root2 = ts15.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
19759
+ if (!stmt || !ts16.isExpressionStatement(stmt)) return expr;
19760
+ const root2 = ts16.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
19263
19761
  const candidates = [];
19264
19762
  const visit3 = (n) => {
19265
- if (ts15.isCallExpression(n) && n.arguments.length === 0 && ts15.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && DATE_METHODS.has(n.expression.name.text)) {
19763
+ if (ts16.isCallExpression(n) && n.arguments.length === 0 && ts16.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && DATE_METHODS.has(n.expression.name.text)) {
19266
19764
  candidates.push(n);
19267
19765
  }
19268
- ts15.forEachChild(n, visit3);
19766
+ ts16.forEachChild(n, visit3);
19269
19767
  };
19270
19768
  visit3(root2);
19271
19769
  if (candidates.length === 0) return expr;
@@ -19605,7 +20103,7 @@ function stringifyReactiveEffects(lines, plan, opts) {
19605
20103
  const outerTexts = plan?.outerTexts ?? [];
19606
20104
  const conditionals = plan?.conditionals ?? [];
19607
20105
  if (pc) {
19608
- emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId);
20106
+ emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId, mapPreambleWrapped);
19609
20107
  emitOuterTexts(lines, indent, elVar, outerTexts, bindingBfId, textClaimPathExprs);
19610
20108
  emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPreambleWrapped);
19611
20109
  } else {
@@ -19626,7 +20124,7 @@ function stringifyReactiveEffects(lines, plan, opts) {
19626
20124
  emitOuterConditional(lines, indent, elVar, cond, pc);
19627
20125
  }
19628
20126
  }
19629
- function emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId) {
20127
+ function emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementIndexBySlot, bindingBfId, mapPreambleWrapped) {
19630
20128
  for (const slot of attrSlots) {
19631
20129
  const varName = `__ra_${varSlotId(slot.slotId)}`;
19632
20130
  const lookupExpr = attrLookupExpr(slot.slotId, varName, elVar, lookup, elementIndexBySlot);
@@ -19634,6 +20132,9 @@ function emitAttrSlotsGranular(lines, indent, elVar, lookup, attrSlots, elementI
19634
20132
  lines.push(`${indent}if (${varName}) {`);
19635
20133
  for (const attr of slot.attrs) {
19636
20134
  lines.push(`${indent} createEffect(() => {`);
20135
+ if (attr.readsPreamble && mapPreambleWrapped) {
20136
+ lines.push(`${indent} ${mapPreambleWrapped}`);
20137
+ }
19637
20138
  for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
19638
20139
  lines.push(`${indent} ${stmt}`);
19639
20140
  }
@@ -19654,6 +20155,9 @@ function emitPreambleRegionsEffect(lines, indent, elVar, preambleRegions, mapPre
19654
20155
  }
19655
20156
  lines.push(`${indent}})`);
19656
20157
  }
20158
+ function attrsReadPreamble(attrSlots) {
20159
+ return attrSlots.some((slot) => slot.attrs.some((a) => a.readsPreamble));
20160
+ }
19657
20161
  function attrLookupExpr(slotId, varName, elVar, lookup, elementIndexBySlot) {
19658
20162
  const pIdx = elementIndexBySlot?.get(slotId);
19659
20163
  return pIdx !== void 0 ? `__p ? __p[${pIdx}] : ${lookup}(${elVar}, '[bf="${slotId}"]')` : `${lookup}(${elVar}, '[bf="${slotId}"]')`;
@@ -19678,6 +20182,9 @@ function emitConsolidatedRowEffect(lines, indent, elVar, lookup, attrSlots, oute
19678
20182
  return;
19679
20183
  }
19680
20184
  lines.push(`${indent}createEffect(() => {`);
20185
+ if (mapPreambleWrapped && (preambleRegions.length > 0 || attrsReadPreamble(attrSlots))) {
20186
+ lines.push(`${indent} ${mapPreambleWrapped}`);
20187
+ }
19681
20188
  for (const slot of attrSlots) {
19682
20189
  const varName = `__ra_${varSlotId(slot.slotId)}`;
19683
20190
  lines.push(`${indent} if (${varName}) {`);
@@ -19690,9 +20197,6 @@ function emitConsolidatedRowEffect(lines, indent, elVar, lookup, attrSlots, oute
19690
20197
  }
19691
20198
  lines.push(`${indent} }`);
19692
20199
  }
19693
- if (preambleRegions.length > 0 && mapPreambleWrapped) {
19694
- lines.push(`${indent} ${mapPreambleWrapped}`);
19695
- }
19696
20200
  for (const text of outerTexts) {
19697
20201
  lines.push(`${indent} ${writer}('${text.slotId}', String(${text.wrappedExpression}))`);
19698
20202
  }
@@ -19838,9 +20342,8 @@ function stringifyLazyRowLoop(lines, o) {
19838
20342
  const { indent, lazyRow, paramHead } = o;
19839
20343
  const mid = o.markerId.replace(/[^A-Za-z0-9_$]/g, "_");
19840
20344
  const tplVar = `__tpl_${mid}`;
19841
- const claimVar = `__lzc_${mid}`;
19842
20345
  const hasRefs = lazyRow.attrSlotIds.length > 0 || lazyRow.texts.length > 0;
19843
- const hasBindings = lazyRow.attrs.length > 0 || lazyRow.texts.length > 0;
20346
+ const hasBindings = lazyRow.attrs.length > 0 || lazyRow.texts.length > 0 || lazyRow.conditionals.length > 0;
19844
20347
  const rwDoor = lazyRow.textNeedsRead;
19845
20348
  const paths = o.skeletonPaths;
19846
20349
  const useHoisted = Boolean(o.skeletonTemplate);
@@ -19869,12 +20372,10 @@ function stringifyLazyRowLoop(lines, o) {
19869
20372
  freshPlanVar = adoptedPlanVar;
19870
20373
  }
19871
20374
  }
19872
- if (hasRefs) {
19873
- const parts = refParts(lazyRow, "__el", null, adoptedPlanVar, true);
19874
- lines.push(`${indent}const ${claimVar} = (__e) => {`);
19875
- if (parts.some((p) => p.includes("__el"))) lines.push(`${indent} const __el = __e.primaryEl`);
19876
- lines.push(`${indent} return [${parts.join(", ")}]`);
19877
- lines.push(`${indent}}`);
20375
+ for (const c of lazyRow.conditionals) {
20376
+ const v = condVars(mid, c.slotId);
20377
+ emitHoistedTemplateDecl(lines, indent, v.trueTpl, c.whenTrueHtml);
20378
+ emitHoistedTemplateDecl(lines, indent, v.falseTpl, c.whenFalseHtml);
19878
20379
  }
19879
20380
  const call = `mapArrayLazy(() => ${o.arrayExpr}, ${o.containerVar}, ${o.keyFn}, {`;
19880
20381
  lines.push(`${indent}${o.guardContainer ? `if (${o.containerVar}) ` : ""}${call}`);
@@ -19882,6 +20383,7 @@ function stringifyLazyRowLoop(lines, o) {
19882
20383
  const b2 = `${indent} `;
19883
20384
  lines.push(`${b1}createRow: (__e, ${o.indexParam}) => {`);
19884
20385
  lines.push(`${b2}const ${paramHead} = () => __e.item`);
20386
+ if (lazyRow.preambleStatements) lines.push(`${b2}${lazyRow.preambleStatements}`);
19885
20387
  const cloneExpr = useHoisted ? hoistedCloneExpr(tplVar, o.skeletonTemplate) : `(() => { ${emitTemplateCloneInline(o.template)} })()`;
19886
20388
  lines.push(`${b2}const __el = ${cloneExpr}`);
19887
20389
  if (hasRefs) {
@@ -19893,55 +20395,70 @@ function stringifyLazyRowLoop(lines, o) {
19893
20395
  const createDoor = `__r[${lazyRow.writerIndex}]`;
19894
20396
  for (const t of lazyRow.texts) emitTextBinding(lines, b2, t, createDoor, "create", rwDoor);
19895
20397
  }
20398
+ for (const c of lazyRow.conditionals) emitConditional(lines, b2, mid, c, "create");
19896
20399
  lines.push(`${b2}return __el`);
19897
20400
  lines.push(`${b1}},`);
19898
20401
  const itemAttrs = lazyRow.attrs.filter((a) => a.readsItem);
19899
20402
  const itemTexts = lazyRow.texts.filter((t) => t.readsItem);
19900
- if (itemAttrs.length === 0 && itemTexts.length === 0) {
20403
+ const itemConds = lazyRow.conditionals.filter((c) => c.readsItem);
20404
+ if (itemAttrs.length === 0 && itemTexts.length === 0 && itemConds.length === 0) {
19901
20405
  lines.push(`${b1}applyItem: () => {},`);
19902
20406
  } else {
19903
20407
  lines.push(`${b1}applyItem: (__e) => {`);
19904
20408
  lines.push(`${b2}const ${paramHead} = () => __e.item`);
19905
- lines.push(`${b2}const __r = __e.refs ?? (__e.refs = ${claimVar}(__e))`);
20409
+ lines.push(`${b2}const __r = __e.refs ?? (__e.refs = [])`);
19906
20410
  lines.push(`${b2}const __l = __e.last ?? (__e.last = [])`);
20411
+ if (lazyRow.itemNeedsPreamble && lazyRow.preambleStatements) {
20412
+ lines.push(`${b2}${lazyRow.preambleStatements}`);
20413
+ }
19907
20414
  for (const a of itemAttrs) emitAttrBinding(lines, b2, a, "item");
19908
20415
  if (itemTexts.length > 0) {
19909
20416
  lines.push(`${b2}const __d = ${doorAccess(lazyRow, lazyRow.writerIndex, adoptedPlanVar)}`);
19910
20417
  for (const t of itemTexts) emitTextBinding(lines, b2, t, "__d", "item", rwDoor);
19911
20418
  }
20419
+ for (const c of itemConds) emitConditional(lines, b2, mid, c, "item");
19912
20420
  lines.push(`${b1}},`);
19913
20421
  }
19914
20422
  const outerAttrs = lazyRow.attrs.filter((a) => a.readsOuter);
19915
20423
  const outerTexts = lazyRow.texts.filter((t) => t.readsOuter);
19916
- if (outerAttrs.length > 0 || outerTexts.length > 0) {
20424
+ const outerConds = lazyRow.conditionals.filter((c) => c.readsOuter);
20425
+ if (outerAttrs.length > 0 || outerTexts.length > 0 || outerConds.length > 0) {
19917
20426
  const b3 = `${indent} `;
19918
20427
  lines.push(`${b1}applyOuter: (__es, __seed) => {`);
19919
20428
  for (const g of lazyRow.outerPrimeGetters) lines.push(`${b2}${g}()`);
19920
20429
  lines.push(`${b2}for (const __e of __es) {`);
19921
20430
  lines.push(`${b3}const ${paramHead} = () => __e.item`);
19922
- lines.push(`${b3}const __r = __e.refs ?? (__e.refs = ${claimVar}(__e))`);
20431
+ lines.push(`${b3}const __r = __e.refs ?? (__e.refs = [])`);
19923
20432
  lines.push(`${b3}const __l = __e.last ?? (__e.last = [])`);
20433
+ if (lazyRow.outerNeedsPreamble && lazyRow.preambleStatements) {
20434
+ lines.push(`${b3}${lazyRow.preambleStatements}`);
20435
+ }
19924
20436
  for (const a of outerAttrs) emitAttrBinding(lines, b3, a, "outer");
19925
20437
  if (outerTexts.length > 0) {
19926
20438
  lines.push(`${b3}const __d = ${doorAccess(lazyRow, lazyRow.writerIndex, adoptedPlanVar)}`);
19927
20439
  for (const t of outerTexts) emitTextBinding(lines, b3, t, "__d", "outer", rwDoor);
19928
20440
  }
20441
+ for (const c of outerConds) emitConditional(lines, b3, mid, c, "outer");
19929
20442
  lines.push(`${b2}}`);
19930
20443
  lines.push(`${b1}},`);
19931
20444
  }
19932
20445
  lines.push(`${indent}}, '${o.markerId}')`);
19933
20446
  }
19934
- function refParts(lazyRow, elVar, skeletonPaths, planVar, deferDoor = false) {
20447
+ function refParts(lazyRow, elVar, skeletonPaths, planVar) {
19935
20448
  const parts = [];
19936
20449
  for (const slotId of lazyRow.attrSlotIds) {
19937
20450
  const path25 = skeletonPaths?.elementPaths.get(slotId);
19938
20451
  parts.push(path25 ? pathExpr(elVar, path25) : `qsa(${elVar}, '[bf="${slotId}"]')`);
19939
20452
  }
19940
20453
  if (lazyRow.texts.length > 0) {
19941
- parts.push(deferDoor ? "null" : `${doorCtor(lazyRow)}(${elVar}, ${planVar})`);
20454
+ parts.push(`${doorCtor(lazyRow)}(${elVar}, ${planVar})`);
19942
20455
  }
19943
20456
  return parts;
19944
20457
  }
20458
+ function elementAccess(a) {
20459
+ const slot = `__r[${a.refIndex}]`;
20460
+ return `${a.refIndex} in __r ? ${slot} : (${slot} = qsa(__e.primaryEl, '[bf="${a.slotId}"]'))`;
20461
+ }
19945
20462
  function doorCtor(lazyRow) {
19946
20463
  return lazyRow.textNeedsRead ? "lazyClaimSlots" : "lazySlots";
19947
20464
  }
@@ -19949,11 +20466,36 @@ function doorAccess(lazyRow, writerIndex, adoptedPlanVar) {
19949
20466
  const slot = `__r[${writerIndex}]`;
19950
20467
  return `${slot} ?? (${slot} = ${doorCtor(lazyRow)}(__e.primaryEl, ${adoptedPlanVar}))`;
19951
20468
  }
20469
+ function condVars(mid, slotId) {
20470
+ const key = `${mid}_${slotId.replace(/[^A-Za-z0-9_$]/g, "_")}`;
20471
+ return { trueTpl: `__cbt_${key}`, falseTpl: `__cbf_${key}` };
20472
+ }
20473
+ function emitConditional(lines, ind, mid, c, mode2) {
20474
+ const v = condVars(mid, c.slotId);
20475
+ if (mode2 === "create") {
20476
+ lines.push(`${ind}__l[${c.ordinal}] = !!(${c.wrappedCondition})`);
20477
+ return;
20478
+ }
20479
+ const slot = `__r[${c.refIndex}]`;
20480
+ lines.push(`${ind}{ const __c = ${c.refIndex} in __r ? ${slot} : (${slot} = qsa(__e.primaryEl, '[bf-c="${c.slotId}"]'))`);
20481
+ lines.push(`${ind}if (__c) {`);
20482
+ lines.push(`${ind} const __x = !!(${c.wrappedCondition})`);
20483
+ lines.push(`${ind} const __w = (__x ? ${v.trueTpl} : ${v.falseTpl}).content.firstElementChild`);
20484
+ const guard = mode2 === "item" ? dedupGuard(c.ordinal) : `__seed ? (__c.outerHTML !== __w.outerHTML) : (${dedupGuard(c.ordinal)})`;
20485
+ lines.push(`${ind} if (${guard}) {`);
20486
+ lines.push(`${ind} const __n = __w.cloneNode(true)`);
20487
+ lines.push(`${ind} __c.replaceWith(__n)`);
20488
+ lines.push(`${ind} ${slot} = __n`);
20489
+ lines.push(`${ind} }`);
20490
+ lines.push(`${ind} __l[${c.ordinal}] = __x`);
20491
+ lines.push(`${ind}} }`);
20492
+ }
19952
20493
  function dedupGuard(ordinal) {
19953
20494
  return `!(${ordinal} in __l) || !Object.is(__l[${ordinal}], __x)`;
19954
20495
  }
19955
20496
  function emitAttrBinding(lines, ind, a, mode2) {
19956
- lines.push(`${ind}{ const __t = __r[${a.refIndex}]`);
20497
+ const target2 = mode2 === "create" ? `__r[${a.refIndex}]` : elementAccess(a);
20498
+ lines.push(`${ind}{ const __t = ${target2}`);
19957
20499
  lines.push(`${ind}if (__t) {`);
19958
20500
  lines.push(`${ind} const __x = ${a.wrappedExpression}`);
19959
20501
  const guard = mode2 === "create" ? null : mode2 === "item" ? dedupGuard(a.ordinal) : `__seed ? (${seedDiffersExpr("__t", a)}) : (${dedupGuard(a.ordinal)})`;
@@ -20463,7 +21005,11 @@ function stringifyCompositeLoop(lines, plan) {
20463
21005
  template,
20464
21006
  bodyIsMultiRoot,
20465
21007
  indent: bodyIndent,
20466
- singleRootLayout: "multiline"
21008
+ singleRootLayout: "multiline",
21009
+ // Composite is exactly the variant that initialises something inside the
21010
+ // row — nested components, inner loops, or both — so it is exactly the
21011
+ // variant whose tail needs the row already connected.
21012
+ mountRow: true
20467
21013
  });
20468
21014
  emitComponentAndEventSetup(lines, bodyIndent, "__el", compsArr, eventsArr, loopParam, loopParamBindings, bodyIsMultiRoot);
20469
21015
  if (innerLoops.length > 0) {
@@ -21016,7 +21562,7 @@ function buildPlainLoopPlan(elem, profileComponentName, lazyScope) {
21016
21562
  const indexParam = elem.index || "__idx";
21017
21563
  const mapPreambleWrapped = elem.preamble ? renderPreamble(elem.preamble, {
21018
21564
  transformJs: wrap,
21019
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
21565
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0)
21020
21566
  }) : "";
21021
21567
  const preambleRegions = buildPreambleRegionPlans(elem.preambleRegions, elem.param, elem.paramBindings);
21022
21568
  return {
@@ -21104,7 +21650,7 @@ function buildStaticLoopMaterialize(elem, unsafeLocalNames) {
21104
21650
  return {
21105
21651
  itemTemplate: elem.staticItemTemplate,
21106
21652
  mapPreamble: elem.preamble ? renderPreamble(elem.preamble, {
21107
- renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0, true)
21653
+ renderLeaf: (ir) => irToHtmlTemplate(ir, void 0, 1, [{ param: elem.param, bindings: elem.paramBindings }], void 0)
21108
21654
  }) : "",
21109
21655
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false
21110
21656
  };
@@ -21400,25 +21946,25 @@ var init_phases = __esm({
21400
21946
  });
21401
21947
 
21402
21948
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
21403
- import ts16 from "typescript";
21949
+ import ts17 from "typescript";
21404
21950
  function rewritePropsObjectRef(code, propsObjectName) {
21405
21951
  const srcPropsName = propsObjectName ?? "props";
21406
21952
  if (srcPropsName === PROPS_PARAM) return code;
21407
21953
  if (!new RegExp(`\\b${srcPropsName}\\b`).test(code)) return code;
21408
- const sourceFile = ts16.createSourceFile(
21954
+ const sourceFile = ts17.createSourceFile(
21409
21955
  "init-body.ts",
21410
21956
  code,
21411
- ts16.ScriptTarget.Latest,
21957
+ ts17.ScriptTarget.Latest,
21412
21958
  /*setParentNodes*/
21413
21959
  true,
21414
- ts16.ScriptKind.TS
21960
+ ts17.ScriptKind.TS
21415
21961
  );
21416
21962
  const spans = [];
21417
21963
  function visit3(node) {
21418
- if (ts16.isIdentifier(node) && node.text === srcPropsName && shouldRewrite(node)) {
21964
+ if (ts17.isIdentifier(node) && node.text === srcPropsName && shouldRewrite(node)) {
21419
21965
  spans.push([node.getStart(sourceFile), node.getEnd()]);
21420
21966
  }
21421
- ts16.forEachChild(node, visit3);
21967
+ ts17.forEachChild(node, visit3);
21422
21968
  }
21423
21969
  visit3(sourceFile);
21424
21970
  if (spans.length === 0) return code;
@@ -21432,12 +21978,12 @@ function rewritePropsObjectRef(code, propsObjectName) {
21432
21978
  function shouldRewrite(node) {
21433
21979
  const parent2 = node.parent;
21434
21980
  if (!parent2) return true;
21435
- if (ts16.isPropertyAccessExpression(parent2) && parent2.name === node) return false;
21436
- if (ts16.isPropertyAssignment(parent2) && parent2.name === node) return false;
21437
- if (ts16.isShorthandPropertyAssignment(parent2) && parent2.name === node) return false;
21438
- if (ts16.isPropertySignature(parent2) && parent2.name === node) return false;
21439
- if (ts16.isPropertyDeclaration(parent2) && parent2.name === node) return false;
21440
- if (ts16.isBindingElement(parent2) && parent2.name === node) return false;
21981
+ if (ts17.isPropertyAccessExpression(parent2) && parent2.name === node) return false;
21982
+ if (ts17.isPropertyAssignment(parent2) && parent2.name === node) return false;
21983
+ if (ts17.isShorthandPropertyAssignment(parent2) && parent2.name === node) return false;
21984
+ if (ts17.isPropertySignature(parent2) && parent2.name === node) return false;
21985
+ if (ts17.isPropertyDeclaration(parent2) && parent2.name === node) return false;
21986
+ if (ts17.isBindingElement(parent2) && parent2.name === node) return false;
21441
21987
  return true;
21442
21988
  }
21443
21989
  var init_rewrite_props_object = __esm({
@@ -21826,7 +22372,6 @@ function generateTemplateOnlyMount(ir, ctx2) {
21826
22372
  ir.root,
21827
22373
  csrInlinableConstants,
21828
22374
  ctx2,
21829
- void 0,
21830
22375
  restSpreadNames,
21831
22376
  ctx2.propsObjectName,
21832
22377
  unsafeLocalNames
@@ -22121,7 +22666,7 @@ var init_css_layer_prefixer = __esm({
22121
22666
  });
22122
22667
 
22123
22668
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
22124
- import ts17 from "typescript";
22669
+ import ts18 from "typescript";
22125
22670
  function preprocessInlineJsxCallbacks(source, filePath) {
22126
22671
  const errors = [];
22127
22672
  const syntheticNames = [];
@@ -22141,15 +22686,15 @@ function preprocessInlineJsxCallbacks(source, filePath) {
22141
22686
  return { source: current, errors, syntheticNames };
22142
22687
  }
22143
22688
  function runSinglePass(source, filePath, startingCounter) {
22144
- const sourceFile = ts17.createSourceFile(
22689
+ const sourceFile = ts18.createSourceFile(
22145
22690
  filePath,
22146
22691
  source,
22147
- ts17.ScriptTarget.Latest,
22692
+ ts18.ScriptTarget.Latest,
22148
22693
  true,
22149
- ts17.ScriptKind.TSX
22694
+ ts18.ScriptKind.TSX
22150
22695
  );
22151
22696
  const hasUseClient = sourceFile.statements.some(
22152
- (stmt) => ts17.isExpressionStatement(stmt) && ts17.isStringLiteral(stmt.expression) && (stmt.expression.text === "use client" || stmt.expression.text === "'use client'")
22697
+ (stmt) => ts18.isExpressionStatement(stmt) && ts18.isStringLiteral(stmt.expression) && (stmt.expression.text === "use client" || stmt.expression.text === "'use client'")
22153
22698
  );
22154
22699
  if (!hasUseClient) {
22155
22700
  return { source, errors: [], syntheticNames: [], counterAfter: startingCounter };
@@ -22172,20 +22717,20 @@ function runSinglePass(source, filePath, startingCounter) {
22172
22717
  }
22173
22718
  }
22174
22719
  function visit3(node) {
22175
- if (ts17.isJsxAttribute(node) && node.initializer && ts17.isJsxExpression(node.initializer) && node.initializer.expression) {
22720
+ if (ts18.isJsxAttribute(node) && node.initializer && ts18.isJsxExpression(node.initializer) && node.initializer.expression) {
22176
22721
  if (tryHandleArrowValue(node.initializer.expression)) {
22177
22722
  return;
22178
22723
  }
22179
22724
  }
22180
- if (ts17.isPropertyAssignment(node) && node.initializer) {
22725
+ if (ts18.isPropertyAssignment(node) && node.initializer) {
22181
22726
  if (tryHandleArrowValue(node.initializer)) return;
22182
22727
  }
22183
- ts17.forEachChild(node, visit3);
22728
+ ts18.forEachChild(node, visit3);
22184
22729
  }
22185
22730
  function tryHandleArrowValue(initializer) {
22186
22731
  let expr = initializer;
22187
- while (ts17.isParenthesizedExpression(expr)) expr = expr.expression;
22188
- if (ts17.isArrowFunction(expr) && arrowBodyContainsJsx(expr)) {
22732
+ while (ts18.isParenthesizedExpression(expr)) expr = expr.expression;
22733
+ if (ts18.isArrowFunction(expr) && arrowBodyContainsJsx(expr)) {
22189
22734
  return handleInlineArrow(expr);
22190
22735
  }
22191
22736
  return false;
@@ -22220,7 +22765,7 @@ function runSinglePass(source, filePath, startingCounter) {
22220
22765
  replacements.push({ start: arrowStart, end: arrowEnd, text: name2 });
22221
22766
  return true;
22222
22767
  }
22223
- ts17.forEachChild(sourceFile, visit3);
22768
+ ts18.forEachChild(sourceFile, visit3);
22224
22769
  if (replacements.length === 0) {
22225
22770
  return { source, errors, syntheticNames, counterAfter: counter };
22226
22771
  }
@@ -22239,48 +22784,48 @@ function errorMessageForCapture(captures) {
22239
22784
  return `Inline JSX-returning arrow function captures non-module identifier(s): ${captures.sort().join(", ")}. Extract the callback into a top-level '\\'use client\\'' component (e.g. \`function MyNode(n) { return <div/> }\` then \`renderNode={MyNode}\`) or pass captured values via component props.`;
22240
22785
  }
22241
22786
  function arrowBodyContainsJsx(arrow) {
22242
- if (ts17.isBlock(arrow.body)) {
22787
+ if (ts18.isBlock(arrow.body)) {
22243
22788
  return blockReturnsJsx(arrow.body);
22244
22789
  }
22245
22790
  let body2 = arrow.body;
22246
- while (ts17.isParenthesizedExpression(body2)) body2 = body2.expression;
22791
+ while (ts18.isParenthesizedExpression(body2)) body2 = body2.expression;
22247
22792
  return isJsxLike(body2);
22248
22793
  }
22249
22794
  function blockReturnsJsx(block) {
22250
22795
  let found = false;
22251
22796
  function visit3(n) {
22252
22797
  if (found) return;
22253
- if (ts17.isReturnStatement(n) && n.expression) {
22798
+ if (ts18.isReturnStatement(n) && n.expression) {
22254
22799
  let e = n.expression;
22255
- while (ts17.isParenthesizedExpression(e)) e = e.expression;
22800
+ while (ts18.isParenthesizedExpression(e)) e = e.expression;
22256
22801
  if (isJsxLike(e)) {
22257
22802
  found = true;
22258
22803
  return;
22259
22804
  }
22260
22805
  }
22261
- if (ts17.isArrowFunction(n) || ts17.isFunctionDeclaration(n) || ts17.isFunctionExpression(n)) return;
22262
- ts17.forEachChild(n, visit3);
22806
+ if (ts18.isArrowFunction(n) || ts18.isFunctionDeclaration(n) || ts18.isFunctionExpression(n)) return;
22807
+ ts18.forEachChild(n, visit3);
22263
22808
  }
22264
- ts17.forEachChild(block, visit3);
22809
+ ts18.forEachChild(block, visit3);
22265
22810
  return found;
22266
22811
  }
22267
22812
  function isJsxLike(expr) {
22268
- return ts17.isJsxElement(expr) || ts17.isJsxSelfClosingElement(expr) || ts17.isJsxFragment(expr);
22813
+ return ts18.isJsxElement(expr) || ts18.isJsxSelfClosingElement(expr) || ts18.isJsxFragment(expr);
22269
22814
  }
22270
22815
  function collectArrowParamNames(arrow) {
22271
22816
  const names = /* @__PURE__ */ new Set();
22272
- for (const p of arrow.parameters) collectBindingNames2(p.name, names);
22817
+ for (const p of arrow.parameters) collectBindingNames3(p.name, names);
22273
22818
  return names;
22274
22819
  }
22275
- function collectBindingNames2(name2, out) {
22820
+ function collectBindingNames3(name2, out) {
22276
22821
  const push = Array.isArray(out) ? (n) => out.push(n) : (n) => out.add(n);
22277
- if (ts17.isIdentifier(name2)) {
22822
+ if (ts18.isIdentifier(name2)) {
22278
22823
  push(name2.text);
22279
- } else if (ts17.isObjectBindingPattern(name2)) {
22280
- name2.elements.forEach((el) => collectBindingNames2(el.name, out));
22281
- } else if (ts17.isArrayBindingPattern(name2)) {
22824
+ } else if (ts18.isObjectBindingPattern(name2)) {
22825
+ name2.elements.forEach((el) => collectBindingNames3(el.name, out));
22826
+ } else if (ts18.isArrayBindingPattern(name2)) {
22282
22827
  name2.elements.forEach((el) => {
22283
- if (!ts17.isOmittedExpression(el)) collectBindingNames2(el.name, out);
22828
+ if (!ts18.isOmittedExpression(el)) collectBindingNames3(el.name, out);
22284
22829
  });
22285
22830
  }
22286
22831
  }
@@ -22289,12 +22834,12 @@ function collectFreeIdentifiers(arrow) {
22289
22834
  const bound = [];
22290
22835
  for (const p of arrow.parameters) {
22291
22836
  const names = [];
22292
- collectBindingNames2(p.name, names);
22837
+ collectBindingNames3(p.name, names);
22293
22838
  bound.push(...names);
22294
22839
  }
22295
22840
  function pushBindings(name2) {
22296
22841
  const names = [];
22297
- collectBindingNames2(name2, names);
22842
+ collectBindingNames3(name2, names);
22298
22843
  bound.push(...names);
22299
22844
  return names;
22300
22845
  }
@@ -22305,71 +22850,71 @@ function collectFreeIdentifiers(arrow) {
22305
22850
  return bound.includes(name2);
22306
22851
  }
22307
22852
  function visit3(node) {
22308
- if (ts17.isIdentifier(node)) {
22853
+ if (ts18.isIdentifier(node)) {
22309
22854
  const parent2 = node.parent;
22310
- if (parent2 && ts17.isPropertyAccessExpression(parent2) && parent2.name === node) return;
22311
- if (parent2 && ts17.isPropertyAssignment(parent2) && parent2.name === node) return;
22312
- if (parent2 && ts17.isPropertySignature(parent2) && parent2.name === node) return;
22313
- if (parent2 && ts17.isPropertyDeclaration(parent2) && parent2.name === node) return;
22314
- if (parent2 && ts17.isMethodDeclaration(parent2) && parent2.name === node) return;
22315
- if (parent2 && ts17.isMethodSignature(parent2) && parent2.name === node) return;
22316
- if (parent2 && ts17.isGetAccessorDeclaration(parent2) && parent2.name === node) return;
22317
- if (parent2 && ts17.isSetAccessorDeclaration(parent2) && parent2.name === node) return;
22318
- if (parent2 && ts17.isEnumMember(parent2) && parent2.name === node) return;
22319
- if (parent2 && ts17.isBindingElement(parent2) && parent2.propertyName === node) return;
22320
- if (parent2 && ts17.isShorthandPropertyAssignment(parent2) && parent2.name === node) {
22855
+ if (parent2 && ts18.isPropertyAccessExpression(parent2) && parent2.name === node) return;
22856
+ if (parent2 && ts18.isPropertyAssignment(parent2) && parent2.name === node) return;
22857
+ if (parent2 && ts18.isPropertySignature(parent2) && parent2.name === node) return;
22858
+ if (parent2 && ts18.isPropertyDeclaration(parent2) && parent2.name === node) return;
22859
+ if (parent2 && ts18.isMethodDeclaration(parent2) && parent2.name === node) return;
22860
+ if (parent2 && ts18.isMethodSignature(parent2) && parent2.name === node) return;
22861
+ if (parent2 && ts18.isGetAccessorDeclaration(parent2) && parent2.name === node) return;
22862
+ if (parent2 && ts18.isSetAccessorDeclaration(parent2) && parent2.name === node) return;
22863
+ if (parent2 && ts18.isEnumMember(parent2) && parent2.name === node) return;
22864
+ if (parent2 && ts18.isBindingElement(parent2) && parent2.propertyName === node) return;
22865
+ if (parent2 && ts18.isShorthandPropertyAssignment(parent2) && parent2.name === node) {
22321
22866
  if (!isBound(node.text)) ids.add(node.text);
22322
22867
  return;
22323
22868
  }
22324
- if (parent2 && ts17.isParameter(parent2) && parent2.name === node) return;
22325
- if (parent2 && ts17.isVariableDeclaration(parent2) && parent2.name === node) return;
22326
- if (parent2 && ts17.isFunctionDeclaration(parent2) && parent2.name === node) return;
22327
- if (parent2 && ts17.isClassDeclaration(parent2) && parent2.name === node) return;
22328
- if (parent2 && ts17.isJsxAttribute(parent2) && parent2.name === node) return;
22329
- if (parent2 && ts17.isJsxOpeningElement(parent2) && parent2.tagName === node) {
22869
+ if (parent2 && ts18.isParameter(parent2) && parent2.name === node) return;
22870
+ if (parent2 && ts18.isVariableDeclaration(parent2) && parent2.name === node) return;
22871
+ if (parent2 && ts18.isFunctionDeclaration(parent2) && parent2.name === node) return;
22872
+ if (parent2 && ts18.isClassDeclaration(parent2) && parent2.name === node) return;
22873
+ if (parent2 && ts18.isJsxAttribute(parent2) && parent2.name === node) return;
22874
+ if (parent2 && ts18.isJsxOpeningElement(parent2) && parent2.tagName === node) {
22330
22875
  if (/^[a-z]/.test(node.text)) return;
22331
22876
  }
22332
- if (parent2 && ts17.isJsxClosingElement(parent2) && parent2.tagName === node) {
22877
+ if (parent2 && ts18.isJsxClosingElement(parent2) && parent2.tagName === node) {
22333
22878
  if (/^[a-z]/.test(node.text)) return;
22334
22879
  }
22335
22880
  if (isBound(node.text)) return;
22336
22881
  ids.add(node.text);
22337
22882
  return;
22338
22883
  }
22339
- if (ts17.isVariableDeclaration(node)) {
22884
+ if (ts18.isVariableDeclaration(node)) {
22340
22885
  const declared = pushBindings(node.name);
22341
22886
  if (node.initializer) visit3(node.initializer);
22342
22887
  declared;
22343
22888
  return;
22344
22889
  }
22345
- if (ts17.isFunctionDeclaration(node)) {
22890
+ if (ts18.isFunctionDeclaration(node)) {
22346
22891
  if (node.name) bound.push(node.name.text);
22347
22892
  visitInsideNewScope(node);
22348
22893
  return;
22349
22894
  }
22350
- if (ts17.isClassDeclaration(node)) {
22895
+ if (ts18.isClassDeclaration(node)) {
22351
22896
  if (node.name) bound.push(node.name.text);
22352
- ts17.forEachChild(node, visit3);
22897
+ ts18.forEachChild(node, visit3);
22353
22898
  return;
22354
22899
  }
22355
- if (ts17.isArrowFunction(node) || ts17.isFunctionExpression(node)) {
22900
+ if (ts18.isArrowFunction(node) || ts18.isFunctionExpression(node)) {
22356
22901
  visitInsideNewScope(node);
22357
22902
  return;
22358
22903
  }
22359
- if (ts17.isCatchClause(node)) {
22904
+ if (ts18.isCatchClause(node)) {
22360
22905
  const before = bound.length;
22361
22906
  if (node.variableDeclaration) pushBindings(node.variableDeclaration.name);
22362
- ts17.forEachChild(node, visit3);
22907
+ ts18.forEachChild(node, visit3);
22363
22908
  popN(bound.length - before);
22364
22909
  return;
22365
22910
  }
22366
- if (ts17.isBlock(node)) {
22911
+ if (ts18.isBlock(node)) {
22367
22912
  const before = bound.length;
22368
- ts17.forEachChild(node, visit3);
22913
+ ts18.forEachChild(node, visit3);
22369
22914
  popN(bound.length - before);
22370
22915
  return;
22371
22916
  }
22372
- ts17.forEachChild(node, visit3);
22917
+ ts18.forEachChild(node, visit3);
22373
22918
  }
22374
22919
  function visitInsideNewScope(fn) {
22375
22920
  const before = bound.length;
@@ -22389,27 +22934,27 @@ function collectFreeIdentifiers(arrow) {
22389
22934
  function collectModuleScopeNames(sourceFile) {
22390
22935
  const names = /* @__PURE__ */ new Set();
22391
22936
  for (const stmt of sourceFile.statements) {
22392
- if (ts17.isFunctionDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
22393
- else if (ts17.isClassDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
22394
- else if (ts17.isVariableStatement(stmt)) {
22395
- for (const decl of stmt.declarationList.declarations) collectBindingNames2(decl.name, names);
22396
- } else if (ts17.isImportDeclaration(stmt) && stmt.importClause) {
22937
+ if (ts18.isFunctionDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
22938
+ else if (ts18.isClassDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
22939
+ else if (ts18.isVariableStatement(stmt)) {
22940
+ for (const decl of stmt.declarationList.declarations) collectBindingNames3(decl.name, names);
22941
+ } else if (ts18.isImportDeclaration(stmt) && stmt.importClause) {
22397
22942
  const ic = stmt.importClause;
22398
22943
  if (ic.name) names.add(ic.name.text);
22399
22944
  if (ic.namedBindings) {
22400
- if (ts17.isNamespaceImport(ic.namedBindings)) names.add(ic.namedBindings.name.text);
22945
+ if (ts18.isNamespaceImport(ic.namedBindings)) names.add(ic.namedBindings.name.text);
22401
22946
  else for (const e of ic.namedBindings.elements) names.add(e.name.text);
22402
22947
  }
22403
- } else if (ts17.isTypeAliasDeclaration(stmt)) names.add(stmt.name.text);
22404
- else if (ts17.isInterfaceDeclaration(stmt)) names.add(stmt.name.text);
22405
- else if (ts17.isEnumDeclaration(stmt)) names.add(stmt.name.text);
22948
+ } else if (ts18.isTypeAliasDeclaration(stmt)) names.add(stmt.name.text);
22949
+ else if (ts18.isInterfaceDeclaration(stmt)) names.add(stmt.name.text);
22950
+ else if (ts18.isEnumDeclaration(stmt)) names.add(stmt.name.text);
22406
22951
  }
22407
22952
  return names;
22408
22953
  }
22409
22954
  function buildSyntheticDeclaration(name2, arrow, sourceFile) {
22410
22955
  const paramsText = arrow.parameters.length === 0 ? "" : arrow.parameters.map((p) => p.getText(sourceFile)).join(", ");
22411
22956
  let bodyText;
22412
- if (ts17.isBlock(arrow.body)) {
22957
+ if (ts18.isBlock(arrow.body)) {
22413
22958
  bodyText = arrow.body.getText(sourceFile);
22414
22959
  } else {
22415
22960
  const expr = arrow.body.getText(sourceFile);
@@ -22428,7 +22973,7 @@ var init_preprocess_inline_jsx_callbacks = __esm({
22428
22973
  });
22429
22974
 
22430
22975
  // ../jsx/src/ssr-defaults.ts
22431
- import ts18 from "typescript";
22976
+ import ts19 from "typescript";
22432
22977
  function extractSsrDefaults(metadata) {
22433
22978
  const out = {};
22434
22979
  const propsLike = /* @__PURE__ */ new Set();
@@ -22436,11 +22981,12 @@ function extractSsrDefaults(metadata) {
22436
22981
  for (const p of metadata.propsParams) propsLike.add(p.name);
22437
22982
  for (const p of metadata.propsParams) {
22438
22983
  if (p.isRest) continue;
22984
+ const callerPropName = p.sourceName ?? p.name;
22439
22985
  if (metadata.propsObjectName === null && p.defaultValue !== void 0) {
22440
22986
  const value2 = tryStaticEval(p.defaultValue, { bindings: {}, propsLike });
22441
- out[p.name] = { propName: p.name, value: resultToJsonable(value2) };
22987
+ out[p.name] = { propName: callerPropName, value: resultToJsonable(value2) };
22442
22988
  } else {
22443
- out[p.name] = { propName: p.name, value: null };
22989
+ out[p.name] = { propName: callerPropName, value: null };
22444
22990
  }
22445
22991
  }
22446
22992
  if (metadata.restPropsName) {
@@ -22488,11 +23034,11 @@ function collectPropRefs(expr, propsObjectName, out) {
22488
23034
  const node = parseExpression2(expr);
22489
23035
  if (!node) return;
22490
23036
  const visit3 = (n) => {
22491
- if (ts18.isPropertyAccessExpression(n) && ts18.isIdentifier(n.expression) && n.expression.text === propsObjectName && ts18.isIdentifier(n.name)) {
23037
+ if (ts19.isPropertyAccessExpression(n) && ts19.isIdentifier(n.expression) && n.expression.text === propsObjectName && ts19.isIdentifier(n.name)) {
22492
23038
  out.add(n.name.text);
22493
23039
  return;
22494
23040
  }
22495
- ts18.forEachChild(n, visit3);
23041
+ ts19.forEachChild(n, visit3);
22496
23042
  };
22497
23043
  visit3(node);
22498
23044
  }
@@ -22509,21 +23055,21 @@ function tryStaticEval(expr, ctx2) {
22509
23055
  }
22510
23056
  function evalStatementsForReturn(statements, ctx2) {
22511
23057
  for (const stmt of statements) {
22512
- if (ts18.isVariableStatement(stmt)) {
23058
+ if (ts19.isVariableStatement(stmt)) {
22513
23059
  for (const d of stmt.declarationList.declarations) {
22514
- if (!ts18.isIdentifier(d.name) || !d.initializer) continue;
23060
+ if (!ts19.isIdentifier(d.name) || !d.initializer) continue;
22515
23061
  const v = evalNode(d.initializer, ctx2);
22516
23062
  if (v !== UNRESOLVED) ctx2.bindings[d.name.text] = v;
22517
23063
  }
22518
- } else if (ts18.isReturnStatement(stmt)) {
23064
+ } else if (ts19.isReturnStatement(stmt)) {
22519
23065
  return stmt.expression ? evalNode(stmt.expression, ctx2) : UNRESOLVED;
22520
- } else if (ts18.isIfStatement(stmt)) {
23066
+ } else if (ts19.isIfStatement(stmt)) {
22521
23067
  const cond = evalNode(stmt.expression, ctx2);
22522
23068
  if (cond === UNRESOLVED) return UNRESOLVED;
22523
23069
  const branch = cond ? stmt.thenStatement : stmt.elseStatement;
22524
23070
  if (branch) {
22525
23071
  const taken = evalStatementsForReturn(
22526
- ts18.isBlock(branch) ? branch.statements : [branch],
23072
+ ts19.isBlock(branch) ? branch.statements : [branch],
22527
23073
  ctx2
22528
23074
  );
22529
23075
  if (taken !== NO_RETURN) return taken;
@@ -22535,64 +23081,64 @@ function evalStatementsForReturn(statements, ctx2) {
22535
23081
  return NO_RETURN;
22536
23082
  }
22537
23083
  function parseExpression2(expr) {
22538
- const sf = ts18.createSourceFile(
23084
+ const sf = ts19.createSourceFile(
22539
23085
  "__ssr_default__.ts",
22540
23086
  `(${expr})`,
22541
- ts18.ScriptTarget.Latest,
23087
+ ts19.ScriptTarget.Latest,
22542
23088
  false,
22543
- ts18.ScriptKind.TS
23089
+ ts19.ScriptKind.TS
22544
23090
  );
22545
23091
  const stmt = sf.statements[0];
22546
- if (!stmt || !ts18.isExpressionStatement(stmt)) return null;
22547
- const inner = ts18.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
23092
+ if (!stmt || !ts19.isExpressionStatement(stmt)) return null;
23093
+ const inner = ts19.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
22548
23094
  return inner;
22549
23095
  }
22550
23096
  function evalNode(node, ctx2) {
22551
- if (ts18.isParenthesizedExpression(node)) return evalNode(node.expression, ctx2);
22552
- if (ts18.isAsExpression(node)) return evalNode(node.expression, ctx2);
22553
- if (ts18.isSatisfiesExpression(node)) return evalNode(node.expression, ctx2);
22554
- if (ts18.isTypeAssertionExpression(node)) return evalNode(node.expression, ctx2);
22555
- if (ts18.isNonNullExpression(node)) return evalNode(node.expression, ctx2);
22556
- if (ts18.isArrowFunction(node)) {
23097
+ if (ts19.isParenthesizedExpression(node)) return evalNode(node.expression, ctx2);
23098
+ if (ts19.isAsExpression(node)) return evalNode(node.expression, ctx2);
23099
+ if (ts19.isSatisfiesExpression(node)) return evalNode(node.expression, ctx2);
23100
+ if (ts19.isTypeAssertionExpression(node)) return evalNode(node.expression, ctx2);
23101
+ if (ts19.isNonNullExpression(node)) return evalNode(node.expression, ctx2);
23102
+ if (ts19.isArrowFunction(node)) {
22557
23103
  if (node.parameters.length !== 0) return UNRESOLVED;
22558
- if (!ts18.isBlock(node.body)) return evalNode(node.body, ctx2);
23104
+ if (!ts19.isBlock(node.body)) return evalNode(node.body, ctx2);
22559
23105
  const localBindings = { ...ctx2.bindings };
22560
23106
  const localCtx = { ...ctx2, bindings: localBindings };
22561
23107
  const result2 = evalStatementsForReturn(node.body.statements, localCtx);
22562
23108
  return result2 === NO_RETURN ? UNRESOLVED : result2;
22563
23109
  }
22564
- if (ts18.isNumericLiteral(node)) return Number(node.text);
22565
- if (ts18.isStringLiteralLike(node)) return node.text;
22566
- if (node.kind === ts18.SyntaxKind.TrueKeyword) return true;
22567
- if (node.kind === ts18.SyntaxKind.FalseKeyword) return false;
22568
- if (node.kind === ts18.SyntaxKind.NullKeyword) return null;
22569
- if (ts18.isIdentifier(node)) {
23110
+ if (ts19.isNumericLiteral(node)) return Number(node.text);
23111
+ if (ts19.isStringLiteralLike(node)) return node.text;
23112
+ if (node.kind === ts19.SyntaxKind.TrueKeyword) return true;
23113
+ if (node.kind === ts19.SyntaxKind.FalseKeyword) return false;
23114
+ if (node.kind === ts19.SyntaxKind.NullKeyword) return null;
23115
+ if (ts19.isIdentifier(node)) {
22570
23116
  if (node.text === "undefined") return void 0;
22571
23117
  if (node.text in ctx2.bindings) return ctx2.bindings[node.text];
22572
23118
  if (ctx2.propsLike.has(node.text)) return void 0;
22573
23119
  return UNRESOLVED;
22574
23120
  }
22575
- if (ts18.isPrefixUnaryExpression(node)) {
23121
+ if (ts19.isPrefixUnaryExpression(node)) {
22576
23122
  const arg = evalNode(node.operand, ctx2);
22577
23123
  if (arg === UNRESOLVED) return UNRESOLVED;
22578
23124
  switch (node.operator) {
22579
- case ts18.SyntaxKind.MinusToken:
23125
+ case ts19.SyntaxKind.MinusToken:
22580
23126
  return typeof arg === "number" ? -arg : UNRESOLVED;
22581
- case ts18.SyntaxKind.PlusToken:
23127
+ case ts19.SyntaxKind.PlusToken:
22582
23128
  return typeof arg === "number" ? +arg : UNRESOLVED;
22583
- case ts18.SyntaxKind.ExclamationToken:
23129
+ case ts19.SyntaxKind.ExclamationToken:
22584
23130
  return !arg;
22585
23131
  }
22586
23132
  return UNRESOLVED;
22587
23133
  }
22588
- if (ts18.isObjectLiteralExpression(node)) {
23134
+ if (ts19.isObjectLiteralExpression(node)) {
22589
23135
  const obj = {};
22590
23136
  for (const prop of node.properties) {
22591
- if (!ts18.isPropertyAssignment(prop)) return UNRESOLVED;
23137
+ if (!ts19.isPropertyAssignment(prop)) return UNRESOLVED;
22592
23138
  let key;
22593
- if (ts18.isIdentifier(prop.name) || ts18.isStringLiteralLike(prop.name)) {
23139
+ if (ts19.isIdentifier(prop.name) || ts19.isStringLiteralLike(prop.name)) {
22594
23140
  key = prop.name.text;
22595
- } else if (ts18.isNumericLiteral(prop.name)) {
23141
+ } else if (ts19.isNumericLiteral(prop.name)) {
22596
23142
  key = prop.name.text;
22597
23143
  } else {
22598
23144
  return UNRESOLVED;
@@ -22603,17 +23149,17 @@ function evalNode(node, ctx2) {
22603
23149
  }
22604
23150
  return obj;
22605
23151
  }
22606
- if (ts18.isArrayLiteralExpression(node)) {
23152
+ if (ts19.isArrayLiteralExpression(node)) {
22607
23153
  const arr = [];
22608
23154
  for (const elem of node.elements) {
22609
- if (ts18.isOmittedExpression(elem)) return UNRESOLVED;
23155
+ if (ts19.isOmittedExpression(elem)) return UNRESOLVED;
22610
23156
  const v = evalNode(elem, ctx2);
22611
23157
  if (v === UNRESOLVED) return UNRESOLVED;
22612
23158
  arr.push(v === void 0 ? null : v);
22613
23159
  }
22614
23160
  return arr;
22615
23161
  }
22616
- if (ts18.isElementAccessExpression(node)) {
23162
+ if (ts19.isElementAccessExpression(node)) {
22617
23163
  const base = evalNode(node.expression, ctx2);
22618
23164
  if (base === void 0) return void 0;
22619
23165
  if (base === UNRESOLVED || base === null || typeof base !== "object") return UNRESOLVED;
@@ -22623,16 +23169,16 @@ function evalNode(node, ctx2) {
22623
23169
  const k = String(key);
22624
23170
  return Object.prototype.hasOwnProperty.call(base, k) ? base[k] : void 0;
22625
23171
  }
22626
- if (ts18.isPropertyAccessExpression(node)) {
23172
+ if (ts19.isPropertyAccessExpression(node)) {
22627
23173
  const baseResult = evalNode(node.expression, ctx2);
22628
23174
  if (baseResult === void 0) return void 0;
22629
23175
  return UNRESOLVED;
22630
23176
  }
22631
- if (ts18.isCallExpression(node)) {
22632
- if (node.arguments.length === 0 && ts18.isIdentifier(node.expression) && node.expression.text in ctx2.bindings) {
23177
+ if (ts19.isCallExpression(node)) {
23178
+ if (node.arguments.length === 0 && ts19.isIdentifier(node.expression) && node.expression.text in ctx2.bindings) {
22633
23179
  return ctx2.bindings[node.expression.text];
22634
23180
  }
22635
- if (ts18.isPropertyAccessExpression(node.expression) && node.expression.name.text === "join") {
23181
+ if (ts19.isPropertyAccessExpression(node.expression) && node.expression.name.text === "join") {
22636
23182
  const recv = evalNode(node.expression.expression, ctx2);
22637
23183
  if (Array.isArray(recv)) {
22638
23184
  let sep = ",";
@@ -22647,24 +23193,24 @@ function evalNode(node, ctx2) {
22647
23193
  }
22648
23194
  return UNRESOLVED;
22649
23195
  }
22650
- if (ts18.isConditionalExpression(node)) {
23196
+ if (ts19.isConditionalExpression(node)) {
22651
23197
  const cond = evalNode(node.condition, ctx2);
22652
23198
  if (cond === UNRESOLVED) return UNRESOLVED;
22653
23199
  return cond ? evalNode(node.whenTrue, ctx2) : evalNode(node.whenFalse, ctx2);
22654
23200
  }
22655
- if (ts18.isBinaryExpression(node)) {
23201
+ if (ts19.isBinaryExpression(node)) {
22656
23202
  const op = node.operatorToken.kind;
22657
- if (op === ts18.SyntaxKind.QuestionQuestionToken) {
23203
+ if (op === ts19.SyntaxKind.QuestionQuestionToken) {
22658
23204
  const l2 = evalNode(node.left, ctx2);
22659
23205
  if (l2 !== UNRESOLVED && l2 !== null && l2 !== void 0) return l2;
22660
23206
  return evalNode(node.right, ctx2);
22661
23207
  }
22662
- if (op === ts18.SyntaxKind.BarBarToken) {
23208
+ if (op === ts19.SyntaxKind.BarBarToken) {
22663
23209
  const l2 = evalNode(node.left, ctx2);
22664
23210
  if (l2 !== UNRESOLVED && l2) return l2;
22665
23211
  return evalNode(node.right, ctx2);
22666
23212
  }
22667
- if (op === ts18.SyntaxKind.AmpersandAmpersandToken) {
23213
+ if (op === ts19.SyntaxKind.AmpersandAmpersandToken) {
22668
23214
  const l2 = evalNode(node.left, ctx2);
22669
23215
  if (l2 === UNRESOLVED) return UNRESOLVED;
22670
23216
  if (!l2) return l2;
@@ -22674,28 +23220,28 @@ function evalNode(node, ctx2) {
22674
23220
  const r2 = evalNode(node.right, ctx2);
22675
23221
  if (l === UNRESOLVED || r2 === UNRESOLVED) return UNRESOLVED;
22676
23222
  switch (op) {
22677
- case ts18.SyntaxKind.PlusToken:
23223
+ case ts19.SyntaxKind.PlusToken:
22678
23224
  if (typeof l === "string" || typeof r2 === "string") return `${l}${r2}`;
22679
23225
  if (typeof l === "number" && typeof r2 === "number") return l + r2;
22680
23226
  return UNRESOLVED;
22681
- case ts18.SyntaxKind.MinusToken:
23227
+ case ts19.SyntaxKind.MinusToken:
22682
23228
  return typeof l === "number" && typeof r2 === "number" ? l - r2 : UNRESOLVED;
22683
- case ts18.SyntaxKind.AsteriskToken:
23229
+ case ts19.SyntaxKind.AsteriskToken:
22684
23230
  return typeof l === "number" && typeof r2 === "number" ? l * r2 : UNRESOLVED;
22685
- case ts18.SyntaxKind.SlashToken:
23231
+ case ts19.SyntaxKind.SlashToken:
22686
23232
  return typeof l === "number" && typeof r2 === "number" && r2 !== 0 ? l / r2 : UNRESOLVED;
22687
- case ts18.SyntaxKind.PercentToken:
23233
+ case ts19.SyntaxKind.PercentToken:
22688
23234
  return typeof l === "number" && typeof r2 === "number" && r2 !== 0 ? l % r2 : UNRESOLVED;
22689
- case ts18.SyntaxKind.EqualsEqualsEqualsToken:
22690
- case ts18.SyntaxKind.EqualsEqualsToken:
23235
+ case ts19.SyntaxKind.EqualsEqualsEqualsToken:
23236
+ case ts19.SyntaxKind.EqualsEqualsToken:
22691
23237
  return l === r2;
22692
- case ts18.SyntaxKind.ExclamationEqualsEqualsToken:
22693
- case ts18.SyntaxKind.ExclamationEqualsToken:
23238
+ case ts19.SyntaxKind.ExclamationEqualsEqualsToken:
23239
+ case ts19.SyntaxKind.ExclamationEqualsToken:
22694
23240
  return l !== r2;
22695
23241
  }
22696
23242
  return UNRESOLVED;
22697
23243
  }
22698
- if (ts18.isTemplateExpression(node)) {
23244
+ if (ts19.isTemplateExpression(node)) {
22699
23245
  if (node.templateSpans.length === 0) return node.head.text;
22700
23246
  let acc = node.head.text;
22701
23247
  for (const span of node.templateSpans) {
@@ -22705,7 +23251,7 @@ function evalNode(node, ctx2) {
22705
23251
  }
22706
23252
  return acc;
22707
23253
  }
22708
- if (ts18.isNoSubstitutionTemplateLiteral(node)) return node.text;
23254
+ if (ts19.isNoSubstitutionTemplateLiteral(node)) return node.text;
22709
23255
  return UNRESOLVED;
22710
23256
  }
22711
23257
  var UNRESOLVED, NO_RETURN;
@@ -22718,7 +23264,7 @@ var init_ssr_defaults = __esm({
22718
23264
  });
22719
23265
 
22720
23266
  // ../jsx/src/augment-inherited-props.ts
22721
- import ts19 from "typescript";
23267
+ import ts20 from "typescript";
22722
23268
  function collectContextConsumers(metadata) {
22723
23269
  const constants = metadata.localConstants ?? [];
22724
23270
  const contextDefaults = /* @__PURE__ */ new Map();
@@ -22745,35 +23291,35 @@ function collectContextConsumers(metadata) {
22745
23291
  }
22746
23292
  function parseUseContextArg(source) {
22747
23293
  const expr = parseSingleExpression(source);
22748
- if (!expr || !ts19.isCallExpression(expr)) return null;
22749
- if (!ts19.isIdentifier(expr.expression) || expr.expression.text !== "useContext") return null;
23294
+ if (!expr || !ts20.isCallExpression(expr)) return null;
23295
+ if (!ts20.isIdentifier(expr.expression) || expr.expression.text !== "useContext") return null;
22750
23296
  if (expr.arguments.length !== 1) return null;
22751
23297
  const arg = expr.arguments[0];
22752
- return ts19.isIdentifier(arg) ? arg.text : null;
23298
+ return ts20.isIdentifier(arg) ? arg.text : null;
22753
23299
  }
22754
23300
  function parseCreateContextDefault(source) {
22755
23301
  const expr = parseSingleExpression(source);
22756
- if (!expr || !ts19.isCallExpression(expr)) return null;
23302
+ if (!expr || !ts20.isCallExpression(expr)) return null;
22757
23303
  if (expr.arguments.length === 0) return null;
22758
23304
  const arg = expr.arguments[0];
22759
- if (ts19.isStringLiteral(arg) || ts19.isNoSubstitutionTemplateLiteral(arg)) return arg.text;
22760
- if (ts19.isNumericLiteral(arg)) return Number(arg.text);
22761
- if (arg.kind === ts19.SyntaxKind.TrueKeyword) return true;
22762
- if (arg.kind === ts19.SyntaxKind.FalseKeyword) return false;
23305
+ if (ts20.isStringLiteral(arg) || ts20.isNoSubstitutionTemplateLiteral(arg)) return arg.text;
23306
+ if (ts20.isNumericLiteral(arg)) return Number(arg.text);
23307
+ if (arg.kind === ts20.SyntaxKind.TrueKeyword) return true;
23308
+ if (arg.kind === ts20.SyntaxKind.FalseKeyword) return false;
22763
23309
  return null;
22764
23310
  }
22765
23311
  function isObjectLiteralCreateContextDefault(source) {
22766
23312
  const expr = parseSingleExpression(source);
22767
- if (!expr || !ts19.isCallExpression(expr)) return false;
23313
+ if (!expr || !ts20.isCallExpression(expr)) return false;
22768
23314
  if (expr.arguments.length === 0) return false;
22769
- return ts19.isObjectLiteralExpression(expr.arguments[0]);
23315
+ return ts20.isObjectLiteralExpression(expr.arguments[0]);
22770
23316
  }
22771
23317
  function parseSingleExpression(source) {
22772
- const sf = ts19.createSourceFile("__ctx.ts", `(${source})`, ts19.ScriptTarget.Latest, false);
23318
+ const sf = ts20.createSourceFile("__ctx.ts", `(${source})`, ts20.ScriptTarget.Latest, false);
22773
23319
  const stmt = sf.statements[0];
22774
- if (!stmt || !ts19.isExpressionStatement(stmt)) return null;
23320
+ if (!stmt || !ts20.isExpressionStatement(stmt)) return null;
22775
23321
  let e = stmt.expression;
22776
- while (ts19.isParenthesizedExpression(e)) e = e.expression;
23322
+ while (ts20.isParenthesizedExpression(e)) e = e.expression;
22777
23323
  return e;
22778
23324
  }
22779
23325
  function augmentInheritedPropAccesses(ir) {
@@ -22794,21 +23340,21 @@ function augmentInheritedPropAccesses(ir) {
22794
23340
  const coalesceLiteralTypes = /* @__PURE__ */ new Map();
22795
23341
  const pinCoalesceLiterals = (s) => {
22796
23342
  if (!s || !s.includes(propsObj)) return;
22797
- const sf = ts19.createSourceFile("__aug.ts", `(${s})`, ts19.ScriptTarget.Latest, false);
23343
+ const sf = ts20.createSourceFile("__aug.ts", `(${s})`, ts20.ScriptTarget.Latest, false);
22798
23344
  const visit3 = (n) => {
22799
- if (ts19.isBinaryExpression(n) && (n.operatorToken.kind === ts19.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts19.SyntaxKind.BarBarToken)) {
23345
+ if (ts20.isBinaryExpression(n) && (n.operatorToken.kind === ts20.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts20.SyntaxKind.BarBarToken)) {
22800
23346
  let left = n.left;
22801
- while (ts19.isParenthesizedExpression(left)) left = left.expression;
22802
- if (ts19.isPropertyAccessExpression(left) && ts19.isIdentifier(left.expression) && left.expression.text === propsObj) {
23347
+ while (ts20.isParenthesizedExpression(left)) left = left.expression;
23348
+ if (ts20.isPropertyAccessExpression(left) && ts20.isIdentifier(left.expression) && left.expression.text === propsObj) {
22803
23349
  const name2 = left.name.text;
22804
23350
  let right = n.right;
22805
- while (ts19.isParenthesizedExpression(right)) right = right.expression;
22806
- if (ts19.isPrefixUnaryExpression(right)) right = right.operand;
22807
- const kind2 = ts19.isNumericLiteral(right) ? "number" : right.kind === ts19.SyntaxKind.TrueKeyword || right.kind === ts19.SyntaxKind.FalseKeyword ? "boolean" : ts19.isStringLiteralLike(right) ? "string" : null;
23351
+ while (ts20.isParenthesizedExpression(right)) right = right.expression;
23352
+ if (ts20.isPrefixUnaryExpression(right)) right = right.operand;
23353
+ const kind2 = ts20.isNumericLiteral(right) ? "number" : right.kind === ts20.SyntaxKind.TrueKeyword || right.kind === ts20.SyntaxKind.FalseKeyword ? "boolean" : ts20.isStringLiteralLike(right) ? "string" : null;
22808
23354
  if (kind2 && !coalesceLiteralTypes.has(name2)) coalesceLiteralTypes.set(name2, kind2);
22809
23355
  }
22810
23356
  }
22811
- ts19.forEachChild(n, visit3);
23357
+ ts20.forEachChild(n, visit3);
22812
23358
  };
22813
23359
  visit3(sf);
22814
23360
  };
@@ -22904,39 +23450,39 @@ function augmentInheritedPropAccesses(ir) {
22904
23450
  }
22905
23451
  }
22906
23452
  function parseStaticStringConst(source) {
22907
- const sf = ts19.createSourceFile(
23453
+ const sf = ts20.createSourceFile(
22908
23454
  "__const.ts",
22909
23455
  `const __x = (${source});`,
22910
- ts19.ScriptTarget.Latest,
23456
+ ts20.ScriptTarget.Latest,
22911
23457
  /*setParentNodes*/
22912
23458
  false
22913
23459
  );
22914
23460
  const stmt = sf.statements[0];
22915
- if (!stmt || !ts19.isVariableStatement(stmt)) return null;
23461
+ if (!stmt || !ts20.isVariableStatement(stmt)) return null;
22916
23462
  let init = stmt.declarationList.declarations[0]?.initializer;
22917
- while (init && ts19.isParenthesizedExpression(init)) init = init.expression;
23463
+ while (init && ts20.isParenthesizedExpression(init)) init = init.expression;
22918
23464
  if (!init) return null;
22919
- if (ts19.isStringLiteral(init) || ts19.isNoSubstitutionTemplateLiteral(init)) {
23465
+ if (ts20.isStringLiteral(init) || ts20.isNoSubstitutionTemplateLiteral(init)) {
22920
23466
  return init.text;
22921
23467
  }
22922
23468
  return evalStringArrayJoin(source);
22923
23469
  }
22924
23470
  function evalTemplateOfStringConsts(source, resolved) {
22925
- const sf = ts19.createSourceFile(
23471
+ const sf = ts20.createSourceFile(
22926
23472
  "__const.ts",
22927
23473
  `const __x = (${source});`,
22928
- ts19.ScriptTarget.Latest,
23474
+ ts20.ScriptTarget.Latest,
22929
23475
  /*setParentNodes*/
22930
23476
  false
22931
23477
  );
22932
23478
  const stmt = sf.statements[0];
22933
- if (!stmt || !ts19.isVariableStatement(stmt)) return null;
23479
+ if (!stmt || !ts20.isVariableStatement(stmt)) return null;
22934
23480
  let init = stmt.declarationList.declarations[0]?.initializer;
22935
- while (init && ts19.isParenthesizedExpression(init)) init = init.expression;
22936
- if (!init || !ts19.isTemplateExpression(init)) return null;
23481
+ while (init && ts20.isParenthesizedExpression(init)) init = init.expression;
23482
+ if (!init || !ts20.isTemplateExpression(init)) return null;
22937
23483
  let out = init.head.text;
22938
23484
  for (const span of init.templateSpans) {
22939
- if (!ts19.isIdentifier(span.expression)) return null;
23485
+ if (!ts20.isIdentifier(span.expression)) return null;
22940
23486
  const value2 = resolved.get(span.expression.text);
22941
23487
  if (value2 === void 0) return null;
22942
23488
  out += value2 + span.literal.text;
@@ -22965,28 +23511,28 @@ function collectModuleStringConsts(constants) {
22965
23511
  function lookupStaticRecordLiteral(objectName, key, constants) {
22966
23512
  const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
22967
23513
  if (constInfo?.value === void 0) return null;
22968
- const sf = ts19.createSourceFile(
23514
+ const sf = ts20.createSourceFile(
22969
23515
  "__rec.ts",
22970
23516
  `(${constInfo.value})`,
22971
- ts19.ScriptTarget.Latest,
23517
+ ts20.ScriptTarget.Latest,
22972
23518
  /*setParentNodes*/
22973
23519
  true
22974
23520
  );
22975
23521
  if (sf.statements.length !== 1) return null;
22976
23522
  const stmt = sf.statements[0];
22977
- if (!ts19.isExpressionStatement(stmt)) return null;
23523
+ if (!ts20.isExpressionStatement(stmt)) return null;
22978
23524
  let parsed = stmt.expression;
22979
- while (ts19.isParenthesizedExpression(parsed)) parsed = parsed.expression;
22980
- if (!ts19.isObjectLiteralExpression(parsed)) return null;
23525
+ while (ts20.isParenthesizedExpression(parsed)) parsed = parsed.expression;
23526
+ if (!ts20.isObjectLiteralExpression(parsed)) return null;
22981
23527
  for (const prop of parsed.properties) {
22982
- if (!ts19.isPropertyAssignment(prop)) continue;
23528
+ if (!ts20.isPropertyAssignment(prop)) continue;
22983
23529
  const name2 = prop.name;
22984
- const propKey = ts19.isIdentifier(name2) || ts19.isStringLiteral(name2) || ts19.isNoSubstitutionTemplateLiteral(name2) ? name2.text : null;
23530
+ const propKey = ts20.isIdentifier(name2) || ts20.isStringLiteral(name2) || ts20.isNoSubstitutionTemplateLiteral(name2) ? name2.text : null;
22985
23531
  if (propKey !== key) continue;
22986
23532
  let v = prop.initializer;
22987
- while (ts19.isParenthesizedExpression(v)) v = v.expression;
22988
- if (ts19.isNumericLiteral(v)) return { kind: "number", text: v.text };
22989
- if (ts19.isStringLiteral(v) || ts19.isNoSubstitutionTemplateLiteral(v)) {
23533
+ while (ts20.isParenthesizedExpression(v)) v = v.expression;
23534
+ if (ts20.isNumericLiteral(v)) return { kind: "number", text: v.text };
23535
+ if (ts20.isStringLiteral(v) || ts20.isNoSubstitutionTemplateLiteral(v)) {
22990
23536
  return { kind: "string", text: v.text };
22991
23537
  }
22992
23538
  return null;
@@ -22994,27 +23540,27 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
22994
23540
  return null;
22995
23541
  }
22996
23542
  function evalStringArrayJoin(source) {
22997
- const sf = ts19.createSourceFile(
23543
+ const sf = ts20.createSourceFile(
22998
23544
  "__join.ts",
22999
23545
  `const __x = (${source});`,
23000
- ts19.ScriptTarget.Latest,
23546
+ ts20.ScriptTarget.Latest,
23001
23547
  /*setParentNodes*/
23002
23548
  false
23003
23549
  );
23004
23550
  const stmt = sf.statements[0];
23005
- if (!stmt || !ts19.isVariableStatement(stmt)) return null;
23551
+ if (!stmt || !ts20.isVariableStatement(stmt)) return null;
23006
23552
  let node = stmt.declarationList.declarations[0]?.initializer;
23007
- while (node && ts19.isParenthesizedExpression(node)) node = node.expression;
23008
- if (!node || !ts19.isCallExpression(node)) return null;
23553
+ while (node && ts20.isParenthesizedExpression(node)) node = node.expression;
23554
+ if (!node || !ts20.isCallExpression(node)) return null;
23009
23555
  const callee = node.expression;
23010
- if (!ts19.isPropertyAccessExpression(callee)) return null;
23556
+ if (!ts20.isPropertyAccessExpression(callee)) return null;
23011
23557
  if (callee.name.text !== "join") return null;
23012
23558
  let recv = callee.expression;
23013
- while (ts19.isParenthesizedExpression(recv)) recv = recv.expression;
23014
- if (!ts19.isArrayLiteralExpression(recv)) return null;
23559
+ while (ts20.isParenthesizedExpression(recv)) recv = recv.expression;
23560
+ if (!ts20.isArrayLiteralExpression(recv)) return null;
23015
23561
  const parts = [];
23016
23562
  for (const el of recv.elements) {
23017
- if (ts19.isStringLiteral(el) || ts19.isNoSubstitutionTemplateLiteral(el)) {
23563
+ if (ts20.isStringLiteral(el) || ts20.isNoSubstitutionTemplateLiteral(el)) {
23018
23564
  parts.push(el.text);
23019
23565
  } else {
23020
23566
  return null;
@@ -23023,16 +23569,16 @@ function evalStringArrayJoin(source) {
23023
23569
  let sep = ",";
23024
23570
  if (node.arguments.length >= 1) {
23025
23571
  const arg = node.arguments[0];
23026
- if (ts19.isStringLiteral(arg) || ts19.isNoSubstitutionTemplateLiteral(arg)) sep = arg.text;
23572
+ if (ts20.isStringLiteral(arg) || ts20.isNoSubstitutionTemplateLiteral(arg)) sep = arg.text;
23027
23573
  else return null;
23028
23574
  }
23029
23575
  return parts.join(sep);
23030
23576
  }
23031
23577
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
23032
- if (!ts19.isElementAccessExpression(val)) return null;
23578
+ if (!ts20.isElementAccessExpression(val)) return null;
23033
23579
  const obj = val.expression;
23034
23580
  const arg = val.argumentExpression;
23035
- if (!ts19.isIdentifier(obj) || !ts19.isIdentifier(arg)) return null;
23581
+ if (!ts20.isIdentifier(obj) || !ts20.isIdentifier(arg)) return null;
23036
23582
  let indexPropName;
23037
23583
  let defaultKey;
23038
23584
  const resolved = resolveKey?.(arg.text);
@@ -23046,35 +23592,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
23046
23592
  }
23047
23593
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
23048
23594
  if (constInfo?.value === void 0) return null;
23049
- const sf = ts19.createSourceFile(
23595
+ const sf = ts20.createSourceFile(
23050
23596
  "__rec.ts",
23051
23597
  `(${constInfo.value})`,
23052
- ts19.ScriptTarget.Latest,
23598
+ ts20.ScriptTarget.Latest,
23053
23599
  /* setParentNodes */
23054
23600
  true
23055
23601
  );
23056
23602
  if (sf.statements.length !== 1) return null;
23057
23603
  const stmt = sf.statements[0];
23058
- if (!ts19.isExpressionStatement(stmt)) return null;
23604
+ if (!ts20.isExpressionStatement(stmt)) return null;
23059
23605
  let parsed = stmt.expression;
23060
- while (ts19.isParenthesizedExpression(parsed)) parsed = parsed.expression;
23061
- if (!ts19.isObjectLiteralExpression(parsed)) return null;
23606
+ while (ts20.isParenthesizedExpression(parsed)) parsed = parsed.expression;
23607
+ if (!ts20.isObjectLiteralExpression(parsed)) return null;
23062
23608
  const entries2 = [];
23063
23609
  for (const prop of parsed.properties) {
23064
- if (!ts19.isPropertyAssignment(prop)) return null;
23610
+ if (!ts20.isPropertyAssignment(prop)) return null;
23065
23611
  let key;
23066
- if (ts19.isIdentifier(prop.name)) {
23612
+ if (ts20.isIdentifier(prop.name)) {
23067
23613
  key = prop.name.text;
23068
- } else if (ts19.isStringLiteral(prop.name) || ts19.isNoSubstitutionTemplateLiteral(prop.name)) {
23614
+ } else if (ts20.isStringLiteral(prop.name) || ts20.isNoSubstitutionTemplateLiteral(prop.name)) {
23069
23615
  key = prop.name.text;
23070
23616
  } else {
23071
23617
  return null;
23072
23618
  }
23073
23619
  let v = prop.initializer;
23074
- while (ts19.isParenthesizedExpression(v)) v = v.expression;
23075
- if (ts19.isNumericLiteral(v)) {
23620
+ while (ts20.isParenthesizedExpression(v)) v = v.expression;
23621
+ if (ts20.isNumericLiteral(v)) {
23076
23622
  entries2.push({ key, value: { kind: "number", text: v.text } });
23077
- } else if (ts19.isStringLiteral(v) || ts19.isNoSubstitutionTemplateLiteral(v)) {
23623
+ } else if (ts20.isStringLiteral(v) || ts20.isNoSubstitutionTemplateLiteral(v)) {
23078
23624
  entries2.push({ key, value: { kind: "string", text: v.text } });
23079
23625
  } else {
23080
23626
  return null;
@@ -23878,7 +24424,7 @@ var init_compiler = __esm({
23878
24424
  });
23879
24425
 
23880
24426
  // ../jsx/src/shared-program.ts
23881
- import ts20 from "typescript";
24427
+ import ts21 from "typescript";
23882
24428
  import path5 from "node:path";
23883
24429
  function commonParent(paths) {
23884
24430
  if (paths.length === 0) return process.cwd();
@@ -23896,10 +24442,10 @@ function commonParent(paths) {
23896
24442
  function createProgramForCorpus(files2, options2 = {}) {
23897
24443
  const baseUrl = options2.baseUrl ?? commonParent(files2);
23898
24444
  const compilerOptions = {
23899
- target: ts20.ScriptTarget.Latest,
23900
- module: ts20.ModuleKind.ESNext,
23901
- moduleResolution: ts20.ModuleResolutionKind.Bundler,
23902
- jsx: ts20.JsxEmit.ReactJSX,
24445
+ target: ts21.ScriptTarget.Latest,
24446
+ module: ts21.ModuleKind.ESNext,
24447
+ moduleResolution: ts21.ModuleResolutionKind.Bundler,
24448
+ jsx: ts21.JsxEmit.ReactJSX,
23903
24449
  strict: true,
23904
24450
  skipLibCheck: true,
23905
24451
  noEmit: true,
@@ -23909,7 +24455,7 @@ function createProgramForCorpus(files2, options2 = {}) {
23909
24455
  ...options2.compilerOptions
23910
24456
  };
23911
24457
  const absolute = files2.map((f) => path5.resolve(f));
23912
- return ts20.createProgram(absolute, compilerOptions, void 0, options2.oldProgram);
24458
+ return ts21.createProgram(absolute, compilerOptions, void 0, options2.oldProgram);
23913
24459
  }
23914
24460
  var init_shared_program = __esm({
23915
24461
  "../jsx/src/shared-program.ts"() {
@@ -24520,6 +25066,7 @@ function collectLoopBoundNames(ir) {
24520
25066
  if (node.index) names.add(node.index);
24521
25067
  for (const binding of node.paramBindings ?? []) names.add(binding.name);
24522
25068
  if (node.filterPredicate) names.add(node.filterPredicate.param);
25069
+ for (const name2 of node.preamble?.declaredNames ?? []) names.add(name2);
24523
25070
  for (const child of node.children) visit3(child);
24524
25071
  if (node.childComponent) {
24525
25072
  for (const child of node.childComponent.children) visit3(child);
@@ -24978,7 +25525,7 @@ var init_dangerous_inner_html = __esm({
24978
25525
  });
24979
25526
 
24980
25527
  // ../jsx/src/combine-client-js.ts
24981
- import ts21 from "typescript";
25528
+ import ts22 from "typescript";
24982
25529
  function combineParentChildClientJs(files2) {
24983
25530
  const result2 = /* @__PURE__ */ new Map();
24984
25531
  const lookup = /* @__PURE__ */ new Map();
@@ -25035,17 +25582,17 @@ function combineParentChildClientJs(files2) {
25035
25582
  return result2;
25036
25583
  }
25037
25584
  function parseAndMerge(content2, importsBySource, otherImports, codeSections) {
25038
- const sourceFile = ts21.createSourceFile(
25585
+ const sourceFile = ts22.createSourceFile(
25039
25586
  "combine.js",
25040
25587
  content2,
25041
- ts21.ScriptTarget.Latest,
25588
+ ts22.ScriptTarget.Latest,
25042
25589
  /*setParentNodes*/
25043
25590
  false,
25044
- ts21.ScriptKind.JS
25591
+ ts22.ScriptKind.JS
25045
25592
  );
25046
25593
  const importSpans = [];
25047
25594
  for (const stmt of sourceFile.statements) {
25048
- if (!ts21.isImportDeclaration(stmt)) continue;
25595
+ if (!ts22.isImportDeclaration(stmt)) continue;
25049
25596
  const start2 = stmt.getStart(sourceFile);
25050
25597
  const end2 = stmt.getEnd();
25051
25598
  importSpans.push([start2, end2]);
@@ -25053,8 +25600,8 @@ function parseAndMerge(content2, importsBySource, otherImports, codeSections) {
25053
25600
  if (stmtText.includes("@bf-child:")) continue;
25054
25601
  const clause = stmt.importClause;
25055
25602
  const bindings = clause?.namedBindings;
25056
- const specifier = ts21.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
25057
- if (clause && !clause.name && bindings && ts21.isNamedImports(bindings)) {
25603
+ const specifier = ts22.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
25604
+ if (clause && !clause.name && bindings && ts22.isNamedImports(bindings)) {
25058
25605
  if (!importsBySource.has(specifier)) {
25059
25606
  importsBySource.set(specifier, /* @__PURE__ */ new Set());
25060
25607
  }
@@ -25248,7 +25795,7 @@ var init_loop_destructure = __esm({
25248
25795
  });
25249
25796
 
25250
25797
  // ../jsx/src/debug.ts
25251
- import ts22 from "typescript";
25798
+ import ts23 from "typescript";
25252
25799
  function buildComponentGraph(source, filePath, componentName) {
25253
25800
  const ctx2 = analyzeComponent(source, filePath, componentName);
25254
25801
  if (!ctx2.jsxReturn) {
@@ -26460,18 +27007,18 @@ function truncateExpr(expr, max = 40) {
26460
27007
  function exprReadsPropMember(expr, propsObjectName) {
26461
27008
  let sf;
26462
27009
  try {
26463
- sf = ts22.createSourceFile("__attr.tsx", `(${expr})`, ts22.ScriptTarget.Latest, true, ts22.ScriptKind.TSX);
27010
+ sf = ts23.createSourceFile("__attr.tsx", `(${expr})`, ts23.ScriptTarget.Latest, true, ts23.ScriptKind.TSX);
26464
27011
  } catch {
26465
27012
  return false;
26466
27013
  }
26467
27014
  let found = false;
26468
27015
  const visit3 = (n) => {
26469
27016
  if (found) return;
26470
- if (ts22.isPropertyAccessExpression(n) && ts22.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
27017
+ if (ts23.isPropertyAccessExpression(n) && ts23.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
26471
27018
  found = true;
26472
27019
  return;
26473
27020
  }
26474
- ts22.forEachChild(n, visit3);
27021
+ ts23.forEachChild(n, visit3);
26475
27022
  };
26476
27023
  visit3(sf);
26477
27024
  return found;
@@ -26548,7 +27095,7 @@ var init_debug = __esm({
26548
27095
  });
26549
27096
 
26550
27097
  // ../jsx/src/profiler.ts
26551
- import ts23 from "typescript";
27098
+ import ts24 from "typescript";
26552
27099
  function buildStaticBudget(source, filePath, componentName, options2 = {}) {
26553
27100
  const threshold = options2.fanOutThreshold ?? DEFAULT_FANOUT_THRESHOLD;
26554
27101
  const program = createProgramForFile(source, filePath)?.program;
@@ -26803,14 +27350,14 @@ function joinProfilerEvents(events, index) {
26803
27350
  return { joined, unattributed, diagnostics };
26804
27351
  }
26805
27352
  function findUninstrumentedEffects(source, filePath, instrumentedLines) {
26806
- const sf = ts23.createSourceFile(filePath, source, ts23.ScriptTarget.Latest, true, ts23.ScriptKind.TSX);
27353
+ const sf = ts24.createSourceFile(filePath, source, ts24.ScriptTarget.Latest, true, ts24.ScriptKind.TSX);
26807
27354
  const out = [];
26808
27355
  const visit3 = (node) => {
26809
- if (ts23.isCallExpression(node) && ts23.isIdentifier(node.expression) && node.expression.text === "createEffect") {
27356
+ if (ts24.isCallExpression(node) && ts24.isIdentifier(node.expression) && node.expression.text === "createEffect") {
26810
27357
  const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
26811
27358
  if (!instrumentedLines.has(line)) out.push({ file: filePath, line });
26812
27359
  }
26813
- ts23.forEachChild(node, visit3);
27360
+ ts24.forEachChild(node, visit3);
26814
27361
  };
26815
27362
  visit3(sf);
26816
27363
  out.sort((a, b) => a.line - b.line);
@@ -27096,19 +27643,19 @@ function assessBatchSafety(args2) {
27096
27643
  const signalGetters = new Set(args2.graph.signals.map((s) => s.name));
27097
27644
  let sf;
27098
27645
  try {
27099
- sf = ts23.createSourceFile("__h.ts", `const __h = ${args2.handler}`, ts23.ScriptTarget.Latest, true);
27646
+ sf = ts24.createSourceFile("__h.ts", `const __h = ${args2.handler}`, ts24.ScriptTarget.Latest, true);
27100
27647
  } catch {
27101
27648
  return "unverified";
27102
27649
  }
27103
27650
  const calls = [];
27104
27651
  const visit3 = (node) => {
27105
- if (ts23.isCallExpression(node) && ts23.isIdentifier(node.expression)) {
27652
+ if (ts24.isCallExpression(node) && ts24.isIdentifier(node.expression)) {
27106
27653
  const name2 = node.expression.text;
27107
27654
  if (setters.has(name2)) calls.push({ pos: node.getStart(sf), kind: "write" });
27108
27655
  else if (D.has(name2) && node.arguments.length === 0) calls.push({ pos: node.getStart(sf), kind: "memoRead" });
27109
27656
  else if (!signalGetters.has(name2) && !memoNames.has(name2)) calls.push({ pos: node.getStart(sf), kind: "risky" });
27110
27657
  }
27111
- ts23.forEachChild(node, visit3);
27658
+ ts24.forEachChild(node, visit3);
27112
27659
  };
27113
27660
  visit3(sf);
27114
27661
  calls.sort((a, b) => a.pos - b.pos);
@@ -27812,6 +28359,7 @@ __export(src_exports, {
27812
28359
  dangerousInnerHtmlDiagnostic: () => dangerousInnerHtmlDiagnostic,
27813
28360
  dangerousInnerHtmlMetacharViolation: () => dangerousInnerHtmlMetacharViolation,
27814
28361
  decideClientOnlyElision: () => decideClientOnlyElision,
28362
+ derivesScopeFromSlot: () => derivesScopeFromSlot,
27815
28363
  describeFallback: () => describeFallback,
27816
28364
  diffProfiles: () => diffProfiles,
27817
28365
  diffStaticBudget: () => diffStaticBudget,
@@ -27935,6 +28483,7 @@ var init_src2 = __esm({
27935
28483
  init_template_imports();
27936
28484
  init_parsed_expr_emitter();
27937
28485
  init_loop_bound_names();
28486
+ init_child_scope();
27938
28487
  init_signal_init_eval();
27939
28488
  init_static_literal();
27940
28489
  init_env_signal();
@@ -28028,7 +28577,7 @@ var init_runtime = __esm({
28028
28577
 
28029
28578
  // src/lib/resolve-imports.ts
28030
28579
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
28031
- import ts24 from "typescript";
28580
+ import ts25 from "typescript";
28032
28581
  function shapeFromDecl(decl) {
28033
28582
  const clause = decl.importClause;
28034
28583
  if (!clause) return null;
@@ -28038,7 +28587,7 @@ function shapeFromDecl(decl) {
28038
28587
  }
28039
28588
  const bindings = clause.namedBindings;
28040
28589
  if (bindings) {
28041
- if (ts24.isNamespaceImport(bindings)) {
28590
+ if (ts25.isNamespaceImport(bindings)) {
28042
28591
  shape.namespace = bindings.name.text;
28043
28592
  } else {
28044
28593
  for (const el of bindings.elements) {
@@ -28055,50 +28604,50 @@ function collectExportInfo(source) {
28055
28604
  const otherValueExports = /* @__PURE__ */ new Set();
28056
28605
  const reExportedNames = /* @__PURE__ */ new Set();
28057
28606
  let hasStarReExport = false;
28058
- const sourceFile = ts24.createSourceFile(
28607
+ const sourceFile = ts25.createSourceFile(
28059
28608
  "mod.ts",
28060
28609
  source,
28061
- ts24.ScriptTarget.Latest,
28610
+ ts25.ScriptTarget.Latest,
28062
28611
  /*setParents*/
28063
28612
  false,
28064
- ts24.ScriptKind.TS
28613
+ ts25.ScriptKind.TS
28065
28614
  );
28066
28615
  function hasExport(node) {
28067
- if (!ts24.canHaveModifiers(node)) return false;
28068
- const mods = ts24.getModifiers(node);
28069
- return mods?.some((m) => m.kind === ts24.SyntaxKind.ExportKeyword) ?? false;
28616
+ if (!ts25.canHaveModifiers(node)) return false;
28617
+ const mods = ts25.getModifiers(node);
28618
+ return mods?.some((m) => m.kind === ts25.SyntaxKind.ExportKeyword) ?? false;
28070
28619
  }
28071
28620
  function isAmbient(node) {
28072
- if (!ts24.canHaveModifiers(node)) return false;
28073
- const mods = ts24.getModifiers(node);
28074
- return mods?.some((m) => m.kind === ts24.SyntaxKind.DeclareKeyword) ?? false;
28621
+ if (!ts25.canHaveModifiers(node)) return false;
28622
+ const mods = ts25.getModifiers(node);
28623
+ return mods?.some((m) => m.kind === ts25.SyntaxKind.DeclareKeyword) ?? false;
28075
28624
  }
28076
28625
  function collectFromBindingName(name2) {
28077
- if (ts24.isIdentifier(name2)) {
28626
+ if (ts25.isIdentifier(name2)) {
28078
28627
  localValueExports.add(name2.text);
28079
28628
  return;
28080
28629
  }
28081
28630
  for (const el of name2.elements) {
28082
- if (ts24.isBindingElement(el)) collectFromBindingName(el.name);
28631
+ if (ts25.isBindingElement(el)) collectFromBindingName(el.name);
28083
28632
  }
28084
28633
  }
28085
28634
  for (const stmt of sourceFile.statements) {
28086
- if (ts24.isVariableStatement(stmt) && hasExport(stmt)) {
28635
+ if (ts25.isVariableStatement(stmt) && hasExport(stmt)) {
28087
28636
  for (const d of stmt.declarationList.declarations) {
28088
28637
  collectFromBindingName(d.name);
28089
28638
  }
28090
- } else if (ts24.isFunctionDeclaration(stmt) && hasExport(stmt) && stmt.name) {
28639
+ } else if (ts25.isFunctionDeclaration(stmt) && hasExport(stmt) && stmt.name) {
28091
28640
  localValueExports.add(stmt.name.text);
28092
- } else if (ts24.isClassDeclaration(stmt) && hasExport(stmt) && stmt.name) {
28641
+ } else if (ts25.isClassDeclaration(stmt) && hasExport(stmt) && stmt.name) {
28093
28642
  localValueExports.add(stmt.name.text);
28094
- } else if (ts24.isEnumDeclaration(stmt) && hasExport(stmt) && stmt.name) {
28643
+ } else if (ts25.isEnumDeclaration(stmt) && hasExport(stmt) && stmt.name) {
28095
28644
  otherValueExports.add(stmt.name.text);
28096
- } else if (ts24.isModuleDeclaration(stmt) && hasExport(stmt) && ts24.isIdentifier(stmt.name)) {
28645
+ } else if (ts25.isModuleDeclaration(stmt) && hasExport(stmt) && ts25.isIdentifier(stmt.name)) {
28097
28646
  if (!isAmbient(stmt)) otherValueExports.add(stmt.name.text);
28098
- } else if (ts24.isExportDeclaration(stmt)) {
28647
+ } else if (ts25.isExportDeclaration(stmt)) {
28099
28648
  if (stmt.isTypeOnly) continue;
28100
28649
  if (!stmt.moduleSpecifier) {
28101
- if (stmt.exportClause && ts24.isNamedExports(stmt.exportClause)) {
28650
+ if (stmt.exportClause && ts25.isNamedExports(stmt.exportClause)) {
28102
28651
  for (const el of stmt.exportClause.elements) {
28103
28652
  if (el.isTypeOnly) continue;
28104
28653
  localValueExports.add(el.name.text);
@@ -28106,9 +28655,9 @@ function collectExportInfo(source) {
28106
28655
  }
28107
28656
  } else if (!stmt.exportClause) {
28108
28657
  hasStarReExport = true;
28109
- } else if (ts24.isNamespaceExport(stmt.exportClause)) {
28658
+ } else if (ts25.isNamespaceExport(stmt.exportClause)) {
28110
28659
  reExportedNames.add(stmt.exportClause.name.text);
28111
- } else if (ts24.isNamedExports(stmt.exportClause)) {
28660
+ } else if (ts25.isNamedExports(stmt.exportClause)) {
28112
28661
  for (const el of stmt.exportClause.elements) {
28113
28662
  if (el.isTypeOnly) continue;
28114
28663
  reExportedNames.add(el.name.text);
@@ -28119,16 +28668,16 @@ function collectExportInfo(source) {
28119
28668
  return { localValueExports, otherValueExports, reExportedNames, hasStarReExport };
28120
28669
  }
28121
28670
  function hasUseClientDirective(source) {
28122
- const sourceFile = ts24.createSourceFile(
28671
+ const sourceFile = ts25.createSourceFile(
28123
28672
  "check.tsx",
28124
28673
  source,
28125
- ts24.ScriptTarget.Latest,
28674
+ ts25.ScriptTarget.Latest,
28126
28675
  /*setParents*/
28127
28676
  false,
28128
- ts24.ScriptKind.TSX
28677
+ ts25.ScriptKind.TSX
28129
28678
  );
28130
28679
  for (const stmt of sourceFile.statements) {
28131
- if (!ts24.isExpressionStatement(stmt) || !ts24.isStringLiteral(stmt.expression)) {
28680
+ if (!ts25.isExpressionStatement(stmt) || !ts25.isStringLiteral(stmt.expression)) {
28132
28681
  return false;
28133
28682
  }
28134
28683
  if (stmt.expression.text === "use client") return true;
@@ -28137,53 +28686,53 @@ function hasUseClientDirective(source) {
28137
28686
  }
28138
28687
  function collectTopLevelBindings(source) {
28139
28688
  const names = /* @__PURE__ */ new Set();
28140
- const sourceFile = ts24.createSourceFile(
28689
+ const sourceFile = ts25.createSourceFile(
28141
28690
  "bundle.ts",
28142
28691
  source,
28143
- ts24.ScriptTarget.Latest,
28692
+ ts25.ScriptTarget.Latest,
28144
28693
  /*setParents*/
28145
28694
  false,
28146
- ts24.ScriptKind.TS
28695
+ ts25.ScriptKind.TS
28147
28696
  );
28148
28697
  function collectFromBindingName(name2) {
28149
- if (ts24.isIdentifier(name2)) {
28698
+ if (ts25.isIdentifier(name2)) {
28150
28699
  names.add(name2.text);
28151
28700
  return;
28152
28701
  }
28153
28702
  for (const el of name2.elements) {
28154
- if (ts24.isBindingElement(el)) collectFromBindingName(el.name);
28703
+ if (ts25.isBindingElement(el)) collectFromBindingName(el.name);
28155
28704
  }
28156
28705
  }
28157
28706
  for (const stmt of sourceFile.statements) {
28158
- if (ts24.isVariableStatement(stmt)) {
28707
+ if (ts25.isVariableStatement(stmt)) {
28159
28708
  for (const d of stmt.declarationList.declarations) {
28160
28709
  collectFromBindingName(d.name);
28161
28710
  }
28162
- } else if (ts24.isFunctionDeclaration(stmt) && stmt.name) {
28711
+ } else if (ts25.isFunctionDeclaration(stmt) && stmt.name) {
28163
28712
  names.add(stmt.name.text);
28164
- } else if (ts24.isClassDeclaration(stmt) && stmt.name) {
28713
+ } else if (ts25.isClassDeclaration(stmt) && stmt.name) {
28165
28714
  names.add(stmt.name.text);
28166
28715
  }
28167
28716
  }
28168
28717
  return names;
28169
28718
  }
28170
28719
  function stripImportsAndExports(body2) {
28171
- const sourceFile = ts24.createSourceFile(
28720
+ const sourceFile = ts25.createSourceFile(
28172
28721
  "body.ts",
28173
28722
  body2,
28174
- ts24.ScriptTarget.Latest,
28723
+ ts25.ScriptTarget.Latest,
28175
28724
  /*setParents*/
28176
28725
  false,
28177
- ts24.ScriptKind.TS
28726
+ ts25.ScriptKind.TS
28178
28727
  );
28179
28728
  const spans = [];
28180
28729
  const hoistedImports = [];
28181
28730
  for (const stmt of sourceFile.statements) {
28182
- if (ts24.isImportDeclaration(stmt)) {
28731
+ if (ts25.isImportDeclaration(stmt)) {
28183
28732
  const start2 = stmt.getStart(sourceFile);
28184
28733
  const end2 = stmt.getEnd();
28185
28734
  const specifier = stmt.moduleSpecifier;
28186
- if (ts24.isStringLiteral(specifier)) {
28735
+ if (ts25.isStringLiteral(specifier)) {
28187
28736
  const path25 = specifier.text;
28188
28737
  const isRelative = path25.startsWith("./") || path25.startsWith("../");
28189
28738
  if (!isRelative) {
@@ -28193,24 +28742,24 @@ function stripImportsAndExports(body2) {
28193
28742
  spans.push([start2, end2]);
28194
28743
  continue;
28195
28744
  }
28196
- if (ts24.isExportDeclaration(stmt)) {
28745
+ if (ts25.isExportDeclaration(stmt)) {
28197
28746
  spans.push([stmt.getStart(sourceFile), stmt.getEnd()]);
28198
28747
  continue;
28199
28748
  }
28200
- if (ts24.isExportAssignment(stmt)) {
28201
- const exportKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts24.SyntaxKind.ExportKeyword);
28202
- const defaultKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts24.SyntaxKind.DefaultKeyword);
28203
- const equalsKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts24.SyntaxKind.EqualsToken);
28749
+ if (ts25.isExportAssignment(stmt)) {
28750
+ const exportKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts25.SyntaxKind.ExportKeyword);
28751
+ const defaultKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts25.SyntaxKind.DefaultKeyword);
28752
+ const equalsKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts25.SyntaxKind.EqualsToken);
28204
28753
  const start2 = exportKw?.getStart(sourceFile) ?? stmt.getStart(sourceFile);
28205
28754
  const end2 = (defaultKw ?? equalsKw)?.getEnd() ?? exportKw?.getEnd() ?? stmt.getStart(sourceFile);
28206
28755
  if (end2 > start2) spans.push([start2, end2]);
28207
28756
  continue;
28208
28757
  }
28209
- if (ts24.canHaveModifiers(stmt)) {
28210
- const mods = ts24.getModifiers(stmt);
28758
+ if (ts25.canHaveModifiers(stmt)) {
28759
+ const mods = ts25.getModifiers(stmt);
28211
28760
  if (!mods) continue;
28212
28761
  for (const mod of mods) {
28213
- if (mod.kind === ts24.SyntaxKind.ExportKeyword) {
28762
+ if (mod.kind === ts25.SyntaxKind.ExportKeyword) {
28214
28763
  const start2 = mod.getStart(sourceFile);
28215
28764
  let end2 = mod.getEnd();
28216
28765
  while (end2 < body2.length && /\s/.test(body2[end2])) end2++;
@@ -28350,23 +28899,23 @@ function detectStrippedReferences(bundleSource, stripped) {
28350
28899
  if (stripped.length === 0) return [];
28351
28900
  let sf;
28352
28901
  try {
28353
- sf = ts24.createSourceFile(
28902
+ sf = ts25.createSourceFile(
28354
28903
  "bundle.js",
28355
28904
  bundleSource,
28356
- ts24.ScriptTarget.Latest,
28905
+ ts25.ScriptTarget.Latest,
28357
28906
  /*setParents*/
28358
28907
  true,
28359
- ts24.ScriptKind.JS
28908
+ ts25.ScriptKind.JS
28360
28909
  );
28361
28910
  } catch {
28362
28911
  return [];
28363
28912
  }
28364
28913
  const firstReference = /* @__PURE__ */ new Map();
28365
28914
  function visit3(node) {
28366
- if (ts24.isIdentifier(node) && isValueReferenceIdentifier(node)) {
28915
+ if (ts25.isIdentifier(node) && isValueReferenceIdentifier(node)) {
28367
28916
  if (!firstReference.has(node.text)) firstReference.set(node.text, node);
28368
28917
  }
28369
- ts24.forEachChild(node, visit3);
28918
+ ts25.forEachChild(node, visit3);
28370
28919
  }
28371
28920
  visit3(sf);
28372
28921
  const errors = [];
@@ -28396,18 +28945,18 @@ function detectStrippedReferences(bundleSource, stripped) {
28396
28945
  return errors;
28397
28946
  }
28398
28947
  async function walkAndCollect(content2, searchDirs, modules2, visiting, loggingPath, stripped, stubDeps, nextId) {
28399
- const sourceFile = ts24.createSourceFile(
28948
+ const sourceFile = ts25.createSourceFile(
28400
28949
  "walk.js",
28401
28950
  content2,
28402
- ts24.ScriptTarget.Latest,
28951
+ ts25.ScriptTarget.Latest,
28403
28952
  /*setParents*/
28404
28953
  false,
28405
- ts24.ScriptKind.JS
28954
+ ts25.ScriptKind.JS
28406
28955
  );
28407
28956
  const sites = [];
28408
28957
  for (const stmt of sourceFile.statements) {
28409
- if (!ts24.isImportDeclaration(stmt)) continue;
28410
- if (!ts24.isStringLiteral(stmt.moduleSpecifier)) continue;
28958
+ if (!ts25.isImportDeclaration(stmt)) continue;
28959
+ if (!ts25.isStringLiteral(stmt.moduleSpecifier)) continue;
28411
28960
  const spec = stmt.moduleSpecifier.text;
28412
28961
  if (!spec.startsWith("./") && !spec.startsWith("../")) continue;
28413
28962
  const start2 = stmt.getStart(sourceFile);
@@ -28861,7 +29410,7 @@ var init_assets_ignore = __esm({
28861
29410
  });
28862
29411
 
28863
29412
  // src/lib/runtime-treeshake.ts
28864
- import ts25 from "typescript";
29413
+ import ts26 from "typescript";
28865
29414
  import { basename, dirname as dirname3 } from "node:path";
28866
29415
  import { build as esbuildBuild } from "esbuild";
28867
29416
  function isBarefootClientSpecifier(spec) {
@@ -28878,13 +29427,13 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
28878
29427
  if (!code.includes("@barefootjs/client") && !code.includes("barefoot.js")) return result2;
28879
29428
  let sourceFile;
28880
29429
  try {
28881
- sourceFile = ts25.createSourceFile(
29430
+ sourceFile = ts26.createSourceFile(
28882
29431
  sourceLabel,
28883
29432
  code,
28884
- ts25.ScriptTarget.Latest,
29433
+ ts26.ScriptTarget.Latest,
28885
29434
  /*setParentNodes*/
28886
29435
  false,
28887
- ts25.ScriptKind.JS
29436
+ ts26.ScriptKind.JS
28888
29437
  );
28889
29438
  } catch (err) {
28890
29439
  result2.unsafe = true;
@@ -28892,13 +29441,13 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
28892
29441
  return result2;
28893
29442
  }
28894
29443
  const visit3 = (node) => {
28895
- if (ts25.isImportDeclaration(node)) {
29444
+ if (ts26.isImportDeclaration(node)) {
28896
29445
  const spec = node.moduleSpecifier;
28897
- if (ts25.isStringLiteral(spec) && isEmittedRuntimeSpecifier(spec.text)) {
29446
+ if (ts26.isStringLiteral(spec) && isEmittedRuntimeSpecifier(spec.text)) {
28898
29447
  const clause = node.importClause;
28899
29448
  if (!clause) {
28900
29449
  } else if (clause.isTypeOnly) {
28901
- } else if (clause.namedBindings && ts25.isNamedImports(clause.namedBindings)) {
29450
+ } else if (clause.namedBindings && ts26.isNamedImports(clause.namedBindings)) {
28902
29451
  for (const el of clause.namedBindings.elements) {
28903
29452
  if (el.isTypeOnly) continue;
28904
29453
  const imported = (el.propertyName ?? el.name).text;
@@ -28908,7 +29457,7 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
28908
29457
  result2.unsafe = true;
28909
29458
  result2.reasons.push(`default import of "${spec.text}" in ${sourceLabel}`);
28910
29459
  }
28911
- } else if (clause.namedBindings && ts25.isNamespaceImport(clause.namedBindings)) {
29460
+ } else if (clause.namedBindings && ts26.isNamespaceImport(clause.namedBindings)) {
28912
29461
  result2.unsafe = true;
28913
29462
  result2.reasons.push(`namespace import (* as ${clause.namedBindings.name.text}) of "${spec.text}" in ${sourceLabel}`);
28914
29463
  } else if (clause.name) {
@@ -28916,14 +29465,14 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
28916
29465
  result2.reasons.push(`default import of "${spec.text}" in ${sourceLabel}`);
28917
29466
  }
28918
29467
  }
28919
- } else if (ts25.isCallExpression(node) && node.expression.kind === ts25.SyntaxKind.ImportKeyword) {
29468
+ } else if (ts26.isCallExpression(node) && node.expression.kind === ts26.SyntaxKind.ImportKeyword) {
28920
29469
  const arg = node.arguments[0];
28921
- if (arg && ts25.isStringLiteral(arg) && isEmittedRuntimeSpecifier(arg.text)) {
29470
+ if (arg && ts26.isStringLiteral(arg) && isEmittedRuntimeSpecifier(arg.text)) {
28922
29471
  result2.unsafe = true;
28923
29472
  result2.reasons.push(`dynamic import("${arg.text}") in ${sourceLabel}`);
28924
29473
  }
28925
29474
  }
28926
- ts25.forEachChild(node, visit3);
29475
+ ts26.forEachChild(node, visit3);
28927
29476
  };
28928
29477
  visit3(sourceFile);
28929
29478
  return result2;
@@ -28996,7 +29545,7 @@ var init_runtime_treeshake = __esm({
28996
29545
  });
28997
29546
 
28998
29547
  // src/lib/build.ts
28999
- import ts26 from "typescript";
29548
+ import ts27 from "typescript";
29000
29549
  import { mkdir, readdir, stat, unlink } from "node:fs/promises";
29001
29550
  import { resolve as resolve6, basename as basename2, relative as relative2, dirname as dirname4, isAbsolute as isAbsolute2 } from "node:path";
29002
29551
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -29644,7 +30193,7 @@ async function build(config, options2 = {}) {
29644
30193
  };
29645
30194
  }
29646
30195
  function extractBareImports(code) {
29647
- const { importedFiles } = ts26.preProcessFile(code, true, true);
30196
+ const { importedFiles } = ts27.preProcessFile(code, true, true);
29648
30197
  const specifiers = /* @__PURE__ */ new Set();
29649
30198
  for (const { fileName } of importedFiles) {
29650
30199
  if (!fileName.startsWith(".") && !fileName.startsWith("/") && !fileName.includes("://")) {
@@ -29711,16 +30260,16 @@ function effectiveOutName(tplPath, entryBaseNoExt) {
29711
30260
  }
29712
30261
  function topLevelImportLines(content2) {
29713
30262
  const lines = /* @__PURE__ */ new Set();
29714
- const sourceFile = ts26.createSourceFile(
30263
+ const sourceFile = ts27.createSourceFile(
29715
30264
  "merge.js",
29716
30265
  content2,
29717
- ts26.ScriptTarget.Latest,
30266
+ ts27.ScriptTarget.Latest,
29718
30267
  /*setParentNodes*/
29719
30268
  true,
29720
- ts26.ScriptKind.JS
30269
+ ts27.ScriptKind.JS
29721
30270
  );
29722
30271
  for (const stmt of sourceFile.statements) {
29723
- if (ts26.isImportDeclaration(stmt)) {
30272
+ if (ts27.isImportDeclaration(stmt)) {
29724
30273
  const { line } = sourceFile.getLineAndCharacterOfPosition(stmt.getStart(sourceFile));
29725
30274
  lines.add(line);
29726
30275
  }
@@ -29729,29 +30278,29 @@ function topLevelImportLines(content2) {
29729
30278
  }
29730
30279
  function rewriteBarefootClientSpecifiers(content2, rel) {
29731
30280
  if (!content2.includes("@barefootjs/client")) return content2;
29732
- const sourceFile = ts26.createSourceFile(
30281
+ const sourceFile = ts27.createSourceFile(
29733
30282
  "client.js",
29734
30283
  content2,
29735
- ts26.ScriptTarget.Latest,
30284
+ ts27.ScriptTarget.Latest,
29736
30285
  /*setParentNodes*/
29737
30286
  true,
29738
- ts26.ScriptKind.JS
30287
+ ts27.ScriptKind.JS
29739
30288
  );
29740
30289
  const isBarefootClient = (s) => s === "@barefootjs/client" || s.startsWith("@barefootjs/client/");
29741
30290
  const spans = [];
29742
30291
  const visit3 = (node) => {
29743
- if (ts26.isImportDeclaration(node) || ts26.isExportDeclaration(node)) {
30292
+ if (ts27.isImportDeclaration(node) || ts27.isExportDeclaration(node)) {
29744
30293
  const ms = node.moduleSpecifier;
29745
- if (ms && ts26.isStringLiteral(ms) && isBarefootClient(ms.text)) {
30294
+ if (ms && ts27.isStringLiteral(ms) && isBarefootClient(ms.text)) {
29746
30295
  spans.push([ms.getStart(sourceFile), ms.getEnd()]);
29747
30296
  }
29748
- } else if (ts26.isCallExpression(node) && node.expression.kind === ts26.SyntaxKind.ImportKeyword) {
30297
+ } else if (ts27.isCallExpression(node) && node.expression.kind === ts27.SyntaxKind.ImportKeyword) {
29749
30298
  const arg = node.arguments[0];
29750
- if (arg && ts26.isStringLiteral(arg) && isBarefootClient(arg.text)) {
30299
+ if (arg && ts27.isStringLiteral(arg) && isBarefootClient(arg.text)) {
29751
30300
  spans.push([arg.getStart(sourceFile), arg.getEnd()]);
29752
30301
  }
29753
30302
  }
29754
- ts26.forEachChild(node, visit3);
30303
+ ts27.forEachChild(node, visit3);
29755
30304
  };
29756
30305
  visit3(sourceFile);
29757
30306
  if (spans.length === 0) return content2;
@@ -31952,12 +32501,13 @@ main {
31952
32501
  });
31953
32502
 
31954
32503
  // src/lib/adapters/runtimes.generated.ts
31955
- var bfGoSource, evalGoSource, streamingGoSource, bfdevGoSource;
32504
+ var bfGoSource, evalGoSource, repropsGoSource, streamingGoSource, bfdevGoSource;
31956
32505
  var init_runtimes_generated = __esm({
31957
32506
  "src/lib/adapters/runtimes.generated.ts"() {
31958
32507
  "use strict";
31959
- bfGoSource = '// Package bf provides runtime helper functions for BarefootJS Go templates.\n// These functions mirror JavaScript behavior for consistent SSR output.\npackage bf\n\nimport (\n "bytes"\n "encoding/json"\n "fmt"\n "html/template"\n "math"\n "math/rand"\n "net/url"\n "os"\n "reflect"\n "regexp"\n "sort"\n "strconv"\n "strings"\n "time"\n "unicode"\n "unicode/utf8"\n)\n\n// FuncMap returns a template.FuncMap with all BarefootJS helper functions.\n// Usage:\n//\n// tmpl := template.New("").Funcs(bf.FuncMap())\nfunc FuncMap() template.FuncMap {\n return template.FuncMap{\n // Nullish coalescing (#2248): JS `??` semantics \u2014 fall back only on\n // nil, keeping present-but-falsy values (`""`, `0`, `false`) that\n // Go\'s truthiness-based `or` would replace.\n "bf_nullish": Nullish,\n\n // Arithmetic\n "bf_add": Add,\n "bf_concat_str": ConcatStr,\n "bf_sub": Sub,\n "bf_mul": Mul,\n "bf_div": Div,\n "bf_mod": Mod,\n "bf_neg": Neg,\n "bf_min": Min,\n "bf_max": Max,\n\n // String\n "bf_lower": Lower,\n "bf_upper": Upper,\n "bf_trim": Trim,\n "bf_trim_start": TrimStart,\n "bf_trim_end": TrimEnd,\n "bf_contains": Contains,\n "bf_join": Join,\n "bf_split": Split,\n "bf_starts_with": StartsWith,\n "bf_ends_with": EndsWith,\n "bf_replace": Replace,\n "bf_replace_all": ReplaceAll,\n "bf_repeat": Repeat,\n "bf_pad_start": PadStart,\n "bf_pad_end": PadEnd,\n "bf_string": String,\n "bf_raw_html": RawHTML,\n "bf_ternary": Ternary,\n "bf_truthy": Truthy,\n\n // URL query builder (#1897 PostList href helpers): conditional\n // (include, key, value) triples \u2192 "base?k=v&\u2026", mirroring a\n // URLSearchParams builder with guarded `.set()` calls.\n "bf_query": Query,\n\n // Date method lowering (#2274, spec entry "date"): the lowering\n // target for a zero-arg call on a Date-typed prop.\n "bf_date": Date,\n\n // formatDate(date, pattern, tz) lowering (#2324, spec entry\n // "format_date"): the total, locale-free date-pattern formatter \u2014\n // see FormatDate\'s docstring for the full contract.\n "bf_format_date": FormatDate,\n\n // JSON / numeric primitives \u2014 JS-compat callees registered on\n // the Go adapter\'s `templatePrimitives` map (#1188).\n "bf_json": JSON,\n "bf_number": Number,\n "bf_floor": Floor,\n "bf_ceil": Ceil,\n "bf_round": Round,\n "bf_abs": Abs,\n "bf_to_fixed": ToFixed,\n\n // Array/Slice\n "bf_len": Len,\n "bf_length": Length,\n "bf_is_element": IsValidElement,\n "bf_style_object": StyleObjectToCSS,\n "bf_at": At,\n "bf_includes": Includes,\n "bf_index_of": IndexOf,\n "bf_last_index_of": LastIndexOf,\n "bf_concat": Concat,\n "bf_slice": Slice,\n "bf_reverse": Reverse,\n "bf_flat": Flat,\n "bf_flat_dynamic": FlatDynamicDepth,\n "bf_flat_map": FlatMap,\n "bf_flat_map_tuple": FlatMapTuple,\n "bf_first": First,\n "bf_last": Last,\n "bf_arr": Arr,\n "bf_filter_truthy": FilterTruthy,\n\n // Higher-order Array Methods\n "bf_every": Every,\n "bf_some": Some,\n "bf_filter": Filter,\n "bf_find": Find,\n "bf_find_index": FindIndex,\n "bf_find_last": FindLast,\n "bf_find_last_index": FindLastIndex,\n "bf_sort": Sort,\n "bf_reduce": Reduce,\n\n // Evaluator-driven higher-order folds (#2018): the comparator / reducer\n // body travels as a serialized ParsedExpr (JSON) evaluated per element,\n // generalizing bf_sort / bf_reduce beyond their fixed catalogues. The\n // adapter falls back to bf_sort for a comparator the evaluator can\'t\n // model (e.g. localeCompare). `bf_env` builds the captured-free-var\n // environment passed as the trailing base_env argument.\n "bf_sort_eval": SortEval,\n "bf_reduce_eval": FoldEval,\n "bf_env": Env,\n\n // Evaluator-driven higher-order predicates (#2018, P2): the predicate\n // body travels as a serialized ParsedExpr (JSON) evaluated per element,\n // generalizing bf_filter / bf_find / bf_find_index / bf_every / bf_some\n // beyond their field-equality / truthiness catalogues. `bf_find_eval` /\n // `bf_find_index_eval` take a `forward` bool (false \u2192 findLast variants).\n "bf_filter_eval": FilterEval,\n "bf_every_eval": EveryEval,\n "bf_some_eval": SomeEval,\n "bf_find_eval": FindEval,\n "bf_find_index_eval": FindIndexEval,\n // `.flatMap(proj)`: project each element through the serialized\n // projection body, then flatten one level.\n "bf_flat_map_eval": FlatMapEval,\n // Value-producing `.map(cb)` (#2073): project each element, one\n // result per element (no flatten).\n "bf_map_eval": MapEval,\n\n // Comment marker (for hydration)\n "bfComment": Comment,\n "bfTextStart": TextStart,\n "bfTextEnd": TextEnd,\n\n // Script collection\n "bfScripts": BfScripts,\n\n // Scope attribute value (#1249: bare scope id, no `~` prefix)\n "bfScopeAttr": ScopeAttr,\n\n // Slot-identity markers (#1249): bf-h, bf-m, bf-r\n "bfHydrationAttrs": HydrationAttrs,\n\n // Child component marker (kept for backward compatibility)\n "bfIsChild": IsChild,\n\n // Props attribute for hydration\n "bfPropsAttr": BfPropsAttr,\n\n // Portal HTML rendering (parses and executes template string)\n "bfPortalHTML": PortalHTML,\n\n // JSX children passed to an imported child component (#1896):\n // the parent renders the children fragment via a companion\n // define (executed through bf_tmpl from TemplateFuncMap) and\n // injects the result into the child\'s Children field.\n "bf_with_children": WithChildren,\n\n // Scope comment for fragment roots\n "bfScopeComment": ScopeComment,\n "bfScopeCommentEnd": ScopeCommentEnd,\n\n // JSX intrinsic-element spread lowering (#1407)\n "bf_spread_attrs": SpreadAttrs,\n\n // Destructure object-rest spread-onto-element residual (#2087):\n // "every field except these" for a struct/map, keyed by json tag.\n "bf_omit": Omit,\n\n // Case-tolerant single-field read off a map or struct (#2087): a\n // `useContext` local whose `createContext` default is object-shaped\n // (e.g. `createContext<{ config: X }>({ config: {} })`) is typed\n // `map[string]interface{}`, and its keys are the SOURCE (JS-cased)\n // property names a `<Ctx.Provider value={{ \u2026 }}>` bakes\n // (`providerObjectValueToGoMap`, go-template-adapter.ts) \u2014 plain\n // `text/template` dot access does an exact-string `MapIndex`, so\n // `ctx.config.label` lowers to nested `bf_get` calls instead of\n // `.Ctx.Config.Label`. Reuses the same `getFieldValue` the\n // project/sort helpers already use for a dynamic field-name lookup;\n // safe on a nil map/interface (returns nil) so a missing Provider or\n // an absent key falls through to `??`\'s fallback.\n "bf_get": getFieldValue,\n }\n}\n\n// Query builds a URL from a base path plus a query string assembled from\n// (include, key, value) triples, in order. A pair is considered only when its\n// `include` flag is true \u2014 mirroring a JS URLSearchParams builder whose\n// `.set(key, value)` calls are each guarded by an `if`. The compiler lowers a\n// conditional `cond ? v : undefined` to the `include` bool and a plain `key: v`\n// to a `true` include; the emptiness check is applied HERE (an included but\n// empty value is dropped), matching the client `queryHref` and the Perl `query`\n// helper. Keys and values use formEscape (application/x-www-form-urlencoded),\n// so the rendered query is byte-for-byte identical to the browser\'s\n// URLSearchParams. An empty query yields the bare base.\n//\n// A value may be a string slice ([]string or []any), which APPENDS one pair per\n// non-empty member (URLSearchParams.append) \u2014 `{tag: [a, b]}` \u2192 `tag=a&tag=b`.\n// A scalar value follows URLSearchParams.set() semantics: repeating a key\n// overwrites the value at the key\'s first position rather than duplicating it\n// (object literals have unique keys, so this is defensive). Trailing args that\n// don\'t complete a triple are ignored.\n//\n// formEscape differs from url.QueryEscape only on `~` (kept by QueryEscape,\n// `%7E` here) and `*` (`%2A` by QueryEscape, kept here).\nfunc Query(base string, triples ...any) string {\n type kv struct{ key, val string }\n pairs := make([]kv, 0, len(triples)/3)\n pos := make(map[string]int)\n for i := 0; i+2 < len(triples); i += 3 {\n include, _ := triples[i].(bool)\n if !include {\n continue\n }\n k := String(triples[i+1])\n if members, ok := asStringSlice(triples[i+2]); ok {\n // Array value \u2192 append each non-empty member; appended pairs never\n // overwrite, so they don\'t participate in the set()-position map.\n for _, m := range members {\n if m == "" {\n continue\n }\n pairs = append(pairs, kv{k, m})\n }\n continue\n }\n v := String(triples[i+2])\n if v == "" {\n continue // omit an included-but-empty value (client / Perl parity)\n }\n if at, ok := pos[k]; ok {\n pairs[at].val = v // set(): overwrite the first occurrence\'s value\n } else {\n pos[k] = len(pairs)\n pairs = append(pairs, kv{k, v})\n }\n }\n var b strings.Builder\n for _, p := range pairs {\n if b.Len() == 0 {\n b.WriteByte(\'?\')\n } else {\n b.WriteByte(\'&\')\n }\n b.WriteString(formEscape(p.key))\n b.WriteByte(\'=\')\n b.WriteString(formEscape(p.val))\n }\n return base + b.String()\n}\n\n// Date implements the `date` helper (spec/template-helpers.md, #2274) \u2014 the\n// lowering target for a zero-arg call on a Date-typed prop\n// (`createdAt.toISOString()`). recv accepts the runtime\'s own `time.Time` /\n// `*time.Time` (however the host framework populated the prop) OR an\n// ISO-8601 string (the wire form a JSON-sourced prop arrives as); either is\n// normalized to UTC before dispatching op, matching the client `Date`\'s own\n// instant semantics regardless of which shape reaches this helper. A nil /\n// unparsable receiver yields this runtime\'s zero value for the requested op\n// (0 for every numeric accessor, "" for toISOString) rather than panicking\n// mid-render \u2014 the same tolerance `String`/`Number` already extend to a nil\n// prop. `getUTCMonth` subtracts 1: Go\'s `time.Month` is 1-based, JS\'s is not\n// (spec entry "date" is explicit that JS wins here).\nfunc Date(recv any, op string) any {\n t, ok := toTime(recv)\n if !ok {\n if op == "toISOString" {\n return ""\n }\n return 0\n }\n t = t.UTC()\n switch op {\n case "getUTCFullYear":\n return t.Year()\n case "getUTCMonth":\n return int(t.Month()) - 1\n case "getUTCDate":\n return t.Day()\n case "getUTCHours":\n return t.Hour()\n case "getUTCMinutes":\n return t.Minute()\n case "getUTCSeconds":\n return t.Second()\n case "getTime":\n return t.UnixMilli()\n case "toISOString":\n return t.Format("2006-01-02T15:04:05.000Z")\n default:\n return 0\n }\n}\n\n// tzOffsetRE matches a fixed UTC offset `\xB1HH:MM` within ECMA-402\'s valid\n// range \u2014 hours 00\u201323, minutes 00\u201359 (`\'+09:00\'`, `\'-05:30\'`) \u2014 one of the\n// three `tz` shapes FormatDate accepts (mirrors OFFSET_RE in\n// packages/client/src/format-date.ts). An out-of-range shape (`\'+25:00\'`)\n// falls through to the tzdata lookup, fails it, and errors \u2014 matching the\n// JS reference\'s RangeError (#2344).\nvar tzOffsetRE = regexp.MustCompile(`^([+-])([01][0-9]|2[0-3]):([0-5][0-9])$`)\n\n// formatDateTokenRE is the longest-match pattern-token alternation (mirrors\n// TOKEN_RE in packages/client/src/format-date.ts). Order matters: MMMM\n// before MMM before MM before M (and dddd before ddd, DD before D) so the\n// longer token wins at a position where a shorter one could also match \u2014\n// Go\'s regexp, like JS\'s, resolves alternation leftmost-first (not POSIX\n// leftmost-longest), so listing the longer alternative first is what makes\n// e.g. "MMMM" consume all four characters instead of "MM" + "MM".\nvar formatDateTokenRE = regexp.MustCompile(`YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D`)\n\n// nameTable section offsets (#2334, mirrors MONTHS_WIDE / MONTHS_ABBR /\n// WEEKDAYS_WIDE / WEEKDAYS_ABBR in packages/client/src/format-date.ts).\nconst (\n monthsWide = 0\n monthsAbbr = 12\n weekdaysWide = 24\n weekdaysAbbr = 31\n)\n\n// formatDateName reads a name-token table entry: index out of range, or a\n// non-string element, both render "" \u2014 the same total, zero-value\n// discipline as an unparseable date (mirrors the JS reference\'s\n// `names[index] ?? ""` fallback, where every table element the vectors ever\n// carry is a string).\nfunc formatDateName(names []any, index int) string {\n if index < 0 || index >= len(names) {\n return ""\n }\n s, ok := names[index].(string)\n if !ok {\n return ""\n }\n return s\n}\n\n// FormatDate implements the `format_date` helper (#2324, #2334, spec entry\n// "format_date") \u2014 the lowering target for\n// `formatDate(date, pattern, tz, names)`\n// (packages/client/src/format-date.ts, the JS-normative reference this must\n// match byte-for-byte). Total and deterministic: no locale, no host\n// timezone, no "now".\n//\n// recv: same receiver contract as the `date` helper above \u2014 the runtime\'s\n// own `time.Time` / `*time.Time`, or an ISO-8601 string \u2014 normalized via\n// `toTime`. A nil / unparseable receiver returns "" (never panics).\n//\n// tz (#2344): "UTC", a range-valid fixed offset `\xB1HH:MM` (shifts by\n// sign*(HH*60+MM) minutes), or a canonical IANA zone name ("Asia/Tokyo")\n// resolved through tzdata via time.LoadLocation \u2014 the zone\'s UTC offset AT\n// THE INSTANT being formatted (DST-aware, historical-transition-aware,\n// seconds precision: pre-standard LMT offsets like Tokyo\'s +09:18:59\n// count). ANY other value \u2014 an unknown zone, a malformed or out-of-range\n// offset ("+9:00", "+25:00"), the empty string or "Local" (LoadLocation\'s\n// implicit-environment aliases) \u2014 returns an ERROR, aborting template\n// execution loudly: the JS reference throws a RangeError there, and a\n// silently substituted timezone is the one failure mode this helper must\n// not have (the pre-#2344 normalize-to-UTC total function is gone). The\n// shifted instant\'s UTC calendar fields (not the original instant\'s) are\n// what pattern tokens read \u2014 the shifted UTC clock face IS the local clock\n// face in that zone, same reasoning as the JS reference.\n//\n// names (#2334): a flat name table in fixed layout \u2014 `[0..11]` wide month\n// names, `[12..23]` abbreviated month names, `[24..30]` wide weekday names\n// (Sunday-first), `[31..37]` abbreviated weekday names. The caller owns the\n// values; this helper only indexes the table.\n//\n// pattern: longest-match token substitution\n// (`YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D`); every other character \u2014 including\n// multi-byte ones like \u5E74/\u6708/\u65E5 \u2014 passes through literally. `YYYY` is\n// `abs(year)` zero-padded to 4 digits, `-`-prefixed for a negative year;\n// `MM`/`DD` zero-pad to 2; `M`/`D` are bare; `MMMM`/`MMM` and `dddd`/`ddd`\n// read the `names` table (weekday computed on the offset-shifted instant,\n// Sunday-first, matching `time.Time.Weekday()`\'s own Sunday=0 encoding).\nfunc FormatDate(recv any, pattern string, tz string, names []any) (string, error) {\n t, ok := toTime(recv)\n if !ok {\n // Receiver contract precedes tz validation (spec receiver-first\n // discipline, mirrored by every port).\n return "", nil\n }\n offsetSeconds := 0\n if tz != "UTC" {\n if m := tzOffsetRE.FindStringSubmatch(tz); m != nil {\n hh, _ := strconv.Atoi(m[2])\n mm, _ := strconv.Atoi(m[3])\n offsetSeconds = (hh*60 + mm) * 60\n if m[1] == "-" {\n offsetSeconds = -offsetSeconds\n }\n } else if tz == "" || tz == "Local" {\n // LoadLocation("") is UTC and LoadLocation("Local") is the host\n // zone \u2014 both implicit-environment reads the contract refuses.\n return "", fmt.Errorf("format_date: unresolvable timeZone %q", tz)\n } else {\n loc, err := time.LoadLocation(tz)\n if err != nil {\n return "", fmt.Errorf("format_date: unresolvable timeZone %q", tz)\n }\n _, offsetSeconds = t.UTC().In(loc).Zone()\n }\n }\n shifted := t.UTC().Add(time.Duration(offsetSeconds) * time.Second)\n year := shifted.Year()\n month := int(shifted.Month())\n day := shifted.Day()\n weekday := int(shifted.Weekday()) // time.Sunday == 0, matching the table\'s Sunday-first layout\n absYear := year\n if absYear < 0 {\n absYear = -absYear\n }\n yyyy := fmt.Sprintf("%04d", absYear)\n if year < 0 {\n yyyy = "-" + yyyy\n }\n out := formatDateTokenRE.ReplaceAllStringFunc(pattern, func(token string) string {\n switch token {\n case "YYYY":\n return yyyy\n case "MMMM":\n return formatDateName(names, monthsWide+month-1)\n case "MMM":\n return formatDateName(names, monthsAbbr+month-1)\n case "MM":\n return fmt.Sprintf("%02d", month)\n case "M":\n return strconv.Itoa(month)\n case "DD":\n return fmt.Sprintf("%02d", day)\n case "D":\n return strconv.Itoa(day)\n case "dddd":\n return formatDateName(names, weekdaysWide+weekday)\n case "ddd":\n return formatDateName(names, weekdaysAbbr+weekday)\n default:\n return token\n }\n })\n return out, nil\n}\n\n// toTime normalizes a `Date` helper receiver to a `time.Time`: the runtime\'s\n// own `time.Time` / `*time.Time`, or an ISO-8601 string parsed with\n// `time.RFC3339Nano` (accepts both the `Z`-suffixed and numeric-offset\n// forms, and any sub-second precision \u2014 including the millisecond precision\n// every value this runtime itself ever produces via `toISOString` above).\n// Anything else (nil, an unparsable string, an unrelated type) reports !ok\n// so `Date` can apply its documented zero-value fallback instead of\n// panicking.\nfunc toTime(recv any) (time.Time, bool) {\n switch v := recv.(type) {\n case time.Time:\n return v, true\n case *time.Time:\n if v == nil {\n return time.Time{}, false\n }\n return *v, true\n case string:\n t, err := time.Parse(time.RFC3339Nano, v)\n if err != nil {\n return time.Time{}, false\n }\n return t, true\n default:\n return time.Time{}, false\n }\n}\n\n// asStringSlice reports whether v is a query *array* value and, if so, returns\n// its members stringified. A compiled template passes a `[]string` field; the\n// golden conformance vectors decode JSON arrays to `[]any`. Anything else is a\n// scalar (false), handled by the set() path.\nfunc asStringSlice(v any) ([]string, bool) {\n switch s := v.(type) {\n case []string:\n return s, true\n case []any:\n out := make([]string, len(s))\n for i, m := range s {\n out[i] = String(m)\n }\n return out, true\n default:\n return nil, false\n }\n}\n\nconst hexUpper = "0123456789ABCDEF"\n\n// formEscape percent-encodes s with the application/x-www-form-urlencoded byte\n// set, matching the browser\'s URLSearchParams serialization (and the Perl\n// `query` helper) so SSR query strings render byte-for-byte identically across\n// adapters. The unreserved set kept verbatim is A-Z a-z 0-9 and `* - . _`; a\n// space becomes `+`; every other byte is `%XX` with uppercase hex. Encoding is\n// byte-wise, so multi-byte UTF-8 is percent-encoded per byte (`\xE9` \u2192 `%C3%A9`).\n//\n// This differs from url.QueryEscape only for `~` (kept by QueryEscape, encoded\n// to `%7E` here) and `*` (encoded to `%2A` by QueryEscape, kept here).\nfunc formEscape(s string) string {\n var b strings.Builder\n for i := 0; i < len(s); i++ {\n c := s[i]\n switch {\n case c >= \'A\' && c <= \'Z\', c >= \'a\' && c <= \'z\', c >= \'0\' && c <= \'9\',\n c == \'*\', c == \'-\', c == \'.\', c == \'_\':\n b.WriteByte(c)\n case c == \' \':\n b.WriteByte(\'+\')\n default:\n b.WriteByte(\'%\')\n b.WriteByte(hexUpper[c>>4])\n b.WriteByte(hexUpper[c&0x0F])\n }\n }\n return b.String()\n}\n\n// ScopeAttr returns the bare bf-s scope id (#1249).\nfunc ScopeAttr(props interface{}) string {\n return getStringField(props, "ScopeID")\n}\n\n// HydrationAttrs emits `bf-h="<host>" bf-m="<slot>" bf-r=""` conditionally.\n// See spec/compiler.md "Slot identity".\nfunc HydrationAttrs(props interface{}) template.HTMLAttr {\n parts := []string{}\n if host := getStringField(props, "BfParent"); host != "" {\n parts = append(parts, fmt.Sprintf(`bf-h="%s"`, template.HTMLEscapeString(host)))\n }\n if mount := getStringField(props, "BfMount"); mount != "" {\n parts = append(parts, fmt.Sprintf(`bf-m="%s"`, template.HTMLEscapeString(mount)))\n }\n if !getBoolField(props, "BfIsChild") {\n parts = append(parts, `bf-r=""`)\n }\n if len(parts) == 0 {\n return ""\n }\n return template.HTMLAttr(strings.Join(parts, " "))\n}\n\n// IsChild is a deprecated no-op stub. Child status is signalled by bf-h\n// presence (#1249); use HydrationAttrs instead.\nfunc IsChild(props interface{}) template.HTMLAttr {\n return ""\n}\n\n// svgCamelCaseAttrs mirrors SVG_CAMEL_CASE_ATTRS from\n// packages/client/src/runtime/spread-attrs.ts. SVG XML attribute\n// names are case-sensitive; the default camelCase \u2192 kebab-case\n// rewrite must NOT apply to these or the SVG stops rendering\n// (#1407). Coordinates with the compile-time SVG_CAMEL_TO_KEBAB\n// table in packages/jsx/src/ir-to-client-js/utils.ts: presentation\n// attrs (clipPath, strokeWidth, \u2026) live there and must NOT appear\n// here, or the same JSX prop would lower to clip-path via the\n// explicit-attr path and stay clipPath via the spread path.\nvar svgCamelCaseAttrs = map[string]struct{}{\n "allowReorder": {}, "attributeName": {}, "attributeType": {}, "autoReverse": {},\n "baseFrequency": {}, "baseProfile": {}, "calcMode": {}, "clipPathUnits": {},\n "contentScriptType": {}, "contentStyleType": {}, "diffuseConstant": {}, "edgeMode": {},\n "externalResourcesRequired": {}, "filterRes": {}, "filterUnits": {}, "glyphRef": {},\n "gradientTransform": {}, "gradientUnits": {}, "kernelMatrix": {}, "kernelUnitLength": {},\n "keyPoints": {}, "keySplines": {}, "keyTimes": {}, "lengthAdjust": {}, "limitingConeAngle": {},\n "markerHeight": {}, "markerUnits": {}, "markerWidth": {}, "maskContentUnits": {},\n "maskUnits": {}, "numOctaves": {}, "pathLength": {}, "patternContentUnits": {},\n "patternTransform": {}, "patternUnits": {}, "pointsAtX": {}, "pointsAtY": {}, "pointsAtZ": {},\n "preserveAlpha": {}, "preserveAspectRatio": {}, "primitiveUnits": {}, "refX": {}, "refY": {},\n "repeatCount": {}, "repeatDur": {}, "requiredExtensions": {}, "requiredFeatures": {},\n "specularConstant": {}, "specularExponent": {}, "spreadMethod": {}, "startOffset": {},\n "stdDeviation": {}, "stitchTiles": {}, "surfaceScale": {}, "systemLanguage": {},\n "tableValues": {}, "targetX": {}, "targetY": {}, "textLength": {}, "viewBox": {}, "viewTarget": {},\n "xChannelSelector": {}, "yChannelSelector": {}, "zoomAndPan": {},\n}\n\n// toAttrName mirrors the JSX\u2192HTML attribute-name rewrite from\n// packages/client/src/runtime/spread-attrs.ts. className \u2192 class,\n// htmlFor \u2192 for, SVG camelCase attrs preserved, other camelCase\n// keys lowered to kebab-case.\nfunc toAttrName(key string) string {\n if key == "className" {\n return "class"\n }\n if key == "htmlFor" {\n return "for"\n }\n if _, ok := svgCamelCaseAttrs[key]; ok {\n return key\n }\n // camelCase \u2192 kebab-case: mirror the JS reference exactly\n // (`key.replace(/([A-Z])/g, \'-$1\').toLowerCase()`). The JS shape\n // produces a leading `-` for an initial uppercase letter\n // (`XData` \u2192 `-x-data`); both this Go path and the matching JS\n // runtime are wrong-by-construction for that case (the resulting\n // HTML attribute name is invalid), but keeping them byte-equal\n // avoids silent SSR/CSR divergence (#1411 review).\n var b strings.Builder\n for _, r := range key {\n if r >= \'A\' && r <= \'Z\' {\n b.WriteByte(\'-\')\n b.WriteRune(r + 32)\n } else {\n b.WriteRune(r)\n }\n }\n return b.String()\n}\n\n// hasUnsafeStyleValue mirrors Hono\'s own CSS-injection guard\n// (`hono/jsx/utils.ts`\'s `hasUnsafeStyleValue` \u2014 the ORACLE this adapter\'s\n// dynamic `style={{...}}` values must match, #2261): a hand-rolled\n// structural scan for characters that could break out of a CSS\n// declaration, NOT real CSSOM property validation. Ported byte-for-byte \u2014\n// every character this scan tests is ASCII, so scanning by byte (Go\n// string indexing) agrees with Hono\'s UTF-16-code-unit scan for every\n// input; a multibyte UTF-8 sequence has no byte in the ASCII range, so it\n// can never spuriously match one of these single-byte comparisons. Skips\n// the reference implementation\'s regex fast-path (a pure optimization \u2014\n// the scan below already returns `false` promptly for a clean value).\nfunc hasUnsafeStyleValue(value string) bool {\n quote := byte(0)\n blockStack := make([]byte, 0, 4)\n for i := 0; i < len(value); i++ {\n c := value[i]\n switch {\n case c == \'\\\\\':\n if i == len(value)-1 {\n return true\n }\n i++\n case quote != 0:\n if c == \'\\n\' || c == \'\\f\' || c == \'\\r\' {\n return true\n }\n if c == quote {\n quote = 0\n }\n case c == \'/\' && i+1 < len(value) && value[i+1] == \'*\':\n end := strings.Index(value[i+2:], "*/")\n if end == -1 {\n return true\n }\n i = i + 2 + end + 1\n case c == \'"\' || c == \'\\\'\':\n quote = c\n case c == \'(\':\n blockStack = append(blockStack, \')\')\n case c == \'[\':\n blockStack = append(blockStack, \']\')\n case c == \'{\' || c == \'}\':\n return true\n case c == \')\' || c == \']\':\n if len(blockStack) == 0 || blockStack[len(blockStack)-1] != c {\n return true\n }\n blockStack = blockStack[:len(blockStack)-1]\n case c == \';\' && len(blockStack) == 0:\n return true\n }\n }\n return quote != 0 || len(blockStack) != 0\n}\n\n// StyleObjectToCSS builds the CSS string for a `style={{...}}` JSX\n// object-literal attribute (#2261) \u2014 `pairs` alternates CSS key (always a\n// compile-time-known literal), then value (`any`, possibly a runtime\n// expression\'s result). A value that fails `hasUnsafeStyleValue` (after\n// JS-`String()`-style stringification) is DROPPED \u2014 the whole `key:value`\n// pair is omitted \u2014 matching Hono\'s oracle behavior exactly, rather than\n// html/template\'s own contextual CSS auto-escaper (which instead emits its\n// `ZgotmplZ` unsafe-content sentinel for the same input). The final joined\n// string is STILL HTML-escaped (mirroring Hono\'s own `escapeToBuffer` call\n// on its accumulated style string) \u2014 a "safe" value can still carry a\n// literal `"`/`\'`/`&` (e.g. a BALANCED-quote CSS string value like\n// `"hello"` passes the structural scan; the quote chars survive into the\n// value) that would otherwise break out of the double-quoted `style="..."`\n// attribute. Returns `template.CSS` (over the escaped result) so\n// html/template treats it as trusted CSS content instead of ALSO applying\n// its own contextual CSS auto-escaper (which would re-derive the exact\n// `ZgotmplZ` divergence this function exists to avoid).\nfunc StyleObjectToCSS(pairs ...any) template.CSS {\n parts := make([]string, 0, len(pairs)/2)\n for i := 0; i+1 < len(pairs); i += 2 {\n key := fmt.Sprint(pairs[i])\n value := String(pairs[i+1])\n if hasUnsafeStyleValue(value) {\n continue\n }\n parts = append(parts, template.HTMLEscapeString(key)+":"+template.HTMLEscapeString(value))\n }\n return template.CSS(strings.Join(parts, ";"))\n}\n\n// StyleToCss mirrors styleToCss from\n// packages/client/src/runtime/style.ts. Accepts a string passthrough,\n// or a map (JSON-deserialized object) whose camelCase keys are\n// lowered to kebab-case and joined with `;`. Returns ("", false) for\n// nullish/empty input so callers can omit the attribute entirely.\nfunc StyleToCss(v any) (string, bool) {\n if v == nil {\n return "", false\n }\n rv := reflect.ValueOf(v)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return "", false\n }\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Map {\n // Non-object: stringify and return as-is, matching the JS\n // `typeof value !== \'object\'` branch.\n s := fmt.Sprint(v)\n if s == "" {\n return "", false\n }\n return s, true\n }\n keys := rv.MapKeys()\n sorted := make([]string, 0, len(keys))\n for _, k := range keys {\n if k.Kind() == reflect.String {\n sorted = append(sorted, k.String())\n }\n }\n sort.Strings(sorted)\n parts := make([]string, 0, len(sorted))\n for _, k := range sorted {\n val := rv.MapIndex(reflect.ValueOf(k))\n // Skip nil entries (matches the JS `if (v == null) continue`).\n if !val.IsValid() {\n continue\n }\n if val.Kind() == reflect.Interface || val.Kind() == reflect.Pointer {\n if val.IsNil() {\n continue\n }\n val = val.Elem()\n }\n prop := toAttrName(k)\n parts = append(parts, fmt.Sprintf("%s:%v", prop, val.Interface()))\n }\n if len(parts) == 0 {\n return "", false\n }\n return strings.Join(parts, ";"), true\n}\n\n// SpreadAttrs lowers a JSX intrinsic-element spread bag (#1407) to\n// an HTML attribute string. Mirrors spreadAttrs from\n// packages/client/src/runtime/spread-attrs.ts so SSR output matches\n// what CSR\'s `applyRestAttrs` writes at hydration.\n//\n// Skip rules: nil/false values, event handlers (`on[A-Z]*`),\n// `children`, `ref`.\n//\n// Key remap: className \u2192 class, htmlFor \u2192 for, SVG camelCase\n// preserved, other camelCase \u2192 kebab-case.\n//\n// `style` is routed through StyleToCss so object literals serialize\n// to a real CSS string instead of Go\'s default `map[k:v]` form.\n//\n// Booleans: true \u2192 bare attribute name, false \u2192 omitted.\n// Other scalar values are HTML-escaped via template.HTMLEscapeString.\n// Returns a `template.HTMLAttr` so html/template emits the result\n// verbatim (the function does its own escaping).\n//\n// Keys are sorted alphabetically before emission for deterministic\n// output. SSR/CSR attribute-order divergence is acceptable per the\n// rest-destructure-object-spread-in-map fixture\'s documented policy\n// \u2014 browsers honor the LAST value when a key is duplicated, so\n// pairing with static attrs (`<div class="x" {...rest}>`) is\n// last-wins regardless of order.\nfunc SpreadAttrs(bag any) template.HTMLAttr {\n if bag == nil {\n return ""\n }\n rv := reflect.ValueOf(bag)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return ""\n }\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Map {\n return ""\n }\n keys := rv.MapKeys()\n sortedKeys := make([]string, 0, len(keys))\n for _, k := range keys {\n if k.Kind() == reflect.String {\n sortedKeys = append(sortedKeys, k.String())\n }\n }\n sort.Strings(sortedKeys)\n parts := make([]string, 0, len(sortedKeys))\n for _, key := range sortedKeys {\n // Event handlers \u2014 skip at SSR the same way\n // packages/client/src/runtime/spread-attrs.ts does at\n // hydration. The JS predicate is\n // `key.startsWith(\'on\') && key.length > 2 && key[2] === key[2].toUpperCase()`,\n // which is true for any character whose uppercase form is\n // itself: ASCII A-Z, digits, underscore, and non-letter\n // symbols. Mirror that here by skipping when key[2] is NOT\n // a lowercase ASCII letter \u2014 so `onClick`, `on_custom`, and\n // `on0` all match (#1411 review).\n if len(key) > 2 && key[0] == \'o\' && key[1] == \'n\' && !(key[2] >= \'a\' && key[2] <= \'z\') {\n continue\n }\n // `children` is a JSX construct rendered inside the element,\n // never a DOM attribute. `ref` is intentionally NOT filtered\n // here so output stays byte-equal with the JS reference\n // `spreadAttrs` in packages/client/src/runtime/spread-attrs.ts\n // (which only filters null/false, event handlers, and\n // children) \u2014 aligning Go\'s filter set diverges from JS in\n // the opposite direction. Filtering `ref` consistently across\n // both SSR runtimes is a separate concern tracked alongside\n // the JS `applyRestAttrs` vs `spreadAttrs` mismatch (#1411\n // review).\n if key == "children" {\n continue\n }\n val := rv.MapIndex(reflect.ValueOf(key))\n if !val.IsValid() {\n continue\n }\n // Unwrap interface wrappers (json.Unmarshal produces\n // interface{}-wrapped values for map[string]any).\n v := val\n for v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer {\n if v.IsNil() {\n // Skip null entries.\n v = reflect.Value{}\n break\n }\n v = v.Elem()\n }\n if !v.IsValid() {\n continue\n }\n // Boolean values: true \u2192 bare attribute, false \u2192 omitted.\n if v.Kind() == reflect.Bool {\n if !v.Bool() {\n continue\n }\n parts = append(parts, toAttrName(key))\n continue\n }\n // `style` routes through StyleToCss so object literals get a\n // real CSS string. The JS side does the same.\n if key == "style" {\n css, ok := StyleToCss(v.Interface())\n if !ok {\n continue\n }\n parts = append(parts, fmt.Sprintf(`style="%s"`, template.HTMLEscapeString(css)))\n continue\n }\n // Stringify and escape. fmt.Sprint handles numbers, bools-as-\n // strings, and arbitrary stringer types the same way the JS\n // `String(value)` coercion does for the analogous cases.\n s := fmt.Sprint(v.Interface())\n parts = append(parts, fmt.Sprintf(`%s="%s"`, toAttrName(key), template.HTMLEscapeString(s)))\n }\n if len(parts) == 0 {\n return ""\n }\n return template.HTMLAttr(strings.Join(parts, " "))\n}\n\n// Omit builds a `map[string]any` residual bag from a struct or map value,\n// excluding the given keys \u2014 powers the `{...rest}` spread-onto-element\n// lowering for a destructured `.map()` loop-item\'s object-rest binding\n// (#2087): `.map(({ id, title, ...rest }) => <li {...rest}>)` needs "every\n// field EXCEPT the ones the pattern already destructured out", and a static\n// Go struct type has no way to express "minus a field" \u2014 the exclude set is\n// known at COMPILE TIME (the sibling keys the destructure pattern names), so\n// the compiler passes them here and this does the per-item field-vs-key\n// matching a static type can\'t. The result feeds `bf_spread_attrs`\n// (`SpreadAttrs`), same as a top-level `{...attrs()}` bag.\n//\n// Struct receiver: iterates exported fields via reflection, keyed by each\n// field\'s `json` struct tag (falling back to the Go field name when absent)\n// \u2014 the generated struct\'s json tag is always the ORIGINAL source property\n// name (see `structFieldsFor` / `typeDefinitionToGo` in the Go adapter), so\n// this reproduces the exact JS key `SpreadAttrs`\'s `toAttrName` expects\n// (`"data-priority"`, not a re-derived `"DataPriority"`). A tag of `"-"`\n// (opt-out) is skipped like `encoding/json` does.\n//\n// Map receiver: copies string keys through directly, same exclude/skip\n// rules.\n//\n// Anything else (nil, a non-struct/non-map interface) returns an empty map.\nfunc Omit(item any, excludeKeys ...string) map[string]any {\n exclude := make(map[string]struct{}, len(excludeKeys))\n for _, k := range excludeKeys {\n exclude[k] = struct{}{}\n }\n out := map[string]any{}\n rv := reflect.ValueOf(item)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return out\n }\n rv = rv.Elem()\n }\n switch rv.Kind() {\n case reflect.Struct:\n rt := rv.Type()\n for i := 0; i < rt.NumField(); i++ {\n field := rt.Field(i)\n if !field.IsExported() {\n continue\n }\n key := field.Name\n if tag, ok := field.Tag.Lookup("json"); ok {\n if comma := strings.Index(tag, ","); comma >= 0 {\n tag = tag[:comma]\n }\n if tag == "-" {\n continue\n }\n if tag != "" {\n key = tag\n }\n }\n if _, skip := exclude[key]; skip {\n continue\n }\n out[key] = rv.Field(i).Interface()\n }\n case reflect.Map:\n for _, k := range rv.MapKeys() {\n if k.Kind() != reflect.String {\n continue\n }\n key := k.String()\n if _, skip := exclude[key]; skip {\n continue\n }\n val := rv.MapIndex(k)\n if val.IsValid() {\n out[key] = val.Interface()\n }\n }\n }\n return out\n}\n\n// BfPropsAttr returns the bf-p attribute with the JSON-serialized\n// props in flat format. Output format: `bf-p=\'{"propName":value,...}\'`.\n// Only emits the attribute for root components (BfIsRoot == true);\n// child components receive props from their parent via initChild().\n//\n// Returns the marshal error so a `template.Execute` call fails\n// loudly on cycles / unsupported props rather than silently\n// dropping the bf-p attribute and breaking client-side hydration.\n// Same loud-failure policy as `JSON` \u2014 user data going through\n// `encoding/json` shouldn\'t fail invisibly.\nfunc BfPropsAttr(props interface{}) (template.HTMLAttr, error) {\n // Only root components should emit bf-p\n if !getBoolField(props, "BfIsRoot") {\n return "", nil\n }\n\n propsJSON, err := json.Marshal(props)\n if err != nil {\n return "", err\n }\n\n escaped := template.HTMLEscapeString(string(propsJSON))\n return template.HTMLAttr(`bf-p="` + escaped + `"`), nil\n}\n\n// =============================================================================\n// Arithmetic Operations\n// =============================================================================\n\n// Add returns a + b. Supports int and float64.\nfunc Add(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n result := av + bv\n // Return int if both inputs were int-like\n if isIntLike(a) && isIntLike(b) && result == float64(int(result)) {\n return int(result)\n }\n return result\n}\n\n// ConcatStr returns a and b concatenated as strings \u2014 the string-typed half\n// of JS `+` (#2168 string-concat-plus). JS `+` is addition when BOTH\n// operands are numeric and concatenation when EITHER is a string; `Add`\n// (above) covers the numeric case, this covers the string one \u2014 `Add`\n// itself can\'t (`toFloat64` returns 0 for a string operand, so `\'Hello, \' +\n// name` silently rendered "0" before this existed).\nfunc ConcatStr(a, b any) string {\n return toString(a) + toString(b)\n}\n\n// Sub returns a - b. Supports int and float64.\nfunc Sub(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n result := av - bv\n if isIntLike(a) && isIntLike(b) && result == float64(int(result)) {\n return int(result)\n }\n return result\n}\n\n// Mul returns a * b. Supports int and float64.\nfunc Mul(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n result := av * bv\n if isIntLike(a) && isIntLike(b) && result == float64(int(result)) {\n return int(result)\n }\n return result\n}\n\n// Div returns a / b. Returns float64 to match JavaScript behavior.\n// Returns 0 if b is 0 (instead of panicking).\nfunc Div(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n if bv == 0 {\n return 0\n }\n return av / bv\n}\n\n// Min returns the smaller of a and b (two-arg `Math.min`). Like Mul, it keeps\n// an integer result when both operands are int-like so a CSS value such as\n// `bf_min 100 x` stays `100` rather than `100.000000`. Uses `Number` (not\n// `toFloat64`, which silently zeroes an unrecognized type like a non-numeric\n// string) plus explicit NaN checks, since IEEE-754 `<`/`>` comparisons\n// against NaN are always false and would otherwise let a non-NaN operand\n// win instead of propagating NaN like JS `Math.min`/`Math.max` do.\nfunc Min(a, b any) any {\n av, bv := Number(a), Number(b)\n if math.IsNaN(av) {\n return av\n }\n if math.IsNaN(bv) {\n return bv\n }\n r := av\n if bv < av {\n r = bv\n }\n if isIntLike(a) && isIntLike(b) && r == float64(int(r)) {\n return int(r)\n }\n return r\n}\n\n// Max returns the larger of a and b (two-arg `Math.max`), with the same\n// int-preserving rule and NaN-propagation as Min.\nfunc Max(a, b any) any {\n av, bv := Number(a), Number(b)\n if math.IsNaN(av) {\n return av\n }\n if math.IsNaN(bv) {\n return bv\n }\n r := av\n if bv > av {\n r = bv\n }\n if isIntLike(a) && isIntLike(b) && r == float64(int(r)) {\n return int(r)\n }\n return r\n}\n\n// Mod returns a % b (modulo). Supports int only.\nfunc Mod(a, b any) int {\n av, bv := toInt(a), toInt(b)\n if bv == 0 {\n return 0\n }\n return av % bv\n}\n\n// Neg returns -a (negation).\nfunc Neg(a any) any {\n if v, ok := a.(int); ok {\n return -v\n }\n return -toFloat64(a)\n}\n\n// =============================================================================\n// String Operations\n// =============================================================================\n\n// Lower returns the lowercase version of s.\nfunc Lower(s string) string {\n return strings.ToLower(s)\n}\n\n// Upper returns the uppercase version of s.\nfunc Upper(s string) string {\n return strings.ToUpper(s)\n}\n\n// Trim returns s with leading and trailing whitespace removed.\nfunc Trim(s string) string {\n return strings.TrimSpace(s)\n}\n\n// TrimStart returns s with leading whitespace removed\n// (String.prototype.trimStart, #2183) \u2014 the one-sided sibling of\n// Trim above, using the same unicode.IsSpace predicate strings.TrimSpace\n// applies to both sides.\nfunc TrimStart(s string) string {\n return strings.TrimLeftFunc(s, unicode.IsSpace)\n}\n\n// TrimEnd returns s with trailing whitespace removed\n// (String.prototype.trimEnd, #2183) \u2014 the one-sided sibling of Trim.\nfunc TrimEnd(s string) string {\n return strings.TrimRightFunc(s, unicode.IsSpace)\n}\n\n// Contains returns true if s contains substr.\nfunc Contains(s, substr string) bool {\n return strings.Contains(s, substr)\n}\n\n// Split lowers `String.prototype.split(sep, limit?)` (#1448 Tier B). It\n// wraps `strings.Split` and normalises the result to `[]any` so the\n// slice composes with the array-method surface downstream (`bf_join`,\n// range loops, `bf_len`, \u2026) the same way `bf_slice` / `bf_reverse`\n// results do. Like JS, an empty separator splits into individual UTF-8\n// characters and trailing empty fields are preserved (`"a,".split(",")`\n// \u2192 `["a", ""]`). An optional `limit` caps the number of returned\n// pieces (`"a,b,c".split(",", 2)` \u2192 `["a", "b"]`); a negative limit is\n// ignored (JS would also return every piece \u2014 its ToUint32 wrap makes\n// the limit effectively unbounded). The no-separator form is handled by\n// the adapter (it emits `bf_arr` for the whole-string single element).\nfunc Split(s, sep string, limit ...int) []any {\n parts := strings.Split(s, sep)\n if len(limit) > 0 && limit[0] >= 0 && limit[0] < len(parts) {\n parts = parts[:limit[0]]\n }\n out := make([]any, len(parts))\n for i, p := range parts {\n out[i] = p\n }\n return out\n}\n\n// StartsWith lowers `String.prototype.startsWith(prefix, position?)`\n// (#1448 Tier B). Wraps `strings.HasPrefix`; an empty prefix is always\n// true (JS parity). The optional `position` re-anchors the test to start\n// at that index (clamped to `[0, len]` so it never panics), matching JS\n// `"abc".startsWith("b", 1) === true`.\nfunc StartsWith(s, prefix string, position ...int) bool {\n if len(position) > 0 {\n p := position[0]\n if p < 0 {\n p = 0\n }\n if p > len(s) {\n p = len(s)\n }\n s = s[p:]\n }\n return strings.HasPrefix(s, prefix)\n}\n\n// EndsWith lowers `String.prototype.endsWith(suffix, endPosition?)`\n// (#1448 Tier B). Wraps `strings.HasSuffix`; an empty suffix is always\n// true (JS parity). The optional `endPosition` treats the string as if\n// it were only that many bytes long (clamped to `[0, len]`), matching JS\n// `"abc".endsWith("b", 2) === true`.\nfunc EndsWith(s, suffix string, endPosition ...int) bool {\n if len(endPosition) > 0 {\n e := endPosition[0]\n if e < 0 {\n e = 0\n }\n if e > len(s) {\n e = len(s)\n }\n s = s[:e]\n }\n return strings.HasSuffix(s, suffix)\n}\n\n// Replace lowers the string-pattern form of `String.prototype.replace`\n// (#1448 Tier B). JS replaces only the FIRST occurrence for a string\n// pattern, so the count is 1 (`strings.Replace` with n=1; `ReplaceAll`\n// below is the every-occurrence sibling, `.replaceAll`, #2182). The\n// replacement is treated literally: unlike JS, special replacement\n// patterns like `$&` / `$1` are NOT interpreted (Go and Perl agree on\n// literal replacement, keeping the two template adapters byte-equal;\n// this diverges from the Hono/CSR JS path only for replacement strings\n// that contain `$`-patterns, which are rare in template position).\nfunc Replace(s, old, new string) string {\n return strings.Replace(s, old, new, 1)\n}\n\n// ReplaceAll lowers the string-pattern form of\n// `String.prototype.replaceAll` (#2182): every occurrence, via\n// `strings.ReplaceAll` (equivalent to `strings.Replace` with n=-1).\n// Same literal-replacement caveat as `Replace` above.\nfunc ReplaceAll(s, old, new string) string {\n return strings.ReplaceAll(s, old, new)\n}\n\n// Repeat lowers `String.prototype.repeat(n)` (#1448 Tier B): the\n// receiver concatenated n times. JS throws RangeError for a negative\n// count and `strings.Repeat` panics, so a negative count clamps to the\n// empty string \u2014 SSR templates degrade rather than crash the render.\n// A zero count is the empty string (JS parity).\nfunc Repeat(s string, n int) string {\n if n <= 0 {\n return ""\n }\n return strings.Repeat(s, n)\n}\n\n// padTo lowers the shared body of `String.prototype.padStart` /\n// `padEnd` (#1448 Tier B): pad `s` to `target` code points using `pad`\n// repeated and truncated to fill, prepended (atStart) or appended.\n// Length is measured in runes (not bytes) so the result matches the\n// Perl `bf->pad_*` helpers \u2014 this diverges from JS\'s UTF-16-unit length\n// only for astral-plane input. An empty pad, or a receiver already at\n// least `target` long, returns `s` unchanged (JS parity).\nfunc padTo(s string, target int, pad string, atStart bool) string {\n if pad == "" {\n return s\n }\n sLen := utf8.RuneCountInString(s)\n if sLen >= target {\n return s\n }\n need := target - sLen\n padRunes := []rune(pad)\n fill := make([]rune, 0, need)\n for len(fill) < need {\n for _, r := range padRunes {\n if len(fill) >= need {\n break\n }\n fill = append(fill, r)\n }\n }\n if atStart {\n return string(fill) + s\n }\n return s + string(fill)\n}\n\n// PadStart lowers `String.prototype.padStart(target, pad?)` (#1448 Tier\n// B). The pad string defaults to a single space when omitted.\nfunc PadStart(s string, target int, pad ...string) string {\n p := " "\n if len(pad) > 0 {\n p = pad[0]\n }\n return padTo(s, target, p, true)\n}\n\n// PadEnd lowers `String.prototype.padEnd(target, pad?)` (#1448 Tier B).\nfunc PadEnd(s string, target int, pad ...string) string {\n p := " "\n if len(pad) > 0 {\n p = pad[0]\n }\n return padTo(s, target, p, false)\n}\n\n// Join concatenates elements of a slice with sep. Accepts both\n// reflect.Slice (the common case \u2014 `bf_arr` and `bf_filter_truthy`\n// both return `[]any`) AND reflect.Array (fixed-size Go arrays like\n// `[3]string{...}`), mirroring JS `Array.prototype.join` which\n// doesn\'t distinguish between the two. Pre-fix this returned "" for\n// fixed-size arrays passed through template data (Copilot review on\n// #1445).\nfunc Join(items any, sep string) string {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return ""\n }\n\n parts := make([]string, v.Len())\n for i := 0; i < v.Len(); i++ {\n parts[i] = toString(v.Index(i).Interface())\n }\n return strings.Join(parts, sep)\n}\n\n// Ternary returns a when cond is true, else b \u2014 the pipeline-position\n// counterpart of a template {{if}} action. Go templates have no\n// expression-level conditional, so a conditional value sitting in\n// ARGUMENT position (a lowering-node helper arg, e.g. the #2324 union\n// stage\'s locale\u2192pattern ternary) cannot be emitted as an {{if}}\n// fragment; the adapter renders it as `(bf_ternary <cond> <a> <b>)`\n// instead. Both branches are evaluated (function-call semantics) \u2014\n// fine for the value shapes the emitter feeds it, wrong for anything\n// with side effects, which template values never have.\nfunc Ternary(cond bool, a, b any) any {\n if cond {\n return a\n }\n return b\n}\n\n// String returns the string form of v. Mirrors JS `String(v)` for\n// non-nil values via `fmt.Sprintf("%v", ...)`. Diverges from JS on\n// nil: JS `String(null)` is "null", but the template path renders\n// `nil` as the empty string here so an unset prop doesn\'t surface\n// as a literal "null"/"undefined" in user-facing HTML. Document the\n// divergence explicitly so callers don\'t rely on JS-exact parity.\nfunc String(v any) string {\n if v == nil {\n return ""\n }\n return fmt.Sprintf("%v", v)\n}\n\n// RawHTML marks a value as trusted, pre-formatted HTML so html/template\'s\n// contextual escaper emits it verbatim instead of escaping it. It is the SSR\n// half of a dynamic `dangerouslySetInnerHTML={{ __html: expr }}` (#2319) \u2014\n// the one raw-output sink Go lacks as bare template syntax, the counterpart\n// to Blade `{!! !!}`, ERB `<%= %>`, Jinja/MiniJinja `| safe`, Twig `| raw`,\n// Mojolicious `<%== %>`, and Xslate `mark_raw`. The caller owns the value\'s\n// safety (React\'s "dangerously" contract); a nil value renders "".\nfunc RawHTML(v any) template.HTML {\n return template.HTML(String(v))\n}\n\n// JSON returns the JSON encoding of v as a string. Mirrors\n// JS `JSON.stringify(v)` for the V1 single-arg shape (no `replacer`\n// or `space`). Object key order is determined by Go\'s `encoding/json`\n// (alphabetical for maps, declaration order for structs) \u2014 the\n// #1187 contract requires value-compat, not order-compat.\n//\n// Top-level NaN / \xB1Inf are pre-handled to match JS \u2014 JS\'s\n// `JSON.stringify(NaN)` and `JSON.stringify(Infinity)` both produce\n// `"null"`, but Go\'s `encoding/json` rejects them with\n// `UnsupportedValueError`. Without this carve-out the common\n// composition `JSON.stringify(Number("garbage"))` would error\n// instead of emitting `"null"` like JS does. Nested NaN/Inf inside\n// a struct/map still surfaces an error \u2014 covering that needs a\n// custom marshaller; out of V1 scope.\n//\n// Returns the marshal error so a `template.Execute` call fails\n// loudly on cycles / unsupported values rather than silently\n// producing `""` and reintroducing the SSR data-loss class\n// #1187 was filed against. Go\'s text/template treats a non-nil\n// error return from a func as an execution failure.\nfunc JSON(v any) (string, error) {\n if f, ok := v.(float64); ok && (math.IsNaN(f) || math.IsInf(f, 0)) {\n return "null", nil\n }\n b, err := json.Marshal(v)\n if err != nil {\n return "", err\n }\n return string(b), nil\n}\n\n// Number coerces v to a float64. Mirrors JS `Number(v)` semantics:\n// numeric / boolean inputs convert as expected; non-numeric strings\n// and other unsupported shapes return `NaN` (matching JS rather\n// than silently substituting 0, which would mis-shape downstream\n// arithmetic and template-side comparisons). Templates that need\n// a deterministic fallback should compose with the user-side\n// default (e.g. `Number(props.x ?? 0)` in JSX).\nfunc Number(v any) float64 {\n if v == nil {\n return math.NaN()\n }\n switch x := v.(type) {\n case float64:\n return x\n case float32:\n return float64(x)\n case int:\n return float64(x)\n case int32:\n return float64(x)\n case int64:\n return float64(x)\n case bool:\n if x {\n return 1\n }\n return 0\n case string:\n f, err := strconv.ParseFloat(x, 64)\n if err != nil {\n return math.NaN()\n }\n return f\n }\n return math.NaN()\n}\n\n// Floor returns the largest integer \u2264 v as a float64. Mirrors JS\n// `Math.floor`. The return type stays float64 so chained primitives\n// (`bf_floor` then `bf_string`) line up with JS\'s number type.\nfunc Floor(v any) float64 {\n return math.Floor(Number(v))\n}\n\n// Abs returns the absolute value of v as a float64, mirroring JS\n// `Math.abs`. #2168 math-methods.\nfunc Abs(v any) float64 {\n return math.Abs(Number(v))\n}\n\n// ToFixed formats v with exactly `digits` decimal places, mirroring JS\n// `Number.prototype.toFixed` (zero-padding + half-toward-+Infinity\n// rounding). JS rounds the scaled integer half up (`(2.5).toFixed(0)`\n// is "3"); bare `fmt.Sprintf("%.*f")` rounds half-to-even ("2"), so we\n// scale, round with `Floor(x + 0.5)` (matching `Round`), then format\n// the exact multiple. #1897.\nfunc ToFixed(v any, digits int) string {\n if digits < 0 {\n digits = 0\n }\n n := Number(v)\n // JS toFixed returns the strings "NaN" / "Infinity" / "-Infinity" for\n // non-finite inputs; fmt would render "NaN"/"+Inf"/"-Inf".\n if math.IsNaN(n) {\n return "NaN"\n }\n if math.IsInf(n, 1) {\n return "Infinity"\n }\n if math.IsInf(n, -1) {\n return "-Infinity"\n }\n factor := math.Pow(10, float64(digits))\n rounded := math.Floor(n*factor + 0.5)\n return fmt.Sprintf("%.*f", digits, rounded/factor)\n}\n\n// Ceil returns the smallest integer \u2265 v as a float64. Mirrors JS\n// `Math.ceil`.\nfunc Ceil(v any) float64 {\n return math.Ceil(Number(v))\n}\n\n// Round returns v rounded to the nearest integer as a float64.\n// Mirrors JS `Math.round` \u2014 half-away-from-zero (Go\'s `math.Round`\n// matches; JS rounds half toward +Infinity which differs at .5\n// negatives; we accept that minor divergence since the conformance\n// contract is value-compat for the common positive case).\nfunc Round(v any) float64 {\n return math.Round(Number(v))\n}\n\n// =============================================================================\n// Array/Slice Operations\n// =============================================================================\n\n// Length lowers JS `.length`, matching JS semantics per receiver shape\n// (#2255): a slice/array/map counts ELEMENTS (`reflect.Value.Len`, same as\n// `Len` below), but a STRING counts UTF-16 CODE UNITS \u2014 JS\n// `String.prototype.length` counts UTF-16 code units, not bytes (Go\'s\n// native `len`) or codepoints. A codepoint outside the Basic Multilingual\n// Plane (astral, U+10000-U+10FFFF \u2014 e.g. \'\u{1F44D}\') is a surrogate PAIR in\n// UTF-16, so it counts as 2, not 1; `\'\u65E5\u672C\u8A9E\'` is 3 either way (BMP-only).\n// Routed from the `.length` member lowering\'s generic (non-array,\n// non-loop-slice) fallback \u2014 see `member()`\'s `bf_length` call site.\nfunc Length(v any) int {\n if v == nil {\n return 0\n }\n rv := reflect.ValueOf(v)\n switch rv.Kind() {\n case reflect.Slice, reflect.Array, reflect.Map, reflect.Chan:\n return rv.Len()\n case reflect.String:\n n := 0\n for _, r := range rv.String() {\n if r > 0xFFFF {\n n += 2\n } else {\n n++\n }\n }\n return n\n default:\n return 0\n }\n}\n\n// IsValidElement lowers React/Hono-style `isValidElement(x)` \u2014 the "is this\n// a renderable element (not plain text)?" predicate the `Slot` component\'s\n// `asChild` pattern (#2266) uses to decide whether to merge props into a\n// child ELEMENT (`children.tag`/`children.props`) or fall back to rendering\n// `children` as-is. The JS runtime checks `\'tag\' in x && \'props\' in x`; on\n// Go SSR a passed-through JSX child is represented as pre-rendered markup\n// (a plain string) OR \u2014 where a struct/map shape carrying `Tag`/`Props`\n// (case-insensitively, mirroring `bf_get`\'s field lookup) is available \u2014 an\n// element-shaped value. A plain string/number/bool/nil is never a valid\n// element, so `isValidElement` must NOT be lowered as bare truthiness\n// (previously done via `renderConditionExpr`) \u2014 a truthy non-empty STRING\n// child wrongly took the element-merge branch and panicked dereferencing\n// `.Props` on a string (`can\'t evaluate field Props in type interface {}`).\nfunc IsValidElement(v any) bool {\n rv := reflect.ValueOf(v)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return false\n }\n rv = rv.Elem()\n }\n switch rv.Kind() {\n case reflect.Map:\n hasTag, hasProps := false, false\n for _, k := range rv.MapKeys() {\n key := fmt.Sprintf("%v", k.Interface())\n if strings.EqualFold(key, "tag") {\n hasTag = true\n }\n if strings.EqualFold(key, "props") {\n hasProps = true\n }\n }\n return hasTag && hasProps\n case reflect.Struct:\n return fieldByFoldedName(rv, "tag").IsValid() && fieldByFoldedName(rv, "props").IsValid()\n default:\n return false\n }\n}\n\n// fieldByFoldedName finds a struct field by case-insensitive name match \u2014\n// shared by IsValidElement; mirrors getFieldValue\'s (bf_get) struct-branch\n// lookup so the two case-tolerant field resolutions stay consistent.\nfunc fieldByFoldedName(rv reflect.Value, name string) reflect.Value {\n t := rv.Type()\n for i := 0; i < t.NumField(); i++ {\n if strings.EqualFold(t.Field(i).Name, name) {\n return rv.Field(i)\n }\n }\n return reflect.Value{}\n}\n\n// Len returns the length of a slice, array, map, string, or channel.\n// Returns 0 for nil or unsupported types.\nfunc Len(v any) int {\n if v == nil {\n return 0\n }\n rv := reflect.ValueOf(v)\n switch rv.Kind() {\n case reflect.Slice, reflect.Array, reflect.Map, reflect.String, reflect.Chan:\n return rv.Len()\n default:\n return 0\n }\n}\n\n// At returns the element at index i from a slice.\n// Supports negative indices (e.g., -1 for last element).\n// Returns nil if index is out of bounds.\nfunc At(items any, index int) any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n\n length := v.Len()\n if length == 0 {\n return nil\n }\n\n // Handle negative indices\n if index < 0 {\n index = length + index\n }\n\n if index < 0 || index >= length {\n return nil\n }\n\n return v.Index(index).Interface()\n}\n\n// Includes returns true if items contains elem. Lowers both\n// `Array.prototype.includes` and `String.prototype.includes` \u2014\n// the adapter can\'t disambiguate the receiver at compile time,\n// so this helper dispatches at runtime on `reflect.Kind()`:\n//\n// - slice/array receiver: SameValueZero element search (matches\n// the evaluator\'s `evalSameValueZero`/`evalIncludes` in eval.go,\n// which back the serialized-callback path) \u2014 numeric types compare\n// by value across int/float64 the way JS\'s single "number" type\n// does, and NaN matches NaN (unlike `===`). This used to be\n// `reflect.DeepEqual`, which is type-strict (`int(2)` != `float64(2)`)\n// and never matches NaN to NaN; that diverged from the evaluator\'s\n// `.includes` and from JS itself, so it was unified here.\n// - string receiver: strings.Contains substring search\n//\n// Anything else returns false (matches the JS semantic where\n// `.includes` is only defined on Array / TypedArray / String).\nfunc Includes(recv any, elem any) bool {\n v := reflect.ValueOf(recv)\n if v.Kind() == reflect.String {\n // JS `String.prototype.includes` accepts only string args;\n // non-string `elem` would TypeError in real JS but our\n // callers have lowered through `convertExpressionToGo`\n // where the arg type is whatever the template binds. Stringify\n // via fmt to keep the helper total.\n needle, ok := elem.(string)\n if !ok {\n needle = fmt.Sprintf("%v", elem)\n }\n return strings.Contains(v.String(), needle)\n }\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return false\n }\n for i := 0; i < v.Len(); i++ {\n if evalSameValueZero(v.Index(i).Interface(), elem) {\n return true\n }\n }\n return false\n}\n\n// IndexOf returns the 0-based position of the first item that\n// DeepEquals `elem`, or -1 if not found. Lowers\n// `Array.prototype.indexOf(x)` (#1448 Tier A). The existing\n// `FindIndex` helper does struct-field equality (used by the\n// higher-order `.find` lowering); this one does value equality\n// against scalar / struct items so callers don\'t have to compose\n// a synthetic predicate.\n//\n// Non-array / non-slice receivers return -1 (matches the JS\n// semantic that `.indexOf` is only defined on Array / TypedArray).\nfunc IndexOf(items any, elem any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := 0; i < v.Len(); i++ {\n if reflect.DeepEqual(v.Index(i).Interface(), elem) {\n return i\n }\n }\n return -1\n}\n\n// LastIndexOf returns the 0-based position of the last item that\n// DeepEquals `elem`, or -1 if not found. Mirrors\n// `Array.prototype.lastIndexOf(x)`. The reverse traversal is the\n// only behavioural difference vs `IndexOf` \u2014 disambiguating a\n// duplicated value\'s first vs last position is the canonical\n// reason a JS author reaches for `lastIndexOf`.\nfunc LastIndexOf(items any, elem any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := v.Len() - 1; i >= 0; i-- {\n if reflect.DeepEqual(v.Index(i).Interface(), elem) {\n return i\n }\n }\n return -1\n}\n\n// Concat merges two arrays (or slices) into a single `[]any`,\n// preserving order: receiver elements first, then `other`\'s.\n// Lowers `Array.prototype.concat(other)` (#1448 Tier A). Non-array\n// operands collapse to an empty source \u2014 matches the JS semantic\n// where `.concat` on a non-Array reads it as a single element only\n// if its `Symbol.isConcatSpreadable` is true; the template-language\n// path doesn\'t have user objects with that flag, so treating\n// non-arrays as empty is the conservative lowering. Variadic\n// `.concat(a, b, c)` is out of scope here (parser gates to a single\n// arg); the helper itself stays binary so a future variadic IR can\n// fold via repeated calls without changing this signature.\nfunc Concat(a, b any) []any {\n flatten := func(v reflect.Value) []any {\n if !v.IsValid() {\n return nil\n }\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n out := make([]any, v.Len())\n for i := 0; i < v.Len(); i++ {\n out[i] = v.Index(i).Interface()\n }\n return out\n }\n left := flatten(reflect.ValueOf(a))\n right := flatten(reflect.ValueOf(b))\n return append(left, right...)\n}\n\n// clampSliceRange normalizes JS `.slice(start, end?)` bounds against a\n// receiver of `length` elements (runes, for the string branch of\n// `Slice` below; array elements, for the array branch) \u2014 shared so\n// both branches clamp identically.\n//\n// JS-compat clamping:\n// - start < 0 \u2192 length + start (e.g. -1 = last index)\n// - end < 0 \u2192 length + end\n// - start < 0 after clamp \u2192 0\n// - end > length \u2192 length\nfunc clampSliceRange(length, start int, end []int) (int, int) {\n if start < 0 {\n start = length + start\n }\n if start < 0 {\n start = 0\n }\n if start > length {\n start = length\n }\n\n stop := length\n if len(end) > 0 {\n stop = end[0]\n if stop < 0 {\n stop = length + stop\n }\n if stop < 0 {\n stop = 0\n }\n if stop > length {\n stop = length\n }\n }\n return start, stop\n}\n\n// Slice carves out a sub-range from `items`. Lowers\n// `Array.prototype.slice(start, end?)` (#1448 Tier A) AND\n// `String.prototype.slice(start, end?)` (the `string-slice`\n// divergence) \u2014 the adapter emits the same `bf_slice` call for both\n// receiver shapes (it can\'t disambiguate string vs. array at compile\n// time), so this helper dispatches at runtime on `reflect.Kind()`,\n// mirroring `Includes` above. The variadic `end` arg lets Go\n// template\'s call dispatcher pass either 2 or 3 arguments; an absent\n// end means "to length".\n//\n// String length/positions are measured in runes, not UTF-16 code\n// units \u2014 the same divergence boundary `padTo` already accepts\n// (differs from JS only for astral-plane input). `start >= end`\n// (after clamping) returns an empty result for either receiver.\n//\n// Any other receiver kind returns an empty `[]any`.\nfunc Slice(items any, start int, end ...int) any {\n v := reflect.ValueOf(items)\n\n if v.Kind() == reflect.String {\n runes := []rune(v.String())\n s, e := clampSliceRange(len(runes), start, end)\n if s >= e {\n return ""\n }\n return string(runes[s:e])\n }\n\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n s, e := clampSliceRange(v.Len(), start, end)\n if s >= e {\n return []any{}\n }\n out := make([]any, 0, e-s)\n for i := s; i < e; i++ {\n out = append(out, v.Index(i).Interface())\n }\n return out\n}\n\n// Reverse returns a new slice with `items`\'s elements in reverse\n// order. Lowers both `Array.prototype.reverse()` and\n// `Array.prototype.toReversed()` (#1448 Tier A) \u2014 SSR templates\n// render a snapshot, so JS\'s mutate-receiver vs return-new-array\n// distinction has no template-level meaning, and the safer\n// non-mutating shape is used uniformly.\n//\n// Non-array receivers return an empty `[]any`.\nfunc Reverse(items any) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n length := v.Len()\n out := make([]any, length)\n for i := 0; i < length; i++ {\n out[length-1-i] = v.Index(i).Interface()\n }\n return out\n}\n\n// Flat flattens nested slices/arrays `depth` levels deep. Lowers\n// `Array.prototype.flat(depth?)` (#1448 Tier C). A `depth` of `-1` is the\n// `Infinity` sentinel (flatten fully); `0` (or negative-from-JS, already\n// normalised to 0 at compile time) returns a shallow copy. Non-array\n// elements are kept as-is (JS only flattens nested arrays). A non-array\n// receiver returns an empty `[]any`.\nfunc Flat(items any, depth int) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n out := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n el := v.Index(i).Interface()\n ev := reflect.ValueOf(el)\n if depth != 0 && (ev.Kind() == reflect.Slice || ev.Kind() == reflect.Array) {\n // `-1` (Infinity) recurses unbounded; a finite depth spends one level.\n next := depth\n if depth > 0 {\n next = depth - 1\n }\n out = append(out, Flat(el, next)...)\n } else {\n out = append(out, el)\n }\n }\n return out\n}\n\n// FlatDynamicDepth coerces `depth` via JS\'s `ToIntegerOrInfinity` and\n// flattens `items` that many levels. Lowers a DYNAMIC `.flat(depth)`\n// (#2094) \u2014 one whose depth isn\'t a compile-time literal, so (unlike\n// `Flat` above) the coercion happens here at render time instead of in the\n// parser.\n//\n// This is a SEPARATE helper from `Flat`/`bf_flat` \u2014 NOT a drop-in\n// replacement \u2014 because `Flat`\'s `depth int` parameter treats `-1` as a\n// compile-time SENTINEL meaning "flatten fully" (the parser\'s own\n// normalisation of a literal `Infinity`). A genuinely dynamic depth value\n// of `-1` means the JS-correct OPPOSITE: `Array.prototype.flat(-1)` never\n// recurses (same as `.flat(0)`, a shallow copy), because\n// `FlattenIntoArray` only recurses when `depth > 0`. Reusing `Flat`\'s int\n// contract for a raw dynamic value would silently invert that case, so\n// this function coerces FIRST \u2014 mapping a real `+Infinity` / huge finite\n// value to `Flat`\'s own `-1` sentinel, and a real negative value to `0` \u2014\n// and only then delegates to `Flat`\'s recursion.\n//\n// Coercion rules (JS `ToIntegerOrInfinity`, mirrored exactly; pinned by the\n// `flat_dynamic` golden-vector cases in\n// packages/adapter-tests/vectors/cases.ts):\n// - the value converts via `ToNumber` first (numeric string / bool /\n// number all coerce; see `flatDepthToFloat`);\n// - a NaN result (including a non-numeric string) \u2192 `0`;\n// - truncates toward zero (`2.7` \u2192 `2`);\n// - negative \u2192 `0`;\n// - `+Infinity` / a huge finite value \u2192 flattens fully.\nfunc FlatDynamicDepth(items any, depth any) []any {\n return Flat(items, coerceFlatDepth(depth))\n}\n\n// coerceFlatDepth implements JS\'s `ToIntegerOrInfinity` for a dynamic\n// `.flat(depth)` argument, returning an int in `Flat`\'s own contract (`-1`\n// = unbounded, `>= 0` = that many levels).\nfunc coerceFlatDepth(depth any) int {\n f, ok := flatDepthToFloat(depth)\n if !ok || math.IsNaN(f) {\n return 0\n }\n if math.IsInf(f, 1) {\n return -1 // Flat\'s "flatten fully" sentinel\n }\n if math.IsInf(f, -1) {\n return 0\n }\n trunc := math.Trunc(f)\n if trunc < 0 {\n return 0\n }\n // A huge finite depth behaves identically to "flatten fully" in\n // practice \u2014 real data bottoms out at its actual nesting depth long\n // before a counter this large would ever reach zero. Capping it here\n // avoids an absurd countdown without needing a second sentinel.\n if trunc > 1_000_000 {\n return -1\n }\n return int(trunc)\n}\n\n// flatDepthToFloat converts a dynamic `.flat(depth)` argument to a float64,\n// mirroring JS\'s `ToNumber` across the value shapes a Go template data\n// model can carry (every numeric kind, bool, numeric string). `ok` is\n// false for a shape `ToNumber` can\'t coerce meaningfully (`nil`, or a\n// non-numeric string) \u2014 `coerceFlatDepth` treats that the same as NaN\n// (\u2192 depth `0`), matching JS.\nfunc flatDepthToFloat(v any) (float64, bool) {\n switch n := v.(type) {\n case nil:\n return 0, false\n case float64:\n return n, true\n case float32:\n return float64(n), true\n case int:\n return float64(n), true\n case int8:\n return float64(n), true\n case int16:\n return float64(n), true\n case int32:\n return float64(n), true\n case int64:\n return float64(n), true\n case uint:\n return float64(n), true\n case uint8:\n return float64(n), true\n case uint16:\n return float64(n), true\n case uint32:\n return float64(n), true\n case uint64:\n return float64(n), true\n case bool:\n if n {\n return 1, true\n }\n return 0, true\n case string:\n s := strings.TrimSpace(n)\n if s == "" {\n return 0, true // JS: Number("") is 0\n }\n f, err := strconv.ParseFloat(s, 64)\n if err != nil {\n return 0, false // not numeric \u2192 NaN path\n }\n return f, true\n default:\n return 0, false\n }\n}\n\n// FlatMap projects each element through a `self` / `field` projection and\n// flattens the result one level. Lowers value-returning\n// `Array.prototype.flatMap(fn)` for the field-projection catalogue\n// (#1448 Tier C): `items.flatMap(i => i)` (self) and\n// `items.flatMap(i => i.field)` (field). A projected non-array value is\n// kept as-is (flatMap = map + flat(1)). Non-array receiver \u2192 empty.\nfunc FlatMap(items any, keyKind, keyName string) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n projected := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n el := v.Index(i).Interface()\n if keyKind == "field" {\n projected = append(projected, getFieldValue(el, keyName))\n } else {\n projected = append(projected, el)\n }\n }\n return Flat(projected, 1)\n}\n\n// FlatMapTuple lowers an array-literal flatMap projection\n// `items.flatMap(i => [i.a, i.b])` (#1448 Tier C). `specs` is a flat list\n// of (kind, name) pairs, one per array-literal leaf: ("self", "") for the\n// item itself, ("field", "<Name>") for a struct field. For each item it\n// appends every leaf\'s value in order. Unlike the scalar `FlatMap`, the\n// per-item array is flattened only one level (flat(1) removes the literal\n// wrapper), so an array-valued leaf is appended verbatim rather than\n// spread \u2014 which is exactly "append each leaf". Non-array receiver \u2192 empty.\nfunc FlatMapTuple(items any, specs ...string) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n out := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n el := v.Index(i).Interface()\n for j := 0; j+1 < len(specs); j += 2 {\n if specs[j] == "field" {\n out = append(out, getFieldValue(el, specs[j+1]))\n } else {\n out = append(out, el)\n }\n }\n }\n return out\n}\n\n// First returns the first element of a slice, or nil if empty.\nfunc First(items any) any {\n return At(items, 0)\n}\n\n// Last returns the last element of a slice, or nil if empty.\nfunc Last(items any) any {\n return At(items, -1)\n}\n\n// Arr builds an []any from variadic args. Used to lower JS array\n// literals like `[a, b]` for the registry Slot\'s\n// `[className, childClass].filter(Boolean).join(\' \')` shape (#1443) \u2014\n// Go templates have no array-literal syntax, so the codegen routes\n// array-literal IR nodes through this helper.\nfunc Arr(items ...any) []any {\n return items\n}\n\n// FilterTruthy returns a new slice containing only truthy items.\n// Mirrors `arr.filter(Boolean)` semantics: drop nil, false, 0, "" \u2014 the\n// same falsy set JavaScript\'s `Boolean(x)` recognises. Used to lower\n// the registry Slot\'s class-merge pattern (#1443); generalising to\n// arbitrary callable predicates would need the callee-resolution path\n// blocked by #1389, so this stays Boolean-specific.\nfunc FilterTruthy(items any) []any {\n v := reflect.ValueOf(items)\n if !v.IsValid() || (v.Kind() != reflect.Slice && v.Kind() != reflect.Array) {\n return nil\n }\n result := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n raw := v.Index(i).Interface()\n if isTruthy(raw) {\n result = append(result, raw)\n }\n }\n return result\n}\n\n// Truthy is the exported form of isTruthy \u2014 JavaScript\'s `Boolean(x)`\n// semantics. Two callers:\n// - generated `NewXxxProps` code lowering a conditional inline-object\n// spread condition on an `interface{}` prop (whose runtime value may be\n// a string, number, bool, \u2026), keeping the spread bag\'s inclusion test\n// faithful to JS rather than string-biased (#1752); and\n// - the `bf_truthy` template FuncMap entry (#2335), which coerces a\n// `bf_ternary` test to a real bool when it isn\'t already a comparison /\n// negation (`Ternary`\'s `cond` parameter is typed `bool`, unlike\n// `{{if}}`\'s built-in truthiness). Uniform across string/number/bool/nil,\n// so a `bf_ternary` test on any prop type can\'t hit a `bool`-vs-`string`\n// comparison error the way a string-only `ne <value> ""` would.\nfunc Truthy(v any) bool { return isTruthy(v) }\n\n// isTruthy mirrors JavaScript\'s `Boolean(x)` for the value shapes the\n// template path actually receives \u2014 nil / false / 0 / "" are falsy.\n// Other shapes (non-empty maps, slices, structs, true) are truthy, in\n// line with JS\'s "objects are truthy" rule.\nfunc isTruthy(v any) bool {\n if v == nil {\n return false\n }\n switch x := v.(type) {\n case bool:\n return x\n case string:\n return x != ""\n case int:\n return x != 0\n case int8, int16, int32, int64:\n return reflect.ValueOf(v).Int() != 0\n case uint, uint8, uint16, uint32, uint64:\n return reflect.ValueOf(v).Uint() != 0\n case float32:\n // JS `Boolean(NaN)` is false regardless of float width \u2014 the\n // float64 arm below was the only one checking IsNaN, which\n // diverged from JS for `float32` NaN inputs (Copilot review on\n // #1445). Widening to float64 for the IsNaN check keeps the\n // two branches in lock-step.\n return x != 0 && !math.IsNaN(float64(x))\n case float64:\n return x != 0 && !math.IsNaN(x)\n }\n return true\n}\n\n// =============================================================================\n// Higher-order Array Methods\n// =============================================================================\n\n// fieldValue projects item.field for the higher-order predicate\n// helpers. The field name arrives in JS casing; structs resolve via\n// the capitalized Go convention (FieldByName inside getFieldValue),\n// maps via getFieldValue\'s case-variant lookup \u2014 the same dual\n// support Sort/Reduce gained in #1487, extended here so JSON-decoded\n// data (map items) participates instead of being silently skipped.\n// nil-safe: missing fields and nil items project to nil.\nfunc fieldValue(item any, field string) any {\n return getFieldValue(item, capitalize(field))\n}\n\n// Every returns true if every item\'s field is truthy under JS\n// `Boolean(item.field)` semantics. Mirrors JavaScript\'s\n// Array.prototype.every(item => item.field) \u2014 including being\n// vacuously true for an empty receiver.\nfunc Every(items any, field string) bool {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return false\n }\n for i := 0; i < v.Len(); i++ {\n if !isTruthy(fieldValue(v.Index(i).Interface(), field)) {\n return false\n }\n }\n return true\n}\n\n// Some returns true if at least one item\'s field is truthy. Mirrors\n// JavaScript\'s Array.prototype.some(item => item.field).\nfunc Some(items any, field string) bool {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return false\n }\n for i := 0; i < v.Len(); i++ {\n if isTruthy(fieldValue(v.Index(i).Interface(), field)) {\n return true\n }\n }\n return false\n}\n\n// Filter returns items where item.field == value.\n// Mirrors JavaScript\'s Array.prototype.filter(item => item.field === value).\n// Returns []any to allow chaining with other bf_* functions.\nfunc Filter(items any, field string, value any) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n var result []any\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i).Interface()\n if reflect.DeepEqual(fieldValue(item, field), value) {\n result = append(result, item)\n }\n }\n return result\n}\n\n// Find returns the first item where item.field == value, or nil if not found.\n// Mirrors JavaScript\'s Array.prototype.find(item => item.field === value).\nfunc Find(items any, field string, value any) any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i).Interface()\n if reflect.DeepEqual(fieldValue(item, field), value) {\n return item\n }\n }\n return nil\n}\n\n// FindIndex returns the index of the first item where item.field == value, or -1.\n// Mirrors JavaScript\'s Array.prototype.findIndex(item => item.field === value).\nfunc FindIndex(items any, field string, value any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := 0; i < v.Len(); i++ {\n if reflect.DeepEqual(fieldValue(v.Index(i).Interface(), field), value) {\n return i\n }\n }\n return -1\n}\n\n// FindLast returns the last item where item.field == value, or nil if not found.\n// Mirrors JavaScript\'s Array.prototype.findLast(item => item.field === value).\nfunc FindLast(items any, field string, value any) any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n for i := v.Len() - 1; i >= 0; i-- {\n item := v.Index(i).Interface()\n if reflect.DeepEqual(fieldValue(item, field), value) {\n return item\n }\n }\n return nil\n}\n\n// FindLastIndex returns the index of the last item where item.field == value, or -1.\n// Mirrors JavaScript\'s Array.prototype.findLastIndex(item => item.field === value).\nfunc FindLastIndex(items any, field string, value any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := v.Len() - 1; i >= 0; i-- {\n if reflect.DeepEqual(fieldValue(v.Index(i).Interface(), field), value) {\n return i\n }\n }\n return -1\n}\n\n// sortKeySpec is one parsed comparison key. A simple comparator has\n// one; a `||`-chained multi-key comparator has several, applied in\n// order as tie-breakers.\ntype sortKeySpec struct {\n kind string // "self" | "field"\n name string // capitalised field name, or "" for "self"\n compareType string // "numeric" | "string" | "auto"\n direction string // "asc" | "desc"\n}\n\n// Sort returns a new stable-sorted slice. Lowers\n// `Array.prototype.sort` / `Array.prototype.toSorted` (#1448 Tier B).\n// Non-mutating \u2014 JS\'s mutate-vs-new distinction is moot in SSR\n// template context (templates render a snapshot).\n//\n// Call shape (the compiler emits one 4-string group per key):\n//\n// bf_sort <items> (<keyKind> <keyName> <compareType> <direction>)+\n//\n// keyKind: "self" | "field"\n// keyName: "" when keyKind == "self"; capitalised struct field\n// name (e.g. "Price") otherwise\n// compareType: "numeric" | "string" | "auto"\n// direction: "asc" | "desc"\n//\n// The groups cover the accepted comparator catalogue: `a.f - b.f`,\n// `a - b`, `a[.f].localeCompare(b[.f])`, and relational-ternary keys\n// (`a.f > b.f ? 1 : -1` \u2192 "auto"), each `||`-chainable for multi-key\n// tie-breaks. Anything outside refuses at compile time (BF101 from the\n// JSX compiler) and never reaches this helper.\n//\n// "auto" compares numerically when both projected keys parse as\n// numbers, else lexically \u2014 mirroring the Perl `bf->sort` helper\'s\n// `looks_like_number` rule so the two template adapters stay\n// byte-equal. This diverges from JS `<`/`>` only for numeric strings.\n//\n// A future `nulls` knob can extend the per-key group without rewriting\n// existing call sites \u2014 each key already projects before comparing.\nfunc Sort(items any, spec ...string) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n\n length := v.Len()\n if length == 0 {\n return []any{}\n }\n\n // Copy into a fresh []any so the sort is non-mutating regardless\n // of whether the receiver is `[]T` or `[]any`.\n result := make([]any, length)\n for i := 0; i < length; i++ {\n result[i] = v.Index(i).Interface()\n }\n\n keys := parseSortSpec(spec)\n sort.SliceStable(result, func(i, j int) bool {\n for _, k := range keys {\n ki := projectSortKey(result[i], k.kind, k.name)\n kj := projectSortKey(result[j], k.kind, k.name)\n c := compareSortKey(ki, kj, k.compareType)\n if c == 0 {\n continue // tie on this key \u2014 fall through to the next\n }\n if k.direction == "desc" {\n return c > 0\n }\n return c < 0\n }\n return false\n })\n\n return result\n}\n\n// parseSortSpec chunks the variadic operand list into 4-string key\n// groups. A trailing partial group (malformed emit) is ignored rather\n// than panicking \u2014 defensive, mirroring the helper\'s nil-safe stance.\nfunc parseSortSpec(spec []string) []sortKeySpec {\n var keys []sortKeySpec\n for i := 0; i+3 < len(spec); i += 4 {\n keys = append(keys, sortKeySpec{\n kind: spec[i],\n name: spec[i+1],\n compareType: spec[i+2],\n direction: spec[i+3],\n })\n }\n return keys\n}\n\n// compareSortKey returns -1 / 0 / 1 for two projected keys under the\n// given compare type (ascending orientation; the caller flips for\n// "desc"). "string" stringifies both (nil \u2192 "", matching the\n// documented `bf->string(undef) === ""` divergence). "auto" compares\n// numerically when both parse as numbers, else lexically.\nfunc compareSortKey(ki, kj any, compareType string) int {\n switch compareType {\n case "string":\n return strings.Compare(toString(ki), toString(kj))\n case "auto":\n ni, okI := toFloat64WithOK(ki)\n nj, okJ := toFloat64WithOK(kj)\n if okI && okJ {\n return cmpFloat(ni, nj)\n }\n return strings.Compare(toString(ki), toString(kj))\n default: // numeric\n return cmpFloat(toFloat64(ki), toFloat64(kj))\n }\n}\n\nfunc cmpFloat(a, b float64) int {\n if a < b {\n return -1\n }\n if a > b {\n return 1\n }\n return 0\n}\n\n// toFloat64WithOK reports a value\'s numeric float and whether it is\n// number-like. Genuine numeric kinds always qualify; strings qualify\n// when they parse as a float (so the "auto" compare path matches the\n// Perl `looks_like_number` rule). Everything else is non-numeric.\nfunc toFloat64WithOK(v any) (float64, bool) {\n switch n := v.(type) {\n case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:\n return toFloat64(v), true\n case string:\n f, err := strconv.ParseFloat(strings.TrimSpace(n), 64)\n if err != nil {\n return 0, false\n }\n return f, true\n default:\n return 0, false\n }\n}\n\n// projectSortKey reduces an item to the value the comparator\n// actually compares. For `keyKind == "field"` it reads the named\n// struct field; for `keyKind == "self"` (primitive arrays) it\n// returns the item unchanged.\nfunc projectSortKey(item any, keyKind, keyName string) any {\n if keyKind == "field" {\n return getFieldValue(item, keyName)\n }\n return item\n}\n\n// getFieldValue extracts a struct field value using reflection. For\n// map receivers it falls back to case-variant lookup so JSON-decoded\n// user data (`map[string]any{"price": 30}`) and PascalCase-emitted\n// test data both resolve under a single key name. (#1487)\nfunc getFieldValue(item any, field string) any {\n v := reflect.ValueOf(item)\n // Defensive IsNil guards mirror `SpreadAttrs` \u2014 keeps the helper\n // safe against typed-nil pointer / nil-interface items inside a\n // `[]any` so a single bad row doesn\'t crash the whole sort.\n if v.Kind() == reflect.Interface {\n if v.IsNil() {\n return nil\n }\n v = v.Elem()\n }\n if v.Kind() == reflect.Ptr {\n if v.IsNil() {\n return nil\n }\n v = v.Elem()\n }\n\n if v.Kind() == reflect.Map {\n keyType := v.Type().Key()\n if keyType.Kind() != reflect.String {\n return nil\n }\n // Convert the lookup string to the map\'s actual key type so\n // maps keyed by a named string type (`type Key string`) don\'t\n // panic with `value of type string is not assignable to type X`.\n lookup := func(s string) (any, bool) {\n k := reflect.ValueOf(s).Convert(keyType)\n if mv := v.MapIndex(k); mv.IsValid() {\n return mv.Interface(), true\n }\n return nil, false\n }\n if r, ok := lookup(field); ok {\n return r\n }\n if cap := capitalize(field); cap != field {\n if r, ok := lookup(cap); ok {\n return r\n }\n }\n if low := decapitalize(field); low != field {\n if r, ok := lookup(low); ok {\n return r\n }\n }\n // All-lowercase fallback: a Go-initialism field projects as an\n // all-caps key (`id` \u2192 `ID`), and `decapitalize("ID")` only\n // lowers the first char (`iD`), so the JS-keyed map ("id") still\n // misses. Try the fully-lowered key last to resolve it.\n if lower := strings.ToLower(field); lower != field && lower != decapitalize(field) {\n if r, ok := lookup(lower); ok {\n return r\n }\n }\n return nil\n }\n\n if v.Kind() != reflect.Struct {\n return nil\n }\n\n fieldVal := v.FieldByName(field)\n if !fieldVal.IsValid() {\n // Case-variant fallback: the evaluator carries the JS field name\n // (`id` / `url`) against a Go-capitalised struct field (`ID` / `URL`),\n // which exact `FieldByName` misses and the initialism rules can\'t be\n // reproduced char-for-char here. Match case-insensitively instead \u2014\n // `FieldByNameFunc` returns the zero Value (\u2192 nil) for an ambiguous\n // match, so it stays safe. The legacy bf_sort/bf_reduce pass an\n // already-capitalised name, so they hit the exact match above and never\n // reach this fallback.\n fieldVal = v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, field) })\n if !fieldVal.IsValid() {\n return nil\n }\n }\n return fieldVal.Interface()\n}\n\n// AsMap normalizes a dynamically-typed prop value into a\n// map[string]interface{} for object-valued context bindings\n// (`lowerProviderMapMemberValue`, go-template-adapter.ts). A caller-side\n// `interface{}` field can legally hold ANY string-keyed map kind \u2014 a Go\n// handler modelling `Record<string, string>` naturally passes\n// map[string]string \u2014 so a bare `.(map[string]interface{})` type assertion\n// would silently drop provided values (#2111 review). Returns nil (never an\n// empty map) when the value is absent \u2014 nil interface, typed-nil map or\n// pointer, or any non-map / non-string-keyed value \u2014 so the generated\n// `?? {}` fallback can distinguish "missing" (fall back) from "present but\n// empty" (use as-is). map[string]interface{} passes through without copying.\nfunc AsMap(v any) map[string]interface{} {\n if v == nil {\n return nil\n }\n if m, ok := v.(map[string]interface{}); ok {\n if m == nil {\n return nil\n }\n return m\n }\n rv := reflect.ValueOf(v)\n if rv.Kind() == reflect.Ptr {\n if rv.IsNil() {\n return nil\n }\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Map || rv.Type().Key().Kind() != reflect.String || rv.IsNil() {\n return nil\n }\n out := make(map[string]interface{}, rv.Len())\n iter := rv.MapRange()\n for iter.Next() {\n out[iter.Key().String()] = iter.Value().Interface()\n }\n return out\n}\n\n// capitalize uppercases the first character of a string.\nfunc capitalize(s string) string {\n if s == "" {\n return s\n }\n return strings.ToUpper(s[:1]) + s[1:]\n}\n\n// decapitalize lowercases the first character of a string. Used by\n// `getFieldValue`\'s map-receiver fallback when the projected key\n// name is PascalCase but the receiver carries lowercase JS-style\n// keys (the inverse of the `capitalize` lookup).\nfunc decapitalize(s string) string {\n if s == "" {\n return s\n }\n return strings.ToLower(s[:1]) + s[1:]\n}\n\n// Reduce folds an array into a scalar via the arithmetic-fold\n// catalogue (#1448 Tier C). It lowers `Array.prototype.reduce(fn, init)`\n// and `Array.prototype.reduceRight(fn, init)` for the shapes\n// `(acc, x) => acc <op> x` and `(acc, x) => acc <op> x.field`:\n//\n// bf_reduce <items> "<op>" "<keyKind>" "<keyName>" "<type>" "<init>" "<direction>"\n//\n// direction: "left" (reduce) | "right" (reduceRight). Only changes the\n// result for string concatenation; numeric folds commute.\n//\n// op: "+" | "*"\n// keyKind: "self" | "field"\n// keyName: "" when keyKind == "self"; capitalised struct field name\n// (e.g. "Duration") otherwise\n// type: "numeric" | "string"\n// init: the fold\'s start value \u2014 the compiler emits the *decoded*\n// seed, so numeric inits arrive as canonical decimal\n// (`1_000`/`0x10` already normalised to `1000`/`16`) that\n// ParseFloat accepts, and string inits arrive as escape-free\n// contents\n//\n// Numeric folds accumulate as float64; each projected key is read via\n// `toFloat64WithOK`, so numeric *strings* ("5" \u2192 5) parse and\n// non-numeric values fold as 0 \u2014 matching Perl\'s\n// `looks_like_number ? $n : 0` so the two template adapters stay\n// byte-equal. String folds concatenate (toString per projected key,\n// matching the documented `bf->string(undef) === ""` convention). The\n// init seeds the accumulator, so an empty receiver returns the init\n// unchanged \u2014 exactly like JS `reduce(fn, init)`. Anything outside the\n// catalogue refuses at compile time (BF101 from the JSX compiler) and\n// never reaches here.\n//\n// Two documented divergences from the JS / Hono path, both rare and\n// mirroring the `bf_sort` "auto" caveat:\n// - float64 stringification differs for sums whose binary expansion\n// isn\'t exact (e.g. 0.1 + 0.2);\n// - numeric-*string* keys fold numerically here, but JS `+`\n// string-concatenates once an operand is a string, so\n// numeric-string data can render differently under CSR.\n//\n// Genuine numbers \u2014 the common SSR case \u2014 agree across all three.\nfunc Reduce(items any, op, keyKind, keyName, typ, init, direction string) any {\n v := reflect.ValueOf(items)\n isSlice := v.Kind() == reflect.Slice || v.Kind() == reflect.Array\n\n // `direction == "right"` (reduceRight) folds right-to-left. Only\n // observable for string concatenation \u2014 numeric sum / product are\n // commutative, so the order doesn\'t change the result there. Build a\n // start/stop/step triple so both folds share one loop shape.\n start, stop, step := 0, 0, 1\n if isSlice {\n stop = v.Len()\n if direction == "right" {\n start, stop, step = v.Len()-1, -1, -1\n }\n }\n\n if typ == "string" {\n acc := init\n if isSlice {\n for i := start; i != stop; i += step {\n key := projectSortKey(v.Index(i).Interface(), keyKind, keyName)\n acc += toString(key)\n }\n }\n return acc\n }\n\n // numeric fold\n acc, _ := strconv.ParseFloat(strings.TrimSpace(init), 64)\n if isSlice {\n for i := start; i != stop; i += step {\n key := projectSortKey(v.Index(i).Interface(), keyKind, keyName)\n // `toFloat64WithOK` parses numeric *strings* ("5" \u2192 5) and\n // returns 0 for non-numeric values \u2014 mirroring Perl\'s\n // `looks_like_number ? $n : 0` so numeric-string data folds\n // byte-equal across adapters (the same rule `bf_sort`\'s\n // "auto" compare uses). Plain `toFloat64` would zero "5".\n n, _ := toFloat64WithOK(key)\n if op == "*" {\n acc *= n\n } else {\n acc += n\n }\n }\n }\n return acc\n}\n\n// =============================================================================\n// HTML/Template Helpers\n// =============================================================================\n\n// Comment returns an HTML comment string for hydration markers.\n// The "bf-" prefix is automatically added.\nfunc Comment(content string) template.HTML {\n return template.HTML("<!--bf-" + content + "-->")\n}\n\n// TextStart returns an HTML comment start marker for reactive text expressions.\n// Format: <!--bf:slotId-->\nfunc TextStart(slotId string) template.HTML {\n return template.HTML("<!--bf:" + slotId + "-->")\n}\n\n// TextEnd returns an HTML comment end marker for reactive text expressions.\n// Format: <!--/-->\nfunc TextEnd() template.HTML {\n return "<!--/-->"\n}\n\n// ScopeComment emits a fragment-rooted scope marker. See spec/compiler.md\n// "Slot identity" for the wire format. Loud-fails on marshal errors\n// (same policy as JSON / BfPropsAttr).\nfunc ScopeComment(props interface{}) (template.HTML, error) {\n scopeID := getStringField(props, "ScopeID")\n hostSegment := ""\n if host := getStringField(props, "BfParent"); host != "" {\n mount := getStringField(props, "BfMount")\n hostSegment = "|h=" + host + "|m=" + mount\n }\n propsJSON := ""\n if getBoolField(props, "BfIsRoot") {\n pJSON, err := json.Marshal(props)\n if err != nil {\n return "", err\n }\n propsJSON = "|" + string(pJSON)\n }\n return template.HTML("<!--bf-scope:" + scopeID + hostSegment + propsJSON + "-->"), nil\n}\n\n// ScopeCommentEnd emits the paired end marker for a fragment-rooted scope\n// (#2289): a fragment root has no single wrapping element to bound the\n// client\'s scope query, so the range leaks onto later siblings without an\n// explicit terminator. Carries only the scope id \u2014 no `|h=`/`|m=`/props\n// segment, unlike ScopeComment \u2014 since the client only needs it to confirm\n// the range closes on the matching scope (getCommentScopeBoundary in\n// packages/client/src/runtime/scope.ts).\nfunc ScopeCommentEnd(props interface{}) template.HTML {\n scopeID := getStringField(props, "ScopeID")\n return template.HTML("<!--bf-/scope:" + scopeID + "-->")\n}\n\n// TemplateFuncMap returns the helpers that need access to the executing\n// template set itself, closed over the *template.Template the component\n// defines are parsed into. Register it alongside FuncMap BEFORE parsing:\n//\n// t := template.New("")\n// t.Funcs(bf.FuncMap()).Funcs(bf.TemplateFuncMap(t))\n// template.Must(t.Parse(src))\n//\n// bf_tmpl executes a named define from the same set and returns its\n// output \u2014 used for the per-call-site children defines the Go adapter\n// emits when JSX children passed to an imported component contain\n// template actions (nested components, dynamic text) and therefore\n// cannot be baked to a static HTML string (#1896). Reentrant execution\n// of an html/template set from inside a FuncMap function is safe: the\n// escape analysis over every define completes before the outer\n// Execute begins evaluating.\nfunc TemplateFuncMap(t *template.Template) template.FuncMap {\n return template.FuncMap{\n "bf_tmpl": func(name string, data interface{}) (template.HTML, error) {\n var buf bytes.Buffer\n if err := t.ExecuteTemplate(&buf, name, data); err != nil {\n return "", err\n }\n return template.HTML(buf.String()), nil\n },\n }\n}\n\n// WithChildren returns a shallow copy of a component Props struct with its\n// Children field replaced by the given pre-rendered fragment (#1896). The\n// props value stays by-value semantics: callers\' originals are untouched.\n// A props type without a Children field passes through unchanged \u2014 the\n// child template then simply has no children to render, matching the\n// pre-#1896 behaviour.\nfunc WithChildren(props interface{}, children template.HTML) (interface{}, error) {\n v := reflect.ValueOf(props)\n if v.Kind() == reflect.Ptr {\n v = v.Elem()\n }\n if v.Kind() != reflect.Struct {\n return props, nil\n }\n field := v.FieldByName("Children")\n if !field.IsValid() {\n return props, nil\n }\n copyPtr := reflect.New(v.Type())\n copyPtr.Elem().Set(v)\n target := copyPtr.Elem().FieldByName("Children")\n switch {\n case target.Kind() == reflect.Interface:\n target.Set(reflect.ValueOf(children))\n case target.Kind() == reflect.String:\n // Covers both `string` and `template.HTML`-typed fields.\n target.SetString(string(children))\n default:\n return props, fmt.Errorf("bf_with_children: unsupported Children field type %s", target.Type())\n }\n return copyPtr.Elem().Interface(), nil\n}\n\n// PortalHTML parses and executes a template string with the provided data.\n// Used for rendering dynamic portal content where the template string\n// contains Go template expressions (e.g., {{if .Open}}open{{end}}).\n//\n// The template string is parsed fresh each time to support dynamic content.\n// Standard Go template functions (if, range, eq, etc.) are available.\nfunc PortalHTML(data interface{}, tmplStr string) template.HTML {\n // Create a new template with the FuncMap for custom functions\n t, err := template.New("portal").Funcs(FuncMap()).Parse(tmplStr)\n if err != nil {\n // Return error message as HTML comment for debugging\n return template.HTML("<!-- bfPortalHTML error: " + err.Error() + " -->")\n }\n\n var buf bytes.Buffer\n if err := t.Execute(&buf, data); err != nil {\n return template.HTML("<!-- bfPortalHTML exec error: " + err.Error() + " -->")\n }\n\n return template.HTML(buf.String())\n}\n\n// =============================================================================\n// Portal Collection\n// =============================================================================\n\n// PortalContent represents a single portal\'s content to be rendered at body end.\ntype PortalContent struct {\n ID string // Unique portal ID for hydration matching\n OwnerID string // Owner scope ID for find() support\n Content template.HTML // Portal HTML content\n}\n\n// PortalCollector collects portal content during template rendering.\n// Portal content is rendered at </body> to avoid z-index issues.\ntype PortalCollector struct {\n portals []PortalContent\n counter int\n}\n\n// NewPortalCollector creates a new PortalCollector.\nfunc NewPortalCollector() *PortalCollector {\n return &PortalCollector{\n portals: []PortalContent{},\n counter: 0,\n }\n}\n\n// Add registers portal content to be rendered at body end.\nfunc (pc *PortalCollector) Add(ownerID string, content template.HTML) string {\n pc.counter++\n id := "bf-portal-" + strconv.Itoa(pc.counter)\n pc.portals = append(pc.portals, PortalContent{\n ID: id,\n OwnerID: ownerID,\n Content: content,\n })\n return "" // Return empty string for template use\n}\n\n// Render outputs all collected portals as HTML.\n// Each portal is wrapped in a div with bf-pi (portal ID) and bf-po (portal owner).\nfunc (pc *PortalCollector) Render() template.HTML {\n if pc == nil || len(pc.portals) == 0 {\n return ""\n }\n var buf strings.Builder\n for _, p := range pc.portals {\n buf.WriteString(`<div bf-pi="`)\n buf.WriteString(p.ID)\n buf.WriteString(`" bf-po="`)\n buf.WriteString(p.OwnerID)\n buf.WriteString(`">`)\n buf.WriteString(string(p.Content))\n buf.WriteString("</div>\\n")\n }\n return template.HTML(buf.String())\n}\n\n// =============================================================================\n// Script Collection\n// =============================================================================\n\n// ScriptCollector collects client scripts with deduplication.\n// It preserves insertion order for deterministic output.\ntype ScriptCollector struct {\n scripts map[string]bool\n order []string\n}\n\n// NewScriptCollector creates a new ScriptCollector.\nfunc NewScriptCollector() *ScriptCollector {\n return &ScriptCollector{\n scripts: make(map[string]bool),\n order: []string{},\n }\n}\n\n// Register adds a script source to the collection.\n// Duplicate scripts are ignored (only first registration counts).\nfunc (sc *ScriptCollector) Register(src string) string {\n if sc.scripts[src] {\n return "" // Already registered\n }\n sc.scripts[src] = true\n sc.order = append(sc.order, src)\n return "" // Return empty string for template use\n}\n\n// Scripts returns all registered scripts in insertion order.\nfunc (sc *ScriptCollector) Scripts() []string {\n return sc.order\n}\n\n// BfScripts generates script tags for all registered scripts.\n// Returns HTML safe for embedding in templates.\nfunc BfScripts(collector *ScriptCollector) template.HTML {\n if collector == nil {\n return ""\n }\n var result strings.Builder\n for _, src := range collector.Scripts() {\n result.WriteString(`<script type="module" src="`)\n result.WriteString(src)\n result.WriteString(`"></script>`)\n result.WriteString("\\n")\n }\n return template.HTML(result.String())\n}\n\n// =============================================================================\n// Component Renderer\n// =============================================================================\n\n// RenderContext contains all data needed to render a component page.\n// The layout function receives this context to build the final HTML.\ntype RenderContext struct {\n // ComponentName is the template name being rendered\n ComponentName string\n\n // Props is the component props (for layout to access if needed)\n Props interface{}\n\n // ComponentHTML is the rendered component template output\n ComponentHTML template.HTML\n\n // Portals contains collected portal content to render at body end\n Portals template.HTML\n\n // Scripts contains the collected JS script tags\n Scripts template.HTML\n\n // Title is the page title (defaults to "{ComponentName} - BarefootJS")\n Title string\n\n // Heading is the page heading. Empty string means no heading.\n Heading string\n\n // Extra holds additional user-defined data for the layout\n Extra map[string]interface{}\n}\n\n// LayoutFunc renders the final HTML page given the render context.\ntype LayoutFunc func(ctx *RenderContext) string\n\n// Renderer renders BarefootJS components with a customizable layout.\ntype Renderer struct {\n templates *template.Template\n layout LayoutFunc\n}\n\n// NewRenderer creates a Renderer with the given templates and layout function.\n//\n// Example usage:\n//\n// renderer := bf.NewRenderer(templates, func(ctx *bf.RenderContext) string {\n// return fmt.Sprintf(`<!DOCTYPE html>\n// <html>\n// <head><title>%s</title></head>\n// <body>%s%s</body>\n// </html>`, ctx.Title, ctx.ComponentHTML, ctx.Scripts)\n// })\nfunc NewRenderer(tmpl *template.Template, layout LayoutFunc) *Renderer {\n return &Renderer{\n templates: tmpl,\n layout: layout,\n }\n}\n\n// RenderOptions configures a single render call.\ntype RenderOptions struct {\n // ComponentName is the template name to render (required)\n ComponentName string\n\n // Props is the component props (must be a pointer to struct with Scripts field)\n Props interface{}\n\n // Title is the page title. If empty, defaults to "{ComponentName} - BarefootJS"\n Title string\n\n // Heading is the page heading. If empty, no heading is shown.\n Heading string\n\n // Extra holds additional data to pass to the layout\n Extra map[string]interface{}\n}\n\n// Render renders a component to a full HTML page using the configured layout.\n// Child component props are automatically detected (any slice field with ScopeID/Scripts).\n// renderTemplateErrorPanel formats a Go template execution error into a\n// fragment of HTML that\'s visible in the browser. The panel is\n// HTML-escaped so a faulty template name (anything from `template:\n// "..."`) can\'t smuggle markup back into the page. Keep the styling\n// inline so the panel surfaces even when the project\'s CSS hasn\'t\n// loaded yet (e.g. the failure aborted before the stylesheet links\n// emitted).\n//\n// Surfaced for the #1442 echo repro: a template referencing\n// `.Todo.Done` (instead of the range dot\'s `.Done`) used to fail\n// silently \u2014 Go\'s html/template aborted mid-stream, the partial body\n// flushed as a 200, and the user saw a truncated list with no console\n// signal. With this panel they get the template name, the error\n// message, and a "what to look at" hint inline.\nfunc renderTemplateErrorPanel(componentName string, err error) string {\n return `<div style="margin:1em 0;padding:1em;border:2px solid #d33;background:#fff5f5;color:#900;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;line-height:1.5"><strong style="display:block;margin-bottom:.5em">Template error in <code>` +\n template.HTMLEscapeString(componentName) +\n `</code></strong><pre style="margin:0;white-space:pre-wrap;word-break:break-word">` +\n template.HTMLEscapeString(err.Error()) +\n `</pre><div style="margin-top:.75em;font-size:12px;opacity:.7">Common cause: a JSX expression referenced a name the adapter could not resolve to a struct field. Open the matching <code>dist/templates/*.tmpl</code> for the unresolved reference, then fix the source component.</div></div>`\n}\n\n// renderComponentInto wires a component\'s props (script/portal collectors,\n// child-slot scope ids + hydration, root marking) against the PROVIDED\n// collectors and returns just the component\'s HTML \u2014 no layout. Both Render\n// (one fresh collector pair per page) and RenderFragment (a shared pair across\n// several islands) funnel through here so their wiring stays identical.\nfunc (r *Renderer) renderComponentInto(opts RenderOptions, scriptCollector *ScriptCollector, portalCollector *PortalCollector) template.HTML {\n // Inject the shared collectors into the props.\n setScriptsField(opts.Props, scriptCollector)\n setPortalsField(opts.Props, portalCollector)\n\n // Auto-detect and process child component props (slices)\n childSlices := findChildComponentSlices(opts.Props)\n for _, slice := range childSlices {\n setScopeIDsOnSlice(slice)\n setScriptsOnSlice(slice, scriptCollector)\n setPortalsOnSlice(slice, portalCollector)\n setBoolOnSlice(slice, "BfIsChild", true)\n }\n\n // Auto-detect and process single child component props\n singleChildren := findSingleChildComponents(opts.Props)\n for _, child := range singleChildren {\n setScopeIDOnSingle(child)\n setScriptsOnSingle(child, scriptCollector)\n setPortalsOnSingle(child, portalCollector)\n setBoolField(child, "BfIsChild", true)\n }\n\n // Mark the root component so BfPropsAttr emits bf-p only for it\n setBoolField(opts.Props, "BfIsRoot", true)\n\n // Render the component template.\n //\n // Errors here are NOT silently dropped. The original implementation\n // ignored the return value of `ExecuteTemplate`, which masked a real\n // onboarding failure mode: a template referencing a non-existent\n // field (`.Todo.Done` instead of the range dot\'s `.Done`) caused\n // html/template to abort mid-stream, the partial output got\n // returned, and the HTTP server happily flushed a 200 with a\n // truncated body. No error log, no signal \u2014 the user just saw a\n // blank list (#1442 echo TodoApp repro).\n //\n // Now we capture the error and replace the partial output with a\n // visible inline panel (dev mode) or a fenced error comment\n // (production), so the cause is on-screen and grep-able in logs.\n // Either way the renderer also writes to stderr so structured log\n // aggregators see it.\n var componentBuf strings.Builder\n if err := r.templates.ExecuteTemplate(&componentBuf, opts.ComponentName, opts.Props); err != nil {\n fmt.Fprintf(os.Stderr, "barefoot: template %q failed to render: %v\\n", opts.ComponentName, err)\n // Preserve whatever the template did manage to emit before\n // failing (Go\'s text/template flushes incrementally), but\n // follow it with a clearly-marked error block so the user\n // notices something is wrong instead of seeing a silent\n // truncation.\n componentBuf.WriteString(renderTemplateErrorPanel(opts.ComponentName, err))\n }\n\n return template.HTML(componentBuf.String())\n}\n\nfunc (r *Renderer) Render(opts RenderOptions) string {\n // One script + portal collector pair for the whole page.\n scriptCollector := NewScriptCollector()\n portalCollector := NewPortalCollector()\n\n componentHTML := r.renderComponentInto(opts, scriptCollector, portalCollector)\n\n // Determine title (default: "{ComponentName} - BarefootJS")\n title := opts.Title\n if title == "" {\n title = opts.ComponentName + " - BarefootJS"\n }\n\n // Heading (empty means no heading)\n heading := opts.Heading\n\n // Build render context\n ctx := &RenderContext{\n ComponentName: opts.ComponentName,\n Props: opts.Props,\n ComponentHTML: componentHTML,\n Portals: portalCollector.Render(),\n Scripts: BfScripts(scriptCollector),\n Title: title,\n Heading: heading,\n Extra: opts.Extra,\n }\n\n return r.layout(ctx)\n}\n\n// RenderFragment renders a single island subtree into the caller-provided\n// script and portal collectors and returns just its HTML \u2014 no page layout.\n//\n// It exists for hand-authored "region shell" pages (the `@barefootjs/router`\n// showcase): a layout that places several independent islands \u2014 e.g. a header\n// ThemeToggle, an `<aside bf-region>` Sidebar, and a `<PageShell>` wrapping the\n// route content \u2014 must collect ALL their scripts and portals into ONE place so\n// the runtime (`barefoot.js`) and each island\'s client JS are emitted exactly\n// once and share a single reactive instance. Render each island with the same\n// collectors, splice the returned HTML into the shell, then emit\n// `BfScripts(sc)` and `pc.Render()` once at the end of the document.\n//\n// Each fragment is treated as its own root (it emits `bf-p` like any top-level\n// island); nested children declared in its props are wired as children, exactly\n// as in Render.\nfunc (r *Renderer) RenderFragment(opts RenderOptions, scriptCollector *ScriptCollector, portalCollector *PortalCollector) template.HTML {\n return r.renderComponentInto(opts, scriptCollector, portalCollector)\n}\n\n// setScriptsField sets the Scripts field on a struct using reflection.\nfunc setScriptsField(v interface{}, collector *ScriptCollector) {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return\n }\n field := val.FieldByName("Scripts")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n}\n\n// setPortalsField sets the Portals field on a struct using reflection.\nfunc setPortalsField(v interface{}, collector *PortalCollector) {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return\n }\n field := val.FieldByName("Portals")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n}\n\n// getStringField extracts a string field from a struct using reflection.\nfunc setBoolField(v interface{}, fieldName string, val bool) {\n rv := reflect.ValueOf(v)\n if rv.Kind() == reflect.Ptr {\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Struct {\n return\n }\n field := rv.FieldByName(fieldName)\n if field.IsValid() && field.CanSet() && field.Kind() == reflect.Bool {\n field.SetBool(val)\n }\n}\n\nfunc getBoolField(v interface{}, fieldName string) bool {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return false\n }\n field := val.FieldByName(fieldName)\n if !field.IsValid() || field.Kind() != reflect.Bool {\n return false\n }\n return field.Bool()\n}\n\nfunc getStringField(v interface{}, fieldName string) string {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return ""\n }\n field := val.FieldByName(fieldName)\n if !field.IsValid() || field.Kind() != reflect.String {\n return ""\n }\n return field.String()\n}\n\n// scopeIDChars is the alphabet for auto-generated ScopeID suffixes. It\n// mirrors the `randomID` helper the go-template adapter emits into the\n// generated New<Component>Props constructors so runtime-assigned and\n// constructor-assigned ids are indistinguishable.\nconst scopeIDChars = "abcdefghijklmnopqrstuvwxyz0123456789"\n\n// randomScopeSuffix returns a random lowercase-alphanumeric string of\n// length n. math/rand (auto-seeded since Go 1.20) is sufficient here: the\n// suffix only needs to be unique enough to keep a page\'s bf-s scope ids\n// from colliding, not cryptographically unpredictable.\nfunc randomScopeSuffix(n int) string {\n b := make([]byte, n)\n for i := range b {\n b[i] = scopeIDChars[rand.Intn(len(scopeIDChars))]\n }\n return string(b)\n}\n\n// scopeIDPrefix derives the human-readable ScopeID prefix from a child\n// component\'s type, e.g. `TodoItemProps` \u2192 `TodoItem`. Matches the\n// `"<Component>_" + randomID(6)` shape the generated constructors use.\nfunc scopeIDPrefix(t reflect.Type) string {\n for t.Kind() == reflect.Ptr {\n t = t.Elem()\n }\n return strings.TrimSuffix(t.Name(), "Props")\n}\n\n// assignScopeID fills a child component\'s ScopeID with a generated id when\n// the caller left it empty, so application code doesn\'t have to mint scope\n// ids by hand (the parent\'s New<Component>Props constructor does the same\n// for components built through it). A non-empty ScopeID is left untouched,\n// so callers can still pin a stable id when they need one.\nfunc assignScopeID(structVal reflect.Value, prefix string) {\n field := structVal.FieldByName("ScopeID")\n if !field.IsValid() || !field.CanSet() || field.Kind() != reflect.String {\n return\n }\n if field.String() != "" {\n return\n }\n id := randomScopeSuffix(6)\n if prefix != "" {\n id = prefix + "_" + id\n }\n field.SetString(id)\n}\n\n// setScopeIDsOnSlice assigns a generated ScopeID to every child in a slice\n// whose ScopeID is empty.\nfunc setScopeIDsOnSlice(slice interface{}) {\n v := reflect.ValueOf(slice)\n if v.Kind() != reflect.Slice {\n return\n }\n prefix := scopeIDPrefix(v.Type().Elem())\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i)\n if item.Kind() == reflect.Ptr {\n if item.IsNil() {\n continue\n }\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n assignScopeID(item, prefix)\n }\n }\n}\n\n// setScopeIDOnSingle assigns a generated ScopeID to a single child\n// component when its ScopeID is empty.\nfunc setScopeIDOnSingle(child interface{}) {\n v := reflect.ValueOf(child)\n if v.Kind() == reflect.Ptr {\n v = v.Elem()\n }\n if v.Kind() != reflect.Struct {\n return\n }\n assignScopeID(v, scopeIDPrefix(v.Type()))\n}\n\n// findChildComponentSlices finds slice fields containing child component props.\n// Child props are identified by having ScopeID and Scripts fields.\nfunc findChildComponentSlices(props interface{}) []interface{} {\n var result []interface{}\n\n val := reflect.ValueOf(props)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return result\n }\n\n for i := 0; i < val.NumField(); i++ {\n field := val.Field(i)\n if field.Kind() != reflect.Slice || field.Len() == 0 {\n continue\n }\n\n elem := field.Index(0)\n if elem.Kind() == reflect.Ptr {\n elem = elem.Elem()\n }\n if elem.Kind() != reflect.Struct {\n continue\n }\n\n hasScopeID := elem.FieldByName("ScopeID").IsValid()\n hasScripts := elem.FieldByName("Scripts").IsValid()\n\n if hasScopeID && hasScripts {\n result = append(result, field.Interface())\n }\n }\n\n return result\n}\n\n// setScriptsOnSlice sets Scripts on all items in a slice.\nfunc setScriptsOnSlice(slice interface{}, collector *ScriptCollector) {\n val := reflect.ValueOf(slice)\n if val.Kind() != reflect.Slice {\n return\n }\n for i := 0; i < val.Len(); i++ {\n item := val.Index(i)\n if item.Kind() == reflect.Ptr {\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n field := item.FieldByName("Scripts")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n }\n}\n\n// setBoolOnSlice sets a bool field on all items in a slice.\nfunc setBoolOnSlice(slice interface{}, fieldName string, val bool) {\n v := reflect.ValueOf(slice)\n if v.Kind() != reflect.Slice {\n return\n }\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i)\n if item.Kind() == reflect.Ptr {\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n field := item.FieldByName(fieldName)\n if field.IsValid() && field.CanSet() && field.Kind() == reflect.Bool {\n field.SetBool(val)\n }\n }\n }\n}\n\n// setPortalsOnSlice sets Portals on all items in a slice.\nfunc setPortalsOnSlice(slice interface{}, collector *PortalCollector) {\n val := reflect.ValueOf(slice)\n if val.Kind() != reflect.Slice {\n return\n }\n for i := 0; i < val.Len(); i++ {\n item := val.Index(i)\n if item.Kind() == reflect.Ptr {\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n field := item.FieldByName("Portals")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n }\n}\n\n// findSingleChildComponents finds single struct fields containing child component props.\n// Child props are identified by having ScopeID and Scripts fields.\nfunc findSingleChildComponents(props interface{}) []interface{} {\n var result []interface{}\n\n val := reflect.ValueOf(props)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return result\n }\n\n for i := 0; i < val.NumField(); i++ {\n field := val.Field(i)\n\n // Handle pointer to struct\n if field.Kind() == reflect.Ptr {\n if field.IsNil() {\n continue\n }\n field = field.Elem()\n }\n\n // Skip non-struct fields (slices handled by findChildComponentSlices)\n if field.Kind() != reflect.Struct {\n continue\n }\n\n hasScopeID := field.FieldByName("ScopeID").IsValid()\n hasScripts := field.FieldByName("Scripts").IsValid()\n\n if hasScopeID && hasScripts {\n result = append(result, field.Addr().Interface())\n }\n }\n\n return result\n}\n\n// setScriptsOnSingle sets Scripts on a single struct child component.\nfunc setScriptsOnSingle(child interface{}, collector *ScriptCollector) {\n val := reflect.ValueOf(child)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() == reflect.Struct {\n field := val.FieldByName("Scripts")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n}\n\n// setPortalsOnSingle sets Portals on a single struct child component.\nfunc setPortalsOnSingle(child interface{}, collector *PortalCollector) {\n val := reflect.ValueOf(child)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() == reflect.Struct {\n field := val.FieldByName("Portals")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n}\n\n// Nullish implements JS `??` for template use (`bf_nullish`, #2248): returns\n// fallback iff v is nil (untyped nil or a nil pointer/map/slice boxed in the\n// interface), otherwise v \u2014 so present-but-falsy `""`/`0`/`false` are KEPT,\n// unlike the truthiness-based template `or`.\nfunc Nullish(v, fallback any) any {\n if v == nil {\n return fallback\n }\n rv := reflect.ValueOf(v)\n switch rv.Kind() {\n case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface, reflect.Func, reflect.Chan:\n if rv.IsNil() {\n return fallback\n }\n }\n return v\n}\n\n// ToInt exposes the runtime\'s numeric coercion for generated constructors\n// (#2248): a nillable-lowered numeric prop arrives as `interface{}`, and an\n// untyped Go literal (`Size: 3`) boxes as int even when the prop is\n// float64-shaped \u2014 a direct type assertion would panic where JS accepts the\n// number. Non-numeric values coerce to 0, matching the helpers\' behaviour.\nfunc ToInt(v any) int { return toInt(v) }\n\n// ToFloat64 is ToInt\'s float64 counterpart \u2014 see ToInt.\nfunc ToFloat64(v any) float64 { return toFloat64(v) }\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\nfunc toFloat64(v any) float64 {\n switch n := v.(type) {\n case int:\n return float64(n)\n case int8:\n return float64(n)\n case int16:\n return float64(n)\n case int32:\n return float64(n)\n case int64:\n return float64(n)\n case uint:\n return float64(n)\n case uint8:\n return float64(n)\n case uint16:\n return float64(n)\n case uint32:\n return float64(n)\n case uint64:\n return float64(n)\n case float32:\n return float64(n)\n case float64:\n return n\n default:\n return 0\n }\n}\n\nfunc toInt(v any) int {\n switch n := v.(type) {\n case int:\n return n\n case int8:\n return int(n)\n case int16:\n return int(n)\n case int32:\n return int(n)\n case int64:\n return int(n)\n case uint:\n return int(n)\n case uint8:\n return int(n)\n case uint16:\n return int(n)\n case uint32:\n return int(n)\n case uint64:\n return int(n)\n case float32:\n return int(n)\n case float64:\n return int(n)\n default:\n return 0\n }\n}\n\nfunc isIntLike(v any) bool {\n switch v.(type) {\n case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:\n return true\n default:\n return false\n }\n}\n\nfunc toString(v any) string {\n switch s := v.(type) {\n case string:\n return s\n case int:\n return strconv.Itoa(s)\n case int64:\n return strconv.FormatInt(s, 10)\n case float64:\n return strconv.FormatFloat(s, \'f\', -1, 64)\n case bool:\n return strconv.FormatBool(s)\n default:\n rv := reflect.ValueOf(v)\n if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {\n // JS `Array.prototype.toString` is `this.join(\',\')`, applied\n // recursively \u2014 a nested array element stringifies the same\n // way rather than via Go\'s `%v`. Reached via `Join`/`ConcatStr`\n // on an element that is itself an array (e.g. `.flat(0)`\'s\n // shallow copy joined afterwards, #2262).\n parts := make([]string, rv.Len())\n for i := 0; i < rv.Len(); i++ {\n parts[i] = toString(rv.Index(i).Interface())\n }\n return strings.Join(parts, ",")\n }\n return ""\n }\n}\n\n// =============================================================================\n// searchParams() \u2014 request-scoped environment signal (router v0.5, #1922)\n// =============================================================================\n\n// SearchParams is the SSR view of the request query string behind the\n// reactive searchParams() environment signal. The route handler builds it\n// from the request URL and assigns it to the component\'s SearchParams input\n// field; the generated template reads it via `.SearchParams.Get "key"`.\n//\n// The zero value is an empty query (url.Values.Get tolerates a nil map), so a\n// render with no request query \u2014 e.g. the adapter conformance harness, which\n// issues no query string \u2014 resolves every key to "", which the template\'s\n// `or`/`??` fallback turns into the author\'s default.\ntype SearchParams struct {\n values url.Values\n}\n\n// NewSearchParams parses a raw query string (with or without a leading "?")\n// into a SearchParams. A malformed query yields an empty set rather than an\n// error, mirroring the browser\'s URLSearchParams, which never throws on junk.\n//\n// Typical handler use (net/http):\n//\n// in := MyComponentInput{SearchParams: bf.NewSearchParams(r.URL.RawQuery)}\nfunc NewSearchParams(raw string) SearchParams {\n raw = strings.TrimPrefix(raw, "?")\n values, err := url.ParseQuery(raw)\n if err != nil {\n values = url.Values{}\n }\n return SearchParams{values: values}\n}\n\n// Get returns the first value associated with key, or "" when the key is\n// absent. This mirrors url.Values.Get, which also returns "" for a\n// present-but-empty value (`?sort=`). Safe on the zero value (nil map).\n//\n// This is not byte-for-byte URLSearchParams.get under the template\'s `??`\n// lowering. JS distinguishes absent (`null`) from present-but-empty (`""`):\n// `null ?? d` yields the default, but `"" ?? d` keeps the empty string. The\n// Go adapter lowers `??` to the `or` builtin \u2014 Go templates have no\n// null-coalescing operator \u2014 so here BOTH an absent key and a present-but-\n// empty value fall back to the author\'s default. The conformance fixture only\n// exercises the absent-key default, where the two runtimes agree; the\n// empty-string divergence is the same general `?? \u2192 or` limitation that\n// applies to any `x ?? default` the Go adapter lowers.\nfunc (s SearchParams) Get(key string) string {\n return s.values.Get(key)\n}\n';
32508
+ bfGoSource = '// Package bf provides runtime helper functions for BarefootJS Go templates.\n// These functions mirror JavaScript behavior for consistent SSR output.\npackage bf\n\nimport (\n "bytes"\n "encoding/json"\n "fmt"\n "html/template"\n "math"\n "math/rand"\n "net/url"\n "os"\n "reflect"\n "regexp"\n "sort"\n "strconv"\n "strings"\n "time"\n "unicode"\n "unicode/utf8"\n)\n\n// FuncMap returns a template.FuncMap with all BarefootJS helper functions.\n// Usage:\n//\n// tmpl := template.New("").Funcs(bf.FuncMap())\nfunc FuncMap() template.FuncMap {\n return template.FuncMap{\n // Nullish coalescing (#2248): JS `??` semantics \u2014 fall back only on\n // nil, keeping present-but-falsy values (`""`, `0`, `false`) that\n // Go\'s truthiness-based `or` would replace.\n "bf_nullish": Nullish,\n\n // Arithmetic\n "bf_add": Add,\n "bf_concat_str": ConcatStr,\n "bf_sub": Sub,\n "bf_mul": Mul,\n "bf_div": Div,\n "bf_mod": Mod,\n "bf_neg": Neg,\n "bf_min": Min,\n "bf_max": Max,\n\n // String\n "bf_lower": Lower,\n "bf_upper": Upper,\n "bf_trim": Trim,\n "bf_trim_start": TrimStart,\n "bf_trim_end": TrimEnd,\n "bf_contains": Contains,\n "bf_join": Join,\n "bf_split": Split,\n "bf_starts_with": StartsWith,\n "bf_ends_with": EndsWith,\n "bf_replace": Replace,\n "bf_replace_all": ReplaceAll,\n "bf_repeat": Repeat,\n "bf_pad_start": PadStart,\n "bf_pad_end": PadEnd,\n "bf_string": String,\n "bf_raw_html": RawHTML,\n "bf_ternary": Ternary,\n "bf_truthy": Truthy,\n\n // URL query builder (#1897 PostList href helpers): conditional\n // (include, key, value) triples \u2192 "base?k=v&\u2026", mirroring a\n // URLSearchParams builder with guarded `.set()` calls.\n "bf_query": Query,\n\n // Date method lowering (#2274, spec entry "date"): the lowering\n // target for a zero-arg call on a Date-typed prop.\n "bf_date": Date,\n\n // formatDate(date, pattern, tz) lowering (#2324, spec entry\n // "format_date"): the total, locale-free date-pattern formatter \u2014\n // see FormatDate\'s docstring for the full contract.\n "bf_format_date": FormatDate,\n\n // JSON / numeric primitives \u2014 JS-compat callees registered on\n // the Go adapter\'s `templatePrimitives` map (#1188).\n "bf_json": JSON,\n "bf_number": Number,\n "bf_floor": Floor,\n "bf_ceil": Ceil,\n "bf_round": Round,\n "bf_abs": Abs,\n "bf_to_fixed": ToFixed,\n\n // Array/Slice\n "bf_len": Len,\n "bf_length": Length,\n "bf_is_element": IsValidElement,\n "bf_style_object": StyleObjectToCSS,\n "bf_at": At,\n "bf_includes": Includes,\n "bf_index_of": IndexOf,\n "bf_last_index_of": LastIndexOf,\n "bf_concat": Concat,\n "bf_slice": Slice,\n "bf_reverse": Reverse,\n "bf_flat": Flat,\n "bf_flat_dynamic": FlatDynamicDepth,\n "bf_flat_map": FlatMap,\n "bf_flat_map_tuple": FlatMapTuple,\n "bf_first": First,\n "bf_last": Last,\n "bf_arr": Arr,\n "bf_filter_truthy": FilterTruthy,\n\n // Higher-order Array Methods\n "bf_every": Every,\n "bf_some": Some,\n "bf_filter": Filter,\n "bf_find": Find,\n "bf_find_index": FindIndex,\n "bf_find_last": FindLast,\n "bf_find_last_index": FindLastIndex,\n "bf_sort": Sort,\n "bf_reduce": Reduce,\n\n // Evaluator-driven higher-order folds (#2018): the comparator / reducer\n // body travels as a serialized ParsedExpr (JSON) evaluated per element,\n // generalizing bf_sort / bf_reduce beyond their fixed catalogues. The\n // adapter falls back to bf_sort for a comparator the evaluator can\'t\n // model (e.g. localeCompare). `bf_env` builds the captured-free-var\n // environment passed as the trailing base_env argument.\n "bf_sort_eval": SortEval,\n "bf_reduce_eval": FoldEval,\n "bf_env": Env,\n\n // Evaluator-driven higher-order predicates (#2018, P2): the predicate\n // body travels as a serialized ParsedExpr (JSON) evaluated per element,\n // generalizing bf_filter / bf_find / bf_find_index / bf_every / bf_some\n // beyond their field-equality / truthiness catalogues. `bf_find_eval` /\n // `bf_find_index_eval` take a `forward` bool (false \u2192 findLast variants).\n "bf_filter_eval": FilterEval,\n "bf_every_eval": EveryEval,\n "bf_some_eval": SomeEval,\n "bf_find_eval": FindEval,\n "bf_find_index_eval": FindIndexEval,\n // `.flatMap(proj)`: project each element through the serialized\n // projection body, then flatten one level.\n "bf_flat_map_eval": FlatMapEval,\n // Value-producing `.map(cb)` (#2073): project each element, one\n // result per element (no flatten).\n "bf_map_eval": MapEval,\n\n // Comment marker (for hydration)\n "bfComment": Comment,\n "bfTextStart": TextStart,\n "bfTextEnd": TextEnd,\n\n // Script collection\n "bfScripts": BfScripts,\n\n // Scope attribute value (#1249: bare scope id, no `~` prefix)\n "bfScopeAttr": ScopeAttr,\n\n // Slot-identity markers (#1249): bf-h, bf-m, bf-r\n "bfHydrationAttrs": HydrationAttrs,\n\n // Child component marker (kept for backward compatibility)\n "bfIsChild": IsChild,\n\n // Props attribute for hydration\n "bfPropsAttr": BfPropsAttr,\n\n // Portal HTML rendering (parses and executes template string)\n "bfPortalHTML": PortalHTML,\n\n // JSX children passed to an imported child component (#1896):\n // the parent renders the children fragment via a companion\n // define (executed through bf_tmpl from TemplateFuncMap) and\n // injects the result into the child\'s Children field.\n "bf_with_children": WithChildren,\n\n // Per-row props for a child component nested inside a composite\n // loop row (#2445): the parent\'s once-per-slot instance is shared\n // across rows, so a prop that depends on the row is reapplied as a\n // copy inside {{range}}, the props-argument sibling of\n // bf_with_children.\n "bf_with_props": WithProps,\n\n // Per-row props for a child whose CONSTRUCTOR derives a field from\n // the overridden prop (#2448). bf_with_props patches fields on the\n // shared instance and cannot re-run New<Child>Props, so a memo body\n // or a signal initial value computed there would stay at the shared\n // instance\'s one-shot value on every row. This entry looks up the\n // component\'s generated rebuilder and re-runs the real constructor\n // instead. See reprops.go for why the lookup is deferred to execute\n // time rather than merged into this map.\n "bf_reprops": Reprops,\n\n // Scope comment for fragment roots\n "bfScopeComment": ScopeComment,\n "bfScopeCommentEnd": ScopeCommentEnd,\n\n // JSX intrinsic-element spread lowering (#1407)\n "bf_spread_attrs": SpreadAttrs,\n\n // Destructure object-rest spread-onto-element residual (#2087):\n // "every field except these" for a struct/map, keyed by json tag.\n "bf_omit": Omit,\n\n // Case-tolerant single-field read off a map or struct (#2087): a\n // `useContext` local whose `createContext` default is object-shaped\n // (e.g. `createContext<{ config: X }>({ config: {} })`) is typed\n // `map[string]interface{}`, and its keys are the SOURCE (JS-cased)\n // property names a `<Ctx.Provider value={{ \u2026 }}>` bakes\n // (`providerObjectValueToGoMap`, go-template-adapter.ts) \u2014 plain\n // `text/template` dot access does an exact-string `MapIndex`, so\n // `ctx.config.label` lowers to nested `bf_get` calls instead of\n // `.Ctx.Config.Label`. Reuses the same `getFieldValue` the\n // project/sort helpers already use for a dynamic field-name lookup;\n // safe on a nil map/interface (returns nil) so a missing Provider or\n // an absent key falls through to `??`\'s fallback.\n "bf_get": getFieldValue,\n }\n}\n\n// Query builds a URL from a base path plus a query string assembled from\n// (include, key, value) triples, in order. A pair is considered only when its\n// `include` flag is true \u2014 mirroring a JS URLSearchParams builder whose\n// `.set(key, value)` calls are each guarded by an `if`. The compiler lowers a\n// conditional `cond ? v : undefined` to the `include` bool and a plain `key: v`\n// to a `true` include; the emptiness check is applied HERE (an included but\n// empty value is dropped), matching the client `queryHref` and the Perl `query`\n// helper. Keys and values use formEscape (application/x-www-form-urlencoded),\n// so the rendered query is byte-for-byte identical to the browser\'s\n// URLSearchParams. An empty query yields the bare base.\n//\n// A value may be a string slice ([]string or []any), which APPENDS one pair per\n// non-empty member (URLSearchParams.append) \u2014 `{tag: [a, b]}` \u2192 `tag=a&tag=b`.\n// A scalar value follows URLSearchParams.set() semantics: repeating a key\n// overwrites the value at the key\'s first position rather than duplicating it\n// (object literals have unique keys, so this is defensive). Trailing args that\n// don\'t complete a triple are ignored.\n//\n// formEscape differs from url.QueryEscape only on `~` (kept by QueryEscape,\n// `%7E` here) and `*` (`%2A` by QueryEscape, kept here).\nfunc Query(base string, triples ...any) string {\n type kv struct{ key, val string }\n pairs := make([]kv, 0, len(triples)/3)\n pos := make(map[string]int)\n for i := 0; i+2 < len(triples); i += 3 {\n include, _ := triples[i].(bool)\n if !include {\n continue\n }\n k := String(triples[i+1])\n if members, ok := asStringSlice(triples[i+2]); ok {\n // Array value \u2192 append each non-empty member; appended pairs never\n // overwrite, so they don\'t participate in the set()-position map.\n for _, m := range members {\n if m == "" {\n continue\n }\n pairs = append(pairs, kv{k, m})\n }\n continue\n }\n v := String(triples[i+2])\n if v == "" {\n continue // omit an included-but-empty value (client / Perl parity)\n }\n if at, ok := pos[k]; ok {\n pairs[at].val = v // set(): overwrite the first occurrence\'s value\n } else {\n pos[k] = len(pairs)\n pairs = append(pairs, kv{k, v})\n }\n }\n var b strings.Builder\n for _, p := range pairs {\n if b.Len() == 0 {\n b.WriteByte(\'?\')\n } else {\n b.WriteByte(\'&\')\n }\n b.WriteString(formEscape(p.key))\n b.WriteByte(\'=\')\n b.WriteString(formEscape(p.val))\n }\n return base + b.String()\n}\n\n// Date implements the `date` helper (spec/template-helpers.md, #2274) \u2014 the\n// lowering target for a zero-arg call on a Date-typed prop\n// (`createdAt.toISOString()`). recv accepts the runtime\'s own `time.Time` /\n// `*time.Time` (however the host framework populated the prop) OR an\n// ISO-8601 string (the wire form a JSON-sourced prop arrives as); either is\n// normalized to UTC before dispatching op, matching the client `Date`\'s own\n// instant semantics regardless of which shape reaches this helper. A nil /\n// unparsable receiver yields this runtime\'s zero value for the requested op\n// (0 for every numeric accessor, "" for toISOString) rather than panicking\n// mid-render \u2014 the same tolerance `String`/`Number` already extend to a nil\n// prop. `getUTCMonth` subtracts 1: Go\'s `time.Month` is 1-based, JS\'s is not\n// (spec entry "date" is explicit that JS wins here).\nfunc Date(recv any, op string) any {\n t, ok := toTime(recv)\n if !ok {\n if op == "toISOString" {\n return ""\n }\n return 0\n }\n t = t.UTC()\n switch op {\n case "getUTCFullYear":\n return t.Year()\n case "getUTCMonth":\n return int(t.Month()) - 1\n case "getUTCDate":\n return t.Day()\n case "getUTCHours":\n return t.Hour()\n case "getUTCMinutes":\n return t.Minute()\n case "getUTCSeconds":\n return t.Second()\n case "getTime":\n return t.UnixMilli()\n case "toISOString":\n return t.Format("2006-01-02T15:04:05.000Z")\n default:\n return 0\n }\n}\n\n// tzOffsetRE matches a fixed UTC offset `\xB1HH:MM` within ECMA-402\'s valid\n// range \u2014 hours 00\u201323, minutes 00\u201359 (`\'+09:00\'`, `\'-05:30\'`) \u2014 one of the\n// three `tz` shapes FormatDate accepts (mirrors OFFSET_RE in\n// packages/client/src/format-date.ts). An out-of-range shape (`\'+25:00\'`)\n// falls through to the tzdata lookup, fails it, and errors \u2014 matching the\n// JS reference\'s RangeError (#2344).\nvar tzOffsetRE = regexp.MustCompile(`^([+-])([01][0-9]|2[0-3]):([0-5][0-9])$`)\n\n// formatDateTokenRE is the longest-match pattern-token alternation (mirrors\n// TOKEN_RE in packages/client/src/format-date.ts). Order matters: MMMM\n// before MMM before MM before M (and dddd before ddd, DD before D) so the\n// longer token wins at a position where a shorter one could also match \u2014\n// Go\'s regexp, like JS\'s, resolves alternation leftmost-first (not POSIX\n// leftmost-longest), so listing the longer alternative first is what makes\n// e.g. "MMMM" consume all four characters instead of "MM" + "MM".\nvar formatDateTokenRE = regexp.MustCompile(`YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D`)\n\n// nameTable section offsets (#2334, mirrors MONTHS_WIDE / MONTHS_ABBR /\n// WEEKDAYS_WIDE / WEEKDAYS_ABBR in packages/client/src/format-date.ts).\nconst (\n monthsWide = 0\n monthsAbbr = 12\n weekdaysWide = 24\n weekdaysAbbr = 31\n)\n\n// formatDateName reads a name-token table entry: index out of range, or a\n// non-string element, both render "" \u2014 the same total, zero-value\n// discipline as an unparseable date (mirrors the JS reference\'s\n// `names[index] ?? ""` fallback, where every table element the vectors ever\n// carry is a string).\nfunc formatDateName(names []any, index int) string {\n if index < 0 || index >= len(names) {\n return ""\n }\n s, ok := names[index].(string)\n if !ok {\n return ""\n }\n return s\n}\n\n// FormatDate implements the `format_date` helper (#2324, #2334, spec entry\n// "format_date") \u2014 the lowering target for\n// `formatDate(date, pattern, tz, names)`\n// (packages/client/src/format-date.ts, the JS-normative reference this must\n// match byte-for-byte). Total and deterministic: no locale, no host\n// timezone, no "now".\n//\n// recv: same receiver contract as the `date` helper above \u2014 the runtime\'s\n// own `time.Time` / `*time.Time`, or an ISO-8601 string \u2014 normalized via\n// `toTime`. A nil / unparseable receiver returns "" (never panics).\n//\n// tz (#2344): "UTC", a range-valid fixed offset `\xB1HH:MM` (shifts by\n// sign*(HH*60+MM) minutes), or a canonical IANA zone name ("Asia/Tokyo")\n// resolved through tzdata via time.LoadLocation \u2014 the zone\'s UTC offset AT\n// THE INSTANT being formatted (DST-aware, historical-transition-aware,\n// seconds precision: pre-standard LMT offsets like Tokyo\'s +09:18:59\n// count). ANY other value \u2014 an unknown zone, a malformed or out-of-range\n// offset ("+9:00", "+25:00"), the empty string or "Local" (LoadLocation\'s\n// implicit-environment aliases) \u2014 returns an ERROR, aborting template\n// execution loudly: the JS reference throws a RangeError there, and a\n// silently substituted timezone is the one failure mode this helper must\n// not have (the pre-#2344 normalize-to-UTC total function is gone). The\n// shifted instant\'s UTC calendar fields (not the original instant\'s) are\n// what pattern tokens read \u2014 the shifted UTC clock face IS the local clock\n// face in that zone, same reasoning as the JS reference.\n//\n// names (#2334): a flat name table in fixed layout \u2014 `[0..11]` wide month\n// names, `[12..23]` abbreviated month names, `[24..30]` wide weekday names\n// (Sunday-first), `[31..37]` abbreviated weekday names. The caller owns the\n// values; this helper only indexes the table.\n//\n// pattern: longest-match token substitution\n// (`YYYY|MMMM|MMM|MM|DD|dddd|ddd|M|D`); every other character \u2014 including\n// multi-byte ones like \u5E74/\u6708/\u65E5 \u2014 passes through literally. `YYYY` is\n// `abs(year)` zero-padded to 4 digits, `-`-prefixed for a negative year;\n// `MM`/`DD` zero-pad to 2; `M`/`D` are bare; `MMMM`/`MMM` and `dddd`/`ddd`\n// read the `names` table (weekday computed on the offset-shifted instant,\n// Sunday-first, matching `time.Time.Weekday()`\'s own Sunday=0 encoding).\nfunc FormatDate(recv any, pattern string, tz string, names []any) (string, error) {\n t, ok := toTime(recv)\n if !ok {\n // Receiver contract precedes tz validation (spec receiver-first\n // discipline, mirrored by every port).\n return "", nil\n }\n offsetSeconds := 0\n if tz != "UTC" {\n if m := tzOffsetRE.FindStringSubmatch(tz); m != nil {\n hh, _ := strconv.Atoi(m[2])\n mm, _ := strconv.Atoi(m[3])\n offsetSeconds = (hh*60 + mm) * 60\n if m[1] == "-" {\n offsetSeconds = -offsetSeconds\n }\n } else if tz == "" || tz == "Local" {\n // LoadLocation("") is UTC and LoadLocation("Local") is the host\n // zone \u2014 both implicit-environment reads the contract refuses.\n return "", fmt.Errorf("format_date: unresolvable timeZone %q", tz)\n } else {\n loc, err := time.LoadLocation(tz)\n if err != nil {\n return "", fmt.Errorf("format_date: unresolvable timeZone %q", tz)\n }\n _, offsetSeconds = t.UTC().In(loc).Zone()\n }\n }\n shifted := t.UTC().Add(time.Duration(offsetSeconds) * time.Second)\n year := shifted.Year()\n month := int(shifted.Month())\n day := shifted.Day()\n weekday := int(shifted.Weekday()) // time.Sunday == 0, matching the table\'s Sunday-first layout\n absYear := year\n if absYear < 0 {\n absYear = -absYear\n }\n yyyy := fmt.Sprintf("%04d", absYear)\n if year < 0 {\n yyyy = "-" + yyyy\n }\n out := formatDateTokenRE.ReplaceAllStringFunc(pattern, func(token string) string {\n switch token {\n case "YYYY":\n return yyyy\n case "MMMM":\n return formatDateName(names, monthsWide+month-1)\n case "MMM":\n return formatDateName(names, monthsAbbr+month-1)\n case "MM":\n return fmt.Sprintf("%02d", month)\n case "M":\n return strconv.Itoa(month)\n case "DD":\n return fmt.Sprintf("%02d", day)\n case "D":\n return strconv.Itoa(day)\n case "dddd":\n return formatDateName(names, weekdaysWide+weekday)\n case "ddd":\n return formatDateName(names, weekdaysAbbr+weekday)\n default:\n return token\n }\n })\n return out, nil\n}\n\n// toTime normalizes a `Date` helper receiver to a `time.Time`: the runtime\'s\n// own `time.Time` / `*time.Time`, or an ISO-8601 string parsed with\n// `time.RFC3339Nano` (accepts both the `Z`-suffixed and numeric-offset\n// forms, and any sub-second precision \u2014 including the millisecond precision\n// every value this runtime itself ever produces via `toISOString` above).\n// Anything else (nil, an unparsable string, an unrelated type) reports !ok\n// so `Date` can apply its documented zero-value fallback instead of\n// panicking.\nfunc toTime(recv any) (time.Time, bool) {\n switch v := recv.(type) {\n case time.Time:\n return v, true\n case *time.Time:\n if v == nil {\n return time.Time{}, false\n }\n return *v, true\n case string:\n t, err := time.Parse(time.RFC3339Nano, v)\n if err != nil {\n return time.Time{}, false\n }\n return t, true\n default:\n return time.Time{}, false\n }\n}\n\n// asStringSlice reports whether v is a query *array* value and, if so, returns\n// its members stringified. A compiled template passes a `[]string` field; the\n// golden conformance vectors decode JSON arrays to `[]any`. Anything else is a\n// scalar (false), handled by the set() path.\nfunc asStringSlice(v any) ([]string, bool) {\n switch s := v.(type) {\n case []string:\n return s, true\n case []any:\n out := make([]string, len(s))\n for i, m := range s {\n out[i] = String(m)\n }\n return out, true\n default:\n return nil, false\n }\n}\n\nconst hexUpper = "0123456789ABCDEF"\n\n// formEscape percent-encodes s with the application/x-www-form-urlencoded byte\n// set, matching the browser\'s URLSearchParams serialization (and the Perl\n// `query` helper) so SSR query strings render byte-for-byte identically across\n// adapters. The unreserved set kept verbatim is A-Z a-z 0-9 and `* - . _`; a\n// space becomes `+`; every other byte is `%XX` with uppercase hex. Encoding is\n// byte-wise, so multi-byte UTF-8 is percent-encoded per byte (`\xE9` \u2192 `%C3%A9`).\n//\n// This differs from url.QueryEscape only for `~` (kept by QueryEscape, encoded\n// to `%7E` here) and `*` (encoded to `%2A` by QueryEscape, kept here).\nfunc formEscape(s string) string {\n var b strings.Builder\n for i := 0; i < len(s); i++ {\n c := s[i]\n switch {\n case c >= \'A\' && c <= \'Z\', c >= \'a\' && c <= \'z\', c >= \'0\' && c <= \'9\',\n c == \'*\', c == \'-\', c == \'.\', c == \'_\':\n b.WriteByte(c)\n case c == \' \':\n b.WriteByte(\'+\')\n default:\n b.WriteByte(\'%\')\n b.WriteByte(hexUpper[c>>4])\n b.WriteByte(hexUpper[c&0x0F])\n }\n }\n return b.String()\n}\n\n// ScopeAttr returns the bare bf-s scope id (#1249).\nfunc ScopeAttr(props interface{}) string {\n return getStringField(props, "ScopeID")\n}\n\n// HydrationAttrs emits `bf-h="<host>" bf-m="<slot>" bf-r=""` conditionally.\n// See spec/compiler.md "Slot identity".\nfunc HydrationAttrs(props interface{}) template.HTMLAttr {\n parts := []string{}\n if host := getStringField(props, "BfParent"); host != "" {\n parts = append(parts, fmt.Sprintf(`bf-h="%s"`, template.HTMLEscapeString(host)))\n }\n if mount := getStringField(props, "BfMount"); mount != "" {\n parts = append(parts, fmt.Sprintf(`bf-m="%s"`, template.HTMLEscapeString(mount)))\n }\n if !getBoolField(props, "BfIsChild") {\n parts = append(parts, `bf-r=""`)\n }\n if len(parts) == 0 {\n return ""\n }\n return template.HTMLAttr(strings.Join(parts, " "))\n}\n\n// IsChild is a deprecated no-op stub. Child status is signalled by bf-h\n// presence (#1249); use HydrationAttrs instead.\nfunc IsChild(props interface{}) template.HTMLAttr {\n return ""\n}\n\n// svgCamelCaseAttrs mirrors SVG_CAMEL_CASE_ATTRS from\n// packages/client/src/runtime/spread-attrs.ts. SVG XML attribute\n// names are case-sensitive; the default camelCase \u2192 kebab-case\n// rewrite must NOT apply to these or the SVG stops rendering\n// (#1407). Coordinates with the compile-time SVG_CAMEL_TO_KEBAB\n// table in packages/jsx/src/ir-to-client-js/utils.ts: presentation\n// attrs (clipPath, strokeWidth, \u2026) live there and must NOT appear\n// here, or the same JSX prop would lower to clip-path via the\n// explicit-attr path and stay clipPath via the spread path.\nvar svgCamelCaseAttrs = map[string]struct{}{\n "allowReorder": {}, "attributeName": {}, "attributeType": {}, "autoReverse": {},\n "baseFrequency": {}, "baseProfile": {}, "calcMode": {}, "clipPathUnits": {},\n "contentScriptType": {}, "contentStyleType": {}, "diffuseConstant": {}, "edgeMode": {},\n "externalResourcesRequired": {}, "filterRes": {}, "filterUnits": {}, "glyphRef": {},\n "gradientTransform": {}, "gradientUnits": {}, "kernelMatrix": {}, "kernelUnitLength": {},\n "keyPoints": {}, "keySplines": {}, "keyTimes": {}, "lengthAdjust": {}, "limitingConeAngle": {},\n "markerHeight": {}, "markerUnits": {}, "markerWidth": {}, "maskContentUnits": {},\n "maskUnits": {}, "numOctaves": {}, "pathLength": {}, "patternContentUnits": {},\n "patternTransform": {}, "patternUnits": {}, "pointsAtX": {}, "pointsAtY": {}, "pointsAtZ": {},\n "preserveAlpha": {}, "preserveAspectRatio": {}, "primitiveUnits": {}, "refX": {}, "refY": {},\n "repeatCount": {}, "repeatDur": {}, "requiredExtensions": {}, "requiredFeatures": {},\n "specularConstant": {}, "specularExponent": {}, "spreadMethod": {}, "startOffset": {},\n "stdDeviation": {}, "stitchTiles": {}, "surfaceScale": {}, "systemLanguage": {},\n "tableValues": {}, "targetX": {}, "targetY": {}, "textLength": {}, "viewBox": {}, "viewTarget": {},\n "xChannelSelector": {}, "yChannelSelector": {}, "zoomAndPan": {},\n}\n\n// toAttrName mirrors the JSX\u2192HTML attribute-name rewrite from\n// packages/client/src/runtime/spread-attrs.ts. className \u2192 class,\n// htmlFor \u2192 for, SVG camelCase attrs preserved, other camelCase\n// keys lowered to kebab-case.\nfunc toAttrName(key string) string {\n if key == "className" {\n return "class"\n }\n if key == "htmlFor" {\n return "for"\n }\n if _, ok := svgCamelCaseAttrs[key]; ok {\n return key\n }\n // camelCase \u2192 kebab-case: mirror the JS reference exactly\n // (`key.replace(/([A-Z])/g, \'-$1\').toLowerCase()`). The JS shape\n // produces a leading `-` for an initial uppercase letter\n // (`XData` \u2192 `-x-data`); both this Go path and the matching JS\n // runtime are wrong-by-construction for that case (the resulting\n // HTML attribute name is invalid), but keeping them byte-equal\n // avoids silent SSR/CSR divergence (#1411 review).\n var b strings.Builder\n for _, r := range key {\n if r >= \'A\' && r <= \'Z\' {\n b.WriteByte(\'-\')\n b.WriteRune(r + 32)\n } else {\n b.WriteRune(r)\n }\n }\n return b.String()\n}\n\n// hasUnsafeStyleValue mirrors Hono\'s own CSS-injection guard\n// (`hono/jsx/utils.ts`\'s `hasUnsafeStyleValue` \u2014 the ORACLE this adapter\'s\n// dynamic `style={{...}}` values must match, #2261): a hand-rolled\n// structural scan for characters that could break out of a CSS\n// declaration, NOT real CSSOM property validation. Ported byte-for-byte \u2014\n// every character this scan tests is ASCII, so scanning by byte (Go\n// string indexing) agrees with Hono\'s UTF-16-code-unit scan for every\n// input; a multibyte UTF-8 sequence has no byte in the ASCII range, so it\n// can never spuriously match one of these single-byte comparisons. Skips\n// the reference implementation\'s regex fast-path (a pure optimization \u2014\n// the scan below already returns `false` promptly for a clean value).\nfunc hasUnsafeStyleValue(value string) bool {\n quote := byte(0)\n blockStack := make([]byte, 0, 4)\n for i := 0; i < len(value); i++ {\n c := value[i]\n switch {\n case c == \'\\\\\':\n if i == len(value)-1 {\n return true\n }\n i++\n case quote != 0:\n if c == \'\\n\' || c == \'\\f\' || c == \'\\r\' {\n return true\n }\n if c == quote {\n quote = 0\n }\n case c == \'/\' && i+1 < len(value) && value[i+1] == \'*\':\n end := strings.Index(value[i+2:], "*/")\n if end == -1 {\n return true\n }\n i = i + 2 + end + 1\n case c == \'"\' || c == \'\\\'\':\n quote = c\n case c == \'(\':\n blockStack = append(blockStack, \')\')\n case c == \'[\':\n blockStack = append(blockStack, \']\')\n case c == \'{\' || c == \'}\':\n return true\n case c == \')\' || c == \']\':\n if len(blockStack) == 0 || blockStack[len(blockStack)-1] != c {\n return true\n }\n blockStack = blockStack[:len(blockStack)-1]\n case c == \';\' && len(blockStack) == 0:\n return true\n }\n }\n return quote != 0 || len(blockStack) != 0\n}\n\n// StyleObjectToCSS builds the CSS string for a `style={{...}}` JSX\n// object-literal attribute (#2261) \u2014 `pairs` alternates CSS key (always a\n// compile-time-known literal), then value (`any`, possibly a runtime\n// expression\'s result). A value that fails `hasUnsafeStyleValue` (after\n// JS-`String()`-style stringification) is DROPPED \u2014 the whole `key:value`\n// pair is omitted \u2014 matching Hono\'s oracle behavior exactly, rather than\n// html/template\'s own contextual CSS auto-escaper (which instead emits its\n// `ZgotmplZ` unsafe-content sentinel for the same input). The final joined\n// string is STILL HTML-escaped (mirroring Hono\'s own `escapeToBuffer` call\n// on its accumulated style string) \u2014 a "safe" value can still carry a\n// literal `"`/`\'`/`&` (e.g. a BALANCED-quote CSS string value like\n// `"hello"` passes the structural scan; the quote chars survive into the\n// value) that would otherwise break out of the double-quoted `style="..."`\n// attribute. Returns `template.CSS` (over the escaped result) so\n// html/template treats it as trusted CSS content instead of ALSO applying\n// its own contextual CSS auto-escaper (which would re-derive the exact\n// `ZgotmplZ` divergence this function exists to avoid).\nfunc StyleObjectToCSS(pairs ...any) template.CSS {\n parts := make([]string, 0, len(pairs)/2)\n for i := 0; i+1 < len(pairs); i += 2 {\n key := fmt.Sprint(pairs[i])\n value := String(pairs[i+1])\n if hasUnsafeStyleValue(value) {\n continue\n }\n parts = append(parts, template.HTMLEscapeString(key)+":"+template.HTMLEscapeString(value))\n }\n return template.CSS(strings.Join(parts, ";"))\n}\n\n// StyleToCss mirrors styleToCss from\n// packages/client/src/runtime/style.ts. Accepts a string passthrough,\n// or a map (JSON-deserialized object) whose camelCase keys are\n// lowered to kebab-case and joined with `;`. Returns ("", false) for\n// nullish/empty input so callers can omit the attribute entirely.\nfunc StyleToCss(v any) (string, bool) {\n if v == nil {\n return "", false\n }\n rv := reflect.ValueOf(v)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return "", false\n }\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Map {\n // Non-object: stringify and return as-is, matching the JS\n // `typeof value !== \'object\'` branch.\n s := fmt.Sprint(v)\n if s == "" {\n return "", false\n }\n return s, true\n }\n keys := rv.MapKeys()\n sorted := make([]string, 0, len(keys))\n for _, k := range keys {\n if k.Kind() == reflect.String {\n sorted = append(sorted, k.String())\n }\n }\n sort.Strings(sorted)\n parts := make([]string, 0, len(sorted))\n for _, k := range sorted {\n val := rv.MapIndex(reflect.ValueOf(k))\n // Skip nil entries (matches the JS `if (v == null) continue`).\n if !val.IsValid() {\n continue\n }\n if val.Kind() == reflect.Interface || val.Kind() == reflect.Pointer {\n if val.IsNil() {\n continue\n }\n val = val.Elem()\n }\n prop := toAttrName(k)\n parts = append(parts, fmt.Sprintf("%s:%v", prop, val.Interface()))\n }\n if len(parts) == 0 {\n return "", false\n }\n return strings.Join(parts, ";"), true\n}\n\n// SpreadAttrs lowers a JSX intrinsic-element spread bag (#1407) to\n// an HTML attribute string. Mirrors spreadAttrs from\n// packages/client/src/runtime/spread-attrs.ts so SSR output matches\n// what CSR\'s `applyRestAttrs` writes at hydration.\n//\n// Skip rules: nil/false values, event handlers (`on[A-Z]*`),\n// `children`, `ref`.\n//\n// Key remap: className \u2192 class, htmlFor \u2192 for, SVG camelCase\n// preserved, other camelCase \u2192 kebab-case.\n//\n// `style` is routed through StyleToCss so object literals serialize\n// to a real CSS string instead of Go\'s default `map[k:v]` form.\n//\n// Booleans: true \u2192 bare attribute name, false \u2192 omitted.\n// Other scalar values are HTML-escaped via template.HTMLEscapeString.\n// Returns a `template.HTMLAttr` so html/template emits the result\n// verbatim (the function does its own escaping).\n//\n// Keys are sorted alphabetically before emission for deterministic\n// output. SSR/CSR attribute-order divergence is acceptable per the\n// rest-destructure-object-spread-in-map fixture\'s documented policy\n// \u2014 browsers honor the LAST value when a key is duplicated, so\n// pairing with static attrs (`<div class="x" {...rest}>`) is\n// last-wins regardless of order.\nfunc SpreadAttrs(bag any) template.HTMLAttr {\n if bag == nil {\n return ""\n }\n rv := reflect.ValueOf(bag)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return ""\n }\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Map {\n return ""\n }\n keys := rv.MapKeys()\n sortedKeys := make([]string, 0, len(keys))\n for _, k := range keys {\n if k.Kind() == reflect.String {\n sortedKeys = append(sortedKeys, k.String())\n }\n }\n sort.Strings(sortedKeys)\n parts := make([]string, 0, len(sortedKeys))\n for _, key := range sortedKeys {\n // Event handlers \u2014 skip at SSR the same way\n // packages/client/src/runtime/spread-attrs.ts does at\n // hydration. The JS predicate is\n // `key.startsWith(\'on\') && key.length > 2 && key[2] === key[2].toUpperCase()`,\n // which is true for any character whose uppercase form is\n // itself: ASCII A-Z, digits, underscore, and non-letter\n // symbols. Mirror that here by skipping when key[2] is NOT\n // a lowercase ASCII letter \u2014 so `onClick`, `on_custom`, and\n // `on0` all match (#1411 review).\n if len(key) > 2 && key[0] == \'o\' && key[1] == \'n\' && !(key[2] >= \'a\' && key[2] <= \'z\') {\n continue\n }\n // `children` is a JSX construct rendered inside the element,\n // never a DOM attribute. `ref` is intentionally NOT filtered\n // here so output stays byte-equal with the JS reference\n // `spreadAttrs` in packages/client/src/runtime/spread-attrs.ts\n // (which only filters null/false, event handlers, and\n // children) \u2014 aligning Go\'s filter set diverges from JS in\n // the opposite direction. Filtering `ref` consistently across\n // both SSR runtimes is a separate concern tracked alongside\n // the JS `applyRestAttrs` vs `spreadAttrs` mismatch (#1411\n // review).\n if key == "children" {\n continue\n }\n val := rv.MapIndex(reflect.ValueOf(key))\n if !val.IsValid() {\n continue\n }\n // Unwrap interface wrappers (json.Unmarshal produces\n // interface{}-wrapped values for map[string]any).\n v := val\n for v.Kind() == reflect.Interface || v.Kind() == reflect.Pointer {\n if v.IsNil() {\n // Skip null entries.\n v = reflect.Value{}\n break\n }\n v = v.Elem()\n }\n if !v.IsValid() {\n continue\n }\n // Boolean values: true \u2192 bare attribute, false \u2192 omitted.\n if v.Kind() == reflect.Bool {\n if !v.Bool() {\n continue\n }\n parts = append(parts, toAttrName(key))\n continue\n }\n // `style` routes through StyleToCss so object literals get a\n // real CSS string. The JS side does the same.\n if key == "style" {\n css, ok := StyleToCss(v.Interface())\n if !ok {\n continue\n }\n parts = append(parts, fmt.Sprintf(`style="%s"`, template.HTMLEscapeString(css)))\n continue\n }\n // Stringify and escape. fmt.Sprint handles numbers, bools-as-\n // strings, and arbitrary stringer types the same way the JS\n // `String(value)` coercion does for the analogous cases.\n s := fmt.Sprint(v.Interface())\n parts = append(parts, fmt.Sprintf(`%s="%s"`, toAttrName(key), template.HTMLEscapeString(s)))\n }\n if len(parts) == 0 {\n return ""\n }\n return template.HTMLAttr(strings.Join(parts, " "))\n}\n\n// Omit builds a `map[string]any` residual bag from a struct or map value,\n// excluding the given keys \u2014 powers the `{...rest}` spread-onto-element\n// lowering for a destructured `.map()` loop-item\'s object-rest binding\n// (#2087): `.map(({ id, title, ...rest }) => <li {...rest}>)` needs "every\n// field EXCEPT the ones the pattern already destructured out", and a static\n// Go struct type has no way to express "minus a field" \u2014 the exclude set is\n// known at COMPILE TIME (the sibling keys the destructure pattern names), so\n// the compiler passes them here and this does the per-item field-vs-key\n// matching a static type can\'t. The result feeds `bf_spread_attrs`\n// (`SpreadAttrs`), same as a top-level `{...attrs()}` bag.\n//\n// Struct receiver: iterates exported fields via reflection, keyed by each\n// field\'s `json` struct tag (falling back to the Go field name when absent)\n// \u2014 the generated struct\'s json tag is always the ORIGINAL source property\n// name (see `structFieldsFor` / `typeDefinitionToGo` in the Go adapter), so\n// this reproduces the exact JS key `SpreadAttrs`\'s `toAttrName` expects\n// (`"data-priority"`, not a re-derived `"DataPriority"`). A tag of `"-"`\n// (opt-out) is skipped like `encoding/json` does.\n//\n// Map receiver: copies string keys through directly, same exclude/skip\n// rules.\n//\n// Anything else (nil, a non-struct/non-map interface) returns an empty map.\nfunc Omit(item any, excludeKeys ...string) map[string]any {\n exclude := make(map[string]struct{}, len(excludeKeys))\n for _, k := range excludeKeys {\n exclude[k] = struct{}{}\n }\n out := map[string]any{}\n rv := reflect.ValueOf(item)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return out\n }\n rv = rv.Elem()\n }\n switch rv.Kind() {\n case reflect.Struct:\n rt := rv.Type()\n for i := 0; i < rt.NumField(); i++ {\n field := rt.Field(i)\n if !field.IsExported() {\n continue\n }\n key := field.Name\n if tag, ok := field.Tag.Lookup("json"); ok {\n if comma := strings.Index(tag, ","); comma >= 0 {\n tag = tag[:comma]\n }\n if tag == "-" {\n continue\n }\n if tag != "" {\n key = tag\n }\n }\n if _, skip := exclude[key]; skip {\n continue\n }\n out[key] = rv.Field(i).Interface()\n }\n case reflect.Map:\n for _, k := range rv.MapKeys() {\n if k.Kind() != reflect.String {\n continue\n }\n key := k.String()\n if _, skip := exclude[key]; skip {\n continue\n }\n val := rv.MapIndex(k)\n if val.IsValid() {\n out[key] = val.Interface()\n }\n }\n }\n return out\n}\n\n// BfPropsAttr returns the bf-p attribute with the JSON-serialized\n// props in flat format. Output format: `bf-p=\'{"propName":value,...}\'`.\n// Only emits the attribute for root components (BfIsRoot == true);\n// child components receive props from their parent via initChild().\n//\n// Returns the marshal error so a `template.Execute` call fails\n// loudly on cycles / unsupported props rather than silently\n// dropping the bf-p attribute and breaking client-side hydration.\n// Same loud-failure policy as `JSON` \u2014 user data going through\n// `encoding/json` shouldn\'t fail invisibly.\nfunc BfPropsAttr(props interface{}) (template.HTMLAttr, error) {\n // Only root components should emit bf-p\n if !getBoolField(props, "BfIsRoot") {\n return "", nil\n }\n\n propsJSON, err := json.Marshal(props)\n if err != nil {\n return "", err\n }\n\n escaped := template.HTMLEscapeString(string(propsJSON))\n return template.HTMLAttr(`bf-p="` + escaped + `"`), nil\n}\n\n// =============================================================================\n// Arithmetic Operations\n// =============================================================================\n\n// Add returns a + b. Supports int and float64.\nfunc Add(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n result := av + bv\n // Return int if both inputs were int-like\n if isIntLike(a) && isIntLike(b) && result == float64(int(result)) {\n return int(result)\n }\n return result\n}\n\n// ConcatStr returns a and b concatenated as strings \u2014 the string-typed half\n// of JS `+` (#2168 string-concat-plus). JS `+` is addition when BOTH\n// operands are numeric and concatenation when EITHER is a string; `Add`\n// (above) covers the numeric case, this covers the string one \u2014 `Add`\n// itself can\'t (`toFloat64` returns 0 for a string operand, so `\'Hello, \' +\n// name` silently rendered "0" before this existed).\nfunc ConcatStr(a, b any) string {\n return toString(a) + toString(b)\n}\n\n// Sub returns a - b. Supports int and float64.\nfunc Sub(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n result := av - bv\n if isIntLike(a) && isIntLike(b) && result == float64(int(result)) {\n return int(result)\n }\n return result\n}\n\n// Mul returns a * b. Supports int and float64.\nfunc Mul(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n result := av * bv\n if isIntLike(a) && isIntLike(b) && result == float64(int(result)) {\n return int(result)\n }\n return result\n}\n\n// Div returns a / b. Returns float64 to match JavaScript behavior.\n// Returns 0 if b is 0 (instead of panicking).\nfunc Div(a, b any) any {\n av, bv := toFloat64(a), toFloat64(b)\n if bv == 0 {\n return 0\n }\n return av / bv\n}\n\n// Min returns the smaller of a and b (two-arg `Math.min`). Like Mul, it keeps\n// an integer result when both operands are int-like so a CSS value such as\n// `bf_min 100 x` stays `100` rather than `100.000000`. Uses `Number` (not\n// `toFloat64`, which silently zeroes an unrecognized type like a non-numeric\n// string) plus explicit NaN checks, since IEEE-754 `<`/`>` comparisons\n// against NaN are always false and would otherwise let a non-NaN operand\n// win instead of propagating NaN like JS `Math.min`/`Math.max` do.\nfunc Min(a, b any) any {\n av, bv := Number(a), Number(b)\n if math.IsNaN(av) {\n return av\n }\n if math.IsNaN(bv) {\n return bv\n }\n r := av\n if bv < av {\n r = bv\n }\n if isIntLike(a) && isIntLike(b) && r == float64(int(r)) {\n return int(r)\n }\n return r\n}\n\n// Max returns the larger of a and b (two-arg `Math.max`), with the same\n// int-preserving rule and NaN-propagation as Min.\nfunc Max(a, b any) any {\n av, bv := Number(a), Number(b)\n if math.IsNaN(av) {\n return av\n }\n if math.IsNaN(bv) {\n return bv\n }\n r := av\n if bv > av {\n r = bv\n }\n if isIntLike(a) && isIntLike(b) && r == float64(int(r)) {\n return int(r)\n }\n return r\n}\n\n// Mod returns a % b (modulo). Supports int only.\nfunc Mod(a, b any) int {\n av, bv := toInt(a), toInt(b)\n if bv == 0 {\n return 0\n }\n return av % bv\n}\n\n// Neg returns -a (negation).\nfunc Neg(a any) any {\n if v, ok := a.(int); ok {\n return -v\n }\n return -toFloat64(a)\n}\n\n// =============================================================================\n// String Operations\n// =============================================================================\n\n// Lower returns the lowercase version of s.\nfunc Lower(s string) string {\n return strings.ToLower(s)\n}\n\n// Upper returns the uppercase version of s.\nfunc Upper(s string) string {\n return strings.ToUpper(s)\n}\n\n// Trim returns s with leading and trailing whitespace removed.\nfunc Trim(s string) string {\n return strings.TrimSpace(s)\n}\n\n// TrimStart returns s with leading whitespace removed\n// (String.prototype.trimStart, #2183) \u2014 the one-sided sibling of\n// Trim above, using the same unicode.IsSpace predicate strings.TrimSpace\n// applies to both sides.\nfunc TrimStart(s string) string {\n return strings.TrimLeftFunc(s, unicode.IsSpace)\n}\n\n// TrimEnd returns s with trailing whitespace removed\n// (String.prototype.trimEnd, #2183) \u2014 the one-sided sibling of Trim.\nfunc TrimEnd(s string) string {\n return strings.TrimRightFunc(s, unicode.IsSpace)\n}\n\n// Contains returns true if s contains substr.\nfunc Contains(s, substr string) bool {\n return strings.Contains(s, substr)\n}\n\n// Split lowers `String.prototype.split(sep, limit?)` (#1448 Tier B). It\n// wraps `strings.Split` and normalises the result to `[]any` so the\n// slice composes with the array-method surface downstream (`bf_join`,\n// range loops, `bf_len`, \u2026) the same way `bf_slice` / `bf_reverse`\n// results do. Like JS, an empty separator splits into individual UTF-8\n// characters and trailing empty fields are preserved (`"a,".split(",")`\n// \u2192 `["a", ""]`). An optional `limit` caps the number of returned\n// pieces (`"a,b,c".split(",", 2)` \u2192 `["a", "b"]`); a negative limit is\n// ignored (JS would also return every piece \u2014 its ToUint32 wrap makes\n// the limit effectively unbounded). The no-separator form is handled by\n// the adapter (it emits `bf_arr` for the whole-string single element).\nfunc Split(s, sep string, limit ...int) []any {\n parts := strings.Split(s, sep)\n if len(limit) > 0 && limit[0] >= 0 && limit[0] < len(parts) {\n parts = parts[:limit[0]]\n }\n out := make([]any, len(parts))\n for i, p := range parts {\n out[i] = p\n }\n return out\n}\n\n// StartsWith lowers `String.prototype.startsWith(prefix, position?)`\n// (#1448 Tier B). Wraps `strings.HasPrefix`; an empty prefix is always\n// true (JS parity). The optional `position` re-anchors the test to start\n// at that index (clamped to `[0, len]` so it never panics), matching JS\n// `"abc".startsWith("b", 1) === true`.\nfunc StartsWith(s, prefix string, position ...int) bool {\n if len(position) > 0 {\n p := position[0]\n if p < 0 {\n p = 0\n }\n if p > len(s) {\n p = len(s)\n }\n s = s[p:]\n }\n return strings.HasPrefix(s, prefix)\n}\n\n// EndsWith lowers `String.prototype.endsWith(suffix, endPosition?)`\n// (#1448 Tier B). Wraps `strings.HasSuffix`; an empty suffix is always\n// true (JS parity). The optional `endPosition` treats the string as if\n// it were only that many bytes long (clamped to `[0, len]`), matching JS\n// `"abc".endsWith("b", 2) === true`.\nfunc EndsWith(s, suffix string, endPosition ...int) bool {\n if len(endPosition) > 0 {\n e := endPosition[0]\n if e < 0 {\n e = 0\n }\n if e > len(s) {\n e = len(s)\n }\n s = s[:e]\n }\n return strings.HasSuffix(s, suffix)\n}\n\n// Replace lowers the string-pattern form of `String.prototype.replace`\n// (#1448 Tier B). JS replaces only the FIRST occurrence for a string\n// pattern, so the count is 1 (`strings.Replace` with n=1; `ReplaceAll`\n// below is the every-occurrence sibling, `.replaceAll`, #2182). The\n// replacement is treated literally: unlike JS, special replacement\n// patterns like `$&` / `$1` are NOT interpreted (Go and Perl agree on\n// literal replacement, keeping the two template adapters byte-equal;\n// this diverges from the Hono/CSR JS path only for replacement strings\n// that contain `$`-patterns, which are rare in template position).\nfunc Replace(s, old, new string) string {\n return strings.Replace(s, old, new, 1)\n}\n\n// ReplaceAll lowers the string-pattern form of\n// `String.prototype.replaceAll` (#2182): every occurrence, via\n// `strings.ReplaceAll` (equivalent to `strings.Replace` with n=-1).\n// Same literal-replacement caveat as `Replace` above.\nfunc ReplaceAll(s, old, new string) string {\n return strings.ReplaceAll(s, old, new)\n}\n\n// Repeat lowers `String.prototype.repeat(n)` (#1448 Tier B): the\n// receiver concatenated n times. JS throws RangeError for a negative\n// count and `strings.Repeat` panics, so a negative count clamps to the\n// empty string \u2014 SSR templates degrade rather than crash the render.\n// A zero count is the empty string (JS parity).\nfunc Repeat(s string, n int) string {\n if n <= 0 {\n return ""\n }\n return strings.Repeat(s, n)\n}\n\n// padTo lowers the shared body of `String.prototype.padStart` /\n// `padEnd` (#1448 Tier B): pad `s` to `target` code points using `pad`\n// repeated and truncated to fill, prepended (atStart) or appended.\n// Length is measured in runes (not bytes) so the result matches the\n// Perl `bf->pad_*` helpers \u2014 this diverges from JS\'s UTF-16-unit length\n// only for astral-plane input. An empty pad, or a receiver already at\n// least `target` long, returns `s` unchanged (JS parity).\nfunc padTo(s string, target int, pad string, atStart bool) string {\n if pad == "" {\n return s\n }\n sLen := utf8.RuneCountInString(s)\n if sLen >= target {\n return s\n }\n need := target - sLen\n padRunes := []rune(pad)\n fill := make([]rune, 0, need)\n for len(fill) < need {\n for _, r := range padRunes {\n if len(fill) >= need {\n break\n }\n fill = append(fill, r)\n }\n }\n if atStart {\n return string(fill) + s\n }\n return s + string(fill)\n}\n\n// PadStart lowers `String.prototype.padStart(target, pad?)` (#1448 Tier\n// B). The pad string defaults to a single space when omitted.\nfunc PadStart(s string, target int, pad ...string) string {\n p := " "\n if len(pad) > 0 {\n p = pad[0]\n }\n return padTo(s, target, p, true)\n}\n\n// PadEnd lowers `String.prototype.padEnd(target, pad?)` (#1448 Tier B).\nfunc PadEnd(s string, target int, pad ...string) string {\n p := " "\n if len(pad) > 0 {\n p = pad[0]\n }\n return padTo(s, target, p, false)\n}\n\n// Join concatenates elements of a slice with sep. Accepts both\n// reflect.Slice (the common case \u2014 `bf_arr` and `bf_filter_truthy`\n// both return `[]any`) AND reflect.Array (fixed-size Go arrays like\n// `[3]string{...}`), mirroring JS `Array.prototype.join` which\n// doesn\'t distinguish between the two. Pre-fix this returned "" for\n// fixed-size arrays passed through template data (Copilot review on\n// #1445).\nfunc Join(items any, sep string) string {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return ""\n }\n\n parts := make([]string, v.Len())\n for i := 0; i < v.Len(); i++ {\n parts[i] = toString(v.Index(i).Interface())\n }\n return strings.Join(parts, sep)\n}\n\n// Ternary returns a when cond is true, else b \u2014 the pipeline-position\n// counterpart of a template {{if}} action. Go templates have no\n// expression-level conditional, so a conditional value sitting in\n// ARGUMENT position (a lowering-node helper arg, e.g. the #2324 union\n// stage\'s locale\u2192pattern ternary) cannot be emitted as an {{if}}\n// fragment; the adapter renders it as `(bf_ternary <cond> <a> <b>)`\n// instead. Both branches are evaluated (function-call semantics) \u2014\n// fine for the value shapes the emitter feeds it, wrong for anything\n// with side effects, which template values never have.\nfunc Ternary(cond bool, a, b any) any {\n if cond {\n return a\n }\n return b\n}\n\n// String returns the string form of v. Mirrors JS `String(v)` for\n// non-nil values via `fmt.Sprintf("%v", ...)`. Diverges from JS on\n// nil: JS `String(null)` is "null", but the template path renders\n// `nil` as the empty string here so an unset prop doesn\'t surface\n// as a literal "null"/"undefined" in user-facing HTML. Document the\n// divergence explicitly so callers don\'t rely on JS-exact parity.\nfunc String(v any) string {\n if v == nil {\n return ""\n }\n return fmt.Sprintf("%v", v)\n}\n\n// RawHTML marks a value as trusted, pre-formatted HTML so html/template\'s\n// contextual escaper emits it verbatim instead of escaping it. It is the SSR\n// half of a dynamic `dangerouslySetInnerHTML={{ __html: expr }}` (#2319) \u2014\n// the one raw-output sink Go lacks as bare template syntax, the counterpart\n// to Blade `{!! !!}`, ERB `<%= %>`, Jinja/MiniJinja `| safe`, Twig `| raw`,\n// Mojolicious `<%== %>`, and Xslate `mark_raw`. The caller owns the value\'s\n// safety (React\'s "dangerously" contract); a nil value renders "".\nfunc RawHTML(v any) template.HTML {\n return template.HTML(String(v))\n}\n\n// JSON returns the JSON encoding of v as a string. Mirrors\n// JS `JSON.stringify(v)` for the V1 single-arg shape (no `replacer`\n// or `space`). Object key order is determined by Go\'s `encoding/json`\n// (alphabetical for maps, declaration order for structs) \u2014 the\n// #1187 contract requires value-compat, not order-compat.\n//\n// Top-level NaN / \xB1Inf are pre-handled to match JS \u2014 JS\'s\n// `JSON.stringify(NaN)` and `JSON.stringify(Infinity)` both produce\n// `"null"`, but Go\'s `encoding/json` rejects them with\n// `UnsupportedValueError`. Without this carve-out the common\n// composition `JSON.stringify(Number("garbage"))` would error\n// instead of emitting `"null"` like JS does. Nested NaN/Inf inside\n// a struct/map still surfaces an error \u2014 covering that needs a\n// custom marshaller; out of V1 scope.\n//\n// Returns the marshal error so a `template.Execute` call fails\n// loudly on cycles / unsupported values rather than silently\n// producing `""` and reintroducing the SSR data-loss class\n// #1187 was filed against. Go\'s text/template treats a non-nil\n// error return from a func as an execution failure.\nfunc JSON(v any) (string, error) {\n if f, ok := v.(float64); ok && (math.IsNaN(f) || math.IsInf(f, 0)) {\n return "null", nil\n }\n b, err := json.Marshal(v)\n if err != nil {\n return "", err\n }\n return string(b), nil\n}\n\n// Number coerces v to a float64. Mirrors JS `Number(v)` semantics:\n// numeric / boolean inputs convert as expected; non-numeric strings\n// and other unsupported shapes return `NaN` (matching JS rather\n// than silently substituting 0, which would mis-shape downstream\n// arithmetic and template-side comparisons). Templates that need\n// a deterministic fallback should compose with the user-side\n// default (e.g. `Number(props.x ?? 0)` in JSX).\nfunc Number(v any) float64 {\n if v == nil {\n return math.NaN()\n }\n switch x := v.(type) {\n case float64:\n return x\n case float32:\n return float64(x)\n case int:\n return float64(x)\n case int32:\n return float64(x)\n case int64:\n return float64(x)\n case bool:\n if x {\n return 1\n }\n return 0\n case string:\n f, err := strconv.ParseFloat(x, 64)\n if err != nil {\n return math.NaN()\n }\n return f\n }\n return math.NaN()\n}\n\n// Floor returns the largest integer \u2264 v as a float64. Mirrors JS\n// `Math.floor`. The return type stays float64 so chained primitives\n// (`bf_floor` then `bf_string`) line up with JS\'s number type.\nfunc Floor(v any) float64 {\n return math.Floor(Number(v))\n}\n\n// Abs returns the absolute value of v as a float64, mirroring JS\n// `Math.abs`. #2168 math-methods.\nfunc Abs(v any) float64 {\n return math.Abs(Number(v))\n}\n\n// ToFixed formats v with exactly `digits` decimal places, mirroring JS\n// `Number.prototype.toFixed` (zero-padding + half-toward-+Infinity\n// rounding). JS rounds the scaled integer half up (`(2.5).toFixed(0)`\n// is "3"); bare `fmt.Sprintf("%.*f")` rounds half-to-even ("2"), so we\n// scale, round with `Floor(x + 0.5)` (matching `Round`), then format\n// the exact multiple. #1897.\nfunc ToFixed(v any, digits int) string {\n if digits < 0 {\n digits = 0\n }\n n := Number(v)\n // JS toFixed returns the strings "NaN" / "Infinity" / "-Infinity" for\n // non-finite inputs; fmt would render "NaN"/"+Inf"/"-Inf".\n if math.IsNaN(n) {\n return "NaN"\n }\n if math.IsInf(n, 1) {\n return "Infinity"\n }\n if math.IsInf(n, -1) {\n return "-Infinity"\n }\n factor := math.Pow(10, float64(digits))\n rounded := math.Floor(n*factor + 0.5)\n return fmt.Sprintf("%.*f", digits, rounded/factor)\n}\n\n// Ceil returns the smallest integer \u2265 v as a float64. Mirrors JS\n// `Math.ceil`.\nfunc Ceil(v any) float64 {\n return math.Ceil(Number(v))\n}\n\n// Round returns v rounded to the nearest integer as a float64.\n// Mirrors JS `Math.round` \u2014 half-away-from-zero (Go\'s `math.Round`\n// matches; JS rounds half toward +Infinity which differs at .5\n// negatives; we accept that minor divergence since the conformance\n// contract is value-compat for the common positive case).\nfunc Round(v any) float64 {\n return math.Round(Number(v))\n}\n\n// =============================================================================\n// Array/Slice Operations\n// =============================================================================\n\n// Length lowers JS `.length`, matching JS semantics per receiver shape\n// (#2255): a slice/array/map counts ELEMENTS (`reflect.Value.Len`, same as\n// `Len` below), but a STRING counts UTF-16 CODE UNITS \u2014 JS\n// `String.prototype.length` counts UTF-16 code units, not bytes (Go\'s\n// native `len`) or codepoints. A codepoint outside the Basic Multilingual\n// Plane (astral, U+10000-U+10FFFF \u2014 e.g. \'\u{1F44D}\') is a surrogate PAIR in\n// UTF-16, so it counts as 2, not 1; `\'\u65E5\u672C\u8A9E\'` is 3 either way (BMP-only).\n// Routed from the `.length` member lowering\'s generic (non-array,\n// non-loop-slice) fallback \u2014 see `member()`\'s `bf_length` call site.\nfunc Length(v any) int {\n if v == nil {\n return 0\n }\n rv := reflect.ValueOf(v)\n switch rv.Kind() {\n case reflect.Slice, reflect.Array, reflect.Map, reflect.Chan:\n return rv.Len()\n case reflect.String:\n n := 0\n for _, r := range rv.String() {\n if r > 0xFFFF {\n n += 2\n } else {\n n++\n }\n }\n return n\n default:\n return 0\n }\n}\n\n// IsValidElement lowers React/Hono-style `isValidElement(x)` \u2014 the "is this\n// a renderable element (not plain text)?" predicate the `Slot` component\'s\n// `asChild` pattern (#2266) uses to decide whether to merge props into a\n// child ELEMENT (`children.tag`/`children.props`) or fall back to rendering\n// `children` as-is. The JS runtime checks `\'tag\' in x && \'props\' in x`; on\n// Go SSR a passed-through JSX child is represented as pre-rendered markup\n// (a plain string) OR \u2014 where a struct/map shape carrying `Tag`/`Props`\n// (case-insensitively, mirroring `bf_get`\'s field lookup) is available \u2014 an\n// element-shaped value. A plain string/number/bool/nil is never a valid\n// element, so `isValidElement` must NOT be lowered as bare truthiness\n// (previously done via `renderConditionExpr`) \u2014 a truthy non-empty STRING\n// child wrongly took the element-merge branch and panicked dereferencing\n// `.Props` on a string (`can\'t evaluate field Props in type interface {}`).\nfunc IsValidElement(v any) bool {\n rv := reflect.ValueOf(v)\n for rv.Kind() == reflect.Interface || rv.Kind() == reflect.Pointer {\n if rv.IsNil() {\n return false\n }\n rv = rv.Elem()\n }\n switch rv.Kind() {\n case reflect.Map:\n hasTag, hasProps := false, false\n for _, k := range rv.MapKeys() {\n key := fmt.Sprintf("%v", k.Interface())\n if strings.EqualFold(key, "tag") {\n hasTag = true\n }\n if strings.EqualFold(key, "props") {\n hasProps = true\n }\n }\n return hasTag && hasProps\n case reflect.Struct:\n return fieldByFoldedName(rv, "tag").IsValid() && fieldByFoldedName(rv, "props").IsValid()\n default:\n return false\n }\n}\n\n// fieldByFoldedName finds a struct field by case-insensitive name match \u2014\n// shared by IsValidElement; mirrors getFieldValue\'s (bf_get) struct-branch\n// lookup so the two case-tolerant field resolutions stay consistent.\nfunc fieldByFoldedName(rv reflect.Value, name string) reflect.Value {\n t := rv.Type()\n for i := 0; i < t.NumField(); i++ {\n if strings.EqualFold(t.Field(i).Name, name) {\n return rv.Field(i)\n }\n }\n return reflect.Value{}\n}\n\n// Len returns the length of a slice, array, map, string, or channel.\n// Returns 0 for nil or unsupported types.\nfunc Len(v any) int {\n if v == nil {\n return 0\n }\n rv := reflect.ValueOf(v)\n switch rv.Kind() {\n case reflect.Slice, reflect.Array, reflect.Map, reflect.String, reflect.Chan:\n return rv.Len()\n default:\n return 0\n }\n}\n\n// At returns the element at index i from a slice.\n// Supports negative indices (e.g., -1 for last element).\n// Returns nil if index is out of bounds.\nfunc At(items any, index int) any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n\n length := v.Len()\n if length == 0 {\n return nil\n }\n\n // Handle negative indices\n if index < 0 {\n index = length + index\n }\n\n if index < 0 || index >= length {\n return nil\n }\n\n return v.Index(index).Interface()\n}\n\n// Includes returns true if items contains elem. Lowers both\n// `Array.prototype.includes` and `String.prototype.includes` \u2014\n// the adapter can\'t disambiguate the receiver at compile time,\n// so this helper dispatches at runtime on `reflect.Kind()`:\n//\n// - slice/array receiver: SameValueZero element search (matches\n// the evaluator\'s `evalSameValueZero`/`evalIncludes` in eval.go,\n// which back the serialized-callback path) \u2014 numeric types compare\n// by value across int/float64 the way JS\'s single "number" type\n// does, and NaN matches NaN (unlike `===`). This used to be\n// `reflect.DeepEqual`, which is type-strict (`int(2)` != `float64(2)`)\n// and never matches NaN to NaN; that diverged from the evaluator\'s\n// `.includes` and from JS itself, so it was unified here.\n// - string receiver: strings.Contains substring search\n//\n// Anything else returns false (matches the JS semantic where\n// `.includes` is only defined on Array / TypedArray / String).\nfunc Includes(recv any, elem any) bool {\n v := reflect.ValueOf(recv)\n if v.Kind() == reflect.String {\n // JS `String.prototype.includes` accepts only string args;\n // non-string `elem` would TypeError in real JS but our\n // callers have lowered through `convertExpressionToGo`\n // where the arg type is whatever the template binds. Stringify\n // via fmt to keep the helper total.\n needle, ok := elem.(string)\n if !ok {\n needle = fmt.Sprintf("%v", elem)\n }\n return strings.Contains(v.String(), needle)\n }\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return false\n }\n for i := 0; i < v.Len(); i++ {\n if evalSameValueZero(v.Index(i).Interface(), elem) {\n return true\n }\n }\n return false\n}\n\n// IndexOf returns the 0-based position of the first item that\n// DeepEquals `elem`, or -1 if not found. Lowers\n// `Array.prototype.indexOf(x)` (#1448 Tier A). The existing\n// `FindIndex` helper does struct-field equality (used by the\n// higher-order `.find` lowering); this one does value equality\n// against scalar / struct items so callers don\'t have to compose\n// a synthetic predicate.\n//\n// Non-array / non-slice receivers return -1 (matches the JS\n// semantic that `.indexOf` is only defined on Array / TypedArray).\nfunc IndexOf(items any, elem any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := 0; i < v.Len(); i++ {\n if reflect.DeepEqual(v.Index(i).Interface(), elem) {\n return i\n }\n }\n return -1\n}\n\n// LastIndexOf returns the 0-based position of the last item that\n// DeepEquals `elem`, or -1 if not found. Mirrors\n// `Array.prototype.lastIndexOf(x)`. The reverse traversal is the\n// only behavioural difference vs `IndexOf` \u2014 disambiguating a\n// duplicated value\'s first vs last position is the canonical\n// reason a JS author reaches for `lastIndexOf`.\nfunc LastIndexOf(items any, elem any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := v.Len() - 1; i >= 0; i-- {\n if reflect.DeepEqual(v.Index(i).Interface(), elem) {\n return i\n }\n }\n return -1\n}\n\n// Concat merges two arrays (or slices) into a single `[]any`,\n// preserving order: receiver elements first, then `other`\'s.\n// Lowers `Array.prototype.concat(other)` (#1448 Tier A). Non-array\n// operands collapse to an empty source \u2014 matches the JS semantic\n// where `.concat` on a non-Array reads it as a single element only\n// if its `Symbol.isConcatSpreadable` is true; the template-language\n// path doesn\'t have user objects with that flag, so treating\n// non-arrays as empty is the conservative lowering. Variadic\n// `.concat(a, b, c)` is out of scope here (parser gates to a single\n// arg); the helper itself stays binary so a future variadic IR can\n// fold via repeated calls without changing this signature.\nfunc Concat(a, b any) []any {\n flatten := func(v reflect.Value) []any {\n if !v.IsValid() {\n return nil\n }\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n out := make([]any, v.Len())\n for i := 0; i < v.Len(); i++ {\n out[i] = v.Index(i).Interface()\n }\n return out\n }\n left := flatten(reflect.ValueOf(a))\n right := flatten(reflect.ValueOf(b))\n return append(left, right...)\n}\n\n// clampSliceRange normalizes JS `.slice(start, end?)` bounds against a\n// receiver of `length` elements (runes, for the string branch of\n// `Slice` below; array elements, for the array branch) \u2014 shared so\n// both branches clamp identically.\n//\n// JS-compat clamping:\n// - start < 0 \u2192 length + start (e.g. -1 = last index)\n// - end < 0 \u2192 length + end\n// - start < 0 after clamp \u2192 0\n// - end > length \u2192 length\nfunc clampSliceRange(length, start int, end []int) (int, int) {\n if start < 0 {\n start = length + start\n }\n if start < 0 {\n start = 0\n }\n if start > length {\n start = length\n }\n\n stop := length\n if len(end) > 0 {\n stop = end[0]\n if stop < 0 {\n stop = length + stop\n }\n if stop < 0 {\n stop = 0\n }\n if stop > length {\n stop = length\n }\n }\n return start, stop\n}\n\n// Slice carves out a sub-range from `items`. Lowers\n// `Array.prototype.slice(start, end?)` (#1448 Tier A) AND\n// `String.prototype.slice(start, end?)` (the `string-slice`\n// divergence) \u2014 the adapter emits the same `bf_slice` call for both\n// receiver shapes (it can\'t disambiguate string vs. array at compile\n// time), so this helper dispatches at runtime on `reflect.Kind()`,\n// mirroring `Includes` above. The variadic `end` arg lets Go\n// template\'s call dispatcher pass either 2 or 3 arguments; an absent\n// end means "to length".\n//\n// String length/positions are measured in runes, not UTF-16 code\n// units \u2014 the same divergence boundary `padTo` already accepts\n// (differs from JS only for astral-plane input). `start >= end`\n// (after clamping) returns an empty result for either receiver.\n//\n// Any other receiver kind returns an empty `[]any`.\nfunc Slice(items any, start int, end ...int) any {\n v := reflect.ValueOf(items)\n\n if v.Kind() == reflect.String {\n runes := []rune(v.String())\n s, e := clampSliceRange(len(runes), start, end)\n if s >= e {\n return ""\n }\n return string(runes[s:e])\n }\n\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n s, e := clampSliceRange(v.Len(), start, end)\n if s >= e {\n return []any{}\n }\n out := make([]any, 0, e-s)\n for i := s; i < e; i++ {\n out = append(out, v.Index(i).Interface())\n }\n return out\n}\n\n// Reverse returns a new slice with `items`\'s elements in reverse\n// order. Lowers both `Array.prototype.reverse()` and\n// `Array.prototype.toReversed()` (#1448 Tier A) \u2014 SSR templates\n// render a snapshot, so JS\'s mutate-receiver vs return-new-array\n// distinction has no template-level meaning, and the safer\n// non-mutating shape is used uniformly.\n//\n// Non-array receivers return an empty `[]any`.\nfunc Reverse(items any) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n length := v.Len()\n out := make([]any, length)\n for i := 0; i < length; i++ {\n out[length-1-i] = v.Index(i).Interface()\n }\n return out\n}\n\n// Flat flattens nested slices/arrays `depth` levels deep. Lowers\n// `Array.prototype.flat(depth?)` (#1448 Tier C). A `depth` of `-1` is the\n// `Infinity` sentinel (flatten fully); `0` (or negative-from-JS, already\n// normalised to 0 at compile time) returns a shallow copy. Non-array\n// elements are kept as-is (JS only flattens nested arrays). A non-array\n// receiver returns an empty `[]any`.\nfunc Flat(items any, depth int) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n out := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n el := v.Index(i).Interface()\n ev := reflect.ValueOf(el)\n if depth != 0 && (ev.Kind() == reflect.Slice || ev.Kind() == reflect.Array) {\n // `-1` (Infinity) recurses unbounded; a finite depth spends one level.\n next := depth\n if depth > 0 {\n next = depth - 1\n }\n out = append(out, Flat(el, next)...)\n } else {\n out = append(out, el)\n }\n }\n return out\n}\n\n// FlatDynamicDepth coerces `depth` via JS\'s `ToIntegerOrInfinity` and\n// flattens `items` that many levels. Lowers a DYNAMIC `.flat(depth)`\n// (#2094) \u2014 one whose depth isn\'t a compile-time literal, so (unlike\n// `Flat` above) the coercion happens here at render time instead of in the\n// parser.\n//\n// This is a SEPARATE helper from `Flat`/`bf_flat` \u2014 NOT a drop-in\n// replacement \u2014 because `Flat`\'s `depth int` parameter treats `-1` as a\n// compile-time SENTINEL meaning "flatten fully" (the parser\'s own\n// normalisation of a literal `Infinity`). A genuinely dynamic depth value\n// of `-1` means the JS-correct OPPOSITE: `Array.prototype.flat(-1)` never\n// recurses (same as `.flat(0)`, a shallow copy), because\n// `FlattenIntoArray` only recurses when `depth > 0`. Reusing `Flat`\'s int\n// contract for a raw dynamic value would silently invert that case, so\n// this function coerces FIRST \u2014 mapping a real `+Infinity` / huge finite\n// value to `Flat`\'s own `-1` sentinel, and a real negative value to `0` \u2014\n// and only then delegates to `Flat`\'s recursion.\n//\n// Coercion rules (JS `ToIntegerOrInfinity`, mirrored exactly; pinned by the\n// `flat_dynamic` golden-vector cases in\n// packages/adapter-tests/vectors/cases.ts):\n// - the value converts via `ToNumber` first (numeric string / bool /\n// number all coerce; see `flatDepthToFloat`);\n// - a NaN result (including a non-numeric string) \u2192 `0`;\n// - truncates toward zero (`2.7` \u2192 `2`);\n// - negative \u2192 `0`;\n// - `+Infinity` / a huge finite value \u2192 flattens fully.\nfunc FlatDynamicDepth(items any, depth any) []any {\n return Flat(items, coerceFlatDepth(depth))\n}\n\n// coerceFlatDepth implements JS\'s `ToIntegerOrInfinity` for a dynamic\n// `.flat(depth)` argument, returning an int in `Flat`\'s own contract (`-1`\n// = unbounded, `>= 0` = that many levels).\nfunc coerceFlatDepth(depth any) int {\n f, ok := flatDepthToFloat(depth)\n if !ok || math.IsNaN(f) {\n return 0\n }\n if math.IsInf(f, 1) {\n return -1 // Flat\'s "flatten fully" sentinel\n }\n if math.IsInf(f, -1) {\n return 0\n }\n trunc := math.Trunc(f)\n if trunc < 0 {\n return 0\n }\n // A huge finite depth behaves identically to "flatten fully" in\n // practice \u2014 real data bottoms out at its actual nesting depth long\n // before a counter this large would ever reach zero. Capping it here\n // avoids an absurd countdown without needing a second sentinel.\n if trunc > 1_000_000 {\n return -1\n }\n return int(trunc)\n}\n\n// flatDepthToFloat converts a dynamic `.flat(depth)` argument to a float64,\n// mirroring JS\'s `ToNumber` across the value shapes a Go template data\n// model can carry (every numeric kind, bool, numeric string). `ok` is\n// false for a shape `ToNumber` can\'t coerce meaningfully (`nil`, or a\n// non-numeric string) \u2014 `coerceFlatDepth` treats that the same as NaN\n// (\u2192 depth `0`), matching JS.\nfunc flatDepthToFloat(v any) (float64, bool) {\n switch n := v.(type) {\n case nil:\n return 0, false\n case float64:\n return n, true\n case float32:\n return float64(n), true\n case int:\n return float64(n), true\n case int8:\n return float64(n), true\n case int16:\n return float64(n), true\n case int32:\n return float64(n), true\n case int64:\n return float64(n), true\n case uint:\n return float64(n), true\n case uint8:\n return float64(n), true\n case uint16:\n return float64(n), true\n case uint32:\n return float64(n), true\n case uint64:\n return float64(n), true\n case bool:\n if n {\n return 1, true\n }\n return 0, true\n case string:\n s := strings.TrimSpace(n)\n if s == "" {\n return 0, true // JS: Number("") is 0\n }\n f, err := strconv.ParseFloat(s, 64)\n if err != nil {\n return 0, false // not numeric \u2192 NaN path\n }\n return f, true\n default:\n return 0, false\n }\n}\n\n// FlatMap projects each element through a `self` / `field` projection and\n// flattens the result one level. Lowers value-returning\n// `Array.prototype.flatMap(fn)` for the field-projection catalogue\n// (#1448 Tier C): `items.flatMap(i => i)` (self) and\n// `items.flatMap(i => i.field)` (field). A projected non-array value is\n// kept as-is (flatMap = map + flat(1)). Non-array receiver \u2192 empty.\nfunc FlatMap(items any, keyKind, keyName string) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n projected := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n el := v.Index(i).Interface()\n if keyKind == "field" {\n projected = append(projected, getFieldValue(el, keyName))\n } else {\n projected = append(projected, el)\n }\n }\n return Flat(projected, 1)\n}\n\n// FlatMapTuple lowers an array-literal flatMap projection\n// `items.flatMap(i => [i.a, i.b])` (#1448 Tier C). `specs` is a flat list\n// of (kind, name) pairs, one per array-literal leaf: ("self", "") for the\n// item itself, ("field", "<Name>") for a struct field. For each item it\n// appends every leaf\'s value in order. Unlike the scalar `FlatMap`, the\n// per-item array is flattened only one level (flat(1) removes the literal\n// wrapper), so an array-valued leaf is appended verbatim rather than\n// spread \u2014 which is exactly "append each leaf". Non-array receiver \u2192 empty.\nfunc FlatMapTuple(items any, specs ...string) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return []any{}\n }\n out := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n el := v.Index(i).Interface()\n for j := 0; j+1 < len(specs); j += 2 {\n if specs[j] == "field" {\n out = append(out, getFieldValue(el, specs[j+1]))\n } else {\n out = append(out, el)\n }\n }\n }\n return out\n}\n\n// First returns the first element of a slice, or nil if empty.\nfunc First(items any) any {\n return At(items, 0)\n}\n\n// Last returns the last element of a slice, or nil if empty.\nfunc Last(items any) any {\n return At(items, -1)\n}\n\n// Arr builds an []any from variadic args. Used to lower JS array\n// literals like `[a, b]` for the registry Slot\'s\n// `[className, childClass].filter(Boolean).join(\' \')` shape (#1443) \u2014\n// Go templates have no array-literal syntax, so the codegen routes\n// array-literal IR nodes through this helper.\nfunc Arr(items ...any) []any {\n return items\n}\n\n// FilterTruthy returns a new slice containing only truthy items.\n// Mirrors `arr.filter(Boolean)` semantics: drop nil, false, 0, "" \u2014 the\n// same falsy set JavaScript\'s `Boolean(x)` recognises. Used to lower\n// the registry Slot\'s class-merge pattern (#1443); generalising to\n// arbitrary callable predicates would need the callee-resolution path\n// blocked by #1389, so this stays Boolean-specific.\nfunc FilterTruthy(items any) []any {\n v := reflect.ValueOf(items)\n if !v.IsValid() || (v.Kind() != reflect.Slice && v.Kind() != reflect.Array) {\n return nil\n }\n result := make([]any, 0, v.Len())\n for i := 0; i < v.Len(); i++ {\n raw := v.Index(i).Interface()\n if isTruthy(raw) {\n result = append(result, raw)\n }\n }\n return result\n}\n\n// Truthy is the exported form of isTruthy \u2014 JavaScript\'s `Boolean(x)`\n// semantics. Two callers:\n// - generated `NewXxxProps` code lowering a conditional inline-object\n// spread condition on an `interface{}` prop (whose runtime value may be\n// a string, number, bool, \u2026), keeping the spread bag\'s inclusion test\n// faithful to JS rather than string-biased (#1752); and\n// - the `bf_truthy` template FuncMap entry (#2335), which coerces a\n// `bf_ternary` test to a real bool when it isn\'t already a comparison /\n// negation (`Ternary`\'s `cond` parameter is typed `bool`, unlike\n// `{{if}}`\'s built-in truthiness). Uniform across string/number/bool/nil,\n// so a `bf_ternary` test on any prop type can\'t hit a `bool`-vs-`string`\n// comparison error the way a string-only `ne <value> ""` would.\nfunc Truthy(v any) bool { return isTruthy(v) }\n\n// isTruthy mirrors JavaScript\'s `Boolean(x)` for the value shapes the\n// template path actually receives \u2014 nil / false / 0 / "" are falsy.\n// Other shapes (non-empty maps, slices, structs, true) are truthy, in\n// line with JS\'s "objects are truthy" rule.\nfunc isTruthy(v any) bool {\n if v == nil {\n return false\n }\n switch x := v.(type) {\n case bool:\n return x\n case string:\n return x != ""\n case int:\n return x != 0\n case int8, int16, int32, int64:\n return reflect.ValueOf(v).Int() != 0\n case uint, uint8, uint16, uint32, uint64:\n return reflect.ValueOf(v).Uint() != 0\n case float32:\n // JS `Boolean(NaN)` is false regardless of float width \u2014 the\n // float64 arm below was the only one checking IsNaN, which\n // diverged from JS for `float32` NaN inputs (Copilot review on\n // #1445). Widening to float64 for the IsNaN check keeps the\n // two branches in lock-step.\n return x != 0 && !math.IsNaN(float64(x))\n case float64:\n return x != 0 && !math.IsNaN(x)\n }\n return true\n}\n\n// =============================================================================\n// Higher-order Array Methods\n// =============================================================================\n\n// fieldValue projects item.field for the higher-order predicate\n// helpers. The field name arrives in JS casing; structs resolve via\n// the capitalized Go convention (FieldByName inside getFieldValue),\n// maps via getFieldValue\'s case-variant lookup \u2014 the same dual\n// support Sort/Reduce gained in #1487, extended here so JSON-decoded\n// data (map items) participates instead of being silently skipped.\n// nil-safe: missing fields and nil items project to nil.\nfunc fieldValue(item any, field string) any {\n return getFieldValue(item, capitalize(field))\n}\n\n// Every returns true if every item\'s field is truthy under JS\n// `Boolean(item.field)` semantics. Mirrors JavaScript\'s\n// Array.prototype.every(item => item.field) \u2014 including being\n// vacuously true for an empty receiver.\nfunc Every(items any, field string) bool {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return false\n }\n for i := 0; i < v.Len(); i++ {\n if !isTruthy(fieldValue(v.Index(i).Interface(), field)) {\n return false\n }\n }\n return true\n}\n\n// Some returns true if at least one item\'s field is truthy. Mirrors\n// JavaScript\'s Array.prototype.some(item => item.field).\nfunc Some(items any, field string) bool {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return false\n }\n for i := 0; i < v.Len(); i++ {\n if isTruthy(fieldValue(v.Index(i).Interface(), field)) {\n return true\n }\n }\n return false\n}\n\n// Filter returns items where item.field == value.\n// Mirrors JavaScript\'s Array.prototype.filter(item => item.field === value).\n// Returns []any to allow chaining with other bf_* functions.\nfunc Filter(items any, field string, value any) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n var result []any\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i).Interface()\n if reflect.DeepEqual(fieldValue(item, field), value) {\n result = append(result, item)\n }\n }\n return result\n}\n\n// Find returns the first item where item.field == value, or nil if not found.\n// Mirrors JavaScript\'s Array.prototype.find(item => item.field === value).\nfunc Find(items any, field string, value any) any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i).Interface()\n if reflect.DeepEqual(fieldValue(item, field), value) {\n return item\n }\n }\n return nil\n}\n\n// FindIndex returns the index of the first item where item.field == value, or -1.\n// Mirrors JavaScript\'s Array.prototype.findIndex(item => item.field === value).\nfunc FindIndex(items any, field string, value any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := 0; i < v.Len(); i++ {\n if reflect.DeepEqual(fieldValue(v.Index(i).Interface(), field), value) {\n return i\n }\n }\n return -1\n}\n\n// FindLast returns the last item where item.field == value, or nil if not found.\n// Mirrors JavaScript\'s Array.prototype.findLast(item => item.field === value).\nfunc FindLast(items any, field string, value any) any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n for i := v.Len() - 1; i >= 0; i-- {\n item := v.Index(i).Interface()\n if reflect.DeepEqual(fieldValue(item, field), value) {\n return item\n }\n }\n return nil\n}\n\n// FindLastIndex returns the index of the last item where item.field == value, or -1.\n// Mirrors JavaScript\'s Array.prototype.findLastIndex(item => item.field === value).\nfunc FindLastIndex(items any, field string, value any) int {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return -1\n }\n for i := v.Len() - 1; i >= 0; i-- {\n if reflect.DeepEqual(fieldValue(v.Index(i).Interface(), field), value) {\n return i\n }\n }\n return -1\n}\n\n// sortKeySpec is one parsed comparison key. A simple comparator has\n// one; a `||`-chained multi-key comparator has several, applied in\n// order as tie-breakers.\ntype sortKeySpec struct {\n kind string // "self" | "field"\n name string // capitalised field name, or "" for "self"\n compareType string // "numeric" | "string" | "auto"\n direction string // "asc" | "desc"\n}\n\n// Sort returns a new stable-sorted slice. Lowers\n// `Array.prototype.sort` / `Array.prototype.toSorted` (#1448 Tier B).\n// Non-mutating \u2014 JS\'s mutate-vs-new distinction is moot in SSR\n// template context (templates render a snapshot).\n//\n// Call shape (the compiler emits one 4-string group per key):\n//\n// bf_sort <items> (<keyKind> <keyName> <compareType> <direction>)+\n//\n// keyKind: "self" | "field"\n// keyName: "" when keyKind == "self"; capitalised struct field\n// name (e.g. "Price") otherwise\n// compareType: "numeric" | "string" | "auto"\n// direction: "asc" | "desc"\n//\n// The groups cover the accepted comparator catalogue: `a.f - b.f`,\n// `a - b`, `a[.f].localeCompare(b[.f])`, and relational-ternary keys\n// (`a.f > b.f ? 1 : -1` \u2192 "auto"), each `||`-chainable for multi-key\n// tie-breaks. Anything outside refuses at compile time (BF101 from the\n// JSX compiler) and never reaches this helper.\n//\n// "auto" compares numerically when both projected keys parse as\n// numbers, else lexically \u2014 mirroring the Perl `bf->sort` helper\'s\n// `looks_like_number` rule so the two template adapters stay\n// byte-equal. This diverges from JS `<`/`>` only for numeric strings.\n//\n// A future `nulls` knob can extend the per-key group without rewriting\n// existing call sites \u2014 each key already projects before comparing.\nfunc Sort(items any, spec ...string) []any {\n v := reflect.ValueOf(items)\n if v.Kind() != reflect.Slice && v.Kind() != reflect.Array {\n return nil\n }\n\n length := v.Len()\n if length == 0 {\n return []any{}\n }\n\n // Copy into a fresh []any so the sort is non-mutating regardless\n // of whether the receiver is `[]T` or `[]any`.\n result := make([]any, length)\n for i := 0; i < length; i++ {\n result[i] = v.Index(i).Interface()\n }\n\n keys := parseSortSpec(spec)\n sort.SliceStable(result, func(i, j int) bool {\n for _, k := range keys {\n ki := projectSortKey(result[i], k.kind, k.name)\n kj := projectSortKey(result[j], k.kind, k.name)\n c := compareSortKey(ki, kj, k.compareType)\n if c == 0 {\n continue // tie on this key \u2014 fall through to the next\n }\n if k.direction == "desc" {\n return c > 0\n }\n return c < 0\n }\n return false\n })\n\n return result\n}\n\n// parseSortSpec chunks the variadic operand list into 4-string key\n// groups. A trailing partial group (malformed emit) is ignored rather\n// than panicking \u2014 defensive, mirroring the helper\'s nil-safe stance.\nfunc parseSortSpec(spec []string) []sortKeySpec {\n var keys []sortKeySpec\n for i := 0; i+3 < len(spec); i += 4 {\n keys = append(keys, sortKeySpec{\n kind: spec[i],\n name: spec[i+1],\n compareType: spec[i+2],\n direction: spec[i+3],\n })\n }\n return keys\n}\n\n// compareSortKey returns -1 / 0 / 1 for two projected keys under the\n// given compare type (ascending orientation; the caller flips for\n// "desc"). "string" stringifies both (nil \u2192 "", matching the\n// documented `bf->string(undef) === ""` divergence). "auto" compares\n// numerically when both parse as numbers, else lexically.\nfunc compareSortKey(ki, kj any, compareType string) int {\n switch compareType {\n case "string":\n return strings.Compare(toString(ki), toString(kj))\n case "auto":\n ni, okI := toFloat64WithOK(ki)\n nj, okJ := toFloat64WithOK(kj)\n if okI && okJ {\n return cmpFloat(ni, nj)\n }\n return strings.Compare(toString(ki), toString(kj))\n default: // numeric\n return cmpFloat(toFloat64(ki), toFloat64(kj))\n }\n}\n\nfunc cmpFloat(a, b float64) int {\n if a < b {\n return -1\n }\n if a > b {\n return 1\n }\n return 0\n}\n\n// toFloat64WithOK reports a value\'s numeric float and whether it is\n// number-like. Genuine numeric kinds always qualify; strings qualify\n// when they parse as a float (so the "auto" compare path matches the\n// Perl `looks_like_number` rule). Everything else is non-numeric.\nfunc toFloat64WithOK(v any) (float64, bool) {\n switch n := v.(type) {\n case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:\n return toFloat64(v), true\n case string:\n f, err := strconv.ParseFloat(strings.TrimSpace(n), 64)\n if err != nil {\n return 0, false\n }\n return f, true\n default:\n return 0, false\n }\n}\n\n// projectSortKey reduces an item to the value the comparator\n// actually compares. For `keyKind == "field"` it reads the named\n// struct field; for `keyKind == "self"` (primitive arrays) it\n// returns the item unchanged.\nfunc projectSortKey(item any, keyKind, keyName string) any {\n if keyKind == "field" {\n return getFieldValue(item, keyName)\n }\n return item\n}\n\n// getFieldValue extracts a struct field value using reflection. For\n// map receivers it falls back to case-variant lookup so JSON-decoded\n// user data (`map[string]any{"price": 30}`) and PascalCase-emitted\n// test data both resolve under a single key name. (#1487)\nfunc getFieldValue(item any, field string) any {\n v := reflect.ValueOf(item)\n // Defensive IsNil guards mirror `SpreadAttrs` \u2014 keeps the helper\n // safe against typed-nil pointer / nil-interface items inside a\n // `[]any` so a single bad row doesn\'t crash the whole sort.\n if v.Kind() == reflect.Interface {\n if v.IsNil() {\n return nil\n }\n v = v.Elem()\n }\n if v.Kind() == reflect.Ptr {\n if v.IsNil() {\n return nil\n }\n v = v.Elem()\n }\n\n if v.Kind() == reflect.Map {\n keyType := v.Type().Key()\n if keyType.Kind() != reflect.String {\n return nil\n }\n // Convert the lookup string to the map\'s actual key type so\n // maps keyed by a named string type (`type Key string`) don\'t\n // panic with `value of type string is not assignable to type X`.\n lookup := func(s string) (any, bool) {\n k := reflect.ValueOf(s).Convert(keyType)\n if mv := v.MapIndex(k); mv.IsValid() {\n return mv.Interface(), true\n }\n return nil, false\n }\n if r, ok := lookup(field); ok {\n return r\n }\n if cap := capitalize(field); cap != field {\n if r, ok := lookup(cap); ok {\n return r\n }\n }\n if low := decapitalize(field); low != field {\n if r, ok := lookup(low); ok {\n return r\n }\n }\n // All-lowercase fallback: a Go-initialism field projects as an\n // all-caps key (`id` \u2192 `ID`), and `decapitalize("ID")` only\n // lowers the first char (`iD`), so the JS-keyed map ("id") still\n // misses. Try the fully-lowered key last to resolve it.\n if lower := strings.ToLower(field); lower != field && lower != decapitalize(field) {\n if r, ok := lookup(lower); ok {\n return r\n }\n }\n return nil\n }\n\n if v.Kind() != reflect.Struct {\n return nil\n }\n\n fieldVal := v.FieldByName(field)\n if !fieldVal.IsValid() {\n // Case-variant fallback: the evaluator carries the JS field name\n // (`id` / `url`) against a Go-capitalised struct field (`ID` / `URL`),\n // which exact `FieldByName` misses and the initialism rules can\'t be\n // reproduced char-for-char here. Match case-insensitively instead \u2014\n // `FieldByNameFunc` returns the zero Value (\u2192 nil) for an ambiguous\n // match, so it stays safe. The legacy bf_sort/bf_reduce pass an\n // already-capitalised name, so they hit the exact match above and never\n // reach this fallback.\n fieldVal = v.FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, field) })\n if !fieldVal.IsValid() {\n return nil\n }\n }\n return fieldVal.Interface()\n}\n\n// AsMap normalizes a dynamically-typed prop value into a\n// map[string]interface{} for object-valued context bindings\n// (`lowerProviderMapMemberValue`, go-template-adapter.ts). A caller-side\n// `interface{}` field can legally hold ANY string-keyed map kind \u2014 a Go\n// handler modelling `Record<string, string>` naturally passes\n// map[string]string \u2014 so a bare `.(map[string]interface{})` type assertion\n// would silently drop provided values (#2111 review). Returns nil (never an\n// empty map) when the value is absent \u2014 nil interface, typed-nil map or\n// pointer, or any non-map / non-string-keyed value \u2014 so the generated\n// `?? {}` fallback can distinguish "missing" (fall back) from "present but\n// empty" (use as-is). map[string]interface{} passes through without copying.\nfunc AsMap(v any) map[string]interface{} {\n if v == nil {\n return nil\n }\n if m, ok := v.(map[string]interface{}); ok {\n if m == nil {\n return nil\n }\n return m\n }\n rv := reflect.ValueOf(v)\n if rv.Kind() == reflect.Ptr {\n if rv.IsNil() {\n return nil\n }\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Map || rv.Type().Key().Kind() != reflect.String || rv.IsNil() {\n return nil\n }\n out := make(map[string]interface{}, rv.Len())\n iter := rv.MapRange()\n for iter.Next() {\n out[iter.Key().String()] = iter.Value().Interface()\n }\n return out\n}\n\n// capitalize uppercases the first character of a string.\nfunc capitalize(s string) string {\n if s == "" {\n return s\n }\n return strings.ToUpper(s[:1]) + s[1:]\n}\n\n// decapitalize lowercases the first character of a string. Used by\n// `getFieldValue`\'s map-receiver fallback when the projected key\n// name is PascalCase but the receiver carries lowercase JS-style\n// keys (the inverse of the `capitalize` lookup).\nfunc decapitalize(s string) string {\n if s == "" {\n return s\n }\n return strings.ToLower(s[:1]) + s[1:]\n}\n\n// Reduce folds an array into a scalar via the arithmetic-fold\n// catalogue (#1448 Tier C). It lowers `Array.prototype.reduce(fn, init)`\n// and `Array.prototype.reduceRight(fn, init)` for the shapes\n// `(acc, x) => acc <op> x` and `(acc, x) => acc <op> x.field`:\n//\n// bf_reduce <items> "<op>" "<keyKind>" "<keyName>" "<type>" "<init>" "<direction>"\n//\n// direction: "left" (reduce) | "right" (reduceRight). Only changes the\n// result for string concatenation; numeric folds commute.\n//\n// op: "+" | "*"\n// keyKind: "self" | "field"\n// keyName: "" when keyKind == "self"; capitalised struct field name\n// (e.g. "Duration") otherwise\n// type: "numeric" | "string"\n// init: the fold\'s start value \u2014 the compiler emits the *decoded*\n// seed, so numeric inits arrive as canonical decimal\n// (`1_000`/`0x10` already normalised to `1000`/`16`) that\n// ParseFloat accepts, and string inits arrive as escape-free\n// contents\n//\n// Numeric folds accumulate as float64; each projected key is read via\n// `toFloat64WithOK`, so numeric *strings* ("5" \u2192 5) parse and\n// non-numeric values fold as 0 \u2014 matching Perl\'s\n// `looks_like_number ? $n : 0` so the two template adapters stay\n// byte-equal. String folds concatenate (toString per projected key,\n// matching the documented `bf->string(undef) === ""` convention). The\n// init seeds the accumulator, so an empty receiver returns the init\n// unchanged \u2014 exactly like JS `reduce(fn, init)`. Anything outside the\n// catalogue refuses at compile time (BF101 from the JSX compiler) and\n// never reaches here.\n//\n// Two documented divergences from the JS / Hono path, both rare and\n// mirroring the `bf_sort` "auto" caveat:\n// - float64 stringification differs for sums whose binary expansion\n// isn\'t exact (e.g. 0.1 + 0.2);\n// - numeric-*string* keys fold numerically here, but JS `+`\n// string-concatenates once an operand is a string, so\n// numeric-string data can render differently under CSR.\n//\n// Genuine numbers \u2014 the common SSR case \u2014 agree across all three.\nfunc Reduce(items any, op, keyKind, keyName, typ, init, direction string) any {\n v := reflect.ValueOf(items)\n isSlice := v.Kind() == reflect.Slice || v.Kind() == reflect.Array\n\n // `direction == "right"` (reduceRight) folds right-to-left. Only\n // observable for string concatenation \u2014 numeric sum / product are\n // commutative, so the order doesn\'t change the result there. Build a\n // start/stop/step triple so both folds share one loop shape.\n start, stop, step := 0, 0, 1\n if isSlice {\n stop = v.Len()\n if direction == "right" {\n start, stop, step = v.Len()-1, -1, -1\n }\n }\n\n if typ == "string" {\n acc := init\n if isSlice {\n for i := start; i != stop; i += step {\n key := projectSortKey(v.Index(i).Interface(), keyKind, keyName)\n acc += toString(key)\n }\n }\n return acc\n }\n\n // numeric fold\n acc, _ := strconv.ParseFloat(strings.TrimSpace(init), 64)\n if isSlice {\n for i := start; i != stop; i += step {\n key := projectSortKey(v.Index(i).Interface(), keyKind, keyName)\n // `toFloat64WithOK` parses numeric *strings* ("5" \u2192 5) and\n // returns 0 for non-numeric values \u2014 mirroring Perl\'s\n // `looks_like_number ? $n : 0` so numeric-string data folds\n // byte-equal across adapters (the same rule `bf_sort`\'s\n // "auto" compare uses). Plain `toFloat64` would zero "5".\n n, _ := toFloat64WithOK(key)\n if op == "*" {\n acc *= n\n } else {\n acc += n\n }\n }\n }\n return acc\n}\n\n// =============================================================================\n// HTML/Template Helpers\n// =============================================================================\n\n// Comment returns an HTML comment string for hydration markers.\n// The "bf-" prefix is automatically added.\nfunc Comment(content string) template.HTML {\n return template.HTML("<!--bf-" + content + "-->")\n}\n\n// TextStart returns an HTML comment start marker for reactive text expressions.\n// Format: <!--bf:slotId-->\nfunc TextStart(slotId string) template.HTML {\n return template.HTML("<!--bf:" + slotId + "-->")\n}\n\n// TextEnd returns an HTML comment end marker for reactive text expressions.\n// Format: <!--/-->\nfunc TextEnd() template.HTML {\n return "<!--/-->"\n}\n\n// ScopeComment emits a fragment-rooted scope marker. See spec/compiler.md\n// "Slot identity" for the wire format. Loud-fails on marshal errors\n// (same policy as JSON / BfPropsAttr).\nfunc ScopeComment(props interface{}) (template.HTML, error) {\n scopeID := getStringField(props, "ScopeID")\n hostSegment := ""\n if host := getStringField(props, "BfParent"); host != "" {\n mount := getStringField(props, "BfMount")\n hostSegment = "|h=" + host + "|m=" + mount\n }\n propsJSON := ""\n if getBoolField(props, "BfIsRoot") {\n pJSON, err := json.Marshal(props)\n if err != nil {\n return "", err\n }\n propsJSON = "|" + string(pJSON)\n }\n return template.HTML("<!--bf-scope:" + scopeID + hostSegment + propsJSON + "-->"), nil\n}\n\n// ScopeCommentEnd emits the paired end marker for a fragment-rooted scope\n// (#2289): a fragment root has no single wrapping element to bound the\n// client\'s scope query, so the range leaks onto later siblings without an\n// explicit terminator. Carries only the scope id \u2014 no `|h=`/`|m=`/props\n// segment, unlike ScopeComment \u2014 since the client only needs it to confirm\n// the range closes on the matching scope (getCommentScopeBoundary in\n// packages/client/src/runtime/scope.ts).\nfunc ScopeCommentEnd(props interface{}) template.HTML {\n scopeID := getStringField(props, "ScopeID")\n return template.HTML("<!--bf-/scope:" + scopeID + "-->")\n}\n\n// TemplateFuncMap returns the helpers that need access to the executing\n// template set itself, closed over the *template.Template the component\n// defines are parsed into. Register it alongside FuncMap BEFORE parsing:\n//\n// t := template.New("")\n// t.Funcs(bf.FuncMap()).Funcs(bf.TemplateFuncMap(t))\n// template.Must(t.Parse(src))\n//\n// bf_tmpl executes a named define from the same set and returns its\n// output \u2014 used for the per-call-site children defines the Go adapter\n// emits when JSX children passed to an imported component contain\n// template actions (nested components, dynamic text) and therefore\n// cannot be baked to a static HTML string (#1896). Reentrant execution\n// of an html/template set from inside a FuncMap function is safe: the\n// escape analysis over every define completes before the outer\n// Execute begins evaluating.\nfunc TemplateFuncMap(t *template.Template) template.FuncMap {\n return template.FuncMap{\n "bf_tmpl": func(name string, data interface{}) (template.HTML, error) {\n var buf bytes.Buffer\n if err := t.ExecuteTemplate(&buf, name, data); err != nil {\n return "", err\n }\n return template.HTML(buf.String()), nil\n },\n }\n}\n\n// WithChildren returns a shallow copy of a component Props struct with its\n// Children field replaced by the given pre-rendered fragment (#1896). The\n// props value stays by-value semantics: callers\' originals are untouched.\n// A props type without a Children field passes through unchanged \u2014 the\n// child template then simply has no children to render, matching the\n// pre-#1896 behaviour.\nfunc WithChildren(props interface{}, children template.HTML) (interface{}, error) {\n v := reflect.ValueOf(props)\n if v.Kind() == reflect.Ptr {\n v = v.Elem()\n }\n if v.Kind() != reflect.Struct {\n return props, nil\n }\n field := v.FieldByName("Children")\n if !field.IsValid() {\n return props, nil\n }\n copyPtr := reflect.New(v.Type())\n copyPtr.Elem().Set(v)\n target := copyPtr.Elem().FieldByName("Children")\n switch {\n case target.Kind() == reflect.Interface:\n target.Set(reflect.ValueOf(children))\n case target.Kind() == reflect.String:\n // Covers both `string` and `template.HTML`-typed fields.\n target.SetString(string(children))\n default:\n return props, fmt.Errorf("bf_with_children: unsupported Children field type %s", target.Type())\n }\n return copyPtr.Elem().Interface(), nil\n}\n\n// WithProps returns a shallow copy of a component Props struct with the\n// given fields overridden (#2445): a child component nested inside a\n// COMPOSITE loop row (row root is a plain element, not the child itself) is\n// constructed ONCE outside `{{range}}` \u2014 every row shares that instance for\n// scope/parent/mount identity \u2014 so a prop that depends on the row\n// (`text={row.label}`) has to be applied per row, at template-execution\n// time, the same way WithChildren applies per-row JSX children to that\n// shared instance. `kv` is a flat name/value list ("Text", .Label, "Count",\n// .N, ...). The props value stays by-value semantics: the caller\'s original\n// is untouched. A name with no matching settable field is left alone for\n// that pair \u2014 the prop routes elsewhere (e.g. a rest bag) and the base\n// instance\'s constructor-built value stands, mirroring WithChildren\'s\n// "props type without a Children field" passthrough.\n//\n// This overrides fields on the ALREADY-CONSTRUCTED instance \u2014 it does not\n// re-run New<Child>Props. A field the child derives FROM the overridden prop\n// at construction time (a memo body, or a signal\'s initial value \u2014 the\n// constructor bakes both) would keep whatever the one-shot constructor\n// computed and never update per row; only the directly-overridden field is\n// correct per row. The compiler therefore does not route that case here: a\n// child with any constructor-derived field gets a generated props rebuilder\n// and the call site emits bf_reprops instead, which re-runs the real\n// constructor per row (#2448, see reprops.go). What reaches this helper is\n// the plain-passthrough case, where patching the field IS the whole update.\n//\n// Still exported and still registered: templates generated before #2448 call\n// it, and it remains the cheaper path when nothing is derived.\nfunc WithProps(props interface{}, kv ...interface{}) (interface{}, error) {\n if len(kv)%2 != 0 {\n return nil, fmt.Errorf("bf_with_props: odd number of key/value arguments (%d)", len(kv))\n }\n v := reflect.ValueOf(props)\n if v.Kind() == reflect.Ptr {\n v = v.Elem()\n }\n if v.Kind() != reflect.Struct {\n return props, nil\n }\n copyPtr := reflect.New(v.Type())\n copyPtr.Elem().Set(v)\n for i := 0; i < len(kv); i += 2 {\n name, ok := kv[i].(string)\n if !ok {\n return nil, fmt.Errorf("bf_with_props: field name at position %d must be a string, got %T", i, kv[i])\n }\n target := copyPtr.Elem().FieldByName(name)\n if !target.IsValid() || !target.CanSet() {\n continue\n }\n if err := setStructFieldValue(target, kv[i+1]); err != nil {\n return nil, fmt.Errorf("bf_with_props: field %s: %w", name, err)\n }\n }\n return copyPtr.Elem().Interface(), nil\n}\n\n// setStructFieldValue assigns val into target, a settable struct field\n// obtained via reflect.Value.FieldByName. Mirrors WithChildren\'s\n// Interface/String branches (a String-kind target covers both `string` and\n// `template.HTML` fields, via String() rather than reflect.Convert \u2014 Go\'s\n// int-to-string conversion produces a rune, not a decimal string) and adds\n// the general assignable/convertible fallback for other field kinds\n// (numeric widening, etc.).\nfunc setStructFieldValue(target reflect.Value, val interface{}) error {\n if val == nil {\n target.Set(reflect.Zero(target.Type()))\n return nil\n }\n switch {\n case target.Kind() == reflect.Interface:\n target.Set(reflect.ValueOf(val))\n return nil\n case target.Kind() == reflect.String:\n target.SetString(String(val))\n return nil\n }\n rv := reflect.ValueOf(val)\n switch {\n case rv.Type().AssignableTo(target.Type()):\n target.Set(rv)\n case rv.Type().ConvertibleTo(target.Type()):\n target.Set(rv.Convert(target.Type()))\n default:\n return fmt.Errorf("cannot assign %T to %s", val, target.Type())\n }\n return nil\n}\n\n// PortalHTML parses and executes a template string with the provided data.\n// Used for rendering dynamic portal content where the template string\n// contains Go template expressions (e.g., {{if .Open}}open{{end}}).\n//\n// The template string is parsed fresh each time to support dynamic content.\n// Standard Go template functions (if, range, eq, etc.) are available.\nfunc PortalHTML(data interface{}, tmplStr string) template.HTML {\n // Create a new template with the FuncMap for custom functions\n t, err := template.New("portal").Funcs(FuncMap()).Parse(tmplStr)\n if err != nil {\n // Return error message as HTML comment for debugging\n return template.HTML("<!-- bfPortalHTML error: " + err.Error() + " -->")\n }\n\n var buf bytes.Buffer\n if err := t.Execute(&buf, data); err != nil {\n return template.HTML("<!-- bfPortalHTML exec error: " + err.Error() + " -->")\n }\n\n return template.HTML(buf.String())\n}\n\n// =============================================================================\n// Portal Collection\n// =============================================================================\n\n// PortalContent represents a single portal\'s content to be rendered at body end.\ntype PortalContent struct {\n ID string // Unique portal ID for hydration matching\n OwnerID string // Owner scope ID for find() support\n Content template.HTML // Portal HTML content\n}\n\n// PortalCollector collects portal content during template rendering.\n// Portal content is rendered at </body> to avoid z-index issues.\ntype PortalCollector struct {\n portals []PortalContent\n counter int\n}\n\n// NewPortalCollector creates a new PortalCollector.\nfunc NewPortalCollector() *PortalCollector {\n return &PortalCollector{\n portals: []PortalContent{},\n counter: 0,\n }\n}\n\n// Add registers portal content to be rendered at body end.\nfunc (pc *PortalCollector) Add(ownerID string, content template.HTML) string {\n pc.counter++\n id := "bf-portal-" + strconv.Itoa(pc.counter)\n pc.portals = append(pc.portals, PortalContent{\n ID: id,\n OwnerID: ownerID,\n Content: content,\n })\n return "" // Return empty string for template use\n}\n\n// Render outputs all collected portals as HTML.\n// Each portal is wrapped in a div with bf-pi (portal ID) and bf-po (portal owner).\nfunc (pc *PortalCollector) Render() template.HTML {\n if pc == nil || len(pc.portals) == 0 {\n return ""\n }\n var buf strings.Builder\n for _, p := range pc.portals {\n buf.WriteString(`<div bf-pi="`)\n buf.WriteString(p.ID)\n buf.WriteString(`" bf-po="`)\n buf.WriteString(p.OwnerID)\n buf.WriteString(`">`)\n buf.WriteString(string(p.Content))\n buf.WriteString("</div>\\n")\n }\n return template.HTML(buf.String())\n}\n\n// =============================================================================\n// Script Collection\n// =============================================================================\n\n// ScriptCollector collects client scripts with deduplication.\n// It preserves insertion order for deterministic output.\ntype ScriptCollector struct {\n scripts map[string]bool\n order []string\n}\n\n// NewScriptCollector creates a new ScriptCollector.\nfunc NewScriptCollector() *ScriptCollector {\n return &ScriptCollector{\n scripts: make(map[string]bool),\n order: []string{},\n }\n}\n\n// Register adds a script source to the collection.\n// Duplicate scripts are ignored (only first registration counts).\nfunc (sc *ScriptCollector) Register(src string) string {\n if sc.scripts[src] {\n return "" // Already registered\n }\n sc.scripts[src] = true\n sc.order = append(sc.order, src)\n return "" // Return empty string for template use\n}\n\n// Scripts returns all registered scripts in insertion order.\nfunc (sc *ScriptCollector) Scripts() []string {\n return sc.order\n}\n\n// BfScripts generates script tags for all registered scripts.\n// Returns HTML safe for embedding in templates.\nfunc BfScripts(collector *ScriptCollector) template.HTML {\n if collector == nil {\n return ""\n }\n var result strings.Builder\n for _, src := range collector.Scripts() {\n result.WriteString(`<script type="module" src="`)\n result.WriteString(src)\n result.WriteString(`"></script>`)\n result.WriteString("\\n")\n }\n return template.HTML(result.String())\n}\n\n// =============================================================================\n// Component Renderer\n// =============================================================================\n\n// RenderContext contains all data needed to render a component page.\n// The layout function receives this context to build the final HTML.\ntype RenderContext struct {\n // ComponentName is the template name being rendered\n ComponentName string\n\n // Props is the component props (for layout to access if needed)\n Props interface{}\n\n // ComponentHTML is the rendered component template output\n ComponentHTML template.HTML\n\n // Portals contains collected portal content to render at body end\n Portals template.HTML\n\n // Scripts contains the collected JS script tags\n Scripts template.HTML\n\n // Title is the page title (defaults to "{ComponentName} - BarefootJS")\n Title string\n\n // Heading is the page heading. Empty string means no heading.\n Heading string\n\n // Extra holds additional user-defined data for the layout\n Extra map[string]interface{}\n}\n\n// LayoutFunc renders the final HTML page given the render context.\ntype LayoutFunc func(ctx *RenderContext) string\n\n// Renderer renders BarefootJS components with a customizable layout.\ntype Renderer struct {\n templates *template.Template\n layout LayoutFunc\n}\n\n// NewRenderer creates a Renderer with the given templates and layout function.\n//\n// Example usage:\n//\n// renderer := bf.NewRenderer(templates, func(ctx *bf.RenderContext) string {\n// return fmt.Sprintf(`<!DOCTYPE html>\n// <html>\n// <head><title>%s</title></head>\n// <body>%s%s</body>\n// </html>`, ctx.Title, ctx.ComponentHTML, ctx.Scripts)\n// })\nfunc NewRenderer(tmpl *template.Template, layout LayoutFunc) *Renderer {\n return &Renderer{\n templates: tmpl,\n layout: layout,\n }\n}\n\n// RenderOptions configures a single render call.\ntype RenderOptions struct {\n // ComponentName is the template name to render (required)\n ComponentName string\n\n // Props is the component props (must be a pointer to struct with Scripts field)\n Props interface{}\n\n // Title is the page title. If empty, defaults to "{ComponentName} - BarefootJS"\n Title string\n\n // Heading is the page heading. If empty, no heading is shown.\n Heading string\n\n // Extra holds additional data to pass to the layout\n Extra map[string]interface{}\n}\n\n// Render renders a component to a full HTML page using the configured layout.\n// Child component props are automatically detected (any slice field with ScopeID/Scripts).\n// renderTemplateErrorPanel formats a Go template execution error into a\n// fragment of HTML that\'s visible in the browser. The panel is\n// HTML-escaped so a faulty template name (anything from `template:\n// "..."`) can\'t smuggle markup back into the page. Keep the styling\n// inline so the panel surfaces even when the project\'s CSS hasn\'t\n// loaded yet (e.g. the failure aborted before the stylesheet links\n// emitted).\n//\n// Surfaced for the #1442 echo repro: a template referencing\n// `.Todo.Done` (instead of the range dot\'s `.Done`) used to fail\n// silently \u2014 Go\'s html/template aborted mid-stream, the partial body\n// flushed as a 200, and the user saw a truncated list with no console\n// signal. With this panel they get the template name, the error\n// message, and a "what to look at" hint inline.\nfunc renderTemplateErrorPanel(componentName string, err error) string {\n return `<div style="margin:1em 0;padding:1em;border:2px solid #d33;background:#fff5f5;color:#900;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;line-height:1.5"><strong style="display:block;margin-bottom:.5em">Template error in <code>` +\n template.HTMLEscapeString(componentName) +\n `</code></strong><pre style="margin:0;white-space:pre-wrap;word-break:break-word">` +\n template.HTMLEscapeString(err.Error()) +\n `</pre><div style="margin-top:.75em;font-size:12px;opacity:.7">Common cause: a JSX expression referenced a name the adapter could not resolve to a struct field. Open the matching <code>dist/templates/*.tmpl</code> for the unresolved reference, then fix the source component.</div></div>`\n}\n\n// renderComponentInto wires a component\'s props (script/portal collectors,\n// child-slot scope ids + hydration, root marking) against the PROVIDED\n// collectors and returns just the component\'s HTML \u2014 no layout. Both Render\n// (one fresh collector pair per page) and RenderFragment (a shared pair across\n// several islands) funnel through here so their wiring stays identical.\nfunc (r *Renderer) renderComponentInto(opts RenderOptions, scriptCollector *ScriptCollector, portalCollector *PortalCollector) template.HTML {\n // Inject the shared collectors into the props.\n setScriptsField(opts.Props, scriptCollector)\n setPortalsField(opts.Props, portalCollector)\n\n // Auto-detect and process child component props (slices)\n childSlices := findChildComponentSlices(opts.Props)\n for _, slice := range childSlices {\n setScopeIDsOnSlice(slice)\n setScriptsOnSlice(slice, scriptCollector)\n setPortalsOnSlice(slice, portalCollector)\n setBoolOnSlice(slice, "BfIsChild", true)\n }\n\n // Auto-detect and process single child component props\n singleChildren := findSingleChildComponents(opts.Props)\n for _, child := range singleChildren {\n setScopeIDOnSingle(child)\n setScriptsOnSingle(child, scriptCollector)\n setPortalsOnSingle(child, portalCollector)\n setBoolField(child, "BfIsChild", true)\n }\n\n // Mark the root component so BfPropsAttr emits bf-p only for it\n setBoolField(opts.Props, "BfIsRoot", true)\n\n // Render the component template.\n //\n // Errors here are NOT silently dropped. The original implementation\n // ignored the return value of `ExecuteTemplate`, which masked a real\n // onboarding failure mode: a template referencing a non-existent\n // field (`.Todo.Done` instead of the range dot\'s `.Done`) caused\n // html/template to abort mid-stream, the partial output got\n // returned, and the HTTP server happily flushed a 200 with a\n // truncated body. No error log, no signal \u2014 the user just saw a\n // blank list (#1442 echo TodoApp repro).\n //\n // Now we capture the error and replace the partial output with a\n // visible inline panel (dev mode) or a fenced error comment\n // (production), so the cause is on-screen and grep-able in logs.\n // Either way the renderer also writes to stderr so structured log\n // aggregators see it.\n var componentBuf strings.Builder\n if err := r.templates.ExecuteTemplate(&componentBuf, opts.ComponentName, opts.Props); err != nil {\n fmt.Fprintf(os.Stderr, "barefoot: template %q failed to render: %v\\n", opts.ComponentName, err)\n // Preserve whatever the template did manage to emit before\n // failing (Go\'s text/template flushes incrementally), but\n // follow it with a clearly-marked error block so the user\n // notices something is wrong instead of seeing a silent\n // truncation.\n componentBuf.WriteString(renderTemplateErrorPanel(opts.ComponentName, err))\n }\n\n return template.HTML(componentBuf.String())\n}\n\nfunc (r *Renderer) Render(opts RenderOptions) string {\n // One script + portal collector pair for the whole page.\n scriptCollector := NewScriptCollector()\n portalCollector := NewPortalCollector()\n\n componentHTML := r.renderComponentInto(opts, scriptCollector, portalCollector)\n\n // Determine title (default: "{ComponentName} - BarefootJS")\n title := opts.Title\n if title == "" {\n title = opts.ComponentName + " - BarefootJS"\n }\n\n // Heading (empty means no heading)\n heading := opts.Heading\n\n // Build render context\n ctx := &RenderContext{\n ComponentName: opts.ComponentName,\n Props: opts.Props,\n ComponentHTML: componentHTML,\n Portals: portalCollector.Render(),\n Scripts: BfScripts(scriptCollector),\n Title: title,\n Heading: heading,\n Extra: opts.Extra,\n }\n\n return r.layout(ctx)\n}\n\n// RenderFragment renders a single island subtree into the caller-provided\n// script and portal collectors and returns just its HTML \u2014 no page layout.\n//\n// It exists for hand-authored "region shell" pages (the `@barefootjs/router`\n// showcase): a layout that places several independent islands \u2014 e.g. a header\n// ThemeToggle, an `<aside bf-region>` Sidebar, and a `<PageShell>` wrapping the\n// route content \u2014 must collect ALL their scripts and portals into ONE place so\n// the runtime (`barefoot.js`) and each island\'s client JS are emitted exactly\n// once and share a single reactive instance. Render each island with the same\n// collectors, splice the returned HTML into the shell, then emit\n// `BfScripts(sc)` and `pc.Render()` once at the end of the document.\n//\n// Each fragment is treated as its own root (it emits `bf-p` like any top-level\n// island); nested children declared in its props are wired as children, exactly\n// as in Render.\nfunc (r *Renderer) RenderFragment(opts RenderOptions, scriptCollector *ScriptCollector, portalCollector *PortalCollector) template.HTML {\n return r.renderComponentInto(opts, scriptCollector, portalCollector)\n}\n\n// setScriptsField sets the Scripts field on a struct using reflection.\nfunc setScriptsField(v interface{}, collector *ScriptCollector) {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return\n }\n field := val.FieldByName("Scripts")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n}\n\n// setPortalsField sets the Portals field on a struct using reflection.\nfunc setPortalsField(v interface{}, collector *PortalCollector) {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return\n }\n field := val.FieldByName("Portals")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n}\n\n// getStringField extracts a string field from a struct using reflection.\nfunc setBoolField(v interface{}, fieldName string, val bool) {\n rv := reflect.ValueOf(v)\n if rv.Kind() == reflect.Ptr {\n rv = rv.Elem()\n }\n if rv.Kind() != reflect.Struct {\n return\n }\n field := rv.FieldByName(fieldName)\n if field.IsValid() && field.CanSet() && field.Kind() == reflect.Bool {\n field.SetBool(val)\n }\n}\n\nfunc getBoolField(v interface{}, fieldName string) bool {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return false\n }\n field := val.FieldByName(fieldName)\n if !field.IsValid() || field.Kind() != reflect.Bool {\n return false\n }\n return field.Bool()\n}\n\nfunc getStringField(v interface{}, fieldName string) string {\n val := reflect.ValueOf(v)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return ""\n }\n field := val.FieldByName(fieldName)\n if !field.IsValid() || field.Kind() != reflect.String {\n return ""\n }\n return field.String()\n}\n\n// scopeIDChars is the alphabet for auto-generated ScopeID suffixes. It\n// mirrors the `randomID` helper the go-template adapter emits into the\n// generated New<Component>Props constructors so runtime-assigned and\n// constructor-assigned ids are indistinguishable.\nconst scopeIDChars = "abcdefghijklmnopqrstuvwxyz0123456789"\n\n// randomScopeSuffix returns a random lowercase-alphanumeric string of\n// length n. math/rand (auto-seeded since Go 1.20) is sufficient here: the\n// suffix only needs to be unique enough to keep a page\'s bf-s scope ids\n// from colliding, not cryptographically unpredictable.\nfunc randomScopeSuffix(n int) string {\n b := make([]byte, n)\n for i := range b {\n b[i] = scopeIDChars[rand.Intn(len(scopeIDChars))]\n }\n return string(b)\n}\n\n// scopeIDPrefix derives the human-readable ScopeID prefix from a child\n// component\'s type, e.g. `TodoItemProps` \u2192 `TodoItem`. Matches the\n// `"<Component>_" + randomID(6)` shape the generated constructors use.\nfunc scopeIDPrefix(t reflect.Type) string {\n for t.Kind() == reflect.Ptr {\n t = t.Elem()\n }\n return strings.TrimSuffix(t.Name(), "Props")\n}\n\n// assignScopeID fills a child component\'s ScopeID with a generated id when\n// the caller left it empty, so application code doesn\'t have to mint scope\n// ids by hand (the parent\'s New<Component>Props constructor does the same\n// for components built through it). A non-empty ScopeID is left untouched,\n// so callers can still pin a stable id when they need one.\nfunc assignScopeID(structVal reflect.Value, prefix string) {\n field := structVal.FieldByName("ScopeID")\n if !field.IsValid() || !field.CanSet() || field.Kind() != reflect.String {\n return\n }\n if field.String() != "" {\n return\n }\n id := randomScopeSuffix(6)\n if prefix != "" {\n id = prefix + "_" + id\n }\n field.SetString(id)\n}\n\n// setScopeIDsOnSlice assigns a generated ScopeID to every child in a slice\n// whose ScopeID is empty.\nfunc setScopeIDsOnSlice(slice interface{}) {\n v := reflect.ValueOf(slice)\n if v.Kind() != reflect.Slice {\n return\n }\n prefix := scopeIDPrefix(v.Type().Elem())\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i)\n if item.Kind() == reflect.Ptr {\n if item.IsNil() {\n continue\n }\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n assignScopeID(item, prefix)\n }\n }\n}\n\n// setScopeIDOnSingle assigns a generated ScopeID to a single child\n// component when its ScopeID is empty.\nfunc setScopeIDOnSingle(child interface{}) {\n v := reflect.ValueOf(child)\n if v.Kind() == reflect.Ptr {\n v = v.Elem()\n }\n if v.Kind() != reflect.Struct {\n return\n }\n assignScopeID(v, scopeIDPrefix(v.Type()))\n}\n\n// findChildComponentSlices finds slice fields containing child component props.\n// Child props are identified by having ScopeID and Scripts fields.\nfunc findChildComponentSlices(props interface{}) []interface{} {\n var result []interface{}\n\n val := reflect.ValueOf(props)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return result\n }\n\n for i := 0; i < val.NumField(); i++ {\n field := val.Field(i)\n if field.Kind() != reflect.Slice || field.Len() == 0 {\n continue\n }\n\n elem := field.Index(0)\n if elem.Kind() == reflect.Ptr {\n elem = elem.Elem()\n }\n if elem.Kind() != reflect.Struct {\n continue\n }\n\n hasScopeID := elem.FieldByName("ScopeID").IsValid()\n hasScripts := elem.FieldByName("Scripts").IsValid()\n\n if hasScopeID && hasScripts {\n result = append(result, field.Interface())\n }\n }\n\n return result\n}\n\n// setScriptsOnSlice sets Scripts on all items in a slice.\nfunc setScriptsOnSlice(slice interface{}, collector *ScriptCollector) {\n val := reflect.ValueOf(slice)\n if val.Kind() != reflect.Slice {\n return\n }\n for i := 0; i < val.Len(); i++ {\n item := val.Index(i)\n if item.Kind() == reflect.Ptr {\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n field := item.FieldByName("Scripts")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n }\n}\n\n// setBoolOnSlice sets a bool field on all items in a slice.\nfunc setBoolOnSlice(slice interface{}, fieldName string, val bool) {\n v := reflect.ValueOf(slice)\n if v.Kind() != reflect.Slice {\n return\n }\n for i := 0; i < v.Len(); i++ {\n item := v.Index(i)\n if item.Kind() == reflect.Ptr {\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n field := item.FieldByName(fieldName)\n if field.IsValid() && field.CanSet() && field.Kind() == reflect.Bool {\n field.SetBool(val)\n }\n }\n }\n}\n\n// setPortalsOnSlice sets Portals on all items in a slice.\nfunc setPortalsOnSlice(slice interface{}, collector *PortalCollector) {\n val := reflect.ValueOf(slice)\n if val.Kind() != reflect.Slice {\n return\n }\n for i := 0; i < val.Len(); i++ {\n item := val.Index(i)\n if item.Kind() == reflect.Ptr {\n item = item.Elem()\n }\n if item.Kind() == reflect.Struct {\n field := item.FieldByName("Portals")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n }\n}\n\n// findSingleChildComponents finds single struct fields containing child component props.\n// Child props are identified by having ScopeID and Scripts fields.\nfunc findSingleChildComponents(props interface{}) []interface{} {\n var result []interface{}\n\n val := reflect.ValueOf(props)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() != reflect.Struct {\n return result\n }\n\n for i := 0; i < val.NumField(); i++ {\n field := val.Field(i)\n\n // Handle pointer to struct\n if field.Kind() == reflect.Ptr {\n if field.IsNil() {\n continue\n }\n field = field.Elem()\n }\n\n // Skip non-struct fields (slices handled by findChildComponentSlices)\n if field.Kind() != reflect.Struct {\n continue\n }\n\n hasScopeID := field.FieldByName("ScopeID").IsValid()\n hasScripts := field.FieldByName("Scripts").IsValid()\n\n if hasScopeID && hasScripts {\n result = append(result, field.Addr().Interface())\n }\n }\n\n return result\n}\n\n// setScriptsOnSingle sets Scripts on a single struct child component.\nfunc setScriptsOnSingle(child interface{}, collector *ScriptCollector) {\n val := reflect.ValueOf(child)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() == reflect.Struct {\n field := val.FieldByName("Scripts")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n}\n\n// setPortalsOnSingle sets Portals on a single struct child component.\nfunc setPortalsOnSingle(child interface{}, collector *PortalCollector) {\n val := reflect.ValueOf(child)\n if val.Kind() == reflect.Ptr {\n val = val.Elem()\n }\n if val.Kind() == reflect.Struct {\n field := val.FieldByName("Portals")\n if field.IsValid() && field.CanSet() {\n field.Set(reflect.ValueOf(collector))\n }\n }\n}\n\n// Nullish implements JS `??` for template use (`bf_nullish`, #2248): returns\n// fallback iff v is nil (untyped nil or a nil pointer/map/slice boxed in the\n// interface), otherwise v \u2014 so present-but-falsy `""`/`0`/`false` are KEPT,\n// unlike the truthiness-based template `or`.\nfunc Nullish(v, fallback any) any {\n if v == nil {\n return fallback\n }\n rv := reflect.ValueOf(v)\n switch rv.Kind() {\n case reflect.Ptr, reflect.Map, reflect.Slice, reflect.Interface, reflect.Func, reflect.Chan:\n if rv.IsNil() {\n return fallback\n }\n }\n return v\n}\n\n// ToInt exposes the runtime\'s numeric coercion for generated constructors\n// (#2248): a nillable-lowered numeric prop arrives as `interface{}`, and an\n// untyped Go literal (`Size: 3`) boxes as int even when the prop is\n// float64-shaped \u2014 a direct type assertion would panic where JS accepts the\n// number. Non-numeric values coerce to 0, matching the helpers\' behaviour.\nfunc ToInt(v any) int { return toInt(v) }\n\n// ToFloat64 is ToInt\'s float64 counterpart \u2014 see ToInt.\nfunc ToFloat64(v any) float64 { return toFloat64(v) }\n\n// =============================================================================\n// Internal Helpers\n// =============================================================================\n\nfunc toFloat64(v any) float64 {\n switch n := v.(type) {\n case int:\n return float64(n)\n case int8:\n return float64(n)\n case int16:\n return float64(n)\n case int32:\n return float64(n)\n case int64:\n return float64(n)\n case uint:\n return float64(n)\n case uint8:\n return float64(n)\n case uint16:\n return float64(n)\n case uint32:\n return float64(n)\n case uint64:\n return float64(n)\n case float32:\n return float64(n)\n case float64:\n return n\n default:\n return 0\n }\n}\n\nfunc toInt(v any) int {\n switch n := v.(type) {\n case int:\n return n\n case int8:\n return int(n)\n case int16:\n return int(n)\n case int32:\n return int(n)\n case int64:\n return int(n)\n case uint:\n return int(n)\n case uint8:\n return int(n)\n case uint16:\n return int(n)\n case uint32:\n return int(n)\n case uint64:\n return int(n)\n case float32:\n return int(n)\n case float64:\n return int(n)\n default:\n return 0\n }\n}\n\nfunc isIntLike(v any) bool {\n switch v.(type) {\n case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:\n return true\n default:\n return false\n }\n}\n\nfunc toString(v any) string {\n switch s := v.(type) {\n case string:\n return s\n case int:\n return strconv.Itoa(s)\n case int64:\n return strconv.FormatInt(s, 10)\n case float64:\n return strconv.FormatFloat(s, \'f\', -1, 64)\n case bool:\n return strconv.FormatBool(s)\n default:\n rv := reflect.ValueOf(v)\n if rv.Kind() == reflect.Slice || rv.Kind() == reflect.Array {\n // JS `Array.prototype.toString` is `this.join(\',\')`, applied\n // recursively \u2014 a nested array element stringifies the same\n // way rather than via Go\'s `%v`. Reached via `Join`/`ConcatStr`\n // on an element that is itself an array (e.g. `.flat(0)`\'s\n // shallow copy joined afterwards, #2262).\n parts := make([]string, rv.Len())\n for i := 0; i < rv.Len(); i++ {\n parts[i] = toString(rv.Index(i).Interface())\n }\n return strings.Join(parts, ",")\n }\n return ""\n }\n}\n\n// =============================================================================\n// searchParams() \u2014 request-scoped environment signal (router v0.5, #1922)\n// =============================================================================\n\n// SearchParams is the SSR view of the request query string behind the\n// reactive searchParams() environment signal. The route handler builds it\n// from the request URL and assigns it to the component\'s SearchParams input\n// field; the generated template reads it via `.SearchParams.Get "key"`.\n//\n// The zero value is an empty query (url.Values.Get tolerates a nil map), so a\n// render with no request query \u2014 e.g. the adapter conformance harness, which\n// issues no query string \u2014 resolves every key to "", which the template\'s\n// `or`/`??` fallback turns into the author\'s default.\ntype SearchParams struct {\n values url.Values\n}\n\n// NewSearchParams parses a raw query string (with or without a leading "?")\n// into a SearchParams. A malformed query yields an empty set rather than an\n// error, mirroring the browser\'s URLSearchParams, which never throws on junk.\n//\n// Typical handler use (net/http):\n//\n// in := MyComponentInput{SearchParams: bf.NewSearchParams(r.URL.RawQuery)}\nfunc NewSearchParams(raw string) SearchParams {\n raw = strings.TrimPrefix(raw, "?")\n values, err := url.ParseQuery(raw)\n if err != nil {\n values = url.Values{}\n }\n return SearchParams{values: values}\n}\n\n// Get returns the first value associated with key, or "" when the key is\n// absent. This mirrors url.Values.Get, which also returns "" for a\n// present-but-empty value (`?sort=`). Safe on the zero value (nil map).\n//\n// This is not byte-for-byte URLSearchParams.get under the template\'s `??`\n// lowering. JS distinguishes absent (`null`) from present-but-empty (`""`):\n// `null ?? d` yields the default, but `"" ?? d` keeps the empty string. The\n// Go adapter lowers `??` to the `or` builtin \u2014 Go templates have no\n// null-coalescing operator \u2014 so here BOTH an absent key and a present-but-\n// empty value fall back to the author\'s default. The conformance fixture only\n// exercises the absent-key default, where the two runtimes agree; the\n// empty-string divergence is the same general `?? \u2192 or` limitation that\n// applies to any `x ?? default` the Go adapter lowers.\nfunc (s SearchParams) Get(key string) string {\n return s.values.Get(key)\n}\n';
31960
32509
  evalGoSource = 'package bf\n\nimport (\n "encoding/json"\n "errors"\n "math"\n "reflect"\n "regexp"\n "sort"\n "strconv"\n "strings"\n "unicode/utf8"\n)\n\n// =============================================================================\n// Lightweight ParsedExpr evaluator (issue #2018)\n// =============================================================================\n//\n// Templates cannot carry a lambda in expression position, which is why the\n// adapter historically special-cased higher-order callbacks (reduce / sort /\n// map / filter / find) into fixed shapes (bf_sort\'s comparator catalogue,\n// bf_reduce\'s +/* fold). This evaluator replaces that ad-hoc list: a callback\n// BODY is carried as a pure `ParsedExpr` subtree (the same structured IR the\n// compiler already produces) and evaluated here against an environment\n// (`{acc, item, \u2026captured free vars}`).\n//\n// Scope: higher-order callback bodies only. Ordinary expressions stay lowered\n// to template-native syntax \u2014 this is NOT a general expression engine.\n//\n// The accepted pure subset and its semantics (evaluation order, coercion,\n// equality, allowed operators / builtins) are documented in spec/compiler.md\n// ("ParsedExpr Evaluator Semantics") and pinned isomorphically by the golden\n// vectors in packages/adapter-tests/vectors/eval-vectors.json (shared\n// with the Perl evaluator). The coercion rules below are the literal JS rules\n// (ToNumber / ToString / ToBoolean) \u2014 deliberately NOT the divergent\n// bf->string / bf_reduce helper conventions \u2014 so the contract is unambiguous\n// and the backends stay byte-isomorphic.\n//\n// String semantics operate on Unicode code points / UTF-8 bytes: `.length`\n// counts code points and relational `<`/`>` compares byte order. This equals\n// the JS reference (UTF-16 code units) across the BMP \u2014 the range template\n// data uses \u2014 and matches the Perl evaluator exactly (the primary "same input\n// \u2192 same output" contract is between the two SSR backends). Astral-plane\n// characters (where JS counts a surrogate pair as length 2 and orders by\n// surrogate code units) are a documented divergence region, alongside the\n// already-documented non-ASCII relational / localeCompare carve-outs\n// (spec/compiler.md). Both backends stay equal; only the JS reference differs,\n// and no corpus vector exercises the astral range.\n\n// EvalExpr evaluates a pure ParsedExpr (carried as its JSON encoding) against\n// env. The result is a value in the JSON domain: a number (Go int or float64 \u2014\n// both are the single JS number type; e.g. `.length` returns int, arithmetic\n// returns float64), string, bool, nil, []any, or map[string]any. A malformed\n// tree yields nil.\nfunc EvalExpr(exprJSON string, env map[string]any) any {\n var node any\n if err := json.Unmarshal([]byte(exprJSON), &node); err != nil {\n return nil\n }\n return EvalNode(node, env)\n}\n\n// EvalNode evaluates an already-decoded ParsedExpr node (a map[string]any with\n// a "kind" discriminator) against env. Exported so callers that already hold a\n// decoded tree (e.g. the golden-vector harness) skip a re-marshal.\nfunc EvalNode(node any, env map[string]any) any {\n n, ok := node.(map[string]any)\n if !ok {\n return nil\n }\n kind, _ := n["kind"].(string)\n switch kind {\n case "literal":\n return n["value"]\n\n case "identifier":\n name, _ := n["name"].(string)\n return env[name]\n\n case "binary":\n op, _ := n["op"].(string)\n return evalBinary(op, EvalNode(n["left"], env), EvalNode(n["right"], env))\n\n case "unary":\n op, _ := n["op"].(string)\n return evalUnary(op, EvalNode(n["argument"], env))\n\n case "logical":\n op, _ := n["op"].(string)\n left := EvalNode(n["left"], env)\n switch op {\n case "&&":\n if !evalTruthy(left) {\n return left\n }\n return EvalNode(n["right"], env)\n case "||":\n if evalTruthy(left) {\n return left\n }\n return EvalNode(n["right"], env)\n case "??":\n if left == nil {\n return EvalNode(n["right"], env)\n }\n return left\n }\n return nil\n\n case "conditional":\n if evalTruthy(EvalNode(n["test"], env)) {\n return EvalNode(n["consequent"], env)\n }\n return EvalNode(n["alternate"], env)\n\n case "member":\n prop, _ := n["property"].(string)\n return evalReadProperty(EvalNode(n["object"], env), prop)\n\n case "index-access":\n return evalReadIndex(EvalNode(n["object"], env), EvalNode(n["index"], env))\n\n case "call":\n // A nested `.map(cb)` / `.filter(cb)` callback call (#2094): syntactically\n // a `call` whose callee is `<recv>.map`/`<recv>.filter` and whose first\n // argument is an `arrow` \u2014 the SAME shape `asCallbackMethodCall`\n // recognizes at compile time, and the shape the `eval-vectors.json`\n // golden corpus itself carries (it stores the genuine `ParsedExpr`, not\n // a bespoke wrapper). Checked BEFORE the builtin-callee gate below,\n // since `<recv>.map` would otherwise resolve to a non-builtin member\n // callee and refuse.\n if method, objNode, arrowNode, ok := evalArrayCallbackCall(n); ok {\n return evalArrayCallback(method, objNode, arrowNode, env)\n }\n name := evalBuiltinName(n["callee"])\n if name == "" {\n return nil\n }\n rawArgs, _ := n["args"].([]any)\n args := make([]any, len(rawArgs))\n for i, a := range rawArgs {\n args[i] = EvalNode(a, env)\n }\n return evalCallBuiltin(name, args)\n\n case "template-literal":\n parts, _ := n["parts"].([]any)\n var sb strings.Builder\n for _, p := range parts {\n pm, _ := p.(map[string]any)\n if t, _ := pm["type"].(string); t == "string" {\n s, _ := pm["value"].(string)\n sb.WriteString(s)\n } else {\n sb.WriteString(evalToString(EvalNode(pm["expr"], env)))\n }\n }\n return sb.String()\n\n case "array-literal":\n elems, _ := n["elements"].([]any)\n out := make([]any, len(elems))\n for i, e := range elems {\n out[i] = EvalNode(e, env)\n }\n return out\n\n case "object-literal":\n props, _ := n["properties"].([]any)\n out := make(map[string]any, len(props))\n for _, p := range props {\n pm, _ := p.(map[string]any)\n key, _ := pm["key"].(string)\n out[key] = EvalNode(pm["value"], env)\n }\n return out\n\n case "array-method":\n // `.includes(x)` / `.join(sep?)` are the `array-method` shapes the\n // evaluator executes (the JS reference\'s `evaluate` "array-method" arm,\n // eval-reference.ts). A nested `.map`/`.filter` is NOT an\n // `array-method` node \u2014 it reaches the `call` case above (it carries\n // an `arrow` callback, not a plain `args` list). Every other\n // array/string method (`slice`, `flat`, \u2026) is refused upstream\n // (BF101) and never reaches here.\n method, _ := n["method"].(string)\n rawArgs, _ := n["args"].([]any)\n if method == "includes" && len(rawArgs) == 1 {\n return evalIncludes(EvalNode(n["object"], env), EvalNode(rawArgs[0], env))\n }\n if method == "join" && len(rawArgs) <= 1 {\n sep := ","\n if len(rawArgs) == 1 {\n sep = evalToString(EvalNode(rawArgs[0], env))\n }\n return evalJoin(EvalNode(n["object"], env), sep)\n }\n return nil\n }\n // arrow-fn / higher-order / unsupported: a callback body containing these\n // is refused upstream (BF101); never reached here.\n return nil\n}\n\n// evalArrayCallbackCall reports whether the decoded `call` node `n` is a\n// nested `.map(cb)` / `.filter(cb)` callback call (#2094): its callee is a\n// non-computed member `<recv>.map`/`<recv>.filter` and its first argument is\n// an `arrow` node. Returns the method name, the (still-encoded) receiver\n// object node, and the (still-encoded) arrow node.\nfunc evalArrayCallbackCall(n map[string]any) (method string, object any, arrow map[string]any, ok bool) {\n callee, _ := n["callee"].(map[string]any)\n if callee == nil || callee["kind"] != "member" {\n return "", nil, nil, false\n }\n if computed, _ := callee["computed"].(bool); computed {\n return "", nil, nil, false\n }\n prop, _ := callee["property"].(string)\n if prop != "map" && prop != "filter" {\n return "", nil, nil, false\n }\n rawArgs, _ := n["args"].([]any)\n if len(rawArgs) == 0 {\n return "", nil, nil, false\n }\n arrowNode, _ := rawArgs[0].(map[string]any)\n if arrowNode == nil || arrowNode["kind"] != "arrow" {\n return "", nil, nil, false\n }\n return prop, callee["object"], arrowNode, true\n}\n\n// evalArrayCallback executes a nested `.map`/`.filter` callback call: evaluates\n// the receiver, then evaluates the arrow body per element in a CHILD env that\n// binds the arrow\'s first param to the element and (when the arrow declares a\n// second param) the second to the integer index \u2014 both 1- and 2-param arrows\n// are supported. `map` keeps one result per element (order-preserving);\n// `filter` keeps the elements whose body evaluates truthy. A non-array\n// receiver degrades to nil (unreachable for a body the compiler validated,\n// since the receiver of a nested `.map`/`.filter` is itself gated upstream).\nfunc evalArrayCallback(method string, objectNode any, arrowNode map[string]any, env map[string]any) any {\n arr := toAnySlice(EvalNode(objectNode, env))\n if arr == nil {\n return nil\n }\n rawParams, _ := arrowNode["params"].([]any)\n params := make([]string, len(rawParams))\n for i, p := range rawParams {\n params[i], _ = p.(string)\n }\n body := arrowNode["body"]\n callCb := func(item any, index int) any {\n inner := make(map[string]any, len(env)+2)\n for k, v := range env {\n inner[k] = v\n }\n if len(params) > 0 {\n inner[params[0]] = item\n }\n if len(params) > 1 {\n inner[params[1]] = index\n }\n return EvalNode(body, inner)\n }\n if method == "map" {\n out := make([]any, len(arr))\n for i, item := range arr {\n out[i] = callCb(item, i)\n }\n return out\n }\n out := []any{}\n for i, item := range arr {\n if evalTruthy(callCb(item, i)) {\n out = append(out, item)\n }\n }\n return out\n}\n\n// evalJoin implements `.join(sep)`: elements ToString\'d and joined; a\n// null/undefined element ToStrings to the empty string (matching JS\n// `Array.prototype.join`, which skips null/undefined rather than rendering\n// the literal string "null"/"undefined"). A non-array receiver degrades to\n// the empty string (unreachable for a validated body).\nfunc evalJoin(obj any, sep string) string {\n arr := toAnySlice(obj)\n if arr == nil {\n return ""\n }\n parts := make([]string, len(arr))\n for i, el := range arr {\n if el == nil {\n parts[i] = ""\n continue\n }\n parts[i] = evalToString(el)\n }\n return strings.Join(parts, sep)\n}\n\n// ---------------------------------------------------------------------------\n// JS coercion primitives (ToNumber / ToString / ToBoolean), pinned so the\n// evaluator matches the JS reference. These are JS-faithful and intentionally\n// distinct from the bf->string / Number helpers, which diverge (null \u2192 "",\n// null/"" \u2192 NaN) for SSR-survival reasons that do not apply to the evaluator.\n// ---------------------------------------------------------------------------\n\n// jsDecimalNumberRe matches the JS StringToNumber decimal numeric literal\n// grammar (ASCII digits only): optional sign, then integer/fraction digits,\n// then an optional exponent. It deliberately excludes underscore digit\n// separators, radix prefixes (0x/0o/0b), and hex-float forms \u2014 none of which\n// are valid JS decimal numeric literals.\nvar jsDecimalNumberRe = regexp.MustCompile(`^[+-]?(?:[0-9]+\\.?[0-9]*|\\.[0-9]+)(?:[eE][+-]?[0-9]+)?$`)\n\nfunc evalToNumber(v any) float64 {\n switch x := v.(type) {\n case nil:\n return 0\n case bool:\n if x {\n return 1\n }\n return 0\n case string:\n t := strings.TrimSpace(x)\n if t == "" {\n return 0\n }\n // Exact JS Infinity spellings (case-sensitive, no other aliases like\n // "infinity"/"inf" are valid JS numeric strings).\n switch t {\n case "Infinity", "+Infinity":\n return math.Inf(1)\n case "-Infinity":\n return math.Inf(-1)\n }\n // Decimal / exponent numeric strings parse JS-faithfully, including\n // overflow: strconv.ParseFloat rejects underscores, hex-floats, and\n // non-canonical "inf"/"nan" spellings via the anchored decimal-grammar\n // gate below, so those correctly yield NaN. The radix-prefixed forms\n // JS Number() also accepts ("0x10" / "0o17" / "0b101") are a\n // documented divergence region: they fail the decimal grammar (a\n // leading "0x"/"0o"/"0b" is not a valid decimal literal) and yield\n // NaN here, as they do in the Perl evaluator (looks_like_number is\n // false for them), so Go==Perl while differing from the JS\n // reference. Template data carries JSON numbers, not radix-string\n // literals, so this never arises in practice.\n if !jsDecimalNumberRe.MatchString(t) {\n return math.NaN()\n }\n f, err := strconv.ParseFloat(t, 64)\n if err == nil {\n return f\n }\n if errors.Is(err, strconv.ErrRange) {\n // ParseFloat still returns the correctly-signed \xB1Inf (or a\n // subnormal) as its best-effort value on overflow/underflow;\n // JS Number() on an overflowing decimal literal yields \xB1Infinity\n // (e.g. "1e1000" -> +Infinity), so surface that value as-is.\n return f\n }\n return math.NaN()\n default:\n if evalIsNumeric(v) {\n return toFloat64(v)\n }\n return math.NaN()\n }\n}\n\n// evalIsNumeric reports whether v is one of the Go numeric types (all of\n// which are the single JS "number" type to the evaluator).\nfunc evalIsNumeric(v any) bool {\n switch v.(type) {\n case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:\n return true\n }\n return false\n}\n\nfunc evalToString(v any) string {\n if v == nil {\n return "null"\n }\n // JS spells the non-finite doubles "Infinity" / "-Infinity" / "NaN"; the\n // runtime String() helper (fmt %v) would render "+Inf" / "-Inf" / "NaN",\n // so the non-finite cases are pinned here to stay JS-faithful (and match\n // the Perl evaluator\'s _to_string). Strings, bools and nil are JS-faithful\n // through String() (nil is already handled above as "null").\n //\n // Finite-number formatting is a documented divergence region. Go\'s fmt is\n // shortest-round-trip (it matches JS\'s *digits*, e.g. 0.1+0.2 \u2192\n // "0.30000000000000004"), but its exponent threshold/padding differ from\n // JS Number::toString for very large / very small magnitudes (Go renders\n // 1e6 as "1e+06" where JS keeps "1000000", and "1e-07" vs JS "1e-7").\n // Perl\'s `%.15g` instead diverges on *precision* (the pinned helper-vector\n // "0.3" case). A fully JS-faithful Number::toString is not reimplemented\n // here because Perl has no shortest-round-trip formatter, so the three\n // could never all agree; the common integer / short-decimal range \u2014 what\n // arithmetic over template data produces \u2014 renders identically across all\n // three. Only the \xB10 sign is normalised below, since it is cheap and the\n // realisticly-reachable case.\n if f, ok := v.(float64); ok {\n if math.IsNaN(f) {\n return "NaN"\n }\n if math.IsInf(f, 1) {\n return "Infinity"\n }\n if math.IsInf(f, -1) {\n return "-Infinity"\n }\n // JS `String(-0)` is "0", but fmt %v renders Go\'s negative zero as\n // "-0". `f == 0` matches both \xB10, normalising to JS\'s spelling. (A\n // unary `-` on a zero operand is the way the evaluator can produce -0.)\n if f == 0 {\n return "0"\n }\n }\n // Non-primitive operands (arrays / objects) in ToString position are\n // outside the evaluator subset \u2014 the JS reference refuses them, and the\n // compiler gates such a callback body with BF101 before it ever reaches\n // the runtime. This evaluator runs already-validated bodies, so it does\n // not re-reject here; String() (fmt %v) is a best-effort fallback for that\n // unreachable path, never exercised by the in-subset corpus.\n return String(v)\n}\n\nfunc evalTruthy(v any) bool {\n return isTruthy(v)\n}\n\n// ---------------------------------------------------------------------------\n// Operators\n// ---------------------------------------------------------------------------\n\nfunc evalBinary(op string, l, r any) any {\n switch op {\n case "+":\n // JS `+`: string concatenation once either operand is a string,\n // numeric addition otherwise.\n if _, lok := l.(string); lok {\n return evalToString(l) + evalToString(r)\n }\n if _, rok := r.(string); rok {\n return evalToString(l) + evalToString(r)\n }\n return evalToNumber(l) + evalToNumber(r)\n case "-":\n return evalToNumber(l) - evalToNumber(r)\n case "*":\n return evalToNumber(l) * evalToNumber(r)\n case "/":\n return evalToNumber(l) / evalToNumber(r)\n case "%":\n return math.Mod(evalToNumber(l), evalToNumber(r))\n case "<", "<=", ">", ">=":\n return evalRelational(op, l, r)\n case "===":\n return evalStrictEq(l, r)\n case "!==":\n return !evalStrictEq(l, r)\n }\n // Loose equality / bitwise / shift are out of the subset.\n return nil\n}\n\nfunc evalRelational(op string, l, r any) bool {\n // JS Abstract Relational Comparison: both strings \u2192 compare by code\n // unit; otherwise coerce both to numbers (a NaN operand makes every\n // comparison false).\n var c int\n ls, lok := l.(string)\n rs, rok := r.(string)\n if lok && rok {\n if ls < rs {\n c = -1\n } else if ls > rs {\n c = 1\n }\n } else {\n ln := evalToNumber(l)\n rn := evalToNumber(r)\n if math.IsNaN(ln) || math.IsNaN(rn) {\n return false\n }\n if ln < rn {\n c = -1\n } else if ln > rn {\n c = 1\n }\n }\n switch op {\n case "<":\n return c < 0\n case "<=":\n return c <= 0\n case ">":\n return c > 0\n case ">=":\n return c >= 0\n }\n return false\n}\n\nfunc evalStrictEq(l, r any) bool {\n // Strict `===`: equal JS type and value, no coercion. All numeric Go\n // types are the single JS "number" type, so int 2 === float64 2.\n lnum := evalIsNumeric(l)\n rnum := evalIsNumeric(r)\n if lnum && rnum {\n lf, rf := toFloat64(l), toFloat64(r)\n if math.IsNaN(lf) || math.IsNaN(rf) {\n return false\n }\n return lf == rf\n }\n if lnum != rnum {\n return false\n }\n switch lv := l.(type) {\n case nil:\n return r == nil\n case string:\n rv, ok := r.(string)\n return ok && rv == lv\n case bool:\n rv, ok := r.(bool)\n return ok && rv == lv\n }\n // Non-primitive operands (arrays / objects) are outside the subset: the JS\n // reference refuses `===` on them and the compiler gates such a body with\n // BF101 upstream, so this is unreachable for an in-subset corpus. The\n // runtime trusts that gate rather than re-validating, returning false\n // here (it does not attempt JS reference identity, which templates can\'t\n // model anyway).\n return false\n}\n\n// evalSameValueZero implements `Array.prototype.includes`\'s membership\n// comparison: `===` except `NaN` equals itself (JS\'s SameValueZero). Reuses\n// evalStrictEq \u2014 the one divergence, both operands NaN, is checked first;\n// every other pair (including "one NaN, one not") falls through to the same\n// equality `evalBinary`\'s `===` uses, so the two operators stay in lockstep.\nfunc evalSameValueZero(a, b any) bool {\n if evalIsNumeric(a) && evalIsNumeric(b) {\n af, bf := toFloat64(a), toFloat64(b)\n if math.IsNaN(af) && math.IsNaN(bf) {\n return true\n }\n }\n return evalStrictEq(a, b)\n}\n\n// evalIncludes implements `.includes(needle)`, shared between\n// `Array.prototype.includes` (SameValueZero membership over a slice/array\n// receiver) and `String.prototype.includes` (substring search), mirroring\n// the receiver-type dispatch the SSR template lowering already does\n// (`bf_includes`). Any other receiver type is not a JS `.includes` target;\n// this degrades to false rather than panicking (there is no receiver here\n// for which JS itself would throw), matching the JS reference\n// (eval-reference.ts `includes`).\nfunc evalIncludes(obj, needle any) bool {\n if arr := toAnySlice(obj); arr != nil {\n for _, el := range arr {\n if evalSameValueZero(el, needle) {\n return true\n }\n }\n return false\n }\n if s, ok := obj.(string); ok {\n return strings.Contains(s, evalToString(needle))\n }\n return false\n}\n\nfunc evalUnary(op string, v any) any {\n switch op {\n case "!":\n return !evalTruthy(v)\n case "-":\n return -evalToNumber(v)\n case "+":\n return evalToNumber(v)\n }\n return nil\n}\n\n// ---------------------------------------------------------------------------\n// Built-in calls (the deterministic allowlist). Locale-sensitive builtins\n// (localeCompare) are deliberately excluded to keep the backends isomorphic.\n// ---------------------------------------------------------------------------\n\n// evalBuiltinName resolves a `call` callee to its builtin name (e.g.\n// "Math.max"), or "" if the callee is not an allowlisted builtin reference.\nfunc evalBuiltinName(callee any) string {\n cm, ok := callee.(map[string]any)\n if !ok {\n return ""\n }\n switch cm["kind"] {\n case "identifier":\n name, _ := cm["name"].(string)\n return name\n case "member":\n if computed, _ := cm["computed"].(bool); computed {\n return ""\n }\n obj, _ := cm["object"].(map[string]any)\n if obj == nil || obj["kind"] != "identifier" {\n return ""\n }\n objName, _ := obj["name"].(string)\n prop, _ := cm["property"].(string)\n return objName + "." + prop\n }\n return ""\n}\n\n// evalMathRound rounds a half toward +Infinity (JS Math.round: 2.5\u21923,\n// -2.5\u2192-2), matching the existing `round` helper rather than Go\'s math.Round.\nfunc evalMathRound(n float64) float64 {\n // floor(n+0.5) yields +0 for x in [-0.5, -0], where JS Math.round returns\n // -0. That sign is only observable through a subsequent division\n // (`1 / Math.round(-0.5)` is -Infinity in JS, +Infinity here) \u2014 through\n // ToString both \xB10 render "0". It is left as +0 deliberately: the two SSR\n // backends must stay equal, and Perl can\'t reproduce the -0 divisor sign\n // without fragile, version-dependent zero handling (its native `/` even\n // dies on a zero divisor). So Math.round\'s -0 is a JS-reference-only\n // divergence region, like the astral-plane / radix-string carve-outs.\n return math.Floor(n + 0.5)\n}\n\nfunc evalCallBuiltin(name string, args []any) any {\n switch name {\n case "Math.max":\n if len(args) == 0 {\n return math.Inf(-1)\n }\n m := evalToNumber(args[0])\n for _, a := range args[1:] {\n m = math.Max(m, evalToNumber(a))\n }\n return m\n case "Math.min":\n if len(args) == 0 {\n return math.Inf(1)\n }\n m := evalToNumber(args[0])\n for _, a := range args[1:] {\n m = math.Min(m, evalToNumber(a))\n }\n return m\n case "Math.abs":\n return math.Abs(evalToNumber(arg0(args)))\n case "Math.floor":\n return math.Floor(evalToNumber(arg0(args)))\n case "Math.ceil":\n return math.Ceil(evalToNumber(arg0(args)))\n case "Math.round":\n return evalMathRound(evalToNumber(arg0(args)))\n case "String":\n return evalToString(arg0(args))\n case "Number":\n return evalToNumber(arg0(args))\n case "Boolean":\n return evalTruthy(arg0(args))\n }\n // Any other callee is outside the subset (refused upstream).\n return nil\n}\n\nfunc arg0(args []any) any {\n if len(args) == 0 {\n return nil\n }\n return args[0]\n}\n\n// ---------------------------------------------------------------------------\n// Member / index access\n// ---------------------------------------------------------------------------\n\nfunc evalReadProperty(obj any, key string) any {\n switch o := obj.(type) {\n case string:\n if key == "length" {\n // Code-point count (== JS UTF-16 length across the BMP, == Perl\n // `length`). RuneCountInString avoids the []rune allocation since\n // this can run inside comparator / reducer evaluation.\n return utf8.RuneCountInString(o)\n }\n return nil\n case []any:\n if key == "length" {\n return len(o)\n }\n return nil\n case map[string]any:\n // Case-variant lookup: a callback body reads the raw JS property\n // (`t.duration`), but test data / Go-keyed maps may carry PascalCase\n // keys (`{"Duration": \u2026}`). Reuse the field reader the sort / reduce\n // helpers use \u2014 it resolves `duration` \u2192 `Duration` and reads a\n // genuinely missing key as null (the backends\' single absent value).\n return getFieldValue(o, key)\n case nil:\n return nil\n default:\n // Real template data (Go structs): reuse the field reader the sort /\n // reduce helpers use, which handles case-variant keys.\n return getFieldValue(obj, key)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Evaluator-driven higher-order folds (the generalization of bf_reduce /\n// bf_sort onto the evaluator)\n//\n// These prove the evaluator subsumes the special-cased callback catalogue:\n// the callback BODY is carried as a pure ParsedExpr (JSON) and evaluated per\n// element against an environment, so the op restriction (bf_reduce\'s +/*),\n// the acc-canonical form, and the comparator pattern restriction (bf_sort)\n// all disappear \u2014 any pure reducer / comparator body works. They are the\n// runtime half of the integration; the compiler-side emit migration (carrying\n// callback bodies as ParsedExpr) and the byte-equal divergence decision for\n// the string-`localeCompare` sort path (won\'t-fix for byte-equal SSR, see\n// spec/compiler.md Known limitations) are the remaining follow-up.\n// ---------------------------------------------------------------------------\n\n// toAnySlice copies a reflect-iterable receiver into a fresh []any, returning\n// nil for a non-slice/array (matching the bf_sort / bf_reduce nil-tolerance).\nfunc toAnySlice(items any) []any {\n v := reflect.ValueOf(items)\n // A nil interface yields an invalid Value (Kind() == Invalid, not a\n // panic), so the Slice/Array guard below already tolerates nil; the\n // explicit IsValid check just documents the nil-tolerance intent.\n if !v.IsValid() || (v.Kind() != reflect.Slice && v.Kind() != reflect.Array) {\n return nil\n }\n out := make([]any, v.Len())\n for i := range out {\n out[i] = v.Index(i).Interface()\n }\n return out\n}\n\n// FoldEval folds items into a value via the ParsedExpr evaluator. The reducer\n// body is a pure ParsedExpr (JSON) evaluated against `{accName: acc, itemName:\n// item}` plus the captured free vars in `baseEnv` for each element; `init`\n// seeds the accumulator and `direction` is "left" (reduce) or "right"\n// (reduceRight). This is the evaluator-based generalization of bf_reduce \u2014 any\n// reducer body, not just the `+`/`*` arithmetic catalogue, and `acc` may\n// appear anywhere in the body. `baseEnv` may be nil when the body captures no\n// outer references; the accName / itemName keys shadow any same-named base key.\nfunc FoldEval(items any, bodyJSON, accName, itemName string, init any, direction string, baseEnv map[string]any) any {\n var body any\n if err := json.Unmarshal([]byte(bodyJSON), &body); err != nil {\n return init\n }\n arr := toAnySlice(items)\n if direction == "right" {\n for i, j := 0, len(arr)-1; i < j; i, j = i+1, j-1 {\n arr[i], arr[j] = arr[j], arr[i]\n }\n }\n acc := init\n // Seed the env from the captured free vars once; acc / item are\n // overwritten each iteration (constant base keys carry through).\n env := make(map[string]any, len(baseEnv)+2)\n for k, v := range baseEnv {\n env[k] = v\n }\n for _, item := range arr {\n env[accName] = acc\n env[itemName] = item\n acc = EvalNode(body, env)\n }\n return acc\n}\n\n// SortEval returns a new stable-sorted slice ordered by a ParsedExpr\n// comparator body (JSON) evaluated against `{paramA: a, paramB: b}` plus the\n// captured free vars in `baseEnv` to a number (negative / zero / positive,\n// like a JS comparator). This is the evaluator-based generalization of bf_sort\n// \u2014 any comparator body, not just the subtraction / relational-ternary\n// catalogue. `baseEnv` may be nil. Non-mutating.\nfunc SortEval(items any, cmpJSON, paramA, paramB string, baseEnv map[string]any) []any {\n arr := toAnySlice(items)\n if arr == nil {\n return nil\n }\n var cmp any\n if err := json.Unmarshal([]byte(cmpJSON), &cmp); err != nil {\n return arr\n }\n // One env seeded from the captured free vars; the two operand keys are\n // overwritten per comparison (the comparator runs synchronously).\n env := make(map[string]any, len(baseEnv)+2)\n for k, v := range baseEnv {\n env[k] = v\n }\n sort.SliceStable(arr, func(i, j int) bool {\n env[paramA] = arr[i]\n env[paramB] = arr[j]\n return evalToNumber(EvalNode(cmp, env)) < 0\n })\n return arr\n}\n\n// ---------------------------------------------------------------------------\n// Evaluator-driven higher-order predicates (#2018, P2) \u2014 the generalization\n// of bf_filter / bf_find / bf_find_index / bf_every / bf_some onto the\n// evaluator. The predicate BODY travels as a pure ParsedExpr (JSON) and is\n// evaluated per element against `{param: item}` plus the captured free vars in\n// `baseEnv`, lifting the field-equality / truthiness restriction of the\n// special-cased helpers to any pure predicate. `baseEnv` may be nil.\n// ---------------------------------------------------------------------------\n\n// decodeEvalBody unmarshals a serialized ParsedExpr body; ok is false on bad\n// JSON (only reachable by a corrupt emit \u2014 the adapter always emits valid JSON).\nfunc decodeEvalBody(bodyJSON string) (any, bool) {\n var body any\n if err := json.Unmarshal([]byte(bodyJSON), &body); err != nil {\n return nil, false\n }\n return body, true\n}\n\n// seedPredEnv copies the captured free vars into a fresh env with room for the\n// single predicate param, which the callers overwrite per element.\nfunc seedPredEnv(baseEnv map[string]any) map[string]any {\n env := make(map[string]any, len(baseEnv)+1)\n for k, v := range baseEnv {\n env[k] = v\n }\n return env\n}\n\n// FilterEval returns a new slice of the elements for which the predicate body\n// evaluates truthy \u2014 the evaluator generalization of bf_filter. Returns a\n// non-nil empty slice when nothing matches (so a downstream `range` / `bf_join`\n// sees a real slice); returns nil only on a bad body.\nfunc FilterEval(items any, predJSON, param string, baseEnv map[string]any) []any {\n pred, ok := decodeEvalBody(predJSON)\n if !ok {\n return nil\n }\n env := seedPredEnv(baseEnv)\n out := []any{}\n for _, item := range toAnySlice(items) {\n env[param] = item\n if evalTruthy(EvalNode(pred, env)) {\n out = append(out, item)\n }\n }\n return out\n}\n\n// EveryEval reports whether every element satisfies the predicate (vacuously\n// true for an empty receiver, like JS) \u2014 the generalization of bf_every.\nfunc EveryEval(items any, predJSON, param string, baseEnv map[string]any) bool {\n pred, ok := decodeEvalBody(predJSON)\n if !ok {\n return false\n }\n env := seedPredEnv(baseEnv)\n for _, item := range toAnySlice(items) {\n env[param] = item\n if !evalTruthy(EvalNode(pred, env)) {\n return false\n }\n }\n return true\n}\n\n// SomeEval reports whether any element satisfies the predicate (false for an\n// empty receiver, like JS) \u2014 the generalization of bf_some.\nfunc SomeEval(items any, predJSON, param string, baseEnv map[string]any) bool {\n pred, ok := decodeEvalBody(predJSON)\n if !ok {\n return false\n }\n env := seedPredEnv(baseEnv)\n for _, item := range toAnySlice(items) {\n env[param] = item\n if evalTruthy(EvalNode(pred, env)) {\n return true\n }\n }\n return false\n}\n\n// FindEval returns the first element satisfying the predicate, or nil when none\n// does \u2014 the generalization of bf_find. `forward` false searches from the end\n// (findLast).\nfunc FindEval(items any, predJSON, param string, forward bool, baseEnv map[string]any) any {\n pred, ok := decodeEvalBody(predJSON)\n if !ok {\n return nil\n }\n env := seedPredEnv(baseEnv)\n arr := toAnySlice(items)\n for n := range arr {\n i := n\n if !forward {\n i = len(arr) - 1 - n\n }\n env[param] = arr[i]\n if evalTruthy(EvalNode(pred, env)) {\n return arr[i]\n }\n }\n return nil\n}\n\n// FindIndexEval returns the index of the first element satisfying the predicate,\n// or -1 when none does \u2014 the generalization of bf_find_index. `forward` false\n// searches from the end (findLastIndex).\nfunc FindIndexEval(items any, predJSON, param string, forward bool, baseEnv map[string]any) int {\n pred, ok := decodeEvalBody(predJSON)\n if !ok {\n return -1\n }\n env := seedPredEnv(baseEnv)\n arr := toAnySlice(items)\n for n := range arr {\n i := n\n if !forward {\n i = len(arr) - 1 - n\n }\n env[param] = arr[i]\n if evalTruthy(EvalNode(pred, env)) {\n return i\n }\n }\n return -1\n}\n\n// FlatMapEval projects each element through the projection body (a pure\n// ParsedExpr JSON evaluated against `{param: item}` + baseEnv) and flattens the\n// results one level \u2014 the evaluator generalization of bf_flat_map /\n// bf_flat_map_tuple. A projection that yields a slice contributes its elements;\n// any other value contributes itself (matching JS `.flatMap`, where a non-array\n// return is kept as a single element). Returns a non-nil empty slice on a bad\n// body so a downstream `range` / `bf_join` sees a real slice.\nfunc FlatMapEval(items any, projJSON, param string, baseEnv map[string]any) []any {\n proj, ok := decodeEvalBody(projJSON)\n if !ok {\n return []any{}\n }\n env := seedPredEnv(baseEnv)\n out := []any{}\n for _, item := range toAnySlice(items) {\n env[param] = item\n v := EvalNode(proj, env)\n // Flatten any slice/array kind one level, not just []any: real Go\n // template data projects a field like `i.tags` to a typed slice\n // (`[]string` / `[]int`), which `toAnySlice` normalizes to []any. A\n // non-slice value (string / number / struct / nil) contributes itself,\n // matching JS `.flatMap` (a non-array return is kept as one element).\n if sub := toAnySlice(v); sub != nil {\n out = append(out, sub...)\n } else {\n out = append(out, v)\n }\n }\n return out\n}\n\n// MapEval projects each element through the projection body (a pure ParsedExpr\n// JSON evaluated against `{param: item}` + baseEnv), keeping one result per\n// element \u2014 the value-producing `.map(cb)` lowering (#2073). Unlike\n// FlatMapEval there is no flatten: a projection yielding a slice contributes\n// that slice as a single element, matching JS `.map`. Returns a non-nil empty\n// slice on a bad body so a downstream `range` / `bf_join` sees a real slice.\nfunc MapEval(items any, projJSON, param string, baseEnv map[string]any) []any {\n proj, ok := decodeEvalBody(projJSON)\n if !ok {\n return []any{}\n }\n env := seedPredEnv(baseEnv)\n out := []any{}\n for _, item := range toAnySlice(items) {\n env[param] = item\n out = append(out, EvalNode(proj, env))\n }\n return out\n}\n\n// Env builds the captured-free-var environment for FoldEval / SortEval from a\n// flat key, value, key, value, \u2026 argument list \u2014 the adapter emits\n// `bf_env "k1" v1 "k2" v2 \u2026` for the free variables a callback body references\n// beyond its own params. An odd trailing key with no value is ignored; with no\n// pairs it returns an empty (non-nil) map, the no-capture case. A non-string\n// key (only reachable by a malformed template call, never by the adapter\'s\n// quoted-literal emit) is skipped rather than collapsed into an `env[""]` slot.\nfunc Env(pairs ...any) map[string]any {\n env := make(map[string]any, len(pairs)/2)\n for i := 0; i+1 < len(pairs); i += 2 {\n key, ok := pairs[i].(string)\n if !ok {\n continue\n }\n env[key] = pairs[i+1]\n }\n return env\n}\n\nfunc evalReadIndex(obj any, index any) any {\n switch o := obj.(type) {\n case []any:\n f := evalToNumber(index)\n i := int(f)\n if float64(i) != f || i < 0 || i >= len(o) {\n return nil\n }\n return o[i]\n case map[string]any:\n return o[evalToString(index)]\n case nil:\n return nil\n default:\n return getFieldValue(obj, evalToString(index))\n }\n}\n';
32510
+ repropsGoSource = '// Per-row child props reconstruction (#2448).\n//\n// `bf_with_props` (#2445) overrides fields on a child\'s already-constructed\n// shared instance by reflection. That is correct for a plain passthrough prop\n// and WRONG for anything the child\'s constructor DERIVES from it: a\n// `createMemo` body and a `createSignal` initial value are both baked into the\n// struct once by `New<Child>Props`, and reflection cannot re-run that.\n//\n// This file is the fix. Instead of patching fields, the parent asks the child\n// to REBUILD its props: reconstruct the constructor Input from the base\n// instance, apply the row\'s overrides, re-run the real constructor. Every\n// derived field recomputes because the real Go code runs again.\n//\n// The constructor cannot be called from a template directly \u2014 `html/template`\n// has no expression language and can only call FuncMap entries. So the\n// compiler emits, per component, a closure that does the rebuild in generated\n// Go (typed field assignments, no reflection), and registers it here from the\n// generated package\'s `init()`. `FuncMap()` gains exactly ONE fixed entry,\n// `bf_reprops`, so `t.Funcs(bf.FuncMap())` keeps working unchanged.\n//\n// The registry is consulted at template EXECUTE time, never at `Funcs()` time.\n// That is load-bearing, not incidental: Go initializes a package\'s variables\n// BEFORE its `init()` functions, so an app that builds its template set in a\n// package-level var \u2014\n//\n// var tmpl = template.Must(template.New("").Funcs(bf.FuncMap()).ParseGlob(...))\n//\n// \u2014 calls `FuncMap()` while the registry is still empty. Merging the\n// constructors into `FuncMap()`\'s return value would fail that app at parse\n// time with `function "bf_new_Badge" not defined`. Looking them up behind one\n// fixed entry, at execute time, makes the ordering irrelevant.\npackage bf\n\nimport (\n "fmt"\n "reflect"\n "sync"\n)\n\n// RepropsFunc rebuilds a child component\'s props from a base instance plus a\n// flat name/value override list (`"Text", .Label, "N", .N, \u2026`), by re-running\n// the component\'s generated constructor.\n//\n// Names in `kv` are the Go FIELD names the PARENT computed from the JSX\n// attribute (`n=` \u2192 `"N"`). A generated implementation maps those onto its own\n// constructor Input, which is what makes an aliased destructure\n// (`{ n: count }`, whose field is `Count`) land correctly \u2014 the mapping lives\n// in generated code that knows both sides.\n//\n// Identity fields (ScopeID / BfParent / BfMount) MUST be carried over from the\n// base rather than re-derived: `New<Child>Props` mints a random ScopeID when\n// given an empty one, so a naive re-run would give every row its own scope and\n// break hydration. Fields that live only on Props and never on Input (Scripts,\n// BfIsChild, BfDataKey) must be carried over for the same reason.\ntype RepropsFunc func(base interface{}, kv ...interface{}) (interface{}, error)\n\nvar (\n repropsMu sync.RWMutex\n repropsRegistry = map[string]RepropsFunc{}\n)\n\n// RegisterReprops registers a component\'s props rebuilder under its component\n// name. Called from the generated package\'s `init()`; re-registering the same\n// name replaces the previous entry, so a rebuilt components file in a\n// long-lived dev process wins.\nfunc RegisterReprops(name string, fn RepropsFunc) {\n repropsMu.Lock()\n defer repropsMu.Unlock()\n repropsRegistry[name] = fn\n}\n\n// Reprops is the `bf_reprops` FuncMap entry: rebuild `base` with the row\'s\n// overrides applied, by re-running the named component\'s constructor.\n//\n// {{template "Badge" (bf_reprops "Badge" $.BadgeSlot0 "Text" .Label "N" .N)}}\n//\n// An unregistered name is an error rather than a silent passthrough: the\n// compiler only emits this call for a component whose rebuilder it also\n// emitted, so a missing entry means the generated package was not linked in,\n// and falling back to the stale shared instance would reintroduce exactly the\n// silently-wrong output this exists to prevent.\nfunc Reprops(name string, base interface{}, kv ...interface{}) (interface{}, error) {\n if len(kv)%2 != 0 {\n return nil, fmt.Errorf("bf_reprops: odd number of key/value arguments (%d) for %q", len(kv), name)\n }\n // Field names are validated HERE, not in each generated rebuilder, so the\n // generated `name, _ := kv[i].(string)` is safe by construction. Rejecting\n // them matches `bf_with_props`: a non-string name would otherwise degrade\n // to `""`, match no case, and silently drop the override \u2014 the same class\n // of silent misrender this whole path exists to remove.\n for i := 0; i < len(kv); i += 2 {\n if _, ok := kv[i].(string); !ok {\n return nil, fmt.Errorf(\n "bf_reprops: %s field name at position %d must be a string, got %T", name, i, kv[i])\n }\n }\n repropsMu.RLock()\n fn, ok := repropsRegistry[name]\n repropsMu.RUnlock()\n if !ok {\n return nil, fmt.Errorf(\n "bf_reprops: no props rebuilder registered for %q \u2014 is the generated components package linked into this binary?",\n name,\n )\n }\n return fn(base, kv...)\n}\n\n// RepropsTypeError is the error a generated rebuilder returns when handed a\n// value that is not its own props struct. Kept here so the generated code\n// stays a fixed shape instead of formatting its own message.\nfunc RepropsTypeError(name string, base interface{}) error {\n return fmt.Errorf("bf_reprops: the %s rebuilder got %T, not its own props struct", name, base)\n}\n\n// RepropsUnknownFieldError is what a generated rebuilder returns for a field\n// name it has no case for.\n//\n// Unlike `bf_with_props`, whose unknown-field passthrough is load-bearing (a\n// prop routed into a rest bag has no named field to set), a rebuilder is only\n// emitted for components with NO rest bag and no spread slot, and the compiler\n// emits a case for every prop the parent can override. So an unknown name here\n// is a compiler gap, and dropping it silently would leave the override\n// unapplied \u2014 the failure mode this path replaced.\nfunc RepropsUnknownFieldError(component, field string) error {\n return fmt.Errorf("bf_reprops: %s has no overridable field %q", component, field)\n}\n\n// RepropsAssign writes `val` into `*target`, which a generated rebuilder passes\n// as a pointer to one field of its constructor Input (`&in.N`).\n//\n// This is the one place the rebuild uses reflection, and it does so on purpose:\n// it delegates to the SAME `setStructFieldValue` that `bf_with_props` uses, so\n// a prop that already assigned correctly under the old helper assigns\n// identically under this one. Re-deriving the conversion rules per Go type in\n// the generator would be more code and would drift from that behaviour.\n//\n// `field` names the field for the error message; `component` names the owner.\nfunc RepropsAssign(component, field string, target interface{}, val interface{}) error {\n p := reflect.ValueOf(target)\n if p.Kind() != reflect.Ptr || p.IsNil() {\n return fmt.Errorf("bf_reprops: %s.%s: target must be a non-nil pointer, got %T", component, field, target)\n }\n if err := setStructFieldValue(p.Elem(), val); err != nil {\n return fmt.Errorf("bf_reprops: %s.%s: %w", component, field, err)\n }\n return nil\n}\n\n// RepropsRegistered reports whether a rebuilder is registered for `name`.\n// For tests and diagnostics.\nfunc RepropsRegistered(name string) bool {\n repropsMu.RLock()\n defer repropsMu.RUnlock()\n _, ok := repropsRegistry[name]\n return ok\n}\n';
31961
32511
  streamingGoSource = `// Package bf \u2014 Out-of-Order Streaming SSR helpers
31962
32512
  //
31963
32513
  // Provides StreamRenderer for progressive page rendering using HTTP
@@ -32176,6 +32726,7 @@ function goCommonFiles() {
32176
32726
  "env.go": GO_ENV_GO,
32177
32727
  "bf-runtime/bf.go": bfGoSource,
32178
32728
  "bf-runtime/eval.go": evalGoSource,
32729
+ "bf-runtime/reprops.go": repropsGoSource,
32179
32730
  "bf-runtime/streaming.go": streamingGoSource,
32180
32731
  "bf-runtime/bfdev/bfdev.go": bfdevGoSource,
32181
32732
  "bf-runtime/go.mod": GO_BF_RUNTIME_GO_MOD,
@@ -33255,6 +33806,7 @@ replace github.com/barefootjs/runtime/bf => ./bf-runtime
33255
33806
  "go.mod": ECHO_GO_MOD,
33256
33807
  "bf-runtime/bf.go": bfGoSource,
33257
33808
  "bf-runtime/eval.go": evalGoSource,
33809
+ "bf-runtime/reprops.go": repropsGoSource,
33258
33810
  "bf-runtime/streaming.go": streamingGoSource,
33259
33811
  "bf-runtime/go.mod": ECHO_BF_RUNTIME_GO_MOD,
33260
33812
  "barefoot.config.ts": ECHO_BAREFOOT_CONFIG_TS,
@@ -111014,7 +111566,7 @@ __export(scenario_driver_exports, {
111014
111566
  import { writeFileSync as writeFileSync9, mkdtempSync, rmSync, readFileSync as readFileSync19, existsSync as existsSync23, statSync as statSync3 } from "node:fs";
111015
111567
  import { join as join2, dirname as dirname7, resolve as resolve11 } from "node:path";
111016
111568
  import { tmpdir } from "node:os";
111017
- import ts27 from "typescript";
111569
+ import ts28 from "typescript";
111018
111570
  function externalRuntimeImport(clientJs) {
111019
111571
  const chunks = Array.isArray(clientJs) ? clientJs : [clientJs];
111020
111572
  for (const chunk of chunks) {
@@ -111084,11 +111636,11 @@ function resolveLocalFile(spec) {
111084
111636
  }
111085
111637
  function rewriteLocalImports(js, chunkPath, inlined) {
111086
111638
  const chunkDir = dirname7(chunkPath);
111087
- const sf = ts27.createSourceFile("chunk.mjs", js, ts27.ScriptTarget.Latest, false, ts27.ScriptKind.JS);
111639
+ const sf = ts28.createSourceFile("chunk.mjs", js, ts28.ScriptTarget.Latest, false, ts28.ScriptKind.JS);
111088
111640
  const edits = [];
111089
111641
  for (const stmt of sf.statements) {
111090
- if (!ts27.isImportDeclaration(stmt)) continue;
111091
- if (!ts27.isStringLiteral(stmt.moduleSpecifier)) continue;
111642
+ if (!ts28.isImportDeclaration(stmt)) continue;
111643
+ if (!ts28.isStringLiteral(stmt.moduleSpecifier)) continue;
111092
111644
  const spec = stmt.moduleSpecifier.text;
111093
111645
  if (!spec.startsWith(".")) continue;
111094
111646
  const resolved = resolveLocalFile(join2(chunkDir, spec.replace(/\.client\.js$/, "")));
@@ -111100,13 +111652,13 @@ function rewriteLocalImports(js, chunkPath, inlined) {
111100
111652
  const abs = resolve11(resolved);
111101
111653
  if (inlined.has(abs)) {
111102
111654
  const clause = stmt.importClause;
111103
- if (clause && (clause.name || clause.namedBindings && ts27.isNamespaceImport(clause.namedBindings))) {
111655
+ if (clause && (clause.name || clause.namedBindings && ts28.isNamespaceImport(clause.namedBindings))) {
111104
111656
  throw new Error(
111105
111657
  `"${spec}" (imported by ${chunkPath}) uses a default or namespace import of a sibling client file, which the dynamic scenario runner cannot bind after inlining that file into the run. Import its exports by name, or use the static budget (\`bf debug profile <component>\`), which needs no run.`
111106
111658
  );
111107
111659
  }
111108
111660
  const shims = [];
111109
- if (clause?.namedBindings && ts27.isNamedImports(clause.namedBindings)) {
111661
+ if (clause?.namedBindings && ts28.isNamedImports(clause.namedBindings)) {
111110
111662
  for (const el of clause.namedBindings.elements) {
111111
111663
  if (el.propertyName) shims.push(`var ${el.name.text} = ${el.propertyName.text}`);
111112
111664
  }
@@ -111615,9 +112167,9 @@ function findProjectConfig(startDir) {
111615
112167
  let dir = path.resolve(startDir);
111616
112168
  const { root: fsRoot } = path.parse(dir);
111617
112169
  while (true) {
111618
- const ts28 = path.join(dir, "barefoot.config.ts");
111619
- if (existsSync2(ts28)) {
111620
- return { dir, tsConfigPath: ts28 };
112170
+ const ts29 = path.join(dir, "barefoot.config.ts");
112171
+ if (existsSync2(ts29)) {
112172
+ return { dir, tsConfigPath: ts29 };
111621
112173
  }
111622
112174
  if (dir === fsRoot) return null;
111623
112175
  dir = path.dirname(dir);