made-refine 0.2.6 → 0.2.8

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/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,12 +64,18 @@ __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,
72
+ getSelectionColors: () => getSelectionColors,
63
73
  getSizingValue: () => getSizingValue,
64
74
  isFlexContainer: () => isFlexContainer,
75
+ isInFlowChild: () => isInFlowChild,
65
76
  isInputFocused: () => isInputFocused,
66
- isTextElement: () => isTextElement,
77
+ isLayoutContainer: () => isLayoutContainer,
78
+ isTextElement: () => isTextElement2,
67
79
  parseColorValue: () => parseColorValue,
68
80
  parsePropertyValue: () => parsePropertyValue,
69
81
  propertyToCSSMap: () => propertyToCSSMap,
@@ -79,7 +91,7 @@ module.exports = __toCommonJS(utils_exports);
79
91
  // src/utils/css-value.ts
80
92
  function parsePropertyValue(value) {
81
93
  const raw = value.trim();
82
- const match = raw.match(/^(-?\d*\.?\d+)(px|rem|em|%)?$/);
94
+ const match = raw.match(/^(-?\d*\.?\d+)(px|rem|em|vh|vw|%)?$/);
83
95
  if (match) {
84
96
  return {
85
97
  numericValue: parseFloat(match[1]),
@@ -100,6 +112,24 @@ function formatPropertyValue(value) {
100
112
  return `${value.numericValue}${value.unit}`;
101
113
  }
102
114
 
115
+ // src/canvas-store.ts
116
+ var import_react = require("react");
117
+ var DEFAULT = { active: false, zoom: 1, panX: 0, panY: 0 };
118
+ var snapshot = DEFAULT;
119
+ var bodyOffset = { x: 0, y: 0 };
120
+ function getBodyOffset() {
121
+ return bodyOffset;
122
+ }
123
+ function getCanvasSnapshot() {
124
+ return snapshot;
125
+ }
126
+
127
+ // src/utils/measurements.ts
128
+ function getZoomScale() {
129
+ const snap = getCanvasSnapshot();
130
+ return snap.active ? snap.zoom : 1;
131
+ }
132
+
103
133
  // src/utils.ts
104
134
  function clamp(value, min, max) {
105
135
  if (!Number.isFinite(value)) return min;
@@ -197,6 +227,7 @@ var ORIGINAL_STYLE_PROPS = [
197
227
  "width",
198
228
  "height",
199
229
  "background-color",
230
+ "background",
200
231
  "color",
201
232
  "border-color",
202
233
  "outline-color",
@@ -579,7 +610,7 @@ function hasDirectNonWhitespaceText(element) {
579
610
  (node) => node.nodeType === Node.TEXT_NODE && Boolean(node.textContent?.trim())
580
611
  );
581
612
  }
582
- function isTextElement(element) {
613
+ function isTextElement2(element) {
583
614
  const tagName = element.tagName.toLowerCase();
584
615
  if (TEXT_ELEMENT_TAGS.has(tagName)) {
585
616
  return true;
@@ -798,6 +829,55 @@ function parseColorValue(cssValue) {
798
829
  return parseNamedColor(raw);
799
830
  }
800
831
  var TRANSPARENT_COLOR = { hex: "000000", alpha: 0, raw: "transparent" };
832
+ function isVisibleBorderSide(side) {
833
+ return side.style !== "none" && side.style !== "hidden" && parseFloat(side.width) > 0;
834
+ }
835
+ function hasVisibleOutline(computed) {
836
+ return computed.outlineStyle !== "none" && parseFloat(computed.outlineWidth) > 0;
837
+ }
838
+ function parseVisibleColor(value, fallbackCurrentColor) {
839
+ const raw = value.trim();
840
+ const lowered = raw.toLowerCase();
841
+ if (!raw || lowered === "none" || lowered === "transparent") {
842
+ return null;
843
+ }
844
+ const resolved = /^currentcolor$/i.test(raw) ? fallbackCurrentColor ?? raw : raw;
845
+ const parsed = parseColorValue(resolved);
846
+ if (parsed.alpha <= 0) {
847
+ return null;
848
+ }
849
+ return parsed;
850
+ }
851
+ function addUniqueColor(colors, color) {
852
+ if (!color) return;
853
+ colors.set(`${color.hex}:${color.alpha}`, color);
854
+ }
855
+ function isTextRenderingFormControl(element) {
856
+ if (element instanceof HTMLTextAreaElement) return true;
857
+ if (element instanceof HTMLSelectElement) return true;
858
+ if (element instanceof HTMLButtonElement) return true;
859
+ if (element instanceof HTMLInputElement) {
860
+ const textlessInputTypes = /* @__PURE__ */ new Set([
861
+ "hidden",
862
+ "checkbox",
863
+ "radio",
864
+ "range",
865
+ "color",
866
+ "file",
867
+ "image"
868
+ ]);
869
+ return !textlessInputTypes.has(element.type.toLowerCase());
870
+ }
871
+ return false;
872
+ }
873
+ function hasRenderableTextNode(element) {
874
+ if (element.isContentEditable) return true;
875
+ if (isTextRenderingFormControl(element)) return true;
876
+ if (!element.textContent?.trim()) return false;
877
+ if (hasDirectNonWhitespaceText(element)) return true;
878
+ const tagName = element.tagName.toLowerCase();
879
+ return TEXT_ELEMENT_TAGS.has(tagName) || element.children.length === 0;
880
+ }
801
881
  function getComputedBoxShadow(element) {
802
882
  const computed = window.getComputedStyle(element);
803
883
  const value = computed.boxShadow.trim();
@@ -811,11 +891,9 @@ function getComputedColorStyles(element) {
811
891
  { style: computed.borderBottomStyle, width: computed.borderBottomWidth, color: computed.borderBottomColor },
812
892
  { style: computed.borderLeftStyle, width: computed.borderLeftWidth, color: computed.borderLeftColor }
813
893
  ];
814
- const visibleBorderSide = borderSides.find(
815
- (side) => side.style !== "none" && side.style !== "hidden" && parseFloat(side.width) > 0
816
- );
894
+ const visibleBorderSide = borderSides.find((side) => isVisibleBorderSide(side));
817
895
  const hasBorder = Boolean(visibleBorderSide);
818
- const hasOutline = computed.outlineStyle !== "none" && parseFloat(computed.outlineWidth) > 0;
896
+ const hasOutline = hasVisibleOutline(computed);
819
897
  return {
820
898
  backgroundColor: parseColorValue(computed.backgroundColor),
821
899
  color: parseColorValue(computed.color),
@@ -823,6 +901,48 @@ function getComputedColorStyles(element) {
823
901
  outlineColor: hasOutline ? parseColorValue(computed.outlineColor) : TRANSPARENT_COLOR
824
902
  };
825
903
  }
904
+ function getSelectionColors(element) {
905
+ const uniqueColors = /* @__PURE__ */ new Map();
906
+ const queue = [element];
907
+ for (let index = 0; index < queue.length; index++) {
908
+ const node = queue[index];
909
+ const computed = window.getComputedStyle(node);
910
+ if (computed.display === "none") {
911
+ continue;
912
+ }
913
+ const isVisibilityHidden = computed.visibility === "hidden" || computed.visibility === "collapse";
914
+ const currentTextColor = computed.color;
915
+ if (!isVisibilityHidden) {
916
+ addUniqueColor(uniqueColors, parseVisibleColor(computed.backgroundColor));
917
+ if (node instanceof HTMLElement && hasRenderableTextNode(node)) {
918
+ addUniqueColor(uniqueColors, parseVisibleColor(currentTextColor));
919
+ }
920
+ const borderSides = [
921
+ { style: computed.borderTopStyle, width: computed.borderTopWidth, color: computed.borderTopColor },
922
+ { style: computed.borderRightStyle, width: computed.borderRightWidth, color: computed.borderRightColor },
923
+ { style: computed.borderBottomStyle, width: computed.borderBottomWidth, color: computed.borderBottomColor },
924
+ { style: computed.borderLeftStyle, width: computed.borderLeftWidth, color: computed.borderLeftColor }
925
+ ];
926
+ for (const side of borderSides) {
927
+ if (!isVisibleBorderSide(side)) continue;
928
+ addUniqueColor(uniqueColors, parseVisibleColor(side.color, currentTextColor));
929
+ }
930
+ if (hasVisibleOutline(computed)) {
931
+ addUniqueColor(uniqueColors, parseVisibleColor(computed.outlineColor, currentTextColor));
932
+ }
933
+ if (node instanceof SVGElement) {
934
+ const fillColor = parseVisibleColor(computed.getPropertyValue("fill"), currentTextColor) ?? parseVisibleColor(node.getAttribute("fill") ?? "", currentTextColor);
935
+ const strokeColor = parseVisibleColor(computed.getPropertyValue("stroke"), currentTextColor) ?? parseVisibleColor(node.getAttribute("stroke") ?? "", currentTextColor);
936
+ addUniqueColor(uniqueColors, fillColor);
937
+ addUniqueColor(uniqueColors, strokeColor);
938
+ }
939
+ }
940
+ for (const child of node.children) {
941
+ queue.push(child);
942
+ }
943
+ }
944
+ return Array.from(uniqueColors.values());
945
+ }
826
946
  function getAllComputedStyles(element) {
827
947
  const { spacing, borderRadius, flex } = getComputedStyles(element);
828
948
  return {
@@ -870,7 +990,7 @@ function getElementInfo(element) {
870
990
  classList: Array.from(element.classList),
871
991
  isFlexContainer: isFlexContainer2,
872
992
  isFlexItem,
873
- isTextElement: isTextElement(element),
993
+ isTextElement: isTextElement2(element),
874
994
  parentElement,
875
995
  hasChildren: element.children.length > 0
876
996
  };
@@ -910,9 +1030,8 @@ function isFitSizing(element, dimension) {
910
1030
  return false;
911
1031
  }
912
1032
  function getDimensionDisplay(element) {
913
- const rect = element.getBoundingClientRect();
914
- const width = Math.round(rect.width);
915
- const height = Math.round(rect.height);
1033
+ const width = Math.round(element.offsetWidth);
1034
+ const height = Math.round(element.offsetHeight);
916
1035
  const widthIsFit = isFitSizing(element, "width");
917
1036
  const heightIsFit = isFitSizing(element, "height");
918
1037
  return {
@@ -945,8 +1064,9 @@ function calculateParentMeasurements(element, container) {
945
1064
  parentInnerRight = paddingBoxRight - (parseFloat(parentStyles.paddingRight) || 0);
946
1065
  parentInnerBottom = paddingBoxBottom - (parseFloat(parentStyles.paddingBottom) || 0);
947
1066
  }
1067
+ const zoom = getZoomScale();
948
1068
  const measurements = [];
949
- const topDistance = Math.round(elementRect.top - parentInnerTop);
1069
+ const topDistance = Math.round((elementRect.top - parentInnerTop) / zoom);
950
1070
  if (topDistance > 0) {
951
1071
  const midX = elementRect.left + elementRect.width / 2;
952
1072
  measurements.push({
@@ -959,7 +1079,7 @@ function calculateParentMeasurements(element, container) {
959
1079
  labelPosition: { x: midX, y: (parentInnerTop + elementRect.top) / 2 }
960
1080
  });
961
1081
  }
962
- const bottomDistance = Math.round(parentInnerBottom - elementRect.bottom);
1082
+ const bottomDistance = Math.round((parentInnerBottom - elementRect.bottom) / zoom);
963
1083
  if (bottomDistance > 0) {
964
1084
  const midX = elementRect.left + elementRect.width / 2;
965
1085
  measurements.push({
@@ -972,7 +1092,7 @@ function calculateParentMeasurements(element, container) {
972
1092
  labelPosition: { x: midX, y: (elementRect.bottom + parentInnerBottom) / 2 }
973
1093
  });
974
1094
  }
975
- const leftDistance = Math.round(elementRect.left - parentInnerLeft);
1095
+ const leftDistance = Math.round((elementRect.left - parentInnerLeft) / zoom);
976
1096
  if (leftDistance > 0) {
977
1097
  const midY = elementRect.top + elementRect.height / 2;
978
1098
  measurements.push({
@@ -985,7 +1105,7 @@ function calculateParentMeasurements(element, container) {
985
1105
  labelPosition: { x: (parentInnerLeft + elementRect.left) / 2, y: midY }
986
1106
  });
987
1107
  }
988
- const rightDistance = Math.round(parentInnerRight - elementRect.right);
1108
+ const rightDistance = Math.round((parentInnerRight - elementRect.right) / zoom);
989
1109
  if (rightDistance > 0) {
990
1110
  const midY = elementRect.top + elementRect.height / 2;
991
1111
  measurements.push({
@@ -1003,6 +1123,7 @@ function calculateParentMeasurements(element, container) {
1003
1123
  function calculateElementMeasurements(from, to) {
1004
1124
  const fromRect = from.getBoundingClientRect();
1005
1125
  const toRect = to.getBoundingClientRect();
1126
+ const zoom = getZoomScale();
1006
1127
  const measurements = [];
1007
1128
  const horizontalOverlap = fromRect.left < toRect.right && fromRect.right > toRect.left;
1008
1129
  const verticalOverlap = fromRect.top < toRect.bottom && fromRect.bottom > toRect.top;
@@ -1011,7 +1132,7 @@ function calculateElementMeasurements(from, to) {
1011
1132
  const overlapBottom = Math.min(fromRect.bottom, toRect.bottom);
1012
1133
  const midY = (overlapTop + overlapBottom) / 2;
1013
1134
  if (fromRect.right <= toRect.left) {
1014
- const distance = Math.round(toRect.left - fromRect.right);
1135
+ const distance = Math.round((toRect.left - fromRect.right) / zoom);
1015
1136
  measurements.push({
1016
1137
  direction: "horizontal",
1017
1138
  x1: fromRect.right,
@@ -1022,7 +1143,7 @@ function calculateElementMeasurements(from, to) {
1022
1143
  labelPosition: { x: (fromRect.right + toRect.left) / 2, y: midY }
1023
1144
  });
1024
1145
  } else if (fromRect.left >= toRect.right) {
1025
- const distance = Math.round(fromRect.left - toRect.right);
1146
+ const distance = Math.round((fromRect.left - toRect.right) / zoom);
1026
1147
  measurements.push({
1027
1148
  direction: "horizontal",
1028
1149
  x1: toRect.right,
@@ -1039,7 +1160,7 @@ function calculateElementMeasurements(from, to) {
1039
1160
  const overlapRight = Math.min(fromRect.right, toRect.right);
1040
1161
  const midX = (overlapLeft + overlapRight) / 2;
1041
1162
  if (fromRect.bottom <= toRect.top) {
1042
- const distance = Math.round(toRect.top - fromRect.bottom);
1163
+ const distance = Math.round((toRect.top - fromRect.bottom) / zoom);
1043
1164
  measurements.push({
1044
1165
  direction: "vertical",
1045
1166
  x1: midX,
@@ -1050,7 +1171,7 @@ function calculateElementMeasurements(from, to) {
1050
1171
  labelPosition: { x: midX, y: (fromRect.bottom + toRect.top) / 2 }
1051
1172
  });
1052
1173
  } else if (fromRect.top >= toRect.bottom) {
1053
- const distance = Math.round(fromRect.top - toRect.bottom);
1174
+ const distance = Math.round((fromRect.top - toRect.bottom) / zoom);
1054
1175
  measurements.push({
1055
1176
  direction: "vertical",
1056
1177
  x1: midX,
@@ -1067,7 +1188,7 @@ function calculateElementMeasurements(from, to) {
1067
1188
  const fromCenterY = fromRect.top + fromRect.height / 2;
1068
1189
  const toCenterX = toRect.left + toRect.width / 2;
1069
1190
  const toCenterY = toRect.top + toRect.height / 2;
1070
- const hDistance = toCenterX > fromCenterX ? Math.round(toRect.left - fromRect.right) : Math.round(fromRect.left - toRect.right);
1191
+ const hDistance = toCenterX > fromCenterX ? Math.round((toRect.left - fromRect.right) / zoom) : Math.round((fromRect.left - toRect.right) / zoom);
1071
1192
  if (hDistance > 0) {
1072
1193
  const startX = toCenterX > fromCenterX ? fromRect.right : fromRect.left;
1073
1194
  const endX = toCenterX > fromCenterX ? toRect.left : toRect.right;
@@ -1082,7 +1203,7 @@ function calculateElementMeasurements(from, to) {
1082
1203
  labelPosition: { x: (startX + endX) / 2, y }
1083
1204
  });
1084
1205
  }
1085
- const vDistance = toCenterY > fromCenterY ? Math.round(toRect.top - fromRect.bottom) : Math.round(fromRect.top - toRect.bottom);
1206
+ const vDistance = toCenterY > fromCenterY ? Math.round((toRect.top - fromRect.bottom) / zoom) : Math.round((fromRect.top - toRect.bottom) / zoom);
1086
1207
  if (vDistance > 0) {
1087
1208
  const x = (fromCenterX + toCenterX) / 2;
1088
1209
  const startY = toCenterY > fromCenterY ? fromRect.bottom : fromRect.top;
@@ -1103,70 +1224,77 @@ function calculateElementMeasurements(from, to) {
1103
1224
  var GUIDELINE_PROXIMITY = 80;
1104
1225
  function calculateGuidelineMeasurements(element, guidelines, mousePosition) {
1105
1226
  if (guidelines.length === 0) return [];
1227
+ const snap = getCanvasSnapshot();
1228
+ const zoom = snap.active ? snap.zoom : 1;
1106
1229
  const rect = element.getBoundingClientRect();
1107
- const scrollX = window.scrollX;
1108
- const scrollY = window.scrollY;
1109
1230
  const measurements = [];
1110
1231
  for (const g of guidelines) {
1232
+ let viewportPos;
1233
+ if (snap.active) {
1234
+ const pan = g.orientation === "horizontal" ? snap.panY : snap.panX;
1235
+ const bo = g.orientation === "horizontal" ? getBodyOffset().y : getBodyOffset().x;
1236
+ viewportPos = bo + (g.position - bo + pan) * zoom;
1237
+ } else {
1238
+ const scroll = g.orientation === "horizontal" ? window.scrollY : window.scrollX;
1239
+ viewportPos = g.position - scroll;
1240
+ }
1111
1241
  if (g.orientation === "horizontal") {
1112
- const gy = g.position - scrollY;
1113
1242
  const midX = rect.left + rect.width / 2;
1114
- if (mousePosition && Math.abs(mousePosition.y - gy) > GUIDELINE_PROXIMITY) continue;
1115
- if (gy < rect.top) {
1116
- const distance = Math.round(rect.top - gy);
1243
+ if (mousePosition && Math.abs(mousePosition.y - viewportPos) > GUIDELINE_PROXIMITY) continue;
1244
+ if (viewportPos < rect.top) {
1245
+ const distance = Math.round((rect.top - viewportPos) / zoom);
1117
1246
  if (distance > 0) {
1118
1247
  measurements.push({
1119
1248
  direction: "vertical",
1120
1249
  x1: midX,
1121
- y1: gy,
1250
+ y1: viewportPos,
1122
1251
  x2: midX,
1123
1252
  y2: rect.top,
1124
1253
  distance,
1125
- labelPosition: { x: midX, y: (gy + rect.top) / 2 }
1254
+ labelPosition: { x: midX, y: (viewportPos + rect.top) / 2 }
1126
1255
  });
1127
1256
  }
1128
- } else if (gy > rect.bottom) {
1129
- const distance = Math.round(gy - rect.bottom);
1257
+ } else if (viewportPos > rect.bottom) {
1258
+ const distance = Math.round((viewportPos - rect.bottom) / zoom);
1130
1259
  if (distance > 0) {
1131
1260
  measurements.push({
1132
1261
  direction: "vertical",
1133
1262
  x1: midX,
1134
1263
  y1: rect.bottom,
1135
1264
  x2: midX,
1136
- y2: gy,
1265
+ y2: viewportPos,
1137
1266
  distance,
1138
- labelPosition: { x: midX, y: (rect.bottom + gy) / 2 }
1267
+ labelPosition: { x: midX, y: (rect.bottom + viewportPos) / 2 }
1139
1268
  });
1140
1269
  }
1141
1270
  }
1142
1271
  } else {
1143
- const gx = g.position - scrollX;
1144
1272
  const midY = rect.top + rect.height / 2;
1145
- if (mousePosition && Math.abs(mousePosition.x - gx) > GUIDELINE_PROXIMITY) continue;
1146
- if (gx < rect.left) {
1147
- const distance = Math.round(rect.left - gx);
1273
+ if (mousePosition && Math.abs(mousePosition.x - viewportPos) > GUIDELINE_PROXIMITY) continue;
1274
+ if (viewportPos < rect.left) {
1275
+ const distance = Math.round((rect.left - viewportPos) / zoom);
1148
1276
  if (distance > 0) {
1149
1277
  measurements.push({
1150
1278
  direction: "horizontal",
1151
- x1: gx,
1279
+ x1: viewportPos,
1152
1280
  y1: midY,
1153
1281
  x2: rect.left,
1154
1282
  y2: midY,
1155
1283
  distance,
1156
- labelPosition: { x: (gx + rect.left) / 2, y: midY }
1284
+ labelPosition: { x: (viewportPos + rect.left) / 2, y: midY }
1157
1285
  });
1158
1286
  }
1159
- } else if (gx > rect.right) {
1160
- const distance = Math.round(gx - rect.right);
1287
+ } else if (viewportPos > rect.right) {
1288
+ const distance = Math.round((viewportPos - rect.right) / zoom);
1161
1289
  if (distance > 0) {
1162
1290
  measurements.push({
1163
1291
  direction: "horizontal",
1164
1292
  x1: rect.right,
1165
1293
  y1: midY,
1166
- x2: gx,
1294
+ x2: viewportPos,
1167
1295
  y2: midY,
1168
1296
  distance,
1169
- labelPosition: { x: (rect.right + gx) / 2, y: midY }
1297
+ labelPosition: { x: (rect.right + viewportPos) / 2, y: midY }
1170
1298
  });
1171
1299
  }
1172
1300
  }
@@ -1182,6 +1310,10 @@ function getFlexDirection(element) {
1182
1310
  const computed = window.getComputedStyle(element);
1183
1311
  return computed.flexDirection;
1184
1312
  }
1313
+ function isInFlowChild(el) {
1314
+ const cs = window.getComputedStyle(el);
1315
+ return cs.display !== "none" && cs.position !== "absolute" && cs.position !== "fixed";
1316
+ }
1185
1317
  function detectChildrenDirection(container, exclude) {
1186
1318
  const computed = window.getComputedStyle(container);
1187
1319
  if (computed.display === "flex" || computed.display === "inline-flex") {
@@ -1194,8 +1326,7 @@ function detectChildrenDirection(container, exclude) {
1194
1326
  const visible = [];
1195
1327
  for (const c of container.children) {
1196
1328
  if (!(c instanceof HTMLElement) || c === exclude) continue;
1197
- const cs = window.getComputedStyle(c);
1198
- if (cs.display === "none" || cs.position === "absolute" || cs.position === "fixed") continue;
1329
+ if (!isInFlowChild(c)) continue;
1199
1330
  visible.push(c);
1200
1331
  if (visible.length === 2) break;
1201
1332
  }
@@ -1208,6 +1339,29 @@ function detectChildrenDirection(container, exclude) {
1208
1339
  }
1209
1340
  return { axis: "vertical", reversed: second.bottom < first.top };
1210
1341
  }
1342
+ function computeIntendedIndex(parent, draggedElement) {
1343
+ const { axis } = detectChildrenDirection(parent, draggedElement);
1344
+ const isHorizontal = axis === "horizontal";
1345
+ const draggedRect = draggedElement.getBoundingClientRect();
1346
+ const intendedCenter = isHorizontal ? draggedRect.left + draggedRect.width / 2 : draggedRect.top + draggedRect.height / 2;
1347
+ const siblings = [];
1348
+ for (const c of parent.children) {
1349
+ if (!(c instanceof HTMLElement) || c === draggedElement) continue;
1350
+ if (!isInFlowChild(c)) continue;
1351
+ siblings.push(c);
1352
+ }
1353
+ if (siblings.length === 0) {
1354
+ return { index: 0, siblingBefore: null, siblingAfter: null };
1355
+ }
1356
+ for (let i = 0; i < siblings.length; i++) {
1357
+ const rect = siblings[i].getBoundingClientRect();
1358
+ const midpoint = isHorizontal ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
1359
+ if (intendedCenter < midpoint) {
1360
+ return { index: i, siblingBefore: i > 0 ? siblings[i - 1] : null, siblingAfter: siblings[i] };
1361
+ }
1362
+ }
1363
+ return { index: siblings.length, siblingBefore: siblings[siblings.length - 1], siblingAfter: null };
1364
+ }
1211
1365
  function htmlChildren(el) {
1212
1366
  return Array.from(el.children).filter(
1213
1367
  (child) => child instanceof HTMLElement
@@ -1366,6 +1520,22 @@ function findContainerAtPoint(x, y, exclude, preferredParent) {
1366
1520
  }
1367
1521
  return findContainerViaTraversal(x, y, exclude);
1368
1522
  }
1523
+ function findLayoutContainerAtPoint(x, y, exclude, preferredParent) {
1524
+ const host = document.querySelector("[data-direct-edit-host]");
1525
+ if (host) host.style.display = "none";
1526
+ const elements = document.elementsFromPoint(x, y);
1527
+ if (host) host.style.display = "";
1528
+ for (const el of elements) {
1529
+ if (skipElement(el, exclude)) continue;
1530
+ if (isLayoutContainer(el)) return el;
1531
+ }
1532
+ if (preferredParent && isLayoutContainer(preferredParent)) {
1533
+ for (const el of elements) {
1534
+ if (el === preferredParent) return preferredParent;
1535
+ }
1536
+ }
1537
+ return null;
1538
+ }
1369
1539
  function calculateDropPosition(container, pointerX, pointerY, draggedElement) {
1370
1540
  const { axis, reversed: isReversed } = detectChildrenDirection(container, draggedElement);
1371
1541
  const isHorizontal = axis === "horizontal";
@@ -1760,7 +1930,18 @@ function getReactComponentStack(element) {
1760
1930
  return getRenderStack(fiber);
1761
1931
  }
1762
1932
  function getElementDisplayName(element) {
1763
- return element.tagName.toLowerCase();
1933
+ const tag = element.tagName.toLowerCase();
1934
+ if (element.id) return `${tag}#${element.id}`;
1935
+ const firstClass = Array.from(element.classList).find((c) => c && !c.startsWith("direct-edit"));
1936
+ if (firstClass) return `${tag}.${firstClass}`;
1937
+ return tag;
1938
+ }
1939
+ function getChildBriefInfo(element) {
1940
+ const name = getElementDisplayName(element);
1941
+ const raw = ((element.innerText || element.textContent) ?? "").replace(/\s+/g, " ").trim();
1942
+ const textPreview = raw.length > 40 ? `${raw.slice(0, 37)}...` : raw;
1943
+ const source = getElementSource(element);
1944
+ return { name, textPreview, source };
1764
1945
  }
1765
1946
  var STABLE_ATTRIBUTES = ["data-testid", "data-qa", "data-cy", "aria-label", "role"];
1766
1947
  var MAX_SELECTOR_DEPTH = 24;
@@ -2048,26 +2229,28 @@ function parseDomSource(element) {
2048
2229
  }
2049
2230
  return { file, line, column };
2050
2231
  }
2051
- function getElementLocator(element) {
2052
- const elementInfo = getElementInfo(element);
2053
- let domSource = parseDomSource(element);
2054
- if (!domSource) {
2055
- const seenFibers = /* @__PURE__ */ new Set();
2056
- let fiber = getFiberForElement(element);
2057
- while (fiber && !seenFibers.has(fiber)) {
2058
- seenFibers.add(fiber);
2059
- const fiberSource = getSourceFromFiber(fiber);
2060
- if (fiberSource?.fileName) {
2061
- domSource = {
2062
- file: fiberSource.fileName,
2063
- line: fiberSource.lineNumber,
2064
- column: fiberSource.columnNumber
2065
- };
2066
- break;
2067
- }
2068
- fiber = fiber._debugOwner ?? fiber.return ?? null;
2232
+ function getElementSource(element) {
2233
+ const domSource = parseDomSource(element);
2234
+ if (domSource) return domSource;
2235
+ const seenFibers = /* @__PURE__ */ new Set();
2236
+ let fiber = getFiberForElement(element);
2237
+ while (fiber && !seenFibers.has(fiber)) {
2238
+ seenFibers.add(fiber);
2239
+ const fiberSource = getSourceFromFiber(fiber);
2240
+ if (fiberSource?.fileName) {
2241
+ return {
2242
+ file: fiberSource.fileName,
2243
+ line: fiberSource.lineNumber,
2244
+ column: fiberSource.columnNumber
2245
+ };
2069
2246
  }
2247
+ fiber = fiber._debugOwner ?? fiber.return ?? null;
2070
2248
  }
2249
+ return null;
2250
+ }
2251
+ function getElementLocator(element) {
2252
+ const elementInfo = getElementInfo(element);
2253
+ const domSource = getElementSource(element);
2071
2254
  return {
2072
2255
  reactStack: getReactComponentStack(element),
2073
2256
  domSelector: buildDomSelector(element),
@@ -2086,7 +2269,7 @@ function getLocatorHeader(locator) {
2086
2269
  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
2270
  return { componentLabel, formattedSource };
2088
2271
  }
2089
- function buildLocatorContextLines(locator) {
2272
+ function buildLocatorContextLines(locator, options) {
2090
2273
  const lines = [];
2091
2274
  const { componentLabel, formattedSource } = getLocatorHeader(locator);
2092
2275
  const target = (locator.targetHtml || locator.domContextHtml || "").trim();
@@ -2099,7 +2282,7 @@ function buildLocatorContextLines(locator) {
2099
2282
  lines.push("target:");
2100
2283
  lines.push(target);
2101
2284
  }
2102
- if (context && context !== target) {
2285
+ if (!options?.skipContext && context && context !== target) {
2103
2286
  lines.push("context:");
2104
2287
  lines.push(context);
2105
2288
  }
@@ -2253,6 +2436,29 @@ function buildEditExport(arg1, arg2, arg3, arg4, arg5, arg6, arg7) {
2253
2436
  }
2254
2437
  return lines.join("\n");
2255
2438
  }
2439
+ function buildEditExportWithOptions(locator, pendingStyles, textEdit, options) {
2440
+ const changes = [];
2441
+ const collapsedStyles = collapseExportShorthands(pendingStyles);
2442
+ for (const [property, value] of Object.entries(collapsedStyles)) {
2443
+ const tailwindClass = stylesToTailwind({ [property]: value });
2444
+ changes.push({ property, value, tailwind: tailwindClass });
2445
+ }
2446
+ const lines = buildLocatorContextLines(locator, options);
2447
+ lines.push("");
2448
+ if (changes.length > 0) {
2449
+ lines.push("edits:");
2450
+ for (const change of changes) {
2451
+ const tailwind = change.tailwind ? ` (${change.tailwind})` : "";
2452
+ lines.push(`${change.property}: ${change.value}${tailwind}`);
2453
+ }
2454
+ }
2455
+ if (textEdit) {
2456
+ lines.push("text content changed:");
2457
+ lines.push(`from: "${textEdit.originalText}"`);
2458
+ lines.push(`to: "${textEdit.newText}"`);
2459
+ }
2460
+ return lines.join("\n");
2461
+ }
2256
2462
  function buildCommentExport(locator, commentText, replies) {
2257
2463
  const lines = buildLocatorContextLines(locator);
2258
2464
  lines.push("");
@@ -2264,55 +2470,554 @@ function buildCommentExport(locator, commentText, replies) {
2264
2470
  }
2265
2471
  return lines.join("\n");
2266
2472
  }
2267
- function formatPosition(siblingBefore, siblingAfter) {
2268
- if (siblingBefore && siblingAfter) return `after <${siblingBefore}>`;
2269
- if (siblingBefore && !siblingAfter) return `after <${siblingBefore}> (last)`;
2270
- if (!siblingBefore && siblingAfter) return `before <${siblingAfter}> (first)`;
2271
- return "(only child)";
2473
+ function normalizeSelector(selector) {
2474
+ const value = selector?.trim();
2475
+ return value && value.length > 0 ? value : null;
2476
+ }
2477
+ function normalizeName(name) {
2478
+ return name?.trim().toLowerCase() || "element";
2272
2479
  }
2273
- function formatMoveSummary(move) {
2274
- const fromPosition = formatPosition(move.fromSiblingBefore, move.fromSiblingAfter);
2275
- const toPosition = formatPosition(move.toSiblingBefore, move.toSiblingAfter);
2276
- if (move.fromParentName === move.toParentName) {
2277
- return `in <${move.toParentName}>, from ${fromPosition} to ${toPosition}`;
2480
+ function buildAnchorRef(name, selector, source) {
2481
+ return {
2482
+ name: name?.trim() || "element",
2483
+ selector: normalizeSelector(selector),
2484
+ source: source?.file ? source : null
2485
+ };
2486
+ }
2487
+ function anchorKey(anchor) {
2488
+ if (!anchor) return "none";
2489
+ const selector = normalizeSelector(anchor.selector);
2490
+ if (selector) return `selector:${selector}`;
2491
+ if (anchor.source?.file) {
2492
+ return `source:${anchor.source.file}:${anchor.source.line ?? 0}:${anchor.source.column ?? 0}`;
2493
+ }
2494
+ return `name:${normalizeName(anchor.name)}`;
2495
+ }
2496
+ function anchorsEqual(a, b) {
2497
+ if (!a && !b) return true;
2498
+ if (!a || !b) return false;
2499
+ const aSelector = normalizeSelector(a.selector);
2500
+ const bSelector = normalizeSelector(b.selector);
2501
+ if (aSelector && bSelector) return aSelector === bSelector;
2502
+ if (a.source?.file && b.source?.file) {
2503
+ return a.source.file === b.source.file && (a.source.line ?? null) === (b.source.line ?? null) && (a.source.column ?? null) === (b.source.column ?? null);
2504
+ }
2505
+ return normalizeName(a.name) === normalizeName(b.name);
2506
+ }
2507
+ function formatAnchorRef(anchor, fallback = "(none)") {
2508
+ if (!anchor) return fallback;
2509
+ const selector = normalizeSelector(anchor.selector);
2510
+ if (selector) return selector;
2511
+ if (anchor.source?.file) return `<${anchor.name}> @ ${formatSourceLocation(anchor.source.file, anchor.source.line, anchor.source.column)}`;
2512
+ return `<${anchor.name}>`;
2513
+ }
2514
+ function buildPlacementRef(before, after) {
2515
+ if (before && after) {
2516
+ return {
2517
+ before,
2518
+ after,
2519
+ description: `between after ${formatAnchorRef(before)} and before ${formatAnchorRef(after)}`
2520
+ };
2278
2521
  }
2279
- return `from <${move.fromParentName}> ${fromPosition} to <${move.toParentName}> ${toPosition}`;
2522
+ if (before) {
2523
+ return {
2524
+ before,
2525
+ after: null,
2526
+ description: `after ${formatAnchorRef(before)}`
2527
+ };
2528
+ }
2529
+ if (after) {
2530
+ return {
2531
+ before: null,
2532
+ after,
2533
+ description: `before ${formatAnchorRef(after)}`
2534
+ };
2535
+ }
2536
+ return {
2537
+ before: null,
2538
+ after: null,
2539
+ description: "as the only child"
2540
+ };
2541
+ }
2542
+ function buildPlacementFromMove(beforeName, beforeSelector, beforeSource, afterName, afterSelector, afterSource) {
2543
+ const before = beforeName || beforeSelector || beforeSource?.file ? buildAnchorRef(beforeName, beforeSelector, beforeSource) : null;
2544
+ const after = afterName || afterSelector || afterSource?.file ? buildAnchorRef(afterName, afterSelector, afterSource) : null;
2545
+ return buildPlacementRef(before, after);
2546
+ }
2547
+ function toRoundedVisualDelta(move) {
2548
+ const delta = move.visualDelta ?? move.positionDelta;
2549
+ if (!delta) return void 0;
2550
+ const rounded = { x: Math.round(delta.x), y: Math.round(delta.y) };
2551
+ return rounded.x === 0 && rounded.y === 0 ? void 0 : rounded;
2552
+ }
2553
+ function hasVisualIntent(move) {
2554
+ return Boolean(toRoundedVisualDelta(move));
2555
+ }
2556
+ function hasStructuralChange(move) {
2557
+ const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
2558
+ const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
2559
+ const fromPlacement = buildPlacementFromMove(
2560
+ move.fromSiblingBefore,
2561
+ move.fromSiblingBeforeSelector,
2562
+ move.fromSiblingBeforeSource,
2563
+ move.fromSiblingAfter,
2564
+ move.fromSiblingAfterSelector,
2565
+ move.fromSiblingAfterSource
2566
+ );
2567
+ const toPlacement = buildPlacementFromMove(
2568
+ move.toSiblingBefore,
2569
+ move.toSiblingBeforeSelector,
2570
+ move.toSiblingBeforeSource,
2571
+ move.toSiblingAfter,
2572
+ move.toSiblingAfterSelector,
2573
+ move.toSiblingAfterSource
2574
+ );
2575
+ if (!anchorsEqual(fromParent, toParent)) return true;
2576
+ if (!anchorsEqual(fromPlacement.before, toPlacement.before)) return true;
2577
+ if (!anchorsEqual(fromPlacement.after, toPlacement.after)) return true;
2578
+ if (typeof move.fromIndex === "number" && typeof move.toIndex === "number" && move.fromIndex !== move.toIndex) return true;
2579
+ return false;
2280
2580
  }
2281
- function formatMoveSelector(selector, fallback) {
2282
- const normalized = selector?.trim();
2283
- return normalized ? normalized : fallback;
2581
+ function isStructuredLayoutContainer(layout) {
2582
+ return layout === "flex" || layout === "grid";
2583
+ }
2584
+ function isExistingFlexWorkflow(move) {
2585
+ const structuralChange = hasStructuralChange(move);
2586
+ if (!structuralChange) return false;
2587
+ const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
2588
+ const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
2589
+ const sameParent = anchorsEqual(fromParent, toParent);
2590
+ const fromLayout = move.fromParentLayout;
2591
+ const toLayout = move.toParentLayout;
2592
+ if (sameParent) {
2593
+ return Boolean(move.mode === "reorder" && (isStructuredLayoutContainer(toLayout) || isStructuredLayoutContainer(fromLayout)));
2594
+ }
2595
+ return Boolean(isStructuredLayoutContainer(fromLayout) && isStructuredLayoutContainer(toLayout));
2596
+ }
2597
+ function classifyMove(move) {
2598
+ const structuralChange = hasStructuralChange(move);
2599
+ const visualIntent = hasVisualIntent(move);
2600
+ if (!structuralChange && !visualIntent) return "noop";
2601
+ if (isExistingFlexWorkflow(move)) return "existing_layout_move";
2602
+ if (move.mode === "free" || move.mode === "position") return "layout_refactor";
2603
+ if (!structuralChange && visualIntent) return "layout_refactor";
2604
+ return "layout_refactor";
2605
+ }
2606
+ function buildNumericClusters(values, tolerance) {
2607
+ if (values.length === 0) return [];
2608
+ const sorted = [...values].sort((a, b) => a - b);
2609
+ const clusters = [{ center: sorted[0], values: [sorted[0]] }];
2610
+ for (let i = 1; i < sorted.length; i++) {
2611
+ const value = sorted[i];
2612
+ const current = clusters[clusters.length - 1];
2613
+ if (Math.abs(value - current.center) <= tolerance) {
2614
+ current.values.push(value);
2615
+ current.center = current.values.reduce((sum, n) => sum + n, 0) / current.values.length;
2616
+ } else {
2617
+ clusters.push({ center: value, values: [value] });
2618
+ }
2619
+ }
2620
+ return clusters;
2284
2621
  }
2285
- function formatMoveSource(source, fallback) {
2286
- if (!source?.file) return fallback;
2287
- return formatSourceLocation(source.file, source.line, source.column);
2622
+ function inferFlexDirection(sameRowCount, sameColumnCount, visualDelta) {
2623
+ if (sameRowCount > sameColumnCount) {
2624
+ return { direction: "row", reason: "Subject aligns with neighboring anchors on the same row." };
2625
+ }
2626
+ if (sameColumnCount > sameRowCount) {
2627
+ return { direction: "column", reason: "Subject aligns with neighboring anchors on the same column." };
2628
+ }
2629
+ if (sameRowCount > 0) {
2630
+ return { direction: "row", reason: "Detected row alignment in final geometry." };
2631
+ }
2632
+ if (sameColumnCount > 0) {
2633
+ return { direction: "column", reason: "Detected column alignment in final geometry." };
2634
+ }
2635
+ const horizontalDominant = Math.abs(visualDelta?.x ?? 0) >= Math.abs(visualDelta?.y ?? 0);
2636
+ return {
2637
+ direction: horizontalDominant ? "row" : "column",
2638
+ reason: "Fell back to movement axis because anchor alignment was ambiguous."
2639
+ };
2288
2640
  }
2289
- function buildMoveExportLines(move) {
2290
- return [
2641
+ function inferLayoutPrescription(edit, operation, reasons) {
2642
+ const parent = edit.element.parentElement;
2643
+ if (!parent || !edit.element.isConnected) {
2644
+ return {
2645
+ recommendedSystem: "flex",
2646
+ intentPatterns: ["no_geometry_context"],
2647
+ refactorSteps: [
2648
+ `Reparent ${formatAnchorRef(operation.subject)} under ${formatAnchorRef(operation.to.parent)} at ${operation.to.placement.description}.`
2649
+ ],
2650
+ styleSteps: [
2651
+ `Convert ${formatAnchorRef(operation.to.parent)} to flex and set a clear primary axis for this relationship.`,
2652
+ "Use `gap` for spacing and keep positioning static."
2653
+ ],
2654
+ itemSteps: [
2655
+ "Remove any inline `left/top/transform` move artifacts from moved elements."
2656
+ ]
2657
+ };
2658
+ }
2659
+ const children = Array.from(parent.children).filter(
2660
+ (node) => node instanceof HTMLElement && isInFlowChild(node) && !node.hasAttribute("data-direct-edit")
2661
+ );
2662
+ const childSnapshots = children.map((child) => {
2663
+ const rect = child.getBoundingClientRect();
2664
+ const locator = getElementLocator(child);
2665
+ const anchor = buildAnchorRef(getElementDisplayName(child), locator.domSelector, locator.domSource);
2666
+ return {
2667
+ child,
2668
+ rect,
2669
+ centerX: rect.left + rect.width / 2,
2670
+ centerY: rect.top + rect.height / 2,
2671
+ anchor,
2672
+ anchorLabel: formatAnchorRef(anchor)
2673
+ };
2674
+ });
2675
+ const subjectSnapshot = childSnapshots.find((snapshot2) => snapshot2.child === edit.element);
2676
+ const subjectRect = edit.element.getBoundingClientRect();
2677
+ const subjectCenterX = subjectRect.left + subjectRect.width / 2;
2678
+ const subjectCenterY = subjectRect.top + subjectRect.height / 2;
2679
+ const rowTolerance = Math.max(8, subjectRect.height * 0.35);
2680
+ const colTolerance = Math.max(8, subjectRect.width * 0.35);
2681
+ const sameRowWith = [];
2682
+ const sameColumnWith = [];
2683
+ const sameRowNodes = [];
2684
+ let aboveAnchor = null;
2685
+ let belowAnchor = null;
2686
+ let bestAboveDistance = Number.POSITIVE_INFINITY;
2687
+ let bestBelowDistance = Number.POSITIVE_INFINITY;
2688
+ for (const node of childSnapshots) {
2689
+ if (node.child === edit.element) continue;
2690
+ if (Math.abs(node.centerY - subjectCenterY) <= rowTolerance) {
2691
+ sameRowWith.push(node.anchorLabel);
2692
+ sameRowNodes.push(node);
2693
+ }
2694
+ if (Math.abs(node.centerX - subjectCenterX) <= colTolerance) {
2695
+ sameColumnWith.push(node.anchorLabel);
2696
+ }
2697
+ const yDelta = node.centerY - subjectCenterY;
2698
+ if (yDelta < 0 && Math.abs(yDelta) < bestAboveDistance) {
2699
+ bestAboveDistance = Math.abs(yDelta);
2700
+ aboveAnchor = node.anchorLabel;
2701
+ }
2702
+ if (yDelta > 0 && yDelta < bestBelowDistance) {
2703
+ bestBelowDistance = yDelta;
2704
+ belowAnchor = node.anchorLabel;
2705
+ }
2706
+ }
2707
+ const rowCenters = childSnapshots.map(({ centerY }) => centerY);
2708
+ const colCenters = childSnapshots.map(({ centerX }) => centerX);
2709
+ const rowClusters = buildNumericClusters(rowCenters, rowTolerance);
2710
+ const colClusters = buildNumericClusters(colCenters, colTolerance);
2711
+ const denseRowClusters = rowClusters.filter((cluster) => cluster.values.length >= 2).length;
2712
+ const denseColClusters = colClusters.filter((cluster) => cluster.values.length >= 2).length;
2713
+ const isTwoDimensional = childSnapshots.length >= 4 && denseRowClusters >= 2 && denseColClusters >= 2;
2714
+ const recommendedSystem = isTwoDimensional ? "grid" : "flex";
2715
+ const intentPatterns = [];
2716
+ if (sameRowWith.length > 0) intentPatterns.push(`same_row_with:${sameRowWith.slice(0, 3).join(", ")}`);
2717
+ if (sameColumnWith.length > 0) intentPatterns.push(`same_column_with:${sameColumnWith.slice(0, 3).join(", ")}`);
2718
+ if (aboveAnchor) intentPatterns.push(`below:${aboveAnchor}`);
2719
+ if (belowAnchor) intentPatterns.push(`above:${belowAnchor}`);
2720
+ if (sameRowWith.length === 0 && sameColumnWith.length === 0) intentPatterns.push("separate_cluster");
2721
+ const visualDelta = operation.visualDelta;
2722
+ const flexDirectionInfo = inferFlexDirection(sameRowWith.length, sameColumnWith.length, visualDelta);
2723
+ const flexDirection = flexDirectionInfo.direction;
2724
+ if (recommendedSystem === "grid") {
2725
+ reasons.push("Detected multiple dense row and column clusters; a 2D layout system is likely intentional.");
2726
+ return {
2727
+ recommendedSystem: "grid",
2728
+ intentPatterns,
2729
+ refactorSteps: [
2730
+ `Create/ensure a shared container around ${formatAnchorRef(operation.subject)} and related anchors under ${formatAnchorRef(operation.to.parent)}.`,
2731
+ `Reorder/reparent elements to satisfy placement ${operation.to.placement.description}.`
2732
+ ],
2733
+ styleSteps: [
2734
+ `Set ${formatAnchorRef(operation.to.parent)} to grid with explicit template rows/columns for the final layout.`,
2735
+ "Use `gap` for consistent spacing and keep placement structural."
2736
+ ],
2737
+ itemSteps: [
2738
+ `Set item alignment on ${formatAnchorRef(operation.subject)} with grid self-alignment (\`justify-self\`/\`align-self\`).`
2739
+ ]
2740
+ };
2741
+ }
2742
+ reasons.push(`${flexDirectionInfo.reason} Use a 1D flex layout instead of literal drag replay.`);
2743
+ let hasStackedCluster = false;
2744
+ const stackedAnchorLabels = /* @__PURE__ */ new Set();
2745
+ if (flexDirection === "row" && subjectSnapshot) {
2746
+ for (const rowPeer of sameRowNodes) {
2747
+ for (const node of childSnapshots) {
2748
+ if (node.child === edit.element || node.child === rowPeer.child) continue;
2749
+ const sameColumnAsPeer = Math.abs(node.centerX - rowPeer.centerX) <= colTolerance;
2750
+ const verticallySeparated = Math.abs(node.centerY - rowPeer.centerY) > rowTolerance;
2751
+ if (sameColumnAsPeer && verticallySeparated) {
2752
+ hasStackedCluster = true;
2753
+ stackedAnchorLabels.add(rowPeer.anchorLabel);
2754
+ stackedAnchorLabels.add(node.anchorLabel);
2755
+ }
2756
+ }
2757
+ }
2758
+ }
2759
+ 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));
2760
+ const refactorSteps = [
2761
+ `Ensure ${formatAnchorRef(operation.subject)} and referenced neighbors share a common container under ${formatAnchorRef(operation.to.parent)}.`,
2762
+ `Reparent/reorder nodes so ${formatAnchorRef(operation.subject)} lands ${operation.to.placement.description}.`
2763
+ ];
2764
+ if (flexDirection === "row" && hasStackedCluster) {
2765
+ const clusterSample = Array.from(stackedAnchorLabels).slice(0, 3).join(", ");
2766
+ refactorSteps.push(`Create a left-side content wrapper for vertically stacked items (${clusterSample}), and keep ${formatAnchorRef(operation.subject)} as the opposite-side sibling.`);
2767
+ }
2768
+ if (hasBelowCluster) {
2769
+ refactorSteps.push("Keep lower content sections in a separate block below the horizontal header row; do not force them into the same row.");
2770
+ }
2771
+ const styleSteps = [
2772
+ `Set ${formatAnchorRef(operation.to.parent)} to flex with direction ${flexDirection}.`,
2773
+ 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.",
2774
+ "Use `gap` for spacing between siblings."
2775
+ ];
2776
+ if (flexDirection === "row" && hasStackedCluster) {
2777
+ styleSteps.push("Set the content wrapper to `display: flex` with `flex-direction: column` and an appropriate vertical gap.");
2778
+ }
2779
+ return {
2780
+ recommendedSystem: "flex",
2781
+ intentPatterns,
2782
+ refactorSteps,
2783
+ styleSteps,
2784
+ itemSteps: [
2785
+ `Apply item-level alignment (\`align-self\` / flex-basis) only when needed for ${formatAnchorRef(operation.subject)}.`,
2786
+ "Do not use absolute positioning, top/left offsets, transforms, or margin hacks to simulate movement."
2787
+ ]
2788
+ };
2789
+ }
2790
+ function buildMoveEntries(edits) {
2791
+ const entries = [];
2792
+ let noopMoveCount = 0;
2793
+ for (const edit of edits) {
2794
+ const move = edit.move;
2795
+ if (!move) continue;
2796
+ const subject = buildAnchorRef(
2797
+ getElementDisplayName(edit.element) || edit.locator.tagName,
2798
+ edit.locator.domSelector,
2799
+ edit.locator.domSource
2800
+ );
2801
+ const fromParent = buildAnchorRef(move.fromParentName, move.fromParentSelector, move.fromParentSource);
2802
+ const toParent = buildAnchorRef(move.toParentName, move.toParentSelector, move.toParentSource);
2803
+ const fromPlacement = buildPlacementFromMove(
2804
+ move.fromSiblingBefore,
2805
+ move.fromSiblingBeforeSelector,
2806
+ move.fromSiblingBeforeSource,
2807
+ move.fromSiblingAfter,
2808
+ move.fromSiblingAfterSelector,
2809
+ move.fromSiblingAfterSource
2810
+ );
2811
+ const toPlacement = buildPlacementFromMove(
2812
+ move.toSiblingBefore,
2813
+ move.toSiblingBeforeSelector,
2814
+ move.toSiblingBeforeSource,
2815
+ move.toSiblingAfter,
2816
+ move.toSiblingAfterSelector,
2817
+ move.toSiblingAfterSource
2818
+ );
2819
+ const reasons = [];
2820
+ const classification = classifyMove(move);
2821
+ if (classification === "noop") {
2822
+ noopMoveCount++;
2823
+ continue;
2824
+ }
2825
+ const interactionMode = move.mode ?? "free";
2826
+ const visualDelta = toRoundedVisualDelta(move);
2827
+ if (visualDelta) {
2828
+ reasons.push(`Non-zero visual delta detected (${visualDelta.x}px, ${visualDelta.y}px).`);
2829
+ }
2830
+ const structuralChange = hasStructuralChange(move);
2831
+ if (structuralChange) reasons.push("Anchor placement changed between source and target.");
2832
+ else reasons.push("No anchor placement change; treating movement as layout intent translation.");
2833
+ const operationBase = {
2834
+ classification,
2835
+ interactionMode,
2836
+ subject,
2837
+ from: { parent: fromParent, placement: fromPlacement },
2838
+ to: { parent: toParent, placement: toPlacement },
2839
+ ...visualDelta ? { visualDelta } : {},
2840
+ confidence: classification === "existing_layout_move" ? "high" : structuralChange ? "medium" : "high",
2841
+ reasons
2842
+ };
2843
+ if (classification === "layout_refactor") {
2844
+ operationBase.layoutPrescription = inferLayoutPrescription(edit, operationBase, reasons);
2845
+ }
2846
+ const sortSource = subject.source?.file ? `${subject.source.file}:${subject.source.line ?? 0}:${subject.source.column ?? 0}` : "";
2847
+ const sortKey = [
2848
+ sortSource,
2849
+ anchorKey(subject),
2850
+ anchorKey(toParent),
2851
+ toPlacement.description
2852
+ ].join("|");
2853
+ entries.push({ edit, operation: operationBase, sortKey });
2854
+ }
2855
+ entries.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
2856
+ return { entries, noopMoveCount };
2857
+ }
2858
+ function buildMovePlanContext(edits, _domContext) {
2859
+ const { entries, noopMoveCount } = buildMoveEntries(edits);
2860
+ if (entries.length === 0) {
2861
+ return {
2862
+ movePlan: null,
2863
+ intentsByEdit: /* @__PURE__ */ new Map(),
2864
+ noopMoveCount
2865
+ };
2866
+ }
2867
+ const operations = [];
2868
+ const intentsByEdit = /* @__PURE__ */ new Map();
2869
+ for (let i = 0; i < entries.length; i++) {
2870
+ const operationId = `op-${i + 1}`;
2871
+ const operation = { operationId, ...entries[i].operation };
2872
+ operations.push(operation);
2873
+ intentsByEdit.set(entries[i].edit, operation);
2874
+ }
2875
+ const affectedContainerMap = /* @__PURE__ */ new Map();
2876
+ for (const operation of operations) {
2877
+ affectedContainerMap.set(anchorKey(operation.from.parent), operation.from.parent);
2878
+ affectedContainerMap.set(anchorKey(operation.to.parent), operation.to.parent);
2879
+ }
2880
+ 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)}.`);
2881
+ const notes = [];
2882
+ if (noopMoveCount > 0) notes.push(`Excluded ${noopMoveCount} no-op move(s).`);
2883
+ if (operations.some((op) => op.classification === "layout_refactor")) {
2884
+ notes.push("Layout refactor operations include best-practice flex/grid prescriptions.");
2885
+ }
2886
+ return {
2887
+ movePlan: {
2888
+ operations,
2889
+ affectedContainers: Array.from(affectedContainerMap.values()),
2890
+ orderingConstraints,
2891
+ notes
2892
+ },
2893
+ intentsByEdit,
2894
+ noopMoveCount
2895
+ };
2896
+ }
2897
+ function buildMovePlan(edits, domContext) {
2898
+ const context = buildMovePlanContext(edits, domContext);
2899
+ return context.movePlan ?? {
2900
+ operations: [],
2901
+ affectedContainers: [],
2902
+ orderingConstraints: [],
2903
+ notes: context.noopMoveCount > 0 ? [`Excluded ${context.noopMoveCount} no-op move(s).`] : []
2904
+ };
2905
+ }
2906
+ function getMoveIntentForEdit(edit, context) {
2907
+ if (!edit.move) return null;
2908
+ if (context?.intentsByEdit.has(edit)) return context.intentsByEdit.get(edit) ?? null;
2909
+ const singleContext = buildMovePlanContext([edit]);
2910
+ return singleContext.intentsByEdit.get(edit) ?? null;
2911
+ }
2912
+ function buildMoveInstructionFromIntent(intent) {
2913
+ if (intent.classification === "existing_layout_move") {
2914
+ return `Apply as a structural move in code: place ${formatAnchorRef(intent.subject)} ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`;
2915
+ }
2916
+ const system = intent.layoutPrescription?.recommendedSystem ?? "flex";
2917
+ return `Treat this as a ${system} layout refactor. Implement the listed structure/style steps in source code instead of drag replay.`;
2918
+ }
2919
+ function formatMoveType(classification) {
2920
+ return classification === "existing_layout_move" ? "structural_move" : "layout_refactor";
2921
+ }
2922
+ function buildMoveExportLines(intent) {
2923
+ const moveType = formatMoveType(intent.classification);
2924
+ const implementationSteps = [];
2925
+ if (intent.classification === "existing_layout_move") {
2926
+ implementationSteps.push(`Reorder/reparent ${formatAnchorRef(intent.subject)} to ${intent.to.placement.description} in ${formatAnchorRef(intent.to.parent)}.`);
2927
+ } else {
2928
+ const prescription = intent.layoutPrescription;
2929
+ if (prescription) {
2930
+ implementationSteps.push(...prescription.refactorSteps);
2931
+ implementationSteps.push(...prescription.styleSteps);
2932
+ implementationSteps.push(...prescription.itemSteps);
2933
+ }
2934
+ }
2935
+ const lines = [
2291
2936
  "moved:",
2292
- `summary: ${formatMoveSummary(move)}`,
2293
- `from_parent_selector: ${formatMoveSelector(move.fromParentSelector, "(unknown)")}`,
2294
- `from_before_selector: ${formatMoveSelector(move.fromSiblingBeforeSelector, "(none)")}`,
2295
- `from_after_selector: ${formatMoveSelector(move.fromSiblingAfterSelector, "(none)")}`,
2296
- `from_parent_source: ${formatMoveSource(move.fromParentSource, "(unknown)")}`,
2297
- `from_before_source: ${formatMoveSource(move.fromSiblingBeforeSource, "(none)")}`,
2298
- `from_after_source: ${formatMoveSource(move.fromSiblingAfterSource, "(none)")}`,
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)")}`
2937
+ `id: ${intent.operationId}`,
2938
+ `type: ${moveType}`,
2939
+ `subject: ${formatAnchorRef(intent.subject, "(unknown)")}`,
2940
+ `parent: ${formatAnchorRef(intent.to.parent, "(unknown)")}`,
2941
+ `current_anchor: ${intent.from.placement.description}`,
2942
+ `target_anchor: ${intent.to.placement.description}`,
2943
+ ...intent.visualDelta ? [`visual_hint: ${intent.visualDelta.x}px horizontal, ${intent.visualDelta.y}px vertical`] : []
2305
2944
  ];
2945
+ if (intent.layoutPrescription) {
2946
+ lines.push(`recommended_layout: ${intent.layoutPrescription.recommendedSystem}`);
2947
+ }
2948
+ lines.push("implementation_steps:");
2949
+ for (const step of implementationSteps) {
2950
+ lines.push(` - ${step}`);
2951
+ }
2952
+ lines.push("guardrails:");
2953
+ lines.push(" - Do not simulate movement with absolute positioning, left/top offsets, transform, or margin hacks.");
2954
+ lines.push(`instruction: ${buildMoveInstructionFromIntent(intent)}`);
2955
+ return lines;
2956
+ }
2957
+ function getExportContentProfile(edits, comments, movePlanOrContext) {
2958
+ const moveOpCount = movePlanOrContext ? "operations" in movePlanOrContext ? movePlanOrContext.operations.length : movePlanOrContext.movePlan?.operations.length ?? 0 : buildMovePlanContext(edits).movePlan?.operations.length ?? 0;
2959
+ return {
2960
+ hasCssEdits: edits.some((e) => Object.keys(e.pendingStyles).length > 0),
2961
+ hasTextEdits: edits.some((e) => e.textEdit != null),
2962
+ hasMoves: moveOpCount > 0,
2963
+ hasComments: comments.length > 0
2964
+ };
2306
2965
  }
2307
- function buildSessionExport(edits, comments = []) {
2966
+ function buildExportInstruction(profile) {
2967
+ const { hasCssEdits, hasTextEdits, hasMoves, hasComments } = profile;
2968
+ if (!hasCssEdits && !hasTextEdits && !hasMoves && !hasComments) return "";
2969
+ if (!hasCssEdits && !hasTextEdits && !hasMoves) {
2970
+ return hasComments ? "Address this feedback on the UI. Use the provided source location and selector to find each element in the codebase." : "";
2971
+ }
2972
+ const parts = [];
2973
+ if (hasCssEdits) parts.push("Apply the CSS changes to the targeted elements using the project's existing styling approach (Tailwind, CSS modules, etc.).");
2974
+ if (hasTextEdits) parts.push("Update the text content as specified.");
2975
+ 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.");
2976
+ if (hasComments) parts.push("Address the comments on the relevant elements.");
2977
+ return `${parts.join(" ")} Use the provided source locations, selectors, and context HTML to locate each element in the codebase.`;
2978
+ }
2979
+ function buildSessionExport(edits, comments = [], options) {
2308
2980
  const blocks = [];
2309
- for (const edit of edits) {
2310
- let block = buildEditExport(edit.locator, edit.pendingStyles, edit.textEdit);
2311
- if (edit.move) {
2312
- block += `
2313
- ${buildMoveExportLines(edit.move).join("\n")}`;
2981
+ const planContext = options?.movePlanContext ?? buildMovePlanContext(edits);
2982
+ const movePlan = planContext.movePlan;
2983
+ const includeMovePlanHeader = options?.includeMovePlanHeader !== false;
2984
+ if (includeMovePlanHeader && movePlan && movePlan.operations.length > 0) {
2985
+ const planLines = [
2986
+ "=== LAYOUT MOVE PLAN ===",
2987
+ `operations: ${movePlan.operations.length}`
2988
+ ];
2989
+ if (movePlan.affectedContainers.length > 0) {
2990
+ planLines.push("containers:");
2991
+ for (const container of movePlan.affectedContainers) {
2992
+ planLines.push(` - ${formatAnchorRef(container, "(unknown)")}`);
2993
+ }
2994
+ }
2995
+ if (movePlan.orderingConstraints.length > 0) {
2996
+ planLines.push("structural_constraints:");
2997
+ for (const constraint of movePlan.orderingConstraints) {
2998
+ planLines.push(` - ${constraint}`);
2999
+ }
3000
+ }
3001
+ if (movePlan.notes.length > 0) {
3002
+ planLines.push("plan_notes:");
3003
+ for (const note of movePlan.notes) {
3004
+ planLines.push(` - ${note}`);
3005
+ }
2314
3006
  }
2315
- blocks.push(block);
3007
+ blocks.push(planLines.join("\n"));
3008
+ }
3009
+ for (const edit of edits) {
3010
+ const moveIntent = getMoveIntentForEdit(edit, planContext);
3011
+ const hasMove = Boolean(moveIntent);
3012
+ const hasStyleOrText = Object.keys(edit.pendingStyles).length > 0 || edit.textEdit != null;
3013
+ if (!hasMove && !hasStyleOrText) continue;
3014
+ const block = hasMove ? buildEditExportWithOptions(edit.locator, edit.pendingStyles, edit.textEdit, { skipContext: true }) : buildEditExport(edit.locator, edit.pendingStyles, edit.textEdit);
3015
+ let moveBlock = "";
3016
+ if (moveIntent) {
3017
+ moveBlock = `
3018
+ ${buildMoveExportLines(moveIntent).join("\n")}`;
3019
+ }
3020
+ blocks.push(block + moveBlock);
2316
3021
  }
2317
3022
  for (const comment of comments) {
2318
3023
  blocks.push(buildCommentExport(comment.locator, comment.text, comment.replies));
@@ -2327,6 +3032,9 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
2327
3032
  buildCommentExport,
2328
3033
  buildEditExport,
2329
3034
  buildElementContext,
3035
+ buildExportInstruction,
3036
+ buildMovePlan,
3037
+ buildMovePlanContext,
2330
3038
  buildSessionExport,
2331
3039
  calculateDropPosition,
2332
3040
  calculateElementMeasurements,
@@ -2338,17 +3046,20 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
2338
3046
  colorPropertyToCSSMap,
2339
3047
  colorToTailwind,
2340
3048
  computeHoverHighlight,
3049
+ computeIntendedIndex,
2341
3050
  detectChildrenDirection,
2342
3051
  detectSizingMode,
2343
3052
  elementFromPointWithoutOverlays,
2344
3053
  ensureDirectTextSpanAtPoint,
2345
3054
  findChildAtPoint,
2346
3055
  findContainerAtPoint,
3056
+ findLayoutContainerAtPoint,
2347
3057
  findTextOwnerAtPoint,
2348
3058
  findTextOwnerByRangeScan,
2349
3059
  flexPropertyToCSSMap,
2350
3060
  formatPropertyValue,
2351
3061
  getAllComputedStyles,
3062
+ getChildBriefInfo,
2352
3063
  getComputedBorderStyles,
2353
3064
  getComputedBoxShadow,
2354
3065
  getComputedColorStyles,
@@ -2359,11 +3070,17 @@ ${buildMoveExportLines(edit.move).join("\n")}`;
2359
3070
  getElementDisplayName,
2360
3071
  getElementInfo,
2361
3072
  getElementLocator,
3073
+ getElementSource,
3074
+ getExportContentProfile,
2362
3075
  getFlexDirection,
3076
+ getMoveIntentForEdit,
2363
3077
  getOriginalInlineStyles,
3078
+ getSelectionColors,
2364
3079
  getSizingValue,
2365
3080
  isFlexContainer,
3081
+ isInFlowChild,
2366
3082
  isInputFocused,
3083
+ isLayoutContainer,
2367
3084
  isTextElement,
2368
3085
  parseColorValue,
2369
3086
  parsePropertyValue,