@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.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +61 -1
- package/corpus/templates/analytics/AGENTS.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +1 -0
- package/corpus/templates/analytics/actions/get-session-replay-summary.ts +9 -65
- package/corpus/templates/analytics/actions/list-event-catalog.ts +1 -1
- package/corpus/templates/analytics/actions/list-session-outputs.ts +40 -0
- package/corpus/templates/analytics/actions/list-session-recordings.ts +17 -4
- package/corpus/templates/analytics/actions/view-screen.ts +29 -3
- package/corpus/templates/analytics/app/i18n/en-US.ts +27 -0
- package/corpus/templates/analytics/app/pages/sessions/EventCatalogPage.tsx +18 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +3 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionOutputs.tsx +197 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsTriagePage.tsx +114 -4
- package/corpus/templates/analytics/changelog/2026-10-15-sessions-can-find-people-who-made-a-slides-deck-but-didn-t-p.md +6 -0
- package/corpus/templates/analytics/changelog/2026-10-16-the-activation-funnel-counts-visitors-who-decline-analytics-.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +67 -0
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +28 -22
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +36 -1
- package/corpus/templates/analytics/server/lib/output-outcomes.ts +712 -0
- package/corpus/templates/analytics/server/lib/replay-origins.ts +33 -0
- package/corpus/templates/analytics/server/lib/session-event-index.ts +20 -0
- package/corpus/templates/analytics/server/lib/session-performance.ts +6 -40
- package/corpus/templates/analytics/server/lib/session-recording-associations.ts +30 -1
- package/corpus/templates/analytics/server/lib/session-replay-triage.ts +99 -0
- package/corpus/templates/analytics/server/lib/session-replay.ts +45 -31
- package/corpus/templates/analytics/server/lib/sessions-triage-lab.ts +8 -2
- package/corpus/templates/analytics/server/plugins/db.ts +46 -0
- package/corpus/templates/analytics/shared/output-use.ts +205 -0
- package/corpus/templates/analytics/shared/session-events.ts +6 -0
- package/corpus/templates/calendar/CHANGELOG.md +6 -0
- package/corpus/templates/calendar/actions/create-event.ts +2 -4
- package/corpus/templates/calendar/actions/update-event.ts +35 -6
- package/corpus/templates/calendar/changelog/2026-10-21-solo-events-avoid-notification-approval.md +6 -0
- package/corpus/templates/community/qr/package.json +1 -1
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +6 -3
- package/corpus/templates/content/CHANGELOG.md +8 -0
- package/corpus/templates/content/actions/_browser-document-save-attempt.ts +23 -1
- package/corpus/templates/content/actions/_builder-cms-blob-custody.ts +467 -0
- package/corpus/templates/content/actions/_builder-cms-execution-lease.ts +17 -0
- package/corpus/templates/content/actions/_builder-cms-intent-lookup.ts +5 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +881 -21
- package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +28 -2
- package/corpus/templates/content/actions/_builder-cms-write-client.ts +109 -11
- package/corpus/templates/content/actions/_builder-docs-client.ts +1 -0
- package/corpus/templates/content/actions/_builder-private-blob.test-fixture.ts +90 -0
- package/corpus/templates/content/actions/_builder-source-execution-guard.ts +48 -0
- package/corpus/templates/content/actions/_content-save-outcomes.ts +5 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +290 -8
- package/corpus/templates/content/actions/_database-utils.ts +93 -7
- package/corpus/templates/content/actions/_database-view-setup.ts +21 -2
- package/corpus/templates/content/actions/_delete-content-space.ts +8 -1
- package/corpus/templates/content/actions/_github-source-freshness.ts +30 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -6
- package/corpus/templates/content/actions/cancel-prepared-builder-source-update.ts +110 -26
- package/corpus/templates/content/actions/change-content-database-source-role.ts +51 -7
- package/corpus/templates/content/actions/delete-document.ts +41 -3
- package/corpus/templates/content/actions/disconnect-content-database-source.ts +68 -2
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +33 -2
- package/corpus/templates/content/actions/execute-builder-source-execution.ts +900 -140
- package/corpus/templates/content/actions/get-document.ts +2 -0
- package/corpus/templates/content/actions/permanently-delete-document.ts +6 -0
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +155 -13
- package/corpus/templates/content/actions/prepare-builder-source-review.ts +299 -73
- package/corpus/templates/content/actions/set-live-body-switch.ts +45 -0
- package/corpus/templates/content/actions/update-document.ts +139 -20
- package/corpus/templates/content/actions/validate-builder-source-execution.ts +135 -26
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +23 -0
- package/corpus/templates/content/app/components/editor/database/GitHubSourceStatusLine.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database/ListItemLayout.tsx +32 -8
- package/corpus/templates/content/app/components/editor/database/ListItemRow.tsx +27 -2
- package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +40 -2
- package/corpus/templates/content/app/hooks/use-documents.ts +14 -0
- package/corpus/templates/content/app/i18n-data.ts +1 -0
- package/corpus/templates/content/changelog/2026-10-17-builder-content-avoids-retrying-a-write-when-builder-accepte.md +6 -0
- package/corpus/templates/content/changelog/2026-10-18-link-a-new-collection-to-a-github-folder-from-its-sources.md +1 -1
- package/corpus/templates/content/changelog/2026-10-20-builder-content-sync-keeps-read-and-write-status-accurate-wh.md +6 -0
- package/corpus/templates/content/changelog/2026-10-21-list-rows-can-show-each-pages-first-lines.md +6 -0
- package/corpus/templates/content/changelog/2026-10-22-a-collection-linked-to-a-big-github-folder-fills-faster-and-p.md +6 -0
- package/corpus/templates/content/docs/product/capabilities/content.source.adapters.md +29 -0
- package/corpus/templates/content/parity/matrix.md +42 -42
- package/corpus/templates/content/parity/matrix.ts +3 -1
- package/corpus/templates/content/server/db/schema.ts +20 -0
- package/corpus/templates/content/server/lib/content-trash-purge.ts +15 -1
- package/corpus/templates/content/server/lib/live-body-writer.ts +138 -26
- package/corpus/templates/content/server/live-copy/binding.ts +31 -0
- package/corpus/templates/content/server/live-copy/bounded-task.d.ts +6 -0
- package/corpus/templates/content/server/live-copy/exec-db.ts +1 -0
- package/corpus/templates/content/server/live-copy/holds.ts +59 -0
- package/corpus/templates/content/server/live-copy/persist.ts +656 -0
- package/corpus/templates/content/server/live-copy/switch.ts +136 -0
- package/corpus/templates/content/server/live-copy/task-names.ts +4 -0
- package/corpus/templates/content/server/live-copy/tasks.ts +183 -0
- package/corpus/templates/content/server/live-copy/write-cause.ts +85 -0
- package/corpus/templates/content/server/live-copy/write.ts +85 -0
- package/corpus/templates/content/server/plugins/builder-private-payload-cleanup.ts +22 -0
- package/corpus/templates/content/server/plugins/collab.ts +3 -0
- package/corpus/templates/content/server/plugins/db.ts +28 -0
- package/corpus/templates/content/shared/api.ts +30 -1
- package/corpus/templates/content/shared/database-item-layout.ts +54 -22
- package/corpus/templates/content/shared/document-intent-merge.ts +16 -9
- package/corpus/templates/content/shared/og-description.ts +258 -36
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +5 -1
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +25 -4
- package/corpus/templates/design/CHANGELOG.md +2 -0
- package/corpus/templates/design/actions/get-getting-started.ts +53 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +10 -0
- package/corpus/templates/design/app/components/design/bridge/native-shader-runtime.bridge.ts +132 -79
- package/corpus/templates/design/app/components/design/bridge/native-source-provider.ts +268 -130
- package/corpus/templates/design/app/components/design/bridge/native-worker-scene-host.ts +122 -42
- package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +41 -4
- package/corpus/templates/design/app/components/design/design-canvas/text-content-change-result.ts +46 -12
- package/corpus/templates/design/app/components/home/DesignHomeLibrary.tsx +15 -8
- package/corpus/templates/design/app/components/layout/GettingStarted.tsx +50 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +3 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateGrid.tsx +163 -0
- package/corpus/templates/design/app/components/templates/DesignTemplateLibrary.tsx +65 -78
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +6 -2
- package/corpus/templates/design/app/global.css +6 -0
- package/corpus/templates/design/app/i18n/en-US.ts +17 -0
- package/corpus/templates/design/app/lib/canvas-load-outcome.ts +147 -0
- package/corpus/templates/design/app/lib/canvas-outcome-tracking.ts +531 -0
- package/corpus/templates/design/app/lib/canvas-style-kind.ts +50 -0
- package/corpus/templates/design/app/pages/Index.tsx +10 -3
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +86 -29
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +38 -3
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-core.ts +9 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-editor-files-and-saving.ts +2 -0
- package/corpus/templates/design/changelog/2026-10-21-a-getting-started-checklist-at-the-bottom-of-the-sidebar-tra.md +6 -0
- package/corpus/templates/design/changelog/2026-10-21-templates-now-open-a-read-only-preview-from-the-card-and-the.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-collapsed-shader-layers-no-longer-interrupt-other-shaders-on.md +6 -0
- package/corpus/templates/design/changelog/2026-10-22-shader-fills-keep-rendering-when-other-screen-content-cannot.md +6 -0
- package/corpus/templates/design/scripts/design-export-validation/export-baseline.json +24 -0
- package/corpus/templates/design/server/lib/getting-started.ts +267 -0
- package/corpus/templates/design/server/plugins/getting-started.ts +5 -0
- package/corpus/templates/design/shared/design-template-presets/analytics-dashboard.ts +127 -0
- package/corpus/templates/design/shared/design-template-presets/design-studio.ts +98 -0
- package/corpus/templates/design/shared/design-template-presets/messaging-app.ts +101 -0
- package/corpus/templates/design/shared/design-template-presets/project-tasks.ts +120 -0
- package/corpus/templates/design/shared/design-template-presets.ts +8 -0
- package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/slides/CHANGELOG.md +6 -0
- package/corpus/templates/slides/actions/delete-deck.ts +7 -0
- package/corpus/templates/slides/changelog/2026-10-21-connected-deck-deletion-requires-approval.md +6 -0
- package/corpus/templates/slides/scripts/edit-fidelity/client-action.ts +61 -0
- package/corpus/templates/slides/scripts/edit-fidelity/run.ts +10 -0
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
style
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
"
|
|
703
|
-
|
|
704
|
-
)
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
:
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
segments
|
|
717
|
-
index
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
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
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
.
|
|
4211
|
-
|
|
4212
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
5682
|
-
|
|
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 =
|
|
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
|
|
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
|
|
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
|
|
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
|
);
|