@agent-native/core-corpus 0.216.0 → 0.217.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 (149) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
  3. package/corpus/templates/analytics/AGENTS.md +1 -1
  4. package/corpus/templates/analytics/CHANGELOG.md +1 -0
  5. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
  6. package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
  7. package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
  8. package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
  9. package/corpus/templates/analytics/actions/view-screen.ts +29 -3
  10. package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
  11. package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
  12. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
  13. package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
  14. package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
  15. package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
  16. package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
  17. package/corpus/templates/analytics/server/db/schema.ts +67 -0
  18. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
  19. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
  20. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
  21. package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
  22. package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
  23. package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
  24. package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
  25. package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
  26. package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
  27. package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
  28. package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
  29. package/corpus/templates/analytics/server/plugins/db.ts +46 -0
  30. package/corpus/templates/analytics/shared/output-use.ts +205 -0
  31. package/corpus/templates/analytics/shared/session-events.ts +6 -0
  32. package/corpus/templates/calendar/CHANGELOG.md +6 -0
  33. package/corpus/templates/calendar/actions/create-event.ts +2 -4
  34. package/corpus/templates/calendar/actions/update-event.ts +35 -6
  35. package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
  36. package/corpus/templates/community/qr/package.json +1 -1
  37. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
  38. package/corpus/templates/content/CHANGELOG.md +8 -0
  39. package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
  40. package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
  41. package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
  42. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
  43. package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
  44. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
  45. package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
  46. package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
  47. package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
  48. package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
  49. package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
  50. package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
  51. package/corpus/templates/content/actions/_database-utils.ts +93 -7
  52. package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
  53. package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
  54. package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
  55. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
  56. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
  57. package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
  58. package/corpus/templates/content/actions/delete-document.ts +41 -3
  59. package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
  60. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
  61. package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
  62. package/corpus/templates/content/actions/get-document.ts +2 -0
  63. package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
  64. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
  65. package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
  66. package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
  67. package/corpus/templates/content/actions/update-document.ts +139 -20
  68. package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
  70. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
  71. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
  72. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
  73. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
  75. package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
  76. package/corpus/templates/content/app/i18n-data.ts +1 -0
  77. package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
  78. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
  79. package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
  80. package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
  81. package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
  82. package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
  83. package/corpus/templates/content/parity/matrix.md +42 -42
  84. package/corpus/templates/content/parity/matrix.ts +3 -1
  85. package/corpus/templates/content/server/db/schema.ts +20 -0
  86. package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
  87. package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
  88. package/corpus/templates/content/server/live-copy/binding.ts +31 -0
  89. package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
  90. package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
  91. package/corpus/templates/content/server/live-copy/holds.ts +59 -0
  92. package/corpus/templates/content/server/live-copy/persist.ts +656 -0
  93. package/corpus/templates/content/server/live-copy/switch.ts +136 -0
  94. package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
  95. package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
  96. package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
  97. package/corpus/templates/content/server/live-copy/write.ts +85 -0
  98. package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
  99. package/corpus/templates/content/server/plugins/collab.ts +3 -0
  100. package/corpus/templates/content/server/plugins/db.ts +28 -0
  101. package/corpus/templates/content/shared/api.ts +30 -1
  102. package/corpus/templates/content/shared/database-item-layout.ts +54 -22
  103. package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
  104. package/corpus/templates/content/shared/og-description.ts +258 -36
  105. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
  106. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
  107. package/corpus/templates/design/CHANGELOG.md +2 -0
  108. package/corpus/templates/design/actions/get-getting-started.ts +53 -0
  109. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
  110. package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
  111. package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
  112. package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
  113. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
  114. package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
  115. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
  116. package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
  117. package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
  118. package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
  119. package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
  120. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
  121. package/corpus/templates/design/app/global.css +6 -0
  122. package/corpus/templates/design/app/i18n/en-US.ts +17 -0
  123. package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
  124. package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
  125. package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
  126. package/corpus/templates/design/app/pages/Index.tsx +10 -3
  127. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
  128. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
  129. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
  130. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
  131. package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
  132. package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
  133. package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
  134. package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
  135. package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
  136. package/corpus/templates/design/server/lib/getting-started.ts +267 -0
  137. package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
  138. package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
  139. package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
  140. package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
  141. package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
  142. package/corpus/templates/design/shared/design-template-presets.ts +8 -0
  143. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  144. package/corpus/templates/slides/CHANGELOG.md +6 -0
  145. package/corpus/templates/slides/actions/delete-deck.ts +7 -0
  146. package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
  147. package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
  148. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
  149. package/package.json +1 -1
