@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
|
@@ -21,7 +21,6 @@ import {
|
|
|
21
21
|
IconPhoto,
|
|
22
22
|
IconComponents,
|
|
23
23
|
IconCheck,
|
|
24
|
-
IconChevronDown,
|
|
25
24
|
IconExternalLink,
|
|
26
25
|
} from "@tabler/icons-react";
|
|
27
26
|
import { useState, useCallback, useRef, useMemo, useEffect } from "react";
|
|
@@ -38,7 +37,6 @@ import {
|
|
|
38
37
|
pollDecodeJobStatus,
|
|
39
38
|
type DecodeJobStatus,
|
|
40
39
|
} from "@/lib/builder-design-system-upload";
|
|
41
|
-
import { cn } from "@/lib/utils";
|
|
42
40
|
|
|
43
41
|
interface GitHubLink {
|
|
44
42
|
id: string;
|
|
@@ -55,7 +53,13 @@ interface UploadedFile {
|
|
|
55
53
|
textContent?: string;
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
type OtherSource =
|
|
56
|
+
type OtherSource =
|
|
57
|
+
| "brand"
|
|
58
|
+
| "code"
|
|
59
|
+
| "design-md"
|
|
60
|
+
| "files"
|
|
61
|
+
| "existing"
|
|
62
|
+
| "notes";
|
|
59
63
|
|
|
60
64
|
interface BuilderIndexResult {
|
|
61
65
|
ok: boolean;
|
|
@@ -91,6 +95,8 @@ interface BuilderIndexInput {
|
|
|
91
95
|
designMd?: string;
|
|
92
96
|
}
|
|
93
97
|
|
|
98
|
+
const MAX_INLINE_DESIGN_MD_BYTES = 2 * 1024 * 1024;
|
|
99
|
+
|
|
94
100
|
export default function DesignSystemSetup() {
|
|
95
101
|
const t = useT();
|
|
96
102
|
const navigate = useNavigate();
|
|
@@ -105,6 +111,7 @@ export default function DesignSystemSetup() {
|
|
|
105
111
|
const [githubPaths, setGithubPaths] = useState("");
|
|
106
112
|
const [githubLinks, setGithubLinks] = useState<GitHubLink[]>([]);
|
|
107
113
|
const [codeFiles, setCodeFiles] = useState<UploadedFile[]>([]);
|
|
114
|
+
const [designMdFiles, setDesignMdFiles] = useState<UploadedFile[]>([]);
|
|
108
115
|
const [docFiles, setDocFiles] = useState<UploadedFile[]>([]);
|
|
109
116
|
const [imageFiles, setImageFiles] = useState<UploadedFile[]>([]);
|
|
110
117
|
const [assets, setAssets] = useState<UploadedFile[]>([]);
|
|
@@ -119,6 +126,8 @@ export default function DesignSystemSetup() {
|
|
|
119
126
|
const imageInputRef = useRef<HTMLInputElement>(null);
|
|
120
127
|
const assetInputRef = useRef<HTMLInputElement>(null);
|
|
121
128
|
const codeInputRef = useRef<HTMLInputElement>(null);
|
|
129
|
+
const designMdInputRef = useRef<HTMLInputElement>(null);
|
|
130
|
+
const designMdUploadGenerationRef = useRef(0);
|
|
122
131
|
const appliedSourceIdRef = useRef<string | null>(null);
|
|
123
132
|
|
|
124
133
|
const { data: designsData } = useActionQuery<{
|
|
@@ -264,6 +273,7 @@ export default function DesignSystemSetup() {
|
|
|
264
273
|
githubUrl.trim() ||
|
|
265
274
|
githubLinks.length > 0 ||
|
|
266
275
|
codeFiles.length > 0 ||
|
|
276
|
+
designMdFiles.length > 0 ||
|
|
267
277
|
builderIndexResult ||
|
|
268
278
|
docFiles.length > 0 ||
|
|
269
279
|
imageFiles.length > 0 ||
|
|
@@ -279,6 +289,7 @@ export default function DesignSystemSetup() {
|
|
|
279
289
|
githubUrl,
|
|
280
290
|
githubLinks,
|
|
281
291
|
codeFiles,
|
|
292
|
+
designMdFiles,
|
|
282
293
|
builderIndexResult,
|
|
283
294
|
docFiles,
|
|
284
295
|
imageFiles,
|
|
@@ -390,6 +401,48 @@ export default function DesignSystemSetup() {
|
|
|
390
401
|
[readTextFiles],
|
|
391
402
|
);
|
|
392
403
|
|
|
404
|
+
const handleDesignMdUpload = useCallback(
|
|
405
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
406
|
+
const files = e.target.files;
|
|
407
|
+
if (!files) return;
|
|
408
|
+
const file = files[0];
|
|
409
|
+
e.target.value = "";
|
|
410
|
+
if (!file) return;
|
|
411
|
+
const uploadGeneration = ++designMdUploadGenerationRef.current;
|
|
412
|
+
setDesignMdFiles([]);
|
|
413
|
+
if (!isDesignMdFile({ name: file.name })) {
|
|
414
|
+
setValidationError(t("designSystemSetup.errors.chooseDesignMd"));
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
if (file.size > MAX_INLINE_DESIGN_MD_BYTES) {
|
|
418
|
+
setValidationError(t("designSystemSetup.errors.designMdTooLarge"));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const uploadedFile: UploadedFile = {
|
|
422
|
+
id: crypto.randomUUID(),
|
|
423
|
+
name: file.name,
|
|
424
|
+
type: file.type || "text/markdown",
|
|
425
|
+
size: file.size,
|
|
426
|
+
};
|
|
427
|
+
file
|
|
428
|
+
.text()
|
|
429
|
+
.then((text) => {
|
|
430
|
+
if (designMdUploadGenerationRef.current !== uploadGeneration) {
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
setDesignMdFiles([{ ...uploadedFile, textContent: text }]);
|
|
434
|
+
setValidationError(null);
|
|
435
|
+
})
|
|
436
|
+
.catch(() => {
|
|
437
|
+
if (designMdUploadGenerationRef.current !== uploadGeneration) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
setValidationError(t("designSystemSetup.errors.readDesignMd"));
|
|
441
|
+
});
|
|
442
|
+
},
|
|
443
|
+
[t],
|
|
444
|
+
);
|
|
445
|
+
|
|
393
446
|
const handleDocUpload = useCallback(
|
|
394
447
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
395
448
|
if (!e.target.files) return;
|
|
@@ -487,6 +540,7 @@ export default function DesignSystemSetup() {
|
|
|
487
540
|
normalizedGithubLinks.length > 0 &&
|
|
488
541
|
normalizedWebsiteUrls.length === 0 &&
|
|
489
542
|
codeFiles.length === 0 &&
|
|
543
|
+
designMdFiles.length === 0 &&
|
|
490
544
|
!builderIndexResult &&
|
|
491
545
|
docFiles.length === 0 &&
|
|
492
546
|
imageFiles.length === 0 &&
|
|
@@ -521,11 +575,53 @@ export default function DesignSystemSetup() {
|
|
|
521
575
|
}
|
|
522
576
|
|
|
523
577
|
const readableCodeFiles = codeFiles.filter((f) => f.textContent);
|
|
524
|
-
const
|
|
578
|
+
const codeDesignMdFiles = readableCodeFiles.filter(isDesignMdFile);
|
|
525
579
|
const builderCodeFiles = readableCodeFiles.filter(
|
|
526
580
|
(file) => !isDesignMdFile(file),
|
|
527
581
|
);
|
|
528
582
|
const unreadableCodeFiles = codeFiles.filter((f) => !f.textContent);
|
|
583
|
+
const readableDesignMdFiles = designMdFiles.filter(
|
|
584
|
+
(file) => file.textContent,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
if (designMdFiles.length > 0 && readableDesignMdFiles.length === 0) {
|
|
588
|
+
setValidationError(t("designSystemSetup.errors.readDesignMd"));
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const isDesignMdOnlySource =
|
|
593
|
+
readableDesignMdFiles.length > 0 &&
|
|
594
|
+
normalizedGithubLinks.length === 0 &&
|
|
595
|
+
normalizedWebsiteUrls.length === 0 &&
|
|
596
|
+
codeFiles.length === 0 &&
|
|
597
|
+
!builderIndexResult &&
|
|
598
|
+
docFiles.length === 0 &&
|
|
599
|
+
imageFiles.length === 0 &&
|
|
600
|
+
assets.length === 0 &&
|
|
601
|
+
!selectedProjectId;
|
|
602
|
+
|
|
603
|
+
if (isDesignMdOnlySource) {
|
|
604
|
+
setValidationError(null);
|
|
605
|
+
try {
|
|
606
|
+
await indexSystemMutation.mutateAsync({
|
|
607
|
+
projectName: companyInfo.trim() || undefined,
|
|
608
|
+
description:
|
|
609
|
+
[notes.trim(), customInstructions.trim()]
|
|
610
|
+
.filter(Boolean)
|
|
611
|
+
.join("\n\n") || undefined,
|
|
612
|
+
designMd: readableDesignMdFiles[0]?.textContent,
|
|
613
|
+
});
|
|
614
|
+
toast.success(t("designSystemSetup.designMdIndexStarted"));
|
|
615
|
+
navigate("/design-systems");
|
|
616
|
+
} catch (error) {
|
|
617
|
+
setValidationError(
|
|
618
|
+
error instanceof Error
|
|
619
|
+
? error.message
|
|
620
|
+
: t("designSystemSetup.errors.designMdIndex"),
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
529
625
|
|
|
530
626
|
const parts: string[] = [];
|
|
531
627
|
parts.push(
|
|
@@ -569,11 +665,11 @@ export default function DesignSystemSetup() {
|
|
|
569
665
|
);
|
|
570
666
|
}
|
|
571
667
|
}
|
|
572
|
-
if (
|
|
668
|
+
if (codeDesignMdFiles.length > 0) {
|
|
573
669
|
parts.push(
|
|
574
|
-
`\n## Optional design.md (${
|
|
670
|
+
`\n## Optional design.md (${codeDesignMdFiles.length} file${codeDesignMdFiles.length === 1 ? "" : "s"})\nPass this content as the \`designMd\` argument to \`index-design-system-with-builder\` alongside any Figma/code sources:`,
|
|
575
671
|
);
|
|
576
|
-
for (const f of
|
|
672
|
+
for (const f of codeDesignMdFiles) {
|
|
577
673
|
parts.push(
|
|
578
674
|
`\n### ${f.name}\n\`\`\`md\n${f.textContent!.slice(0, 5000)}\n\`\`\``,
|
|
579
675
|
);
|
|
@@ -586,6 +682,17 @@ export default function DesignSystemSetup() {
|
|
|
586
682
|
}
|
|
587
683
|
}
|
|
588
684
|
|
|
685
|
+
if (designMdFiles.length > 0) {
|
|
686
|
+
parts.push(
|
|
687
|
+
`\n## design.md\nPass this content as the \`designMd\` argument to \`index-design-system-with-builder\` alongside any Figma/code sources:`,
|
|
688
|
+
);
|
|
689
|
+
for (const f of readableDesignMdFiles) {
|
|
690
|
+
parts.push(
|
|
691
|
+
`\n### ${f.name}\n\`\`\`md\n${f.textContent!.slice(0, 5000)}\n\`\`\``,
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
589
696
|
if (builderIndexResult) {
|
|
590
697
|
parts.push(
|
|
591
698
|
`\n## Connect Figma: Builder-Indexed Figma File\nBuilder DSI indexing has already started.\n- Design system: ${builderIndexResult.designSystemId}\n- Local selectable design system: ${builderIndexResult.localDesignSystemId ?? "(not returned)"}\n- Project: ${builderIndexResult.projectId}\n- Job: ${builderIndexResult.jobId}\n- URL: ${builderIndexResult.builderUrl}\n\nUse Builder as the source of truth for indexed tokens, assets, components, and guidance. Do not call \`create-design-system\` again for this Builder-indexed source.`,
|
|
@@ -680,6 +787,7 @@ export default function DesignSystemSetup() {
|
|
|
680
787
|
githubPaths,
|
|
681
788
|
githubLinks,
|
|
682
789
|
codeFiles,
|
|
790
|
+
designMdFiles,
|
|
683
791
|
builderIndexResult,
|
|
684
792
|
docFiles,
|
|
685
793
|
imageFiles,
|
|
@@ -782,6 +890,14 @@ export default function DesignSystemSetup() {
|
|
|
782
890
|
selected={sourcePanel === "other" && otherSource === "code"}
|
|
783
891
|
onClick={() => selectOtherSource("code")}
|
|
784
892
|
/>
|
|
893
|
+
<SourceChoice
|
|
894
|
+
icon={IconFileDescription}
|
|
895
|
+
title={t("designSystemSetup.sections.designMd.title")}
|
|
896
|
+
selected={
|
|
897
|
+
sourcePanel === "other" && otherSource === "design-md"
|
|
898
|
+
}
|
|
899
|
+
onClick={() => selectOtherSource("design-md")}
|
|
900
|
+
/>
|
|
785
901
|
<SourceChoice
|
|
786
902
|
icon={IconFileDescription}
|
|
787
903
|
title={t("designSystemSetup.sections.designFiles.title")}
|
|
@@ -1103,6 +1219,45 @@ export default function DesignSystemSetup() {
|
|
|
1103
1219
|
</div>
|
|
1104
1220
|
</Section>
|
|
1105
1221
|
|
|
1222
|
+
{/* Import design.md guidance directly into Builder DSI. */}
|
|
1223
|
+
<Section
|
|
1224
|
+
title={t("designSystemSetup.sections.designMd.title")}
|
|
1225
|
+
description={t("designSystemSetup.sections.designMd.description")}
|
|
1226
|
+
hidden={sourcePanel !== "other" || otherSource !== "design-md"}
|
|
1227
|
+
hideHeading
|
|
1228
|
+
id="design-system-design-md-source"
|
|
1229
|
+
className="rounded-lg border border-border bg-card p-4"
|
|
1230
|
+
>
|
|
1231
|
+
<button
|
|
1232
|
+
type="button"
|
|
1233
|
+
onClick={() => designMdInputRef.current?.click()}
|
|
1234
|
+
className="w-full rounded-xl border border-dashed border-border bg-card p-8 text-center hover:border-foreground/15 cursor-pointer"
|
|
1235
|
+
>
|
|
1236
|
+
<div className="flex flex-col items-center gap-2">
|
|
1237
|
+
<div className="flex h-12 w-12 items-center justify-center rounded-xl bg-primary/10">
|
|
1238
|
+
<IconFileDescription className="size-6 text-primary" />
|
|
1239
|
+
</div>
|
|
1240
|
+
<p className="text-sm font-medium text-foreground/90">
|
|
1241
|
+
{t("designSystemSetup.designMdUpload")}
|
|
1242
|
+
</p>
|
|
1243
|
+
<p className="text-xs text-muted-foreground/70">
|
|
1244
|
+
{t("designSystemSetup.designMdHelp")}
|
|
1245
|
+
</p>
|
|
1246
|
+
</div>
|
|
1247
|
+
</button>
|
|
1248
|
+
<input
|
|
1249
|
+
ref={designMdInputRef}
|
|
1250
|
+
type="file"
|
|
1251
|
+
accept=".md,.mdx"
|
|
1252
|
+
onChange={handleDesignMdUpload}
|
|
1253
|
+
className="hidden"
|
|
1254
|
+
/>
|
|
1255
|
+
<FileList
|
|
1256
|
+
files={designMdFiles}
|
|
1257
|
+
onRemove={() => setDesignMdFiles([])}
|
|
1258
|
+
/>
|
|
1259
|
+
</Section>
|
|
1260
|
+
|
|
1106
1261
|
{/* Design Files */}
|
|
1107
1262
|
<Section
|
|
1108
1263
|
title={t("designSystemSetup.sections.designFiles.title")}
|
|
@@ -1507,7 +1662,7 @@ function formatSize(bytes: number): string {
|
|
|
1507
1662
|
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
1508
1663
|
}
|
|
1509
1664
|
|
|
1510
|
-
function isDesignMdFile(file: UploadedFile): boolean {
|
|
1665
|
+
function isDesignMdFile(file: Pick<UploadedFile, "name">): boolean {
|
|
1511
1666
|
const name = file.name.split(/[\\/]/).pop()?.toLowerCase() ?? file.name;
|
|
1512
1667
|
return name === "design.md" || name === "design.mdx";
|
|
1513
1668
|
}
|
|
@@ -42,6 +42,7 @@ import { useState, useRef, useCallback, useEffect, useMemo } from "react";
|
|
|
42
42
|
import { useNavigate, Link, useSearchParams } from "react-router";
|
|
43
43
|
import { toast } from "sonner";
|
|
44
44
|
|
|
45
|
+
import { trace } from "@/components/design/design-trace";
|
|
45
46
|
import PromptPopover from "@/components/editor/PromptDialog";
|
|
46
47
|
import type {
|
|
47
48
|
PromptTemplateOption,
|
|
@@ -624,8 +625,24 @@ export default function Index() {
|
|
|
624
625
|
skipQuestions: pendingOptions?.skipQuestions,
|
|
625
626
|
...options,
|
|
626
627
|
});
|
|
628
|
+
// Rejecting here is what lets PromptPopover restore the typed prompt.
|
|
629
|
+
// Navigating first strands the user in an empty editor with no error.
|
|
630
|
+
try {
|
|
631
|
+
await ready;
|
|
632
|
+
} catch (error) {
|
|
633
|
+
clearPendingGeneration(id);
|
|
634
|
+
setNewDesignHandoffPending(false);
|
|
635
|
+
trace("persist", "create-design-failed", {
|
|
636
|
+
id,
|
|
637
|
+
designSystemId,
|
|
638
|
+
message: error instanceof Error ? error.message : String(error),
|
|
639
|
+
});
|
|
640
|
+
toast.error(t("home.failedToCreateDesign"));
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
627
643
|
}
|
|
628
644
|
|
|
645
|
+
trace("persist", "new-design-handoff", { id, designSystemId });
|
|
629
646
|
setNewDesignHandoffPending(true);
|
|
630
647
|
navigate(`/design/${id}`);
|
|
631
648
|
},
|
|
@@ -9,11 +9,12 @@ import type { CanvasPrimitiveInsert } from "@/components/design/multi-screen/typ
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
CANVAS_TEXT_DEFAULT_FONT_FAMILY,
|
|
12
|
+
defaultCanvasFrameFill,
|
|
12
13
|
defaultCanvasTextColor,
|
|
13
14
|
} from "./canvas-primitives";
|
|
14
15
|
import {
|
|
15
16
|
BOARD_TEXT_AUTO_COLOR_MARKER,
|
|
16
|
-
|
|
17
|
+
destinationBackgroundLightness,
|
|
17
18
|
} from "./cross-screen-text-color";
|
|
18
19
|
import { escapeHtmlAttributeValue, escapeHtmlText } from "./dom-utils";
|
|
19
20
|
import { isStandaloneHttpUrl } from "./editor-state";
|
|
@@ -219,8 +220,9 @@ function absoluteRect(
|
|
|
219
220
|
) {
|
|
220
221
|
continue;
|
|
221
222
|
}
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
const inset = inlineBorderInset(ancestor);
|
|
224
|
+
originX += (read(ancestor.style.left) ?? 0) + inset.x;
|
|
225
|
+
originY += (read(ancestor.style.top) ?? 0) + inset.y;
|
|
224
226
|
}
|
|
225
227
|
return { x: originX + x, y: originY + y, w, h };
|
|
226
228
|
}
|
|
@@ -230,12 +232,20 @@ function absoluteRect(
|
|
|
230
232
|
* `data-an-primitive="frame"` adopts. Bounds come from inline geometry
|
|
231
233
|
* because this document is parsed, never laid out.
|
|
232
234
|
*/
|
|
235
|
+
/** Inline border widths, which an absolute child's offsets resolve inside of. */
|
|
236
|
+
function inlineBorderInset(element: Element): { x: number; y: number } {
|
|
237
|
+
const style = (element as HTMLElement).style;
|
|
238
|
+
const read = (value: string) => {
|
|
239
|
+
const parsed = Number.parseFloat(value);
|
|
240
|
+
return Number.isFinite(parsed) ? parsed : 0;
|
|
241
|
+
};
|
|
242
|
+
return { x: read(style.borderLeftWidth), y: read(style.borderTopWidth) };
|
|
243
|
+
}
|
|
244
|
+
|
|
233
245
|
function deepestFrameContaining(
|
|
234
246
|
root: Element,
|
|
235
247
|
x: number,
|
|
236
248
|
y: number,
|
|
237
|
-
w: number,
|
|
238
|
-
h: number,
|
|
239
249
|
): { element: Element; x: number; y: number } | null {
|
|
240
250
|
const contained = Array.from(
|
|
241
251
|
root.querySelectorAll('[data-an-primitive="frame"]'),
|
|
@@ -248,11 +258,14 @@ function deepestFrameContaining(
|
|
|
248
258
|
element: Element;
|
|
249
259
|
rect: { x: number; y: number; w: number; h: number };
|
|
250
260
|
} =>
|
|
261
|
+
// Nest on the origin, as Figma does. Requiring the whole box to fit
|
|
262
|
+
// drops a click-created text (default width) out to the root, where
|
|
263
|
+
// it overlaps the frame it looks like it belongs to.
|
|
251
264
|
candidate.rect !== null &&
|
|
252
265
|
x >= candidate.rect.x &&
|
|
253
266
|
y >= candidate.rect.y &&
|
|
254
|
-
x
|
|
255
|
-
y
|
|
267
|
+
x <= candidate.rect.x + candidate.rect.w &&
|
|
268
|
+
y <= candidate.rect.y + candidate.rect.h,
|
|
256
269
|
)
|
|
257
270
|
.sort((a, b) => a.rect.w * a.rect.h - b.rect.w * b.rect.h);
|
|
258
271
|
const best = contained[0];
|
|
@@ -288,8 +301,14 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
288
301
|
const layerName = primitiveLayerName(primitive);
|
|
289
302
|
// Resolved once so every primitive kind nests identically, and so text can
|
|
290
303
|
// pick a fill that is legible against its actual container.
|
|
291
|
-
const host = deepestFrameContaining(doc.body, left, top
|
|
304
|
+
const host = deepestFrameContaining(doc.body, left, top);
|
|
292
305
|
const hostOrBody: Element = host?.element ?? doc.body;
|
|
306
|
+
// An absolute child resolves against the host's PADDING box, while
|
|
307
|
+
// host.x/host.y are its border-box origin — so a bordered frame shifts
|
|
308
|
+
// everything dropped into it by the border width.
|
|
309
|
+
const hostBorder = host ? inlineBorderInset(host.element) : { x: 0, y: 0 };
|
|
310
|
+
const hostLeft = host ? left - host.x - hostBorder.x : left;
|
|
311
|
+
const hostTop = host ? top - host.y - hostBorder.y : top;
|
|
293
312
|
|
|
294
313
|
if (
|
|
295
314
|
primitive.kind === "path" ||
|
|
@@ -398,8 +417,8 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
398
417
|
"style",
|
|
399
418
|
[
|
|
400
419
|
"position:absolute",
|
|
401
|
-
`left:${
|
|
402
|
-
`top:${
|
|
420
|
+
`left:${hostLeft}px`,
|
|
421
|
+
`top:${hostTop}px`,
|
|
403
422
|
`width:${width}px`,
|
|
404
423
|
`height:${height}px`,
|
|
405
424
|
"overflow:visible",
|
|
@@ -441,8 +460,8 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
441
460
|
"style",
|
|
442
461
|
[
|
|
443
462
|
"position:absolute",
|
|
444
|
-
`left:${
|
|
445
|
-
`top:${
|
|
463
|
+
`left:${hostLeft}px`,
|
|
464
|
+
`top:${hostTop}px`,
|
|
446
465
|
`width:${width}px`,
|
|
447
466
|
`height:${height}px`,
|
|
448
467
|
"overflow:visible",
|
|
@@ -464,8 +483,8 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
464
483
|
// glyph. Read by treeTypeForNode in shared/code-layer.ts.
|
|
465
484
|
element.setAttribute("data-an-primitive", primitive.kind);
|
|
466
485
|
element.style.position = "absolute";
|
|
467
|
-
element.style.left = `${
|
|
468
|
-
element.style.top = `${
|
|
486
|
+
element.style.left = `${hostLeft}px`;
|
|
487
|
+
element.style.top = `${hostTop}px`;
|
|
469
488
|
if (!(primitive.kind === "text" && primitive.autoSize)) {
|
|
470
489
|
element.style.width = `${width}px`;
|
|
471
490
|
element.style.height = `${height}px`;
|
|
@@ -482,19 +501,12 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
482
501
|
primitive.kind === "rectangle" ? "rect" : primitive.kind,
|
|
483
502
|
);
|
|
484
503
|
if (primitive.kind === "frame") {
|
|
485
|
-
// A committed frame
|
|
486
|
-
//
|
|
487
|
-
//
|
|
488
|
-
//
|
|
489
|
-
// pollution). This deliberately diverges from the draft PREVIEW's
|
|
490
|
-
// faint-tint/dashed look (canvas-primitive-style.ts), which is editor
|
|
491
|
-
// affordance chrome during the drag; on commit the new frame is
|
|
492
|
-
// immediately selected, so its bounds stay visible via selection
|
|
493
|
-
// chrome instead. Explicit user-chosen fill/stroke still applies.
|
|
504
|
+
// A committed frame carries a real surface, not the draft preview's
|
|
505
|
+
// dashed tint (editor chrome, canvas-primitive-style.ts): selection
|
|
506
|
+
// chrome only covers the frame while it stays selected, and a bare
|
|
507
|
+
// container is invisible the moment it is not.
|
|
494
508
|
// overflow:hidden matches Figma frames clipping their content.
|
|
495
|
-
|
|
496
|
-
element.style.background = primitive.fill;
|
|
497
|
-
}
|
|
509
|
+
element.style.background = primitive.fill ?? defaultCanvasFrameFill();
|
|
498
510
|
if (
|
|
499
511
|
primitive.stroke !== undefined ||
|
|
500
512
|
primitive.strokeWidth !== undefined
|
|
@@ -516,9 +528,13 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
516
528
|
// "currentColor" so text still inherits their theme.
|
|
517
529
|
// Measure the frame the text actually lands in: a dark frame on a light
|
|
518
530
|
// page would otherwise keep currentColor and render invisible.
|
|
531
|
+
// isBoardTarget only says which surface is behind the text, and the
|
|
532
|
+
// board is not always dark — a measured background always wins.
|
|
533
|
+
const measuredLightness = destinationBackgroundLightness(hostOrBody);
|
|
519
534
|
const autoTextNeedsLightFill =
|
|
520
|
-
|
|
521
|
-
|
|
535
|
+
measuredLightness === null
|
|
536
|
+
? options?.isBoardTarget === true
|
|
537
|
+
: !measuredLightness;
|
|
522
538
|
const resolvedTextColor =
|
|
523
539
|
primitive.fill ?? defaultCanvasTextColor(autoTextNeedsLightFill);
|
|
524
540
|
element.style.color = resolvedTextColor;
|
|
@@ -560,10 +576,6 @@ export function appendCanvasPrimitiveToHtml(
|
|
|
560
576
|
element.style.borderRadius = canonical.borderRadius;
|
|
561
577
|
}
|
|
562
578
|
|
|
563
|
-
if (host) {
|
|
564
|
-
element.style.left = `${left - host.x}px`;
|
|
565
|
-
element.style.top = `${top - host.y}px`;
|
|
566
|
-
}
|
|
567
579
|
hostOrBody.appendChild(element);
|
|
568
580
|
return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
|
|
569
581
|
} catch {
|
|
@@ -222,6 +222,16 @@ export function defaultCanvasTextColor(isBoardTarget: boolean): string {
|
|
|
222
222
|
return isBoardTarget ? "#ffffff" : "currentColor";
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* A committed frame needs a surface of its own: once deselected, a bare
|
|
227
|
+
* container is indistinguishable from empty canvas, and so is the next one
|
|
228
|
+
* drawn beside it. White on every destination, as in Figma.
|
|
229
|
+
*/
|
|
230
|
+
export function defaultCanvasFrameFill(): string {
|
|
231
|
+
// guard:allow-raw-color — written into the user's design HTML, where the editor's theme tokens do not exist
|
|
232
|
+
return "#ffffff";
|
|
233
|
+
}
|
|
234
|
+
|
|
225
235
|
/** Default font stack for board-drawn text — Inter with the app's standard
|
|
226
236
|
* system-font fallback chain, matching the rest of the editor's UI type
|
|
227
237
|
* instead of the browser's serif default for an unstyled <div>. */
|
|
@@ -12,7 +12,10 @@ import {
|
|
|
12
12
|
} from "@/lib/design-clipboard-managed-styles";
|
|
13
13
|
|
|
14
14
|
import { uniqueLayerId } from "./canvas-primitive-insert";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
reassignClonedAuthoredIds,
|
|
17
|
+
reassignClonedSourceIdentity,
|
|
18
|
+
} from "./clone-idrefs";
|
|
16
19
|
import { queryUniqueSelector } from "./dom-utils";
|
|
17
20
|
import { isStandaloneHttpUrl } from "./editor-state";
|
|
18
21
|
import {
|
|
@@ -184,10 +187,27 @@ function setRootLayerPosition(
|
|
|
184
187
|
host.style.bottom = "";
|
|
185
188
|
}
|
|
186
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Keep an incoming node id the destination does not already use, so a clone
|
|
192
|
+
* the caller already rendered stays addressable by the id it carries.
|
|
193
|
+
*/
|
|
194
|
+
function claimClonedNodeId(
|
|
195
|
+
previousId: string | null,
|
|
196
|
+
fallbackPrefix: string,
|
|
197
|
+
reservedNodeIds: Set<string> | null,
|
|
198
|
+
): string {
|
|
199
|
+
if (!reservedNodeIds || !previousId || reservedNodeIds.has(previousId)) {
|
|
200
|
+
return uniqueLayerId(fallbackPrefix);
|
|
201
|
+
}
|
|
202
|
+
reservedNodeIds.add(previousId);
|
|
203
|
+
return previousId;
|
|
204
|
+
}
|
|
205
|
+
|
|
187
206
|
function prepareClonedHtmlLayer(
|
|
188
207
|
doc: Document,
|
|
189
208
|
layerHtml: string,
|
|
190
209
|
styleSnapshot?: PortableStyleSnapshot,
|
|
210
|
+
reservedNodeIds: Set<string> | null = null,
|
|
191
211
|
): {
|
|
192
212
|
element: Element;
|
|
193
213
|
rootNodeId: string;
|
|
@@ -215,13 +235,21 @@ function prepareClonedHtmlLayer(
|
|
|
215
235
|
}
|
|
216
236
|
const nodeIdMap = new Map<string, string>();
|
|
217
237
|
const previousRootNodeId = clone.getAttribute("data-agent-native-node-id");
|
|
218
|
-
const rootNodeId =
|
|
238
|
+
const rootNodeId = claimClonedNodeId(
|
|
239
|
+
previousRootNodeId,
|
|
240
|
+
"copy",
|
|
241
|
+
reservedNodeIds,
|
|
242
|
+
);
|
|
219
243
|
clone.setAttribute("data-agent-native-node-id", rootNodeId);
|
|
220
244
|
if (previousRootNodeId) nodeIdMap.set(previousRootNodeId, rootNodeId);
|
|
221
245
|
Array.from(clone.querySelectorAll("[data-agent-native-node-id]")).forEach(
|
|
222
246
|
(node) => {
|
|
223
247
|
const previousChildId = node.getAttribute("data-agent-native-node-id");
|
|
224
|
-
const nextChildId =
|
|
248
|
+
const nextChildId = claimClonedNodeId(
|
|
249
|
+
previousChildId,
|
|
250
|
+
"copy-child",
|
|
251
|
+
reservedNodeIds,
|
|
252
|
+
);
|
|
225
253
|
node.setAttribute("data-agent-native-node-id", nextChildId);
|
|
226
254
|
if (previousChildId) nodeIdMap.set(previousChildId, nextChildId);
|
|
227
255
|
},
|
|
@@ -234,6 +262,7 @@ function prepareClonedHtmlLayer(
|
|
|
234
262
|
// happens to match first (typically the ORIGINAL, not the new copy),
|
|
235
263
|
// silently misapplying edits meant for the duplicate.
|
|
236
264
|
reassignClonedAuthoredIds(clone, () => uniqueLayerId("copy-id"));
|
|
265
|
+
reassignClonedSourceIdentity(clone, () => uniqueLayerId("copy-child"));
|
|
237
266
|
return { element: clone, rootNodeId, nodeIdMap };
|
|
238
267
|
}
|
|
239
268
|
|
|
@@ -345,6 +374,13 @@ export function insertClonedHtmlLayers(
|
|
|
345
374
|
managedStyleSnapshots?: Array<
|
|
346
375
|
DesignClipboardManagedStyleSnapshot | null | undefined
|
|
347
376
|
>;
|
|
377
|
+
/**
|
|
378
|
+
* The layer html describes an element the caller has ALREADY rendered
|
|
379
|
+
* under these ids. Re-minting them strands that element: every later
|
|
380
|
+
* message addresses an id the document lacks, and the fuzzy resolver
|
|
381
|
+
* behind it lands on the node the clone was copied from.
|
|
382
|
+
*/
|
|
383
|
+
preserveIncomingNodeIds?: boolean;
|
|
348
384
|
} = {},
|
|
349
385
|
): {
|
|
350
386
|
content: string;
|
|
@@ -365,11 +401,19 @@ export function insertClonedHtmlLayers(
|
|
|
365
401
|
const fragment = doc.createDocumentFragment();
|
|
366
402
|
const rootNodeIds: string[] = [];
|
|
367
403
|
const nodeIdMap = new Map<string, string>();
|
|
404
|
+
const reservedNodeIds = options.preserveIncomingNodeIds
|
|
405
|
+
? new Set(
|
|
406
|
+
Array.from(doc.querySelectorAll("[data-agent-native-node-id]"))
|
|
407
|
+
.map((node) => node.getAttribute("data-agent-native-node-id"))
|
|
408
|
+
.filter((value): value is string => Boolean(value)),
|
|
409
|
+
)
|
|
410
|
+
: null;
|
|
368
411
|
layerHtmls.forEach((layerHtml, index) => {
|
|
369
412
|
const prepared = prepareClonedHtmlLayer(
|
|
370
413
|
doc,
|
|
371
414
|
layerHtml,
|
|
372
415
|
options.styleSnapshots?.[index] ?? undefined,
|
|
416
|
+
reservedNodeIds,
|
|
373
417
|
);
|
|
374
418
|
if (!prepared) return;
|
|
375
419
|
const position = options.positions?.[index];
|
|
@@ -447,6 +491,7 @@ export function insertClonedHtmlLayer(
|
|
|
447
491
|
targetSelectors: string[];
|
|
448
492
|
anchorSelectors?: string[];
|
|
449
493
|
placement?: "before" | "after" | "inside";
|
|
494
|
+
preserveIncomingNodeIds?: boolean;
|
|
450
495
|
},
|
|
451
496
|
): string | null {
|
|
452
497
|
return (
|
|
@@ -454,6 +499,7 @@ export function insertClonedHtmlLayer(
|
|
|
454
499
|
targetSelectors: options.targetSelectors,
|
|
455
500
|
anchorSelectors: options.anchorSelectors,
|
|
456
501
|
placement: options.placement,
|
|
502
|
+
preserveIncomingNodeIds: options.preserveIncomingNodeIds,
|
|
457
503
|
})?.content ?? null
|
|
458
504
|
);
|
|
459
505
|
}
|
|
@@ -12,6 +12,16 @@ const SPACE_SEPARATED_IDREF_ATTRIBUTES = [
|
|
|
12
12
|
const SINGLE_IDREF_ATTRIBUTES = ["for", "form", "list"] as const;
|
|
13
13
|
const FRAGMENT_REFERENCE_ATTRIBUTES = ["href", "xlink:href"] as const;
|
|
14
14
|
|
|
15
|
+
// Lookups test every stable id attribute, so re-stamping
|
|
16
|
+
// `data-agent-native-node-id` does not shadow an inherited one.
|
|
17
|
+
// `data-source-file`/`-line`/`-column` are provenance, not identity — keep them.
|
|
18
|
+
const INHERITED_SOURCE_IDENTITY_ATTRIBUTES = [
|
|
19
|
+
"data-code-layer-id",
|
|
20
|
+
"data-layer-id",
|
|
21
|
+
"data-builder-id",
|
|
22
|
+
"data-loc",
|
|
23
|
+
] as const;
|
|
24
|
+
|
|
15
25
|
function allElements(root: Element): Element[] {
|
|
16
26
|
return [root, ...Array.from(root.querySelectorAll("*"))];
|
|
17
27
|
}
|
|
@@ -111,3 +121,24 @@ export function reassignClonedAuthoredIds(
|
|
|
111
121
|
|
|
112
122
|
return idMap;
|
|
113
123
|
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Replace a cloned subtree's inherited source identity with fresh node ids, so
|
|
127
|
+
* edits and deletes addressed to the clone cannot resolve to the original.
|
|
128
|
+
*/
|
|
129
|
+
export function reassignClonedSourceIdentity(
|
|
130
|
+
root: Element,
|
|
131
|
+
createNodeId: () => string,
|
|
132
|
+
): void {
|
|
133
|
+
for (const element of allElements(root)) {
|
|
134
|
+
let inheritedIdentity = false;
|
|
135
|
+
for (const attribute of INHERITED_SOURCE_IDENTITY_ATTRIBUTES) {
|
|
136
|
+
if (!element.hasAttribute(attribute)) continue;
|
|
137
|
+
element.removeAttribute(attribute);
|
|
138
|
+
inheritedIdentity = true;
|
|
139
|
+
}
|
|
140
|
+
if (!inheritedIdentity) continue;
|
|
141
|
+
if (element.hasAttribute("data-agent-native-node-id")) continue;
|
|
142
|
+
element.setAttribute("data-agent-native-node-id", createNodeId());
|
|
143
|
+
}
|
|
144
|
+
}
|