@agent-native/core 0.80.10 → 0.80.11
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/core/CHANGELOG.md +6 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +108 -15
- package/corpus/core/src/client/AssistantChat.tsx +123 -26
- package/corpus/core/src/client/sse-event-processor.ts +9 -0
- package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
- package/corpus/templates/assets/actions/generate-image.ts +118 -67
- package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
- package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
- package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
- package/corpus/templates/assets/app/i18n-data.ts +61 -0
- package/corpus/templates/assets/app/routes/_index.tsx +4 -0
- package/corpus/templates/assets/app/routes/library.tsx +145 -38
- package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
- package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
- package/corpus/templates/assets/server/lib/generation.ts +135 -11
- package/corpus/templates/assets/shared/api.ts +4 -0
- package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
- package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +13 -5
- package/corpus/templates/design/actions/apply-design-state.ts +17 -4
- package/corpus/templates/design/actions/apply-motion-edit.ts +9 -46
- package/corpus/templates/design/actions/delete-design.ts +20 -0
- package/corpus/templates/design/actions/get-component-details.ts +16 -7
- package/corpus/templates/design/actions/index-design-tokens.ts +8 -4
- package/corpus/templates/design/actions/list-design-states.ts +19 -1
- package/corpus/templates/design/actions/open-component-source.ts +7 -2
- package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
- package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
- package/corpus/templates/design/actions/run-design-audit.ts +21 -6
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +139 -14
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +20 -3
- package/corpus/templates/design/app/components/design/EditPanel.tsx +428 -31
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +110 -30
- package/corpus/templates/design/app/components/design/MotionDock.tsx +9 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1 -1
- package/corpus/templates/design/app/components/design/ReviewPanel.tsx +12 -5
- package/corpus/templates/design/app/components/design/StatesPanel.tsx +4 -2
- package/corpus/templates/design/app/components/design/TokensPanel.tsx +13 -8
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/i18n-data.ts +11 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +684 -179
- package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
- package/corpus/templates/design/e2e/global-setup.ts +94 -2
- package/corpus/templates/design/shared/component-model.ts +35 -0
- package/corpus/templates/design/shared/motion-compiler.ts +79 -8
- package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +94 -10
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +14 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +115 -22
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +10 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/package.json +1 -1
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
type CodeLayerTreeNode,
|
|
52
52
|
} from "@shared/code-layer";
|
|
53
53
|
import { componentNameFor, isComponentInstance } from "@shared/component-model";
|
|
54
|
+
import type { A11yFinding } from "@shared/design-review";
|
|
54
55
|
import {
|
|
55
56
|
DESIGN_CAPABILITY_NAMES,
|
|
56
57
|
hasCapability,
|
|
@@ -60,7 +61,7 @@ import {
|
|
|
60
61
|
resolveTweaksToCssVars,
|
|
61
62
|
type TweakSelections,
|
|
62
63
|
} from "@shared/resolve-tweaks";
|
|
63
|
-
import { widthToPrefix } from "@shared/responsive-classes";
|
|
64
|
+
import { utilityStem, widthToPrefix } from "@shared/responsive-classes";
|
|
64
65
|
import { normalizeDesignSourceType } from "@shared/source-mode";
|
|
65
66
|
import {
|
|
66
67
|
IconArrowLeft,
|
|
@@ -223,8 +224,6 @@ import {
|
|
|
223
224
|
import { prettyScreenName } from "@/lib/screen-names";
|
|
224
225
|
import { cn } from "@/lib/utils";
|
|
225
226
|
|
|
226
|
-
import type { A11yFinding } from "../../shared/design-review.js";
|
|
227
|
-
|
|
228
227
|
const TAB_ID = generateTabId();
|
|
229
228
|
|
|
230
229
|
// Selection is tab-scoped (like navigation) so a second editor tab cannot
|
|
@@ -434,6 +433,32 @@ export function getLayerMoveSourceContent(args: {
|
|
|
434
433
|
);
|
|
435
434
|
}
|
|
436
435
|
|
|
436
|
+
export function getFreshActiveFileContent(args: {
|
|
437
|
+
activeContent: string;
|
|
438
|
+
latestContent?: string | null;
|
|
439
|
+
lastLocalContent?: string | null;
|
|
440
|
+
}) {
|
|
441
|
+
return args.latestContent ?? args.lastLocalContent ?? args.activeContent;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export function getFreshScreenContent(args: {
|
|
445
|
+
screenId: string;
|
|
446
|
+
activeFileId?: string | null;
|
|
447
|
+
freshActiveContent: string;
|
|
448
|
+
fileContentById: ReadonlyMap<string, string>;
|
|
449
|
+
}) {
|
|
450
|
+
return args.screenId === args.activeFileId
|
|
451
|
+
? args.freshActiveContent
|
|
452
|
+
: (args.fileContentById.get(args.screenId) ?? "");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export function getLayerMoveIterationOrder<T>(
|
|
456
|
+
orderedIds: readonly T[],
|
|
457
|
+
placement: "before" | "after" | "inside",
|
|
458
|
+
): T[] {
|
|
459
|
+
return placement === "after" ? [...orderedIds].reverse() : [...orderedIds];
|
|
460
|
+
}
|
|
461
|
+
|
|
437
462
|
function resolveZoomUpdate(update: SetStateAction<number>, current: number) {
|
|
438
463
|
return typeof update === "function" ? update(current) : update;
|
|
439
464
|
}
|
|
@@ -939,6 +964,113 @@ function applyInlineStylesToHtml(
|
|
|
939
964
|
}
|
|
940
965
|
}
|
|
941
966
|
|
|
967
|
+
const CSS_PROPERTY_UTILITY_STEMS: Record<string, string[]> = {
|
|
968
|
+
color: ["text-color"],
|
|
969
|
+
"background-color": ["background-color"],
|
|
970
|
+
background: ["background-color", "background-image"],
|
|
971
|
+
"font-size": ["font-size"],
|
|
972
|
+
"font-weight": ["font-weight"],
|
|
973
|
+
"font-family": ["font-family"],
|
|
974
|
+
"text-align": ["text-align"],
|
|
975
|
+
display: ["display"],
|
|
976
|
+
position: ["position"],
|
|
977
|
+
width: ["w"],
|
|
978
|
+
height: ["h"],
|
|
979
|
+
opacity: ["opacity"],
|
|
980
|
+
"border-radius": ["rounded"],
|
|
981
|
+
padding: ["p"],
|
|
982
|
+
"padding-left": ["px", "pl"],
|
|
983
|
+
"padding-right": ["px", "pr"],
|
|
984
|
+
"padding-top": ["py", "pt"],
|
|
985
|
+
"padding-bottom": ["py", "pb"],
|
|
986
|
+
margin: ["m"],
|
|
987
|
+
"margin-left": ["mx", "ml"],
|
|
988
|
+
"margin-right": ["mx", "mr"],
|
|
989
|
+
"margin-top": ["my", "mt"],
|
|
990
|
+
"margin-bottom": ["my", "mb"],
|
|
991
|
+
gap: ["gap"],
|
|
992
|
+
"column-gap": ["gap-x"],
|
|
993
|
+
"row-gap": ["gap-y"],
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const DEFAULT_STATES_PANEL_BREAKPOINTS = [
|
|
997
|
+
{ id: "bp-mobile", label: "Mobile", widthPx: 390 },
|
|
998
|
+
{ id: "bp-tablet", label: "Tablet", widthPx: 768 },
|
|
999
|
+
{ id: "bp-desktop", label: "Desktop", widthPx: 1280 },
|
|
1000
|
+
] as const;
|
|
1001
|
+
|
|
1002
|
+
function normalizeCssPropertyName(property: string): string {
|
|
1003
|
+
return property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
function looksLikeTailwindUtility(value: string): boolean {
|
|
1007
|
+
const trimmed = value.trim();
|
|
1008
|
+
if (!trimmed || /\s/.test(trimmed)) return false;
|
|
1009
|
+
if (/[;{}]/.test(trimmed) || /\/\*/.test(trimmed)) return false;
|
|
1010
|
+
if (/^(?:#|rgb\(|rgba\(|hsl\(|hsla\(|var\(|calc\()/i.test(trimmed)) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
if (trimmed.includes(":")) return false;
|
|
1014
|
+
return /^[!-]?[a-z0-9][a-z0-9[\]()./%_-]*$/i.test(trimmed);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
function responsiveUtilityMatchesStyleProperty(
|
|
1018
|
+
property: string,
|
|
1019
|
+
value: string,
|
|
1020
|
+
): boolean {
|
|
1021
|
+
if (!looksLikeTailwindUtility(value)) return false;
|
|
1022
|
+
const normalizedProperty = normalizeCssPropertyName(property);
|
|
1023
|
+
const stem = utilityStem(value.trim());
|
|
1024
|
+
const allowed = CSS_PROPERTY_UTILITY_STEMS[normalizedProperty];
|
|
1025
|
+
return allowed ? allowed.includes(stem) : stem === normalizedProperty;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
interface DesignStatePreviewRow {
|
|
1029
|
+
captureData?: Record<string, unknown> | null;
|
|
1030
|
+
fixtureData?: Record<string, unknown> | null;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
const STATE_PREVIEW_HTML_KEYS = [
|
|
1034
|
+
"domHtml",
|
|
1035
|
+
"domSnapshot",
|
|
1036
|
+
"documentHtml",
|
|
1037
|
+
"html",
|
|
1038
|
+
"content",
|
|
1039
|
+
"markup",
|
|
1040
|
+
] as const;
|
|
1041
|
+
|
|
1042
|
+
function looksLikePreviewHtml(value: string): boolean {
|
|
1043
|
+
return /<!doctype|<html\b|<body\b|<[a-zA-Z][\s>]/i.test(value);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function findStatePreviewHtml(value: unknown, depth = 0): string | null {
|
|
1047
|
+
if (typeof value === "string") {
|
|
1048
|
+
return looksLikePreviewHtml(value) ? value : null;
|
|
1049
|
+
}
|
|
1050
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || depth > 2)
|
|
1051
|
+
return null;
|
|
1052
|
+
const record = value as Record<string, unknown>;
|
|
1053
|
+
for (const key of STATE_PREVIEW_HTML_KEYS) {
|
|
1054
|
+
const hit = findStatePreviewHtml(record[key], depth + 1);
|
|
1055
|
+
if (hit) return hit;
|
|
1056
|
+
}
|
|
1057
|
+
for (const entry of Object.values(record)) {
|
|
1058
|
+
const hit = findStatePreviewHtml(entry, depth + 1);
|
|
1059
|
+
if (hit) return hit;
|
|
1060
|
+
}
|
|
1061
|
+
return null;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
function designStatePreviewHtml(
|
|
1065
|
+
row: DesignStatePreviewRow | undefined,
|
|
1066
|
+
): string | null {
|
|
1067
|
+
if (!row) return null;
|
|
1068
|
+
return (
|
|
1069
|
+
findStatePreviewHtml(row.captureData) ??
|
|
1070
|
+
findStatePreviewHtml(row.fixtureData)
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
942
1074
|
function escapeHtmlAttributeValue(value: string): string {
|
|
943
1075
|
return value
|
|
944
1076
|
.replace(/&/g, "&")
|
|
@@ -2096,6 +2228,36 @@ function collectCodeLayerAncestors(
|
|
|
2096
2228
|
return [];
|
|
2097
2229
|
}
|
|
2098
2230
|
|
|
2231
|
+
export function sortCodeLayerIdsByTreeOrder(
|
|
2232
|
+
ids: readonly string[],
|
|
2233
|
+
tree: readonly CodeLayerTreeNode[],
|
|
2234
|
+
): string[] {
|
|
2235
|
+
const treeOrder = new Map<string, number>();
|
|
2236
|
+
let index = 0;
|
|
2237
|
+
const visit = (nodes: readonly CodeLayerTreeNode[]) => {
|
|
2238
|
+
for (const node of nodes) {
|
|
2239
|
+
treeOrder.set(node.id, index);
|
|
2240
|
+
index += 1;
|
|
2241
|
+
visit(node.children);
|
|
2242
|
+
}
|
|
2243
|
+
};
|
|
2244
|
+
visit(tree);
|
|
2245
|
+
|
|
2246
|
+
const originalOrder = new Map(
|
|
2247
|
+
ids.map((id, originalIndex) => [id, originalIndex]),
|
|
2248
|
+
);
|
|
2249
|
+
return [...ids].sort((a, b) => {
|
|
2250
|
+
const aOrder = treeOrder.get(a);
|
|
2251
|
+
const bOrder = treeOrder.get(b);
|
|
2252
|
+
if (aOrder === undefined && bOrder === undefined) {
|
|
2253
|
+
return (originalOrder.get(a) ?? 0) - (originalOrder.get(b) ?? 0);
|
|
2254
|
+
}
|
|
2255
|
+
if (aOrder === undefined) return 1;
|
|
2256
|
+
if (bOrder === undefined) return -1;
|
|
2257
|
+
return aOrder - bOrder;
|
|
2258
|
+
});
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2099
2261
|
function findCodeLayerNodeInProjection(
|
|
2100
2262
|
projection: CodeLayerProjection,
|
|
2101
2263
|
previousNode: CodeLayerNode,
|
|
@@ -2819,6 +2981,11 @@ function buildAuthoritativeTweakSelections(
|
|
|
2819
2981
|
? persistedSelections[tweak.id]
|
|
2820
2982
|
: tweak.defaultValue;
|
|
2821
2983
|
}
|
|
2984
|
+
for (const [key, value] of Object.entries(persistedSelections)) {
|
|
2985
|
+
if (/^--[-_a-zA-Z0-9]+$/.test(key)) {
|
|
2986
|
+
selections[key] = value;
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2822
2989
|
return selections;
|
|
2823
2990
|
}
|
|
2824
2991
|
|
|
@@ -3035,6 +3202,9 @@ export default function DesignEditor() {
|
|
|
3035
3202
|
const [hiddenLayerIds, setHiddenLayerIds] = useState<Set<string>>(
|
|
3036
3203
|
() => new Set(),
|
|
3037
3204
|
);
|
|
3205
|
+
const layerStateOverridesRef = useRef<
|
|
3206
|
+
Map<string, { hidden?: boolean; locked?: boolean }>
|
|
3207
|
+
>(new Map());
|
|
3038
3208
|
const [overviewSelectAllRequest, setOverviewSelectAllRequest] = useState(0);
|
|
3039
3209
|
const [overviewClearSelectionRequest, setOverviewClearSelectionRequest] =
|
|
3040
3210
|
useState(0);
|
|
@@ -3054,6 +3224,11 @@ export default function DesignEditor() {
|
|
|
3054
3224
|
const [motionDockOpen, setMotionDockOpen] = useState(false);
|
|
3055
3225
|
const [motionTracks, setMotionTracks] = useState<MotionDockTrack[]>([]);
|
|
3056
3226
|
const [motionDurationMs, setMotionDurationMs] = useState(1000);
|
|
3227
|
+
const [shaderFillPreview, setShaderFillPreview] = useState<{
|
|
3228
|
+
selector?: string;
|
|
3229
|
+
nodeId?: string;
|
|
3230
|
+
css: string;
|
|
3231
|
+
} | null>(null);
|
|
3057
3232
|
|
|
3058
3233
|
// ── Breakpoint preview state (§6.4) ─────────────────────────────────────────
|
|
3059
3234
|
// Active breakpoint width for the current design (pixels). Controls which
|
|
@@ -3065,6 +3240,11 @@ export default function DesignEditor() {
|
|
|
3065
3240
|
// ── Design state selection (§6.4 / §8) ───────────────────────────────────────
|
|
3066
3241
|
// null = Default (live) view; a string id = one of the design_state rows.
|
|
3067
3242
|
const [selectedStateId, setSelectedStateId] = useState<string | null>(null);
|
|
3243
|
+
const [reviewFileId, setReviewFileId] = useState<string | null>(null);
|
|
3244
|
+
const [reviewFindings, setReviewFindings] = useState<A11yFinding[]>([]);
|
|
3245
|
+
const [reviewAuditLoading, setReviewAuditLoading] = useState(false);
|
|
3246
|
+
const [reviewAuditedAt, setReviewAuditedAt] = useState<string | null>(null);
|
|
3247
|
+
const [reviewAuditError, setReviewAuditError] = useState<string | null>(null);
|
|
3068
3248
|
|
|
3069
3249
|
useEffect(() => {
|
|
3070
3250
|
if (!isBuilderDesignEmbed) return;
|
|
@@ -3213,6 +3393,14 @@ export default function DesignEditor() {
|
|
|
3213
3393
|
geometryRedoStackRef.current.length > 0,
|
|
3214
3394
|
);
|
|
3215
3395
|
}, []);
|
|
3396
|
+
const clearLocalUndoRedoStacks = useCallback(() => {
|
|
3397
|
+
contentUndoStackRef.current = [];
|
|
3398
|
+
contentRedoStackRef.current = [];
|
|
3399
|
+
geometryUndoStackRef.current = [];
|
|
3400
|
+
geometryRedoStackRef.current = [];
|
|
3401
|
+
historyOrderRef.current = [];
|
|
3402
|
+
redoOrderRef.current = [];
|
|
3403
|
+
}, []);
|
|
3216
3404
|
const persistedSelectionStateRef = useRef<string | null>(null);
|
|
3217
3405
|
const designSelectionOwnerIdRef = useRef(`${TAB_ID}:${generateTabId()}`);
|
|
3218
3406
|
const frameGeometrySaveTimerRef = useRef<number | null>(null);
|
|
@@ -4421,70 +4609,21 @@ export default function DesignEditor() {
|
|
|
4421
4609
|
}, [files, activeFileId]);
|
|
4422
4610
|
|
|
4423
4611
|
const activeFile = files.find((f) => f.id === activeFileId) ?? files[0];
|
|
4612
|
+
useEffect(() => {
|
|
4613
|
+
if (!reviewFileId || reviewFileId === activeFile?.id) return;
|
|
4614
|
+
setReviewFileId(null);
|
|
4615
|
+
setReviewFindings([]);
|
|
4616
|
+
setReviewAuditedAt(null);
|
|
4617
|
+
setReviewAuditError(null);
|
|
4618
|
+
setReviewAuditLoading(false);
|
|
4619
|
+
}, [activeFile?.id, reviewFileId]);
|
|
4620
|
+
|
|
4424
4621
|
const initialGenerationReadOnly = shouldLockInspectorForInitialGeneration({
|
|
4425
4622
|
fileCount: files.length,
|
|
4426
4623
|
generating,
|
|
4427
4624
|
pendingGenerationActive,
|
|
4428
4625
|
});
|
|
4429
4626
|
|
|
4430
|
-
// §6.5 Review panel — lazy, on-demand accessibility audit for the active
|
|
4431
|
-
// file. The audit walks the file's rendered HTML, so it must never run on
|
|
4432
|
-
// editor load: it is gated behind `auditRequested` (flipped by the panel's
|
|
4433
|
-
// "Run" button) and reset whenever the active file changes so each file is
|
|
4434
|
-
// audited explicitly. The ReviewPanel itself owns the apply-a11y-fix mutation
|
|
4435
|
-
// behind its inline "Fix" button; we just supply the active design source and
|
|
4436
|
-
// refetch on apply so a resolved finding drops out of the list.
|
|
4437
|
-
const [auditRequested, setAuditRequested] = useState(false);
|
|
4438
|
-
useEffect(() => {
|
|
4439
|
-
setAuditRequested(false);
|
|
4440
|
-
}, [activeFile?.id]);
|
|
4441
|
-
|
|
4442
|
-
const auditQuery = useActionQuery<{
|
|
4443
|
-
auditedAt: string;
|
|
4444
|
-
findings: A11yFinding[];
|
|
4445
|
-
}>(
|
|
4446
|
-
"run-design-audit",
|
|
4447
|
-
{ designId: id ?? "", fileId: activeFile?.id },
|
|
4448
|
-
{
|
|
4449
|
-
enabled: auditRequested && !!id && !!activeFile?.id,
|
|
4450
|
-
refetchOnWindowFocus: false,
|
|
4451
|
-
},
|
|
4452
|
-
);
|
|
4453
|
-
|
|
4454
|
-
const reviewPanelProps = useMemo<
|
|
4455
|
-
Omit<ReviewPanelProps, "className"> | undefined
|
|
4456
|
-
>(() => {
|
|
4457
|
-
if (!id || !activeFile) return undefined;
|
|
4458
|
-
const audit = auditRequested ? auditQuery.data : undefined;
|
|
4459
|
-
return {
|
|
4460
|
-
findings: audit?.findings ?? [],
|
|
4461
|
-
auditLoading: auditRequested && auditQuery.isFetching,
|
|
4462
|
-
auditedAt: audit?.auditedAt ?? null,
|
|
4463
|
-
auditError:
|
|
4464
|
-
auditRequested && auditQuery.isError
|
|
4465
|
-
? auditQuery.error?.message ||
|
|
4466
|
-
"Audit failed. Try again." /* i18n-ignore design review fallback */
|
|
4467
|
-
: null,
|
|
4468
|
-
onRunAudit: () => {
|
|
4469
|
-
// First run flips the gate (the query auto-fetches); later runs refetch.
|
|
4470
|
-
if (auditRequested) {
|
|
4471
|
-
void auditQuery.refetch();
|
|
4472
|
-
} else {
|
|
4473
|
-
setAuditRequested(true);
|
|
4474
|
-
}
|
|
4475
|
-
},
|
|
4476
|
-
fixSource: {
|
|
4477
|
-
designId: id,
|
|
4478
|
-
fileId: activeFile.id,
|
|
4479
|
-
filename: activeFile.filename,
|
|
4480
|
-
},
|
|
4481
|
-
onFixApplied: () => {
|
|
4482
|
-
// Re-run the audit so the resolved finding drops out of the list.
|
|
4483
|
-
void auditQuery.refetch();
|
|
4484
|
-
},
|
|
4485
|
-
};
|
|
4486
|
-
}, [id, activeFile, auditRequested, auditQuery]);
|
|
4487
|
-
|
|
4488
4627
|
const selectedScreenIds = useMemo(
|
|
4489
4628
|
() =>
|
|
4490
4629
|
getSelectedScreenIdsForEditorState({
|
|
@@ -4873,6 +5012,7 @@ export default function DesignEditor() {
|
|
|
4873
5012
|
// The last content this client itself wrote into the Y.Doc (inline-style
|
|
4874
5013
|
// edits) — so the reconcile/observe doesn't treat our own echo as external.
|
|
4875
5014
|
const lastLocalContentRef = useRef<string | null>(null);
|
|
5015
|
+
const latestActiveContentRef = useRef<string | null>(null);
|
|
4876
5016
|
// Freshest known DB `updatedAt` for the active file, kept in a ref so the
|
|
4877
5017
|
// Yjs observe handler can advance the reconcile watermark without re-subscribing.
|
|
4878
5018
|
const documentFileUpdatedAtRef = useRef<string | null>(null);
|
|
@@ -4916,8 +5056,8 @@ export default function DesignEditor() {
|
|
|
4916
5056
|
setCollabContentFileId(null);
|
|
4917
5057
|
lastAppliedFileUpdatedAtRef.current = null;
|
|
4918
5058
|
lastLocalContentRef.current = null;
|
|
4919
|
-
|
|
4920
|
-
|
|
5059
|
+
latestActiveContentRef.current = null;
|
|
5060
|
+
clearLocalUndoRedoStacks();
|
|
4921
5061
|
clearStaleAgentCollabRecovery();
|
|
4922
5062
|
syncUndoRedoState();
|
|
4923
5063
|
return;
|
|
@@ -4928,13 +5068,14 @@ export default function DesignEditor() {
|
|
|
4928
5068
|
setCollabContentFileId(null);
|
|
4929
5069
|
lastAppliedFileUpdatedAtRef.current = null;
|
|
4930
5070
|
lastLocalContentRef.current = null;
|
|
4931
|
-
|
|
4932
|
-
|
|
5071
|
+
latestActiveContentRef.current = null;
|
|
5072
|
+
clearLocalUndoRedoStacks();
|
|
4933
5073
|
clearStaleAgentCollabRecovery();
|
|
4934
5074
|
syncUndoRedoState();
|
|
4935
5075
|
}
|
|
4936
5076
|
}, [
|
|
4937
5077
|
activeFileId,
|
|
5078
|
+
clearLocalUndoRedoStacks,
|
|
4938
5079
|
clearStaleAgentCollabRecovery,
|
|
4939
5080
|
syncUndoRedoState,
|
|
4940
5081
|
viewMode,
|
|
@@ -4956,6 +5097,7 @@ export default function DesignEditor() {
|
|
|
4956
5097
|
setCollabContent(pendingLocalContent);
|
|
4957
5098
|
setCollabContentFileId(fileId);
|
|
4958
5099
|
lastLocalContentRef.current = pendingLocalContent;
|
|
5100
|
+
latestActiveContentRef.current = pendingLocalContent;
|
|
4959
5101
|
setContentRenderRevision((revision) => revision + 1);
|
|
4960
5102
|
ydoc.transact(() => {
|
|
4961
5103
|
ytext.delete(0, ytext.length);
|
|
@@ -4975,6 +5117,7 @@ export default function DesignEditor() {
|
|
|
4975
5117
|
setCollabContent(storedContent);
|
|
4976
5118
|
setCollabContentFileId(fileId);
|
|
4977
5119
|
lastLocalContentRef.current = storedContent;
|
|
5120
|
+
latestActiveContentRef.current = storedContent;
|
|
4978
5121
|
setContentRenderRevision((revision) => revision + 1);
|
|
4979
5122
|
ydoc.transact(() => {
|
|
4980
5123
|
ytext.delete(0, ytext.length);
|
|
@@ -4987,6 +5130,7 @@ export default function DesignEditor() {
|
|
|
4987
5130
|
// confirms or applies the current DB content.
|
|
4988
5131
|
setCollabContent(text);
|
|
4989
5132
|
setCollabContentFileId(fileId);
|
|
5133
|
+
latestActiveContentRef.current = text;
|
|
4990
5134
|
setContentRenderRevision((revision) => revision + 1);
|
|
4991
5135
|
}
|
|
4992
5136
|
}, [
|
|
@@ -5029,6 +5173,7 @@ export default function DesignEditor() {
|
|
|
5029
5173
|
setCollabContent(pendingLocalContent);
|
|
5030
5174
|
setCollabContentFileId(fileId);
|
|
5031
5175
|
lastLocalContentRef.current = pendingLocalContent;
|
|
5176
|
+
latestActiveContentRef.current = pendingLocalContent;
|
|
5032
5177
|
setContentRenderRevision((revision) => revision + 1);
|
|
5033
5178
|
ydoc.transact(() => {
|
|
5034
5179
|
ytext.delete(0, ytext.length);
|
|
@@ -5038,6 +5183,7 @@ export default function DesignEditor() {
|
|
|
5038
5183
|
}
|
|
5039
5184
|
setCollabContent(next);
|
|
5040
5185
|
setCollabContentFileId(fileId);
|
|
5186
|
+
latestActiveContentRef.current = next;
|
|
5041
5187
|
if (isLocalEdit) {
|
|
5042
5188
|
lastLocalContentRef.current = next;
|
|
5043
5189
|
} else {
|
|
@@ -5142,6 +5288,7 @@ export default function DesignEditor() {
|
|
|
5142
5288
|
setCollabContent(dbContent);
|
|
5143
5289
|
setCollabContentFileId(activeFile.id);
|
|
5144
5290
|
lastLocalContentRef.current = dbContent;
|
|
5291
|
+
latestActiveContentRef.current = dbContent;
|
|
5145
5292
|
if (dbUpdatedAt) lastAppliedFileUpdatedAtRef.current = dbUpdatedAt;
|
|
5146
5293
|
setContentRenderRevision((revision) => revision + 1);
|
|
5147
5294
|
|
|
@@ -5195,6 +5342,7 @@ export default function DesignEditor() {
|
|
|
5195
5342
|
setCollabContent(expectedContent);
|
|
5196
5343
|
setCollabContentFileId(expectedFileId);
|
|
5197
5344
|
lastLocalContentRef.current = expectedContent;
|
|
5345
|
+
latestActiveContentRef.current = expectedContent;
|
|
5198
5346
|
lastAppliedFileUpdatedAtRef.current = expectedUpdatedAt;
|
|
5199
5347
|
setContentRenderRevision((revision) => revision + 1);
|
|
5200
5348
|
|
|
@@ -5220,6 +5368,7 @@ export default function DesignEditor() {
|
|
|
5220
5368
|
setCollabContent(dbContent);
|
|
5221
5369
|
setCollabContentFileId(activeFile.id);
|
|
5222
5370
|
lastLocalContentRef.current = dbContent;
|
|
5371
|
+
latestActiveContentRef.current = dbContent;
|
|
5223
5372
|
if (dbUpdatedAt) lastAppliedFileUpdatedAtRef.current = dbUpdatedAt;
|
|
5224
5373
|
setContentRenderRevision((revision) => revision + 1);
|
|
5225
5374
|
|
|
@@ -5281,6 +5430,56 @@ export default function DesignEditor() {
|
|
|
5281
5430
|
[],
|
|
5282
5431
|
);
|
|
5283
5432
|
|
|
5433
|
+
const handleRunDesignAudit = useCallback(async () => {
|
|
5434
|
+
if (!id || !activeFile?.id) return;
|
|
5435
|
+
const auditFileId = activeFile.id;
|
|
5436
|
+
setReviewFileId(auditFileId);
|
|
5437
|
+
setReviewAuditLoading(true);
|
|
5438
|
+
setReviewAuditError(null);
|
|
5439
|
+
try {
|
|
5440
|
+
const result = await callAction<{
|
|
5441
|
+
findings: A11yFinding[];
|
|
5442
|
+
auditedAt: string;
|
|
5443
|
+
}>("run-design-audit", {
|
|
5444
|
+
designId: id,
|
|
5445
|
+
fileId: auditFileId,
|
|
5446
|
+
} as any);
|
|
5447
|
+
setReviewFileId(auditFileId);
|
|
5448
|
+
setReviewFindings(Array.isArray(result.findings) ? result.findings : []);
|
|
5449
|
+
setReviewAuditedAt(result.auditedAt ?? new Date().toISOString());
|
|
5450
|
+
} catch (error) {
|
|
5451
|
+
const message =
|
|
5452
|
+
error instanceof Error
|
|
5453
|
+
? error.message
|
|
5454
|
+
: t("designEditor.toasts.auditRunFailed");
|
|
5455
|
+
setReviewAuditError(message);
|
|
5456
|
+
toast.error(message);
|
|
5457
|
+
} finally {
|
|
5458
|
+
setReviewAuditLoading(false);
|
|
5459
|
+
}
|
|
5460
|
+
}, [activeFile?.id, id, t]);
|
|
5461
|
+
|
|
5462
|
+
const handleReviewFindingClick = useCallback(
|
|
5463
|
+
(finding: A11yFinding) => {
|
|
5464
|
+
const selector =
|
|
5465
|
+
finding.selector ??
|
|
5466
|
+
(finding.nodeId
|
|
5467
|
+
? `[data-agent-native-node-id="${finding.nodeId.replace(/"/g, '\\"')}"]`
|
|
5468
|
+
: null);
|
|
5469
|
+
if (!selector) return;
|
|
5470
|
+
canvasIframeRef.current?.contentWindow?.postMessage(
|
|
5471
|
+
{
|
|
5472
|
+
type: "select-element",
|
|
5473
|
+
selector,
|
|
5474
|
+
nodeId: finding.nodeId ?? undefined,
|
|
5475
|
+
},
|
|
5476
|
+
"*",
|
|
5477
|
+
);
|
|
5478
|
+
if (finding.nodeId) setSelectedLayerIdsState([finding.nodeId]);
|
|
5479
|
+
},
|
|
5480
|
+
[canvasIframeRef],
|
|
5481
|
+
);
|
|
5482
|
+
|
|
5284
5483
|
// Broadcast pointer position (normalized to canvas container) and
|
|
5285
5484
|
// selected element selector so peers can see where the user is working.
|
|
5286
5485
|
const handleCanvasPointerMove = useCallback(
|
|
@@ -5411,6 +5610,9 @@ export default function DesignEditor() {
|
|
|
5411
5610
|
: (activeFile?.content ?? ""));
|
|
5412
5611
|
const activeContent =
|
|
5413
5612
|
typeof activeContentSource === "string" ? activeContentSource : "";
|
|
5613
|
+
useLayoutEffect(() => {
|
|
5614
|
+
latestActiveContentRef.current = activeContent;
|
|
5615
|
+
}, [activeContent]);
|
|
5414
5616
|
const fileContentById = useMemo(() => {
|
|
5415
5617
|
const map = new Map<string, string>();
|
|
5416
5618
|
for (const file of files) {
|
|
@@ -5420,9 +5622,16 @@ export default function DesignEditor() {
|
|
|
5420
5622
|
}, [files]);
|
|
5421
5623
|
const getScreenContent = useCallback(
|
|
5422
5624
|
(screenId: string) =>
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
:
|
|
5625
|
+
getFreshScreenContent({
|
|
5626
|
+
screenId,
|
|
5627
|
+
activeFileId: activeFile?.id,
|
|
5628
|
+
freshActiveContent: getFreshActiveFileContent({
|
|
5629
|
+
activeContent,
|
|
5630
|
+
latestContent: latestActiveContentRef.current,
|
|
5631
|
+
lastLocalContent: lastLocalContentRef.current,
|
|
5632
|
+
}),
|
|
5633
|
+
fileContentById,
|
|
5634
|
+
}),
|
|
5426
5635
|
[activeContent, activeFile?.id, fileContentById],
|
|
5427
5636
|
);
|
|
5428
5637
|
const pageStyles = useMemo(
|
|
@@ -5449,6 +5658,38 @@ export default function DesignEditor() {
|
|
|
5449
5658
|
}, [selectedCodeLayerNode, selectedElement?.selector]);
|
|
5450
5659
|
const selectedCanvasSelector = selectedCanvasSelectorCandidates[0] ?? null;
|
|
5451
5660
|
|
|
5661
|
+
const handleDesignStateSelect = useCallback(
|
|
5662
|
+
(stateId: string | null, row?: DesignStatePreviewRow) => {
|
|
5663
|
+
setSelectedStateId(stateId);
|
|
5664
|
+
const win = canvasIframeRef.current?.contentWindow;
|
|
5665
|
+
if (!win) return;
|
|
5666
|
+
|
|
5667
|
+
if (stateId === null) {
|
|
5668
|
+
win.postMessage(
|
|
5669
|
+
{
|
|
5670
|
+
type: "replace-document-content",
|
|
5671
|
+
content: activeContent,
|
|
5672
|
+
forceFullDocument: true,
|
|
5673
|
+
},
|
|
5674
|
+
"*",
|
|
5675
|
+
);
|
|
5676
|
+
return;
|
|
5677
|
+
}
|
|
5678
|
+
|
|
5679
|
+
const html = designStatePreviewHtml(row);
|
|
5680
|
+
if (!html) return;
|
|
5681
|
+
win.postMessage(
|
|
5682
|
+
{
|
|
5683
|
+
type: "replace-document-content",
|
|
5684
|
+
content: html,
|
|
5685
|
+
forceFullDocument: true,
|
|
5686
|
+
},
|
|
5687
|
+
"*",
|
|
5688
|
+
);
|
|
5689
|
+
},
|
|
5690
|
+
[activeContent, canvasIframeRef],
|
|
5691
|
+
);
|
|
5692
|
+
|
|
5452
5693
|
// ── Inspector header quick actions (Create component / Inspect code) ───────
|
|
5453
5694
|
// Resolve the design-level source type + capability map so the inspector can
|
|
5454
5695
|
// gate the real-app affordances (jump-to-source, prop write-back).
|
|
@@ -5497,10 +5738,14 @@ export default function DesignEditor() {
|
|
|
5497
5738
|
const selectedComponentNodeId = useMemo(() => {
|
|
5498
5739
|
if (!selectedCodeLayerNode) return undefined;
|
|
5499
5740
|
return isComponentInstance(selectedCodeLayerNode)
|
|
5500
|
-
? selectedCodeLayerNode
|
|
5741
|
+
? bridgeSourceIdForCodeLayerNode(selectedCodeLayerNode)
|
|
5501
5742
|
: undefined;
|
|
5502
5743
|
}, [selectedCodeLayerNode]);
|
|
5503
5744
|
|
|
5745
|
+
useEffect(() => {
|
|
5746
|
+
setShaderFillPreview(null);
|
|
5747
|
+
}, [activeFile?.id, selectedElement?.selector, selectedElement?.sourceId]);
|
|
5748
|
+
|
|
5504
5749
|
// A friendly default name for the create-component dialog, derived from the
|
|
5505
5750
|
// selected element's layer name / tag.
|
|
5506
5751
|
const defaultComponentName = useMemo(() => {
|
|
@@ -5729,6 +5974,7 @@ export default function DesignEditor() {
|
|
|
5729
5974
|
setCollabContent(nextContent);
|
|
5730
5975
|
setCollabContentFileId(activeFile.id);
|
|
5731
5976
|
lastLocalContentRef.current = nextContent;
|
|
5977
|
+
latestActiveContentRef.current = nextContent;
|
|
5732
5978
|
if (id) {
|
|
5733
5979
|
queryClient.setQueryData(
|
|
5734
5980
|
["action", "get-design", { id }],
|
|
@@ -5829,6 +6075,68 @@ export default function DesignEditor() {
|
|
|
5829
6075
|
],
|
|
5830
6076
|
);
|
|
5831
6077
|
|
|
6078
|
+
const handleComponentPropApplied = useCallback(
|
|
6079
|
+
(fileId: string, nextContent: string) => {
|
|
6080
|
+
applyFileContentUpdate(fileId, nextContent, {
|
|
6081
|
+
refreshPreview: fileId === activeFile?.id,
|
|
6082
|
+
});
|
|
6083
|
+
},
|
|
6084
|
+
[activeFile?.id, applyFileContentUpdate],
|
|
6085
|
+
);
|
|
6086
|
+
|
|
6087
|
+
const handleReviewFixApplied = useCallback(
|
|
6088
|
+
(
|
|
6089
|
+
_finding: A11yFinding,
|
|
6090
|
+
result?: { fileId?: string; patchedContent?: string },
|
|
6091
|
+
) => {
|
|
6092
|
+
setReviewFindings((prev) =>
|
|
6093
|
+
prev.filter((finding) => finding.id !== _finding.id),
|
|
6094
|
+
);
|
|
6095
|
+
if (
|
|
6096
|
+
typeof result?.fileId === "string" &&
|
|
6097
|
+
typeof result.patchedContent === "string"
|
|
6098
|
+
) {
|
|
6099
|
+
applyFileContentUpdate(result.fileId, result.patchedContent, {
|
|
6100
|
+
refreshPreview: result.fileId === activeFile?.id,
|
|
6101
|
+
});
|
|
6102
|
+
}
|
|
6103
|
+
void handleRunDesignAudit();
|
|
6104
|
+
},
|
|
6105
|
+
[activeFile?.id, applyFileContentUpdate, handleRunDesignAudit],
|
|
6106
|
+
);
|
|
6107
|
+
|
|
6108
|
+
const resolvedReviewPanelProps = useMemo<
|
|
6109
|
+
Omit<ReviewPanelProps, "className"> | undefined
|
|
6110
|
+
>(() => {
|
|
6111
|
+
if (!id || !activeFile) return undefined;
|
|
6112
|
+
const reviewMatchesActiveFile = reviewFileId === activeFile.id;
|
|
6113
|
+
return {
|
|
6114
|
+
findings: reviewMatchesActiveFile ? reviewFindings : [],
|
|
6115
|
+
auditLoading: reviewMatchesActiveFile ? reviewAuditLoading : false,
|
|
6116
|
+
auditedAt: reviewMatchesActiveFile ? reviewAuditedAt : null,
|
|
6117
|
+
auditError: reviewMatchesActiveFile ? reviewAuditError : null,
|
|
6118
|
+
onRunAudit: handleRunDesignAudit,
|
|
6119
|
+
onFindingClick: handleReviewFindingClick,
|
|
6120
|
+
fixSource: {
|
|
6121
|
+
designId: id,
|
|
6122
|
+
fileId: activeFile.id,
|
|
6123
|
+
filename: activeFile.filename,
|
|
6124
|
+
},
|
|
6125
|
+
onFixApplied: handleReviewFixApplied,
|
|
6126
|
+
};
|
|
6127
|
+
}, [
|
|
6128
|
+
activeFile,
|
|
6129
|
+
handleReviewFindingClick,
|
|
6130
|
+
handleReviewFixApplied,
|
|
6131
|
+
handleRunDesignAudit,
|
|
6132
|
+
id,
|
|
6133
|
+
reviewAuditError,
|
|
6134
|
+
reviewAuditLoading,
|
|
6135
|
+
reviewAuditedAt,
|
|
6136
|
+
reviewFileId,
|
|
6137
|
+
reviewFindings,
|
|
6138
|
+
]);
|
|
6139
|
+
|
|
5832
6140
|
const handleCreatePrimitive = useCallback(
|
|
5833
6141
|
(screenId: string, primitive: CanvasPrimitiveInsert) => {
|
|
5834
6142
|
if (!canEditDesign) return false;
|
|
@@ -6321,6 +6629,15 @@ export default function DesignEditor() {
|
|
|
6321
6629
|
mode,
|
|
6322
6630
|
activeTool,
|
|
6323
6631
|
tweakValues: tweakSelections,
|
|
6632
|
+
onShaderFillPreview: (_descriptor, css) => {
|
|
6633
|
+
setShaderFillPreview({
|
|
6634
|
+
selector: selectedElement?.selector ?? undefined,
|
|
6635
|
+
nodeId:
|
|
6636
|
+
selectedElement?.sourceId ?? selectedCodeLayerNode?.id ?? undefined,
|
|
6637
|
+
css,
|
|
6638
|
+
});
|
|
6639
|
+
},
|
|
6640
|
+
onShaderFillPreviewClear: () => setShaderFillPreview(null),
|
|
6324
6641
|
}),
|
|
6325
6642
|
[
|
|
6326
6643
|
activeFile?.filename,
|
|
@@ -6333,6 +6650,7 @@ export default function DesignEditor() {
|
|
|
6333
6650
|
mode,
|
|
6334
6651
|
overviewSelectedScreenIds,
|
|
6335
6652
|
selectedElement,
|
|
6653
|
+
selectedCodeLayerNode?.id,
|
|
6336
6654
|
selectedScreenIds,
|
|
6337
6655
|
tweakSelections,
|
|
6338
6656
|
viewMode,
|
|
@@ -6557,7 +6875,10 @@ export default function DesignEditor() {
|
|
|
6557
6875
|
// advance lastLocalContentRef.current to resolvedNextContent below, the
|
|
6558
6876
|
// next synchronous call reads the previous call's result and the patches
|
|
6559
6877
|
// compose. Falls back to activeContent when the ref is unset (file switch).
|
|
6560
|
-
const baseContent =
|
|
6878
|
+
const baseContent =
|
|
6879
|
+
latestActiveContentRef.current ??
|
|
6880
|
+
lastLocalContentRef.current ??
|
|
6881
|
+
activeContent;
|
|
6561
6882
|
const [firstProperty, firstValue] = entries[0];
|
|
6562
6883
|
const projection = buildCodeLayerProjection(baseContent);
|
|
6563
6884
|
const targetInfo = options.elementInfo ?? selectedElement;
|
|
@@ -6624,8 +6945,19 @@ export default function DesignEditor() {
|
|
|
6624
6945
|
});
|
|
6625
6946
|
const sendStyleChange = (window as any).__designCanvasSendStyle;
|
|
6626
6947
|
if (!options.runtimeApplied && typeof sendStyleChange === "function") {
|
|
6948
|
+
const selectorCandidates = targetNode
|
|
6949
|
+
? codeLayerSelectorAliases(targetNode)
|
|
6950
|
+
: selector
|
|
6951
|
+
? [selector]
|
|
6952
|
+
: [];
|
|
6953
|
+
const nodeId = targetNode
|
|
6954
|
+
? bridgeSourceIdForCodeLayerNode(targetNode)
|
|
6955
|
+
: targetInfo?.sourceId;
|
|
6627
6956
|
entries.forEach(([property, value]) => {
|
|
6628
|
-
sendStyleChange(selector, property, value
|
|
6957
|
+
sendStyleChange(selector, property, value, {
|
|
6958
|
+
selectorCandidates,
|
|
6959
|
+
nodeId,
|
|
6960
|
+
});
|
|
6629
6961
|
});
|
|
6630
6962
|
}
|
|
6631
6963
|
|
|
@@ -6661,16 +6993,19 @@ export default function DesignEditor() {
|
|
|
6661
6993
|
if (current.failed) return current;
|
|
6662
6994
|
// Try responsive-class path first when appropriate.
|
|
6663
6995
|
if (hasResponsiveCapability && activeBreakpointPrefix) {
|
|
6664
|
-
const
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6996
|
+
const utility = value.trim();
|
|
6997
|
+
if (responsiveUtilityMatchesStyleProperty(property, utility)) {
|
|
6998
|
+
const rcPatch = applyVisualEdit(current.content, {
|
|
6999
|
+
kind: "responsive-class",
|
|
7000
|
+
target: targetNode ? { nodeId: targetNode.id } : { selector },
|
|
7001
|
+
prefix: activeBreakpointPrefix,
|
|
7002
|
+
operation: "replace",
|
|
7003
|
+
utility,
|
|
7004
|
+
stem: utilityStem(utility),
|
|
7005
|
+
});
|
|
7006
|
+
if (rcPatch.result.status === "applied") {
|
|
7007
|
+
return { content: rcPatch.content, failed: null };
|
|
7008
|
+
}
|
|
6674
7009
|
}
|
|
6675
7010
|
// Responsive-class path didn't apply (e.g. value is a raw CSS value,
|
|
6676
7011
|
// not a Tailwind utility); fall through to the inline-style path.
|
|
@@ -6744,6 +7079,7 @@ export default function DesignEditor() {
|
|
|
6744
7079
|
// Mark as our own write so the get-design reconcile + Yjs observe don't
|
|
6745
7080
|
// treat the echo as an external edit and fight the live value.
|
|
6746
7081
|
lastLocalContentRef.current = resolvedNextContent;
|
|
7082
|
+
latestActiveContentRef.current = resolvedNextContent;
|
|
6747
7083
|
// Write the edit into the shared Y.Doc so other open clients see it live
|
|
6748
7084
|
// through Yjs (not only via the slower update-file → applyText round-trip).
|
|
6749
7085
|
// Use LOCAL_EDIT_ORIGIN so the UndoManager captures this transaction.
|
|
@@ -6803,7 +7139,10 @@ export default function DesignEditor() {
|
|
|
6803
7139
|
) {
|
|
6804
7140
|
const sendStyleChange = (window as any).__designCanvasSendStyle;
|
|
6805
7141
|
if (typeof sendStyleChange === "function") {
|
|
6806
|
-
sendStyleChange(selector, property, value
|
|
7142
|
+
sendStyleChange(selector, property, value, {
|
|
7143
|
+
selectorCandidates: selectedCanvasSelectorCandidates,
|
|
7144
|
+
nodeId: selectedElement?.sourceId,
|
|
7145
|
+
});
|
|
6807
7146
|
return;
|
|
6808
7147
|
}
|
|
6809
7148
|
}
|
|
@@ -6812,6 +7151,8 @@ export default function DesignEditor() {
|
|
|
6812
7151
|
[
|
|
6813
7152
|
commitVisualStyles,
|
|
6814
7153
|
selectedElement?.selector,
|
|
7154
|
+
selectedElement?.sourceId,
|
|
7155
|
+
selectedCanvasSelectorCandidates,
|
|
6815
7156
|
textEditingState.active,
|
|
6816
7157
|
textEditingState.hasRange,
|
|
6817
7158
|
textEditingState.selector,
|
|
@@ -6830,6 +7171,16 @@ export default function DesignEditor() {
|
|
|
6830
7171
|
[commitVisualStyles, selectedElement?.selector],
|
|
6831
7172
|
);
|
|
6832
7173
|
|
|
7174
|
+
const getFreshActiveContent = useCallback(
|
|
7175
|
+
() =>
|
|
7176
|
+
getFreshActiveFileContent({
|
|
7177
|
+
activeContent,
|
|
7178
|
+
latestContent: latestActiveContentRef.current,
|
|
7179
|
+
lastLocalContent: lastLocalContentRef.current,
|
|
7180
|
+
}),
|
|
7181
|
+
[activeContent],
|
|
7182
|
+
);
|
|
7183
|
+
|
|
6833
7184
|
const handleVisualStyleChange = useCallback(
|
|
6834
7185
|
(
|
|
6835
7186
|
selector: string,
|
|
@@ -6858,7 +7209,8 @@ export default function DesignEditor() {
|
|
|
6858
7209
|
) => {
|
|
6859
7210
|
if (!canEditDesign) return false;
|
|
6860
7211
|
if (!activeFile) return false;
|
|
6861
|
-
const
|
|
7212
|
+
const baseContent = getFreshActiveContent();
|
|
7213
|
+
const projection = buildCodeLayerProjection(baseContent);
|
|
6862
7214
|
const resolveBridgeNode = (targetSelector: string, sourceId?: string) =>
|
|
6863
7215
|
resolveCodeLayerNodeFromBridge(projection, targetSelector, sourceId);
|
|
6864
7216
|
const targetInfo = elementInfo
|
|
@@ -6875,7 +7227,7 @@ export default function DesignEditor() {
|
|
|
6875
7227
|
anchorSelector,
|
|
6876
7228
|
details?.anchorSourceId,
|
|
6877
7229
|
);
|
|
6878
|
-
const patch = applyVisualEdit(
|
|
7230
|
+
const patch = applyVisualEdit(baseContent, {
|
|
6879
7231
|
kind: "moveNode",
|
|
6880
7232
|
target: targetNode ? { nodeId: targetNode.id } : { selector },
|
|
6881
7233
|
anchor: anchorNode
|
|
@@ -6923,7 +7275,13 @@ export default function DesignEditor() {
|
|
|
6923
7275
|
}
|
|
6924
7276
|
return true;
|
|
6925
7277
|
},
|
|
6926
|
-
[
|
|
7278
|
+
[
|
|
7279
|
+
activeFile,
|
|
7280
|
+
applyLocalContentUpdate,
|
|
7281
|
+
canEditDesign,
|
|
7282
|
+
getFreshActiveContent,
|
|
7283
|
+
t,
|
|
7284
|
+
],
|
|
6927
7285
|
);
|
|
6928
7286
|
|
|
6929
7287
|
const handleVisualDuplicateChange = useCallback(
|
|
@@ -6940,7 +7298,8 @@ export default function DesignEditor() {
|
|
|
6940
7298
|
) => {
|
|
6941
7299
|
if (!canEditDesign) return false;
|
|
6942
7300
|
if (!activeFile) return false;
|
|
6943
|
-
const
|
|
7301
|
+
const baseContent = getFreshActiveContent();
|
|
7302
|
+
const projection = buildCodeLayerProjection(baseContent);
|
|
6944
7303
|
const targetInfo = elementInfo
|
|
6945
7304
|
? {
|
|
6946
7305
|
...elementInfo,
|
|
@@ -6960,7 +7319,7 @@ export default function DesignEditor() {
|
|
|
6960
7319
|
details?.anchorSelector,
|
|
6961
7320
|
details?.anchorSourceId,
|
|
6962
7321
|
);
|
|
6963
|
-
const nextContent = insertClonedHtmlLayer(
|
|
7322
|
+
const nextContent = insertClonedHtmlLayer(baseContent, cloneHtml, {
|
|
6964
7323
|
targetSelectors: targetNode
|
|
6965
7324
|
? codeLayerSelectorAliases(targetNode)
|
|
6966
7325
|
: [selector],
|
|
@@ -6994,7 +7353,13 @@ export default function DesignEditor() {
|
|
|
6994
7353
|
}
|
|
6995
7354
|
return true;
|
|
6996
7355
|
},
|
|
6997
|
-
[
|
|
7356
|
+
[
|
|
7357
|
+
activeFile,
|
|
7358
|
+
applyLocalContentUpdate,
|
|
7359
|
+
canEditDesign,
|
|
7360
|
+
getFreshActiveContent,
|
|
7361
|
+
t,
|
|
7362
|
+
],
|
|
6998
7363
|
);
|
|
6999
7364
|
|
|
7000
7365
|
const handleTextContentChange = useCallback(
|
|
@@ -7006,7 +7371,8 @@ export default function DesignEditor() {
|
|
|
7006
7371
|
) => {
|
|
7007
7372
|
if (!canEditDesign) return;
|
|
7008
7373
|
if (!activeFile) return;
|
|
7009
|
-
const
|
|
7374
|
+
const baseContent = getFreshActiveContent();
|
|
7375
|
+
const projection = buildCodeLayerProjection(baseContent);
|
|
7010
7376
|
const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
|
|
7011
7377
|
const targetNode = targetInfo
|
|
7012
7378
|
? resolveCodeLayerNodeFromElementInfo(projection, targetInfo)
|
|
@@ -7014,10 +7380,10 @@ export default function DesignEditor() {
|
|
|
7014
7380
|
const isEmpty = value.trim().length === 0;
|
|
7015
7381
|
const removedContent =
|
|
7016
7382
|
isEmpty && targetNode
|
|
7017
|
-
? removeCodeLayerNodeFromHtml(
|
|
7383
|
+
? removeCodeLayerNodeFromHtml(baseContent, targetNode)
|
|
7018
7384
|
: null;
|
|
7019
7385
|
const patch = !removedContent
|
|
7020
|
-
? applyVisualEdit(
|
|
7386
|
+
? applyVisualEdit(baseContent, {
|
|
7021
7387
|
kind: "textContent",
|
|
7022
7388
|
target: targetNode ? { nodeId: targetNode.id } : { selector },
|
|
7023
7389
|
value,
|
|
@@ -7027,12 +7393,7 @@ export default function DesignEditor() {
|
|
|
7027
7393
|
const nextContent =
|
|
7028
7394
|
removedContent ??
|
|
7029
7395
|
(patch?.result.status === "applied" ? patch.content : null) ??
|
|
7030
|
-
updateElementContentInHtml(
|
|
7031
|
-
activeContent,
|
|
7032
|
-
selector,
|
|
7033
|
-
value,
|
|
7034
|
-
details?.html,
|
|
7035
|
-
);
|
|
7396
|
+
updateElementContentInHtml(baseContent, selector, value, details?.html);
|
|
7036
7397
|
if (!nextContent) {
|
|
7037
7398
|
toast.error(
|
|
7038
7399
|
codeLayerPatchMessage(
|
|
@@ -7081,7 +7442,13 @@ export default function DesignEditor() {
|
|
|
7081
7442
|
: previous;
|
|
7082
7443
|
});
|
|
7083
7444
|
},
|
|
7084
|
-
[
|
|
7445
|
+
[
|
|
7446
|
+
activeFile,
|
|
7447
|
+
applyLocalContentUpdate,
|
|
7448
|
+
canEditDesign,
|
|
7449
|
+
getFreshActiveContent,
|
|
7450
|
+
t,
|
|
7451
|
+
],
|
|
7085
7452
|
);
|
|
7086
7453
|
|
|
7087
7454
|
const handleScreenVisualStyleChange = useCallback(
|
|
@@ -7402,7 +7769,10 @@ export default function DesignEditor() {
|
|
|
7402
7769
|
|
|
7403
7770
|
const handleCopySelection = useCallback(async () => {
|
|
7404
7771
|
if (!selectedElement?.selector) return;
|
|
7405
|
-
const html = getElementOuterHtml(
|
|
7772
|
+
const html = getElementOuterHtml(
|
|
7773
|
+
getFreshActiveContent(),
|
|
7774
|
+
selectedElement.selector,
|
|
7775
|
+
);
|
|
7406
7776
|
if (!html) return;
|
|
7407
7777
|
copiedLayerHtmlRef.current = html;
|
|
7408
7778
|
pasteCascadeRef.current = 0;
|
|
@@ -7412,11 +7782,12 @@ export default function DesignEditor() {
|
|
|
7412
7782
|
} catch {
|
|
7413
7783
|
toast.error(t("designEditor.toasts.clipboardBlocked"));
|
|
7414
7784
|
}
|
|
7415
|
-
}, [
|
|
7785
|
+
}, [getFreshActiveContent, selectedElement, t]);
|
|
7416
7786
|
|
|
7417
7787
|
const handlePasteSelection = useCallback(
|
|
7418
7788
|
(position?: { x: number; y: number }) => {
|
|
7419
7789
|
if (!activeFile || !canEditDesign || !copiedLayerHtmlRef.current) return;
|
|
7790
|
+
const baseContent = getFreshActiveContent();
|
|
7420
7791
|
|
|
7421
7792
|
// B7 fix: when an element is selected and no explicit canvas position was
|
|
7422
7793
|
// given, insert the clone as an in-flow sibling right AFTER the selected
|
|
@@ -7451,7 +7822,7 @@ export default function DesignEditor() {
|
|
|
7451
7822
|
}
|
|
7452
7823
|
})();
|
|
7453
7824
|
|
|
7454
|
-
const nextContent = insertClonedHtmlLayer(
|
|
7825
|
+
const nextContent = insertClonedHtmlLayer(baseContent, strippedHtml, {
|
|
7455
7826
|
targetSelectors: [selector],
|
|
7456
7827
|
placement: "after",
|
|
7457
7828
|
});
|
|
@@ -7476,7 +7847,7 @@ export default function DesignEditor() {
|
|
|
7476
7847
|
: { x: 120 + offset, y: 120 + offset };
|
|
7477
7848
|
})();
|
|
7478
7849
|
const nextContent = cloneHtmlLayerAtPosition(
|
|
7479
|
-
|
|
7850
|
+
baseContent,
|
|
7480
7851
|
copiedLayerHtmlRef.current,
|
|
7481
7852
|
targetPosition,
|
|
7482
7853
|
);
|
|
@@ -7485,10 +7856,10 @@ export default function DesignEditor() {
|
|
|
7485
7856
|
applyLocalContentUpdate(nextContent);
|
|
7486
7857
|
},
|
|
7487
7858
|
[
|
|
7488
|
-
activeContent,
|
|
7489
7859
|
activeFile,
|
|
7490
7860
|
applyLocalContentUpdate,
|
|
7491
7861
|
canEditDesign,
|
|
7862
|
+
getFreshActiveContent,
|
|
7492
7863
|
selectedCanvasSelector,
|
|
7493
7864
|
selectedElement,
|
|
7494
7865
|
],
|
|
@@ -7496,10 +7867,11 @@ export default function DesignEditor() {
|
|
|
7496
7867
|
|
|
7497
7868
|
const handlePasteOverSelection = useCallback(() => {
|
|
7498
7869
|
if (!activeFile || !copiedLayerHtmlRef.current) return;
|
|
7870
|
+
const baseContent = getFreshActiveContent();
|
|
7499
7871
|
if (selectedElement?.boundingRect) {
|
|
7500
7872
|
const { x, y } = selectedElement.boundingRect;
|
|
7501
7873
|
const nextContent = cloneHtmlLayerAtPosition(
|
|
7502
|
-
|
|
7874
|
+
baseContent,
|
|
7503
7875
|
copiedLayerHtmlRef.current,
|
|
7504
7876
|
{ x, y },
|
|
7505
7877
|
);
|
|
@@ -7509,9 +7881,9 @@ export default function DesignEditor() {
|
|
|
7509
7881
|
handlePasteSelection();
|
|
7510
7882
|
}
|
|
7511
7883
|
}, [
|
|
7512
|
-
activeContent,
|
|
7513
7884
|
activeFile,
|
|
7514
7885
|
applyLocalContentUpdate,
|
|
7886
|
+
getFreshActiveContent,
|
|
7515
7887
|
handlePasteSelection,
|
|
7516
7888
|
selectedElement,
|
|
7517
7889
|
]);
|
|
@@ -7519,7 +7891,8 @@ export default function DesignEditor() {
|
|
|
7519
7891
|
const handleDuplicateSelection = useCallback(() => {
|
|
7520
7892
|
if (!canEditDesign) return;
|
|
7521
7893
|
if (selectedElement?.selector) {
|
|
7522
|
-
const
|
|
7894
|
+
const baseContent = getFreshActiveContent();
|
|
7895
|
+
const html = getElementOuterHtml(baseContent, selectedElement.selector);
|
|
7523
7896
|
if (!html) {
|
|
7524
7897
|
toast.error(t("designEditor.toasts.duplicateElementFailed"));
|
|
7525
7898
|
return;
|
|
@@ -7550,7 +7923,7 @@ export default function DesignEditor() {
|
|
|
7550
7923
|
return html;
|
|
7551
7924
|
}
|
|
7552
7925
|
})();
|
|
7553
|
-
const nextContent = insertClonedHtmlLayer(
|
|
7926
|
+
const nextContent = insertClonedHtmlLayer(baseContent, strippedHtml, {
|
|
7554
7927
|
targetSelectors: [selector],
|
|
7555
7928
|
placement: "after",
|
|
7556
7929
|
});
|
|
@@ -7563,10 +7936,10 @@ export default function DesignEditor() {
|
|
|
7563
7936
|
}
|
|
7564
7937
|
if (activeFile) handleDuplicateScreen(activeFile.id);
|
|
7565
7938
|
}, [
|
|
7566
|
-
activeContent,
|
|
7567
7939
|
activeFile,
|
|
7568
7940
|
applyLocalContentUpdate,
|
|
7569
7941
|
canEditDesign,
|
|
7942
|
+
getFreshActiveContent,
|
|
7570
7943
|
handleDuplicateScreen,
|
|
7571
7944
|
selectedCanvasSelector,
|
|
7572
7945
|
selectedElement,
|
|
@@ -7575,6 +7948,7 @@ export default function DesignEditor() {
|
|
|
7575
7948
|
|
|
7576
7949
|
const handleDeleteSelection = useCallback(() => {
|
|
7577
7950
|
if (!canEditDesign) return;
|
|
7951
|
+
const baseContent = getFreshActiveContent();
|
|
7578
7952
|
// Multi-select delete: when several DOM/code layers are selected in the
|
|
7579
7953
|
// panel, remove all of them — not just the single focused element. Compose
|
|
7580
7954
|
// the removals against the running content (re-projecting each pass) so
|
|
@@ -7586,7 +7960,7 @@ export default function DesignEditor() {
|
|
|
7586
7960
|
!files.some((file) => file.id === layerId),
|
|
7587
7961
|
);
|
|
7588
7962
|
if (candidateIds.length > 1) {
|
|
7589
|
-
let content =
|
|
7963
|
+
let content = baseContent;
|
|
7590
7964
|
const removedSelectors: string[] = [];
|
|
7591
7965
|
for (const layerId of candidateIds) {
|
|
7592
7966
|
const projection = buildCodeLayerProjection(content);
|
|
@@ -7600,7 +7974,7 @@ export default function DesignEditor() {
|
|
|
7600
7974
|
if (selector) removedSelectors.push(selector);
|
|
7601
7975
|
content = next;
|
|
7602
7976
|
}
|
|
7603
|
-
if (content !==
|
|
7977
|
+
if (content !== baseContent) {
|
|
7604
7978
|
removedSelectors.forEach((selector) => deleteRuntimeElement(selector));
|
|
7605
7979
|
applyLocalContentUpdate(content, { refreshPreview: false });
|
|
7606
7980
|
setSelectedElement(null);
|
|
@@ -7611,27 +7985,26 @@ export default function DesignEditor() {
|
|
|
7611
7985
|
}
|
|
7612
7986
|
|
|
7613
7987
|
if (!selectedElement?.selector) return;
|
|
7614
|
-
const projection = buildCodeLayerProjection(
|
|
7988
|
+
const projection = buildCodeLayerProjection(baseContent);
|
|
7615
7989
|
const targetNode = resolveCodeLayerNodeFromElementInfo(
|
|
7616
7990
|
projection,
|
|
7617
7991
|
selectedElement,
|
|
7618
7992
|
);
|
|
7619
7993
|
const nextContent =
|
|
7620
7994
|
(targetNode
|
|
7621
|
-
? removeCodeLayerNodeFromHtml(
|
|
7622
|
-
: null) ??
|
|
7623
|
-
removeElementFromHtml(activeContent, selectedElement.selector);
|
|
7995
|
+
? removeCodeLayerNodeFromHtml(baseContent, targetNode)
|
|
7996
|
+
: null) ?? removeElementFromHtml(baseContent, selectedElement.selector);
|
|
7624
7997
|
if (!nextContent) return;
|
|
7625
7998
|
deleteRuntimeElement(selectedElement.selector);
|
|
7626
7999
|
applyLocalContentUpdate(nextContent, { refreshPreview: false });
|
|
7627
8000
|
setSelectedElement(null);
|
|
7628
8001
|
setSelectedLayerIdsState([]);
|
|
7629
8002
|
}, [
|
|
7630
|
-
activeContent,
|
|
7631
8003
|
applyLocalContentUpdate,
|
|
7632
8004
|
canEditDesign,
|
|
7633
8005
|
deleteRuntimeElement,
|
|
7634
8006
|
files,
|
|
8007
|
+
getFreshActiveContent,
|
|
7635
8008
|
selectedElement,
|
|
7636
8009
|
selectedLayerIdsState,
|
|
7637
8010
|
]);
|
|
@@ -7639,6 +8012,7 @@ export default function DesignEditor() {
|
|
|
7639
8012
|
// Wrap the current multi-layer selection into a new group container.
|
|
7640
8013
|
const handleGroupSelection = useCallback(() => {
|
|
7641
8014
|
if (!canEditDesign || !activeFile) return;
|
|
8015
|
+
const baseContent = getFreshActiveContent();
|
|
7642
8016
|
// Collect the DOM-node layer ids that belong to the active screen.
|
|
7643
8017
|
// Build a set of ids present in the active content so stale ids from
|
|
7644
8018
|
// other files (which can persist in selectedLayerIdsState after a
|
|
@@ -7648,14 +8022,14 @@ export default function DesignEditor() {
|
|
|
7648
8022
|
// selection.
|
|
7649
8023
|
const fileIds = new Set(files.map((f) => f.id));
|
|
7650
8024
|
const activeNodeIdSet = buildActiveFileNodeIdSet(
|
|
7651
|
-
buildCodeLayerProjection(
|
|
8025
|
+
buildCodeLayerProjection(baseContent),
|
|
7652
8026
|
);
|
|
7653
8027
|
const nodeIds = selectedLayerIdsState.filter(
|
|
7654
8028
|
(id) =>
|
|
7655
8029
|
!id.startsWith("__") && !fileIds.has(id) && activeNodeIdSet.has(id),
|
|
7656
8030
|
);
|
|
7657
8031
|
if (nodeIds.length < 2) return;
|
|
7658
|
-
const patch = applyVisualEdit(
|
|
8032
|
+
const patch = applyVisualEdit(baseContent, {
|
|
7659
8033
|
kind: "wrapNodes",
|
|
7660
8034
|
targetIds: nodeIds,
|
|
7661
8035
|
autoLayout: false,
|
|
@@ -7684,11 +8058,11 @@ export default function DesignEditor() {
|
|
|
7684
8058
|
}
|
|
7685
8059
|
}
|
|
7686
8060
|
}, [
|
|
7687
|
-
activeContent,
|
|
7688
8061
|
activeFile,
|
|
7689
8062
|
applyLocalContentUpdate,
|
|
7690
8063
|
canEditDesign,
|
|
7691
8064
|
files,
|
|
8065
|
+
getFreshActiveContent,
|
|
7692
8066
|
selectedLayerIdsState,
|
|
7693
8067
|
t,
|
|
7694
8068
|
]);
|
|
@@ -7696,12 +8070,13 @@ export default function DesignEditor() {
|
|
|
7696
8070
|
// Unwrap the currently selected single-container layer.
|
|
7697
8071
|
const handleUngroupSelection = useCallback(() => {
|
|
7698
8072
|
if (!canEditDesign || !activeFile) return;
|
|
8073
|
+
const baseContent = getFreshActiveContent();
|
|
7699
8074
|
// Filter to active-file nodes only (mirrors handleGroupSelection fix).
|
|
7700
8075
|
// A stale id from another file must not be passed to unwrap or it will
|
|
7701
8076
|
// fail with "conflict" even though the actual selection is valid.
|
|
7702
8077
|
const fileIds = new Set(files.map((f) => f.id));
|
|
7703
8078
|
const activeNodeIdSet = buildActiveFileNodeIdSet(
|
|
7704
|
-
buildCodeLayerProjection(
|
|
8079
|
+
buildCodeLayerProjection(baseContent),
|
|
7705
8080
|
);
|
|
7706
8081
|
const nodeIds = selectedLayerIdsState.filter(
|
|
7707
8082
|
(id) =>
|
|
@@ -7709,7 +8084,7 @@ export default function DesignEditor() {
|
|
|
7709
8084
|
);
|
|
7710
8085
|
const targetId = nodeIds[0];
|
|
7711
8086
|
if (!targetId) return;
|
|
7712
|
-
const patch = applyVisualEdit(
|
|
8087
|
+
const patch = applyVisualEdit(baseContent, {
|
|
7713
8088
|
kind: "unwrap",
|
|
7714
8089
|
targetId,
|
|
7715
8090
|
});
|
|
@@ -7727,11 +8102,11 @@ export default function DesignEditor() {
|
|
|
7727
8102
|
setSelectedElement(null);
|
|
7728
8103
|
setSelectedLayerIdsState([]);
|
|
7729
8104
|
}, [
|
|
7730
|
-
activeContent,
|
|
7731
8105
|
activeFile,
|
|
7732
8106
|
applyLocalContentUpdate,
|
|
7733
8107
|
canEditDesign,
|
|
7734
8108
|
files,
|
|
8109
|
+
getFreshActiveContent,
|
|
7735
8110
|
selectedLayerIdsState,
|
|
7736
8111
|
t,
|
|
7737
8112
|
]);
|
|
@@ -7748,7 +8123,8 @@ export default function DesignEditor() {
|
|
|
7748
8123
|
opts?: { direction?: "row" | "column"; gap?: string },
|
|
7749
8124
|
) => {
|
|
7750
8125
|
if (!canEditDesign || !activeFile) return;
|
|
7751
|
-
const
|
|
8126
|
+
const baseContent = getFreshActiveContent();
|
|
8127
|
+
const patch = applyVisualEdit(baseContent, {
|
|
7752
8128
|
kind: "autoLayout",
|
|
7753
8129
|
targetId: targetNodeId,
|
|
7754
8130
|
enabled: true,
|
|
@@ -7777,7 +8153,13 @@ export default function DesignEditor() {
|
|
|
7777
8153
|
setSelectedElement(elementInfoFromCodeLayerNode(containerNode));
|
|
7778
8154
|
}
|
|
7779
8155
|
},
|
|
7780
|
-
[
|
|
8156
|
+
[
|
|
8157
|
+
activeFile,
|
|
8158
|
+
applyLocalContentUpdate,
|
|
8159
|
+
canEditDesign,
|
|
8160
|
+
getFreshActiveContent,
|
|
8161
|
+
t,
|
|
8162
|
+
],
|
|
7781
8163
|
);
|
|
7782
8164
|
|
|
7783
8165
|
/**
|
|
@@ -9703,14 +10085,29 @@ ${serializedHtml}
|
|
|
9703
10085
|
)
|
|
9704
10086
|
.map((node) => node.id),
|
|
9705
10087
|
);
|
|
10088
|
+
const allLayerIds = new Set([
|
|
10089
|
+
...fileIds,
|
|
10090
|
+
...allCodeLayerNodes.map((node) => node.id),
|
|
10091
|
+
]);
|
|
9706
10092
|
const reconcile = (
|
|
9707
10093
|
current: Set<string>,
|
|
9708
10094
|
sourceIds: Set<string>,
|
|
10095
|
+
kind: "hidden" | "locked",
|
|
9709
10096
|
): Set<string> => {
|
|
9710
10097
|
const next = new Set(sourceIds);
|
|
9711
10098
|
current.forEach((id) => {
|
|
9712
10099
|
if (fileIds.has(id)) next.add(id);
|
|
9713
10100
|
});
|
|
10101
|
+
layerStateOverridesRef.current.forEach((override, id) => {
|
|
10102
|
+
if (!allLayerIds.has(id)) {
|
|
10103
|
+
layerStateOverridesRef.current.delete(id);
|
|
10104
|
+
return;
|
|
10105
|
+
}
|
|
10106
|
+
const value = override[kind];
|
|
10107
|
+
if (value === undefined) return;
|
|
10108
|
+
if (value) next.add(id);
|
|
10109
|
+
else next.delete(id);
|
|
10110
|
+
});
|
|
9714
10111
|
if (
|
|
9715
10112
|
next.size === current.size &&
|
|
9716
10113
|
Array.from(next).every((id) => current.has(id))
|
|
@@ -9720,8 +10117,12 @@ ${serializedHtml}
|
|
|
9720
10117
|
return next;
|
|
9721
10118
|
};
|
|
9722
10119
|
|
|
9723
|
-
setLockedLayerIds((current) =>
|
|
9724
|
-
|
|
10120
|
+
setLockedLayerIds((current) =>
|
|
10121
|
+
reconcile(current, lockedFromSource, "locked"),
|
|
10122
|
+
);
|
|
10123
|
+
setHiddenLayerIds((current) =>
|
|
10124
|
+
reconcile(current, hiddenFromSource, "hidden"),
|
|
10125
|
+
);
|
|
9725
10126
|
}, [codeLayerModelsByFile, files]);
|
|
9726
10127
|
const lockedLayerSelectors = useMemo(() => {
|
|
9727
10128
|
const selectors = Array.from(lockedLayerIds)
|
|
@@ -9973,7 +10374,11 @@ ${serializedHtml}
|
|
|
9973
10374
|
const match = statesPanelBreakpoints.find(
|
|
9974
10375
|
(bp) => bp.widthPx === activeBreakpointWidthState,
|
|
9975
10376
|
);
|
|
9976
|
-
return match
|
|
10377
|
+
if (match) return match.id;
|
|
10378
|
+
const defaultMatch = DEFAULT_STATES_PANEL_BREAKPOINTS.find(
|
|
10379
|
+
(bp) => bp.widthPx === activeBreakpointWidthState,
|
|
10380
|
+
);
|
|
10381
|
+
return defaultMatch?.id ?? "auto";
|
|
9977
10382
|
}, [activeBreakpointWidthState, statesPanelBreakpoints]);
|
|
9978
10383
|
|
|
9979
10384
|
const handleOpenDesignPreview = useCallback(() => {
|
|
@@ -10090,10 +10495,11 @@ ${serializedHtml}
|
|
|
10090
10495
|
) {
|
|
10091
10496
|
return;
|
|
10092
10497
|
}
|
|
10498
|
+
const freshActiveContent = getFreshActiveContent();
|
|
10093
10499
|
const destFile = files.find((file) => file.id === targetOwner.fileId);
|
|
10094
10500
|
const destContent =
|
|
10095
10501
|
targetOwner.fileId === activeFile?.id
|
|
10096
|
-
?
|
|
10502
|
+
? freshActiveContent
|
|
10097
10503
|
: (destFile?.content ?? "");
|
|
10098
10504
|
if (!destContent) return;
|
|
10099
10505
|
|
|
@@ -10124,10 +10530,26 @@ ${serializedHtml}
|
|
|
10124
10530
|
}
|
|
10125
10531
|
}
|
|
10126
10532
|
|
|
10533
|
+
const targetTreeForMove =
|
|
10534
|
+
targetOwner.fileId === activeFile?.id
|
|
10535
|
+
? buildCodeLayerTree(buildCodeLayerProjection(freshActiveContent))
|
|
10536
|
+
: targetOwner.tree;
|
|
10537
|
+
const orderedSameFileDragIds = sortCodeLayerIdsByTreeOrder(
|
|
10538
|
+
sameFileDragIds,
|
|
10539
|
+
targetTreeForMove,
|
|
10540
|
+
);
|
|
10541
|
+
const movedIdOrder = [
|
|
10542
|
+
...orderedSameFileDragIds,
|
|
10543
|
+
...crossFileDrags.map((drag) => drag.draggedId),
|
|
10544
|
+
];
|
|
10545
|
+
|
|
10127
10546
|
// --- Same-file moves (existing path) ---
|
|
10128
10547
|
let nextDestContent = destContent;
|
|
10129
10548
|
let moved = false;
|
|
10130
|
-
for (const draggedId of
|
|
10549
|
+
for (const draggedId of getLayerMoveIterationOrder(
|
|
10550
|
+
orderedSameFileDragIds,
|
|
10551
|
+
intent.placement,
|
|
10552
|
+
)) {
|
|
10131
10553
|
const patch = applyVisualEdit(nextDestContent, {
|
|
10132
10554
|
kind: "moveNode",
|
|
10133
10555
|
target: { nodeId: draggedId },
|
|
@@ -10158,7 +10580,7 @@ ${serializedHtml}
|
|
|
10158
10580
|
const currentSourceContent = getLayerMoveSourceContent({
|
|
10159
10581
|
sourceFileId,
|
|
10160
10582
|
activeFileId: activeFile?.id,
|
|
10161
|
-
activeContent,
|
|
10583
|
+
activeContent: freshActiveContent,
|
|
10162
10584
|
sourceFileContent: srcFile.content,
|
|
10163
10585
|
sourceContentMap,
|
|
10164
10586
|
});
|
|
@@ -10207,7 +10629,7 @@ ${serializedHtml}
|
|
|
10207
10629
|
const finalDestTree = finalDestProjection
|
|
10208
10630
|
? buildCodeLayerTree(finalDestProjection)
|
|
10209
10631
|
: [];
|
|
10210
|
-
const movedNodesAfterMove =
|
|
10632
|
+
const movedNodesAfterMove = movedIdOrder
|
|
10211
10633
|
.map((draggedId) => movedNodeSnapshots.get(draggedId))
|
|
10212
10634
|
.map((node) =>
|
|
10213
10635
|
node && finalDestProjection
|
|
@@ -10249,14 +10671,14 @@ ${serializedHtml}
|
|
|
10249
10671
|
}
|
|
10250
10672
|
},
|
|
10251
10673
|
[
|
|
10252
|
-
activeContent,
|
|
10253
10674
|
activeFile?.id,
|
|
10254
10675
|
applyFileContentUpdate,
|
|
10255
10676
|
canEditDesign,
|
|
10256
10677
|
canMoveLayer,
|
|
10257
10678
|
codeLayerOwnerByNodeId,
|
|
10258
|
-
files,
|
|
10259
10679
|
effectiveCodeLayerState,
|
|
10680
|
+
files,
|
|
10681
|
+
getFreshActiveContent,
|
|
10260
10682
|
t,
|
|
10261
10683
|
],
|
|
10262
10684
|
);
|
|
@@ -10406,7 +10828,7 @@ ${serializedHtml}
|
|
|
10406
10828
|
const sourceFile = files.find((file) => file.id === owner.fileId);
|
|
10407
10829
|
const sourceContent =
|
|
10408
10830
|
owner.fileId === activeFile?.id
|
|
10409
|
-
?
|
|
10831
|
+
? getFreshActiveContent()
|
|
10410
10832
|
: (sourceFile?.content ?? "");
|
|
10411
10833
|
if (!sourceContent) return;
|
|
10412
10834
|
const nextContent = setCodeLayerAttributeInHtml(
|
|
@@ -10422,12 +10844,12 @@ ${serializedHtml}
|
|
|
10422
10844
|
setSelectedLayerIdsState([layerId]);
|
|
10423
10845
|
},
|
|
10424
10846
|
[
|
|
10425
|
-
activeContent,
|
|
10426
10847
|
activeFile?.id,
|
|
10427
10848
|
applyFileContentUpdate,
|
|
10428
10849
|
canEditDesign,
|
|
10429
10850
|
codeLayerOwnerByNodeId,
|
|
10430
10851
|
files,
|
|
10852
|
+
getFreshActiveContent,
|
|
10431
10853
|
queryClient,
|
|
10432
10854
|
t,
|
|
10433
10855
|
updateFileMutation,
|
|
@@ -10437,6 +10859,10 @@ ${serializedHtml}
|
|
|
10437
10859
|
const handleToggleLayerLocked = useCallback(
|
|
10438
10860
|
(layerId: string, locked: boolean) => {
|
|
10439
10861
|
if (!canEditDesign) return;
|
|
10862
|
+
layerStateOverridesRef.current.set(layerId, {
|
|
10863
|
+
...layerStateOverridesRef.current.get(layerId),
|
|
10864
|
+
locked,
|
|
10865
|
+
});
|
|
10440
10866
|
const applyLockedState = () => {
|
|
10441
10867
|
setLockedLayerIds((current) => {
|
|
10442
10868
|
const next = new Set(current);
|
|
@@ -10451,38 +10877,47 @@ ${serializedHtml}
|
|
|
10451
10877
|
}
|
|
10452
10878
|
const owner = codeLayerOwnerByNodeId.get(layerId);
|
|
10453
10879
|
const node = owner?.node;
|
|
10454
|
-
if (!owner || !node)
|
|
10880
|
+
if (!owner || !node) {
|
|
10881
|
+
applyLockedState();
|
|
10882
|
+
return;
|
|
10883
|
+
}
|
|
10455
10884
|
const sourceFile = files.find((file) => file.id === owner.fileId);
|
|
10456
10885
|
const sourceContent =
|
|
10457
10886
|
owner.fileId === activeFile?.id
|
|
10458
|
-
?
|
|
10887
|
+
? getFreshActiveContent()
|
|
10459
10888
|
: (sourceFile?.content ?? "");
|
|
10460
|
-
if (
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
|
|
10464
|
-
|
|
10465
|
-
|
|
10466
|
-
|
|
10467
|
-
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10889
|
+
if (sourceContent) {
|
|
10890
|
+
const nextContent = setCodeLayerAttributeInHtml(
|
|
10891
|
+
sourceContent,
|
|
10892
|
+
node,
|
|
10893
|
+
"data-agent-native-locked",
|
|
10894
|
+
locked ? "true" : null,
|
|
10895
|
+
);
|
|
10896
|
+
if (nextContent && nextContent !== sourceContent) {
|
|
10897
|
+
applyFileContentUpdate(owner.fileId, nextContent, {
|
|
10898
|
+
refreshPreview: false,
|
|
10899
|
+
});
|
|
10900
|
+
}
|
|
10901
|
+
}
|
|
10471
10902
|
applyLockedState();
|
|
10472
10903
|
},
|
|
10473
10904
|
[
|
|
10474
|
-
activeContent,
|
|
10475
10905
|
activeFile?.id,
|
|
10476
10906
|
applyFileContentUpdate,
|
|
10477
10907
|
canEditDesign,
|
|
10478
10908
|
codeLayerOwnerByNodeId,
|
|
10479
10909
|
files,
|
|
10910
|
+
getFreshActiveContent,
|
|
10480
10911
|
],
|
|
10481
10912
|
);
|
|
10482
10913
|
|
|
10483
10914
|
const handleToggleLayerHidden = useCallback(
|
|
10484
10915
|
(layerId: string, hidden: boolean) => {
|
|
10485
10916
|
if (!canEditDesign) return;
|
|
10917
|
+
layerStateOverridesRef.current.set(layerId, {
|
|
10918
|
+
...layerStateOverridesRef.current.get(layerId),
|
|
10919
|
+
hidden,
|
|
10920
|
+
});
|
|
10486
10921
|
const applyHiddenState = () => {
|
|
10487
10922
|
setHiddenLayerIds((current) => {
|
|
10488
10923
|
const next = new Set(current);
|
|
@@ -10497,32 +10932,37 @@ ${serializedHtml}
|
|
|
10497
10932
|
}
|
|
10498
10933
|
const owner = codeLayerOwnerByNodeId.get(layerId);
|
|
10499
10934
|
const node = owner?.node;
|
|
10500
|
-
if (!owner || !node)
|
|
10935
|
+
if (!owner || !node) {
|
|
10936
|
+
applyHiddenState();
|
|
10937
|
+
return;
|
|
10938
|
+
}
|
|
10501
10939
|
const sourceFile = files.find((file) => file.id === owner.fileId);
|
|
10502
10940
|
const sourceContent =
|
|
10503
10941
|
owner.fileId === activeFile?.id
|
|
10504
|
-
?
|
|
10942
|
+
? getFreshActiveContent()
|
|
10505
10943
|
: (sourceFile?.content ?? "");
|
|
10506
|
-
if (
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10944
|
+
if (sourceContent) {
|
|
10945
|
+
const nextContent = setCodeLayerAttributeInHtml(
|
|
10946
|
+
sourceContent,
|
|
10947
|
+
node,
|
|
10948
|
+
"data-agent-native-hidden",
|
|
10949
|
+
hidden ? "true" : null,
|
|
10950
|
+
);
|
|
10951
|
+
if (nextContent && nextContent !== sourceContent) {
|
|
10952
|
+
applyFileContentUpdate(owner.fileId, nextContent, {
|
|
10953
|
+
refreshPreview: false,
|
|
10954
|
+
});
|
|
10955
|
+
}
|
|
10956
|
+
}
|
|
10517
10957
|
applyHiddenState();
|
|
10518
10958
|
},
|
|
10519
10959
|
[
|
|
10520
|
-
activeContent,
|
|
10521
10960
|
activeFile?.id,
|
|
10522
10961
|
applyFileContentUpdate,
|
|
10523
10962
|
canEditDesign,
|
|
10524
10963
|
codeLayerOwnerByNodeId,
|
|
10525
10964
|
files,
|
|
10965
|
+
getFreshActiveContent,
|
|
10526
10966
|
],
|
|
10527
10967
|
);
|
|
10528
10968
|
|
|
@@ -11864,11 +12304,8 @@ ${serializedHtml}
|
|
|
11864
12304
|
sourceType={designSourceType}
|
|
11865
12305
|
fusionUrl={designFusionUrl}
|
|
11866
12306
|
previewWidthPx={activeBreakpointWidthState}
|
|
12307
|
+
shaderFillPreview={shaderFillPreview}
|
|
11867
12308
|
onComponentSourceJump={handleComponentSourceJump}
|
|
11868
|
-
// TODO: thread motionTracks / shaderFillPreview live-preview
|
|
11869
|
-
// props once the canvas-side live preview state is plumbed
|
|
11870
|
-
// here (non-trivial new state; MotionDock already drives
|
|
11871
|
-
// motion-preview directly via canvasIframeRef for now).
|
|
11872
12309
|
motionTracks={motionTracksWire}
|
|
11873
12310
|
editMode={mode === "edit"}
|
|
11874
12311
|
interactMode={mode === "interact"}
|
|
@@ -12036,6 +12473,51 @@ ${serializedHtml}
|
|
|
12036
12473
|
tweakValues={tweakSelections}
|
|
12037
12474
|
extensionContext={designExtensionContext}
|
|
12038
12475
|
readOnly={initialGenerationReadOnly}
|
|
12476
|
+
onComponentPropApplied={handleComponentPropApplied}
|
|
12477
|
+
onTokensApplied={(resolvedCssVars) => {
|
|
12478
|
+
if (!canEditDesign || !id) return;
|
|
12479
|
+
setTweakSelections((prev) => ({
|
|
12480
|
+
...prev,
|
|
12481
|
+
...resolvedCssVars,
|
|
12482
|
+
}));
|
|
12483
|
+
queryClient.setQueryData(
|
|
12484
|
+
["action", "get-design", { id }],
|
|
12485
|
+
(old: any) => {
|
|
12486
|
+
if (!old || typeof old !== "object") return old;
|
|
12487
|
+
let currentData: Record<string, unknown> = {};
|
|
12488
|
+
if (typeof old.data === "string" && old.data) {
|
|
12489
|
+
try {
|
|
12490
|
+
const parsed = JSON.parse(old.data);
|
|
12491
|
+
if (
|
|
12492
|
+
parsed &&
|
|
12493
|
+
typeof parsed === "object" &&
|
|
12494
|
+
!Array.isArray(parsed)
|
|
12495
|
+
) {
|
|
12496
|
+
currentData = parsed;
|
|
12497
|
+
}
|
|
12498
|
+
} catch {
|
|
12499
|
+
currentData = {};
|
|
12500
|
+
}
|
|
12501
|
+
}
|
|
12502
|
+
const currentSelections =
|
|
12503
|
+
currentData.tweakSelections &&
|
|
12504
|
+
typeof currentData.tweakSelections === "object" &&
|
|
12505
|
+
!Array.isArray(currentData.tweakSelections)
|
|
12506
|
+
? currentData.tweakSelections
|
|
12507
|
+
: {};
|
|
12508
|
+
return {
|
|
12509
|
+
...old,
|
|
12510
|
+
data: JSON.stringify({
|
|
12511
|
+
...currentData,
|
|
12512
|
+
tweakSelections: {
|
|
12513
|
+
...currentSelections,
|
|
12514
|
+
...resolvedCssVars,
|
|
12515
|
+
},
|
|
12516
|
+
}),
|
|
12517
|
+
};
|
|
12518
|
+
},
|
|
12519
|
+
);
|
|
12520
|
+
}}
|
|
12039
12521
|
onTweakChange={(tweakId, value) =>
|
|
12040
12522
|
setTweakSelections((prev) => {
|
|
12041
12523
|
if (!canEditDesign) return prev;
|
|
@@ -12068,9 +12550,7 @@ ${serializedHtml}
|
|
|
12068
12550
|
activeStateId: selectedStateId,
|
|
12069
12551
|
activeBreakpointId: statesPanelActiveBreakpointId,
|
|
12070
12552
|
breakpoints: statesPanelBreakpoints,
|
|
12071
|
-
onStateSelect:
|
|
12072
|
-
setSelectedStateId(stateId);
|
|
12073
|
-
},
|
|
12553
|
+
onStateSelect: handleDesignStateSelect,
|
|
12074
12554
|
onBreakpointSelect: (breakpointId) => {
|
|
12075
12555
|
// "auto" = clear the active breakpoint (overview).
|
|
12076
12556
|
if (breakpointId === "auto") {
|
|
@@ -12083,9 +12563,13 @@ ${serializedHtml}
|
|
|
12083
12563
|
}
|
|
12084
12564
|
return;
|
|
12085
12565
|
}
|
|
12086
|
-
const bp =
|
|
12087
|
-
(
|
|
12088
|
-
|
|
12566
|
+
const bp =
|
|
12567
|
+
statesPanelBreakpoints.find(
|
|
12568
|
+
(b) => b.id === breakpointId,
|
|
12569
|
+
) ??
|
|
12570
|
+
DEFAULT_STATES_PANEL_BREAKPOINTS.find(
|
|
12571
|
+
(b) => b.id === breakpointId,
|
|
12572
|
+
);
|
|
12089
12573
|
if (!bp) return;
|
|
12090
12574
|
setActiveBreakpointWidthState(bp.widthPx);
|
|
12091
12575
|
if (id) {
|
|
@@ -12105,7 +12589,7 @@ ${serializedHtml}
|
|
|
12105
12589
|
}
|
|
12106
12590
|
: undefined
|
|
12107
12591
|
}
|
|
12108
|
-
reviewPanelProps={
|
|
12592
|
+
reviewPanelProps={resolvedReviewPanelProps}
|
|
12109
12593
|
/>
|
|
12110
12594
|
</div>
|
|
12111
12595
|
) : (
|
|
@@ -12132,12 +12616,33 @@ ${serializedHtml}
|
|
|
12132
12616
|
selectedTarget={motionSelectedTarget}
|
|
12133
12617
|
onApply={(tracks, durationMs) => {
|
|
12134
12618
|
if (!id) return;
|
|
12135
|
-
applyMotionEditMutation.mutate(
|
|
12136
|
-
|
|
12137
|
-
|
|
12138
|
-
|
|
12139
|
-
|
|
12140
|
-
|
|
12619
|
+
applyMotionEditMutation.mutate(
|
|
12620
|
+
{
|
|
12621
|
+
designId: id,
|
|
12622
|
+
fileId: activeFile.id,
|
|
12623
|
+
tracks: tracks.map(({ label: _label, ...t }) => t),
|
|
12624
|
+
durationMs,
|
|
12625
|
+
includeContent: true,
|
|
12626
|
+
},
|
|
12627
|
+
{
|
|
12628
|
+
onSuccess: (result) => {
|
|
12629
|
+
const response = result as {
|
|
12630
|
+
fileId?: unknown;
|
|
12631
|
+
patchedContent?: unknown;
|
|
12632
|
+
};
|
|
12633
|
+
if (
|
|
12634
|
+
typeof response.fileId === "string" &&
|
|
12635
|
+
typeof response.patchedContent === "string"
|
|
12636
|
+
) {
|
|
12637
|
+
applyFileContentUpdate(
|
|
12638
|
+
response.fileId,
|
|
12639
|
+
response.patchedContent,
|
|
12640
|
+
{ refreshPreview: response.fileId === activeFile.id },
|
|
12641
|
+
);
|
|
12642
|
+
}
|
|
12643
|
+
},
|
|
12644
|
+
},
|
|
12645
|
+
);
|
|
12141
12646
|
}}
|
|
12142
12647
|
applying={applyMotionEditMutation.isPending}
|
|
12143
12648
|
/>
|