@agent-native/core-corpus 0.204.0-nightly-20261008011018 → 0.204.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 (68) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/actions/references/action-fields.md +1 -0
  3. package/corpus/templates/analytics/CHANGELOG.md +10 -0
  4. package/corpus/templates/analytics/app/i18n/en-US.ts +7 -0
  5. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +116 -40
  6. package/corpus/templates/analytics/app/pages/sessions/session-replay-screenshot.ts +107 -10
  7. package/corpus/templates/analytics/changelog/2026-10-07-session-replay-screenshots-can-be-copied-directly-into-desig.md +6 -0
  8. package/corpus/templates/assets/.agents/skills/actions/references/action-fields.md +1 -0
  9. package/corpus/templates/brain/.agents/skills/actions/references/action-fields.md +1 -0
  10. package/corpus/templates/calendar/.agents/skills/actions/references/action-fields.md +1 -0
  11. package/corpus/templates/chat/.agents/skills/actions/references/action-fields.md +1 -0
  12. package/corpus/templates/clips/.agents/skills/actions/references/action-fields.md +1 -0
  13. package/corpus/templates/clips/CHANGELOG.md +10 -0
  14. package/corpus/templates/content/.agents/skills/actions/references/action-fields.md +1 -0
  15. package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +13 -7
  16. package/corpus/templates/content/CHANGELOG.md +9 -0
  17. package/corpus/templates/content/actions/_database-setup-mutation.ts +3 -7
  18. package/corpus/templates/content/actions/_document-lifecycle.ts +250 -0
  19. package/corpus/templates/content/actions/delete-content-database.ts +6 -0
  20. package/corpus/templates/content/actions/delete-document.ts +169 -45
  21. package/corpus/templates/content/actions/list-content-trash.ts +2 -1
  22. package/corpus/templates/content/actions/resolve-content-landing.ts +14 -3
  23. package/corpus/templates/content/actions/restore-content-database.ts +20 -4
  24. package/corpus/templates/content/actions/restore-document.ts +121 -6
  25. package/corpus/templates/content/app/lib/content-landing.ts +67 -0
  26. package/corpus/templates/content/app/root.tsx +7 -1
  27. package/corpus/templates/content/app/routes/_app.home.tsx +65 -24
  28. package/corpus/templates/content/changelog/2026-10-06-connecting-notion-no-longer-leaves-its-used-authorization-co.md +6 -0
  29. package/corpus/templates/content/changelog/2026-10-06-external-agents-can-now-move-a-page-and-its-sub-pages-to-rec.md +6 -0
  30. package/corpus/templates/content/changelog/2026-10-06-opening-content-from-home-shows-your-last-page-sooner.md +6 -0
  31. package/corpus/templates/content/docs/product/capabilities/content.agent.action-parity.md +5 -2
  32. package/corpus/templates/content/e2e/helpers.ts +4 -3
  33. package/corpus/templates/content/parity/matrix.md +2 -2
  34. package/corpus/templates/content/parity/matrix.ts +2 -0
  35. package/corpus/templates/content/server/plugins/agent-chat.ts +1 -1
  36. package/corpus/templates/content/server/routes/api/notion/callback.get.ts +14 -2
  37. package/corpus/templates/content/shared/content-landing.ts +2 -0
  38. package/corpus/templates/crm/.agents/skills/actions/references/action-fields.md +1 -0
  39. package/corpus/templates/design/.agents/skills/actions/references/action-fields.md +1 -0
  40. package/corpus/templates/design/CHANGELOG.md +7 -0
  41. package/corpus/templates/design/app/components/design/KeyboardShortcutsDialog.tsx +480 -0
  42. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +1 -4
  43. package/corpus/templates/design/app/components/design/keyboard-shortcut-sections.ts +114 -0
  44. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +1 -1
  45. package/corpus/templates/design/app/i18n/en-US.ts +5 -6
  46. package/corpus/templates/design/app/lib/design-sync-invalidation.ts +86 -0
  47. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +2 -1
  48. package/corpus/templates/design/app/root.tsx +2 -0
  49. package/corpus/templates/design/changelog/2026-10-07-collaborator-edits-refresh-fewer-unrelated-panels.md +6 -0
  50. package/corpus/templates/design/changelog/2026-10-07-keyboard-shortcuts-now-open-in-a-dialog-with-search-and-a-ca.md +6 -0
  51. package/corpus/templates/dispatch/.agents/skills/actions/references/action-fields.md +1 -0
  52. package/corpus/templates/dispatch/CHANGELOG.md +1 -0
  53. package/corpus/templates/dispatch/changelog/2026-10-06-signing-in-to-another-app-through-dispatch-no-longer-leaves-.md +6 -0
  54. package/corpus/templates/dispatch/server/plugins/identity-sso.ts +9 -2
  55. package/corpus/templates/factory/.agents/skills/actions/references/action-fields.md +1 -0
  56. package/corpus/templates/forms/.agents/skills/actions/references/action-fields.md +1 -0
  57. package/corpus/templates/mail/.agents/skills/actions/references/action-fields.md +1 -0
  58. package/corpus/templates/plan/.agents/skills/actions/references/action-fields.md +1 -0
  59. package/corpus/templates/slides/.agents/skills/actions/references/action-fields.md +1 -0
  60. package/corpus/templates/slides/CHANGELOG.md +24 -0
  61. package/corpus/templates/slides/actions/edit-image.ts +1 -18
  62. package/corpus/templates/slides/actions/generate-image.ts +1 -18
  63. package/corpus/templates/slides/app/context/DeckContext.tsx +12 -2
  64. package/corpus/templates/slides/changelog/2026-10-07-slide-image-actions-preserve-argument-text-that-starts-with-.md +6 -0
  65. package/corpus/templates/slides/changelog/2026-10-07-two-people-typing-in-different-text-boxes-on-the-same-slide-.md +6 -0
  66. package/corpus/templates/tasks/.agents/skills/actions/references/action-fields.md +1 -0
  67. package/package.json +1 -1
  68. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +0 -434
