octane 0.3.1 → 0.3.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.
Files changed (45) hide show
  1. package/dist/cjs/constants.cjs +18 -25
  2. package/dist/cjs/css.cjs +6 -0
  3. package/dist/cjs/dom-binding-protocol.cjs +37 -14
  4. package/dist/cjs/hydration/control-capture.cjs +12 -4
  5. package/dist/cjs/hydration/event-capture.cjs +6 -7
  6. package/dist/cjs/hydration-markers.cjs +40 -2
  7. package/dist/cjs/runtime-tags.cjs +55 -0
  8. package/dist/cjs/runtime.cjs +136 -113
  9. package/dist/cjs/runtime.server.cjs +34 -41
  10. package/dist/cjs/server/streamed-signals.cjs +7 -4
  11. package/dist/cjs/signals/control-binding.cjs +2 -1
  12. package/dist/cjs/stream-protocol.cjs +9 -8
  13. package/dist/cjs/version.cjs +1 -1
  14. package/dist/compiler/compile.js +70 -17
  15. package/dist/compiler/dom-bindings.js +5 -0
  16. package/dist/constants.d.ts +7 -18
  17. package/dist/constants.js +11 -9
  18. package/dist/css.d.ts +7 -0
  19. package/dist/css.js +4 -0
  20. package/dist/dom-binding-program.js +12 -5
  21. package/dist/dom-binding-protocol.d.ts +14 -1
  22. package/dist/dom-binding-protocol.js +40 -14
  23. package/dist/hydration/control-capture.d.ts +2 -0
  24. package/dist/hydration/control-capture.js +18 -5
  25. package/dist/hydration/event-capture.js +1 -2
  26. package/dist/hydration/independent-island.js +5 -5
  27. package/dist/hydration-markers.d.ts +30 -0
  28. package/dist/hydration-markers.js +27 -1
  29. package/dist/react/index.js +1 -1
  30. package/dist/react/react-compat-shared.js +5 -3
  31. package/dist/react/shared.d.ts +1 -3
  32. package/dist/react/shared.js +1 -1
  33. package/dist/runtime-tags.d.ts +34 -0
  34. package/dist/runtime-tags.js +22 -0
  35. package/dist/runtime.d.ts +5 -14
  36. package/dist/runtime.js +116 -71
  37. package/dist/runtime.server.d.ts +2 -3
  38. package/dist/runtime.server.js +25 -17
  39. package/dist/server/streamed-signals.js +7 -4
  40. package/dist/signals/control-binding.js +4 -2
  41. package/dist/stream-protocol.js +20 -9
  42. package/dist/universal-core.js +8 -4
  43. package/dist/universal-native.js +1 -1
  44. package/dist/version.js +1 -1
  45. package/package.json +3 -3
@@ -5,7 +5,13 @@ import {
5
5
  __releaseBinding,
6
6
  __writeBinding
7
7
  } from "./dom-bindings.js";
8
- import { encodeBindingKey, parseBindingMarker } from "./dom-binding-protocol.js";
8
+ import {
9
+ bindingRootMarker,
10
+ BINDING_OPEN_PREFIX,
11
+ encodeBindingKey,
12
+ parseBindingMarker
13
+ } from "./dom-binding-protocol.js";
14
+ import { HYDRATION_FOR_PREFIX } from "./hydration-markers.js";
9
15
  import { moveNativeNodeBefore } from "./dom-focused-move.js";
10
16
  import { rendererRangeClose } from "./stream-protocol.js";
