@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,710 @@
|
|
|
1
|
+
import type { CodeLayerProjection } from "@shared/code-layer";
|
|
2
|
+
import { buildCodeLayerProjection } from "@shared/code-layer";
|
|
3
|
+
import { assertDesignHtmlEditIntegrity } from "@shared/html-integrity";
|
|
4
|
+
import type { InteractionState } from "@shared/interaction-states";
|
|
5
|
+
import { isRunningAppSourceType } from "@shared/source-mode";
|
|
6
|
+
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
7
|
+
import { toast } from "sonner";
|
|
8
|
+
import * as Y from "yjs";
|
|
9
|
+
|
|
10
|
+
import { trace } from "@/components/design/design-trace";
|
|
11
|
+
import {
|
|
12
|
+
isShaderWriteInFlight,
|
|
13
|
+
waitForShaderWriteToSettle,
|
|
14
|
+
} from "@/components/design/inspector/GlslShaderPanel";
|
|
15
|
+
import type { ElementInfo } from "@/components/design/types";
|
|
16
|
+
import {
|
|
17
|
+
bridgeSourceIdForCodeLayerNode,
|
|
18
|
+
codeLayerPatchMessage,
|
|
19
|
+
codeLayerSelectorAliases,
|
|
20
|
+
codeLayerSelectorMatches,
|
|
21
|
+
elementInfoIsRuntimeOnly,
|
|
22
|
+
ensureGoogleFontLinkInHtml,
|
|
23
|
+
isClientRenderedMountShell,
|
|
24
|
+
preferredCodeLayerSelector,
|
|
25
|
+
resolveCodeLayerTargetFromBridge,
|
|
26
|
+
resolveCodeLayerTargetFromElementInfo,
|
|
27
|
+
} from "@/pages/design-editor/code-layer-state";
|
|
28
|
+
import type {
|
|
29
|
+
LiveScreenSnapshot,
|
|
30
|
+
PatchProofState,
|
|
31
|
+
ResponsiveEditScope,
|
|
32
|
+
} from "@/pages/design-editor/command-types";
|
|
33
|
+
import { clearAutoTextColorMarkerOnExplicitColorCommit } from "@/pages/design-editor/cross-screen-text-color";
|
|
34
|
+
import {
|
|
35
|
+
LOCAL_EDIT_ORIGIN,
|
|
36
|
+
TAB_ID,
|
|
37
|
+
} from "@/pages/design-editor/editor-session";
|
|
38
|
+
import type { PreviewContentReplaceResult } from "@/pages/design-editor/editor-state";
|
|
39
|
+
import {
|
|
40
|
+
isStandaloneHttpUrl,
|
|
41
|
+
previewContentReplaceNeedsRenderFallback,
|
|
42
|
+
shouldReplacePreviewAfterVisualStyleCommit,
|
|
43
|
+
} from "@/pages/design-editor/editor-state";
|
|
44
|
+
import type {
|
|
45
|
+
ContentHistoryChange,
|
|
46
|
+
ContentHistoryEntry,
|
|
47
|
+
} from "@/pages/design-editor/history";
|
|
48
|
+
import {
|
|
49
|
+
applyScopedVisualStyleEdit,
|
|
50
|
+
replayPendingVisualStyleRuntimePatch,
|
|
51
|
+
resolveVisualStyleCommitContent,
|
|
52
|
+
} from "@/pages/design-editor/pending-edits";
|
|
53
|
+
import { designSaveErrorMessage } from "@/pages/design-editor/save-failure";
|
|
54
|
+
import { applyInlineStylesToHtml } from "@/pages/design-editor/screen-command-utils";
|
|
55
|
+
import type { DesignFile } from "@/pages/design-editor/types";
|
|
56
|
+
|
|
57
|
+
export interface CommitVisualStylesArgs {
|
|
58
|
+
activeBreakpointUpperBoundPx: number | null;
|
|
59
|
+
activeBreakpointWidthStateRef: RefObject<number | undefined>;
|
|
60
|
+
activeCanvasSourceType: "inline" | "localhost" | "fusion";
|
|
61
|
+
activeCodeLayerProjection: CodeLayerProjection;
|
|
62
|
+
activeContent: string;
|
|
63
|
+
activeFile: DesignFile;
|
|
64
|
+
activeProjectionContent: string;
|
|
65
|
+
canEditDesign: boolean;
|
|
66
|
+
commitVisualStyles: (
|
|
67
|
+
selector: string,
|
|
68
|
+
styles: Record<string, string>,
|
|
69
|
+
options?: {
|
|
70
|
+
runtimeApplied?: boolean;
|
|
71
|
+
elementInfo?: ElementInfo;
|
|
72
|
+
originalStyles?: Record<string, string>;
|
|
73
|
+
},
|
|
74
|
+
) => void;
|
|
75
|
+
isSynced: boolean;
|
|
76
|
+
lastDuplicateTransformRef: RefObject<{
|
|
77
|
+
rootNodeIds: string[];
|
|
78
|
+
dx: number;
|
|
79
|
+
dy: number;
|
|
80
|
+
} | null>;
|
|
81
|
+
lastLocalContentRef: RefObject<string | null>;
|
|
82
|
+
latestActiveContentRef: RefObject<string | null>;
|
|
83
|
+
liveScreenSnapshotsById: Record<string, LiveScreenSnapshot>;
|
|
84
|
+
queueFileContentSave: (
|
|
85
|
+
fileId: string,
|
|
86
|
+
content: string,
|
|
87
|
+
options?: { syncCollab?: boolean; immediate?: boolean },
|
|
88
|
+
) => void;
|
|
89
|
+
recordContentHistoryEntry: (entry: ContentHistoryEntry) => void;
|
|
90
|
+
recordLocalContentHistoryChangeFallback: (
|
|
91
|
+
change: ContentHistoryChange,
|
|
92
|
+
) => void;
|
|
93
|
+
recordLocalContentHistoryEntry: (change: ContentHistoryChange) => void;
|
|
94
|
+
recordPendingVisualStyleEdit: (
|
|
95
|
+
screenId: string,
|
|
96
|
+
selector: string,
|
|
97
|
+
styles: Record<string, string>,
|
|
98
|
+
elementInfo?: ElementInfo,
|
|
99
|
+
metadata?: {
|
|
100
|
+
originalStyles?: Record<string, string>;
|
|
101
|
+
interactionState?: InteractionState;
|
|
102
|
+
},
|
|
103
|
+
) => void;
|
|
104
|
+
replacePreviewContent: (
|
|
105
|
+
nextContent: string,
|
|
106
|
+
selector?: string | null,
|
|
107
|
+
options?: { forceFullDocument?: boolean },
|
|
108
|
+
) => PreviewContentReplaceResult;
|
|
109
|
+
responsiveEditScopeRef: RefObject<ResponsiveEditScope>;
|
|
110
|
+
selectedElement: ElementInfo | null;
|
|
111
|
+
setCollabContent: Dispatch<SetStateAction<string | null>>;
|
|
112
|
+
setCollabContentFileId: Dispatch<SetStateAction<string | null>>;
|
|
113
|
+
setContentRenderRevision: Dispatch<SetStateAction<number>>;
|
|
114
|
+
setPatchProof: Dispatch<SetStateAction<PatchProofState | null>>;
|
|
115
|
+
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
116
|
+
setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
|
|
117
|
+
suppressContentHistoryRef: RefObject<boolean>;
|
|
118
|
+
t: (key: string, options?: Record<string, unknown>) => string;
|
|
119
|
+
undoManagerRef: RefObject<Y.UndoManager | null>;
|
|
120
|
+
updateLiveScreenSnapshotContent: (
|
|
121
|
+
screenId: string,
|
|
122
|
+
html: string,
|
|
123
|
+
options?: { recordHistory?: boolean },
|
|
124
|
+
) => boolean;
|
|
125
|
+
upsertMotionKeyframesFromStyles: (
|
|
126
|
+
styles: Record<string, string>,
|
|
127
|
+
elementInfo?: ElementInfo,
|
|
128
|
+
selector?: string,
|
|
129
|
+
) => void;
|
|
130
|
+
viewModeRef: RefObject<"single" | "overview">;
|
|
131
|
+
ydoc: Y.Doc | null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function runCommitVisualStyles(
|
|
135
|
+
{
|
|
136
|
+
activeBreakpointUpperBoundPx,
|
|
137
|
+
activeBreakpointWidthStateRef,
|
|
138
|
+
activeCanvasSourceType,
|
|
139
|
+
activeCodeLayerProjection,
|
|
140
|
+
activeContent,
|
|
141
|
+
activeFile,
|
|
142
|
+
activeProjectionContent,
|
|
143
|
+
canEditDesign,
|
|
144
|
+
commitVisualStyles,
|
|
145
|
+
isSynced,
|
|
146
|
+
lastDuplicateTransformRef,
|
|
147
|
+
lastLocalContentRef,
|
|
148
|
+
latestActiveContentRef,
|
|
149
|
+
liveScreenSnapshotsById,
|
|
150
|
+
queueFileContentSave,
|
|
151
|
+
recordContentHistoryEntry,
|
|
152
|
+
recordLocalContentHistoryChangeFallback,
|
|
153
|
+
recordLocalContentHistoryEntry,
|
|
154
|
+
recordPendingVisualStyleEdit,
|
|
155
|
+
replacePreviewContent,
|
|
156
|
+
responsiveEditScopeRef,
|
|
157
|
+
selectedElement,
|
|
158
|
+
setCollabContent,
|
|
159
|
+
setCollabContentFileId,
|
|
160
|
+
setContentRenderRevision,
|
|
161
|
+
setPatchProof,
|
|
162
|
+
setSelectedElement,
|
|
163
|
+
setSelectedLayerIdsState,
|
|
164
|
+
suppressContentHistoryRef,
|
|
165
|
+
t,
|
|
166
|
+
undoManagerRef,
|
|
167
|
+
updateLiveScreenSnapshotContent,
|
|
168
|
+
upsertMotionKeyframesFromStyles,
|
|
169
|
+
viewModeRef,
|
|
170
|
+
ydoc,
|
|
171
|
+
}: CommitVisualStylesArgs,
|
|
172
|
+
selector: string,
|
|
173
|
+
styles: Record<string, string>,
|
|
174
|
+
options: {
|
|
175
|
+
runtimeApplied?: boolean;
|
|
176
|
+
elementInfo?: ElementInfo;
|
|
177
|
+
/** Pre-gesture values, for the pending-edit revert stack. */
|
|
178
|
+
originalStyles?: Record<string, string>;
|
|
179
|
+
} = {},
|
|
180
|
+
) {
|
|
181
|
+
trace("persist", "commit-styles", {
|
|
182
|
+
selector: typeof selector === "string" ? selector : null,
|
|
183
|
+
props: Object.keys(styles ?? {}),
|
|
184
|
+
});
|
|
185
|
+
if (!activeFile || !canEditDesign) return;
|
|
186
|
+
// Cross-pipeline write race guard (see GlslShaderPanel.tsx's module doc
|
|
187
|
+
// comment on withShaderWriteLock/waitForShaderWriteToSettle): a shader
|
|
188
|
+
// apply/remove/knob-commit for this same file goes through a completely
|
|
189
|
+
// separate round trip (read-source-file -> apply-source-edit) than this
|
|
190
|
+
// function's own commit, and both eventually rewrite the SAME Y.Doc —
|
|
191
|
+
// one via a server-side diff, this one via a synchronous, untracked
|
|
192
|
+
// full-document ydoc.transact rewrite below. Racing the two produces a
|
|
193
|
+
// corrupted, doubled document (verified). The common case (no shader
|
|
194
|
+
// write in flight for this file) stays fully synchronous — only defer
|
|
195
|
+
// when isShaderWriteInFlight is actually true, so this never adds a
|
|
196
|
+
// microtask tick to the hot path or breaks the same-tick multi-property
|
|
197
|
+
// composition the baseContent comment below depends on.
|
|
198
|
+
if (isShaderWriteInFlight(activeFile.id)) {
|
|
199
|
+
void waitForShaderWriteToSettle(activeFile.id).then(() => {
|
|
200
|
+
commitVisualStyles(selector, styles, options);
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const entries = Object.entries(styles).filter(
|
|
205
|
+
([, value]) => value !== undefined,
|
|
206
|
+
);
|
|
207
|
+
if (entries.length === 0) return;
|
|
208
|
+
upsertMotionKeyframesFromStyles(styles, options.elementInfo, selector);
|
|
209
|
+
// §gesture-persistence — a localhost screen's source of truth is the
|
|
210
|
+
// running app's own files, which this client cannot write. Everything
|
|
211
|
+
// below patches the design's STORED html, which for such a screen is
|
|
212
|
+
// only the bridged route URL, so an inspector commit updated the model
|
|
213
|
+
// and the undo stack while the running app kept rendering the old value
|
|
214
|
+
// and no pending edit was ever queued for the Apply pass. Push the value
|
|
215
|
+
// into the live DOM and queue it, exactly like a canvas gesture
|
|
216
|
+
// (handleVisualStyleChange delegates here with runtimeApplied set
|
|
217
|
+
// because its gesture already moved the live DOM).
|
|
218
|
+
if (isRunningAppSourceType(activeCanvasSourceType)) {
|
|
219
|
+
const targetInfo = options.elementInfo ?? selectedElement ?? undefined;
|
|
220
|
+
// Breakpoint-scoped writes are excluded for the same reason as the
|
|
221
|
+
// base path below (Item 5, edit-flash): the agent persists them as a
|
|
222
|
+
// width-scoped class or an `@media` rule, which an inline style would
|
|
223
|
+
// preview wrong.
|
|
224
|
+
if (
|
|
225
|
+
!options.runtimeApplied &&
|
|
226
|
+
activeBreakpointUpperBoundPx == null &&
|
|
227
|
+
typeof (window as any).__designCanvasSendStyleForScreen === "function"
|
|
228
|
+
) {
|
|
229
|
+
replayPendingVisualStyleRuntimePatch(
|
|
230
|
+
{
|
|
231
|
+
screenId: activeFile.id,
|
|
232
|
+
selector,
|
|
233
|
+
sourceId: targetInfo?.runtimeSourceId ?? targetInfo?.sourceId ?? null,
|
|
234
|
+
styles: Object.fromEntries(entries),
|
|
235
|
+
},
|
|
236
|
+
(window as any).__designCanvasSendStyleForScreen,
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
recordPendingVisualStyleEdit(activeFile.id, selector, styles, targetInfo, {
|
|
240
|
+
originalStyles: options.originalStyles,
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Base every patch off the freshest known content, not the closed-over
|
|
245
|
+
// render value. Handlers that fire several onStyleChange calls in one
|
|
246
|
+
// synchronous user action (e.g. fixed-size text → width+height+whiteSpace,
|
|
247
|
+
// constraints center → both axes, linked padding → 4 sides) would
|
|
248
|
+
// otherwise each read the same pre-render `activeContent` and clobber one
|
|
249
|
+
// another, so only the last property survived in the saved HTML. Since we
|
|
250
|
+
// advance lastLocalContentRef.current to resolvedNextContent below, the
|
|
251
|
+
// next synchronous call reads the previous call's result and the patches
|
|
252
|
+
// compose. Falls back to activeContent when the ref is unset (file switch).
|
|
253
|
+
const activeLiveSnapshot = activeFile
|
|
254
|
+
? liveScreenSnapshotsById[activeFile.id]
|
|
255
|
+
: undefined;
|
|
256
|
+
const baseContent =
|
|
257
|
+
activeLiveSnapshot?.html ??
|
|
258
|
+
latestActiveContentRef.current ??
|
|
259
|
+
lastLocalContentRef.current ??
|
|
260
|
+
activeContent;
|
|
261
|
+
// A localhost screen's stored content IS its route URL, so with no
|
|
262
|
+
// snapshot yet the chain above yields that URL string. Projecting it gives
|
|
263
|
+
// a 3-node document where nothing resolves: a snapshot that has not
|
|
264
|
+
// arrived is not an empty document.
|
|
265
|
+
if (isStandaloneHttpUrl(baseContent)) {
|
|
266
|
+
toast.error(t("designEditor.patchProof.snapshotNotLoaded"), {
|
|
267
|
+
duration: 4000,
|
|
268
|
+
});
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const [firstProperty, firstValue] = entries[0];
|
|
272
|
+
// PF12: reuse the already-built activeCodeLayerProjection when its
|
|
273
|
+
// source content is exactly the content this commit is about to patch
|
|
274
|
+
// (the common case — no pending live snapshot/local-edit divergence).
|
|
275
|
+
// Style commits fire on every slider/color-picker drag tick, so
|
|
276
|
+
// skipping a redundant full-document reparse here matters.
|
|
277
|
+
const projection =
|
|
278
|
+
baseContent === activeProjectionContent
|
|
279
|
+
? activeCodeLayerProjection
|
|
280
|
+
: buildCodeLayerProjection(baseContent);
|
|
281
|
+
const targetInfo = options.elementInfo ?? selectedElement;
|
|
282
|
+
const targetResolution = targetInfo
|
|
283
|
+
? resolveCodeLayerTargetFromElementInfo(projection, targetInfo)
|
|
284
|
+
: resolveCodeLayerTargetFromBridge(projection, selector);
|
|
285
|
+
const targetNode =
|
|
286
|
+
targetResolution.status === "resolved" ? targetResolution.node : null;
|
|
287
|
+
const sendStyleChange = (window as any).__designCanvasSendStyle;
|
|
288
|
+
// Item 5 (edit-flash): a breakpoint-scoped commit
|
|
289
|
+
// (activeBreakpointUpperBoundPx set) never persists as a plain inline
|
|
290
|
+
// style — planBreakpointStyleWrite below turns it into a width-scoped
|
|
291
|
+
// Tailwind class or an `@media` rule in the managed breakpoints <style>
|
|
292
|
+
// block. sendStyleChange only knows how to patch the live element's
|
|
293
|
+
// INLINE style, which unconditionally beats any `@media` rule's
|
|
294
|
+
// specificity. Applying it here would preview the wrong
|
|
295
|
+
// (inline-style-overridden) value immediately, then visibly flash to the
|
|
296
|
+
// correct cascaded value once the next full document patch/reload catches
|
|
297
|
+
// up — so skip the runtime shortcut entirely for breakpoint-scoped writes
|
|
298
|
+
// and fall through to the full content patch path below, which reflects
|
|
299
|
+
// the actual persisted class/`@media` result.
|
|
300
|
+
const runtimeStyleApplied =
|
|
301
|
+
!options.runtimeApplied &&
|
|
302
|
+
activeBreakpointUpperBoundPx == null &&
|
|
303
|
+
typeof sendStyleChange === "function";
|
|
304
|
+
// Shared by both terminal paths below so neither drifts into previewing a
|
|
305
|
+
// different element than the other.
|
|
306
|
+
const sendRuntimeStylePreview = (): void => {
|
|
307
|
+
if (!runtimeStyleApplied) return;
|
|
308
|
+
// A stamped id goes stale the moment React re-creates the node, so send
|
|
309
|
+
// the bridge-minted identities as fallbacks (see
|
|
310
|
+
// canonicalElementInfoForCodeLayerNode).
|
|
311
|
+
const selectorCandidates = targetNode
|
|
312
|
+
? codeLayerSelectorAliases(targetNode)
|
|
313
|
+
: Array.from(
|
|
314
|
+
new Set(
|
|
315
|
+
[
|
|
316
|
+
selector,
|
|
317
|
+
targetInfo?.runtimeSelector,
|
|
318
|
+
targetInfo?.selector,
|
|
319
|
+
].filter((candidate): candidate is string => Boolean(candidate)),
|
|
320
|
+
),
|
|
321
|
+
);
|
|
322
|
+
const nodeId = targetNode
|
|
323
|
+
? bridgeSourceIdForCodeLayerNode(targetNode)
|
|
324
|
+
: (targetInfo?.runtimeSourceId ?? targetInfo?.sourceId);
|
|
325
|
+
entries.forEach(([property, value]) => {
|
|
326
|
+
sendStyleChange(selector, property, value, {
|
|
327
|
+
selectorCandidates,
|
|
328
|
+
nodeId,
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// U7: if this style commit repositions (left/top) the node(s) most
|
|
334
|
+
// recently created by Cmd+D, record the delta so the next Cmd+D on that
|
|
335
|
+
// same selection can replay it instead of landing back in place.
|
|
336
|
+
if (targetNode && lastDuplicateTransformRef.current) {
|
|
337
|
+
const nodeId =
|
|
338
|
+
targetNode.dataAttributes["data-agent-native-node-id"] ?? targetNode.id;
|
|
339
|
+
if (lastDuplicateTransformRef.current.rootNodeIds.includes(nodeId)) {
|
|
340
|
+
const nextLeft = parseFloat(styles.left ?? "");
|
|
341
|
+
const nextTop = parseFloat(styles.top ?? "");
|
|
342
|
+
const prevLeft = parseFloat(targetNode.style.left ?? "");
|
|
343
|
+
const prevTop = parseFloat(targetNode.style.top ?? "");
|
|
344
|
+
if (
|
|
345
|
+
Number.isFinite(nextLeft) &&
|
|
346
|
+
Number.isFinite(nextTop) &&
|
|
347
|
+
Number.isFinite(prevLeft) &&
|
|
348
|
+
Number.isFinite(prevTop)
|
|
349
|
+
) {
|
|
350
|
+
lastDuplicateTransformRef.current = {
|
|
351
|
+
...lastDuplicateTransformRef.current,
|
|
352
|
+
dx: nextLeft - prevLeft,
|
|
353
|
+
dy: nextTop - prevTop,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const capability =
|
|
359
|
+
selectedElement?.editCapabilities?.find((item) =>
|
|
360
|
+
item.kind.startsWith("deterministic"),
|
|
361
|
+
) ?? selectedElement?.editCapabilities?.[0];
|
|
362
|
+
const proofId =
|
|
363
|
+
typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
364
|
+
? crypto.randomUUID()
|
|
365
|
+
: `${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
366
|
+
if (!targetNode && elementInfoIsRuntimeOnly(targetInfo)) {
|
|
367
|
+
// Fail LOUD (same contract as the resolveVisualStyleCommitContent
|
|
368
|
+
// error branch below): patch-proof state alone is too quiet for a
|
|
369
|
+
// user-initiated edit that will never persist.
|
|
370
|
+
//
|
|
371
|
+
// Three facts, three remedies: ambiguous needs scoping, a mount shell
|
|
372
|
+
// has no app markup at ANY selector, and only an authored document that
|
|
373
|
+
// lost the node is actually "missing".
|
|
374
|
+
const resolutionFailure =
|
|
375
|
+
targetResolution.status === "ambiguous"
|
|
376
|
+
? t("designEditor.patchProof.selectorAmbiguous", {
|
|
377
|
+
count: targetResolution.candidates.length,
|
|
378
|
+
})
|
|
379
|
+
: isClientRenderedMountShell(projection)
|
|
380
|
+
? t("designEditor.patchProof.clientRenderedShell")
|
|
381
|
+
: t("designEditor.patchProof.selectorMissing");
|
|
382
|
+
toast.error(resolutionFailure, {
|
|
383
|
+
duration: 4000,
|
|
384
|
+
});
|
|
385
|
+
setPatchProof({
|
|
386
|
+
id: proofId,
|
|
387
|
+
fileId: activeFile.id,
|
|
388
|
+
filename: activeFile.filename,
|
|
389
|
+
selector,
|
|
390
|
+
sourceId: targetInfo?.sourceId,
|
|
391
|
+
property:
|
|
392
|
+
entries.length === 1
|
|
393
|
+
? firstProperty
|
|
394
|
+
: entries.map(([property]) => property).join(", "),
|
|
395
|
+
previousValue: targetInfo?.computedStyles?.[firstProperty],
|
|
396
|
+
nextValue:
|
|
397
|
+
entries.length === 1
|
|
398
|
+
? firstValue
|
|
399
|
+
: entries
|
|
400
|
+
.map(([property, value]) => `${property}: ${value}`)
|
|
401
|
+
.join("; "),
|
|
402
|
+
previousContent: baseContent,
|
|
403
|
+
capability: "unsupported",
|
|
404
|
+
confidence: 0.3,
|
|
405
|
+
status: "failed",
|
|
406
|
+
error: resolutionFailure,
|
|
407
|
+
createdAt: Date.now(),
|
|
408
|
+
});
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
setPatchProof({
|
|
412
|
+
id: proofId,
|
|
413
|
+
fileId: activeFile.id,
|
|
414
|
+
filename: activeFile.filename,
|
|
415
|
+
selector,
|
|
416
|
+
sourceId: selectedElement?.sourceId,
|
|
417
|
+
property:
|
|
418
|
+
entries.length === 1
|
|
419
|
+
? firstProperty
|
|
420
|
+
: entries.map(([property]) => property).join(", "),
|
|
421
|
+
previousValue: selectedElement?.computedStyles?.[firstProperty],
|
|
422
|
+
nextValue:
|
|
423
|
+
entries.length === 1
|
|
424
|
+
? firstValue
|
|
425
|
+
: entries
|
|
426
|
+
.map(([property, value]) => `${property}: ${value}`)
|
|
427
|
+
.join("; "),
|
|
428
|
+
previousContent: baseContent,
|
|
429
|
+
capability: capability?.kind ?? "deterministic-style-edit",
|
|
430
|
+
confidence: capability?.confidence ?? 0.92,
|
|
431
|
+
status: "runtime",
|
|
432
|
+
createdAt: Date.now(),
|
|
433
|
+
});
|
|
434
|
+
sendRuntimeStylePreview();
|
|
435
|
+
|
|
436
|
+
const nextContent = applyInlineStylesToHtml(baseContent, selector, {
|
|
437
|
+
...Object.fromEntries(entries),
|
|
438
|
+
});
|
|
439
|
+
// §6.4 — Breakpoint-scoped editing (Framer cascade). Reuses the
|
|
440
|
+
// `projection` and `targetNode` resolved above for the patch-proof
|
|
441
|
+
// block (same baseContent). When a non-base breakpoint frame is
|
|
442
|
+
// active, EVERY property routes through the single class-vs-media
|
|
443
|
+
// decision (planBreakpointStyleWrite):
|
|
444
|
+
//
|
|
445
|
+
// - Tailwind-utility values become width-scoped responsive classes
|
|
446
|
+
// (`max-[<bound>px]:text-lg`), replacing any same-stem token at the
|
|
447
|
+
// same bound.
|
|
448
|
+
// guard:allow-raw-color - prose naming CSS value kinds, not a color literal
|
|
449
|
+
// - Raw CSS values (exact px from drags, rgb()/calc(), …) become
|
|
450
|
+
// managed `@media (max-width: <bound>px)` rules in the
|
|
451
|
+
// `<style data-agent-native-breakpoints>` block, targeting the
|
|
452
|
+
// element's stable node id.
|
|
453
|
+
//
|
|
454
|
+
// Base edits (no active breakpoint, or the active frame is the widest
|
|
455
|
+
// context) keep the plain inline-style path and cascade down to every
|
|
456
|
+
// narrower breakpoint unless overridden there.
|
|
457
|
+
const stylePatch = entries.reduce<{
|
|
458
|
+
content: string;
|
|
459
|
+
failed: string | null;
|
|
460
|
+
}>(
|
|
461
|
+
(current, [property, value]) => {
|
|
462
|
+
if (current.failed) return current;
|
|
463
|
+
const patch = applyScopedVisualStyleEdit({
|
|
464
|
+
content: current.content,
|
|
465
|
+
target: targetNode ? { nodeId: targetNode.id } : { selector },
|
|
466
|
+
property,
|
|
467
|
+
value,
|
|
468
|
+
upperBoundPx: activeBreakpointUpperBoundPx,
|
|
469
|
+
lowerBoundPx:
|
|
470
|
+
responsiveEditScopeRef.current === "only"
|
|
471
|
+
? activeBreakpointWidthStateRef.current
|
|
472
|
+
: null,
|
|
473
|
+
});
|
|
474
|
+
if (patch.result.status !== "applied") {
|
|
475
|
+
return {
|
|
476
|
+
content: current.content,
|
|
477
|
+
failed: codeLayerPatchMessage(
|
|
478
|
+
patch.result.message,
|
|
479
|
+
t("designEditor.patchProof.selectorMissing"),
|
|
480
|
+
),
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
return { content: patch.content, failed: null };
|
|
484
|
+
},
|
|
485
|
+
{ content: baseContent, failed: null },
|
|
486
|
+
);
|
|
487
|
+
// §6.4 — the legacy selector-based inline-style fallback (nextContent)
|
|
488
|
+
// is a BASE write: safe when editing the base, but while a narrower
|
|
489
|
+
// breakpoint is active it would clobber every viewport width with a
|
|
490
|
+
// value the user meant to scope. Fail loud (patch-proof error) instead
|
|
491
|
+
// of silently widening the edit.
|
|
492
|
+
const commitResolution = resolveVisualStyleCommitContent({
|
|
493
|
+
scopedContent: stylePatch.content,
|
|
494
|
+
scopedFailure: stylePatch.failed,
|
|
495
|
+
legacyFallbackContent: nextContent,
|
|
496
|
+
breakpointScoped: activeBreakpointUpperBoundPx != null,
|
|
497
|
+
});
|
|
498
|
+
if ("error" in commitResolution) {
|
|
499
|
+
const failureMessage = codeLayerPatchMessage(
|
|
500
|
+
commitResolution.error,
|
|
501
|
+
t("designEditor.patchProof.selectorMissing"),
|
|
502
|
+
);
|
|
503
|
+
// Fail LOUD, never silently: an unresolvable commit target (e.g. an
|
|
504
|
+
// Alpine template-instance element with no per-instance source node)
|
|
505
|
+
// used to only flip the patch-proof panel to "failed" — no toast, no
|
|
506
|
+
// revert, while the inspector kept displaying the new value, so users
|
|
507
|
+
// had no idea their edit never persisted (verified on real content:
|
|
508
|
+
// Gap scrub on an x-for todo-card subtask row). Same toast pattern as
|
|
509
|
+
// handleVisualStructureChange's move failure.
|
|
510
|
+
toast.error(failureMessage, { duration: 4000 });
|
|
511
|
+
setPatchProof((prev) =>
|
|
512
|
+
prev?.id === proofId
|
|
513
|
+
? { ...prev, status: "failed", error: failureMessage }
|
|
514
|
+
: prev,
|
|
515
|
+
);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const resolvedNextContentBeforeFontLink = commitResolution.content;
|
|
519
|
+
|
|
520
|
+
// T16: if this commit set fontFamily to a known Google Font not
|
|
521
|
+
// already loaded in this screen, inject its <link> into <head>.
|
|
522
|
+
const fontFamilyValue = Object.fromEntries(entries).fontFamily;
|
|
523
|
+
const resolvedNextContentAfterFontLink = fontFamilyValue
|
|
524
|
+
? ensureGoogleFontLinkInHtml(
|
|
525
|
+
resolvedNextContentBeforeFontLink,
|
|
526
|
+
fontFamilyValue,
|
|
527
|
+
)
|
|
528
|
+
: resolvedNextContentBeforeFontLink;
|
|
529
|
+
|
|
530
|
+
// Finding 2(b): an explicit "color" commit on a node that still carries
|
|
531
|
+
// BOARD_TEXT_AUTO_COLOR_MARKER means the user just deliberately chose a
|
|
532
|
+
// color — the marker no longer describes an auto-applied default and
|
|
533
|
+
// must not survive to mislead a later reparent/cross-screen move (see
|
|
534
|
+
// isStaleAutoTextColorMarker / clearAutoTextColorMarkerOnExplicitColorCommit).
|
|
535
|
+
const committedNodeId =
|
|
536
|
+
targetNode?.dataAttributes["data-agent-native-node-id"];
|
|
537
|
+
const resolvedNextContent =
|
|
538
|
+
"color" in Object.fromEntries(entries) && committedNodeId
|
|
539
|
+
? clearAutoTextColorMarkerOnExplicitColorCommit(
|
|
540
|
+
resolvedNextContentAfterFontLink,
|
|
541
|
+
committedNodeId,
|
|
542
|
+
)
|
|
543
|
+
: resolvedNextContentAfterFontLink;
|
|
544
|
+
|
|
545
|
+
try {
|
|
546
|
+
assertDesignHtmlEditIntegrity({
|
|
547
|
+
previousContent: baseContent,
|
|
548
|
+
nextContent: resolvedNextContent,
|
|
549
|
+
fileType: activeFile.fileType,
|
|
550
|
+
});
|
|
551
|
+
} catch (error) {
|
|
552
|
+
const message = designSaveErrorMessage(error) ?? t("common.genericError");
|
|
553
|
+
toast.error(message, {
|
|
554
|
+
id: `design-source-integrity:${activeFile.id}`,
|
|
555
|
+
});
|
|
556
|
+
setPatchProof((previous) =>
|
|
557
|
+
previous?.id === proofId
|
|
558
|
+
? { ...previous, status: "failed", error: message }
|
|
559
|
+
: previous,
|
|
560
|
+
);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const nextProjection = buildCodeLayerProjection(resolvedNextContent);
|
|
565
|
+
const resolvedNode = selectedElement
|
|
566
|
+
? nextProjection.nodes.find((node) => {
|
|
567
|
+
const aliases = codeLayerSelectorAliases(node);
|
|
568
|
+
return (
|
|
569
|
+
(selectedElement.sourceId &&
|
|
570
|
+
(node.id === selectedElement.sourceId ||
|
|
571
|
+
node.dataAttributes["data-agent-native-node-id"] ===
|
|
572
|
+
selectedElement.sourceId ||
|
|
573
|
+
node.dataAttributes["data-code-layer-id"] ===
|
|
574
|
+
selectedElement.sourceId ||
|
|
575
|
+
node.dataAttributes["data-layer-id"] ===
|
|
576
|
+
selectedElement.sourceId ||
|
|
577
|
+
node.dataAttributes["data-builder-id"] ===
|
|
578
|
+
selectedElement.sourceId ||
|
|
579
|
+
node.dataAttributes["data-loc"] === selectedElement.sourceId ||
|
|
580
|
+
node.attributes.id === selectedElement.sourceId)) ||
|
|
581
|
+
aliases.includes(selector) ||
|
|
582
|
+
codeLayerSelectorMatches(node, selector)
|
|
583
|
+
);
|
|
584
|
+
})
|
|
585
|
+
: null;
|
|
586
|
+
const liveSnapshotUpdated = activeLiveSnapshot
|
|
587
|
+
? updateLiveScreenSnapshotContent(activeFile.id, resolvedNextContent)
|
|
588
|
+
: false;
|
|
589
|
+
if (liveSnapshotUpdated) {
|
|
590
|
+
setPatchProof((prev) =>
|
|
591
|
+
prev?.id === proofId ? { ...prev, status: "queued" } : prev,
|
|
592
|
+
);
|
|
593
|
+
if (!runtimeStyleApplied) {
|
|
594
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
595
|
+
}
|
|
596
|
+
} else {
|
|
597
|
+
const yjsHistoryAvailable = Boolean(
|
|
598
|
+
viewModeRef.current !== "overview" &&
|
|
599
|
+
ydoc &&
|
|
600
|
+
isSynced &&
|
|
601
|
+
undoManagerRef.current,
|
|
602
|
+
);
|
|
603
|
+
if (
|
|
604
|
+
!yjsHistoryAvailable &&
|
|
605
|
+
!suppressContentHistoryRef.current &&
|
|
606
|
+
baseContent !== resolvedNextContent
|
|
607
|
+
) {
|
|
608
|
+
const change = {
|
|
609
|
+
fileId: activeFile.id,
|
|
610
|
+
before: baseContent,
|
|
611
|
+
after: resolvedNextContent,
|
|
612
|
+
};
|
|
613
|
+
if (viewModeRef.current === "overview") {
|
|
614
|
+
recordContentHistoryEntry(change);
|
|
615
|
+
} else {
|
|
616
|
+
recordLocalContentHistoryEntry(change);
|
|
617
|
+
}
|
|
618
|
+
} else if (
|
|
619
|
+
yjsHistoryAvailable &&
|
|
620
|
+
!suppressContentHistoryRef.current &&
|
|
621
|
+
baseContent !== resolvedNextContent
|
|
622
|
+
) {
|
|
623
|
+
// BUG-UNDO-RESIZE-STACK: mirror the same before/after into the local
|
|
624
|
+
// fallback stack that applyLocalContentUpdate already maintains for
|
|
625
|
+
// every other commit path (text edits, moves, structure changes).
|
|
626
|
+
// The Yjs UndoManager is destroyed and recreated whenever `docId`
|
|
627
|
+
// changes — a view-mode switch, a zoom-triggered re-render, or a
|
|
628
|
+
// breakpoint switch — which silently drops its entire undo stack.
|
|
629
|
+
// Without this mirror, a gesture-driven style/resize commit (the
|
|
630
|
+
// ONLY commit path that skipped this call) became permanently
|
|
631
|
+
// unrecoverable the moment that happened: handleUndo's um.canUndo()
|
|
632
|
+
// goes false with nothing to fall back to, so Cmd+Z does nothing at
|
|
633
|
+
// all for a resize-drag even though every other edit kind still has
|
|
634
|
+
// a working fallback. Only consulted once Yjs itself has nothing
|
|
635
|
+
// left to undo (see handleUndo), so this never causes a double-undo.
|
|
636
|
+
recordLocalContentHistoryChangeFallback({
|
|
637
|
+
fileId: activeFile.id,
|
|
638
|
+
before: baseContent,
|
|
639
|
+
after: resolvedNextContent,
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
setCollabContent(resolvedNextContent);
|
|
644
|
+
setCollabContentFileId(activeFile.id);
|
|
645
|
+
setPatchProof((prev) =>
|
|
646
|
+
prev?.id === proofId ? { ...prev, status: "queued" } : prev,
|
|
647
|
+
);
|
|
648
|
+
// Mark as our own write so the get-design reconcile + Yjs observe don't
|
|
649
|
+
// treat the echo as an external edit and fight the live value.
|
|
650
|
+
lastLocalContentRef.current = resolvedNextContent;
|
|
651
|
+
latestActiveContentRef.current = resolvedNextContent;
|
|
652
|
+
// Write the edit into the shared Y.Doc so other open clients see it live
|
|
653
|
+
// through Yjs (not only via the slower update-file → applyText round-trip).
|
|
654
|
+
// Single-screen edits use the active-file UndoManager. Overview edits are
|
|
655
|
+
// tracked in the global file-content stack so all screens share one order.
|
|
656
|
+
if (ydoc && isSynced) {
|
|
657
|
+
const ytext = ydoc.getText("content");
|
|
658
|
+
if (ytext.toString() !== resolvedNextContent) {
|
|
659
|
+
if (!yjsHistoryAvailable) {
|
|
660
|
+
// Untracked full rewrite (overview mode with a still-live
|
|
661
|
+
// single-mode UndoManager, or history-suppressed replay) —
|
|
662
|
+
// see U1 note: clear the undo stack so a stale tracked delta
|
|
663
|
+
// can't be replayed against content it no longer matches.
|
|
664
|
+
undoManagerRef.current?.clear(true, false);
|
|
665
|
+
}
|
|
666
|
+
ydoc.transact(
|
|
667
|
+
() => {
|
|
668
|
+
ytext.delete(0, ytext.length);
|
|
669
|
+
ytext.insert(0, resolvedNextContent);
|
|
670
|
+
},
|
|
671
|
+
yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
queueFileContentSave(activeFile.id, resolvedNextContent, {
|
|
676
|
+
syncCollab: !(ydoc && isSynced),
|
|
677
|
+
});
|
|
678
|
+
if (
|
|
679
|
+
shouldReplacePreviewAfterVisualStyleCommit({
|
|
680
|
+
runtimeApplied: options.runtimeApplied,
|
|
681
|
+
runtimeStyleApplied,
|
|
682
|
+
}) &&
|
|
683
|
+
previewContentReplaceNeedsRenderFallback(
|
|
684
|
+
replacePreviewContent(resolvedNextContent, selector),
|
|
685
|
+
)
|
|
686
|
+
) {
|
|
687
|
+
setContentRenderRevision((revision) => revision + 1);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (resolvedNode) setSelectedLayerIdsState([resolvedNode.id]);
|
|
691
|
+
setSelectedElement((prev) => {
|
|
692
|
+
if (options.elementInfo) return options.elementInfo;
|
|
693
|
+
if (!prev) return prev;
|
|
694
|
+
const stablePatch = resolvedNode
|
|
695
|
+
? {
|
|
696
|
+
sourceId: bridgeSourceIdForCodeLayerNode(resolvedNode),
|
|
697
|
+
selector: preferredCodeLayerSelector(resolvedNode),
|
|
698
|
+
classes: resolvedNode.classes,
|
|
699
|
+
}
|
|
700
|
+
: {};
|
|
701
|
+
return {
|
|
702
|
+
...prev,
|
|
703
|
+
...stablePatch,
|
|
704
|
+
computedStyles: {
|
|
705
|
+
...prev.computedStyles,
|
|
706
|
+
...Object.fromEntries(entries),
|
|
707
|
+
},
|
|
708
|
+
};
|
|
709
|
+
});
|
|
710
|
+
}
|