octane 0.1.20 → 0.1.21

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.
@@ -3995,11 +3995,22 @@ function isConditionalJsx(node) {
3995
3995
  );
3996
3996
  }
3997
3997
 
3998
- /** Wrap an expression as a BlockStatement body, so makeIfCall can consume it. */
3998
+ /** Wrap a ternary arm as a BlockStatement body, so makeIfCall can consume it. */
3999
3999
  function wrapAsBlockStmt(node) {
4000
4000
  if (!node) return null;
4001
4001
  // null / Literal(null) / Literal(false) → no branch
4002
4002
  if (node.type === 'Literal' && (node.value === null || node.value === false)) return null;
4003
+ if (!isJsxLike(node)) {
4004
+ // A non-JSX arm (`{cond ? xs.map(…) : <Jsx/>}`, a string, a variable
4005
+ // holding an element, a nested ternary) is the branch's render VALUE. As
4006
+ // a bare statement the branch body would evaluate-and-discard it, so wrap
4007
+ // it as the authored-equivalent `<>{expr}</>` — the branch renders the
4008
+ // value through the fragment's child hole. The hole sits at the
4009
+ // fragment's ROOT, a value position on both compilers: a nested ternary
4010
+ // stays a value hole here (it is NOT re-claimed into a nested ifBlock),
4011
+ // which is why the server claim requires host-child position too.
4012
+ node = inheritOriginLoc(b.jsx_fragment([b.jsx_expression_container(node, node)]), node);
4013
+ }
4003
4014
  return b.block([node]);
4004
4015
  }
4005
4016
 
