@barefootjs/cli 0.19.1 → 0.21.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.
package/dist/index.js CHANGED
@@ -5444,6 +5444,130 @@ var init_errors = __esm({
5444
5444
  }
5445
5445
  });
5446
5446
 
5447
+ // ../jsx/src/rich-type-evidence.ts
5448
+ function baseTypeName(raw) {
5449
+ const idx = raw.indexOf("<");
5450
+ return (idx === -1 ? raw : raw.slice(0, idx)).trim();
5451
+ }
5452
+ function isNullishArm(t) {
5453
+ if (t.kind === "primitive" && (t.primitive === "null" || t.primitive === "undefined")) return true;
5454
+ return t.kind === "unknown" && (t.raw === "null" || t.raw === "undefined");
5455
+ }
5456
+ function stripUnion(type2) {
5457
+ if (!type2 || type2.kind !== "union" || !type2.unionTypes) return type2;
5458
+ const nonNullish = type2.unionTypes.filter((t) => !isNullishArm(t));
5459
+ return nonNullish.length === 1 ? stripUnion(nonNullish[0]) : type2;
5460
+ }
5461
+ function derefNamedType(type2, meta) {
5462
+ if (type2.kind !== "interface") return type2;
5463
+ if (type2.properties && type2.properties.length > 0) return type2;
5464
+ const name2 = baseTypeName(type2.raw);
5465
+ const def = meta.typeDefinitions.find((d) => d.name === name2);
5466
+ if (!def?.properties) return type2;
5467
+ return { ...type2, properties: def.properties };
5468
+ }
5469
+ function lookupProperty(objType, propName, meta) {
5470
+ const stripped = stripUnion(objType);
5471
+ if (!stripped) return null;
5472
+ const deref = derefNamedType(stripped, meta);
5473
+ const prop = deref.properties?.find((p) => p.name === propName);
5474
+ return prop ? stripUnion(prop.type) : null;
5475
+ }
5476
+ function resolveReceiverType(expr, meta, bindings) {
5477
+ if (expr.kind === "identifier") {
5478
+ if (bindings.has(expr.name)) return stripUnion(bindings.get(expr.name) ?? null);
5479
+ if (meta.propsObjectName !== null) {
5480
+ return expr.name === meta.propsObjectName ? stripUnion(meta.propsType) : null;
5481
+ }
5482
+ const param = meta.propsParams.find((p) => p.name === expr.name && !p.isRest);
5483
+ if (!param) return null;
5484
+ return lookupProperty(meta.propsType, param.sourceName ?? param.name, meta);
5485
+ }
5486
+ if (expr.kind === "member" && !expr.computed) {
5487
+ const objType = resolveReceiverType(expr.object, meta, bindings);
5488
+ return lookupProperty(objType, expr.property, meta);
5489
+ }
5490
+ return null;
5491
+ }
5492
+ var HOST_RICH_TYPE_NAMES;
5493
+ var init_rich_type_evidence = __esm({
5494
+ "../jsx/src/rich-type-evidence.ts"() {
5495
+ "use strict";
5496
+ HOST_RICH_TYPE_NAMES = /* @__PURE__ */ new Set([
5497
+ "Date",
5498
+ "Map",
5499
+ "Set",
5500
+ "WeakMap",
5501
+ "WeakSet",
5502
+ "URL",
5503
+ "URLSearchParams",
5504
+ "RegExp",
5505
+ "Promise",
5506
+ "Error",
5507
+ "Symbol",
5508
+ "BigInt",
5509
+ "Function"
5510
+ ]);
5511
+ }
5512
+ });
5513
+
5514
+ // ../jsx/src/date-lowering.ts
5515
+ function typeReachesDate(type2, meta, seen) {
5516
+ const stripped = stripUnion(type2);
5517
+ if (!stripped) return false;
5518
+ if (stripped.kind === "interface") {
5519
+ const name2 = baseTypeName(stripped.raw);
5520
+ if (name2 === "Date") return true;
5521
+ if (seen.has(name2)) return false;
5522
+ seen.add(name2);
5523
+ } else if (stripped.kind !== "object") {
5524
+ return false;
5525
+ }
5526
+ const deref = derefNamedType(stripped, meta);
5527
+ if (!deref.properties) return false;
5528
+ return deref.properties.some((p) => typeReachesDate(p.type, meta, seen));
5529
+ }
5530
+ function matchDateCall(callee, args2, metadata) {
5531
+ if (callee.kind !== "member" || callee.computed) return null;
5532
+ if (args2.length !== 0 || !DATE_METHODS.has(callee.property)) return null;
5533
+ const receiverType = resolveReceiverType(callee.object, metadata, EMPTY_BINDINGS);
5534
+ if (!receiverType || receiverType.kind !== "interface") return null;
5535
+ const typeName = baseTypeName(receiverType.raw);
5536
+ if (typeName !== "Date") return null;
5537
+ if (metadata.typeDefinitions.some((d) => d.name === typeName)) return null;
5538
+ return {
5539
+ kind: "helper-call",
5540
+ helper: "date",
5541
+ args: [callee.object, { kind: "literal", value: callee.property, literalType: "string" }]
5542
+ };
5543
+ }
5544
+ var CATALOGUED_RICH_TYPE_NAMES, DATE_METHODS, EMPTY_BINDINGS, datePlugin;
5545
+ var init_date_lowering = __esm({
5546
+ "../jsx/src/date-lowering.ts"() {
5547
+ "use strict";
5548
+ init_rich_type_evidence();
5549
+ CATALOGUED_RICH_TYPE_NAMES = /* @__PURE__ */ new Set(["Date"]);
5550
+ DATE_METHODS = /* @__PURE__ */ new Set([
5551
+ "getUTCFullYear",
5552
+ "getUTCMonth",
5553
+ "getUTCDate",
5554
+ "getUTCHours",
5555
+ "getUTCMinutes",
5556
+ "getUTCSeconds",
5557
+ "getTime",
5558
+ "toISOString"
5559
+ ]);
5560
+ EMPTY_BINDINGS = /* @__PURE__ */ new Map();
5561
+ datePlugin = {
5562
+ name: "date",
5563
+ prepare(metadata) {
5564
+ if (!metadata.propsType || !typeReachesDate(metadata.propsType, metadata, /* @__PURE__ */ new Set())) return null;
5565
+ return (callee, args2) => matchDateCall(callee, args2, metadata);
5566
+ }
5567
+ };
5568
+ }
5569
+ });
5570
+
5447
5571
  // ../jsx/src/analyzer.ts
5448
5572
  import ts8 from "typescript";
5449
5573
  import path4 from "node:path";
@@ -7083,7 +7207,9 @@ function extractProps(param, ctx2) {
7083
7207
  // the caller may omit the prop.
7084
7208
  optional: !!member?.optional || !!element.initializer,
7085
7209
  defaultValue: defaultValue2,
7086
- defaultContainsArrow: defaultContainsArrow || void 0
7210
+ defaultContainsArrow: defaultContainsArrow || void 0,
7211
+ // Only aliased bindings carry the source key — see ParamInfo.sourceName.
7212
+ ...sourcePropName !== localName2 && { sourceName: sourcePropName }
7087
7213
  });
7088
7214
  }
7089
7215
  }
@@ -7139,7 +7265,7 @@ function collectKeysFromMembers(members, ctx2) {
7139
7265
  return keys;
7140
7266
  }
