@barefootjs/jsx 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/dist/compiler.d.ts.map +1 -1
  2. package/dist/debug-profile.d.ts +115 -0
  3. package/dist/debug-profile.d.ts.map +1 -0
  4. package/dist/debug.d.ts +4 -3
  5. package/dist/debug.d.ts.map +1 -1
  6. package/dist/expression-parser.d.ts +31 -0
  7. package/dist/expression-parser.d.ts.map +1 -1
  8. package/dist/index.d.ts +8 -1
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1905 -207
  11. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts +6 -0
  12. package/dist/ir-to-client-js/control-flow/plan/branch-loop.d.ts.map +1 -1
  13. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts +1 -1
  14. package/dist/ir-to-client-js/control-flow/plan/build-branch-loop.d.ts.map +1 -1
  15. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts +1 -1
  16. package/dist/ir-to-client-js/control-flow/plan/build-component-loop.d.ts.map +1 -1
  17. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts +2 -2
  18. package/dist/ir-to-client-js/control-flow/plan/build-composite-loop.d.ts.map +1 -1
  19. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts +3 -3
  20. package/dist/ir-to-client-js/control-flow/plan/build-event-delegation.d.ts.map +1 -1
  21. package/dist/ir-to-client-js/control-flow/plan/build-inner-loop.d.ts.map +1 -1
  22. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts +2 -0
  23. package/dist/ir-to-client-js/control-flow/plan/build-insert.d.ts.map +1 -1
  24. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts +2 -0
  25. package/dist/ir-to-client-js/control-flow/plan/build-loop-child-arm.d.ts.map +1 -1
  26. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts +4 -2
  27. package/dist/ir-to-client-js/control-flow/plan/build-loop.d.ts.map +1 -1
  28. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts +3 -1
  29. package/dist/ir-to-client-js/control-flow/plan/build-reactive-effects.d.ts.map +1 -1
  30. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts +7 -0
  31. package/dist/ir-to-client-js/control-flow/plan/event-delegation.d.ts.map +1 -1
  32. package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts +7 -0
  33. package/dist/ir-to-client-js/control-flow/plan/inner-loop.d.ts.map +1 -1
  34. package/dist/ir-to-client-js/control-flow/plan/insert.d.ts +8 -0
  35. package/dist/ir-to-client-js/control-flow/plan/insert.d.ts.map +1 -1
  36. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts +8 -0
  37. package/dist/ir-to-client-js/control-flow/plan/loop-child-arm.d.ts.map +1 -1
  38. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts +28 -0
  39. package/dist/ir-to-client-js/control-flow/plan/loop.d.ts.map +1 -1
  40. package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts +7 -0
  41. package/dist/ir-to-client-js/control-flow/plan/reactive-effects.d.ts.map +1 -1
  42. package/dist/ir-to-client-js/control-flow/stringify/component-loop.d.ts.map +1 -1
  43. package/dist/ir-to-client-js/control-flow/stringify/composite-loop.d.ts.map +1 -1
  44. package/dist/ir-to-client-js/control-flow/stringify/event-delegation.d.ts.map +1 -1
  45. package/dist/ir-to-client-js/control-flow/stringify/event-listener.d.ts +1 -1
  46. package/dist/ir-to-client-js/control-flow/stringify/event-listener.d.ts.map +1 -1
  47. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts +1 -1
  48. package/dist/ir-to-client-js/control-flow/stringify/inner-loop.d.ts.map +1 -1
  49. package/dist/ir-to-client-js/control-flow/stringify/insert.d.ts.map +1 -1
  50. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts +2 -2
  51. package/dist/ir-to-client-js/control-flow/stringify/loop-child-arm.d.ts.map +1 -1
  52. package/dist/ir-to-client-js/control-flow/stringify/loop.d.ts.map +1 -1
  53. package/dist/ir-to-client-js/control-flow/stringify/reactive-effects.d.ts.map +1 -1
  54. package/dist/ir-to-client-js/control-flow.d.ts.map +1 -1
  55. package/dist/ir-to-client-js/emit-reactive.d.ts.map +1 -1
  56. package/dist/ir-to-client-js/imports.d.ts +2 -2
  57. package/dist/ir-to-client-js/imports.d.ts.map +1 -1
  58. package/dist/ir-to-client-js/index.d.ts +2 -2
  59. package/dist/ir-to-client-js/index.d.ts.map +1 -1
  60. package/dist/ir-to-client-js/phases/effects-and-on-mounts.d.ts.map +1 -1
  61. package/dist/ir-to-client-js/phases/event-handlers.d.ts.map +1 -1
  62. package/dist/ir-to-client-js/plan/build-declaration-emit.d.ts.map +1 -1
  63. package/dist/ir-to-client-js/plan/declaration-emit.d.ts +6 -0
  64. package/dist/ir-to-client-js/plan/declaration-emit.d.ts.map +1 -1
  65. package/dist/ir-to-client-js/types.d.ts +5 -0
  66. package/dist/ir-to-client-js/types.d.ts.map +1 -1
  67. package/dist/ir-to-client-js/utils.d.ts +29 -0
  68. package/dist/ir-to-client-js/utils.d.ts.map +1 -1
  69. package/dist/loop-destructure.d.ts +26 -0
  70. package/dist/loop-destructure.d.ts.map +1 -0
  71. package/dist/profiler.d.ts +492 -0
  72. package/dist/profiler.d.ts.map +1 -0
  73. package/dist/ssr-defaults.d.ts.map +1 -1
  74. package/dist/types.d.ts +8 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/package.json +2 -2
  77. package/src/__tests__/debug-profile.test.ts +405 -0
  78. package/src/__tests__/expression-parser.test.ts +44 -1
  79. package/src/__tests__/profile-bfid-emission.test.ts +63 -0
  80. package/src/__tests__/profile-binding-ids.test.ts +123 -0
  81. package/src/__tests__/profile-cond-binding-ids.test.ts +80 -0
  82. package/src/__tests__/profile-loop-binding-ids.test.ts +106 -0
  83. package/src/__tests__/profile-nested-binding-ids.test.ts +153 -0
  84. package/src/__tests__/profile-turn-markers-branch.test.ts +83 -0
  85. package/src/__tests__/profile-turn-markers-delegation.test.ts +63 -0
  86. package/src/__tests__/profile-turn-markers.test.ts +54 -0
  87. package/src/__tests__/profiler-batch-advisor.test.ts +198 -0
  88. package/src/__tests__/profiler-coverage-conformance.test.ts +360 -0
  89. package/src/__tests__/profiler-e2e.test.ts +104 -0
  90. package/src/__tests__/profiler-hot-subscribers.test.ts +263 -0
  91. package/src/__tests__/profiler-wasted-re-runs.test.ts +147 -0
  92. package/src/__tests__/profiler.test.ts +408 -0
  93. package/src/__tests__/ssr-defaults.test.ts +24 -0
  94. package/src/compiler.ts +3 -0
  95. package/src/debug-profile.ts +543 -0
  96. package/src/debug.ts +192 -28
  97. package/src/expression-parser.ts +53 -0
  98. package/src/index.ts +72 -1
  99. package/src/ir-to-client-js/control-flow/plan/branch-loop.ts +6 -0
  100. package/src/ir-to-client-js/control-flow/plan/build-branch-loop.ts +5 -3
  101. package/src/ir-to-client-js/control-flow/plan/build-component-loop.ts +3 -1
  102. package/src/ir-to-client-js/control-flow/plan/build-composite-loop.ts +8 -2
  103. package/src/ir-to-client-js/control-flow/plan/build-event-delegation.ts +19 -3
  104. package/src/ir-to-client-js/control-flow/plan/build-inner-loop.ts +2 -0
  105. package/src/ir-to-client-js/control-flow/plan/build-insert.ts +9 -2
  106. package/src/ir-to-client-js/control-flow/plan/build-loop-child-arm.ts +9 -1
  107. package/src/ir-to-client-js/control-flow/plan/build-loop.ts +12 -8
  108. package/src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts +10 -4
  109. package/src/ir-to-client-js/control-flow/plan/event-delegation.ts +7 -0
  110. package/src/ir-to-client-js/control-flow/plan/inner-loop.ts +7 -0
  111. package/src/ir-to-client-js/control-flow/plan/insert.ts +8 -0
  112. package/src/ir-to-client-js/control-flow/plan/loop-child-arm.ts +8 -0
  113. package/src/ir-to-client-js/control-flow/plan/loop.ts +28 -0
  114. package/src/ir-to-client-js/control-flow/plan/reactive-effects.ts +7 -0
  115. package/src/ir-to-client-js/control-flow/stringify/branch-loop.ts +5 -3
  116. package/src/ir-to-client-js/control-flow/stringify/component-loop.ts +4 -2
  117. package/src/ir-to-client-js/control-flow/stringify/composite-loop.ts +6 -3
  118. package/src/ir-to-client-js/control-flow/stringify/event-delegation.ts +14 -2
  119. package/src/ir-to-client-js/control-flow/stringify/event-listener.ts +5 -2
  120. package/src/ir-to-client-js/control-flow/stringify/inner-loop.ts +13 -11
  121. package/src/ir-to-client-js/control-flow/stringify/insert.ts +19 -7
  122. package/src/ir-to-client-js/control-flow/stringify/loop-child-arm.ts +18 -13
  123. package/src/ir-to-client-js/control-flow/stringify/loop.ts +9 -7
  124. package/src/ir-to-client-js/control-flow/stringify/reactive-effects.ts +18 -14
  125. package/src/ir-to-client-js/control-flow.ts +12 -6
  126. package/src/ir-to-client-js/emit-reactive.ts +18 -5
  127. package/src/ir-to-client-js/imports.ts +2 -0
  128. package/src/ir-to-client-js/index.ts +6 -1
  129. package/src/ir-to-client-js/phases/effects-and-on-mounts.ts +10 -4
  130. package/src/ir-to-client-js/phases/event-handlers.ts +6 -2
  131. package/src/ir-to-client-js/plan/build-declaration-emit.ts +7 -1
  132. package/src/ir-to-client-js/plan/declaration-emit.ts +6 -0
  133. package/src/ir-to-client-js/stringify/declaration-emit.ts +12 -6
  134. package/src/ir-to-client-js/types.ts +5 -0
  135. package/src/ir-to-client-js/utils.ts +37 -0
  136. package/src/jsx-to-ir.ts +2 -2
  137. package/src/loop-destructure.ts +170 -0
  138. package/src/profiler.ts +1488 -0
  139. package/src/ssr-defaults.ts +65 -0
  140. package/src/types.ts +8 -0
