@agent-native/core 0.184.0 → 0.184.1-nightly-20260922211241

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 (62) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
  3. package/corpus/templates/calendar/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
  4. package/corpus/templates/clips/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
  5. package/corpus/templates/clips/changelog/2026-09-22-rewind-uses-less-storage-by-default.md +6 -0
  6. package/corpus/templates/clips/desktop/src/app.tsx +1 -1
  7. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +30 -3
  8. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +1 -1
  9. package/corpus/templates/content/AGENTS.md +1 -1
  10. package/corpus/templates/content/actions/_document-search-ranking.ts +191 -0
  11. package/corpus/templates/content/actions/search-documents.ts +184 -49
  12. package/corpus/templates/content/changelog/2026-09-21-search-ranks-title-matches-first.md +6 -0
  13. package/corpus/templates/content/docs/product/capabilities/content.knowledge.search.md +11 -8
  14. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +10 -3
  15. package/corpus/templates/design/.generated/bridge/nav.generated.ts +1 -0
  16. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +250 -22
  17. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +16 -0
  18. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +32 -0
  19. package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -2
  20. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +22 -1
  21. package/corpus/templates/design/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
  22. package/corpus/templates/design/changelog/2026-09-22-blocks-can-be-selected-with-one-click-inside-screens.md +6 -0
  23. package/corpus/templates/design/e2e/drag-and-drop.shared.ts +10 -5
  24. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +136 -123
  25. package/corpus/templates/mail/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
  26. package/corpus/templates/slides/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
  27. package/dist/agent/engine/anthropic-engine.d.ts +1 -1
  28. package/dist/agent/model-config.d.ts +8 -8
  29. package/dist/agent/model-config.js +43 -20
  30. package/dist/client/AppShellSkeleton.d.ts +5 -0
  31. package/dist/client/AppShellSkeleton.js +69 -0
  32. package/dist/client/ClientOnly.d.ts +1 -1
  33. package/dist/client/ClientOnly.js +1 -1
  34. package/dist/client/LanguagePicker.d.ts +6 -0
  35. package/dist/client/LanguagePicker.js +128 -0
  36. package/dist/client/analytics.d.ts +3 -2
  37. package/dist/client/analytics.js +1 -1
  38. package/dist/client/app-providers.d.ts +3 -3
  39. package/dist/client/app-providers.js +53 -23
  40. package/dist/client/client-status-requests.d.ts +5 -0
  41. package/dist/client/client-status-requests.js +31 -18
  42. package/dist/client/i18n.d.ts +1 -6
  43. package/dist/client/i18n.js +3 -117
  44. package/dist/client/onboarding/first-run-startup-gate.js +14 -3
  45. package/dist/client/require-session.d.ts +1 -1
  46. package/dist/client/require-session.js +3 -3
  47. package/dist/client/session-replay-context.d.ts +16 -0
  48. package/dist/client/session-replay-context.js +93 -0
  49. package/dist/client/session-replay.d.ts +2 -26
  50. package/dist/client/session-replay.js +1 -64
  51. package/dist/client/settings/SettingsPanel.js +12 -8
  52. package/dist/deploy/build.js +19 -29
  53. package/dist/notifications/routes.d.ts +1 -1
  54. package/dist/observability/routes.d.ts +5 -5
  55. package/dist/secrets/routes.d.ts +6 -6
  56. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  57. package/dist/server/core-routes-plugin.d.ts +1 -1
  58. package/dist/shared/reasoning-effort.js +14 -20
  59. package/dist/shared/ssr-session-bootstrap.d.ts +3 -0
  60. package/dist/shared/ssr-session-bootstrap.js +44 -0
  61. package/dist/triggers/actions/manage-automation.d.ts +2 -2
  62. package/package.json +4 -4
@@ -59,7 +59,7 @@ Given a stale connected Source result, when it appears, then its freshness state
59
59
 
60
60
  ## Current evidence
61
61
 
62
- Existing search paths are in progress donor substrate, but complete indexed, freshness, ranking, and agent-parity proof is incomplete. This Capability remains `in_progress`.
62
+ Existing search paths are in progress donor substrate, but complete indexed, freshness, and agent-parity proof is incomplete. This Capability remains `in_progress`.
63
63
 
64
64
  The command picker uses the shared paginated search Action, with title-only,
65
65
  document-type and modified-date predicates applied before pagination. Parent
@@ -67,13 +67,16 @@ context is independently access-scoped. The query supports a bounded
67
67
  Google-style operator set: quoted "exact phrases", `-excludedTerm`,
68
68
  uppercase `OR` between terms, `intitle:term`, and implicit AND across bare
69
69
  words; `%`, `_`, and `*` stay literal, and an all-workspaces scope option
