@parhelia/core 0.1.12928 → 0.1.12931

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 (138) hide show
  1. package/dist/agents-view/AgentCard.js +4 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentProfileIcon.js +8 -8
  4. package/dist/agents-view/AgentProfileIcon.js.map +1 -1
  5. package/dist/agents-view/AgentsView.js +1 -1
  6. package/dist/components/WorkspaceHomeView.d.ts +13 -0
  7. package/dist/components/WorkspaceHomeView.js +13 -0
  8. package/dist/components/WorkspaceHomeView.js.map +1 -0
  9. package/dist/components/ui/styled-dialog-title.d.ts +3 -1
  10. package/dist/components/ui/styled-dialog-title.js +2 -2
  11. package/dist/components/ui/styled-dialog-title.js.map +1 -1
  12. package/dist/config/config.js +7 -19
  13. package/dist/config/config.js.map +1 -1
  14. package/dist/config/notificationRoutes.js +1 -1
  15. package/dist/config/notificationRoutes.js.map +1 -1
  16. package/dist/editor/ConfirmationDialog.js +1 -1
  17. package/dist/editor/ConfirmationDialog.js.map +1 -1
  18. package/dist/editor/ImageEditButton.js +14 -2
  19. package/dist/editor/ImageEditButton.js.map +1 -1
  20. package/dist/editor/ImageEditor.js +1 -1
  21. package/dist/editor/ImageEditor.js.map +1 -1
  22. package/dist/editor/PictureEditor.js +1 -1
  23. package/dist/editor/PictureEditor.js.map +1 -1
  24. package/dist/editor/ai/Agents.js +15 -79
  25. package/dist/editor/ai/Agents.js.map +1 -1
  26. package/dist/editor/ai/terminal/components/AgentMemoryPanel.d.ts +12 -0
  27. package/dist/editor/ai/terminal/components/AgentMemoryPanel.js +131 -0
  28. package/dist/editor/ai/terminal/components/AgentMemoryPanel.js.map +1 -0
  29. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js +10 -2
  30. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js.map +1 -1
  31. package/dist/editor/ai/terminal/components/AgentTerminalContextPanels.js +11 -0
  32. package/dist/editor/ai/terminal/components/AgentTerminalContextPanels.js.map +1 -1
  33. package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js +27 -20
  34. package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js.map +1 -1
  35. package/dist/editor/ai/terminal/useAgentSettingsData.js +4 -4
  36. package/dist/editor/ai/terminal/useAgentSettingsData.js.map +1 -1
  37. package/dist/editor/client/EditorShell.js +11 -51
  38. package/dist/editor/client/EditorShell.js.map +1 -1
  39. package/dist/editor/client/editContext.d.ts +3 -1
  40. package/dist/editor/client/operations.js +20 -1
  41. package/dist/editor/client/operations.js.map +1 -1
  42. package/dist/editor/commands/componentCommands.js +2 -1
  43. package/dist/editor/commands/componentCommands.js.map +1 -1
  44. package/dist/editor/context-menu/InsertMenu.js +1 -1
  45. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  46. package/dist/editor/menubar/PageSelector.js +1 -1
  47. package/dist/editor/menubar/PageSelector.js.map +1 -1
  48. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -2
  49. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  50. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +6 -0
  51. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  52. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -5
  53. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  54. package/dist/editor/page-editor-chrome/DragInsertionOverlay.d.ts +8 -1
  55. package/dist/editor/page-editor-chrome/DragInsertionOverlay.js +154 -34
  56. package/dist/editor/page-editor-chrome/DragInsertionOverlay.js.map +1 -1
  57. package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.d.ts +10 -0
  58. package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.js +236 -0
  59. package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.js.map +1 -0
  60. package/dist/editor/page-editor-chrome/PageEditorChrome.js +2 -1
  61. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  62. package/dist/editor/page-editor-chrome/PictureEditorOverlay.d.ts +3 -0
  63. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +1 -1
  64. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  65. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +6 -2
  66. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  67. package/dist/editor/page-editor-chrome/overlay/placeholderZones.d.ts +10 -0
  68. package/dist/editor/page-editor-chrome/overlay/placeholderZones.js +18 -0
  69. package/dist/editor/page-editor-chrome/overlay/placeholderZones.js.map +1 -1
  70. package/dist/editor/page-viewer/EditorForm.js +40 -2
  71. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  72. package/dist/editor/page-viewer/MiniMap.js +70 -5
  73. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  74. package/dist/editor/page-viewer/PageViewerFrame.js +123 -29
  75. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  76. package/dist/editor/page-viewer/minimapVisibility.d.ts +33 -0
  77. package/dist/editor/page-viewer/minimapVisibility.js +72 -0
  78. package/dist/editor/page-viewer/minimapVisibility.js.map +1 -0
  79. package/dist/editor/page-viewer/renderUrl.js +29 -7
  80. package/dist/editor/page-viewer/renderUrl.js.map +1 -1
  81. package/dist/editor/reviews/CreateReviewConfirmStep.js +5 -4
  82. package/dist/editor/reviews/CreateReviewConfirmStep.js.map +1 -1
  83. package/dist/editor/reviews/CreateReviewDialog.js +120 -202
  84. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  85. package/dist/editor/reviews/CreateReviewItemsStep.d.ts +18 -0
  86. package/dist/editor/reviews/CreateReviewItemsStep.js +6 -0
  87. package/dist/editor/reviews/CreateReviewItemsStep.js.map +1 -0
  88. package/dist/editor/reviews/CreateReviewLanguagesStep.d.ts +9 -0
  89. package/dist/editor/reviews/CreateReviewLanguagesStep.js +36 -0
  90. package/dist/editor/reviews/CreateReviewLanguagesStep.js.map +1 -0
  91. package/dist/editor/reviews/CreateReviewReviewersStep.d.ts +17 -0
  92. package/dist/editor/reviews/CreateReviewReviewersStep.js +33 -0
  93. package/dist/editor/reviews/CreateReviewReviewersStep.js.map +1 -0
  94. package/dist/editor/reviews/CreateReviewSettingsStep.d.ts +21 -0
  95. package/dist/editor/reviews/CreateReviewSettingsStep.js +33 -0
  96. package/dist/editor/reviews/CreateReviewSettingsStep.js.map +1 -0
  97. package/dist/editor/reviews/MultiReviewManager.js +1 -4
  98. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  99. package/dist/editor/reviews/openCommentsFeedback.d.ts +2 -6
  100. package/dist/editor/reviews/openCommentsFeedback.js +3 -11
  101. package/dist/editor/reviews/openCommentsFeedback.js.map +1 -1
  102. package/dist/editor/services/agentService.d.ts +36 -0
  103. package/dist/editor/services/agentService.js +42 -0
  104. package/dist/editor/services/agentService.js.map +1 -1
  105. package/dist/editor/services/aiService.d.ts +4 -2
  106. package/dist/editor/services/aiService.js.map +1 -1
  107. package/dist/editor/services/editService.d.ts +1 -1
  108. package/dist/editor/services/editService.js +2 -2
  109. package/dist/editor/services/editService.js.map +1 -1
  110. package/dist/editor/services/reviewsService.d.ts +6 -0
  111. package/dist/editor/services/reviewsService.js +15 -0
  112. package/dist/editor/services/reviewsService.js.map +1 -1
  113. package/dist/editor/settings/panels/AgentProfileConfigPanel.js +2 -1
  114. package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -1
  115. package/dist/editor/sidebar/ComponentTree.js +21 -1
  116. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  117. package/dist/editor/tree-indicators/GutterColumns.js +24 -6
  118. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  119. package/dist/editor/ui/ItemCollectionEditor.js +4 -4
  120. package/dist/editor/ui/ItemCollectionEditor.js.map +1 -1
  121. package/dist/editor/ui/PerfectTree.js +5 -5
  122. package/dist/editor/ui/PerfectTree.js.map +1 -1
  123. package/dist/editor/ui/PublishItemDialog.js +1 -1
  124. package/dist/editor/ui/PublishItemDialog.js.map +1 -1
  125. package/dist/editor/ui/PublishRestrictionsDialog.js +179 -148
  126. package/dist/editor/ui/PublishRestrictionsDialog.js.map +1 -1
  127. package/dist/editor/utils/keyboardNavigation.js +7 -0
  128. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  129. package/dist/editor/views/SingleEditView.js +32 -2
  130. package/dist/editor/views/SingleEditView.js.map +1 -1
  131. package/dist/index.d.ts +2 -0
  132. package/dist/index.js +1 -0
  133. package/dist/index.js.map +1 -1
  134. package/dist/revision.d.ts +2 -2
  135. package/dist/revision.js +2 -2
  136. package/dist/task-board/components/TaskBoardHomeView.js +3 -6
  137. package/dist/task-board/components/TaskBoardHomeView.js.map +1 -1
  138. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState } from "react";
