octane 0.4.0 → 0.4.2

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.
@@ -149,6 +149,21 @@ for (const name of [
149
149
  ]) {
150
150
  KNOWN_SVG_PROPERTY_SPELLINGS.set(name.toLowerCase(), name);
151
151
  }
152
+ const NON_NATIVE_LOWERCASE_PROPERTIES = /* @__PURE__ */ new Set([
153
+ "autoFocus",
154
+ "defaultValue",
155
+ "defaultChecked",
156
+ "dangerouslySetInnerHTML",
157
+ "className",
158
+ "suppressContentEditableWarning",
159
+ "suppressHydrationWarning",
160
+ "suppressNativeChangeWarning",
161
+ "__octaneNativeChangeDiagnostic",
162
+ "acceptCharset",
163
+ "htmlFor",
164
+ "httpEquiv",
165
+ "viewBox"
166
+ ]);
152
167
  function hostPropertyWarning(name, value, tag, isSvg = false) {
153
168
  const lower = name.toLowerCase();
154
169
  if (name === "for" && tag === "label") return null;
@@ -164,6 +179,9 @@ function hostPropertyWarning(name, value, tag, isSvg = false) {
164
179
  if (name.startsWith("aria-") || /^aria[A-Z]/.test(name) || name.startsWith("data-")) return null;
165
180
  const known = KNOWN_PROPERTY_SPELLINGS.get(lower) ?? (isSvg ? KNOWN_SVG_PROPERTY_SPELLINGS.get(lower) : void 0);
166
181
  if (known !== void 0) {
182
+ if (!isSvg && name === lower && // Hyphenated/namespaced presentation aliases belong to SVG, not HTML.
183
+ !name.includes("-") && !name.includes(":") && !NON_NATIVE_LOWERCASE_PROPERTIES.has(known) && (import_constants.ATTRIBUTE_ALIASES.get(known) ?? lower) === lower)
184
+ return null;
167
185
  if (isSvg && import_constants.ATTRIBUTE_ALIASES.get(known) === name) return null;
168
186
  return name !== known ? `Invalid DOM property \`${name}\`. Did you mean \`${known}\`?` : null;
169
187
  }
@@ -158,6 +158,7 @@ __export(runtime_exports, {
158
158
  isChildrenBlock: () => isChildrenBlock,
159
159
  isHydratingStyle: () => isHydratingStyle,
160
160
  isInActScope: () => isInActScope,
161
+ isNativeSignalDiagnosticProbe: () => isNativeSignalDiagnosticProbe,
161
162
  isValidElement: () => isValidElement,
162
163
  keyedForBlock: () => keyedForBlock,
163
164
  lazy: () => lazy,
@@ -357,6 +358,7 @@ var import_transition_state = require("./signals/transition-state.cjs");
357
358
  var import_transition_candidate = require("./signals/transition-candidate.cjs");
358
359
  var import_read_protocol = require("./signals/read-protocol.cjs");
359
360
  var import_native_read_events = require("./signals/native-read-events.cjs");
361
+ var import_diagnostic_local_hook = require("./signals/diagnostic-local-hook.cjs");
360
362
  var import_native_read_seeds = require("./signals/native-read-seeds.cjs");
361
363
  var import_owner_context = require("./signals/owner-context.cjs");
362
364
  var import_document_owner = require("./signals/document-owner.cjs");
@@ -833,12 +835,12 @@ function nativePresentationBinding(owner, slotIndex, el, body, props) {
833
835
  }
834
836
  function enableNativeReadCollection(abi = 1) {
835
837
  if (abi !== 1) throw new Error((0, import_error_codes_client_generated.formatClientError)(58));
838
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return;
836
839
  ensureNativeReadDriver();
837
840
  }
838
841
  function beginNativeReadScope(scope, abi = 1) {
839
- if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null)
840
- throw new PresentationAdoptionMiss(void 0, false);
841
842
  if (abi !== 1) throw new Error((0, import_error_codes_client_generated.formatClientError)(58));
843
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return -1;
842
844
  const block = CURRENT_BLOCK;
843
845
  const owner = scope ?? CURRENT_SCOPE;
844
846
  if (block === null || owner === null) return -1;
@@ -954,12 +956,14 @@ function endNativeReadScope(token, _completed) {
954
956
  if (token >= 0) NATIVE_READ_DRIVER.endScope(token);
955
957
  }
956
958
  function beginNativeReadWitness(detached = false) {
959
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return -1;
957
960
  return detached ? ensureNativeReadDriver().beginWitness(true) : NATIVE_READ_DRIVER?.beginWitness() ?? -1;
958
961
  }
959
962
  function finishNativeReadWitness(token, completed) {
960
963
  return token < 0 ? null : NATIVE_READ_DRIVER.finishWitness(token, completed);
961
964
  }
962
965
  function replayNativeReadWitness(witness) {
966
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return;
963
967
  NATIVE_READ_DRIVER?.replay(witness);
964
968
  }
965
969
  function retainNativeRetryReads(block, reads) {
@@ -6789,7 +6793,8 @@ function renderBlockInner(block) {
6789
6793
  let profileThrown;
6790
6794
  let renderCompleted = false;
6791
6795
  let renderRetry = false;
6792
- NATIVE_READ_DRIVER?.beginRender(block);
6796
+ if (process.env.NODE_ENV === "production" || hydrateAttributeProbe === null)
6797
+ NATIVE_READ_DRIVER?.beginRender(block);
6793
6798
  NATIVE_BLOCK_RETRIES?.get(block)?.clear();
6794
6799
  try {
6795
6800
  if (urgentTransitionRender !== null)
@@ -6857,7 +6862,8 @@ function renderBlockInner(block) {
6857
6862
  CURRENT_SCOPE = prevScope;
6858
6863
  CURRENT_BLOCK = prevBlock;
6859
6864
  try {
6860
- NATIVE_READ_DRIVER?.endRender(block, renderCompleted, isSuspenseException(profileThrown));
6865
+ if (process.env.NODE_ENV === "production" || hydrateAttributeProbe === null)
6866
+ NATIVE_READ_DRIVER?.endRender(block, renderCompleted, isSuspenseException(profileThrown));
6861
6867
  } finally {
6862
6868
  if (urgentTransitionRender !== null)
6863
6869
  urgentTransitionDriver.endUrgent(block, urgentTransitionRender);
@@ -7475,15 +7481,20 @@ function resolveSlot(slot) {
7475
7481
  const resolved = (0, import_hook_slot_cache.resolveHookPath)(slotStack, slot, false);
7476
7482
  return typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__ ? (0, import_profiling.__profileResolveHook)(resolved, typeof slot === "symbol" ? slot : void 0) : resolved;
7477
7483
  }
7478
- function nativeLocalHook(name, initialize, dispose, slot) {
7479
- if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null)
7484
+ function isNativeSignalDiagnosticProbe() {
7485
+ return process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null;
7486
+ }
7487
+ function nativeLocalHook(name, initialize, dispose, slot, diagnosticInitialize) {
7488
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null && (name !== "useSignal$" || diagnosticInitialize === void 0 || !(0, import_diagnostic_local_hook.isNativeSignalDiagnosticInitializer)(diagnosticInitialize)))
7480
7489
  throw new PresentationAdoptionMiss(void 0, false);
7481
7490
  slot = resolveSlot(slot);
7482
7491
  if (slot === void 0 || CURRENT_SCOPE === null) missingSlot(name);
7483
7492
  const scope = CURRENT_SCOPE;
7484
7493
  let cell = scope.hooks?.get(slot);
7485
7494
  if (cell === void 0) {
7486
- cell = { nativeValue: initialize() };
7495
+ cell = {
7496
+ nativeValue: process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null ? diagnosticInitialize() : initialize()
7497
+ };
7487
7498
  ensureHooks(scope).set(slot, cell);
7488
7499
  const value = cell.nativeValue;
7489
7500
  registerHookCleanup(scope, () => dispose(value));
@@ -8941,6 +8952,7 @@ function collectInitialHydrateAttributes(state, snapshot) {
8941
8952
  if (state.seedRaw !== null) hydration.seeds = hydration.parseSeeds(state.seedRaw);
8942
8953
  hydration.protectRootAnchor(end);
8943
8954
  const capture = createOffscreenCapture();
8955
+ const diagnosticNative = state.nativeSeedRaw === void 0 ? void 0 : (0, import_native_read_seeds.createNativeAdoptionState)((0, import_native_read_seeds.parseNativeSignalManifest)(state.nativeSeedRaw));
8944
8956
  const previousHydration = currentHydration;
8945
8957
  const previousCapture = WIP_CAPTURE;
8946
8958
  const previousProbe = hydrateAttributeProbe;
@@ -8960,7 +8972,10 @@ function collectInitialHydrateAttributes(state, snapshot) {
8960
8972
  const previousSelectDefaultSyncs = SELECT_DEFAULT_SYNCS;
8961
8973
  const previousFormChecks = DEV_FORM_CHECKS;
8962
8974
  const previousAutofocus = AUTOFOCUS_QUEUE;
8963
- const previousNative = (0, import_read_protocol.setNativeAdoptionResolver)(null);
8975
+ const diagnosticDriver = NATIVE_READ_DRIVER;
8976
+ const nativeProbeToken = diagnosticDriver?.pauseLifecycle() ?? -1;
8977
+ const previousWriteGuard = (0, import_read_protocol.beginNativeWriteGuard)();
8978
+ const previousNative = (0, import_read_protocol.setNativeAdoptionResolver)(diagnosticNative?.resolve ?? null);
8964
8979
  const refDetachCheckpoint = refDetachQueue.length;
8965
8980
  currentHydration = hydration;
8966
8981
  WIP_CAPTURE = capture;
@@ -8990,27 +9005,36 @@ function collectInitialHydrateAttributes(state, snapshot) {
8990
9005
  disposeWip({ block, start, end, capture, domParent: wrapper, refDetachCheckpoint });
8991
9006
  drainHydrationRenderPhaseUpdates(block);
8992
9007
  } finally {
8993
- (0, import_read_protocol.setNativeAdoptionResolver)(previousNative);
8994
- currentHydration = previousHydration;
8995
- WIP_CAPTURE = previousCapture;
8996
- hydrateAttributeProbe = previousProbe;
8997
- ROOT_RENDER_TRANSACTION = previousTransaction;
8998
- TRANSITION_JOURNAL = previousJournal;
8999
- ACTIVE_TRANSITION_ATTEMPT = previousAttempt;
9000
- SCOPED_READ_TRACKING = previousScopedReadTracking;
9001
- SCOPED_READS = previousScopedReads;
9002
- CURRENT_WARM = previousWarm;
9003
- CURRENT_WARM_CLAIMS = previousWarmClaims;
9004
- CURRENT_WARM_EPISODE = previousWarmEpisode;
9005
- ACTIVE_WARM_PLANS.length = 0;
9006
- ACTIVE_WARM_PLANS.push(...previousWarmPlans);
9007
- STAGED_DOM = previousStagedDom;
9008
- STAGED_COMMIT_CAPTURE = previousStagedCapture;
9009
- PRESENTATION_HYDRATION = previousPresentation;
9010
- SELECT_SYNCS = previousSelectSyncs;
9011
- SELECT_DEFAULT_SYNCS = previousSelectDefaultSyncs;
9012
- DEV_FORM_CHECKS = previousFormChecks;
9013
- AUTOFOCUS_QUEUE = previousAutofocus;
9008
+ try {
9009
+ diagnosticNative?.release();
9010
+ } finally {
9011
+ try {
9012
+ (0, import_read_protocol.setNativeAdoptionResolver)(previousNative);
9013
+ (0, import_read_protocol.endNativeWriteGuard)(previousWriteGuard);
9014
+ if (nativeProbeToken >= 0) diagnosticDriver.resumeLifecycle(nativeProbeToken);
9015
+ } finally {
9016
+ currentHydration = previousHydration;
9017
+ WIP_CAPTURE = previousCapture;
9018
+ hydrateAttributeProbe = previousProbe;
9019
+ ROOT_RENDER_TRANSACTION = previousTransaction;
9020
+ TRANSITION_JOURNAL = previousJournal;
9021
+ ACTIVE_TRANSITION_ATTEMPT = previousAttempt;
9022
+ SCOPED_READ_TRACKING = previousScopedReadTracking;
9023
+ SCOPED_READS = previousScopedReads;
9024
+ CURRENT_WARM = previousWarm;
9025
+ CURRENT_WARM_CLAIMS = previousWarmClaims;
9026
+ CURRENT_WARM_EPISODE = previousWarmEpisode;
9027
+ ACTIVE_WARM_PLANS.length = 0;
9028
+ ACTIVE_WARM_PLANS.push(...previousWarmPlans);
9029
+ STAGED_DOM = previousStagedDom;
9030
+ STAGED_COMMIT_CAPTURE = previousStagedCapture;
9031
+ PRESENTATION_HYDRATION = previousPresentation;
9032
+ SELECT_SYNCS = previousSelectSyncs;
9033
+ SELECT_DEFAULT_SYNCS = previousSelectDefaultSyncs;
9034
+ DEV_FORM_CHECKS = previousFormChecks;
9035
+ AUTOFOCUS_QUEUE = previousAutofocus;
9036
+ }
9037
+ }
9014
9038
  }
9015
9039
  }
9016
9040
  return probe;
@@ -24550,12 +24574,43 @@ function preservesCommittedSuspense(block) {
24550
24574
  }
24551
24575
  return false;
24552
24576
  }
24577
+ function markerlessBranchRoots(scope, domParent, after, end = null) {
24578
+ let roots = 0;
24579
+ const slots = scope._slots;
24580
+ if (slots !== null) {
24581
+ for (let i = 0; i < slots.length; i++) {
24582
+ const slot = slots[i];
24583
+ if (slot.anchor === after && (slot.__kind === "ifBlockSlot" || slot.__kind === "switchBlockSlot" || slot.__kind === "componentSlotSlot" && slot.block?.parentNode === domParent)) {
24584
+ roots++;
24585
+ if (end !== null) {
24586
+ journalRootProperty(slot, "anchor", slot.anchor);
24587
+ slot.anchor = end;
24588
+ }
24589
+ }
24590
+ }
24591
+ }
24592
+ const children = scope.children;
24593
+ if (children !== null) {
24594
+ for (let i = 0; i < children.length; i++) {
24595
+ const child2 = children[i].scope;
24596
+ const block = child2.block;
24597
+ if (block !== scope.block && block.parentNode === domParent && block.endMarker === after) {
24598
+ roots += markerlessBranchRoots(child2, domParent, after, end) || 1;
24599
+ if (end !== null) {
24600
+ journalRootProperty(block, "endMarker", block.endMarker);
24601
+ block.endMarker = end;
24602
+ }
24603
+ }
24604
+ }
24605
+ }
24606
+ return roots;
24607
+ }
24553
24608
  function finalizeMarkerlessBranch(state, domParent, block, marker, before, after) {
24554
24609
  state.markerlessBefore = void 0;
24555
24610
  if (state.borrowed && state.start === null) return;
24556
24611
  const first = before ? getNextSibling(before) : getFirstChild(domParent);
24557
24612
  const last = after ? (STAGED_DOM?.view(after) ?? after).previousSibling : (STAGED_DOM?.view(domParent) ?? domParent).lastChild;
24558
- if (last !== null && first === last && first.nodeType === 1) {
24613
+ if (last !== null && first === last && first.nodeType === 1 && markerlessBranchRoots(block, domParent, after) <= 1) {
24559
24614
  block.startMarker = first;
24560
24615
  block.endMarker = first;
24561
24616
  state.end = first;
@@ -24569,6 +24624,7 @@ function finalizeMarkerlessBranch(state, domParent, block, marker, before, after
24569
24624
  block.exclusiveMarkers = true;
24570
24625
  state.start = start;
24571
24626
  state.end = end;
24627
+ markerlessBranchRoots(block, domParent, after, end);
24572
24628
  }
24573
24629
  }
24574
24630
  function replaceSharedBlockBoundary(parent, oldStart, oldEnd, newStart, newEnd) {
@@ -28703,6 +28759,7 @@ function scriptResource(attrs) {
28703
28759
  isChildrenBlock,
28704
28760
  isHydratingStyle,
28705
28761
  isInActScope,
28762
+ isNativeSignalDiagnosticProbe,
28706
28763
  isValidElement,
28707
28764
  keyedForBlock,
28708
28765
  lazy,
@@ -24,6 +24,7 @@ __export(client_exports, {
24
24
  module.exports = __toCommonJS(client_exports);
25
25
  __reExport(client_exports, require("./index.cjs"), module.exports);
26
26
  var import_runtime = require("../runtime.cjs");
27
+ var import_diagnostic_local_hook = require("./diagnostic-local-hook.cjs");
27
28
  var import_engine = require("./engine.cjs");
28
29
  function disposeLocalSignal(cell) {
29
30
  cell.scope.dispose();
@@ -37,7 +38,11 @@ function useSignal$(initial, slot) {
37
38
  return { scope, signal$: scope.signal$("value", value) };
38
39
  },
39
40
  disposeLocalSignal,
40
- slot
41
+ slot,
42
+ process.env.NODE_ENV === "production" || typeof initial === "function" || !(0, import_runtime.isNativeSignalDiagnosticProbe)() ? void 0 : (0, import_diagnostic_local_hook.createNativeSignalDiagnosticInitializer)(() => {
43
+ const scope = (0, import_engine.createDiagnosticLocalScope)("octane/useSignal$");
44
+ return { scope, signal$: scope.signal$("value", initial) };
45
+ })
41
46
  );
42
47
  return cell.signal$;
43
48
  }
@@ -0,0 +1,37 @@
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 diagnostic_local_hook_exports = {};
20
+ __export(diagnostic_local_hook_exports, {
21
+ createNativeSignalDiagnosticInitializer: () => createNativeSignalDiagnosticInitializer,
22
+ isNativeSignalDiagnosticInitializer: () => isNativeSignalDiagnosticInitializer
23
+ });
24
+ module.exports = __toCommonJS(diagnostic_local_hook_exports);
25
+ const DIAGNOSTIC_SIGNAL_INITIALIZERS = /* @__PURE__ */ new WeakSet();
26
+ function createNativeSignalDiagnosticInitializer(initialize) {
27
+ DIAGNOSTIC_SIGNAL_INITIALIZERS.add(initialize);
28
+ return initialize;
29
+ }
30
+ function isNativeSignalDiagnosticInitializer(value) {
31
+ return DIAGNOSTIC_SIGNAL_INITIALIZERS.has(value);
32
+ }
33
+ // Annotate the CommonJS export names for ESM import in node:
34
+ 0 && (module.exports = {
35
+ createNativeSignalDiagnosticInitializer,
36
+ isNativeSignalDiagnosticInitializer
37
+ });
@@ -24,6 +24,7 @@ __export(engine_exports, {
24
24
  bindScopeStreamedSelection: () => bindScopeStreamedSelection,
25
25
  createDeclaredSignalCell: () => createDeclaredSignalCell,
26
26
  createDerivedCellWith: () => createDerivedCellWith,
27
+ createDiagnosticLocalScope: () => createDiagnosticLocalScope,
27
28
  createLocalScope: () => createLocalScope,
28
29
  createResourceCellWith: () => createResourceCellWith,
29
30
  createScope: () => createScope,
@@ -105,6 +106,7 @@ function decodeSeed(scopeKey, seed) {
105
106
  }
106
107
  return entries;
107
108
  }
109
+ const DIAGNOSTIC_LOCAL_SCOPES = /* @__PURE__ */ new WeakSet();
108
110
  class ScopeImpl {
109
111
  constructor(key, options, seedable = true) {
110
112
  this.key = key;
@@ -591,7 +593,8 @@ class ScopeImpl {
591
593
  }
592
594
  dispose() {
593
595
  if (this.disposed) return;
594
- (0, import_graph.assertWritable)();
596
+ if (process.env.NODE_ENV === "production" || !DIAGNOSTIC_LOCAL_SCOPES.has(this))
597
+ (0, import_graph.assertWritable)();
595
598
  (0, import_graph.signalBatch)(() => {
596
599
  this.disposed = true;
597
600
  this.lifetime++;
@@ -734,6 +737,13 @@ function createScope(options) {
734
737
  function createLocalScope(scopeKey) {
735
738
  return new ScopeImpl(scopeKey, { scopeKey }, false);
736
739
  }
740
+ function createDiagnosticLocalScope(scopeKey) {
741
+ if (process.env.NODE_ENV === "production" || !(0, import_read_protocol.isNativeWriteGuarded)() || (0, import_read_protocol.getNativeReadObserver)() !== null)
742
+ throw new import_errors.SignalFrameError("Diagnostic local scopes require an isolated historical read.");
743
+ const scope = new ScopeImpl(scopeKey, { scopeKey }, false);
744
+ DIAGNOSTIC_LOCAL_SCOPES.add(scope);
745
+ return scope;
746
+ }
737
747
  function createDeclaredSignalCell(owner, key, initial, preferInitial = false) {
738
748
  if (!(owner instanceof ScopeImpl)) throw new TypeError("A direct signal needs an Octane scope.");
739
749
  return owner.createSignalDeclaration(key, initial, preferInitial);
@@ -776,6 +786,7 @@ function failScopeStreamedResult(owner, identity, error) {
776
786
  bindScopeStreamedSelection,
777
787
  createDeclaredSignalCell,
778
788
  createDerivedCellWith,
789
+ createDiagnosticLocalScope,
779
790
  createLocalScope,
780
791
  createResourceCellWith,
781
792
  createScope,
@@ -21,7 +21,7 @@ __export(version_exports, {
21
21
  version: () => version
22
22
  });
23
23
  module.exports = __toCommonJS(version_exports);
24
- const version = "0.4.0";
24
+ const version = "0.4.2";
25
25
  // Annotate the CommonJS export names for ESM import in node:
26
26
  0 && (module.exports = {
27
27
  version
@@ -14366,6 +14366,7 @@ function ssrEmitActivity(node, ctx, name, inlinedSubs, parentNs, cssHash, compon
14366
14366
 
14367
14367
  function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs) {
14368
14368
  const bindingSite = node._octaneBindingSite;
14369
+ const itemBody = forItemTemplateBody(node, ctx);
14369
14370
  // rewriteHookCalls: key any `use(thenable)` in the @for iterable expression.
14370
14371
  const itemsExpr = rewriteHookCalls(node.right, ctx, name);
14371
14372
  const mapCall = node.nativeArrayMap || null;
@@ -14373,7 +14374,7 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
14373
14374
  const params = [itemId];
14374
14375
  if (node.index) params.push(node.index);
14375
14376
  const itemSub = ssrCompileSub(
14376
- node.body.body,
14377
+ itemBody,
14377
14378
  ctx,
14378
14379
  '__sitem',
14379
14380
  params,
@@ -14461,8 +14462,8 @@ function ssrEmitFor(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs
14461
14462
  // attribute serialization stay on the allocation-free path, matching the
14462
14463
  // client forBlock's existing PURE-body proof.
14463
14464
  const itemNeedsIdentity =
14464
- containsComponentCallOrControlFlow(node.body.body) ||
14465
- containsRenderCall(node.body.body) ||
14465
+ containsComponentCallOrControlFlow(itemBody) ||
14466
+ containsRenderCall(itemBody) ||
14466
14467
  ssrAstCallsAny(itemSub.fn, ['_$ssrChild', '_$ssrComponent']);
14467
14468
  const itemCall = ssrSubCall(
14468
14469
  itemSub.fnName,
@@ -25141,6 +25142,7 @@ function planJsx(
25141
25142
  b.deferred =
25142
25143
  DEFERRABLE_MOUNT_KINDS.has(b.kind) &&
25143
25144
  !sharesSpreadHost &&
25145
+ !b.beforeConnect &&
25144
25146
  b.name !== 'dangerouslySetInnerHTML';
25145
25147
  }
25146
25148
 
@@ -25213,7 +25215,9 @@ function planJsx(
25213
25215
  // these text mounts and flush them AFTER every walk has been emitted, so all navigation
25214
25216
  // happens on the intact template. (Regression: StoryRow's `.meta` interleaves text holes
25215
25217
  // with `<Link>` components.)
25216
- const deferredTextMounts = [];
25218
+ // Connection-sensitive attributes also wait for every mount flag (including
25219
+ // suppressHydrationWarning) before writing, but still run before insertion.
25220
+ const postWalkMounts = [];
25217
25221
  let nativeStyleSlots = 0;
25218
25222
  // Emit per-binding mount code.
25219
25223
  for (const b of elementBindings) {
@@ -25329,7 +25333,7 @@ function planJsx(
25329
25333
  // `htextSwap` (sibling text hole) detaches its `<!>`; defer it past all walks (see above).
25330
25334
  const mountEmit = emitBindingMount(b, elVar, bag);
25331
25335
  if (mountEmit !== null) {
25332
- const target = b.kind === 'text' ? deferredTextMounts : mountLines;
25336
+ const target = b.kind === 'text' || b.beforeConnect ? postWalkMounts : mountLines;
25333
25337
  if (Array.isArray(mountEmit)) target.push(...mountEmit);
25334
25338
  else target.push(mountEmit);
25335
25339
  }
@@ -25395,9 +25399,9 @@ function planJsx(
25395
25399
  anchorKey: 'portalAnchor',
25396
25400
  stampLoc: false,
25397
25401
  });
25398
- // Flush the deferred sibling-text-hole mounts now that every element walk is emitted —
25399
- // `htextSwap` can safely detach its `<!>` placeholder without breaking later navigation.
25400
- for (const line of deferredTextMounts) mountLines.push(line);
25402
+ // Every element walk and mount flag is ready: text holes can replace their
25403
+ // placeholders and iframe sources can initialize before the root is inserted.
25404
+ for (const line of postWalkMounts) mountLines.push(line);
25401
25405
 
25402
25406
  if (!noTemplate) {
25403
25407
  // Allocate + insert + commit in ONE shared-factory call, LAST — see the
@@ -29349,6 +29353,9 @@ function emitElementHtml(
29349
29353
  id: bindings.length,
29350
29354
  kind: 'attr',
29351
29355
  attributeHelper: writer?.helper,
29356
+ // Connecting an iframe without its source creates an initial
29357
+ // document before the intended navigation and sandbox checks.
29358
+ beforeConnect: tag === 'iframe' && attrName.toLowerCase() === 'src',
29352
29359
  name:
29353
29360
  ctx.dev && (rawAttrName === 'tabIndex' || rawAttrName === 'htmlFor')
29354
29361
  ? rawAttrName
@@ -31580,6 +31587,79 @@ function keyedSelectionDepIndex(itemName, keyBody, subStmts, runtimeDepNames, ct
31580
31587
  return runtimeDepNames.indexOf(selected.name);
31581
31588
  }
31582
31589
 
31590
+ // The first root's legacy key already owns the entire row through keyFn.
31591
+ // Avoid a second descriptor boundary for that same key. Only consume a host
31592
+ // key when the row cannot change a collected key before a later child reads it.
31593
+ // Opaque rendering, spreads and duplicate keys retain the existing boundary.
31594
+ // Component keys and nested host keys retain their own reconciliation ranges.
31595
+ function forItemTemplateBody(node, ctx) {
31596
+ const body = node.body.body;
31597
+ if (ctx._universalRuntimeUnit != null || body.length !== 1) return body;
31598
+ const root = body[0];
31599
+ if (!isPlainHostRoot(root) || isActivityLongForm(root, ctx) || isFragmentLongForm(root, ctx))
31600
+ return body;
31601
+ const attrs = root.attributes || root.openingElement?.attributes || [];
31602
+ let keyIndex = -1;
31603
+ for (let i = 0; i < attrs.length; i++) {
31604
+ const attr = attrs[i];
31605
+ if (attr.type === 'SpreadAttribute' || attr.type === 'JSXSpreadAttribute') return body;
31606
+ if (jsxAttrRawName(attr) !== 'key') continue;
31607
+ if (keyIndex !== -1 || attr.value == null) return body;
31608
+ keyIndex = i;
31609
+ }
31610
+ if (keyIndex === -1) return body;
31611
+ const value = attrs[keyIndex].value;
31612
+ const key = value.type === 'JSXExpressionContainer' ? value.expression : value;
31613
+ if (!isDeferralSafeBundleArg(key)) return body;
31614
+ // Destructuring must not invoke defaults, computed reads or an iterator.
31615
+ if (!isAutoMemoPropsParam(node.left.declarations[0].id)) return body;
31616
+ function stableHost(host) {
31617
+ if (!isPlainHostRoot(host) || isActivityLongForm(host, ctx) || isFragmentLongForm(host, ctx))
31618
+ return false;
31619
+ const tag = host.id?.name ?? host.openingElement?.name?.name;
31620
+ if (tag.includes('-') || tag === 'script') return false;
31621
+ const attributes = host.attributes || host.openingElement?.attributes || [];
31622
+ if (
31623
+ attributes.some(
31624
+ (attr) =>
31625
+ attr.type === 'SpreadAttribute' ||
31626
+ attr.type === 'JSXSpreadAttribute' ||
31627
+ jsxAttrRawName(attr) === 'is' ||
31628
+ jsxAttrRawName(attr) === 'children',
31629
+ )
31630
+ )
31631
+ return false;
31632
+ // Inspect values so deferred event/ref callbacks remain eligible.
31633
+ const values = attributes.map((attr) => attr.value);
31634
+ if (containsRenderCall(values) || containsAutoMemoUnsafeStructure(values)) return false;
31635
+ for (const child of host.children || []) {
31636
+ if (child.type === 'JSXText') continue;
31637
+ if (child.type === 'Text' || child.type === 'JSXExpressionContainer') {
31638
+ const expr = child.expression;
31639
+ if (!expr || expr.type === 'JSXEmptyExpression') continue;
31640
+ if (
31641
+ !isKnownTextChildExpression(expr) ||
31642
+ containsRenderCall([expr]) ||
31643
+ containsAutoMemoUnsafeStructure([expr])
31644
+ )
31645
+ return false;
31646
+ } else if (!stableHost(child)) return false;
31647
+ }
31648
+ return true;
31649
+ }
31650
+ if (!stableHost(root)) return body;
31651
+ const kept = attrs.filter((_, index) => index !== keyIndex);
31652
+ return [
31653
+ root.openingElement
31654
+ ? {
31655
+ ...root,
31656
+ ...(root.attributes === undefined ? {} : { attributes: kept }),
31657
+ openingElement: { ...root.openingElement, attributes: kept },
31658
+ }
31659
+ : { ...root, attributes: kept },
31660
+ ];
31661
+ }
31662
+
31583
31663
  function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null) {
31584
31664
  // `@for await (...)` (async iteration) has no meaning for the runtime's
31585
31665
  // synchronous keyed reconciler. The TSRX parser currently rejects the surface
@@ -31613,7 +31693,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
31613
31693
  // we synthesize a fresh name and emit the destructuring inside the body so
31614
31694
  // the keyFn still gets the whole item and the body still sees the fields.
31615
31695
  const itemName = isDestructured ? '_item' : leftDeclId.name;
31616
- const subStmts = node.body.body;
31696
+ const subStmts = forItemTemplateBody(node, ctx);
31617
31697
 
31618
31698
  // Key resolution priority (matches @tsrx/core's build_hoisted_for_of_with_hooks):
31619
31699
  // 1. `key={…}` attribute on the first Element child (legacy / explicit).
@@ -31638,7 +31718,7 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
31638
31718
  // body for the first Element and pull its `key=` attr if any. Accept both
31639
31719
  // the old `Element` IR and the raw new `JSXElement` shape that's reached
31640
31720
  // here when the body wasn't routed through normalizeChildren.
31641
- const firstEl = subStmts.find((n) => n.type === 'Element' || n.type === 'JSXElement');
31721
+ const firstEl = node.body.body.find((n) => n.type === 'Element' || n.type === 'JSXElement');
31642
31722
  if (firstEl) {
31643
31723
  const keyAttr = (firstEl.attributes || firstEl.openingElement?.attributes || []).find(
31644
31724
  (a) => (a.name?.name || a.name) === 'key',
@@ -31848,18 +31928,17 @@ function makeForCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null)
31848
31928
  const regionStmts = emptyStmts ? [...subStmts, ...emptyStmts] : subStmts;
31849
31929
  const regionAst = b.block(regionStmts);
31850
31930
  const regionFree = collectFreeIdentifiers(regionAst, bodyScope);
31851
- if (node.key) {
31852
- for (const name of collectFreeIdentifiers(node.key, bodyScope)) regionFree.add(name);
31853
- }
31931
+ for (const name of collectFreeIdentifiers(keyBody, bodyScope)) regionFree.add(name);
31854
31932
  let listSafe =
31855
31933
  ctx.autoMemo === true &&
31856
31934
  !hasPerItemEventClosure &&
31857
31935
  isAutoMemoCalculationDependency(node.right) &&
31858
31936
  !hasHook &&
31859
31937
  !containsRenderCall(regionStmts, ctx) &&
31860
- !containsRenderCall(node.key ? [node.key] : [], ctx) &&
31938
+ !containsRenderCall([keyBody], ctx) &&
31861
31939
  !containsAutoMemoUnsafeStructure(regionStmts, ctx) &&
31862
- !containsAutoMemoUnsafeStructure(node.key ? [node.key] : [], ctx) &&
31940
+ !containsAutoMemoUnsafeStructure([keyBody], ctx) &&
31941
+ !containsImportedMemberRead(keyBody, ctx.importedNames) &&
31863
31942
  !containsImportedMemberRead(regionAst, ctx.importedNames);
31864
31943
  const listDeps = new Set();
31865
31944
  const witnesses = collectImportedComponentReferences(regionAst, ctx.importedNames);
@@ -122,6 +122,21 @@ for (const name of [
122
122
  ]) {
123
123
  KNOWN_SVG_PROPERTY_SPELLINGS.set(name.toLowerCase(), name);
124
124
  }
125
+ const NON_NATIVE_LOWERCASE_PROPERTIES = /* @__PURE__ */ new Set([
126
+ "autoFocus",
127
+ "defaultValue",
128
+ "defaultChecked",
129
+ "dangerouslySetInnerHTML",
130
+ "className",
131
+ "suppressContentEditableWarning",
132
+ "suppressHydrationWarning",
133
+ "suppressNativeChangeWarning",
134
+ "__octaneNativeChangeDiagnostic",
135
+ "acceptCharset",
136
+ "htmlFor",
137
+ "httpEquiv",
138
+ "viewBox"
139
+ ]);
125
140
  function hostPropertyWarning(name, value, tag, isSvg = false) {
126
141
  const lower = name.toLowerCase();
127
142
  if (name === "for" && tag === "label") return null;
@@ -137,6 +152,9 @@ function hostPropertyWarning(name, value, tag, isSvg = false) {
137
152
  if (name.startsWith("aria-") || /^aria[A-Z]/.test(name) || name.startsWith("data-")) return null;
138
153
  const known = KNOWN_PROPERTY_SPELLINGS.get(lower) ?? (isSvg ? KNOWN_SVG_PROPERTY_SPELLINGS.get(lower) : void 0);
139
154
  if (known !== void 0) {
155
+ if (!isSvg && name === lower && // Hyphenated/namespaced presentation aliases belong to SVG, not HTML.
156
+ !name.includes("-") && !name.includes(":") && !NON_NATIVE_LOWERCASE_PROPERTIES.has(known) && (ATTRIBUTE_ALIASES.get(known) ?? lower) === lower)
157
+ return null;
140
158
  if (isSvg && ATTRIBUTE_ALIASES.get(known) === name) return null;
141
159
  return name !== known ? `Invalid DOM property \`${name}\`. Did you mean \`${known}\`?` : null;
142
160
  }
package/dist/runtime.d.ts CHANGED
@@ -24,6 +24,7 @@ import { validateNativeReadWitness, type NativeReadWitness } from './signals/nat
24
24
  import { type NativeReadRetry } from './signals/native-read-retry.js';
25
25
  import type { SignalActionFrame } from './signals/transition-action.js';
26
26
  import { setNativeAdoptionResolver } from './signals/read-protocol.js';
27
+ import { type DiagnosticSignalInitializer } from './signals/diagnostic-local-hook.js';
27
28
  import { type NativeAdoptionState } from './signals/native-read-seeds.js';
28
29
  export { documentSignalOwner, installStreamedSignalOwnerActivator, } from './signals/document-owner.js';
29
30
  import { type SignalHandle, type SignalOwner, type SignalRendererOwnerIdentity } from './signals/types.js';
@@ -824,8 +825,10 @@ export declare function manualHook<F extends (...args: any[]) => any>(fn: F, nam
824
825
  /** Invoke a cached optional-chain method without consulting its call/bind properties. */
825
826
  export declare function callWithReceiver<T>(fn: (...args: any[]) => T, receiver: unknown, ...args: any[]): T;
826
827
  export declare function withSlot<T>(sym: symbol, fn: (...a: any[]) => T, ...args: any[]): T;
828
+ /** @internal Built-in adapters are allocated only during historical comparison. */
829
+ export declare function isNativeSignalDiagnosticProbe(): boolean;
827
830
  /** @internal Optional native hooks use the existing compiler slot and Scope lifetime. */
828
- export declare function nativeLocalHook<T>(name: string, initialize: () => T, dispose: (value: T) => void, slot?: HookSlot): T;
831
+ export declare function nativeLocalHook<T>(name: string, initialize: () => T, dispose: (value: T) => void, slot?: HookSlot, diagnosticInitialize?: DiagnosticSignalInitializer<T>): T;
829
832
  interface StateSlot<T> {
830
833
  value: T;
831
834
  setter: (next: T | ((prev: T) => T)) => void;
package/dist/runtime.js CHANGED
@@ -180,9 +180,14 @@ import {
180
180
  registerNativeActionResolver,
181
181
  runNativeBatch,
182
182
  setNativeCandidateResolver,
183
- setNativeAdoptionResolver
183
+ setNativeAdoptionResolver,
184
+ beginNativeWriteGuard,
185
+ endNativeWriteGuard
184
186
  } from "./signals/read-protocol.js";
185
187
  import { beginNativeEventBatch, endNativeEventBatch } from "./signals/native-read-events.js";
188
+ import {
189
+ isNativeSignalDiagnosticInitializer
190
+ } from "./signals/diagnostic-local-hook.js";
186
191
  import {
187
192
  createNativeAdoptionState,
188
193
  NATIVE_SIGNAL_SEED_ATTR,
@@ -686,12 +691,12 @@ function nativePresentationBinding(owner, slotIndex, el, body, props) {
686
691
  }
687
692
  function enableNativeReadCollection(abi = 1) {
688
693
  if (abi !== 1) throw new Error(formatClientError(58));
694
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return;
689
695
  ensureNativeReadDriver();
690
696
  }
691
697
  function beginNativeReadScope(scope, abi = 1) {
692
- if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null)
693
- throw new PresentationAdoptionMiss(void 0, false);
694
698
  if (abi !== 1) throw new Error(formatClientError(58));
699
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return -1;
695
700
  const block = CURRENT_BLOCK;
696
701
  const owner = scope ?? CURRENT_SCOPE;
697
702
  if (block === null || owner === null) return -1;
@@ -807,12 +812,14 @@ function endNativeReadScope(token, _completed) {
807
812
  if (token >= 0) NATIVE_READ_DRIVER.endScope(token);
808
813
  }
809
814
  function beginNativeReadWitness(detached = false) {
815
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return -1;
810
816
  return detached ? ensureNativeReadDriver().beginWitness(true) : NATIVE_READ_DRIVER?.beginWitness() ?? -1;
811
817
  }
812
818
  function finishNativeReadWitness(token, completed) {
813
819
  return token < 0 ? null : NATIVE_READ_DRIVER.finishWitness(token, completed);
814
820
  }
815
821
  function replayNativeReadWitness(witness) {
822
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null) return;
816
823
  NATIVE_READ_DRIVER?.replay(witness);
817
824
  }
818
825
  function retainNativeRetryReads(block, reads) {
@@ -6642,7 +6649,8 @@ function renderBlockInner(block) {
6642
6649
  let profileThrown;
6643
6650
  let renderCompleted = false;
6644
6651
  let renderRetry = false;
6645
- NATIVE_READ_DRIVER?.beginRender(block);
6652
+ if (process.env.NODE_ENV === "production" || hydrateAttributeProbe === null)
6653
+ NATIVE_READ_DRIVER?.beginRender(block);
6646
6654
  NATIVE_BLOCK_RETRIES?.get(block)?.clear();
6647
6655
  try {
6648
6656
  if (urgentTransitionRender !== null)
@@ -6710,7 +6718,8 @@ function renderBlockInner(block) {
6710
6718
  CURRENT_SCOPE = prevScope;
6711
6719
  CURRENT_BLOCK = prevBlock;
6712
6720
  try {
6713
- NATIVE_READ_DRIVER?.endRender(block, renderCompleted, isSuspenseException(profileThrown));
6721
+ if (process.env.NODE_ENV === "production" || hydrateAttributeProbe === null)
6722
+ NATIVE_READ_DRIVER?.endRender(block, renderCompleted, isSuspenseException(profileThrown));
6714
6723
  } finally {
6715
6724
  if (urgentTransitionRender !== null)
6716
6725
  urgentTransitionDriver.endUrgent(block, urgentTransitionRender);
@@ -7328,15 +7337,20 @@ function resolveSlot(slot) {
7328
7337
  const resolved = resolveHookPath(slotStack, slot, false);
7329
7338
  return typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__ ? __profileResolveHook(resolved, typeof slot === "symbol" ? slot : void 0) : resolved;
7330
7339
  }
7331
- function nativeLocalHook(name, initialize, dispose, slot) {
7332
- if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null)
7340
+ function isNativeSignalDiagnosticProbe() {
7341
+ return process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null;
7342
+ }
7343
+ function nativeLocalHook(name, initialize, dispose, slot, diagnosticInitialize) {
7344
+ if (process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null && (name !== "useSignal$" || diagnosticInitialize === void 0 || !isNativeSignalDiagnosticInitializer(diagnosticInitialize)))
7333
7345
  throw new PresentationAdoptionMiss(void 0, false);
7334
7346
  slot = resolveSlot(slot);
7335
7347
  if (slot === void 0 || CURRENT_SCOPE === null) missingSlot(name);
7336
7348
  const scope = CURRENT_SCOPE;
7337
7349
  let cell = scope.hooks?.get(slot);
7338
7350
  if (cell === void 0) {
7339
- cell = { nativeValue: initialize() };
7351
+ cell = {
7352
+ nativeValue: process.env.NODE_ENV !== "production" && hydrateAttributeProbe !== null ? diagnosticInitialize() : initialize()
7353
+ };
7340
7354
  ensureHooks(scope).set(slot, cell);
7341
7355
  const value = cell.nativeValue;
7342
7356
  registerHookCleanup(scope, () => dispose(value));
@@ -8794,6 +8808,7 @@ function collectInitialHydrateAttributes(state, snapshot) {
8794
8808
  if (state.seedRaw !== null) hydration.seeds = hydration.parseSeeds(state.seedRaw);
8795
8809
  hydration.protectRootAnchor(end);
8796
8810
  const capture = createOffscreenCapture();
8811
+ const diagnosticNative = state.nativeSeedRaw === void 0 ? void 0 : createNativeAdoptionState(parseNativeSignalManifest(state.nativeSeedRaw));
8797
8812
  const previousHydration = currentHydration;
8798
8813
  const previousCapture = WIP_CAPTURE;
8799
8814
  const previousProbe = hydrateAttributeProbe;
@@ -8813,7 +8828,10 @@ function collectInitialHydrateAttributes(state, snapshot) {
8813
8828
  const previousSelectDefaultSyncs = SELECT_DEFAULT_SYNCS;
8814
8829
  const previousFormChecks = DEV_FORM_CHECKS;
8815
8830
  const previousAutofocus = AUTOFOCUS_QUEUE;
8816
- const previousNative = setNativeAdoptionResolver(null);
8831
+ const diagnosticDriver = NATIVE_READ_DRIVER;
8832
+ const nativeProbeToken = diagnosticDriver?.pauseLifecycle() ?? -1;
8833
+ const previousWriteGuard = beginNativeWriteGuard();
8834
+ const previousNative = setNativeAdoptionResolver(diagnosticNative?.resolve ?? null);
8817
8835
  const refDetachCheckpoint = refDetachQueue.length;
8818
8836
  currentHydration = hydration;
8819
8837
  WIP_CAPTURE = capture;
@@ -8843,27 +8861,36 @@ function collectInitialHydrateAttributes(state, snapshot) {
8843
8861
  disposeWip({ block, start, end, capture, domParent: wrapper, refDetachCheckpoint });
8844
8862
  drainHydrationRenderPhaseUpdates(block);
8845
8863
  } finally {
8846
- setNativeAdoptionResolver(previousNative);
8847
- currentHydration = previousHydration;
8848
- WIP_CAPTURE = previousCapture;
8849
- hydrateAttributeProbe = previousProbe;
8850
- ROOT_RENDER_TRANSACTION = previousTransaction;
8851
- TRANSITION_JOURNAL = previousJournal;
8852
- ACTIVE_TRANSITION_ATTEMPT = previousAttempt;
8853
- SCOPED_READ_TRACKING = previousScopedReadTracking;
8854
- SCOPED_READS = previousScopedReads;
8855
- CURRENT_WARM = previousWarm;
8856
- CURRENT_WARM_CLAIMS = previousWarmClaims;
8857
- CURRENT_WARM_EPISODE = previousWarmEpisode;
8858
- ACTIVE_WARM_PLANS.length = 0;
8859
- ACTIVE_WARM_PLANS.push(...previousWarmPlans);
8860
- STAGED_DOM = previousStagedDom;
8861
- STAGED_COMMIT_CAPTURE = previousStagedCapture;
8862
- PRESENTATION_HYDRATION = previousPresentation;
8863
- SELECT_SYNCS = previousSelectSyncs;
8864
- SELECT_DEFAULT_SYNCS = previousSelectDefaultSyncs;
8865
- DEV_FORM_CHECKS = previousFormChecks;
8866
- AUTOFOCUS_QUEUE = previousAutofocus;
8864
+ try {
8865
+ diagnosticNative?.release();
8866
+ } finally {
8867
+ try {
8868
+ setNativeAdoptionResolver(previousNative);
8869
+ endNativeWriteGuard(previousWriteGuard);
8870
+ if (nativeProbeToken >= 0) diagnosticDriver.resumeLifecycle(nativeProbeToken);
8871
+ } finally {
8872
+ currentHydration = previousHydration;
8873
+ WIP_CAPTURE = previousCapture;
8874
+ hydrateAttributeProbe = previousProbe;
8875
+ ROOT_RENDER_TRANSACTION = previousTransaction;
8876
+ TRANSITION_JOURNAL = previousJournal;
8877
+ ACTIVE_TRANSITION_ATTEMPT = previousAttempt;
8878
+ SCOPED_READ_TRACKING = previousScopedReadTracking;
8879
+ SCOPED_READS = previousScopedReads;
8880
+ CURRENT_WARM = previousWarm;
8881
+ CURRENT_WARM_CLAIMS = previousWarmClaims;
8882
+ CURRENT_WARM_EPISODE = previousWarmEpisode;
8883
+ ACTIVE_WARM_PLANS.length = 0;
8884
+ ACTIVE_WARM_PLANS.push(...previousWarmPlans);
8885
+ STAGED_DOM = previousStagedDom;
8886
+ STAGED_COMMIT_CAPTURE = previousStagedCapture;
8887
+ PRESENTATION_HYDRATION = previousPresentation;
8888
+ SELECT_SYNCS = previousSelectSyncs;
8889
+ SELECT_DEFAULT_SYNCS = previousSelectDefaultSyncs;
8890
+ DEV_FORM_CHECKS = previousFormChecks;
8891
+ AUTOFOCUS_QUEUE = previousAutofocus;
8892
+ }
8893
+ }
8867
8894
  }
8868
8895
  }
8869
8896
  return probe;
@@ -24412,12 +24439,43 @@ function preservesCommittedSuspense(block) {
24412
24439
  }
24413
24440
  return false;
24414
24441
  }
24442
+ function markerlessBranchRoots(scope, domParent, after, end = null) {
24443
+ let roots = 0;
24444
+ const slots = scope._slots;
24445
+ if (slots !== null) {
24446
+ for (let i = 0; i < slots.length; i++) {
24447
+ const slot = slots[i];
24448
+ if (slot.anchor === after && (slot.__kind === "ifBlockSlot" || slot.__kind === "switchBlockSlot" || slot.__kind === "componentSlotSlot" && slot.block?.parentNode === domParent)) {
24449
+ roots++;
24450
+ if (end !== null) {
24451
+ journalRootProperty(slot, "anchor", slot.anchor);
24452
+ slot.anchor = end;
24453
+ }
24454
+ }
24455
+ }
24456
+ }
24457
+ const children = scope.children;
24458
+ if (children !== null) {
24459
+ for (let i = 0; i < children.length; i++) {
24460
+ const child2 = children[i].scope;
24461
+ const block = child2.block;
24462
+ if (block !== scope.block && block.parentNode === domParent && block.endMarker === after) {
24463
+ roots += markerlessBranchRoots(child2, domParent, after, end) || 1;
24464
+ if (end !== null) {
24465
+ journalRootProperty(block, "endMarker", block.endMarker);
24466
+ block.endMarker = end;
24467
+ }
24468
+ }
24469
+ }
24470
+ }
24471
+ return roots;
24472
+ }
24415
24473
  function finalizeMarkerlessBranch(state, domParent, block, marker, before, after) {
24416
24474
  state.markerlessBefore = void 0;
24417
24475
  if (state.borrowed && state.start === null) return;
24418
24476
  const first = before ? getNextSibling(before) : getFirstChild(domParent);
24419
24477
  const last = after ? (STAGED_DOM?.view(after) ?? after).previousSibling : (STAGED_DOM?.view(domParent) ?? domParent).lastChild;
24420
- if (last !== null && first === last && first.nodeType === 1) {
24478
+ if (last !== null && first === last && first.nodeType === 1 && markerlessBranchRoots(block, domParent, after) <= 1) {
24421
24479
  block.startMarker = first;
24422
24480
  block.endMarker = first;
24423
24481
  state.end = first;
@@ -24431,6 +24489,7 @@ function finalizeMarkerlessBranch(state, domParent, block, marker, before, after
24431
24489
  block.exclusiveMarkers = true;
24432
24490
  state.start = start;
24433
24491
  state.end = end;
24492
+ markerlessBranchRoots(block, domParent, after, end);
24434
24493
  }
24435
24494
  }
24436
24495
  function replaceSharedBlockBoundary(parent, oldStart, oldEnd, newStart, newEnd) {
@@ -28564,6 +28623,7 @@ export {
28564
28623
  isChildrenBlock,
28565
28624
  isHydratingStyle,
28566
28625
  isInActScope,
28626
+ isNativeSignalDiagnosticProbe,
28567
28627
  isValidElement,
28568
28628
  keyedForBlock,
28569
28629
  lazy,
@@ -1,6 +1,7 @@
1
1
  export * from "./index.js";
2
- import { nativeLocalHook } from "../runtime.js";
3
- import { createLocalScope } from "./engine.js";
2
+ import { isNativeSignalDiagnosticProbe, nativeLocalHook } from "../runtime.js";
3
+ import { createNativeSignalDiagnosticInitializer } from "./diagnostic-local-hook.js";
4
+ import { createDiagnosticLocalScope, createLocalScope } from "./engine.js";
4
5
  function disposeLocalSignal(cell) {
5
6
  cell.scope.dispose();
6
7
  }
@@ -13,7 +14,11 @@ function useSignal$(initial, slot) {
13
14
  return { scope, signal$: scope.signal$("value", value) };
14
15
  },
15
16
  disposeLocalSignal,
16
- slot
17
+ slot,
18
+ process.env.NODE_ENV === "production" || typeof initial === "function" || !isNativeSignalDiagnosticProbe() ? void 0 : createNativeSignalDiagnosticInitializer(() => {
19
+ const scope = createDiagnosticLocalScope("octane/useSignal$");
20
+ return { scope, signal$: scope.signal$("value", initial) };
21
+ })
17
22
  );
18
23
  return cell.signal$;
19
24
  }
@@ -0,0 +1,8 @@
1
+ declare const DIAGNOSTIC_SIGNAL_INITIALIZER: unique symbol;
2
+ export type DiagnosticSignalInitializer<T> = (() => T) & {
3
+ readonly [DIAGNOSTIC_SIGNAL_INITIALIZER]: true;
4
+ };
5
+ /** @internal Private built-in useSignal$ adapter; not a public native-hook resource factory. */
6
+ export declare function createNativeSignalDiagnosticInitializer<T>(initialize: () => T): DiagnosticSignalInitializer<T>;
7
+ export declare function isNativeSignalDiagnosticInitializer(value: () => unknown): boolean;
8
+ export {};
@@ -0,0 +1,12 @@
1
+ const DIAGNOSTIC_SIGNAL_INITIALIZERS = /* @__PURE__ */ new WeakSet();
2
+ function createNativeSignalDiagnosticInitializer(initialize) {
3
+ DIAGNOSTIC_SIGNAL_INITIALIZERS.add(initialize);
4
+ return initialize;
5
+ }
6
+ function isNativeSignalDiagnosticInitializer(value) {
7
+ return DIAGNOSTIC_SIGNAL_INITIALIZERS.has(value);
8
+ }
9
+ export {
10
+ createNativeSignalDiagnosticInitializer,
11
+ isNativeSignalDiagnosticInitializer
12
+ };
@@ -105,6 +105,8 @@ declare class AdoptionFrameImpl implements AdoptionFrame {
105
105
  export declare function createScope(options: ScopeOptions): Scope;
106
106
  /** Only the native hook adapter may create a component-owned, non-serializable scope. */
107
107
  export declare function createLocalScope(scopeKey: string): Scope;
108
+ /** @internal Isolated historical useSignal$ state, never a live presentation cell. */
109
+ export declare function createDiagnosticLocalScope(scopeKey: string): Scope;
108
110
  export declare function createDeclaredSignalCell<T>(owner: Scope, key: string, initial: T, preferInitial?: boolean): WritableSignal<T>;
109
111
  export declare function createDerivedCellWith<T>(owner: Scope, key: string, compute: DerivedCompute<T>, options: DerivedOptions | undefined, Binding: DerivedBindingFactory<T>): DerivedSignal<T>;
110
112
  export declare function createResourceCellWith<T>(owner: Scope, key: string, describe: () => QueryRequest<T> | typeof skip, initialize: typeof initializeResource, unique?: boolean): Resource<T>;
@@ -30,6 +30,8 @@ import {
30
30
  beginNativeWriteGuard,
31
31
  endNativeWriteGuard,
32
32
  getNativeAdoptionResolver,
33
+ getNativeReadObserver,
34
+ isNativeWriteGuarded,
33
35
  registerNativeBatchHooks,
34
36
  reportNativeRead
35
37
  } from "./read-protocol.js";
@@ -99,6 +101,7 @@ function decodeSeed(scopeKey, seed) {
99
101
  }
100
102
  return entries;
101
103
  }
104
+ const DIAGNOSTIC_LOCAL_SCOPES = /* @__PURE__ */ new WeakSet();
102
105
  class ScopeImpl {
103
106
  constructor(key, options, seedable = true) {
104
107
  this.key = key;
@@ -585,7 +588,8 @@ class ScopeImpl {
585
588
  }
586
589
  dispose() {
587
590
  if (this.disposed) return;
588
- assertWritable();
591
+ if (process.env.NODE_ENV === "production" || !DIAGNOSTIC_LOCAL_SCOPES.has(this))
592
+ assertWritable();
589
593
  signalBatch(() => {
590
594
  this.disposed = true;
591
595
  this.lifetime++;
@@ -728,6 +732,13 @@ function createScope(options) {
728
732
  function createLocalScope(scopeKey) {
729
733
  return new ScopeImpl(scopeKey, { scopeKey }, false);
730
734
  }
735
+ function createDiagnosticLocalScope(scopeKey) {
736
+ if (process.env.NODE_ENV === "production" || !isNativeWriteGuarded() || getNativeReadObserver() !== null)
737
+ throw new SignalFrameError("Diagnostic local scopes require an isolated historical read.");
738
+ const scope = new ScopeImpl(scopeKey, { scopeKey }, false);
739
+ DIAGNOSTIC_LOCAL_SCOPES.add(scope);
740
+ return scope;
741
+ }
731
742
  function createDeclaredSignalCell(owner, key, initial, preferInitial = false) {
732
743
  if (!(owner instanceof ScopeImpl)) throw new TypeError("A direct signal needs an Octane scope.");
733
744
  return owner.createSignalDeclaration(key, initial, preferInitial);
@@ -769,6 +780,7 @@ export {
769
780
  bindScopeStreamedSelection,
770
781
  createDeclaredSignalCell,
771
782
  createDerivedCellWith,
783
+ createDiagnosticLocalScope,
772
784
  createLocalScope,
773
785
  createResourceCellWith,
774
786
  createScope,
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.4.0";
1
+ const version = "0.4.2";
2
2
  export {
3
3
  version
4
4
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octane",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": [