octane 0.2.13 → 0.2.15

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 (36) hide show
  1. package/dist/cjs/constants.cjs +7 -7
  2. package/dist/cjs/dom-binding-claims.cjs +28 -0
  3. package/dist/cjs/dom-binding-handoff.cjs +2 -2
  4. package/dist/cjs/dom-bindings.cjs +105 -13
  5. package/dist/cjs/runtime.cjs +311 -55
  6. package/dist/cjs/server/streamed-signals.cjs +3 -2
  7. package/dist/cjs/signals/control-binding.cjs +33 -9
  8. package/dist/cjs/signals/control-handoff.cjs +39 -0
  9. package/dist/cjs/stream-protocol.cjs +6 -0
  10. package/dist/cjs/version.cjs +1 -1
  11. package/dist/compiler/compile.js +86 -24
  12. package/dist/compiler/dom-binding-program.js +30 -3
  13. package/dist/compiler/dom-bindings.js +115 -34
  14. package/dist/compiler/volar.js +58 -17
  15. package/dist/constants.d.ts +2 -2
  16. package/dist/constants.js +2 -2
  17. package/dist/dom-binding-claims.d.ts +1 -0
  18. package/dist/dom-binding-claims.js +4 -0
  19. package/dist/dom-binding-handoff.d.ts +6 -4
  20. package/dist/dom-binding-handoff.js +1 -1
  21. package/dist/dom-binding-program.d.ts +2 -1
  22. package/dist/dom-binding-program.js +16 -1
  23. package/dist/dom-bindings.d.ts +5 -2
  24. package/dist/dom-bindings.js +105 -13
  25. package/dist/runtime.d.ts +34 -3
  26. package/dist/runtime.js +314 -55
  27. package/dist/server/streamed-signals.js +3 -2
  28. package/dist/signals/control-binding.d.ts +9 -5
  29. package/dist/signals/control-binding.js +38 -6
  30. package/dist/signals/control-handoff.d.ts +26 -0
  31. package/dist/signals/control-handoff.js +12 -0
  32. package/dist/signals/index.d.ts +1 -0
  33. package/dist/stream-protocol.d.ts +4 -0
  34. package/dist/stream-protocol.js +4 -0
  35. package/dist/version.js +1 -1
  36. package/package.json +1 -1
@@ -25,11 +25,11 @@ module.exports = __toCommonJS(control_binding_exports);
25
25
  var import_control_capture = require("../hydration/control-capture.cjs");
26
26
  var import_early_values = require("./early-values.cjs");
27
27
  var import_handle_protocol = require("./handle-protocol.cjs");
28
+ var import_control_handoff = require("./control-handoff.cjs");
28
29
  var import_owner_context = require("./owner-context.cjs");
29
30
  var import_read_protocol = require("./read-protocol.cjs");
30
31
  var import_types = require("./types.cjs");
31
32
  var import_transition_state = require("./transition-state.cjs");
32
- const CONTROL_BINDINGS = /* @__PURE__ */ new WeakMap();
33
33
  const RADIO_WRITERS = /* @__PURE__ */ new WeakMap();
