@agent-native/core-corpus 0.218.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 (32) 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/community/qr/package.json +1 -1
  4. package/corpus/templates/content/CHANGELOG.md +5 -0
  5. package/corpus/templates/content/app/components/editor/CommentStack.tsx +301 -0
  6. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +651 -149
  7. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +19 -14
  8. package/corpus/templates/content/app/components/editor/block-field-comment-threads.ts +15 -0
  9. package/corpus/templates/content/app/components/editor/block-field-comments.tsx +5 -9
  10. package/corpus/templates/content/app/components/editor/comment-stacks.ts +143 -0
  11. package/corpus/templates/content/app/i18n-data.ts +8 -0
  12. package/corpus/templates/content/changelog/2026-10-22-blocks-with-several-comment-threads-fold-into-one-stack-in-t.md +6 -0
  13. package/corpus/templates/content/changelog/2026-10-22-opening-a-page-no-longer-saves-an-edit-again-after-another-o.md +6 -0
  14. package/corpus/templates/content/docs/product/capabilities/content.comment.page-owned.md +8 -1
  15. package/corpus/templates/design/.generated/bridge/interact-links.generated.ts +8 -8
  16. package/corpus/templates/design/CHANGELOG.md +5 -0
  17. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +18 -1
  18. package/corpus/templates/design/app/components/design/bridge/interact-links.bridge.ts +8 -8
  19. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -13
  20. package/corpus/templates/design/app/i18n/en-US.ts +1 -1
  21. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-modes.ts +15 -6
  22. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-screen-rendering.tsx +29 -5
  23. package/corpus/templates/design/app/pages/design-editor/prototype-navigation-transition.ts +423 -0
  24. package/corpus/templates/design/app/pages/design-editor/view/single-screen-canvas.tsx +35 -9
  25. package/corpus/templates/design/changelog/2026-10-20-the-design-full-app-toggle-on-the-home-page-no-longer-hides-.md +6 -0
  26. package/corpus/templates/design/changelog/2026-10-22-prototype-links-can-play-code-authored-screen-transitions.md +6 -0
  27. package/corpus/templates/design/shared/motion-authored-css-import.ts +938 -7
  28. package/corpus/templates/design/shared/motion-html-source.ts +1689 -135
  29. package/corpus/templates/design/shared/motion-prototype-transition.ts +329 -0
  30. package/corpus/templates/design/shared/motion-react-source.ts +2154 -126
  31. package/corpus/templates/design/shared/motion-text-runtime-v1.fixture.txt +221 -0
  32. package/package.json +1 -1
@@ -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: 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.