7141
7267
  function collectMemberTypes(typeNode, ctx2) {
7142
- const isResolvablePrimitive = (info) => info.kind === "primitive" && (info.primitive === "string" || info.primitive === "number" || info.primitive === "boolean");
7268
+ const isResolvablePrimitive = (info) => info.kind === "primitive" && (info.primitive === "string" || info.primitive === "number" || info.primitive === "boolean") || info.kind === "interface" && CATALOGUED_RICH_TYPE_NAMES.has(baseTypeName(info.raw));
7143
7269
  const fromMembers = (members) => {
7144
7270
  const map = /* @__PURE__ */ new Map();
7145
7271
  for (const member of members) {
@@ -7729,6 +7855,8 @@ var init_analyzer = __esm({
7729
7855
  init_instrumentation();
7730
7856
  init_analyzer_context();
7731
7857
  init_errors();
7858
+ init_rich_type_evidence();
7859
+ init_date_lowering();
7732
7860
  REACTIVE_BRAND_PACKAGES = [
7733
7861
  "@barefootjs/form"
7734
7862
  ];
@@ -8419,16 +8547,55 @@ function exprHasFunctionCalls(expr) {
8419
8547
  visit3(expr);
8420
8548
  return found;
8421
8549
  }
8550
+ function getDateLoweringMatcher(ctx2) {
8551
+ if (ctx2._dateLoweringMatcher === void 0) {
8552
+ const a = ctx2.analyzer;
8553
+ const metadataSlice = {
8554
+ propsType: a.propsType,
8555
+ propsObjectName: a.propsObjectName,
8556
+ propsParams: a.propsParams,
8557
+ typeDefinitions: a.typeDefinitions
8558
+ };
8559
+ ctx2._dateLoweringMatcher = datePlugin.prepare(metadataSlice);
8560
+ }
8561
+ return ctx2._dateLoweringMatcher;
8562
+ }
8563
+ function lowerDateCalls(text, expr, ctx2) {
8564
+ const matcher = getDateLoweringMatcher(ctx2);
8565
+ if (!matcher) return text;
8566
+ const candidates = [];
8567
+ function visit3(n) {
8568
+ if (ts11.isCallExpression(n) && n.arguments.length === 0 && ts11.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && DATE_METHODS.has(n.expression.name.text)) {
8569
+ candidates.push(n);
8570
+ }
8571
+ ts11.forEachChild(n, visit3);
8572
+ }
8573
+ visit3(expr);
8574
+ if (candidates.length === 0) return text;
8575
+ const { protect, restore } = createTemplateAwareStringProtector();
8576
+ let result2 = protect(text);
8577
+ for (const call of candidates) {
8578
+ const propAccess = call.expression;
8579
+ const node = matcher(tsNodeToParsedExpr(propAccess), []);
8580
+ if (!node || node.kind !== "helper-call" || node.helper !== "date") continue;
8581
+ const op = propAccess.name.text;
8582
+ const receiverText = ctx2.getJS(propAccess.expression);
8583
+ const matchText = ctx2.getJS(call);
8584
+ result2 = result2.replace(matchText, () => `date(${receiverText}, "${op}")`);
8585
+ }
8586
+ return restore(result2);
8587
+ }
8422
8588
  function rewriteBarePropRefs2(text, expr, ctx2) {
8589
+ const dateLowered = lowerDateCalls(text, expr, ctx2);
8423
8590
  let propNames = getDestructuredPropNames(ctx2);
8424
- if (!propNames) return void 0;
8591
+ if (!propNames) return dateLowered === text ? void 0 : dateLowered;
8425
8592
  if (ctx2.loopParams.size > 0) {
8426
8593
  const filtered = new Set([...propNames].filter((n) => !ctx2.loopParams.has(n)));
8427
- if (filtered.size === 0) return void 0;
8594
+ if (filtered.size === 0) return dateLowered === text ? void 0 : dateLowered;
8428
8595
  propNames = filtered;
8429
8596
  }
8430
8597
  const extraPropRefs = collectBranchLocalPropRefsViaSubstitution(expr, ctx2);
8431
- return rewriteBarePropRefs(text, expr, propNames, extraPropRefs);
8598
+ return rewriteBarePropRefs(dateLowered, expr, propNames, extraPropRefs);
8432
8599
  }
8433
8600
  function collectBranchLocalPropRefsViaSubstitution(node, ctx2) {
8434
8601
  const propDepsMap = ctx2._branchScopePropDeps;
@@ -10436,11 +10603,11 @@ function transformMapCall(node, ctx2, isClientOnly = false, method2 = "map") {
10436
10603
  if (stmt === returnStmt) break;
10437
10604
  const js = ctx2.getJS(stmt);
10438
10605
  const tjs = ctx2.getTemplateJS(stmt);
10439
- const ts26 = stmt.getText(ctx2.sourceFile);
10606
+ const ts27 = stmt.getText(ctx2.sourceFile);
10440
10607
  preambleStmts.push(js.endsWith(";") ? js : js + ";");
10441
10608
  templatePreambleStmts.push(tjs.endsWith(";") ? tjs : tjs + ";");
10442
- typedPreambleStmts.push(ts26.endsWith(";") ? ts26 : ts26 + ";");
10443
- if (js !== ts26) hasTypeDiff = true;
10609
+ typedPreambleStmts.push(ts27.endsWith(";") ? ts27 : ts27 + ";");
10610
+ if (js !== ts27) hasTypeDiff = true;
10444
10611
  if (js !== tjs) hasTemplateDiff = true;
10445
10612
  }
10446
10613
  if (preambleStmts.length > 0) {
@@ -11552,6 +11719,8 @@ var init_jsx_to_ir = __esm({
11552
11719
  init_prop_rewrite();
11553
11720
  init_free_refs();
11554
11721
  init_component_scope();
11722
+ init_html_template();
11723
+ init_date_lowering();
11555
11724
  init_analyzer();
11556
11725
  init_js_scanner();
11557
11726
  init_src();
@@ -11843,7 +12012,8 @@ function collectLoopChildReactiveTexts(node, ctx2, loopParam, loopParamBindings)
11843
12012
  if (!n.slotId) return;
11844
12013
  const originFreeIds = freeIdsFromRefs(n.origin?.freeRefs);
11845
12014
  const expanded = expandConstantForReactivity(n.expr, ctx2, originFreeIds);
11846
- if (classifyReactivity(expanded.expr, ctx2, loopParam, loopParamBindings, expanded.freeIds).kind === "none") return;
12015
+ const reactive = classifyReactivity(expanded.expr, ctx2, loopParam, loopParamBindings, expanded.freeIds).kind !== "none" || decideWrapFromAstFlags(n).wrap;
12016
+ if (!reactive) return;
11847
12017
  texts.push({
11848
12018
  slotId: n.slotId,
11849
12019
  expression: expanded.expr,
@@ -13335,7 +13505,11 @@ var init_imports = __esm({
13335
13505
  "tAfter",
13336
13506
  // Profile mode (#1690, SR3) — turn-boundary markers around event handlers.
13337
13507
  "beginTurn",
13338
- "endTurn"
13508
+ "endTurn",
13509
+ // Catalogued `Date` lowering (#2274/#2292) — the client counterpart to
13510
+ // every SSR adapter's `date` runtime helper (`date-lowering.ts`'s
13511
+ // `datePlugin`).
13512
+ "date"
13339
13513
  ];
13340
13514
  RUNTIME_MODULE = "@barefootjs/client/runtime";
13341
13515
  IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
@@ -16495,6 +16669,7 @@ var init_build_insert = __esm({
16495
16669
  });
16496
16670
 
16497
16671
  // ../jsx/src/ir-to-client-js/emit-reactive.ts
16672
+ import ts14 from "typescript";
16498
16673
  function bindingIdArg(ctx2, slotId) {
16499
16674
  if (!ctx2.profile || !slotId) return "";
16500
16675
  return `, ${JSON.stringify(`${ctx2.componentName}#binding:${slotId}`)}`;
@@ -16550,7 +16725,51 @@ function rewriteDestructuredPropsInExpr(expr, ctx2) {
16550
16725
  }
16551
16726
  return restore(result2);
16552
16727
  }
16728
+ function getReactiveDateLoweringMatcher(ctx2) {
16729
+ if (!ctx2.propsType) return null;
16730
+ const metadataSlice = {
16731
+ propsType: ctx2.propsType,
16732
+ propsObjectName: ctx2.propsObjectName,
16733
+ propsParams: ctx2.propsParams,
16734
+ typeDefinitions: ctx2.typeDefinitions ?? []
16735
+ };
16736
+ return datePlugin.prepare(metadataSlice);
16737
+ }
16738
+ function lowerDateCallsInReactiveExpr(expr, matcher) {
16739
+ if (!matcher) return expr;
16740
+ let sourceFile;
16741
+ try {
16742
+ sourceFile = ts14.createSourceFile("__reactive_expr__.ts", `(${expr});`, ts14.ScriptTarget.Latest, true, ts14.ScriptKind.TS);
16743
+ } catch {
16744
+ return expr;
16745
+ }
16746
+ const stmt = sourceFile.statements[0];
16747
+ if (!stmt || !ts14.isExpressionStatement(stmt)) return expr;
16748
+ const root2 = ts14.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
16749
+ const candidates = [];
16750
+ const visit3 = (n) => {
16751
+ if (ts14.isCallExpression(n) && n.arguments.length === 0 && ts14.isPropertyAccessExpression(n.expression) && !n.expression.questionDotToken && DATE_METHODS.has(n.expression.name.text)) {
16752
+ candidates.push(n);
16753
+ }
16754
+ ts14.forEachChild(n, visit3);
16755
+ };
16756
+ visit3(root2);
16757
+ if (candidates.length === 0) return expr;
16758
+ const { protect, restore } = createTemplateAwareStringProtector();
16759
+ let result2 = protect(expr);
16760
+ for (const call of candidates) {
16761
+ const propAccess = call.expression;
16762
+ const node = matcher(tsNodeToParsedExpr(propAccess), []);
16763
+ if (!node || node.kind !== "helper-call" || node.helper !== "date") continue;
16764
+ const op = propAccess.name.text;
16765
+ const receiverText = propAccess.expression.getText(sourceFile);
16766
+ const matchText = call.getText(sourceFile);
16767
+ result2 = result2.replace(matchText, () => `date(${receiverText}, "${op}")`);
16768
+ }
16769
+ return restore(result2);
16770
+ }
16553
16771
  function emitDynamicTextUpdates(lines, ctx2) {
16772
+ const dateLoweringMatcher = getReactiveDateLoweringMatcher(ctx2);
16554
16773
  const byExpression = /* @__PURE__ */ new Map();
16555
16774
  for (const elem of ctx2.dynamicElements) {
16556
16775
  const key = elem.expression;
@@ -16559,7 +16778,8 @@ function emitDynamicTextUpdates(lines, ctx2) {
16559
16778
  }
16560
16779
  byExpression.get(key).push(elem);
16561
16780
  }
16562
- for (const [expr, elems] of byExpression) {
16781
+ for (const [rawExpr, elems] of byExpression) {
16782
+ const expr = lowerDateCallsInReactiveExpr(rawExpr, dateLoweringMatcher);
16563
16783
  const conditionalElems = elems.filter((e) => e.insideConditional);
16564
16784
  const normalElems = elems.filter((e) => !e.insideConditional);
16565
16785
  if (normalElems.length > 0 || conditionalElems.length > 0) {
@@ -16708,6 +16928,8 @@ var init_emit_reactive = __esm({
16708
16928
  init_html_constants();
16709
16929
  init_utils();
16710
16930
  init_html_template();
16931
+ init_date_lowering();
16932
+ init_expression_parser();
16711
16933
  }
16712
16934
  });
16713
16935
 
@@ -18206,25 +18428,25 @@ var init_phases = __esm({
18206
18428
  });
18207
18429
 
18208
18430
  // ../jsx/src/ir-to-client-js/rewrite-props-object.ts
18209
- import ts14 from "typescript";
18431
+ import ts15 from "typescript";
18210
18432
  function rewritePropsObjectRef(code, propsObjectName) {
18211
18433
  const srcPropsName = propsObjectName ?? "props";
18212
18434
  if (srcPropsName === PROPS_PARAM) return code;
18213
18435
  if (!new RegExp(`\\b${srcPropsName}\\b`).test(code)) return code;
18214
- const sourceFile = ts14.createSourceFile(
18436
+ const sourceFile = ts15.createSourceFile(
18215
18437
  "init-body.ts",
18216
18438
  code,
18217
- ts14.ScriptTarget.Latest,
18439
+ ts15.ScriptTarget.Latest,
18218
18440
  /*setParentNodes*/
18219
18441
  true,
18220
- ts14.ScriptKind.TS
18442
+ ts15.ScriptKind.TS
18221
18443
  );
18222
18444
  const spans = [];
18223
18445
  function visit3(node) {
18224
- if (ts14.isIdentifier(node) && node.text === srcPropsName && shouldRewrite(node)) {
18446
+ if (ts15.isIdentifier(node) && node.text === srcPropsName && shouldRewrite(node)) {
18225
18447
  spans.push([node.getStart(sourceFile), node.getEnd()]);
18226
18448
  }
18227
- ts14.forEachChild(node, visit3);
18449
+ ts15.forEachChild(node, visit3);
18228
18450
  }
18229
18451
  visit3(sourceFile);
18230
18452
  if (spans.length === 0) return code;
@@ -18238,12 +18460,12 @@ function rewritePropsObjectRef(code, propsObjectName) {
18238
18460
  function shouldRewrite(node) {
18239
18461
  const parent2 = node.parent;
18240
18462
  if (!parent2) return true;
18241
- if (ts14.isPropertyAccessExpression(parent2) && parent2.name === node) return false;
18242
- if (ts14.isPropertyAssignment(parent2) && parent2.name === node) return false;
18243
- if (ts14.isShorthandPropertyAssignment(parent2) && parent2.name === node) return false;
18244
- if (ts14.isPropertySignature(parent2) && parent2.name === node) return false;
18245
- if (ts14.isPropertyDeclaration(parent2) && parent2.name === node) return false;
18246
- if (ts14.isBindingElement(parent2) && parent2.name === node) return false;
18463
+ if (ts15.isPropertyAccessExpression(parent2) && parent2.name === node) return false;
18464
+ if (ts15.isPropertyAssignment(parent2) && parent2.name === node) return false;
18465
+ if (ts15.isShorthandPropertyAssignment(parent2) && parent2.name === node) return false;
18466
+ if (ts15.isPropertySignature(parent2) && parent2.name === node) return false;
18467
+ if (ts15.isPropertyDeclaration(parent2) && parent2.name === node) return false;
18468
+ if (ts15.isBindingElement(parent2) && parent2.name === node) return false;
18247
18469
  return true;
18248
18470
  }
18249
18471
  var init_rewrite_props_object = __esm({
@@ -18288,14 +18510,15 @@ function generateInitFunction(ir, ctx2, siblingComponents, localImportPrefixes)
18288
18510
  const hydrateLine = emitRegistrationAndHydration(lines, ctx2, ir, graph, inlinability);
18289
18511
  let generatedCode = rewritePropsObjectRef(lines.join("\n"), ctx2.propsObjectName);
18290
18512
  generatedCode += "\n" + hydrateLine;
18291
- const allImportLines = resolveFinalImports(generatedCode, ir, localImportPrefixes);
18292
18513
  const moduleConstantsCode = emitModuleLevelDeclarations(
18293
18514
  classification.moduleLevelConstants,
18294
18515
  classification.moduleLevelFunctions,
18295
18516
  classification.moduleLevelSignals,
18296
18517
  classification.moduleLevelMemos
18297
18518
  );
18298
- return generatedCode.replace(IMPORT_PLACEHOLDER, allImportLines).replace(MODULE_CONSTANTS_PLACEHOLDER, moduleConstantsCode);
18519
+ const codeWithModuleConstants = generatedCode.replace(MODULE_CONSTANTS_PLACEHOLDER, () => moduleConstantsCode);
18520
+ const allImportLines = resolveFinalImports(codeWithModuleConstants, ir, localImportPrefixes);
18521
+ return codeWithModuleConstants.replace(IMPORT_PLACEHOLDER, () => allImportLines);
18299
18522
  }
18300
18523
  var init_generate_init = __esm({
18301
18524
  "../jsx/src/ir-to-client-js/generate-init.ts"() {
@@ -18578,6 +18801,8 @@ function createContext2(ir, scope, adapterCapabilities, profile) {
18578
18801
  propsParams: ir.metadata.propsParams,
18579
18802
  propsObjectName: ir.metadata.propsObjectName,
18580
18803
  restPropsName: ir.metadata.restPropsName,
18804
+ propsType: ir.metadata.propsType,
18805
+ typeDefinitions: ir.metadata.typeDefinitions,
18581
18806
  interactiveElements: [],
18582
18807
  dynamicElements: [],
18583
18808
  conditionalElements: [],
@@ -18823,7 +19048,7 @@ var init_css_layer_prefixer = __esm({
18823
19048
  });
18824
19049
 
18825
19050
  // ../jsx/src/preprocess-inline-jsx-callbacks.ts
18826
- import ts15 from "typescript";
19051
+ import ts16 from "typescript";
18827
19052
  function preprocessInlineJsxCallbacks(source, filePath) {
18828
19053
  const errors = [];
18829
19054
  const syntheticNames = [];
@@ -18843,15 +19068,15 @@ function preprocessInlineJsxCallbacks(source, filePath) {
18843
19068
  return { source: current, errors, syntheticNames };
18844
19069
  }
18845
19070
  function runSinglePass(source, filePath, startingCounter) {
18846
- const sourceFile = ts15.createSourceFile(
19071
+ const sourceFile = ts16.createSourceFile(
18847
19072
  filePath,
18848
19073
  source,
18849
- ts15.ScriptTarget.Latest,
19074
+ ts16.ScriptTarget.Latest,
18850
19075
  true,
18851
- ts15.ScriptKind.TSX
19076
+ ts16.ScriptKind.TSX
18852
19077
  );
18853
19078
  const hasUseClient = sourceFile.statements.some(
18854
- (stmt) => ts15.isExpressionStatement(stmt) && ts15.isStringLiteral(stmt.expression) && (stmt.expression.text === "use client" || stmt.expression.text === "'use client'")
19079
+ (stmt) => ts16.isExpressionStatement(stmt) && ts16.isStringLiteral(stmt.expression) && (stmt.expression.text === "use client" || stmt.expression.text === "'use client'")
18855
19080
  );
18856
19081
  if (!hasUseClient) {
18857
19082
  return { source, errors: [], syntheticNames: [], counterAfter: startingCounter };
@@ -18874,20 +19099,20 @@ function runSinglePass(source, filePath, startingCounter) {
18874
19099
  }
18875
19100
  }
18876
19101
  function visit3(node) {
18877
- if (ts15.isJsxAttribute(node) && node.initializer && ts15.isJsxExpression(node.initializer) && node.initializer.expression) {
19102
+ if (ts16.isJsxAttribute(node) && node.initializer && ts16.isJsxExpression(node.initializer) && node.initializer.expression) {
18878
19103
  if (tryHandleArrowValue(node.initializer.expression)) {
18879
19104
  return;
18880
19105
  }
18881
19106
  }
18882
- if (ts15.isPropertyAssignment(node) && node.initializer) {
19107
+ if (ts16.isPropertyAssignment(node) && node.initializer) {
18883
19108
  if (tryHandleArrowValue(node.initializer)) return;
18884
19109
  }
18885
- ts15.forEachChild(node, visit3);
19110
+ ts16.forEachChild(node, visit3);
18886
19111
  }
18887
19112
  function tryHandleArrowValue(initializer) {
18888
19113
  let expr = initializer;
18889
- while (ts15.isParenthesizedExpression(expr)) expr = expr.expression;
18890
- if (ts15.isArrowFunction(expr) && arrowBodyContainsJsx(expr)) {
19114
+ while (ts16.isParenthesizedExpression(expr)) expr = expr.expression;
19115
+ if (ts16.isArrowFunction(expr) && arrowBodyContainsJsx(expr)) {
18891
19116
  return handleInlineArrow(expr);
18892
19117
  }
18893
19118
  return false;
@@ -18922,7 +19147,7 @@ function runSinglePass(source, filePath, startingCounter) {
18922
19147
  replacements.push({ start: arrowStart, end: arrowEnd, text: name2 });
18923
19148
  return true;
18924
19149
  }
18925
- ts15.forEachChild(sourceFile, visit3);
19150
+ ts16.forEachChild(sourceFile, visit3);
18926
19151
  if (replacements.length === 0) {
18927
19152
  return { source, errors, syntheticNames, counterAfter: counter };
18928
19153
  }
@@ -18941,33 +19166,33 @@ function errorMessageForCapture(captures) {
18941
19166
  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.`;
18942
19167
  }
18943
19168
  function arrowBodyContainsJsx(arrow) {
18944
- if (ts15.isBlock(arrow.body)) {
19169
+ if (ts16.isBlock(arrow.body)) {
18945
19170
  return blockReturnsJsx(arrow.body);
18946
19171
  }
18947
19172
  let body2 = arrow.body;
18948
- while (ts15.isParenthesizedExpression(body2)) body2 = body2.expression;
19173
+ while (ts16.isParenthesizedExpression(body2)) body2 = body2.expression;
18949
19174
  return isJsxLike(body2);
18950
19175
  }
18951
19176
  function blockReturnsJsx(block) {
18952
19177
  let found = false;
18953
19178
  function visit3(n) {
18954
19179
  if (found) return;
18955
- if (ts15.isReturnStatement(n) && n.expression) {
19180
+ if (ts16.isReturnStatement(n) && n.expression) {
18956
19181
  let e = n.expression;
18957
- while (ts15.isParenthesizedExpression(e)) e = e.expression;
19182
+ while (ts16.isParenthesizedExpression(e)) e = e.expression;
18958
19183
  if (isJsxLike(e)) {
18959
19184
  found = true;
18960
19185
  return;
18961
19186
  }
18962
19187
  }
18963
- if (ts15.isArrowFunction(n) || ts15.isFunctionDeclaration(n) || ts15.isFunctionExpression(n)) return;
18964
- ts15.forEachChild(n, visit3);
19188
+ if (ts16.isArrowFunction(n) || ts16.isFunctionDeclaration(n) || ts16.isFunctionExpression(n)) return;
19189
+ ts16.forEachChild(n, visit3);
18965
19190
  }
18966
- ts15.forEachChild(block, visit3);
19191
+ ts16.forEachChild(block, visit3);
18967
19192
  return found;
18968
19193
  }
18969
19194
  function isJsxLike(expr) {
18970
- return ts15.isJsxElement(expr) || ts15.isJsxSelfClosingElement(expr) || ts15.isJsxFragment(expr);
19195
+ return ts16.isJsxElement(expr) || ts16.isJsxSelfClosingElement(expr) || ts16.isJsxFragment(expr);
18971
19196
  }
18972
19197
  function collectArrowParamNames(arrow) {
18973
19198
  const names = /* @__PURE__ */ new Set();
@@ -18976,13 +19201,13 @@ function collectArrowParamNames(arrow) {
18976
19201
  }
18977
19202
  function collectBindingNames(name2, out) {
18978
19203
  const push = Array.isArray(out) ? (n) => out.push(n) : (n) => out.add(n);
18979
- if (ts15.isIdentifier(name2)) {
19204
+ if (ts16.isIdentifier(name2)) {
18980
19205
  push(name2.text);
18981
- } else if (ts15.isObjectBindingPattern(name2)) {
19206
+ } else if (ts16.isObjectBindingPattern(name2)) {
18982
19207
  name2.elements.forEach((el) => collectBindingNames(el.name, out));
18983
- } else if (ts15.isArrayBindingPattern(name2)) {
19208
+ } else if (ts16.isArrayBindingPattern(name2)) {
18984
19209
  name2.elements.forEach((el) => {
18985
- if (!ts15.isOmittedExpression(el)) collectBindingNames(el.name, out);
19210
+ if (!ts16.isOmittedExpression(el)) collectBindingNames(el.name, out);
18986
19211
  });
18987
19212
  }
18988
19213
  }
@@ -19007,71 +19232,71 @@ function collectFreeIdentifiers(arrow) {
19007
19232
  return bound.includes(name2);
19008
19233
  }
19009
19234
  function visit3(node) {
19010
- if (ts15.isIdentifier(node)) {
19235
+ if (ts16.isIdentifier(node)) {
19011
19236
  const parent2 = node.parent;
19012
- if (parent2 && ts15.isPropertyAccessExpression(parent2) && parent2.name === node) return;
19013
- if (parent2 && ts15.isPropertyAssignment(parent2) && parent2.name === node) return;
19014
- if (parent2 && ts15.isPropertySignature(parent2) && parent2.name === node) return;
19015
- if (parent2 && ts15.isPropertyDeclaration(parent2) && parent2.name === node) return;
19016
- if (parent2 && ts15.isMethodDeclaration(parent2) && parent2.name === node) return;
19017
- if (parent2 && ts15.isMethodSignature(parent2) && parent2.name === node) return;
19018
- if (parent2 && ts15.isGetAccessorDeclaration(parent2) && parent2.name === node) return;
19019
- if (parent2 && ts15.isSetAccessorDeclaration(parent2) && parent2.name === node) return;
19020
- if (parent2 && ts15.isEnumMember(parent2) && parent2.name === node) return;
19021
- if (parent2 && ts15.isBindingElement(parent2) && parent2.propertyName === node) return;
19022
- if (parent2 && ts15.isShorthandPropertyAssignment(parent2) && parent2.name === node) {
19237
+ if (parent2 && ts16.isPropertyAccessExpression(parent2) && parent2.name === node) return;
19238
+ if (parent2 && ts16.isPropertyAssignment(parent2) && parent2.name === node) return;
19239
+ if (parent2 && ts16.isPropertySignature(parent2) && parent2.name === node) return;
19240
+ if (parent2 && ts16.isPropertyDeclaration(parent2) && parent2.name === node) return;
19241
+ if (parent2 && ts16.isMethodDeclaration(parent2) && parent2.name === node) return;
19242
+ if (parent2 && ts16.isMethodSignature(parent2) && parent2.name === node) return;
19243
+ if (parent2 && ts16.isGetAccessorDeclaration(parent2) && parent2.name === node) return;
19244
+ if (parent2 && ts16.isSetAccessorDeclaration(parent2) && parent2.name === node) return;
19245
+ if (parent2 && ts16.isEnumMember(parent2) && parent2.name === node) return;
19246
+ if (parent2 && ts16.isBindingElement(parent2) && parent2.propertyName === node) return;
19247
+ if (parent2 && ts16.isShorthandPropertyAssignment(parent2) && parent2.name === node) {
19023
19248
  if (!isBound(node.text)) ids.add(node.text);
19024
19249
  return;
19025
19250
  }
19026
- if (parent2 && ts15.isParameter(parent2) && parent2.name === node) return;
19027
- if (parent2 && ts15.isVariableDeclaration(parent2) && parent2.name === node) return;
19028
- if (parent2 && ts15.isFunctionDeclaration(parent2) && parent2.name === node) return;
19029
- if (parent2 && ts15.isClassDeclaration(parent2) && parent2.name === node) return;
19030
- if (parent2 && ts15.isJsxAttribute(parent2) && parent2.name === node) return;
19031
- if (parent2 && ts15.isJsxOpeningElement(parent2) && parent2.tagName === node) {
19251
+ if (parent2 && ts16.isParameter(parent2) && parent2.name === node) return;
19252
+ if (parent2 && ts16.isVariableDeclaration(parent2) && parent2.name === node) return;
19253
+ if (parent2 && ts16.isFunctionDeclaration(parent2) && parent2.name === node) return;
19254
+ if (parent2 && ts16.isClassDeclaration(parent2) && parent2.name === node) return;
19255
+ if (parent2 && ts16.isJsxAttribute(parent2) && parent2.name === node) return;
19256
+ if (parent2 && ts16.isJsxOpeningElement(parent2) && parent2.tagName === node) {
19032
19257
  if (/^[a-z]/.test(node.text)) return;
19033
19258
  }
19034
- if (parent2 && ts15.isJsxClosingElement(parent2) && parent2.tagName === node) {
19259
+ if (parent2 && ts16.isJsxClosingElement(parent2) && parent2.tagName === node) {
19035
19260
  if (/^[a-z]/.test(node.text)) return;
19036
19261
  }
19037
19262
  if (isBound(node.text)) return;
19038
19263
  ids.add(node.text);
19039
19264
  return;
19040
19265
  }
19041
- if (ts15.isVariableDeclaration(node)) {
19266
+ if (ts16.isVariableDeclaration(node)) {
19042
19267
  const declared = pushBindings(node.name);
19043
19268
  if (node.initializer) visit3(node.initializer);
19044
19269
  declared;
19045
19270
  return;
19046
19271
  }
19047
- if (ts15.isFunctionDeclaration(node)) {
19272
+ if (ts16.isFunctionDeclaration(node)) {
19048
19273
  if (node.name) bound.push(node.name.text);
19049
19274
  visitInsideNewScope(node);
19050
19275
  return;
19051
19276
  }
19052
- if (ts15.isClassDeclaration(node)) {
19277
+ if (ts16.isClassDeclaration(node)) {
19053
19278
  if (node.name) bound.push(node.name.text);
19054
- ts15.forEachChild(node, visit3);
19279
+ ts16.forEachChild(node, visit3);
19055
19280
  return;
19056
19281
  }
19057
- if (ts15.isArrowFunction(node) || ts15.isFunctionExpression(node)) {
19282
+ if (ts16.isArrowFunction(node) || ts16.isFunctionExpression(node)) {
19058
19283
  visitInsideNewScope(node);
19059
19284
  return;
19060
19285
  }
19061
- if (ts15.isCatchClause(node)) {
19286
+ if (ts16.isCatchClause(node)) {
19062
19287
  const before = bound.length;
19063
19288
  if (node.variableDeclaration) pushBindings(node.variableDeclaration.name);
19064
- ts15.forEachChild(node, visit3);
19289
+ ts16.forEachChild(node, visit3);
19065
19290
  popN(bound.length - before);
19066
19291
  return;
19067
19292
  }
19068
- if (ts15.isBlock(node)) {
19293
+ if (ts16.isBlock(node)) {
19069
19294
  const before = bound.length;
19070
- ts15.forEachChild(node, visit3);
19295
+ ts16.forEachChild(node, visit3);
19071
19296
  popN(bound.length - before);
19072
19297
  return;
19073
19298
  }
19074
- ts15.forEachChild(node, visit3);
19299
+ ts16.forEachChild(node, visit3);
19075
19300
  }
19076
19301
  function visitInsideNewScope(fn) {
19077
19302
  const before = bound.length;
@@ -19091,27 +19316,27 @@ function collectFreeIdentifiers(arrow) {
19091
19316
  function collectModuleScopeNames(sourceFile) {
19092
19317
  const names = /* @__PURE__ */ new Set();
19093
19318
  for (const stmt of sourceFile.statements) {
19094
- if (ts15.isFunctionDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
19095
- else if (ts15.isClassDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
19096
- else if (ts15.isVariableStatement(stmt)) {
19319
+ if (ts16.isFunctionDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
19320
+ else if (ts16.isClassDeclaration(stmt) && stmt.name) names.add(stmt.name.text);
19321
+ else if (ts16.isVariableStatement(stmt)) {
19097
19322
  for (const decl of stmt.declarationList.declarations) collectBindingNames(decl.name, names);
19098
- } else if (ts15.isImportDeclaration(stmt) && stmt.importClause) {
19323
+ } else if (ts16.isImportDeclaration(stmt) && stmt.importClause) {
19099
19324
  const ic = stmt.importClause;
19100
19325
  if (ic.name) names.add(ic.name.text);
19101
19326
  if (ic.namedBindings) {
19102
- if (ts15.isNamespaceImport(ic.namedBindings)) names.add(ic.namedBindings.name.text);
19327
+ if (ts16.isNamespaceImport(ic.namedBindings)) names.add(ic.namedBindings.name.text);
19103
19328
  else for (const e of ic.namedBindings.elements) names.add(e.name.text);
19104
19329
  }
19105
- } else if (ts15.isTypeAliasDeclaration(stmt)) names.add(stmt.name.text);
19106
- else if (ts15.isInterfaceDeclaration(stmt)) names.add(stmt.name.text);
19107
- else if (ts15.isEnumDeclaration(stmt)) names.add(stmt.name.text);
19330
+ } else if (ts16.isTypeAliasDeclaration(stmt)) names.add(stmt.name.text);
19331
+ else if (ts16.isInterfaceDeclaration(stmt)) names.add(stmt.name.text);
19332
+ else if (ts16.isEnumDeclaration(stmt)) names.add(stmt.name.text);
19108
19333
  }
19109
19334
  return names;
19110
19335
  }
19111
19336
  function buildSyntheticDeclaration(name2, arrow, sourceFile) {
19112
19337
  const paramsText = arrow.parameters.length === 0 ? "" : arrow.parameters.map((p) => p.getText(sourceFile)).join(", ");
19113
19338
  let bodyText;
19114
- if (ts15.isBlock(arrow.body)) {
19339
+ if (ts16.isBlock(arrow.body)) {
19115
19340
  bodyText = arrow.body.getText(sourceFile);
19116
19341
  } else {
19117
19342
  const expr = arrow.body.getText(sourceFile);
@@ -19130,7 +19355,7 @@ var init_preprocess_inline_jsx_callbacks = __esm({
19130
19355
  });
19131
19356
 
19132
19357
  // ../jsx/src/ssr-defaults.ts
19133
- import ts16 from "typescript";
19358
+ import ts17 from "typescript";
19134
19359
  function extractSsrDefaults(metadata) {
19135
19360
  const out = {};
19136
19361
  const propsLike = /* @__PURE__ */ new Set();
@@ -19190,11 +19415,11 @@ function collectPropRefs(expr, propsObjectName, out) {
19190
19415
  const node = parseExpression2(expr);
19191
19416
  if (!node) return;
19192
19417
  const visit3 = (n) => {
19193
- if (ts16.isPropertyAccessExpression(n) && ts16.isIdentifier(n.expression) && n.expression.text === propsObjectName && ts16.isIdentifier(n.name)) {
19418
+ if (ts17.isPropertyAccessExpression(n) && ts17.isIdentifier(n.expression) && n.expression.text === propsObjectName && ts17.isIdentifier(n.name)) {
19194
19419
  out.add(n.name.text);
19195
19420
  return;
19196
19421
  }
19197
- ts16.forEachChild(n, visit3);
19422
+ ts17.forEachChild(n, visit3);
19198
19423
  };
19199
19424
  visit3(node);
19200
19425
  }
@@ -19211,21 +19436,21 @@ function tryStaticEval(expr, ctx2) {
19211
19436
  }
19212
19437
  function evalStatementsForReturn(statements, ctx2) {
19213
19438
  for (const stmt of statements) {
19214
- if (ts16.isVariableStatement(stmt)) {
19439
+ if (ts17.isVariableStatement(stmt)) {
19215
19440
  for (const d of stmt.declarationList.declarations) {
19216
- if (!ts16.isIdentifier(d.name) || !d.initializer) continue;
19441
+ if (!ts17.isIdentifier(d.name) || !d.initializer) continue;
19217
19442
  const v = evalNode(d.initializer, ctx2);
19218
19443
  if (v !== UNRESOLVED) ctx2.bindings[d.name.text] = v;
19219
19444
  }
19220
- } else if (ts16.isReturnStatement(stmt)) {
19445
+ } else if (ts17.isReturnStatement(stmt)) {
19221
19446
  return stmt.expression ? evalNode(stmt.expression, ctx2) : UNRESOLVED;
19222
- } else if (ts16.isIfStatement(stmt)) {
19447
+ } else if (ts17.isIfStatement(stmt)) {
19223
19448
  const cond = evalNode(stmt.expression, ctx2);
19224
19449
  if (cond === UNRESOLVED) return UNRESOLVED;
19225
19450
  const branch = cond ? stmt.thenStatement : stmt.elseStatement;
19226
19451
  if (branch) {
19227
19452
  const taken = evalStatementsForReturn(
19228
- ts16.isBlock(branch) ? branch.statements : [branch],
19453
+ ts17.isBlock(branch) ? branch.statements : [branch],
19229
19454
  ctx2
19230
19455
  );
19231
19456
  if (taken !== NO_RETURN) return taken;
@@ -19237,64 +19462,64 @@ function evalStatementsForReturn(statements, ctx2) {
19237
19462
  return NO_RETURN;
19238
19463
  }
19239
19464
  function parseExpression2(expr) {
19240
- const sf = ts16.createSourceFile(
19465
+ const sf = ts17.createSourceFile(
19241
19466
  "__ssr_default__.ts",
19242
19467
  `(${expr})`,
19243
- ts16.ScriptTarget.Latest,
19468
+ ts17.ScriptTarget.Latest,
19244
19469
  false,
19245
- ts16.ScriptKind.TS
19470
+ ts17.ScriptKind.TS
19246
19471
  );
19247
19472
  const stmt = sf.statements[0];
19248
- if (!stmt || !ts16.isExpressionStatement(stmt)) return null;
19249
- const inner = ts16.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
19473
+ if (!stmt || !ts17.isExpressionStatement(stmt)) return null;
19474
+ const inner = ts17.isParenthesizedExpression(stmt.expression) ? stmt.expression.expression : stmt.expression;
19250
19475
  return inner;
19251
19476
  }
19252
19477
  function evalNode(node, ctx2) {
19253
- if (ts16.isParenthesizedExpression(node)) return evalNode(node.expression, ctx2);
19254
- if (ts16.isAsExpression(node)) return evalNode(node.expression, ctx2);
19255
- if (ts16.isSatisfiesExpression(node)) return evalNode(node.expression, ctx2);
19256
- if (ts16.isTypeAssertionExpression(node)) return evalNode(node.expression, ctx2);
19257
- if (ts16.isNonNullExpression(node)) return evalNode(node.expression, ctx2);
19258
- if (ts16.isArrowFunction(node)) {
19478
+ if (ts17.isParenthesizedExpression(node)) return evalNode(node.expression, ctx2);
19479
+ if (ts17.isAsExpression(node)) return evalNode(node.expression, ctx2);
19480
+ if (ts17.isSatisfiesExpression(node)) return evalNode(node.expression, ctx2);
19481
+ if (ts17.isTypeAssertionExpression(node)) return evalNode(node.expression, ctx2);
19482
+ if (ts17.isNonNullExpression(node)) return evalNode(node.expression, ctx2);
19483
+ if (ts17.isArrowFunction(node)) {
19259
19484
  if (node.parameters.length !== 0) return UNRESOLVED;
19260
- if (!ts16.isBlock(node.body)) return evalNode(node.body, ctx2);
19485
+ if (!ts17.isBlock(node.body)) return evalNode(node.body, ctx2);
19261
19486
  const localBindings = { ...ctx2.bindings };
19262
19487
  const localCtx = { ...ctx2, bindings: localBindings };
19263
19488
  const result2 = evalStatementsForReturn(node.body.statements, localCtx);
19264
19489
  return result2 === NO_RETURN ? UNRESOLVED : result2;
19265
19490
  }
19266
- if (ts16.isNumericLiteral(node)) return Number(node.text);
19267
- if (ts16.isStringLiteralLike(node)) return node.text;
19268
- if (node.kind === ts16.SyntaxKind.TrueKeyword) return true;
19269
- if (node.kind === ts16.SyntaxKind.FalseKeyword) return false;
19270
- if (node.kind === ts16.SyntaxKind.NullKeyword) return null;
19271
- if (ts16.isIdentifier(node)) {
19491
+ if (ts17.isNumericLiteral(node)) return Number(node.text);
19492
+ if (ts17.isStringLiteralLike(node)) return node.text;
19493
+ if (node.kind === ts17.SyntaxKind.TrueKeyword) return true;
19494
+ if (node.kind === ts17.SyntaxKind.FalseKeyword) return false;
19495
+ if (node.kind === ts17.SyntaxKind.NullKeyword) return null;
19496
+ if (ts17.isIdentifier(node)) {
19272
19497
  if (node.text === "undefined") return void 0;
19273
19498
  if (node.text in ctx2.bindings) return ctx2.bindings[node.text];
19274
19499
  if (ctx2.propsLike.has(node.text)) return void 0;
19275
19500
  return UNRESOLVED;
19276
19501
  }
19277
- if (ts16.isPrefixUnaryExpression(node)) {
19502
+ if (ts17.isPrefixUnaryExpression(node)) {
19278
19503
  const arg = evalNode(node.operand, ctx2);
19279
19504
  if (arg === UNRESOLVED) return UNRESOLVED;
19280
19505
  switch (node.operator) {
19281
- case ts16.SyntaxKind.MinusToken:
19506
+ case ts17.SyntaxKind.MinusToken:
19282
19507
  return typeof arg === "number" ? -arg : UNRESOLVED;
19283
- case ts16.SyntaxKind.PlusToken:
19508
+ case ts17.SyntaxKind.PlusToken:
19284
19509
  return typeof arg === "number" ? +arg : UNRESOLVED;
19285
- case ts16.SyntaxKind.ExclamationToken:
19510
+ case ts17.SyntaxKind.ExclamationToken:
19286
19511
  return !arg;
19287
19512
  }
19288
19513
  return UNRESOLVED;
19289
19514
  }
19290
- if (ts16.isObjectLiteralExpression(node)) {
19515
+ if (ts17.isObjectLiteralExpression(node)) {
19291
19516
  const obj = {};
19292
19517
  for (const prop of node.properties) {
19293
- if (!ts16.isPropertyAssignment(prop)) return UNRESOLVED;
19518
+ if (!ts17.isPropertyAssignment(prop)) return UNRESOLVED;
19294
19519
  let key;
19295
- if (ts16.isIdentifier(prop.name) || ts16.isStringLiteralLike(prop.name)) {
19520
+ if (ts17.isIdentifier(prop.name) || ts17.isStringLiteralLike(prop.name)) {
19296
19521
  key = prop.name.text;
19297
- } else if (ts16.isNumericLiteral(prop.name)) {
19522
+ } else if (ts17.isNumericLiteral(prop.name)) {
19298
19523
  key = prop.name.text;
19299
19524
  } else {
19300
19525
  return UNRESOLVED;
@@ -19305,17 +19530,17 @@ function evalNode(node, ctx2) {
19305
19530
  }
19306
19531
  return obj;
19307
19532
  }
19308
- if (ts16.isArrayLiteralExpression(node)) {
19533
+ if (ts17.isArrayLiteralExpression(node)) {
19309
19534
  const arr = [];
19310
19535
  for (const elem of node.elements) {
19311
- if (ts16.isOmittedExpression(elem)) return UNRESOLVED;
19536
+ if (ts17.isOmittedExpression(elem)) return UNRESOLVED;
19312
19537
  const v = evalNode(elem, ctx2);
19313
19538
  if (v === UNRESOLVED) return UNRESOLVED;
19314
19539
  arr.push(v === void 0 ? null : v);
19315
19540
  }
19316
19541
  return arr;
19317
19542
  }
19318
- if (ts16.isElementAccessExpression(node)) {
19543
+ if (ts17.isElementAccessExpression(node)) {
19319
19544
  const base = evalNode(node.expression, ctx2);
19320
19545
  if (base === void 0) return void 0;
19321
19546
  if (base === UNRESOLVED || base === null || typeof base !== "object") return UNRESOLVED;
@@ -19325,16 +19550,16 @@ function evalNode(node, ctx2) {
19325
19550
  const k = String(key);
19326
19551
  return Object.prototype.hasOwnProperty.call(base, k) ? base[k] : void 0;
19327
19552
  }
19328
- if (ts16.isPropertyAccessExpression(node)) {
19553
+ if (ts17.isPropertyAccessExpression(node)) {
19329
19554
  const baseResult = evalNode(node.expression, ctx2);
19330
19555
  if (baseResult === void 0) return void 0;
19331
19556
  return UNRESOLVED;
19332
19557
  }
19333
- if (ts16.isCallExpression(node)) {
19334
- if (node.arguments.length === 0 && ts16.isIdentifier(node.expression) && node.expression.text in ctx2.bindings) {
19558
+ if (ts17.isCallExpression(node)) {
19559
+ if (node.arguments.length === 0 && ts17.isIdentifier(node.expression) && node.expression.text in ctx2.bindings) {
19335
19560
  return ctx2.bindings[node.expression.text];
19336
19561
  }
19337
- if (ts16.isPropertyAccessExpression(node.expression) && node.expression.name.text === "join") {
19562
+ if (ts17.isPropertyAccessExpression(node.expression) && node.expression.name.text === "join") {
19338
19563
  const recv = evalNode(node.expression.expression, ctx2);
19339
19564
  if (Array.isArray(recv)) {
19340
19565
  let sep = ",";
@@ -19349,24 +19574,24 @@ function evalNode(node, ctx2) {
19349
19574
  }
19350
19575
  return UNRESOLVED;
19351
19576
  }
19352
- if (ts16.isConditionalExpression(node)) {
19577
+ if (ts17.isConditionalExpression(node)) {
19353
19578
  const cond = evalNode(node.condition, ctx2);
19354
19579
  if (cond === UNRESOLVED) return UNRESOLVED;
19355
19580
  return cond ? evalNode(node.whenTrue, ctx2) : evalNode(node.whenFalse, ctx2);
19356
19581
  }
19357
- if (ts16.isBinaryExpression(node)) {
19582
+ if (ts17.isBinaryExpression(node)) {
19358
19583
  const op = node.operatorToken.kind;
19359
- if (op === ts16.SyntaxKind.QuestionQuestionToken) {
19584
+ if (op === ts17.SyntaxKind.QuestionQuestionToken) {
19360
19585
  const l2 = evalNode(node.left, ctx2);
19361
19586
  if (l2 !== UNRESOLVED && l2 !== null && l2 !== void 0) return l2;
19362
19587
  return evalNode(node.right, ctx2);
19363
19588
  }
19364
- if (op === ts16.SyntaxKind.BarBarToken) {
19589
+ if (op === ts17.SyntaxKind.BarBarToken) {
19365
19590
  const l2 = evalNode(node.left, ctx2);
19366
19591
  if (l2 !== UNRESOLVED && l2) return l2;
19367
19592
  return evalNode(node.right, ctx2);
19368
19593
  }
19369
- if (op === ts16.SyntaxKind.AmpersandAmpersandToken) {
19594
+ if (op === ts17.SyntaxKind.AmpersandAmpersandToken) {
19370
19595
  const l2 = evalNode(node.left, ctx2);
19371
19596
  if (l2 === UNRESOLVED) return UNRESOLVED;
19372
19597
  if (!l2) return l2;
@@ -19376,28 +19601,28 @@ function evalNode(node, ctx2) {
19376
19601
  const r2 = evalNode(node.right, ctx2);
19377
19602
  if (l === UNRESOLVED || r2 === UNRESOLVED) return UNRESOLVED;
19378
19603
  switch (op) {
19379
- case ts16.SyntaxKind.PlusToken:
19604
+ case ts17.SyntaxKind.PlusToken:
19380
19605
  if (typeof l === "string" || typeof r2 === "string") return `${l}${r2}`;
19381
19606
  if (typeof l === "number" && typeof r2 === "number") return l + r2;
19382
19607
  return UNRESOLVED;
19383
- case ts16.SyntaxKind.MinusToken:
19608
+ case ts17.SyntaxKind.MinusToken:
19384
19609
  return typeof l === "number" && typeof r2 === "number" ? l - r2 : UNRESOLVED;
19385
- case ts16.SyntaxKind.AsteriskToken:
19610
+ case ts17.SyntaxKind.AsteriskToken:
19386
19611
  return typeof l === "number" && typeof r2 === "number" ? l * r2 : UNRESOLVED;
19387
- case ts16.SyntaxKind.SlashToken:
19612
+ case ts17.SyntaxKind.SlashToken:
19388
19613
  return typeof l === "number" && typeof r2 === "number" && r2 !== 0 ? l / r2 : UNRESOLVED;
19389
- case ts16.SyntaxKind.PercentToken:
19614
+ case ts17.SyntaxKind.PercentToken:
19390
19615
  return typeof l === "number" && typeof r2 === "number" && r2 !== 0 ? l % r2 : UNRESOLVED;
19391
- case ts16.SyntaxKind.EqualsEqualsEqualsToken:
19392
- case ts16.SyntaxKind.EqualsEqualsToken:
19616
+ case ts17.SyntaxKind.EqualsEqualsEqualsToken:
19617
+ case ts17.SyntaxKind.EqualsEqualsToken:
19393
19618
  return l === r2;
19394
- case ts16.SyntaxKind.ExclamationEqualsEqualsToken:
19395
- case ts16.SyntaxKind.ExclamationEqualsToken:
19619
+ case ts17.SyntaxKind.ExclamationEqualsEqualsToken:
19620
+ case ts17.SyntaxKind.ExclamationEqualsToken:
19396
19621
  return l !== r2;
19397
19622
  }
19398
19623
  return UNRESOLVED;
19399
19624
  }
19400
- if (ts16.isTemplateExpression(node)) {
19625
+ if (ts17.isTemplateExpression(node)) {
19401
19626
  if (node.templateSpans.length === 0) return node.head.text;
19402
19627
  let acc = node.head.text;
19403
19628
  for (const span of node.templateSpans) {
@@ -19407,7 +19632,7 @@ function evalNode(node, ctx2) {
19407
19632
  }
19408
19633
  return acc;
19409
19634
  }
19410
- if (ts16.isNoSubstitutionTemplateLiteral(node)) return node.text;
19635
+ if (ts17.isNoSubstitutionTemplateLiteral(node)) return node.text;
19411
19636
  return UNRESOLVED;
19412
19637
  }
19413
19638
  var UNRESOLVED, NO_RETURN;
@@ -19420,7 +19645,7 @@ var init_ssr_defaults = __esm({
19420
19645
  });
19421
19646
 
19422
19647
  // ../jsx/src/augment-inherited-props.ts
19423
- import ts17 from "typescript";
19648
+ import ts18 from "typescript";
19424
19649
  function collectContextConsumers(metadata) {
19425
19650
  const constants = metadata.localConstants ?? [];
19426
19651
  const contextDefaults = /* @__PURE__ */ new Map();
@@ -19447,35 +19672,35 @@ function collectContextConsumers(metadata) {
19447
19672
  }
19448
19673
  function parseUseContextArg(source) {
19449
19674
  const expr = parseSingleExpression(source);
19450
- if (!expr || !ts17.isCallExpression(expr)) return null;
19451
- if (!ts17.isIdentifier(expr.expression) || expr.expression.text !== "useContext") return null;
19675
+ if (!expr || !ts18.isCallExpression(expr)) return null;
19676
+ if (!ts18.isIdentifier(expr.expression) || expr.expression.text !== "useContext") return null;
19452
19677
  if (expr.arguments.length !== 1) return null;
19453
19678
  const arg = expr.arguments[0];
19454
- return ts17.isIdentifier(arg) ? arg.text : null;
19679
+ return ts18.isIdentifier(arg) ? arg.text : null;
19455
19680
  }
19456
19681
  function parseCreateContextDefault(source) {
19457
19682
  const expr = parseSingleExpression(source);
19458
- if (!expr || !ts17.isCallExpression(expr)) return null;
19683
+ if (!expr || !ts18.isCallExpression(expr)) return null;
19459
19684
  if (expr.arguments.length === 0) return null;
19460
19685
  const arg = expr.arguments[0];
19461
- if (ts17.isStringLiteral(arg) || ts17.isNoSubstitutionTemplateLiteral(arg)) return arg.text;
19462
- if (ts17.isNumericLiteral(arg)) return Number(arg.text);
19463
- if (arg.kind === ts17.SyntaxKind.TrueKeyword) return true;
19464
- if (arg.kind === ts17.SyntaxKind.FalseKeyword) return false;
19686
+ if (ts18.isStringLiteral(arg) || ts18.isNoSubstitutionTemplateLiteral(arg)) return arg.text;
19687
+ if (ts18.isNumericLiteral(arg)) return Number(arg.text);
19688
+ if (arg.kind === ts18.SyntaxKind.TrueKeyword) return true;
19689
+ if (arg.kind === ts18.SyntaxKind.FalseKeyword) return false;
19465
19690
  return null;
19466
19691
  }
19467
19692
  function isObjectLiteralCreateContextDefault(source) {
19468
19693
  const expr = parseSingleExpression(source);
19469
- if (!expr || !ts17.isCallExpression(expr)) return false;
19694
+ if (!expr || !ts18.isCallExpression(expr)) return false;
19470
19695
  if (expr.arguments.length === 0) return false;
19471
- return ts17.isObjectLiteralExpression(expr.arguments[0]);
19696
+ return ts18.isObjectLiteralExpression(expr.arguments[0]);
19472
19697
  }
19473
19698
  function parseSingleExpression(source) {
19474
- const sf = ts17.createSourceFile("__ctx.ts", `(${source})`, ts17.ScriptTarget.Latest, false);
19699
+ const sf = ts18.createSourceFile("__ctx.ts", `(${source})`, ts18.ScriptTarget.Latest, false);
19475
19700
  const stmt = sf.statements[0];
19476
- if (!stmt || !ts17.isExpressionStatement(stmt)) return null;
19701
+ if (!stmt || !ts18.isExpressionStatement(stmt)) return null;
19477
19702
  let e = stmt.expression;
19478
- while (ts17.isParenthesizedExpression(e)) e = e.expression;
19703
+ while (ts18.isParenthesizedExpression(e)) e = e.expression;
19479
19704
  return e;
19480
19705
  }
19481
19706
  function augmentInheritedPropAccesses(ir) {
@@ -19496,21 +19721,21 @@ function augmentInheritedPropAccesses(ir) {
19496
19721
  const coalesceLiteralTypes = /* @__PURE__ */ new Map();
19497
19722
  const pinCoalesceLiterals = (s) => {
19498
19723
  if (!s || !s.includes(propsObj)) return;
19499
- const sf = ts17.createSourceFile("__aug.ts", `(${s})`, ts17.ScriptTarget.Latest, false);
19724
+ const sf = ts18.createSourceFile("__aug.ts", `(${s})`, ts18.ScriptTarget.Latest, false);
19500
19725
  const visit3 = (n) => {
19501
- if (ts17.isBinaryExpression(n) && (n.operatorToken.kind === ts17.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts17.SyntaxKind.BarBarToken)) {
19726
+ if (ts18.isBinaryExpression(n) && (n.operatorToken.kind === ts18.SyntaxKind.QuestionQuestionToken || n.operatorToken.kind === ts18.SyntaxKind.BarBarToken)) {
19502
19727
  let left = n.left;
19503
- while (ts17.isParenthesizedExpression(left)) left = left.expression;
19504
- if (ts17.isPropertyAccessExpression(left) && ts17.isIdentifier(left.expression) && left.expression.text === propsObj) {
19728
+ while (ts18.isParenthesizedExpression(left)) left = left.expression;
19729
+ if (ts18.isPropertyAccessExpression(left) && ts18.isIdentifier(left.expression) && left.expression.text === propsObj) {
19505
19730
  const name2 = left.name.text;
19506
19731
  let right = n.right;
19507
- while (ts17.isParenthesizedExpression(right)) right = right.expression;
19508
- if (ts17.isPrefixUnaryExpression(right)) right = right.operand;
19509
- const kind2 = ts17.isNumericLiteral(right) ? "number" : right.kind === ts17.SyntaxKind.TrueKeyword || right.kind === ts17.SyntaxKind.FalseKeyword ? "boolean" : ts17.isStringLiteralLike(right) ? "string" : null;
19732
+ while (ts18.isParenthesizedExpression(right)) right = right.expression;
19733
+ if (ts18.isPrefixUnaryExpression(right)) right = right.operand;
19734
+ const kind2 = ts18.isNumericLiteral(right) ? "number" : right.kind === ts18.SyntaxKind.TrueKeyword || right.kind === ts18.SyntaxKind.FalseKeyword ? "boolean" : ts18.isStringLiteralLike(right) ? "string" : null;
19510
19735
  if (kind2 && !coalesceLiteralTypes.has(name2)) coalesceLiteralTypes.set(name2, kind2);
19511
19736
  }
19512
19737
  }
19513
- ts17.forEachChild(n, visit3);
19738
+ ts18.forEachChild(n, visit3);
19514
19739
  };
19515
19740
  visit3(sf);
19516
19741
  };
@@ -19606,39 +19831,39 @@ function augmentInheritedPropAccesses(ir) {
19606
19831
  }
19607
19832
  }
19608
19833
  function parseStaticStringConst(source) {
19609
- const sf = ts17.createSourceFile(
19834
+ const sf = ts18.createSourceFile(
19610
19835
  "__const.ts",
19611
19836
  `const __x = (${source});`,
19612
- ts17.ScriptTarget.Latest,
19837
+ ts18.ScriptTarget.Latest,
19613
19838
  /*setParentNodes*/
19614
19839
  false
19615
19840
  );
19616
19841
  const stmt = sf.statements[0];
19617
- if (!stmt || !ts17.isVariableStatement(stmt)) return null;
19842
+ if (!stmt || !ts18.isVariableStatement(stmt)) return null;
19618
19843
  let init = stmt.declarationList.declarations[0]?.initializer;
19619
- while (init && ts17.isParenthesizedExpression(init)) init = init.expression;
19844
+ while (init && ts18.isParenthesizedExpression(init)) init = init.expression;
19620
19845
  if (!init) return null;
19621
- if (ts17.isStringLiteral(init) || ts17.isNoSubstitutionTemplateLiteral(init)) {
19846
+ if (ts18.isStringLiteral(init) || ts18.isNoSubstitutionTemplateLiteral(init)) {
19622
19847
  return init.text;
19623
19848
  }
19624
19849
  return evalStringArrayJoin(source);
19625
19850
  }
19626
19851
  function evalTemplateOfStringConsts(source, resolved) {
19627
- const sf = ts17.createSourceFile(
19852
+ const sf = ts18.createSourceFile(
19628
19853
  "__const.ts",
19629
19854
  `const __x = (${source});`,
19630
- ts17.ScriptTarget.Latest,
19855
+ ts18.ScriptTarget.Latest,
19631
19856
  /*setParentNodes*/
19632
19857
  false
19633
19858
  );
19634
19859
  const stmt = sf.statements[0];
19635
- if (!stmt || !ts17.isVariableStatement(stmt)) return null;
19860
+ if (!stmt || !ts18.isVariableStatement(stmt)) return null;
19636
19861
  let init = stmt.declarationList.declarations[0]?.initializer;
19637
- while (init && ts17.isParenthesizedExpression(init)) init = init.expression;
19638
- if (!init || !ts17.isTemplateExpression(init)) return null;
19862
+ while (init && ts18.isParenthesizedExpression(init)) init = init.expression;
19863
+ if (!init || !ts18.isTemplateExpression(init)) return null;
19639
19864
  let out = init.head.text;
19640
19865
  for (const span of init.templateSpans) {
19641
- if (!ts17.isIdentifier(span.expression)) return null;
19866
+ if (!ts18.isIdentifier(span.expression)) return null;
19642
19867
  const value2 = resolved.get(span.expression.text);
19643
19868
  if (value2 === void 0) return null;
19644
19869
  out += value2 + span.literal.text;
@@ -19667,28 +19892,28 @@ function collectModuleStringConsts(constants) {
19667
19892
  function lookupStaticRecordLiteral(objectName, key, constants) {
19668
19893
  const constInfo = (constants ?? []).find((c) => c.name === objectName && c.isModule);
19669
19894
  if (constInfo?.value === void 0) return null;
19670
- const sf = ts17.createSourceFile(
19895
+ const sf = ts18.createSourceFile(
19671
19896
  "__rec.ts",
19672
19897
  `(${constInfo.value})`,
19673
- ts17.ScriptTarget.Latest,
19898
+ ts18.ScriptTarget.Latest,
19674
19899
  /*setParentNodes*/
19675
19900
  true
19676
19901
  );
19677
19902
  if (sf.statements.length !== 1) return null;
19678
19903
  const stmt = sf.statements[0];
19679
- if (!ts17.isExpressionStatement(stmt)) return null;
19904
+ if (!ts18.isExpressionStatement(stmt)) return null;
19680
19905
  let parsed = stmt.expression;
19681
- while (ts17.isParenthesizedExpression(parsed)) parsed = parsed.expression;
19682
- if (!ts17.isObjectLiteralExpression(parsed)) return null;
19906
+ while (ts18.isParenthesizedExpression(parsed)) parsed = parsed.expression;
19907
+ if (!ts18.isObjectLiteralExpression(parsed)) return null;
19683
19908
  for (const prop of parsed.properties) {
19684
- if (!ts17.isPropertyAssignment(prop)) continue;
19909
+ if (!ts18.isPropertyAssignment(prop)) continue;
19685
19910
  const name2 = prop.name;
19686
- const propKey = ts17.isIdentifier(name2) || ts17.isStringLiteral(name2) || ts17.isNoSubstitutionTemplateLiteral(name2) ? name2.text : null;
19911
+ const propKey = ts18.isIdentifier(name2) || ts18.isStringLiteral(name2) || ts18.isNoSubstitutionTemplateLiteral(name2) ? name2.text : null;
19687
19912
  if (propKey !== key) continue;
19688
19913
  let v = prop.initializer;
19689
- while (ts17.isParenthesizedExpression(v)) v = v.expression;
19690
- if (ts17.isNumericLiteral(v)) return { kind: "number", text: v.text };
19691
- if (ts17.isStringLiteral(v) || ts17.isNoSubstitutionTemplateLiteral(v)) {
19914
+ while (ts18.isParenthesizedExpression(v)) v = v.expression;
19915
+ if (ts18.isNumericLiteral(v)) return { kind: "number", text: v.text };
19916
+ if (ts18.isStringLiteral(v) || ts18.isNoSubstitutionTemplateLiteral(v)) {
19692
19917
  return { kind: "string", text: v.text };
19693
19918
  }
19694
19919
  return null;
@@ -19696,27 +19921,27 @@ function lookupStaticRecordLiteral(objectName, key, constants) {
19696
19921
  return null;
19697
19922
  }
19698
19923
  function evalStringArrayJoin(source) {
19699
- const sf = ts17.createSourceFile(
19924
+ const sf = ts18.createSourceFile(
19700
19925
  "__join.ts",
19701
19926
  `const __x = (${source});`,
19702
- ts17.ScriptTarget.Latest,
19927
+ ts18.ScriptTarget.Latest,
19703
19928
  /*setParentNodes*/
19704
19929
  false
19705
19930
  );
19706
19931
  const stmt = sf.statements[0];
19707
- if (!stmt || !ts17.isVariableStatement(stmt)) return null;
19932
+ if (!stmt || !ts18.isVariableStatement(stmt)) return null;
19708
19933
  let node = stmt.declarationList.declarations[0]?.initializer;
19709
- while (node && ts17.isParenthesizedExpression(node)) node = node.expression;
19710
- if (!node || !ts17.isCallExpression(node)) return null;
19934
+ while (node && ts18.isParenthesizedExpression(node)) node = node.expression;
19935
+ if (!node || !ts18.isCallExpression(node)) return null;
19711
19936
  const callee = node.expression;
19712
- if (!ts17.isPropertyAccessExpression(callee)) return null;
19937
+ if (!ts18.isPropertyAccessExpression(callee)) return null;
19713
19938
  if (callee.name.text !== "join") return null;
19714
19939
  let recv = callee.expression;
19715
- while (ts17.isParenthesizedExpression(recv)) recv = recv.expression;
19716
- if (!ts17.isArrayLiteralExpression(recv)) return null;
19940
+ while (ts18.isParenthesizedExpression(recv)) recv = recv.expression;
19941
+ if (!ts18.isArrayLiteralExpression(recv)) return null;
19717
19942
  const parts = [];
19718
19943
  for (const el of recv.elements) {
19719
- if (ts17.isStringLiteral(el) || ts17.isNoSubstitutionTemplateLiteral(el)) {
19944
+ if (ts18.isStringLiteral(el) || ts18.isNoSubstitutionTemplateLiteral(el)) {
19720
19945
  parts.push(el.text);
19721
19946
  } else {
19722
19947
  return null;
@@ -19725,16 +19950,16 @@ function evalStringArrayJoin(source) {
19725
19950
  let sep = ",";
19726
19951
  if (node.arguments.length >= 1) {
19727
19952
  const arg = node.arguments[0];
19728
- if (ts17.isStringLiteral(arg) || ts17.isNoSubstitutionTemplateLiteral(arg)) sep = arg.text;
19953
+ if (ts18.isStringLiteral(arg) || ts18.isNoSubstitutionTemplateLiteral(arg)) sep = arg.text;
19729
19954
  else return null;
19730
19955
  }
19731
19956
  return parts.join(sep);
19732
19957
  }
19733
19958
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
19734
- if (!ts17.isElementAccessExpression(val)) return null;
19959
+ if (!ts18.isElementAccessExpression(val)) return null;
19735
19960
  const obj = val.expression;
19736
19961
  const arg = val.argumentExpression;
19737
- if (!ts17.isIdentifier(obj) || !ts17.isIdentifier(arg)) return null;
19962
+ if (!ts18.isIdentifier(obj) || !ts18.isIdentifier(arg)) return null;
19738
19963
  let indexPropName;
19739
19964
  let defaultKey;
19740
19965
  const resolved = resolveKey?.(arg.text);
@@ -19748,35 +19973,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
19748
19973
  }
19749
19974
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
19750
19975
  if (constInfo?.value === void 0) return null;
19751
- const sf = ts17.createSourceFile(
19976
+ const sf = ts18.createSourceFile(
19752
19977
  "__rec.ts",
19753
19978
  `(${constInfo.value})`,
19754
- ts17.ScriptTarget.Latest,
19979
+ ts18.ScriptTarget.Latest,
19755
19980
  /* setParentNodes */
19756
19981
  true
19757
19982
  );
19758
19983
  if (sf.statements.length !== 1) return null;
19759
19984
  const stmt = sf.statements[0];
19760
- if (!ts17.isExpressionStatement(stmt)) return null;
19985
+ if (!ts18.isExpressionStatement(stmt)) return null;
19761
19986
  let parsed = stmt.expression;
19762
- while (ts17.isParenthesizedExpression(parsed)) parsed = parsed.expression;
19763
- if (!ts17.isObjectLiteralExpression(parsed)) return null;
19987
+ while (ts18.isParenthesizedExpression(parsed)) parsed = parsed.expression;
19988
+ if (!ts18.isObjectLiteralExpression(parsed)) return null;
19764
19989
  const entries2 = [];
19765
19990
  for (const prop of parsed.properties) {
19766
- if (!ts17.isPropertyAssignment(prop)) return null;
19991
+ if (!ts18.isPropertyAssignment(prop)) return null;
19767
19992
  let key;
19768
- if (ts17.isIdentifier(prop.name)) {
19993
+ if (ts18.isIdentifier(prop.name)) {
19769
19994
  key = prop.name.text;
19770
- } else if (ts17.isStringLiteral(prop.name) || ts17.isNoSubstitutionTemplateLiteral(prop.name)) {
19995
+ } else if (ts18.isStringLiteral(prop.name) || ts18.isNoSubstitutionTemplateLiteral(prop.name)) {
19771
19996
  key = prop.name.text;
19772
19997
  } else {
19773
19998
  return null;
19774
19999
  }
19775
20000
  let v = prop.initializer;
19776
- while (ts17.isParenthesizedExpression(v)) v = v.expression;
19777
- if (ts17.isNumericLiteral(v)) {
20001
+ while (ts18.isParenthesizedExpression(v)) v = v.expression;
20002
+ if (ts18.isNumericLiteral(v)) {
19778
20003
  entries2.push({ key, value: { kind: "number", text: v.text } });
19779
- } else if (ts17.isStringLiteral(v) || ts17.isNoSubstitutionTemplateLiteral(v)) {
20004
+ } else if (ts18.isStringLiteral(v) || ts18.isNoSubstitutionTemplateLiteral(v)) {
19780
20005
  entries2.push({ key, value: { kind: "string", text: v.text } });
19781
20006
  } else {
19782
20007
  return null;
@@ -19843,6 +20068,206 @@ var init_ssr_seed_plan = __esm({
19843
20068
  }
19844
20069
  });
19845
20070
 
20071
+ // ../jsx/src/rich-type-refusal.ts
20072
+ function checkRichTypeMethodCalls(root2, metadata, errors) {
20073
+ if (!metadata.propsType) return;
20074
+ const matchers = prepareLoweringMatchers(metadata);
20075
+ const seen = /* @__PURE__ */ new Set();
20076
+ walkNode(root2, metadata, EMPTY_BINDINGS2, matchers, errors, seen);
20077
+ }
20078
+ function isLoweringClaimed(matchers, callee, args2) {
20079
+ return matchers.some((m) => m(callee, args2) !== null);
20080
+ }
20081
+ function describeReceiverPath(expr) {
20082
+ if (expr.kind === "identifier") return expr.name;
20083
+ if (expr.kind === "member" && !expr.computed) return `${describeReceiverPath(expr.object)}.${expr.property}`;
20084
+ return "<expression>";
20085
+ }
20086
+ function receiverRootIsProp(expr, bindings) {
20087
+ let root2 = expr;
20088
+ while (root2.kind === "member" && !root2.computed) root2 = root2.object;
20089
+ return root2.kind === "identifier" && !bindings.has(root2.name);
20090
+ }
20091
+ function pushDiagnostic(errors, seen, loc, method2, receiverPath, isProp, typeName) {
20092
+ const key = `${loc.start.line}:${loc.start.column}:${receiverPath}.${method2}`;
20093
+ if (seen.has(key)) return;
20094
+ seen.add(key);
20095
+ const receiver = isProp ? `prop '${receiverPath}'` : `'${receiverPath}'`;
20096
+ errors.push({
20097
+ code: ErrorCodes.UNSUPPORTED_JSX_PATTERN,
20098
+ severity: "error",
20099
+ message: `Expression cannot be compiled to marked template: method '.${method2}()' on ${receiver} of host type '${typeName}' has no catalogued lowering.`,
20100
+ loc,
20101
+ suggestion: {
20102
+ message: "Add /* @client */ to evaluate this expression on the client only, or pre-compute the value server-side."
20103
+ }
20104
+ });
20105
+ }
20106
+ function checkExpr(expr, loc, meta, bindings, matchers, errors, seen) {
20107
+ const recurse = (e, b = bindings) => checkExpr(e, loc, meta, b, matchers, errors, seen);
20108
+ switch (expr.kind) {
20109
+ case "call": {
20110
+ if (expr.callee.kind === "member") {
20111
+ const receiverType = resolveReceiverType(expr.callee.object, meta, bindings);
20112
+ if (receiverType && receiverType.kind === "interface") {
20113
+ const typeName = baseTypeName(receiverType.raw);
20114
+ const inFileShadow = meta.typeDefinitions.some((d) => d.name === typeName);
20115
+ if (HOST_RICH_TYPE_NAMES.has(typeName) && !inFileShadow && !isLoweringClaimed(matchers, expr.callee, expr.args)) {
20116
+ pushDiagnostic(
20117
+ errors,
20118
+ seen,
20119
+ loc,
20120
+ expr.callee.property,
20121
+ describeReceiverPath(expr.callee.object),
20122
+ receiverRootIsProp(expr.callee.object, bindings),
20123
+ typeName
20124
+ );
20125
+ }
20126
+ }
20127
+ }
20128
+ recurse(expr.callee);
20129
+ for (const arg of expr.args) recurse(arg);
20130
+ break;
20131
+ }
20132
+ case "member":
20133
+ recurse(expr.object);
20134
+ break;
20135
+ case "index-access":
20136
+ recurse(expr.object);
20137
+ recurse(expr.index);
20138
+ break;
20139
+ case "binary":
20140
+ recurse(expr.left);
20141
+ recurse(expr.right);
20142
+ break;
20143
+ case "unary":
20144
+ recurse(expr.argument);
20145
+ break;
20146
+ case "conditional":
20147
+ recurse(expr.test);
20148
+ recurse(expr.consequent);
20149
+ recurse(expr.alternate);
20150
+ break;
20151
+ case "logical":
20152
+ recurse(expr.left);
20153
+ recurse(expr.right);
20154
+ break;
20155
+ case "template-literal":
20156
+ for (const part of expr.parts) if (part.type === "expression") recurse(part.expr);
20157
+ break;
20158
+ case "arrow": {
20159
+ const shadowed = new Map(bindings);
20160
+ for (const param of expr.params) shadowed.set(param, null);
20161
+ recurse(expr.body, shadowed);
20162
+ break;
20163
+ }
20164
+ case "array-literal":
20165
+ for (const el of expr.elements) recurse(el);
20166
+ break;
20167
+ case "object-literal":
20168
+ for (const prop of expr.properties) recurse(prop.value);
20169
+ break;
20170
+ case "array-method":
20171
+ recurse(expr.object);
20172
+ for (const arg of expr.args) recurse(arg);
20173
+ break;
20174
+ case "identifier":
20175
+ case "literal":
20176
+ case "regex":
20177
+ case "unsupported":
20178
+ break;
20179
+ }
20180
+ }
20181
+ function walkTemplateParts(parts, loc, meta, bindings, matchers, errors, seen) {
20182
+ for (const part of parts) {
20183
+ if (part.type === "ternary") {
20184
+ const trimmed = part.condition.trim();
20185
+ if (trimmed) checkExpr(parseExpression(trimmed), loc, meta, bindings, matchers, errors, seen);
20186
+ } else if (part.type === "lookup") {
20187
+ const trimmed = part.key.trim();
20188
+ if (trimmed) checkExpr(parseExpression(trimmed), loc, meta, bindings, matchers, errors, seen);
20189
+ }
20190
+ }
20191
+ }
20192
+ function walkAttrValue(value2, clientOnly, loc, meta, bindings, matchers, errors, seen) {
20193
+ if (clientOnly) return;
20194
+ if (value2.kind === "expression") {
20195
+ if (value2.parsed) checkExpr(value2.parsed, loc, meta, bindings, matchers, errors, seen);
20196
+ if (value2.parts) walkTemplateParts(value2.parts, loc, meta, bindings, matchers, errors, seen);
20197
+ } else if (value2.kind === "spread") {
20198
+ if (value2.parsed) checkExpr(value2.parsed, loc, meta, bindings, matchers, errors, seen);
20199
+ } else if (value2.kind === "template") {
20200
+ walkTemplateParts(value2.parts, loc, meta, bindings, matchers, errors, seen);
20201
+ }
20202
+ }
20203
+ function walkNode(node, meta, bindings, matchers, errors, seen) {
20204
+ if (node.type === "expression") {
20205
+ if (!node.clientOnly && node.parsed) checkExpr(node.parsed, node.loc, meta, bindings, matchers, errors, seen);
20206
+ } else if (node.type === "conditional") {
20207
+ if (!node.clientOnly && node.parsedCondition) checkExpr(node.parsedCondition, node.loc, meta, bindings, matchers, errors, seen);
20208
+ } else if (node.type === "if-statement") {
20209
+ if (node.parsedCondition) checkExpr(node.parsedCondition, node.loc, meta, bindings, matchers, errors, seen);
20210
+ }
20211
+ if (node.type === "element") {
20212
+ for (const attr of node.attrs) walkAttrValue(attr.value, attr.clientOnly, attr.loc, meta, bindings, matchers, errors, seen);
20213
+ } else if (node.type === "component") {
20214
+ for (const prop of node.props) walkAttrValue(prop.value, prop.clientOnly, prop.loc, meta, bindings, matchers, errors, seen);
20215
+ } else if (node.type === "provider") {
20216
+ walkAttrValue(node.valueProp.value, node.valueProp.clientOnly, node.valueProp.loc, meta, bindings, matchers, errors, seen);
20217
+ }
20218
+ switch (node.type) {
20219
+ case "element":
20220
+ case "component":
20221
+ case "fragment":
20222
+ case "provider":
20223
+ for (const child of node.children) walkNode(child, meta, bindings, matchers, errors, seen);
20224
+ break;
20225
+ case "async":
20226
+ walkNode(node.fallback, meta, bindings, matchers, errors, seen);
20227
+ for (const child of node.children) walkNode(child, meta, bindings, matchers, errors, seen);
20228
+ break;
20229
+ case "loop": {
20230
+ if (node.clientOnly) break;
20231
+ if (node.arrayParsed) checkExpr(node.arrayParsed, node.loc, meta, bindings, matchers, errors, seen);
20232
+ const loopBindings = new Map(bindings);
20233
+ const arrayType = node.arrayParsed ? resolveReceiverType(node.arrayParsed, meta, bindings) : null;
20234
+ loopBindings.set(node.param, arrayType?.kind === "array" ? arrayType.elementType ?? null : null);
20235
+ if (node.index) loopBindings.set(node.index, null);
20236
+ for (const child of node.children) walkNode(child, meta, loopBindings, matchers, errors, seen);
20237
+ if (node.childComponent) {
20238
+ for (const child of node.childComponent.children) walkNode(child, meta, loopBindings, matchers, errors, seen);
20239
+ }
20240
+ for (const nested of node.nestedComponents ?? []) {
20241
+ for (const child of nested.children) walkNode(child, meta, loopBindings, matchers, errors, seen);
20242
+ }
20243
+ for (const frag of node.flatMapCallback?.fragments ?? []) {
20244
+ walkNode(frag.ir, meta, loopBindings, matchers, errors, seen);
20245
+ }
20246
+ break;
20247
+ }
20248
+ case "conditional":
20249
+ if (node.clientOnly) break;
20250
+ walkNode(node.whenTrue, meta, bindings, matchers, errors, seen);
20251
+ walkNode(node.whenFalse, meta, bindings, matchers, errors, seen);
20252
+ break;
20253
+ case "if-statement":
20254
+ walkNode(node.consequent, meta, bindings, matchers, errors, seen);
20255
+ if (node.alternate) walkNode(node.alternate, meta, bindings, matchers, errors, seen);
20256
+ break;
20257
+ }
20258
+ }
20259
+ var EMPTY_BINDINGS2;
20260
+ var init_rich_type_refusal = __esm({
20261
+ "../jsx/src/rich-type-refusal.ts"() {
20262
+ "use strict";
20263
+ init_expression_parser();
20264
+ init_lowering_registry();
20265
+ init_errors();
20266
+ init_rich_type_evidence();
20267
+ EMPTY_BINDINGS2 = /* @__PURE__ */ new Map();
20268
+ }
20269
+ });
20270
+
19846
20271
  // ../jsx/src/compiler.ts
19847
20272
  function mergeTemplateImports(lines) {
19848
20273
  const result2 = [];
@@ -19899,6 +20324,7 @@ function compileMultipleComponents(source, filePath, componentNames, options2) {
19899
20324
  errors: []
19900
20325
  };
19901
20326
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
20327
+ checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
19902
20328
  if (options2.cssLayerPrefix) {
19903
20329
  applyCssLayerPrefix(componentIR, options2.cssLayerPrefix);
19904
20330
  }
@@ -20235,6 +20661,7 @@ function compileJSX(source, filePath, options2) {
20235
20661
  errors: []
20236
20662
  };
20237
20663
  componentIR.metadata.clientAnalysis = analyzeClientNeeds(componentIR);
20664
+ checkRichTypeMethodCalls(componentIR.root, componentIR.metadata, errors);
20238
20665
  if (ctx2.importedClientSignalNames.size > 0) {
20239
20666
  const sources = /* @__PURE__ */ new Set();
20240
20667
  for (const imp of ctx2.imports) {
@@ -20364,11 +20791,12 @@ var init_compiler = __esm({
20364
20791
  init_preprocess_inline_jsx_callbacks();
20365
20792
  init_ssr_defaults();
20366
20793
  init_ssr_seed_plan();
20794
+ init_rich_type_refusal();
20367
20795
  }
20368
20796
  });
20369
20797
 
20370
20798
  // ../jsx/src/shared-program.ts
20371
- import ts18 from "typescript";
20799
+ import ts19 from "typescript";
20372
20800
  import path5 from "node:path";
20373
20801
  function commonParent(paths) {
20374
20802
  if (paths.length === 0) return process.cwd();
@@ -20386,10 +20814,10 @@ function commonParent(paths) {
20386
20814
  function createProgramForCorpus(files2, options2 = {}) {
20387
20815
  const baseUrl = options2.baseUrl ?? commonParent(files2);
20388
20816
  const compilerOptions = {
20389
- target: ts18.ScriptTarget.Latest,
20390
- module: ts18.ModuleKind.ESNext,
20391
- moduleResolution: ts18.ModuleResolutionKind.Bundler,
20392
- jsx: ts18.JsxEmit.ReactJSX,
20817
+ target: ts19.ScriptTarget.Latest,
20818
+ module: ts19.ModuleKind.ESNext,
20819
+ moduleResolution: ts19.ModuleResolutionKind.Bundler,
20820
+ jsx: ts19.JsxEmit.ReactJSX,
20393
20821
  strict: true,
20394
20822
  skipLibCheck: true,
20395
20823
  noEmit: true,
@@ -20399,7 +20827,7 @@ function createProgramForCorpus(files2, options2 = {}) {
20399
20827
  ...options2.compilerOptions
20400
20828
  };
20401
20829
  const absolute = files2.map((f) => path5.resolve(f));
20402
- return ts18.createProgram(absolute, compilerOptions, void 0, options2.oldProgram);
20830
+ return ts19.createProgram(absolute, compilerOptions, void 0, options2.oldProgram);
20403
20831
  }
20404
20832
  var init_shared_program = __esm({
20405
20833
  "../jsx/src/shared-program.ts"() {
@@ -21260,6 +21688,7 @@ var init_builtin_lowering_plugins = __esm({
21260
21688
  init_lowering_registry();
21261
21689
  init_env_signal();
21262
21690
  init_query_href_lowering();
21691
+ init_date_lowering();
21263
21692
  queryHrefPlugin = {
21264
21693
  name: "queryHref",
21265
21694
  prepare(metadata) {
@@ -21271,7 +21700,7 @@ var init_builtin_lowering_plugins = __esm({
21271
21700
  };
21272
21701
  }
21273
21702
  };
21274
- BUILTIN_LOWERING_PLUGINS = [queryHrefPlugin];
21703
+ BUILTIN_LOWERING_PLUGINS = [queryHrefPlugin, datePlugin];
21275
21704
  }
21276
21705
  });
21277
21706
 
@@ -21411,7 +21840,7 @@ var init_dangerous_inner_html = __esm({
21411
21840
  });
21412
21841
 
21413
21842
  // ../jsx/src/combine-client-js.ts
21414
- import ts19 from "typescript";
21843
+ import ts20 from "typescript";
21415
21844
  function combineParentChildClientJs(files2) {
21416
21845
  const result2 = /* @__PURE__ */ new Map();
21417
21846
  const lookup = /* @__PURE__ */ new Map();
@@ -21468,17 +21897,17 @@ function combineParentChildClientJs(files2) {
21468
21897
  return result2;
21469
21898
  }
21470
21899
  function parseAndMerge(content2, importsBySource, otherImports, codeSections) {
21471
- const sourceFile = ts19.createSourceFile(
21900
+ const sourceFile = ts20.createSourceFile(
21472
21901
  "combine.js",
21473
21902
  content2,
21474
- ts19.ScriptTarget.Latest,
21903
+ ts20.ScriptTarget.Latest,
21475
21904
  /*setParentNodes*/
21476
21905
  false,
21477
- ts19.ScriptKind.JS
21906
+ ts20.ScriptKind.JS
21478
21907
  );
21479
21908
  const importSpans = [];
21480
21909
  for (const stmt of sourceFile.statements) {
21481
- if (!ts19.isImportDeclaration(stmt)) continue;
21910
+ if (!ts20.isImportDeclaration(stmt)) continue;
21482
21911
  const start2 = stmt.getStart(sourceFile);
21483
21912
  const end2 = stmt.getEnd();
21484
21913
  importSpans.push([start2, end2]);
@@ -21486,8 +21915,8 @@ function parseAndMerge(content2, importsBySource, otherImports, codeSections) {
21486
21915
  if (stmtText.includes("@bf-child:")) continue;
21487
21916
  const clause = stmt.importClause;
21488
21917
  const bindings = clause?.namedBindings;
21489
- const specifier = ts19.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
21490
- if (clause && !clause.name && bindings && ts19.isNamedImports(bindings)) {
21918
+ const specifier = ts20.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
21919
+ if (clause && !clause.name && bindings && ts20.isNamedImports(bindings)) {
21491
21920
  if (!importsBySource.has(specifier)) {
21492
21921
  importsBySource.set(specifier, /* @__PURE__ */ new Set());
21493
21922
  }
@@ -21677,7 +22106,7 @@ var init_loop_destructure = __esm({
21677
22106
  });
21678
22107
 
21679
22108
  // ../jsx/src/debug.ts
21680
- import ts20 from "typescript";
22109
+ import ts21 from "typescript";
21681
22110
  function buildComponentGraph(source, filePath, componentName) {
21682
22111
  const ctx2 = analyzeComponent(source, filePath, componentName);
21683
22112
  if (!ctx2.jsxReturn) {
@@ -22889,18 +23318,18 @@ function truncateExpr(expr, max = 40) {
22889
23318
  function exprReadsPropMember(expr, propsObjectName) {
22890
23319
  let sf;
22891
23320
  try {
22892
- sf = ts20.createSourceFile("__attr.tsx", `(${expr})`, ts20.ScriptTarget.Latest, true, ts20.ScriptKind.TSX);
23321
+ sf = ts21.createSourceFile("__attr.tsx", `(${expr})`, ts21.ScriptTarget.Latest, true, ts21.ScriptKind.TSX);
22893
23322
  } catch {
22894
23323
  return false;
22895
23324
  }
22896
23325
  let found = false;
22897
23326
  const visit3 = (n) => {
22898
23327
  if (found) return;
22899
- if (ts20.isPropertyAccessExpression(n) && ts20.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
23328
+ if (ts21.isPropertyAccessExpression(n) && ts21.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
22900
23329
  found = true;
22901
23330
  return;
22902
23331
  }
22903
- ts20.forEachChild(n, visit3);
23332
+ ts21.forEachChild(n, visit3);
22904
23333
  };
22905
23334
  visit3(sf);
22906
23335
  return found;
@@ -22977,7 +23406,7 @@ var init_debug = __esm({
22977
23406
  });
22978
23407
 
22979
23408
  // ../jsx/src/profiler.ts
22980
- import ts21 from "typescript";
23409
+ import ts22 from "typescript";
22981
23410
  function buildStaticBudget(source, filePath, componentName, options2 = {}) {
22982
23411
  const threshold = options2.fanOutThreshold ?? DEFAULT_FANOUT_THRESHOLD;
22983
23412
  const program = createProgramForFile(source, filePath)?.program;
@@ -23232,14 +23661,14 @@ function joinProfilerEvents(events, index) {
23232
23661
  return { joined, unattributed, diagnostics };
23233
23662
  }
23234
23663
  function findUninstrumentedEffects(source, filePath, instrumentedLines) {
23235
- const sf = ts21.createSourceFile(filePath, source, ts21.ScriptTarget.Latest, true, ts21.ScriptKind.TSX);
23664
+ const sf = ts22.createSourceFile(filePath, source, ts22.ScriptTarget.Latest, true, ts22.ScriptKind.TSX);
23236
23665
  const out = [];
23237
23666
  const visit3 = (node) => {
23238
- if (ts21.isCallExpression(node) && ts21.isIdentifier(node.expression) && node.expression.text === "createEffect") {
23667
+ if (ts22.isCallExpression(node) && ts22.isIdentifier(node.expression) && node.expression.text === "createEffect") {
23239
23668
  const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
23240
23669
  if (!instrumentedLines.has(line)) out.push({ file: filePath, line });
23241
23670
  }
23242
- ts21.forEachChild(node, visit3);
23671
+ ts22.forEachChild(node, visit3);
23243
23672
  };
23244
23673
  visit3(sf);
23245
23674
  out.sort((a, b) => a.line - b.line);
@@ -23525,19 +23954,19 @@ function assessBatchSafety(args2) {
23525
23954
  const signalGetters = new Set(args2.graph.signals.map((s) => s.name));
23526
23955
  let sf;
23527
23956
  try {
23528
- sf = ts21.createSourceFile("__h.ts", `const __h = ${args2.handler}`, ts21.ScriptTarget.Latest, true);
23957
+ sf = ts22.createSourceFile("__h.ts", `const __h = ${args2.handler}`, ts22.ScriptTarget.Latest, true);
23529
23958
  } catch {
23530
23959
  return "unverified";
23531
23960
  }
23532
23961
  const calls = [];
23533
23962
  const visit3 = (node) => {
23534
- if (ts21.isCallExpression(node) && ts21.isIdentifier(node.expression)) {
23963
+ if (ts22.isCallExpression(node) && ts22.isIdentifier(node.expression)) {
23535
23964
  const name2 = node.expression.text;
23536
23965
  if (setters.has(name2)) calls.push({ pos: node.getStart(sf), kind: "write" });
23537
23966
  else if (D.has(name2) && node.arguments.length === 0) calls.push({ pos: node.getStart(sf), kind: "memoRead" });
23538
23967
  else if (!signalGetters.has(name2) && !memoNames.has(name2)) calls.push({ pos: node.getStart(sf), kind: "risky" });
23539
23968
  }
23540
- ts21.forEachChild(node, visit3);
23969
+ ts22.forEachChild(node, visit3);
23541
23970
  };
23542
23971
  visit3(sf);
23543
23972
  calls.sort((a, b) => a.pos - b.pos);
@@ -24450,7 +24879,7 @@ var init_runtime = __esm({
24450
24879
 
24451
24880
  // src/lib/resolve-imports.ts
24452
24881
  import { dirname as dirname2, resolve as resolve2 } from "node:path";
24453
- import ts22 from "typescript";
24882
+ import ts23 from "typescript";
24454
24883
  function shapeFromDecl(decl) {
24455
24884
  const clause = decl.importClause;
24456
24885
  if (!clause) return null;
@@ -24460,7 +24889,7 @@ function shapeFromDecl(decl) {
24460
24889
  }
24461
24890
  const bindings = clause.namedBindings;
24462
24891
  if (bindings) {
24463
- if (ts22.isNamespaceImport(bindings)) {
24892
+ if (ts23.isNamespaceImport(bindings)) {
24464
24893
  shape.namespace = bindings.name.text;
24465
24894
  } else {
24466
24895
  for (const el of bindings.elements) {
@@ -24474,38 +24903,38 @@ function shapeFromDecl(decl) {
24474
24903
  }
24475
24904
  function collectExportedNames(source) {
24476
24905
  const names = /* @__PURE__ */ new Set();
24477
- const sourceFile = ts22.createSourceFile(
24906
+ const sourceFile = ts23.createSourceFile(
24478
24907
  "mod.ts",
24479
24908
  source,
24480
- ts22.ScriptTarget.Latest,
24909
+ ts23.ScriptTarget.Latest,
24481
24910
  /*setParents*/
24482
24911
  false,
24483
- ts22.ScriptKind.TS
24912
+ ts23.ScriptKind.TS
24484
24913
  );
24485
24914
  function hasExport(node) {
24486
- if (!ts22.canHaveModifiers(node)) return false;
24487
- const mods = ts22.getModifiers(node);
24488
- return mods?.some((m) => m.kind === ts22.SyntaxKind.ExportKeyword) ?? false;
24915
+ if (!ts23.canHaveModifiers(node)) return false;
24916
+ const mods = ts23.getModifiers(node);
24917
+ return mods?.some((m) => m.kind === ts23.SyntaxKind.ExportKeyword) ?? false;
24489
24918
  }
24490
24919
  function collectFromBindingName(name2) {
24491
- if (ts22.isIdentifier(name2)) {
24920
+ if (ts23.isIdentifier(name2)) {
24492
24921
  names.add(name2.text);
24493
24922
  return;
24494
24923
  }
24495
24924
  for (const el of name2.elements) {
24496
- if (ts22.isBindingElement(el)) collectFromBindingName(el.name);
24925
+ if (ts23.isBindingElement(el)) collectFromBindingName(el.name);
24497
24926
  }
24498
24927
  }
24499
24928
  for (const stmt of sourceFile.statements) {
24500
- if (ts22.isVariableStatement(stmt) && hasExport(stmt)) {
24929
+ if (ts23.isVariableStatement(stmt) && hasExport(stmt)) {
24501
24930
  for (const d of stmt.declarationList.declarations) {
24502
24931
  collectFromBindingName(d.name);
24503
24932
  }
24504
- } else if (ts22.isFunctionDeclaration(stmt) && hasExport(stmt) && stmt.name) {
24933
+ } else if (ts23.isFunctionDeclaration(stmt) && hasExport(stmt) && stmt.name) {
24505
24934
  names.add(stmt.name.text);
24506
- } else if (ts22.isClassDeclaration(stmt) && hasExport(stmt) && stmt.name) {
24935
+ } else if (ts23.isClassDeclaration(stmt) && hasExport(stmt) && stmt.name) {
24507
24936
  names.add(stmt.name.text);
24508
- } else if (ts22.isExportDeclaration(stmt) && !stmt.moduleSpecifier && stmt.exportClause && ts22.isNamedExports(stmt.exportClause)) {
24937
+ } else if (ts23.isExportDeclaration(stmt) && !stmt.moduleSpecifier && stmt.exportClause && ts23.isNamedExports(stmt.exportClause)) {
24509
24938
  if (stmt.isTypeOnly) continue;
24510
24939
  for (const el of stmt.exportClause.elements) {
24511
24940
  if (el.isTypeOnly) continue;
@@ -24516,16 +24945,16 @@ function collectExportedNames(source) {
24516
24945
  return [...names];
24517
24946
  }
24518
24947
  function hasUseClientDirective(source) {
24519
- const sourceFile = ts22.createSourceFile(
24948
+ const sourceFile = ts23.createSourceFile(
24520
24949
  "check.tsx",
24521
24950
  source,
24522
- ts22.ScriptTarget.Latest,
24951
+ ts23.ScriptTarget.Latest,
24523
24952
  /*setParents*/
24524
24953
  false,
24525
- ts22.ScriptKind.TSX
24954
+ ts23.ScriptKind.TSX
24526
24955
  );
24527
24956
  for (const stmt of sourceFile.statements) {
24528
- if (!ts22.isExpressionStatement(stmt) || !ts22.isStringLiteral(stmt.expression)) {
24957
+ if (!ts23.isExpressionStatement(stmt) || !ts23.isStringLiteral(stmt.expression)) {
24529
24958
  return false;
24530
24959
  }
24531
24960
  if (stmt.expression.text === "use client") return true;
@@ -24534,53 +24963,53 @@ function hasUseClientDirective(source) {
24534
24963
  }
24535
24964
  function collectTopLevelBindings(source) {
24536
24965
  const names = /* @__PURE__ */ new Set();
24537
- const sourceFile = ts22.createSourceFile(
24966
+ const sourceFile = ts23.createSourceFile(
24538
24967
  "bundle.ts",
24539
24968
  source,
24540
- ts22.ScriptTarget.Latest,
24969
+ ts23.ScriptTarget.Latest,
24541
24970
  /*setParents*/
24542
24971
  false,
24543
- ts22.ScriptKind.TS
24972
+ ts23.ScriptKind.TS
24544
24973
  );
24545
24974
  function collectFromBindingName(name2) {
24546
- if (ts22.isIdentifier(name2)) {
24975
+ if (ts23.isIdentifier(name2)) {
24547
24976
  names.add(name2.text);
24548
24977
  return;
24549
24978
  }
24550
24979
  for (const el of name2.elements) {
24551
- if (ts22.isBindingElement(el)) collectFromBindingName(el.name);
24980
+ if (ts23.isBindingElement(el)) collectFromBindingName(el.name);
24552
24981
  }
24553
24982
  }
24554
24983
  for (const stmt of sourceFile.statements) {
24555
- if (ts22.isVariableStatement(stmt)) {
24984
+ if (ts23.isVariableStatement(stmt)) {
24556
24985
  for (const d of stmt.declarationList.declarations) {
24557
24986
  collectFromBindingName(d.name);
24558
24987
  }
24559
- } else if (ts22.isFunctionDeclaration(stmt) && stmt.name) {
24988
+ } else if (ts23.isFunctionDeclaration(stmt) && stmt.name) {
24560
24989
  names.add(stmt.name.text);
24561
- } else if (ts22.isClassDeclaration(stmt) && stmt.name) {
24990
+ } else if (ts23.isClassDeclaration(stmt) && stmt.name) {
24562
24991
  names.add(stmt.name.text);
24563
24992
  }
24564
24993
  }
24565
24994
  return names;
24566
24995
  }
24567
24996
  function stripImportsAndExports(body2) {
24568
- const sourceFile = ts22.createSourceFile(
24997
+ const sourceFile = ts23.createSourceFile(
24569
24998
  "body.ts",
24570
24999
  body2,
24571
- ts22.ScriptTarget.Latest,
25000
+ ts23.ScriptTarget.Latest,
24572
25001
  /*setParents*/
24573
25002
  false,
24574
- ts22.ScriptKind.TS
25003
+ ts23.ScriptKind.TS
24575
25004
  );
24576
25005
  const spans = [];
24577
25006
  const hoistedImports = [];
24578
25007
  for (const stmt of sourceFile.statements) {
24579
- if (ts22.isImportDeclaration(stmt)) {
25008
+ if (ts23.isImportDeclaration(stmt)) {
24580
25009
  const start2 = stmt.getStart(sourceFile);
24581
25010
  const end2 = stmt.getEnd();
24582
25011
  const specifier = stmt.moduleSpecifier;
24583
- if (ts22.isStringLiteral(specifier)) {
25012
+ if (ts23.isStringLiteral(specifier)) {
24584
25013
  const path25 = specifier.text;
24585
25014
  const isRelative = path25.startsWith("./") || path25.startsWith("../");
24586
25015
  if (!isRelative) {
@@ -24590,24 +25019,24 @@ function stripImportsAndExports(body2) {
24590
25019
  spans.push([start2, end2]);
24591
25020
  continue;
24592
25021
  }
24593
- if (ts22.isExportDeclaration(stmt)) {
25022
+ if (ts23.isExportDeclaration(stmt)) {
24594
25023
  spans.push([stmt.getStart(sourceFile), stmt.getEnd()]);
24595
25024
  continue;
24596
25025
  }
24597
- if (ts22.isExportAssignment(stmt)) {
24598
- const exportKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts22.SyntaxKind.ExportKeyword);
24599
- const defaultKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts22.SyntaxKind.DefaultKeyword);
24600
- const equalsKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts22.SyntaxKind.EqualsToken);
25026
+ if (ts23.isExportAssignment(stmt)) {
25027
+ const exportKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts23.SyntaxKind.ExportKeyword);
25028
+ const defaultKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts23.SyntaxKind.DefaultKeyword);
25029
+ const equalsKw = stmt.getChildren(sourceFile).find((c) => c.kind === ts23.SyntaxKind.EqualsToken);
24601
25030
  const start2 = exportKw?.getStart(sourceFile) ?? stmt.getStart(sourceFile);
24602
25031
  const end2 = (defaultKw ?? equalsKw)?.getEnd() ?? exportKw?.getEnd() ?? stmt.getStart(sourceFile);
24603
25032
  if (end2 > start2) spans.push([start2, end2]);
24604
25033
  continue;
24605
25034
  }
24606
- if (ts22.canHaveModifiers(stmt)) {
24607
- const mods = ts22.getModifiers(stmt);
25035
+ if (ts23.canHaveModifiers(stmt)) {
25036
+ const mods = ts23.getModifiers(stmt);
24608
25037
  if (!mods) continue;
24609
25038
  for (const mod of mods) {
24610
- if (mod.kind === ts22.SyntaxKind.ExportKeyword) {
25039
+ if (mod.kind === ts23.SyntaxKind.ExportKeyword) {
24611
25040
  const start2 = mod.getStart(sourceFile);
24612
25041
  let end2 = mod.getEnd();
24613
25042
  while (end2 < body2.length && /\s/.test(body2[end2])) end2++;
@@ -24716,48 +25145,48 @@ function buildDanglingReferenceMessage(binding, s) {
24716
25145
  function isValueReference(id2) {
24717
25146
  const parent2 = id2.parent;
24718
25147
  if (!parent2) return false;
24719
- if (ts22.isPropertyAccessExpression(parent2) && parent2.name === id2) return false;
24720
- if (ts22.isPropertyAssignment(parent2) && parent2.name === id2) return false;
24721
- if ((ts22.isMethodDeclaration(parent2) || ts22.isGetAccessorDeclaration(parent2) || ts22.isSetAccessorDeclaration(parent2)) && parent2.name === id2) {
25148
+ if (ts23.isPropertyAccessExpression(parent2) && parent2.name === id2) return false;
25149
+ if (ts23.isPropertyAssignment(parent2) && parent2.name === id2) return false;
25150
+ if ((ts23.isMethodDeclaration(parent2) || ts23.isGetAccessorDeclaration(parent2) || ts23.isSetAccessorDeclaration(parent2)) && parent2.name === id2) {
24722
25151
  return false;
24723
25152
  }
24724
- if (ts22.isVariableDeclaration(parent2) && parent2.name === id2) return false;
24725
- if (ts22.isFunctionDeclaration(parent2) && parent2.name === id2) return false;
24726
- if (ts22.isFunctionExpression(parent2) && parent2.name === id2) return false;
24727
- if (ts22.isClassDeclaration(parent2) && parent2.name === id2) return false;
24728
- if (ts22.isClassExpression(parent2) && parent2.name === id2) return false;
24729
- if (ts22.isParameter(parent2) && parent2.name === id2) return false;
24730
- if (ts22.isBindingElement(parent2) && (parent2.name === id2 || parent2.propertyName === id2)) return false;
24731
- if (ts22.isLabeledStatement(parent2) && parent2.label === id2) return false;
24732
- if (ts22.isBreakOrContinueStatement(parent2) && parent2.label === id2) return false;
24733
- if (ts22.isImportSpecifier(parent2) && (parent2.name === id2 || parent2.propertyName === id2)) return false;
24734
- if (ts22.isExportSpecifier(parent2) && (parent2.name === id2 || parent2.propertyName === id2)) return false;
24735
- if (ts22.isImportClause(parent2) && parent2.name === id2) return false;
24736
- if (ts22.isNamespaceImport(parent2) && parent2.name === id2) return false;
24737
- if (ts22.isQualifiedName(parent2) && parent2.right === id2) return false;
25153
+ if (ts23.isVariableDeclaration(parent2) && parent2.name === id2) return false;
25154
+ if (ts23.isFunctionDeclaration(parent2) && parent2.name === id2) return false;
25155
+ if (ts23.isFunctionExpression(parent2) && parent2.name === id2) return false;
25156
+ if (ts23.isClassDeclaration(parent2) && parent2.name === id2) return false;
25157
+ if (ts23.isClassExpression(parent2) && parent2.name === id2) return false;
25158
+ if (ts23.isParameter(parent2) && parent2.name === id2) return false;
25159
+ if (ts23.isBindingElement(parent2) && (parent2.name === id2 || parent2.propertyName === id2)) return false;
25160
+ if (ts23.isLabeledStatement(parent2) && parent2.label === id2) return false;
25161
+ if (ts23.isBreakOrContinueStatement(parent2) && parent2.label === id2) return false;
25162
+ if (ts23.isImportSpecifier(parent2) && (parent2.name === id2 || parent2.propertyName === id2)) return false;
25163
+ if (ts23.isExportSpecifier(parent2) && (parent2.name === id2 || parent2.propertyName === id2)) return false;
25164
+ if (ts23.isImportClause(parent2) && parent2.name === id2) return false;
25165
+ if (ts23.isNamespaceImport(parent2) && parent2.name === id2) return false;
25166
+ if (ts23.isQualifiedName(parent2) && parent2.right === id2) return false;
24738
25167
  return true;
24739
25168
  }
24740
25169
  function detectStrippedReferences(bundleSource, stripped) {
24741
25170
  if (stripped.length === 0) return [];
24742
25171
  let sf;
24743
25172
  try {
24744
- sf = ts22.createSourceFile(
25173
+ sf = ts23.createSourceFile(
24745
25174
  "bundle.js",
24746
25175
  bundleSource,
24747
- ts22.ScriptTarget.Latest,
25176
+ ts23.ScriptTarget.Latest,
24748
25177
  /*setParents*/
24749
25178
  true,
24750
- ts22.ScriptKind.JS
25179
+ ts23.ScriptKind.JS
24751
25180
  );
24752
25181
  } catch {
24753
25182
  return [];
24754
25183
  }
24755
25184
  const firstReference = /* @__PURE__ */ new Map();
24756
25185
  function visit3(node) {
24757
- if (ts22.isIdentifier(node) && isValueReference(node)) {
25186
+ if (ts23.isIdentifier(node) && isValueReference(node)) {
24758
25187
  if (!firstReference.has(node.text)) firstReference.set(node.text, node);
24759
25188
  }
24760
- ts22.forEachChild(node, visit3);
25189
+ ts23.forEachChild(node, visit3);
24761
25190
  }
24762
25191
  visit3(sf);
24763
25192
  const errors = [];
@@ -24787,18 +25216,18 @@ function detectStrippedReferences(bundleSource, stripped) {
24787
25216
  return errors;
24788
25217
  }
24789
25218
  async function walkAndCollect(content2, searchDirs, modules2, visiting, loggingPath, stripped, stubDeps, nextId) {
24790
- const sourceFile = ts22.createSourceFile(
25219
+ const sourceFile = ts23.createSourceFile(
24791
25220
  "walk.js",
24792
25221
  content2,
24793
- ts22.ScriptTarget.Latest,
25222
+ ts23.ScriptTarget.Latest,
24794
25223
  /*setParents*/
24795
25224
  false,
24796
- ts22.ScriptKind.JS
25225
+ ts23.ScriptKind.JS
24797
25226
  );
24798
25227
  const sites = [];
24799
25228
  for (const stmt of sourceFile.statements) {
24800
- if (!ts22.isImportDeclaration(stmt)) continue;
24801
- if (!ts22.isStringLiteral(stmt.moduleSpecifier)) continue;
25229
+ if (!ts23.isImportDeclaration(stmt)) continue;
25230
+ if (!ts23.isStringLiteral(stmt.moduleSpecifier)) continue;
24802
25231
  const spec = stmt.moduleSpecifier.text;
24803
25232
  if (!spec.startsWith("./") && !spec.startsWith("../")) continue;
24804
25233
  const start2 = stmt.getStart(sourceFile);
@@ -25250,7 +25679,7 @@ var init_assets_ignore = __esm({
25250
25679
  });
25251
25680
 
25252
25681
  // src/lib/runtime-treeshake.ts
25253
- import ts23 from "typescript";
25682
+ import ts24 from "typescript";
25254
25683
  import { basename, dirname as dirname3 } from "node:path";
25255
25684
  import { build as esbuildBuild } from "esbuild";
25256
25685
  function isBarefootClientSpecifier(spec) {
@@ -25264,13 +25693,13 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
25264
25693
  if (!code.includes("@barefootjs/client")) return result2;
25265
25694
  let sourceFile;
25266
25695
  try {
25267
- sourceFile = ts23.createSourceFile(
25696
+ sourceFile = ts24.createSourceFile(
25268
25697
  sourceLabel,
25269
25698
  code,
25270
- ts23.ScriptTarget.Latest,
25699
+ ts24.ScriptTarget.Latest,
25271
25700
  /*setParentNodes*/
25272
25701
  false,
25273
- ts23.ScriptKind.JS
25702
+ ts24.ScriptKind.JS
25274
25703
  );
25275
25704
  } catch (err) {
25276
25705
  result2.unsafe = true;
@@ -25278,13 +25707,13 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
25278
25707
  return result2;
25279
25708
  }
25280
25709
  const visit3 = (node) => {
25281
- if (ts23.isImportDeclaration(node)) {
25710
+ if (ts24.isImportDeclaration(node)) {
25282
25711
  const spec = node.moduleSpecifier;
25283
- if (ts23.isStringLiteral(spec) && isBarefootClientSpecifier(spec.text)) {
25712
+ if (ts24.isStringLiteral(spec) && isBarefootClientSpecifier(spec.text)) {
25284
25713
  const clause = node.importClause;
25285
25714
  if (!clause) {
25286
25715
  } else if (clause.isTypeOnly) {
25287
- } else if (clause.namedBindings && ts23.isNamedImports(clause.namedBindings)) {
25716
+ } else if (clause.namedBindings && ts24.isNamedImports(clause.namedBindings)) {
25288
25717
  for (const el of clause.namedBindings.elements) {
25289
25718
  if (el.isTypeOnly) continue;
25290
25719
  const imported = (el.propertyName ?? el.name).text;
@@ -25294,7 +25723,7 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
25294
25723
  result2.unsafe = true;
25295
25724
  result2.reasons.push(`default import of "${spec.text}" in ${sourceLabel}`);
25296
25725
  }
25297
- } else if (clause.namedBindings && ts23.isNamespaceImport(clause.namedBindings)) {
25726
+ } else if (clause.namedBindings && ts24.isNamespaceImport(clause.namedBindings)) {
25298
25727
  result2.unsafe = true;
25299
25728
  result2.reasons.push(`namespace import (* as ${clause.namedBindings.name.text}) of "${spec.text}" in ${sourceLabel}`);
25300
25729
  } else if (clause.name) {
@@ -25302,14 +25731,14 @@ function collectUsedRuntimeExports(code, sourceLabel = "<input>") {
25302
25731
  result2.reasons.push(`default import of "${spec.text}" in ${sourceLabel}`);
25303
25732
  }
25304
25733
  }
25305
- } else if (ts23.isCallExpression(node) && node.expression.kind === ts23.SyntaxKind.ImportKeyword) {
25734
+ } else if (ts24.isCallExpression(node) && node.expression.kind === ts24.SyntaxKind.ImportKeyword) {
25306
25735
  const arg = node.arguments[0];
25307
- if (arg && ts23.isStringLiteral(arg) && isBarefootClientSpecifier(arg.text)) {
25736
+ if (arg && ts24.isStringLiteral(arg) && isBarefootClientSpecifier(arg.text)) {
25308
25737
  result2.unsafe = true;
25309
25738
  result2.reasons.push(`dynamic import("${arg.text}") in ${sourceLabel}`);
25310
25739
  }
25311
25740
  }
25312
- ts23.forEachChild(node, visit3);
25741
+ ts24.forEachChild(node, visit3);
25313
25742
  };
25314
25743
  visit3(sourceFile);
25315
25744
  return result2;
@@ -25381,7 +25810,7 @@ var init_runtime_treeshake = __esm({
25381
25810
  });
25382
25811
 
25383
25812
  // src/lib/build.ts
25384
- import ts24 from "typescript";
25813
+ import ts25 from "typescript";
25385
25814
  import { mkdir, readdir, stat, unlink } from "node:fs/promises";
25386
25815
  import { resolve as resolve6, basename as basename2, relative as relative2, dirname as dirname4, isAbsolute as isAbsolute2 } from "node:path";
25387
25816
  import { fileURLToPath as fileURLToPath2 } from "node:url";
@@ -26029,7 +26458,7 @@ async function build(config, options2 = {}) {
26029
26458
  };
26030
26459
  }
26031
26460
  function extractBareImports(code) {
26032
- const { importedFiles } = ts24.preProcessFile(code, true, true);
26461
+ const { importedFiles } = ts25.preProcessFile(code, true, true);
26033
26462
  const specifiers = /* @__PURE__ */ new Set();
26034
26463
  for (const { fileName } of importedFiles) {
26035
26464
  if (!fileName.startsWith(".") && !fileName.startsWith("/") && !fileName.includes("://")) {
@@ -26096,16 +26525,16 @@ function effectiveOutName(tplPath, entryBaseNoExt) {
26096
26525
  }
26097
26526
  function topLevelImportLines(content2) {
26098
26527
  const lines = /* @__PURE__ */ new Set();
26099
- const sourceFile = ts24.createSourceFile(
26528
+ const sourceFile = ts25.createSourceFile(
26100
26529
  "merge.js",
26101
26530
  content2,
26102
- ts24.ScriptTarget.Latest,
26531
+ ts25.ScriptTarget.Latest,
26103
26532
  /*setParentNodes*/
26104
26533
  true,
26105
- ts24.ScriptKind.JS
26534
+ ts25.ScriptKind.JS
26106
26535
  );
26107
26536
  for (const stmt of sourceFile.statements) {
26108
- if (ts24.isImportDeclaration(stmt)) {
26537
+ if (ts25.isImportDeclaration(stmt)) {
26109
26538
  const { line } = sourceFile.getLineAndCharacterOfPosition(stmt.getStart(sourceFile));
26110
26539
  lines.add(line);
26111
26540
  }
@@ -26114,29 +26543,29 @@ function topLevelImportLines(content2) {
26114
26543
  }
26115
26544
  function rewriteBarefootClientSpecifiers(content2, rel) {
26116
26545
  if (!content2.includes("@barefootjs/client")) return content2;
26117
- const sourceFile = ts24.createSourceFile(
26546
+ const sourceFile = ts25.createSourceFile(
26118
26547
  "client.js",
26119
26548
  content2,
26120
- ts24.ScriptTarget.Latest,
26549
+ ts25.ScriptTarget.Latest,
26121
26550
  /*setParentNodes*/
26122
26551
  true,
26123
- ts24.ScriptKind.JS
26552
+ ts25.ScriptKind.JS
26124
26553
  );
26125
26554
  const isBarefootClient = (s) => s === "@barefootjs/client" || s.startsWith("@barefootjs/client/");
26126
26555
  const spans = [];
26127
26556
  const visit3 = (node) => {
26128
- if (ts24.isImportDeclaration(node) || ts24.isExportDeclaration(node)) {
26557
+ if (ts25.isImportDeclaration(node) || ts25.isExportDeclaration(node)) {
26129
26558
  const ms = node.moduleSpecifier;
26130
- if (ms && ts24.isStringLiteral(ms) && isBarefootClient(ms.text)) {
26559
+ if (ms && ts25.isStringLiteral(ms) && isBarefootClient(ms.text)) {
26131
26560
  spans.push([ms.getStart(sourceFile), ms.getEnd()]);
26132
26561
  }
26133
- } else if (ts24.isCallExpression(node) && node.expression.kind === ts24.SyntaxKind.ImportKeyword) {
26562
+ } else if (ts25.isCallExpression(node) && node.expression.kind === ts25.SyntaxKind.ImportKeyword) {
26134
26563
  const arg = node.arguments[0];
26135
- if (arg && ts24.isStringLiteral(arg) && isBarefootClient(arg.text)) {
26564
+ if (arg && ts25.isStringLiteral(arg) && isBarefootClient(arg.text)) {
26136
26565
  spans.push([arg.getStart(sourceFile), arg.getEnd()]);
26137
26566
  }
26138
26567
  }
26139
- ts24.forEachChild(node, visit3);
26568
+ ts25.forEachChild(node, visit3);
26140
26569
  };
26141
26570
  visit3(sourceFile);
26142
26571
  if (spans.length === 0) return content2;
@@ -28341,7 +28770,7 @@ var bfGoSource, evalGoSource, streamingGoSource, bfdevGoSource;
28341
28770
  var init_runtimes_generated = __esm({
28342
28771
  "src/lib/adapters/runtimes.generated.ts"() {
28343
28772
  "use strict";
28344
- 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 "sort"\n "strconv"\n "strings"\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\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 // 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\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// 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// 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// 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 \u2014 for generated `NewXxxProps` code lowering a conditional\n// inline-object spread condition on an `interface{}` prop (whose runtime\n// value may be a string, number, bool, \u2026). Keeps the spread bag\'s\n// inclusion test faithful to JS rather than string-biased (#1752).\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// 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';
28773
+ 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 "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\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 // 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// 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// 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// 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 \u2014 for generated `NewXxxProps` code lowering a conditional\n// inline-object spread condition on an `interface{}` prop (whose runtime\n// value may be a string, number, bool, \u2026). Keeps the spread bag\'s\n// inclusion test faithful to JS rather than string-biased (#1752).\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';
28345
28774
  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';
28346
28775
  streamingGoSource = `// Package bf \u2014 Out-of-Order Streaming SSR helpers
28347
28776
  //
@@ -85412,9 +85841,9 @@ var init_WebSocketReadyStateEnum = __esm({
85412
85841
  }
85413
85842
  });
85414
85843
 
85415
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/constants.js
85844
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/constants.js
85416
85845
  var require_constants = __commonJS({
85417
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/constants.js"(exports, module) {
85846
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/constants.js"(exports, module) {
85418
85847
  "use strict";
85419
85848
  var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
85420
85849
  var hasBlob = typeof Blob !== "undefined";
@@ -85435,9 +85864,9 @@ var require_constants = __commonJS({
85435
85864
  }
85436
85865
  });
85437
85866
 
85438
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/buffer-util.js
85867
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/buffer-util.js
85439
85868
  var require_buffer_util = __commonJS({
85440
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/buffer-util.js"(exports, module) {
85869
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/buffer-util.js"(exports, module) {
85441
85870
  "use strict";
85442
85871
  var { EMPTY_BUFFER } = require_constants();
85443
85872
  var FastBuffer = Buffer[Symbol.species];
@@ -85510,9 +85939,9 @@ var require_buffer_util = __commonJS({
85510
85939
  }
85511
85940
  });
85512
85941
 
85513
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/limiter.js
85942
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/limiter.js
85514
85943
  var require_limiter = __commonJS({
85515
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/limiter.js"(exports, module) {
85944
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/limiter.js"(exports, module) {
85516
85945
  "use strict";
85517
85946
  var kDone = Symbol("kDone");
85518
85947
  var kRun = Symbol("kRun");
@@ -85560,9 +85989,9 @@ var require_limiter = __commonJS({
85560
85989
  }
85561
85990
  });
85562
85991
 
85563
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/permessage-deflate.js
85992
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/permessage-deflate.js
85564
85993
  var require_permessage_deflate = __commonJS({
85565
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/permessage-deflate.js"(exports, module) {
85994
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/permessage-deflate.js"(exports, module) {
85566
85995
  "use strict";
85567
85996
  var zlib = __require("node:zlib");
85568
85997
  var bufferUtil = require_buffer_util();
@@ -85943,9 +86372,9 @@ var require_permessage_deflate = __commonJS({
85943
86372
  }
85944
86373
  });
85945
86374
 
85946
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/validation.js
86375
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/validation.js
85947
86376
  var require_validation = __commonJS({
85948
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/validation.js"(exports, module) {
86377
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/validation.js"(exports, module) {
85949
86378
  "use strict";
85950
86379
  var { isUtf8 } = __require("node:buffer");
85951
86380
  var { hasBlob } = require_constants();
@@ -86144,9 +86573,9 @@ var require_validation = __commonJS({
86144
86573
  }
86145
86574
  });
86146
86575
 
86147
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/receiver.js
86576
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/receiver.js
86148
86577
  var require_receiver = __commonJS({
86149
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/receiver.js"(exports, module) {
86578
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/receiver.js"(exports, module) {
86150
86579
  "use strict";
86151
86580
  var { Writable } = __require("node:stream");
86152
86581
  var PerMessageDeflate2 = require_permessage_deflate();
@@ -86209,6 +86638,7 @@ var require_receiver = __commonJS({
86209
86638
  this._opcode = 0;
86210
86639
  this._totalPayloadLength = 0;
86211
86640
  this._messageLength = 0;
86641
+ this._numFragments = 0;
86212
86642
  this._fragments = [];
86213
86643
  this._errored = false;
86214
86644
  this._loop = false;
@@ -86559,23 +86989,23 @@ var require_receiver = __commonJS({
86559
86989
  this.controlMessage(data2, cb);
86560
86990
  return;
86561
86991
  }
86992
+ if (this._maxFragments > 0 && ++this._numFragments > this._maxFragments) {
86993
+ const error2 = this.createError(
86994
+ RangeError,
86995
+ "Too many message fragments",
86996
+ false,
86997
+ 1008,
86998
+ "WS_ERR_TOO_MANY_BUFFERED_PARTS"
86999
+ );
87000
+ cb(error2);
87001
+ return;
87002
+ }
86562
87003
  if (this._compressed) {
86563
87004
  this._state = INFLATING;
86564
87005
  this.decompress(data2, cb);
86565
87006
  return;
86566
87007
  }
86567
87008
  if (data2.length) {
86568
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
86569
- const error2 = this.createError(
86570
- RangeError,
86571
- "Too many message fragments",
86572
- false,
86573
- 1008,
86574
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
86575
- );
86576
- cb(error2);
86577
- return;
86578
- }
86579
87009
  this._messageLength = this._totalPayloadLength;
86580
87010
  this._fragments.push(data2);
86581
87011
  }
@@ -86605,17 +87035,6 @@ var require_receiver = __commonJS({
86605
87035
  cb(error2);
86606
87036
  return;
86607
87037
  }
86608
- if (this._maxFragments > 0 && this._fragments.length >= this._maxFragments) {
86609
- const error2 = this.createError(
86610
- RangeError,
86611
- "Too many message fragments",
86612
- false,
86613
- 1008,
86614
- "WS_ERR_TOO_MANY_BUFFERED_PARTS"
86615
- );
86616
- cb(error2);
86617
- return;
86618
- }
86619
87038
  this._fragments.push(buf);
86620
87039
  }
86621
87040
  this.dataMessage(cb);
@@ -86638,6 +87057,7 @@ var require_receiver = __commonJS({
86638
87057
  this._totalPayloadLength = 0;
86639
87058
  this._messageLength = 0;
86640
87059
  this._fragmented = 0;
87060
+ this._numFragments = 0;
86641
87061
  this._fragments = [];
86642
87062
  if (this._opcode === 2) {
86643
87063
  let data2;
@@ -86776,9 +87196,9 @@ var require_receiver = __commonJS({
86776
87196
  }
86777
87197
  });
86778
87198
 
86779
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/sender.js
87199
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/sender.js
86780
87200
  var require_sender = __commonJS({
86781
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/sender.js"(exports, module) {
87201
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/sender.js"(exports, module) {
86782
87202
  "use strict";
86783
87203
  var { Duplex } = __require("node:stream");
86784
87204
  var { randomFillSync } = __require("node:crypto");
@@ -87269,9 +87689,9 @@ var require_sender = __commonJS({
87269
87689
  }
87270
87690
  });
87271
87691
 
87272
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/event-target.js
87692
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/event-target.js
87273
87693
  var require_event_target = __commonJS({
87274
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/event-target.js"(exports, module) {
87694
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/event-target.js"(exports, module) {
87275
87695
  "use strict";
87276
87696
  var { kForOnEventAttribute, kListener } = require_constants();
87277
87697
  var kCode = Symbol("kCode");
@@ -87498,9 +87918,9 @@ var require_event_target = __commonJS({
87498
87918
  }
87499
87919
  });
87500
87920
 
87501
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/extension.js
87921
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/extension.js
87502
87922
  var require_extension = __commonJS({
87503
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/extension.js"(exports, module) {
87923
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/extension.js"(exports, module) {
87504
87924
  "use strict";
87505
87925
  var { tokenChars } = require_validation();
87506
87926
  function push(dest, name2, elem) {
@@ -87651,9 +88071,9 @@ var require_extension = __commonJS({
87651
88071
  }
87652
88072
  });
87653
88073
 
87654
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/websocket.js
88074
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/websocket.js
87655
88075
  var require_websocket = __commonJS({
87656
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/websocket.js"(exports, module) {
88076
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/websocket.js"(exports, module) {
87657
88077
  "use strict";
87658
88078
  var EventEmitter = __require("node:events");
87659
88079
  var https = __require("node:https");
@@ -88138,8 +88558,8 @@ var require_websocket = __commonJS({
88138
88558
  autoPong: true,
88139
88559
  closeTimeout: CLOSE_TIMEOUT,
88140
88560
  protocolVersion: protocolVersions[1],
88141
- maxBufferedChunks: 1024 * 1024,
88142
- maxFragments: 128 * 1024,
88561
+ maxBufferedChunks: 256 * 1024,
88562
+ maxFragments: 16 * 1024,
88143
88563
  maxPayload: 100 * 1024 * 1024,
88144
88564
  skipUTF8Validation: false,
88145
88565
  perMessageDeflate: true,
@@ -88547,9 +88967,9 @@ var require_websocket = __commonJS({
88547
88967
  }
88548
88968
  });
88549
88969
 
88550
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/stream.js
88970
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/stream.js
88551
88971
  var require_stream = __commonJS({
88552
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/stream.js"(exports, module) {
88972
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/stream.js"(exports, module) {
88553
88973
  "use strict";
88554
88974
  var WebSocket3 = require_websocket();
88555
88975
  var { Duplex } = __require("node:stream");
@@ -88645,9 +89065,9 @@ var require_stream = __commonJS({
88645
89065
  }
88646
89066
  });
88647
89067
 
88648
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/subprotocol.js
89068
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/subprotocol.js
88649
89069
  var require_subprotocol = __commonJS({
88650
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/subprotocol.js"(exports, module) {
89070
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/subprotocol.js"(exports, module) {
88651
89071
  "use strict";
88652
89072
  var { tokenChars } = require_validation();
88653
89073
  function parse(header) {
@@ -88690,9 +89110,9 @@ var require_subprotocol = __commonJS({
88690
89110
  }
88691
89111
  });
88692
89112
 
88693
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/websocket-server.js
89113
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/websocket-server.js
88694
89114
  var require_websocket_server = __commonJS({
88695
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
89115
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/lib/websocket-server.js"(exports, module) {
88696
89116
  "use strict";
88697
89117
  var EventEmitter = __require("node:events");
88698
89118
  var http = __require("node:http");
@@ -88726,9 +89146,9 @@ var require_websocket_server = __commonJS({
88726
89146
  * called
88727
89147
  * @param {Function} [options.handleProtocols] A hook to handle protocols
88728
89148
  * @param {String} [options.host] The hostname where to bind the server
88729
- * @param {Number} [options.maxBufferedChunks=1048576] The maximum number of
89149
+ * @param {Number} [options.maxBufferedChunks=262144] The maximum number of
88730
89150
  * buffered data chunks
88731
- * @param {Number} [options.maxFragments=131072] The maximum number of message
89151
+ * @param {Number} [options.maxFragments=16384] The maximum number of message
88732
89152
  * fragments
88733
89153
  * @param {Number} [options.maxPayload=104857600] The maximum allowed message
88734
89154
  * size
@@ -88751,8 +89171,8 @@ var require_websocket_server = __commonJS({
88751
89171
  options2 = {
88752
89172
  allowSynchronousEvents: true,
88753
89173
  autoPong: true,
88754
- maxBufferedChunks: 1024 * 1024,
88755
- maxFragments: 128 * 1024,
89174
+ maxBufferedChunks: 256 * 1024,
89175
+ maxFragments: 16 * 1024,
88756
89176
  maxPayload: 100 * 1024 * 1024,
88757
89177
  skipUTF8Validation: false,
88758
89178
  perMessageDeflate: false,
@@ -89091,10 +89511,10 @@ var require_websocket_server = __commonJS({
89091
89511
  }
89092
89512
  });
89093
89513
 
89094
- // ../../node_modules/.bun/ws@8.21.0/node_modules/ws/wrapper.mjs
89514
+ // ../../node_modules/.bun/ws@8.21.1/node_modules/ws/wrapper.mjs
89095
89515
  var import_stream3, import_extension, import_permessage_deflate, import_receiver, import_sender, import_subprotocol, import_websocket, import_websocket_server, wrapper_default;
89096
89516
  var init_wrapper = __esm({
89097
- "../../node_modules/.bun/ws@8.21.0/node_modules/ws/wrapper.mjs"() {
89517
+ "../../node_modules/.bun/ws@8.21.1/node_modules/ws/wrapper.mjs"() {
89098
89518
  import_stream3 = __toESM(require_stream(), 1);
89099
89519
  import_extension = __toESM(require_extension(), 1);
89100
89520
  import_permessage_deflate = __toESM(require_permessage_deflate(), 1);
@@ -107117,7 +107537,7 @@ __export(scenario_driver_exports, {
107117
107537
  import { writeFileSync as writeFileSync9, mkdtempSync, rmSync, readFileSync as readFileSync19, existsSync as existsSync23, statSync as statSync3 } from "node:fs";
107118
107538
  import { join as join2, dirname as dirname7, resolve as resolve11 } from "node:path";
107119
107539
  import { tmpdir } from "node:os";
107120
- import ts25 from "typescript";
107540
+ import ts26 from "typescript";
107121
107541
  function externalRuntimeImport(clientJs) {
107122
107542
  const chunks = Array.isArray(clientJs) ? clientJs : [clientJs];
107123
107543
  for (const chunk of chunks) {
@@ -107187,11 +107607,11 @@ function resolveLocalFile(spec) {
107187
107607
  }
107188
107608
  function rewriteLocalImports(js, chunkPath, inlined) {
107189
107609
  const chunkDir = dirname7(chunkPath);
107190
- const sf = ts25.createSourceFile("chunk.mjs", js, ts25.ScriptTarget.Latest, false, ts25.ScriptKind.JS);
107610
+ const sf = ts26.createSourceFile("chunk.mjs", js, ts26.ScriptTarget.Latest, false, ts26.ScriptKind.JS);
107191
107611
  const edits = [];
107192
107612
  for (const stmt of sf.statements) {
107193
- if (!ts25.isImportDeclaration(stmt)) continue;
107194
- if (!ts25.isStringLiteral(stmt.moduleSpecifier)) continue;
107613
+ if (!ts26.isImportDeclaration(stmt)) continue;
107614
+ if (!ts26.isStringLiteral(stmt.moduleSpecifier)) continue;
107195
107615
  const spec = stmt.moduleSpecifier.text;
107196
107616
  if (!spec.startsWith(".")) continue;
107197
107617
  const resolved = resolveLocalFile(join2(chunkDir, spec.replace(/\.client\.js$/, "")));
@@ -107203,13 +107623,13 @@ function rewriteLocalImports(js, chunkPath, inlined) {
107203
107623
  const abs = resolve11(resolved);
107204
107624
  if (inlined.has(abs)) {
107205
107625
  const clause = stmt.importClause;
107206
- if (clause && (clause.name || clause.namedBindings && ts25.isNamespaceImport(clause.namedBindings))) {
107626
+ if (clause && (clause.name || clause.namedBindings && ts26.isNamespaceImport(clause.namedBindings))) {
107207
107627
  throw new Error(
107208
107628
  `"${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.`
107209
107629
  );
107210
107630
  }
107211
107631
  const shims = [];
107212
- if (clause?.namedBindings && ts25.isNamedImports(clause.namedBindings)) {
107632
+ if (clause?.namedBindings && ts26.isNamedImports(clause.namedBindings)) {
107213
107633
  for (const el of clause.namedBindings.elements) {
107214
107634
  if (el.propertyName) shims.push(`var ${el.name.text} = ${el.propertyName.text}`);
107215
107635
  }
@@ -107718,9 +108138,9 @@ function findProjectConfig(startDir) {
107718
108138
  let dir = path.resolve(startDir);
107719
108139
  const { root: fsRoot } = path.parse(dir);
107720
108140
  while (true) {
107721
- const ts26 = path.join(dir, "barefoot.config.ts");
107722
- if (existsSync2(ts26)) {
107723
- return { dir, tsConfigPath: ts26 };
108141
+ const ts27 = path.join(dir, "barefoot.config.ts");
108142
+ if (existsSync2(ts27)) {
108143
+ return { dir, tsConfigPath: ts27 };
107724
108144
  }
107725
108145
  if (dir === fsRoot) return null;
107726
108146
  dir = path.dirname(dir);