@agent-native/core 0.163.4 → 0.164.3
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/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
- package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
- package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
- package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
- package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
- package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
- package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
- package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
- package/corpus/templates/analytics/netlify.toml +2 -0
- package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
- package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
- package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
- package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
- package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
- package/corpus/templates/assets/app/routes/picker.tsx +4 -1
- 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/_app.wispr.tsx +2 -1
- package/corpus/templates/clips/app/routes/_index.tsx +7 -3
- 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/app/routes/examples.tsx +2 -1
- 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/app/routes/_index.tsx +3 -2
- package/corpus/templates/forms/netlify.toml +2 -0
- package/corpus/templates/mail/app/routes/_index.tsx +3 -2
- 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/corpus/templates/tasks/app/routes/_index.tsx +2 -1
- 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/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.js +10 -4
- 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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- 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/ssr-handler.js +13 -4
- package/dist/settings/store.d.ts +9 -0
- package/dist/settings/store.js +19 -0
- package/dist/shared/cache-control.d.ts +22 -0
- package/dist/shared/cache-control.js +25 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +1 -0
- package/dist/templates/chat/netlify.toml +2 -0
- 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,132 @@
|
|
|
1
|
+
import { callAction } from "@agent-native/core/client/hooks";
|
|
2
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
4
|
+
|
|
5
|
+
import { sanitizeLocalhostSourceSnapshotHtml } from "@/components/design/design-canvas/external-preview";
|
|
6
|
+
import type { LocalhostWriteConsentPayload } from "@/components/design/LocalhostWriteConsentDialog";
|
|
7
|
+
import {
|
|
8
|
+
NO_LOCALHOST_CONNECTION_MESSAGE,
|
|
9
|
+
NO_LOCALHOST_WRITE_CONTENT_MESSAGE,
|
|
10
|
+
NO_LOCALHOST_WRITE_PATH_MESSAGE,
|
|
11
|
+
} from "@/pages/design-editor/editor-constants";
|
|
12
|
+
import { resolveLocalhostSourceWriteContent } from "@/pages/design-editor/editor-state";
|
|
13
|
+
|
|
14
|
+
export interface ApplyToSourceArgs {
|
|
15
|
+
activeLocalhostConnectionId: string;
|
|
16
|
+
activeLocalhostRelPath: string | undefined;
|
|
17
|
+
activeLocalhostSourceSnapshotHtml: string | undefined;
|
|
18
|
+
canEditDesign: boolean;
|
|
19
|
+
id: string | undefined;
|
|
20
|
+
latestActiveContentRef: RefObject<string | null>;
|
|
21
|
+
requestLocalhostWrite: (opts: {
|
|
22
|
+
files: string[];
|
|
23
|
+
onGranted: LocalhostWriteConsentPayload["onGranted"];
|
|
24
|
+
onCancel?: () => void;
|
|
25
|
+
}) => void;
|
|
26
|
+
setApplyToSourcePending: Dispatch<SetStateAction<boolean>>;
|
|
27
|
+
stripEditorOnlyAttributes: (html: string) => string;
|
|
28
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function runApplyToSource({
|
|
32
|
+
activeLocalhostConnectionId,
|
|
33
|
+
activeLocalhostRelPath,
|
|
34
|
+
activeLocalhostSourceSnapshotHtml,
|
|
35
|
+
canEditDesign,
|
|
36
|
+
id,
|
|
37
|
+
latestActiveContentRef,
|
|
38
|
+
requestLocalhostWrite,
|
|
39
|
+
setApplyToSourcePending,
|
|
40
|
+
stripEditorOnlyAttributes,
|
|
41
|
+
t,
|
|
42
|
+
}: ApplyToSourceArgs) {
|
|
43
|
+
if (!id || !canEditDesign) return;
|
|
44
|
+
// VE7: surface why "Apply to source" can't proceed instead of silently
|
|
45
|
+
// no-oping when the connection or the resolved source path is missing.
|
|
46
|
+
if (!activeLocalhostConnectionId) {
|
|
47
|
+
toast.error(NO_LOCALHOST_CONNECTION_MESSAGE);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!activeLocalhostRelPath) {
|
|
51
|
+
toast.error(NO_LOCALHOST_WRITE_PATH_MESSAGE);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const relPath = activeLocalhostRelPath;
|
|
55
|
+
const connectionId = activeLocalhostConnectionId;
|
|
56
|
+
// URL-backed Design files persist the route URL in `content`; HTML source
|
|
57
|
+
// comes from the authenticated snapshot model populated by DesignCanvas.
|
|
58
|
+
// Fail closed until that source exists so this button can never serialize
|
|
59
|
+
// a localhost URL and overwrite the user's actual file with it.
|
|
60
|
+
const ext = (relPath.match(/\.[^.]+$/) ?? [])[0]?.toLowerCase() ?? "";
|
|
61
|
+
const rawContent = resolveLocalhostSourceWriteContent({
|
|
62
|
+
extension: ext,
|
|
63
|
+
persistedContent: latestActiveContentRef.current,
|
|
64
|
+
liveSnapshotHtml: activeLocalhostSourceSnapshotHtml,
|
|
65
|
+
});
|
|
66
|
+
if (!rawContent) {
|
|
67
|
+
toast.error(NO_LOCALHOST_WRITE_CONTENT_MESSAGE);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
// Strip editor-only attributes before writing so the on-disk file stays
|
|
71
|
+
// clean. Only strip for HTML routes; CSS files have no DOM attributes.
|
|
72
|
+
const content =
|
|
73
|
+
ext === ".html" || ext === ".htm"
|
|
74
|
+
? stripEditorOnlyAttributes(
|
|
75
|
+
sanitizeLocalhostSourceSnapshotHtml(rawContent),
|
|
76
|
+
)
|
|
77
|
+
: rawContent;
|
|
78
|
+
|
|
79
|
+
requestLocalhostWrite({
|
|
80
|
+
files: [relPath],
|
|
81
|
+
onGranted: () => {
|
|
82
|
+
void (async () => {
|
|
83
|
+
setApplyToSourcePending(true);
|
|
84
|
+
try {
|
|
85
|
+
// Read the file's current versionHash immediately before writing
|
|
86
|
+
// so write-local-file can reject a concurrent on-disk edit. Never
|
|
87
|
+
// downgrade to an unguarded overwrite when the read fails: the
|
|
88
|
+
// semantic React path is agent-driven, and this legacy whole-file
|
|
89
|
+
// path is intentionally limited to directly writable HTML/CSS.
|
|
90
|
+
const readResult = (await callAction(
|
|
91
|
+
"read-local-file",
|
|
92
|
+
{
|
|
93
|
+
designId: id,
|
|
94
|
+
connectionId,
|
|
95
|
+
path: relPath,
|
|
96
|
+
},
|
|
97
|
+
{ method: "GET" },
|
|
98
|
+
)) as { versionHash?: string } | undefined;
|
|
99
|
+
const expectedVersionHash = readResult?.versionHash;
|
|
100
|
+
if (!expectedVersionHash) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Could not verify the current version of ${relPath}; no source was written.`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
await callAction("write-local-file", {
|
|
107
|
+
designId: id,
|
|
108
|
+
connectionId,
|
|
109
|
+
relPath,
|
|
110
|
+
content,
|
|
111
|
+
expectedVersionHash,
|
|
112
|
+
});
|
|
113
|
+
toast.success(t("designEditor.appliedToSource", { path: relPath }));
|
|
114
|
+
} catch (err) {
|
|
115
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
116
|
+
if (message.includes("version conflict")) {
|
|
117
|
+
toast.error(
|
|
118
|
+
t("designEditor.applyToSourceConflict", { path: relPath }),
|
|
119
|
+
);
|
|
120
|
+
} else {
|
|
121
|
+
toast.error(t("designEditor.applyToSourceError", { message }));
|
|
122
|
+
}
|
|
123
|
+
} finally {
|
|
124
|
+
setApplyToSourcePending(false);
|
|
125
|
+
}
|
|
126
|
+
})();
|
|
127
|
+
},
|
|
128
|
+
onCancel: () => {
|
|
129
|
+
setApplyToSourcePending(false);
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
|
|
2
|
+
|
|
3
|
+
import type { LayersPanelMoveIntent } from "@/components/design/LayersPanel";
|
|
4
|
+
import type { EffectiveCodeLayerState } from "@/pages/design-editor/code-layer-state";
|
|
5
|
+
import { collectCodeLayerAncestors } from "@/pages/design-editor/code-layer-state";
|
|
6
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
7
|
+
|
|
8
|
+
export interface CanMoveLayerArgs {
|
|
9
|
+
codeLayerOwnerByNodeId: Map<
|
|
10
|
+
string,
|
|
11
|
+
{
|
|
12
|
+
fileId: string;
|
|
13
|
+
node: CodeLayerNode;
|
|
14
|
+
tree: CodeLayerTreeNode[];
|
|
15
|
+
runtimeOnly: boolean;
|
|
16
|
+
}
|
|
17
|
+
>;
|
|
18
|
+
effectiveCodeLayerState: EffectiveCodeLayerState;
|
|
19
|
+
files: DesignFile[];
|
|
20
|
+
lockedLayerIds: Set<string>;
|
|
21
|
+
visualScreenFileIds: Set<string>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function runCanMoveLayer(
|
|
25
|
+
{
|
|
26
|
+
codeLayerOwnerByNodeId,
|
|
27
|
+
effectiveCodeLayerState,
|
|
28
|
+
files,
|
|
29
|
+
lockedLayerIds,
|
|
30
|
+
visualScreenFileIds,
|
|
31
|
+
}: CanMoveLayerArgs,
|
|
32
|
+
intent: LayersPanelMoveIntent,
|
|
33
|
+
) {
|
|
34
|
+
const targetOwner = codeLayerOwnerByNodeId.get(intent.targetId);
|
|
35
|
+
// L19: a file/screen row is a valid drop target too — dropping a layer
|
|
36
|
+
// "on" a screen row (no code-layer owner, since it isn't a DOM node)
|
|
37
|
+
// appends it into that screen's body via moveNodeBetweenDocuments.
|
|
38
|
+
const targetFile = !targetOwner
|
|
39
|
+
? files.find((file) => file.id === intent.targetId)
|
|
40
|
+
: undefined;
|
|
41
|
+
const draggedScreenIds = intent.draggedIds.filter((draggedId) =>
|
|
42
|
+
visualScreenFileIds.has(draggedId),
|
|
43
|
+
);
|
|
44
|
+
if (draggedScreenIds.length > 0) {
|
|
45
|
+
return Boolean(
|
|
46
|
+
draggedScreenIds.length === intent.draggedIds.length &&
|
|
47
|
+
targetFile &&
|
|
48
|
+
visualScreenFileIds.has(targetFile.id) &&
|
|
49
|
+
intent.placement !== "inside" &&
|
|
50
|
+
draggedScreenIds.every(
|
|
51
|
+
(screenId) =>
|
|
52
|
+
screenId !== targetFile.id && !lockedLayerIds.has(screenId),
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
// L8: a locked or hidden row can still be used as a before/after/inside
|
|
57
|
+
// drop ANCHOR — locking/hiding a layer shouldn't make it impossible to
|
|
58
|
+
// position other layers relative to it in the panel. Only dragging
|
|
59
|
+
// (see per-draggedId checks below) stays gated by locked/hidden.
|
|
60
|
+
if (!targetOwner && !targetFile) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
const runtimeDraggedOwners = intent.draggedIds.map((draggedId) =>
|
|
64
|
+
codeLayerOwnerByNodeId.get(draggedId),
|
|
65
|
+
);
|
|
66
|
+
const hasRuntimeDraggedOwner = runtimeDraggedOwners.some(
|
|
67
|
+
(owner) => owner?.runtimeOnly,
|
|
68
|
+
);
|
|
69
|
+
if (targetOwner?.runtimeOnly || hasRuntimeDraggedOwner) {
|
|
70
|
+
if (!targetOwner?.runtimeOnly || intent.draggedIds.length !== 1) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
const draggedId = intent.draggedIds[0]!;
|
|
74
|
+
const draggedOwner = runtimeDraggedOwners[0];
|
|
75
|
+
return Boolean(
|
|
76
|
+
draggedOwner?.runtimeOnly &&
|
|
77
|
+
draggedId !== intent.targetId &&
|
|
78
|
+
draggedOwner.fileId === targetOwner.fileId &&
|
|
79
|
+
!effectiveCodeLayerState.lockedIds.has(draggedId) &&
|
|
80
|
+
!collectCodeLayerAncestors(targetOwner.tree, intent.targetId).includes(
|
|
81
|
+
draggedId,
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return intent.draggedIds.some((draggedId) => {
|
|
86
|
+
const draggedOwner = codeLayerOwnerByNodeId.get(draggedId);
|
|
87
|
+
if (
|
|
88
|
+
draggedId === intent.targetId ||
|
|
89
|
+
!draggedOwner ||
|
|
90
|
+
draggedOwner.runtimeOnly ||
|
|
91
|
+
// L8: dragging a LOCKED row is still blocked (locked means
|
|
92
|
+
// don't-touch-this-layer). A HIDDEN row is now draggable — hidden
|
|
93
|
+
// only means "not rendered in canvas", not "structurally frozen".
|
|
94
|
+
effectiveCodeLayerState.lockedIds.has(draggedId)
|
|
95
|
+
) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
if (targetFile) {
|
|
99
|
+
// Dropping directly onto a screen row: any non-locked DOM layer
|
|
100
|
+
// from any file can be appended into that screen's body, EXCEPT a
|
|
101
|
+
// node that's already a top-level (parentless) child of that same
|
|
102
|
+
// screen and would end up in the same place — still allow it
|
|
103
|
+
// through here; handleLayerMove no-ops that case cheaply.
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
// Same-file move: also exclude ancestor drags (would orphan the node).
|
|
107
|
+
if (targetOwner && draggedOwner.fileId === targetOwner.fileId) {
|
|
108
|
+
return !collectCodeLayerAncestors(
|
|
109
|
+
targetOwner.tree,
|
|
110
|
+
intent.targetId,
|
|
111
|
+
).includes(draggedId);
|
|
112
|
+
}
|
|
113
|
+
// Cross-file move: allowed as long as the dragged side isn't locked
|
|
114
|
+
// (already checked above). File-row ids are excluded by the owner check.
|
|
115
|
+
return true;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CodeLayerNode,
|
|
3
|
+
CodeLayerTreeNode,
|
|
4
|
+
MoveNodeEditIntent,
|
|
5
|
+
} from "@shared/code-layer";
|
|
6
|
+
import {
|
|
7
|
+
applyVisualEdit,
|
|
8
|
+
buildCodeLayerProjection,
|
|
9
|
+
buildCodeLayerTree,
|
|
10
|
+
} from "@shared/code-layer";
|
|
11
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
12
|
+
|
|
13
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
14
|
+
import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
|
|
15
|
+
import {
|
|
16
|
+
elementInfoFromCodeLayerNode,
|
|
17
|
+
findCodeLayerSiblingOrder,
|
|
18
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
19
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
20
|
+
|
|
21
|
+
export interface ChangeSelectedZIndexArgs {
|
|
22
|
+
activeFile: DesignFile;
|
|
23
|
+
applyLocalContentUpdate: (
|
|
24
|
+
nextContent: string,
|
|
25
|
+
options?: {
|
|
26
|
+
refreshPreview?: boolean;
|
|
27
|
+
skipPreview?: boolean;
|
|
28
|
+
forcePreviewFullDocument?: boolean;
|
|
29
|
+
immediateSave?: boolean;
|
|
30
|
+
persist?: boolean;
|
|
31
|
+
recordHistory?: boolean;
|
|
32
|
+
historyBeforeContent?: string;
|
|
33
|
+
updatedAt?: string;
|
|
34
|
+
clipboardMutation?: ClipboardContentMutationPublication;
|
|
35
|
+
},
|
|
36
|
+
) => void;
|
|
37
|
+
canEditDesign: boolean;
|
|
38
|
+
codeLayerOwnerByNodeIdRef: RefObject<
|
|
39
|
+
Map<
|
|
40
|
+
string,
|
|
41
|
+
{
|
|
42
|
+
fileId: string;
|
|
43
|
+
node: CodeLayerNode;
|
|
44
|
+
tree: CodeLayerTreeNode[];
|
|
45
|
+
runtimeOnly: boolean;
|
|
46
|
+
}
|
|
47
|
+
>
|
|
48
|
+
>;
|
|
49
|
+
commitVisualStyles: (
|
|
50
|
+
selector: string,
|
|
51
|
+
styles: Record<string, string>,
|
|
52
|
+
options?: {
|
|
53
|
+
runtimeApplied?: boolean;
|
|
54
|
+
elementInfo?: ElementInfo;
|
|
55
|
+
originalStyles?: Record<string, string>;
|
|
56
|
+
},
|
|
57
|
+
) => void;
|
|
58
|
+
getFreshActiveContent: () => string;
|
|
59
|
+
selectedElement: ElementInfo | null;
|
|
60
|
+
selectedLayerIdsState: string[];
|
|
61
|
+
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function runChangeSelectedZIndex(
|
|
65
|
+
{
|
|
66
|
+
activeFile,
|
|
67
|
+
applyLocalContentUpdate,
|
|
68
|
+
canEditDesign,
|
|
69
|
+
codeLayerOwnerByNodeIdRef,
|
|
70
|
+
commitVisualStyles,
|
|
71
|
+
getFreshActiveContent,
|
|
72
|
+
selectedElement,
|
|
73
|
+
selectedLayerIdsState,
|
|
74
|
+
setSelectedElement,
|
|
75
|
+
}: ChangeSelectedZIndexArgs,
|
|
76
|
+
mode: "forward" | "front" | "backward" | "back",
|
|
77
|
+
) {
|
|
78
|
+
if (!canEditDesign) return;
|
|
79
|
+
const selector = selectedElement?.selector;
|
|
80
|
+
if (!selector) return;
|
|
81
|
+
const currentSelectedElement = selectedElement;
|
|
82
|
+
|
|
83
|
+
const zIndexFallback = () => {
|
|
84
|
+
const current = Number.parseInt(
|
|
85
|
+
currentSelectedElement.computedStyles.zIndex || "0",
|
|
86
|
+
10,
|
|
87
|
+
);
|
|
88
|
+
const base = Number.isFinite(current) ? current : 0;
|
|
89
|
+
const next =
|
|
90
|
+
mode === "front"
|
|
91
|
+
? 999
|
|
92
|
+
: mode === "back"
|
|
93
|
+
? 0
|
|
94
|
+
: mode === "forward"
|
|
95
|
+
? base + 1
|
|
96
|
+
: Math.max(0, base - 1);
|
|
97
|
+
commitVisualStyles(selector, {
|
|
98
|
+
position:
|
|
99
|
+
currentSelectedElement.computedStyles.position === "static"
|
|
100
|
+
? "relative"
|
|
101
|
+
: currentSelectedElement.computedStyles.position || "relative",
|
|
102
|
+
zIndex: String(next),
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const targetId =
|
|
107
|
+
selectedLayerIdsState.length === 1 ? selectedLayerIdsState[0] : undefined;
|
|
108
|
+
if (!targetId || !activeFile) {
|
|
109
|
+
zIndexFallback();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const owner = codeLayerOwnerByNodeIdRef.current.get(targetId);
|
|
113
|
+
if (!owner || owner.fileId !== activeFile.id) {
|
|
114
|
+
zIndexFallback();
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const baseContent = getFreshActiveContent();
|
|
118
|
+
const tree = buildCodeLayerTree(buildCodeLayerProjection(baseContent));
|
|
119
|
+
const siblingOrder = findCodeLayerSiblingOrder(tree, targetId);
|
|
120
|
+
if (!siblingOrder || siblingOrder.siblingIds.length < 2) {
|
|
121
|
+
// Nothing to reorder against (only child, or unresolved) — z-index
|
|
122
|
+
// is the only lever left.
|
|
123
|
+
zIndexFallback();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const { siblingIds, index, parentId } = siblingOrder;
|
|
127
|
+
const lastIndex = siblingIds.length - 1;
|
|
128
|
+
|
|
129
|
+
// Already at the requested end of the stack — no-op.
|
|
130
|
+
if (
|
|
131
|
+
(mode === "front" && index === lastIndex) ||
|
|
132
|
+
(mode === "back" && index === 0) ||
|
|
133
|
+
(mode === "forward" && index === lastIndex) ||
|
|
134
|
+
(mode === "backward" && index === 0)
|
|
135
|
+
) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let editIntent: MoveNodeEditIntent | null = null;
|
|
140
|
+
if (mode === "forward") {
|
|
141
|
+
// Move DOM-after the next sibling (paints one step higher).
|
|
142
|
+
const nextSiblingId = siblingIds[index + 1];
|
|
143
|
+
if (nextSiblingId) {
|
|
144
|
+
editIntent = {
|
|
145
|
+
kind: "moveNode",
|
|
146
|
+
target: { nodeId: targetId },
|
|
147
|
+
anchor: { nodeId: nextSiblingId },
|
|
148
|
+
placement: "after",
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
} else if (mode === "backward") {
|
|
152
|
+
// Move DOM-before the previous sibling (paints one step lower).
|
|
153
|
+
const prevSiblingId = siblingIds[index - 1];
|
|
154
|
+
if (prevSiblingId) {
|
|
155
|
+
editIntent = {
|
|
156
|
+
kind: "moveNode",
|
|
157
|
+
target: { nodeId: targetId },
|
|
158
|
+
anchor: { nodeId: prevSiblingId },
|
|
159
|
+
placement: "before",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
} else if (mode === "front") {
|
|
163
|
+
if (parentId) {
|
|
164
|
+
editIntent = {
|
|
165
|
+
kind: "moveNode",
|
|
166
|
+
target: { nodeId: targetId },
|
|
167
|
+
anchor: { nodeId: parentId },
|
|
168
|
+
placement: "inside",
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
171
|
+
const lastSiblingId = siblingIds[lastIndex];
|
|
172
|
+
if (lastSiblingId) {
|
|
173
|
+
editIntent = {
|
|
174
|
+
kind: "moveNode",
|
|
175
|
+
target: { nodeId: targetId },
|
|
176
|
+
anchor: { nodeId: lastSiblingId },
|
|
177
|
+
placement: "after",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
const firstSiblingId = siblingIds[0];
|
|
183
|
+
if (firstSiblingId) {
|
|
184
|
+
editIntent = {
|
|
185
|
+
kind: "moveNode",
|
|
186
|
+
target: { nodeId: targetId },
|
|
187
|
+
anchor: { nodeId: firstSiblingId },
|
|
188
|
+
placement: "before",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!editIntent) {
|
|
194
|
+
zIndexFallback();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const patch = applyVisualEdit(baseContent, editIntent);
|
|
199
|
+
if (patch.result.status !== "applied") {
|
|
200
|
+
zIndexFallback();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
applyLocalContentUpdate(patch.content, { skipPreview: true });
|
|
204
|
+
const movedNode = patch.projection.nodes.find(
|
|
205
|
+
(n) =>
|
|
206
|
+
n.dataAttributes["data-agent-native-node-id"] === targetId ||
|
|
207
|
+
n.id === targetId,
|
|
208
|
+
);
|
|
209
|
+
if (movedNode) {
|
|
210
|
+
setSelectedElement(elementInfoFromCodeLayerNode(movedNode));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { buildCodeLayerProjection } from "@shared/code-layer";
|
|
2
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
3
|
+
|
|
4
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
5
|
+
import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
|
|
6
|
+
import type {
|
|
7
|
+
EffectiveCodeLayerState,
|
|
8
|
+
SelectedLayerTarget,
|
|
9
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
10
|
+
import {
|
|
11
|
+
bridgeSourceIdForCodeLayerNode,
|
|
12
|
+
canonicalElementInfoForCodeLayerNode,
|
|
13
|
+
codeLayerNodeMatchesBridgeTarget,
|
|
14
|
+
preferredCodeLayerSelector,
|
|
15
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
16
|
+
import type { ResponsiveEditScope } from "@/pages/design-editor/command-types";
|
|
17
|
+
import {
|
|
18
|
+
applyRelativeDeltaToStyleValue,
|
|
19
|
+
getFreshActiveFileContent,
|
|
20
|
+
} from "@/pages/design-editor/editor-state";
|
|
21
|
+
import { applyScopedVisualStyleEdit } from "@/pages/design-editor/pending-edits";
|
|
22
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
23
|
+
|
|
24
|
+
export interface CommitRelativeStyleDeltaToSelectedLayersArgs {
|
|
25
|
+
activeBreakpointUpperBoundPx: number | null;
|
|
26
|
+
activeBreakpointWidthStateRef: RefObject<number | undefined>;
|
|
27
|
+
activeContent: string;
|
|
28
|
+
activeFile: DesignFile;
|
|
29
|
+
applyFileContentUpdate: (
|
|
30
|
+
fileId: string,
|
|
31
|
+
nextContent: string,
|
|
32
|
+
options?: {
|
|
33
|
+
refreshPreview?: boolean;
|
|
34
|
+
skipPreview?: boolean;
|
|
35
|
+
forcePreviewFullDocument?: boolean;
|
|
36
|
+
persist?: boolean;
|
|
37
|
+
recordHistory?: boolean;
|
|
38
|
+
updatedAt?: string;
|
|
39
|
+
clipboardMutation?: ClipboardContentMutationPublication;
|
|
40
|
+
},
|
|
41
|
+
) => void;
|
|
42
|
+
canEditDesign: boolean;
|
|
43
|
+
effectiveCodeLayerStateRef: RefObject<EffectiveCodeLayerState>;
|
|
44
|
+
getScreenContent: (screenId: string) => string;
|
|
45
|
+
lastLocalContentRef: RefObject<string | null>;
|
|
46
|
+
latestActiveContentRef: RefObject<string | null>;
|
|
47
|
+
responsiveEditScopeRef: RefObject<ResponsiveEditScope>;
|
|
48
|
+
selectedLayerTargetsRef: RefObject<SelectedLayerTarget[]>;
|
|
49
|
+
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function runCommitRelativeStyleDeltaToSelectedLayers(
|
|
53
|
+
{
|
|
54
|
+
activeBreakpointUpperBoundPx,
|
|
55
|
+
activeBreakpointWidthStateRef,
|
|
56
|
+
activeContent,
|
|
57
|
+
activeFile,
|
|
58
|
+
applyFileContentUpdate,
|
|
59
|
+
canEditDesign,
|
|
60
|
+
effectiveCodeLayerStateRef,
|
|
61
|
+
getScreenContent,
|
|
62
|
+
lastLocalContentRef,
|
|
63
|
+
latestActiveContentRef,
|
|
64
|
+
responsiveEditScopeRef,
|
|
65
|
+
selectedLayerTargetsRef,
|
|
66
|
+
setSelectedElement,
|
|
67
|
+
}: CommitRelativeStyleDeltaToSelectedLayersArgs,
|
|
68
|
+
property: string,
|
|
69
|
+
delta: number,
|
|
70
|
+
) {
|
|
71
|
+
if (!canEditDesign) return false;
|
|
72
|
+
const effectiveLayerState = effectiveCodeLayerStateRef.current;
|
|
73
|
+
const targets = selectedLayerTargetsRef.current.filter(
|
|
74
|
+
(target) =>
|
|
75
|
+
!effectiveLayerState.lockedIds.has(target.fileId) &&
|
|
76
|
+
!effectiveLayerState.hiddenIds.has(target.fileId) &&
|
|
77
|
+
!effectiveLayerState.lockedIds.has(target.layerId) &&
|
|
78
|
+
!effectiveLayerState.hiddenIds.has(target.layerId),
|
|
79
|
+
);
|
|
80
|
+
if (targets.length <= 1) return false;
|
|
81
|
+
|
|
82
|
+
const targetsByFile = new Map<string, SelectedLayerTarget[]>();
|
|
83
|
+
targets.forEach((target) => {
|
|
84
|
+
targetsByFile.set(target.fileId, [
|
|
85
|
+
...(targetsByFile.get(target.fileId) ?? []),
|
|
86
|
+
target,
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
let appliedAny = false;
|
|
91
|
+
const appliedValueByLayerId = new Map<string, string>();
|
|
92
|
+
targetsByFile.forEach((fileTargets, fileId) => {
|
|
93
|
+
const baseContent =
|
|
94
|
+
fileId === activeFile?.id
|
|
95
|
+
? getFreshActiveFileContent({
|
|
96
|
+
activeContent,
|
|
97
|
+
latestContent: latestActiveContentRef.current,
|
|
98
|
+
lastLocalContent: lastLocalContentRef.current,
|
|
99
|
+
})
|
|
100
|
+
: getScreenContent(fileId);
|
|
101
|
+
if (!baseContent) return;
|
|
102
|
+
let nextContent = baseContent;
|
|
103
|
+
let projection = buildCodeLayerProjection(nextContent);
|
|
104
|
+
fileTargets.forEach((target) => {
|
|
105
|
+
const nextValue = applyRelativeDeltaToStyleValue(
|
|
106
|
+
target.elementInfo.computedStyles[
|
|
107
|
+
property as keyof typeof target.elementInfo.computedStyles
|
|
108
|
+
] as string | undefined,
|
|
109
|
+
delta,
|
|
110
|
+
);
|
|
111
|
+
if (nextValue === null) return;
|
|
112
|
+
const sourceId = bridgeSourceIdForCodeLayerNode(target.node);
|
|
113
|
+
const selector = preferredCodeLayerSelector(target.node);
|
|
114
|
+
const node =
|
|
115
|
+
projection.nodes.find((candidate) =>
|
|
116
|
+
codeLayerNodeMatchesBridgeTarget(candidate, selector, sourceId),
|
|
117
|
+
) ??
|
|
118
|
+
projection.nodes.find((candidate) => candidate.id === target.node.id);
|
|
119
|
+
if (!node) return;
|
|
120
|
+
// §6.4 — relative-delta commits (mixed-value arrow steps) route
|
|
121
|
+
// through the same breakpoint scoping as absolute commits.
|
|
122
|
+
const patch = applyScopedVisualStyleEdit({
|
|
123
|
+
content: nextContent,
|
|
124
|
+
target: { nodeId: node.id },
|
|
125
|
+
property,
|
|
126
|
+
value: nextValue,
|
|
127
|
+
upperBoundPx: activeBreakpointUpperBoundPx,
|
|
128
|
+
lowerBoundPx:
|
|
129
|
+
responsiveEditScopeRef.current === "only"
|
|
130
|
+
? activeBreakpointWidthStateRef.current
|
|
131
|
+
: null,
|
|
132
|
+
});
|
|
133
|
+
if (patch.result.status !== "applied") return;
|
|
134
|
+
nextContent = patch.content;
|
|
135
|
+
projection = patch.projection;
|
|
136
|
+
appliedValueByLayerId.set(target.layerId, nextValue);
|
|
137
|
+
});
|
|
138
|
+
if (nextContent === baseContent) return;
|
|
139
|
+
appliedAny = true;
|
|
140
|
+
// Same flash-free full-document routing (and same persist caveat) as
|
|
141
|
+
// commitStylesToSelectedLayers above.
|
|
142
|
+
applyFileContentUpdate(fileId, nextContent, {
|
|
143
|
+
forcePreviewFullDocument: fileId === activeFile?.id,
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (appliedAny) {
|
|
148
|
+
const primaryTarget = targets[targets.length - 1];
|
|
149
|
+
const primaryValue = primaryTarget
|
|
150
|
+
? appliedValueByLayerId.get(primaryTarget.layerId)
|
|
151
|
+
: undefined;
|
|
152
|
+
if (primaryTarget && primaryValue !== undefined) {
|
|
153
|
+
setSelectedElement((previous) => {
|
|
154
|
+
const previousMatches =
|
|
155
|
+
previous &&
|
|
156
|
+
codeLayerNodeMatchesBridgeTarget(
|
|
157
|
+
primaryTarget.node,
|
|
158
|
+
previous.selector,
|
|
159
|
+
previous.sourceId ?? previous.id,
|
|
160
|
+
);
|
|
161
|
+
const base = previousMatches
|
|
162
|
+
? canonicalElementInfoForCodeLayerNode(previous, primaryTarget.node)
|
|
163
|
+
: primaryTarget.elementInfo;
|
|
164
|
+
return {
|
|
165
|
+
...base,
|
|
166
|
+
computedStyles: {
|
|
167
|
+
...base.computedStyles,
|
|
168
|
+
[property]: primaryValue,
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return appliedAny;
|
|
176
|
+
}
|