package/dist/index.js CHANGED
@@ -232,6 +232,11 @@ function keyAttrName(loopDepth) {
232
232
  function varSlotId(slotId) {
233
233
  return slotId.startsWith("^") ? slotId.slice(1) : slotId;
234
234
  }
235
+ function profileBindingId(componentName, slotId) {
236
+ if (!componentName || slotId === "?")
237
+ return "";
238
+ return `, ${JSON.stringify(`${componentName}#binding:${slotId}`)}`;
239
+ }
235
240
  function templatePartsToJsExpr(parts, opts) {
236
241
  let result = "`";
237
242
  for (const part of parts) {
@@ -312,6 +317,10 @@ function wrapHandlerInBlock(handler) {
312
317
  }
313
318
  return trimmed;
314
319
  }
320
+ function wrapHandlerForTurn(handler, handlerId, loc) {
321
+ const idArg = loc ? `${JSON.stringify(handlerId)}, ${JSON.stringify(loc)}` : JSON.stringify(handlerId);
322
+ return `(...__bfa) => { beginTurn(${idArg}); try { return (${handler.trim()})(...__bfa) } finally { endTurn() } }`;
323
+ }
315
324
  function emitRefCall(callback, elementVar) {
316
325
  const trimmed = callback.trim();
317
326
  const isBareIdent = /^[a-zA-Z_$][\w$]*$/.test(trimmed);
@@ -5373,6 +5382,40 @@ function extractArrowBodyExpression(source) {
5373
5382
  return null;
5374
5383
  return expr.body.getText(sf).trim();
5375
5384
  }
5385
+ function cssKebabCase(name) {
5386
+ return name.replace(/[A-Z]/g, (m) => "-" + m.toLowerCase()).replace(/^ms-/, "-ms-");
5387
+ }
5388
+ function parseStyleObjectEntries(source) {
5389
+ const sf = ts8.createSourceFile("__style__.ts", `(${source})`, ts8.ScriptTarget.Latest, true);
5390
+ const stmt = sf.statements[0];
5391
+ if (!stmt || !ts8.isExpressionStatement(stmt) || sf.statements.length !== 1)
5392
+ return null;
5393
+ let expr = stmt.expression;
5394
+ while (ts8.isParenthesizedExpression(expr))
5395
+ expr = expr.expression;
5396
+ if (!ts8.isObjectLiteralExpression(expr))
5397
+ return null;
5398
+ const entries = [];
5399
+ for (const prop of expr.properties) {
5400
+ if (!ts8.isPropertyAssignment(prop))
5401
+ return null;
5402
+ let key;
5403
+ if (ts8.isIdentifier(prop.name))
5404
+ key = prop.name.text;
5405
+ else if (ts8.isStringLiteral(prop.name))
5406
+ key = prop.name.text;
5407
+ else
5408
+ return null;
5409
+ const cssKey = cssKebabCase(key);
5410
+ const init = prop.initializer;
5411
+ if (ts8.isStringLiteral(init) || ts8.isNoSubstitutionTemplateLiteral(init)) {
5412
+ entries.push({ cssKey, kind: "literal", value: init.text });
5413
+ } else {
5414
+ entries.push({ cssKey, kind: "expr", expr: init.getText(sf).trim() });
5415
+ }
5416
+ }
5417
+ return entries.length > 0 ? entries : null;
5418
+ }
5376
5419
  function parseExpression(expr) {
5377
5420
  const trimmed = expr.trim();
5378
5421
  if (!trimmed) {
@@ -9221,7 +9264,7 @@ function tryStaticStyleObjectToCss(expr) {
9221
9264
  return null;
9222
9265
  if (!ts11.isStringLiteral(prop.initializer))
9223
9266
  return null;
9224
- const key = prop.name.text.replace(/[A-Z]/g, (m) => `-${m.toLowerCase()}`);
9267
+ const key = cssKebabCase(prop.name.text);
9225
9268
  parts.push(`${key}:${prop.initializer.text}`);
9226
9269
  }
9227
9270
  return parts.join(";");
@@ -11331,7 +11374,9 @@ var RUNTIME_IMPORT_CANDIDATES = [
11331
11374
  "upsertChildItem",
11332
11375
  "__slot",
11333
11376
  "__bfSlot",
11334
- "__bfText"
11377
+ "__bfText",
11378
+ "beginTurn",
11379
+ "endTurn"
11335
11380
  ];
11336
11381
  var RUNTIME_MODULE = "@barefootjs/client/runtime";
11337
11382
  var IMPORT_PLACEHOLDER = "/* __BAREFOOTJS_DOM_IMPORTS__ */";
@@ -12520,7 +12565,8 @@ function buildDeclarationEmitPlan(decl, ctx, lookups) {
12520
12565
  return {
12521
12566
  kind: "memo",
12522
12567
  name: decl.info.name,
12523
- computationExpr: decl.info.computation
12568
+ computationExpr: decl.info.computation,
12569
+ bfId: ctx.profile ? `${ctx.componentName}#memo:${decl.info.name}` : undefined
12524
12570
  };
12525
12571
  case "function": {
12526
12572
  const fn = decl.info;
@@ -12538,13 +12584,18 @@ function buildDeclarationEmitPlan(decl, ctx, lookups) {
12538
12584
  }
12539
12585
  }
12540
12586
  function buildSignalPlan(signal, ctx, lookups) {
12587
+ const controlledEffect = buildControlledSignalEffect(signal, lookups);
12588
+ if (controlledEffect && ctx.profile) {
12589
+ controlledEffect.bfId = `${ctx.componentName}#effect:controlled:${signal.setter}`;
12590
+ }
12541
12591
  return {
12542
12592
  kind: "signal",
12543
12593
  getter: signal.getter,
12544
12594
  setter: signal.setter,
12545
12595
  initialValueExpr: resolveSignalInitialValue(signal, ctx, lookups),
12546
- controlledEffect: buildControlledSignalEffect(signal, lookups),
12547
- branchCondition: signal.branchCondition
12596
+ controlledEffect,
12597
+ branchCondition: signal.branchCondition,
12598
+ bfId: ctx.profile ? `${ctx.componentName}#signal:${signal.getter}` : undefined
12548
12599
  };
12549
12600
  }
12550
12601
  function resolveSignalInitialValue(signal, ctx, lookups) {
@@ -12608,25 +12659,29 @@ function emitConstant(lines, plan) {
12608
12659
  lines.push(` ${plan.keyword} ${plan.name}`);
12609
12660
  }
12610
12661
  }
12662
+ function bfIdArg(bfId) {
12663
+ return bfId ? `, ${JSON.stringify(bfId)}` : "";
12664
+ }
12611
12665
  function emitSignal(lines, plan) {
12666
+ const id = bfIdArg(plan.bfId);
12612
12667
  if (plan.branchCondition) {
12613
12668
  if (plan.setter) {
12614
12669
  lines.push(` let ${plan.getter}, ${plan.setter}`);
12615
12670
  lines.push(` if (${plan.branchCondition}) {`);
12616
- lines.push(` ;[${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr})`);
12671
+ lines.push(` ;[${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id})`);
12617
12672
  lines.push(` }`);
12618
12673
  } else {
12619
12674
  lines.push(` let ${plan.getter}`);
12620
12675
  lines.push(` if (${plan.branchCondition}) {`);
12621
- lines.push(` ;[${plan.getter}] = createSignal(${plan.initialValueExpr})`);
12676
+ lines.push(` ;[${plan.getter}] = createSignal(${plan.initialValueExpr}${id})`);
12622
12677
  lines.push(` }`);
12623
12678
  }
12624
12679
  return;
12625
12680
  }
12626
12681
  if (plan.setter) {
12627
- lines.push(` const [${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr})`);
12682
+ lines.push(` const [${plan.getter}, ${plan.setter}] = createSignal(${plan.initialValueExpr}${id})`);
12628
12683
  } else {
12629
- lines.push(` const [${plan.getter}] = createSignal(${plan.initialValueExpr})`);
12684
+ lines.push(` const [${plan.getter}] = createSignal(${plan.initialValueExpr}${id})`);
12630
12685
  }
12631
12686
  if (plan.controlledEffect) {
12632
12687
  emitControlledEffect(lines, plan.controlledEffect);
@@ -12636,10 +12691,10 @@ function emitControlledEffect(lines, plan) {
12636
12691
  lines.push(` createEffect(() => {`);
12637
12692
  lines.push(` const __val = ${plan.accessorExpr}`);
12638
12693
  lines.push(` if (__val !== undefined) ${plan.setter}(__val)`);
12639
- lines.push(` })`);
12694
+ lines.push(` }${bfIdArg(plan.bfId)})`);
12640
12695
  }
12641
12696
  function emitMemo(lines, plan) {
12642
- lines.push(` const ${plan.name} = createMemo(${plan.computationExpr})`);
12697
+ lines.push(` const ${plan.name} = createMemo(${plan.computationExpr}${bfIdArg(plan.bfId)})`);
12643
12698
  }
12644
12699
  function emitFunction(lines, plan) {
12645
12700
  const asyncKw = plan.isAsync ? "async " : "";
@@ -12851,13 +12906,14 @@ function collectConditionalSlotIds(ctx) {
12851
12906
 
12852
12907
  // src/ir-to-client-js/phases/effects-and-on-mounts.ts
12853
12908
  function emitEffectsAndOnMounts(lines, ctx) {
12854
- for (const effect of ctx.effects) {
12909
+ ctx.effects.forEach((effect, i) => {
12910
+ const idArg = ctx.profile ? `, ${JSON.stringify(`${ctx.componentName}#effect:${effect.captureName ?? effect.loc?.start.line ?? i}`)}` : "";
12855
12911
  if (effect.captureName) {
12856
- lines.push(` const ${effect.captureName} = createEffect(${effect.body})`);
12912
+ lines.push(` const ${effect.captureName} = createEffect(${effect.body}${idArg})`);
12857
12913
  } else {
12858
- lines.push(` createEffect(${effect.body})`);
12914
+ lines.push(` createEffect(${effect.body}${idArg})`);
12859
12915
  }
12860
- }
12916
+ });
12861
12917
  for (const onMount of ctx.onMounts) {
12862
12918
  lines.push(` onMount(${onMount.body})`);
12863
12919
  }
@@ -12870,7 +12926,7 @@ function emitEventHandlers(lines, ctx, conditionalSlotIds) {
12870
12926
  continue;
12871
12927
  for (const event of elem.events) {
12872
12928
  const eventName = toDomEventName(event.name);
12873
- const wrappedHandler = wrapHandlerInBlock(event.handler);
12929
+ const wrappedHandler = ctx.profile ? wrapHandlerForTurn(event.handler, `${ctx.componentName}#handler:${elem.slotId}:${event.name}`) : wrapHandlerInBlock(event.handler);
12874
12930
  if (elem.slotId === "__scope") {
12875
12931
  lines.push(` if (__scope) __scope.addEventListener('${eventName}', ${wrappedHandler})`);
12876
12932
  } else {
@@ -13002,8 +13058,8 @@ function emitRestAttrApplications(lines, ctx) {
13002
13058
  }
13003
13059
 
13004
13060
  // src/ir-to-client-js/control-flow/stringify/event-listener.ts
13005
- function emitListenerLine(lines, indent, elementVar, eventName, handler, mode = "dom") {
13006
- const wrapped = wrapHandlerInBlock(handler);
13061
+ function emitListenerLine(lines, indent, elementVar, eventName, handler, mode = "dom", turnId) {
13062
+ const wrapped = turnId ? wrapHandlerForTurn(handler, turnId) : wrapHandlerInBlock(handler);
13007
13063
  const name = mode === "dom" ? toDomEventName(eventName) : eventName;
13008
13064
  lines.push(`${indent}if (${elementVar}) ${elementVar}.addEventListener('${name}', ${wrapped})`);
13009
13065
  }
@@ -13432,7 +13488,7 @@ function wrapAttrValueExpression(value, wrap) {
13432
13488
  }
13433
13489
  }
13434
13490
  function buildBranchEventBindingsPlan(args) {
13435
- const { events, wrap } = args;
13491
+ const { events, wrap, profileComponentName: pc } = args;
13436
13492
  if (!events || events.length === 0)
13437
13493
  return [];
13438
13494
  const eventsBySlot = new Map;
@@ -13444,7 +13500,8 @@ function buildBranchEventBindingsPlan(args) {
13444
13500
  }
13445
13501
  bucket.push({
13446
13502
  eventName: ev.eventName,
13447
- wrappedHandler: wrap(ev.handler)
13503
+ wrappedHandler: wrap(ev.handler),
13504
+ turnId: pc ? `${pc}#handler:${ev.slotId}:${ev.eventName}` : undefined
13448
13505
  });
13449
13506
  }
13450
13507
  const slots = [];
@@ -13548,6 +13605,7 @@ function buildBranchInnerLoopsPlan(args) {
13548
13605
  plan.push({
13549
13606
  uidSuffix: `br_${i}`,
13550
13607
  markerId: inner.markerId,
13608
+ slotId: csl ?? "?",
13551
13609
  containerExpr,
13552
13610
  arrayExpr: wrapOuter(inner.array),
13553
13611
  keyFn: loopKeyFn(inner),
@@ -13633,7 +13691,7 @@ function buildLoopChildArmPlan(args) {
13633
13691
 
13634
13692
  // src/ir-to-client-js/control-flow/plan/build-reactive-effects.ts
13635
13693
  function buildReactiveEffectsPlan(args) {
13636
- const { attrs, texts, conditionals, loopParam, loopParamBindings } = args;
13694
+ const { attrs, texts, conditionals, loopParam, loopParamBindings, profileComponentName } = args;
13637
13695
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, loopParam, loopParamBindings);
13638
13696
  const attrsBySlot = new Map;
13639
13697
  for (const attr of attrs) {
@@ -13696,22 +13754,24 @@ function buildReactiveEffectsPlan(args) {
13696
13754
  wrappedCondition: wrap(cond.condition),
13697
13755
  whenTrueTemplateHtml: addCondAttrToTemplate(wrap(cond.whenTrueHtml), cond.slotId),
13698
13756
  whenFalseTemplateHtml: addCondAttrToTemplate(wrap(cond.whenFalseHtml), cond.slotId),
13699
- whenTrueArm: buildOuterArm(cond.whenTrue, trueTexts, wrap, loopParam, loopParamBindings),
13700
- whenFalseArm: buildOuterArm(cond.whenFalse, falseTexts, wrap, loopParam, loopParamBindings)
13757
+ whenTrueArm: buildOuterArm(cond.whenTrue, trueTexts, wrap, loopParam, loopParamBindings, profileComponentName),
13758
+ whenFalseArm: buildOuterArm(cond.whenFalse, falseTexts, wrap, loopParam, loopParamBindings, profileComponentName)
13701
13759
  });
13702
13760
  }
13703
13761
  }
13704
13762
  return {
13705
13763
  attrSlots,
13706
13764
  outerTexts,
13707
- conditionals: conditionalPlans
13765
+ conditionals: conditionalPlans,
13766
+ profileComponentName
13708
13767
  };
13709
13768
  }
13710
- function buildOuterArm(branch, texts, wrap, loopParam, loopParamBindings) {
13769
+ function buildOuterArm(branch, texts, wrap, loopParam, loopParamBindings, profileComponentName) {
13711
13770
  return {
13712
13771
  events: buildBranchEventBindingsPlan({
13713
13772
  events: branch.events,
13714
- wrap
13773
+ wrap,
13774
+ profileComponentName
13715
13775
  }),
13716
13776
  childComponents: buildBranchChildComponentInitsPlan({
13717
13777
  components: branch.childComponents,
@@ -13734,13 +13794,14 @@ function buildOuterArm(branch, texts, wrap, loopParam, loopParamBindings) {
13734
13794
  texts
13735
13795
  };
13736
13796
  }
13737
- function buildLoopReactiveEffectsPlan(elem) {
13797
+ function buildLoopReactiveEffectsPlan(elem, profileComponentName) {
13738
13798
  return buildReactiveEffectsPlan({
13739
13799
  attrs: elem.bindings.reactiveAttrs,
13740
13800
  texts: elem.bindings.reactiveTexts,
13741
13801
  conditionals: elem.bindings.conditionals,
13742
13802
  loopParam: elem.param,
13743
- loopParamBindings: elem.paramBindings
13803
+ loopParamBindings: elem.paramBindings,
13804
+ profileComponentName
13744
13805
  });
13745
13806
  }
13746
13807
 
@@ -13802,6 +13863,7 @@ function buildInnerLoopsPlan(args) {
13802
13863
  plan.push({
13803
13864
  uidSuffix,
13804
13865
  markerId: inner.markerId,
13866
+ slotId: inner.containerSlotId ?? "?",
13805
13867
  containerExpr,
13806
13868
  arrayExpr,
13807
13869
  arraySrc: inner.array,
@@ -13896,7 +13958,7 @@ function buildStaticEmit(inner, level) {
13896
13958
  }
13897
13959
 
13898
13960
  // src/ir-to-client-js/control-flow/plan/build-composite-loop.ts
13899
- function buildTopLevelCompositePlan(elem) {
13961
+ function buildTopLevelCompositePlan(elem, profileComponentName) {
13900
13962
  const nestedComps = elem.nestedComponents;
13901
13963
  const depthLevels = buildDepthLevels(elem.innerLoops ?? [], nestedComps, elem.bindings.events);
13902
13964
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
@@ -13929,15 +13991,18 @@ function buildTopLevelCompositePlan(elem) {
13929
13991
  texts: elem.bindings.reactiveTexts,
13930
13992
  conditionals: elem.bindings.conditionals,
13931
13993
  loopParam: elem.param,
13932
- loopParamBindings: elem.paramBindings
13994
+ loopParamBindings: elem.paramBindings,
13995
+ profileComponentName
13933
13996
  }) : null,
13934
13997
  branchClearChildren: false,
13935
13998
  topIndent: " ",
13936
13999
  bodyIndent: " ",
13937
- bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false
14000
+ bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
14001
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
14002
+ profileComponentName
13938
14003
  };
13939
14004
  }
13940
- function buildBranchCompositePlan(loop, cv) {
14005
+ function buildBranchCompositePlan(loop, cv, profileComponentName) {
13941
14006
  const nestedComps = loop.nestedComponents;
13942
14007
  const innerLoops = loop.innerLoops ?? [];
13943
14008
  const childEvents = loop.bindings.events;
@@ -13972,12 +14037,15 @@ function buildBranchCompositePlan(loop, cv) {
13972
14037
  texts: loop.bindings.reactiveTexts,
13973
14038
  conditionals: loop.bindings.conditionals,
13974
14039
  loopParam: loop.param,
13975
- loopParamBindings: loop.paramBindings
14040
+ loopParamBindings: loop.paramBindings,
14041
+ profileComponentName
13976
14042
  }) : null,
13977
14043
  branchClearChildren: true,
13978
14044
  topIndent: " ",
13979
14045
  bodyIndent: " ",
13980
- bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false
14046
+ bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
14047
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${loop.containerSlotId}` : undefined,
14048
+ profileComponentName
13981
14049
  };
13982
14050
  }
13983
14051
  function filterCondCompsOut(outerComps, conditionals) {
@@ -14002,11 +14070,12 @@ function hasReactiveBranch(loop) {
14002
14070
  }
14003
14071
 
14004
14072
  // src/ir-to-client-js/control-flow/plan/build-event-delegation.ts
14005
- function buildDynamicLoopDelegationPlan(elem) {
14073
+ function buildDynamicLoopDelegationPlan(elem, profileComponentName) {
14006
14074
  return {
14007
14075
  kind: "event-delegation",
14008
14076
  containerVar: `_${varSlotId(elem.slotId)}`,
14009
14077
  events: elem.bindings.events,
14078
+ profileComponentName,
14010
14079
  itemLookup: buildKeyedOrIndexLookup({
14011
14080
  array: buildChainedArrayExpr(elem),
14012
14081
  param: elem.param,
@@ -14016,11 +14085,12 @@ function buildDynamicLoopDelegationPlan(elem) {
14016
14085
  })
14017
14086
  };
14018
14087
  }
14019
- function buildBranchLoopDelegationPlan(loop, cv) {
14088
+ function buildBranchLoopDelegationPlan(loop, cv, profileComponentName) {
14020
14089
  return {
14021
14090
  kind: "event-delegation",
14022
14091
  containerVar: `__loop_${cv}`,
14023
14092
  events: loop.bindings.events,
14093
+ profileComponentName,
14024
14094
  itemLookup: buildKeyedOrIndexLookup({
14025
14095
  array: buildChainedArrayExpr(loop),
14026
14096
  param: loop.param,
@@ -14030,11 +14100,12 @@ function buildBranchLoopDelegationPlan(loop, cv) {
14030
14100
  })
14031
14101
  };
14032
14102
  }
14033
- function buildStaticArrayDelegationPlan(elem) {
14103
+ function buildStaticArrayDelegationPlan(elem, profileComponentName) {
14034
14104
  return {
14035
14105
  kind: "event-delegation",
14036
14106
  containerVar: `_${varSlotId(elem.slotId)}`,
14037
14107
  events: elem.bindings.events,
14108
+ profileComponentName,
14038
14109
  itemLookup: {
14039
14110
  kind: "static-index",
14040
14111
  arrayExpr: buildChainedArrayExpr(elem),
@@ -14068,14 +14139,14 @@ function buildKeyedOrIndexLookup(args) {
14068
14139
  }
14069
14140
 
14070
14141
  // src/ir-to-client-js/control-flow/plan/build-branch-loop.ts
14071
- function buildBranchLoopPlan(loop) {
14142
+ function buildBranchLoopPlan(loop, profileComponentName) {
14072
14143
  const containerSlotId = loop.containerSlotId;
14073
14144
  const cv = varSlotId(containerSlotId);
14074
14145
  const containerVar = `__loop_${cv}`;
14075
14146
  if (loop.useElementReconciliation && (loop.nestedComponents?.length || loop.innerLoops?.length)) {
14076
14147
  const composite = {
14077
14148
  kind: "composite",
14078
- composite: buildBranchCompositePlan(loop, cv),
14149
+ composite: buildBranchCompositePlan(loop, cv, profileComponentName),
14079
14150
  containerSlotId,
14080
14151
  containerVar
14081
14152
  };
@@ -14100,11 +14171,13 @@ function buildBranchLoopPlan(loop) {
14100
14171
  texts: loop.bindings.reactiveTexts,
14101
14172
  conditionals: loop.bindings.conditionals,
14102
14173
  loopParam: loop.param,
14103
- loopParamBindings: loop.paramBindings
14174
+ loopParamBindings: loop.paramBindings,
14175
+ profileComponentName
14104
14176
  }) : null,
14105
- eventDelegation: buildBranchLoopDelegationPlan(loop, cv),
14177
+ eventDelegation: buildBranchLoopDelegationPlan(loop, cv, profileComponentName),
14106
14178
  childRefs: buildChildRefBindings(loop.bindings.refs, loop.param, loop.paramBindings),
14107
- bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false
14179
+ bodyIsMultiRoot: loop.bodyIsMultiRoot ?? false,
14180
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${containerSlotId}` : undefined
14108
14181
  };
14109
14182
  return plan;
14110
14183
  }
@@ -14117,6 +14190,7 @@ function buildInsertPlan(elem, options) {
14117
14190
  slotId: elem.slotId,
14118
14191
  condition: elem.condition,
14119
14192
  eventNameMode: options.eventNameMode,
14193
+ profileComponentName: options.profileComponentName,
14120
14194
  arms: [
14121
14195
  buildArm(elem.whenTrueHtml, elem.slotId, elem.whenTrue, options),
14122
14196
  buildArm(elem.whenFalseHtml, elem.slotId, elem.whenFalse, options)
@@ -14130,11 +14204,13 @@ function buildArm(html, slotId, branch, options) {
14130
14204
  };
14131
14205
  }
14132
14206
  function buildArmBody(branch, options) {
14207
+ const pc = options.profileComponentName;
14133
14208
  return {
14134
14209
  events: branch.events.map((e) => ({
14135
14210
  slotId: e.slotId,
14136
14211
  eventName: e.eventName,
14137
- handler: e.handler
14212
+ handler: e.handler,
14213
+ turnId: pc ? `${pc}#handler:${e.slotId}:${e.eventName}` : undefined
14138
14214
  })),
14139
14215
  refs: branch.refs.map((r) => ({
14140
14216
  slotId: r.slotId,
@@ -14150,12 +14226,17 @@ function buildArmBody(branch, options) {
14150
14226
  slotId: t.slotId,
14151
14227
  expression: t.expression
14152
14228
  })),
14153
- loops: branch.loops.map(buildBranchLoopPlan),
14154
- conditionals: branch.conditionals.map((c) => buildInsertPlan(c, { scope: { kind: "branchScope" }, eventNameMode: options.eventNameMode }))
14229
+ loops: branch.loops.map((l) => buildBranchLoopPlan(l, pc)),
14230
+ conditionals: branch.conditionals.map((c) => buildInsertPlan(c, { scope: { kind: "branchScope" }, eventNameMode: options.eventNameMode, profileComponentName: pc }))
14155
14231
  };
14156
14232
  }
14157
14233
 
14158
14234
  // src/ir-to-client-js/emit-reactive.ts
14235
+ function bindingIdArg(ctx, slotId) {
14236
+ if (!ctx.profile || !slotId)
14237
+ return "";
14238
+ return `, ${JSON.stringify(`${ctx.componentName}#binding:${slotId}`)}`;
14239
+ }
14159
14240
  function emitAttrUpdate(target, attrName, expression, meta) {
14160
14241
  const htmlName = toHtmlAttrName(attrName);
14161
14242
  if (attrName === "dangerouslySetInnerHTML" || htmlName === "dangerouslySetInnerHTML") {
@@ -14227,6 +14308,7 @@ function emitDynamicTextUpdates(lines, ctx) {
14227
14308
  const v = varSlotId(elem.slotId);
14228
14309
  lines.push(` let __anchor_${v} = _${v}`);
14229
14310
  }
14311
+ const __textSlot = (normalElems[0] ?? conditionalElems[0])?.slotId;
14230
14312
  lines.push(` createEffect(() => {`);
14231
14313
  if (normalElems.length > 0) {
14232
14314
  lines.push(` const __val = ${expr}`);
@@ -14248,7 +14330,7 @@ function emitDynamicTextUpdates(lines, ctx) {
14248
14330
  lines.push(` __bfText(__el_${v}, __val)`);
14249
14331
  }
14250
14332
  }
14251
- lines.push(` })`);
14333
+ lines.push(` }${bindingIdArg(ctx, __textSlot)})`);
14252
14334
  lines.push("");
14253
14335
  }
14254
14336
  }
@@ -14258,7 +14340,7 @@ function emitClientOnlyExpressions(lines, ctx) {
14258
14340
  lines.push(` // @client: ${elem.slotId}`);
14259
14341
  lines.push(` createEffect(() => {`);
14260
14342
  lines.push(` updateClientMarker(__scope, '${elem.slotId}', ${elem.expression})`);
14261
- lines.push(` })`);
14343
+ lines.push(` }${bindingIdArg(ctx, elem.slotId)})`);
14262
14344
  lines.push("");
14263
14345
  }
14264
14346
  }
@@ -14282,7 +14364,7 @@ function emitReactiveAttributeUpdates(lines, ctx) {
14282
14364
  }
14283
14365
  }
14284
14366
  lines.push(` }`);
14285
- lines.push(` })`);
14367
+ lines.push(` }${bindingIdArg(ctx, slotId)})`);
14286
14368
  lines.push("");
14287
14369
  }
14288
14370
  }
@@ -14328,7 +14410,7 @@ function emitReactivePropBindings(lines, ctx) {
14328
14410
  }
14329
14411
  lines.push(` }`);
14330
14412
  }
14331
- lines.push(` })`);
14413
+ lines.push(` }${bindingIdArg(ctx, ctx.reactiveProps[0]?.slotId)})`);
14332
14414
  }
14333
14415
  }
14334
14416
  function emitReactiveChildProps(lines, ctx) {
@@ -14358,7 +14440,7 @@ function emitReactiveChildProps(lines, ctx) {
14358
14440
  }
14359
14441
  lines.push(` }`);
14360
14442
  }
14361
- lines.push(` })`);
14443
+ lines.push(` }${bindingIdArg(ctx, ctx.reactiveChildProps[0]?.slotId ?? undefined)})`);
14362
14444
  }
14363
14445
  }
14364
14446
 
@@ -14504,7 +14586,7 @@ function stringifyBranchEventBindings(lines, plan, indent) {
14504
14586
  const v = varSlotId(slot.slotId);
14505
14587
  lines.push(`${indent}{ const _${v} = qsa(__branchScope, '[bf="${slot.slotId}"]')`);
14506
14588
  for (const ev of slot.listeners) {
14507
- emitListenerLine(lines, `${indent} `, `_${v}`, ev.eventName, ev.wrappedHandler);
14589
+ emitListenerLine(lines, `${indent} `, `_${v}`, ev.eventName, ev.wrappedHandler, "dom", ev.turnId);
14508
14590
  }
14509
14591
  lines.push(`${indent}}`);
14510
14592
  }
@@ -14514,7 +14596,7 @@ function stringifyBranchChildComponentInits(lines, plan, indent) {
14514
14596
  lines.push(`${indent}{ let __c = qsa(__branchScope, ${init.selector}); if (!__c) { const __ph = __branchScope.querySelector('[${DATA_BF_PH}="${init.placeholderId}"]'); if (__ph) { __c = createComponent('${nameForRegistryRef(init.name)}', ${init.propsExpr}); __ph.replaceWith(__c) } } if (__c) initChild('${nameForRegistryRef(init.name)}', __c, ${init.propsExpr}) }`);
14515
14597
  }
14516
14598
  }
14517
- function stringifyBranchInnerLoops(lines, plan, indent) {
14599
+ function stringifyBranchInnerLoops(lines, plan, indent, pc) {
14518
14600
  for (const inner of plan) {
14519
14601
  const uid = inner.uidSuffix;
14520
14602
  lines.push(`${indent}{ const __bic${uid} = ${inner.containerExpr}`);
@@ -14535,48 +14617,49 @@ function stringifyBranchInnerLoops(lines, plan, indent) {
14535
14617
  emitComponentAndEventSetup(lines, `${indent} `, `__bel${uid}`, [...inner.legacyComponents], [...inner.legacyEvents], inner.outerLoopParam, inner.outerLoopParamBindings);
14536
14618
  }
14537
14619
  for (const text of inner.reactiveTexts) {
14620
+ const bf = profileBindingId(pc, text.slotId);
14538
14621
  if (text.insideConditional) {
14539
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__bel${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) })`);
14622
+ lines.push(`${indent} createEffect(() => { const [__rt] = $t(__bel${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`);
14540
14623
  } else {
14541
14624
  lines.push(`${indent} { const [__rt] = $t(__bel${uid}, '${text.slotId}')`);
14542
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }) }`);
14625
+ lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`);
14543
14626
  }
14544
14627
  }
14545
14628
  if (inner.nestedConditionals.length > 0) {
14546
- stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `);
14629
+ stringifyLoopChildConditionals(lines, inner.nestedConditionals, `${indent} `, pc);
14547
14630
  }
14548
14631
  lines.push(`${indent} return __bel${uid}`);
14549
- lines.push(`${indent}}, '${inner.markerId}') }`);
14632
+ lines.push(`${indent}}, '${inner.markerId}'${profileBindingId(pc, inner.slotId)}) }`);
14550
14633
  }
14551
14634
  }
14552
- function stringifyLoopChildConditionals(lines, conditionals, indent) {
14635
+ function stringifyLoopChildConditionals(lines, conditionals, indent, pc) {
14553
14636
  for (const cond of conditionals) {
14554
- stringifyLoopChildConditional(lines, cond, indent);
14637
+ stringifyLoopChildConditional(lines, cond, indent, pc);
14555
14638
  }
14556
14639
  }
14557
- function stringifyLoopChildConditional(lines, cond, indent) {
14640
+ function stringifyLoopChildConditional(lines, cond, indent, pc) {
14558
14641
  const armIndent = `${indent} `;
14559
14642
  lines.push(`${indent}insert(${cond.scopeVar}, '${cond.slotId}', () => ${cond.wrappedCondition}, {`);
14560
14643
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`);
14561
14644
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`);
14562
- stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent);
14645
+ stringifyLoopChildArm(lines, cond.whenTrueArm, armIndent, pc);
14563
14646
  lines.push(`${indent} }`);
14564
14647
  lines.push(`${indent}}, {`);
14565
14648
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`);
14566
14649
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`);
14567
- stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent);
14650
+ stringifyLoopChildArm(lines, cond.whenFalseArm, armIndent, pc);
14568
14651
  lines.push(`${indent} }`);
14569
- lines.push(`${indent}})`);
14652
+ lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`);
14570
14653
  }
14571
- function stringifyLoopChildArm(lines, arm, armIndent) {
14654
+ function stringifyLoopChildArm(lines, arm, armIndent, pc) {
14572
14655
  stringifyBranchEventBindings(lines, arm.events, armIndent);
14573
14656
  stringifyBranchChildComponentInits(lines, arm.childComponents, armIndent);
14574
- stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent);
14575
- stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent);
14657
+ stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent, pc);
14658
+ stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent, pc);
14576
14659
  for (const text of arm.texts) {
14577
14660
  const varName = `__rt_${varSlotId(text.slotId)}`;
14578
14661
  lines.push(`${armIndent}{ const [${varName}] = $t(__branchScope, '${text.slotId}')`);
14579
- lines.push(`${armIndent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }) }`);
14662
+ lines.push(`${armIndent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`);
14580
14663
  }
14581
14664
  }
14582
14665
 
@@ -14584,6 +14667,8 @@ function stringifyLoopChildArm(lines, arm, armIndent) {
14584
14667
  function stringifyReactiveEffects(lines, plan, opts) {
14585
14668
  const { indent, elVar, bodyIsMultiRoot } = opts;
14586
14669
  const lookup = bodyIsMultiRoot ? "qsaItem" : "qsa";
14670
+ const pc = plan.profileComponentName;
14671
+ const bindingBfId = (slotId) => profileBindingId(pc, slotId);
14587
14672
  for (const slot of plan.attrSlots) {
14588
14673
  const varName = `__ra_${varSlotId(slot.slotId)}`;
14589
14674
  lines.push(`${indent}{ const ${varName} = ${lookup}(${elVar}, '[bf="${slot.slotId}"]')`);
@@ -14593,49 +14678,49 @@ function stringifyReactiveEffects(lines, plan, opts) {
14593
14678
  for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.wrappedExpression, attr.meta)) {
14594
14679
  lines.push(`${indent} ${stmt}`);
14595
14680
  }
14596
- lines.push(`${indent} })`);
14681
+ lines.push(`${indent} }${bindingBfId(slot.slotId)})`);
14597
14682
  }
14598
14683
  lines.push(`${indent}} }`);
14599
14684
  }
14600
14685
  for (const text of plan.outerTexts) {
14601
- emitOuterText(lines, indent, elVar, text);
14686
+ emitOuterText(lines, indent, elVar, text, bindingBfId(text.slotId));
14602
14687
  }
14603
14688
  for (const cond of plan.conditionals) {
14604
- emitOuterConditional(lines, indent, elVar, cond);
14689
+ emitOuterConditional(lines, indent, elVar, cond, pc);
14605
14690
  }
14606
14691
  }
14607
- function emitOuterText(lines, indent, elVar, text) {
14692
+ function emitOuterText(lines, indent, elVar, text, bfId = "") {
14608
14693
  const varName = `__rt_${varSlotId(text.slotId)}`;
14609
14694
  lines.push(`${indent}{ const [${varName}] = $t(${elVar}, '${text.slotId}')`);
14610
- lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }) }`);
14695
+ lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${bfId}) }`);
14611
14696
  }
14612
- function emitOuterConditional(lines, indent, elVar, cond) {
14697
+ function emitOuterConditional(lines, indent, elVar, cond, pc) {
14613
14698
  const armIndent = `${indent} `;
14614
14699
  lines.push(`${indent}insert(${elVar}, '${cond.slotId}', () => ${cond.wrappedCondition}, {`);
14615
14700
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenTrueTemplateHtml}\`, slots: __slots } },`);
14616
14701
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`);
14617
- emitArmBody(lines, cond.whenTrueArm, armIndent);
14702
+ emitArmBody(lines, cond.whenTrueArm, armIndent, pc);
14618
14703
  lines.push(`${indent} }`);
14619
14704
  lines.push(`${indent}}, {`);
14620
14705
  lines.push(`${indent} template: () => { const __slots = []; return { html: \`${cond.whenFalseTemplateHtml}\`, slots: __slots } },`);
14621
14706
  lines.push(`${indent} bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`);
14622
- emitArmBody(lines, cond.whenFalseArm, armIndent);
14707
+ emitArmBody(lines, cond.whenFalseArm, armIndent, pc);
14623
14708
  lines.push(`${indent} }`);
14624
- lines.push(`${indent}})`);
14709
+ lines.push(`${indent}}${profileBindingId(pc, cond.slotId)})`);
14625
14710
  }
14626
- function emitArmBody(lines, arm, armIndent) {
14711
+ function emitArmBody(lines, arm, armIndent, pc) {
14627
14712
  stringifyBranchEventBindings(lines, arm.events, armIndent);
14628
14713
  stringifyBranchChildComponentInits(lines, arm.childComponents, armIndent);
14629
- stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent);
14630
- stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent);
14714
+ stringifyBranchInnerLoops(lines, arm.innerLoops, armIndent, pc);
14715
+ stringifyLoopChildConditionals(lines, arm.nestedConditionals, armIndent, pc);
14631
14716
  for (const text of arm.texts) {
14632
- emitArmText(lines, armIndent, text);
14717
+ emitArmText(lines, armIndent, text, pc);
14633
14718
  }
14634
14719
  }
14635
- function emitArmText(lines, indent, text) {
14720
+ function emitArmText(lines, indent, text, pc) {
14636
14721
  const varName = `__rt_${varSlotId(text.slotId)}`;
14637
14722
  lines.push(`${indent}{ const [${varName}] = $t(__branchScope, '${text.slotId}')`);
14638
- lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }) }`);
14723
+ lines.push(`${indent}if (${varName}) createEffect(() => { ${varName}.textContent = String(${text.wrappedExpression}) }${profileBindingId(pc, text.slotId)}) }`);
14639
14724
  }
14640
14725
 
14641
14726
  // src/ir-to-client-js/control-flow/stringify/component-loop.ts
@@ -14652,8 +14737,10 @@ function stringifyComponentLoop(lines, plan) {
14652
14737
  componentPropsExpr,
14653
14738
  keyExpr,
14654
14739
  nestedComps,
14655
- childConditionalEffects
14740
+ childConditionalEffects,
14741
+ profileLoopId
14656
14742
  } = plan;
14743
+ const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
14657
14744
  lines.push(` mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => {`);
14658
14745
  if (paramUnwrap)
14659
14746
  lines.push(` ${paramUnwrap}`);
@@ -14661,7 +14748,7 @@ function stringifyComponentLoop(lines, plan) {
14661
14748
  if (nestedComps.length === 0) {
14662
14749
  lines.push(` if (__existing) { initChild('${scopedComp}', __existing, ${componentPropsExpr}); return __existing }`);
14663
14750
  lines.push(` return createComponent('${scopedComp}', ${componentPropsExpr}, ${keyExpr})`);
14664
- lines.push(` }, '${markerId}')`);
14751
+ lines.push(` }, '${markerId}'${loopBfId})`);
14665
14752
  return;
14666
14753
  }
14667
14754
  lines.push(` if (__existing) {`);
@@ -14680,7 +14767,7 @@ function stringifyComponentLoop(lines, plan) {
14680
14767
  stringifyReactiveEffects(lines, childConditionalEffects, { indent: " ", elVar: "__csrEl" });
14681
14768
  }
14682
14769
  lines.push(` return __csrEl`);
14683
- lines.push(` }, '${markerId}')`);
14770
+ lines.push(` }, '${markerId}'${loopBfId})`);
14684
14771
  }
14685
14772
  function emitNestedInit(lines, indent, parentVar, nc) {
14686
14773
  const scopedNc = nameForRegistryRef(nc.componentName);
@@ -14749,11 +14836,12 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
14749
14836
  stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr);
14750
14837
  return;
14751
14838
  }
14839
+ const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
14752
14840
  if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
14753
14841
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
14754
14842
  const preamble = mapPreambleWrapped ? `${mapPreambleWrapped}; ` : "";
14755
14843
  const cloneExpr = emitTemplateCloneInline(template);
14756
- lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}${preamble}if (__existing) return __existing; ${cloneExpr} }, '${markerId}')`);
14844
+ lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}${preamble}if (__existing) return __existing; ${cloneExpr} }, '${markerId}'${loopBfId})`);
14757
14845
  return;
14758
14846
  }
14759
14847
  lines.push(`${topIndent}mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => {`);
@@ -14773,7 +14861,7 @@ function stringifyPlainLoop(lines, plan, topIndent = " ") {
14773
14861
  }
14774
14862
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
14775
14863
  lines.push(`${bodyIndent}return __el`);
14776
- lines.push(`${topIndent}}, '${markerId}')`);
14864
+ lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
14777
14865
  }
14778
14866
  function stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr) {
14779
14867
  const {
@@ -14808,10 +14896,11 @@ function stringifyAnchoredLoop(lines, plan, topIndent, anchorKeyExpr) {
14808
14896
  stringifyReactiveEffects(lines, reactiveEffects, { indent: bodyIndent, elVar: "__anchor", bodyIsMultiRoot: false });
14809
14897
  }
14810
14898
  lines.push(`${bodyIndent}return __frag ?? __anchor`);
14811
- lines.push(`${topIndent}}, '${markerId}')`);
14899
+ const loopBfId = plan.profileLoopId ? `, ${JSON.stringify(plan.profileLoopId)}` : "";
14900
+ lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
14812
14901
  }
14813
14902
  function stringifyStaticLoop(lines, plan) {
14814
- const { containerVar, arrayExpr, param, indexParam, childIndexExpr, attrsBySlot, texts, childRefs, csrMaterialize } = plan;
14903
+ const { containerVar, arrayExpr, param, indexParam, childIndexExpr, attrsBySlot, texts, childRefs, csrMaterialize, profileComponentName: pc } = plan;
14815
14904
  const hasAttrs = attrsBySlot.length > 0;
14816
14905
  const hasTexts = texts.length > 0;
14817
14906
  const hasRefs = childRefs.length > 0;
@@ -14873,14 +14962,14 @@ function stringifyStaticLoop(lines, plan) {
14873
14962
  for (const stmt of emitAttrUpdate(varName, attr.attrName, attr.expression, attr)) {
14874
14963
  lines.push(` ${stmt}`);
14875
14964
  }
14876
- lines.push(` })`);
14965
+ lines.push(` }${profileBindingId(pc, slotId)})`);
14877
14966
  }
14878
14967
  lines.push(` }`);
14879
14968
  }
14880
14969
  for (const text of texts) {
14881
14970
  const vn = `__rt_${varSlotId(text.slotId)}`;
14882
14971
  lines.push(` { const [${vn}] = $t(__iterEl, '${text.slotId}')`);
14883
- lines.push(` if (${vn}) createEffect(() => { ${vn}.textContent = String(${text.expression}) }) }`);
14972
+ lines.push(` if (${vn}) createEffect(() => { ${vn}.textContent = String(${text.expression}) }${profileBindingId(pc, text.slotId)}) }`);
14884
14973
  }
14885
14974
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__iterEl", bodyIsMultiRoot: false });
14886
14975
  lines.push(` }`);
@@ -14890,16 +14979,16 @@ function stringifyStaticLoop(lines, plan) {
14890
14979
  }
14891
14980
 
14892
14981
  // src/ir-to-client-js/control-flow/stringify/inner-loop.ts
14893
- function stringifyInnerLoops(lines, plan, indent) {
14982
+ function stringifyInnerLoops(lines, plan, indent, pc) {
14894
14983
  for (const inner of plan) {
14895
14984
  if (inner.emit.mode === "reactive") {
14896
- emitReactive(lines, inner, indent);
14985
+ emitReactive(lines, inner, indent, pc);
14897
14986
  } else {
14898
- emitStatic(lines, inner, indent);
14987
+ emitStatic(lines, inner, indent, pc);
14899
14988
  }
14900
14989
  }
14901
14990
  }
14902
- function emitReactive(lines, inner, indent) {
14991
+ function emitReactive(lines, inner, indent, pc) {
14903
14992
  const uid = inner.uidSuffix;
14904
14993
  const emit = inner.emit;
14905
14994
  if (emit.mode !== "reactive")
@@ -14930,14 +15019,15 @@ function emitReactive(lines, inner, indent) {
14930
15019
  emitComponentAndEventSetup(lines, `${indent} `, `__innerEl${uid}`, [...emit.components], [...emit.events], inner.outerLoopParam, inner.outerLoopParamBindings);
14931
15020
  }
14932
15021
  if (inner.childLevels.length > 0) {
14933
- stringifyInnerLoops(lines, inner.childLevels, `${indent} `);
15022
+ stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc);
14934
15023
  }
14935
15024
  for (const text of emit.reactiveTexts) {
15025
+ const bf = profileBindingId(pc, text.slotId);
14936
15026
  if (text.insideConditional) {
14937
- lines.push(`${indent} createEffect(() => { const [__rt] = $t(__innerEl${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) })`);
15027
+ lines.push(`${indent} createEffect(() => { const [__rt] = $t(__innerEl${uid}, '${text.slotId}'); if (__rt) __rt.textContent = String(${text.wrappedExpression}) }${bf})`);
14938
15028
  } else {
14939
15029
  lines.push(`${indent} { const [__rt] = $t(__innerEl${uid}, '${text.slotId}')`);
14940
- lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }) }`);
15030
+ lines.push(`${indent} if (__rt) createEffect(() => { __rt.textContent = String(${text.wrappedExpression}) }${bf}) }`);
14941
15031
  }