70
- omits the space filter while keeping server-side access scoping. Snippet
71
- windows anchor at the first in-body occurrence of the first match needle,
72
- so deep-body matches show their own context. Focused
73
- tests cover later pages, private-parent suppression, hidden-result counts,
74
- type filters, and each operator. This is bounded lexical search substrate,
75
- not proof of indexed retrieval across every Source, authoritative author
76
- filtering, ranking, or policy-governed global search.
70
+ omits the space filter while keeping server-side access scoping. Search ranks
71
+ the complete authorized match set before pagination. Exact and contiguous
72
+ partial titles form protected tiers above title-word, description, and body
73
+ matches; modification time only breaks otherwise equal results. Snippet windows
74
+ prefer context containing more positive terms. Focused tests cover later pages,
75
+ private-parent suppression, hidden-result counts, title tiers, useful snippets,
76
+ type filters, and each operator. A 10,000-document PGlite benchmark covers the
77
+ current warm-query budget. This is bounded lexical search substrate, not proof
78
+ of indexed retrieval across every Source, authoritative author filtering,
79
+ freshness ranking, or policy-governed global search.
77
80
 
78
81
  Local browser verification of the command picker covered 27 authorized matches
79
82
  across two result pages, equal-title parent context, keyboard opening, title/body
@@ -2539,6 +2539,13 @@ export const editorChromeBridgeScript: string = `"use strict";
2539
2539
  }
2540
2540
  return containerScopeAncestor(resolved, scope);
2541
2541
  }
2542
+ function plainClickSelectionTarget(hit) {
2543
+ if (!designCanvasBoardSurface) {
2544
+ selectionContainerScope = null;
2545
+ return selectionTargetForHit(hit);
2546
+ }
2547
+ return containerFirstSelectionTarget(hit);
2548
+ }
2542
2549
  function clickThroughSelectionTarget(hit, ev) {
2543
2550
  if (ev.detail > 1) return null;
2544
2551
  if (!selectedEl || !document.documentElement.contains(selectedEl)) {
@@ -8247,7 +8254,7 @@ export const editorChromeBridgeScript: string = `"use strict";
8247
8254
  return;
8248
8255
  }
8249
8256
  hoveredSpacingHandleKey = "";
8250
- var resolvedClickTarget = e.metaKey || e.ctrlKey ? selectionTargetForHit(target) : containerFirstSelectionTarget(target);
8257
+ var resolvedClickTarget = e.metaKey || e.ctrlKey ? selectionTargetForHit(target) : plainClickSelectionTarget(target);
8251
8258
  var toggled = resolveShiftClickToggleOff(resolvedClickTarget, e);