3
3
  import { BRIDGE_DOM_UPDATED_EVENT, MiniMap } from "./MiniMap";
4
+ import { resolveMiniMapVisibility, } from "./minimapVisibility";
4
5
  import { useCanvasSelection, useEditContext, useEditContextRef, useFieldsEditContext, useFieldsEditContextRef, } from "../client/editContext";
5
6
  import { useSlotContext } from "../views/editorSlotContext";
6
7
  import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
@@ -25,6 +26,7 @@ import { Spinner } from "../ui/Spinner";
25
26
  import { DeviceToolbar } from "./DeviceToolbar";
26
27
  import { FullscreenControls } from "../client/ui/FullscreenControls";
27
28
  import { toSitecoreDate } from "../utils/sitecoreDate";
29
+ import { pickVersionAtDate } from "../utils/versionAtDate";
28
30
  import { BridgeClient } from "../bridge/BridgeClient";
29
31
  import { beginPerfTrace, isPerfTraceActive, isPerfTraceEnabled, perfTraceStep, } from "../perfTrace";
30
32
  import { getSuggestionDisplayValue } from "../reviews/suggestionDisplayValue";
@@ -642,6 +644,21 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
642
644
  const directOverlayScrollSyncRef = useRef(false);
643
645
  const lastBridgeHoverInteractionRef = useRef(undefined);
