@agent-native/core 0.92.7 → 0.92.8
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/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
- package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/engine/builtin.ts +1 -1
- package/corpus/core/src/agent/model-config.ts +30 -30
- package/corpus/core/src/agent/production-agent.ts +129 -7
- package/corpus/core/src/cli/design-connect.ts +438 -83
- package/corpus/core/src/cli/recap.ts +2 -2
- package/corpus/core/src/client/AgentPanel.tsx +8 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
- package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
- package/corpus/core/src/client/index.ts +4 -0
- package/corpus/core/src/client/org/TeamPage.tsx +41 -26
- package/corpus/core/src/client/use-action.ts +113 -1
- package/corpus/core/src/db/client.ts +12 -2
- package/corpus/core/src/db/create-get-db.ts +4 -1
- package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
- package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
- package/corpus/core/src/server/transcribe-voice.ts +1 -1
- package/corpus/core/src/usage/store.ts +13 -4
- package/corpus/core/src/vite/client.ts +38 -1
- package/corpus/templates/analytics/AGENTS.md +3 -0
- package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
- package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
- package/corpus/templates/analytics/app/global.css +11 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/analytics/app/i18n-data.ts +20 -0
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
- package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
- package/corpus/templates/analytics/server/db/index.ts +6 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
- package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
- package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
- package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
- package/corpus/templates/content/app/i18n-data.ts +28 -0
- package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
- package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
- package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
- package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
- package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
- package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
- package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
- package/corpus/templates/design/actions/capture-design-state.ts +2 -2
- package/corpus/templates/design/actions/connect-localhost.ts +58 -12
- package/corpus/templates/design/actions/create-component.ts +2 -18
- package/corpus/templates/design/actions/create-design-branch.ts +38 -18
- package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
- package/corpus/templates/design/actions/delete-file.ts +19 -34
- package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
- package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
- package/corpus/templates/design/actions/export-pdf.ts +2 -1
- package/corpus/templates/design/actions/export-zip.ts +9 -3
- package/corpus/templates/design/actions/generate-design.ts +107 -70
- package/corpus/templates/design/actions/get-component-details.ts +2 -18
- package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
- package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
- package/corpus/templates/design/actions/get-design.ts +2 -1
- package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
- package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
- package/corpus/templates/design/actions/index-components.ts +2 -18
- package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
- package/corpus/templates/design/actions/list-local-files.ts +9 -2
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
- package/corpus/templates/design/actions/open-component-source.ts +2 -18
- package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
- package/corpus/templates/design/actions/present-design-variants.ts +70 -70
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
- package/corpus/templates/design/actions/read-local-file.ts +9 -2
- package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
- package/corpus/templates/design/actions/rename-screen.ts +425 -0
- package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
- package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
- package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
- package/corpus/templates/design/actions/update-design.ts +376 -61
- package/corpus/templates/design/actions/update-file.ts +455 -188
- package/corpus/templates/design/actions/write-local-file.ts +10 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
- package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
- package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
- package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
- package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
- package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
- package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
- package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
- package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
- package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
- package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
- package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
- package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
- package/corpus/templates/design/app/components/design/index.ts +2 -2
- package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
- package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
- package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
- package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
- package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
- package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
- package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
- package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
- package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
- package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
- package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
- package/corpus/templates/design/app/i18n-data.ts +223 -0
- package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
- package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
- package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
- package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
- package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
- package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
- package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
- package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
- package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
- package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
- package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
- package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
- package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
- package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
- package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
- package/corpus/templates/design/server/db/index.ts +2 -52
- package/corpus/templates/design/server/db/schema.ts +16 -0
- package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
- package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
- package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
- package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
- package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
- package/corpus/templates/design/server/plugins/db.ts +19 -0
- package/corpus/templates/design/server/source-workspace.ts +12 -13
- package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
- package/corpus/templates/design/shared/screen-rename.ts +42 -0
- package/corpus/templates/design/shared/source-mode.ts +84 -4
- package/dist/agent/engine/builder-engine.d.ts +1 -1
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builtin.js +1 -1
- package/dist/agent/engine/builtin.js.map +1 -1
- package/dist/agent/model-config.d.ts +9 -9
- package/dist/agent/model-config.d.ts.map +1 -1
- package/dist/agent/model-config.js +30 -30
- package/dist/agent/model-config.js.map +1 -1
- package/dist/agent/production-agent.d.ts +60 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +106 -7
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/design-connect.d.ts +30 -1
- package/dist/cli/design-connect.d.ts.map +1 -1
- package/dist/cli/design-connect.js +359 -75
- package/dist/cli/design-connect.js.map +1 -1
- package/dist/cli/recap.js +2 -2
- package/dist/cli/recap.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +6 -1
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +10 -5
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/composer/TiptapComposer.js +1 -1
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +6 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/use-action.d.ts +32 -0
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +68 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts +3 -0
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +7 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +2 -2
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
- package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/transcribe-voice.js +1 -1
- package/dist/server/transcribe-voice.js.map +1 -1
- package/dist/usage/store.d.ts.map +1 -1
- package/dist/usage/store.js +13 -4
- package/dist/usage/store.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +36 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
- package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
- package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
- package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
- package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
- package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
- package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
- package/docs/content/pr-visual-recap.mdx +3 -3
- package/docs/content/pure-agent-apps.mdx +22 -0
- package/package.json +2 -2
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
import { callAction } from "@agent-native/core/client";
|
|
2
|
+
import { sourceContentHash } from "@shared/source-workspace";
|
|
3
|
+
|
|
4
|
+
export type DesignSaveActionName =
|
|
5
|
+
| "update-file"
|
|
6
|
+
| "update-design"
|
|
7
|
+
| "apply-tweaks";
|
|
8
|
+
|
|
9
|
+
export interface DesignSaveOutboxEntry {
|
|
10
|
+
key: string;
|
|
11
|
+
designId: string;
|
|
12
|
+
/** Prevents a later signed-in user on the same browser from replaying it. */
|
|
13
|
+
actorScope: string;
|
|
14
|
+
actionName: DesignSaveActionName;
|
|
15
|
+
resourceId: string;
|
|
16
|
+
operationSource: string;
|
|
17
|
+
operationRevision: number;
|
|
18
|
+
payload: Record<string, unknown>;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DesignSaveOutboxStorage {
|
|
23
|
+
putLatest(entry: DesignSaveOutboxEntry): Promise<void>;
|
|
24
|
+
deleteIfRevision(entry: DesignSaveOutboxEntry): Promise<boolean>;
|
|
25
|
+
list(designId: string, actorScope: string): Promise<DesignSaveOutboxEntry[]>;
|
|
26
|
+
pruneOlderThan(updatedAt: number): Promise<number>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface DrainDesignSaveOutboxResult {
|
|
30
|
+
saved: DesignSaveOutboxEntry[];
|
|
31
|
+
failed: Array<{ entry: DesignSaveOutboxEntry; error: unknown }>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DATABASE_NAME = "agent-native-design-save-outbox";
|
|
35
|
+
const DATABASE_VERSION = 2;
|
|
36
|
+
const ENTRY_STORE = "entries";
|
|
37
|
+
const DESIGN_ID_INDEX = "by-design-id";
|
|
38
|
+
const UPDATED_AT_INDEX = "by-updated-at";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Failed/conflicted writes remain available for a month, but abandoned
|
|
42
|
+
* browser-tab sessions must not retain full HTML documents forever.
|
|
43
|
+
*/
|
|
44
|
+
export const DESIGN_SAVE_OUTBOX_RETENTION_MS = 30 * 24 * 60 * 60 * 1_000;
|
|
45
|
+
|
|
46
|
+
let databasePromise: Promise<IDBDatabase> | null = null;
|
|
47
|
+
|
|
48
|
+
function openDatabase(): Promise<IDBDatabase> {
|
|
49
|
+
if (typeof indexedDB === "undefined") {
|
|
50
|
+
return Promise.reject(
|
|
51
|
+
new Error("IndexedDB is unavailable; design changes cannot be journaled"),
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (databasePromise) return databasePromise;
|
|
55
|
+
|
|
56
|
+
databasePromise = new Promise((resolve, reject) => {
|
|
57
|
+
const request = indexedDB.open(DATABASE_NAME, DATABASE_VERSION);
|
|
58
|
+
request.onupgradeneeded = () => {
|
|
59
|
+
const database = request.result;
|
|
60
|
+
const store = database.objectStoreNames.contains(ENTRY_STORE)
|
|
61
|
+
? request.transaction?.objectStore(ENTRY_STORE)
|
|
62
|
+
: database.createObjectStore(ENTRY_STORE, { keyPath: "key" });
|
|
63
|
+
if (store && !store.indexNames.contains(DESIGN_ID_INDEX)) {
|
|
64
|
+
store.createIndex(DESIGN_ID_INDEX, "designId", { unique: false });
|
|
65
|
+
}
|
|
66
|
+
if (store && !store.indexNames.contains(UPDATED_AT_INDEX)) {
|
|
67
|
+
store.createIndex(UPDATED_AT_INDEX, "updatedAt", { unique: false });
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
request.onsuccess = () => {
|
|
71
|
+
const database = request.result;
|
|
72
|
+
database.onversionchange = () => {
|
|
73
|
+
database.close();
|
|
74
|
+
databasePromise = null;
|
|
75
|
+
};
|
|
76
|
+
resolve(database);
|
|
77
|
+
};
|
|
78
|
+
request.onerror = () => {
|
|
79
|
+
databasePromise = null;
|
|
80
|
+
reject(
|
|
81
|
+
request.error ?? new Error("Failed to open the design save outbox"),
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
request.onblocked = () => {
|
|
85
|
+
databasePromise = null;
|
|
86
|
+
reject(new Error("The design save outbox database upgrade was blocked"));
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
return databasePromise;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function transactionError(
|
|
94
|
+
transaction: IDBTransaction,
|
|
95
|
+
fallback: string,
|
|
96
|
+
): Error {
|
|
97
|
+
return transaction.error ?? new Error(fallback);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const indexedDbStorage: DesignSaveOutboxStorage = {
|
|
101
|
+
async putLatest(entry) {
|
|
102
|
+
const database = await openDatabase();
|
|
103
|
+
await new Promise<void>((resolve, reject) => {
|
|
104
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite");
|
|
105
|
+
const store = transaction.objectStore(ENTRY_STORE);
|
|
106
|
+
const request = store.get(entry.key);
|
|
107
|
+
request.onsuccess = () => {
|
|
108
|
+
const current = request.result as DesignSaveOutboxEntry | undefined;
|
|
109
|
+
const shouldReplace =
|
|
110
|
+
!current ||
|
|
111
|
+
entry.operationRevision > current.operationRevision ||
|
|
112
|
+
(entry.operationRevision === current.operationRevision &&
|
|
113
|
+
entry.updatedAt >= current.updatedAt);
|
|
114
|
+
if (shouldReplace) store.put(entry);
|
|
115
|
+
};
|
|
116
|
+
request.onerror = () => transaction.abort();
|
|
117
|
+
transaction.oncomplete = () => resolve();
|
|
118
|
+
transaction.onerror = () =>
|
|
119
|
+
reject(
|
|
120
|
+
transactionError(transaction, "Failed to journal design changes"),
|
|
121
|
+
);
|
|
122
|
+
transaction.onabort = () =>
|
|
123
|
+
reject(
|
|
124
|
+
transactionError(transaction, "Failed to journal design changes"),
|
|
125
|
+
);
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
async deleteIfRevision(entry) {
|
|
130
|
+
const database = await openDatabase();
|
|
131
|
+
return await new Promise<boolean>((resolve, reject) => {
|
|
132
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite");
|
|
133
|
+
const store = transaction.objectStore(ENTRY_STORE);
|
|
134
|
+
const request = store.get(entry.key);
|
|
135
|
+
let deleted = false;
|
|
136
|
+
request.onsuccess = () => {
|
|
137
|
+
const current = request.result as DesignSaveOutboxEntry | undefined;
|
|
138
|
+
if (
|
|
139
|
+
current?.operationSource === entry.operationSource &&
|
|
140
|
+
current.operationRevision === entry.operationRevision
|
|
141
|
+
) {
|
|
142
|
+
store.delete(entry.key);
|
|
143
|
+
deleted = true;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
request.onerror = () => transaction.abort();
|
|
147
|
+
transaction.oncomplete = () => resolve(deleted);
|
|
148
|
+
transaction.onerror = () =>
|
|
149
|
+
reject(
|
|
150
|
+
transactionError(transaction, "Failed to acknowledge design changes"),
|
|
151
|
+
);
|
|
152
|
+
transaction.onabort = () =>
|
|
153
|
+
reject(
|
|
154
|
+
transactionError(transaction, "Failed to acknowledge design changes"),
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
async list(designId, actorScope) {
|
|
160
|
+
const database = await openDatabase();
|
|
161
|
+
return await new Promise<DesignSaveOutboxEntry[]>((resolve, reject) => {
|
|
162
|
+
const transaction = database.transaction(ENTRY_STORE, "readonly");
|
|
163
|
+
const request = transaction
|
|
164
|
+
.objectStore(ENTRY_STORE)
|
|
165
|
+
.index(DESIGN_ID_INDEX)
|
|
166
|
+
.getAll(designId);
|
|
167
|
+
let entries: DesignSaveOutboxEntry[] = [];
|
|
168
|
+
request.onsuccess = () => {
|
|
169
|
+
entries = (request.result as DesignSaveOutboxEntry[])
|
|
170
|
+
.filter((entry) => entry.actorScope === actorScope)
|
|
171
|
+
.sort((left, right) => left.updatedAt - right.updatedAt);
|
|
172
|
+
};
|
|
173
|
+
request.onerror = () => transaction.abort();
|
|
174
|
+
transaction.oncomplete = () => resolve(entries);
|
|
175
|
+
transaction.onerror = () =>
|
|
176
|
+
reject(transactionError(transaction, "Failed to read design changes"));
|
|
177
|
+
transaction.onabort = () =>
|
|
178
|
+
reject(transactionError(transaction, "Failed to read design changes"));
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
async pruneOlderThan(updatedAt) {
|
|
183
|
+
const database = await openDatabase();
|
|
184
|
+
return await new Promise<number>((resolve, reject) => {
|
|
185
|
+
const transaction = database.transaction(ENTRY_STORE, "readwrite");
|
|
186
|
+
const request = transaction
|
|
187
|
+
.objectStore(ENTRY_STORE)
|
|
188
|
+
.index(UPDATED_AT_INDEX)
|
|
189
|
+
.openCursor(IDBKeyRange.upperBound(updatedAt, true));
|
|
190
|
+
let pruned = 0;
|
|
191
|
+
request.onsuccess = () => {
|
|
192
|
+
const cursor = request.result;
|
|
193
|
+
if (!cursor) return;
|
|
194
|
+
cursor.delete();
|
|
195
|
+
pruned += 1;
|
|
196
|
+
cursor.continue();
|
|
197
|
+
};
|
|
198
|
+
request.onerror = () => transaction.abort();
|
|
199
|
+
transaction.oncomplete = () => resolve(pruned);
|
|
200
|
+
transaction.onerror = () =>
|
|
201
|
+
reject(
|
|
202
|
+
transactionError(transaction, "Failed to prune old design changes"),
|
|
203
|
+
);
|
|
204
|
+
transaction.onabort = () =>
|
|
205
|
+
reject(
|
|
206
|
+
transactionError(transaction, "Failed to prune old design changes"),
|
|
207
|
+
);
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export function designSaveOutboxKey(input: {
|
|
213
|
+
designId: string;
|
|
214
|
+
actorScope: string;
|
|
215
|
+
actionName: DesignSaveActionName;
|
|
216
|
+
resourceId: string;
|
|
217
|
+
operationSource: string;
|
|
218
|
+
}): string {
|
|
219
|
+
return [
|
|
220
|
+
input.designId,
|
|
221
|
+
input.actorScope,
|
|
222
|
+
input.actionName,
|
|
223
|
+
input.resourceId,
|
|
224
|
+
input.operationSource,
|
|
225
|
+
]
|
|
226
|
+
.map(encodeURIComponent)
|
|
227
|
+
.join(":");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function createDesignSaveOutboxEntry(input: {
|
|
231
|
+
designId: string;
|
|
232
|
+
actorScope: string;
|
|
233
|
+
actionName: DesignSaveActionName;
|
|
234
|
+
resourceId: string;
|
|
235
|
+
operationSource: string;
|
|
236
|
+
operationRevision: number;
|
|
237
|
+
payload: Record<string, unknown>;
|
|
238
|
+
updatedAt?: number;
|
|
239
|
+
}): DesignSaveOutboxEntry {
|
|
240
|
+
return {
|
|
241
|
+
...input,
|
|
242
|
+
key: designSaveOutboxKey(input),
|
|
243
|
+
updatedAt: input.updatedAt ?? Date.now(),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export async function journalDesignSaveOutboxEntry(
|
|
248
|
+
entry: DesignSaveOutboxEntry,
|
|
249
|
+
storage: DesignSaveOutboxStorage = indexedDbStorage,
|
|
250
|
+
): Promise<void> {
|
|
251
|
+
await storage.putLatest(entry);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export async function acknowledgeDesignSaveOutboxEntry(
|
|
255
|
+
entry: DesignSaveOutboxEntry,
|
|
256
|
+
storage: DesignSaveOutboxStorage = indexedDbStorage,
|
|
257
|
+
): Promise<boolean> {
|
|
258
|
+
return await storage.deleteIfRevision(entry);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function discardDesignSaveOutboxEntry(
|
|
262
|
+
entry: DesignSaveOutboxEntry,
|
|
263
|
+
storage: DesignSaveOutboxStorage = indexedDbStorage,
|
|
264
|
+
): Promise<boolean> {
|
|
265
|
+
return await storage.deleteIfRevision(entry);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** A versioned update-file no-op is safe to acknowledge only when the server
|
|
269
|
+
* proves the exact requested content is already persisted. A higher revision
|
|
270
|
+
* from the same source also reports skippedStaleOperation, but its version hash
|
|
271
|
+
* belongs to different content and must leave this entry conflict-retained. */
|
|
272
|
+
export function updateFileResultPersistedContent(
|
|
273
|
+
actionResult: unknown,
|
|
274
|
+
expectedContent: string,
|
|
275
|
+
): boolean {
|
|
276
|
+
if (!actionResult || typeof actionResult !== "object") return true;
|
|
277
|
+
const result = actionResult as {
|
|
278
|
+
skippedStaleMirror?: unknown;
|
|
279
|
+
skippedStaleOperation?: unknown;
|
|
280
|
+
versionHash?: unknown;
|
|
281
|
+
};
|
|
282
|
+
if (result.skippedStaleMirror) return false;
|
|
283
|
+
if (!result.skippedStaleOperation) return true;
|
|
284
|
+
return result.versionHash === sourceContentHash(expectedContent);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function drainEntries(
|
|
288
|
+
designId: string,
|
|
289
|
+
actorScope: string,
|
|
290
|
+
invokeAction: (
|
|
291
|
+
actionName: DesignSaveActionName,
|
|
292
|
+
payload: Record<string, unknown>,
|
|
293
|
+
) => Promise<unknown>,
|
|
294
|
+
storage: DesignSaveOutboxStorage,
|
|
295
|
+
): Promise<DrainDesignSaveOutboxResult> {
|
|
296
|
+
const result: DrainDesignSaveOutboxResult = { saved: [], failed: [] };
|
|
297
|
+
for (const entry of await storage.list(designId, actorScope)) {
|
|
298
|
+
try {
|
|
299
|
+
if (
|
|
300
|
+
entry.actionName === "update-file" &&
|
|
301
|
+
entry.payload.syncCollab === false &&
|
|
302
|
+
typeof entry.payload.expectedVersionHash !== "string"
|
|
303
|
+
) {
|
|
304
|
+
const conflict = new Error(
|
|
305
|
+
"A live-collaboration mirror cannot be replayed without a known base version",
|
|
306
|
+
);
|
|
307
|
+
(conflict as Error & { status?: number }).status = 409;
|
|
308
|
+
throw conflict;
|
|
309
|
+
}
|
|
310
|
+
if (
|
|
311
|
+
entry.actionName === "apply-tweaks" &&
|
|
312
|
+
typeof entry.payload.expectedSelectionsHash !== "string"
|
|
313
|
+
) {
|
|
314
|
+
const conflict = new Error(
|
|
315
|
+
"A full tweak snapshot cannot be replayed without a known base version",
|
|
316
|
+
);
|
|
317
|
+
(conflict as Error & { status?: number }).status = 409;
|
|
318
|
+
throw conflict;
|
|
319
|
+
}
|
|
320
|
+
const actionResult = await invokeAction(entry.actionName, entry.payload);
|
|
321
|
+
if (
|
|
322
|
+
entry.actionName === "update-file" &&
|
|
323
|
+
typeof entry.payload.content === "string" &&
|
|
324
|
+
!updateFileResultPersistedContent(actionResult, entry.payload.content)
|
|
325
|
+
) {
|
|
326
|
+
const conflict = new Error(
|
|
327
|
+
"The saved file changed elsewhere before this edit could be replayed",
|
|
328
|
+
);
|
|
329
|
+
(conflict as Error & { status?: number }).status = 409;
|
|
330
|
+
throw conflict;
|
|
331
|
+
}
|
|
332
|
+
await storage.deleteIfRevision(entry);
|
|
333
|
+
result.saved.push(entry);
|
|
334
|
+
} catch (error) {
|
|
335
|
+
result.failed.push({ entry, error });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return result;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export async function drainDesignSaveOutbox(options: {
|
|
342
|
+
designId: string;
|
|
343
|
+
actorScope: string;
|
|
344
|
+
invokeAction?: (
|
|
345
|
+
actionName: DesignSaveActionName,
|
|
346
|
+
payload: Record<string, unknown>,
|
|
347
|
+
) => Promise<unknown>;
|
|
348
|
+
storage?: DesignSaveOutboxStorage;
|
|
349
|
+
}): Promise<DrainDesignSaveOutboxResult> {
|
|
350
|
+
const invokeAction =
|
|
351
|
+
options.invokeAction ??
|
|
352
|
+
((actionName: DesignSaveActionName, payload: Record<string, unknown>) =>
|
|
353
|
+
(
|
|
354
|
+
callAction as (
|
|
355
|
+
name: string,
|
|
356
|
+
params: Record<string, unknown>,
|
|
357
|
+
) => Promise<unknown>
|
|
358
|
+
)(actionName, payload));
|
|
359
|
+
const storage = options.storage ?? indexedDbStorage;
|
|
360
|
+
await storage.pruneOlderThan(Date.now() - DESIGN_SAVE_OUTBOX_RETENTION_MS);
|
|
361
|
+
const run = () =>
|
|
362
|
+
drainEntries(options.designId, options.actorScope, invokeAction, storage);
|
|
363
|
+
const lockManager =
|
|
364
|
+
typeof navigator === "undefined"
|
|
365
|
+
? undefined
|
|
366
|
+
: (
|
|
367
|
+
navigator as Navigator & {
|
|
368
|
+
locks?: {
|
|
369
|
+
request<T>(name: string, callback: () => Promise<T>): Promise<T>;
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
).locks;
|
|
373
|
+
|
|
374
|
+
if (!lockManager) return await run();
|
|
375
|
+
return await lockManager.request(
|
|
376
|
+
`agent-native-design-save-outbox:${options.designId}`,
|
|
377
|
+
run,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export type PngClipboardErrorCode = "unsupported" | "blocked" | "write-failed";
|
|
2
|
+
|
|
3
|
+
export class PngClipboardError extends Error {
|
|
4
|
+
readonly code: PngClipboardErrorCode;
|
|
5
|
+
|
|
6
|
+
constructor(code: PngClipboardErrorCode, cause?: unknown) {
|
|
7
|
+
super(`PNG clipboard ${code}`);
|
|
8
|
+
this.name = "PngClipboardError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
if (cause !== undefined) {
|
|
11
|
+
Object.defineProperty(this, "cause", {
|
|
12
|
+
configurable: true,
|
|
13
|
+
value: cause,
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
type ClipboardWriter = Pick<Clipboard, "write">;
|
|
20
|
+
|
|
21
|
+
type ClipboardItemConstructor = {
|
|
22
|
+
new (items: Record<string, Blob | Promise<Blob>>): ClipboardItem;
|
|
23
|
+
supports?: (type: string) => boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface PngClipboardEnvironment {
|
|
27
|
+
clipboard?: ClipboardWriter | null;
|
|
28
|
+
ClipboardItem?: ClipboardItemConstructor | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultPngClipboardEnvironment(): PngClipboardEnvironment {
|
|
32
|
+
return {
|
|
33
|
+
clipboard:
|
|
34
|
+
typeof navigator === "undefined" ? null : (navigator.clipboard ?? null),
|
|
35
|
+
ClipboardItem:
|
|
36
|
+
typeof globalThis.ClipboardItem === "undefined"
|
|
37
|
+
? null
|
|
38
|
+
: globalThis.ClipboardItem,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function canCopyPngToClipboard(
|
|
43
|
+
environment: PngClipboardEnvironment = defaultPngClipboardEnvironment(),
|
|
44
|
+
): boolean {
|
|
45
|
+
const ClipboardItemCtor = environment.ClipboardItem;
|
|
46
|
+
if (!environment.clipboard?.write || !ClipboardItemCtor) return false;
|
|
47
|
+
try {
|
|
48
|
+
return (
|
|
49
|
+
typeof ClipboardItemCtor.supports !== "function" ||
|
|
50
|
+
ClipboardItemCtor.supports("image/png")
|
|
51
|
+
);
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function classifyClipboardWriteError(error: unknown): PngClipboardErrorCode {
|
|
58
|
+
const name =
|
|
59
|
+
error && typeof error === "object" && "name" in error
|
|
60
|
+
? String(error.name)
|
|
61
|
+
: "";
|
|
62
|
+
if (name === "NotAllowedError" || name === "SecurityError") {
|
|
63
|
+
return "blocked";
|
|
64
|
+
}
|
|
65
|
+
if (name === "NotSupportedError" || error instanceof TypeError) {
|
|
66
|
+
return "unsupported";
|
|
67
|
+
}
|
|
68
|
+
return "write-failed";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Write a real `image/png` ClipboardItem while the initiating pointer/key event
|
|
73
|
+
* still owns transient clipboard activation. Passing the pending render promise
|
|
74
|
+
* to ClipboardItem is deliberate: waiting for html2canvas before calling
|
|
75
|
+
* `clipboard.write()` causes Safari and hardened Chromium configurations to
|
|
76
|
+
* expire the user gesture.
|
|
77
|
+
*/
|
|
78
|
+
export async function copyPngPromiseToClipboard(
|
|
79
|
+
pngBlob: Promise<Blob>,
|
|
80
|
+
environment: PngClipboardEnvironment = defaultPngClipboardEnvironment(),
|
|
81
|
+
): Promise<void> {
|
|
82
|
+
const ClipboardItemCtor = environment.ClipboardItem;
|
|
83
|
+
const clipboard = environment.clipboard;
|
|
84
|
+
if (!canCopyPngToClipboard(environment) || !ClipboardItemCtor || !clipboard) {
|
|
85
|
+
throw new PngClipboardError("unsupported");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let renderError: unknown;
|
|
89
|
+
const trackedPngBlob = pngBlob.catch((error: unknown) => {
|
|
90
|
+
renderError = error;
|
|
91
|
+
throw error;
|
|
92
|
+
});
|
|
93
|
+
// ClipboardItem owns the promise in browsers. This additional rejection
|
|
94
|
+
// observer prevents an unhandled rejection in test doubles or browsers that
|
|
95
|
+
// reject the write before reading the promised representation.
|
|
96
|
+
void trackedPngBlob.catch(() => undefined);
|
|
97
|
+
|
|
98
|
+
let item: ClipboardItem;
|
|
99
|
+
try {
|
|
100
|
+
item = new ClipboardItemCtor({ "image/png": trackedPngBlob });
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new PngClipboardError("unsupported", error);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await clipboard.write([item]);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
// Preserve the renderer's real error instead of misreporting it as a
|
|
109
|
+
// clipboard permission failure when the promised representation rejected.
|
|
110
|
+
if (renderError !== undefined) throw renderError;
|
|
111
|
+
throw new PngClipboardError(classifyClipboardWriteError(error), error);
|
|
112
|
+
}
|
|
113
|
+
}
|