made-refine 0.2.6 → 0.2.7
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/dist/index.d.mts +17 -7
- package/dist/index.d.ts +17 -7
- package/dist/index.js +1332 -224
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1330 -224
- package/dist/index.mjs.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/{utils-C7RBdUAE.d.mts → utils-dcZIwXa7.d.mts} +114 -2
- package/dist/{utils-C7RBdUAE.d.ts → utils-dcZIwXa7.d.ts} +114 -2
- package/dist/utils.d.mts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +661 -62
- package/dist/utils.js.map +1 -1
- package/dist/utils.mjs +650 -62
- package/dist/utils.mjs.map +1 -1
- package/package.json +1 -1
package/dist/utils.mjs
CHANGED
|
@@ -1104,6 +1104,10 @@ function getFlexDirection(element) {
|
|
|
1104
1104
|
const computed = window.getComputedStyle(element);
|
|
1105
1105
|
return computed.flexDirection;
|
|
1106
1106
|
}
|
|
1107
|
+
function isInFlowChild(el) {
|
|
1108
|
+
const cs = window.getComputedStyle(el);
|
|
1109
|
+
return cs.display !== "none" && cs.position !== "absolute" && cs.position !== "fixed";
|
|
1110
|
+
}
|
|
1107
1111
|
function detectChildrenDirection(container, exclude) {
|
|
1108
1112
|
const computed = window.getComputedStyle(container);
|
|
1109
1113
|
if (computed.display === "flex" || computed.display === "inline-flex") {
|
|
@@ -1116,8 +1120,7 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1116
1120
|
const visible = [];
|
|
1117
1121
|
for (const c of container.children) {
|
|
1118
1122
|
if (!(c instanceof HTMLElement) || c === exclude) continue;
|
|
1119
|
-
|
|
1120
|
-
if (cs.display === "none" || cs.position === "absolute" || cs.position === "fixed") continue;
|
|
1123
|
+
if (!isInFlowChild(c)) continue;
|
|
1121
1124
|
visible.push(c);
|
|
1122
1125
|
if (visible.length === 2) break;
|
|
1123
1126
|
}
|
|
@@ -1130,6 +1133,29 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1130
1133
|
}
|
|
1131
1134
|
return { axis: "vertical", reversed: second.bottom < first.top };
|
|
1132
1135
|
}
|
|
1136
|
+
function computeIntendedIndex(parent, draggedElement) {
|
|
1137
|
+
const { axis } = detectChildrenDirection(parent, draggedElement);
|
|
1138
|
+
const isHorizontal = axis === "horizontal";
|
|
1139
|
+
const draggedRect = draggedElement.getBoundingClientRect();
|
|
1140
|
+
const intendedCenter = isHorizontal ? draggedRect.left + draggedRect.width / 2 : draggedRect.top + draggedRect.height / 2;
|
|
1141
|
+
const siblings = [];
|
|
1142
|
+
for (const c of parent.children) {
|
|
1143
|
+
if (!(c instanceof HTMLElement) || c === draggedElement) continue;
|
|
1144
|
+
if (!isInFlowChild(c)) continue;
|
|
1145
|
+
siblings.push(c);
|
|
1146
|
+
}
|
|
1147
|
+
if (siblings.length === 0) {
|
|
1148
|
+
return { index: 0, siblingBefore: null, siblingAfter: null };
|
|
1149
|
+
}
|
|
1150
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
1151
|
+
const rect = siblings[i].getBoundingClientRect();
|
|
1152
|
+
const midpoint = isHorizontal ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
1153
|
+
if (intendedCenter < midpoint) {
|
|
1154
|
+
return { index: i, siblingBefore: i > 0 ? siblings[i - 1] : null, siblingAfter: siblings[i] };
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return { index: siblings.length, siblingBefore: siblings[siblings.length - 1], siblingAfter: null };
|
|
1158
|
+
}
|
|
1133
1159
|
function htmlChildren(el) {
|
|
1134
1160
|
return Array.from(el.children).filter(
|
|
1135
1161
|
(child) => child instanceof HTMLElement
|
|
@@ -1288,6 +1314,22 @@ function findContainerAtPoint(x, y, exclude, preferredParent) {
|
|
|
1288
1314
|
}
|
|
1289
1315
|
return findContainerViaTraversal(x, y, exclude);
|
|
1290
1316
|
}
|
|
1317
|
+
function findLayoutContainerAtPoint(x, y, exclude, preferredParent) {
|
|
1318
|
+
const host = document.querySelector("[data-direct-edit-host]");
|
|
1319
|
+
if (host) host.style.display = "none";
|
|
1320
|
+
const elements = document.elementsFromPoint(x, y);
|
|
1321
|
+
if (host) host.style.display = "";
|
|
1322
|
+
for (const el of elements) {
|
|
1323
|
+
if (skipElement(el, exclude)) continue;
|
|
1324
|
+
if (isLayoutContainer(el)) return el;
|
|
1325
|
+
}
|
|
1326
|
+
if (preferredParent && isLayoutContainer(preferredParent)) {
|
|
1327
|
+
for (const el of elements) {
|
|
1328
|
+
if (el === preferredParent) return preferredParent;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1291
1333
|
function calculateDropPosition(container, pointerX, pointerY, draggedElement) {
|
|
1292
1334
|
const { axis, reversed: isReversed } = detectChildrenDirection(container, draggedElement);
|
|
1293
1335
|
const isHorizontal = axis === "horizontal";
|
|
@@ -1682,7 +1724,18 @@ function getReactComponentStack(element) {
|
|
|
1682
1724
|
return getRenderStack(fiber);
|
|
1683
1725
|
}
|
|
1684
1726
|
function getElementDisplayName(element) {
|
|
1685
|
-
|
|
1727
|
+
const tag = element.tagName.toLowerCase();
|
|
1728
|
+
if (element.id) return `${tag}#${element.id}`;
|
|
1729
|
+
const firstClass = Array.from(element.classList).find((c) => c && !c.startsWith("direct-edit"));
|
|
1730
|
+
if (firstClass) return `${tag}.${firstClass}`;
|
|
1731
|
+
return tag;
|
|
1732
|
+
}
|
|
1733
|
+
function getChildBriefInfo(element) {
|
|
1734
|
+
const name = getElementDisplayName(element);
|
|
1735
|
+
const raw = ((element.innerText || element.textContent) ?? "").replace(/\s+/g, " ").trim();
|
|
1736
|
+
const textPreview = raw.length > 40 ? `${raw.slice(0, 37)}...` : raw;
|
|
1737
|
+
const source = getElementSource(element);
|
|
1738
|
+
return { name, textPreview, source };
|
|
1686
1739
|
}
|
|
1687
1740
|
var STABLE_ATTRIBUTES = ["data-testid", "data-qa", "data-cy", "aria-label", "role"];
|
|
1688
1741
|
var MAX_SELECTOR_DEPTH = 24;
|
|
@@ -1970,26 +2023,28 @@ function parseDomSource(element) {
|
|
|
1970
2023
|
}
|
|
1971
2024
|
return { file, line, column };
|
|
1972
2025
|
}
|
|
1973
|
-
function
|
|
1974
|
-
const
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
};
|
|
1988
|
-
break;
|
|
1989
|
-
}
|
|
1990
|
-
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
2026
|
+
function getElementSource(element) {
|
|
2027
|
+
const domSource = parseDomSource(element);
|
|
2028
|
+
if (domSource) return domSource;
|
|
2029
|
+
const seenFibers = /* @__PURE__ */ new Set();
|
|
2030
|
+
let fiber = getFiberForElement(element);
|
|
2031
|
+
while (fiber && !seenFibers.has(fiber)) {
|
|
2032
|
+
seenFibers.add(fiber);
|
|
2033
|
+
const fiberSource = getSourceFromFiber(fiber);
|
|
2034
|
+
if (fiberSource?.fileName) {
|
|
2035
|
+
return {
|
|
2036
|
+
file: fiberSource.fileName,
|
|
2037
|
+
line: fiberSource.lineNumber,
|
|
2038
|
+
column: fiberSource.columnNumber
|
|
2039
|
+
};
|
|
1991
2040
|
}
|
|
2041
|
+
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
1992
2042
|
}
|
|
2043
|
+
return null;
|
|
2044
|
+
}
|
|
2045
|
+
function getElementLocator(element) {
|
|
2046
|
+
const elementInfo = getElementInfo(element);
|
|
2047
|
+
const domSource = getElementSource(element);
|
|
1993
2048
|
return {
|
|
1994
2049
|
reactStack: getReactComponentStack(element),
|
|
1995
2050
|
domSelector: buildDomSelector(element),
|
|
@@ -2008,7 +2063,7 @@ function getLocatorHeader(locator) {
|
|
|
2008
2063
|
const formattedSource = locator.domSource?.file ? formatSourceLocation(locator.domSource.file, locator.domSource.line, locator.domSource.column) : primaryFrame?.file ? formatSourceLocation(primaryFrame.file, primaryFrame.line, primaryFrame.column) : null;
|
|
2009
2064
|
return { componentLabel, formattedSource };
|
|
2010
2065
|
}
|
|
2011
|
-
function buildLocatorContextLines(locator) {
|
|
2066
|
+
function buildLocatorContextLines(locator, options) {
|
|
2012
2067
|
const lines = [];
|
|
2013
2068
|
const { componentLabel, formattedSource } = getLocatorHeader(locator);
|
|
2014
2069
|
const target = (locator.targetHtml || locator.domContextHtml || "").trim();
|
|
@@ -2021,7 +2076,7 @@ function buildLocatorContextLines(locator) {
|
|
|
2021
2076
|
lines.push("target:");
|
|
2022
2077
|
lines.push(target);
|
|
2023
2078
|
}
|
|
2024
|
-
if (context && context !== target) {
|
|
2079
|
+
if (!options?.skipContext && context && context !== target) {
|
|
2025
2080
|
lines.push("context:");
|
|
2026
2081
|
lines.push(context);
|
|
2027
2082
|
}
|
|
@@ -2175,6 +2230,29 @@ function buildEditExport(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
|
|
|
2175
2230
|
}
|
|
2176
2231
|
return lines.join("\n");
|
|
2177
2232
|
}
|
|
2233
|
+
function buildEditExportWithOptions(locator, pendingStyles, textEdit, options) {
|
|
2234
|
+
const changes = [];
|
|
2235
|
+
const collapsedStyles = collapseExportShorthands(pendingStyles);
|
|
2236
|
+
for (const [property, value] of Object.entries(collapsedStyles)) {
|
|
2237
|
+
const tailwindClass = stylesToTailwind({ [property]: value });
|
|
2238
|
+
changes.push({ property, value, tailwind: tailwindClass });
|
|
2239
|
+
}
|
|
2240
|
+
const lines = buildLocatorContextLines(locator, options);
|
|
2241
|
+
lines.push("");
|
|
2242
|
+
if (changes.length > 0) {
|
|
2243
|
+
lines.push("edits:");
|
|
2244
|
+
for (const change of changes) {
|
|
2245
|
+
const tailwind = change.tailwind ? ` (${change.tailwind})` : "";
|
|
2246
|
+
lines.push(`${change.property}: ${change.value}${tailwind}`);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
if (textEdit) {
|
|
2250
|
+
lines.push("text content changed:");
|
|
2251
|
+
lines.push(`from: "${textEdit.originalText}"`);
|
|
2252
|
+
lines.push(`to: "${textEdit.newText}"`);
|
|
2253
|
+
}
|
|
2254
|
+
return lines.join("\n");
|
|
2255
|
+
}
|
|
2178
2256
|
function buildCommentExport(locator, commentText, replies) {
|
|
2179
2257
|
const lines = buildLocatorContextLines(locator);
|
|
2180
2258
|
lines.push("");
|
|
@@ -2186,55 +2264,554 @@ function buildCommentExport(locator, commentText, replies) {
|
|
|
2186
2264
|
}
|
|
2187
2265
|
return lines.join("\n");
|
|
2188
2266
|
}
|
|
2189
|
-
function
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2267
|
+
function normalizeSelector(selector) {
|
|
2268
|
+
const value = selector?.trim();
|
|
2269
|
+
return value && value.length > 0 ? value : null;
|
|
2270
|
+
}
|
|
2271
|
+
function normalizeName(name) {
|
|
2272
|
+
return name?.trim().toLowerCase() || "element";
|
|
2273
|
+
}
|
|
2274
|
+
function buildAnchorRef(name, selector, source) {
|
|
2275
|
+
return {
|
|
2276
|
+
name: name?.trim() || "element",
|
|
2277
|
+
selector: normalizeSelector(selector),
|
|
2278
|
+
source: source?.file ? source : null
|
|
2279
|
+
};
|
|
2194
2280
|
}
|
|
2195
|
-
function
|
|
2196
|
-
|
|
2197
|
-
const
|
|
2198
|
-
if (
|
|
2199
|
-
|
|
2281
|
+
function anchorKey(anchor) {
|
|
2282
|
+
if (!anchor) return "none";
|
|
2283
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2284
|
+
if (selector) return `selector:${selector}`;
|
|
2285
|
+
if (anchor.source?.file) {
|
|
2286
|
+
return `source:${anchor.source.file}:${anchor.source.line ?? 0}:${anchor.source.column ?? 0}`;
|
|
2287
|
+
}
|
|
2288
|
+
return `name:${normalizeName(anchor.name)}`;
|
|
2289
|
+
}
|
|
2290
|
+
function anchorsEqual(a, b) {
|
|
2291
|
+
if (!a && !b) return true;
|
|
2292
|
+
if (!a || !b) return false;
|
|
2293
|
+
const aSelector = normalizeSelector(a.selector);
|
|
2294
|
+
const bSelector = normalizeSelector(b.selector);
|
|
2295
|
+
if (aSelector && bSelector) return aSelector === bSelector;
|
|
2296
|
+
if (a.source?.file && b.source?.file) {
|
|
2297
|
+
return a.source.file === b.source.file && (a.source.line ?? null) === (b.source.line ?? null) && (a.source.column ?? null) === (b.source.column ?? null);
|
|
2298
|
+
}
|
|
2299
|
+
return normalizeName(a.name) === normalizeName(b.name);
|
|
2300
|
+
}
|
|
2301
|
+
function formatAnchorRef(anchor, fallback = "(none)") {
|
|
2302
|
+
if (!anchor) return fallback;
|
|
2303
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2304
|
+
if (selector) return selector;
|
|
2305
|
+
if (anchor.source?.file) return `<${anchor.name}> @ ${formatSourceLocation(anchor.source.file, anchor.source.line, anchor.source.column)}`;
|
|
2306
|
+
return `<${anchor.name}>`;
|
|
2307
|
+
}
|
|
2308
|
+
function buildPlacementRef(before, after) {
|
|
2309
|
+
if (before && after) {
|
|
2310
|
+
return {
|
|
2311
|
+
before,
|
|
2312
|
+
after,
|
|
2313
|
+
description: `between after ${formatAnchorRef(before)} and before ${formatAnchorRef(after)}`
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
if (before) {
|
|
2317
|
+
return {
|
|
2318
|
+
before,
|
|
2319
|
+
after: null,
|
|
2320
|
+
description: `after ${formatAnchorRef(before)}`
|
|
2321
|
+
};
|
|
2200
2322
|
}
|
|
2201
|
-
|
|
2323
|
+
if (after) {
|
|
2324
|
+
return {
|
|
2325
|
+
before: null,
|
|
2326
|
+
after,
|
|
2327
|
+
description: `before ${formatAnchorRef(after)}`
|
|
2328
|
+
};
|
|
2329
|
+
}
|
|
2330
|
+
return {
|
|
2331
|
+
before: null,
|
|
2332
|
+
after: null,
|
|
2333
|
+
description: "as the only child"
|
|
2334
|
+
};
|
|
2202
2335
|
}
|
|
2203
|
-
function
|
|
2204
|
-
const
|
|
2205
|
-
|
|
2336
|
+
function buildPlacementFromMove(beforeName, beforeSelector, beforeSource, afterName, afterSelector, afterSource) {
|
|
2337
|
+
const before = beforeName || beforeSelector || beforeSource?.file ? buildAnchorRef(beforeName, beforeSelector, beforeSource) : null;
|
|
2338
|
+
const after = afterName || afterSelector || afterSource?.file ? buildAnchorRef(afterName, afterSelector, afterSource) : null;
|
|
2339
|
+
return buildPlacementRef(before, after);
|
|
2340
|
+
}
|
|
2341
|
+
function toRoundedVisualDelta(move) {
|
|
2342
|
+
const delta = move.visualDelta ?? move.positionDelta;
|
|
2343
|
+
if (!delta) return void 0;
|
|
2344
|
+
const rounded = { x: Math.round(delta.x), y: Math.round(delta.y) };
|
|
2345
|
+
return rounded.x === 0 && rounded.y === 0 ? void 0 : rounded;
|
|
2346
|
+
}
|
|
2347
|
+
function hasVisualIntent(move) {
|
|
2348
|
+
return Boolean(toRoundedVisualDelta(move));
|
|
2349
|
+
}
|
|
2350
|
+
function hasStructuralChange(move) {
|
|
2351
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2352
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2353
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2354
|
+
move.fromSiblingBefore,
|
|
2355
|
+
move.fromSiblingBeforeSelector,
|
|
2356
|
+
move.fromSiblingBeforeSource,
|
|
2357
|
+
move.fromSiblingAfter,
|
|
2358
|
+
move.fromSiblingAfterSelector,
|
|
2359
|
+
move.fromSiblingAfterSource
|
|
2360
|
+
);
|
|
2361
|
+
const toPlacement = buildPlacementFromMove(
|
|
2362
|
+
move.toSiblingBefore,
|
|
2363
|
+
move.toSiblingBeforeSelector,
|
|
2364
|
+
move.toSiblingBeforeSource,
|
|
2365
|
+
move.toSiblingAfter,
|
|
2366
|
+
move.toSiblingAfterSelector,
|
|
2367
|
+
move.toSiblingAfterSource
|
|
2368
|
+
);
|
|
2369
|
+
if (!anchorsEqual(fromParent, toParent)) return true;
|
|
2370
|
+
if (!anchorsEqual(fromPlacement.before, toPlacement.before)) return true;
|
|
2371
|
+
if (!anchorsEqual(fromPlacement.after, toPlacement.after)) return true;
|
|
2372
|
+
if (typeof move.fromIndex === "number" && typeof move.toIndex === "number" && move.fromIndex !== move.toIndex) return true;
|
|
2373
|
+
return false;
|
|
2206
2374
|
}
|
|
2207
|
-
function
|
|
2208
|
-
|
|
2209
|
-
|
|
2375
|
+
function isStructuredLayoutContainer(layout) {
|
|
2376
|
+
return layout === "flex" || layout === "grid";
|
|
2377
|
+
}
|
|
2378
|
+
function isExistingFlexWorkflow(move) {
|
|
2379
|
+
const structuralChange = hasStructuralChange(move);
|
|
2380
|
+
if (!structuralChange) return false;
|
|
2381
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2382
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2383
|
+
const sameParent = anchorsEqual(fromParent, toParent);
|
|
2384
|
+
const fromLayout = move.fromParentLayout;
|
|
2385
|
+
const toLayout = move.toParentLayout;
|
|
2386
|
+
if (sameParent) {
|
|
2387
|
+
return Boolean(move.mode === "reorder" && (isStructuredLayoutContainer(toLayout) || isStructuredLayoutContainer(fromLayout)));
|
|
2388
|
+
}
|
|
2389
|
+
return Boolean(isStructuredLayoutContainer(fromLayout) && isStructuredLayoutContainer(toLayout));
|
|
2390
|
+
}
|
|
2391
|
+
function classifyMove(move) {
|
|
2392
|
+
const structuralChange = hasStructuralChange(move);
|
|
2393
|
+
const visualIntent = hasVisualIntent(move);
|
|
2394
|
+
if (!structuralChange && !visualIntent) return "noop";
|
|
2395
|
+
if (isExistingFlexWorkflow(move)) return "existing_layout_move";
|
|
2396
|
+
if (move.mode === "free" || move.mode === "position") return "layout_refactor";
|
|
2397
|
+
if (!structuralChange && visualIntent) return "layout_refactor";
|
|
2398
|
+
return "layout_refactor";
|
|
2399
|
+
}
|
|
2400
|
+
function buildNumericClusters(values, tolerance) {
|
|
2401
|
+
if (values.length === 0) return [];
|
|
2402
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2403
|
+
const clusters = [{ center: sorted[0], values: [sorted[0]] }];
|
|
2404
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
2405
|
+
const value = sorted[i];
|
|
2406
|
+
const current = clusters[clusters.length - 1];
|
|
2407
|
+
if (Math.abs(value - current.center) <= tolerance) {
|
|
2408
|
+
current.values.push(value);
|
|
2409
|
+
current.center = current.values.reduce((sum, n) => sum + n, 0) / current.values.length;
|
|
2410
|
+
} else {
|
|
2411
|
+
clusters.push({ center: value, values: [value] });
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
return clusters;
|
|
2210
2415
|
}
|
|
2211
|
-
function
|
|
2212
|
-
|
|
2416
|
+
function inferFlexDirection(sameRowCount, sameColumnCount, visualDelta) {
|
|
2417
|
+
if (sameRowCount > sameColumnCount) {
|
|
2418
|
+
return { direction: "row", reason: "Subject aligns with neighboring anchors on the same row." };
|
|
2419
|
+
}
|
|
2420
|
+
if (sameColumnCount > sameRowCount) {
|
|
2421
|
+
return { direction: "column", reason: "Subject aligns with neighboring anchors on the same column." };
|
|
2422
|
+
}
|
|
2423
|
+
if (sameRowCount > 0) {
|
|
2424
|
+
return { direction: "row", reason: "Detected row alignment in final geometry." };
|
|
2425
|
+
}
|
|
2426
|
+
if (sameColumnCount > 0) {
|
|
2427
|
+
return { direction: "column", reason: "Detected column alignment in final geometry." };
|
|
2428
|
+
}
|
|
2429
|
+
const horizontalDominant = Math.abs(visualDelta?.x ?? 0) >= Math.abs(visualDelta?.y ?? 0);
|
|
2430
|
+
return {
|
|
2431
|
+
direction: horizontalDominant ? "row" : "column",
|
|
2432
|
+
reason: "Fell back to movement axis because anchor alignment was ambiguous."
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
function inferLayoutPrescription(edit, operation, reasons) {
|
|
2436
|
+
const parent = edit.element.parentElement;
|
|
2437
|
+
if (!parent || !edit.element.isConnected) {
|
|
2438
|
+
return {
|
|
2439
|
+
recommendedSystem: "flex",
|
|
2440
|
+
intentPatterns: ["no_geometry_context"],
|
|
2441
|
+
refactorSteps: [
|
|
2442
|
+
`Reparent ${formatAnchorRef(operation.subject)} under ${formatAnchorRef(operation.to.parent)} at ${operation.to.placement.description}.`
|
|
2443
|
+
],
|
|
2444
|
+
styleSteps: [
|
|
2445
|
+
`Convert ${formatAnchorRef(operation.to.parent)} to flex and set a clear primary axis for this relationship.`,
|
|
2446
|
+
"Use `gap` for spacing and keep positioning static."
|
|
2447
|
+
],
|
|
2448
|
+
itemSteps: [
|
|
2449
|
+
"Remove any inline `left/top/transform` move artifacts from moved elements."
|
|
2450
|
+
]
|
|
2451
|
+
};
|
|
2452
|
+
}
|
|
2453
|
+
const children = Array.from(parent.children).filter(
|
|
2454
|
+
(node) => node instanceof HTMLElement && isInFlowChild(node) && !node.hasAttribute("data-direct-edit")
|
|
2455
|
+
);
|
|
2456
|
+
const childSnapshots = children.map((child) => {
|
|
2457
|
+
const rect = child.getBoundingClientRect();
|
|
2458
|
+
const locator = getElementLocator(child);
|
|
2459
|
+
const anchor = buildAnchorRef(getElementDisplayName(child), locator.domSelector, locator.domSource);
|
|
2460
|
+
return {
|
|
2461
|
+
child,
|
|
2462
|
+
rect,
|
|
2463
|
+
centerX: rect.left + rect.width / 2,
|
|
2464
|
+
centerY: rect.top + rect.height / 2,
|
|
2465
|
+
anchor,
|
|
2466
|
+
anchorLabel: formatAnchorRef(anchor)
|
|
2467
|
+
};
|
|
2468
|
+
});
|
|
2469
|
+
const subjectSnapshot = childSnapshots.find((snapshot) => snapshot.child === edit.element);
|
|
2470
|
+
const subjectRect = edit.element.getBoundingClientRect();
|
|
2471
|
+
const subjectCenterX = subjectRect.left + subjectRect.width / 2;
|
|
2472
|
+
const subjectCenterY = subjectRect.top + subjectRect.height / 2;
|
|
2473
|
+
const rowTolerance = Math.max(8, subjectRect.height * 0.35);
|
|
2474
|
+
const colTolerance = Math.max(8, subjectRect.width * 0.35);
|
|
2475
|
+
const sameRowWith = [];
|
|
2476
|
+
const sameColumnWith = [];
|
|
2477
|
+
const sameRowNodes = [];
|
|
2478
|
+
let aboveAnchor = null;
|
|
2479
|
+
let belowAnchor = null;
|
|
2480
|
+
let bestAboveDistance = Number.POSITIVE_INFINITY;
|
|
2481
|
+
let bestBelowDistance = Number.POSITIVE_INFINITY;
|
|
2482
|
+
for (const node of childSnapshots) {
|
|
2483
|
+
if (node.child === edit.element) continue;
|
|
2484
|
+
if (Math.abs(node.centerY - subjectCenterY) <= rowTolerance) {
|
|
2485
|
+
sameRowWith.push(node.anchorLabel);
|
|
2486
|
+
sameRowNodes.push(node);
|
|
2487
|
+
}
|
|
2488
|
+
if (Math.abs(node.centerX - subjectCenterX) <= colTolerance) {
|
|
2489
|
+
sameColumnWith.push(node.anchorLabel);
|
|
2490
|
+
}
|
|
2491
|
+
const yDelta = node.centerY - subjectCenterY;
|
|
2492
|
+
if (yDelta < 0 && Math.abs(yDelta) < bestAboveDistance) {
|
|
2493
|
+
bestAboveDistance = Math.abs(yDelta);
|
|
2494
|
+
aboveAnchor = node.anchorLabel;
|
|
2495
|
+
}
|
|
2496
|
+
if (yDelta > 0 && yDelta < bestBelowDistance) {
|
|
2497
|
+
bestBelowDistance = yDelta;
|
|
2498
|
+
belowAnchor = node.anchorLabel;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
const rowCenters = childSnapshots.map(({ centerY }) => centerY);
|
|
2502
|
+
const colCenters = childSnapshots.map(({ centerX }) => centerX);
|
|
2503
|
+
const rowClusters = buildNumericClusters(rowCenters, rowTolerance);
|
|
2504
|
+
const colClusters = buildNumericClusters(colCenters, colTolerance);
|
|
2505
|
+
const denseRowClusters = rowClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2506
|
+
const denseColClusters = colClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2507
|
+
const isTwoDimensional = childSnapshots.length >= 4 && denseRowClusters >= 2 && denseColClusters >= 2;
|
|
2508
|
+
const recommendedSystem = isTwoDimensional ? "grid" : "flex";
|
|
2509
|
+
const intentPatterns = [];
|
|
2510
|
+
if (sameRowWith.length > 0) intentPatterns.push(`same_row_with:${sameRowWith.slice(0, 3).join(", ")}`);
|
|
2511
|
+
if (sameColumnWith.length > 0) intentPatterns.push(`same_column_with:${sameColumnWith.slice(0, 3).join(", ")}`);
|
|
2512
|
+
if (aboveAnchor) intentPatterns.push(`below:${aboveAnchor}`);
|
|
2513
|
+
if (belowAnchor) intentPatterns.push(`above:${belowAnchor}`);
|
|
2514
|
+
if (sameRowWith.length === 0 && sameColumnWith.length === 0) intentPatterns.push("separate_cluster");
|
|
2515
|
+
const visualDelta = operation.visualDelta;
|
|
2516
|
+
const flexDirectionInfo = inferFlexDirection(sameRowWith.length, sameColumnWith.length, visualDelta);
|
|
2517
|
+
const flexDirection = flexDirectionInfo.direction;
|
|
2518
|
+
if (recommendedSystem === "grid") {
|
|
2519
|
+
reasons.push("Detected multiple dense row and column clusters; a 2D layout system is likely intentional.");
|
|
2520
|
+
return {
|
|
2521
|
+
recommendedSystem: "grid",
|
|
2522
|
+
intentPatterns,
|
|
2523
|
+
refactorSteps: [
|
|
2524
|
+
`Create/ensure a shared container around ${formatAnchorRef(operation.subject)} and related anchors under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2525
|
+
`Reorder/reparent elements to satisfy placement ${operation.to.placement.description}.`
|
|
2526
|
+
],
|
|
2527
|
+
styleSteps: [
|
|
2528
|
+
`Set ${formatAnchorRef(operation.to.parent)} to grid with explicit template rows/columns for the final layout.`,
|
|
2529
|
+
"Use `gap` for consistent spacing and keep placement structural."
|
|
2530
|
+
],
|
|
2531
|
+
itemSteps: [
|
|
2532
|
+
`Set item alignment on ${formatAnchorRef(operation.subject)} with grid self-alignment (\`justify-self\`/\`align-self\`).`
|
|
2533
|
+
]
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
reasons.push(`${flexDirectionInfo.reason} Use a 1D flex layout instead of literal drag replay.`);
|
|
2537
|
+
let hasStackedCluster = false;
|
|
2538
|
+
const stackedAnchorLabels = /* @__PURE__ */ new Set();
|
|
2539
|
+
if (flexDirection === "row" && subjectSnapshot) {
|
|
2540
|
+
for (const rowPeer of sameRowNodes) {
|
|
2541
|
+
for (const node of childSnapshots) {
|
|
2542
|
+
if (node.child === edit.element || node.child === rowPeer.child) continue;
|
|
2543
|
+
const sameColumnAsPeer = Math.abs(node.centerX - rowPeer.centerX) <= colTolerance;
|
|
2544
|
+
const verticallySeparated = Math.abs(node.centerY - rowPeer.centerY) > rowTolerance;
|
|
2545
|
+
if (sameColumnAsPeer && verticallySeparated) {
|
|
2546
|
+
hasStackedCluster = true;
|
|
2547
|
+
stackedAnchorLabels.add(rowPeer.anchorLabel);
|
|
2548
|
+
stackedAnchorLabels.add(node.anchorLabel);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
const hasBelowCluster = childSnapshots.some((node) => node.child !== edit.element && node.centerY - subjectCenterY > rowTolerance * 1.5 && Math.abs(node.centerY - subjectCenterY) > Math.abs(node.centerX - subjectCenterX));
|
|
2554
|
+
const refactorSteps = [
|
|
2555
|
+
`Ensure ${formatAnchorRef(operation.subject)} and referenced neighbors share a common container under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2556
|
+
`Reparent/reorder nodes so ${formatAnchorRef(operation.subject)} lands ${operation.to.placement.description}.`
|
|
2557
|
+
];
|
|
2558
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2559
|
+
const clusterSample = Array.from(stackedAnchorLabels).slice(0, 3).join(", ");
|
|
2560
|
+
refactorSteps.push(`Create a left-side content wrapper for vertically stacked items (${clusterSample}), and keep ${formatAnchorRef(operation.subject)} as the opposite-side sibling.`);
|
|
2561
|
+
}
|
|
2562
|
+
if (hasBelowCluster) {
|
|
2563
|
+
refactorSteps.push("Keep lower content sections in a separate block below the horizontal header row; do not force them into the same row.");
|
|
2564
|
+
}
|
|
2565
|
+
const styleSteps = [
|
|
2566
|
+
`Set ${formatAnchorRef(operation.to.parent)} to flex with direction ${flexDirection}.`,
|
|
2567
|
+
flexDirection === "row" ? "Use `justify-content: space-between` and `align-items: flex-start` when the moved element should sit on the opposite edge." : "Use `justify-content` / `align-items` to establish top-bottom alignment.",
|
|
2568
|
+
"Use `gap` for spacing between siblings."
|
|
2569
|
+
];
|
|
2570
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2571
|
+
styleSteps.push("Set the content wrapper to `display: flex` with `flex-direction: column` and an appropriate vertical gap.");
|
|
2572
|
+
}
|
|
2573
|
+
return {
|
|
2574
|
+
recommendedSystem: "flex",
|
|
2575
|
+
intentPatterns,
|
|
2576
|
+
refactorSteps,
|
|
2577
|
+
styleSteps,
|
|
2578
|
+
itemSteps: [
|
|
2579
|
+
`Apply item-level alignment (\`align-self\` / flex-basis) only when needed for ${formatAnchorRef(operation.subject)}.`,
|
|
2580
|
+
"Do not use absolute positioning, top/left offsets, transforms, or margin hacks to simulate movement."
|
|
2581
|
+
]
|
|
2582
|
+
};
|
|
2583
|
+
}
|
|
2584
|
+
function buildMoveEntries(edits) {
|
|
2585
|
+
const entries = [];
|
|
2586
|
+
let noopMoveCount = 0;
|
|
2587
|
+
for (const edit of edits) {
|
|
2588
|
+
const move = edit.move;
|
|
2589
|
+
if (!move) continue;
|
|
2590
|
+
const subject = buildAnchorRef(
|
|
2591
|
+
getElementDisplayName(edit.element) || edit.locator.tagName,
|
|
2592
|
+
edit.locator.domSelector,
|
|
2593
|
+
edit.locator.domSource
|
|
2594
|
+
);
|
|
2595
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2596
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2597
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2598
|
+
move.fromSiblingBefore,
|
|
2599
|
+
move.fromSiblingBeforeSelector,
|
|
2600
|
+
move.fromSiblingBeforeSource,
|
|
2601
|
+
move.fromSiblingAfter,
|
|
2602
|
+
move.fromSiblingAfterSelector,
|
|
2603
|
+
move.fromSiblingAfterSource
|
|
2604
|
+
);
|
|
2605
|
+
const toPlacement = buildPlacementFromMove(
|
|
2606
|
+
move.toSiblingBefore,
|
|
2607
|
+
move.toSiblingBeforeSelector,
|
|
2608
|
+
move.toSiblingBeforeSource,
|
|
2609
|
+
move.toSiblingAfter,
|
|
2610
|
+
move.toSiblingAfterSelector,
|
|
2611
|
+
move.toSiblingAfterSource
|
|
2612
|
+
);
|
|
2613
|
+
const reasons = [];
|
|
2614
|
+
const classification = classifyMove(move);
|
|
2615
|
+
if (classification === "noop") {
|
|
2616
|
+
noopMoveCount++;
|
|
2617
|
+
continue;
|
|
2618
|
+
}
|
|
2619
|
+
const interactionMode = move.mode ?? "free";
|
|
2620
|
+
const visualDelta = toRoundedVisualDelta(move);
|
|
2621
|
+
if (visualDelta) {
|
|
2622
|
+
reasons.push(`Non-zero visual delta detected (${visualDelta.x}px, ${visualDelta.y}px).`);
|
|
2623
|
+
}
|
|
2624
|
+
const structuralChange = hasStructuralChange(move);
|
|
2625
|
+
if (structuralChange) reasons.push("Anchor placement changed between source and target.");
|
|
2626
|
+
else reasons.push("No anchor placement change; treating movement as layout intent translation.");
|
|
2627
|
+
const operationBase = {
|
|
2628
|
+
classification,
|
|
2629
|
+
interactionMode,
|
|
2630
|
+
subject,
|
|
2631
|
+
from: { parent: fromParent, placement: fromPlacement },
|
|
2632
|
+
to: { parent: toParent, placement: toPlacement },
|
|
2633
|
+
...visualDelta ? { visualDelta } : {},
|
|
2634
|
+
confidence: classification === "existing_layout_move" ? "high" : structuralChange ? "medium" : "high",
|
|
2635
|
+
reasons
|
|
2636
|
+
};
|
|
2637
|
+
if (classification === "layout_refactor") {
|
|
2638
|
+
operationBase.layoutPrescription = inferLayoutPrescription(edit, operationBase, reasons);
|
|
2639
|
+
}
|
|
2640
|
+
const sortSource = subject.source?.file ? `${subject.source.file}:${subject.source.line ?? 0}:${subject.source.column ?? 0}` : "";
|
|
2641
|
+
const sortKey = [
|
|
2642
|
+
sortSource,
|
|
2643
|
+
anchorKey(subject),
|
|
2644
|
+
anchorKey(toParent),
|
|
2645
|
+
toPlacement.description
|
|
2646
|
+
].join("|");
|
|
2647
|
+
entries.push({ edit, operation: operationBase, sortKey });
|
|
2648
|
+
}
|
|
2649
|
+
entries.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
2650
|
+
return { entries, noopMoveCount };
|
|
2651
|
+
}
|
|
2652
|
+
function buildMovePlanContext(edits, _domContext) {
|
|
2653
|
+
const { entries, noopMoveCount } = buildMoveEntries(edits);
|
|
2654
|
+
if (entries.length === 0) {
|
|
2655
|
+
return {
|
|
2656
|
+
movePlan: null,
|
|
2657
|
+
intentsByEdit: /* @__PURE__ */ new Map(),
|
|
2658
|
+
noopMoveCount
|
|
2659
|
+
};
|
|
2660
|
+
}
|
|
2661
|
+
const operations = [];
|
|
2662
|
+
const intentsByEdit = /* @__PURE__ */ new Map();
|
|
2663
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2664
|
+
const operationId = `op-${i + 1}`;
|
|
2665
|
+
const operation = { operationId, ...entries[i].operation };
|
|
2666
|
+
operations.push(operation);
|
|
2667
|
+
intentsByEdit.set(entries[i].edit, operation);
|
|
2668
|
+
}
|
|
2669
|
+
const affectedContainerMap = /* @__PURE__ */ new Map();
|
|
2670
|
+
for (const operation of operations) {
|
|
2671
|
+
affectedContainerMap.set(anchorKey(operation.from.parent), operation.from.parent);
|
|
2672
|
+
affectedContainerMap.set(anchorKey(operation.to.parent), operation.to.parent);
|
|
2673
|
+
}
|
|
2674
|
+
const orderingConstraints = operations.filter((op) => op.classification === "existing_layout_move").map((op) => `${op.operationId}: place ${formatAnchorRef(op.subject)} ${op.to.placement.description} in ${formatAnchorRef(op.to.parent)}.`);
|
|
2675
|
+
const notes = [];
|
|
2676
|
+
if (noopMoveCount > 0) notes.push(`Excluded ${noopMoveCount} no-op move(s).`);
|
|
2677
|
+
if (operations.some((op) => op.classification === "layout_refactor")) {
|
|
2678
|
+
notes.push("Layout refactor operations include best-practice flex/grid prescriptions.");
|
|
2679
|
+
}
|
|
2680
|
+
return {
|
|
2681
|
+
movePlan: {
|
|
2682
|
+
operations,
|
|
2683
|
+
affectedContainers: Array.from(affectedContainerMap.values()),
|
|
2684
|
+
orderingConstraints,
|
|
2685
|
+
notes
|
|
2686
|
+
},
|
|
2687
|
+
intentsByEdit,
|
|
2688
|
+
noopMoveCount
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2691
|
+
function buildMovePlan(edits, domContext) {
|
|
2692
|
+
const context = buildMovePlanContext(edits, domContext);
|
|
2693
|
+
return context.movePlan ?? {
|
|
2694
|
+
operations: [],
|
|
2695
|
+
affectedContainers: [],
|
|
2696
|
+
orderingConstraints: [],
|
|
2697
|
+
notes: context.noopMoveCount > 0 ? [`Excluded ${context.noopMoveCount} no-op move(s).`] : []
|
|
2698
|
+
};
|
|
2699
|
+
}
|
|
2700
|
+
function getMoveIntentForEdit(edit, context) {
|
|
2701
|
+
if (!edit.move) return null;
|
|
2702
|
+
if (context?.intentsByEdit.has(edit)) return context.intentsByEdit.get(edit) ?? null;
|
|
2703
|
+
const singleContext = buildMovePlanContext([edit]);
|
|
2704
|
+
return singleContext.intentsByEdit.get(edit) ?? null;
|
|
2705
|
+
}
|
|
2706
|
+
function buildMoveInstructionFromIntent(intent) {
|
|
2707
|
+
if (intent.classification === "existing_layout_move") {
|
|
2708
|
+
return `Apply as a structural move in code: place ${formatAnchorRef(intent.subject)} ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`;
|
|
2709
|
+
}
|
|
2710
|
+
const system = intent.layoutPrescription?.recommendedSystem ?? "flex";
|
|
2711
|
+
return `Treat this as a ${system} layout refactor. Implement the listed structure/style steps in source code instead of drag replay.`;
|
|
2712
|
+
}
|
|
2713
|
+
function formatMoveType(classification) {
|
|
2714
|
+
return classification === "existing_layout_move" ? "structural_move" : "layout_refactor";
|
|
2715
|
+
}
|
|
2716
|
+
function buildMoveExportLines(intent) {
|
|
2717
|
+
const moveType = formatMoveType(intent.classification);
|
|
2718
|
+
const implementationSteps = [];
|
|
2719
|
+
if (intent.classification === "existing_layout_move") {
|
|
2720
|
+
implementationSteps.push(`Reorder/reparent ${formatAnchorRef(intent.subject)} to ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`);
|
|
2721
|
+
} else {
|
|
2722
|
+
const prescription = intent.layoutPrescription;
|
|
2723
|
+
if (prescription) {
|
|
2724
|
+
implementationSteps.push(...prescription.refactorSteps);
|
|
2725
|
+
implementationSteps.push(...prescription.styleSteps);
|
|
2726
|
+
implementationSteps.push(...prescription.itemSteps);
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
const lines = [
|
|
2213
2730
|
"moved:",
|
|
2214
|
-
`
|
|
2215
|
-
`
|
|
2216
|
-
`
|
|
2217
|
-
`
|
|
2218
|
-
`
|
|
2219
|
-
`
|
|
2220
|
-
`
|
|
2221
|
-
`to_parent_selector: ${formatMoveSelector(move.toParentSelector, "(unknown)")}`,
|
|
2222
|
-
`to_before_selector: ${formatMoveSelector(move.toSiblingBeforeSelector, "(none)")}`,
|
|
2223
|
-
`to_after_selector: ${formatMoveSelector(move.toSiblingAfterSelector, "(none)")}`,
|
|
2224
|
-
`to_parent_source: ${formatMoveSource(move.toParentSource, "(unknown)")}`,
|
|
2225
|
-
`to_before_source: ${formatMoveSource(move.toSiblingBeforeSource, "(none)")}`,
|
|
2226
|
-
`to_after_source: ${formatMoveSource(move.toSiblingAfterSource, "(none)")}`
|
|
2731
|
+
`id: ${intent.operationId}`,
|
|
2732
|
+
`type: ${moveType}`,
|
|
2733
|
+
`subject: ${formatAnchorRef(intent.subject, "(unknown)")}`,
|
|
2734
|
+
`parent: ${formatAnchorRef(intent.to.parent, "(unknown)")}`,
|
|
2735
|
+
`current_anchor: ${intent.from.placement.description}`,
|
|
2736
|
+
`target_anchor: ${intent.to.placement.description}`,
|
|
2737
|
+
...intent.visualDelta ? [`visual_hint: ${intent.visualDelta.x}px horizontal, ${intent.visualDelta.y}px vertical`] : []
|
|
2227
2738
|
];
|
|
2739
|
+
if (intent.layoutPrescription) {
|
|
2740
|
+
lines.push(`recommended_layout: ${intent.layoutPrescription.recommendedSystem}`);
|
|
2741
|
+
}
|
|
2742
|
+
lines.push("implementation_steps:");
|
|
2743
|
+
for (const step of implementationSteps) {
|
|
2744
|
+
lines.push(` - ${step}`);
|
|
2745
|
+
}
|
|
2746
|
+
lines.push("guardrails:");
|
|
2747
|
+
lines.push(" - Do not simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
|
|
2748
|
+
lines.push(`instruction: ${buildMoveInstructionFromIntent(intent)}`);
|
|
2749
|
+
return lines;
|
|
2750
|
+
}
|
|
2751
|
+
function getExportContentProfile(edits, comments, movePlanOrContext) {
|
|
2752
|
+
const moveOpCount = movePlanOrContext ? "operations" in movePlanOrContext ? movePlanOrContext.operations.length : movePlanOrContext.movePlan?.operations.length ?? 0 : buildMovePlanContext(edits).movePlan?.operations.length ?? 0;
|
|
2753
|
+
return {
|
|
2754
|
+
hasCssEdits: edits.some((e) => Object.keys(e.pendingStyles).length > 0),
|
|
2755
|
+
hasTextEdits: edits.some((e) => e.textEdit != null),
|
|
2756
|
+
hasMoves: moveOpCount > 0,
|
|
2757
|
+
hasComments: comments.length > 0
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
function buildExportInstruction(profile) {
|
|
2761
|
+
const { hasCssEdits, hasTextEdits, hasMoves, hasComments } = profile;
|
|
2762
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves && !hasComments) return "";
|
|
2763
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves) {
|
|
2764
|
+
return hasComments ? "Address this feedback on the UI. Use the provided source location and selector to find each element in the codebase." : "";
|
|
2765
|
+
}
|
|
2766
|
+
const parts = [];
|
|
2767
|
+
if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.).");
|
|
2768
|
+
if (hasTextEdits) parts.push("Update the text content as specified.");
|
|
2769
|
+
if (hasMoves) parts.push("Implement the move plan below directly in source code. For `structural_move`, reorder/reparent elements using the target anchors. For `layout_refactor`, apply the listed flex/grid refactor steps. Do NOT simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
|
|
2770
|
+
if (hasComments) parts.push("Address the comments on the relevant elements.");
|
|
2771
|
+
return `${parts.join(" ")} Use the provided source locations, selectors, and context HTML to locate each element in the codebase.`;
|
|
2228
2772
|
}
|
|
2229
|
-
function buildSessionExport(edits, comments = []) {
|
|
2773
|
+
function buildSessionExport(edits, comments = [], options) {
|
|
2230
2774
|
const blocks = [];
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2775
|
+
const planContext = options?.movePlanContext ?? buildMovePlanContext(edits);
|
|
2776
|
+
const movePlan = planContext.movePlan;
|
|
2777
|
+
const includeMovePlanHeader = options?.includeMovePlanHeader !== false;
|
|
2778
|
+
if (includeMovePlanHeader && movePlan && movePlan.operations.length > 0) {
|
|
2779
|
+
const planLines = [
|
|
2780
|
+
"=== LAYOUT MOVE PLAN ===",
|
|
2781
|
+
`operations: ${movePlan.operations.length}`
|
|
2782
|
+
];
|
|
2783
|
+
if (movePlan.affectedContainers.length > 0) {
|
|
2784
|
+
planLines.push("containers:");
|
|
2785
|
+
for (const container of movePlan.affectedContainers) {
|
|
2786
|
+
planLines.push(` - ${formatAnchorRef(container, "(unknown)")}`);
|
|
2787
|
+
}
|
|
2788
|
+
}
|
|
2789
|
+
if (movePlan.orderingConstraints.length > 0) {
|
|
2790
|
+
planLines.push("structural_constraints:");
|
|
2791
|
+
for (const constraint of movePlan.orderingConstraints) {
|
|
2792
|
+
planLines.push(` - ${constraint}`);
|
|
2793
|
+
}
|
|
2236
2794
|
}
|
|
2237
|
-
|
|
2795
|
+
if (movePlan.notes.length > 0) {
|
|
2796
|
+
planLines.push("plan_notes:");
|
|
2797
|
+
for (const note of movePlan.notes) {
|
|
2798
|
+
planLines.push(` - ${note}`);
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
blocks.push(planLines.join("\n"));
|
|
2802
|
+
}
|
|
2803
|
+
for (const edit of edits) {
|
|
2804
|
+
const moveIntent = getMoveIntentForEdit(edit, planContext);
|
|
2805
|
+
const hasMove = Boolean(moveIntent);
|
|
2806
|
+
const hasStyleOrText = Object.keys(edit.pendingStyles).length > 0 || edit.textEdit != null;
|
|
2807
|
+
if (!hasMove && !hasStyleOrText) continue;
|
|
2808
|
+
const block = hasMove ? buildEditExportWithOptions(edit.locator, edit.pendingStyles, edit.textEdit, { skipContext: true }) : buildEditExport(edit.locator, edit.pendingStyles, edit.textEdit);
|
|
2809
|
+
let moveBlock = "";
|
|
2810
|
+
if (moveIntent) {
|
|
2811
|
+
moveBlock = `
|
|
2812
|
+
${buildMoveExportLines(moveIntent).join("\n")}`;
|
|
2813
|
+
}
|
|
2814
|
+
blocks.push(block + moveBlock);
|
|
2238
2815
|
}
|
|
2239
2816
|
for (const comment of comments) {
|
|
2240
2817
|
blocks.push(buildCommentExport(comment.locator, comment.text, comment.replies));
|
|
@@ -2248,6 +2825,9 @@ export {
|
|
|
2248
2825
|
buildCommentExport,
|
|
2249
2826
|
buildEditExport,
|
|
2250
2827
|
buildElementContext,
|
|
2828
|
+
buildExportInstruction,
|
|
2829
|
+
buildMovePlan,
|
|
2830
|
+
buildMovePlanContext,
|
|
2251
2831
|
buildSessionExport,
|
|
2252
2832
|
calculateDropPosition,
|
|
2253
2833
|
calculateElementMeasurements,
|
|
@@ -2259,17 +2839,20 @@ export {
|
|
|
2259
2839
|
colorPropertyToCSSMap,
|
|
2260
2840
|
colorToTailwind,
|
|
2261
2841
|
computeHoverHighlight,
|
|
2842
|
+
computeIntendedIndex,
|
|
2262
2843
|
detectChildrenDirection,
|
|
2263
2844
|
detectSizingMode,
|
|
2264
2845
|
elementFromPointWithoutOverlays,
|
|
2265
2846
|
ensureDirectTextSpanAtPoint,
|
|
2266
2847
|
findChildAtPoint,
|
|
2267
2848
|
findContainerAtPoint,
|
|
2849
|
+
findLayoutContainerAtPoint,
|
|
2268
2850
|
findTextOwnerAtPoint,
|
|
2269
2851
|
findTextOwnerByRangeScan,
|
|
2270
2852
|
flexPropertyToCSSMap,
|
|
2271
2853
|
formatPropertyValue,
|
|
2272
2854
|
getAllComputedStyles,
|
|
2855
|
+
getChildBriefInfo,
|
|
2273
2856
|
getComputedBorderStyles,
|
|
2274
2857
|
getComputedBoxShadow,
|
|
2275
2858
|
getComputedColorStyles,
|
|
@@ -2280,11 +2863,16 @@ export {
|
|
|
2280
2863
|
getElementDisplayName,
|
|
2281
2864
|
getElementInfo,
|
|
2282
2865
|
getElementLocator,
|
|
2866
|
+
getElementSource,
|
|
2867
|
+
getExportContentProfile,
|
|
2283
2868
|
getFlexDirection,
|
|
2869
|
+
getMoveIntentForEdit,
|
|
2284
2870
|
getOriginalInlineStyles,
|
|
2285
2871
|
getSizingValue,
|
|
2286
2872
|
isFlexContainer,
|
|
2873
|
+
isInFlowChild,
|
|
2287
2874
|
isInputFocused,
|
|
2875
|
+
isLayoutContainer,
|
|
2288
2876
|
isTextElement,
|
|
2289
2877
|
parseColorValue,
|
|
2290
2878
|
parsePropertyValue,
|