644
646
  const pendingRefreshScrollRef = useRef(undefined);
647
+ // When the pending refresh scroll was captured, and when the last explicit
648
+ // scrollBy command (minimap click/wheel) was issued. A capture is only safe
649
+ // to restore while no explicit command is newer than it: the capture
650
+ // describes where the canvas happened to be at refresh-request time, and
651
+ // blindly re-asserting it after the user commanded a scroll yanks the
652
+ // canvas away from where they sent it.
653
+ const pendingRefreshScrollCapturedAtRef = useRef(0);
654
+ const lastExplicitScrollCommandAtRef = useRef(0);
655
+ // Signature of the last canvas render actually requested (URL sans the
656
+ // per-request edit_rev nonce, plus the edit revision). When a re-run of the
657
+ // render effect produces the same signature — e.g. an edit<->suggestions
658
+ // mode toggle, which changes no host-rendered output — the refresh is
659
+ // skipped entirely: the bridge learns the mode via setEditorMode, and a
660
+ // needless refresh would drop scroll position and inline-edit state.
661
+ const lastRenderSignatureRef = useRef(undefined);
645
662
  // Set when a canvas refresh (bridge patch or full reload) has been requested
646
663
  // and its result has not landed yet. While in flight, bridgeGeometry still
647
664
  // describes the PRE-refresh DOM — a moved component is still measured at its
@@ -671,6 +688,15 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
671
688
  pendingRefreshScrollRef.current = undefined;
672
689
  }
673
690
  };
