@agent-native/core-corpus 0.216.0 → 0.218.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 (196) 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 +3 -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/components/layout/Sidebar.tsx +38 -167
  11. package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
  12. package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
  13. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
  14. package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
  15. package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
  16. package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
  17. package/corpus/templates/analytics/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
  19. package/corpus/templates/analytics/server/db/schema.ts +67 -0
  20. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
  21. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
  22. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
  23. package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
  24. package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
  25. package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
  26. package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
  27. package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
  28. package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
  29. package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
  30. package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
  31. package/corpus/templates/analytics/server/plugins/db.ts +46 -0
  32. package/corpus/templates/analytics/shared/output-use.ts +205 -0
  33. package/corpus/templates/analytics/shared/session-events.ts +6 -0
  34. package/corpus/templates/assets/CHANGELOG.md +6 -0
  35. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +51 -117
  36. package/corpus/templates/assets/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  37. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +34 -101
  38. package/corpus/templates/brain/app/routes/sources.tsx +1 -1
  39. package/corpus/templates/brain/vitest.config.ts +7 -0
  40. package/corpus/templates/calendar/CHANGELOG.md +12 -0
  41. package/corpus/templates/calendar/actions/create-event.ts +2 -4
  42. package/corpus/templates/calendar/actions/update-event.ts +35 -6
  43. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +1 -1
  44. package/corpus/templates/calendar/changelog/2026-10-16-booking-links-show-localized-visibility-badges.md +6 -0
  45. package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
  46. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +62 -101
  47. package/corpus/templates/clips/CHANGELOG.md +7 -20
  48. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +20 -77
  49. package/corpus/templates/clips/app/components/player/share-dialog.tsx +63 -90
  50. package/corpus/templates/clips/app/components/sharing/share-test-support.tsx +180 -0
  51. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +5 -584
  52. package/corpus/templates/clips/changelog/2026-10-16-share-dialogs-make-it-easier-to-see-who-has-access-and-manag.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-10-21-share-panels-describe-general-access-in-a-sentence.md +6 -0
  54. package/corpus/templates/community/qr/package.json +1 -1
  55. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
  56. package/corpus/templates/content/CHANGELOG.md +10 -0
  57. package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
  58. package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
  59. package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
  60. package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
  61. package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
  62. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
  63. package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
  64. package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
  65. package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
  66. package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
  67. package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
  68. package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
  69. package/corpus/templates/content/actions/_database-utils.ts +93 -7
  70. package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
  71. package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
  72. package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
  73. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
  74. package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
  75. package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
  76. package/corpus/templates/content/actions/delete-document.ts +41 -3
  77. package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
  78. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
  79. package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
  80. package/corpus/templates/content/actions/get-document.ts +2 -0
  81. package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
  82. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
  83. package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
  84. package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
  85. package/corpus/templates/content/actions/update-document.ts +139 -20
  86. package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
  87. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +119 -30
  88. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +56 -13
  89. package/corpus/templates/content/app/components/editor/SuggestionText.tsx +18 -3
  90. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +89 -22
  91. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
  92. package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
  93. package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
  94. package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
  95. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
  96. package/corpus/templates/content/app/components/editor/extensions/SuggestionHighlight.ts +383 -34
  97. package/corpus/templates/content/app/components/editor/suggestions/draft-session.ts +31 -0
  98. package/corpus/templates/content/app/components/editor/suggestions/presentation-rebase.ts +82 -4
  99. package/corpus/templates/content/app/global.css +31 -0
  100. package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
  101. package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
  102. package/corpus/templates/content/app/i18n-data.ts +1 -0
  103. package/corpus/templates/content/changelog/2026-10-16-accepting-a-suggestion-on-a-page-that-changed-since-it-was-s.md +6 -0
  104. package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
  105. package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
  106. package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
  107. package/corpus/templates/content/changelog/2026-10-20-suggested-edits-that-add-remove-split-or-merge-paragraphs-li.md +6 -0
  108. package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
  109. package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
  110. package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
  111. package/corpus/templates/content/parity/matrix.md +42 -42
  112. package/corpus/templates/content/parity/matrix.ts +3 -1
  113. package/corpus/templates/content/server/db/schema.ts +20 -0
  114. package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
  115. package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
  116. package/corpus/templates/content/server/live-copy/binding.ts +31 -0
  117. package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
  118. package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
  119. package/corpus/templates/content/server/live-copy/holds.ts +59 -0
  120. package/corpus/templates/content/server/live-copy/persist.ts +656 -0
  121. package/corpus/templates/content/server/live-copy/switch.ts +136 -0
  122. package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
  123. package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
  124. package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
  125. package/corpus/templates/content/server/live-copy/write.ts +85 -0
  126. package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
  127. package/corpus/templates/content/server/plugins/collab.ts +3 -0
  128. package/corpus/templates/content/server/plugins/db.ts +28 -0
  129. package/corpus/templates/content/shared/api.ts +30 -1
  130. package/corpus/templates/content/shared/database-item-layout.ts +54 -22
  131. package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
  132. package/corpus/templates/content/shared/og-description.ts +258 -36
  133. package/corpus/templates/content/shared/suggestion-diff.ts +23 -6
  134. package/corpus/templates/content/shared/suggestion-formatting.ts +44 -14
  135. package/corpus/templates/content/shared/suggestion-text.ts +20 -5
  136. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
  137. package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
  138. package/corpus/templates/design/CHANGELOG.md +5 -0
  139. package/corpus/templates/design/actions/get-getting-started.ts +53 -0
  140. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
  141. package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
  142. package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
  143. package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
  144. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
  145. package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
  146. package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
  147. package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
  148. package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
  149. package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
  150. package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
  151. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
  152. package/corpus/templates/design/app/global.css +6 -0
  153. package/corpus/templates/design/app/i18n/en-US.ts +17 -0
  154. package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
  155. package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
  156. package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
  157. package/corpus/templates/design/app/pages/DesignSystems.tsx +1 -1
  158. package/corpus/templates/design/app/pages/Index.tsx +28 -67
  159. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
  160. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
  161. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
  162. package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
  163. package/corpus/templates/design/changelog/2026-10-16-design-pages-use-localized-visibility-badges-and-a-shared-ow.md +6 -0
  164. package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
  165. package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
  166. package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
  167. package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
  168. package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
  169. package/corpus/templates/design/server/lib/getting-started.ts +267 -0
  170. package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
  171. package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
  172. package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
  173. package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
  174. package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
  175. package/corpus/templates/design/shared/design-template-presets.ts +8 -0
  176. package/corpus/templates/dispatch/CHANGELOG.md +4 -0
  177. package/corpus/templates/dispatch/app/i18n-data.ts +75 -0
  178. package/corpus/templates/dispatch/changelog/2026-10-21-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  179. package/corpus/templates/factory/app/components/layout/Sidebar.tsx +34 -100
  180. package/corpus/templates/forms/app/pages/FormsListPage.tsx +1 -1
  181. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  182. package/corpus/templates/plan/CHANGELOG.md +6 -0
  183. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +34 -109
  184. package/corpus/templates/plan/changelog/2026-10-16-owned-and-shared-chats-appear-in-separate-history-sections.md +6 -0
  185. package/corpus/templates/slides/CHANGELOG.md +7 -0
  186. package/corpus/templates/slides/actions/delete-deck.ts +7 -0
  187. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +1 -1
  188. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +1 -1
  189. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  190. package/corpus/templates/slides/changelog/2026-10-16-deck-lists-use-the-shared-ownership-filter-and-visibility-ba.md +6 -0
  191. package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
  192. package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
  193. package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
  194. package/package.json +1 -1
  195. package/corpus/templates/chat/app/lib/sidebar-thread-state.ts +0 -29
  196. package/corpus/templates/slides/app/components/deck/DeckFilterMenu.tsx +0 -83