8252
8259
  if (toggled !== void 0) {
8253
8260
  postToggledSelection(toggled);
@@ -16258,7 +16265,7 @@ export const editorChromeBridgeScript: string = `"use strict";
16258
16265
  }
16259
16266
  if (ev) stopNativeInteraction(ev);
16260
16267
  var cycledEl = !readOnly && (e.metaKey || e.ctrlKey) && !e.shiftKey ? stackCycleTarget(e.clientX, e.clientY, selectedEl) : null;
16261
- var primaryClickTarget = !readOnly && (e.metaKey || e.ctrlKey) ? selectionTargetForHit(hit) : (!readOnly && !e.shiftKey ? clickThroughSelectionTarget(hit, ev) : null) || containerFirstSelectionTarget(hit);
16268
+ var primaryClickTarget = !readOnly && (e.metaKey || e.ctrlKey) ? selectionTargetForHit(hit) : !designCanvasBoardSurface ? plainClickSelectionTarget(hit) : (!readOnly && !e.shiftKey ? clickThroughSelectionTarget(hit, ev) : null) || containerFirstSelectionTarget(hit);
16262
16269
  if (cycledEl) {
16263
16270
  selectTarget(cycledEl, ev, true);
16264
16271
  } else {
@@ -17205,7 +17212,7 @@ export const editorChromeBridgeScript: string = `"use strict";
17205
17212
  var lastHoverClientPoint = null;
17206
17213
  function resolveHoverTarget(clientX, clientY, deepSelect) {
17207
17214
  var rawHit = elementFromEditorPoint(clientX, clientY);
17208
- return deepSelect ? selectionTargetForHit(rawHit) : containerFirstSelectionTarget(rawHit);
17215
+ return deepSelect || !designCanvasBoardSurface ? selectionTargetForHit(rawHit) : containerFirstSelectionTarget(rawHit);
17209
17216
  }
17210
17217
  function reresolveHoverAtLastPoint(deepSelect) {
17211
17218
  if (!lastHoverClientPoint) return;
@@ -28,6 +28,7 @@ export const navBridgeScript: string = `"use strict";
28
28
  if (!t || !t.closest) return;
29
29
  var a = t.closest("a[href], [data-screen]");
30
30
  if (!a) return;
31
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
31
32
  var ds = a.getAttribute && a.getAttribute("data-screen");
32
33
  if (!ds) {
33
34
  var rawHref = a.getAttribute("href");
@@ -294,7 +294,9 @@ import {
294
294
  getBoardSurfaceHtml,
295
295
  getBoardSurfaceRenderContent,
296
296
  getBoardSurfaceStaticPreviewContent,
297
+ hasBoardRuntimeSurfaceContent,
297
298
  hasBoardSurfaceContent,
299
+ shouldMountBoardSurface,
298
300
  shouldRenderEmptyBoardReviewCanvas,
299
301
  } from "./multi-screen/board-surface-html";
300
302
  import {
@@ -663,6 +665,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
663
665
  boardIsActive = false,
664
666
  boardRuntimeStructureInsertRequest,
665
667
  boardRuntimeStructureRollbackRequest,
668
+ runtimeStructurePendingTransactionRef,
666
669
  onBoardRuntimeStructureInsertRejected,
667
670
  onBoardRuntimeStructureInsertApplied,
668
671
  onBoardRuntimeStructureRollbackResult,
@@ -729,6 +732,51 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
729
732
  // before the first layout measurement.
730
733
  const [surfaceSize, setSurfaceSize] = useState({ width: 0, height: 0 });
731
734
  const [crossScreenDragActive, setCrossScreenDragActive] = useState(false);
735
+ const [boardRuntimeSurfaceActive, setBoardRuntimeSurfaceActive] = useState<
736
+ string | null
737
+ >(null);
738
+ const boardCrossScreenDropPendingRef = useRef(false);
739
+ const boardCrossScreenDropTransactionRef = useRef<string | null>(null);
740
+ const boardCrossScreenDropTimeoutTransactionRef = useRef<string | null>(null);
741
+ const boardCrossScreenDropTimeoutRef = useRef<number | null>(null);
742
+ const onBoardRuntimeStructureInsertRejectedRef = useRef(
743
+ onBoardRuntimeStructureInsertRejected,
744
+ );
745
+ onBoardRuntimeStructureInsertRejectedRef.current =
746
+ onBoardRuntimeStructureInsertRejected;
747
+ const finishBoardCrossScreenDrop = useCallback(
748
+ (options?: { preserveTransaction?: boolean; force?: boolean }) => {
749
+ if (!boardCrossScreenDropPendingRef.current && !options?.force) return;
750
+ boardCrossScreenDropPendingRef.current = false;
751
+ if (boardCrossScreenDropTimeoutRef.current !== null) {
752
+ window.clearTimeout(boardCrossScreenDropTimeoutRef.current);
753
+ boardCrossScreenDropTimeoutRef.current = null;
754
+ }
755
+ boardCrossScreenDropTimeoutTransactionRef.current = null;
756
+ if (!options?.preserveTransaction) {
757
+ boardCrossScreenDropTransactionRef.current = null;
758
+ }
759
+ setCrossScreenDragActive(false);
760
+ },
761
+ [],
762
+ );
763
+ useEffect(() => {
764
+ const transactionId = boardRuntimeStructureInsertRequest?.transactionId;
765
+ if (!transactionId || !boardCrossScreenDropPendingRef.current) return;
766
+ boardCrossScreenDropTransactionRef.current = transactionId;
767
+ }, [boardRuntimeStructureInsertRequest]);
768
+ useEffect(
769
+ () => () => {
770
+ if (boardCrossScreenDropTimeoutRef.current !== null) {
771
+ window.clearTimeout(boardCrossScreenDropTimeoutRef.current);
772
+ }
773
+ },
774
+ [],
775
+ );
776
+ const [boardRuntimeSurfaceState, setBoardRuntimeSurfaceState] = useState<{
777
+ boardFileId: string | null;
778
+ requestKeys: string[];
779
+ }>({ boardFileId: null, requestKeys: [] });
732
780
  const [frameGeometry, setFrameGeometry] = useState<FrameGeometryById>({});
733
781
  const frameGeometryRef = useRef(frameGeometry);
734
782
  const renderedScreenIdsRef = useRef<Set<string>>(new Set());
@@ -896,12 +944,70 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
896
944
  const boardFrameGeometryRef = useRef(boardFrameGeometry);
897
945
  boardFrameGeometryRef.current = boardFrameGeometry;
898
946
  // Keep the empty board unmounted during normal editing so the text/shape
899
- // creation path can still own its first mount. Mount it for the duration of
900
- // a cross-screen drag so the drop has a real DOM target before release.
901
- const boardSurfaceHtml =
902
- crossScreenDragActive || hasBoardSurfaceContent(boardFileContent)
903
- ? getBoardSurfaceHtml(boardFileContent)
904
- : undefined;
947
+ // creation path can still own its first mount. Mount it for a cross-screen
948
+ // drag and keep it through the runtime insert acknowledgement.
949
+ const handleBoardRuntimeStructureInsertApplied = useCallback(
950
+ (details: {
951
+ requestId: string;
952
+ transactionId?: string;
953
+ routePath?: string;
954
+ selector: string;
955
+ sourceId?: string;
956
+ applied?: boolean;
957
+ }) => {
958
+ if (details.applied !== false && details.selector && boardFileId) {
959
+ const requestKey = details.transactionId ?? details.requestId;
960
+ setBoardRuntimeSurfaceState((current) => {
961
+ const base =
962
+ current.boardFileId === boardFileId
963
+ ? current
964
+ : { boardFileId, requestKeys: [] };
965
+ return base.requestKeys.includes(requestKey)
966
+ ? base
967
+ : { ...base, requestKeys: [...base.requestKeys, requestKey] };
968
+ });
969
+ }
970
+ onBoardRuntimeStructureInsertApplied?.(details);
971
+ },
972
+ [boardFileId, onBoardRuntimeStructureInsertApplied],
973
+ );
974
+ const handleBoardRuntimeStructureRollbackResult = useCallback(
975
+ (details: {
976
+ requestId: string;
977
+ transactionId?: string;
978
+ applied: boolean;
979
+ reason?: string;
980
+ }) => {
981
+ if (details.applied && details.transactionId && boardFileId) {
982
+ setBoardRuntimeSurfaceState((current) => {
983
+ if (current.boardFileId !== boardFileId) return current;
984
+ const requestKeys = current.requestKeys.filter(
985
+ (key) => key !== details.transactionId,
986
+ );
987
+ return requestKeys.length > 0
988
+ ? { ...current, requestKeys }
989
+ : { boardFileId: null, requestKeys: [] };
990
+ });
991
+ }
992
+ onBoardRuntimeStructureRollbackResult?.(details);
993
+ },
994
+ [boardFileId, onBoardRuntimeStructureRollbackResult],
995
+ );
996
+ const boardSurfaceHtml = shouldMountBoardSurface({
997
+ hasAuthoredContent: hasBoardSurfaceContent(boardFileContent),
998
+ crossScreenDragActive,
999
+ hasPendingRuntimeInsert: Boolean(boardRuntimeStructureInsertRequest),
1000
+ hasPendingRuntimeRollback: Boolean(boardRuntimeStructureRollbackRequest),
1001
+ runtimeContentBoardId: boardRuntimeSurfaceActive,
1002
+ boardFileId,
1003
+ hasRuntimeContent: hasBoardRuntimeSurfaceContent({
1004
+ boardFileId,
1005
+ runtimeBoardFileId: boardRuntimeSurfaceState.boardFileId,
1006
+ runtimeRequestKeys: boardRuntimeSurfaceState.requestKeys,
1007
+ }),
1008
+ })
1009
+ ? getBoardSurfaceHtml(boardFileContent)
1010
+ : undefined;
905
1011
  const boardHasSurfaceContent = boardSurfaceHtml !== undefined;
906
1012
  const boardReviewGeometry = boardSurfaceRenderGeometry ?? {
907
1013
  x: 0,
@@ -2713,7 +2819,7 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
2713
2819
  );
2714
2820
  };
2715
2821
 
2716
- const clearCrossScreenDrag = () => {
2822
+ const clearCrossScreenDrag = (options?: { keepBoardMounted?: boolean }) => {
2717
2823
  crossScreenPreviewGenerationRef.current += 1;
2718
2824
  stopParentCrossScreenDrag();
2719
2825
  // The parent key listeners can be removed before a held Control keyup
@@ -2734,7 +2840,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
2734
2840
  clearCrossScreenDropGuide();
2735
2841
  crossScreenTargetRef.current = null;
2736
2842
  crossScreenDragMsgRef.current = null;
2737
- setCrossScreenDragActive(false);
2843
+ if (!options?.keepBoardMounted) {
2844
+ finishBoardCrossScreenDrop();
2845
+ setCrossScreenDragActive(false);
2846
+ }
2738
2847
  // A cancelled/blurred drag must not donate its last board coordinate to
2739
2848
  // the next gesture. In particular, a second drag can emit start -> end
2740
2849
  // without an out-of-iframe move; retaining the previous point would make
@@ -3232,14 +3341,19 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3232
3341
  ignoreAutoLayout: ignoreAutoLayoutAtRelease,
3233
3342
  };
3234
3343
  crossScreenEndSeenRef.current = true;
3235
- clearCrossScreenDrag();
3236
3344
  const dropSeq = crossScreenDropSeqRef.current;
3237
3345
  const isCurrentDrop = () =>
3238
3346
  canvasMountedRef.current && crossScreenDropSeqRef.current === dropSeq;
3239
3347
  crossScreenLastBoardPointRef.current = null;
3240
3348
  const hasIdentifier = !!(payload.selector || payload.sourceId);
3241
- if (!hasIdentifier || !sourceScreenId) return;
3242
- if (!lastBoardPoint) return;
3349
+ if (!hasIdentifier || !sourceScreenId) {
3350
+ clearCrossScreenDrag();
3351
+ return;
3352
+ }
3353
+ if (!lastBoardPoint) {
3354
+ clearCrossScreenDrag();
3355
+ return;
3356
+ }
3243
3357
  // No candidate means the pointer never left the source screen, so the
3244
3358
  // bridge already handled it as an in-place reorder. Falling back to the
3245
3359
  // board here re-persists the move against a file that does not contain
@@ -3252,7 +3366,10 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3252
3366
  lastBoardPoint.x <= sourceFrameGeometry.x + sourceFrameGeometry.width &&
3253
3367
  lastBoardPoint.y >= sourceFrameGeometry.y &&
3254
3368
  lastBoardPoint.y <= sourceFrameGeometry.y + sourceFrameGeometry.height;
3255
- if (droppedInsideSourceScreen) return;
3369
+ if (droppedInsideSourceScreen) {
3370
+ clearCrossScreenDrag();
3371
+ return;
3372
+ }
3256
3373
  trace("drop", "finalize", {
3257
3374
  candidate: candidate?.id ?? null,
3258
3375
  sourceScreen: sourceScreenId,
@@ -3280,9 +3397,47 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3280
3397
  sourceScreen: sourceScreenId,
3281
3398
  lastBoardPoint,
3282
3399
  });
3400
+ clearCrossScreenDrag();
3283
3401
  return;
3284
3402
  }
3285
3403
  crossScreenHostCommittedRef.current = true;
3404
+ if (targetCandidate.id === boardFileId) {
3405
+ // A successful prior handoff retains its identity until the paired
3406
+ // source-delete/rollback settles. A new handoff must not let a late
3407
+ // acknowledgement for that prior transaction settle this one.
3408
+ boardCrossScreenDropTransactionRef.current = null;
3409
+ boardCrossScreenDropPendingRef.current = true;
3410
+ boardCrossScreenDropTimeoutTransactionRef.current = null;
3411
+ if (boardCrossScreenDropTimeoutRef.current !== null) {
3412
+ window.clearTimeout(boardCrossScreenDropTimeoutRef.current);
3413
+ }
3414
+ const expireBoardCrossScreenDrop = () => {
3415
+ if (!boardCrossScreenDropPendingRef.current) return;
3416
+ // A late hit-test must not create a new runtime request after the
3417
+ // host has cancelled this handoff. Once a runtime request exists,
3418
+ // its iframe ack/reject path owns settlement and clears this timer.
3419
+ crossScreenDropSeqRef.current += 1;
3420
+ const transactionId =
3421
+ boardCrossScreenDropTimeoutTransactionRef.current;
3422
+ if (
3423
+ transactionId &&
3424
+ boardCrossScreenDropTransactionRef.current === transactionId
3425
+ ) {
3426
+ onBoardRuntimeStructureInsertRejectedRef.current?.(
3427
+ "board-drop-timeout",
3428
+ transactionId,
3429
+ );
3430
+ return;
3431
+ }
3432
+ finishBoardCrossScreenDrop();
3433
+ };
3434
+ boardCrossScreenDropTimeoutRef.current = window.setTimeout(() => {
3435
+ expireBoardCrossScreenDrop();
3436
+ }, HIT_TEST_COMMIT_TIMEOUT_MS + 1000);
3437
+ }
3438
+ clearCrossScreenDrag({
3439
+ keepBoardMounted: targetCandidate.id === boardFileId,
3440
+ });
3286
3441
 
3287
3442
  if (targetCandidate.id === boardFileId) {
3288
3443
  void runHitTest(targetCandidate, lastBoardPoint, {
@@ -3303,6 +3458,8 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3303
3458
  const hasAnchor = Boolean(
3304
3459
  anchorNodeId || pendingNodeId || anchorSelector,
3305
3460
  );
3461
+ const transactionBeforeDrop =
3462
+ runtimeStructurePendingTransactionRef?.current ?? null;
3306
3463
  onCrossScreenElementDropRef.current?.({
3307
3464
  sourceSelector: payload.selector,
3308
3465
  sourceNodeId: payload.sourceId,
@@ -3334,6 +3491,22 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3334
3491
  styleSnapshot: payload.styleSnapshot,
3335
3492
  styleSnapshotCaptureFailed: payload.styleSnapshotCaptureFailed,
3336
3493
  });
3494
+ const transactionAfterDrop =
3495
+ runtimeStructurePendingTransactionRef?.current ?? null;
3496
+ if (
3497
+ transactionAfterDrop &&
3498
+ transactionAfterDrop !== transactionBeforeDrop
3499
+ ) {
3500
+ boardCrossScreenDropTransactionRef.current = transactionAfterDrop;
3501
+ boardCrossScreenDropTimeoutTransactionRef.current =
3502
+ transactionAfterDrop;
3503
+ } else {
3504
+ // A refused transaction must not leave a timer that can later
3505
+ // reject an unrelated runtime request.
3506
+ finishBoardCrossScreenDrop({
3507
+ preserveTransaction: transactionBeforeDrop !== null,
3508
+ });
3509
+ }
3337
3510
  },
3338
3511
  );
3339
3512
  return;
@@ -3482,7 +3655,9 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
3482
3655
  if (!crossScreenEndSeenRef.current) {
3483
3656
  crossScreenDropSeqRef.current += 1;
3484
3657
  }
3485
- clearCrossScreenDrag();
3658
+ clearCrossScreenDrag({
3659
+ keepBoardMounted: boardCrossScreenDropPendingRef.current,
3660
+ });
3486
3661
  return;
3487
3662
  }
3488
3663
 
@@ -4004,11 +4179,13 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
4004
4179
  boardFileId,
4005
4180
  boardFrameGeometry,
4006
4181
  boardSurfaceRenderGeometry,
4182
+ finishBoardCrossScreenDrop,
4007
4183
  getFrameEntryAtPoint,
4008
4184
  getFrameViewportSize,
4009
4185
  getCanvasPoint,
4010
4186
  getResolvedMetadata,
4011
4187
  onCrossScreenElementDrop,
4188
+ runtimeStructurePendingTransactionRef,
4012
4189
  ]);
4013
4190
 
4014
4191
  // Unmount only — deliberately NOT part of the message effect's cleanup
@@ -10735,15 +10912,66 @@ export const MultiScreenCanvas = memo(function MultiScreenCanvas({
10735
10912
  runtimeStructureRollbackRequest={
10736
10913
  boardRuntimeStructureRollbackRequest
10737
10914
  }
10738
- onRuntimeStructureInsertRejected={
10739
- onBoardRuntimeStructureInsertRejected
10740
- }
10741
- onRuntimeStructureInsertApplied={
10742
- onBoardRuntimeStructureInsertApplied
10743
- }
10744
- onRuntimeStructureRollbackResult={
10745
- onBoardRuntimeStructureRollbackResult
10746
- }
10915
+ onRuntimeStructureInsertRejected={(reason, transactionId) => {
10916
+ const rollbackScheduled =
10917
+ onBoardRuntimeStructureInsertRejected?.(
10918
+ reason,
10919
+ transactionId,
10920
+ ) === true;
10921
+ const expectedTransactionId =
10922
+ boardCrossScreenDropTransactionRef.current;
10923
+ if (
10924
+ !expectedTransactionId ||
10925
+ !transactionId ||
10926
+ expectedTransactionId !== transactionId
10927
+ ) {
10928
+ return;
10929
+ }
10930
+ if (reason === "board-drop-timeout") {
10931
+ // Keep the board iframe mounted until the host's
10932
+ // rollback request settles. The insert acknowledgement
10933
+ // may have been lost after the bridge mutated the DOM.
10934
+ if (!rollbackScheduled) {
10935
+ setBoardRuntimeSurfaceActive(null);
10936
+ finishBoardCrossScreenDrop();
10937
+ }
10938
+ return;
10939
+ }
10940
+ setBoardRuntimeSurfaceActive(null);
10941
+ finishBoardCrossScreenDrop();
10942
+ }}
10943
+ onRuntimeStructureInsertApplied={(details) => {
10944
+ const expectedTransactionId =
10945
+ boardCrossScreenDropTransactionRef.current;
10946
+ if (
10947
+ !expectedTransactionId ||
10948
+ !details.transactionId ||
10949
+ expectedTransactionId !== details.transactionId
10950
+ ) {
10951
+ handleBoardRuntimeStructureInsertApplied(details);
10952
+ return;
10953
+ }
10954
+ if (details.applied !== false) {
10955
+ setBoardRuntimeSurfaceActive(boardFileId ?? null);
10956
+ }
10957
+ handleBoardRuntimeStructureInsertApplied(details);
10958
+ finishBoardCrossScreenDrop({ preserveTransaction: true });
10959
+ }}
10960
+ onRuntimeStructureRollbackResult={(details) => {
10961
+ const expectedTransactionId =
10962
+ boardCrossScreenDropTransactionRef.current;
10963
+ if (
10964
+ !expectedTransactionId ||
10965
+ !details.transactionId ||
10966
+ expectedTransactionId !== details.transactionId
10967
+ ) {
10968
+ handleBoardRuntimeStructureRollbackResult(details);
10969
+ return;
10970
+ }
10971
+ if (details.applied) setBoardRuntimeSurfaceActive(null);
10972
+ handleBoardRuntimeStructureRollbackResult(details);
10973
+ finishBoardCrossScreenDrop({ force: true });
10974
+ }}
10747
10975
  clearSelectionRequest={boardClearSelectionRequest}
10748
10976
  selectedSelector={boardSelectedSelector ?? null}
10749
10977
  selectedSelectorCandidates={
@@ -11,6 +11,13 @@
11
11
  * - internal/relative links (or an explicit `data-screen`): asked to switch
12
12
  * to the matching screen in a multi-screen design; otherwise a no-op so the
13
13
  * prototype never blows itself away.
14
+ * - Cmd/Ctrl/Shift-click and middle-click on a real `<a href>`: left
15
+ * completely alone so the browser's own new-tab/new-window gesture runs
16
+ * (see the modifier-click guard below). A screen switch is a same-tab
17
+ * mutation of the live editor's mode/selection state; forcing it on a
18
+ * click whose entire point was "don't disturb my current tab" corrupts
19
+ * that state instead of opening a new one, which is what produced the
20
+ * Interact-mode crash this guard fixes.
14
21
  *
15
22
  * Protocol (iframe → parent):
16
23
  *
@@ -47,6 +54,15 @@
47
54
  if (!t || !t.closest) return;
48
55
  var a = t.closest("a[href], [data-screen]") as HTMLElement | null;
49
56
  if (!a) return;
57
+ // Cmd/Ctrl-click (new tab), Shift-click (new window), and middle-click
58
+ // are the browser's own "open this somewhere else, leave my tab alone"
59
+ // gestures. Forcing our same-tab screen-switch/postMessage path on top
60
+ // of one of these ignores that intent and mutates the live editor's
61
+ // mode/selection state on a click the user never meant to touch the
62
+ // current tab with. Step aside entirely and let the browser (or, for a
63
+ // `data-screen`-only element with no real destination, simply nothing)
64
+ // handle it instead.
65
+ if (e.metaKey || e.ctrlKey || e.shiftKey || e.button !== 0) return;
50
66
  var ds = a.getAttribute && a.getAttribute("data-screen");
51
67
  // In-page anchors ('#...') and empty hrefs must be handled in-document.
52
68
  // A srcdoc document resolves '#'/'' against the PARENT app URL, so the
@@ -17,6 +17,38 @@ export function getBoardSurfaceHtml(html: string | undefined) {
17
17
  return hasBoardSurfaceContent(html) ? html : EMPTY_BOARD_SURFACE_HTML;
18
18
  }
19
19
 
20
+ export function shouldMountBoardSurface(args: {
21
+ hasAuthoredContent: boolean;
22
+ crossScreenDragActive: boolean;
23
+ hasPendingRuntimeInsert: boolean;
24
+ hasPendingRuntimeRollback?: boolean;
25
+ hasRuntimeContent?: boolean;
26
+ runtimeContentBoardId?: string | null;
27
+ boardFileId?: string;
28
+ }): boolean {
29
+ return (
30
+ args.hasAuthoredContent ||
31
+ args.crossScreenDragActive ||
32
+ args.hasPendingRuntimeInsert ||
33
+ Boolean(args.hasPendingRuntimeRollback) ||
34
+ Boolean(args.hasRuntimeContent) ||
35
+ (args.runtimeContentBoardId != null &&
36
+ args.runtimeContentBoardId === args.boardFileId)
37
+ );
38
+ }
39
+
40
+ export function hasBoardRuntimeSurfaceContent(args: {
41
+ boardFileId?: string;
42
+ runtimeBoardFileId: string | null;
43
+ runtimeRequestKeys: readonly string[];
44
+ }): boolean {
45
+ return (
46
+ args.boardFileId !== undefined &&
47
+ args.runtimeBoardFileId === args.boardFileId &&
48
+ args.runtimeRequestKeys.length > 0
49
+ );
50
+ }
51
+
20
52
  export function shouldRenderEmptyBoardReviewCanvas(args: {
21
53
  hasSurfaceContent: boolean;
22
54
  reviewPinMode: boolean;
@@ -10,7 +10,7 @@ import type { LayoutGridById } from "@shared/layout-grid";
10
10
  import type { PenCuspLatch, PenPath } from "@shared/pen-path";
11
11
  import type { SourceNodeProvenance } from "@shared/preview-source-provenance";
12
12
  import type { VectorEndpointStyle } from "@shared/vector-endpoints";
13
- import type { ReactNode } from "react";
13
+ import type { ReactNode, RefObject } from "react";
14
14
 
15
15
  import type {
16
16
  IframeContextMenuPayload,
@@ -508,10 +508,12 @@ export interface MultiScreenCanvasProps {
508
508
  screenId: string;
509
509
  })
510
510
  | null;
511
+ /** Shared admission lock used to associate a board timeout with its transaction. */
512
+ runtimeStructurePendingTransactionRef?: RefObject<string | null>;
511
513
  onBoardRuntimeStructureInsertRejected?: (
512
514
  reason: string,
513
515
  transactionId?: string,
514
- ) => void;
516
+ ) => boolean | void;
515
517
  onBoardRuntimeStructureInsertApplied?: (details: {
516
518
  requestId: string;
517
519
  transactionId?: string;
@@ -150,6 +150,7 @@ export interface CrossScreenElementDropArgs {
150
150
  clearPendingHistory?: () => void;
151
151
  syncUndoRedoState?: () => void;
152
152
  runtimeStructureInsertRevisionRef: RefObject<number>;
153
+ runtimeStructurePendingTransactionRef?: RefObject<string | null>;
153
154
  sendRuntimeLayerMoveSemanticHandoff: (
154
155
  subjectLayerId: string,
155
156
  targetLayerId: string,
@@ -201,6 +202,7 @@ export function runCrossScreenElementDrop(
201
202
  clearPendingHistory,
202
203
  syncUndoRedoState,
203
204
  runtimeStructureInsertRevisionRef,
205
+ runtimeStructurePendingTransactionRef,
204
206
  sendRuntimeLayerMoveSemanticHandoff,
205
207
  setActiveFileId,
206
208
  setCreatedOverviewLayerSelection,
@@ -406,6 +408,18 @@ export function runCrossScreenElementDrop(
406
408
  if (!canEditDesign && !canEditLiveCrossScreen && !canEditLiveBoardDrop)
407
409
  return;
408
410
 
411
+ const beginRuntimeStructureTransaction = () => {
412
+ if (runtimeStructurePendingTransactionRef?.current) {
413
+ toast.error(t("designEditor.toasts.layerMoveFailed"), { duration: 4000 });
414
+ return null;
415
+ }
416
+ const transactionId = `cross-screen-${Date.now()}-${Math.random().toString(16).slice(2)}`;
417
+ if (runtimeStructurePendingTransactionRef) {
418
+ runtimeStructurePendingTransactionRef.current = transactionId;
419
+ }
420
+ return transactionId;
421
+ };
422
+
409
423
  // Duplicate intent must be resolved before live/semantic move routing. A
410
424
  // fresh clone cannot resolve to a source owner, so those paths would reject
411
425
  // the copy or treat it as a move without consuming sourceCloneHtml.
@@ -475,7 +489,8 @@ export function runCrossScreenElementDrop(
475
489
  });
476
490
  return;
477
491
  }
478
- const transactionId = `cross-screen-${Date.now()}-${Math.random().toString(16).slice(2)}`;
492
+ const transactionId = beginRuntimeStructureTransaction();
493
+ if (!transactionId) return;
479
494
  runtimeStructureInsertRevisionRef.current += 1;
480
495
  setRuntimeStructureInsertRequest({
481
496
  requestId: runtimeStructureInsertRevisionRef.current,
@@ -558,9 +573,12 @@ export function runCrossScreenElementDrop(
558
573
  });
559
574
  return;
560
575
  }
576
+ const transactionId = beginRuntimeStructureTransaction();
577
+ if (!transactionId) return;
561
578
  runtimeStructureInsertRevisionRef.current += 1;
562
579
  setRuntimeStructureInsertRequest({
563
580
  requestId: runtimeStructureInsertRevisionRef.current,
581
+ transactionId,
564
582
  screenId: targetScreenId,
565
583
  sourceScreenId,
566
584
  remintCollidingNodeIds: true,
@@ -834,9 +852,12 @@ export function runCrossScreenElementDrop(
834
852
  });
835
853
  return;
836
854
  }
855
+ const transactionId = beginRuntimeStructureTransaction();
856
+ if (!transactionId) return;
837
857
  runtimeStructureInsertRevisionRef.current += 1;
838
858
  setRuntimeStructureInsertRequest({
839
859
  requestId: runtimeStructureInsertRevisionRef.current,
860
+ transactionId,
840
861
  screenId: targetScreenId,
841
862
  sourceScreenId,
842
863
  remintCollidingNodeIds: true,
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-09-22
4
+ ---
5
+
6
+ Apps start with an app-shaped skeleton while session data loads immediately.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-09-22
4
+ ---
5
+
6
+ Blocks can be selected with one click inside screens
@@ -152,11 +152,16 @@ export function layerRow(page: Page, name: string): Locator {
152
152
  }
153
153
 
154
154
  export function node(page: Page, id: string): Locator {
155
- return page
156
- .locator("iframe[data-design-preview-iframe]")
157
- .first()
158
- .contentFrame()
159
- .locator(`[data-agent-native-node-id="${id}"]`);
155
+ return (
156
+ page
157
+ // A live board surface is mounted ahead of screen iframes during a
158
+ // cross-screen drag. Keep this helper bound to authored screen content;
159
+ // board documents intentionally omit data-screen-iframe-id.
160
+ .locator("iframe[data-design-preview-iframe][data-screen-iframe-id]")
161
+ .first()
162
+ .contentFrame()
163
+ .locator(`[data-agent-native-node-id="${id}"]`)
164
+ );
160
165
  }
161
166
 
162
167
  export async function openEditor(page: Page, designId: string): Promise<void> {