@agent-native/core 0.163.3 → 0.164.1
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/app/hooks/use-dashboard-chat-context.ts +29 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
- package/corpus/templates/analytics/netlify.toml +2 -0
- package/corpus/templates/assets/netlify.toml +2 -0
- package/corpus/templates/brain/netlify.toml +2 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
- package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
- package/corpus/templates/calendar/netlify.toml +2 -0
- package/corpus/templates/chat/netlify.toml +2 -0
- package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
- package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
- package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
- package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
- package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
- package/corpus/templates/clips/app/routes/_index.tsx +10 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
- package/corpus/templates/clips/netlify.toml +2 -0
- package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
- package/corpus/templates/content/netlify.toml +2 -0
- package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
- package/corpus/templates/design/AGENTS.md +3 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
- package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
- package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
- package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
- package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
- package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
- package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
- package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
- package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
- package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
- package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
- package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
- package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
- package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
- package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
- package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
- package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
- package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
- package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
- package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
- package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
- package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
- package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
- package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
- package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
- package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
- package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
- package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
- package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
- package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
- package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
- package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
- package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
- package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
- package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
- package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
- package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
- package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
- package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
- package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
- package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
- package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
- package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
- package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
- package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
- package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
- package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
- package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
- package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
- package/corpus/templates/design/netlify.toml +2 -0
- package/corpus/templates/design/shared/board-file.ts +2 -1
- package/corpus/templates/dispatch/netlify.toml +2 -0
- package/corpus/templates/forms/netlify.toml +2 -0
- package/corpus/templates/mail/netlify.toml +2 -0
- package/corpus/templates/plan/netlify.toml +2 -0
- package/corpus/templates/slides/netlify.toml +2 -0
- package/dist/agent/engine/builder-engine.d.ts +6 -4
- package/dist/agent/engine/builder-engine.js +91 -26
- package/dist/agent/engine/registry.js +16 -0
- package/dist/cli/code-agent-agent-tools.d.ts +2 -0
- package/dist/cli/code-agent-agent-tools.js +188 -0
- package/dist/cli/code-agent-collaboration.d.ts +34 -0
- package/dist/cli/code-agent-collaboration.js +104 -0
- package/dist/cli/code-agent-executor.js +6 -0
- package/dist/cli/code-agent-schedules.d.ts +54 -0
- package/dist/cli/code-agent-schedules.js +198 -0
- package/dist/client/AgentPanel.js +1 -1
- package/dist/client/ConnectBuilderCard.js +5 -2
- package/dist/client/EnvironmentBadge.js +6 -5
- package/dist/client/chat/run-recovery.js +3 -2
- package/dist/client/chat/tool-call-display.js +2 -3
- package/dist/client/components/CodeRequiredDialog.js +4 -2
- package/dist/client/i18n.js +5 -4
- package/dist/client/settings/SettingsPanel.js +1 -1
- package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
- package/dist/client/settings/useBuilderStatus.d.ts +6 -1
- package/dist/client/settings/useBuilderStatus.js +22 -33
- package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
- package/dist/code-agents/index.d.ts +2 -0
- package/dist/code-agents/index.js +2 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/localization/shared.d.ts +2 -0
- package/dist/localization/shared.js +5 -0
- package/dist/mcp-client/oauth-client.d.ts +1 -0
- package/dist/mcp-client/oauth-client.js +50 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/auth-marketing-locales.d.ts +7 -0
- package/dist/server/auth-marketing-locales.js +1322 -0
- package/dist/server/auth-marketing.d.ts +1 -0
- package/dist/server/auth-marketing.js +35 -6
- package/dist/server/auth.js +4 -17
- package/dist/server/builder-browser.d.ts +22 -0
- package/dist/server/builder-browser.js +77 -1
- package/dist/server/builder-oauth.d.ts +50 -0
- package/dist/server/builder-oauth.js +263 -0
- package/dist/server/core-routes-plugin.d.ts +11 -0
- package/dist/server/core-routes-plugin.js +291 -415
- package/dist/server/credential-provider.d.ts +1 -1
- package/dist/server/credential-provider.js +1 -1
- package/dist/server/csrf.js +2 -2
- package/dist/server/onboarding-html.js +52 -120
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/store.d.ts +9 -0
- package/dist/settings/store.js +19 -0
- package/dist/templates/chat/netlify.toml +2 -0
- package/dist/vite/client.js +35 -37
- package/docs/content/deployment.mdx +2 -0
- package/docs/content/multi-app-workspace.mdx +1 -1
- package/package.json +2 -2
- package/src/templates/chat/netlify.toml +2 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { getBrowserTabId } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { buildSignInReturnHref } from "@agent-native/core/client/ui";
|
|
3
|
+
import { buildCodeLayerProjection } from "@shared/code-layer";
|
|
4
|
+
|
|
5
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
6
|
+
import { designSelectionStateKeysForTab } from "@/hooks/use-navigation-state";
|
|
7
|
+
import type {
|
|
8
|
+
PostAuthDesignIntent,
|
|
9
|
+
RuntimeLayerSnapshot,
|
|
10
|
+
} from "@/pages/design-editor/command-types";
|
|
11
|
+
|
|
12
|
+
// Selection is tab-scoped (like navigation) so a second editor tab cannot
|
|
13
|
+
// overwrite this tab's selection context. The global key is mirrored as a
|
|
14
|
+
// fallback for CLI/external agents that do not send a browser tab id.
|
|
15
|
+
export function designSelectionStateKeys(): string[] {
|
|
16
|
+
return designSelectionStateKeysForTab(getBrowserTabId());
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function runtimeMultiplicityForElementProvenance(
|
|
20
|
+
snapshots: Record<string, RuntimeLayerSnapshot>,
|
|
21
|
+
info: ElementInfo | null | undefined,
|
|
22
|
+
): number {
|
|
23
|
+
const provenance = info?.provenance;
|
|
24
|
+
if (!provenance?.sourceFile || !provenance.line || !provenance.column) {
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
let count = 0;
|
|
28
|
+
for (const snapshot of Object.values(snapshots)) {
|
|
29
|
+
const projection = buildCodeLayerProjection(snapshot.html);
|
|
30
|
+
for (const node of projection.nodes) {
|
|
31
|
+
const attrs = node.dataAttributes;
|
|
32
|
+
if (
|
|
33
|
+
attrs["data-source-file"] === provenance.sourceFile &&
|
|
34
|
+
Number(attrs["data-source-line"]) === provenance.line &&
|
|
35
|
+
Number(attrs["data-source-column"]) === provenance.column &&
|
|
36
|
+
(!provenance.component ||
|
|
37
|
+
attrs["data-component-name"] === provenance.component)
|
|
38
|
+
) {
|
|
39
|
+
count += 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return Math.max(1, count);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function buildSignInHrefForDesignIntent(
|
|
47
|
+
intent: PostAuthDesignIntent,
|
|
48
|
+
): string {
|
|
49
|
+
if (typeof window === "undefined") return buildSignInReturnHref();
|
|
50
|
+
return buildSignInReturnHref({
|
|
51
|
+
returnTo: `${window.location.pathname}?intent=${encodeURIComponent(intent)}`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function buildSignInHrefForComment(): string {
|
|
56
|
+
if (typeof window === "undefined") return buildSignInReturnHref();
|
|
57
|
+
return buildSignInReturnHref({ returnTo: window.location.pathname });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* True only when the incoming (intent-less) selection authoritatively refers to
|
|
62
|
+
* a different element than the committed one (sourceId match wins, else CSS
|
|
63
|
+
* selector). Returns false when identity can't be compared, so a real selection
|
|
64
|
+
* is never dropped.
|
|
65
|
+
*/
|
|
66
|
+
export function isSupersededSelectionEcho(
|
|
67
|
+
incoming: ElementInfo,
|
|
68
|
+
current: ElementInfo | null,
|
|
69
|
+
): boolean {
|
|
70
|
+
if (!current) return false;
|
|
71
|
+
const incomingId = incoming.sourceId?.trim();
|
|
72
|
+
const currentId = current.sourceId?.trim();
|
|
73
|
+
if (incomingId && currentId) return incomingId !== currentId;
|
|
74
|
+
const incomingSelector = incoming.selector?.trim();
|
|
75
|
+
const currentSelector = current.selector?.trim();
|
|
76
|
+
if (incomingSelector && currentSelector)
|
|
77
|
+
return incomingSelector !== currentSelector;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function describeSelectionForHost(element: ElementInfo): {
|
|
82
|
+
label: string;
|
|
83
|
+
detail: string;
|
|
84
|
+
} {
|
|
85
|
+
const text = element.textContent?.trim();
|
|
86
|
+
const label =
|
|
87
|
+
element.provenance?.component ||
|
|
88
|
+
element.componentName ||
|
|
89
|
+
(text ? text.slice(0, 60) : "") ||
|
|
90
|
+
element.tagName.toLowerCase();
|
|
91
|
+
const provenance = element.provenance;
|
|
92
|
+
const sourceFile = provenance?.sourceFile
|
|
93
|
+
? `${provenance.sourceFile}${provenance.line ? `:${provenance.line}` : ""}`
|
|
94
|
+
: null;
|
|
95
|
+
const classSelector = element.classes?.length
|
|
96
|
+
? `${element.tagName.toLowerCase()}.${element.classes.slice(0, 2).join(".")}`
|
|
97
|
+
: null;
|
|
98
|
+
const detail =
|
|
99
|
+
sourceFile ??
|
|
100
|
+
classSelector ??
|
|
101
|
+
element.selector ??
|
|
102
|
+
element.tagName.toLowerCase();
|
|
103
|
+
return { label, detail };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function reloadRunningAppPreviewFrames(): void {
|
|
107
|
+
if (typeof document === "undefined") return;
|
|
108
|
+
const frames = document.querySelectorAll<HTMLIFrameElement>(
|
|
109
|
+
"iframe[data-design-preview-iframe]",
|
|
110
|
+
);
|
|
111
|
+
for (const frame of frames) {
|
|
112
|
+
const src = frame.getAttribute("src");
|
|
113
|
+
if (!src) continue;
|
|
114
|
+
let origin: string;
|
|
115
|
+
try {
|
|
116
|
+
origin = new URL(src, window.location.href).origin;
|
|
117
|
+
// coercion-ok: an unparsable src names no container to reload.
|
|
118
|
+
} catch {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (origin === window.location.origin) continue;
|
|
122
|
+
frame.contentWindow?.postMessage({ type: "agentNative.reload" }, origin);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* A code-layer-derived ElementInfo carries authored inline styles and a zero
|
|
128
|
+
* rect, so the inspector shows 0 for anything the source does not state
|
|
129
|
+
* (hug sizing, in-flow position). Measure the live preview node instead.
|
|
130
|
+
*/
|
|
131
|
+
export function withMeasuredGeometry(
|
|
132
|
+
info: ElementInfo,
|
|
133
|
+
screenId?: string,
|
|
134
|
+
): ElementInfo {
|
|
135
|
+
const rect = info.boundingRect;
|
|
136
|
+
if (rect && (rect.width > 0 || rect.height > 0)) return info;
|
|
137
|
+
if (typeof document === "undefined") return info;
|
|
138
|
+
const selector = info.runtimeSelector ?? info.selector;
|
|
139
|
+
if (!selector) return info;
|
|
140
|
+
// Selectors and stamped ids are per-screen, so an unscoped scan can measure
|
|
141
|
+
// identical markup on a different screen.
|
|
142
|
+
const owning = screenId
|
|
143
|
+
? document.querySelector<HTMLIFrameElement>(
|
|
144
|
+
`iframe[data-design-preview-iframe][data-screen-iframe-id="${CSS.escape(screenId)}"]`,
|
|
145
|
+
)
|
|
146
|
+
: null;
|
|
147
|
+
const frames = owning
|
|
148
|
+
? [owning]
|
|
149
|
+
: Array.from(
|
|
150
|
+
document.querySelectorAll<HTMLIFrameElement>(
|
|
151
|
+
"iframe[data-design-preview-iframe]",
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
for (const frame of frames) {
|
|
155
|
+
let node: Element | null = null;
|
|
156
|
+
try {
|
|
157
|
+
node = frame.contentDocument?.querySelector(selector) ?? null;
|
|
158
|
+
} catch {
|
|
159
|
+
node = null;
|
|
160
|
+
}
|
|
161
|
+
if (!node) continue;
|
|
162
|
+
const box = node.getBoundingClientRect();
|
|
163
|
+
if (box.width <= 0 && box.height <= 0) continue;
|
|
164
|
+
const computed = frame.contentWindow?.getComputedStyle(node);
|
|
165
|
+
return {
|
|
166
|
+
...info,
|
|
167
|
+
boundingRect: {
|
|
168
|
+
x: box.x,
|
|
169
|
+
y: box.y,
|
|
170
|
+
width: box.width,
|
|
171
|
+
height: box.height,
|
|
172
|
+
},
|
|
173
|
+
computedStyles: computed
|
|
174
|
+
? {
|
|
175
|
+
width: computed.width,
|
|
176
|
+
height: computed.height,
|
|
177
|
+
...info.computedStyles,
|
|
178
|
+
}
|
|
179
|
+
: info.computedStyles,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return info;
|
|
183
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { shouldUseLiveFileContent } from "@shared/html-content";
|
|
2
|
+
import { sourceContentHash } from "@shared/source-workspace";
|
|
3
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
4
|
+
import * as Y from "yjs";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
TAB_ID,
|
|
8
|
+
shouldAdoptExternalReconcileContent,
|
|
9
|
+
} from "@/pages/design-editor/editor-session";
|
|
10
|
+
import type { PreviewContentReplaceResult } from "@/pages/design-editor/editor-state";
|
|
11
|
+
import { previewContentReplaceNeedsRenderFallback } from "@/pages/design-editor/editor-state";
|
|
12
|
+
import type { ContentHistoryChange } from "@/pages/design-editor/history";
|
|
13
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
14
|
+
|
|
15
|
+
export interface AdoptDbFileContentArgs {
|
|
16
|
+
activeFile: DesignFile;
|
|
17
|
+
agentActive: boolean;
|
|
18
|
+
clearStaleAgentCollabRecovery: () => void;
|
|
19
|
+
collabContent: string | null;
|
|
20
|
+
collabContentFileId: string | null;
|
|
21
|
+
collabContentFileIdRef: RefObject<string | null>;
|
|
22
|
+
collabContentRef: RefObject<string | null>;
|
|
23
|
+
documentFileContentRef: RefObject<string | null>;
|
|
24
|
+
documentFileUpdatedAtRef: RefObject<string | null>;
|
|
25
|
+
isLeadClient: boolean;
|
|
26
|
+
isSynced: boolean;
|
|
27
|
+
lastAckedFileContentHashRef: RefObject<Record<string, string>>;
|
|
28
|
+
lastAppliedFileUpdatedAtRef: RefObject<string | null>;
|
|
29
|
+
lastLocalContentRef: RefObject<string | null>;
|
|
30
|
+
latestActiveContentRef: RefObject<string | null>;
|
|
31
|
+
recordExternalContentHistoryCheckpoint: (
|
|
32
|
+
change: ContentHistoryChange,
|
|
33
|
+
) => void;
|
|
34
|
+
replacePreviewContent: (
|
|
35
|
+
nextContent: string,
|
|
36
|
+
selector?: string | null,
|
|
37
|
+
options?: { forceFullDocument?: boolean },
|
|
38
|
+
) => PreviewContentReplaceResult;
|
|
39
|
+
setCollabContent: Dispatch<SetStateAction<string | null>>;
|
|
40
|
+
setCollabContentFileId: Dispatch<SetStateAction<string | null>>;
|
|
41
|
+
setContentRenderRevision: Dispatch<SetStateAction<number>>;
|
|
42
|
+
staleAgentCollabRecoveryTimerRef: RefObject<number | null>;
|
|
43
|
+
undoManagerRef: RefObject<Y.UndoManager | null>;
|
|
44
|
+
ydoc: Y.Doc | null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function runAdoptDbFileContent({
|
|
48
|
+
activeFile,
|
|
49
|
+
agentActive,
|
|
50
|
+
clearStaleAgentCollabRecovery,
|
|
51
|
+
collabContent,
|
|
52
|
+
collabContentFileId,
|
|
53
|
+
collabContentFileIdRef,
|
|
54
|
+
collabContentRef,
|
|
55
|
+
documentFileContentRef,
|
|
56
|
+
documentFileUpdatedAtRef,
|
|
57
|
+
isLeadClient,
|
|
58
|
+
isSynced,
|
|
59
|
+
lastAckedFileContentHashRef,
|
|
60
|
+
lastAppliedFileUpdatedAtRef,
|
|
61
|
+
lastLocalContentRef,
|
|
62
|
+
latestActiveContentRef,
|
|
63
|
+
recordExternalContentHistoryCheckpoint,
|
|
64
|
+
replacePreviewContent,
|
|
65
|
+
setCollabContent,
|
|
66
|
+
setCollabContentFileId,
|
|
67
|
+
setContentRenderRevision,
|
|
68
|
+
staleAgentCollabRecoveryTimerRef,
|
|
69
|
+
undoManagerRef,
|
|
70
|
+
ydoc,
|
|
71
|
+
}: AdoptDbFileContentArgs) {
|
|
72
|
+
if (!activeFile || !isSynced) return;
|
|
73
|
+
const dbContent = activeFile.content ?? "";
|
|
74
|
+
const dbUpdatedAt = activeFile.updatedAt ?? null;
|
|
75
|
+
const activeScopedCollabContent =
|
|
76
|
+
collabContentFileId === activeFile.id ? collabContent : null;
|
|
77
|
+
if (
|
|
78
|
+
typeof activeScopedCollabContent === "string" &&
|
|
79
|
+
!shouldUseLiveFileContent({
|
|
80
|
+
liveContent: activeScopedCollabContent,
|
|
81
|
+
storedContent: dbContent,
|
|
82
|
+
fileType: activeFile.fileType,
|
|
83
|
+
})
|
|
84
|
+
) {
|
|
85
|
+
clearStaleAgentCollabRecovery();
|
|
86
|
+
setCollabContent(dbContent);
|
|
87
|
+
setCollabContentFileId(activeFile.id);
|
|
88
|
+
lastLocalContentRef.current = dbContent;
|
|
89
|
+
latestActiveContentRef.current = dbContent;
|
|
90
|
+
lastAckedFileContentHashRef.current[activeFile.id] =
|
|
91
|
+
sourceContentHash(dbContent);
|
|
92
|
+
if (dbUpdatedAt) lastAppliedFileUpdatedAtRef.current = dbUpdatedAt;
|
|
93
|
+
if (
|
|
94
|
+
previewContentReplaceNeedsRenderFallback(
|
|
95
|
+
replacePreviewContent(dbContent, null, {
|
|
96
|
+
forceFullDocument: true,
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
) {
|
|
100
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isLeadClient && ydoc) {
|
|
104
|
+
const ytext = ydoc.getText("content");
|
|
105
|
+
if (ytext.toString() !== dbContent) {
|
|
106
|
+
// Untracked full rewrite (agent edit / external DB content
|
|
107
|
+
// replacing a live doc that diverged) — clear the undo stack so a
|
|
108
|
+
// stale tracked delta can't be replayed against content it no
|
|
109
|
+
// longer matches (see U1: this is the primary corruption path —
|
|
110
|
+
// agent edits, motion autosave, and id-stamping all land here).
|
|
111
|
+
undoManagerRef.current?.clear(true, false);
|
|
112
|
+
ydoc.transact(() => {
|
|
113
|
+
ytext.delete(0, ytext.length);
|
|
114
|
+
ytext.insert(0, dbContent);
|
|
115
|
+
}, TAB_ID);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Already reflecting this exact content (our own echo or Yjs already
|
|
122
|
+
// delivered it) — just advance the watermark and stop.
|
|
123
|
+
if (
|
|
124
|
+
activeScopedCollabContent === dbContent ||
|
|
125
|
+
lastLocalContentRef.current === dbContent
|
|
126
|
+
) {
|
|
127
|
+
if (dbUpdatedAt) lastAppliedFileUpdatedAtRef.current = dbUpdatedAt;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Only adopt genuinely newer content. No baseline yet (fresh file load)
|
|
132
|
+
// always adopts so a stale persisted Y.Doc can't shadow newer SQL. See
|
|
133
|
+
// shouldAdoptExternalReconcileContent's doc comment
|
|
134
|
+
// (design-editor/editor-session.ts) for the same-millisecond tie-break
|
|
135
|
+
// fix this closes: a strict `>` used to silently drop a real external
|
|
136
|
+
// write that landed in the same millisecond as the one already applied,
|
|
137
|
+
// whenever agentActive was false.
|
|
138
|
+
const applied = lastAppliedFileUpdatedAtRef.current;
|
|
139
|
+
const externalNewer = shouldAdoptExternalReconcileContent({
|
|
140
|
+
appliedUpdatedAt: applied,
|
|
141
|
+
dbUpdatedAt,
|
|
142
|
+
agentActive,
|
|
143
|
+
});
|
|
144
|
+
const staleAgentEchoPossible =
|
|
145
|
+
agentActive &&
|
|
146
|
+
!!applied &&
|
|
147
|
+
!!dbUpdatedAt &&
|
|
148
|
+
dbUpdatedAt === applied &&
|
|
149
|
+
lastLocalContentRef.current !== activeScopedCollabContent;
|
|
150
|
+
if (!externalNewer) {
|
|
151
|
+
if (staleAgentEchoPossible) {
|
|
152
|
+
if (staleAgentCollabRecoveryTimerRef.current === null) {
|
|
153
|
+
const expectedContent = dbContent;
|
|
154
|
+
const expectedUpdatedAt = dbUpdatedAt;
|
|
155
|
+
const expectedFileId = activeFile.id;
|
|
156
|
+
staleAgentCollabRecoveryTimerRef.current = window.setTimeout(() => {
|
|
157
|
+
staleAgentCollabRecoveryTimerRef.current = null;
|
|
158
|
+
const currentCollab = collabContentRef.current;
|
|
159
|
+
if (collabContentFileIdRef.current !== expectedFileId) return;
|
|
160
|
+
if (documentFileUpdatedAtRef.current !== expectedUpdatedAt) return;
|
|
161
|
+
if (documentFileContentRef.current !== expectedContent) return;
|
|
162
|
+
if (currentCollab === expectedContent) return;
|
|
163
|
+
if (lastLocalContentRef.current === currentCollab) return;
|
|
164
|
+
|
|
165
|
+
setCollabContent(expectedContent);
|
|
166
|
+
setCollabContentFileId(expectedFileId);
|
|
167
|
+
lastLocalContentRef.current = expectedContent;
|
|
168
|
+
latestActiveContentRef.current = expectedContent;
|
|
169
|
+
lastAckedFileContentHashRef.current[expectedFileId] =
|
|
170
|
+
sourceContentHash(expectedContent);
|
|
171
|
+
lastAppliedFileUpdatedAtRef.current = expectedUpdatedAt;
|
|
172
|
+
if (
|
|
173
|
+
previewContentReplaceNeedsRenderFallback(
|
|
174
|
+
replacePreviewContent(expectedContent, null, {
|
|
175
|
+
forceFullDocument: true,
|
|
176
|
+
}),
|
|
177
|
+
)
|
|
178
|
+
) {
|
|
179
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (isLeadClient && ydoc) {
|
|
183
|
+
const ytext = ydoc.getText("content");
|
|
184
|
+
if (ytext.toString() !== expectedContent) {
|
|
185
|
+
// Untracked full rewrite — see U1 note above.
|
|
186
|
+
undoManagerRef.current?.clear(true, false);
|
|
187
|
+
ydoc.transact(() => {
|
|
188
|
+
ytext.delete(0, ytext.length);
|
|
189
|
+
ytext.insert(0, expectedContent);
|
|
190
|
+
}, TAB_ID);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}, 1200);
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
clearStaleAgentCollabRecovery();
|
|
197
|
+
}
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
clearStaleAgentCollabRecovery();
|
|
201
|
+
|
|
202
|
+
// U21: this whole effect exists BECAUSE the Yjs observe path (which
|
|
203
|
+
// already checkpoints agent edits into the local undo fallback, see U3
|
|
204
|
+
// above at the ytext.observe handler) can miss the update — same-tab
|
|
205
|
+
// background, a paused collab poll, or no collab session ever
|
|
206
|
+
// established for this viewer. Every branch below used to respond to
|
|
207
|
+
// that exact case by clearing the undo manager with nothing to fall back
|
|
208
|
+
// to, so Cmd+Z after an agent-driven full-content replacement that
|
|
209
|
+
// landed via THIS path had nothing left to restore — the reported
|
|
210
|
+
// "pressed cmd+z but unable to change it back", reproduced end-to-end:
|
|
211
|
+
// an `edit-design`/`update-file` write (mode=replace-file) lands purely
|
|
212
|
+
// through `writeInlineSourceFile`'s SQL write, never through the Yjs
|
|
213
|
+
// collab broadcast that the U3 checkpoint above depends on to learn
|
|
214
|
+
// `agentActive` — so gating this path on the SAME `agentActive` flag
|
|
215
|
+
// (sourced from `useCollaborativeDoc`'s Yjs `requestSource === "agent"`
|
|
216
|
+
// signal) would almost never fire here, since the very reason a change
|
|
217
|
+
// lands on THIS path instead of the Yjs-observe path is that no matching
|
|
218
|
+
// Yjs broadcast arrived to flip that flag. Unlike the Yjs-observe site
|
|
219
|
+
// (which can distinguish an agent transaction from a human peer's by
|
|
220
|
+
// origin), this path cannot reliably tell "agent" from "another human
|
|
221
|
+
// editing the same design without a live collab session" apart — but
|
|
222
|
+
// recording a local undo checkpoint either way is still correct and
|
|
223
|
+
// safe: it only affects what THIS viewer's Cmd+Z reverts to, never what
|
|
224
|
+
// gets written back to any other collaborator.
|
|
225
|
+
const previousActiveContentForCheckpoint = latestActiveContentRef.current;
|
|
226
|
+
if (
|
|
227
|
+
typeof previousActiveContentForCheckpoint === "string" &&
|
|
228
|
+
previousActiveContentForCheckpoint !== dbContent
|
|
229
|
+
) {
|
|
230
|
+
recordExternalContentHistoryCheckpoint({
|
|
231
|
+
fileId: activeFile.id,
|
|
232
|
+
before: previousActiveContentForCheckpoint,
|
|
233
|
+
after: dbContent,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Render the newer content immediately so the preview is never stale.
|
|
238
|
+
setCollabContent(dbContent);
|
|
239
|
+
setCollabContentFileId(activeFile.id);
|
|
240
|
+
lastLocalContentRef.current = dbContent;
|
|
241
|
+
latestActiveContentRef.current = dbContent;
|
|
242
|
+
lastAckedFileContentHashRef.current[activeFile.id] =
|
|
243
|
+
sourceContentHash(dbContent);
|
|
244
|
+
if (dbUpdatedAt) lastAppliedFileUpdatedAtRef.current = dbUpdatedAt;
|
|
245
|
+
if (
|
|
246
|
+
previewContentReplaceNeedsRenderFallback(
|
|
247
|
+
replacePreviewContent(dbContent, null, { forceFullDocument: true }),
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
250
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Lead client mirrors it into the shared Y.Doc so other open clients
|
|
254
|
+
// receive it through Yjs and the durable collab state stays in step. The
|
|
255
|
+
// agent's update-file/generate-design already wrote the Y.Doc in-process,
|
|
256
|
+
// so in the common case this is a no-op diff; it only does real work when
|
|
257
|
+
// the Yjs update was missed (the failure this fallback exists to cover).
|
|
258
|
+
if (isLeadClient && ydoc) {
|
|
259
|
+
const ytext = ydoc.getText("content");
|
|
260
|
+
if (ytext.toString() !== dbContent) {
|
|
261
|
+
// Untracked full rewrite — see U1 note above. The view-appropriate
|
|
262
|
+
// checkpoint recorded above (U21) is what Cmd+Z now falls back to.
|
|
263
|
+
undoManagerRef.current?.clear(true, false);
|
|
264
|
+
ydoc.transact(() => {
|
|
265
|
+
ytext.delete(0, ytext.length);
|
|
266
|
+
ytext.insert(0, dbContent);
|
|
267
|
+
}, TAB_ID);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
removeAgentChatContextItem,
|
|
3
|
+
setAgentChatContextItem,
|
|
4
|
+
} from "@agent-native/core/client/agent-chat";
|
|
5
|
+
import type { CodeLayerNode } from "@shared/code-layer";
|
|
6
|
+
import type { RefObject } from "react";
|
|
7
|
+
|
|
8
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
9
|
+
import { nodeRepromptSubtreeExcerpt } from "@/lib/node-reprompt";
|
|
10
|
+
import { shouldMirrorSelectedElementToAgentChat } from "@/pages/design-editor/selection-state";
|
|
11
|
+
import type { DesignData, DesignFile } from "@/pages/design-editor/types";
|
|
12
|
+
|
|
13
|
+
export interface MirrorSelectionToAgentChatArgs {
|
|
14
|
+
activeFile: DesignFile;
|
|
15
|
+
activeProjectionContent: string;
|
|
16
|
+
composerContextHasOurKeyRef: RefObject<boolean>;
|
|
17
|
+
design: DesignData | null;
|
|
18
|
+
id: string | undefined;
|
|
19
|
+
isSignedIn: boolean;
|
|
20
|
+
mirroredSelectionIdRef: RefObject<string | null>;
|
|
21
|
+
selectedCodeLayerNode: CodeLayerNode | null;
|
|
22
|
+
selectedElement: ElementInfo | null;
|
|
23
|
+
sentSelectionIdRef: RefObject<string | null>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function runMirrorSelectionToAgentChat({
|
|
27
|
+
activeFile,
|
|
28
|
+
activeProjectionContent,
|
|
29
|
+
composerContextHasOurKeyRef,
|
|
30
|
+
design,
|
|
31
|
+
id,
|
|
32
|
+
isSignedIn,
|
|
33
|
+
mirroredSelectionIdRef,
|
|
34
|
+
selectedCodeLayerNode,
|
|
35
|
+
selectedElement,
|
|
36
|
+
sentSelectionIdRef,
|
|
37
|
+
}: MirrorSelectionToAgentChatArgs) {
|
|
38
|
+
const key = "design:selected-element";
|
|
39
|
+
if (!isSignedIn) return;
|
|
40
|
+
if (!id || !shouldMirrorSelectedElementToAgentChat(selectedElement)) {
|
|
41
|
+
mirroredSelectionIdRef.current = null;
|
|
42
|
+
sentSelectionIdRef.current = null;
|
|
43
|
+
removeAgentChatContextItem(key);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const selectionId = `${activeFile?.id ?? ""}::${selectedElement.sourceId ?? selectedElement.selector}`;
|
|
48
|
+
if (selectionId !== mirroredSelectionIdRef.current) {
|
|
49
|
+
// A genuinely new/changed selection always (re)attaches, regardless of
|
|
50
|
+
// whether the previous one was marked sent.
|
|
51
|
+
sentSelectionIdRef.current = null;
|
|
52
|
+
} else if (
|
|
53
|
+
sentSelectionIdRef.current === selectionId ||
|
|
54
|
+
!composerContextHasOurKeyRef.current
|
|
55
|
+
) {
|
|
56
|
+
// Same selection as before, and either it was already marked sent, or
|
|
57
|
+
// the shared store no longer carries our key (a send just cleared it,
|
|
58
|
+
// observed by the bookkeeping effect below) — stay cleared. Critically:
|
|
59
|
+
// do nothing else here, so this branch never calls
|
|
60
|
+
// setAgentChatContextItem for a selection that hasn't changed.
|
|
61
|
+
if (!composerContextHasOurKeyRef.current) {
|
|
62
|
+
sentSelectionIdRef.current = selectionId;
|
|
63
|
+
}
|
|
64
|
+
return;
|
|
65
|
+
} else {
|
|
66
|
+
// Same selection, still present in the shared store, nothing to do —
|
|
67
|
+
// avoid republishing (and thus avoid the feedback loop above) when
|
|
68
|
+
// nothing about the selection actually changed.
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
mirroredSelectionIdRef.current = selectionId;
|
|
72
|
+
|
|
73
|
+
const labelSource =
|
|
74
|
+
selectedElement.textContent?.trim() ||
|
|
75
|
+
selectedCodeLayerNode?.layerName ||
|
|
76
|
+
selectedElement.id ||
|
|
77
|
+
selectedElement.tagName.toLowerCase();
|
|
78
|
+
const shortLabel =
|
|
79
|
+
labelSource.length > 28 ? `${labelSource.slice(0, 25)}...` : labelSource;
|
|
80
|
+
const targetNodeId =
|
|
81
|
+
selectedCodeLayerNode?.dataAttributes[
|
|
82
|
+
"data-agent-native-node-id"
|
|
83
|
+
]?.trim() ??
|
|
84
|
+
selectedElement.sourceId ??
|
|
85
|
+
null;
|
|
86
|
+
const targetSelector =
|
|
87
|
+
selectedCodeLayerNode?.selector ?? selectedElement.selector ?? null;
|
|
88
|
+
// Excerpt the outerHTML out of the SOURCE projection rather than the
|
|
89
|
+
// rendered DOM: this is the exact text edit-design's search/replace has
|
|
90
|
+
// to match, so an edit anchored to it cannot drift onto a child or
|
|
91
|
+
// sibling that merely measures the same on canvas.
|
|
92
|
+
const selectedNodeSpan = selectedCodeLayerNode?.source;
|
|
93
|
+
const outerHtmlExcerpt = selectedNodeSpan
|
|
94
|
+
? nodeRepromptSubtreeExcerpt(
|
|
95
|
+
activeProjectionContent.slice(
|
|
96
|
+
selectedNodeSpan.start,
|
|
97
|
+
selectedNodeSpan.end,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
: "";
|
|
101
|
+
const contextLines = [
|
|
102
|
+
`Selected design element in design "${design?.title ?? id}".`,
|
|
103
|
+
`designId: ${id}`,
|
|
104
|
+
activeFile ? `fileId: ${activeFile.id}` : "",
|
|
105
|
+
activeFile ? `Active screen: ${activeFile.filename}` : "",
|
|
106
|
+
`target: ${targetNodeId ?? targetSelector ?? "unknown"}`,
|
|
107
|
+
targetNodeId ? `targetNodeId: ${targetNodeId}` : "",
|
|
108
|
+
targetSelector ? `targetSelector: ${targetSelector}` : "",
|
|
109
|
+
`Element: <${selectedElement.tagName.toLowerCase()}> ${shortLabel}`,
|
|
110
|
+
selectedCodeLayerNode ? `Code layer id: ${selectedCodeLayerNode.id}` : "",
|
|
111
|
+
selectedElement.classes.length
|
|
112
|
+
? `Classes: ${selectedElement.classes.join(" ")}`
|
|
113
|
+
: "",
|
|
114
|
+
selectedElement.textContent?.trim()
|
|
115
|
+
? `Text: ${selectedElement.textContent.trim()}`
|
|
116
|
+
: "",
|
|
117
|
+
outerHtmlExcerpt
|
|
118
|
+
? `--- selected element (outerHTML excerpt, truncated) ---\n${outerHtmlExcerpt}`
|
|
119
|
+
: "",
|
|
120
|
+
].filter(Boolean);
|
|
121
|
+
|
|
122
|
+
setAgentChatContextItem({
|
|
123
|
+
key,
|
|
124
|
+
title: shortLabel,
|
|
125
|
+
context: contextLines.join("\n"),
|
|
126
|
+
openSidebar: false,
|
|
127
|
+
// Mirror the selection into chat context without stealing focus: this
|
|
128
|
+
// effect re-fires on every selection change and on each get-design poll
|
|
129
|
+
// during an agent run, and focusing the composer here would blur (and
|
|
130
|
+
// tear down) an in-progress inline text edit on the canvas.
|
|
131
|
+
focus: false,
|
|
132
|
+
});
|
|
133
|
+
composerContextHasOurKeyRef.current = true;
|
|
134
|
+
}
|