14942
15032
  }
14943
15033
  for (const attr of emit.reactiveAttrs) {
@@ -14947,7 +15037,7 @@ function emitReactive(lines, inner, indent) {
14947
15037
  for (const stmt of emitAttrUpdate(targetVar, attr.attrName, attr.wrappedExpression, attr.meta)) {
14948
15038
  lines.push(`${indent} ${stmt}`);
14949
15039
  }
14950
- lines.push(`${indent} }) }`);
15040
+ lines.push(`${indent} }${profileBindingId(pc, attr.slotId)}) }`);
14951
15041
  }
14952
15042
  emitLoopChildRefs(lines, emit.childRefs, {
14953
15043
  indent: `${indent} `,
@@ -14955,9 +15045,9 @@ function emitReactive(lines, inner, indent) {
14955
15045
  bodyIsMultiRoot: emit.bodyIsMultiRoot
14956
15046
  });
14957
15047
  lines.push(`${indent} return __innerEl${uid}`);
14958
- lines.push(`${indent}}, '${inner.markerId}') }`);
15048
+ lines.push(`${indent}}, '${inner.markerId}'${profileBindingId(pc, inner.slotId)}) }`);
14959
15049
  }
14960
- function emitStatic(lines, inner, indent) {
15050
+ function emitStatic(lines, inner, indent, pc) {
14961
15051
  const uid = inner.uidSuffix;
14962
15052
  const emit = inner.emit;
14963
15053
  if (emit.mode !== "static")
@@ -14975,7 +15065,7 @@ function emitStatic(lines, inner, indent) {
14975
15065
  }
14976
15066
  emitComponentAndEventSetup(lines, `${indent} `, `__innerEl${uid}`, [...emit.components], [...emit.events], inner.outerLoopParam, inner.outerLoopParamBindings);
14977
15067
  if (inner.childLevels.length > 0) {
14978
- stringifyInnerLoops(lines, inner.childLevels, `${indent} `);
15068
+ stringifyInnerLoops(lines, inner.childLevels, `${indent} `, pc);
14979
15069
  }
14980
15070
  emitLoopChildRefs(lines, emit.childRefs, {
14981
15071
  indent: `${indent} `,
@@ -15007,7 +15097,9 @@ function stringifyCompositeLoop(lines, plan) {
15007
15097
  branchClearChildren,
15008
15098
  topIndent,
15009
15099
  bodyIndent: rawBodyIndent,
15010
- bodyIsMultiRoot
15100
+ bodyIsMultiRoot,
15101
+ profileComponentName: pc,
15102
+ profileLoopId
15011
15103
  } = plan;
15012
15104
  const bodyIndent = branchClearChildren ? rawBodyIndent + " " : rawBodyIndent;
15013
15105
  const mapArrayIndent = branchClearChildren ? topIndent + " " : topIndent;
@@ -15033,18 +15125,19 @@ function stringifyCompositeLoop(lines, plan) {
15033
15125
  });
15034
15126
  emitComponentAndEventSetup(lines, bodyIndent, "__el", compsArr, eventsArr, loopParam, loopParamBindings, bodyIsMultiRoot);
15035
15127
  if (innerLoops.length > 0) {
15036
- stringifyInnerLoops(lines, innerLoops, bodyIndent);
15128
+ stringifyInnerLoops(lines, innerLoops, bodyIndent, pc);
15037
15129
  }
15038
15130
  if (reactiveEffects) {
15039
15131
  stringifyReactiveEffects(lines, reactiveEffects, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
15040
15132
  }
15041
15133
  emitLoopChildRefs(lines, childRefs, { indent: bodyIndent, elVar: "__el", bodyIsMultiRoot });
15042
15134
  lines.push(`${bodyIndent}return __el`);
15135
+ const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
15043
15136
  if (branchClearChildren) {
15044
- lines.push(`${mapArrayIndent}}, '${markerId}')`);
15137
+ lines.push(`${mapArrayIndent}}, '${markerId}'${loopBfId})`);
15045
15138
  lines.push(`${topIndent}}))`);
15046
15139
  } else {
15047
- lines.push(`${topIndent}}, '${markerId}')`);
15140
+ lines.push(`${topIndent}}, '${markerId}'${loopBfId})`);
15048
15141
  }
15049
15142
  }
15050
15143
 
@@ -15059,8 +15152,14 @@ var NON_BUBBLING_EVENTS = new Set([
15059
15152
  "pointerenter",
15060
15153
  "pointerleave"
15061
15154
  ]);
15155
+ function withTurn(call, componentName, childSlotId, eventName) {
15156
+ if (!componentName)
15157
+ return call;
15158
+ const id = JSON.stringify(`${componentName}#handler:${childSlotId}:${eventName}`);
15159
+ return `beginTurn(${id}); try { ${call} } finally { endTurn() }`;
15160
+ }
15062
15161
  function stringifyEventDelegation(lines, plan) {
15063
- const { containerVar, events, itemLookup } = plan;
15162
+ const { containerVar, events, itemLookup, profileComponentName } = plan;
15064
15163
  const eventsByName = new Map;
15065
15164
  for (const ev of events) {
15066
15165
  if (!eventsByName.has(ev.eventName))
@@ -15080,7 +15179,7 @@ function stringifyEventDelegation(lines, plan) {
15080
15179
  const childVar = varSlotId(ev.childSlotId);
15081
15180
  lines.push(` const ${childVar}El = target.closest('[bf="${ev.childSlotId}"]')`);
15082
15181
  lines.push(` if (${childVar}El) {`);
15083
- const handlerCall = `(${ev.handler.trim()})(__bfEvt)`;
15182
+ const handlerCall = withTurn(`(${ev.handler.trim()})(__bfEvt)`, profileComponentName, ev.childSlotId, ev.eventName);
15084
15183
  switch (itemLookup.kind) {
15085
15184
  case "keyed":
15086
15185
  emitKeyedLookup(lines, ev, handlerCall, itemLookup);
@@ -15215,16 +15314,18 @@ function emitPlain(lines, plan) {
15215
15314
  reactiveEffects,
15216
15315
  eventDelegation,
15217
15316
  childRefs,
15218
- bodyIsMultiRoot
15317
+ bodyIsMultiRoot,
15318
+ profileLoopId
15219
15319
  } = plan;
15320
+ const loopBfId = profileLoopId ? `, ${JSON.stringify(profileLoopId)}` : "";
15220
15321
  const unwrapInline = paramUnwrap ? `${paramUnwrap} ` : "";
15221
15322
  lines.push(` __disposers.push(createDisposableEffect(() => {`);
15222
15323
  if (reactiveEffects === null && !bodyIsMultiRoot && childRefs.length === 0) {
15223
15324
  const cloneExpr = emitTemplateCloneInline(template);
15224
15325
  if (mapPreambleWrapped) {
15225
- lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}')`);
15326
+ lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${mapPreambleWrapped}; ${cloneExpr} }, '${markerId}'${loopBfId})`);
15226
15327
  } else {
15227
- lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${cloneExpr} }, '${markerId}')`);
15328
+ lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => { ${unwrapInline}if (__existing) return __existing; ${cloneExpr} }, '${markerId}'${loopBfId})`);
15228
15329
  }
15229
15330
  } else {
15230
15331
  lines.push(` if (${containerVar}) mapArray(() => ${arrayExpr}, ${containerVar}, ${keyFn}, (${paramHead}, ${indexParam}, __existing) => {`);
@@ -15245,7 +15346,7 @@ function emitPlain(lines, plan) {
15245
15346
  }
15246
15347
  emitLoopChildRefs(lines, childRefs, { indent: " ", elVar: "__el", bodyIsMultiRoot });
15247
15348
  lines.push(` return __el`);
15248
- lines.push(` }, '${markerId}')`);
15349
+ lines.push(` }, '${markerId}'${loopBfId})`);
15249
15350
  }
15250
15351
  lines.push(` }))`);
15251
15352
  stringifyEventDelegation(lines, eventDelegation);
@@ -15256,19 +15357,21 @@ function stringifyInsert(lines, plan, opts) {
15256
15357
  const { leadingIndent, bodyIndent } = opts;
15257
15358
  const scopeVar = scopeRefToVar(plan.scope);
15258
15359
  const armIndent = leadingIndent + " ";
15360
+ const condBfId = plan.profileComponentName ? `, ${JSON.stringify(`${plan.profileComponentName}#binding:${plan.slotId}`)}` : "";
15259
15361
  lines.push(`${leadingIndent}insert(${scopeVar}, '${plan.slotId}', () => ${plan.condition}, {`);
15260
- emitArm(lines, plan.arms[0], plan.eventNameMode, armIndent, bodyIndent);
15362
+ emitArm(lines, plan.arms[0], plan.eventNameMode, armIndent, bodyIndent, plan.profileComponentName);
15261
15363
  lines.push(`${leadingIndent}}, {`);
15262
- emitArm(lines, plan.arms[1], plan.eventNameMode, armIndent, bodyIndent);
15263
- lines.push(`${leadingIndent}})`);
15364
+ emitArm(lines, plan.arms[1], plan.eventNameMode, armIndent, bodyIndent, plan.profileComponentName);
15365
+ lines.push(`${leadingIndent}}${condBfId})`);
15264
15366
  }
15265
- function emitArm(lines, arm, mode, armIndent, bodyIndent) {
15367
+ function emitArm(lines, arm, mode, armIndent, bodyIndent, profileComponentName) {
15266
15368
  lines.push(`${armIndent}template: () => { const __slots = []; return { html: \`${arm.templateHtml}\`, slots: __slots } },`);
15267
15369
  lines.push(`${armIndent}bindEvents: (__branchScope, { isFirstRun: __bfFirstRun = false } = {}) => {`);
15268
- emitArmBody2(lines, arm.body, mode, bodyIndent);
15370
+ emitArmBody2(lines, arm.body, mode, bodyIndent, profileComponentName);
15269
15371
  lines.push(`${armIndent}}`);
15270
15372
  }