package/corpus/README.md CHANGED
@@ -32,4 +32,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core-corpus/corpu
32
32
 
33
33
  ## Generated Counts
34
34
 
35
- - template files: 11374
35
+ - template files: 11386
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -3,6 +3,16 @@
3
3
  All notable user-facing changes to Agent-Native Analytics are documented here. Open it any
4
4
  time from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-07
7
+
8
+ ### Added
9
+
10
+ - Session replay screenshots can be copied directly into Design.
11
+
12
+ ### Fixed
13
+
14
+ - Session replay screenshot export captures readable video frames and safe poster images.
15
+
6
16
  ## 2026-10-06
7
17
 
8
18
  ### Added
@@ -1581,6 +1581,13 @@ export default {
1581
1581
  savingScreenshot: "Saving screenshot…",
1582
1582
  screenshotDownloaded: "Screenshot downloaded",
1583
1583
  screenshotSaveFailed: "Couldn't save screenshot",
1584
+ copyScreenshot: "Copy to Design",
1585
+ copyingScreenshot: "Copying screenshot…",
1586
+ screenshotCopiedForDesign: "Screenshot copied. Paste it into Design.",
1587
+ screenshotCopyFailed:
1588
+ "Couldn't copy the screenshot to the clipboard. Try Save screenshot, then upload the PNG in Design.",
1589
+ screenshotCopyUnsupportedAssets:
1590
+ "Screenshot not copied: this moment contains media or images that can't be captured securely. Try another moment in the replay.",
1584
1591
  screenshotUnsupportedAssets:
1585
1592
  "Screenshot not saved: some embedded media or images can't be captured securely.",
1586
1593
  timeline: "Event timeline",
@@ -96,9 +96,11 @@ import {
96
96
  import { extractReplayDiagnostics } from "./session-replay-devtools";
97
97
  import type { ReplayDevToolsDiagnostics } from "./session-replay-devtools";
98
98
  import {
99
+ captureReplayScreenshot,
99
100
  completeReplayScreenshotCapture,
100
- downloadReplayScreenshot,
101
+ downloadReplayScreenshotBlob,
101
102
  ReplayScreenshotAssetError,
103
+ writeReplayScreenshotToClipboard,
102
104
  } from "./session-replay-screenshot";
103
105
  import {
104
106
  type SessionIssueMatch,
@@ -582,6 +584,9 @@ function ReplayPlayer({
582
584
  const [currentTime, setCurrentTime] = useState(0);
583
585
  const [totalTime, setTotalTime] = useState(0);
584
586
  const [speed, setSpeed] = useState(DEFAULT_SPEED);
587
+ const [screenshotAction, setScreenshotAction] = useState<
588
+ "copy" | "save" | null
589
+ >(null);
585
590
  const [skipInactive, setSkipInactive] = useState(true);
586
591
  const [devToolsOpen, setDevToolsOpen] = useState(false);
587
592
  const [devToolsHeight, setDevToolsHeight] = useState(DEFAULT_DEVTOOLS_HEIGHT);
@@ -1032,18 +1037,23 @@ function ReplayPlayer({
1032
1037
  }
1033
1038
  }
1034
1039
 
1035
- async function saveScreenshot() {
1040
+ function runScreenshotAction(
1041
+ action: "copy" | "save",
1042
+ operation: (
1043
+ screenshot: Promise<Blob>,
1044
+ filename: string,
1045
+ onClipboardWriteFailure: () => void,
1046
+ ) => Promise<void>,
1047
+ onSuccess: () => void,
1048
+ onFailure: (error: unknown) => void,
1049
+ ) {
1036
1050
  const replayer = replayerRef.current;
1037
1051
  const iframe = replayer?.iframe as HTMLIFrameElement | undefined;
1038
1052
  const stage = stageAreaRef.current;
1039
1053
  const stageRoot = stageRootRef.current;
1040
- if (
1041
- !replayer ||
1042
- !iframe ||
1043
- !stage ||
1044
- !stageRoot ||
1045
- savingScreenshotRef.current
1046
- ) {
1054
+ if (savingScreenshotRef.current) return;
1055
+ if (!replayer || !iframe || !stage || !stageRoot) {
1056
+ onFailure(new Error("Replay screenshot is unavailable"));
1047
1057
  return;
1048
1058
  }
1049
1059
 
@@ -1052,56 +1062,108 @@ function ReplayPlayer({
1052
1062
  replayer.getCurrentTime?.() ?? currentTimeRef.current,
1053
1063
  );
1054
1064
  const capture = new AbortController();
1065
+ let clipboardWriteFailed = false;
1066
+ const onClipboardWriteFailure = () => {
1067
+ if (screenshotCaptureRef.current !== capture) return;
1068
+ clipboardWriteFailed = true;
1069
+ capture.abort();
1070
+ };
1055
1071
  screenshotCaptureRef.current = capture;
1056
1072
  savingScreenshotRef.current = true;
1057
1073
  setSavingScreenshot(true);
1074
+ setScreenshotAction(action);
1058
1075
 
1076
+ const filename = `session-replay-${Math.floor(captureAt / 1000)
1077
+ .toString()
1078
+ .padStart(4, "0")}.png`;
1079
+ let actionPromise: Promise<void>;
1059
1080
  try {
1060
1081
  replayer.pause(captureAt);
1061
1082
  setPlaying(false);
1062
1083
  updateTime(captureAt);
1063
- await downloadReplayScreenshot(
1084
+ const screenshot = captureReplayScreenshot(
1064
1085
  stage,
1065
1086
  stageRoot,
1066
1087
  iframe,
1067
- `session-replay-${Math.floor(captureAt / 1000)
1068
- .toString()
1069
- .padStart(4, "0")}.png`,
1070
1088
  capture.signal,
1071
1089
  );
1072
- toast.success(t("sessions.screenshotDownloaded"));
1090
+ // Clipboard writes need this click's activation, so start the operation before awaiting capture.
1091
+ actionPromise = operation(screenshot, filename, onClipboardWriteFailure);
1073
1092
  } catch (error) {
1074
- if (!capture.signal.aborted) {
1093
+ actionPromise = Promise.reject(error);
1094
+ }
1095
+
1096
+ void actionPromise
1097
+ .then(onSuccess, (error: unknown) => {
1098
+ if (
1099
+ !capture.signal.aborted ||
1100
+ (clipboardWriteFailed && screenshotCaptureRef.current === capture)
1101
+ ) {
1102
+ capture.abort();
1103
+ onFailure(error);
1104
+ }
1105
+ })
1106
+ .finally(() => {
1107
+ const captureStillCurrent = completeReplayScreenshotCapture(
1108
+ screenshotCaptureRef,
1109
+ capture,
1110
+ () => {
1111
+ savingScreenshotRef.current = false;
1112
+ setSavingScreenshot(false);
1113
+ },
1114
+ );
1115
+ if (captureStillCurrent) setScreenshotAction(null);
1116
+ if (
1117
+ captureStillCurrent &&
1118
+ wasPlaying &&
1119
+ replayerRef.current === replayer
1120
+ ) {
1121
+ try {
1122
+ replayer.play(captureAt);
1123
+ setPlaying(true);
1124
+ } catch {
1125
+ setPlaying(false);
1126
+ }
1127
+ }
1128
+ });
1129
+ }
1130
+
1131
+ function saveScreenshot() {
1132
+ runScreenshotAction(
1133
+ "save",
1134
+ (screenshot, filename) =>
1135
+ screenshot.then((blob) => downloadReplayScreenshotBlob(blob, filename)),
1136
+ () => toast.success(t("sessions.screenshotDownloaded")),
1137
+ (error) =>
1075
1138
  toast.error(
1076
1139
  t(
1077
1140
  error instanceof ReplayScreenshotAssetError
1078
1141
  ? "sessions.screenshotUnsupportedAssets"
1079
1142
  : "sessions.screenshotSaveFailed",
1080
1143
  ),
1081
- );
1082
- }
1083
- } finally {
1084
- const captureStillCurrent = completeReplayScreenshotCapture(
1085
- screenshotCaptureRef,
1086
- capture,
1087
- () => {
1088
- savingScreenshotRef.current = false;
1089
- setSavingScreenshot(false);
1090
- },
1091
- );
1092
- if (
1093
- captureStillCurrent &&
1094
- wasPlaying &&
1095
- replayerRef.current === replayer
1096
- ) {
1097
- try {
1098
- replayer.play(captureAt);
1099
- setPlaying(true);
1100
- } catch {
1101
- setPlaying(false);
1102
- }
1103
- }
1104
- }
1144
+ ),
1145
+ );
1146
+ }
1147
+
1148
+ function copyScreenshotToDesign() {
1149
+ runScreenshotAction(
1150
+ "copy",
1151
+ (screenshot, _filename, onClipboardWriteFailure) =>
1152
+ writeReplayScreenshotToClipboard(
1153
+ screenshot,
1154
+ undefined,
1155
+ onClipboardWriteFailure,
1156
+ ),
1157
+ () => toast.success(t("sessions.screenshotCopiedForDesign")),
1158
+ (error) =>
1159
+ toast.error(
1160
+ t(
1161
+ error instanceof ReplayScreenshotAssetError
1162
+ ? "sessions.screenshotCopyUnsupportedAssets"
1163
+ : "sessions.screenshotCopyFailed",
1164
+ ),
1165
+ ),
1166
+ );
1105
1167
  }
1106
1168
 
1107
1169
  const disabled = status !== "ready" || savingScreenshot;
@@ -1224,11 +1286,25 @@ function ReplayPlayer({
1224
1286
  >
1225
1287
  <IconDownload className="me-1.5 h-4 w-4" />
1226
1288
  {t(
1227
- savingScreenshot
1289
+ savingScreenshot && screenshotAction === "save"
1228
1290
  ? "sessions.savingScreenshot"
1229
1291
  : "sessions.saveScreenshot",
1230
1292
  )}
1231
1293
  </Button>
1294
+ <Button
1295
+ type="button"
1296
+ variant="outline"
1297
+ size="sm"
1298
+ disabled={disabled}
1299
+ onClick={copyScreenshotToDesign}
1300
+ >
1301
+ <IconCopy className="me-1.5 h-4 w-4" />
1302
+ {t(
1303
+ savingScreenshot && screenshotAction === "copy"
1304
+ ? "sessions.copyingScreenshot"
1305
+ : "sessions.copyScreenshot",
1306
+ )}
1307
+ </Button>
1232
1308
 
1233
1309
  <span className="w-12 text-center font-mono text-xs text-muted-foreground">
1234
1310
  {formatClock(currentTime)}
@@ -15,6 +15,7 @@ const MAX_INLINE_ASSET_BYTES = 32_000_000;
15
15
  const MAX_IMAGE_RESPONSE_BYTES = 12_000_000;
16
16
  const MAX_SCREENSHOT_DIMENSION = 8_192;
17
17
  const MAX_SCREENSHOT_PIXELS = 16_000_000;
18
+ const MAX_CLIPBOARD_PNG_BYTES = 32_000_000;
18
19
  const MAX_REPLAY_IFRAME_DEPTH = 8;
19
20
  const VIDEO_READY_STATE_HAVE_CURRENT_DATA = 2;
20
21
  const REPLAY_SCREENSHOT_MARKER = "data-replay-screenshot-map";
@@ -1179,13 +1180,87 @@ function waitForReplayPaint(signal?: AbortSignal): Promise<void> {
1179
1180
  });
1180
1181
  }
1181
1182
 
1182
- export async function downloadReplayScreenshot(
1183
+ export class ReplayScreenshotClipboardError extends Error {
1184
+ constructor() {
1185
+ super("Replay screenshot could not be copied to the clipboard");
1186
+ this.name = "ReplayScreenshotClipboardError";
1187
+ }
1188
+ }
1189
+
1190
+ export function writeReplayScreenshotToClipboard(
1191
+ screenshot: Blob | Promise<Blob>,
1192
+ clipboard: Pick<Clipboard, "write"> | undefined = globalThis.navigator
1193
+ ?.clipboard,
1194
+ onClipboardWriteFailure?: () => void,
1195
+ ): Promise<void> {
1196
+ const ClipboardItemConstructor = globalThis.ClipboardItem;
1197
+ const boundedPng = Promise.resolve(screenshot).then((blob) => {
1198
+ if (
1199
+ blob.type !== "image/png" ||
1200
+ blob.size === 0 ||
1201
+ blob.size > MAX_CLIPBOARD_PNG_BYTES
1202
+ ) {
1203
+ throw new ReplayScreenshotClipboardError();
1204
+ }
1205
+ return blob;
1206
+ });
1207
+ const pngResult = boundedPng.then(
1208
+ () => ({ kind: "png-ready" as const }),
1209
+ (error: unknown) => ({ error, kind: "png-failed" as const }),
1210
+ );
1211
+
1212
+ let clipboardWrite: Promise<void>;
1213
+ try {
1214
+ if (!clipboard?.write || !ClipboardItemConstructor) {
1215
+ throw new ReplayScreenshotClipboardError();
1216
+ }
1217
+
1218
+ // Start the clipboard write with the click; the promised PNG can render afterward.
1219
+ const item = new ClipboardItemConstructor({ "image/png": boundedPng });
1220
+ clipboardWrite = Promise.resolve(clipboard.write([item]));
1221
+ } catch (error) {
1222
+ clipboardWrite = Promise.reject(error);
1223
+ }
1224
+
1225
+ const writeResult = clipboardWrite.then(
1226
+ () => ({ kind: "write-succeeded" as const }),
1227
+ () => ({ kind: "write-failed" as const }),
1228
+ );
1229
+
1230
+ return Promise.race([pngResult, writeResult]).then(async (firstResult) => {
1231
+ if (firstResult.kind === "write-failed") {
1232
+ const error = new ReplayScreenshotClipboardError();
1233
+ onClipboardWriteFailure?.();
1234
+ throw error;
1235
+ }
1236
+
1237
+ const finalPngResult =
1238
+ firstResult.kind === "png-ready" || firstResult.kind === "png-failed"
1239
+ ? firstResult
1240
+ : await pngResult;
1241
+ if (finalPngResult.kind === "png-failed") {
1242
+ if (finalPngResult.error instanceof ReplayScreenshotAssetError) {
1243
+ throw finalPngResult.error;
1244
+ }
1245
+ throw new ReplayScreenshotClipboardError();
1246
+ }
1247
+
1248
+ const finalWriteResult =
1249
+ firstResult.kind === "write-succeeded" ? firstResult : await writeResult;
1250
+ if (finalWriteResult.kind === "write-failed") {
1251
+ const error = new ReplayScreenshotClipboardError();
1252
+ onClipboardWriteFailure?.();
1253
+ throw error;
1254
+ }
1255
+ });
1256
+ }
1257
+
1258
+ export async function captureReplayScreenshot(
1183
1259
  stage: HTMLElement,
1184
1260
  stageRoot: HTMLElement,
1185
1261
  iframe: HTMLIFrameElement,
1186
- filename: string,
1187
1262
  signal?: AbortSignal,
1188
- ): Promise<void> {
1263
+ ): Promise<Blob> {
1189
1264
  const replayWindow = iframe.contentWindow;
1190
1265
  const replayDocument = iframe.contentDocument;
1191
1266
  if (
@@ -1293,13 +1368,7 @@ export async function downloadReplayScreenshot(
1293
1368
  assertScreenshotDimensions(canvas.width, canvas.height);
1294
1369
  const blob = await canvasToBlob(canvas);
1295
1370
  assertCaptureAvailable();
1296
-
1297
- const downloadUrl = URL.createObjectURL(blob);
1298
- const link = document.createElement("a");
1299
- link.download = filename;
1300
- link.href = downloadUrl;
1301
- link.click();
1302
- window.setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000);
1371
+ return blob;
1303
1372
  } finally {
1304
1373
  if (previousStageFrameMarker === null) {
1305
1374
  iframe.removeAttribute(REPLAY_SCREENSHOT_MARKER);
@@ -1309,6 +1378,34 @@ export async function downloadReplayScreenshot(
1309
1378
  }
1310
1379
  }
1311
1380
 
1381
+ export function downloadReplayScreenshotBlob(
1382
+ blob: Blob,
1383
+ filename: string,
1384
+ ): void {
1385
+ const downloadUrl = URL.createObjectURL(blob);
1386
+ const link = document.createElement("a");
1387
+ link.download = filename;
1388
+ link.href = downloadUrl;
1389
+ link.click();
1390
+ window.setTimeout(() => URL.revokeObjectURL(downloadUrl), 1000);
1391
+ }
1392
+
1393
+ export async function downloadReplayScreenshot(
1394
+ stage: HTMLElement,
1395
+ stageRoot: HTMLElement,
1396
+ iframe: HTMLIFrameElement,
1397
+ filename: string,
1398
+ signal?: AbortSignal,
1399
+ ): Promise<void> {
1400
+ const screenshot = await captureReplayScreenshot(
1401
+ stage,
1402
+ stageRoot,
1403
+ iframe,
1404
+ signal,
1405
+ );
1406
+ downloadReplayScreenshotBlob(screenshot, filename);
1407
+ }
1408
+
1312
1409
  function assertScreenshotDimensions(width: number, height: number): void {
1313
1410
  if (
1314
1411
  !Number.isSafeInteger(width) ||
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-10-07
4
+ ---
5
+
6
+ Session replay screenshots can be copied directly into Design.
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -3,6 +3,16 @@
3
3
  All notable user-facing changes to Clips are documented here. Open it any time
4
4
  from the command menu (Cmd+K → "What's new") or from Settings.
5
5
 
6
+ ## 2026-10-07
7
+
8
+ ### Improved
9
+
10
+ - Builder.io setup choices now say “Use Builder.io”.
11
+
12
+ ### Fixed
13
+
14
+ - Clips sizes Settings and recording views correctly while a recording is active.
15
+
6
16
  ## 2026-10-06
7
17
 
8
18
  ### Fixed
@@ -16,6 +16,7 @@ Rules:
16
16
  - To override the inference on an unusual action (e.g. a `POST` that only reads), pass `readOnly: true` on the action definition.
17
17
  - To let a mutating action run concurrently with other same-turn tool calls, pass `parallelSafe: true`. Only do this when the action is internally concurrency-safe and order-independent (for example, it uses an app-level lock or idempotent upsert semantics). Mutating actions remain serialized by default.
18
18
  - If a successful call hands control to the user — it renders a question, an intake form, or anything else the turn must wait on — pass `endsTurn: true` and make it the first or only call in the turn. It stops subsequent model steps and later calls, but cannot undo calls emitted before it. Without it a completion guard sees a turn that legitimately paused as one that failed to finish. `endsTurn: true` also hides the action from MCP, WebMCP, and A2A callers by default (`isActionExposedToExternalAgents` in core `action.ts`) — an external host has no in-app chat to receive the answer on. Pass `mcpTool: true` alongside it only when an external caller can complete the same operation without that answer.
19
+ - MCP hosts read tool annotations to decide how carefully to treat a call. Every MCP catalog advertises declared `mcpAnnotations` (`readOnlyHint`, `destructiveHint`, `openWorldHint`, optional `idempotentHint`); otherwise `/mcp` derives only `readOnlyHint` and an approval-based `destructiveHint`. Declare them on any external write that removes, overwrites, or replaces user content — recoverable Trash included — and mark idempotency-key-guarded writes `idempotentHint: true`.
19
20
 
20
21
  Agents do NOT need to call `refresh-screen` after a normal action — it's already handled. `refresh-screen` is only needed when the agent mutates data via a path the framework can't see (e.g. writing to an external system the app mirrors) or when the agent wants to pass a `scope` hint for narrower invalidation.
21
22
 
@@ -169,17 +169,23 @@ pnpm action suggest-document-edit --id abc123 \
169
169
 
170
170
  ### delete-document
171
171
 
172
- Move a document and all its children to Trash. IDs, bodies, hierarchy, and
173
- collection membership remain intact so the subtree can be restored.
172
+ Move a page and all its sub-pages to Trash. IDs, bodies, hierarchy, and
173
+ collection membership remain intact so the subtree can be restored. Agents pass
174
+ the page's exact `updatedAt` from `<current-screen>` or a fresh read, plus an
175
+ idempotency key. A conflict means the page changed: read it again before
176
+ deciding to trash it. Collection pages use `delete-content-database` instead.
174
177
 
175
178
  ```bash
176
- pnpm action delete-document --id abc123
179
+ pnpm action delete-document --id abc123 \
180
+ --expectedUpdatedAt '<updatedAt>' --idempotencyKey '<uuid>'
177
181
  ```
178
182
 
179
- Restore the root subtree, or permanently delete it only after it is in Trash:
183
+ Restore the receipt's `trashRootId` with its `trashedAt` (also listed by
184
+ `list-content-trash`), or permanently delete it only after it is in Trash:
180
185
 
181
186
  ```bash
182
- pnpm action restore-document --id abc123
187
+ pnpm action restore-document --id abc123 \
188
+ --expectedTrashedAt '<trashedAt>' --idempotencyKey '<uuid>'
183
189
  pnpm action plan-content-trash-purge --mode selection --documentIds '["abc123"]'
184
190
  pnpm action permanently-delete-document --id abc123 --planId '<reviewed plan ID>' --scopeToken '<opaque plan token>'
185
191
  ```
@@ -335,7 +341,7 @@ failures stop the run.
335
341
  | "What am I looking at?" | Answer from `<current-screen>` (call `view-screen` only if truncated) |
336
342
  | "Create a page about X" | `create-document`, then `navigate --documentId <returned id>` and verify with `view-screen` |
337
343
  | "Fix a typo / small edit" | ID from `<current-screen>`, `edit-document --id ... --find "old" --replace "new"` |
338
- | "Delete this page" | ID from `<current-screen>`, `delete-document --id ...` |
344
+ | "Delete this page" | ID and `updatedAt` from `<current-screen>`, `delete-document --id ... --expectedUpdatedAt ... --idempotencyKey ...` |
339
345
 
340
346
  ## Common Tasks
341
347
 
@@ -346,7 +352,7 @@ failures stop the run.
346
352
  | "Find my meeting notes" | `search-documents --query "meeting notes"` |
347
353
  | "Fix a typo / edit a line" | `view-screen` to get ID, then `edit-document --id ... --find "old" --replace "new"` |
348
354
  | "Rewrite this document" | `view-screen` to get ID, then `update-document --id ... --content ...` |
349
- | "Delete this page" | `view-screen` to get ID, then `delete-document --id ...` |
355
+ | "Delete this page" | `view-screen` for ID and `updatedAt`, then `delete-document` with both and an idempotency key |
350
356
  | "Add a sub-page" | `create-document --title "Sub" --parentId <parentId>` |
351
357
  | "Show me the document tree" | `list-documents` |
352
358
 
@@ -7,6 +7,10 @@ time from the command menu (Cmd+K → "What's new").
7
7
 
8
8
  ### Fixed
9
9
 
10
+ - ChatGPT widgets identify a stalled Content editor instead of keeping its body skeleton onscreen.
11
+ - ChatGPT widgets open document content without a cookie session
12
+ - Content documents now open in ChatGPT widgets with their full body
13
+ - Text two people type at the same spot now reaches the saved page for both of them, so readers and agents see it
10
14
  - ChatGPT widget previews show saved document bodies without a Content session.
11
15
  - Documents opened in ChatGPT show their saved content right away.
12
16
  - ChatGPT widgets show saved document bodies in read-only mode without a Content session.
@@ -14,12 +18,17 @@ time from the command menu (Cmd+K → "What's new").
14
18
 
15
19
  ## 2026-10-06
16
20
 
21
+ ### Added
22
+
23
+ - External agents can now move a page and its sub-pages to recoverable Trash, list Trash, and restore it through Content's MCP tools.
24
+
17
25
  ### Improved
18
26
 
19
27
  - ChatGPT can open Content document and database editors in the existing workspace, and document links use the right label.
20
28
 
21
29
  ### Fixed
22
30
 
31
+ - Connecting Notion no longer leaves its used authorization code in the address bar.
23
32
  - Documents and databases opened from ChatGPT load in the Content editor without a separate sign-in.
24
33
  - ChatGPT Content widgets load database views correctly.
25
34
  - Reopening a ChatGPT Content widget restores the editor for its saved document or collection.
@@ -148,8 +148,7 @@ export async function claimSetupIntent(
148
148
  payload: unknown,
149
149
  ): Promise<Claim> {
150
150
  const email = getRequestUserEmail()?.trim().toLowerCase();
151
- if (!email)
152
- setupError("UNAUTHORIZED", "Sign in to change a Content database.", 401);
151
+ if (!email) setupError("UNAUTHORIZED", "Sign in to change Content.", 401);
153
152
  const payloadDigest = digest(payload);
154
153
  const table = schema.contentDatabaseSetupReceipts;
155
154
  const inserted = await tx
@@ -177,14 +176,11 @@ export async function claimSetupIntent(
177
176
  )
178
177
  .returning();
179
178
  if (!claim)
180
- setupError(
181
- "RECEIPT_MISMATCH",
182
- "The database operation claim could not be read.",
183
- );
179
+ setupError("RECEIPT_MISMATCH", "The operation claim could not be read.");
184
180
  if (claim.payloadDigest !== payloadDigest)
185
181
  setupError(
186
182
  "IDEMPOTENCY_KEY_REUSED",
187
- "This key was already used for a different database operation. Use the original payload to retry it.",
183
+ "This key was already used for a different Content operation. Use the original payload to retry it.",
188
184
  );
189
185
  if (inserted.length === 0 && claim.resultJson === null)
190
186
  setupError(