34
34
  function controlString(value) {
35
35
  return typeof value === "string" ? value : typeof value === "function" || typeof value === "symbol" ? "" : String(value);
@@ -48,8 +48,7 @@ function publishRadioInput(input) {
48
48
  for (const [write, checked] of edits) if (!checked) write(false);
49
49
  for (const [write, checked] of edits) if (checked) write(true);
50
50
  }
51
- function __createBindingControls() {
52
- const owner = (0, import_owner_context.currentSignalOwner)();
51
+ function __createBindingControls(owner = (0, import_owner_context.currentSignalOwner)()) {
53
52
  const run = owner === null ? (callback) => callback() : (0, import_owner_context.captureSignalOwner)(owner);
54
53
  return {
55
54
  claim(element, channel, notify) {
@@ -58,12 +57,12 @@ function __createBindingControls() {
58
57
  throw new TypeError(
59
58
  "A signal control requires a native value/checked property and a signal."
60
59
  );
61
- if (CONTROL_BINDINGS.get(control)?.has(channel) || (0, import_early_values.hasHydrationControlSignalWriter)(control, channel))
60
+ if (import_control_handoff.CONTROL_BINDINGS.get(control)?.has(channel) || (0, import_early_values.hasHydrationControlSignalWriter)(control, channel))
62
61
  throw new Error(
63
62
  "This control property already has a signal binding. Dispose it before rebinding."
64
63
  );
65
- let channels = CONTROL_BINDINGS.get(control);
66
- if (!channels) CONTROL_BINDINGS.set(control, channels = /* @__PURE__ */ new Set());
64
+ let channels = import_control_handoff.CONTROL_BINDINGS.get(control);
65
+ if (!channels) import_control_handoff.CONTROL_BINDINGS.set(control, channels = /* @__PURE__ */ new Set());
67
66
  channels.add(channel);
68
67
  let disposed = false;
69
68
  let initialized = false;
@@ -135,7 +134,7 @@ function __createBindingControls() {
135
134
  control.removeEventListener("compositionstart", compositionStart);
136
135
  control.removeEventListener("compositionend", compositionEnd);
137
136
  channels.delete(channel);
138
- if (channels.size === 0) CONTROL_BINDINGS.delete(control);
137
+ if (channels.size === 0) import_control_handoff.CONTROL_BINDINGS.delete(control);
139
138
  if (failed) throw failure;
140
139
  };
141
140
  const prepare = (next, preview = false) => {
@@ -298,11 +297,16 @@ function __createBindingControls() {
298
297
  prepare,
299
298
  preview: (value) => prepare(value, true),
300
299
  prepareCurrent: () => prepare(raw),
300
+ active: () => !disposed,
301
+ composing: () => composing,
301
302
  dispose
302
303
  };
303
304
  }
304
305
  };
305
306
  }
307
+ function controlSignalOwner(owner, document) {
308
+ return owner !== null && "documentOwner" in owner ? document ? owner.documentOwner : owner.instanceOwner : owner;
309
+ }
306
310
  function bindSignalControl(control, channel, handle$) {
307
311
  if (!(0, import_handle_protocol.isSignalHandle)(handle$))
308
312
  throw new TypeError("A signal control requires a native value/checked property and a signal.");
@@ -310,6 +314,7 @@ function bindSignalControl(control, channel, handle$) {
310
314
  let dirty = false;
311
315
  let disposed = false;
312
316
  let lease;
317
+ let handoff;
313
318
  const dispose = () => {
314
319
  disposed = true;
315
320
  lease.dispose();
@@ -357,9 +362,28 @@ function bindSignalControl(control, channel, handle$) {
357
362
  };
358
363
  }
359
364
  };
360
- lease = __createBindingControls().claim(control, channel, refresh);
365
+ const owner = (0, import_owner_context.currentSignalOwner)();
366
+ lease = __createBindingControls(owner).claim(control, channel, refresh);
361
367
  refresh();
362
- return dispose;
368
+ return Object.assign(dispose, {
369
+ [import_control_handoff.CONTROL_HANDOFF]() {
370
+ return handoff ??= {
371
+ control,
372
+ channel,
373
+ active: () => !disposed && lease.active(),
374
+ matches(handle) {
375
+ if (disposed || !lease.active() || handle !== handle$) return false;
376
+ if (!(import_types.SIGNAL_OWNER_RESOLVE in handle$)) return true;
377
+ const document = handle$[import_types.SIGNAL_BINDING_IDENTITY]().scope === "document";
378
+ return owner !== null && controlSignalOwner(owner, document) === controlSignalOwner((0, import_owner_context.currentSignalOwner)(), document) && !disposed && lease.active();
379
+ },
380
+ composing: lease.composing,
381
+ // dispose revokes authority before invoking fallible subscription
382
+ // cleanup; a later call cannot release the renderer's replacement.
383
+ retire: dispose
384
+ };
385
+ }
386
+ });
363
387
  }
364
388
  // Annotate the CommonJS export names for ESM import in node:
365
389
  0 && (module.exports = {
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var control_handoff_exports = {};
20
+ __export(control_handoff_exports, {
21
+ BINDING_HANDOFF: () => BINDING_HANDOFF,
22
+ CONTROL_BINDINGS: () => CONTROL_BINDINGS,
23
+ CONTROL_HANDOFF: () => CONTROL_HANDOFF,
24
+ hasSignalControlBinding: () => hasSignalControlBinding
25
+ });
26
+ module.exports = __toCommonJS(control_handoff_exports);
27
+ const BINDING_HANDOFF = /* @__PURE__ */ Symbol.for("octane.binding-handoff");
28
+ const CONTROL_HANDOFF = /* @__PURE__ */ Symbol.for("octane.control-handoff");
29
+ const CONTROL_BINDINGS = /* @__PURE__ */ new WeakMap();
30
+ function hasSignalControlBinding(control, channel) {
31
+ return CONTROL_BINDINGS.get(control)?.has(channel) ?? false;
32
+ }
33
+ // Annotate the CommonJS export names for ESM import in node:
34
+ 0 && (module.exports = {
35
+ BINDING_HANDOFF,
36
+ CONTROL_BINDINGS,
37
+ CONTROL_HANDOFF,
38
+ hasSignalControlBinding
39
+ });
@@ -20,6 +20,8 @@ var stream_protocol_exports = {};
20
20
  __export(stream_protocol_exports, {
21
21
  HYDRATE_STREAM_TOKEN_ATTR: () => HYDRATE_STREAM_TOKEN_ATTR,
22
22
  STREAM_BOUNDARY_ATTR: () => STREAM_BOUNDARY_ATTR,
23
+ STREAM_SCRIPT_ATTR: () => STREAM_SCRIPT_ATTR,
24
+ SUSPENSE_SCRIPT_ATTR: () => SUSPENSE_SCRIPT_ATTR,
23
25
  isRendererStreamBoundaryTemplate: () => isRendererStreamBoundaryTemplate,
24
26
  isRendererStreamToken: () => isRendererStreamToken,
25
27
  rendererRangeClose: () => rendererRangeClose,
@@ -28,6 +30,8 @@ __export(stream_protocol_exports, {
28
30
  module.exports = __toCommonJS(stream_protocol_exports);
29
31
  var import_dom_binding_protocol = require("./dom-binding-protocol.cjs");
30
32
  const STREAM_BOUNDARY_ATTR = "data-oct-b";
33
+ const SUSPENSE_SCRIPT_ATTR = "data-octane-suspense";
34
+ const STREAM_SCRIPT_ATTR = "data-octane-stream";
31
35
  const HYDRATE_STREAM_TOKEN_ATTR = "data-octane-stream-token";
32
36
  function hydrationMarkerMultiplicity(data, open) {
33
37
  const marker = open ? "[" : "]";
@@ -93,6 +97,8 @@ function isRendererStreamBoundaryTemplate(node, expectedToken) {
93
97
  0 && (module.exports = {
94
98
  HYDRATE_STREAM_TOKEN_ATTR,
95
99
  STREAM_BOUNDARY_ATTR,
100
+ STREAM_SCRIPT_ATTR,
101
+ SUSPENSE_SCRIPT_ATTR,
96
102
  isRendererStreamBoundaryTemplate,
97
103
  isRendererStreamToken,
98
104
  rendererRangeClose,
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  version: () => version
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const version = "0.2.13";
24
+ const version = "0.2.15";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  version
@@ -9163,6 +9163,7 @@ function markKnownAttributeSpreads(ast, contracts) {
9163
9163
  }
9164
9164
  }
9165
9165
  const imports = new Map();
9166
+ const unboundImports = new Set();
9166
9167
  for (const declaration of ast.body) {
9167
9168
  if (declaration.type !== 'ImportDeclaration' || declaration.importKind === 'type') continue;
9168
9169
  for (const specifier of declaration.specifiers) {
@@ -9173,6 +9174,8 @@ function markKnownAttributeSpreads(ast, contracts) {
9173
9174
  : specifier.type === 'ImportDefaultSpecifier'
9174
9175
  ? 'default'
9175
9176
  : (specifier.imported.name ?? specifier.imported.value);
9177
+ if (declaration.source.value === 'octane/behavior' && imported === 'unbound')
9178
+ unboundImports.add(specifier.local.name);
9176
9179
  const matches = contracts.filter(
9177
9180
  (contract) =>
9178
9181
  contract.source === declaration.source.value && contract.imported === imported,
@@ -9293,13 +9296,24 @@ function markKnownAttributeSpreads(ast, contracts) {
9293
9296
  };
9294
9297
  }
9295
9298
  if (node.type !== 'JSXSpreadAttribute' && node.type !== 'SpreadAttribute') return null;
9296
- const call = node.argument;
9299
+ let call = unwrapTsExpr(node.argument);
9300
+ const externalCallee = unwrapTsExpr(call?.callee);
9301
+ const external =
9302
+ call?.type === 'CallExpression' &&
9303
+ !call.optional &&
9304
+ externalCallee?.type === 'Identifier' &&
9305
+ unboundImports.has(externalCallee.name) &&
9306
+ lexical.resolveBinding(lexical.nodeScopes.get(externalCallee), externalCallee.name)?.scope ===
9307
+ lexical.rootScope &&
9308
+ call.arguments.length === 1 &&
9309
+ call.arguments[0].type !== 'SpreadElement';
9310
+ if (external) call = unwrapTsExpr(call.arguments[0]);
9297
9311
  if (call?.type !== 'CallExpression' || call.optional) return null;
9298
- let callee = call.callee;
9312
+ let callee = unwrapTsExpr(call.callee);
9299
9313
  const members = [];
9300
9314
  while (callee?.type === 'MemberExpression' && !callee.computed && !callee.optional) {
9301
9315
  members.unshift(callee.property.name);
9302
- callee = callee.object;
9316
+ callee = unwrapTsExpr(callee.object);
9303
9317
  }
9304
9318
  if (callee?.type !== 'Identifier') return null;
9305
9319
  const matches = imports.get(callee.name);
@@ -9319,7 +9333,11 @@ function markKnownAttributeSpreads(ast, contracts) {
9319
9333
  return contract
9320
9334
  ? {
9321
9335
  ...node,
9322
- _octaneKnownAttributeSpread: { fields: [...contract.fields], style: contract.style },
9336
+ _octaneKnownAttributeSpread: {
9337
+ fields: [...contract.fields],
9338
+ style: contract.style,
9339
+ ...(external ? { unbound: true } : {}),
9340
+ },
9323
9341
  }
9324
9342
  : null;
9325
9343
  });
@@ -15202,11 +15220,13 @@ function compileComponent(node, ctx, options) {
15202
15220
  }
15203
15221
 
15204
15222
  /** Gate explicitly eligible presentation views; ordinary codegen stays direct. */
15205
- function preparePresentationHydration(body, node, ctx) {
15223
+ function preparePresentationHydration(body, node, ctx, hostEnd) {
15206
15224
  const proof = node._octanePresentationHydration;
15207
15225
  if (!proof || ctx.mode === 'server' || ctx._universalRuntimeUnit != null) return body;
15208
15226
  const writers = new Set([
15209
15227
  'bindSignalAttribute',
15228
+ 'bindSignalValue',
15229
+ 'queueNativeChangeDiagnostic',
15210
15230
  'setEventHandler',
15211
15231
  'setAttribute',
15212
15232
  'setPlainAttribute',
@@ -15272,6 +15292,7 @@ function preparePresentationHydration(body, node, ctx) {
15272
15292
  const visit = (value, rewrite) => {
15273
15293
  if (!value || typeof value !== 'object') return value;
15274
15294
  if (Array.isArray(value)) return value.map((item) => visit(item, rewrite));
15295
+ if (proof.host && value._octanePresentationUnbound) return value;
15275
15296
  if (!value.type || /Function/.test(value.type)) return value;
15276
15297
  if (value.type === 'ReturnStatement' && !value._octanePendingOutputGuard) supported = false;
15277
15298
  const helper =
@@ -15343,11 +15364,15 @@ function preparePresentationHydration(body, node, ctx) {
15343
15364
  )
15344
15365
  : result;
15345
15366
  };
15346
- visit(body, false);
15367
+ const captured = proof.host ? body.slice(0, hostEnd) : body;
15368
+ visit(captured, false);
15347
15369
  const frame = b.id(allocCompilerName(ctx, '__presentationHydration'));
15348
15370
  const completed = b.id(allocCompilerName(ctx, '__presentationComplete'));
15349
- const failure = proof.structural ? b.id(allocCompilerName(ctx, '__presentationFailure')) : null;
15350
- const statements = supported ? visit(body, true) : body;
15371
+ const failure =
15372
+ proof.structural || proof.nativeControl || proof.host
15373
+ ? b.id(allocCompilerName(ctx, '__presentationFailure'))
15374
+ : null;
15375
+ const statements = supported ? visit(captured, true) : captured;
15351
15376
  let directiveEnd = 0;
15352
15377
  while (
15353
15378
  statements[directiveEnd]?.type === 'ExpressionStatement' &&
@@ -15359,21 +15384,33 @@ function preparePresentationHydration(body, node, ctx) {
15359
15384
  b.id('__s'),
15360
15385
  b.literal(proof.id),
15361
15386
  b.literal(supported),
15362
- ...(proof.structural ? [b.literal(true)] : []),
15363
- ...(proof.conditionalRest ? [b.literal(true)] : []),
15387
+ ...(proof.structural || proof.host ? [b.literal(proof.structural === true)] : []),
15388
+ ...(proof.conditionalRest || proof.host ? [b.literal(proof.conditionalRest === true)] : []),
15389
+ ...(proof.host ? [b.literal(true)] : []),
15364
15390
  );
15365
15391
  // A new, unsupported writer can still render normally, but must reject an
15366
15392
  // attempted lease before publishing anything. It never opens a frame.
15367
15393
  if (!supported)
15368
- return [...statements.slice(0, directiveEnd), b.stmt(begin), ...statements.slice(directiveEnd)];
15394
+ return [...body.slice(0, directiveEnd), b.stmt(begin), ...body.slice(directiveEnd)];
15369
15395
  return [
15370
15396
  ...statements.slice(0, directiveEnd),
15371
- b.const(frame, begin),
15397
+ proof.host ? b.let(frame, begin) : b.const(frame, begin),
15372
15398
  b.let(completed, b.literal(false)),
15373
15399
  b.try(
15374
15400
  b.block([
15375
15401
  ...statements.slice(directiveEnd),
15376
15402
  b.stmt(b.assignment('=', completed, b.literal(true))),
15403
+ ...(proof.host
15404
+ ? [
15405
+ // Descendants hydrate under their own receipts. The completed
15406
+ // host frame remains pending in this root's commit capture.
15407
+ b.stmt(
15408
+ b.call(requireRuntimeForContext(ctx, 'endPresentationHydration'), frame, completed),
15409
+ ),
15410
+ b.stmt(b.assignment('=', frame, b.literal(null))),
15411
+ ...body.slice(hostEnd),
15412
+ ]
15413
+ : []),
15377
15414
  ]),
15378
15415
  failure === null
15379
15416
  ? null
@@ -15957,6 +15994,8 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
15957
15994
  );
15958
15995
  bodyStatements.push(...plan.everyRender);
15959
15996
  }
15997
+ // Native host presentation ends before any child slot can execute.
15998
+ let presentationHostEnd = bodyStatements.length;
15960
15999
  if (plan?.after) bodyStatements.push(...plan.after);
15961
16000
  // Hoisted `<title>`/`<meta>`/`<link>` → headBlock into document.head
15962
16001
  // (out-of-band; re-applied each render for reactivity, removed on unmount).
@@ -15985,6 +16024,7 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
15985
16024
  bodyStatements.unshift(
15986
16025
  inheritOriginLoc(b.const('__block', b.member(b.id('__s'), 'block')), node),
15987
16026
  );
16027
+ presentationHostEnd++;
15988
16028
  }
15989
16029
  // PROPS-FIRST convention: `(…userProps, __s, __extra)`. The scope is the 2nd arg
15990
16030
  // (a placeholder leads when there are no user params), so a plain function
@@ -16001,7 +16041,12 @@ function compileFunctionBody(node, ctx, name, parentNs = 'html', cssHash = null,
16001
16041
  ctx.currentMapTemps = prevMapTemps;
16002
16042
  // ONE FunctionDeclaration node — the caller prints it (once, with the full
16003
16043
  // esrap map for top-level components) or embeds it in an enclosing body.
16004
- const presentationBody = preparePresentationHydration(bodyStatements, node, ctx);
16044
+ const presentationBody = preparePresentationHydration(
16045
+ bodyStatements,
16046
+ node,
16047
+ ctx,
16048
+ presentationHostEnd,
16049
+ );
16005
16050
  ctx.presentationHydration = previousPresentationHydration;
16006
16051
  const emittedFunction = b.function_declaration(
16007
16052
  b.id(name, node.id ?? node),
@@ -24344,6 +24389,17 @@ function planJsx(
24344
24389
  }
24345
24390
  }
24346
24391
  for (const b of elementBindings) {
24392
+ if (ctx.presentationHydration?.host && b.path.length === 0) {
24393
+ const name =
24394
+ b.kind === 'ref'
24395
+ ? 'ref'
24396
+ : b.kind === 'event' || b.kind === 'event-bundle'
24397
+ ? b.slotKeyOrigin?.name
24398
+ : b.name;
24399
+ b.presentationUnbound =
24400
+ typeof name === 'string' &&
24401
+ ctx.presentationHydration.unboundAttributes.has(name.toLowerCase());
24402
+ }
24347
24403
  const sharesSpreadHost = spreadPaths.has(b.path.join(','));
24348
24404
  const sharesEventSlot =
24349
24405
  (b.kind === 'event' || b.kind === 'event-bundle') &&
@@ -25912,11 +25968,13 @@ function propertyIsEnumerableCall(objNode, name) {
25912
25968
  );
25913
25969
  }
25914
25970
 
25915
- // `(__s.cleanups ??= []).push(<fn>)` — the scope's cleanup array is lazily allocated (most
25916
- // scopes never register one), so the registration site owns creating it.
25971
+ // Ref teardown reads the live bag because updates replace refs. An interrupted
25972
+ // mount has no committed bag and never attached its refs, so its cleanup is inert.
25917
25973
  function cleanupsPush(fnNode) {
25918
25974
  const lazyArray = b.assignment('??=', b.member(b.id('__s'), 'cleanups'), b.array([]));
25919
- return b.stmt(b.call(b.member(lazyArray, 'push'), fnNode));
25975
+ return b.stmt(
25976
+ b.call(b.member(lazyArray, 'push'), b.arrow([], b.logical('&&', b.id('_b'), fnNode.body))),
25977
+ );
25920
25978
  }
25921
25979
 
25922
25980
  /**
@@ -26107,7 +26165,10 @@ function emitBindingMount(bind, elVar, bag) {
26107
26165
  if (knownExpression !== bind.expr) bind = { ...bind, expr: knownExpression };
26108
26166
  if (bind.deferred) return emitDeferredMount(bind, elVar, bag);
26109
26167
  const org = bindingOrigin(bind);
26110
- const st = (node) => inheritOriginLoc(node, org);
26168
+ const st = (node) => {
26169
+ const emitted = inheritOriginLoc(node, org);
26170
+ return bind.presentationUnbound ? { ...emitted, _octanePresentationUnbound: true } : emitted;
26171
+ };
26111
26172
  const el = () => hostVarNode(elVar);
26112
26173
  const local = (key) => b.id(bag.local(key));
26113
26174
  const hostKey = `_el$${bind.id}`;
@@ -26315,9 +26376,8 @@ function emitBindingMount(bind, elVar, bag) {
26315
26376
  ]),
26316
26377
  );
26317
26378
  }
26318
- // The cleanup closure reads the bag through the captured `_b` — the bag
26319
- // exists by the time any cleanup runs (committed at mount end), and the
26320
- // `_host$` field is re-written by updates, so the read must be live.
26379
+ // The `_host$` field is re-written by updates, so cleanup reads the live
26380
+ // bag. cleanupsPush skips that read when mount never committed it.
26321
26381
  const cleanup = b.arrow(
26322
26382
  [],
26323
26383
  b.call(
@@ -26494,9 +26554,8 @@ function emitBindingMount(bind, elVar, bag) {
26494
26554
  // (queueRefDetach: unmount cleanups run mid-render, and a state-setter
26495
26555
  // ref firing null synchronously can render before a replacement
26496
26556
  // element's deferred attach — commit-phase detach batches the two).
26497
- // The cleanup closure reads the bag through the captured `_b` — the bag
26498
- // exists by the time any cleanup runs (committed at mount end), and the
26499
- // `_sp$` field is re-written by updates, so the read must be live.
26557
+ // The `_sp$` field is re-written by updates, so cleanup reads the live
26558
+ // bag. cleanupsPush skips that read when mount never committed it.
26500
26559
  const flags = bind.skipFormControls
26501
26560
  ? [b.literal(bind.skipDangerouslySetInnerHTML === true), b.literal(true)]
26502
26561
  : bind.skipDangerouslySetInnerHTML
@@ -26632,7 +26691,10 @@ function emitBindingUpdate(bind, bag, inlineBindingGuards = false) {
26632
26691
  if (knownExpression !== bind.expr) bind = { ...bind, expr: knownExpression };
26633
26692
  if (bind.mountOnly) return null;
26634
26693
  const org = bindingOrigin(bind);
26635
- const st = (node) => inheritOriginLoc(node, org);
26694
+ const st = (node) => {
26695
+ const emitted = inheritOriginLoc(node, org);
26696
+ return bind.presentationUnbound ? { ...emitted, _octanePresentationUnbound: true } : emitted;
26697
+ };
26636
26698
  const V = () => b.id('_v');
26637
26699
  // 1-char bag field names (see makeBag) — resolved from the same registry the
26638
26700
  // mount pass registered them in; an unmounted field throws at compile time.
@@ -55,12 +55,25 @@ export function needsBindingProgram(node, isUnbound) {
55
55
  if (!/^[a-z]/.test(tagOf(node) ?? '')) return true;
56
56
  if (
57
57
  (node.openingElement?.attributes ?? node.attributes ?? []).some(
58
- (attr) => rawName(attr) === 'ref' || /^on[A-Z]/.test(rawName(attr) ?? ''),
58
+ (attr) =>
59
+ (rawName(attr) === 'ref' || /^on[A-Z]/.test(rawName(attr) ?? '')) &&
60
+ !isUnbound(attrValue(attr)),
59
61
  )
60
62
  )
61
63
  return true;
62
64
  if (tagOf(node) === 'textarea') return false;
63
- return (node.children ?? []).some((child) => needsBindingProgram(child, isUnbound));
65
+ const children = (node.children ?? []).filter(significant);
66
+ const child = children.length === 1 ? children[0] : null;
67
+ const expression = child?.type === 'JSXExpressionContainer' ? child.expression : null;
68
+ if (
69
+ child?.type === 'JSXText' ||
70
+ (expression?.type === 'Literal' && typeof expression.value === 'string') ||
71
+ (expression?.type === 'TSAsExpression' &&
72
+ (expression.typeAnnotation.type === 'TSStringKeyword' ||
73
+ expression.typeAnnotation.type === 'TSNumberKeyword'))
74
+ )
75
+ return false;
76
+ return children.some((child) => needsBindingProgram(child, isUnbound));
64
77
  }
65
78
 
66
79
  function data(value) {
@@ -590,6 +603,7 @@ export function planBindingProgram(fn, render, context) {
590
603
  if (context.annotationsOnly) return name === 'class' || name === 'className';
591
604
  if (name !== 'ref' && !/^on[A-Z]/.test(name ?? '')) return true;
592
605
  const expression = attrValue(attr);
606
+ if (isUnbound(expression)) return true;
593
607
  // Event/ref callbacks are native owner adapters, not eager projections.
594
608
  assertAdapter(expression);
595
609
  if (
@@ -695,6 +709,19 @@ export function planBindingProgram(fn, render, context) {
695
709
  selfNs,
696
710
  tag === 'textarea' || opaqueChildren ? null : 0,
697
711
  ];
712
+ if (
713
+ tag === 'textarea' &&
714
+ selfNs === 0 &&
715
+ normalizedChildren.length === 0 &&
716
+ (node.attributes ?? []).some(
717
+ (attr) => rawName(attr) === 'value' && native.unbound.has(unwrap(attrValue(attr))),
718
+ ) &&
719
+ !(node.attributes ?? []).some((attr) => rawName(attr) === 'dangerouslySetInnerHTML')
720
+ ) {
721
+ // Its SSR text is native value content, not an arbitrary opaque subtree.
722
+ // The handoff validates the live text-only topology before accepting it.
723
+ nativeNode.push('value');
724
+ }
698
725
  if (opaqueChildren)
699
726
  constructionError ??=
700
727
  'This view contains externally owned children that cannot be constructed by a binding program.';
@@ -710,7 +737,7 @@ export function planBindingProgram(fn, render, context) {
710
737
  }
711
738
  const attributes = createTemplateIr();
712
739
  for (const attr of node.attributes ?? []) {
713
- if (attr._octaneKnownAttributeSpread) continue;
740
+ if (attr._octaneKnownAttributeSpread && !attr._octaneKnownAttributeSpread.unbound) continue;
714
741
  if (rawName(attr) === 'ref' || /^on[A-Z]/.test(rawName(attr) ?? '')) continue;
715
742
  if (attr.type === 'SpreadAttribute' || attr.type === 'JSXSpreadAttribute') {
716
743
  constructionError ??=