11
17
  import {
@@ -527,7 +533,7 @@ function createFragment(definition, id, document, transaction) {
527
533
  const template = document.createElement("template");
528
534
  template.innerHTML = definition.ns === 1 ? `<svg>${definition.html}</svg>` : definition.html;
529
535
  const content = document.createDocumentFragment();
530
- const start = document.createComment(`[b;${id};root`);
536
+ const start = document.createComment(bindingRootMarker(id));
531
537
  const end = document.createComment("]");
532
538
  content.appendChild(start);
533
539
  if (definition.ns === 1) {
@@ -875,7 +881,8 @@ function commitRegion(plan, id, transaction) {
875
881
  transaction
876
882
  );
877
883
  }
878
- if (definition.kind === "if") region.range.start.data = `[b;${id};${region.site};${plan.arm}`;
884
+ if (definition.kind === "if")
885
+ region.range.start.data = `${BINDING_OPEN_PREFIX}${id};${region.site};${plan.arm}`;
879
886
  }
880
887
  function writeText(region, value) {
881
888
  if (region.text === null) {
@@ -1469,13 +1476,13 @@ function commitList(plan, id, transaction) {
1469
1476
  }
1470
1477
  let anchor = region.range.end;
1471
1478
  for (const [key, item] of [...plan.items].reverse()) {
1472
- item.instance.range.start.data = `[b;${id};${region.site};k;${key}`;
1479
+ item.instance.range.start.data = `${BINDING_OPEN_PREFIX}${id};${region.site};k;${key}`;
1473
1480
  moveRange(item.instance.range, region.range.end.parentNode, anchor, transaction);
1474
1481
  if (transaction.disposed) return;
1475
1482
  anchor = item.instance.range.start;
1476
1483
  }
1477
1484
  }
1478
- region.range.start.data = `[f${plan.arm};b;${id};${region.site}`;
1485
+ region.range.start.data = `${HYDRATION_FOR_PREFIX}${plan.arm};b;${id};${region.site}`;
1479
1486
  return;
1480
1487
  }
1481
1488
  function claimProgramControl(instance, index, node, transaction) {
@@ -1,5 +1,12 @@
1
- /** Compiler-owned DOM presentation markers. This leaf is shared by client and SSR. */
2
1
  export type BindingKey = string | number;
2
+ /** Payload prefix opening a presentation-binding range. */
3
+ export declare const BINDING_OPEN_PREFIX: string;
4
+ /**
5
+ * Comment payload opening an authored presentation view's own component range.
6
+ * The client runtime and the SSR serializer both stamp this onto the view they
7
+ * bind, so the two must never spell it separately.
8
+ */
9
+ export declare function bindingRootMarker(id: string): string;
3
10
  export declare function encodeBindingKey(key: BindingKey): string;
4
11
  export declare function decodeBindingKey(encoded: string): BindingKey;
5
12
  export interface BindingMarker {
@@ -11,3 +18,9 @@ export interface BindingMarker {
11
18
  }
12
19
  export declare function parseBindingMarker(data: string): BindingMarker | null;
13
20
  export declare function isBindingOpenComment(data: string): boolean;
21
+ /**
22
+ * True when a binding-open payload carries an `@for` outer arm. The prefix test
23
+ * alone is not a decision: `isBindingOpenComment` still requires the full
24
+ * receipt, so this only spares callers from spelling the arm prefixes again.
25
+ */
26
+ export declare function isForBindingOpenComment(data: string): boolean;
@@ -1,3 +1,21 @@
1
+ import {
2
+ HYDRATION_FOR_ARM_INDEX,
3
+ HYDRATION_FOR_EMPTY,
4
+ HYDRATION_FOR_ITEMS,
5
+ HYDRATION_FOR_PREFIX,
6
+ HYDRATION_START
7
+ } from "./hydration-markers.js";
8
+ const BINDING_OPEN_PREFIX = HYDRATION_START + "b;";
9
+ const BINDING_RECEIPT = ";b;";
10
+ const BINDING_FOR_EMPTY_PREFIX = HYDRATION_FOR_EMPTY + BINDING_RECEIPT;
11
+ const BINDING_FOR_ITEMS_PREFIX = HYDRATION_FOR_ITEMS + BINDING_RECEIPT;
12
+ const BINDING_FOR_ID_START = BINDING_FOR_EMPTY_PREFIX.length;
13
+ const BINDING_ID_START = BINDING_OPEN_PREFIX.length;
14
+ const BINDING_ROOT_SITE = "root";
15
+ const BINDING_ROOT_SUFFIX = ";" + BINDING_ROOT_SITE;
16
+ function bindingRootMarker(id) {
17
+ return BINDING_OPEN_PREFIX + id + BINDING_ROOT_SUFFIX;
18
+ }
1
19
  function encodeBindingKey(key) {
2
20
  if (typeof key === "string")
3
21
  return "s:" + JSON.stringify(key).replace(/-/g, "\\u002d").replace(/</g, "\\u003c");
@@ -17,21 +35,22 @@ function decodeBindingKey(encoded) {
17
35
  throw new TypeError("A DOM presentation key has invalid serialized identity.");
18
36
  }
19
37
  function parseBindingMarker(data) {
20
- if (data.startsWith("[f0;b;") || data.startsWith("[f1;b;")) {
21
- const separator = data.indexOf(";", 6);
22
- if (separator <= 6 || separator === data.length - 1) return null;
38
+ if (data.startsWith(BINDING_FOR_EMPTY_PREFIX) || data.startsWith(BINDING_FOR_ITEMS_PREFIX)) {
39
+ const separator = data.indexOf(";", BINDING_FOR_ID_START);
40
+ if (separator <= BINDING_FOR_ID_START || separator === data.length - 1) return null;
23
41
  return {
24
- id: data.slice(6, separator),
42
+ id: data.slice(BINDING_FOR_ID_START, separator),
25
43
  site: data.slice(separator + 1),
26
44
  kind: "for",
27
- arm: data.charCodeAt(2) - 48
45
+ arm: data.charCodeAt(HYDRATION_FOR_ARM_INDEX) - 48
28
46
  };
29
47
  }
30
- if (!data.startsWith("[b;")) return null;
31
- const idEnd = data.indexOf(";", 3);
32
- if (idEnd <= 3 || idEnd === data.length - 1) return null;
33
- const id = data.slice(3, idEnd);
34
- if (data.slice(idEnd + 1) === "root") return { id, site: "root", kind: "root" };
48
+ if (!data.startsWith(BINDING_OPEN_PREFIX)) return null;
49
+ const idEnd = data.indexOf(";", BINDING_ID_START);
50
+ if (idEnd <= BINDING_ID_START || idEnd === data.length - 1) return null;
51
+ const id = data.slice(BINDING_ID_START, idEnd);
52
+ if (data.slice(idEnd + 1) === BINDING_ROOT_SITE)
53
+ return { id, site: BINDING_ROOT_SITE, kind: "root" };
35
54
  const siteEnd = data.indexOf(";", idEnd + 1);
36
55
  if (siteEnd <= idEnd + 1 || siteEnd === data.length - 1) return null;
37
56
  const site = data.slice(idEnd + 1, siteEnd);
@@ -56,13 +75,14 @@ function parseBindingMarker(data) {
56
75
  return null;
57
76
  }
58
77
  function isBindingOpenComment(data) {
59
- const list = data.startsWith("[f0;b;") || data.startsWith("[f1;b;");
60
- if (!list && !data.startsWith("[b;")) return false;
61
- const idStart = list ? 6 : 3;
78
+ const list = data.startsWith(BINDING_FOR_EMPTY_PREFIX) || data.startsWith(BINDING_FOR_ITEMS_PREFIX);
79
+ if (!list && !data.startsWith(BINDING_OPEN_PREFIX)) return false;
80
+ const idStart = list ? BINDING_FOR_ID_START : BINDING_ID_START;
62
81
  const idEnd = data.indexOf(";", idStart);
63
82
  if (idEnd <= idStart || idEnd === data.length - 1) return false;
64
83
  if (list) return data.indexOf(";", idEnd + 1) === -1;
65
- if (data.length - idEnd === 5 && data.endsWith(";root")) return true;
84
+ if (data.length - idEnd === BINDING_ROOT_SUFFIX.length && data.endsWith(BINDING_ROOT_SUFFIX))
85
+ return true;
66
86
  const siteEnd = data.indexOf(";", idEnd + 1);
67
87
  if (siteEnd <= idEnd + 1 || siteEnd === data.length - 1) return false;
68
88
  const tail = siteEnd + 1;
@@ -80,9 +100,15 @@ function isBindingOpenComment(data) {
80
100
  }
81
101
  return true;
82
102
  }
103
+ function isForBindingOpenComment(data) {
104
+ return data.startsWith(HYDRATION_FOR_PREFIX) && isBindingOpenComment(data);
105
+ }
83
106
  export {
107
+ BINDING_OPEN_PREFIX,
108
+ bindingRootMarker,
84
109
  decodeBindingKey,
85
110
  encodeBindingKey,
86
111
  isBindingOpenComment,
112
+ isForBindingOpenComment,
87
113
  parseBindingMarker
88
114
  };
@@ -45,6 +45,8 @@ export interface HydrationControlCandidateValue {
45
45
  * the state changed while ownership was being transferred.
46
46
  */
47
47
  export declare function snapshotHydrationControl(control: Element): HydrationControlSnapshot | null;
48
+ /** @internal Only unchanged server state may yield to an eventless textarea restore. */
49
+ export declare function isRestoredHydrationTextarea(control: Element, value: unknown): boolean;
48
50
  /**
49
51
  * Capture the exact DOM/edit authority an async storage read is allowed to replace.
50
52
  * A focus, selection, composition, input (including clear), node replacement, or
@@ -1,4 +1,10 @@
1
- import { HYDRATE_INDEPENDENT_ATTR, HYDRATE_INPUT_ATTR } from "../hydration-markers.js";
1
+ import {
2
+ HYDRATE_ID_ATTR,
3
+ HYDRATE_INDEPENDENT_ATTR,
4
+ HYDRATE_INPUT_ATTR,
5
+ HYDRATE_MARKER_SELECTOR,
6
+ HYDRATE_WHEN_ATTR
7
+ } from "../hydration-markers.js";
2
8
  import {
3
9
  EARLY_HYDRATION_INTENTS_KEY,
4
10
  HYDRATE_INTERACTION_EVENTS_ATTR
@@ -9,10 +15,9 @@ import {
9
15
  publishHydrationControlSignalValues,
10
16
  readEarlyHydrationControlRevision
11
17
  } from "../signals/early-values.js";
12
- const HYDRATE_MARKER_SELECTOR = "[data-octane-hydrate-id]";
13
18
  const HYDRATE_CONTROL_DOCUMENTS = /* @__PURE__ */ new WeakSet();
14
19
  function isEarlyHydrationIntentCurrent([event, target, boundary, id, when, events], ownerDocument) {
15
- return event.target === target && target.isConnected && boundary.isConnected && target.ownerDocument === ownerDocument && boundary.ownerDocument === ownerDocument && target.closest(`[${HYDRATE_INDEPENDENT_ATTR}]`) === boundary && boundary.getAttribute("data-octane-hydrate-id") === id && boundary.getAttribute("data-octane-hydrate-when") === when && boundary.getAttribute(HYDRATE_INTERACTION_EVENTS_ATTR) === events;
20
+ return event.target === target && target.isConnected && boundary.isConnected && target.ownerDocument === ownerDocument && boundary.ownerDocument === ownerDocument && target.closest(`[${HYDRATE_INDEPENDENT_ATTR}]`) === boundary && boundary.getAttribute(HYDRATE_ID_ATTR) === id && boundary.getAttribute(HYDRATE_WHEN_ATTR) === when && boundary.getAttribute(HYDRATE_INTERACTION_EVENTS_ATTR) === events;
16
21
  }
17
22
  function isHydrationElement(target) {
18
23
  return target !== null && target.nodeType === 1;
@@ -108,6 +113,13 @@ function snapshotHydrationControl(control) {
108
113
  composing: record?.composing ?? false
109
114
  };
110
115
  }
116
+ function isRestoredHydrationTextarea(control, value) {
117
+ if (control.localName !== "textarea" || typeof value !== "string" || HYDRATE_CONTROL_RECORDS.get(control)?.composing === true)
118
+ return false;
119
+ const textarea = control;
120
+ const baseline = textarea.defaultValue.replace(/\r\n?/g, "\n");
121
+ return value.replace(/\r\n?/g, "\n") === baseline && textarea.value !== baseline;
122
+ }
111
123
  function captureHydrationControlCandidate(control) {
112
124
  const snapshot = snapshotHydrationControl(control);
113
125
  if (snapshot === null) return null;
@@ -115,7 +127,7 @@ function captureHydrationControlCandidate(control) {
115
127
  return {
116
128
  control,
117
129
  bindingId: control.getAttribute(HYDRATE_INPUT_ATTR),
118
- boundaryId: control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute("data-octane-hydrate-id") ?? null,
130
+ boundaryId: control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute(HYDRATE_ID_ATTR) ?? null,
119
131
  snapshot
120
132
  };
121
133
  }
@@ -130,7 +142,7 @@ function sameHydrationControlValue(left, right) {
130
142
  }
131
143
  function applyHydrationControlCandidate(candidate, value) {
132
144
  const control = candidate.control;
133
- if (!control.isConnected || control.getAttribute(HYDRATE_INPUT_ATTR) !== candidate.bindingId || (control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute("data-octane-hydrate-id") ?? null) !== candidate.boundaryId) {
145
+ if (!control.isConnected || control.getAttribute(HYDRATE_INPUT_ATTR) !== candidate.bindingId || (control.closest(HYDRATE_MARKER_SELECTOR)?.getAttribute(HYDRATE_ID_ATTR) ?? null) !== candidate.boundaryId) {
134
146
  return false;
135
147
  }
136
148
  const current = snapshotHydrationControl(control);
@@ -219,5 +231,6 @@ export {
219
231
  consumeHydrationControl,
220
232
  initializeHydrationControlCapture,
221
233
  isEarlyHydrationIntentCurrent,
234
+ isRestoredHydrationTextarea,
222
235
  snapshotHydrationControl
223
236
  };
@@ -19,8 +19,7 @@ import {
19
19
  snapshotHydrationControl
20
20
  } from "./control-capture.js";
21
21
  import { HYDRATE_STREAM_TOKEN_ATTR, isRendererStreamBoundaryTemplate } from "../stream-protocol.js";
22
- const HYDRATE_MARKER_SELECTOR = "[data-octane-hydrate-id]";
23
- const HYDRATE_WHEN_ATTR = "data-octane-hydrate-when";
22
+ import { HYDRATE_MARKER_SELECTOR, HYDRATE_WHEN_ATTR } from "../hydration-markers.js";
24
23
  function isHydrationNode(target) {
25
24
  return target !== null && typeof target.nodeType === "number";
26
25
  }
@@ -1,5 +1,7 @@
1
1
  import { decodeSignalValue } from "../data-encoding.js";
2
2
  import {
3
+ HYDRATE_ID_ATTR,
4
+ HYDRATE_WHEN_ATTR,
3
5
  HYDRATE_INDEPENDENT_ATTR,
4
6
  INDEPENDENT_HYDRATE_MANIFEST_ATTR
5
7
  } from "../hydration-markers.js";
@@ -19,7 +21,7 @@ function registerIndependentHydrationIsland(element, manifest, registration) {
19
21
  if (!isIndependentHydrateManifest(manifest)) {
20
22
  throw new TypeError("Invalid independent Hydrate manifest.");
21
23
  }
22
- if (element.getAttribute("data-octane-hydrate-id") !== manifest.boundaryId) {
24
+ if (element.getAttribute(HYDRATE_ID_ATTR) !== manifest.boundaryId) {
23
25
  throw new Error("Independent Hydrate boundary identity mismatch.");
24
26
  }
25
27
  element.setAttribute(HYDRATE_INDEPENDENT_ATTR, "");
@@ -85,8 +87,7 @@ function registerIndependentHydrationIsland(element, manifest, registration) {
85
87
  return status;
86
88
  };
87
89
  registerHydrationIntentBoundary(element, boundary);
88
- if (intents.length !== 0 || element.getAttribute("data-octane-hydrate-when") === "load")
89
- activate();
90
+ if (intents.length !== 0 || element.getAttribute(HYDRATE_WHEN_ATTR) === "load") activate();
90
91
  return Object.assign(
91
92
  () => {
92
93
  if (disposed) return;
@@ -107,8 +108,7 @@ function registerIndependentHydrationIsland(element, manifest, registration) {
107
108
  resume() {
108
109
  if (disposed || !paused) return;
109
110
  paused = false;
110
- if (intents.length || element.getAttribute("data-octane-hydrate-when") === "load")
111
- activate();
111
+ if (intents.length || element.getAttribute(HYDRATE_WHEN_ATTR) === "load") activate();
112
112
  }
113
113
  }
114
114
  );
@@ -5,3 +5,33 @@ export declare const HYDRATE_INDEPENDENT_ATTR = "data-octane-hydrate-independent
5
5
  export declare const HYDRATE_INPUT_ATTR = "data-octane-input";
6
6
  /** Server-only writable-signal identity joined to a compiler control site. */
7
7
  export declare const SIGNAL_CONTROL_ATTR = "data-octane-signal-control";
8
+ /** Stable id of a server-rendered deferred hydration boundary. */
9
+ export declare const HYDRATE_ID_ATTR = "data-octane-hydrate-id";
10
+ /** Serialized strategy kind (`visible`, `idle`, `dynamic`, …). */
11
+ export declare const HYDRATE_WHEN_ATTR = "data-octane-hydrate-when";
12
+ /** Number of `useId()` slots consumed while rendering the deferred child. */
13
+ export declare const HYDRATE_ID_COUNT_ATTR = "data-octane-hydrate-id-count";
14
+ /** Direct-child JSON script carrying this boundary's `use()` seed slice. */
15
+ export declare const HYDRATE_SEED_ATTR = "data-octane-hydrate-seed";
16
+ /** Matches any server-rendered deferred hydration boundary wrapper. */
17
+ export declare const HYDRATE_MARKER_SELECTOR = "[data-octane-hydrate-id]";
18
+ /** Single-character payload of a block-open comment. */
19
+ export declare const HYDRATION_START = "[";
20
+ /** Single-character payload of a block-close comment. */
21
+ export declare const HYDRATION_END = "]";
22
+ /** Leading payload shared by both `@for` outer-open markers. */
23
+ export declare const HYDRATION_FOR_PREFIX = "[f";
24
+ /** @for outer-open payload: the server rendered its @empty arm. */
25
+ export declare const HYDRATION_FOR_EMPTY = "[f0";
26
+ /** @for outer-open payload: the server rendered one or more direct-host items. */
27
+ export declare const HYDRATION_FOR_ITEMS = "[f1";
28
+ /** Index of the arm digit ('0' or '1') inside either @for outer-open payload. */
29
+ export declare const HYDRATION_FOR_ARM_INDEX: number;
30
+ /**
31
+ * Serialize one `useId()` value. The client regenerates the id the server
32
+ * already wrote, so the namespace prefix, the `in-` infix, the base-36 ordinal,
33
+ * and both colons must be produced in exactly one place: a divergence here
34
+ * survives every test that only checks one side and then mismatches every
35
+ * `useId` consumer at hydration.
36
+ */
37
+ export declare function formatUseId(prefix: string, ordinal: number): string;
@@ -2,9 +2,35 @@ const INDEPENDENT_HYDRATE_MANIFEST_ATTR = "data-octane-independent";
2
2
  const HYDRATE_INDEPENDENT_ATTR = "data-octane-hydrate-independent";
3
3
  const HYDRATE_INPUT_ATTR = "data-octane-input";
4
4
  const SIGNAL_CONTROL_ATTR = "data-octane-signal-control";
5
+ const HYDRATE_ID_ATTR = "data-octane-hydrate-id";
6
+ const HYDRATE_WHEN_ATTR = "data-octane-hydrate-when";
7
+ const HYDRATE_ID_COUNT_ATTR = "data-octane-hydrate-id-count";
8
+ const HYDRATE_SEED_ATTR = "data-octane-hydrate-seed";
9
+ const HYDRATE_MARKER_SELECTOR = "[data-octane-hydrate-id]";
10
+ const HYDRATION_START = "[";
11
+ const HYDRATION_END = "]";
12
+ const HYDRATION_FOR_PREFIX = "[f";
13
+ const HYDRATION_FOR_EMPTY = "[f0";
14
+ const HYDRATION_FOR_ITEMS = "[f1";
15
+ const HYDRATION_FOR_ARM_INDEX = HYDRATION_FOR_PREFIX.length;
16
+ function formatUseId(prefix, ordinal) {
17
+ return ":" + prefix + "in-" + ordinal.toString(36) + ":";
18
+ }
5
19
  export {
20
+ HYDRATE_ID_ATTR,
21
+ HYDRATE_ID_COUNT_ATTR,
6
22
  HYDRATE_INDEPENDENT_ATTR,
7
23
  HYDRATE_INPUT_ATTR,
24
+ HYDRATE_MARKER_SELECTOR,
25
+ HYDRATE_SEED_ATTR,
26
+ HYDRATE_WHEN_ATTR,
27
+ HYDRATION_END,
28
+ HYDRATION_FOR_ARM_INDEX,
29
+ HYDRATION_FOR_EMPTY,
30
+ HYDRATION_FOR_ITEMS,
31
+ HYDRATION_FOR_PREFIX,
32
+ HYDRATION_START,
8
33
  INDEPENDENT_HYDRATE_MANIFEST_ATTR,
9
- SIGNAL_CONTROL_ATTR
34
+ SIGNAL_CONTROL_ATTR,
35
+ formatUseId
10
36
  };
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { RENDERER_REGION_OWNER_TAG as RENDERER_REGION_OWNER } from "../runtime-tags.js";
2
3
  import { ReactCompat } from "./react-compat.js";
3
4
  import { bridgeReactContext } from "./react-compat-shared.js";
4
5
  import * as React from "react";
@@ -21,7 +22,6 @@ import {
21
22
  import { drainPassiveEffects } from "../runtime.js";
22
23
  import { readNearestProviderValue } from "./fiber-adapter.js";
23
24
  import { __hostContextFiberWalks } from "./fiber-adapter.js";
24
- const RENDERER_REGION_OWNER = /* @__PURE__ */ Symbol.for("octane.renderer-region.owner");
25
25
  const EMPTY_SNAPSHOT = { entries: [], values: [] };
26
26
  function hostedRootEnvelope(props) {
27
27
  bindRendererRegionOwner(props);
@@ -1,7 +1,9 @@
1
1
  import * as React from "react";
2
- const OCTANE_ELEMENT_TAG = /* @__PURE__ */ Symbol.for("octane.element");
3
- const OCTANE_CONTEXT_TAG = /* @__PURE__ */ Symbol.for("octane.context");
4
- const REACT_CONTEXT_TAG = /* @__PURE__ */ Symbol.for("react.context");
2
+ import {
3
+ CONTEXT_TAG as OCTANE_CONTEXT_TAG,
4
+ ELEMENT_TAG as OCTANE_ELEMENT_TAG,
5
+ REACT_CONTEXT_TAG
6
+ } from "../runtime-tags.js";
5
7
  const REACT_MEMO_TAG = /* @__PURE__ */ Symbol.for("react.memo");
6
8
  const REACT_LAZY_TAG = /* @__PURE__ */ Symbol.for("react.lazy");
7
9
  const REACT_FORWARD_REF_TAG = /* @__PURE__ */ Symbol.for("react.forward_ref");
@@ -64,8 +64,7 @@ export type OctaneRenderedNode = React.ReactElement & {
64
64
  * `ComponentBody<P>` so one declaration can serve both hosts.
65
65
  */
66
66
  export type OctaneReactComponent<P = Record<string, never>> = (props: P) => OctaneRenderedNode;
67
- /** React 19 context objects carry $$typeof: Symbol.for('react.context'). */
68
- export declare const REACT_CONTEXT_TAG: unique symbol;
67
+ export { REACT_CONTEXT_TAG } from '../runtime-tags.js';
69
68
  /** The client's stable opaque-host sentinel; the server writes real island HTML. */
70
69
  export declare const OPAQUE_HOST_SENTINEL_COMMENT = "octane-compat-island";
71
70
  export declare const OPAQUE_HOST_SENTINEL: Readonly<{
@@ -86,4 +85,3 @@ export declare function validateIslandChild(children: React.ReactNode): Transpor
86
85
  * itself, which React resolves by remounting the wrapper.
87
86
  */
88
87
  export declare function resolveHostedIsland(props: OctaneCompatProps | OctaneCompatComponentProps<unknown>): TransportedChild;
89
- export {};
@@ -1,5 +1,5 @@
1
1
  import * as React from "react";
2
- const REACT_CONTEXT_TAG = /* @__PURE__ */ Symbol.for("react.context");
2
+ import { REACT_CONTEXT_TAG } from "../runtime-tags.js";
3
3
  const OPAQUE_HOST_SENTINEL_COMMENT = "octane-compat-island";
4
4
  const OPAQUE_HOST_SENTINEL = Object.freeze({
5
5
  __html: `<!--${OPAQUE_HOST_SENTINEL_COMMENT}-->`
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Cross-realm value-kind tags. Every one of these is a `Symbol.for` registry
3
+ * key, which makes the STRING the contract: the client runtime, the SSR
4
+ * serializer, the universal core, and the React compat layers all recognize a
5
+ * value by looking its tag up in the same global registry. A typo in any one of
6
+ * them mints a different symbol and silently fails every `$$kind` identity
7
+ * check that module performs, so each key is spelled exactly once, here.
8
+ *
9
+ * This leaf has no imports on purpose: a consumer that needs one tag must not
10
+ * pull in the DOM tables or the renderer graph to get it.
11
+ */
12
+ /** `createElement` descriptor marker. */
13
+ export declare const ELEMENT_TAG: unique symbol;
14
+ /** `createPortal` descriptor marker. */
15
+ export declare const PORTAL_TAG: unique symbol;
16
+ /** React-compatible Fragment sentinel. */
17
+ export declare const FRAGMENT_TAG: unique symbol;
18
+ /** React-19 `<Activity>` sentinel. */
19
+ export declare const ACTIVITY_TAG: unique symbol;
20
+ /** Native Octane context object marker. */
21
+ export declare const CONTEXT_TAG: unique symbol;
22
+ /** `lazy()` wrapper marker. */
23
+ export declare const LAZY_COMPONENT_TAG: unique symbol;
24
+ /** Suspense boundary component type. */
25
+ export declare const SUSPENSE_TAG: unique symbol;
26
+ /** Lexical children body marker. */
27
+ export declare const CHILDREN_BLOCK_TAG: unique symbol;
28
+ /** Owner stamp for a renderer region's host boundary. */
29
+ export declare const RENDERER_REGION_OWNER_TAG: unique symbol;
30
+ /**
31
+ * React 19 context objects carry this `$$typeof`. Octane only ever READS it to
32
+ * recognize a foreign React context, but it is the same registry contract.
33
+ */
34
+ export declare const REACT_CONTEXT_TAG: unique symbol;
@@ -0,0 +1,22 @@
1
+ const ELEMENT_TAG = /* @__PURE__ */ Symbol.for("octane.element");
2
+ const PORTAL_TAG = /* @__PURE__ */ Symbol.for("octane.portal");
3
+ const FRAGMENT_TAG = /* @__PURE__ */ Symbol.for("octane.Fragment");
4
+ const ACTIVITY_TAG = /* @__PURE__ */ Symbol.for("octane.Activity");
5
+ const CONTEXT_TAG = /* @__PURE__ */ Symbol.for("octane.context");
6
+ const LAZY_COMPONENT_TAG = /* @__PURE__ */ Symbol.for("octane.lazy");
7
+ const SUSPENSE_TAG = /* @__PURE__ */ Symbol.for("octane.suspense");
8
+ const CHILDREN_BLOCK_TAG = /* @__PURE__ */ Symbol.for("octane.childrenBlock");
9
+ const RENDERER_REGION_OWNER_TAG = /* @__PURE__ */ Symbol.for("octane.renderer-region.owner");
10
+ const REACT_CONTEXT_TAG = /* @__PURE__ */ Symbol.for("react.context");
11
+ export {
12
+ ACTIVITY_TAG,
13
+ CHILDREN_BLOCK_TAG,
14
+ CONTEXT_TAG,
15
+ ELEMENT_TAG,
16
+ FRAGMENT_TAG,
17
+ LAZY_COMPONENT_TAG,
18
+ PORTAL_TAG,
19
+ REACT_CONTEXT_TAG,
20
+ RENDERER_REGION_OWNER_TAG,
21
+ SUSPENSE_TAG
22
+ };
package/dist/runtime.d.ts CHANGED
@@ -19,6 +19,7 @@ import { EXTERNAL_HYDRATION_PROMISE, HYDRATION_RANGE_BOUNDARY } from './constant
19
19
  import type { HydrateProps } from './hydration/types.js';
20
20
  import type { IndependentHydrateActivator } from './hydration/independent-island.js';
21
21
  import { snapshotHydrationControl } from './hydration/event-capture.js';
22
+ import { CONTEXT_TAG, ELEMENT_TAG, PORTAL_TAG } from './runtime-tags.js';
22
23
  import { validateNativeReadWitness, type NativeReadWitness } from './signals/native-read-collector.js';
23
24
  import { type NativeReadRetry } from './signals/native-read-retry.js';
24
25
  import type { SignalActionFrame } from './signals/transition-action.js';
@@ -935,7 +936,6 @@ export declare function useSyncExternalStore<T>(subscribe: (onStoreChange: () =>
935
936
  * suspended or failed render from leaking an uncommitted closure.
936
937
  */
937
938
  export declare function useEffectEvent<F extends (...args: any[]) => any>(fn: F, slot?: symbol): F;
938
- declare const CONTEXT_TAG: unique symbol;
939
939
  export interface Context<T> {
940
940
  (props: {
941
941
  value: T;
@@ -1477,6 +1477,8 @@ export declare function child<T extends Node>(node: T): Node | null;
1477
1477
  export declare function sibling(node: Node, n?: number): Node | null;
1478
1478
  export declare function setText(node: Text, value: any): void;
1479
1479
  declare const DIRECT_SIGNAL_BINDING: unique symbol;
1480
+ /** @internal Invalidate scalar-view caches when hydration retained a claimed publication. */
1481
+ export declare function hydrateClaimedBindingCaches(scope: Scope, fields: readonly (string | number)[]): void;
1480
1482
  type DirectSignalBindingKind = 'text' | 'textOnlyChild' | 'attribute' | 'value' | 'checked';
1481
1483
  type DirectSignalAttributeKind = 'attr' | 'class' | 'booleanAttr' | 'ariaAttr' | 'stringData';
1482
1484
  interface DirectSignalBindingPolicy {
@@ -1783,7 +1785,7 @@ type HostPropSource = readonly [
1783
1785
  value?: unknown,
1784
1786
  merge?: boolean
1785
1787
  ];
1786
- export declare function setHostPropSources(el: Element, sources: readonly HostPropSource[], prev: Record<string, unknown> | undefined, scope: Scope, hasNestedChildren?: boolean, readStyle?: (value: unknown) => unknown, deferControl?: boolean, writableTextareaValue?: boolean): Record<string, unknown>;
1788
+ export declare function setHostPropSources(el: Element, sources: readonly HostPropSource[], prev: Record<string, unknown> | undefined, scope: Scope, hasNestedChildren?: boolean, readStyle?: (value: unknown) => unknown, deferControl?: boolean): Record<string, unknown>;
1787
1789
  /** @internal Compiler target for spread-bearing hosts containing direct signals. */
1788
1790
  export declare function bindSignalHostPropSources(scope: Scope, previous: unknown, element: Element, sources: readonly HostPropSource[], site: string, hasNestedChildren?: boolean, readStyle?: (value: unknown) => unknown): unknown;
1789
1791
  export declare function setSpread(el: Element, value: any, prev: any, mountScope?: Scope, skipDangerouslySetInnerHTML?: boolean, skipFormControls?: boolean): void;
@@ -1884,7 +1886,7 @@ export declare function markNativeChangeDiagnosticStatic(el: Element): void;
1884
1886
  * attribute write never clobbers what the user typed, and it keeps SSR
1885
1887
  * output, form.reset() baselines, and differential byte-compares aligned.
1886
1888
  */
1887
- export declare function setValue(el: Element, value: unknown, writableTextareaEcho?: boolean): void;
1889
+ export declare function setValue(el: Element, value: unknown): void;
1888
1890
  export declare function setChecked(el: Element, value: unknown): void;
1889
1891
  /**
1890
1892
  * Compiler-only checked binding for a statically-known checkbox/radio whose
@@ -1939,16 +1941,6 @@ interface PortalSlot {
1939
1941
  * down and removes its DOM from `target`.
1940
1942
  */
1941
1943
  export declare function portal(parentScope: Scope, slotKey: number, target: Element | DocumentFragment, body: ComponentBody, props: any, host?: Node, env?: any[], fragmentOwners?: FragmentInstance | readonly FragmentInstance[], fragmentAnchor?: Node): void;
1942
- /**
1943
- * `createPortal(children, target, keyOrProps?)`. A string/number third argument
1944
- * is a reconciliation key. An object remains Octane's optional props for a
1945
- * function portal body. The
1946
- * compiler recognises `{createPortal(...)}` at JSX child position and lowers it to a
1947
- * direct `portal(...)` runtime call — no descriptor allocation on the hot path. This
1948
- * function exists so non-JSX call sites (storing in a variable, passing through props,
1949
- * etc.) still produce something the runtime can dispatch on.
1950
- */
1951
- declare const PORTAL_TAG: unique symbol;
1952
1944
  export interface PortalDescriptor {
1953
1945
  $$kind: typeof PORTAL_TAG;
1954
1946
  key: string | null;
@@ -1957,7 +1949,6 @@ export interface PortalDescriptor {
1957
1949
  props: any;
1958
1950
  }
1959
1951
  export declare function createPortal(body: ComponentBody | ElementDescriptor | unknown, target: Element | DocumentFragment, props?: any): PortalDescriptor;
1960
- declare const ELEMENT_TAG: unique symbol;
1961
1952
  export interface ElementDescriptor<P = any> {
1962
1953
  $$kind: typeof ELEMENT_TAG;
1963
1954
  type: ComponentBody<P> | string | typeof Fragment;