@@ -82,6 +82,8 @@ export class NativeSourceError extends Error {
82
82
  }
83
83
  }
84
84
 
85
+ export class NativeSourceCapabilityError extends NativeSourceError {}
86
+
85
87
  export function nativePresentationVisible(
86
88
  canvas: HTMLCanvasElement,
87
89
  viewport: { width: number; height: number },
@@ -425,13 +427,13 @@ function assertLeafSupported(
425
427
  readStyle: SourceStyleReader = getComputedStyle,
426
428
  ): void {
427
429
  if (element.shadowRoot) {
428
- throw new NativeSourceError(
430
+ throw new NativeSourceCapabilityError(
429
431
  "source-shadow-dom",
430
432
  "Shadow DOM source content is unsupported.",
431
433
  );
432
434
  }
433
435
  if (isNativeHtmlTag(element, "iframe")) {
434
- throw new NativeSourceError(
436
+ throw new NativeSourceCapabilityError(
435
437
  "source-iframe",
436
438
  "Iframe source content is unsupported.",
437
439
  );
@@ -442,7 +444,7 @@ function assertLeafSupported(
442
444
  style.filter !== "none" ||
443
445
  style.backdropFilter !== "none"
444
446
  ) {
445
- throw new NativeSourceError(
447
+ throw new NativeSourceCapabilityError(
446
448
  "source-css-composite",
447
449
  "CSS blend modes, filters, and backdrop filters need a separate compositor path.",
448
450
  );
@@ -491,7 +493,7 @@ function assertLeafSupported(
491
493
  "scale",
492
494
  ].includes(key)
493
495
  ) {
494
- throw new NativeSourceError(
496
+ throw new NativeSourceCapabilityError(
495
497
  "source-animation",
496
498
  "An animated source changes pixels; cached DOM capture supports transform-only motion.",
497
499
  );
@@ -505,7 +507,7 @@ function assertLeafSupported(
505
507
  for (const pseudo of ["::before", "::after"]) {
506
508
  const content = getComputedStyle(element, pseudo).content;
507
509
  if (content && content !== "none" && content !== "normal") {
508
- throw new NativeSourceError(
510
+ throw new NativeSourceCapabilityError(
509
511
  "source-pseudo",
510
512
  "CSS pseudo-element content is unsupported.",
511
513
  );
@@ -610,6 +612,108 @@ export function nativeTextFlowUsesDocumentFont(element: HTMLElement): boolean {
610
612
  return false;
611
613
  }
612
614
 
615
+ function nativeRenderedTextFlow(element: HTMLElement): {
616
+ read(node: Node, start: number, end: number): string;
617
+ dispose(): void;
618
+ } {
619
+ const clone = element.cloneNode(true) as HTMLElement;
620
+ const originals = [element, ...element.querySelectorAll<HTMLElement>("*")];
621
+ const copies = [clone, ...clone.querySelectorAll<HTMLElement>("*")];
622
+ for (let index = 0; index < originals.length; index++) {
623
+ const copy = copies[index]!;
624
+ copyComputedCss(originals[index]!, copy);
625
+ copy.removeAttribute("id");
626
+ copy.removeAttribute("data-agent-native-node-id");
627
+ for (const attribute of [...copy.attributes])
628
+ if (attribute.name.startsWith("data-an-native-"))
629
+ copy.removeAttribute(attribute.name);
630
+ copy.style.setProperty("animation", "none", "important");
631
+ copy.style.setProperty("transition", "none", "important");
632
+ }
633
+ const language = element.closest("[lang]")?.getAttribute("lang");
634
+ if (language !== undefined && language !== null)
635
+ clone.setAttribute("lang", language);
636
+ clone.setAttribute("data-an-native-presentation", "");
637
+ for (const [name, value] of Object.entries({
638
+ position: "fixed",
639
+ left: "-100000px",
640
+ top: "0px",
641
+ opacity: "0",
642
+ visibility: "visible",
643
+ "pointer-events": "none",
644
+ width: `${element.getBoundingClientRect().width}px`,
645
+ "min-width": "0px",
646
+ "max-width": "none",
647
+ "box-sizing": "border-box",
648
+ transform: "none",
649
+ translate: "none",
650
+ rotate: "none",
651
+ scale: "none",
652
+ }))
653
+ clone.style.setProperty(name, value, "important");
654
+ const nodes = new Map<Node, Text>();
655
+ const sourceWalker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
656
+ const cloneWalker = document.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
657
+ for (
658
+ let node = sourceWalker.nextNode();
659
+ node;
660
+ node = sourceWalker.nextNode()
661
+ ) {
662
+ const copy = cloneWalker.nextNode();
663
+ if (!copy)
664
+ throw new NativeSourceCapabilityError(
665
+ "source-webfont-layout",
666
+ "The rendered text clone has a different text structure.",
667
+ );
668
+ nodes.set(node, copy as Text);
669
+ }
670
+ document.body.append(clone);
671
+ const fail = (): never => {
672
+ throw new NativeSourceCapabilityError(
673
+ "source-webfont-style",
674
+ "The browser could not preserve transformed text while measuring its rendered lines.",
675
+ );
676
+ };
677
+ try {
678
+ if (
679
+ typeof element.innerText !== "string" ||
680
+ clone.innerText !== element.innerText
681
+ )
682
+ fail();
683
+ } catch (error) {
684
+ clone.remove();
685
+ throw error;
686
+ }
687
+ return {
688
+ read(node, start, end) {
689
+ const copy = nodes.get(node);
690
+ if (!copy?.parentNode || copy.textContent !== node.textContent)
691
+ return fail();
692
+ const value = copy.textContent ?? "";
693
+ const before = document.createTextNode(value.slice(0, start));
694
+ const after = document.createTextNode(value.slice(end));
695
+ const span = document.createElement("span");
696
+ span.style.cssText =
697
+ "all:inherit!important;display:inline!important;position:static!important;float:none!important;margin:0!important;padding:0!important;border:0!important;contain:none!important";
698
+ span.textContent = value.slice(start, end);
699
+ copy.replaceWith(before, span, after);
700
+ try {
701
+ if (
702
+ clone.innerText !== element.innerText ||
703
+ typeof span.innerText !== "string"
704
+ )
705
+ return fail();
706
+ return span.innerText;
707
+ } finally {
708
+ before.replaceWith(copy);
709
+ span.remove();
710
+ after.remove();
711
+ }
712
+ },
713
+ dispose: () => clone.remove(),
714
+ };
715
+ }
716
+
613
717
  export async function paintNativeTextFlow(
614
718
  element: HTMLElement,
615
719
  canvas: HTMLCanvasElement,
@@ -618,7 +722,7 @@ export async function paintNativeTextFlow(
618
722
  captureRoi?: { density: number; pixelOffset: { x: number; y: number } },
619
723
  ): Promise<void> {
620
724
  if (!isPureTextFlow(element))
621
- throw new NativeSourceError(
725
+ throw new NativeSourceCapabilityError(
622
726
  "source-webfont-layout",
623
727
  "Canvas text capture needs a text-only flow without embedded media.",
624
728
  );
@@ -640,7 +744,7 @@ export async function paintNativeTextFlow(
640
744
  Math.abs(matrix.c) > 1e-5 ||
641
745
  Math.abs(matrix.d - 1) > 1e-5
642
746
  )
643
- throw new NativeSourceError(
747
+ throw new NativeSourceCapabilityError(
644
748
  "source-webfont-transform-unsupported",
645
749
  "Viewport Range measurements cannot capture transformed webfont text in local coordinates.",
646
750
  );
@@ -663,96 +767,102 @@ export async function paintNativeTextFlow(
663
767
  if (captureRoi)
664
768
  context.translate(captureRoi.pixelOffset.x, captureRoi.pixelOffset.y);
665
769
  context.scale(scaleX, scaleY);
666
- for (let node = walker.nextNode(); node; node = walker.nextNode()) {
667
- const value = node.textContent ?? "";
668
- if (!value.trim()) continue;
669
- if (/[\u200d\uFE0F\p{Mark}]/u.test(value))
670
- throw new NativeSourceError(
671
- "source-webfont-grapheme",
672
- "Complex joined glyphs need browser-shaped text capture.",
673
- );
674
- const parent = node.parentElement;
675
- if (!parent || parent.closest("[data-an-native-canvas]")) continue;
676
- const style = getComputedStyle(parent);
677
- if (
678
- style.writingMode !== "horizontal-tb" ||
679
- style.direction !== "ltr" ||
680
- style.textTransform !== "none" ||
681
- style.textDecorationLine !== "none" ||
682
- style.textShadow !== "none" ||
683
- style.fontVariantCaps !== "normal"
684
- )
685
- throw new NativeSourceError(
686
- "source-webfont-style",
687
- "This text style needs browser-shaped glyph capture.",
688
- );
689
- const family = style.fontFamily;
690
- const face = [...document.fonts].find((font) =>
691
- family.split(",").some(
692
- (name) =>
693
- name
694
- .trim()
695
- .replace(/^['"]|['"]$/g, "")
696
- .toLowerCase() ===
697
- font.family.replace(/^['"]|['"]$/g, "").toLowerCase(),
698
- ),
699
- );
700
- if (face && face.status !== "loaded")
701
- throw new NativeSourceError(
702
- "source-webfont-pending",
703
- `Font ${face.family} is not loaded for Canvas2D capture.`,
704
- );
705
- context.font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
706
- context.fontKerning = "normal";
707
- context.letterSpacing = style.letterSpacing;
708
- context.textBaseline = "alphabetic";
709
- // guard:allow-raw-color — text masks encode opaque coverage, independent of the editor theme.
710
- context.fillStyle = mask
711
- ? OPAQUE_COVERAGE_COLOR
712
- : (glyphColors?.get(parent) ?? style.color);
713
- const segments: { index: number; segment: string }[] = [];
714
- for (let index = 0; index < value.length; ) {
715
- const segment = String.fromCodePoint(value.codePointAt(index)!);
716
- segments.push({ index, segment });
717
- index += segment.length;
718
- }
719
- let lineStart = 0;
720
- let lineTop = Number.NaN;
721
- let lineRect: DOMRect | null = null;
722
- const drawLine = (end: number): void => {
723
- if (!lineRect || end <= lineStart) return;
724
- const text = value.slice(lineStart, end);
725
- const metric = context.measureText(text);
726
- const ascent =
727
- metric.actualBoundingBoxAscent ||
728
- Number.parseFloat(style.fontSize) * 0.8;
729
- const descent =
730
- metric.actualBoundingBoxDescent ||
731
- Number.parseFloat(style.fontSize) * 0.2;
732
- const baseline =
733
- lineRect.top - bounds.top + (lineRect.height + ascent - descent) / 2;
734
- context.fillText(text, lineRect.left - bounds.left, baseline);
735
- };
736
- for (const segment of segments) {
737
- range.setStart(node, segment.index);
738
- range.setEnd(node, segment.index + segment.segment.length);
739
- const rect = range.getBoundingClientRect();
740
- if (!rect.width && !rect.height) continue;
741
- if (lineRect && Math.abs(rect.top - lineTop) > 1) {
742
- drawLine(segment.index);
743
- lineStart = segment.index;
744
- lineRect = null;
770
+ let rendered: ReturnType<typeof nativeRenderedTextFlow> | undefined;
771
+ try {
772
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
773
+ const value = node.textContent ?? "";
774
+ if (!value.trim()) continue;
775
+ if (/[\u200d\uFE0F\p{Mark}]/u.test(value))
776
+ throw new NativeSourceCapabilityError(
777
+ "source-webfont-grapheme",
778
+ "Complex joined glyphs need browser-shaped text capture.",
779
+ );
780
+ const parent = node.parentElement;
781
+ if (!parent || parent.closest("[data-an-native-canvas]")) continue;
782
+ const style = getComputedStyle(parent);
783
+ if (
784
+ style.writingMode !== "horizontal-tb" ||
785
+ style.direction !== "ltr" ||
786
+ style.textDecorationLine !== "none" ||
787
+ style.textShadow !== "none" ||
788
+ style.fontVariantCaps !== "normal"
789
+ )
790
+ throw new NativeSourceCapabilityError(
791
+ "source-webfont-style",
792
+ "This text style needs browser-shaped glyph capture.",
793
+ );
794
+ const renderedText = (start: number, end: number): string =>
795
+ style.textTransform === "none"
796
+ ? value.slice(start, end)
797
+ : (rendered ??= nativeRenderedTextFlow(element)).read(
798
+ node,
799
+ start,
800
+ end,
801
+ );
802
+ const font = `${style.fontStyle} ${style.fontWeight} ${style.fontSize} ${style.fontFamily}`;
803
+ context.font = font;
804
+ if (
805
+ document.fonts &&
806
+ (typeof document.fonts.check !== "function" ||
807
+ !document.fonts.check(font, renderedText(0, value.length)))
808
+ )
809
+ throw new NativeSourceError(
810
+ "source-webfont-pending",
811
+ "The font for the rendered glyphs is not loaded for Canvas2D capture.",
812
+ );
813
+ context.fontKerning = "normal";
814
+ context.letterSpacing = style.letterSpacing;
815
+ context.textBaseline = "alphabetic";
816
+ // guard:allow-raw-color — text masks encode opaque coverage, independent of the editor theme.
817
+ context.fillStyle = mask
818
+ ? OPAQUE_COVERAGE_COLOR
819
+ : (glyphColors?.get(parent) ?? style.color);
820
+ const segments: { index: number; segment: string }[] = [];
821
+ for (let index = 0; index < value.length; ) {
822
+ const segment = String.fromCodePoint(value.codePointAt(index)!);
823
+ segments.push({ index, segment });
824
+ index += segment.length;
745
825
  }
746
- if (!lineRect) {
747
- lineRect = rect;
748
- lineTop = rect.top;
749
- lineStart = segment.index;
826
+ let lineStart = 0;
827
+ let lineTop = Number.NaN;
828
+ let lineRect: DOMRect | null = null;
829
+ const drawLine = (end: number): void => {
830
+ if (!lineRect || end <= lineStart) return;
831
+ const text = renderedText(lineStart, end);
832
+ const metric = context.measureText(text);
833
+ const ascent =
834
+ metric.actualBoundingBoxAscent ||
835
+ Number.parseFloat(style.fontSize) * 0.8;
836
+ const descent =
837
+ metric.actualBoundingBoxDescent ||
838
+ Number.parseFloat(style.fontSize) * 0.2;
839
+ const baseline =
840
+ lineRect.top - bounds.top + (lineRect.height + ascent - descent) / 2;
841
+ context.fillText(text, lineRect.left - bounds.left, baseline);
842
+ };
843
+ for (const segment of segments) {
844
+ range.setStart(node, segment.index);
845
+ range.setEnd(node, segment.index + segment.segment.length);
846
+ const rect = range.getBoundingClientRect();
847
+ if (!rect.width && !rect.height) continue;
848
+ if (lineRect && Math.abs(rect.top - lineTop) > 1) {
849
+ drawLine(segment.index);
850
+ lineStart = segment.index;
851
+ lineRect = null;
852
+ }
853
+ if (!lineRect) {
854
+ lineRect = rect;
855
+ lineTop = rect.top;
856
+ lineStart = segment.index;
857
+ }
750
858
  }
859
+ drawLine(value.length);
751
860
  }
752
- drawLine(value.length);
861
+ } finally {
862
+ rendered?.dispose();
863
+ context.restore();
864
+ range.detach();
753
865
  }
754
- context.restore();
755
- range.detach();
756
866
  }
757
867
 
758
868
  function preserveTextFlowStyles(
@@ -797,6 +907,12 @@ function hasVisibleBoxPaint(style: CSSStyleDeclaration): boolean {
797
907
  // guard:allow-raw-color — this compares the browser's computed transparent pixel value.
798
908
  (style.backgroundColor !== "rgba(0, 0, 0, 0)" &&
799
909
  style.backgroundColor !== "transparent") ||
910
+ hasVisibleBoxDecoration(style)
911
+ );
912
+ }
913
+
914
+ function hasVisibleBoxDecoration(style: CSSStyleDeclaration): boolean {
915
+ return (
800
916
  style.boxShadow !== "none" ||
801
917
  (style.outlineStyle &&
802
918
  style.outlineStyle !== "none" &&
@@ -1818,7 +1934,7 @@ export function nativeTargetOverflowInsets(
1818
1934
  throw new NativeSourceError(`source-${overflow.reason}`, overflow.detail);
1819
1935
  if (isNativeSvgRoot(target)) {
1820
1936
  if (overflow.edge === "none")
1821
- throw new NativeSourceError(
1937
+ throw new NativeSourceCapabilityError(
1822
1938
  "source-svg-overflow-unsupported",
1823
1939
  "An SVG root needs a bounded vector viewport clip.",
1824
1940
  );
@@ -2594,7 +2710,7 @@ function cloneOwnPaint(
2594
2710
  isNativeHtmlTag(child, "canvas") ||
2595
2711
  isNativeHtmlTag(child, "video")
2596
2712
  ) {
2597
- throw new NativeSourceError(
2713
+ throw new NativeSourceCapabilityError(
2598
2714
  "source-inline-media",
2599
2715
  "Inline media mixed into a text flow needs a separate source path.",
2600
2716
  );
@@ -2603,7 +2719,7 @@ function cloneOwnPaint(
2603
2719
  continue;
2604
2720
  }
2605
2721
  if (ownText)
2606
- throw new NativeSourceError(
2722
+ throw new NativeSourceCapabilityError(
2607
2723
  "source-mixed-flow",
2608
2724
  "Block children mixed with direct text cannot be split without changing text layout.",
2609
2725
  );
@@ -3235,7 +3351,7 @@ async function rasterOwnPaint(
3235
3351
  nativeTextFlowUsesDocumentFont(element);
3236
3352
  if (canvasText) {
3237
3353
  if (!isPureTextFlow(element))
3238
- throw new NativeSourceError(
3354
+ throw new NativeSourceCapabilityError(
3239
3355
  "source-webfont-layout",
3240
3356
  "Webfont text mixed with independent child paint needs a separate source path.",
3241
3357
  );
@@ -3470,7 +3586,7 @@ export class CachedLeaf<T extends HTMLElement | SVGSVGElement = HTMLElement> {
3470
3586
  density: number,
3471
3587
  ) => Promise<void> = async (element, canvas, density) => {
3472
3588
  if (!isNativeHtmlElement(element))
3473
- throw new NativeSourceError(
3589
+ throw new NativeSourceCapabilityError(
3474
3590
  "source-inline-svg",
3475
3591
  "An SVG source needs its vector raster path.",
3476
3592
  );
@@ -4191,25 +4307,42 @@ class SceneProvider implements NativeSceneProvider {
4191
4307
 
4192
4308
  needsContinuousFrames(): boolean {
4193
4309
  const scope = this.placement === "backdrop" ? document.body : this.target;
4310
+ const captured = (element: Element): boolean => {
4311
+ for (
4312
+ let current: Element | null = element;
4313
+ current;
4314
+ current = current.parentElement
4315
+ )
4316
+ if (this.omit(current)) return false;
4317
+ return true;
4318
+ };
4194
4319
  if (
4195
- (isNativeHtmlTag(scope, "video") && !scope.paused && !scope.ended) ||
4196
- [...scope.querySelectorAll<HTMLVideoElement>("video")].some(
4197
- (video) => !video.paused && !video.ended,
4198
- ) ||
4199
- (isNativeHtmlTag(scope, "canvas") &&
4200
- !scope.hasAttribute("data-an-native-canvas") &&
4201
- !scope.hasAttribute("data-an-native-presentation")) ||
4202
- scope.querySelector(
4203
- "canvas:not([data-an-native-canvas]):not([data-an-native-presentation])",
4320
+ [scope, ...scope.querySelectorAll("video,canvas")].some(
4321
+ (element) =>
4322
+ captured(element) &&
4323
+ ((isNativeHtmlTag(element, "video") &&
4324
+ !element.paused &&
4325
+ !element.ended) ||
4326
+ (isNativeHtmlTag(element, "canvas") &&
4327
+ !element.hasAttribute("data-an-native-canvas") &&
4328
+ !element.hasAttribute("data-an-native-presentation"))),
4204
4329
  )
4205
4330
  )
4206
4331
  return true;
4207
- const animated = (element: Element): boolean =>
4208
- typeof element.getAnimations === "function" &&
4209
- element
4210
- .getAnimations({ subtree: true })
4211
- .some((animation) => animation.playState === "running");
4212
- if (animated(scope)) return true;
4332
+ const running = (animation: Animation): boolean => {
4333
+ if (animation.playState !== "running") return false;
4334
+ try {
4335
+ const target = (animation.effect as KeyframeEffect | null)?.target;
4336
+ return !isNativeElement(target) || captured(target);
4337
+ } catch {
4338
+ return true;
4339
+ }
4340
+ };
4341
+ if (
4342
+ typeof scope.getAnimations === "function" &&
4343
+ scope.getAnimations({ subtree: true }).some(running)
4344
+ )
4345
+ return true;
4213
4346
  for (
4214
4347
  let parent = scope.parentElement;
4215
4348
  parent;
@@ -4217,9 +4350,7 @@ class SceneProvider implements NativeSceneProvider {
4217
4350
  )
4218
4351
  if (
4219
4352
  typeof parent.getAnimations === "function" &&
4220
- parent
4221
- .getAnimations({ subtree: false })
4222
- .some((animation) => animation.playState === "running")
4353
+ parent.getAnimations({ subtree: false }).some(running)
4223
4354
  )
4224
4355
  return true;
4225
4356
  return false;
@@ -4757,7 +4888,7 @@ class SceneProvider implements NativeSceneProvider {
4757
4888
  !(current === record.node && isNativeSvgRoot(current)) &&
4758
4889
  hasUnsupportedSourceMask(style.clipPath, style.maskImage)
4759
4890
  )
4760
- throw new NativeSourceError(
4891
+ throw new NativeSourceCapabilityError(
4761
4892
  "source-mask-unsupported",
4762
4893
  "CSS clip-path and masks need an explicit source mask compositor.",
4763
4894
  );
@@ -4841,7 +4972,7 @@ class SceneProvider implements NativeSceneProvider {
4841
4972
  "[data-an-native-canvas],[data-an-native-fill-instance],[data-an-native-layer-instance]",
4842
4973
  )
4843
4974
  )
4844
- throw new NativeSourceError(
4975
+ throw new NativeSourceCapabilityError(
4845
4976
  "source-svg-group-native-composition-unsupported",
4846
4977
  "An SVG vector group with nested native effects needs a composed vector source.",
4847
4978
  );
@@ -4873,7 +5004,7 @@ class SceneProvider implements NativeSceneProvider {
4873
5004
  return [];
4874
5005
  assertLeafSupported(svg);
4875
5006
  if (!supportsNativeOutput2D(style))
4876
- throw new NativeSourceError(
5007
+ throw new NativeSourceCapabilityError(
4877
5008
  "source-svg-transform-unsupported",
4878
5009
  "An SVG vector group has unsupported child geometry.",
4879
5010
  );
@@ -4909,7 +5040,7 @@ class SceneProvider implements NativeSceneProvider {
4909
5040
  const style = getComputedStyle(element);
4910
5041
  if (style.display === "none" || style.visibility === "hidden") return;
4911
5042
  if (isNativeSvgRoot(element) && element.hasAttribute("transform"))
4912
- throw new NativeSourceError(
5043
+ throw new NativeSourceCapabilityError(
4913
5044
  "source-svg-transform-unsupported",
4914
5045
  "An SVG root transform needs an affine vector source path.",
4915
5046
  );
@@ -4924,7 +5055,7 @@ class SceneProvider implements NativeSceneProvider {
4924
5055
  ) {
4925
5056
  if (metrics) metrics.recordDirectStyleReads += 1;
4926
5057
  if (!supportsNativeOutput2D(getComputedStyle(ancestor)))
4927
- throw new NativeSourceError(
5058
+ throw new NativeSourceCapabilityError(
4928
5059
  "source-svg-transform-unsupported",
4929
5060
  "An SVG source has a transformed HTML layout boundary.",
4930
5061
  );
@@ -5093,7 +5224,7 @@ class SceneProvider implements NativeSceneProvider {
5093
5224
  allowVisibleTargetOverflow,
5094
5225
  );
5095
5226
  if (isNativeSvgElement(element))
5096
- throw new NativeSourceError(
5227
+ throw new NativeSourceCapabilityError(
5097
5228
  "source-inline-svg",
5098
5229
  "An SVG fragment outside an SVG root cannot be captured.",
5099
5230
  );
@@ -5168,7 +5299,7 @@ class SceneProvider implements NativeSceneProvider {
5168
5299
  };
5169
5300
  const visible = intersect(box, sourceBounds);
5170
5301
  if (visible.width > 0 && visible.height > 0)
5171
- throw new NativeSourceError(
5302
+ throw new NativeSourceCapabilityError(
5172
5303
  "source-legacy-shader-unsupported",
5173
5304
  "A visible legacy WebGL shader has no synchronized texture and clock source for native composition.",
5174
5305
  );
@@ -5194,7 +5325,7 @@ class SceneProvider implements NativeSceneProvider {
5194
5325
  !isCanvasBackgroundOnly(element, style) &&
5195
5326
  (hasDirectText(element) || hasVisibleBoxPaint(style))
5196
5327
  )
5197
- throw new NativeSourceError(
5328
+ throw new NativeSourceCapabilityError(
5198
5329
  "source-empty-own-paint-unsupported",
5199
5330
  "A zero-size source has own paint that cannot be captured faithfully.",
5200
5331
  );
@@ -5678,8 +5809,12 @@ class SceneProvider implements NativeSceneProvider {
5678
5809
  overflow.detail,
5679
5810
  );
5680
5811
  if (overflow.edge !== "none") {
5681
- if (hasDirectText(element) || hasVisibleBoxPaint(style))
5682
- throw new NativeSourceError(
5812
+ if (
5813
+ hasDirectText(element) ||
5814
+ hasVisibleBoxDecoration(style) ||
5815
+ (overflow.outset > 0 && hasVisibleBoxPaint(style))
5816
+ )
5817
+ throw new NativeSourceCapabilityError(
5683
5818
  "source-empty-own-paint-unsupported",
5684
5819
  "A zero-size clipped source has own paint that cannot be captured faithfully.",
5685
5820
  );
@@ -5696,7 +5831,10 @@ class SceneProvider implements NativeSceneProvider {
5696
5831
  const layerInstance =
5697
5832
  (element !== this.target || this.targetCapture === "appearance") &&
5698
5833
  element.getAttribute("data-an-native-layer-instance");
5699
- const replacementInstance = layerInstance || fillInstance;
5834
+ const replacementInstance =
5835
+ initialSize && hasEmptySourceLayout(initialSize)
5836
+ ? null
5837
+ : layerInstance || fillInstance;
5700
5838
  if (replacementInstance) {
5701
5839
  const presentations = [...(element.parentElement?.children ?? [])].filter(
5702
5840
  (child): child is HTMLCanvasElement =>
@@ -6154,14 +6292,14 @@ class SceneProvider implements NativeSceneProvider {
6154
6292
  getComputedStyle(root).opacity !== "1" ||
6155
6293
  (source !== root && style.opacity !== "1")
6156
6294
  )
6157
- throw new NativeSourceError(
6295
+ throw new NativeSourceCapabilityError(
6158
6296
  "source-viewport-background-unsupported",
6159
6297
  "The CSS canvas background extends beyond the root box and needs a supported opaque solid source.",
6160
6298
  );
6161
6299
  const hasImage = style.backgroundImage !== "none";
6162
6300
  if (!hasImage && alpha === 0) return null;
6163
6301
  if (!supportedViewportBackgroundImage(style.backgroundImage))
6164
- throw new NativeSourceError(
6302
+ throw new NativeSourceCapabilityError(
6165
6303
  "source-viewport-background-image-unsupported",
6166
6304
  "The CSS canvas background uses an image type the native renderer cannot capture.",
6167
6305
  );
@@ -6671,7 +6809,7 @@ class SceneProvider implements NativeSceneProvider {
6671
6809
  if (isNativeSvgRoot(this.target)) return this.markReplayRead([]);
6672
6810
  if (isPureTextFlow(this.target)) return this.markReplayRead([]);
6673
6811
  if (hasDirectText(this.target))
6674
- throw new NativeSourceError(
6812
+ throw new NativeSourceCapabilityError(
6675
6813
  "fill-mixed-text-layout-unsupported",
6676
6814
  "Direct text mixed with independent child paint needs an ordered text source.",
6677
6815
  );