@@ -7482,7 +7493,15 @@ function ssrCompileBodyWithMapTemps(
7482
7493
  ((!!(node.body && node.body.type === 'JSXCodeBlock') && !returnedOutput) ||
7483
7494
  returnedFragmentRoot) &&
7484
7495
  inheritSoleCompRoot(bodyNodes, ctx);
7496
+ // Body/sub ROOTS (including fragment-root children) are not host-element
7497
+ // children: the client routes rich holes there through the de-opt value
7498
+ // path (a portal has no host to stamp — see the emitNodeHtml root branch),
7499
+ // so the ternary claim must stay off until an element's own children walk
7500
+ // (ssrEmitElement) turns the flag on.
7501
+ const prevHostChildPos = ctx._ssrHostChildPos;
7502
+ ctx._ssrHostChildPos = false;
7485
7503
  const htmlExpr = ssrEmitNodes(bodyNodes, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
7504
+ ctx._ssrHostChildPos = prevHostChildPos;
7486
7505
  ctx._ssrInheritRoot = prevInheritRoot;
7487
7506
  ctx._returnedFragmentTemplate = prevReturnedFragmentTemplate;
7488
7507
  ctx._tsxValuePos = prevValuePos;
@@ -8587,6 +8606,11 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
8587
8606
  // pre/textarea/listing: the parser eats a '\n' right after the opening tag —
8588
8607
  // the first text part must protect a leading newline (see ssrEmitNodes).
8589
8608
  const nlGuardFirst = tag === 'pre' || tag === 'textarea' || tag === 'listing';
8609
+ // These children sit directly under a host element — the one position
8610
+ // where the client claims `{cond ? A : B}` holes (emitElementHtml). Root
8611
+ // positions reset this in ssrCompileBody.
8612
+ const prevHostChildPos = ctx._ssrHostChildPos;
8613
+ ctx._ssrHostChildPos = true;
8590
8614
  childrenExpr = ssrEmitNodes(
8591
8615
  normChildren,
8592
8616
  ctx,
@@ -8597,6 +8621,7 @@ function ssrEmitElement(node, ctx, name, inlinedSubs, parentNs, cssHash, compone
8597
8621
  childComponentNs,
8598
8622
  nlGuardFirst,
8599
8623
  );
8624
+ ctx._ssrHostChildPos = prevHostChildPos;
8600
8625
  }
8601
8626
  // `children=` and spread-held children are content props, not attributes.
8602
8627
  // With no nested JSX children, the last present writer renders as the host's
@@ -8925,18 +8950,35 @@ function ssrCompileSub(
8925
8950
  ) {
8926
8951
  const fnName = `${baseName}$${ctx.nextHelperId++}`;
8927
8952
  const synth = { params: paramNodes || [], body: bodyStmts };
8928
- const fn = ssrCompileBody(
8929
- synth,
8930
- ctx,
8931
- fnName,
8932
- cssHash,
8933
- [],
8934
- parentNs || 'html',
8935
- false,
8936
- componentNs,
8937
- returnedFragmentTemplate,
8938
- returnedFragmentRoot,
8939
- );
8953
+ // Returned-tree mirror subs (`__sfragment`): extractFragment folds EVERY
8954
+ // expression hole in the mirrored tree into a descriptor value hole on the
8955
+ // client (`props.hN` + childTextHole), even holes sitting inside host
8956
+ // elements. Mark them so ssrEmitTsrxExpression keeps `{cond ? A : B}` on
8957
+ // ssrChild throughout the mirror, matching that fold. Directive-arm subs
8958
+ // reset the flag through this same save/restore, so an @if INSIDE a mirror
8959
+ // claims again, exactly like the client. (Component `__schildren` subs are
8960
+ // NOT folded wholesale — only their ROOT holes are the children-as-props
8961
+ // value, which `_ssrHostChildPos` already leaves unclaimed — while a hole
8962
+ // inside a host element within children claims like any template child.)
8963
+ const prevFoldedExprHoles = ctx._ssrFoldedExprHoles;
8964
+ ctx._ssrFoldedExprHoles = baseName === '__sfragment';
8965
+ let fn;
8966
+ try {
8967
+ fn = ssrCompileBody(
8968
+ synth,
8969
+ ctx,
8970
+ fnName,
8971
+ cssHash,
8972
+ [],
8973
+ parentNs || 'html',
8974
+ false,
8975
+ componentNs,
8976
+ returnedFragmentTemplate,
8977
+ returnedFragmentRoot,
8978
+ );
8979
+ } finally {
8980
+ ctx._ssrFoldedExprHoles = prevFoldedExprHoles;
8981
+ }
8940
8982
  return { fnName, fn };
8941
8983
  }
8942
8984
 
@@ -8944,10 +8986,19 @@ function ssrEmitIf(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs)
8944
8986
  // rewriteHookCalls: key any `use(thenable)` in the @if test (it bypasses the
8945
8987
  // setup rewrite, so without a stable key it collides with sibling/body use()).
8946
8988
  const testExpr = rewriteHookCalls(node.test, ctx, name);
8947
- const thenStmts =
8948
- node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
8949
- const thenSub = ssrCompileSub(thenStmts, ctx, '__sif', [], cssHash, parentNs, componentNs);
8950
- inlinedSubs.push(thenSub.fn);
8989
+ // A null consequent (`{cond ? null : <Jsx/>}` lowered by wrapAsBlockStmt)
8990
+ // means "no then branch". It must emit like a MISSING @else — plain '' with
8991
+ // no inner arm range — because the hydrating client renders that branch with
8992
+ // a null body and adopts an empty slot range.
8993
+ const thenStmts = node.consequent
8994
+ ? node.consequent.type === 'BlockStatement'
8995
+ ? node.consequent.body
8996
+ : [node.consequent]
8997
+ : null;
8998
+ const thenSub = thenStmts
8999
+ ? ssrCompileSub(thenStmts, ctx, '__sif', [], cssHash, parentNs, componentNs)
9000
+ : null;
9001
+ if (thenSub) inlinedSubs.push(thenSub.fn);
8951
9002
  let elseCall = ssrHtmlTemplate([], node, ctx);
8952
9003
  let elseFnName = null;
8953
9004
  if (node.alternate) {
@@ -8968,26 +9019,33 @@ function ssrEmitIf(node, ctx, name, inlinedSubs, parentNs, cssHash, componentNs)
8968
9019
  ctx.runtimeNeeded.add('ssrBlock');
8969
9020
  ctx.runtimeNeeded.add('ssrControl');
8970
9021
  ctx.runtimeNeeded.add('ssrArm');
8971
- registerDirectiveOrigin(ctx, node, ['_$ssrControl', '_$ssrArm', thenSub.fnName, elseFnName]);
9022
+ registerDirectiveOrigin(ctx, node, [
9023
+ '_$ssrControl',
9024
+ '_$ssrArm',
9025
+ thenSub ? thenSub.fnName : null,
9026
+ elseFnName,
9027
+ ]);
8972
9028
  // Nested ranges: the OUTER ssrBlock is the if-slot; the INNER one wraps the
8973
9029
  // taken branch's content. The client adopts BOTH on hydration (slot = outer,
8974
9030
  // branch = inner) so no comment markers are inserted — byte-for-byte, exactly
8975
9031
  // like @for. The not-taken arm emits no inner range (just `''`).
8976
- const thenInner = ssrCall(
8977
- 'ssrArm',
8978
- [
8979
- b.literal('then', '"then"'),
8980
- ssrThunk(
8981
- ssrCall(
8982
- 'ssrBlock',
8983
- [ssrSubCall(thenSub.fnName, [b.id('undefined')], node.consequent)],
8984
- node.consequent,
8985
- ),
9032
+ const thenInner = thenSub
9033
+ ? ssrCall(
9034
+ 'ssrArm',
9035
+ [
9036
+ b.literal('then', '"then"'),
9037
+ ssrThunk(
9038
+ ssrCall(
9039
+ 'ssrBlock',
9040
+ [ssrSubCall(thenSub.fnName, [b.id('undefined')], node.consequent)],
9041
+ node.consequent,
9042
+ ),
9043
+ node.consequent,
9044
+ ),
9045
+ ],
8986
9046
  node.consequent,
8987
- ),
8988
- ],
8989
- node.consequent,
8990
- );
9047
+ )
9048
+ : ssrHtmlTemplate([], node, ctx);
8991
9049
  const elseInner = node.alternate
8992
9050
  ? ssrCall(
8993
9051
  'ssrArm',
@@ -9441,6 +9499,30 @@ function ssrEmitTsrxExpression(node, ctx, name, inlinedSubs, parentNs, cssHash,
9441
9499
  ctx.runtimeNeeded.add('ssrPortal');
9442
9500
  return ssrCall('ssrPortal', [], node);
9443
9501
  }
9502
+ if (
9503
+ node.returnedJsxValue !== true &&
9504
+ ctx._tsxValuePos !== true &&
9505
+ ctx._ssrFoldedExprHoles !== true &&
9506
+ ctx._ssrHostChildPos === true &&
9507
+ isConditionalJsx(expr)
9508
+ ) {
9509
+ // Mirror the client's claim EXACTLY: only emitElementHtml's children
9510
+ // walk lowers `{cond ? A : B}` with a JSX arm to an @if, so both sides
9511
+ // compile each arm as an ordinary arm body and the hydration shapes
9512
+ // (control key, arm ranges, a keyed `.map` claimed inside a value arm)
9513
+ // agree by construction. Everywhere else the hole is a VALUE on the
9514
+ // client and must stay on ssrChild here: returned `.tsx` trees
9515
+ // (`_tsxValuePos`), returned-tree mirrors whose holes extractFragment
9516
+ // folds to `props.hN` (`_ssrFoldedExprHoles`), and every non-host-child
9517
+ // position — body/arm/fragment roots and component-children roots,
9518
+ // where a rich hole rides the de-opt value path because a portal there
9519
+ // has no host to stamp (`_ssrHostChildPos`).
9520
+ const asIf = {
9521
+ ...b.if(expr.test, wrapAsBlockStmt(expr.consequent), wrapAsBlockStmt(expr.alternate)),
9522
+ loc: expr.loc, // same devLoc/control-key position as the client's claim
9523
+ };
9524
+ return ssrEmitIf(asIf, ctx, name, inlinedSubs, parentNs, cssHash, componentNs);
9525
+ }
9444
9526
  ctx.runtimeNeeded.add('ssrChild');
9445
9527
  // rewriteHookCalls first (key any `use(thenable)` in the hole — it bypasses the
9446
9528
  // setup rewrite), then rewriteJsxValues (lower nested JSX to createElement).
@@ -20169,10 +20251,16 @@ function hoistBodyHelper(
20169
20251
  // ===========================================================================
20170
20252
 
20171
20253
  function makeIfCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null) {
20172
- // node.test, node.consequent (BlockStatement | Element), node.alternate (BlockStatement | IfStatement | null)
20173
-
20174
- const thenStmts =
20175
- node.consequent.type === 'BlockStatement' ? node.consequent.body : [node.consequent];
20254
+ // node.test, node.consequent (BlockStatement | Element | null), node.alternate (BlockStatement | IfStatement | null)
20255
+ // A null consequent (`{cond ? null : <Jsx/>}` lowered by wrapAsBlockStmt)
20256
+ // means "no then branch" — same contract as the null alternate: the helper
20257
+ // slot compiles to `null` and the runtime renders an empty branch.
20258
+
20259
+ const thenStmts = node.consequent
20260
+ ? node.consequent.type === 'BlockStatement'
20261
+ ? node.consequent.body
20262
+ : [node.consequent]
20263
+ : null;
20176
20264
  const elseStmts = node.alternate
20177
20265
  ? node.alternate.type === 'BlockStatement'
20178
20266
  ? node.alternate.body
@@ -20180,20 +20268,22 @@ function makeIfCall(node, ctx, inlinedSubs, parentNs = 'html', cssHash = null) {
20180
20268
  : null;
20181
20269
  // Phase 2: one shared env tuple for both branches (see unionEnv).
20182
20270
  const envNames = unionEnv(ctx, [
20183
- { stmts: thenStmts, params: [] },
20271
+ thenStmts && { stmts: thenStmts, params: [] },
20184
20272
  elseStmts && { stmts: elseStmts, params: [] },
20185
20273
  ]);
20186
- const thenHelperName = hoistBodyHelper(
20187
- ctx,
20188
- inlinedSubs,
20189
- '__then',
20190
- thenStmts,
20191
- [],
20192
- parentNs,
20193
- cssHash,
20194
- envNames,
20195
- directiveKeywordOrigin(ctx, node),
20196
- );
20274
+ const thenHelperName = thenStmts
20275
+ ? hoistBodyHelper(
20276
+ ctx,
20277
+ inlinedSubs,
20278
+ '__then',
20279
+ thenStmts,
20280
+ [],
20281
+ parentNs,
20282
+ cssHash,
20283
+ envNames,
20284
+ directiveKeywordOrigin(ctx, node),
20285
+ )
20286
+ : null;
20197
20287
 
20198
20288
  let elseHelperName = null;
20199
20289
  if (elseStmts) {
package/dist/runtime.js CHANGED
@@ -315,6 +315,13 @@ function flushTransitionActionBatch(batch) {
315
315
  );
316
316
  scheduleRender(block);
317
317
  }
318
+ if (batch.updates.size > 0) {
319
+ const retained = [];
320
+ for (const update of batch.updates.values()) {
321
+ if (!update.block.disposed) retained.push(update);
322
+ }
323
+ if (retained.length > 0) FLUSHED_TRANSITION_UPDATES.push(retained);
324
+ }
318
325
  batch.updates.clear();
319
326
  if (IN_FLIGHT_TRANSITION_ACTION_BATCH === batch) {
320
327
  IN_FLIGHT_TRANSITION_ACTION_BATCH = null;
@@ -331,6 +338,183 @@ const HELD_TRANSITIONS = /* @__PURE__ */ new Set();
331
338
  const STAGED_REVEALS = /* @__PURE__ */ new Set();
332
339
  let flushingStagedReveals = false;
333
340
  let deferringStagedRevealEffects = false;
341
+ let ACTIVE_TRANSITION_ATTEMPT = null;
342
+ let FLUSHED_TRANSITION_UPDATES = [];
343
+ let HELD_SYNC_TRANSITION = null;
344
+ let PROMOTED_PU_SWAPS = null;
345
+ let PROMOTED_WARM_HARVEST = null;
346
+ function beginTransitionAttempt(block) {
347
+ if (block.pendingMode !== "transition" || ACTIVE_TRANSITION_ATTEMPT !== null) return null;
348
+ TRANSITION_JOURNAL ??= [];
349
+ TRANSITION_JOURNAL_BAGS ??= /* @__PURE__ */ new Set();
350
+ TRANSITION_JOURNAL_DEPTH++;
351
+ const attempt = {
352
+ origin: block,
353
+ journalCheckpoint: TRANSITION_JOURNAL.length,
354
+ effects: [effectQueues[0].length, effectQueues[1].length, effectQueues[2].length],
355
+ effectEvents: effectEventQueue.length,
356
+ effectEventActions: effectEventCommitActions.length,
357
+ stores: storeSyncQueue.length,
358
+ refAttach: refAttachQueue.length,
359
+ refDetach: refDetachQueue.length,
360
+ effectDeps: snapshotSubtreeEffectDeps(block),
361
+ heldSlots: null,
362
+ puSwaps: null
363
+ };
364
+ ACTIVE_TRANSITION_ATTEMPT = attempt;
365
+ return attempt;
366
+ }
367
+ function endTransitionAttempt(attempt) {
368
+ if (attempt === null) return;
369
+ ACTIVE_TRANSITION_ATTEMPT = null;
370
+ const held = attempt.heldSlots;
371
+ if (held === null || held.size === 0) {
372
+ PROMOTED_PU_SWAPS = null;
373
+ PROMOTED_WARM_HARVEST = null;
374
+ }
375
+ let entries = null;
376
+ if (held !== null && held.size > 0) {
377
+ entries = [];
378
+ for (let i = FLUSHED_TRANSITION_UPDATES.length - 1; i >= 0; i--) {
379
+ const group = FLUSHED_TRANSITION_UPDATES[i];
380
+ let single = true;
381
+ for (let k = 0; k < group.length; k++) {
382
+ if (group[k].block !== attempt.origin) {
383
+ single = false;
384
+ break;
385
+ }
386
+ }
387
+ if (!single) continue;
388
+ for (let k = 0; k < group.length; k++) entries.push(group[k]);
389
+ FLUSHED_TRANSITION_UPDATES.splice(i, 1);
390
+ }
391
+ }
392
+ if (held !== null && held.size > 0 && entries !== null && entries.length > 0) {
393
+ rollbackTransitionJournal(attempt.journalCheckpoint);
394
+ for (let phase = 0; phase < 3; phase++) {
395
+ effectQueues[phase].length = attempt.effects[phase];
396
+ }
397
+ effectEventQueue.length = attempt.effectEvents;
398
+ effectEventCommitActions.length = attempt.effectEventActions;
399
+ for (let i = attempt.stores; i < storeSyncQueue.length; i++) {
400
+ storeSyncQueue[i].queued = false;
401
+ }
402
+ storeSyncQueue.length = attempt.stores;
403
+ refAttachQueue.length = attempt.refAttach;
404
+ refDetachQueue.length = attempt.refDetach;
405
+ restoreSubtreeEffectDeps(attempt.origin, attempt.effectDeps);
406
+ const continuing = PROMOTED_PU_SWAPS !== null || PROMOTED_WARM_HARVEST !== null;
407
+ let puSwaps = attempt.puSwaps;
408
+ if (continuing) {
409
+ if (PROMOTED_PU_SWAPS !== null) {
410
+ puSwaps = puSwaps === null ? PROMOTED_PU_SWAPS : PROMOTED_PU_SWAPS.concat(puSwaps);
411
+ PROMOTED_PU_SWAPS = null;
412
+ }
413
+ } else if (puSwaps !== null) {
414
+ for (let i = puSwaps.length - 1; i >= 0; i--) {
415
+ const [hooks, slot, prev] = puSwaps[i];
416
+ if (prev === void 0) hooks.delete(slot);
417
+ else hooks.set(slot, prev);
418
+ }
419
+ }
420
+ for (let i = 0; i < entries.length; i++) {
421
+ entries[i].slot.value = entries[i].baseValue;
422
+ }
423
+ let warmHarvest = PROMOTED_WARM_HARVEST;
424
+ PROMOTED_WARM_HARVEST = null;
425
+ if (warmHarvest !== null) {
426
+ for (let i = 0; i < warmHarvest.length; i++) warmHarvest[i].taken = false;
427
+ }
428
+ const harvest = (scope) => {
429
+ const cache = scope.block.__warmCache;
430
+ if (cache !== void 0) {
431
+ for (const [slot, list] of cache) {
432
+ for (let i = 0; i < list.length; i++) {
433
+ const entry = list[i];
434
+ if (entry.available) {
435
+ (warmHarvest ??= []).push({
436
+ slot,
437
+ deps: entry.deps,
438
+ value: entry.value,
439
+ taken: false
440
+ });
441
+ }
442
+ }
443
+ }
444
+ }
445
+ forEachSubtreeChild(scope, harvest);
446
+ };
447
+ harvest(attempt.origin);
448
+ HELD_SYNC_TRANSITION = {
449
+ origin: attempt.origin,
450
+ entries,
451
+ puSwaps,
452
+ warmHarvest,
453
+ holders: new Set(held)
454
+ };
455
+ if (!continuing) scheduleRender(attempt.origin);
456
+ }
457
+ if (--TRANSITION_JOURNAL_DEPTH === 0) {
458
+ TRANSITION_JOURNAL = null;
459
+ TRANSITION_JOURNAL_BAGS = null;
460
+ flushParkedItems();
461
+ }
462
+ }
463
+ function journalPuEntry(scope, slot, next) {
464
+ const attempt = ACTIVE_TRANSITION_ATTEMPT;
465
+ if (attempt === null) return;
466
+ const hooks = ensureHooks(scope);
467
+ (attempt.puSwaps ??= []).push([hooks, slot, hooks.get(slot), next]);
468
+ }
469
+ function heldSyncCellsIntact(state) {
470
+ const held = HELD_SYNC_TRANSITION;
471
+ if (held === null || !held.holders.has(state)) return false;
472
+ for (let i = 0; i < held.entries.length; i++) {
473
+ const entry = held.entries[i];
474
+ if (!Object.is(entry.slot.value, entry.baseValue)) return false;
475
+ }
476
+ return true;
477
+ }
478
+ function promoteHeldSyncTransition() {
479
+ const held = HELD_SYNC_TRANSITION;
480
+ if (held === null) return false;
481
+ HELD_SYNC_TRANSITION = null;
482
+ const puSwaps = held.puSwaps;
483
+ if (puSwaps !== null) {
484
+ for (let i = 0; i < puSwaps.length; i++) {
485
+ const [hooks, slot, , next] = puSwaps[i];
486
+ hooks.set(slot, next);
487
+ }
488
+ PROMOTED_PU_SWAPS = PROMOTED_PU_SWAPS === null ? puSwaps : PROMOTED_PU_SWAPS.concat(puSwaps);
489
+ }
490
+ PROMOTED_WARM_HARVEST = held.warmHarvest;
491
+ const promoted = [];
492
+ TRANSITION_DEPTH++;
493
+ try {
494
+ for (let i = 0; i < held.entries.length; i++) {
495
+ const entry = held.entries[i];
496
+ if (!Object.is(entry.slot.value, entry.baseValue)) continue;
497
+ entry.slot.value = entry.value;
498
+ if (!entry.block.disposed) {
499
+ promoted.push(entry);
500
+ scheduleRender(entry.block);
501
+ }
502
+ }
503
+ } finally {
504
+ TRANSITION_DEPTH--;
505
+ }
506
+ if (promoted.length > 0) FLUSHED_TRANSITION_UPDATES.push(promoted);
507
+ return true;
508
+ }
509
+ function discardHeldSyncTransition(state) {
510
+ const held = HELD_SYNC_TRANSITION;
511
+ if (held === null || !held.holders.delete(state)) return;
512
+ if (held.holders.size === 0) {
513
+ HELD_SYNC_TRANSITION = null;
514
+ PROMOTED_PU_SWAPS = null;
515
+ PROMOTED_WARM_HARVEST = null;
516
+ }
517
+ }
334
518
  const JOURNAL_TEXT = 0;
335
519
  const JOURNAL_ATTR = 1;
336
520
  const JOURNAL_BAG = 2;
@@ -1007,7 +1191,12 @@ function drainQueue() {
1007
1191
  block.drainStamp = drainId;
1008
1192
  block.drainRenders = 1;
1009
1193
  }
1010
- renderBlock(block);
1194
+ const attempt = beginTransitionAttempt(block);
1195
+ try {
1196
+ renderBlock(block);
1197
+ } finally {
1198
+ endTransitionAttempt(attempt);
1199
+ }
1011
1200
  } catch (err) {
1012
1201
  try {
1013
1202
  handleRenderError(block, err);
@@ -1040,6 +1229,7 @@ function flush() {
1040
1229
  }
1041
1230
  function flushWork() {
1042
1231
  inFlush = true;
1232
+ const clearRetainedTransitionUpdates = FLUSHED_TRANSITION_UPDATES.length > 0;
1043
1233
  const viewTransitionDriver = VIEW_TRANSITION_DRIVER;
1044
1234
  const clearViewTransitionTypes = viewTransitionDriver?.shouldClearTypesAfterFlush() === true;
1045
1235
  try {
@@ -1049,6 +1239,8 @@ function flushWork() {
1049
1239
  if (pendingError !== null) throw pendingError.err;
1050
1240
  } finally {
1051
1241
  inFlush = false;
1242
+ if (clearRetainedTransitionUpdates || FLUSHED_TRANSITION_UPDATES.length > 0)
1243
+ FLUSHED_TRANSITION_UPDATES.length = 0;
1052
1244
  if (typeof __OCTANE_PROFILE_ENABLED__ !== "undefined" && __OCTANE_PROFILE_ENABLED__)
1053
1245
  __devtoolsNotifyFlush();
1054
1246
  if (clearViewTransitionTypes) viewTransitionDriver.clearTypes();
@@ -2766,16 +2958,19 @@ function useMemo(compute, deps, slot) {
2766
2958
  if (WARM_EVER && d !== void 0) {
2767
2959
  const adopted = adoptWarmValue(s, d);
2768
2960
  if (adopted !== WARM_MISS) {
2769
- ensureHooks(scope).set(s, {
2961
+ const adoptedEntry = {
2770
2962
  deps: d,
2771
2963
  value: adopted,
2772
2964
  warmEpisode: CURRENT_WARM_EPISODE
2773
- });
2965
+ };
2966
+ if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(scope, s, adoptedEntry);
2967
+ ensureHooks(scope).set(s, adoptedEntry);
2774
2968
  return adopted;
2775
2969
  }
2776
2970
  }
2777
2971
  const value = compute.apply(null, d ?? []);
2778
2972
  const entry = { deps: d, value };
2973
+ if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(scope, s, entry);
2779
2974
  ensureHooks(scope).set(s, entry);
2780
2975
  if (d !== void 0 && recordRealWarmMemo(s, d, entry)) {
2781
2976
  entry.warmEpisode = CURRENT_WARM_EPISODE;
@@ -4332,6 +4527,16 @@ function adoptWarmValue(slot, deps) {
4332
4527
  }
4333
4528
  b = b.parentBlock;
4334
4529
  }
4530
+ const harvestList = HELD_SYNC_TRANSITION?.warmHarvest ?? PROMOTED_WARM_HARVEST;
4531
+ if (harvestList !== null && harvestList !== void 0) {
4532
+ for (let i = 0; i < harvestList.length; i++) {
4533
+ const entry = harvestList[i];
4534
+ if (!entry.taken && entry.slot === slot && !depsChanged(entry.deps, deps)) {
4535
+ entry.taken = true;
4536
+ return entry.value;
4537
+ }
4538
+ }
4539
+ }
4335
4540
  return WARM_MISS;
4336
4541
  }
4337
4542
  const puMiss = /* @__PURE__ */ Symbol("octane.pu.miss");
@@ -4344,11 +4549,13 @@ function puHit(slot, entry) {
4344
4549
  function puAdopt(slot, deps) {
4345
4550
  const adopted = adoptWarmValue(slot, deps);
4346
4551
  if (adopted === WARM_MISS) return puMiss;
4347
- ensureHooks(CURRENT_SCOPE).set(slot, {
4552
+ const adoptedEntry = {
4348
4553
  deps,
4349
4554
  value: adopted,
4350
4555
  warmEpisode: CURRENT_WARM_EPISODE
4351
- });
4556
+ };
4557
+ if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(CURRENT_SCOPE, slot, adoptedEntry);
4558
+ ensureHooks(CURRENT_SCOPE).set(slot, adoptedEntry);
4352
4559
  return adopted;
4353
4560
  }
4354
4561
  function puTake0(slot) {
@@ -4403,6 +4610,7 @@ function puTake4(slot, d0, d1, d2, d3) {
4403
4610
  }
4404
4611
  function puPub(slot, value, ...deps) {
4405
4612
  const entry = { deps, value };
4613
+ if (ACTIVE_TRANSITION_ATTEMPT !== null) journalPuEntry(CURRENT_SCOPE, slot, entry);
4406
4614
  ensureHooks(CURRENT_SCOPE).set(slot, entry);
4407
4615
  if (recordRealWarmMemo(slot, deps, entry)) entry.warmEpisode = CURRENT_WARM_EPISODE;
4408
4616
  return value;
@@ -9721,6 +9929,10 @@ function childSlot(parentScope, slotKey, domParent, value, anchor, ownEnd, ownsH
9721
9929
  if (preparedList !== null) {
9722
9930
  if (state.forSlot === null) {
9723
9931
  if (hydration === null && !upgradeArmed) clearChildContent(state);
9932
+ if (state.end !== null && state.end.parentNode === null) {
9933
+ state.end = null;
9934
+ state.start = null;
9935
+ }
9724
9936
  if (state.end === null) {
9725
9937
  state.end = document.createComment("");
9726
9938
  domParent.insertBefore(state.end, null);
@@ -10563,8 +10775,10 @@ function tryBlock(parentScope, slotKey, domParent, tryBody, catchBody, pendingBo
10563
10775
  const journalCheckpoint = armTransitionJournal(s);
10564
10776
  try {
10565
10777
  renderBlock(s.tryBlock);
10566
- releaseHeldTransition(s);
10567
- s.pendingThenable = null;
10778
+ if (!heldSyncCellsIntact(s)) {
10779
+ releaseHeldTransition(s);
10780
+ s.pendingThenable = null;
10781
+ }
10568
10782
  } catch (err) {
10569
10783
  if (isHostContextRequest(err)) throw err;
10570
10784
  if (isSuspenseException(err)) {
@@ -10790,6 +11004,7 @@ function showTryBlock(state) {
10790
11004
  }
10791
11005
  }
10792
11006
  function releaseHeldTransition(state) {
11007
+ discardHeldSyncTransition(state);
10793
11008
  if (state.transitionHeld) {
10794
11009
  state.transitionHeld = false;
10795
11010
  tickTransitionCount(-1);
@@ -10804,6 +11019,9 @@ function handleSuspense(state, thenable, sourceBlock, journalCheckpoint = -1) {
10804
11019
  const isTransition = sourceBlock.currentRenderMode === "transition";
10805
11020
  if ((isTransition || state.transitionHeld) && state.hasResolved && state.branch === 1 && state.hiddenDom === null) {
10806
11021
  rollbackTransitionJournal(journalCheckpoint);
11022
+ if (ACTIVE_TRANSITION_ATTEMPT !== null) {
11023
+ (ACTIVE_TRANSITION_ATTEMPT.heldSlots ??= /* @__PURE__ */ new Set()).add(state);
11024
+ }
10807
11025
  if (!state.transitionHeld) {
10808
11026
  state.transitionHeld = true;
10809
11027
  tickTransitionCount(1);
@@ -10920,6 +11138,9 @@ function refreshPendingBody(state) {
10920
11138
  }
10921
11139
  function swapToPendingFallback(state) {
10922
11140
  if (!state.pendingBody || state.branch !== 1 || !state.tryBlock) return;
11141
+ if (HELD_SYNC_TRANSITION !== null && HELD_SYNC_TRANSITION.holders.has(state)) {
11142
+ promoteHeldSyncTransition();
11143
+ }
10923
11144
  hideTryContentAndMountPending(state);
10924
11145
  }
10925
11146
  function commitResume(state) {
@@ -11275,6 +11496,20 @@ function rebaseOffscreenCaptureSeq(capture) {
11275
11496
  }
11276
11497
  function flushStagedReveals() {
11277
11498
  if (flushingStagedReveals) return;
11499
+ if (HELD_SYNC_TRANSITION !== null) {
11500
+ let allVisible = true;
11501
+ for (const holder of HELD_SYNC_TRANSITION.holders) {
11502
+ if (holder.hiddenDom !== null) {
11503
+ allVisible = false;
11504
+ break;
11505
+ }
11506
+ }
11507
+ if (allVisible && promoteHeldSyncTransition()) {
11508
+ STAGED_REVEALS.clear();
11509
+ flush();
11510
+ return;
11511
+ }
11512
+ }
11278
11513
  flushingStagedReveals = true;
11279
11514
  try {
11280
11515
  const run = () => {
@@ -11970,10 +12205,31 @@ function renderBranchSlot(parentScope, slotKey, state, domParent, next, body, ma
11970
12205
  let bStart;
11971
12206
  let bEnd;
11972
12207
  let borrowed = false;
12208
+ let rebuild = false;
12209
+ let inner = null;
12210
+ let innerEnd = null;
11973
12211
  if (hydration !== null && hydration.isOpen(state.start.nextSibling)) {
11974
- bStart = state.start.nextSibling;
11975
- bEnd = hydration.close(bStart);
11976
- hydration.node = bStart.nextSibling;
12212
+ inner = state.start.nextSibling;
12213
+ innerEnd = hydration.close(inner);
12214
+ if (innerEnd !== state.end && innerEnd.nextSibling !== state.end) {
12215
+ if (process.env.NODE_ENV !== "production") {
12216
+ const mmLoc = siteLoc(parentScope, slotKey);
12217
+ if (mmLoc)
12218
+ hydration.warnStructural(
12219
+ mmLoc,
12220
+ "a single branch range",
12221
+ hydration.describe(innerEnd.nextSibling)
12222
+ );
12223
+ }
12224
+ removeRange(state.start.nextSibling, state.end);
12225
+ inner = null;
12226
+ rebuild = true;
12227
+ }
12228
+ }
12229
+ if (inner !== null) {
12230
+ bStart = inner;
12231
+ bEnd = innerEnd;
12232
+ hydration.node = inner.nextSibling;
11977
12233
  } else {
11978
12234
  bStart = state.start;
11979
12235
  bEnd = state.end;
@@ -11992,7 +12248,12 @@ function renderBranchSlot(parentScope, slotKey, state, domParent, next, body, ma
11992
12248
  );
11993
12249
  if (borrowed) b.exclusiveMarkers = true;
11994
12250
  state.block = b;
11995
- renderBlock(b);
12251
+ if (rebuild) {
12252
+ hydration.suspend(() => renderBlock(b));
12253
+ hydration.node = state.end.nextSibling;
12254
+ } else {
12255
+ renderBlock(b);
12256
+ }
11996
12257
  } else if (hydration !== null && state.start.nextSibling !== state.end) {
11997
12258
  if (process.env.NODE_ENV !== "production") {
11998
12259
  const mmLoc = siteLoc(parentScope, slotKey);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "octane",
3
- "version": "0.1.20",
3
+ "version": "0.1.21",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "sideEffects": false,