691
+ if (lastExplicitScrollCommandAtRef.current >
692
+ pendingRefreshScrollCapturedAtRef.current) {
693
+ // The user commanded a scroll after this position was captured; the
694
+ // bridge is delivering that newer target. Restoring the stale capture
695
+ // would fight it (restoreIframeWindowScroll re-asserts for ~150
696
+ // frames and cancels in-flight smooth scrolls).
697
+ clearPendingTarget();
698
+ return;
699
+ }
674
700
  const currentScroll = getIframeWindowScroll(iframe) ??
675
701
  observedScroll ?? { x: 0, y: 0 };
676
702
  const deltaX = targetScroll.x - currentScroll.x;
@@ -716,6 +742,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
716
742
  const [loadedIframeSrc, setLoadedIframeSrc] = useState();
717
743
  const [scroll, setScroll] = useState(0);
718
744
  const [showMiniMap, setShowMiniMap] = useState(false);
745
+ const miniMapVisibilityMemoryRef = useRef({});
719
746
  const [bridgeGeometryRevision, setBridgeGeometryRevision] = useState(0);
720
747
  const [bridgeDomRevision, setBridgeDomRevision] = useState(0);
721
748
  const bridgeGeometryRevisionRafRef = useRef(null);
@@ -1300,16 +1327,22 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
1300
1327
  const requestBridgeTextCompletion = useCallback(async (interaction, force = false) => {
1301
1328
  const editor = editContextRef.current;
1302
1329
  if (!editor?.enableCompletions || editor.mode === "preview") {
1330
+ console.debug("[Parhelia] text completion skipped", JSON.stringify({
1331
+ enableCompletions: editor?.enableCompletions,
1332
+ mode: editor?.mode,
1333
+ }));
1303
1334
  clearBridgeTextCompletion();
1304
1335
  return;
1305
1336
  }
1306
1337
  const fieldDescriptor = await resolveBridgeFieldDescriptor(interaction);
1307
1338
  if (!fieldDescriptor) {
1339
+ console.debug("[Parhelia] text completion skipped: no field descriptor", JSON.stringify({ elementKey: interaction.elementKey }));
1308
1340
  clearBridgeTextCompletion();
1309
1341
  return;
1310
1342
  }
1311
1343
  const field = await editor.itemsRepository.getField(fieldDescriptor);
1312
1344
  if (!field) {
1345
+ console.debug("[Parhelia] text completion skipped: field not in repository", JSON.stringify(fieldDescriptor));
1313
1346
  clearBridgeTextCompletion();
1314
1347
  return;
1315
1348
  }
@@ -1324,24 +1357,15 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
1324
1357
  if (!elementKey ||
1325
1358
  !hostFieldId ||
1326
1359
  !isBridgePatchableTextField(field, isRichText)) {
1360
+ console.debug("[Parhelia] text completion skipped: not patchable", JSON.stringify({
1361
+ elementKey,
1362
+ hostFieldId,
1363
+ fieldType: field.type,
1364
+ isRichText,
1365
+ }));
1327
1366
  clearBridgeTextCompletion();
1328
1367
  return;
1329
1368
  }
1330
- const selectedText = selectionMatches && typeof bridgeSelection?.text === "string"
1331
- ? bridgeSelection.text
1332
- : typeof interaction.selectedText === "string"
1333
- ? interaction.selectedText
1334
- : "";
1335
- const selectionStartOffset = selectionMatches && typeof bridgeSelection?.startOffset === "number"
1336
- ? bridgeSelection.startOffset
1337
- : typeof interaction.selectionStartOffset === "number"
1338
- ? interaction.selectionStartOffset
1339
- : undefined;
1340
- const selectionEndOffset = selectionMatches && typeof bridgeSelection?.endOffset === "number"
1341
- ? bridgeSelection.endOffset
1342
- : typeof interaction.selectionEndOffset === "number"
1343
- ? interaction.selectionEndOffset
1344
- : undefined;
1345
1369
  const liveSelectionStartOffset = typeof interaction.selectionStartOffset === "number"
1346
1370
  ? interaction.selectionStartOffset
1347
1371
  : undefined;
@@ -1354,6 +1378,19 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
1354
1378
  const bridgeSelectionEndOffset = selectionMatches && typeof bridgeSelection?.endOffset === "number"
1355
1379
  ? bridgeSelection?.endOffset
1356
1380
  : undefined;
1381
+ const selectedText = typeof interaction.selectedText === "string"
1382
+ ? interaction.selectedText
1383
+ : selectionMatches && typeof bridgeSelection?.text === "string"
1384
+ ? bridgeSelection.text
1385
+ : "";
1386
+ const selectionStartOffset = liveSelectionStartOffset ??
1387
+ (selectionMatches && typeof bridgeSelection?.startOffset === "number"
1388
+ ? bridgeSelection.startOffset
1389
+ : undefined);
1390
+ const selectionEndOffset = liveSelectionEndOffset ??
1391
+ (selectionMatches && typeof bridgeSelection?.endOffset === "number"
1392
+ ? bridgeSelection.endOffset
1393
+ : undefined);
1357
1394
  const activeInlineEdit = activeBridgeInlineEditRef.current;
1358
1395
  const activeInlineEditMatches = !!(activeInlineEdit &&
1359
1396
  activeInlineEdit.elementKey === elementKey &&
@@ -1379,7 +1416,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
1379
1416
  !hasLiveInteractionOffsets ||
1380
1417
  (bridgeSelectionStartOffset === liveSelectionStartOffset &&
1381
1418
  bridgeSelectionEndOffset === liveSelectionEndOffset);
1382
- const metadataMatchesLiveOffsets = !selectionSnapshotMatchesLiveOffsets ||
1419
+ const metadataMatchesLiveOffsets = selectionSnapshotMatchesLiveOffsets ||
1383
1420
  ((contextBefore?.length ?? 0) === interaction.selectionStartOffset &&
1384
1421
  (contextBefore?.length ?? 0) + selectedText.length ===
1385
1422
  interaction.selectionEndOffset);
@@ -1490,6 +1527,12 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
1490
1527
  return;
1491
1528
  }
1492
1529
  }
1530
+ // Escape leaves insert mode, same as in the editor window itself
1531
+ if (interaction.key === "Escape" &&
1532
+ editContextRef.current?.insertMode) {
1533
+ editContextRef.current.setInsertMode(false);
1534
+ return;
1535
+ }
1493
1536
  if (isBridgeTextCompletionApplyShortcut(interaction)) {
1494
1537
  if (bridgeTextCompletionRef.current) {
1495
1538
  applyBridgeTextCompletion();
@@ -2445,7 +2488,11 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2445
2488
  };
2446
2489
  pageViewContext.requestBridgeGeometry = requestBridgeGeometry;
2447
2490
  const requestBridgeScrollBy = (payload) => {
2448
- return bridgeClientRef.current?.sendCommand("scrollBy", payload) ?? false;
2491
+ const accepted = bridgeClientRef.current?.sendCommand("scrollBy", payload) ?? false;
2492
+ if (accepted) {
2493
+ lastExplicitScrollCommandAtRef.current = Date.now();
2494
+ }
2495
+ return accepted;
2449
2496
  };
2450
2497
  const requestBridgeRichTextCommand = (payload) => {
2451
2498
  return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
@@ -2531,24 +2578,24 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2531
2578
  const bridgeDom = pageViewContextRef.current?.bridgeDom;
2532
2579
  const contentHeight = bridgeDom?.scrollHeight ??
2533
2580
  getBridgeGeometryDocumentSize(bridgeGeometry)?.height;
2581
+ const contentWidth = bridgeDom?.viewport.width ?? bridgeGeometry?.viewport.width;
2534
2582
  const clientHeight = iframe.clientHeight;
2535
2583
  if (!contentHeight || !clientHeight)
2536
2584
  return;
2537
- const upperThreshold = clientHeight + 100; // show minimap if content exceeds this height
2538
2585
  // Check if minimap is enabled in settings and user controls
2539
2586
  const minimapEnabled = editContext.parheliaSettings?.showMinimap !== false &&
2540
2587
  editContext.showMinimap;
2541
2588
  if (!minimapEnabled) {
2589
+ miniMapVisibilityMemoryRef.current = {};
2542
2590
  if (showMiniMap) {
2543
2591
  setShowMiniMap(false);
2544
2592
  }
2545
2593
  return;
2546
2594
  }
2547
- if (showMiniMap) {
2548
- return;
2549
- }
2550
- if (contentHeight > upperThreshold) {
2551
- setShowMiniMap(true);
2595
+ const resolution = resolveMiniMapVisibility(showMiniMap, { contentHeight, contentWidth, clientHeight }, miniMapVisibilityMemoryRef.current);
2596
+ miniMapVisibilityMemoryRef.current = resolution.memory;
2597
+ if (resolution.visible !== showMiniMap) {
2598
+ setShowMiniMap(resolution.visible);
2552
2599
  }
2553
2600
  }, 100);
2554
2601
  useEffect(() => {
@@ -2569,6 +2616,13 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2569
2616
  editContextRef.current?.setSelectedRange(undefined);
2570
2617
  }
2571
2618
  }, [editContext.mode]);
2619
+ // Version that is valid at the active preview date (based on the versions'
2620
+ // publishing restrictions). Derived outside the refresh effect so the effect
2621
+ // re-runs on the resolved version number, not on itemVersions' identity.
2622
+ const previewDateVersion = editContext.mode === "preview" && editContext.previewDate
2623
+ ? pickVersionAtDate(editContext.itemVersions, editContext.previewDate)
2624
+ ?.version
2625
+ : undefined;
2572
2626
  useEffect(() => {
2573
2627
  if (!pageItemDescriptor ||
2574
2628
  !pageViewContext.editUrl ||
@@ -2587,7 +2641,23 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2587
2641
  renderUrl.searchParams.set("parhelia_session", editContext.sessionId);
2588
2642
  }
2589
2643
  if (editContext.mode === "preview" && editContext.previewDate) {
2590
- renderUrl.searchParams.delete("sc_version");
2644
+ // Pin the version that is valid at the preview date. The render URL can
2645
+ // carry the version pin as `sc_version`, `version`, or both; leaving
2646
+ // either behind keeps the page pinned to the previously selected
2647
+ // version, so every pin present must be rewritten. Only update params
2648
+ // that exist: proxied host URLs must not gain sc_* params (they break
2649
+ // Sitecore's httpRequestBegin pipeline — see applyParheliaRenderContext).
2650
+ // sc_date stays on the URL for hosts that resolve datasources by date.
2651
+ if (previewDateVersion != null) {
2652
+ if (renderUrl.searchParams.has("sc_version"))
2653
+ renderUrl.searchParams.set("sc_version", String(previewDateVersion));
2654
+ if (renderUrl.searchParams.has("version"))
2655
+ renderUrl.searchParams.set("version", String(previewDateVersion));
2656
+ }
2657
+ else {
2658
+ renderUrl.searchParams.delete("sc_version");
2659
+ renderUrl.searchParams.delete("version");
2660
+ }
2591
2661
  renderUrl.searchParams.set("sc_date", toSitecoreDate(editContext.previewDate));
2592
2662
  }
2593
2663
  // Layout-mode marker. Only set when editing shared layout, paired with the
@@ -2606,21 +2676,42 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2606
2676
  const currentIframeOrigin = getUrlOrigin(currentIframeUrl);
2607
2677
  const renderUrlIsCrossOrigin = renderUrl.origin !== window.location.origin;
2608
2678
  const iframeOriginChanged = !!(currentIframeOrigin && currentIframeOrigin !== renderUrl.origin);
2679
+ // Proxied host URLs carry the version pin as `version` (sc_* params are
2680
+ // stripped), so consider both param names when detecting a change.
2681
+ const getUrlVersionPin = (url) => url.searchParams.get("sc_version") ?? url.searchParams.get("version");
2609
2682
  const currentVersion = currentIframeUrl
2610
- ? new URL(currentIframeUrl).searchParams.get("sc_version")
2683
+ ? getUrlVersionPin(new URL(currentIframeUrl))
2611
2684
  : null;
2612
- const newVersion = renderUrl.searchParams.get("sc_version");
2685
+ const newVersion = getUrlVersionPin(renderUrl);
2613
2686
  const versionChanged = (currentVersion !== null || newVersion !== null) &&
2614
2687
  currentVersion !== newVersion;
2615
2688
  const initialLoad = currentItemDescriptor?.id !== pageItemDescriptor.id ||
2616
2689
  currentItemDescriptor?.language !== pageItemDescriptor.language ||
2617
2690
  currentItemDescriptor?.version !== pageItemDescriptor.version;
2618
2691
  const shouldUseIframeSrcReload = initialLoad || pageViewContext.fullscreen || iframeOriginChanged;
2692
+ const signatureUrl = new URL(renderUrl.toString());
2693
+ signatureUrl.searchParams.delete("edit_rev");
2694
+ const renderSignature = `${signatureUrl.toString()}|rev:${editContext.revision}`;
2695
+ // Only skip when a live bridge confirms the last refresh actually reached
2696
+ // the host. Before the bridge is connected, re-runs must keep going: the
2697
+ // retry/fallback-reload path below is what recovers a canvas whose first
2698
+ // load raced the bridge injection.
2699
+ if (!shouldUseIframeSrcReload &&
2700
+ !versionChanged &&
2701
+ lastRenderSignatureRef.current === renderSignature &&
2702
+ bridgeClientRef.current?.supportsCapability("refresh")) {
2703
+ setCurrentItemDescriptor(pageItemDescriptor);
2704
+ return;
2705
+ }
2619
2706
  function runFallbackRefresh() {
2707
+ lastRenderSignatureRef.current = renderSignature;
2620
2708
  pageViewContext.setIframeSupportsRefresh(false);
2621
- pendingRefreshScrollRef.current ??=
2622
- getIframeWindowScroll(iframeRef.current) ??
2623
- latestBridgeScrollRef.current;
2709
+ if (!pendingRefreshScrollRef.current) {
2710
+ pendingRefreshScrollRef.current =
2711
+ getIframeWindowScroll(iframeRef.current) ??
2712
+ latestBridgeScrollRef.current;
2713
+ pendingRefreshScrollCapturedAtRef.current = Date.now();
2714
+ }
2624
2715
  canvasRefreshInFlightSinceRef.current = Date.now();
2625
2716
  awaitingHostAppRefreshRenderRef.current = false;
2626
2717
  setShowSpinner(true);
@@ -2635,9 +2726,11 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2635
2726
  setIframeSrc(renderUrl.toString());
2636
2727
  }
2637
2728
  function runBridgeRefresh() {
2729
+ lastRenderSignatureRef.current = renderSignature;
2638
2730
  pendingRefreshScrollRef.current =
2639
2731
  getIframeWindowScroll(iframeRef.current) ??
2640
2732
  latestBridgeScrollRef.current;
2733
+ pendingRefreshScrollCapturedAtRef.current = Date.now();
2641
2734
  const bridge = bridgeClientRef.current;
2642
2735
  if (!bridge?.supportsCapability("refresh")) {
2643
2736
  runFallbackRefresh();
@@ -2686,6 +2779,7 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
2686
2779
  pageViewContext.fullscreen,
2687
2780
  editContext.mode,
2688
2781
  editContext.previewDate,
2782
+ previewDateVersion,
2689
2783
  editContext.sessionId,
2690
2784
  editContext.layoutMode,
2691
2785
  ]);