@@ -30,6 +30,7 @@ import {
30
30
  createNativeSceneProvider,
31
31
  cloneNativeTextFlow,
32
32
  NativeSourceError,
33
+ NativeSourceCapabilityError,
33
34
  nativeTargetOverflowInsets,
34
35
  nativeTextFlowUsesDocumentFont,
35
36
  paintNativeTextFlow,
@@ -50,6 +51,12 @@ import {
50
51
  } from "./native-worker-protocol";
51
52
  import { NativeWorkerSourceSnapshot } from "./native-worker-source-snapshot";
52
53
 
54
+ export class NativeWorkerSceneSourceError extends NativeSourceError {
55
+ constructor(readonly captureFailure: NativeSourceCapabilityError) {
56
+ super("worker-scene-source-unsupported", captureFailure.message);
57
+ }
58
+ }
59
+
53
60
  export type NativeWorkerSceneEntry = {
54
61
  instance: EffectInstance;
55
62
  definition: EffectDefinition;
@@ -147,6 +154,7 @@ type Publication = {
147
154
  targetEpochs: Map<string, { source: number; authored: number }>;
148
155
  rootRecords: readonly NativeSourceRecord[];
149
156
  payloads: Map<string, { payload: string; definitionHash: string }>;
157
+ authoredPayloads: Map<string, { payload: string; definitionHash: string }>;
150
158
  clockIdentity?: { origin: number | null; sourcePlaying: boolean };
151
159
  };
152
160
 
@@ -216,13 +224,27 @@ export function nativeWorkerOutputSceneRecords(
216
224
  return nativeWorkerOutputRecords(records, sizes);
217
225
  }
218
226
 
227
+ function nativeWorkerTargetDrawable(
228
+ rect: Pick<DOMRect, "x" | "y" | "width" | "height">,
229
+ ): boolean {
230
+ if (
231
+ ![rect.x, rect.y, rect.width, rect.height].every(Number.isFinite) ||
232
+ rect.width < 0 ||
233
+ rect.height < 0
234
+ )
235
+ throw new NativeSourceError(
236
+ "worker-target-geometry-invalid",
237
+ "A native effect target has unavailable or oversized geometry.",
238
+ );
239
+ return rect.width > 0 && rect.height > 0;
240
+ }
241
+
219
242
  function nativeWorkerTargetGeometry(
220
- target: HTMLElement | SVGSVGElement,
243
+ rect: DOMRect,
221
244
  pixelRatio: number,
222
245
  cssWidth: number,
223
246
  cssHeight: number,
224
247
  ): { width: number; height: number; rect: NativeWorkerEffectTarget["rect"] } {
225
- const rect = target.getBoundingClientRect();
226
248
  const width = Math.max(1, Math.ceil(cssWidth * pixelRatio));
227
249
  const height = Math.max(1, Math.ceil(cssHeight * pixelRatio));
228
250
  if (
@@ -330,6 +352,8 @@ export class NativeWorkerSceneHost {
330
352
  compact: boolean;
331
353
  },
332
354
  private readonly getClockOrigin?: () => number,
355
+ private readonly hasPresentationToRetire?: () => boolean,
356
+ private readonly beforeCommit?: () => void,
333
357
  ) {
334
358
  const body = document.body;
335
359
  if (!body)
@@ -451,26 +475,36 @@ export class NativeWorkerSceneHost {
451
475
  this.targets.delete(id);
452
476
  }
453
477
  this.entries = [...entries];
454
- for (const target of this.markedTargets) {
455
- target.removeAttribute("data-an-native-fill-instance");
456
- target.removeAttribute("data-an-native-layer-instance");
457
- }
458
- this.markedTargets.clear();
459
478
  for (const entry of entries) {
460
479
  const existing = this.targets.get(entry.instance.id);
461
480
  if (existing) existing.entry = entry;
462
481
  else this.targets.set(entry.instance.id, this.createTarget(entry));
463
482
  }
483
+ this.restoreEntryMarkersAndVisibility();
484
+ this.requestUpdate();
485
+ }
486
+
487
+ private restoreEntryMarkersAndVisibility(): void {
488
+ for (const target of this.markedTargets) {
489
+ target.removeAttribute("data-an-native-fill-instance");
490
+ target.removeAttribute("data-an-native-layer-instance");
491
+ }
492
+ this.markedTargets.clear();
464
493
  const topFill = new Map<Element, string>();
465
494
  const topLayer = new Map<Element, string>();
466
- for (const entry of entries) {
495
+ for (const entry of this.entries) {
467
496
  if (entry.instance.placement === "fill")
468
497
  topFill.set(entry.target, entry.instance.id);
469
498
  if (entry.instance.placement === "layer")
470
499
  topLayer.set(entry.target, entry.instance.id);
471
500
  }
472
- for (const entry of entries) {
473
- const state = this.targets.get(entry.instance.id)!;
501
+ for (const entry of this.entries) {
502
+ const state = this.targets.get(entry.instance.id);
503
+ if (!state)
504
+ throw new NativeWorkerHostError(
505
+ "worker-instance-state-missing",
506
+ "A native worker instance is unavailable.",
507
+ );
474
508
  this.markedTargets.add(entry.target);
475
509
  if (topFill.get(entry.target) === entry.instance.id)
476
510
  entry.target.setAttribute(
@@ -490,7 +524,6 @@ export class NativeWorkerSceneHost {
490
524
  ? "visible"
491
525
  : "hidden";
492
526
  }
493
- this.requestUpdate();
494
527
  }
495
528
 
496
529
  setEntriesAndWait(
@@ -671,11 +704,13 @@ export class NativeWorkerSceneHost {
671
704
  Math.abs(pixelRatio - committed.scene.pixelRatio) > 0.000001 ||
672
705
  (committed.scene.frameRateTarget ?? 60) !== cadence.frameRateTarget ||
673
706
  (committed.scene.compact ?? false) !== cadence.compact ||
674
- this.entries.length !== committed.publication.payloads.size
707
+ this.entries.length !== committed.publication.authoredPayloads.size
675
708
  )
676
709
  return null;
677
710
  for (const entry of this.entries) {
678
- const expected = committed.publication.payloads.get(entry.instance.id);
711
+ const expected = committed.publication.authoredPayloads.get(
712
+ entry.instance.id,
713
+ );
679
714
  const epoch = committed.publication.targetEpochs.get(entry.instance.id);
680
715
  const state = this.targets.get(entry.instance.id);
681
716
  const target = committed.scene.targets.find(
@@ -689,11 +724,15 @@ export class NativeWorkerSceneHost {
689
724
  !state ||
690
725
  state.entry.target !== entry.target ||
691
726
  state.provider.sourceEpoch?.() !== epoch.source ||
692
- state.provider.authoredSourceEpoch?.() !== epoch.authored ||
693
- !target
727
+ state.provider.authoredSourceEpoch?.() !== epoch.authored
694
728
  )
695
729
  return null;
696
730
  const rect = entry.target.getBoundingClientRect();
731
+ if (!nativeWorkerTargetDrawable(rect)) {
732
+ if (target) return null;
733
+ continue;
734
+ }
735
+ if (!target) return null;
697
736
  if (
698
737
  Math.abs(rect.x - target.rect.x) > 0.001 ||
699
738
  Math.abs(rect.y - target.rect.y) > 0.001 ||
@@ -811,12 +850,13 @@ export class NativeWorkerSceneHost {
811
850
  }
812
851
 
813
852
  private async updateLoop(): Promise<void> {
814
- if (this.disposed || this.updating) return;
853
+ if (this.disposed || this.holdingLastFrame || this.updating) return;
815
854
  this.updating = true;
816
855
  try {
817
856
  while (
818
857
  this.dirty &&
819
858
  !this.disposed &&
859
+ !this.holdingLastFrame &&
820
860
  !this.capturingExport &&
821
861
  this.visible
822
862
  ) {
@@ -824,6 +864,7 @@ export class NativeWorkerSceneHost {
824
864
  try {
825
865
  await this.sendSnapshot();
826
866
  } catch (error) {
867
+ if (this.disposed || this.holdingLastFrame) return;
827
868
  this.assetRevisions.clear();
828
869
  this.onFailure(
829
870
  error instanceof Error ? error : new Error(String(error)),
@@ -933,7 +974,16 @@ export class NativeWorkerSceneHost {
933
974
  >();
934
975
  const extents = new Map<string, NativeEffectExtentPlan>();
935
976
  const previousByTarget = new Map<Element, string>();
936
- for (const entry of this.entries) {
977
+ const targetRects = new Map<HTMLElement | SVGSVGElement, DOMRect>();
978
+ const drawableEntries = this.entries.filter((entry) => {
979
+ let rect = targetRects.get(entry.target);
980
+ if (!rect) {
981
+ rect = entry.target.getBoundingClientRect();
982
+ targetRects.set(entry.target, rect);
983
+ }
984
+ return nativeWorkerTargetDrawable(rect);
985
+ });
986
+ for (const entry of drawableEntries) {
937
987
  const state = this.targets.get(entry.instance.id);
938
988
  if (!state || state.entry.target !== entry.target)
939
989
  throw new NativeSourceError(
@@ -941,14 +991,14 @@ export class NativeWorkerSceneHost {
941
991
  "A native worker target changed during scene capture.",
942
992
  );
943
993
  const layout = this.layout(entry.target);
944
- const visible = entry.target.getBoundingClientRect();
994
+ const visible = targetRects.get(entry.target)!;
945
995
  const displayScale = Math.max(
946
996
  visible.width / layout.width,
947
997
  visible.height / layout.height,
948
998
  );
949
999
  const targetPixelRatio = pixelRatio * displayScale;
950
1000
  const geometry = nativeWorkerTargetGeometry(
951
- entry.target,
1001
+ visible,
952
1002
  targetPixelRatio,
953
1003
  layout.width,
954
1004
  layout.height,
@@ -1212,13 +1262,13 @@ export class NativeWorkerSceneHost {
1212
1262
  ...(fillPhases ? { fillPhases } : {}),
1213
1263
  });
1214
1264
  }
1215
- for (const entry of this.entries) {
1265
+ for (const entry of drawableEntries) {
1216
1266
  if (entry.instance.placement !== "backdrop") continue;
1217
- const owners = this.entries.filter(
1267
+ const owners = drawableEntries.filter(
1218
1268
  (candidate, index) =>
1219
1269
  (candidate.instance.placement === "layer" ||
1220
1270
  (candidate.instance.placement === "fill" &&
1221
- this.entries.every(
1271
+ drawableEntries.every(
1222
1272
  (other, otherIndex) =>
1223
1273
  otherIndex <= index ||
1224
1274
  other.instance.placement !== "fill" ||
@@ -1337,10 +1387,17 @@ export class NativeWorkerSceneHost {
1337
1387
  records,
1338
1388
  );
1339
1389
  }
1340
- const rawRootRecords = await this.provider.readScene(
1341
- undefined,
1342
- "scene-presentation",
1343
- );
1390
+ let rawRootRecords: NativeSourceRecord[];
1391
+ try {
1392
+ rawRootRecords = await this.provider.readScene(
1393
+ undefined,
1394
+ "scene-presentation",
1395
+ );
1396
+ } catch (error) {
1397
+ if (!options && error instanceof NativeSourceCapabilityError)
1398
+ throw new NativeWorkerSceneSourceError(error);
1399
+ throw error;
1400
+ }
1344
1401
  if (
1345
1402
  this.provider.needsContinuousFrames() ||
1346
1403
  (await this.provider.replaySafety(
@@ -1413,8 +1470,8 @@ export class NativeWorkerSceneHost {
1413
1470
  compact: cadence.compact,
1414
1471
  clock: this.snapshotClock(viewport, !!options, sourceClock),
1415
1472
  requiredAssets,
1416
- definitions: nativeWorkerDefinitions(this.entries),
1417
- instances: this.entries.map((entry) => entry.instance),
1473
+ definitions: nativeWorkerDefinitions(drawableEntries),
1474
+ instances: drawableEntries.map((entry) => entry.instance),
1418
1475
  targets: targetSnapshots,
1419
1476
  records,
1420
1477
  ...(selected ? { backdropRecords: normalizedRootRecords } : {}),
@@ -1459,6 +1516,15 @@ export class NativeWorkerSceneHost {
1459
1516
  ),
1460
1517
  rootRecords: rawRootRecords,
1461
1518
  payloads: new Map(
1519
+ drawableEntries.map((entry) => [
1520
+ entry.instance.id,
1521
+ {
1522
+ payload: JSON.stringify(entry.instance),
1523
+ definitionHash: entry.definitionHash,
1524
+ },
1525
+ ]),
1526
+ ),
1527
+ authoredPayloads: new Map(
1462
1528
  this.entries.map((entry) => [
1463
1529
  entry.instance.id,
1464
1530
  {
@@ -1716,16 +1782,18 @@ export class NativeWorkerSceneHost {
1716
1782
  this.requestUpdate();
1717
1783
  return;
1718
1784
  }
1719
- const cameraOnly = this.canReuseCommittedPresentation(publication);
1785
+ const replacedPresentation = this.hasPresentationToRetire?.() === true;
1786
+ const cameraOnly =
1787
+ !replacedPresentation && this.canReuseCommittedPresentation(publication);
1788
+ const paintedChildren = new Set<Element>();
1789
+ const authoredOpacities = new Map<Element, string>();
1720
1790
  if (!cameraOnly) {
1721
1791
  const body = document.body;
1722
- const paintedChildren = new Set<Element>();
1723
1792
  for (const record of publication.rootRecords) {
1724
1793
  const root = scenePaintRoot(record.node, body);
1725
1794
  if (root && root !== this.canvas && !root.matches(OMIT_SCENE_CHILD))
1726
1795
  paintedChildren.add(root);
1727
1796
  }
1728
- const authoredOpacities = new Map<Element, string>();
1729
1797
  for (const child of paintedChildren) {
1730
1798
  const opacity =
1731
1799
  child.getAttribute("data-an-native-authored-opacity") ??
@@ -1747,6 +1815,10 @@ export class NativeWorkerSceneHost {
1747
1815
  }
1748
1816
  authoredOpacities.set(child, opacity);
1749
1817
  }
1818
+ }
1819
+ this.beforeCommit?.();
1820
+ if (replacedPresentation) this.restoreEntryMarkersAndVisibility();
1821
+ if (!cameraOnly) {
1750
1822
  for (const old of this.suppressed) {
1751
1823
  if (paintedChildren.has(old)) continue;
1752
1824
  old.removeAttribute("data-an-native-scene-suppressed");
@@ -1807,7 +1879,7 @@ export class NativeWorkerSceneHost {
1807
1879
  });
1808
1880
  if (drainCamera) this.requestUpdate();
1809
1881
  for (const waiter of this.publicationWaiters) {
1810
- const expected = publication.payloads.get(waiter.instanceId);
1882
+ const expected = publication.authoredPayloads.get(waiter.instanceId);
1811
1883
  const committedScene = this.committedSceneSnapshot;
1812
1884
  if (
1813
1885
  publication.generation < waiter.minimumGeneration ||
@@ -1835,6 +1907,7 @@ export class NativeWorkerSceneHost {
1835
1907
  committed.targetEpochs !== publication.targetEpochs ||
1836
1908
  committed.rootRecords !== publication.rootRecords ||
1837
1909
  committed.payloads !== publication.payloads ||
1910
+ committed.authoredPayloads !== publication.authoredPayloads ||
1838
1911
  this.canvas.style.visibility !== "visible" ||
1839
1912
  !document.documentElement.hasAttribute("data-an-native-scene-managed")
1840
1913
  )
@@ -1981,25 +2054,32 @@ export class NativeWorkerSceneHost {
1981
2054
  scene.authoredEpoch === hiddenAuthoredEpoch &&
1982
2055
  this.provider.sourceEpoch?.() === scene.sourceEpoch &&
1983
2056
  this.provider.authoredSourceEpoch?.() === hiddenAuthoredEpoch &&
1984
- scene.instances.length === hiddenPayloads.length &&
1985
- scene.targets.length === hiddenPayloads.length &&
2057
+ this.entries.length === hiddenPayloads.length &&
2058
+ captured.publication.authoredPayloads.size === hiddenPayloads.length &&
1986
2059
  !hiddenPayloads.some((expected, index) => {
1987
2060
  const entry = this.entries[index];
1988
- const target = scene.targets[index];
1989
- const instance = scene.instances[index];
2061
+ const target = scene.targets.find(
2062
+ (item) => item.instanceId === expected.id,
2063
+ );
2064
+ const instance = scene.instances.find(
2065
+ (item) => item.id === expected.id,
2066
+ );
2067
+ const payload = captured.publication.authoredPayloads.get(
2068
+ expected.id,
2069
+ );
1990
2070
  const provider = this.targets.get(expected.id)?.provider;
1991
2071
  const capturedEpoch = captured.publication.targetEpochs.get(
1992
2072
  expected.id,
1993
2073
  );
1994
2074
  return (
1995
2075
  !entry ||
1996
- !target ||
1997
- !instance ||
1998
- instance.id !== expected.id ||
1999
- target.instanceId !== expected.id ||
2000
- JSON.stringify(instance) !== expected.payload ||
2076
+ !payload ||
2077
+ payload.payload !== expected.payload ||
2078
+ payload.definitionHash !== expected.definitionHash ||
2079
+ !!target !== !!instance ||
2080
+ (instance && JSON.stringify(instance) !== expected.payload) ||
2001
2081
  JSON.stringify(entry.instance) !== expected.payload ||
2002
- target.definitionHash !== expected.definitionHash ||
2082
+ (target && target.definitionHash !== expected.definitionHash) ||
2003
2083
  entry.definitionHash !== expected.definitionHash ||
2004
2084
  !capturedEpoch ||
2005
2085
  provider?.sourceEpoch?.() !== capturedEpoch.source ||
@@ -111,8 +111,12 @@
111
111
  const registry: Record<string, AnShaderDef> = {};
112
112
  const mounts: AnShaderMount[] = [];
113
113
  let rafId: number | null = null;
114
+ let lastFrameTs: number | null = null;
114
115
  let needsRender = false;
115
116
  let scanTimer: ReturnType<typeof setTimeout> | null = null;
117
+ // How large the editor shows this document, from its zoom. Outside the
118
+ // editor nothing sends one, so standalone pages keep full resolution.
119
+ let viewScale = 1;
116
120
 
117
121
  function nowMs(): number {
118
122
  return W.performance && W.performance.now
@@ -420,9 +424,13 @@
420
424
  const rect = el.getBoundingClientRect();
421
425
  const wCss = Math.max(1, Math.round(rect.width));
422
426
  const hCss = Math.max(1, Math.round(rect.height));
423
- const dpr = Math.min(2, W.devicePixelRatio || 1);
424
- const wPx = Math.max(1, Math.round(wCss * dpr));
425
- const hPx = Math.max(1, Math.round(hCss * dpr));
427
+ // Zoomed out, render at the on-screen size, but never below one canvas
428
+ // pixel per CSS pixel: presets size grain and halftone dots in canvas
429
+ // pixels, so this keeps them no coarser than a standard display shows.
430
+ const deviceRatio = Math.min(2, W.devicePixelRatio || 1);
431
+ const ratio = Math.max(Math.min(1, deviceRatio), deviceRatio * viewScale);
432
+ const wPx = Math.max(1, Math.round(wCss * ratio));
433
+ const hPx = Math.max(1, Math.round(hCss * ratio));
426
434
  if (canvas.width !== wPx || canvas.height !== hPx) {
427
435
  canvas.width = wPx;
428
436
  canvas.height = hPx;
@@ -455,8 +463,18 @@
455
463
  gl.drawArrays(gl.TRIANGLES, 0, 3);
456
464
  }
457
465
 
458
- function frame(): void {
466
+ function frame(ts?: number): void {
459
467
  rafId = null;
468
+ // On a board of several screens, Chromium can run each screen's
469
+ // animation-frame callbacks once per screen per display frame, all with
470
+ // the same timestamp. Draw on the first; the rest would repeat its pixels.
471
+ if (typeof ts === "number") {
472
+ if (ts === lastFrameTs) {
473
+ startLoop();
474
+ return;
475
+ }
476
+ lastFrameTs = ts;
477
+ }
460
478
  const t = (nowMs() - t0) / 1000;
461
479
  const animate = !reducedMotion && !document.hidden;
462
480
  for (let i = mounts.length - 1; i >= 0; i--) {
@@ -562,6 +580,25 @@
562
580
  if (!document.hidden) requestRender();
563
581
  });
564
582
 
583
+ // The editor posts its zoom to each screen. Zooming in keeps the
584
+ // document's own resolution; zoom steps round up to eighths so a pinch
585
+ // doesn't reallocate the canvases every frame.
586
+ W.addEventListener("message", function (event: MessageEvent) {
587
+ if (W.parent === W || event.source !== W.parent) return;
588
+ const data = event.data as {
589
+ type?: unknown;
590
+ scaleX?: unknown;
591
+ scaleY?: unknown;
592
+ } | null;
593
+ if (!data || data.type !== "set-editor-chrome-scale") return;
594
+ const scale = Math.max(Number(data.scaleX) || 0, Number(data.scaleY) || 0);
595
+ if (!(scale > 0)) return;
596
+ const next = Math.min(1, Math.ceil(scale * 8) / 8);
597
+ if (next === viewScale) return;
598
+ viewScale = next;
599
+ requestRender();
600
+ });
601
+
565
602
  try {
566
603
  new MutationObserver(function () {
567
604
  scheduleScan();
@@ -1,3 +1,9 @@
1
+ import {
2
+ currentCanvasOutcomeDesignId,
3
+ recordCanvasOutcome,
4
+ runCanvasOperation,
5
+ type CanvasOperationHandle,
6
+ } from "@/lib/canvas-outcome-tracking";
1
7
  import type { TextCommitStatus } from "@/pages/design-editor/command-types";
2
8
 
3
9
  export type QueuePendingTextHostCommit = (
@@ -156,16 +162,7 @@ export function consumePendingTextCreation(
156
162
  return true;
157
163
  }
158
164
 
159
- export function commitTextContentChange({
160
- owner,
161
- nodeId,
162
- value,
163
- isPendingTextCreation,
164
- commit,
165
- queueHostCommit,
166
- cancelHostCommit,
167
- requestSnapshot,
168
- }: {
165
+ type CommitTextContentChangeArgs = {
169
166
  owner: string | null;
170
167
  nodeId: string | null;
171
168
  value: string;
@@ -174,15 +171,50 @@ export function commitTextContentChange({
174
171
  queueHostCommit: QueuePendingTextHostCommit;
175
172
  cancelHostCommit: CancelPendingTextHostCommit;
176
173
  requestSnapshot: () => void;
177
- }): "published" | "fallback-queued" {
174
+ };
175
+
176
+ export function commitTextContentChange(
177
+ args: CommitTextContentChangeArgs,
178
+ ): "published" | "fallback-queued" {
179
+ return runCanvasOperation("text_edit", (op) =>
180
+ commitTextContentChangeWithOutcome(op, args),
181
+ );
182
+ }
183
+
184
+ function commitTextContentChangeWithOutcome(
185
+ op: CanvasOperationHandle,
186
+ {
187
+ owner,
188
+ nodeId,
189
+ value,
190
+ isPendingTextCreation,
191
+ commit,
192
+ queueHostCommit,
193
+ cancelHostCommit,
194
+ requestSnapshot,
195
+ }: CommitTextContentChangeArgs,
196
+ ): "published" | "fallback-queued" {
178
197
  const canFallback = Boolean(
179
198
  isPendingTextCreation && owner && nodeId && value.length > 0,
180
199
  );
181
200
  const queueFallback = () => {
182
201
  if (!canFallback || !owner || !nodeId || value.length === 0) return false;
183
- return queueHostCommit(owner, nodeId, value, (outcome) => {
202
+ // The retry can settle after the user opened another design.
203
+ const designId = currentCanvasOutcomeDesignId();
204
+ const queued = queueHostCommit(owner, nodeId, value, (outcome) => {
184
205
  if (outcome === "accepted") requestSnapshot();
206
+ recordCanvasOutcome(
207
+ "text_retry",
208
+ outcome === "accepted" ? "succeeded" : "failed",
209
+ outcome === "accepted" ? "none" : "not_written",
210
+ { designId },
211
+ );
185
212
  });
213
+ // A queued retry can be cancelled or replaced without settling, so the
214
+ // edit counts as deferred rather than waiting on a result that may never
215
+ // come. A retry that does settle counts on its own, as `text_retry`.
216
+ if (queued) op.defer("fallback_queued");
217
+ return queued;
186
218
  };
187
219
 
188
220
  let status: TextCommitStatus | void;
@@ -201,6 +233,8 @@ export function commitTextContentChange({
201
233
  // Any later direct result supersedes an older retry. A retry must never
202
234
  // overwrite text that a newer source edit already accepted.
203
235
  if (owner && nodeId) cancelHostCommit(owner, nodeId);
236
+ if (status === "accepted") op.succeed();
237
+ else if (status === "refused") op.fail("refused");
204
238
  requestSnapshot();
205
239
  return "published";
206
240
  }
@@ -2,6 +2,9 @@ import type { ReactNode } from "react";
2
2
 
3
3
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4
4
 
5
+ const TAB_CLASS =
6
+ "h-auto px-0 py-0 text-lg leading-[27px] font-medium group-data-[variant=line]/tabs-list:h-auto group-data-[variant=line]/tabs-list:border-b group-data-[variant=line]/tabs-list:text-muted-foreground";
7
+
5
8
  export type DesignHomeLibraryTab = "templates" | "recent";
6
9
 
7
10
  interface DesignHomeLibraryProps {
@@ -29,7 +32,7 @@ export function DesignHomeLibrary({
29
32
 
30
33
  return (
31
34
  <section
32
- className="agent-prompt-home-library"
35
+ className="agent-prompt-home-library mx-auto w-full max-w-[63rem]"
33
36
  aria-label={labels.templates}
34
37
  >
35
38
  <Tabs
@@ -42,19 +45,19 @@ export function DesignHomeLibrary({
42
45
  }}
43
46
  >
44
47
  <div className="flex flex-wrap items-center justify-between gap-3">
45
- <TabsList variant="line">
46
- <TabsTrigger value="recent" className="flex-none">
47
- {labels.recent}
48
- </TabsTrigger>
48
+ <TabsList variant="line" className="gap-2 border-b-0">
49
49
  <TabsTrigger
50
50
  value="templates"
51
- className="flex-none"
51
+ className={TAB_CLASS}
52
52
  onClick={() => {
53
53
  if (activeValue === "templates") onValueChange("templates");
54
54
  }}
55
55
  >
56
56
  {labels.templates}
57
57
  </TabsTrigger>
58
+ <TabsTrigger value="recent" className={TAB_CLASS}>
59
+ {labels.recent}
60
+ </TabsTrigger>
58
61
  </TabsList>
59
62
  {activeValue === "recent" ? (
60
63
  <div className="flex w-full min-w-0 flex-wrap items-center justify-end gap-2 sm:w-auto">
@@ -65,8 +68,12 @@ export function DesignHomeLibrary({
65
68
  browseAll
66
69
  )}
67
70
  </div>
68
- <TabsContent value="templates">{templates}</TabsContent>
69
- <TabsContent value="recent">{recent}</TabsContent>
71
+ <TabsContent value="templates" className="mt-4">
72
+ {templates}
73
+ </TabsContent>
74
+ <TabsContent value="recent" className="mt-4">
75
+ {recent}
76
+ </TabsContent>
70
77
  </Tabs>
71
78
  </section>
72
79
  );
@@ -0,0 +1,50 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { GettingStartedCard } from "@agent-native/toolkit/app/onboarding";
3
+ import { useMemo } from "react";
4
+ import { Link } from "react-router";
5
+
6
+ const GETTING_STARTED_STEP_KEYS = {
7
+ "create-design": "createDesign",
8
+ "add-brand": "addBrand",
9
+ "use-real-data": "useRealData",
10
+ "share-feedback": "shareFeedback",
11
+ "invite-teammate": "inviteTeammate",
12
+ "save-template": "saveTemplate",
13
+ } as const;
14
+
15
+ export function GettingStarted({ className }: { className?: string }) {
16
+ const t = useT();
17
+ const labels = useMemo(
18
+ () => ({
19
+ title: t("gettingStarted.title"),
20
+ progress: (completeCount: number, totalCount: number) =>
21
+ t("gettingStarted.progress", { completeCount, totalCount }),
22
+ collapse: t("gettingStarted.collapse"),
23
+ expand: t("gettingStarted.expand"),
24
+ completed: t("gettingStarted.completed"),
25
+ loadError: t("gettingStarted.loadError"),
26
+ retry: t("gettingStarted.retry"),
27
+ }),
28
+ [t],
29
+ );
30
+ const stepTitles = useMemo(
31
+ () =>
32
+ Object.fromEntries(
33
+ Object.entries(GETTING_STARTED_STEP_KEYS).map(([id, key]) => [
34
+ id,
35
+ t(`gettingStarted.steps.${key}`),
36
+ ]),
37
+ ),
38
+ [t],
39
+ );
40
+
41
+ return (
42
+ <GettingStartedCard
43
+ checklist="getting-started"
44
+ labels={labels}
45
+ stepTitles={stepTitles}
46
+ LinkComponent={Link}
47
+ className={className}
48
+ />
49
+ );
50
+ }
@@ -14,6 +14,8 @@ import { IconComponents, IconHome, IconTemplate } from "@tabler/icons-react";
14
14
  import { useEffect, useState } from "react";
15
15
  import { useLocation } from "react-router";
16
16
 
17
+ import { GettingStarted } from "./GettingStarted";
18
+
17
19
  const COLLAPSE_KEY = "design.sidebar.collapsed";
18
20
 
19
21
  export function Sidebar() {
@@ -79,6 +81,7 @@ export function Sidebar() {
79
81
  items={items}
80
82
  footerContent={
81
83
  <>
84
+ {collapsed ? null : <GettingStarted className="mx-2 mb-2" />}
82
85
  <div className={collapsed ? "flex flex-col items-center" : undefined}>
83
86
  <BuilderCreditNotice compact showAtLimitOnly className="mb-2" />
84
87
  </div>