@agent-native/core 0.165.4 → 0.167.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/secrets/SKILL.md +5 -1
- package/corpus/templates/analytics/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +93 -56
- package/corpus/templates/analytics/app/i18n-data.ts +23 -20
- package/corpus/templates/analytics/app/pages/Settings.tsx +0 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +0 -1
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +2 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +0 -1
- package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +0 -32
- package/corpus/templates/analytics/server/lib/session-replay.ts +1 -5
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/assets/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/assets/actions/navigate.ts +2 -2
- package/corpus/templates/assets/actions/save-generated-image.ts +0 -5
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +2 -262
- package/corpus/templates/assets/app/routes/library.tsx +1 -1
- package/corpus/templates/assets/app/routes/settings.tsx +0 -3
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/brain/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/brain/actions/ask-brain.ts +0 -1
- package/corpus/templates/brain/actions/navigate.ts +2 -2
- package/corpus/templates/brain/app/routes/sources.tsx +0 -4
- package/corpus/templates/brain/server/lib/capture-sanitization.ts +0 -11
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/calendar/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +0 -20
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +0 -1
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/chat/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/chat/actions/navigate.ts +2 -2
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/clips/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/clips/CHANGELOG.md +1 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +16 -4
- package/corpus/templates/clips/actions/navigate.ts +2 -2
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +0 -1
- package/corpus/templates/clips/app/components/library/space-dialogs.tsx +0 -1
- package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +0 -1
- package/corpus/templates/clips/app/components/player/clips-share-trigger.tsx +17 -0
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +3 -3
- package/corpus/templates/clips/app/components/player/video-player.tsx +2 -2
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +0 -1
- package/corpus/templates/clips/app/components/settings/ai-setup-section.tsx +0 -1
- package/corpus/templates/clips/app/global.css +31 -0
- package/corpus/templates/clips/app/hooks/use-playback-position.ts +0 -2
- package/corpus/templates/clips/app/lib/download-release-channel.ts +10 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +1 -3
- package/corpus/templates/clips/app/routes/download.tsx +67 -44
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -12
- package/corpus/templates/clips/app/routes/record.tsx +6 -6
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +26 -29
- package/corpus/templates/clips/changelog/2026-08-20-clip-viewer-share-actions.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-20-preparing-recording-countdown-overlay.md +1 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +0 -13
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +0 -1
- package/corpus/templates/clips/chrome-extension/src/popup.ts +0 -20
- package/corpus/templates/clips/desktop/src/app.tsx +6 -18
- package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +0 -1
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +0 -8
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/content/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/content/actions/_database-source-utils.ts +0 -3
- package/corpus/templates/content/actions/attach-content-database-source.ts +1 -4
- package/corpus/templates/content/actions/navigate.ts +2 -2
- package/corpus/templates/content/actions/prepare-builder-source-execution.ts +1 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +0 -6
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +0 -580
- package/corpus/templates/content/app/components/editor/database/FormView.tsx +2 -2
- package/corpus/templates/content/app/components/editor/database/filter-sort.ts +1 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +0 -23
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +0 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +8 -304
- package/corpus/templates/content/app/hooks/use-documents.ts +0 -1
- package/corpus/templates/content/shared/api.ts +0 -1
- package/corpus/templates/content/shared/builder-mdx.ts +0 -1
- package/corpus/templates/content/shared/nfm.ts +1 -7
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/crm/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/crm/actions/merge-crm-records.ts +0 -4
- package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +0 -1
- package/corpus/templates/crm/server/crm/hubspot-adapter.ts +0 -1
- package/corpus/templates/crm/server/crm/salesforce-adapter.ts +0 -1
- package/corpus/templates/crm/server/db/crm-store.ts +1 -1
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/design/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1 -14
- package/corpus/templates/design/AGENTS.md +2 -1
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +1 -5
- package/corpus/templates/design/actions/generate-design.ts +0 -1
- package/corpus/templates/design/actions/generate-screens.ts +5 -2
- package/corpus/templates/design/actions/go-to-main-component.ts +2 -2
- package/corpus/templates/design/actions/list-design-systems.ts +10 -0
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/open-component-source.ts +4 -4
- package/corpus/templates/design/actions/present-design-variants.ts +1 -2
- package/corpus/templates/design/actions/run-design-audit.ts +0 -22
- package/corpus/templates/design/actions/show-design-questions.ts +5 -2
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +19 -8
- package/corpus/templates/design/app/components/design/EditPanel.tsx +11 -183
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +21 -4
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -5
- package/corpus/templates/design/app/components/design/bridge/codegen.ts +1 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +10 -19
- package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +1 -1
- package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +13 -3
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +38 -32
- package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +1 -4
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +0 -4
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +15 -6
- package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +2 -2
- package/corpus/templates/design/app/components/visual-editor/NodeRewriteProposal.tsx +0 -1
- package/corpus/templates/design/app/components/visual-editor/ReviewCanvasPins.tsx +1 -1
- package/corpus/templates/design/app/i18n-data.ts +122 -0
- package/corpus/templates/design/app/lib/clipboard-content-lineage.ts +11 -1
- package/corpus/templates/design/app/pages/DesignEditor.tsx +50 -254
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +163 -8
- package/corpus/templates/design/app/pages/Index.tsx +17 -0
- package/corpus/templates/design/app/pages/design-editor/canvas-primitive-insert.ts +44 -32
- package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +10 -0
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +49 -3
- package/corpus/templates/design/app/pages/design-editor/clone-idrefs.ts +31 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +28 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +3 -3
- package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +44 -14
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +16 -5
- package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +142 -17
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +25 -9
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/measure-child-rects.ts +83 -0
- package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +66 -0
- package/corpus/templates/design/server/routes/[...page].get.ts +0 -1
- package/corpus/templates/design/shared/code-layer.ts +120 -70
- package/corpus/templates/design/shared/design-surface-index.ts +0 -1
- package/corpus/templates/design/shared/pen-path.ts +0 -4
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/dispatch/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/dispatch/app/routes/integrations.tsx +0 -109
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/factory/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/factory/actions/navigate.ts +2 -2
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +0 -13
- package/corpus/templates/factory/app/routes/factory.tsx +0 -1
- package/corpus/templates/factory/server/factory-graph/store.ts +0 -1
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/forms/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/macros/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +0 -1
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/mail/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/mail/app/components/email/EmailList.tsx +0 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +2 -2
- package/corpus/templates/mail/shared/markdown.ts +3 -0
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/plan/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/slides/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/slides/actions/add-slide.ts +0 -1
- package/corpus/templates/slides/actions/list-decks.ts +1 -1
- package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +1 -4
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +0 -1
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +1 -6
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +5 -1
- package/corpus/templates/tasks/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/corpus/templates/tasks/app/components/layout/Layout.tsx +0 -1
- package/corpus/templates/tasks/app/hooks/use-visible-task-fields.ts +1 -4
- package/corpus/templates/tasks/server/custom-fields/store.ts +1 -6
- package/corpus/templates/tasks/shared/list-screen-snapshot.ts +2 -2
- package/dist/agent/engine/registry.js +27 -14
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/production-agent.d.ts +19 -6
- package/dist/agent/production-agent.js +57 -16
- package/dist/agent/types.d.ts +3 -0
- package/dist/client/EnvironmentBadge.d.ts +1 -0
- package/dist/client/EnvironmentBadge.js +7 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.js +248 -0
- package/dist/deploy/build.js +12 -1
- package/dist/index.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-chat/plugin-options.d.ts +3 -2
- package/dist/server/agent-chat-plugin.js +5 -2
- package/dist/server/index.d.ts +1 -1
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +5 -1
- package/dist/templates/chat/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/dist/templates/chat/actions/navigate.ts +2 -2
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +5 -1
- package/dist/templates/default/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/dist/vite/agent-native-config-loader.d.ts +1 -0
- package/dist/vite/agent-native-config-loader.js +3 -3
- package/dist/vite/agents-bundle-plugin.d.ts +1 -1
- package/dist/vite/agents-bundle-plugin.js +13 -1
- package/dist/vite/client.js +14 -14
- package/docs/content/agent-native-config.mdx +62 -11
- package/docs/content/agent-surfaces.mdx +6 -0
- package/docs/content/environment-variables.mdx +5 -2
- package/package.json +3 -3
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +5 -1
- package/src/templates/chat/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/src/templates/chat/actions/navigate.ts +2 -2
- package/src/templates/default/.agents/skills/secrets/SKILL.md +5 -1
- package/src/templates/default/.agents/skills/workspace-conventions/SKILL.md +4 -1
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +5 -1
- package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +5 -1
- package/src/templates/workspace-core/.agents/skills/workspace-conventions/SKILL.md +4 -1
|
@@ -506,11 +506,39 @@ export function camelCaseCssProperty(property: string): string {
|
|
|
506
506
|
);
|
|
507
507
|
}
|
|
508
508
|
|
|
509
|
+
// The inspector reads font longhands, and a class-less element's computed
|
|
510
|
+
// styles are discarded by refreshedComputedStyles — so an authored `font`
|
|
511
|
+
// shorthand is the only place a family/size/weight can come from.
|
|
512
|
+
function fontShorthandLonghands(value: string): Record<string, string> {
|
|
513
|
+
const match =
|
|
514
|
+
/^\s*(.*?)\s*(-?[\d.]+(?:px|r?em|%|pt)|x{1,2}-(?:small|large)|small|medium|large)\s*(?:\/\s*([^\s]+)\s*)?(\S.*)$/.exec(
|
|
515
|
+
value,
|
|
516
|
+
);
|
|
517
|
+
if (!match) return {};
|
|
518
|
+
const [, leading, size, lineHeight, family] = match;
|
|
519
|
+
const longhands: Record<string, string> = {
|
|
520
|
+
fontSize: size,
|
|
521
|
+
fontFamily: family.trim(),
|
|
522
|
+
};
|
|
523
|
+
if (lineHeight) longhands.lineHeight = lineHeight;
|
|
524
|
+
for (const token of leading.split(/\s+/).filter(Boolean)) {
|
|
525
|
+
if (/^(?:normal|italic|oblique)$/.test(token)) longhands.fontStyle = token;
|
|
526
|
+
else if (/^(?:\d{3}|bold|bolder|lighter)$/.test(token)) {
|
|
527
|
+
longhands.fontWeight = token;
|
|
528
|
+
} else if (/^small-caps$/.test(token)) longhands.fontVariant = token;
|
|
529
|
+
}
|
|
530
|
+
return longhands;
|
|
531
|
+
}
|
|
532
|
+
|
|
509
533
|
export function cssStyleAliases(
|
|
510
534
|
styles: Record<string, string>,
|
|
511
535
|
): Record<string, string> {
|
|
512
536
|
const result: Record<string, string> = {};
|
|
513
537
|
for (const [property, value] of Object.entries(styles)) {
|
|
538
|
+
// Expanded in source order so a later explicit longhand still wins.
|
|
539
|
+
if (property === "font") {
|
|
540
|
+
Object.assign(result, fontShorthandLonghands(value));
|
|
541
|
+
}
|
|
514
542
|
result[property] = value;
|
|
515
543
|
if (property.includes("-")) {
|
|
516
544
|
result[camelCaseCssProperty(property)] = value;
|
|
@@ -282,7 +282,7 @@ export function runAddAutoLayout({
|
|
|
282
282
|
}
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
|
-
applyLocalContentUpdate(nextContent, {
|
|
285
|
+
applyLocalContentUpdate(nextContent, { forcePreviewFullDocument: true });
|
|
286
286
|
if (wrapperId) {
|
|
287
287
|
const taggedProjection = buildCodeLayerProjection(nextContent);
|
|
288
288
|
const wrapperNode = taggedProjection.nodes.find(
|
|
@@ -338,7 +338,7 @@ export function runAddAutoLayout({
|
|
|
338
338
|
if (styled.result.status === "applied") nextContent = styled.content;
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
-
applyLocalContentUpdate(nextContent, {
|
|
341
|
+
applyLocalContentUpdate(nextContent, { forcePreviewFullDocument: true });
|
|
342
342
|
if (wrapperId) {
|
|
343
343
|
const projectionAfter = buildCodeLayerProjection(nextContent);
|
|
344
344
|
const wrapperNode = projectionAfter.nodes.find(
|
|
@@ -412,5 +412,5 @@ export function runAddAutoLayout({
|
|
|
412
412
|
nextContent = paddingPatch.content;
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
|
-
applyLocalContentUpdate(nextContent, {
|
|
415
|
+
applyLocalContentUpdate(nextContent, { forcePreviewFullDocument: true });
|
|
416
416
|
}
|
|
@@ -66,8 +66,11 @@ export function runAlignSelection(
|
|
|
66
66
|
}: AlignSelectionArgs,
|
|
67
67
|
edge: DesignHotkeyAlignEdge,
|
|
68
68
|
) {
|
|
69
|
+
const abandon = (reason: string, data?: Record<string, unknown>) => {
|
|
70
|
+
trace("structure", "align-abandoned", { reason, edge, ...data });
|
|
71
|
+
};
|
|
69
72
|
trace("structure", "align", { layers: selectedLayerIdsState.length });
|
|
70
|
-
if (!canEditDesign) return;
|
|
73
|
+
if (!canEditDesign) return abandon("read-only");
|
|
71
74
|
|
|
72
75
|
// Overview, 2+ selected SCREENS: align each screen's frame geometry to
|
|
73
76
|
// the selection's combined bounding box through the same
|
|
@@ -82,7 +85,11 @@ export function runAlignSelection(
|
|
|
82
85
|
fileIds: files.map((file) => file.id),
|
|
83
86
|
})
|
|
84
87
|
) {
|
|
85
|
-
if (overviewSelectedScreenIds.length < 2)
|
|
88
|
+
if (overviewSelectedScreenIds.length < 2) {
|
|
89
|
+
return abandon("overview: needs 2+ screens", {
|
|
90
|
+
selected: overviewSelectedScreenIds.length,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
86
93
|
const before = getCanvasFrameGeometry(designDataJsonRef.current);
|
|
87
94
|
const screenRects: AlignableRect[] = [];
|
|
88
95
|
overviewSelectedScreenIds.forEach((screenId) => {
|
|
@@ -100,7 +107,10 @@ export function runAlignSelection(
|
|
|
100
107
|
: boardFileId === screenId
|
|
101
108
|
? boardFrameGeometry
|
|
102
109
|
: undefined;
|
|
103
|
-
if (!fallbackGeometry)
|
|
110
|
+
if (!fallbackGeometry) {
|
|
111
|
+
abandon("overview: screen has no geometry", { screenId });
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
104
114
|
const geometry = { ...fallbackGeometry, ...before[screenId] };
|
|
105
115
|
screenRects.push({
|
|
106
116
|
id: screenId,
|
|
@@ -110,9 +120,13 @@ export function runAlignSelection(
|
|
|
110
120
|
height: geometry.height,
|
|
111
121
|
});
|
|
112
122
|
});
|
|
113
|
-
if (screenRects.length < 2)
|
|
123
|
+
if (screenRects.length < 2) {
|
|
124
|
+
return abandon("overview: fewer than 2 measurable screens", {
|
|
125
|
+
measured: screenRects.length,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
114
128
|
const bounds = getFrameGroupBounds(screenRects);
|
|
115
|
-
if (!bounds) return;
|
|
129
|
+
if (!bounds) return abandon("no combined bounds for selection");
|
|
116
130
|
const positions = computeAlignedPositions(
|
|
117
131
|
screenRects,
|
|
118
132
|
{
|
|
@@ -123,7 +137,9 @@ export function runAlignSelection(
|
|
|
123
137
|
},
|
|
124
138
|
edge,
|
|
125
139
|
);
|
|
126
|
-
if (positions.size === 0)
|
|
140
|
+
if (positions.size === 0) {
|
|
141
|
+
return abandon("already aligned; nothing to move", { edge });
|
|
142
|
+
}
|
|
127
143
|
const after = cloneCanvasFrameGeometry(before);
|
|
128
144
|
positions.forEach((position, screenId) => {
|
|
129
145
|
after[screenId] = { ...after[screenId]!, ...position };
|
|
@@ -133,22 +149,28 @@ export function runAlignSelection(
|
|
|
133
149
|
}
|
|
134
150
|
|
|
135
151
|
// Single-screen mode: in-screen DOM-node layers.
|
|
136
|
-
if (!activeFile) return;
|
|
152
|
+
if (!activeFile) return abandon("no active file");
|
|
137
153
|
const baseContent = getFreshActiveContent();
|
|
138
154
|
const nodeIds = getActiveFileSelectedNodeIds(baseContent);
|
|
139
|
-
if (nodeIds.length === 0)
|
|
155
|
+
if (nodeIds.length === 0) {
|
|
156
|
+
return abandon("selection has no nodes in the active file", {
|
|
157
|
+
selectedLayerIds: selectedLayerIdsState.length,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
140
160
|
const projection = buildCodeLayerProjection(baseContent);
|
|
141
161
|
const nodesById = new Map(projection.nodes.map((node) => [node.id, node]));
|
|
142
162
|
const selectedNodes = nodeIds
|
|
143
163
|
.map((nodeId) => nodesById.get(nodeId))
|
|
144
164
|
.filter((node): node is CodeLayerNode => Boolean(node));
|
|
145
|
-
if (selectedNodes.length === 0)
|
|
165
|
+
if (selectedNodes.length === 0) {
|
|
166
|
+
return abandon("selected ids resolve to no projection nodes", { nodeIds });
|
|
167
|
+
}
|
|
146
168
|
const selectedRects = selectedNodes.map(rectFromCodeLayerNode);
|
|
147
169
|
|
|
148
170
|
if (selectedRects.length >= 2) {
|
|
149
171
|
// Multi-selection: align to the selection's own combined bbox.
|
|
150
172
|
const bounds = getFrameGroupBounds(selectedRects);
|
|
151
|
-
if (!bounds) return;
|
|
173
|
+
if (!bounds) return abandon("no combined bounds for selection");
|
|
152
174
|
const positions = computeAlignedPositions(
|
|
153
175
|
selectedRects,
|
|
154
176
|
{
|
|
@@ -159,7 +181,9 @@ export function runAlignSelection(
|
|
|
159
181
|
},
|
|
160
182
|
edge,
|
|
161
183
|
);
|
|
162
|
-
if (positions.size === 0)
|
|
184
|
+
if (positions.size === 0) {
|
|
185
|
+
return abandon("already aligned; nothing to move", { edge });
|
|
186
|
+
}
|
|
163
187
|
commitNodePositions(baseContent, positions);
|
|
164
188
|
return;
|
|
165
189
|
}
|
|
@@ -169,15 +193,21 @@ export function runAlignSelection(
|
|
|
169
193
|
// Figma (there's nothing to align a lone top-level frame against).
|
|
170
194
|
const soleNode = selectedNodes[0]!;
|
|
171
195
|
const parentId = soleNode.parentId;
|
|
172
|
-
if (!parentId)
|
|
196
|
+
if (!parentId) {
|
|
197
|
+
return abandon("single selection has no parent to align against", {
|
|
198
|
+
nodeId: soleNode.id,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
173
201
|
const parentNode = nodesById.get(parentId);
|
|
174
|
-
if (!parentNode) return;
|
|
202
|
+
if (!parentNode) return abandon("parent id not in projection", { parentId });
|
|
175
203
|
const parentRect = rectFromCodeLayerNode(parentNode);
|
|
176
204
|
const positions = computeAlignedPositions(
|
|
177
205
|
[selectedRects[0]!],
|
|
178
206
|
{ x: 0, y: 0, width: parentRect.width, height: parentRect.height },
|
|
179
207
|
edge,
|
|
180
208
|
);
|
|
181
|
-
if (positions.size === 0)
|
|
209
|
+
if (positions.size === 0) {
|
|
210
|
+
return abandon("already aligned; nothing to move", { edge });
|
|
211
|
+
}
|
|
182
212
|
commitNodePositions(baseContent, positions);
|
|
183
213
|
}
|
package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts
CHANGED
|
@@ -104,6 +104,12 @@ export function runApplyLocalContentUpdate(
|
|
|
104
104
|
nextContent: string,
|
|
105
105
|
options: {
|
|
106
106
|
refreshPreview?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Requires the caller to own the preview: it already patched the live
|
|
109
|
+
* iframe, or its target is not the rendered document. Set on a
|
|
110
|
+
* host-computed edit to the active screen and the canvas renders stale
|
|
111
|
+
* content until a reload.
|
|
112
|
+
*/
|
|
107
113
|
skipPreview?: boolean;
|
|
108
114
|
forcePreviewFullDocument?: boolean;
|
|
109
115
|
immediateSave?: boolean;
|
|
@@ -258,7 +264,7 @@ export function runApplyLocalContentUpdate(
|
|
|
258
264
|
// couldn't run (bridge not registered for this surface yet, or an
|
|
259
265
|
// explicit forceRefresh request).
|
|
260
266
|
const replacedPreview = options.skipPreview
|
|
261
|
-
? "
|
|
267
|
+
? "skipped-caller-owns-preview"
|
|
262
268
|
: forceRefresh
|
|
263
269
|
? "unavailable"
|
|
264
270
|
: replacePreviewContent(
|
|
@@ -268,10 +274,15 @@ export function runApplyLocalContentUpdate(
|
|
|
268
274
|
? { forceFullDocument: true }
|
|
269
275
|
: undefined,
|
|
270
276
|
);
|
|
271
|
-
|
|
272
|
-
forceRefresh ||
|
|
273
|
-
|
|
274
|
-
|
|
277
|
+
const renderFallback =
|
|
278
|
+
forceRefresh || previewContentReplaceNeedsRenderFallback(replacedPreview);
|
|
279
|
+
trace("persist", "preview", {
|
|
280
|
+
outcome: replacedPreview,
|
|
281
|
+
forceFullDocument: options.forcePreviewFullDocument === true,
|
|
282
|
+
renderFallback,
|
|
283
|
+
bytes: nextContent.length,
|
|
284
|
+
});
|
|
285
|
+
if (renderFallback) {
|
|
275
286
|
setContentRenderRevision((revision) => revision + 1);
|
|
276
287
|
}
|
|
277
288
|
if (ydoc && isSynced) {
|
|
@@ -200,7 +200,7 @@ export function runChangeSelectedZIndex(
|
|
|
200
200
|
zIndexFallback();
|
|
201
201
|
return;
|
|
202
202
|
}
|
|
203
|
-
applyLocalContentUpdate(patch.content, {
|
|
203
|
+
applyLocalContentUpdate(patch.content, { forcePreviewFullDocument: true });
|
|
204
204
|
const movedNode = patch.projection.nodes.find(
|
|
205
205
|
(n) =>
|
|
206
206
|
n.dataAttributes["data-agent-native-node-id"] === targetId ||
|
|
@@ -111,7 +111,7 @@ export function runFrameSelection({
|
|
|
111
111
|
patch.result.wrapperNodeId,
|
|
112
112
|
) ?? wrapperNode;
|
|
113
113
|
}
|
|
114
|
-
applyLocalContentUpdate(nextContent, {
|
|
114
|
+
applyLocalContentUpdate(nextContent, { forcePreviewFullDocument: true });
|
|
115
115
|
if (wrapperNode) {
|
|
116
116
|
setSelectedLayerIdsState([wrapperNode.id]);
|
|
117
117
|
setSelectedElement(elementInfoFromCodeLayerNode(wrapperNode));
|
|
@@ -111,7 +111,7 @@ export function runGroupSelection({
|
|
|
111
111
|
);
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
|
-
applyLocalContentUpdate(patch.content, {
|
|
114
|
+
applyLocalContentUpdate(patch.content, { forcePreviewFullDocument: true });
|
|
115
115
|
// Select the new wrapper node if the substrate reported its id.
|
|
116
116
|
const wrapperId = patch.result.wrapperNodeId;
|
|
117
117
|
if (wrapperId) {
|
|
@@ -180,7 +180,7 @@ export function runNudgeSelection(
|
|
|
180
180
|
placement: intent.placement,
|
|
181
181
|
} satisfies MoveNodeEditIntent);
|
|
182
182
|
if (patch.result.status !== "applied") return;
|
|
183
|
-
applyLocalContentUpdate(patch.content, {
|
|
183
|
+
applyLocalContentUpdate(patch.content, { forcePreviewFullDocument: true });
|
|
184
184
|
// A node with no stable `data-agent-native-node-id` has its id derived
|
|
185
185
|
// from path/offset, and the move changes both — so the pre-move id
|
|
186
186
|
// finds nothing and the selection has to be re-resolved by identity.
|
|
@@ -2,6 +2,8 @@ import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
|
2
2
|
import { toast } from "sonner";
|
|
3
3
|
import * as Y from "yjs";
|
|
4
4
|
|
|
5
|
+
import { trace } from "@/components/design/design-trace";
|
|
6
|
+
import { findCanvasIframeForScreen } from "@/components/design/multi-screen/iframe-targeting";
|
|
5
7
|
import type {
|
|
6
8
|
ElementInfo,
|
|
7
9
|
RuntimeStructureInsertRequest,
|
|
@@ -21,9 +23,15 @@ import type { CanvasLayerClipboardEntry } from "@/pages/design-editor/command-ty
|
|
|
21
23
|
import { isStandaloneHttpUrl } from "@/pages/design-editor/editor-state";
|
|
22
24
|
import type { ContentHistoryChange } from "@/pages/design-editor/history";
|
|
23
25
|
import { MAX_DESIGN_UNDO_STACK } from "@/pages/design-editor/history";
|
|
24
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
resolvePastePlacementForSelection,
|
|
28
|
+
resolvePasteSourceAnchor,
|
|
29
|
+
} from "@/pages/design-editor/paste-placement";
|
|
25
30
|
import type { DesignFile } from "@/pages/design-editor/types";
|
|
26
31
|
|
|
32
|
+
/** Inset for a copy whose original parent is gone, so it lands on screen. */
|
|
33
|
+
const ORPHANED_PASTE_INSET = 24;
|
|
34
|
+
|
|
27
35
|
export interface PasteSelectionArgs {
|
|
28
36
|
activeFile: DesignFile;
|
|
29
37
|
applyFileContentUpdate: (
|
|
@@ -83,6 +91,7 @@ export interface PasteSelectionArgs {
|
|
|
83
91
|
baseContent: string;
|
|
84
92
|
nextContent: string;
|
|
85
93
|
origin: ClipboardContentMutationOrigin;
|
|
94
|
+
baseSource?: "lineage" | "document";
|
|
86
95
|
}) => ClipboardContentMutationPublication | null;
|
|
87
96
|
refreshClipboardFromSystemClipboard: () => Promise<void>;
|
|
88
97
|
remapMotionTracksForClone: (
|
|
@@ -153,10 +162,6 @@ export async function runPasteSelection(
|
|
|
153
162
|
undoManagerRef.current?.stopCapturing();
|
|
154
163
|
await refreshClipboardFromSystemClipboard();
|
|
155
164
|
const entries = getCanvasClipboardEntries();
|
|
156
|
-
const targetFileId =
|
|
157
|
-
viewModeRef.current === "overview" && position && boardFileId
|
|
158
|
-
? boardFileId
|
|
159
|
-
: activeFile?.id;
|
|
160
165
|
if (entries.length === 0) {
|
|
161
166
|
// No layer-level clipboard content — fall back to whole-screen paste
|
|
162
167
|
// (U6) when the clipboard instead carries copied screen snapshots.
|
|
@@ -166,7 +171,35 @@ export async function runPasteSelection(
|
|
|
166
171
|
}
|
|
167
172
|
return;
|
|
168
173
|
}
|
|
174
|
+
const sourceAnchor = position
|
|
175
|
+
? null
|
|
176
|
+
: resolvePasteSourceAnchor({ entries, getContent: getScreenContent });
|
|
177
|
+
const activeSurfaceFileId =
|
|
178
|
+
viewModeRef.current === "overview" && position && boardFileId
|
|
179
|
+
? boardFileId
|
|
180
|
+
: activeFile?.id;
|
|
181
|
+
// The board is an infinite canvas layer behind every screen, so a screen
|
|
182
|
+
// layer dropped on it renders under a frame at coordinates no camera or
|
|
183
|
+
// selection outline resolves. A keyboard paste goes back to its source.
|
|
184
|
+
const returnToSourceFileId =
|
|
185
|
+
sourceAnchor &&
|
|
186
|
+
activeSurfaceFileId === boardFileId &&
|
|
187
|
+
sourceAnchor.fileId !== boardFileId
|
|
188
|
+
? sourceAnchor.fileId
|
|
189
|
+
: null;
|
|
190
|
+
const targetFileId = returnToSourceFileId ?? activeSurfaceFileId;
|
|
169
191
|
if (!targetFileId || !canEditDesign) return;
|
|
192
|
+
if (!position && targetFileId === boardFileId && !sourceAnchor) {
|
|
193
|
+
trace("structure", "paste-refused", {
|
|
194
|
+
reason: "clipboard has no readable source file to return the copy to",
|
|
195
|
+
targetFileId,
|
|
196
|
+
entries: entries.length,
|
|
197
|
+
});
|
|
198
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"), {
|
|
199
|
+
duration: 4000,
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
170
203
|
// The pending-local map is the synchronous write-through source for
|
|
171
204
|
// same-task/repeated operations. React query/collab mirrors can lag one
|
|
172
205
|
// render behind a just-completed paste even after its save is already
|
|
@@ -174,13 +207,39 @@ export async function runPasteSelection(
|
|
|
174
207
|
// mirror makes its history `before` skip the first clone, so one undo
|
|
175
208
|
// removes both. Prefer the pending snapshot exactly like primitive and
|
|
176
209
|
// cross-screen structure writes do elsewhere in this editor.
|
|
210
|
+
const pendingBase =
|
|
211
|
+
pendingLocalFileContentsRef.current.get(targetFileId)?.content;
|
|
212
|
+
const lineageBase =
|
|
213
|
+
latestClipboardMutationContentRef.current.get(targetFileId)?.content;
|
|
214
|
+
// The lineage is NOT a content source: it advances only on a clipboard
|
|
215
|
+
// mutation, so after a delete it still describes the pre-delete document
|
|
216
|
+
// and rebasing there resurrects what was removed.
|
|
177
217
|
const baseContent =
|
|
178
|
-
|
|
179
|
-
pendingLocalFileContentsRef.current.get(targetFileId)?.content ??
|
|
218
|
+
pendingBase ??
|
|
180
219
|
(targetFileId === activeFile?.id
|
|
181
220
|
? getFreshActiveContent()
|
|
182
221
|
: (getScreenContent(targetFileId) ?? ""));
|
|
183
|
-
|
|
222
|
+
trace("structure", "paste-base", {
|
|
223
|
+
targetFileId,
|
|
224
|
+
rebasedOn: pendingBase
|
|
225
|
+
? "pending-local"
|
|
226
|
+
: targetFileId === activeFile?.id
|
|
227
|
+
? "fresh-active"
|
|
228
|
+
: "screen-content",
|
|
229
|
+
bytes: baseContent.length,
|
|
230
|
+
lineageBytes: lineageBase?.length ?? null,
|
|
231
|
+
entries: entries.length,
|
|
232
|
+
});
|
|
233
|
+
if (!baseContent && targetFileId !== boardFileId) {
|
|
234
|
+
trace("structure", "paste-refused", {
|
|
235
|
+
reason: "destination file has no readable content",
|
|
236
|
+
targetFileId,
|
|
237
|
+
});
|
|
238
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"), {
|
|
239
|
+
duration: 4000,
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
184
243
|
const layerHtmls = entries.map((entry) => entry.html);
|
|
185
244
|
const styleSnapshots = entries.map((entry) => entry.portableStyleSnapshot);
|
|
186
245
|
const managedStyleSnapshots = entries.map(
|
|
@@ -292,8 +351,18 @@ export async function runPasteSelection(
|
|
|
292
351
|
baseContent,
|
|
293
352
|
nextContent,
|
|
294
353
|
origin: "clipboard-paste",
|
|
354
|
+
baseSource: "document",
|
|
295
355
|
});
|
|
296
|
-
if (!clipboardMutation)
|
|
356
|
+
if (!clipboardMutation) {
|
|
357
|
+
trace("structure", "paste-refused", {
|
|
358
|
+
reason: "clipboard lineage refused the publication",
|
|
359
|
+
targetFileId,
|
|
360
|
+
});
|
|
361
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"), {
|
|
362
|
+
duration: 4000,
|
|
363
|
+
});
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
297
366
|
if (nextContent !== baseContent) {
|
|
298
367
|
// Capture the exact immutable pre-paste document here, before the
|
|
299
368
|
// optimistic cache/collab mirrors can advance independently. The
|
|
@@ -335,7 +404,12 @@ export async function runPasteSelection(
|
|
|
335
404
|
// Inside a frame, after an object — but always into normal flow: a
|
|
336
405
|
// container is not a free canvas, so carrying the source's left/top
|
|
337
406
|
// across drops the clone on top of the target's content.
|
|
338
|
-
if (
|
|
407
|
+
if (
|
|
408
|
+
!position &&
|
|
409
|
+
!returnToSourceFileId &&
|
|
410
|
+
targetFileId !== boardFileId &&
|
|
411
|
+
selectedElement?.selector
|
|
412
|
+
) {
|
|
339
413
|
const selector = selectedCanvasSelector ?? selectedElement.selector;
|
|
340
414
|
const decision = resolvePastePlacementForSelection({
|
|
341
415
|
content: baseContent,
|
|
@@ -384,24 +458,56 @@ export async function runPasteSelection(
|
|
|
384
458
|
const pastingIntoSourceScreen = entries.every(
|
|
385
459
|
(entry) => entry.sourceFileId === targetFileId,
|
|
386
460
|
);
|
|
461
|
+
const sourceParentSelectors =
|
|
462
|
+
sourceAnchor?.fileId === targetFileId ? sourceAnchor.parentSelectors : null;
|
|
463
|
+
// The copy is going to the screen root because its parent is gone. Its
|
|
464
|
+
// stored left/top belong to that parent, so reusing them can place it off
|
|
465
|
+
// the screen entirely; a fixed inset is always somewhere the user can see.
|
|
466
|
+
const rootFallbackPlacement =
|
|
467
|
+
sourceAnchor !== null &&
|
|
468
|
+
sourceAnchor.fileId === targetFileId &&
|
|
469
|
+
sourceParentSelectors === null;
|
|
387
470
|
const viewportCenter = (() => {
|
|
471
|
+
const container = canvasContainerRef.current;
|
|
472
|
+
const factor = zoom / 100;
|
|
473
|
+
if (factor <= 0) return { x: 120, y: 120 };
|
|
388
474
|
if (viewModeRef.current === "single") {
|
|
389
|
-
const iframe =
|
|
475
|
+
const iframe = container?.querySelector<HTMLElement>(
|
|
390
476
|
"[data-design-preview-iframe]",
|
|
391
477
|
);
|
|
392
478
|
if (iframe) {
|
|
393
479
|
const iframeRect = iframe.getBoundingClientRect();
|
|
394
|
-
const factor = zoom / 100;
|
|
395
480
|
return {
|
|
396
481
|
x: Math.max(0, iframeRect.width / 2 / factor),
|
|
397
482
|
y: Math.max(0, iframeRect.height / 2 / factor),
|
|
398
483
|
};
|
|
399
484
|
}
|
|
400
485
|
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
486
|
+
// The container's rect is screen pixels; left/top are document pixels.
|
|
487
|
+
// Only the destination frame's own rect converts between them — the
|
|
488
|
+
// board's cannot, because its iframe is a render window, not its origin.
|
|
489
|
+
const frameRect =
|
|
490
|
+
targetFileId !== boardFileId
|
|
491
|
+
? findCanvasIframeForScreen(
|
|
492
|
+
container,
|
|
493
|
+
targetFileId,
|
|
494
|
+
)?.getBoundingClientRect()
|
|
495
|
+
: null;
|
|
496
|
+
const containerRect = container?.getBoundingClientRect();
|
|
497
|
+
if (!frameRect || !containerRect) return { x: 120, y: 120 };
|
|
498
|
+
const clamp = (value: number, extent: number) =>
|
|
499
|
+
Math.max(0, Math.min(value, extent / factor));
|
|
500
|
+
return {
|
|
501
|
+
x: clamp(
|
|
502
|
+
(containerRect.left + containerRect.width / 2 - frameRect.left) /
|
|
503
|
+
factor,
|
|
504
|
+
frameRect.width,
|
|
505
|
+
),
|
|
506
|
+
y: clamp(
|
|
507
|
+
(containerRect.top + containerRect.height / 2 - frameRect.top) / factor,
|
|
508
|
+
frameRect.height,
|
|
509
|
+
),
|
|
510
|
+
};
|
|
405
511
|
})();
|
|
406
512
|
const positions = entries.map((_, index) => {
|
|
407
513
|
const source = sourcePositions[index];
|
|
@@ -413,6 +519,12 @@ export async function runPasteSelection(
|
|
|
413
519
|
}
|
|
414
520
|
: { x: position.x + index * 16, y: position.y + index * 16 };
|
|
415
521
|
}
|
|
522
|
+
if (rootFallbackPlacement) {
|
|
523
|
+
return {
|
|
524
|
+
x: ORPHANED_PASTE_INSET + cascadeOffset + index * 16,
|
|
525
|
+
y: ORPHANED_PASTE_INSET + cascadeOffset + index * 16,
|
|
526
|
+
};
|
|
527
|
+
}
|
|
416
528
|
return source && pastingIntoSourceScreen
|
|
417
529
|
? {
|
|
418
530
|
x: source.x + 10 + cascadeOffset,
|
|
@@ -427,8 +539,21 @@ export async function runPasteSelection(
|
|
|
427
539
|
positions,
|
|
428
540
|
styleSnapshots,
|
|
429
541
|
managedStyleSnapshots,
|
|
542
|
+
...(sourceParentSelectors
|
|
543
|
+
? { targetSelectors: sourceParentSelectors, placement: "inside" as const }
|
|
544
|
+
: {}),
|
|
430
545
|
});
|
|
431
|
-
if (!result)
|
|
546
|
+
if (!result) {
|
|
547
|
+
trace("structure", "paste-refused", {
|
|
548
|
+
reason: "destination document refused the clone",
|
|
549
|
+
targetFileId,
|
|
550
|
+
anchors: sourceParentSelectors?.length ?? 0,
|
|
551
|
+
});
|
|
552
|
+
toast.error(t("designEditor.toasts.primitiveInsertFailed"), {
|
|
553
|
+
duration: 4000,
|
|
554
|
+
});
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
432
557
|
if (!position) pasteCascadeRef.current += 1;
|
|
433
558
|
if (!applyPasteContentUpdate(result.content)) return;
|
|
434
559
|
remapMotionTracksForClone(result.nodeIdMap, targetFileId);
|
package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts
CHANGED
|
@@ -96,6 +96,7 @@ export function runScreenVisualDuplicateChange(
|
|
|
96
96
|
? [details.anchorSelector]
|
|
97
97
|
: undefined,
|
|
98
98
|
placement: details?.placement ?? "after",
|
|
99
|
+
preserveIncomingNodeIds: true,
|
|
99
100
|
});
|
|
100
101
|
if (!nextContent) {
|
|
101
102
|
toast.error(t("designEditor.toasts.layerMoveFailed"), {
|
|
@@ -150,7 +150,7 @@ export function runUngroupSelection({
|
|
|
150
150
|
);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
applyLocalContentUpdate(content, {
|
|
153
|
+
applyLocalContentUpdate(content, { forcePreviewFullDocument: true });
|
|
154
154
|
|
|
155
155
|
const finalProjection = buildCodeLayerProjection(content);
|
|
156
156
|
const releasedNodes = finalProjection.nodes.filter((n) => {
|
|
@@ -110,12 +110,12 @@ export function shouldAdaptAutoTextColorForCrossScreenMove(params: {
|
|
|
110
110
|
* treated as transparent so the walk keeps climbing instead of trusting a
|
|
111
111
|
* near-invisible tint. A dark-class hint only counts when no color signal
|
|
112
112
|
* was present on that element (a real color always wins over a guessed
|
|
113
|
-
* class). No signal anywhere in the chain
|
|
114
|
-
*
|
|
113
|
+
* class). No signal anywhere in the chain yields null — distinct from a
|
|
114
|
+
* measured light surface, so each caller picks its own fallback.
|
|
115
115
|
*/
|
|
116
|
-
export function
|
|
116
|
+
export function resolveDestinationBackgroundLightnessOrNull(
|
|
117
117
|
chain: ReadonlyArray<{ color: string | null } | { darkClassHint: boolean }>,
|
|
118
|
-
): boolean {
|
|
118
|
+
): boolean | null {
|
|
119
119
|
for (const entry of chain) {
|
|
120
120
|
if ("color" in entry && entry.color) {
|
|
121
121
|
const rgba = parseCssColorExtended(entry.color);
|
|
@@ -130,7 +130,14 @@ export function resolveDestinationBackgroundLightness(
|
|
|
130
130
|
return false;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
return
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Unreadable counts as light, so the default-white heuristic can still fire. */
|
|
137
|
+
export function resolveDestinationBackgroundLightness(
|
|
138
|
+
chain: ReadonlyArray<{ color: string | null } | { darkClassHint: boolean }>,
|
|
139
|
+
): boolean {
|
|
140
|
+
return resolveDestinationBackgroundLightnessOrNull(chain) ?? true;
|
|
134
141
|
}
|
|
135
142
|
|
|
136
143
|
// Cheap, best-effort utility-class signal for "this element's classes look
|
|
@@ -209,10 +216,11 @@ function collectDestinationBackgroundSignals(
|
|
|
209
216
|
* dark-class-hint chain when no live document is available (e.g. the
|
|
210
217
|
* destination screen isn't currently mounted).
|
|
211
218
|
*/
|
|
212
|
-
|
|
219
|
+
/** null means "no readable signal", so callers can pick their own fallback. */
|
|
220
|
+
export function destinationBackgroundLightness(
|
|
213
221
|
element: Element,
|
|
214
222
|
liveDoc?: Document | null,
|
|
215
|
-
): boolean {
|
|
223
|
+
): boolean | null {
|
|
216
224
|
try {
|
|
217
225
|
const liveElement =
|
|
218
226
|
liveDoc && element.hasAttribute("data-agent-native-node-id")
|
|
@@ -223,12 +231,20 @@ export function destinationBackgroundIsLightForNode(
|
|
|
223
231
|
)
|
|
224
232
|
: null;
|
|
225
233
|
const chain = collectDestinationBackgroundSignals(element, liveElement);
|
|
226
|
-
return
|
|
234
|
+
return resolveDestinationBackgroundLightnessOrNull(chain);
|
|
227
235
|
} catch {
|
|
228
|
-
|
|
236
|
+
// coercion-ok: null is the typed "unreadable" value callers branch on
|
|
237
|
+
return null;
|
|
229
238
|
}
|
|
230
239
|
}
|
|
231
240
|
|
|
241
|
+
export function destinationBackgroundIsLightForNode(
|
|
242
|
+
element: Element,
|
|
243
|
+
liveDoc?: Document | null,
|
|
244
|
+
): boolean {
|
|
245
|
+
return destinationBackgroundLightness(element, liveDoc) ?? true;
|
|
246
|
+
}
|
|
247
|
+
|
|
232
248
|
/**
|
|
233
249
|
* Cross-screen counterpart to adaptAutoTextColorForNest — runs HOST-SIDE
|
|
234
250
|
* after moveNodeBetweenDocuments has already re-parented the text node into
|