15271
- function emitArmBody2(lines, body, mode, indent) {
15373
+ function emitArmBody2(lines, body, mode, indent, profileComponentName) {
15374
+ const bindingBfId = (slotId) => profileComponentName ? `, ${JSON.stringify(`${profileComponentName}#binding:${slotId}`)}` : "";
15272
15375
  const allSlotIds = new Set;
15273
15376
  for (const ev of body.events)
15274
15377
  allSlotIds.add(ev.slotId);
@@ -15289,7 +15392,7 @@ function emitArmBody2(lines, body, mode, indent) {
15289
15392
  for (const [slotId, slotEvents] of eventsBySlot) {
15290
15393
  const v = varSlotId(slotId);
15291
15394
  for (const ev of slotEvents) {
15292
- emitListenerLine(lines, indent, `_${v}`, ev.eventName, ev.handler, mode);
15395
+ emitListenerLine(lines, indent, `_${v}`, ev.eventName, ev.handler, mode, ev.turnId);
15293
15396
  }
15294
15397
  }
15295
15398
  for (const ref of body.refs) {
@@ -15323,7 +15426,7 @@ function emitArmBody2(lines, body, mode, indent) {
15323
15426
  for (const stmt of emitAttrUpdate(elVar, attr.attrName, attr.expression, attr)) {
15324
15427
  lines.push(`${indent} ${stmt}`);
15325
15428
  }
15326
- lines.push(`${indent} }))`);
15429
+ lines.push(`${indent} }${bindingBfId(slotId)}))`);
15327
15430
  }
15328
15431
  lines.push(`${indent}} }`);
15329
15432
  }
@@ -15333,7 +15436,7 @@ function emitArmBody2(lines, body, mode, indent) {
15333
15436
  lines.push(`${indent}__disposers.push(createDisposableEffect(() => {`);
15334
15437
  lines.push(`${indent} const __val = ${te.expression}`);
15335
15438
  lines.push(`${indent} __anchor_${v} = __bfText(__anchor_${v}, __val)`);
15336
- lines.push(`${indent}}))`);
15439
+ lines.push(`${indent}}${bindingBfId(te.slotId)}))`);
15337
15440
  }
15338
15441
  if (body.loops.length > 0) {
15339
15442
  stringifyBranchLoops(lines, body.loops);
@@ -15360,7 +15463,7 @@ function scopeRefToVar(ref) {
15360
15463
  }
15361
15464
 
15362
15465
  // src/ir-to-client-js/control-flow/plan/build-component-loop.ts
15363
- function buildComponentLoopPlan(elem) {
15466
+ function buildComponentLoopPlan(elem, profileComponentName) {
15364
15467
  const { name } = elem.childComponent;
15365
15468
  const propsExpr = buildComponentPropsExpr2(elem.childComponent, elem.param);
15366
15469
  const keyExpr = wrapLoopParamAsAccessor(elem.key || "__idx", elem.param, elem.paramBindings);
@@ -15393,12 +15496,14 @@ function buildComponentLoopPlan(elem) {
15393
15496
  keyExpr,
15394
15497
  nestedComps,
15395
15498
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
15499
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
15396
15500
  childConditionalEffects: hasChildConds ? buildReactiveEffectsPlan({
15397
15501
  attrs: [],
15398
15502
  texts: [],
15399
15503
  conditionals: elem.bindings.conditionals,
15400
15504
  loopParam: elem.param,
15401
- loopParamBindings: elem.paramBindings
15505
+ loopParamBindings: elem.paramBindings,
15506
+ profileComponentName
15402
15507
  }) : null
15403
15508
  };
15404
15509
  }
@@ -15406,21 +15511,21 @@ function buildComponentLoopPlan(elem) {
15406
15511
  // src/ir-to-client-js/control-flow/plan/build-loop.ts
15407
15512
  function buildLoopPlan(elem, opts) {
15408
15513
  if (elem.bodyIsItemConditional) {
15409
- return buildPlainLoopPlan(elem);
15514
+ return buildPlainLoopPlan(elem, opts.profileComponentName);
15410
15515
  }
15411
15516
  if (elem.isStaticArray) {
15412
- return buildStaticLoopPlan(elem, opts.unsafeLocalNames);
15517
+ return buildStaticLoopPlan(elem, opts.unsafeLocalNames, opts.profileComponentName);
15413
15518
  }
15414
15519
  const hasInnerStructure = (elem.nestedComponents?.length ?? 0) > 0 || (elem.innerLoops?.length ?? 0) > 0;
15415
15520
  if (elem.useElementReconciliation && hasInnerStructure) {
15416
- return buildTopLevelCompositePlan(elem);
15521
+ return buildTopLevelCompositePlan(elem, opts.profileComponentName);
15417
15522
  }
15418
15523
  if (elem.childComponent) {
15419
- return buildComponentLoopPlan(elem);
15524
+ return buildComponentLoopPlan(elem, opts.profileComponentName);
15420
15525
  }
15421
- return buildPlainLoopPlan(elem);
15526
+ return buildPlainLoopPlan(elem, opts.profileComponentName);
15422
15527
  }
15423
- function buildPlainLoopPlan(elem) {
15528
+ function buildPlainLoopPlan(elem, profileComponentName) {
15424
15529
  const wrap = (expr) => wrapLoopParamAsAccessor(expr, elem.param, elem.paramBindings);
15425
15530
  const { head: paramHead, unwrap: paramUnwrap } = destructureLoopParam(elem.param, elem.paramBindings);
15426
15531
  const hasReactive2 = elem.bindings.reactiveAttrs.length > 0 || elem.bindings.reactiveTexts.length > 0 || elem.bindings.conditionals.length > 0;
@@ -15428,6 +15533,7 @@ function buildPlainLoopPlan(elem) {
15428
15533
  kind: "plain",
15429
15534
  containerVar: `_${varSlotId(elem.slotId)}`,
15430
15535
  markerId: elem.markerId,
15536
+ profileLoopId: profileComponentName ? `${profileComponentName}#binding:${elem.slotId}` : undefined,
15431
15537
  arrayExpr: buildChainedArrayExpr(elem),
15432
15538
  keyFn: loopKeyFn(elem),
15433
15539
  paramHead,
@@ -15435,14 +15541,14 @@ function buildPlainLoopPlan(elem) {
15435
15541
  indexParam: elem.index || "__idx",
15436
15542
  mapPreambleWrapped: elem.mapPreamble ? wrap(elem.mapPreamble) : "",
15437
15543
  template: elem.template,
15438
- reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem) : null,
15544
+ reactiveEffects: hasReactive2 ? buildLoopReactiveEffectsPlan(elem, profileComponentName) : null,
15439
15545
  childRefs: buildChildRefBindings(elem.bindings.refs, elem.param, elem.paramBindings),
15440
15546
  bodyIsMultiRoot: elem.bodyIsMultiRoot ?? false,
15441
15547
  anchored: elem.bodyIsItemConditional ?? false,
15442
15548
  anchorKeyExpr: elem.key ? wrap(elem.key) : elem.index || "__idx"
15443
15549
  };
15444
15550
  }
15445
- function buildStaticLoopPlan(elem, unsafeLocalNames) {
15551
+ function buildStaticLoopPlan(elem, unsafeLocalNames, profileComponentName) {
15446
15552
  const attrsBySlotMap = new Map;
15447
15553
  if (!elem.childComponent) {
15448
15554
  for (const attr of elem.bindings.reactiveAttrs) {
@@ -15463,6 +15569,7 @@ function buildStaticLoopPlan(elem, unsafeLocalNames) {
15463
15569
  param: elem.param,
15464
15570
  indexParam,
15465
15571
  childIndexExpr,
15572
+ profileComponentName,
15466
15573
  attrsBySlot: [...attrsBySlotMap].map(([slotId, attrs]) => [slotId, attrs]),
15467
15574
  texts: elem.bindings.reactiveTexts,
15468
15575
  childRefs: buildStaticChildRefBindings(elem.bindings.refs),
@@ -15485,15 +15592,17 @@ function buildStaticLoopMaterialize(elem, unsafeLocalNames) {
15485
15592
 
15486
15593
  // src/ir-to-client-js/control-flow.ts
15487
15594
  function emitConditionalUpdates(lines, ctx) {
15595
+ const profileComponentName = ctx.profile ? ctx.componentName : undefined;
15488
15596
  for (const elem of ctx.conditionalElements) {
15489
- const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "dom" });
15597
+ const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "dom", profileComponentName });
15490
15598
  stringifyInsert(lines, plan, { leadingIndent: " ", bodyIndent: " " });
15491
15599
  lines.push("");
15492
15600
  }
15493
15601
  }
15494
15602
  function emitClientOnlyConditionals(lines, ctx) {
15603
+ const profileComponentName = ctx.profile ? ctx.componentName : undefined;
15495
15604
  for (const elem of ctx.clientOnlyConditionals) {
15496
- const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "raw" });
15605
+ const plan = buildInsertPlan(elem, { scope: { kind: "top" }, eventNameMode: "raw", profileComponentName });
15497
15606
  lines.push(` // @client conditional: ${elem.slotId}`);
15498
15607
  stringifyInsert(lines, plan, { leadingIndent: " ", bodyIndent: " " });
15499
15608
  lines.push("");
@@ -15501,20 +15610,23 @@ function emitClientOnlyConditionals(lines, ctx) {
15501
15610
  }
15502
15611
  function emitLoopUpdates(lines, ctx, unsafeLocalNames) {
15503
15612
  for (const elem of ctx.loopElements) {
15504
- const plan = buildLoopPlan(elem, { unsafeLocalNames });
15613
+ const plan = buildLoopPlan(elem, {
15614
+ unsafeLocalNames,
15615
+ profileComponentName: ctx.profile ? ctx.componentName : undefined
15616
+ });
15505
15617
  stringifyLoop(lines, plan);
15506
- emitLoopEventDelegation(lines, elem, plan.kind);
15618
+ emitLoopEventDelegation(lines, elem, plan.kind, ctx.profile ? ctx.componentName : undefined);
15507
15619
  }
15508
15620
  }
15509
- function emitLoopEventDelegation(lines, elem, kind) {
15621
+ function emitLoopEventDelegation(lines, elem, kind, profileComponentName) {
15510
15622
  if (kind === "static") {
15511
15623
  if (!elem.childComponent && elem.bindings.events.length > 0) {
15512
- stringifyEventDelegation(lines, buildStaticArrayDelegationPlan(elem));
15624
+ stringifyEventDelegation(lines, buildStaticArrayDelegationPlan(elem, profileComponentName));
15513
15625
  }
15514
15626
  return;
15515
15627
  }
15516
15628
  if (kind === "plain" && !elem.useElementReconciliation && elem.bindings.events.length > 0) {
15517
- stringifyEventDelegation(lines, buildDynamicLoopDelegationPlan(elem));
15629
+ stringifyEventDelegation(lines, buildDynamicLoopDelegationPlan(elem, profileComponentName));
15518
15630
  }
15519
15631
  }
15520
15632
 
@@ -15973,11 +16085,11 @@ function mapEventHandlers(lines, node, gen) {
15973
16085
  }
15974
16086
 
15975
16087
  // src/ir-to-client-js/index.ts
15976
- function generateClientJs(ir, siblingComponents, localImportPrefixes, scope, adapterCapabilities) {
15977
- return generateClientJsWithSourceMap(ir, siblingComponents, localImportPrefixes, undefined, scope, adapterCapabilities).code;
16088
+ function generateClientJs(ir, siblingComponents, localImportPrefixes, scope, adapterCapabilities, profile) {
16089
+ return generateClientJsWithSourceMap(ir, siblingComponents, localImportPrefixes, undefined, scope, adapterCapabilities, profile).code;
15978
16090
  }
15979
- function generateClientJsWithSourceMap(ir, siblingComponents, localImportPrefixes, options, scope, adapterCapabilities) {
15980
- const ctx = createContext(ir, scope, adapterCapabilities);
16091
+ function generateClientJsWithSourceMap(ir, siblingComponents, localImportPrefixes, options, scope, adapterCapabilities, profile) {
16092
+ const ctx = createContext(ir, scope, adapterCapabilities, profile);
15981
16093
  const siblingOffsets = computeLoopSiblingOffsets(ir.root);
15982
16094
  collectElements(ir.root, ctx, siblingOffsets);
15983
16095
  if (!needsClientJs(ctx)) {
@@ -16022,11 +16134,12 @@ function analyzeClientNeeds(ir) {
16022
16134
  }
16023
16135
  return { needsInit: true, usedProps: [...neededProps] };
16024
16136
  }
16025
- function createContext(ir, scope, adapterCapabilities) {
16137
+ function createContext(ir, scope, adapterCapabilities, profile) {
16026
16138
  return {
16027
16139
  componentName: ir.metadata.componentName,
16028
16140
  fileScope: scope?.fileScope ?? "",
16029
16141
  nonExportedSiblings: scope?.nonExportedSiblings ?? new Set,
16142
+ profile: profile ?? false,
16030
16143
  signals: ir.metadata.signals,
16031
16144
  memos: ir.metadata.memos,
16032
16145
  effects: ir.metadata.effects,
@@ -16775,8 +16888,41 @@ function extractSsrDefaults(metadata) {
16775
16888
  out[memo.name] = { value: resultToJsonable(value) };
16776
16889
  bindings[memo.name] = value;
16777
16890
  }
16891
+ if (metadata.propsObjectName !== null) {
16892
+ const referenced = new Set;
16893
+ for (const sig of metadata.signals) {
16894
+ if (!sig.getter || sig.isModule)
16895
+ continue;
16896
+ collectPropRefs(sig.initialValue, metadata.propsObjectName, referenced);
16897
+ }
16898
+ for (const memo of metadata.memos) {
16899
+ if (memo.isModule)
16900
+ continue;
16901
+ collectPropRefs(memo.computation, metadata.propsObjectName, referenced);
16902
+ }
16903
+ for (const name of referenced) {
16904
+ if (name in out)
16905
+ continue;
16906
+ out[name] = { propName: name, value: null };
16907
+ }
16908
+ }
16778
16909
  return Object.keys(out).length === 0 ? undefined : out;
16779
16910
  }
16911
+ function collectPropRefs(expr, propsObjectName, out) {
16912
+ if (!expr || !expr.trim())
16913
+ return;
16914
+ const node = parseExpression2(expr);
16915
+ if (!node)
16916
+ return;
16917
+ const visit3 = (n) => {
16918
+ if (ts16.isPropertyAccessExpression(n) && ts16.isIdentifier(n.expression) && n.expression.text === propsObjectName && ts16.isIdentifier(n.name)) {
16919
+ out.add(n.name.text);
16920
+ return;
16921
+ }
16922
+ ts16.forEachChild(n, visit3);
16923
+ };
16924
+ visit3(node);
16925
+ }
16780
16926
  function resultToJsonable(v) {
16781
16927
  if (v === UNRESOLVED)
16782
16928
  return null;
@@ -17135,7 +17281,7 @@ function compileMultipleComponents(source, filePath, componentNames, options) {
17135
17281
  types,
17136
17282
  moduleExports: moduleExports || "",
17137
17283
  component,
17138
- clientJs: generateClientJs(componentIR, componentNames, options.localImportPrefixes, undefined, multiAdapterCaps) || undefined,
17284
+ clientJs: generateClientJs(componentIR, componentNames, options.localImportPrefixes, undefined, multiAdapterCaps, options.profile) || undefined,
17139
17285
  adapterTypes: adapterOutput.types || undefined
17140
17286
  });
17141
17287
  errors.push(...componentIR.errors);
@@ -17488,7 +17634,7 @@ function compileJSX(source, filePath, options) {
17488
17634
  const result = generateClientJsWithSourceMap(componentIR, undefined, options.localImportPrefixes, {
17489
17635
  sourceMaps: true,
17490
17636
  generatedFileName: clientJsPath.split("/").pop()
17491
- }, undefined, adapterCaps);
17637
+ }, undefined, adapterCaps, options.profile);
17492
17638
  errors.push(...componentIR.errors);
17493
17639
  if (result.code) {
17494
17640
  files.push({ path: clientJsPath, content: result.code, type: "clientJs" });
@@ -17497,7 +17643,7 @@ function compileJSX(source, filePath, options) {
17497
17643
  }
17498
17644
  }
17499
17645
  } else {
17500
- const clientJs = generateClientJs(componentIR, undefined, options.localImportPrefixes, undefined, adapterCaps);
17646
+ const clientJs = generateClientJs(componentIR, undefined, options.localImportPrefixes, undefined, adapterCaps, options.profile);
17501
17647
  errors.push(...componentIR.errors);
17502
17648
  if (clientJs) {
17503
17649
  files.push({ path: clientJsPath, content: clientJs, type: "clientJs" });
@@ -17679,6 +17825,7 @@ class JsxAdapter extends BaseAdapter {
17679
17825
  return `${BF_COND}="${condId}"`;
17680
17826
  }
17681
17827
  }
17828
+
17682
17829
  // src/adapters/template-imports.ts
17683
17830
  var CLIENT_PACKAGE_SOURCES = new Set([
17684
17831
  "@barefootjs/client",
@@ -17726,6 +17873,265 @@ function rewriteImportsForTemplate(imports, shimSource, rewriteRelative) {
17726
17873
  function specKey(s) {
17727
17874
  return `${s.isDefault ? "d" : ""}${s.isNamespace ? "n" : ""}:${s.name}:${s.alias ?? ""}`;
17728
17875
  }
17876
+
17877
+ // src/adapters/test-adapter.ts
17878
+ class TestAdapter extends JsxAdapter {
17879
+ name = "test";
17880
+ extension = ".test.tsx";
17881
+ jsxConfig = { preserveTypes: false };
17882
+ generate(ir) {
17883
+ this.componentName = ir.metadata.componentName;
17884
+ const imports = this.generateImports(ir);
17885
+ const types = this.generateTypes(ir);
17886
+ const component = this.generateComponent(ir);
17887
+ const defaultExport = ir.metadata.hasDefaultExport ? `
17888
+ export default ${this.componentName}` : "";
17889
+ const sections = {
17890
+ imports,
17891
+ types: types || "",
17892
+ component,
17893
+ defaultExport
17894
+ };
17895
+ const template = [imports, types, component].filter(Boolean).join(`
17896
+
17897
+ `) + defaultExport;
17898
+ return {
17899
+ template,
17900
+ sections,
17901
+ types: types || undefined,
17902
+ extension: this.extension
17903
+ };
17904
+ }
17905
+ generateImports(ir) {
17906
+ const lines = [];
17907
+ const templateImports = rewriteImportsForTemplate(ir.metadata.templateImports, undefined);
17908
+ for (const imp of templateImports) {
17909
+ if (imp.specifiers.length === 0) {
17910
+ if (!imp.isTypeOnly) {
17911
+ lines.push(`import '${imp.source}'`);
17912
+ }
17913
+ continue;
17914
+ }
17915
+ if (imp.isTypeOnly) {
17916
+ lines.push(`import type ${this.formatImportSpecifiers(imp.specifiers)} from '${imp.source}'`);
17917
+ } else {
17918
+ lines.push(`import ${this.formatImportSpecifiers(imp.specifiers)} from '${imp.source}'`);
17919
+ }
17920
+ }
17921
+ return lines.join(`
17922
+ `);
17923
+ }
17924
+ generateTypes(ir) {
17925
+ const lines = [];
17926
+ for (const typeDef of ir.metadata.typeDefinitions) {
17927
+ lines.push(typeDef.definition);
17928
+ }
17929
+ const propsTypeName = ir.metadata.propsType?.raw;
17930
+ if (propsTypeName && !ir.metadata.propsObjectName) {
17931
+ lines.push("");
17932
+ lines.push(`type ${this.componentName}PropsWithHydration = ${propsTypeName} & {`);
17933
+ lines.push(" __instanceId?: string");
17934
+ lines.push(" __bfScope?: string");
17935
+ lines.push("}");
17936
+ }
17937
+ return lines.length > 0 ? lines.join(`
17938
+ `) : null;
17939
+ }
17940
+ generateComponent(ir) {
17941
+ const name = ir.metadata.componentName;
17942
+ const propsTypeName = ir.metadata.propsType?.raw;
17943
+ const hasClientInteractivity = ir.metadata.signals.length > 0 || ir.metadata.memos.length > 0;
17944
+ const typeAnnotation = propsTypeName ? `: ${name}PropsWithHydration` : ": { __instanceId?: string; __bfScope?: string }";
17945
+ const jsxBody = this.renderNode(ir.root);
17946
+ const signalInits = this.generateSignalInitializers(ir, jsxBody);
17947
+ const scopeIdLine = hasClientInteractivity ? `(/_s\\d/.test(__bfScope || '') ? __bfScope : null) || __instanceId` : `__bfScope || __instanceId`;
17948
+ const bodyRefText = [jsxBody, signalInits, scopeIdLine].join(`
17949
+ `);
17950
+ const bfScopeAlias = /\b__bfScope\b/.test(bodyRefText) ? "__bfScope" : "__bfScope: _bfScope";
17951
+ const propsParams = ir.metadata.propsParams.map((p) => p.defaultValue ? `${p.name} = ${p.defaultValue}` : p.name).join(", ");
17952
+ const restPropsName = ir.metadata.restPropsName;
17953
+ const hydrationProps = `__instanceId, ${bfScopeAlias}`;
17954
+ const parts = [];
17955
+ if (propsParams) {
17956
+ parts.push(propsParams);
17957
+ }
17958
+ parts.push(hydrationProps);
17959
+ if (restPropsName) {
17960
+ parts.push(`...${restPropsName}`);
17961
+ }
17962
+ const fullPropsDestructure = `{ ${parts.join(", ")} }`;
17963
+ const hasRequiredProps = ir.metadata.propsParams.some((p) => !p.optional && p.defaultValue === undefined && !p.isRest);
17964
+ const propsTypeExpr = typeAnnotation.replace(/^:\s*/, "");
17965
+ const noArgDefault = hasRequiredProps ? "" : ` = {} as ${propsTypeExpr}`;
17966
+ const lines = [];
17967
+ const exportPrefix = ir.metadata.isExported === false ? "" : "export ";
17968
+ lines.push(`${exportPrefix}function ${name}(${fullPropsDestructure}${typeAnnotation}${noArgDefault}) {`);
17969
+ if (hasClientInteractivity) {
17970
+ lines.push(` const __scopeId = (/_s\\d/.test(__bfScope || '') ? __bfScope : null) || __instanceId || \`${name}_\${Math.random().toString(36).slice(2, 8)}\``);
17971
+ } else {
17972
+ lines.push(` const __scopeId = __bfScope || __instanceId || \`${name}_\${Math.random().toString(36).slice(2, 8)}\``);
17973
+ }
17974
+ if (signalInits) {
17975
+ lines.push(signalInits);
17976
+ }
17977
+ lines.push("");
17978
+ lines.push(` return (`);
17979
+ lines.push(` ${jsxBody}`);
17980
+ lines.push(` )`);
17981
+ lines.push(`}`);
17982
+ return lines.join(`
17983
+ `);
17984
+ }
17985
+ renderNode(node) {
17986
+ switch (node.type) {
17987
+ case "element":
17988
+ return this.renderElement(node);
17989
+ case "text":
17990
+ return node.value;
17991
+ case "expression":
17992
+ return this.renderExpression(node);
17993
+ case "conditional":
17994
+ return this.renderConditional(node);
17995
+ case "loop":
17996
+ return this.renderLoop(node);
17997
+ case "component":
17998
+ return this.renderComponent(node);
17999
+ case "fragment":
18000
+ return this.renderFragment(node);
18001
+ case "slot":
18002
+ return "{children}";
18003
+ default:
18004
+ return "";
18005
+ }
18006
+ }
18007
+ renderElement(element) {
18008
+ const tag = element.tag;
18009
+ const attrs = this.renderAttributes(element);
18010
+ const children = this.renderChildren(element.children);
18011
+ let hydrationAttrs = "";
18012
+ if (element.needsScope) {
18013
+ hydrationAttrs += " bf-s={__scopeId}";
18014
+ }
18015
+ if (element.slotId) {
18016
+ hydrationAttrs += ` bf="${element.slotId}"`;
18017
+ }
18018
+ if (children) {
18019
+ return `<${tag}${attrs}${hydrationAttrs}>${children}</${tag}>`;
18020
+ } else {
18021
+ return `<${tag}${attrs}${hydrationAttrs} />`;
18022
+ }
18023
+ }
18024
+ renderExpression(expr) {
18025
+ if (expr.expr === "null" || expr.expr === "undefined") {
18026
+ return "null";
18027
+ }
18028
+ if (expr.reactive && expr.slotId) {
18029
+ return `{bfText("${expr.slotId}")}{${expr.expr}}{bfTextEnd()}`;
18030
+ }
18031
+ return `{${expr.expr}}`;
18032
+ }
18033
+ renderConditional(cond) {
18034
+ const whenTrue = this.renderNodeRaw(cond.whenTrue);
18035
+ let whenFalse = this.renderNodeRaw(cond.whenFalse);
18036
+ if (!whenFalse || whenFalse === "" || whenFalse === "null") {
18037
+ whenFalse = "null";
18038
+ }
18039
+ return `{${cond.condition} ? ${whenTrue} : ${whenFalse}}`;
18040
+ }
18041
+ renderLoop(loop) {
18042
+ const indexParam = loop.index ? `, ${loop.index}` : "";
18043
+ const children = this.renderChildren(loop.children);
18044
+ const safeChildren = children.startsWith("{") ? `<>${children}</>` : children;
18045
+ return `{${loop.array}.map((${loop.param}${indexParam}) => ${safeChildren})}`;
18046
+ }
18047
+ renderComponent(comp) {
18048
+ const props = this.renderComponentProps(comp);
18049
+ const children = this.renderChildren(comp.children);
18050
+ const scopeAttr = " __bfScope={__scopeId}";
18051
+ if (children) {
18052
+ return `<${comp.name}${props}${scopeAttr}>${children}</${comp.name}>`;
18053
+ } else {
18054
+ return `<${comp.name}${props}${scopeAttr} />`;
18055
+ }
18056
+ }
18057
+ renderFragment(fragment) {
18058
+ const children = this.renderChildren(fragment.children);
18059
+ return `<>${children}</>`;
18060
+ }
18061
+ renderAttributes(element) {
18062
+ const parts = [];
18063
+ for (const attr of element.attrs) {
18064
+ const attrName = attr.name === "class" ? "className" : attr.name;
18065
+ switch (attr.value.kind) {
18066
+ case "spread":
18067
+ parts.push(`{...${attr.value.expr}}`);
18068
+ break;
18069
+ case "boolean-attr":
18070
+ parts.push(attrName);
18071
+ break;
18072
+ case "expression":
18073
+ parts.push(`${attrName}={${attr.value.expr}}`);
18074
+ break;
18075
+ case "template":
18076
+ parts.push(`${attrName}={${this.flattenTemplate(attr.value)}}`);
18077
+ break;
18078
+ case "literal":
18079
+ parts.push(`${attrName}="${attr.value.value}"`);
18080
+ break;
18081
+ case "boolean-shorthand":
18082
+ case "jsx-children":
18083
+ break;
18084
+ }
18085
+ }
18086
+ for (const event of element.events) {
18087
+ const handlerName = event.originalAttr ?? `on${event.name.charAt(0).toUpperCase()}${event.name.slice(1)}`;
18088
+ parts.push(`${handlerName}={() => {}}`);
18089
+ }
18090
+ return parts.length > 0 ? " " + parts.join(" ") : "";
18091
+ }
18092
+ flattenTemplate(value) {
18093
+ const v = value;
18094
+ return "`" + v.parts.map((p) => {
18095
+ if (p.type === "string")
18096
+ return p.value;
18097
+ if (p.type === "ternary")
18098
+ return `\${${p.condition} ? '${p.whenTrue}' : '${p.whenFalse}'}`;
18099
+ return `\${(${JSON.stringify(p.cases)})[${p.key}]}`;
18100
+ }).join("") + "`";
18101
+ }
18102
+ renderComponentProps(comp) {
18103
+ const parts = [];
18104
+ for (const prop of comp.props) {
18105
+ switch (prop.value.kind) {
18106
+ case "jsx-children": {
18107
+ const rendered = prop.value.children.map((c) => this.renderNode(c)).join("");
18108
+ parts.push(`${prop.name}={<>${rendered}</>}`);
18109
+ break;
18110
+ }
18111
+ case "spread":
18112
+ parts.push(`{...${prop.value.expr}}`);
18113
+ break;
18114
+ case "expression":
18115
+ parts.push(`${prop.name}={${prop.value.expr}}`);
18116
+ break;
18117
+ case "template":
18118
+ parts.push(`${prop.name}={${this.flattenTemplate(prop.value)}}`);
18119
+ break;
18120
+ case "boolean-shorthand":
18121
+ parts.push(prop.name);
18122
+ break;
18123
+ case "literal":
18124
+ parts.push(`${prop.name}="${prop.value.value}"`);
18125
+ break;
18126
+ case "boolean-attr":
18127
+ parts.push(prop.name);
18128
+ break;
18129
+ }
18130
+ }
18131
+ return parts.length > 0 ? " " + parts.join(" ") : "";
18132
+ }
18133
+ }
18134
+ var testAdapter = new TestAdapter;
17729
18135
  // src/adapters/parsed-expr-emitter.ts
17730
18136
  function emitParsedExpr(expr, emitter) {
17731
18137
  const emit = (child) => emitParsedExpr(child, emitter);
@@ -17930,7 +18336,139 @@ function parseAndMerge(content, importsBySource, otherImports, codeSections) {
17930
18336
  codeSections.push(code);
17931
18337
  }
17932
18338
  }
18339
+ // src/loop-destructure.ts
18340
+ var SIMPLE_FIELD = /^\.[A-Za-z_$][\w$]*$/;
18341
+ function isLowerableObjectRestDestructure(loop) {
18342
+ const bindings = loop.paramBindings;
18343
+ if (!bindings || bindings.length === 0)
18344
+ return false;
18345
+ if (loop.filterPredicate)
18346
+ return false;
18347
+ for (const name of [...bindings.map((b) => b.name), loop.index]) {
18348
+ if (name && name.startsWith("__bf_"))
18349
+ return false;
18350
+ }
18351
+ for (const b of bindings) {
18352
+ if (b.rest) {
18353
+ if (b.rest.kind !== "object")
18354
+ return false;
18355
+ } else if (!SIMPLE_FIELD.test(b.path)) {
18356
+ return false;
18357
+ }
18358
+ }
18359
+ const restNames = bindings.filter((b) => b.rest).map((b) => b.name);
18360
+ if (restNames.length === 0)
18361
+ return true;
18362
+ return !restNamesMisused(loop, restNames);
18363
+ }
18364
+ function restNamesMisused(loop, names) {
18365
+ const valueUse = names.map((n) => new RegExp(`(?<![\\w.$])${escapeRe(n)}(?!\\s*\\??\\.)(?![\\w$])`));
18366
+ let misused = false;
18367
+ const check = (s) => {
18368
+ if (!s || misused)
18369
+ return;
18370
+ for (const re of valueUse) {
18371
+ if (re.test(s)) {
18372
+ misused = true;
18373
+ return;
18374
+ }
18375
+ }
18376
+ };
18377
+ const attr = (v) => {
18378
+ if (v.kind === "expression" || v.kind === "spread") {
18379
+ check(v.expr);
18380
+ check(v.templateExpr);
18381
+ } else if (v.kind === "template") {
18382
+ v.parts.forEach(part);
18383
+ }
18384
+ };
18385
+ const part = (p) => {
18386
+ if (p.type === "ternary") {
18387
+ check(p.condition);
18388
+ check(p.templateCondition);
18389
+ check(p.whenTrue);
18390
+ check(p.whenFalse);
18391
+ } else if (p.type === "lookup") {
18392
+ check(p.key);
18393
+ check(p.templateKey);
18394
+ }
18395
+ };
18396
+ const visitLoop = (l) => {
18397
+ if (misused)
18398
+ return;
18399
+ check(l.array);
18400
+ check(l.templateArray);
18401
+ check(l.key);
18402
+ check(l.mapPreamble);
18403
+ check(l.templateMapPreamble);
18404
+ if (l.flatMapCallback) {
18405
+ check(l.flatMapCallback.body);
18406
+ check(l.flatMapCallback.templateBody);
18407
+ l.flatMapCallback.fragments.forEach((f) => visit3(f.ir));
18408
+ }
18409
+ l.children.forEach(visit3);
18410
+ };
18411
+ const visit3 = (node) => {
18412
+ if (misused)
18413
+ return;
18414
+ switch (node.type) {
18415
+ case "expression":
18416
+ check(node.expr);
18417
+ check(node.templateExpr);
18418
+ break;
18419
+ case "conditional":
18420
+ check(node.condition);
18421
+ check(node.templateCondition);
18422
+ visit3(node.whenTrue);
18423
+ visit3(node.whenFalse);
18424
+ break;
18425
+ case "if-statement":
18426
+ check(node.condition);
18427
+ check(node.templateCondition);
18428
+ for (const sv of node.scopeVariables) {
18429
+ check(sv.initializer);
18430
+ check(sv.templateInitializer);
18431
+ }
18432
+ visit3(node.consequent);
18433
+ if (node.alternate)
18434
+ visit3(node.alternate);
18435
+ break;
18436
+ case "element":
18437
+ node.attrs.forEach((a) => attr(a.value));
18438
+ node.events.forEach((e) => check(e.handler));
18439
+ node.children.forEach(visit3);
18440
+ break;
18441
+ case "component":
18442
+ node.props.forEach((p) => attr(p.value));
18443
+ node.children.forEach(visit3);
18444
+ break;
18445
+ case "provider":
18446
+ attr(node.valueProp.value);
18447
+ node.children.forEach(visit3);
18448
+ break;
18449
+ case "fragment":
18450
+ node.children.forEach(visit3);
18451
+ break;
18452
+ case "async":
18453
+ visit3(node.fallback);
18454
+ node.children.forEach(visit3);
18455
+ break;
18456
+ case "loop":
18457
+ visitLoop(node);
18458
+ break;
18459
+ case "text":
18460
+ case "slot":
18461
+ break;
18462
+ }
18463
+ };
18464
+ visitLoop(loop);
18465
+ return misused;
18466
+ }
18467
+ function escapeRe(s) {
18468
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18469
+ }
17933
18470
  // src/debug.ts
18471
+ import ts19 from "typescript";
17934
18472
  function buildComponentGraph(source, filePath, componentName) {
17935
18473
  const ctx = analyzeComponent(source, filePath, componentName);
17936
18474
  if (!ctx.jsxReturn) {
@@ -17961,15 +18499,55 @@ function buildComponentGraph(source, filePath, componentName) {
17961
18499
  root: ir,
17962
18500
  errors: []
17963
18501
  };
17964
- return buildGraphFromIR(componentIR);
18502
+ const graph = buildGraphFromIR(componentIR);
18503
+ return graph.sourceFile ? graph : { ...graph, sourceFile: filePath };
17965
18504
  }
17966
18505
  function buildGraphFromIR(ir) {
17967
18506
  const meta = ir.metadata;
17968
18507
  const signalGetters = new Set(meta.signals.map((s) => s.getter));
17969
18508
  const memoNames = new Set(meta.memos.map((m) => m.name));
17970
18509
  const signalSetters = new Map(meta.signals.filter((s) => s.setter).map((s) => [s.setter, s.getter]));
18510
+ const destructuredPropNames = new Set(meta.propsParams.map((p) => p.name).filter((n) => n !== "children"));
18511
+ const propsObjectName = meta.propsObjectName;
18512
+ const constByName = new Map(meta.localConstants.map((c) => [c.name, c]));
18513
+ const propDerivedConsts = new Set;
18514
+ {
18515
+ const onStack = new Set;
18516
+ const derivesFromProp = (name) => {
18517
+ if (propDerivedConsts.has(name))
18518
+ return true;
18519
+ const c = constByName.get(name);
18520
+ if (!c?.freeIdentifiers || onStack.has(name))
18521
+ return false;
18522
+ onStack.add(name);
18523
+ let derived = false;
18524
+ for (const free of c.freeIdentifiers) {
18525
+ if (destructuredPropNames.has(free) || free === propsObjectName || derivesFromProp(free)) {
18526
+ derived = true;
18527
+ break;
18528
+ }
18529
+ }
18530
+ onStack.delete(name);
18531
+ if (derived)
18532
+ propDerivedConsts.add(name);
18533
+ return derived;
18534
+ };
18535
+ for (const c of meta.localConstants)
18536
+ derivesFromProp(c.name);
18537
+ }
18538
+ const exprReadsProp = (expr, freeIds) => {
18539
+ for (const name of destructuredPropNames) {
18540
+ if (freeIds ? freeIds.has(name) : tokenContainsIdent(expr, name))
18541
+ return true;
18542
+ }
18543
+ for (const name of propDerivedConsts) {
18544
+ if (freeIds ? freeIds.has(name) : tokenContainsIdent(expr, name))
18545
+ return true;
18546
+ }
18547
+ return propsObjectName ? exprReadsPropMember(expr, propsObjectName) : false;
18548
+ };
17971
18549
  const domBindings = [];
17972
- collectDomBindings(ir.root, domBindings, signalGetters, memoNames);
18550
+ collectDomBindings(ir.root, domBindings, signalGetters, memoNames, undefined, new Set, exprReadsProp);
17973
18551
  const signalConsumers = new Map;
17974
18552
  for (const s of meta.signals)
17975
18553
  signalConsumers.set(s.getter, []);
@@ -18040,8 +18618,8 @@ function buildGraphFromIR(ir) {
18040
18618
  domBindings
18041
18619
  };
18042
18620
  }
18043
- function buildComponentAnalysis(source, filePath, componentName) {
18044
- const ctx = analyzeComponent(source, filePath, componentName);
18621
+ function buildComponentAnalysis(source, filePath, componentName, program) {
18622
+ const ctx = analyzeComponent(source, filePath, componentName, program);
18045
18623
  const emptyIR = {
18046
18624
  version: "0.1",
18047
18625
  metadata: buildMetadata(ctx),
@@ -18058,8 +18636,8 @@ function buildComponentAnalysis(source, filePath, componentName) {
18058
18636
  const ir = { version: "0.1", metadata: buildMetadata(ctx), root, errors: [] };
18059
18637
  return { graph: buildGraphFromIR(ir), ir };
18060
18638
  }
18061
- function buildEventSummary(source, filePath, componentName) {
18062
- const { graph, ir } = buildComponentAnalysis(source, filePath, componentName);
18639
+ function buildEventSummary(source, filePath, componentName, program) {
18640
+ const { graph, ir } = buildComponentAnalysis(source, filePath, componentName, program);
18063
18641
  const setterToSignal = new Map;
18064
18642
  for (const s of ir.metadata.signals) {
18065
18643
  if (s.setter)
@@ -18892,8 +19470,8 @@ function formatFallbackExplanations(componentName, fallbacks) {
18892
19470
  return lines.join(`
18893
19471
  `);
18894
19472
  }
18895
- function buildComponentSummary(source, filePath, componentName) {
18896
- const { graph, ir } = buildComponentAnalysis(source, filePath, componentName);
19473
+ function buildComponentSummary(source, filePath, componentName, program) {
19474
+ const { graph, ir } = buildComponentAnalysis(source, filePath, componentName, program);
18897
19475
  const meta = ir.metadata;
18898
19476
  const clientNeeds = analyzeClientNeeds(ir);
18899
19477
  const hasReactiveState = meta.signals.length > 0 || meta.memos.length > 0 || meta.effects.length > 0;
@@ -18995,18 +19573,23 @@ function inferWrapReasonForAttrLike(hasStringReactive, hasPropsRef, flags) {
18995
19573
  const decision = decideWrapFromAstFlags(flags);
18996
19574
  return decision.wrap ? decision.reason : undefined;
18997
19575
  }
18998
- function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag) {
19576
+ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag, loopParams = new Set, readsProp = () => false) {
19577
+ const exprReadsLoopParam = (n) => loopParams.size > 0 && (n.origin?.freeRefs?.some((r) => loopParams.has(r.name)) ?? false);
19578
+ const attrReadsLoopParam = (free) => loopParams.size > 0 && free !== undefined && [...loopParams].some((p) => free.has(p));
18999
19579
  switch (node.type) {
19000
19580
  case "element": {
19001
19581
  for (const attr of node.attrs) {
19002
19582
  if (attr.value.kind !== "expression" && attr.value.kind !== "template" && attr.value.kind !== "spread")
19003
19583
  continue;
19584
+ if (attr.name === "key" && loopParams.size > 0)
19585
+ continue;
19004
19586
  const expr = attrValueToString2(attr.value);
19005
19587
  if (!expr)
19006
19588
  continue;
19007
19589
  const deps = extractReactiveDeps(expr, signalGetters, memoNames);
19008
- const isReactive = deps.length > 0;
19009
- const wrapReason = inferWrapReasonForAttrLike(isReactive, false, attr);
19590
+ const isReactive = deps.length > 0 || attrReadsLoopParam(attr.freeIdentifiers);
19591
+ const hasPropsRef = readsProp(expr, attr.freeIdentifiers);
19592
+ const wrapReason = inferWrapReasonForAttrLike(isReactive, hasPropsRef, attr);
19010
19593
  if (wrapReason) {
19011
19594
  bindings.push({
19012
19595
  kind: "dom",
@@ -19014,7 +19597,7 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19014
19597
  slotId: node.slotId ?? "?",
19015
19598
  deps,
19016
19599
  type: "attribute",
19017
- classification: isReactive ? "reactive" : "fallback",
19600
+ classification: isReactive || hasPropsRef ? "reactive" : "fallback",
19018
19601
  expression: expr,
19019
19602
  wrapReason,
19020
19603
  loc: attr.loc,
@@ -19035,13 +19618,14 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19035
19618
  });
19036
19619
  }
19037
19620
  for (const child of node.children) {
19038
- collectDomBindings(child, bindings, signalGetters, memoNames, node.tag);
19621
+ collectDomBindings(child, bindings, signalGetters, memoNames, node.tag, loopParams, readsProp);
19039
19622
  }
19040
19623
  break;
19041
19624
  }
19042
19625
  case "expression": {
19043
19626
  const decision = decideWrapFromAstFlags(node);
19044
- if (decision.wrap && node.slotId) {
19627
+ const loopReactive = exprReadsLoopParam(node);
19628
+ if ((decision.wrap || loopReactive) && node.slotId) {
19045
19629
  const deps = extractReactiveDeps(node.expr, signalGetters, memoNames);
19046
19630
  const preview = parentTag ? `<${parentTag}>{${truncateExpr(node.expr)}}</${parentTag}>` : `{${truncateExpr(node.expr)}}`;
19047
19631
  bindings.push({
@@ -19050,9 +19634,9 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19050
19634
  slotId: node.slotId,
19051
19635
  deps,
19052
19636
  type: "text",
19053
- classification: decision.reason === "proven-reactive" ? "reactive" : "fallback",
19637
+ classification: decision.wrap && decision.reason === "proven-reactive" || loopReactive ? "reactive" : "fallback",
19054
19638
  expression: node.expr,
19055
- wrapReason: decision.reason,
19639
+ wrapReason: decision.wrap ? decision.reason : "string-reactive",
19056
19640
  loc: node.loc,
19057
19641
  jsxPreview: preview
19058
19642
  });
@@ -19061,7 +19645,8 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19061
19645
  }
19062
19646
  case "conditional": {
19063
19647
  const decision = decideWrapFromAstFlags(node);
19064
- if (decision.wrap && node.slotId) {
19648
+ const loopReactive = loopParams.size > 0 && (node.origin?.freeRefs?.some((r) => loopParams.has(r.name)) ?? false);
19649
+ if ((decision.wrap || loopReactive) && node.slotId) {
19065
19650
  const deps = extractReactiveDeps(node.condition, signalGetters, memoNames);
19066
19651
  bindings.push({
19067
19652
  kind: "dom",
@@ -19069,24 +19654,25 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19069
19654
  slotId: node.slotId,
19070
19655
  deps,
19071
19656
  type: "conditional",
19072
- classification: decision.reason === "proven-reactive" ? "reactive" : "fallback",
19657
+ classification: decision.wrap && decision.reason === "proven-reactive" || loopReactive ? "reactive" : "fallback",
19073
19658
  expression: node.condition,
19074
- wrapReason: decision.reason,
19659
+ wrapReason: decision.wrap ? decision.reason : "string-reactive",
19075
19660
  loc: node.loc,
19076
19661
  jsxPreview: `{${truncateExpr(node.condition)} ? ... : ...}`
19077
19662
  });
19078
19663
  }
19079
- collectDomBindings(node.whenTrue, bindings, signalGetters, memoNames, parentTag);
19080
- collectDomBindings(node.whenFalse, bindings, signalGetters, memoNames, parentTag);
19664
+ collectDomBindings(node.whenTrue, bindings, signalGetters, memoNames, parentTag, loopParams, readsProp);
19665
+ collectDomBindings(node.whenFalse, bindings, signalGetters, memoNames, parentTag, loopParams, readsProp);
19081
19666
  break;
19082
19667
  }
19083
19668
  case "loop": {
19084
19669
  if (node.slotId) {
19085
19670
  const deps = extractReactiveDeps(node.array, signalGetters, memoNames);
19086
- const isReactive = deps.length > 0 || node.callsReactiveGetters === true;
19671
+ const loopReactive = loopParams.size > 0 && node.arrayFreeIdentifiers !== undefined && [...loopParams].some((p) => node.arrayFreeIdentifiers.has(p));
19672
+ const isReactive = deps.length > 0 || node.callsReactiveGetters === true || loopReactive;
19087
19673
  const isFallback = !isReactive && node.hasFunctionCalls === true;
19088
19674
  if (isReactive || isFallback) {
19089
- const wrapReason = deps.length > 0 ? "string-reactive" : node.callsReactiveGetters ? "proven-reactive" : "fallback-function-calls";
19675
+ const wrapReason = deps.length > 0 || loopReactive ? "string-reactive" : node.callsReactiveGetters ? "proven-reactive" : "fallback-function-calls";
19090
19676
  bindings.push({
19091
19677
  kind: "dom",
19092
19678
  label: `loop "${node.slotId}"`,
@@ -19101,8 +19687,13 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19101
19687
  });
19102
19688
  }
19103
19689
  }
19690
+ const childLoopParams = new Set(loopParams);
19691
+ for (const p of extractLoopParamNames(node.param, node))
19692
+ childLoopParams.add(p);
19693
+ if (node.index)
19694
+ childLoopParams.add(node.index);
19104
19695
  for (const child of node.children) {
19105
- collectDomBindings(child, bindings, signalGetters, memoNames, parentTag);
19696
+ collectDomBindings(child, bindings, signalGetters, memoNames, parentTag, childLoopParams, readsProp);
19106
19697
  }
19107
19698
  break;
19108
19699
  }
@@ -19116,7 +19707,7 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19116
19707
  if (!propValue)
19117
19708
  continue;
19118
19709
  const deps = extractReactiveDeps(propValue, signalGetters, memoNames);
19119
- const hasPropsRef = propValue.includes("props.");
19710
+ const hasPropsRef = readsProp(propValue, prop.freeIdentifiers);
19120
19711
  const isReactive = deps.length > 0 || hasPropsRef;
19121
19712
  const wrapReason = inferWrapReasonForAttrLike(deps.length > 0, hasPropsRef, prop);
19122
19713
  if (wrapReason) {
@@ -19135,21 +19726,21 @@ function collectDomBindings(node, bindings, signalGetters, memoNames, parentTag)
19135
19726
  }
19136
19727
  }
19137
19728
  for (const child of node.children) {
19138
- collectDomBindings(child, bindings, signalGetters, memoNames, parentTag);
19729
+ collectDomBindings(child, bindings, signalGetters, memoNames, parentTag, loopParams, readsProp);
19139
19730
  }
19140
19731
  break;
19141
19732
  }
19142
19733
  case "fragment":
19143
19734
  case "provider": {
19144
19735
  for (const child of node.children) {
19145
- collectDomBindings(child, bindings, signalGetters, memoNames, parentTag);
19736
+ collectDomBindings(child, bindings, signalGetters, memoNames, parentTag, loopParams, readsProp);
19146
19737
  }
19147
19738
  break;
19148
19739
  }
19149
19740
  case "if-statement": {
19150
- collectDomBindings(node.consequent, bindings, signalGetters, memoNames, parentTag);
19741
+ collectDomBindings(node.consequent, bindings, signalGetters, memoNames, parentTag, loopParams, readsProp);
19151
19742
  if (node.alternate) {
19152
- collectDomBindings(node.alternate, bindings, signalGetters, memoNames, parentTag);
19743
+ collectDomBindings(node.alternate, bindings, signalGetters, memoNames, parentTag, loopParams, readsProp);
19153
19744
  }
19154
19745
  break;
19155
19746
  }
@@ -19159,6 +19750,26 @@ function truncateExpr(expr, max = 40) {
19159
19750
  const s = expr.replace(/\s+/g, " ").trim();
19160
19751
  return s.length > max ? s.slice(0, max - 1) + "…" : s;
19161
19752
  }
19753
+ function exprReadsPropMember(expr, propsObjectName) {
19754
+ let sf;
19755
+ try {
19756
+ sf = ts19.createSourceFile("__attr.tsx", `(${expr})`, ts19.ScriptTarget.Latest, true, ts19.ScriptKind.TSX);
19757
+ } catch {
19758
+ return false;
19759
+ }
19760
+ let found = false;
19761
+ const visit3 = (n) => {
19762
+ if (found)
19763
+ return;
19764
+ if (ts19.isPropertyAccessExpression(n) && ts19.isIdentifier(n.expression) && n.expression.text === propsObjectName && n.name.text !== "children") {
19765
+ found = true;
19766
+ return;
19767
+ }
19768
+ ts19.forEachChild(n, visit3);
19769
+ };
19770
+ visit3(sf);
19771
+ return found;
19772
+ }
19162
19773
  function attrValueToString2(value) {
19163
19774
  switch (value.kind) {
19164
19775
  case "literal":
@@ -19223,8 +19834,1068 @@ function findSourceFile2(meta) {
19223
19834
  }
19224
19835
  return null;
19225
19836
  }
19837
+ // src/profiler.ts
19838
+ import ts20 from "typescript";
19839
+ var DEFAULT_FANOUT_THRESHOLD = 8;
19840
+ function buildStaticBudget(source, filePath, componentName, options = {}) {
19841
+ const threshold = options.fanOutThreshold ?? DEFAULT_FANOUT_THRESHOLD;
19842
+ const program = createProgramForFile(source, filePath)?.program;
19843
+ const { graph } = buildComponentAnalysis(source, filePath, componentName, program);
19844
+ const summary = buildComponentSummary(source, filePath, componentName, program);
19845
+ const subscriptions = graph.signals.reduce((n, s) => n + s.consumers.filter((c) => !isEventHandlerConsumer(c)).length, 0);
19846
+ const fanOut = graph.signals.map((s) => {
19847
+ const subscribers = transitiveSubscriberCount(graph, s.name);
19848
+ return { signal: s.name, subscribers, hot: subscribers >= threshold, loc: s.loc };
19849
+ }).sort((a, b) => b.subscribers - a.subscribers);
19850
+ const { depth, chain } = longestMemoChain(graph);
19851
+ const hasReactiveState = graph.signals.length > 0 || graph.memos.length > 0;
19852
+ const observedInComponent = graph.signals.some((s) => transitiveSubscriberCount(graph, s.name) > 0) || graph.memos.some((m) => transitiveSubscriberCount(graph, m.name) > 0);
19853
+ const crossComponentOnly = hasReactiveState && !observedInComponent;
19854
+ return {
19855
+ componentName: summary.componentName,
19856
+ sourceFile: summary.sourceFile,
19857
+ kind: "static-budget",
19858
+ signals: graph.signals.length,
19859
+ memos: graph.memos.length,
19860
+ effects: graph.effects.length,
19861
+ loops: summary.loops,
19862
+ subscriptions,
19863
+ memoChainDepth: depth,
19864
+ memoChainLongest: chain,
19865
+ fanOut,
19866
+ crossComponentOnly
19867
+ };
19868
+ }
19869
+ function isEventHandlerEntry(kind, name) {
19870
+ return kind === "dom" && /^\w+ handler "/.test(name);
19871
+ }
19872
+ function isEventHandlerConsumer(consumer) {
19873
+ const i = consumer.indexOf(":");
19874
+ return i > 0 && isEventHandlerEntry(consumer.slice(0, i), consumer.slice(i + 1));
19875
+ }
19876
+ function transitiveSubscriberCount(graph, name) {
19877
+ const path = traceUpdatePath(graph, name);
19878
+ if (!path)
19879
+ return 0;
19880
+ const seen = new Set;
19881
+ const walk = (entries) => {
19882
+ for (const e of entries) {
19883
+ if (!isEventHandlerEntry(e.kind, e.name))
19884
+ seen.add(`${e.kind}:${e.name}`);
19885
+ walk(e.children);
19886
+ }
19887
+ };
19888
+ walk(path.dependents);
19889
+ return seen.size;
19890
+ }
19891
+ function longestMemoChain(graph) {
19892
+ const memoChainFrom = (entry) => {
19893
+ if (entry.kind !== "memo")
19894
+ return [];
19895
+ let best2 = [];
19896
+ for (const child of entry.children) {
19897
+ const c = memoChainFrom(child);
19898
+ if (c.length > best2.length)
19899
+ best2 = c;
19900
+ }
19901
+ return [entry.name, ...best2];
19902
+ };
19903
+ let best = [];
19904
+ for (const memo of graph.memos) {
19905
+ const path = traceUpdatePath(graph, memo.name);
19906
+ let downstream = [];
19907
+ for (const dep of path?.dependents ?? []) {
19908
+ const c = memoChainFrom(dep);
19909
+ if (c.length > downstream.length)
19910
+ downstream = c;
19911
+ }
19912
+ const chain = [memo.name, ...downstream];
19913
+ if (chain.length > best.length)
19914
+ best = chain;
19915
+ }
19916
+ return { depth: best.length, chain: best };
19917
+ }
19918
+ function formatStaticBudget(b) {
19919
+ const lines = [];
19920
+ lines.push(`${b.componentName} — static reactivity budget`);
19921
+ lines.push(` signals: ${b.signals} memos: ${b.memos} effects: ${b.effects} loops: ${b.loops}`);
19922
+ lines.push(` subscriptions: ${b.subscriptions}`);
19923
+ if (b.memoChainDepth > 0) {
19924
+ lines.push(` memo-chain depth: ${b.memoChainDepth} (${b.memoChainLongest.join(" → ")})`);
19925
+ }
19926
+ const shown = b.fanOut.filter((f) => f.subscribers > 0).slice(0, 5);
19927
+ if (shown.length > 0) {
19928
+ lines.push(" fan-out (top):");
19929
+ for (const f of shown) {
19930
+ lines.push(` ${f.signal.padEnd(12)} → ${f.subscribers} subscribers${f.hot ? " ⚠ high" : ""}`);
19931
+ }
19932
+ }
19933
+ if (b.crossComponentOnly) {
19934
+ lines.push(` ⓘ compound: ${b.signals} signal(s) / ${b.memos} memo(s) but 0 in-component subscriptions —`);
19935
+ lines.push(" consumers are likely in composed child components (or it is read only from handlers);");
19936
+ lines.push(" run with --scenario to measure across the composition boundary.");
19937
+ }
19938
+ lines.push(" note: run with --scenario to measure actual cost; static budget is predictive only.");
19939
+ return lines.join(`
19940
+ `);
19941
+ }
19942
+ function diffStaticBudget(base, head) {
19943
+ const baseFan = new Map(base.fanOut.map((f) => [f.signal, f.subscribers]));
19944
+ const headFan = new Map(head.fanOut.map((f) => [f.signal, f.subscribers]));
19945
+ const signals = new Set([...baseFan.keys(), ...headFan.keys()]);
19946
+ const fanOut = [];
19947
+ for (const sig of signals) {
19948
+ const before = baseFan.get(sig) ?? 0;
19949
+ const after = headFan.get(sig) ?? 0;
19950
+ if (before !== after)
19951
+ fanOut.push({ signal: sig, before, after });
19952
+ }
19953
+ fanOut.sort((a, b) => b.after - b.before - (a.after - a.before));
19954
+ const d = {
19955
+ kind: "diff",
19956
+ componentName: head.componentName,
19957
+ signals: head.signals - base.signals,
19958
+ memos: head.memos - base.memos,
19959
+ effects: head.effects - base.effects,
19960
+ loops: head.loops - base.loops,
19961
+ subscriptions: head.subscriptions - base.subscriptions,
19962
+ memoChainDepth: head.memoChainDepth - base.memoChainDepth,
19963
+ fanOut
19964
+ };
19965
+ const regressed = d.signals > 0 || d.memos > 0 || d.effects > 0 || d.subscriptions > 0 || d.memoChainDepth > 0 || fanOut.some((f) => f.after > f.before);
19966
+ return { ...d, regressed };
19967
+ }
19968
+ function formatBudgetDiff(d) {
19969
+ const lines = [];
19970
+ lines.push(`${d.componentName} — reactivity diff`);
19971
+ const metric = (label, v) => {
19972
+ if (v !== 0)
19973
+ lines.push(` ${v > 0 ? "+" : ""}${v} ${label}`);
19974
+ };
19975
+ metric("signals", d.signals);
19976
+ metric("memos", d.memos);
19977
+ metric("effects", d.effects);
19978
+ metric("loops", d.loops);
19979
+ metric("subscriptions", d.subscriptions);
19980
+ if (d.memoChainDepth !== 0) {
19981
+ lines.push(` memo chain ${d.memoChainDepth > 0 ? "deepened" : "shortened"} by ${Math.abs(d.memoChainDepth)}`);
19982
+ }
19983
+ for (const f of d.fanOut) {
19984
+ lines.push(` signal \`${f.signal}\` fan-out ${f.before}→${f.after}`);
19985
+ }
19986
+ if (lines.length === 1)
19987
+ lines.push(" no structural reactivity change");
19988
+ else
19989
+ lines.push(d.regressed ? " ⚠ reactivity regressed" : " ✓ no regression");
19990
+ return lines.join(`
19991
+ `);
19992
+ }
19993
+ function parseProfilerId(id) {
19994
+ const hash = id.indexOf("#");
19995
+ if (hash < 0)
19996
+ return null;
19997
+ const colon = id.indexOf(":", hash);
19998
+ if (colon < 0)
19999
+ return null;
20000
+ const component = id.slice(0, hash);
20001
+ const kind = id.slice(hash + 1, colon);
20002
+ const rest = id.slice(colon + 1);
20003
+ if (!component || !kind || !rest)
20004
+ return null;
20005
+ return { component, kind, rest };
20006
+ }
20007
+ function buildIdIndex(graph) {
20008
+ const comp = graph.componentName;
20009
+ const index = new Map;
20010
+ for (const s of graph.signals) {
20011
+ index.set(`${comp}#signal:${s.name}`, { kind: "signal", name: s.name, loc: s.loc });
20012
+ if (s.setter) {
20013
+ index.set(`${comp}#effect:controlled:${s.setter}`, {
20014
+ kind: "effect",
20015
+ name: `controlled:${s.setter}`,
20016
+ loc: s.loc
20017
+ });
20018
+ }
20019
+ }
20020
+ for (const m of graph.memos) {
20021
+ index.set(`${comp}#memo:${m.name}`, { kind: "memo", name: m.name, loc: m.loc });
20022
+ }
20023
+ for (const e of graph.effects) {
20024
+ const node = { kind: "effect", name: e.label, loc: e.loc };
20025
+ index.set(`${comp}#effect:${e.loc.line}`, node);
20026
+ index.set(`${comp}#effect:${e.label}`, node);
20027
+ }
20028
+ for (const b of graph.domBindings) {
20029
+ if (!b.loc)
20030
+ continue;
20031
+ const loc = { file: b.loc.file, line: b.loc.start.line };
20032
+ if (b.type === "event") {
20033
+ const eventName = b.label.match(/^(\w+)\s+handler/)?.[1];
20034
+ if (eventName) {
20035
+ index.set(`${comp}#handler:${b.slotId}:${eventName}`, {
20036
+ kind: "handler",
20037
+ name: `${eventName}@${b.slotId}`,
20038
+ loc
20039
+ });
20040
+ }
20041
+ continue;
20042
+ }
20043
+ index.set(`${comp}#binding:${b.slotId}`, { kind: "effect", name: `${b.slotId} (${b.type})`, loc });
20044
+ }
20045
+ return index;
20046
+ }
20047
+ function isRuntimeBookkeepingId(id) {
20048
+ return /^[serm]\d+$/.test(id);
20049
+ }
20050
+ function isFallbackEffectId(id) {
20051
+ return /^e\d+$/.test(id);
20052
+ }
20053
+ function joinProfilerEvents(events, index) {
20054
+ const joined = [];
20055
+ const gaps = new Map;
20056
+ const resolve2 = (id) => {
20057
+ if (id === undefined)
20058
+ return;
20059
+ const node = index.get(id);
20060
+ if (!node)
20061
+ gaps.set(id, (gaps.get(id) ?? 0) + 1);
20062
+ return node;
20063
+ };
20064
+ for (const event of events) {
20065
+ joined.push({
20066
+ event,
20067
+ subscriber: resolve2(event.subscriber),
20068
+ signal: resolve2(event.signal)
20069
+ });
20070
+ }
20071
+ const unattributed = [];
20072
+ const diagnostics = [];
20073
+ for (const [id, count] of gaps.entries()) {
20074
+ (isRuntimeBookkeepingId(id) ? diagnostics : unattributed).push({ id, count });
20075
+ }
20076
+ const byCount = (a, b) => b.count - a.count;
20077
+ unattributed.sort(byCount);
20078
+ diagnostics.sort(byCount);
20079
+ return { joined, unattributed, diagnostics };
20080
+ }
20081
+ function findUninstrumentedEffects(source, filePath, instrumentedLines) {
20082
+ const sf = ts20.createSourceFile(filePath, source, ts20.ScriptTarget.Latest, true, ts20.ScriptKind.TSX);
20083
+ const out = [];
20084
+ const visit3 = (node) => {
20085
+ if (ts20.isCallExpression(node) && ts20.isIdentifier(node.expression) && node.expression.text === "createEffect") {
20086
+ const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
20087
+ if (!instrumentedLines.has(line))
20088
+ out.push({ file: filePath, line });
20089
+ }
20090
+ ts20.forEachChild(node, visit3);
20091
+ };
20092
+ visit3(sf);
20093
+ out.sort((a, b) => a.line - b.line);
20094
+ return out;
20095
+ }
20096
+ var DEFAULT_HOT_RUNS_PER_TURN = 2;
20097
+ function analyzeHotSubscribers(events, index, options = {}) {
20098
+ const threshold = options.hotRunsPerTurn ?? DEFAULT_HOT_RUNS_PER_TURN;
20099
+ const byId = new Map;
20100
+ const acc = (id) => {
20101
+ let a = byId.get(id);
20102
+ if (!a) {
20103
+ a = { runs: 0, mountRuns: 0, totalMs: 0, turns: new Set };
20104
+ byId.set(id, a);
20105
+ }
20106
+ return a;
20107
+ };
20108
+ for (const e of events) {
20109
+ if (e.subscriber === undefined)
20110
+ continue;
20111
+ if (e.type === "effectEnter") {
20112
+ const a = acc(e.subscriber);
20113
+ a.runs++;
20114
+ if (e.turn === null)
20115
+ a.mountRuns++;
20116
+ else
20117
+ a.turns.add(String(e.turnSeq ?? e.turn));
20118
+ } else if (e.type === "effectExit" && e.dur !== undefined) {
20119
+ acc(e.subscriber).totalMs += e.dur;
20120
+ }
20121
+ }
20122
+ const { joined, unattributed } = joinProfilerEvents(events, index);
20123
+ const nodeFor = new Map;
20124
+ for (const j of joined) {
20125
+ if (j.event.subscriber !== undefined && j.subscriber)
20126
+ nodeFor.set(j.event.subscriber, j.subscriber);
20127
+ }
20128
+ let subscribers = [...byId.entries()].map(([subscriber, a]) => {
20129
+ const node = nodeFor.get(subscriber);
20130
+ const turns = a.turns.size;
20131
+ const interactionRuns = a.runs - a.mountRuns;
20132
+ const runsPerTurn = turns > 0 ? interactionRuns / turns : 0;
20133
+ const uninstrumented = !node && isFallbackEffectId(subscriber);
20134
+ return {
20135
+ subscriber,
20136
+ loc: node?.loc,
20137
+ name: node?.name,
20138
+ kind: node?.kind,
20139
+ runs: a.runs,
20140
+ mountRuns: a.mountRuns,
20141
+ totalMs: a.totalMs,
20142
+ turns,
20143
+ runsPerTurn,
20144
+ hot: runsPerTurn >= threshold,
20145
+ ...uninstrumented ? {
20146
+ resolution: "uninstrumented",
20147
+ resolutionNote: "createEffect in non-JSX function scope (not attributed by compiler)",
20148
+ candidates: [...options.uninstrumentedCandidates ?? []]
20149
+ } : {}
20150
+ };
20151
+ });
20152
+ const roundMs = (m) => Math.round(m * 10) / 10;
20153
+ subscribers.sort((x, y) => roundMs(y.totalMs) - roundMs(x.totalMs) || y.runs - x.runs || (x.subscriber < y.subscriber ? -1 : x.subscriber > y.subscriber ? 1 : 0));
20154
+ if (options.minMs !== undefined)
20155
+ subscribers = subscribers.filter((s) => roundMs(s.totalMs) >= options.minMs);
20156
+ if (options.topN !== undefined)
20157
+ subscribers = subscribers.slice(0, options.topN);
20158
+ const subscriberIds = new Set(byId.keys());
20159
+ const gaps = unattributed.filter((u) => subscriberIds.has(u.id));
20160
+ return { kind: "hot-subscribers", subscribers, unattributed: gaps };
20161
+ }
20162
+ var BAR_EIGHTHS = ["", "▏", "▎", "▍", "▌", "▋", "▊", "▉"];
20163
+ function fitLabel(s, width) {
20164
+ return s.length > width ? `${s.slice(0, width - 1)}…` : s.padEnd(width);
20165
+ }
20166
+ function bar(value, max, width) {
20167
+ if (max <= 0 || value <= 0)
20168
+ return "".padEnd(width);
20169
+ const units = Math.min(value / max, 1) * width;
20170
+ let full = Math.floor(units);
20171
+ let rem = Math.round((units - full) * 8);
20172
+ if (rem === 8) {
20173
+ full++;
20174
+ rem = 0;
20175
+ }
20176
+ return ("█".repeat(full) + (rem > 0 ? BAR_EIGHTHS[rem] : "")).padEnd(width);
20177
+ }
20178
+ function formatEffectCandidates(candidates) {
20179
+ const byFile = new Map;
20180
+ for (const c of candidates) {
20181
+ const arr = byFile.get(c.file) ?? [];
20182
+ arr.push(c.line);
20183
+ byFile.set(c.file, arr);
20184
+ }
20185
+ const groups = [];
20186
+ for (const [file, ls] of byFile) {
20187
+ const sorted = [...new Set(ls)].sort((a, b) => a - b);
20188
+ groups.push(`${file}:${sorted[0]}${sorted.slice(1).map((l) => `, :${l}`).join("")}`);
20189
+ }
20190
+ return groups.join("; ");
20191
+ }
20192
+ function formatHotSubscribers(r, limit = 12) {
20193
+ const lines = [];
20194
+ lines.push("hot subscribers — most run / most time");
20195
+ if (r.subscribers.length === 0) {
20196
+ lines.push(" (no effect/memo runs recorded)");
20197
+ }
20198
+ const shown = r.subscribers.slice(0, limit);
20199
+ const maxMs = shown.reduce((m, s) => Math.max(m, s.totalMs), 0);
20200
+ for (const s of shown) {
20201
+ const where = s.loc ? `${s.loc.file}:${s.loc.line}` : s.resolution === "uninstrumented" ? "uninstrumented — createEffect in non-JSX scope" : "unresolved";
20202
+ const base = s.name ?? s.subscriber;
20203
+ const label = s.kind && !base.endsWith(")") ? `${base} (${s.kind})` : base;
20204
+ const note = s.hot ? ` ⚠ ${s.runsPerTurn.toFixed(1)}/turn` : "";
20205
+ lines.push(` ${fitLabel(label, 24)} ${bar(s.totalMs, maxMs, 14)} ${s.totalMs.toFixed(1)}ms ${String(s.runs).padStart(3)}× (${where})${note}`);
20206
+ if (s.candidates && s.candidates.length > 0) {
20207
+ lines.push(`${" ".repeat(26)}candidates: ${formatEffectCandidates(s.candidates)}`);
20208
+ }
20209
+ }
20210
+ if (r.subscribers.length > shown.length) {
20211
+ lines.push(` … and ${r.subscribers.length - shown.length} more`);
20212
+ }
20213
+ if (r.unattributed.length > 0) {
20214
+ lines.push(` ⚠ coverage: ${r.unattributed.length} unresolved subscriber id(s)`);
20215
+ }
20216
+ return lines.join(`
20217
+ `);
20218
+ }
20219
+ var DEFAULT_WASTED_RATIO = 0.5;
20220
+ function analyzeWastedReReruns(events, index, options = {}) {
20221
+ const threshold = options.wastedRatio ?? DEFAULT_WASTED_RATIO;
20222
+ const byId = new Map;
20223
+ for (const e of events) {
20224
+ if (e.type !== "effectOutput" || e.subscriber === undefined || e.changed === undefined)
20225
+ continue;
20226
+ let a = byId.get(e.subscriber);
20227
+ if (!a) {
20228
+ a = { total: 0, wasted: 0 };
20229
+ byId.set(e.subscriber, a);
20230
+ }
20231
+ a.total++;
20232
+ if (!e.changed)
20233
+ a.wasted++;
20234
+ }
20235
+ const { unattributed } = joinProfilerEvents(events, index);
20236
+ const nodeFor = new Map;
20237
+ for (const [id] of byId) {
20238
+ const node = index.get(id);
20239
+ if (node)
20240
+ nodeFor.set(id, node);
20241
+ }
20242
+ let subscribers = [...byId.entries()].map(([subscriber, a]) => {
20243
+ const node = nodeFor.get(subscriber);
20244
+ const wastedRatio = a.total > 0 ? a.wasted / a.total : 0;
20245
+ return {
20246
+ subscriber,
20247
+ loc: node?.loc,
20248
+ name: node?.name,
20249
+ kind: node?.kind,
20250
+ totalRuns: a.total,
20251
+ wastedRuns: a.wasted,
20252
+ wastedRatio,
20253
+ wasted: wastedRatio >= threshold
20254
+ };
20255
+ }).filter((s) => s.wastedRuns > 0);
20256
+ subscribers.sort((x, y) => y.wastedRuns - x.wastedRuns || y.wastedRatio - x.wastedRatio || (x.subscriber < y.subscriber ? -1 : x.subscriber > y.subscriber ? 1 : 0));
20257
+ if (options.topN !== undefined)
20258
+ subscribers = subscribers.slice(0, options.topN);
20259
+ const subscriberIds = new Set(byId.keys());
20260
+ const gaps = unattributed.filter((u) => subscriberIds.has(u.id));
20261
+ return { kind: "wasted-re-runs", subscribers, unattributed: gaps };
20262
+ }
20263
+ function wastedOutputNoun(kind) {
20264
+ return kind === "memo" ? "identical value" : "identical DOM";
20265
+ }
20266
+ function formatWastedReReruns(r, limit = 12) {
20267
+ const lines = [];
20268
+ lines.push("wasted re-runs — re-ran but produced identical output");
20269
+ if (r.subscribers.length === 0) {
20270
+ lines.push(" (no wasted re-runs recorded)");
20271
+ }
20272
+ const shown = r.subscribers.slice(0, limit);
20273
+ const maxWasted = shown.reduce((m, s) => Math.max(m, s.wastedRuns), 0);
20274
+ for (const s of shown) {
20275
+ const where = s.loc ? `${s.loc.file}:${s.loc.line}` : "(unresolved)";
20276
+ const base = s.name ?? s.subscriber;
20277
+ const label = s.kind && !base.endsWith(")") ? `${base} (${s.kind})` : base;
20278
+ const pct = Math.round(s.wastedRatio * 100);
20279
+ const note = s.wasted ? " ⚠ split so it doesn’t re-run on unrelated changes" : "";
20280
+ lines.push(` ${fitLabel(label, 24)} ${bar(s.wastedRuns, maxWasted, 14)} wasted: ${s.wastedRuns}/${s.totalRuns} produced ${wastedOutputNoun(s.kind)} (${pct}%) (${where})${note}`);
20281
+ }
20282
+ if (r.subscribers.length > shown.length) {
20283
+ lines.push(` … and ${r.subscribers.length - shown.length} more`);
20284
+ }
20285
+ if (r.unattributed.length > 0) {
20286
+ lines.push(` ⚠ coverage: ${r.unattributed.length} unresolved subscriber id(s)`);
20287
+ }
20288
+ return lines.join(`
20289
+ `);
20290
+ }
20291
+ function analyzeBatchAdvisor(events, index) {
20292
+ const byInvocation = new Map;
20293
+ const get = (e) => {
20294
+ if (e.turn === null)
20295
+ return;
20296
+ const key = String(e.turnSeq ?? e.turn);
20297
+ let acc = byInvocation.get(key);
20298
+ if (!acc) {
20299
+ acc = { handlerId: e.turn, totalRuns: 0, writes: 0, depth: 0, subscribers: new Set };
20300
+ byInvocation.set(key, acc);
20301
+ }
20302
+ return acc;
20303
+ };
20304
+ for (const e of events) {
20305
+ if (e.type === "signalSet") {
20306
+ const acc = get(e);
20307
+ if (acc && acc.depth === 0)
20308
+ acc.writes++;
20309
+ } else if (e.type === "effectEnter") {
20310
+ const acc = get(e);
20311
+ if (!acc)
20312
+ continue;
20313
+ acc.totalRuns++;
20314
+ acc.depth++;
20315
+ if (e.subscriber !== undefined)
20316
+ acc.subscribers.add(e.subscriber);
20317
+ } else if (e.type === "effectExit") {
20318
+ const acc = get(e);
20319
+ if (acc && acc.depth > 0)
20320
+ acc.depth--;
20321
+ }
20322
+ }
20323
+ const byHandler = new Map;
20324
+ for (const acc of byInvocation.values()) {
20325
+ if (acc.writes < 2)
20326
+ continue;
20327
+ const distinctSubscribers = acc.subscribers.size;
20328
+ const savings = acc.totalRuns - distinctSubscribers;
20329
+ if (savings <= 0)
20330
+ continue;
20331
+ const prev = byHandler.get(acc.handlerId);
20332
+ if (prev && prev.savings >= savings)
20333
+ continue;
20334
+ const node = index?.get(acc.handlerId);
20335
+ byHandler.set(acc.handlerId, {
20336
+ turn: acc.handlerId,
20337
+ loc: node?.loc,
20338
+ handler: node?.name,
20339
+ totalRuns: acc.totalRuns,
20340
+ distinctSubscribers,
20341
+ writes: acc.writes,
20342
+ savings,
20343
+ safety: "unverified"
20344
+ });
20345
+ }
20346
+ const candidates = [...byHandler.values()];
20347
+ candidates.sort((a, b) => b.savings - a.savings || b.totalRuns - a.totalRuns);
20348
+ return { kind: "batch-advisor", candidates };
20349
+ }
20350
+ function formatBatchAdvisor(r) {
20351
+ const lines = [];
20352
+ lines.push("batch advisor — unbatched multi-write turns");
20353
+ if (r.candidates.length === 0) {
20354
+ lines.push(" (no turn would benefit from batching)");
20355
+ }
20356
+ const maxSavings = r.candidates.reduce((m, c) => Math.max(m, c.savings), 0);
20357
+ for (const c of r.candidates) {
20358
+ const safe = c.safety === "safe" ? ", safe" : c.safety === "unsafe" ? ", UNSAFE" : ", safety unverified";
20359
+ const where = c.loc ? ` (${c.loc.file}:${c.loc.line})` : "";
20360
+ const label = fitLabel(c.handler ?? c.turn, 24);
20361
+ lines.push(` ${label} ${bar(c.savings, maxSavings, 14)} batch candidate ${c.totalRuns}→${c.distinctSubscribers} (saves ${c.savings}${safe})${where}`);
20362
+ }
20363
+ return lines.join(`
20364
+ `);
20365
+ }
20366
+ function downstreamMemos(graph, written) {
20367
+ const byName = new Map(graph.memos.map((m) => [m.name, m]));
20368
+ const result = new Set;
20369
+ const dependsOnWritten = (memoName, seen) => {
20370
+ if (seen.has(memoName))
20371
+ return false;
20372
+ seen.add(memoName);
20373
+ const m = byName.get(memoName);
20374
+ if (!m)
20375
+ return false;
20376
+ for (const dep of m.deps) {
20377
+ if (written.has(dep))
20378
+ return true;
20379
+ if (byName.has(dep) && dependsOnWritten(dep, seen))
20380
+ return true;
20381
+ }
20382
+ return false;
20383
+ };
20384
+ for (const m of graph.memos)
20385
+ if (dependsOnWritten(m.name, new Set))
20386
+ result.add(m.name);
20387
+ return result;
20388
+ }
20389
+ function assessBatchSafety(args) {
20390
+ if (args.hasIndirectSetters || args.setterNames.length === 0)
20391
+ return "unverified";
20392
+ const D = downstreamMemos(args.graph, new Set(args.writtenSignals));
20393
+ const setters = new Set(args.setterNames);
20394
+ const memoNames = new Set(args.graph.memos.map((m) => m.name));
20395
+ const signalGetters = new Set(args.graph.signals.map((s) => s.name));
20396
+ let sf;
20397
+ try {
20398
+ sf = ts20.createSourceFile("__h.ts", `const __h = ${args.handler}`, ts20.ScriptTarget.Latest, true);
20399
+ } catch {
20400
+ return "unverified";
20401
+ }
20402
+ const calls = [];
20403
+ const visit3 = (node) => {
20404
+ if (ts20.isCallExpression(node) && ts20.isIdentifier(node.expression)) {
20405
+ const name = node.expression.text;
20406
+ if (setters.has(name))
20407
+ calls.push({ pos: node.getStart(sf), kind: "write" });
20408
+ else if (D.has(name) && node.arguments.length === 0)
20409
+ calls.push({ pos: node.getStart(sf), kind: "memoRead" });
20410
+ else if (!signalGetters.has(name) && !memoNames.has(name))
20411
+ calls.push({ pos: node.getStart(sf), kind: "risky" });
20412
+ }
20413
+ ts20.forEachChild(node, visit3);
20414
+ };
20415
+ visit3(sf);
20416
+ calls.sort((a, b) => a.pos - b.pos);
20417
+ let seenWrite = false;
20418
+ let risky = false;
20419
+ for (const c of calls) {
20420
+ if (c.kind === "write")
20421
+ seenWrite = true;
20422
+ else if (seenWrite && c.kind === "memoRead")
20423
+ return "unsafe";
20424
+ else if (seenWrite && c.kind === "risky")
20425
+ risky = true;
20426
+ }
20427
+ if (!seenWrite)
20428
+ return "unverified";
20429
+ return risky ? "unverified" : "safe";
20430
+ }
20431
+ function turnToEventBinding(graph, events) {
20432
+ const byLine = new Map;
20433
+ for (const e of events)
20434
+ if (e.loc)
20435
+ byLine.set(e.loc.start.line, e);
20436
+ const out = new Map;
20437
+ for (const b of graph.domBindings) {
20438
+ if (b.type !== "event" || !b.loc)
20439
+ continue;
20440
+ const eventName = b.label.match(/^(\w+)\s+handler/)?.[1];
20441
+ const binding = byLine.get(b.loc.start.line);
20442
+ if (eventName && binding)
20443
+ out.set(`${graph.componentName}#handler:${b.slotId}:${eventName}`, binding);
20444
+ }
20445
+ return out;
20446
+ }
20447
+ function buildProfileReport(input) {
20448
+ const { source, filePath, componentName, scenario, events } = input;
20449
+ const primary = buildComponentAnalysis(source, filePath, componentName).graph;
20450
+ const allSources = [{ source, filePath }, ...input.extraSources ?? []];
20451
+ const index = new Map;
20452
+ const turnBindings = new Map;
20453
+ let handlersTotal = 0;
20454
+ const instrumentedEffectLines = new Map;
20455
+ for (const s of allSources) {
20456
+ const program = createProgramForFile(s.source, s.filePath)?.program;
20457
+ let componentNames;
20458
+ try {
20459
+ componentNames = listComponentFunctions(s.source, s.filePath);
20460
+ } catch {
20461
+ componentNames = [];
20462
+ }
20463
+ if (componentNames.length === 0)
20464
+ componentNames = [undefined];
20465
+ for (const name of componentNames) {
20466
+ let graph;
20467
+ try {
20468
+ graph = buildComponentAnalysis(s.source, s.filePath, name, program).graph;
20469
+ } catch {
20470
+ continue;
20471
+ }
20472
+ for (const [k, v] of buildIdIndex(graph))
20473
+ index.set(k, v);
20474
+ for (const e of graph.effects) {
20475
+ const set = instrumentedEffectLines.get(e.loc.file) ?? new Set;
20476
+ set.add(e.loc.line);
20477
+ instrumentedEffectLines.set(e.loc.file, set);
20478
+ }
20479
+ try {
20480
+ const summary = buildEventSummary(s.source, s.filePath, name, program);
20481
+ handlersTotal += summary.events.length;
20482
+ for (const [turn, binding] of turnToEventBinding(graph, summary.events)) {
20483
+ turnBindings.set(turn, { binding, graph });
20484
+ }
20485
+ } catch {}
20486
+ }
20487
+ }
20488
+ const hasFallbackEffectId = events.some((e) => e.subscriber !== undefined && isFallbackEffectId(e.subscriber));
20489
+ const uninstrumentedCandidates = [];
20490
+ if (hasFallbackEffectId) {
20491
+ for (const s of allSources) {
20492
+ uninstrumentedCandidates.push(...findUninstrumentedEffects(s.source, s.filePath, instrumentedEffectLines.get(s.filePath) ?? new Set));
20493
+ }
20494
+ }
20495
+ const hotSubscribers = analyzeHotSubscribers(events, index, {
20496
+ topN: input.topN,
20497
+ minMs: input.minMs,
20498
+ uninstrumentedCandidates
20499
+ });
20500
+ const wastedReReruns = analyzeWastedReReruns(events, index, { wastedRatio: input.wastedRatio });
20501
+ const batchAdvisor = analyzeBatchAdvisor(events, index);
20502
+ const { unattributed, diagnostics } = joinProfilerEvents(events, index);
20503
+ for (const c of batchAdvisor.candidates) {
20504
+ const hit = turnBindings.get(c.turn);
20505
+ if (!hit)
20506
+ continue;
20507
+ c.safety = assessBatchSafety({
20508
+ handler: hit.binding.handler,
20509
+ setterNames: hit.binding.setterCalls.map((s) => s.setter),
20510
+ hasIndirectSetters: hit.binding.setterCalls.some((s) => s.via && s.via.length > 0),
20511
+ writtenSignals: hit.binding.setterCalls.map((s) => s.signal).filter((s) => s !== null),
20512
+ graph: hit.graph
20513
+ });
20514
+ }
20515
+ const turnSeqs = new Set;
20516
+ const handlerIds = new Set;
20517
+ for (const e of events) {
20518
+ if (e.turn !== null) {
20519
+ turnSeqs.add(String(e.turnSeq ?? e.turn));
20520
+ handlerIds.add(e.turn);
20521
+ }
20522
+ }
20523
+ return {
20524
+ kind: "profile",
20525
+ componentName: primary.componentName,
20526
+ sourceFile: primary.sourceFile,
20527
+ scenario,
20528
+ events: events.length,
20529
+ turns: turnSeqs.size,
20530
+ hotSubscribers,
20531
+ wastedReReruns,
20532
+ batchAdvisor,
20533
+ coverage: {
20534
+ handlersFired: handlerIds.size,
20535
+ handlersTotal,
20536
+ unattributed,
20537
+ diagnostics: { count: diagnostics.length, sample: diagnostics.slice(0, 3).map((d) => d.id) }
20538
+ }
20539
+ };
20540
+ }
20541
+ function formatProfileReport(r) {
20542
+ const lines = [];
20543
+ lines.push(`${r.componentName} — profile (scenario: ${r.scenario})`);
20544
+ lines.push(` ${r.events} events across ${r.turns} turn(s)`);
20545
+ if (r.turns === 0) {
20546
+ lines.push(r.coverage.handlersTotal === 0 ? " note: no event handlers — run `bf debug profile <component>` for the static budget." : " note: no interactions measured (handlers are likely in composed children) — try `--scenario <story.tsx>`.");
20547
+ }
20548
+ lines.push("");
20549
+ lines.push(formatHotSubscribers(r.hotSubscribers));
20550
+ lines.push("");
20551
+ lines.push(formatWastedReReruns(r.wastedReReruns));
20552
+ lines.push("");
20553
+ lines.push(formatBatchAdvisor(r.batchAdvisor));
20554
+ lines.push("");
20555
+ const c = r.coverage;
20556
+ lines.push(`coverage: ${c.handlersFired}/${c.handlersTotal} handlers exercised`);
20557
+ if (c.unattributed.length > 0) {
20558
+ lines.push(` ⚠ ${c.unattributed.length} unattributed id(s): ${c.unattributed.slice(0, 3).map((u) => u.id).join(", ")}`);
20559
+ }
20560
+ if (c.diagnostics.count > 0) {
20561
+ lines.push(` · ${c.diagnostics.count} anonymous runtime id(s) (non-actionable bookkeeping)`);
20562
+ }
20563
+ return lines.join(`
20564
+ `);
20565
+ }
20566
+ // src/debug-profile.ts
20567
+ var THRESHOLDS = {
20568
+ highFanOut: 3,
20569
+ deepMemoChain: 3,
20570
+ fallbackHeavyMin: 3,
20571
+ fallbackHeavyRatio: 0.5,
20572
+ batchMinSignals: 2
20573
+ };
20574
+ function buildReactiveProfile(source, filePath, componentName) {
20575
+ const graph = buildComponentGraph(source, filePath, componentName);
20576
+ const eventSummary = buildEventSummary(source, filePath, componentName);
20577
+ const hydrated = graph.signals.length > 0 || graph.memos.length > 0 || graph.effects.length > 0;
20578
+ return buildProfileFromGraph(graph, eventSummary, hydrated);
20579
+ }
20580
+ function buildProfileFromGraph(graph, eventSummary, hydrated) {
20581
+ const metrics = computeMetrics(graph, eventSummary, hydrated);
20582
+ const findings = computeFindings(metrics, graph, eventSummary);
20583
+ return { metrics, findings };
20584
+ }
20585
+ function computeMetrics(graph, eventSummary, hydrated) {
20586
+ let maxFanOut = 0;
20587
+ let hotSignal = null;
20588
+ for (const signal of graph.signals) {
20589
+ if (signal.consumers.length > maxFanOut) {
20590
+ maxFanOut = signal.consumers.length;
20591
+ hotSignal = signal.name;
20592
+ }
20593
+ }
20594
+ const maxMemoChainDepth = computeMaxMemoChainDepth(graph);
20595
+ const dynamicBindings = graph.domBindings.length;
20596
+ const fallbacks = graph.domBindings.filter((d) => d.classification === "fallback").length;
20597
+ const eventHandlers = graph.domBindings.filter((d) => d.type === "event").length;
20598
+ const conditionals = graph.domBindings.filter((d) => d.type === "conditional").length;
20599
+ const loops = graph.domBindings.filter((d) => d.type === "loop").length;
20600
+ const totalSubscriptions = graph.memos.reduce((s, m) => s + m.deps.length, 0) + graph.effects.reduce((s, e) => s + e.deps.length, 0) + graph.domBindings.reduce((s, b) => s + b.deps.length, 0);
20601
+ const batchDedupeKeys = new Set;
20602
+ for (const event of eventSummary.events) {
20603
+ const distinct = new Set;
20604
+ for (const sc of event.setterCalls) {
20605
+ if (sc.signal)
20606
+ distinct.add(sc.signal);
20607
+ }
20608
+ if (distinct.size >= THRESHOLDS.batchMinSignals) {
20609
+ const key = `${event.eventName}|${event.loc.file ?? ""}|${event.loc.start.line}|${[...distinct].sort().join(",")}`;
20610
+ batchDedupeKeys.add(key);
20611
+ }
20612
+ }
20613
+ const batchCandidateCount = batchDedupeKeys.size;
20614
+ return {
20615
+ componentName: graph.componentName,
20616
+ sourceFile: graph.sourceFile,
20617
+ hydrated,
20618
+ signals: graph.signals.length,
20619
+ memos: graph.memos.length,
20620
+ effects: graph.effects.length,
20621
+ loops,
20622
+ eventHandlers,
20623
+ dynamicBindings,
20624
+ fallbacks,
20625
+ conditionals,
20626
+ maxSignalFanOut: maxFanOut,
20627
+ hotSignal,
20628
+ maxMemoChainDepth,
20629
+ totalSubscriptions,
20630
+ batchCandidateCount
20631
+ };
20632
+ }
20633
+ function computeMaxMemoChainDepth(graph) {
20634
+ if (graph.memos.length === 0)
20635
+ return 0;
20636
+ const memoSet = new Set(graph.memos.map((m) => m.name));
20637
+ const memoDeps = new Map;
20638
+ for (const memo of graph.memos) {
20639
+ memoDeps.set(memo.name, memo.deps.filter((d) => memoSet.has(d)));
20640
+ }
20641
+ const cache = new Map;
20642
+ function depth(name, visited) {
20643
+ if (cache.has(name))
20644
+ return cache.get(name);
20645
+ if (visited.has(name))
20646
+ return 0;
20647
+ const children = memoDeps.get(name) ?? [];
20648
+ if (children.length === 0) {
20649
+ cache.set(name, 1);
20650
+ return 1;
20651
+ }
20652
+ visited.add(name);
20653
+ const d = 1 + Math.max(...children.map((c) => depth(c, new Set(visited))));
20654
+ cache.set(name, d);
20655
+ return d;
20656
+ }
20657
+ let max = 0;
20658
+ for (const memo of graph.memos) {
20659
+ const d = depth(memo.name, new Set);
20660
+ if (d > max)
20661
+ max = d;
20662
+ }
20663
+ return max;
20664
+ }
20665
+ function computeFindings(metrics, graph, eventSummary) {
20666
+ const findings = [];
20667
+ for (const signal of graph.signals) {
20668
+ if (signal.consumers.length > THRESHOLDS.highFanOut) {
20669
+ findings.push({
20670
+ kind: "high-fan-out",
20671
+ severity: "warning",
20672
+ signal: signal.name,
20673
+ message: `${signal.name} has ${signal.consumers.length} consumers (fan-out > ${THRESHOLDS.highFanOut})`,
20674
+ suggestion: `Split ${signal.name} into finer-grained signals, or add a createMemo to shield downstream consumers from unrelated updates`,
20675
+ loc: { file: signal.loc.file, line: signal.loc.line }
20676
+ });
20677
+ }
20678
+ }
20679
+ if (metrics.maxMemoChainDepth > THRESHOLDS.deepMemoChain) {
20680
+ findings.push({
20681
+ kind: "deep-memo-chain",
20682
+ severity: "warning",
20683
+ depth: metrics.maxMemoChainDepth,
20684
+ message: `Memo chain depth ${metrics.maxMemoChainDepth} (threshold: ${THRESHOLDS.deepMemoChain}) — a single signal update cascades through ${metrics.maxMemoChainDepth} memo levels`,
20685
+ suggestion: "Flatten intermediate memos that do not cache expensive computations; deep chains increase propagation latency"
20686
+ });
20687
+ }
20688
+ const batchSeen = new Set;
20689
+ for (const event of eventSummary.events) {
20690
+ const distinct = new Set;
20691
+ const setterNames = [];
20692
+ for (const sc of event.setterCalls) {
20693
+ if (sc.signal) {
20694
+ distinct.add(sc.signal);
20695
+ setterNames.push(sc.setter);
20696
+ }
20697
+ }
20698
+ if (distinct.size >= THRESHOLDS.batchMinSignals) {
20699
+ const dedupeKey = `${event.eventName}|${event.loc.file ?? ""}|${event.loc.start.line}|${[...distinct].sort().join(",")}`;
20700
+ if (batchSeen.has(dedupeKey))
20701
+ continue;
20702
+ batchSeen.add(dedupeKey);
20703
+ findings.push({
20704
+ kind: "batch-candidate",
20705
+ severity: "info",
20706
+ signals: [...distinct],
20707
+ message: `${event.eventName} on <${event.elementContext}> sets ${distinct.size} signals (${[...distinct].join(", ")}) — triggers ${distinct.size} separate update cycles (static; verify setters are not in separate if/else branches)`,
20708
+ suggestion: `If all listed setters fire unconditionally in the same handler path, wrap in batch(() => { ${setterNames.join("; ")}; }) to collapse ${distinct.size} cycles into 1`,
20709
+ loc: event.loc.file ? { file: event.loc.file, line: event.loc.start.line } : undefined
20710
+ });
20711
+ }
20712
+ }
20713
+ if (metrics.dynamicBindings > 0 && metrics.fallbacks >= THRESHOLDS.fallbackHeavyMin && metrics.fallbacks / metrics.dynamicBindings > THRESHOLDS.fallbackHeavyRatio) {
20714
+ findings.push({
20715
+ kind: "fallback-heavy",
20716
+ severity: "info",
20717
+ message: `${metrics.fallbacks}/${metrics.dynamicBindings} bindings (${Math.round(metrics.fallbacks / metrics.dynamicBindings * 100)}%) are fallback-wrapped — reactivity not statically provable`,
20718
+ suggestion: "Run `bf debug fallbacks` to see each expression and fix them so the compiler can prove reactivity without the fallback wrapper"
20719
+ });
20720
+ }
20721
+ return findings;
20722
+ }
20723
+ function diffProfiles(before, after) {
20724
+ const worseWhenHigher = new Set([
20725
+ "fallbacks",
20726
+ "maxSignalFanOut",
20727
+ "maxMemoChainDepth",
20728
+ "totalSubscriptions",
20729
+ "batchCandidateCount"
20730
+ ]);
20731
+ const numericKeys = [
20732
+ "signals",
20733
+ "memos",
20734
+ "effects",
20735
+ "loops",
20736
+ "eventHandlers",
20737
+ "dynamicBindings",
20738
+ "fallbacks",
20739
+ "conditionals",
20740
+ "maxSignalFanOut",
20741
+ "maxMemoChainDepth",
20742
+ "totalSubscriptions",
20743
+ "batchCandidateCount"
20744
+ ];
20745
+ const regressions = [];
20746
+ const improvements = [];
20747
+ const neutral = [];
20748
+ for (const key of numericKeys) {
20749
+ const b = before[key];
20750
+ const a = after[key];
20751
+ if (a === b)
20752
+ continue;
20753
+ const entry = { metric: key, before: b, after: a, delta: a - b };
20754
+ if (worseWhenHigher.has(key)) {
20755
+ if (a > b)
20756
+ regressions.push(entry);
20757
+ else
20758
+ improvements.push(entry);
20759
+ } else {
20760
+ neutral.push(entry);
20761
+ }
20762
+ }
20763
+ return { componentName: after.componentName, before, after, regressions, improvements, neutral };
20764
+ }
20765
+ function formatSingleProfile(profile) {
20766
+ const m = profile.metrics;
20767
+ const lines = [];
20768
+ lines.push(`${m.componentName} — reactive profile (static)`);
20769
+ if (m.sourceFile)
20770
+ lines.push(` source: ${m.sourceFile}`);
20771
+ lines.push(` hydrated: ${m.hydrated ? "yes" : "no"}`);
20772
+ lines.push("");
20773
+ lines.push(" Counts:");
20774
+ lines.push(` signals: ${m.signals}`);
20775
+ lines.push(` memos: ${m.memos}`);
20776
+ if (m.effects > 0)
20777
+ lines.push(` effects: ${m.effects}`);
20778
+ lines.push(` dynamic bindings: ${m.dynamicBindings}`);
20779
+ if (m.fallbacks > 0)
20780
+ lines.push(` fallbacks: ${m.fallbacks}`);
20781
+ if (m.loops > 0)
20782
+ lines.push(` loops: ${m.loops}`);
20783
+ if (m.conditionals > 0)
20784
+ lines.push(` conditionals: ${m.conditionals}`);
20785
+ if (m.eventHandlers > 0)
20786
+ lines.push(` event handlers: ${m.eventHandlers}`);
20787
+ lines.push("");
20788
+ lines.push(" Reactive budget (SR5):");
20789
+ const fanOutSuffix = m.hotSignal ? ` (${m.hotSignal})` : "";
20790
+ lines.push(` max signal fan-out: ${m.maxSignalFanOut}${fanOutSuffix}`);
20791
+ lines.push(` max memo chain depth: ${m.maxMemoChainDepth}`);
20792
+ lines.push(` total subscriptions: ${m.totalSubscriptions}`);
20793
+ if (m.batchCandidateCount > 0) {
20794
+ lines.push(` batch candidates: ${m.batchCandidateCount} handler(s) set ≥2 signals`);
20795
+ }
20796
+ if (profile.findings.length > 0) {
20797
+ lines.push("");
20798
+ lines.push(" Findings:");
20799
+ for (const f of profile.findings) {
20800
+ const icon = f.severity === "warning" ? "⚠" : "→";
20801
+ lines.push(` ${icon} [${f.kind}] ${f.message}`);
20802
+ lines.push(` fix: ${f.suggestion}`);
20803
+ if (f.loc) {
20804
+ const file = f.loc.file.split("/").pop() ?? f.loc.file;
20805
+ lines.push(` at ${file}:${f.loc.line}`);
20806
+ }
20807
+ }
20808
+ } else {
20809
+ lines.push("");
20810
+ lines.push(" No findings — component is within all thresholds.");
20811
+ }
20812
+ return lines.join(`
20813
+ `);
20814
+ }
20815
+ function formatProfileTable(profiles) {
20816
+ if (profiles.length === 0)
20817
+ return "No components found.";
20818
+ const sorted = [...profiles].sort((a, b) => b.metrics.totalSubscriptions - a.metrics.totalSubscriptions);
20819
+ const lines = [];
20820
+ lines.push("Component sig memo bind fall fanOut chain subs batch findings");
20821
+ lines.push("─".repeat(90));
20822
+ for (const p of sorted) {
20823
+ const m = p.metrics;
20824
+ const name = m.componentName.padEnd(23).slice(0, 23);
20825
+ const findingStr = p.findings.length > 0 ? p.findings.map((f) => f.kind.replace(/-/g, "_")).join(",") : "—";
20826
+ const row = [
20827
+ name,
20828
+ String(m.signals).padStart(3),
20829
+ String(m.memos).padStart(5),
20830
+ String(m.dynamicBindings).padStart(5),
20831
+ String(m.fallbacks).padStart(5),
20832
+ String(m.maxSignalFanOut).padStart(7),
20833
+ String(m.maxMemoChainDepth).padStart(6),
20834
+ String(m.totalSubscriptions).padStart(5),
20835
+ String(m.batchCandidateCount).padStart(6),
20836
+ ` ${findingStr}`
20837
+ ].join(" ");
20838
+ lines.push(row);
20839
+ }
20840
+ const allFindings = sorted.flatMap((p) => p.findings.map((f) => ({ component: p.metrics.componentName, finding: f })));
20841
+ if (allFindings.length > 0) {
20842
+ lines.push("");
20843
+ lines.push("Findings:");
20844
+ for (const { component, finding } of allFindings) {
20845
+ const icon = finding.severity === "warning" ? "⚠" : "→";
20846
+ lines.push(` ${icon} ${component}: ${finding.message}`);
20847
+ lines.push(` fix: ${finding.suggestion}`);
20848
+ if (finding.loc) {
20849
+ const file = finding.loc.file.split("/").pop() ?? finding.loc.file;
20850
+ lines.push(` at ${file}:${finding.loc.line}`);
20851
+ }
20852
+ }
20853
+ } else {
20854
+ lines.push("");
20855
+ lines.push("No findings across all components.");
20856
+ }
20857
+ return lines.join(`
20858
+ `);
20859
+ }
20860
+ function formatProfileDiff(diff) {
20861
+ const lines = [];
20862
+ lines.push(`${diff.componentName} — reactive profile diff (before → after)`);
20863
+ lines.push("");
20864
+ if (diff.regressions.length === 0 && diff.improvements.length === 0 && diff.neutral.length === 0) {
20865
+ lines.push(" No changes in reactive metrics.");
20866
+ return lines.join(`
20867
+ `);
20868
+ }
20869
+ if (diff.regressions.length > 0) {
20870
+ lines.push(" Regressions (reactive cost increased):");
20871
+ for (const e of diff.regressions) {
20872
+ lines.push(` ${e.metric}: ${e.before} → ${e.after} (+${e.delta})`);
20873
+ }
20874
+ }
20875
+ if (diff.improvements.length > 0) {
20876
+ lines.push(" Improvements (reactive cost decreased):");
20877
+ for (const e of diff.improvements) {
20878
+ lines.push(` ${e.metric}: ${e.before} → ${e.after} (${e.delta})`);
20879
+ }
20880
+ }
20881
+ if (diff.neutral.length > 0) {
20882
+ lines.push(" Structural changes (count changes, no clear direction):");
20883
+ for (const e of diff.neutral) {
20884
+ const sign = (e.delta ?? 0) > 0 ? "+" : "";
20885
+ lines.push(` ${e.metric}: ${e.before} → ${e.after} (${sign}${e.delta})`);
20886
+ }
20887
+ }
20888
+ return lines.join(`
20889
+ `);
20890
+ }
20891
+ function profileToJSON(profile) {
20892
+ return {
20893
+ metrics: profile.metrics,
20894
+ findings: profile.findings
20895
+ };
20896
+ }
19226
20897
  // src/augment-inherited-props.ts
19227
- import ts19 from "typescript";
20898
+ import ts21 from "typescript";
19228
20899
  function collectContextConsumers(metadata) {
19229
20900
  const constants = metadata.localConstants ?? [];
19230
20901
  const contextDefaults = new Map;
@@ -19252,39 +20923,39 @@ function collectContextConsumers(metadata) {
19252
20923
  }
19253
20924
  function parseUseContextArg(source) {
19254
20925
  const expr = parseSingleExpression(source);
19255
- if (!expr || !ts19.isCallExpression(expr))
20926
+ if (!expr || !ts21.isCallExpression(expr))
19256
20927
  return null;
19257
- if (!ts19.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
20928
+ if (!ts21.isIdentifier(expr.expression) || expr.expression.text !== "useContext")
19258
20929
  return null;
19259
20930
  if (expr.arguments.length !== 1)
19260
20931
  return null;
19261
20932
  const arg = expr.arguments[0];
19262
- return ts19.isIdentifier(arg) ? arg.text : null;
20933
+ return ts21.isIdentifier(arg) ? arg.text : null;
19263
20934
  }
19264
20935
  function parseCreateContextDefault(source) {
19265
20936
  const expr = parseSingleExpression(source);
19266
- if (!expr || !ts19.isCallExpression(expr))
20937
+ if (!expr || !ts21.isCallExpression(expr))
19267
20938
  return null;
19268
20939
  if (expr.arguments.length === 0)
19269
20940
  return null;
19270
20941
  const arg = expr.arguments[0];
19271
- if (ts19.isStringLiteral(arg) || ts19.isNoSubstitutionTemplateLiteral(arg))
20942
+ if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
19272
20943
  return arg.text;
19273
- if (ts19.isNumericLiteral(arg))
20944
+ if (ts21.isNumericLiteral(arg))
19274
20945
  return Number(arg.text);
19275
- if (arg.kind === ts19.SyntaxKind.TrueKeyword)
20946
+ if (arg.kind === ts21.SyntaxKind.TrueKeyword)
19276
20947
  return true;
19277
- if (arg.kind === ts19.SyntaxKind.FalseKeyword)
20948
+ if (arg.kind === ts21.SyntaxKind.FalseKeyword)
19278
20949
  return false;
19279
20950
  return null;
19280
20951
  }
19281
20952
  function parseSingleExpression(source) {
19282
- const sf = ts19.createSourceFile("__ctx.ts", `(${source})`, ts19.ScriptTarget.Latest, false);
20953
+ const sf = ts21.createSourceFile("__ctx.ts", `(${source})`, ts21.ScriptTarget.Latest, false);
19283
20954
  const stmt = sf.statements[0];
19284
- if (!stmt || !ts19.isExpressionStatement(stmt))
20955
+ if (!stmt || !ts21.isExpressionStatement(stmt))
19285
20956
  return null;
19286
20957
  let e = stmt.expression;
19287
- while (ts19.isParenthesizedExpression(e))
20958
+ while (ts21.isParenthesizedExpression(e))
19288
20959
  e = e.expression;
19289
20960
  return e;
19290
20961
  }
@@ -19359,28 +21030,28 @@ function augmentInheritedPropAccesses(ir) {
19359
21030
  }
19360
21031
  }
19361
21032
  function evalStringArrayJoin(source) {
19362
- const sf = ts19.createSourceFile("__join.ts", `const __x = (${source});`, ts19.ScriptTarget.Latest, false);
21033
+ const sf = ts21.createSourceFile("__join.ts", `const __x = (${source});`, ts21.ScriptTarget.Latest, false);
19363
21034
  const stmt = sf.statements[0];
19364
- if (!stmt || !ts19.isVariableStatement(stmt))
21035
+ if (!stmt || !ts21.isVariableStatement(stmt))
19365
21036
  return null;
19366
21037
  let node = stmt.declarationList.declarations[0]?.initializer;
19367
- while (node && ts19.isParenthesizedExpression(node))
21038
+ while (node && ts21.isParenthesizedExpression(node))
19368
21039
  node = node.expression;
19369
- if (!node || !ts19.isCallExpression(node))
21040
+ if (!node || !ts21.isCallExpression(node))
19370
21041
  return null;
19371
21042
  const callee = node.expression;
19372
- if (!ts19.isPropertyAccessExpression(callee))
21043
+ if (!ts21.isPropertyAccessExpression(callee))
19373
21044
  return null;
19374
21045
  if (callee.name.text !== "join")
19375
21046
  return null;
19376
21047
  let recv = callee.expression;
19377
- while (ts19.isParenthesizedExpression(recv))
21048
+ while (ts21.isParenthesizedExpression(recv))
19378
21049
  recv = recv.expression;
19379
- if (!ts19.isArrayLiteralExpression(recv))
21050
+ if (!ts21.isArrayLiteralExpression(recv))
19380
21051
  return null;
19381
21052
  const parts = [];
19382
21053
  for (const el of recv.elements) {
19383
- if (ts19.isStringLiteral(el) || ts19.isNoSubstitutionTemplateLiteral(el)) {
21054
+ if (ts21.isStringLiteral(el) || ts21.isNoSubstitutionTemplateLiteral(el)) {
19384
21055
  parts.push(el.text);
19385
21056
  } else {
19386
21057
  return null;
@@ -19389,7 +21060,7 @@ function evalStringArrayJoin(source) {
19389
21060
  let sep2 = ",";
19390
21061
  if (node.arguments.length >= 1) {
19391
21062
  const arg = node.arguments[0];
19392
- if (ts19.isStringLiteral(arg) || ts19.isNoSubstitutionTemplateLiteral(arg))
21063
+ if (ts21.isStringLiteral(arg) || ts21.isNoSubstitutionTemplateLiteral(arg))
19393
21064
  sep2 = arg.text;
19394
21065
  else
19395
21066
  return null;
@@ -19397,11 +21068,11 @@ function evalStringArrayJoin(source) {
19397
21068
  return parts.join(sep2);
19398
21069
  }
19399
21070
  function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
19400
- if (!ts19.isElementAccessExpression(val))
21071
+ if (!ts21.isElementAccessExpression(val))
19401
21072
  return null;
19402
21073
  const obj = val.expression;
19403
21074
  const arg = val.argumentExpression;
19404
- if (!ts19.isIdentifier(obj) || !ts19.isIdentifier(arg))
21075
+ if (!ts21.isIdentifier(obj) || !ts21.isIdentifier(arg))
19405
21076
  return null;
19406
21077
  let indexPropName;
19407
21078
  let defaultKey;
@@ -19417,35 +21088,35 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
19417
21088
  const constInfo = localConstants.find((c) => c.name === obj.text && c.isModule);
19418
21089
  if (constInfo?.value === undefined)
19419
21090
  return null;
19420
- const sf = ts19.createSourceFile("__rec.ts", `(${constInfo.value})`, ts19.ScriptTarget.Latest, true);
21091
+ const sf = ts21.createSourceFile("__rec.ts", `(${constInfo.value})`, ts21.ScriptTarget.Latest, true);
19421
21092
  if (sf.statements.length !== 1)
19422
21093
  return null;
19423
21094
  const stmt = sf.statements[0];
19424
- if (!ts19.isExpressionStatement(stmt))
21095
+ if (!ts21.isExpressionStatement(stmt))
19425
21096
  return null;
19426
21097
  let parsed = stmt.expression;
19427
- while (ts19.isParenthesizedExpression(parsed))
21098
+ while (ts21.isParenthesizedExpression(parsed))
19428
21099
  parsed = parsed.expression;
19429
- if (!ts19.isObjectLiteralExpression(parsed))
21100
+ if (!ts21.isObjectLiteralExpression(parsed))
19430
21101
  return null;
19431
21102
  const entries = [];
19432
21103
  for (const prop of parsed.properties) {
19433
- if (!ts19.isPropertyAssignment(prop))
21104
+ if (!ts21.isPropertyAssignment(prop))
19434
21105
  return null;
19435
21106
  let key;
19436
- if (ts19.isIdentifier(prop.name)) {
21107
+ if (ts21.isIdentifier(prop.name)) {
19437
21108
  key = prop.name.text;
19438
- } else if (ts19.isStringLiteral(prop.name) || ts19.isNoSubstitutionTemplateLiteral(prop.name)) {
21109
+ } else if (ts21.isStringLiteral(prop.name) || ts21.isNoSubstitutionTemplateLiteral(prop.name)) {
19439
21110
  key = prop.name.text;
19440
21111
  } else {
19441
21112
  return null;
19442
21113
  }
19443
21114
  let v = prop.initializer;
19444
- while (ts19.isParenthesizedExpression(v))
21115
+ while (ts21.isParenthesizedExpression(v))
19445
21116
  v = v.expression;
19446
- if (ts19.isNumericLiteral(v)) {
21117
+ if (ts21.isNumericLiteral(v)) {
19447
21118
  entries.push({ key, value: { kind: "number", text: v.text } });
19448
- } else if (ts19.isStringLiteral(v) || ts19.isNoSubstitutionTemplateLiteral(v)) {
21119
+ } else if (ts21.isStringLiteral(v) || ts21.isNoSubstitutionTemplateLiteral(v)) {
19449
21120
  entries.push({ key, value: { kind: "string", text: v.text } });
19450
21121
  } else {
19451
21122
  return null;
@@ -19455,12 +21126,16 @@ function parseRecordIndexAccess(val, localConstants, propsParams, resolveKey) {
19455
21126
  }
19456
21127
  export {
19457
21128
  traceUpdatePath,
21129
+ testAdapter,
19458
21130
  stringifyParsedExpr,
19459
21131
  rewriteImportsForTemplate,
19460
21132
  resolveSetters,
19461
21133
  resetCompilerCounters,
19462
21134
  renderImportMapHtml,
21135
+ profileToJSON,
21136
+ parseStyleObjectEntries,
19463
21137
  parseRecordIndexAccess,
21138
+ parseProfilerId,
19464
21139
  parseExpression,
19465
21140
  parseBlockBody,
19466
21141
  needsTypeBasedDetection,
@@ -19468,7 +21143,9 @@ export {
19468
21143
  listComponentFunctions as listExportedComponents,
19469
21144
  listComponentFunctions,
19470
21145
  jsxToIR,
21146
+ joinProfilerEvents,
19471
21147
  isSupported,
21148
+ isLowerableObjectRestDestructure,
19472
21149
  isBooleanAttr,
19473
21150
  identifierPath,
19474
21151
  graphToJSON,
@@ -19479,15 +21156,25 @@ export {
19479
21156
  generateClientJsWithSourceMap,
19480
21157
  generateClientJs,
19481
21158
  formatWhyUpdate,
21159
+ formatWastedReReruns,
19482
21160
  formatUpdatePath,
21161
+ formatStaticBudget,
21162
+ formatSingleProfile,
19483
21163
  formatSignalTrace,
21164
+ formatProfileTable,
21165
+ formatProfileReport,
21166
+ formatProfileDiff,
19484
21167
  formatParamWithType,
19485
21168
  formatLoopSummary,
21169
+ formatHotSubscribers,
19486
21170
  formatFallbackExplanations,
19487
21171
  formatEventSummary,
19488
21172
  formatError,
19489
21173
  formatComponentSummary,
19490
21174
  formatComponentGraph,
21175
+ formatBudgetDiff,
21176
+ formatBatchAdvisor,
21177
+ findUninstrumentedEffects,
19491
21178
  findReachableNames,
19492
21179
  extractSsrDefaults,
19493
21180
  extractFunctionParams,
@@ -19499,6 +21186,8 @@ export {
19499
21186
  emitIRNode,
19500
21187
  emitAttrValue,
19501
21188
  disableCompilerInstrumentation,
21189
+ diffStaticBudget,
21190
+ diffProfiles,
19502
21191
  describeFallback,
19503
21192
  createProgramForFile,
19504
21193
  createProgramForCorpus,
@@ -19508,11 +21197,16 @@ export {
19508
21197
  combineParentChildClientJs,
19509
21198
  collectContextConsumers,
19510
21199
  buildWhyUpdate,
21200
+ buildStaticBudget,
19511
21201
  buildSourceMapFromIR,
21202
+ buildReactiveProfile,
21203
+ buildProfileReport,
21204
+ buildProfileFromGraph,
19512
21205
  buildMetadata,
19513
21206
  buildLoopSummary,
19514
21207
  buildLoopChainExpr,
19515
21208
  buildLocalFunctionSetterMap,
21209
+ buildIdIndex,
19516
21210
  buildGraphFromIR,
19517
21211
  buildEventSummary,
19518
21212
  buildComponentSummary,
@@ -19520,8 +21214,12 @@ export {
19520
21214
  buildComponentAnalysis,
19521
21215
  augmentInheritedPropAccesses,
19522
21216
  applyCssLayerPrefix,
21217
+ analyzeWastedReReruns,
21218
+ analyzeHotSubscribers,
19523
21219
  analyzeComponent,
19524
21220
  analyzeClientNeeds,
21221
+ analyzeBatchAdvisor,
21222
+ TestAdapter,
19525
21223
  SourceMapGenerator,
19526
21224
  REACTIVE_PRIMITIVES,
19527
21225
  JsxAdapter,