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.js
CHANGED
|
@@ -26,6 +26,9 @@ __export(utils_exports, {
|
|
|
26
26
|
buildCommentExport: () => buildCommentExport,
|
|
27
27
|
buildEditExport: () => buildEditExport,
|
|
28
28
|
buildElementContext: () => buildElementContext,
|
|
29
|
+
buildExportInstruction: () => buildExportInstruction,
|
|
30
|
+
buildMovePlan: () => buildMovePlan,
|
|
31
|
+
buildMovePlanContext: () => buildMovePlanContext,
|
|
29
32
|
buildSessionExport: () => buildSessionExport,
|
|
30
33
|
calculateDropPosition: () => calculateDropPosition,
|
|
31
34
|
calculateElementMeasurements: () => calculateElementMeasurements,
|
|
@@ -37,17 +40,20 @@ __export(utils_exports, {
|
|
|
37
40
|
colorPropertyToCSSMap: () => colorPropertyToCSSMap,
|
|
38
41
|
colorToTailwind: () => colorToTailwind,
|
|
39
42
|
computeHoverHighlight: () => computeHoverHighlight,
|
|
43
|
+
computeIntendedIndex: () => computeIntendedIndex,
|
|
40
44
|
detectChildrenDirection: () => detectChildrenDirection,
|
|
41
45
|
detectSizingMode: () => detectSizingMode,
|
|
42
46
|
elementFromPointWithoutOverlays: () => elementFromPointWithoutOverlays,
|
|
43
47
|
ensureDirectTextSpanAtPoint: () => ensureDirectTextSpanAtPoint,
|
|
44
48
|
findChildAtPoint: () => findChildAtPoint,
|
|
45
49
|
findContainerAtPoint: () => findContainerAtPoint,
|
|
50
|
+
findLayoutContainerAtPoint: () => findLayoutContainerAtPoint,
|
|
46
51
|
findTextOwnerAtPoint: () => findTextOwnerAtPoint,
|
|
47
52
|
findTextOwnerByRangeScan: () => findTextOwnerByRangeScan,
|
|
48
53
|
flexPropertyToCSSMap: () => flexPropertyToCSSMap,
|
|
49
54
|
formatPropertyValue: () => formatPropertyValue,
|
|
50
55
|
getAllComputedStyles: () => getAllComputedStyles,
|
|
56
|
+
getChildBriefInfo: () => getChildBriefInfo,
|
|
51
57
|
getComputedBorderStyles: () => getComputedBorderStyles,
|
|
52
58
|
getComputedBoxShadow: () => getComputedBoxShadow,
|
|
53
59
|
getComputedColorStyles: () => getComputedColorStyles,
|
|
@@ -58,11 +64,16 @@ __export(utils_exports, {
|
|
|
58
64
|
getElementDisplayName: () => getElementDisplayName,
|
|
59
65
|
getElementInfo: () => getElementInfo,
|
|
60
66
|
getElementLocator: () => getElementLocator,
|
|
67
|
+
getElementSource: () => getElementSource,
|
|
68
|
+
getExportContentProfile: () => getExportContentProfile,
|
|
61
69
|
getFlexDirection: () => getFlexDirection,
|
|
70
|
+
getMoveIntentForEdit: () => getMoveIntentForEdit,
|
|
62
71
|
getOriginalInlineStyles: () => getOriginalInlineStyles,
|
|
63
72
|
getSizingValue: () => getSizingValue,
|
|
64
73
|
isFlexContainer: () => isFlexContainer,
|
|
74
|
+
isInFlowChild: () => isInFlowChild,
|
|
65
75
|
isInputFocused: () => isInputFocused,
|
|
76
|
+
isLayoutContainer: () => isLayoutContainer,
|
|
66
77
|
isTextElement: () => isTextElement,
|
|
67
78
|
parseColorValue: () => parseColorValue,
|
|
68
79
|
parsePropertyValue: () => parsePropertyValue,
|
|
@@ -1182,6 +1193,10 @@ function getFlexDirection(element) {
|
|
|
1182
1193
|
const computed = window.getComputedStyle(element);
|
|
1183
1194
|
return computed.flexDirection;
|
|
1184
1195
|
}
|
|
1196
|
+
function isInFlowChild(el) {
|
|
1197
|
+
const cs = window.getComputedStyle(el);
|
|
1198
|
+
return cs.display !== "none" && cs.position !== "absolute" && cs.position !== "fixed";
|
|
1199
|
+
}
|
|
1185
1200
|
function detectChildrenDirection(container, exclude) {
|
|
1186
1201
|
const computed = window.getComputedStyle(container);
|
|
1187
1202
|
if (computed.display === "flex" || computed.display === "inline-flex") {
|
|
@@ -1194,8 +1209,7 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1194
1209
|
const visible = [];
|
|
1195
1210
|
for (const c of container.children) {
|
|
1196
1211
|
if (!(c instanceof HTMLElement) || c === exclude) continue;
|
|
1197
|
-
|
|
1198
|
-
if (cs.display === "none" || cs.position === "absolute" || cs.position === "fixed") continue;
|
|
1212
|
+
if (!isInFlowChild(c)) continue;
|
|
1199
1213
|
visible.push(c);
|
|
1200
1214
|
if (visible.length === 2) break;
|
|
1201
1215
|
}
|
|
@@ -1208,6 +1222,29 @@ function detectChildrenDirection(container, exclude) {
|
|
|
1208
1222
|
}
|
|
1209
1223
|
return { axis: "vertical", reversed: second.bottom < first.top };
|
|
1210
1224
|
}
|
|
1225
|
+
function computeIntendedIndex(parent, draggedElement) {
|
|
1226
|
+
const { axis } = detectChildrenDirection(parent, draggedElement);
|
|
1227
|
+
const isHorizontal = axis === "horizontal";
|
|
1228
|
+
const draggedRect = draggedElement.getBoundingClientRect();
|
|
1229
|
+
const intendedCenter = isHorizontal ? draggedRect.left + draggedRect.width / 2 : draggedRect.top + draggedRect.height / 2;
|
|
1230
|
+
const siblings = [];
|
|
1231
|
+
for (const c of parent.children) {
|
|
1232
|
+
if (!(c instanceof HTMLElement) || c === draggedElement) continue;
|
|
1233
|
+
if (!isInFlowChild(c)) continue;
|
|
1234
|
+
siblings.push(c);
|
|
1235
|
+
}
|
|
1236
|
+
if (siblings.length === 0) {
|
|
1237
|
+
return { index: 0, siblingBefore: null, siblingAfter: null };
|
|
1238
|
+
}
|
|
1239
|
+
for (let i = 0; i < siblings.length; i++) {
|
|
1240
|
+
const rect = siblings[i].getBoundingClientRect();
|
|
1241
|
+
const midpoint = isHorizontal ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
|
|
1242
|
+
if (intendedCenter < midpoint) {
|
|
1243
|
+
return { index: i, siblingBefore: i > 0 ? siblings[i - 1] : null, siblingAfter: siblings[i] };
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
return { index: siblings.length, siblingBefore: siblings[siblings.length - 1], siblingAfter: null };
|
|
1247
|
+
}
|
|
1211
1248
|
function htmlChildren(el) {
|
|
1212
1249
|
return Array.from(el.children).filter(
|
|
1213
1250
|
(child) => child instanceof HTMLElement
|
|
@@ -1366,6 +1403,22 @@ function findContainerAtPoint(x, y, exclude, preferredParent) {
|
|
|
1366
1403
|
}
|
|
1367
1404
|
return findContainerViaTraversal(x, y, exclude);
|
|
1368
1405
|
}
|
|
1406
|
+
function findLayoutContainerAtPoint(x, y, exclude, preferredParent) {
|
|
1407
|
+
const host = document.querySelector("[data-direct-edit-host]");
|
|
1408
|
+
if (host) host.style.display = "none";
|
|
1409
|
+
const elements = document.elementsFromPoint(x, y);
|
|
1410
|
+
if (host) host.style.display = "";
|
|
1411
|
+
for (const el of elements) {
|
|
1412
|
+
if (skipElement(el, exclude)) continue;
|
|
1413
|
+
if (isLayoutContainer(el)) return el;
|
|
1414
|
+
}
|
|
1415
|
+
if (preferredParent && isLayoutContainer(preferredParent)) {
|
|
1416
|
+
for (const el of elements) {
|
|
1417
|
+
if (el === preferredParent) return preferredParent;
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1369
1422
|
function calculateDropPosition(container, pointerX, pointerY, draggedElement) {
|
|
1370
1423
|
const { axis, reversed: isReversed } = detectChildrenDirection(container, draggedElement);
|
|
1371
1424
|
const isHorizontal = axis === "horizontal";
|
|
@@ -1760,7 +1813,18 @@ function getReactComponentStack(element) {
|
|
|
1760
1813
|
return getRenderStack(fiber);
|
|
1761
1814
|
}
|
|
1762
1815
|
function getElementDisplayName(element) {
|
|
1763
|
-
|
|
1816
|
+
const tag = element.tagName.toLowerCase();
|
|
1817
|
+
if (element.id) return `${tag}#${element.id}`;
|
|
1818
|
+
const firstClass = Array.from(element.classList).find((c) => c && !c.startsWith("direct-edit"));
|
|
1819
|
+
if (firstClass) return `${tag}.${firstClass}`;
|
|
1820
|
+
return tag;
|
|
1821
|
+
}
|
|
1822
|
+
function getChildBriefInfo(element) {
|
|
1823
|
+
const name = getElementDisplayName(element);
|
|
1824
|
+
const raw = ((element.innerText || element.textContent) ?? "").replace(/\s+/g, " ").trim();
|
|
1825
|
+
const textPreview = raw.length > 40 ? `${raw.slice(0, 37)}...` : raw;
|
|
1826
|
+
const source = getElementSource(element);
|
|
1827
|
+
return { name, textPreview, source };
|
|
1764
1828
|
}
|
|
1765
1829
|
var STABLE_ATTRIBUTES = ["data-testid", "data-qa", "data-cy", "aria-label", "role"];
|
|
1766
1830
|
var MAX_SELECTOR_DEPTH = 24;
|
|
@@ -2048,26 +2112,28 @@ function parseDomSource(element) {
|
|
|
2048
2112
|
}
|
|
2049
2113
|
return { file, line, column };
|
|
2050
2114
|
}
|
|
2051
|
-
function
|
|
2052
|
-
const
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
};
|
|
2066
|
-
break;
|
|
2067
|
-
}
|
|
2068
|
-
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
2115
|
+
function getElementSource(element) {
|
|
2116
|
+
const domSource = parseDomSource(element);
|
|
2117
|
+
if (domSource) return domSource;
|
|
2118
|
+
const seenFibers = /* @__PURE__ */ new Set();
|
|
2119
|
+
let fiber = getFiberForElement(element);
|
|
2120
|
+
while (fiber && !seenFibers.has(fiber)) {
|
|
2121
|
+
seenFibers.add(fiber);
|
|
2122
|
+
const fiberSource = getSourceFromFiber(fiber);
|
|
2123
|
+
if (fiberSource?.fileName) {
|
|
2124
|
+
return {
|
|
2125
|
+
file: fiberSource.fileName,
|
|
2126
|
+
line: fiberSource.lineNumber,
|
|
2127
|
+
column: fiberSource.columnNumber
|
|
2128
|
+
};
|
|
2069
2129
|
}
|
|
2130
|
+
fiber = fiber._debugOwner ?? fiber.return ?? null;
|
|
2070
2131
|
}
|
|
2132
|
+
return null;
|
|
2133
|
+
}
|
|
2134
|
+
function getElementLocator(element) {
|
|
2135
|
+
const elementInfo = getElementInfo(element);
|
|
2136
|
+
const domSource = getElementSource(element);
|
|
2071
2137
|
return {
|
|
2072
2138
|
reactStack: getReactComponentStack(element),
|
|
2073
2139
|
domSelector: buildDomSelector(element),
|
|
@@ -2086,7 +2152,7 @@ function getLocatorHeader(locator) {
|
|
|
2086
2152
|
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;
|
|
2087
2153
|
return { componentLabel, formattedSource };
|
|
2088
2154
|
}
|
|
2089
|
-
function buildLocatorContextLines(locator) {
|
|
2155
|
+
function buildLocatorContextLines(locator, options) {
|
|
2090
2156
|
const lines = [];
|
|
2091
2157
|
const { componentLabel, formattedSource } = getLocatorHeader(locator);
|
|
2092
2158
|
const target = (locator.targetHtml || locator.domContextHtml || "").trim();
|
|
@@ -2099,7 +2165,7 @@ function buildLocatorContextLines(locator) {
|
|
|
2099
2165
|
lines.push("target:");
|
|
2100
2166
|
lines.push(target);
|
|
2101
2167
|
}
|
|
2102
|
-
if (context && context !== target) {
|
|
2168
|
+
if (!options?.skipContext && context && context !== target) {
|
|
2103
2169
|
lines.push("context:");
|
|
2104
2170
|
lines.push(context);
|
|
2105
2171
|
}
|
|
@@ -2253,6 +2319,29 @@ function buildEditExport(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
|
|
|
2253
2319
|
}
|
|
2254
2320
|
return lines.join("\n");
|
|
2255
2321
|
}
|
|
2322
|
+
function buildEditExportWithOptions(locator, pendingStyles, textEdit, options) {
|
|
2323
|
+
const changes = [];
|
|
2324
|
+
const collapsedStyles = collapseExportShorthands(pendingStyles);
|
|
2325
|
+
for (const [property, value] of Object.entries(collapsedStyles)) {
|
|
2326
|
+
const tailwindClass = stylesToTailwind({ [property]: value });
|
|
2327
|
+
changes.push({ property, value, tailwind: tailwindClass });
|
|
2328
|
+
}
|
|
2329
|
+
const lines = buildLocatorContextLines(locator, options);
|
|
2330
|
+
lines.push("");
|
|
2331
|
+
if (changes.length > 0) {
|
|
2332
|
+
lines.push("edits:");
|
|
2333
|
+
for (const change of changes) {
|
|
2334
|
+
const tailwind = change.tailwind ? ` (${change.tailwind})` : "";
|
|
2335
|
+
lines.push(`${change.property}: ${change.value}${tailwind}`);
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
if (textEdit) {
|
|
2339
|
+
lines.push("text content changed:");
|
|
2340
|
+
lines.push(`from: "${textEdit.originalText}"`);
|
|
2341
|
+
lines.push(`to: "${textEdit.newText}"`);
|
|
2342
|
+
}
|
|
2343
|
+
return lines.join("\n");
|
|
2344
|
+
}
|
|
2256
2345
|
function buildCommentExport(locator, commentText, replies) {
|
|
2257
2346
|
const lines = buildLocatorContextLines(locator);
|
|
2258
2347
|
lines.push("");
|
|
@@ -2264,55 +2353,554 @@ function buildCommentExport(locator, commentText, replies) {
|
|
|
2264
2353
|
}
|
|
2265
2354
|
return lines.join("\n");
|
|
2266
2355
|
}
|
|
2267
|
-
function
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2356
|
+
function normalizeSelector(selector) {
|
|
2357
|
+
const value = selector?.trim();
|
|
2358
|
+
return value && value.length > 0 ? value : null;
|
|
2359
|
+
}
|
|
2360
|
+
function normalizeName(name) {
|
|
2361
|
+
return name?.trim().toLowerCase() || "element";
|
|
2362
|
+
}
|
|
2363
|
+
function buildAnchorRef(name, selector, source) {
|
|
2364
|
+
return {
|
|
2365
|
+
name: name?.trim() || "element",
|
|
2366
|
+
selector: normalizeSelector(selector),
|
|
2367
|
+
source: source?.file ? source : null
|
|
2368
|
+
};
|
|
2272
2369
|
}
|
|
2273
|
-
function
|
|
2274
|
-
|
|
2275
|
-
const
|
|
2276
|
-
if (
|
|
2277
|
-
|
|
2370
|
+
function anchorKey(anchor) {
|
|
2371
|
+
if (!anchor) return "none";
|
|
2372
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2373
|
+
if (selector) return `selector:${selector}`;
|
|
2374
|
+
if (anchor.source?.file) {
|
|
2375
|
+
return `source:${anchor.source.file}:${anchor.source.line ?? 0}:${anchor.source.column ?? 0}`;
|
|
2376
|
+
}
|
|
2377
|
+
return `name:${normalizeName(anchor.name)}`;
|
|
2378
|
+
}
|
|
2379
|
+
function anchorsEqual(a, b) {
|
|
2380
|
+
if (!a && !b) return true;
|
|
2381
|
+
if (!a || !b) return false;
|
|
2382
|
+
const aSelector = normalizeSelector(a.selector);
|
|
2383
|
+
const bSelector = normalizeSelector(b.selector);
|
|
2384
|
+
if (aSelector && bSelector) return aSelector === bSelector;
|
|
2385
|
+
if (a.source?.file && b.source?.file) {
|
|
2386
|
+
return a.source.file === b.source.file && (a.source.line ?? null) === (b.source.line ?? null) && (a.source.column ?? null) === (b.source.column ?? null);
|
|
2387
|
+
}
|
|
2388
|
+
return normalizeName(a.name) === normalizeName(b.name);
|
|
2389
|
+
}
|
|
2390
|
+
function formatAnchorRef(anchor, fallback = "(none)") {
|
|
2391
|
+
if (!anchor) return fallback;
|
|
2392
|
+
const selector = normalizeSelector(anchor.selector);
|
|
2393
|
+
if (selector) return selector;
|
|
2394
|
+
if (anchor.source?.file) return `<${anchor.name}> @ ${formatSourceLocation(anchor.source.file, anchor.source.line, anchor.source.column)}`;
|
|
2395
|
+
return `<${anchor.name}>`;
|
|
2396
|
+
}
|
|
2397
|
+
function buildPlacementRef(before, after) {
|
|
2398
|
+
if (before && after) {
|
|
2399
|
+
return {
|
|
2400
|
+
before,
|
|
2401
|
+
after,
|
|
2402
|
+
description: `between after ${formatAnchorRef(before)} and before ${formatAnchorRef(after)}`
|
|
2403
|
+
};
|
|
2404
|
+
}
|
|
2405
|
+
if (before) {
|
|
2406
|
+
return {
|
|
2407
|
+
before,
|
|
2408
|
+
after: null,
|
|
2409
|
+
description: `after ${formatAnchorRef(before)}`
|
|
2410
|
+
};
|
|
2278
2411
|
}
|
|
2279
|
-
|
|
2412
|
+
if (after) {
|
|
2413
|
+
return {
|
|
2414
|
+
before: null,
|
|
2415
|
+
after,
|
|
2416
|
+
description: `before ${formatAnchorRef(after)}`
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
return {
|
|
2420
|
+
before: null,
|
|
2421
|
+
after: null,
|
|
2422
|
+
description: "as the only child"
|
|
2423
|
+
};
|
|
2280
2424
|
}
|
|
2281
|
-
function
|
|
2282
|
-
const
|
|
2283
|
-
|
|
2425
|
+
function buildPlacementFromMove(beforeName, beforeSelector, beforeSource, afterName, afterSelector, afterSource) {
|
|
2426
|
+
const before = beforeName || beforeSelector || beforeSource?.file ? buildAnchorRef(beforeName, beforeSelector, beforeSource) : null;
|
|
2427
|
+
const after = afterName || afterSelector || afterSource?.file ? buildAnchorRef(afterName, afterSelector, afterSource) : null;
|
|
2428
|
+
return buildPlacementRef(before, after);
|
|
2429
|
+
}
|
|
2430
|
+
function toRoundedVisualDelta(move) {
|
|
2431
|
+
const delta = move.visualDelta ?? move.positionDelta;
|
|
2432
|
+
if (!delta) return void 0;
|
|
2433
|
+
const rounded = { x: Math.round(delta.x), y: Math.round(delta.y) };
|
|
2434
|
+
return rounded.x === 0 && rounded.y === 0 ? void 0 : rounded;
|
|
2435
|
+
}
|
|
2436
|
+
function hasVisualIntent(move) {
|
|
2437
|
+
return Boolean(toRoundedVisualDelta(move));
|
|
2438
|
+
}
|
|
2439
|
+
function hasStructuralChange(move) {
|
|
2440
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2441
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2442
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2443
|
+
move.fromSiblingBefore,
|
|
2444
|
+
move.fromSiblingBeforeSelector,
|
|
2445
|
+
move.fromSiblingBeforeSource,
|
|
2446
|
+
move.fromSiblingAfter,
|
|
2447
|
+
move.fromSiblingAfterSelector,
|
|
2448
|
+
move.fromSiblingAfterSource
|
|
2449
|
+
);
|
|
2450
|
+
const toPlacement = buildPlacementFromMove(
|
|
2451
|
+
move.toSiblingBefore,
|
|
2452
|
+
move.toSiblingBeforeSelector,
|
|
2453
|
+
move.toSiblingBeforeSource,
|
|
2454
|
+
move.toSiblingAfter,
|
|
2455
|
+
move.toSiblingAfterSelector,
|
|
2456
|
+
move.toSiblingAfterSource
|
|
2457
|
+
);
|
|
2458
|
+
if (!anchorsEqual(fromParent, toParent)) return true;
|
|
2459
|
+
if (!anchorsEqual(fromPlacement.before, toPlacement.before)) return true;
|
|
2460
|
+
if (!anchorsEqual(fromPlacement.after, toPlacement.after)) return true;
|
|
2461
|
+
if (typeof move.fromIndex === "number" && typeof move.toIndex === "number" && move.fromIndex !== move.toIndex) return true;
|
|
2462
|
+
return false;
|
|
2284
2463
|
}
|
|
2285
|
-
function
|
|
2286
|
-
|
|
2287
|
-
|
|
2464
|
+
function isStructuredLayoutContainer(layout) {
|
|
2465
|
+
return layout === "flex" || layout === "grid";
|
|
2466
|
+
}
|
|
2467
|
+
function isExistingFlexWorkflow(move) {
|
|
2468
|
+
const structuralChange = hasStructuralChange(move);
|
|
2469
|
+
if (!structuralChange) return false;
|
|
2470
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2471
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2472
|
+
const sameParent = anchorsEqual(fromParent, toParent);
|
|
2473
|
+
const fromLayout = move.fromParentLayout;
|
|
2474
|
+
const toLayout = move.toParentLayout;
|
|
2475
|
+
if (sameParent) {
|
|
2476
|
+
return Boolean(move.mode === "reorder" && (isStructuredLayoutContainer(toLayout) || isStructuredLayoutContainer(fromLayout)));
|
|
2477
|
+
}
|
|
2478
|
+
return Boolean(isStructuredLayoutContainer(fromLayout) && isStructuredLayoutContainer(toLayout));
|
|
2479
|
+
}
|
|
2480
|
+
function classifyMove(move) {
|
|
2481
|
+
const structuralChange = hasStructuralChange(move);
|
|
2482
|
+
const visualIntent = hasVisualIntent(move);
|
|
2483
|
+
if (!structuralChange && !visualIntent) return "noop";
|
|
2484
|
+
if (isExistingFlexWorkflow(move)) return "existing_layout_move";
|
|
2485
|
+
if (move.mode === "free" || move.mode === "position") return "layout_refactor";
|
|
2486
|
+
if (!structuralChange && visualIntent) return "layout_refactor";
|
|
2487
|
+
return "layout_refactor";
|
|
2488
|
+
}
|
|
2489
|
+
function buildNumericClusters(values, tolerance) {
|
|
2490
|
+
if (values.length === 0) return [];
|
|
2491
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
2492
|
+
const clusters = [{ center: sorted[0], values: [sorted[0]] }];
|
|
2493
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
2494
|
+
const value = sorted[i];
|
|
2495
|
+
const current = clusters[clusters.length - 1];
|
|
2496
|
+
if (Math.abs(value - current.center) <= tolerance) {
|
|
2497
|
+
current.values.push(value);
|
|
2498
|
+
current.center = current.values.reduce((sum, n) => sum + n, 0) / current.values.length;
|
|
2499
|
+
} else {
|
|
2500
|
+
clusters.push({ center: value, values: [value] });
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
return clusters;
|
|
2288
2504
|
}
|
|
2289
|
-
function
|
|
2290
|
-
|
|
2505
|
+
function inferFlexDirection(sameRowCount, sameColumnCount, visualDelta) {
|
|
2506
|
+
if (sameRowCount > sameColumnCount) {
|
|
2507
|
+
return { direction: "row", reason: "Subject aligns with neighboring anchors on the same row." };
|
|
2508
|
+
}
|
|
2509
|
+
if (sameColumnCount > sameRowCount) {
|
|
2510
|
+
return { direction: "column", reason: "Subject aligns with neighboring anchors on the same column." };
|
|
2511
|
+
}
|
|
2512
|
+
if (sameRowCount > 0) {
|
|
2513
|
+
return { direction: "row", reason: "Detected row alignment in final geometry." };
|
|
2514
|
+
}
|
|
2515
|
+
if (sameColumnCount > 0) {
|
|
2516
|
+
return { direction: "column", reason: "Detected column alignment in final geometry." };
|
|
2517
|
+
}
|
|
2518
|
+
const horizontalDominant = Math.abs(visualDelta?.x ?? 0) >= Math.abs(visualDelta?.y ?? 0);
|
|
2519
|
+
return {
|
|
2520
|
+
direction: horizontalDominant ? "row" : "column",
|
|
2521
|
+
reason: "Fell back to movement axis because anchor alignment was ambiguous."
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
function inferLayoutPrescription(edit, operation, reasons) {
|
|
2525
|
+
const parent = edit.element.parentElement;
|
|
2526
|
+
if (!parent || !edit.element.isConnected) {
|
|
2527
|
+
return {
|
|
2528
|
+
recommendedSystem: "flex",
|
|
2529
|
+
intentPatterns: ["no_geometry_context"],
|
|
2530
|
+
refactorSteps: [
|
|
2531
|
+
`Reparent ${formatAnchorRef(operation.subject)} under ${formatAnchorRef(operation.to.parent)} at ${operation.to.placement.description}.`
|
|
2532
|
+
],
|
|
2533
|
+
styleSteps: [
|
|
2534
|
+
`Convert ${formatAnchorRef(operation.to.parent)} to flex and set a clear primary axis for this relationship.`,
|
|
2535
|
+
"Use `gap` for spacing and keep positioning static."
|
|
2536
|
+
],
|
|
2537
|
+
itemSteps: [
|
|
2538
|
+
"Remove any inline `left/top/transform` move artifacts from moved elements."
|
|
2539
|
+
]
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
const children = Array.from(parent.children).filter(
|
|
2543
|
+
(node) => node instanceof HTMLElement && isInFlowChild(node) && !node.hasAttribute("data-direct-edit")
|
|
2544
|
+
);
|
|
2545
|
+
const childSnapshots = children.map((child) => {
|
|
2546
|
+
const rect = child.getBoundingClientRect();
|
|
2547
|
+
const locator = getElementLocator(child);
|
|
2548
|
+
const anchor = buildAnchorRef(getElementDisplayName(child), locator.domSelector, locator.domSource);
|
|
2549
|
+
return {
|
|
2550
|
+
child,
|
|
2551
|
+
rect,
|
|
2552
|
+
centerX: rect.left + rect.width / 2,
|
|
2553
|
+
centerY: rect.top + rect.height / 2,
|
|
2554
|
+
anchor,
|
|
2555
|
+
anchorLabel: formatAnchorRef(anchor)
|
|
2556
|
+
};
|
|
2557
|
+
});
|
|
2558
|
+
const subjectSnapshot = childSnapshots.find((snapshot) => snapshot.child === edit.element);
|
|
2559
|
+
const subjectRect = edit.element.getBoundingClientRect();
|
|
2560
|
+
const subjectCenterX = subjectRect.left + subjectRect.width / 2;
|
|
2561
|
+
const subjectCenterY = subjectRect.top + subjectRect.height / 2;
|
|
2562
|
+
const rowTolerance = Math.max(8, subjectRect.height * 0.35);
|
|
2563
|
+
const colTolerance = Math.max(8, subjectRect.width * 0.35);
|
|
2564
|
+
const sameRowWith = [];
|
|
2565
|
+
const sameColumnWith = [];
|
|
2566
|
+
const sameRowNodes = [];
|
|
2567
|
+
let aboveAnchor = null;
|
|
2568
|
+
let belowAnchor = null;
|
|
2569
|
+
let bestAboveDistance = Number.POSITIVE_INFINITY;
|
|
2570
|
+
let bestBelowDistance = Number.POSITIVE_INFINITY;
|
|
2571
|
+
for (const node of childSnapshots) {
|
|
2572
|
+
if (node.child === edit.element) continue;
|
|
2573
|
+
if (Math.abs(node.centerY - subjectCenterY) <= rowTolerance) {
|
|
2574
|
+
sameRowWith.push(node.anchorLabel);
|
|
2575
|
+
sameRowNodes.push(node);
|
|
2576
|
+
}
|
|
2577
|
+
if (Math.abs(node.centerX - subjectCenterX) <= colTolerance) {
|
|
2578
|
+
sameColumnWith.push(node.anchorLabel);
|
|
2579
|
+
}
|
|
2580
|
+
const yDelta = node.centerY - subjectCenterY;
|
|
2581
|
+
if (yDelta < 0 && Math.abs(yDelta) < bestAboveDistance) {
|
|
2582
|
+
bestAboveDistance = Math.abs(yDelta);
|
|
2583
|
+
aboveAnchor = node.anchorLabel;
|
|
2584
|
+
}
|
|
2585
|
+
if (yDelta > 0 && yDelta < bestBelowDistance) {
|
|
2586
|
+
bestBelowDistance = yDelta;
|
|
2587
|
+
belowAnchor = node.anchorLabel;
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2590
|
+
const rowCenters = childSnapshots.map(({ centerY }) => centerY);
|
|
2591
|
+
const colCenters = childSnapshots.map(({ centerX }) => centerX);
|
|
2592
|
+
const rowClusters = buildNumericClusters(rowCenters, rowTolerance);
|
|
2593
|
+
const colClusters = buildNumericClusters(colCenters, colTolerance);
|
|
2594
|
+
const denseRowClusters = rowClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2595
|
+
const denseColClusters = colClusters.filter((cluster) => cluster.values.length >= 2).length;
|
|
2596
|
+
const isTwoDimensional = childSnapshots.length >= 4 && denseRowClusters >= 2 && denseColClusters >= 2;
|
|
2597
|
+
const recommendedSystem = isTwoDimensional ? "grid" : "flex";
|
|
2598
|
+
const intentPatterns = [];
|
|
2599
|
+
if (sameRowWith.length > 0) intentPatterns.push(`same_row_with:${sameRowWith.slice(0, 3).join(", ")}`);
|
|
2600
|
+
if (sameColumnWith.length > 0) intentPatterns.push(`same_column_with:${sameColumnWith.slice(0, 3).join(", ")}`);
|
|
2601
|
+
if (aboveAnchor) intentPatterns.push(`below:${aboveAnchor}`);
|
|
2602
|
+
if (belowAnchor) intentPatterns.push(`above:${belowAnchor}`);
|
|
2603
|
+
if (sameRowWith.length === 0 && sameColumnWith.length === 0) intentPatterns.push("separate_cluster");
|
|
2604
|
+
const visualDelta = operation.visualDelta;
|
|
2605
|
+
const flexDirectionInfo = inferFlexDirection(sameRowWith.length, sameColumnWith.length, visualDelta);
|
|
2606
|
+
const flexDirection = flexDirectionInfo.direction;
|
|
2607
|
+
if (recommendedSystem === "grid") {
|
|
2608
|
+
reasons.push("Detected multiple dense row and column clusters; a 2D layout system is likely intentional.");
|
|
2609
|
+
return {
|
|
2610
|
+
recommendedSystem: "grid",
|
|
2611
|
+
intentPatterns,
|
|
2612
|
+
refactorSteps: [
|
|
2613
|
+
`Create/ensure a shared container around ${formatAnchorRef(operation.subject)} and related anchors under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2614
|
+
`Reorder/reparent elements to satisfy placement ${operation.to.placement.description}.`
|
|
2615
|
+
],
|
|
2616
|
+
styleSteps: [
|
|
2617
|
+
`Set ${formatAnchorRef(operation.to.parent)} to grid with explicit template rows/columns for the final layout.`,
|
|
2618
|
+
"Use `gap` for consistent spacing and keep placement structural."
|
|
2619
|
+
],
|
|
2620
|
+
itemSteps: [
|
|
2621
|
+
`Set item alignment on ${formatAnchorRef(operation.subject)} with grid self-alignment (\`justify-self\`/\`align-self\`).`
|
|
2622
|
+
]
|
|
2623
|
+
};
|
|
2624
|
+
}
|
|
2625
|
+
reasons.push(`${flexDirectionInfo.reason} Use a 1D flex layout instead of literal drag replay.`);
|
|
2626
|
+
let hasStackedCluster = false;
|
|
2627
|
+
const stackedAnchorLabels = /* @__PURE__ */ new Set();
|
|
2628
|
+
if (flexDirection === "row" && subjectSnapshot) {
|
|
2629
|
+
for (const rowPeer of sameRowNodes) {
|
|
2630
|
+
for (const node of childSnapshots) {
|
|
2631
|
+
if (node.child === edit.element || node.child === rowPeer.child) continue;
|
|
2632
|
+
const sameColumnAsPeer = Math.abs(node.centerX - rowPeer.centerX) <= colTolerance;
|
|
2633
|
+
const verticallySeparated = Math.abs(node.centerY - rowPeer.centerY) > rowTolerance;
|
|
2634
|
+
if (sameColumnAsPeer && verticallySeparated) {
|
|
2635
|
+
hasStackedCluster = true;
|
|
2636
|
+
stackedAnchorLabels.add(rowPeer.anchorLabel);
|
|
2637
|
+
stackedAnchorLabels.add(node.anchorLabel);
|
|
2638
|
+
}
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
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));
|
|
2643
|
+
const refactorSteps = [
|
|
2644
|
+
`Ensure ${formatAnchorRef(operation.subject)} and referenced neighbors share a common container under ${formatAnchorRef(operation.to.parent)}.`,
|
|
2645
|
+
`Reparent/reorder nodes so ${formatAnchorRef(operation.subject)} lands ${operation.to.placement.description}.`
|
|
2646
|
+
];
|
|
2647
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2648
|
+
const clusterSample = Array.from(stackedAnchorLabels).slice(0, 3).join(", ");
|
|
2649
|
+
refactorSteps.push(`Create a left-side content wrapper for vertically stacked items (${clusterSample}), and keep ${formatAnchorRef(operation.subject)} as the opposite-side sibling.`);
|
|
2650
|
+
}
|
|
2651
|
+
if (hasBelowCluster) {
|
|
2652
|
+
refactorSteps.push("Keep lower content sections in a separate block below the horizontal header row; do not force them into the same row.");
|
|
2653
|
+
}
|
|
2654
|
+
const styleSteps = [
|
|
2655
|
+
`Set ${formatAnchorRef(operation.to.parent)} to flex with direction ${flexDirection}.`,
|
|
2656
|
+
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.",
|
|
2657
|
+
"Use `gap` for spacing between siblings."
|
|
2658
|
+
];
|
|
2659
|
+
if (flexDirection === "row" && hasStackedCluster) {
|
|
2660
|
+
styleSteps.push("Set the content wrapper to `display: flex` with `flex-direction: column` and an appropriate vertical gap.");
|
|
2661
|
+
}
|
|
2662
|
+
return {
|
|
2663
|
+
recommendedSystem: "flex",
|
|
2664
|
+
intentPatterns,
|
|
2665
|
+
refactorSteps,
|
|
2666
|
+
styleSteps,
|
|
2667
|
+
itemSteps: [
|
|
2668
|
+
`Apply item-level alignment (\`align-self\` / flex-basis) only when needed for ${formatAnchorRef(operation.subject)}.`,
|
|
2669
|
+
"Do not use absolute positioning, top/left offsets, transforms, or margin hacks to simulate movement."
|
|
2670
|
+
]
|
|
2671
|
+
};
|
|
2672
|
+
}
|
|
2673
|
+
function buildMoveEntries(edits) {
|
|
2674
|
+
const entries = [];
|
|
2675
|
+
let noopMoveCount = 0;
|
|
2676
|
+
for (const edit of edits) {
|
|
2677
|
+
const move = edit.move;
|
|
2678
|
+
if (!move) continue;
|
|
2679
|
+
const subject = buildAnchorRef(
|
|
2680
|
+
getElementDisplayName(edit.element) || edit.locator.tagName,
|
|
2681
|
+
edit.locator.domSelector,
|
|
2682
|
+
edit.locator.domSource
|
|
2683
|
+
);
|
|
2684
|
+
const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
|
|
2685
|
+
const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
|
|
2686
|
+
const fromPlacement = buildPlacementFromMove(
|
|
2687
|
+
move.fromSiblingBefore,
|
|
2688
|
+
move.fromSiblingBeforeSelector,
|
|
2689
|
+
move.fromSiblingBeforeSource,
|
|
2690
|
+
move.fromSiblingAfter,
|
|
2691
|
+
move.fromSiblingAfterSelector,
|
|
2692
|
+
move.fromSiblingAfterSource
|
|
2693
|
+
);
|
|
2694
|
+
const toPlacement = buildPlacementFromMove(
|
|
2695
|
+
move.toSiblingBefore,
|
|
2696
|
+
move.toSiblingBeforeSelector,
|
|
2697
|
+
move.toSiblingBeforeSource,
|
|
2698
|
+
move.toSiblingAfter,
|
|
2699
|
+
move.toSiblingAfterSelector,
|
|
2700
|
+
move.toSiblingAfterSource
|
|
2701
|
+
);
|
|
2702
|
+
const reasons = [];
|
|
2703
|
+
const classification = classifyMove(move);
|
|
2704
|
+
if (classification === "noop") {
|
|
2705
|
+
noopMoveCount++;
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
const interactionMode = move.mode ?? "free";
|
|
2709
|
+
const visualDelta = toRoundedVisualDelta(move);
|
|
2710
|
+
if (visualDelta) {
|
|
2711
|
+
reasons.push(`Non-zero visual delta detected (${visualDelta.x}px, ${visualDelta.y}px).`);
|
|
2712
|
+
}
|
|
2713
|
+
const structuralChange = hasStructuralChange(move);
|
|
2714
|
+
if (structuralChange) reasons.push("Anchor placement changed between source and target.");
|
|
2715
|
+
else reasons.push("No anchor placement change; treating movement as layout intent translation.");
|
|
2716
|
+
const operationBase = {
|
|
2717
|
+
classification,
|
|
2718
|
+
interactionMode,
|
|
2719
|
+
subject,
|
|
2720
|
+
from: { parent: fromParent, placement: fromPlacement },
|
|
2721
|
+
to: { parent: toParent, placement: toPlacement },
|
|
2722
|
+
...visualDelta ? { visualDelta } : {},
|
|
2723
|
+
confidence: classification === "existing_layout_move" ? "high" : structuralChange ? "medium" : "high",
|
|
2724
|
+
reasons
|
|
2725
|
+
};
|
|
2726
|
+
if (classification === "layout_refactor") {
|
|
2727
|
+
operationBase.layoutPrescription = inferLayoutPrescription(edit, operationBase, reasons);
|
|
2728
|
+
}
|
|
2729
|
+
const sortSource = subject.source?.file ? `${subject.source.file}:${subject.source.line ?? 0}:${subject.source.column ?? 0}` : "";
|
|
2730
|
+
const sortKey = [
|
|
2731
|
+
sortSource,
|
|
2732
|
+
anchorKey(subject),
|
|
2733
|
+
anchorKey(toParent),
|
|
2734
|
+
toPlacement.description
|
|
2735
|
+
].join("|");
|
|
2736
|
+
entries.push({ edit, operation: operationBase, sortKey });
|
|
2737
|
+
}
|
|
2738
|
+
entries.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
|
|
2739
|
+
return { entries, noopMoveCount };
|
|
2740
|
+
}
|
|
2741
|
+
function buildMovePlanContext(edits, _domContext) {
|
|
2742
|
+
const { entries, noopMoveCount } = buildMoveEntries(edits);
|
|
2743
|
+
if (entries.length === 0) {
|
|
2744
|
+
return {
|
|
2745
|
+
movePlan: null,
|
|
2746
|
+
intentsByEdit: /* @__PURE__ */ new Map(),
|
|
2747
|
+
noopMoveCount
|
|
2748
|
+
};
|
|
2749
|
+
}
|
|
2750
|
+
const operations = [];
|
|
2751
|
+
const intentsByEdit = /* @__PURE__ */ new Map();
|
|
2752
|
+
for (let i = 0; i < entries.length; i++) {
|
|
2753
|
+
const operationId = `op-${i + 1}`;
|
|
2754
|
+
const operation = { operationId, ...entries[i].operation };
|
|
2755
|
+
operations.push(operation);
|
|
2756
|
+
intentsByEdit.set(entries[i].edit, operation);
|
|
2757
|
+
}
|
|
2758
|
+
const affectedContainerMap = /* @__PURE__ */ new Map();
|
|
2759
|
+
for (const operation of operations) {
|
|
2760
|
+
affectedContainerMap.set(anchorKey(operation.from.parent), operation.from.parent);
|
|
2761
|
+
affectedContainerMap.set(anchorKey(operation.to.parent), operation.to.parent);
|
|
2762
|
+
}
|
|
2763
|
+
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)}.`);
|
|
2764
|
+
const notes = [];
|
|
2765
|
+
if (noopMoveCount > 0) notes.push(`Excluded ${noopMoveCount} no-op move(s).`);
|
|
2766
|
+
if (operations.some((op) => op.classification === "layout_refactor")) {
|
|
2767
|
+
notes.push("Layout refactor operations include best-practice flex/grid prescriptions.");
|
|
2768
|
+
}
|
|
2769
|
+
return {
|
|
2770
|
+
movePlan: {
|
|
2771
|
+
operations,
|
|
2772
|
+
affectedContainers: Array.from(affectedContainerMap.values()),
|
|
2773
|
+
orderingConstraints,
|
|
2774
|
+
notes
|
|
2775
|
+
},
|
|
2776
|
+
intentsByEdit,
|
|
2777
|
+
noopMoveCount
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function buildMovePlan(edits, domContext) {
|
|
2781
|
+
const context = buildMovePlanContext(edits, domContext);
|
|
2782
|
+
return context.movePlan ?? {
|
|
2783
|
+
operations: [],
|
|
2784
|
+
affectedContainers: [],
|
|
2785
|
+
orderingConstraints: [],
|
|
2786
|
+
notes: context.noopMoveCount > 0 ? [`Excluded ${context.noopMoveCount} no-op move(s).`] : []
|
|
2787
|
+
};
|
|
2788
|
+
}
|
|
2789
|
+
function getMoveIntentForEdit(edit, context) {
|
|
2790
|
+
if (!edit.move) return null;
|
|
2791
|
+
if (context?.intentsByEdit.has(edit)) return context.intentsByEdit.get(edit) ?? null;
|
|
2792
|
+
const singleContext = buildMovePlanContext([edit]);
|
|
2793
|
+
return singleContext.intentsByEdit.get(edit) ?? null;
|
|
2794
|
+
}
|
|
2795
|
+
function buildMoveInstructionFromIntent(intent) {
|
|
2796
|
+
if (intent.classification === "existing_layout_move") {
|
|
2797
|
+
return `Apply as a structural move in code: place ${formatAnchorRef(intent.subject)} ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`;
|
|
2798
|
+
}
|
|
2799
|
+
const system = intent.layoutPrescription?.recommendedSystem ?? "flex";
|
|
2800
|
+
return `Treat this as a ${system} layout refactor. Implement the listed structure/style steps in source code instead of drag replay.`;
|
|
2801
|
+
}
|
|
2802
|
+
function formatMoveType(classification) {
|
|
2803
|
+
return classification === "existing_layout_move" ? "structural_move" : "layout_refactor";
|
|
2804
|
+
}
|
|
2805
|
+
function buildMoveExportLines(intent) {
|
|
2806
|
+
const moveType = formatMoveType(intent.classification);
|
|
2807
|
+
const implementationSteps = [];
|
|
2808
|
+
if (intent.classification === "existing_layout_move") {
|
|
2809
|
+
implementationSteps.push(`Reorder/reparent ${formatAnchorRef(intent.subject)} to ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`);
|
|
2810
|
+
} else {
|
|
2811
|
+
const prescription = intent.layoutPrescription;
|
|
2812
|
+
if (prescription) {
|
|
2813
|
+
implementationSteps.push(...prescription.refactorSteps);
|
|
2814
|
+
implementationSteps.push(...prescription.styleSteps);
|
|
2815
|
+
implementationSteps.push(...prescription.itemSteps);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
const lines = [
|
|
2291
2819
|
"moved:",
|
|
2292
|
-
`
|
|
2293
|
-
`
|
|
2294
|
-
`
|
|
2295
|
-
`
|
|
2296
|
-
`
|
|
2297
|
-
`
|
|
2298
|
-
`
|
|
2299
|
-
`to_parent_selector: ${formatMoveSelector(move.toParentSelector, "(unknown)")}`,
|
|
2300
|
-
`to_before_selector: ${formatMoveSelector(move.toSiblingBeforeSelector, "(none)")}`,
|
|
2301
|
-
`to_after_selector: ${formatMoveSelector(move.toSiblingAfterSelector, "(none)")}`,
|
|
2302
|
-
`to_parent_source: ${formatMoveSource(move.toParentSource, "(unknown)")}`,
|
|
2303
|
-
`to_before_source: ${formatMoveSource(move.toSiblingBeforeSource, "(none)")}`,
|
|
2304
|
-
`to_after_source: ${formatMoveSource(move.toSiblingAfterSource, "(none)")}`
|
|
2820
|
+
`id: ${intent.operationId}`,
|
|
2821
|
+
`type: ${moveType}`,
|
|
2822
|
+
`subject: ${formatAnchorRef(intent.subject, "(unknown)")}`,
|
|
2823
|
+
`parent: ${formatAnchorRef(intent.to.parent, "(unknown)")}`,
|
|
2824
|
+
`current_anchor: ${intent.from.placement.description}`,
|
|
2825
|
+
`target_anchor: ${intent.to.placement.description}`,
|
|
2826
|
+
...intent.visualDelta ? [`visual_hint: ${intent.visualDelta.x}px horizontal, ${intent.visualDelta.y}px vertical`] : []
|
|
2305
2827
|
];
|
|
2828
|
+
if (intent.layoutPrescription) {
|
|
2829
|
+
lines.push(`recommended_layout: ${intent.layoutPrescription.recommendedSystem}`);
|
|
2830
|
+
}
|
|
2831
|
+
lines.push("implementation_steps:");
|
|
2832
|
+
for (const step of implementationSteps) {
|
|
2833
|
+
lines.push(` - ${step}`);
|
|
2834
|
+
}
|
|
2835
|
+
lines.push("guardrails:");
|
|
2836
|
+
lines.push(" - Do not simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
|
|
2837
|
+
lines.push(`instruction: ${buildMoveInstructionFromIntent(intent)}`);
|
|
2838
|
+
return lines;
|
|
2839
|
+
}
|
|
2840
|
+
function getExportContentProfile(edits, comments, movePlanOrContext) {
|
|
2841
|
+
const moveOpCount = movePlanOrContext ? "operations" in movePlanOrContext ? movePlanOrContext.operations.length : movePlanOrContext.movePlan?.operations.length ?? 0 : buildMovePlanContext(edits).movePlan?.operations.length ?? 0;
|
|
2842
|
+
return {
|
|
2843
|
+
hasCssEdits: edits.some((e) => Object.keys(e.pendingStyles).length > 0),
|
|
2844
|
+
hasTextEdits: edits.some((e) => e.textEdit != null),
|
|
2845
|
+
hasMoves: moveOpCount > 0,
|
|
2846
|
+
hasComments: comments.length > 0
|
|
2847
|
+
};
|
|
2848
|
+
}
|
|
2849
|
+
function buildExportInstruction(profile) {
|
|
2850
|
+
const { hasCssEdits, hasTextEdits, hasMoves, hasComments } = profile;
|
|
2851
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves && !hasComments) return "";
|
|
2852
|
+
if (!hasCssEdits && !hasTextEdits && !hasMoves) {
|
|
2853
|
+
return hasComments ? "Address this feedback on the UI. Use the provided source location and selector to find each element in the codebase." : "";
|
|
2854
|
+
}
|
|
2855
|
+
const parts = [];
|
|
2856
|
+
if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.).");
|
|
2857
|
+
if (hasTextEdits) parts.push("Update the text content as specified.");
|
|
2858
|
+
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.");
|
|
2859
|
+
if (hasComments) parts.push("Address the comments on the relevant elements.");
|
|
2860
|
+
return `${parts.join(" ")} Use the provided source locations, selectors, and context HTML to locate each element in the codebase.`;
|
|
2306
2861
|
}
|
|
2307
|
-
function buildSessionExport(edits, comments = []) {
|
|
2862
|
+
function buildSessionExport(edits, comments = [], options) {
|
|
2308
2863
|
const blocks = [];
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2864
|
+
const planContext = options?.movePlanContext ?? buildMovePlanContext(edits);
|
|
2865
|
+
const movePlan = planContext.movePlan;
|
|
2866
|
+
const includeMovePlanHeader = options?.includeMovePlanHeader !== false;
|
|
2867
|
+
if (includeMovePlanHeader && movePlan && movePlan.operations.length > 0) {
|
|
2868
|
+
const planLines = [
|
|
2869
|
+
"=== LAYOUT MOVE PLAN ===",
|
|
2870
|
+
`operations: ${movePlan.operations.length}`
|
|
2871
|
+
];
|
|
2872
|
+
if (movePlan.affectedContainers.length > 0) {
|
|
2873
|
+
planLines.push("containers:");
|
|
2874
|
+
for (const container of movePlan.affectedContainers) {
|
|
2875
|
+
planLines.push(` - ${formatAnchorRef(container, "(unknown)")}`);
|
|
2876
|
+
}
|
|
2877
|
+
}
|
|
2878
|
+
if (movePlan.orderingConstraints.length > 0) {
|
|
2879
|
+
planLines.push("structural_constraints:");
|
|
2880
|
+
for (const constraint of movePlan.orderingConstraints) {
|
|
2881
|
+
planLines.push(` - ${constraint}`);
|
|
2882
|
+
}
|
|
2314
2883
|
}
|
|
2315
|
-
|
|
2884
|
+
if (movePlan.notes.length > 0) {
|
|
2885
|
+
planLines.push("plan_notes:");
|
|
2886
|
+
for (const note of movePlan.notes) {
|
|
2887
|
+
planLines.push(` - ${note}`);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
blocks.push(planLines.join("\n"));
|
|
2891
|
+
}
|
|
2892
|
+
for (const edit of edits) {
|
|
2893
|
+
const moveIntent = getMoveIntentForEdit(edit, planContext);
|
|
2894
|
+
const hasMove = Boolean(moveIntent);
|
|
2895
|
+
const hasStyleOrText = Object.keys(edit.pendingStyles).length > 0 || edit.textEdit != null;
|
|
2896
|
+
if (!hasMove && !hasStyleOrText) continue;
|
|
2897
|
+
const block = hasMove ? buildEditExportWithOptions(edit.locator, edit.pendingStyles, edit.textEdit, { skipContext: true }) : buildEditExport(edit.locator, edit.pendingStyles, edit.textEdit);
|
|
2898
|
+
let moveBlock = "";
|
|
2899
|
+
if (moveIntent) {
|
|
2900
|
+
moveBlock = `
|
|
2901
|
+
${buildMoveExportLines(moveIntent).join("\n")}`;
|
|
2902
|
+
}
|
|
2903
|
+
blocks.push(block + moveBlock);
|
|
2316
2904
|
}
|
|
2317
2905
|
for (const comment of comments) {
|
|
2318
2906
|
blocks.push(buildCommentExport(comment.locator, comment.text, comment.replies));
|
|
@@ -2327,6 +2915,9 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
|
|
|
2327
2915
|
buildCommentExport,
|
|
2328
2916
|
buildEditExport,
|
|
2329
2917
|
buildElementContext,
|
|
2918
|
+
buildExportInstruction,
|
|
2919
|
+
buildMovePlan,
|
|
2920
|
+
buildMovePlanContext,
|
|
2330
2921
|
buildSessionExport,
|
|
2331
2922
|
calculateDropPosition,
|
|
2332
2923
|
calculateElementMeasurements,
|
|
@@ -2338,17 +2929,20 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
|
|
|
2338
2929
|
colorPropertyToCSSMap,
|
|
2339
2930
|
colorToTailwind,
|
|
2340
2931
|
computeHoverHighlight,
|
|
2932
|
+
computeIntendedIndex,
|
|
2341
2933
|
detectChildrenDirection,
|
|
2342
2934
|
detectSizingMode,
|
|
2343
2935
|
elementFromPointWithoutOverlays,
|
|
2344
2936
|
ensureDirectTextSpanAtPoint,
|
|
2345
2937
|
findChildAtPoint,
|
|
2346
2938
|
findContainerAtPoint,
|
|
2939
|
+
findLayoutContainerAtPoint,
|
|
2347
2940
|
findTextOwnerAtPoint,
|
|
2348
2941
|
findTextOwnerByRangeScan,
|
|
2349
2942
|
flexPropertyToCSSMap,
|
|
2350
2943
|
formatPropertyValue,
|
|
2351
2944
|
getAllComputedStyles,
|
|
2945
|
+
getChildBriefInfo,
|
|
2352
2946
|
getComputedBorderStyles,
|
|
2353
2947
|
getComputedBoxShadow,
|
|
2354
2948
|
getComputedColorStyles,
|
|
@@ -2359,11 +2953,16 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
|
|
|
2359
2953
|
getElementDisplayName,
|
|
2360
2954
|
getElementInfo,
|
|
2361
2955
|
getElementLocator,
|
|
2956
|
+
getElementSource,
|
|
2957
|
+
getExportContentProfile,
|
|
2362
2958
|
getFlexDirection,
|
|
2959
|
+
getMoveIntentForEdit,
|
|
2363
2960
|
getOriginalInlineStyles,
|
|
2364
2961
|
getSizingValue,
|
|
2365
2962
|
isFlexContainer,
|
|
2963
|
+
isInFlowChild,
|
|
2366
2964
|
isInputFocused,
|
|
2965
|
+
isLayoutContainer,
|
|
2367
2966
|
isTextElement,
|
|
2368
2967
|
parseColorValue,
|
|
2369
2968
|
parsePropertyValue,
|