@agent-native/core-corpus 0.217.0 → 0.219.0
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/.agents/skills/hubspot/SKILL.md +5 -3
- package/corpus/templates/analytics/CHANGELOG.md +2 -0
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
- package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/assets/CHANGELOG.md +6 -0
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
- package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
- package/corpus/templates/brain/app/routes/sources.tsx +1 -1
- package/corpus/templates/brain/vitest.config.ts +7 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
- package/corpus/templates/clips/CHANGELOG.md +7 -20
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
- package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
- package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
- package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/CHANGELOG.md +7 -0
- package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
- package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
- package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
- package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
- package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
- package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
- package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
- package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
- package/corpus/templates/content/app/global.css +31 -0
- package/corpus/templates/content/app/i18n-data.ts +8 -0
- package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
- package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
- package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
- package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
- package/corpus/templates/content/shared/suggestion-text.ts +20 -5
- package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
- package/corpus/templates/design/CHANGELOG.md +8 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
- package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
- package/corpus/templates/design/app/i18n/en-US.ts +1 -1
- package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
- package/corpus/templates/design/app/pages/Index.tsx +18 -64
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
- package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
- package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
- package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
- package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
- package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
- package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
- package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
- package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
- package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
- package/corpus/templates/dispatch/CHANGELOG.md +4 -0
- package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
- package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
- package/corpus/templates/plan/CHANGELOG.md +6 -0
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
- package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +1 -0
- package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
- package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
- package/corpus/templates/slides/app/pages/Index.tsx +7 -2
- package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
- package/package.json +1 -1
- package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
- package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
getScreenFrameOriginCanvas,
|
|
46
46
|
shouldPopToOverviewOnZoomChange,
|
|
47
47
|
} from "../overview-camera";
|
|
48
|
+
import { cancelPrototypeNavigationTransition } from "../prototype-navigation-transition";
|
|
48
49
|
import { computeInteractZoomToFit } from "../responsive-interact";
|
|
49
50
|
import { hasSelectableCodeLayerParent } from "../selection-state";
|
|
50
51
|
import {
|
|
@@ -306,8 +307,11 @@ export function useEditorModes({
|
|
|
306
307
|
}, []);
|
|
307
308
|
|
|
308
309
|
const focusOverviewScreen = useCallback(
|
|
309
|
-
(screenId: string) => {
|
|
310
|
+
(screenId: string, options?: { fromPrototypeNavigation?: boolean }) => {
|
|
310
311
|
if (!files.some((file) => file.id === screenId)) return;
|
|
312
|
+
if (!options?.fromPrototypeNavigation) {
|
|
313
|
+
cancelPrototypeNavigationTransition();
|
|
314
|
+
}
|
|
311
315
|
const geometry = exportCanvasFrameGeometryById[screenId];
|
|
312
316
|
const plan = geometry
|
|
313
317
|
? getFocusedScreenNavigationPlan({
|
|
@@ -507,9 +511,12 @@ export function useEditorModes({
|
|
|
507
511
|
pendingLiveEditsAlreadyHandled?: boolean;
|
|
508
512
|
targetFileId?: string;
|
|
509
513
|
keepInteractDevice?: boolean;
|
|
514
|
+
fromPrototypeNavigation?: boolean;
|
|
510
515
|
},
|
|
511
|
-
) =>
|
|
512
|
-
|
|
516
|
+
) => {
|
|
517
|
+
const { fromPrototypeNavigation, ...modeOptions } = options ?? {};
|
|
518
|
+
if (!fromPrototypeNavigation) cancelPrototypeNavigationTransition();
|
|
519
|
+
return runModeChange(
|
|
513
520
|
{
|
|
514
521
|
activeFile,
|
|
515
522
|
annotateLab,
|
|
@@ -548,8 +555,9 @@ export function useEditorModes({
|
|
|
548
555
|
viewModeRef,
|
|
549
556
|
},
|
|
550
557
|
next,
|
|
551
|
-
options,
|
|
552
|
-
)
|
|
558
|
+
options ? modeOptions : undefined,
|
|
559
|
+
);
|
|
560
|
+
},
|
|
553
561
|
[
|
|
554
562
|
activeFile,
|
|
555
563
|
annotateLab,
|
|
@@ -707,7 +715,8 @@ export function useEditorModes({
|
|
|
707
715
|
]);
|
|
708
716
|
|
|
709
717
|
const handleSidebarScreenSelect = useCallback(
|
|
710
|
-
(screenId: string) =>
|
|
718
|
+
(screenId: string, options?: { fromPrototypeNavigation?: boolean }) =>
|
|
719
|
+
focusOverviewScreen(screenId, options),
|
|
711
720
|
[focusOverviewScreen],
|
|
712
721
|
);
|
|
713
722
|
|
package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { openCommandMenu } from "@agent-native/toolkit/app/shared";
|
|
2
2
|
import { isBoardFile } from "@shared/board-file";
|
|
3
|
+
import type { MotionPrototypeTransitionDecodeResult } from "@shared/motion-prototype-transition";
|
|
3
4
|
import { isRunningAppSourceType } from "@shared/source-mode";
|
|
4
5
|
import { useState, useEffect, useCallback } from "react";
|
|
5
6
|
import { toast } from "sonner";
|
|
@@ -43,6 +44,10 @@ import {
|
|
|
43
44
|
resolveOverviewScreenSourceType,
|
|
44
45
|
shouldUseRuntimeLayerProjection,
|
|
45
46
|
} from "../pending-edits";
|
|
47
|
+
import {
|
|
48
|
+
getPrototypeDestinationFrameId,
|
|
49
|
+
runPrototypeNavigationTransition,
|
|
50
|
+
} from "../prototype-navigation-transition";
|
|
46
51
|
import { findInteractLinkTarget } from "../screen-command-utils";
|
|
47
52
|
import {
|
|
48
53
|
getContentSignature,
|
|
@@ -451,17 +456,36 @@ export function useEditorScreenRendering({
|
|
|
451
456
|
// A link to another screen in the Interact preview switches the previewed
|
|
452
457
|
// screen, keeping Interact and its device, the same hop the screen picker makes.
|
|
453
458
|
const handleInteractLinkNavigate = useCallback(
|
|
454
|
-
(
|
|
459
|
+
(
|
|
460
|
+
screen: string,
|
|
461
|
+
_href: string,
|
|
462
|
+
transition: MotionPrototypeTransitionDecodeResult,
|
|
463
|
+
sourceFrame: HTMLIFrameElement,
|
|
464
|
+
) => {
|
|
455
465
|
const target = findInteractLinkTarget(
|
|
456
466
|
files,
|
|
457
467
|
screen,
|
|
458
468
|
activeFileIdRef.current,
|
|
459
469
|
);
|
|
460
470
|
if (!target) return;
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
471
|
+
runPrototypeNavigationTransition(
|
|
472
|
+
transition.status === "valid" ? transition.transition : undefined,
|
|
473
|
+
() => {
|
|
474
|
+
handleModeChange("interact", {
|
|
475
|
+
targetFileId: target.id,
|
|
476
|
+
keepInteractDevice: true,
|
|
477
|
+
fromPrototypeNavigation: true,
|
|
478
|
+
});
|
|
479
|
+
},
|
|
480
|
+
undefined,
|
|
481
|
+
{
|
|
482
|
+
sourceFrame,
|
|
483
|
+
destinationFrameId: getPrototypeDestinationFrameId(
|
|
484
|
+
target.id,
|
|
485
|
+
sourceFrame,
|
|
486
|
+
),
|
|
487
|
+
},
|
|
488
|
+
);
|
|
465
489
|
},
|
|
466
490
|
[files, handleModeChange],
|
|
467
491
|
);
|
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
MotionPrototypeDirection,
|
|
3
|
+
MotionPrototypeEase,
|
|
4
|
+
MotionPrototypeNavigationTransition,
|
|
5
|
+
} from "@shared/motion-prototype-transition";
|
|
6
|
+
import { validateMotionPrototypeNavigationTransition } from "@shared/motion-prototype-transition";
|
|
7
|
+
import { flushSync } from "react-dom";
|
|
8
|
+
|
|
9
|
+
type PrototypeTransitionDocument = Pick<
|
|
10
|
+
Document,
|
|
11
|
+
"querySelector" | "querySelectorAll"
|
|
12
|
+
>;
|
|
13
|
+
type PrototypeNavigationTransitionOptions = {
|
|
14
|
+
sourceFrame?: HTMLIFrameElement;
|
|
15
|
+
destinationFrameId?: string;
|
|
16
|
+
};
|
|
17
|
+
type TransitionRun = {
|
|
18
|
+
generation: number;
|
|
19
|
+
animation?: Animation;
|
|
20
|
+
cleanup?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type PrototypeNavigationTransitionResult =
|
|
24
|
+
| "instant"
|
|
25
|
+
| "animated"
|
|
26
|
+
| "browser-fallback"
|
|
27
|
+
| "unsupported-transition";
|
|
28
|
+
|
|
29
|
+
const PREVIEW_FRAME_SELECTOR = "iframe[data-an-prototype-preview]";
|
|
30
|
+
const DIRECTION_OFFSETS: Record<
|
|
31
|
+
MotionPrototypeDirection,
|
|
32
|
+
readonly [string, string, string, string]
|
|
33
|
+
> = {
|
|
34
|
+
left: ["-100%", "0%", "100%", "0%"],
|
|
35
|
+
right: ["100%", "0%", "-100%", "0%"],
|
|
36
|
+
top: ["0%", "-100%", "0%", "100%"],
|
|
37
|
+
bottom: ["0%", "100%", "0%", "-100%"],
|
|
38
|
+
};
|
|
39
|
+
const transitionRuns = new WeakMap<object, TransitionRun>();
|
|
40
|
+
|
|
41
|
+
export function getPrototypeDestinationFrameId(
|
|
42
|
+
screenId: string,
|
|
43
|
+
sourceFrame: HTMLIFrameElement,
|
|
44
|
+
): string {
|
|
45
|
+
const breakpoint = sourceFrame
|
|
46
|
+
.getAttribute("data-screen-iframe-id")
|
|
47
|
+
?.match(/::bp-\d+$/)?.[0];
|
|
48
|
+
return `${screenId}${breakpoint ?? ""}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function findPreviewFrame(
|
|
52
|
+
documentRef: PrototypeTransitionDocument,
|
|
53
|
+
frameId?: string,
|
|
54
|
+
): HTMLIFrameElement | null {
|
|
55
|
+
const frames = documentRef.querySelectorAll<HTMLIFrameElement>(
|
|
56
|
+
PREVIEW_FRAME_SELECTOR,
|
|
57
|
+
);
|
|
58
|
+
if (!frameId) return frames[0] ?? null;
|
|
59
|
+
return (
|
|
60
|
+
Array.from(frames).find(
|
|
61
|
+
(frame) => frame.getAttribute("data-screen-iframe-id") === frameId,
|
|
62
|
+
) ?? null
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function beginTransitionRun(documentRef: PrototypeTransitionDocument): number {
|
|
67
|
+
const previous = transitionRuns.get(documentRef);
|
|
68
|
+
if (previous) {
|
|
69
|
+
previous.generation += 1;
|
|
70
|
+
previous.animation?.cancel();
|
|
71
|
+
previous.animation = undefined;
|
|
72
|
+
previous.cleanup?.();
|
|
73
|
+
previous.cleanup = undefined;
|
|
74
|
+
return previous.generation;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
transitionRuns.set(documentRef, { generation: 1 });
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function cancelPrototypeNavigationTransition(
|
|
82
|
+
documentRef: PrototypeTransitionDocument | undefined = typeof document ===
|
|
83
|
+
"undefined"
|
|
84
|
+
? undefined
|
|
85
|
+
: document,
|
|
86
|
+
): void {
|
|
87
|
+
if (documentRef) beginTransitionRun(documentRef);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isCurrentTransitionRun(
|
|
91
|
+
documentRef: PrototypeTransitionDocument,
|
|
92
|
+
generation: number,
|
|
93
|
+
): boolean {
|
|
94
|
+
return transitionRuns.get(documentRef)?.generation === generation;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function setRunAnimation(
|
|
98
|
+
documentRef: PrototypeTransitionDocument,
|
|
99
|
+
generation: number,
|
|
100
|
+
animation: Animation | undefined,
|
|
101
|
+
): void {
|
|
102
|
+
const run = transitionRuns.get(documentRef);
|
|
103
|
+
if (run?.generation === generation) run.animation = animation;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function setRunCleanup(
|
|
107
|
+
documentRef: PrototypeTransitionDocument,
|
|
108
|
+
generation: number,
|
|
109
|
+
cleanup: (() => void) | undefined,
|
|
110
|
+
): void {
|
|
111
|
+
const run = transitionRuns.get(documentRef);
|
|
112
|
+
if (run?.generation === generation) run.cleanup = cleanup;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function easeToCss(ease: MotionPrototypeEase): string {
|
|
116
|
+
if (ease.kind === "preset") return ease.preset;
|
|
117
|
+
return `cubic-bezier(${ease.x1}, ${ease.y1}, ${ease.x2}, ${ease.y2})`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function transitionKeyframes(
|
|
121
|
+
transition: MotionPrototypeNavigationTransition,
|
|
122
|
+
phase: "enter" | "exit",
|
|
123
|
+
): Keyframe[] {
|
|
124
|
+
if (transition.transition === "dissolve") {
|
|
125
|
+
return phase === "enter"
|
|
126
|
+
? [{ opacity: 0 }, { opacity: 1 }]
|
|
127
|
+
: [{ opacity: 1 }, { opacity: 0 }];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const [enterX, enterY, exitX, exitY] = transition.direction
|
|
131
|
+
? DIRECTION_OFFSETS[transition.direction]
|
|
132
|
+
: ["0%", "0%", "0%", "0%"];
|
|
133
|
+
const offsetX =
|
|
134
|
+
phase === "exit" && transition.transition === "push" ? exitX : enterX;
|
|
135
|
+
const offsetY =
|
|
136
|
+
phase === "exit" && transition.transition === "push" ? exitY : enterY;
|
|
137
|
+
const transform = `translate(${offsetX}, ${offsetY})`;
|
|
138
|
+
|
|
139
|
+
return phase === "enter"
|
|
140
|
+
? [{ transform }, { transform: "translate(0%, 0%)" }]
|
|
141
|
+
: [
|
|
142
|
+
{ transform: "translate(0%, 0%)" },
|
|
143
|
+
{
|
|
144
|
+
transform:
|
|
145
|
+
transition.transition === "push"
|
|
146
|
+
? transform
|
|
147
|
+
: `translate(${enterX}, ${enterY})`,
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function startFrameAnimation(
|
|
153
|
+
frame: HTMLIFrameElement,
|
|
154
|
+
transition: MotionPrototypeNavigationTransition,
|
|
155
|
+
phase: "enter" | "exit",
|
|
156
|
+
durationMs: number,
|
|
157
|
+
): Animation {
|
|
158
|
+
return frame.animate(transitionKeyframes(transition, phase), {
|
|
159
|
+
duration: durationMs,
|
|
160
|
+
easing: easeToCss(transition.ease!),
|
|
161
|
+
fill: "both",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
type FrameDocumentLoadState = "loaded" | "pending" | "invalid-source";
|
|
166
|
+
|
|
167
|
+
function getFrameDocumentLoadState(
|
|
168
|
+
frame: HTMLIFrameElement,
|
|
169
|
+
previousDocument: Document | null,
|
|
170
|
+
sourceChanged: boolean,
|
|
171
|
+
loadEventObserved = false,
|
|
172
|
+
): FrameDocumentLoadState {
|
|
173
|
+
const isSrcdoc = frame.hasAttribute("srcdoc");
|
|
174
|
+
const src = isSrcdoc ? null : frame.getAttribute("src");
|
|
175
|
+
const baseURI = frame.ownerDocument.baseURI;
|
|
176
|
+
if (src !== null && !URL.canParse(src, baseURI)) return "invalid-source";
|
|
177
|
+
|
|
178
|
+
const current = frame.contentDocument;
|
|
179
|
+
if (current === null) {
|
|
180
|
+
return sourceChanged && !loadEventObserved ? "pending" : "loaded";
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
current.readyState !== "complete" ||
|
|
184
|
+
(sourceChanged && current === previousDocument)
|
|
185
|
+
) {
|
|
186
|
+
return "pending";
|
|
187
|
+
}
|
|
188
|
+
if (isSrcdoc) {
|
|
189
|
+
return current.URL === "about:srcdoc" ? "loaded" : "pending";
|
|
190
|
+
}
|
|
191
|
+
if (src === null) return "loaded";
|
|
192
|
+
const destinationUrl = new URL(src, baseURI);
|
|
193
|
+
if (current.URL === destinationUrl.href) return "loaded";
|
|
194
|
+
return loadEventObserved &&
|
|
195
|
+
new URL(current.URL).origin === destinationUrl.origin
|
|
196
|
+
? "loaded"
|
|
197
|
+
: "pending";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function animateEntry(
|
|
201
|
+
transition: MotionPrototypeNavigationTransition,
|
|
202
|
+
update: () => void,
|
|
203
|
+
documentRef: PrototypeTransitionDocument,
|
|
204
|
+
durationMs: number,
|
|
205
|
+
generation: number,
|
|
206
|
+
options: PrototypeNavigationTransitionOptions,
|
|
207
|
+
beforeStart?: () => void,
|
|
208
|
+
): PrototypeNavigationTransitionResult {
|
|
209
|
+
const previousFrame = findPreviewFrame(
|
|
210
|
+
documentRef,
|
|
211
|
+
options.destinationFrameId,
|
|
212
|
+
);
|
|
213
|
+
const previousSource = previousFrame
|
|
214
|
+
? [
|
|
215
|
+
previousFrame.getAttribute("src"),
|
|
216
|
+
previousFrame.getAttribute("srcdoc"),
|
|
217
|
+
].join("\u0000")
|
|
218
|
+
: null;
|
|
219
|
+
const previousDocument = previousFrame?.contentDocument ?? null;
|
|
220
|
+
flushSync(update);
|
|
221
|
+
const frame = findPreviewFrame(documentRef, options.destinationFrameId);
|
|
222
|
+
if (!frame || typeof frame.animate !== "function") {
|
|
223
|
+
return "browser-fallback";
|
|
224
|
+
}
|
|
225
|
+
const sourceChanged =
|
|
226
|
+
frame !== previousFrame ||
|
|
227
|
+
[frame.getAttribute("src"), frame.getAttribute("srcdoc")].join("\u0000") !==
|
|
228
|
+
previousSource;
|
|
229
|
+
|
|
230
|
+
const start = (): boolean => {
|
|
231
|
+
if (!isCurrentTransitionRun(documentRef, generation)) return false;
|
|
232
|
+
setRunCleanup(documentRef, generation, undefined);
|
|
233
|
+
beforeStart?.();
|
|
234
|
+
try {
|
|
235
|
+
const animation = startFrameAnimation(
|
|
236
|
+
frame,
|
|
237
|
+
transition,
|
|
238
|
+
"enter",
|
|
239
|
+
durationMs,
|
|
240
|
+
);
|
|
241
|
+
setRunAnimation(documentRef, generation, animation);
|
|
242
|
+
void animation.finished.then(
|
|
243
|
+
() => {
|
|
244
|
+
animation.cancel();
|
|
245
|
+
setRunAnimation(documentRef, generation, undefined);
|
|
246
|
+
},
|
|
247
|
+
() => {},
|
|
248
|
+
);
|
|
249
|
+
return true;
|
|
250
|
+
} catch {
|
|
251
|
+
setRunAnimation(documentRef, generation, undefined);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const loadState = getFrameDocumentLoadState(
|
|
257
|
+
frame,
|
|
258
|
+
previousDocument,
|
|
259
|
+
sourceChanged,
|
|
260
|
+
);
|
|
261
|
+
if (loadState === "invalid-source") return "browser-fallback";
|
|
262
|
+
if (loadState === "loaded") {
|
|
263
|
+
return start() ? "animated" : "browser-fallback";
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const onLoad = () => {
|
|
267
|
+
const loadState = getFrameDocumentLoadState(
|
|
268
|
+
frame,
|
|
269
|
+
previousDocument,
|
|
270
|
+
sourceChanged,
|
|
271
|
+
true,
|
|
272
|
+
);
|
|
273
|
+
if (loadState === "pending") return;
|
|
274
|
+
|
|
275
|
+
frame.removeEventListener("load", onLoad);
|
|
276
|
+
setRunCleanup(documentRef, generation, undefined);
|
|
277
|
+
if (loadState === "loaded") {
|
|
278
|
+
start();
|
|
279
|
+
} else {
|
|
280
|
+
beforeStart?.();
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
frame.addEventListener("load", onLoad);
|
|
284
|
+
setRunCleanup(documentRef, generation, () =>
|
|
285
|
+
frame.removeEventListener("load", onLoad),
|
|
286
|
+
);
|
|
287
|
+
return "animated";
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function animateExit(
|
|
291
|
+
transition: MotionPrototypeNavigationTransition,
|
|
292
|
+
update: () => void,
|
|
293
|
+
documentRef: PrototypeTransitionDocument,
|
|
294
|
+
durationMs: number,
|
|
295
|
+
continueWithEntry: boolean,
|
|
296
|
+
generation: number,
|
|
297
|
+
options: PrototypeNavigationTransitionOptions,
|
|
298
|
+
): PrototypeNavigationTransitionResult {
|
|
299
|
+
const frame = options.sourceFrame ?? findPreviewFrame(documentRef);
|
|
300
|
+
if (!frame || typeof frame.animate !== "function") {
|
|
301
|
+
flushSync(update);
|
|
302
|
+
return "browser-fallback";
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let animation: Animation;
|
|
306
|
+
try {
|
|
307
|
+
animation = startFrameAnimation(frame, transition, "exit", durationMs);
|
|
308
|
+
} catch {
|
|
309
|
+
flushSync(update);
|
|
310
|
+
return "browser-fallback";
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
setRunAnimation(documentRef, generation, animation);
|
|
314
|
+
void animation.finished.then(
|
|
315
|
+
() => {
|
|
316
|
+
if (!isCurrentTransitionRun(documentRef, generation)) return;
|
|
317
|
+
if (continueWithEntry) {
|
|
318
|
+
let released = false;
|
|
319
|
+
const releaseExit = () => {
|
|
320
|
+
if (released) return;
|
|
321
|
+
released = true;
|
|
322
|
+
animation.cancel();
|
|
323
|
+
if (transitionRuns.get(documentRef)?.animation === animation) {
|
|
324
|
+
setRunAnimation(documentRef, generation, undefined);
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const result = animateEntry(
|
|
328
|
+
transition,
|
|
329
|
+
update,
|
|
330
|
+
documentRef,
|
|
331
|
+
transition.durationMs! - durationMs,
|
|
332
|
+
generation,
|
|
333
|
+
options,
|
|
334
|
+
releaseExit,
|
|
335
|
+
);
|
|
336
|
+
if (result !== "animated") releaseExit();
|
|
337
|
+
} else {
|
|
338
|
+
flushSync(update);
|
|
339
|
+
animation.cancel();
|
|
340
|
+
setRunAnimation(documentRef, generation, undefined);
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
() => {
|
|
344
|
+
if (isCurrentTransitionRun(documentRef, generation)) {
|
|
345
|
+
setRunAnimation(documentRef, generation, undefined);
|
|
346
|
+
flushSync(update);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
);
|
|
350
|
+
return "animated";
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Animates the preview frame around a code-authored screen navigation. */
|
|
354
|
+
export function runPrototypeNavigationTransition(
|
|
355
|
+
transition: MotionPrototypeNavigationTransition | undefined,
|
|
356
|
+
update: () => void,
|
|
357
|
+
documentRef: PrototypeTransitionDocument | undefined = typeof document ===
|
|
358
|
+
"undefined"
|
|
359
|
+
? undefined
|
|
360
|
+
: document,
|
|
361
|
+
options: PrototypeNavigationTransitionOptions = {},
|
|
362
|
+
): PrototypeNavigationTransitionResult {
|
|
363
|
+
const generation = documentRef ? beginTransitionRun(documentRef) : 0;
|
|
364
|
+
if (!transition) {
|
|
365
|
+
update();
|
|
366
|
+
return "instant";
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const validation = validateMotionPrototypeNavigationTransition(transition);
|
|
370
|
+
if (!validation.valid) {
|
|
371
|
+
update();
|
|
372
|
+
return "unsupported-transition";
|
|
373
|
+
}
|
|
374
|
+
const config = validation.transition;
|
|
375
|
+
if (config.transition === "instant") {
|
|
376
|
+
update();
|
|
377
|
+
return "instant";
|
|
378
|
+
}
|
|
379
|
+
if (config.transition === "smart-animate") {
|
|
380
|
+
update();
|
|
381
|
+
return "unsupported-transition";
|
|
382
|
+
}
|
|
383
|
+
if (!documentRef) {
|
|
384
|
+
update();
|
|
385
|
+
return "browser-fallback";
|
|
386
|
+
}
|
|
387
|
+
if (
|
|
388
|
+
typeof window !== "undefined" &&
|
|
389
|
+
window.matchMedia?.("(prefers-reduced-motion: reduce)").matches
|
|
390
|
+
) {
|
|
391
|
+
update();
|
|
392
|
+
return "browser-fallback";
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const durationMs = config.durationMs!;
|
|
396
|
+
const staged =
|
|
397
|
+
config.transition === "dissolve" || config.transition === "push";
|
|
398
|
+
const exitsFirst =
|
|
399
|
+
staged ||
|
|
400
|
+
config.transition === "move-out" ||
|
|
401
|
+
config.transition === "slide-out";
|
|
402
|
+
if (exitsFirst) {
|
|
403
|
+
const exitDuration = staged ? durationMs / 2 : durationMs;
|
|
404
|
+
return animateExit(
|
|
405
|
+
config,
|
|
406
|
+
update,
|
|
407
|
+
documentRef,
|
|
408
|
+
exitDuration,
|
|
409
|
+
staged,
|
|
410
|
+
generation,
|
|
411
|
+
options,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return animateEntry(
|
|
416
|
+
config,
|
|
417
|
+
update,
|
|
418
|
+
documentRef,
|
|
419
|
+
durationMs,
|
|
420
|
+
generation,
|
|
421
|
+
options,
|
|
422
|
+
);
|
|
423
|
+
}
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
RecentEditHighlights,
|
|
4
4
|
LiveCursorOverlay,
|
|
5
5
|
} from "@agent-native/toolkit/collab-ui";
|
|
6
|
+
import type { MotionPrototypeTransitionDecodeResult } from "@shared/motion-prototype-transition";
|
|
6
7
|
|
|
7
8
|
import { DesignCanvas } from "@/components/design/DesignCanvas";
|
|
8
9
|
import { prettyScreenName } from "@/lib/screen-names";
|
|
@@ -28,6 +29,10 @@ import type { EditorScreenInspector } from "../domains/use-editor-screen-inspect
|
|
|
28
29
|
import type { EditorScreenRendering } from "../domains/use-editor-screen-rendering";
|
|
29
30
|
import type { EditorSelectionAndStyles } from "../domains/use-editor-selection-and-styles";
|
|
30
31
|
import type { EditorToolsAndVectors } from "../domains/use-editor-tools-and-vectors";
|
|
32
|
+
import {
|
|
33
|
+
getPrototypeDestinationFrameId,
|
|
34
|
+
runPrototypeNavigationTransition,
|
|
35
|
+
} from "../prototype-navigation-transition";
|
|
31
36
|
import type { DesignData } from "../types";
|
|
32
37
|
|
|
33
38
|
export function renderSingleScreenCanvas({
|
|
@@ -550,7 +555,12 @@ export function renderSingleScreenCanvas({
|
|
|
550
555
|
}
|
|
551
556
|
nodeRewriteCanvasTarget
|
|
552
557
|
onRepromptDraftConsumed={handleRepromptDraftConsumed}
|
|
553
|
-
onPrototypeNavigate={(
|
|
558
|
+
onPrototypeNavigate={(
|
|
559
|
+
screen,
|
|
560
|
+
_href,
|
|
561
|
+
transition: MotionPrototypeTransitionDecodeResult,
|
|
562
|
+
sourceFrame: HTMLIFrameElement,
|
|
563
|
+
) => {
|
|
554
564
|
if (!screen) return;
|
|
555
565
|
const norm = (s: string) =>
|
|
556
566
|
s
|
|
@@ -561,14 +571,30 @@ export function renderSingleScreenCanvas({
|
|
|
561
571
|
if (!target) return;
|
|
562
572
|
const match = files.find((f) => norm(f.filename) === target);
|
|
563
573
|
if (match) {
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
574
|
+
runPrototypeNavigationTransition(
|
|
575
|
+
transition.status === "valid" ? transition.transition : undefined,
|
|
576
|
+
() => {
|
|
577
|
+
if (mode === "interact") {
|
|
578
|
+
handleModeChange("interact", {
|
|
579
|
+
targetFileId: match.id,
|
|
580
|
+
keepInteractDevice: true,
|
|
581
|
+
fromPrototypeNavigation: true,
|
|
582
|
+
});
|
|
583
|
+
} else {
|
|
584
|
+
handleSidebarScreenSelect(match.id, {
|
|
585
|
+
fromPrototypeNavigation: true,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
},
|
|
589
|
+
undefined,
|
|
590
|
+
{
|
|
591
|
+
sourceFrame,
|
|
592
|
+
destinationFrameId: getPrototypeDestinationFrameId(
|
|
593
|
+
match.id,
|
|
594
|
+
sourceFrame,
|
|
595
|
+
),
|
|
596
|
+
},
|
|
597
|
+
);
|
|
572
598
|
}
|
|
573
599
|
}}
|
|
574
600
|
/>
|