@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.
Files changed (81) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +5 -3
  3. package/corpus/templates/analytics/CHANGELOG.md +2 -0
  4. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +38 -167
  5. package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  6. package/corpus/templates/assets/CHANGELOG.md +6 -0
  7. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
  8. package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  9. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
  10. package/corpus/templates/brain/app/routes/sources.tsx +1 -1
  11. package/corpus/templates/brain/vitest.config.ts +7 -0
  12. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  13. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
  14. package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
  15. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  16. package/corpus/templates/clips/CHANGELOG.md +7 -20
  17. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
  18. package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
  19. package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
  20. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
  21. package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
  22. package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
  23. package/corpus/templates/community/qr/package.json +1 -1
  24. package/corpus/templates/content/CHANGELOG.md +7 -0
  25. package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
  26. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +770 -179
  27. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +75 -27
  28. package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
  29. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
  30. package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
  31. package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
  32. package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
  33. package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
  34. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
  35. package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
  36. package/corpus/templates/content/app/global.css +31 -0
  37. package/corpus/templates/content/app/i18n-data.ts +8 -0
  38. package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
  39. package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
  40. package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
  41. package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
  42. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
  43. package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
  44. package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
  45. package/corpus/templates/content/shared/suggestion-text.ts +20 -5
  46. package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
  47. package/corpus/templates/design/CHANGELOG.md +8 -0
  48. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
  49. package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
  50. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
  51. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  52. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  53. package/corpus/templates/design/app/pages/Index.tsx +18 -64
  54. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
  55. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
  56. package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
  57. package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
  58. package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
  59. package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
  60. package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
  61. package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
  62. package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
  63. package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
  64. package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
  65. package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
  66. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  67. package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
  68. package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  69. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
  70. package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
  71. package/corpus/templates/plan/CHANGELOG.md +6 -0
  72. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
  73. package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  74. package/corpus/templates/slides/CHANGELOG.md +1 -0
  75. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  76. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
  77. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  78. package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
  79. package/package.json +1 -1
  80. package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  81. 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
- runModeChange(
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) => focusOverviewScreen(screenId),
718
+ (screenId: string, options?: { fromPrototypeNavigation?: boolean }) =>
719
+ focusOverviewScreen(screenId, options),
711
720
  [focusOverviewScreen],
712
721
  );
713
722
 
@@ -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
- (screen: string) => {
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
- handleModeChange("interact", {
462
- targetFileId: target.id,
463
- keepInteractDevice: true,
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={(screen) => {
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
- if (mode === "interact") {
565
- handleModeChange("interact", {
566
- targetFileId: match.id,
567
- keepInteractDevice: true,
568
- });
569
- } else {
570
- handleSidebarScreenSelect(match.id);
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
  />
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-10-16
4
+ ---
5
+
6
+ Design pages use localized visibility badges and a shared ownership filter.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-10-20
4
+ ---
5
+
6
+ The Design / Full app toggle on the home page no longer hides behind the Connect AI card.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-10-22
4
+ ---
5
+
6
+ Prototype links can play code-authored screen transitions.