@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-22-rewind-uses-less-storage-by-default.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +1 -1
- package/corpus/templates/clips/desktop/src-tauri/src/config.rs +30 -3
- package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +1 -1
- package/corpus/templates/content/AGENTS.md +1 -1
- package/corpus/templates/content/actions/_document-search-ranking.ts +191 -0
- package/corpus/templates/content/actions/search-documents.ts +184 -49
- package/corpus/templates/content/changelog/2026-09-21-search-ranks-title-matches-first.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.knowledge.search.md +11 -8
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +10 -3
- package/corpus/templates/design/.generated/bridge/nav.generated.ts +1 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +250 -22
- package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +16 -0
- package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +32 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -2
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +22 -1
- package/corpus/templates/design/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
- package/corpus/templates/design/changelog/2026-09-22-blocks-can-be-selected-with-one-click-inside-screens.md +6 -0
- package/corpus/templates/design/e2e/drag-and-drop.shared.ts +10 -5
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +136 -123
- package/corpus/templates/mail/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
- package/corpus/templates/slides/changelog/2026-09-22-apps-start-with-an-app-shaped-skeleton-while-session-data-lo.md +6 -0
- package/dist/agent/engine/anthropic-engine.d.ts +1 -1
- package/dist/agent/model-config.d.ts +8 -8
- package/dist/agent/model-config.js +43 -20
- package/dist/client/AppShellSkeleton.d.ts +5 -0
- package/dist/client/AppShellSkeleton.js +69 -0
- package/dist/client/ClientOnly.d.ts +1 -1
- package/dist/client/ClientOnly.js +1 -1
- package/dist/client/LanguagePicker.d.ts +6 -0
- package/dist/client/LanguagePicker.js +128 -0
- package/dist/client/analytics.d.ts +3 -2
- package/dist/client/analytics.js +1 -1
- package/dist/client/app-providers.d.ts +3 -3
- package/dist/client/app-providers.js +53 -23
- package/dist/client/client-status-requests.d.ts +5 -0
- package/dist/client/client-status-requests.js +31 -18
- package/dist/client/i18n.d.ts +1 -6
- package/dist/client/i18n.js +3 -117
- package/dist/client/onboarding/first-run-startup-gate.js +14 -3
- package/dist/client/require-session.d.ts +1 -1
- package/dist/client/require-session.js +3 -3
- package/dist/client/session-replay-context.d.ts +16 -0
- package/dist/client/session-replay-context.js +93 -0
- package/dist/client/session-replay.d.ts +2 -26
- package/dist/client/session-replay.js +1 -64
- package/dist/client/settings/SettingsPanel.js +12 -8
- package/dist/deploy/build.js +19 -29
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/dist/shared/reasoning-effort.js +14 -20
- package/dist/shared/ssr-session-bootstrap.d.ts +3 -0
- package/dist/shared/ssr-session-bootstrap.js +44 -0
- package/dist/triggers/actions/manage-automation.d.ts +2 -2
- 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,
|
|
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.
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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) :
|
|
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
|
|
900
|
-
//
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
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
|
-
|
|
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)
|
|
3242
|
-
|
|
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)
|
|
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
|
-
|
|
10740
|
-
|
|
10741
|
-
|
|
10742
|
-
|
|
10743
|
-
|
|
10744
|
-
|
|
10745
|
-
|
|
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;
|
package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts
CHANGED
|
@@ -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 =
|
|
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,
|
|
@@ -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
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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> {
|