@spotpatch/runtime 1.3.0 → 1.5.0

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.js CHANGED
@@ -10,45 +10,46 @@ import {
10
10
  MAX_ANNOTATION_TARGETS,
11
11
  MAX_TARGET_INSTRUCTION_CHARACTERS
12
12
  } from "@spotpatch/shared";
13
- function freezeSource(source, code) {
13
+ function freezeSource(source2, code2) {
14
14
  return Object.freeze({
15
- ...source,
16
- ...source.relativePath === void 0 && code !== void 0 ? { relativePath: code.relativePath } : {}
15
+ ...source2,
16
+ ...source2.relativePath === void 0 && code2 !== void 0 ? { relativePath: code2.relativePath } : {}
17
17
  });
18
18
  }
19
- function freezeReact(react) {
19
+ function freezeReact(react2) {
20
20
  return Object.freeze({
21
- ...react,
22
- componentStack: Object.freeze([...react.componentStack]),
23
- ...react.source === void 0 ? {} : { source: freezeSource(react.source) }
21
+ ...react2,
22
+ componentStack: Object.freeze([...react2.componentStack]),
23
+ ...react2.source === void 0 ? {} : { source: freezeSource(react2.source) }
24
24
  });
25
25
  }
26
- function freezeElement(element) {
26
+ function freezeElement(element2) {
27
27
  return Object.freeze({
28
- ...element,
29
- rect: Object.freeze({ ...element.rect })
28
+ ...element2,
29
+ rect: Object.freeze({ ...element2.rect })
30
30
  });
31
31
  }
32
- function freezeStyles(styles) {
32
+ function freezeStyles(styles2) {
33
33
  return Object.freeze({
34
- ...styles,
35
- classNames: Object.freeze([...styles.classNames]),
34
+ ...styles2,
35
+ classNames: Object.freeze([...styles2.classNames]),
36
36
  matchedRules: Object.freeze(
37
- styles.matchedRules.map((rule) => Object.freeze({ ...rule }))
37
+ styles2.matchedRules.map((rule) => Object.freeze({ ...rule }))
38
38
  ),
39
- computed: Object.freeze({ ...styles.computed }),
40
- warnings: Object.freeze([...styles.warnings])
39
+ computed: Object.freeze({ ...styles2.computed }),
40
+ warnings: Object.freeze([...styles2.warnings])
41
41
  });
42
42
  }
43
- function freezeTarget(target) {
43
+ function freezeTarget(target2) {
44
44
  return Object.freeze({
45
- instruction: target.instruction.trim(),
46
- source: freezeSource(target.source, target.code),
47
- react: freezeReact(target.react),
48
- element: freezeElement(target.element),
49
- styles: freezeStyles(target.styles),
50
- ...target.code === void 0 ? {} : { code: Object.freeze({ ...target.code }) },
51
- warnings: Object.freeze([...target.warnings])
45
+ instruction: target2.instruction.trim(),
46
+ ...target2.page === void 0 ? {} : { page: Object.freeze({ ...target2.page }) },
47
+ source: freezeSource(target2.source, target2.code),
48
+ react: freezeReact(target2.react),
49
+ element: freezeElement(target2.element),
50
+ styles: freezeStyles(target2.styles),
51
+ ...target2.code === void 0 ? {} : { code: Object.freeze({ ...target2.code }) },
52
+ warnings: Object.freeze([...target2.warnings])
52
53
  });
53
54
  }
54
55
  function createAnnotation(input) {
@@ -57,7 +58,7 @@ function createAnnotation(input) {
57
58
  `SpotPatch annotations require between 1 and ${String(MAX_ANNOTATION_TARGETS)} targets.`
58
59
  );
59
60
  }
60
- const instructions = input.targets.map((target) => target.instruction.trim());
61
+ const instructions = input.targets.map((target2) => target2.instruction.trim());
61
62
  if (instructions.some(
62
63
  (instruction) => instruction.length === 0 || instruction.length > MAX_TARGET_INSTRUCTION_CHARACTERS
63
64
  ) || instructions.reduce((total, instruction) => total + instruction.length, 0) > MAX_ANNOTATION_INSTRUCTION_CHARACTERS) {
@@ -254,10 +255,10 @@ var MAX_EVENT_LINE_BYTES = 1e5;
254
255
  var MAX_EVENT_STREAM_BYTES = 4e6;
255
256
  var RuntimeApiError = class extends Error {
256
257
  code;
257
- constructor(code) {
258
+ constructor(code2) {
258
259
  super("SpotPatch local API request failed.");
259
260
  this.name = "RuntimeApiError";
260
- this.code = code;
261
+ this.code = code2;
261
262
  }
262
263
  };
263
264
  function isRecord2(value) {
@@ -329,9 +330,9 @@ async function readBoundedText(response, maximumBytes) {
329
330
  reader.releaseLock();
330
331
  }
331
332
  }
332
- function parseJson(text) {
333
+ function parseJson(text2) {
333
334
  try {
334
- return JSON.parse(text);
335
+ return JSON.parse(text2);
335
336
  } catch {
336
337
  throw new RuntimeApiError();
337
338
  }
@@ -374,13 +375,13 @@ function omitBrowserCodeContext(request) {
374
375
  ...request,
375
376
  annotation: {
376
377
  ...request.annotation,
377
- targets: request.annotation.targets.map((target) => ({
378
- instruction: target.instruction,
379
- source: target.source,
380
- react: target.react,
381
- element: target.element,
382
- styles: target.styles,
383
- warnings: target.warnings
378
+ targets: request.annotation.targets.map((target2) => ({
379
+ instruction: target2.instruction,
380
+ source: target2.source,
381
+ react: target2.react,
382
+ element: target2.element,
383
+ styles: target2.styles,
384
+ warnings: target2.warnings
384
385
  }))
385
386
  }
386
387
  };
@@ -671,12 +672,12 @@ function contextCharacterCount(classNames, inlineStyle, rules, computed, warning
671
672
  function stylesheetSource(sheet, baseUrl) {
672
673
  return sheet.href === null ? void 0 : sanitizeUrl(sheet.href, baseUrl);
673
674
  }
674
- function collectMatchedRules(element, rules, source, inheritedConditions, output, warnings) {
675
+ function collectMatchedRules(element2, rules, source2, inheritedConditions, output, warnings) {
675
676
  for (const rule of rules) {
676
677
  if (isStyleRule(rule)) {
677
678
  let matches = false;
678
679
  try {
679
- matches = element.matches(rule.selectorText);
680
+ matches = element2.matches(rule.selectorText);
680
681
  } catch {
681
682
  warnings.add(CSS_COLLECTION_WARNINGS.selector);
682
683
  continue;
@@ -686,7 +687,7 @@ function collectMatchedRules(element, rules, source, inheritedConditions, output
686
687
  Object.freeze({
687
688
  selector: sanitizeCssText(rule.selectorText),
688
689
  declarations: sanitizeCssText(rule.style.cssText),
689
- ...source === void 0 ? {} : { source },
690
+ ...source2 === void 0 ? {} : { source: source2 },
690
691
  ...inheritedConditions.length === 0 ? {} : { media: inheritedConditions.join(" and ") }
691
692
  })
692
693
  );
@@ -696,9 +697,9 @@ function collectMatchedRules(element, rules, source, inheritedConditions, output
696
697
  if (isGroupingRule(rule)) {
697
698
  const condition = groupingCondition(rule);
698
699
  collectMatchedRules(
699
- element,
700
+ element2,
700
701
  rule.cssRules,
701
- source,
702
+ source2,
702
703
  condition === void 0 ? inheritedConditions : [...inheritedConditions, condition],
703
704
  output,
704
705
  warnings
@@ -706,11 +707,11 @@ function collectMatchedRules(element, rules, source, inheritedConditions, output
706
707
  }
707
708
  }
708
709
  }
709
- function collectComputedStyle(element, getComputedStyle) {
710
+ function collectComputedStyle(element2, getComputedStyle) {
710
711
  if (getComputedStyle === void 0) {
711
712
  return {};
712
713
  }
713
- const declaration = getComputedStyle(element);
714
+ const declaration = getComputedStyle(element2);
714
715
  const computed = {};
715
716
  for (const property of COMPUTED_STYLE_PROPERTIES) {
716
717
  const value = sanitizeCssText(declaration.getPropertyValue(property).trim());
@@ -848,11 +849,11 @@ function escapeHtml(value) {
848
849
  function isPreservedAttribute(name) {
849
850
  return SAFE_ATTRIBUTE_NAMES.has(name) || name.startsWith("aria-") || name === "data-testid";
850
851
  }
851
- function openingTag(element) {
852
- const tagName = element.tagName.toLowerCase();
852
+ function openingTag(element2) {
853
+ const tagName = element2.tagName.toLowerCase();
853
854
  const attributes = [];
854
- const baseUrl = element.ownerDocument.baseURI;
855
- for (const attribute of element.attributes) {
855
+ const baseUrl = element2.ownerDocument.baseURI;
856
+ for (const attribute of element2.attributes) {
856
857
  const name = attribute.name.toLowerCase();
857
858
  if (!isPreservedAttribute(name)) {
858
859
  continue;
@@ -867,28 +868,28 @@ function openingTag(element) {
867
868
  function normalizedText(value) {
868
869
  return redactSensitiveText(value).replaceAll(/\s+/gu, " ").trim();
869
870
  }
870
- function serializeElement(element, depth, state, lines) {
871
+ function serializeElement(element2, depth, state, lines) {
871
872
  if (state.nodeCount >= DOM_COLLECTION_LIMITS.maxNodes) {
872
873
  state.truncated = true;
873
874
  return;
874
875
  }
875
876
  state.nodeCount += 1;
876
877
  const indent = " ".repeat(depth);
877
- const tagName = element.tagName.toLowerCase();
878
- lines.push(`${indent}${openingTag(element)}`);
878
+ const tagName = element2.tagName.toLowerCase();
879
+ lines.push(`${indent}${openingTag(element2)}`);
879
880
  if (VOID_ELEMENT_NAMES.has(tagName)) {
880
881
  return;
881
882
  }
882
- for (const child of element.childNodes) {
883
+ for (const child of element2.childNodes) {
883
884
  if (state.nodeCount >= DOM_COLLECTION_LIMITS.maxNodes) {
884
885
  state.truncated = true;
885
886
  break;
886
887
  }
887
888
  if (child.nodeType === 3) {
888
- const text = normalizedText(child.textContent ?? "");
889
- if (text.length > 0) {
889
+ const text2 = normalizedText(child.textContent ?? "");
890
+ if (text2.length > 0) {
890
891
  state.nodeCount += 1;
891
- const truncated = text.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text : `${text.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`;
892
+ const truncated = text2.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text2 : `${text2.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`;
892
893
  lines.push(`${" ".repeat(depth + 1)}${escapeHtml(truncated)}`);
893
894
  }
894
895
  continue;
@@ -907,9 +908,9 @@ function serializeElement(element, depth, state, lines) {
907
908
  }
908
909
  lines.push(`${indent}</${tagName}>`);
909
910
  }
910
- function parentContext(element) {
911
+ function parentContext(element2) {
911
912
  const parents = [];
912
- let parent = element.parentElement;
913
+ let parent = element2.parentElement;
913
914
  while (parent !== null && parents.length < DOM_COLLECTION_LIMITS.maxParentDepth) {
914
915
  parents.push(openingTag(parent));
915
916
  parent = parent.parentElement;
@@ -930,26 +931,26 @@ function escapeCssIdentifier(value) {
930
931
  function escapeCssAttribute(value) {
931
932
  return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
932
933
  }
933
- function selectorSegment(element) {
934
- const tagName = element.tagName.toLowerCase();
935
- if (element.id.length > 0) {
936
- return `${tagName}#${escapeCssIdentifier(element.id)}`;
934
+ function selectorSegment(element2) {
935
+ const tagName = element2.tagName.toLowerCase();
936
+ if (element2.id.length > 0) {
937
+ return `${tagName}#${escapeCssIdentifier(element2.id)}`;
937
938
  }
938
- const testId = element.getAttribute("data-testid");
939
+ const testId = element2.getAttribute("data-testid");
939
940
  if (testId !== null && testId.length > 0) {
940
941
  return `${tagName}[data-testid="${escapeCssAttribute(testId)}"]`;
941
942
  }
942
- const classes = Array.from(element.classList).slice(0, 2).map((name) => `.${escapeCssIdentifier(name)}`).join("");
943
- const sameTagSiblings = element.parentElement === null ? [] : Array.from(element.parentElement.children).filter(
944
- (sibling) => sibling.tagName === element.tagName
943
+ const classes = Array.from(element2.classList).slice(0, 2).map((name) => `.${escapeCssIdentifier(name)}`).join("");
944
+ const sameTagSiblings = element2.parentElement === null ? [] : Array.from(element2.parentElement.children).filter(
945
+ (sibling) => sibling.tagName === element2.tagName
945
946
  );
946
- const position = sameTagSiblings.indexOf(element);
947
+ const position = sameTagSiblings.indexOf(element2);
947
948
  const nth = sameTagSiblings.length > 1 && position >= 0 ? `:nth-of-type(${String(position + 1)})` : "";
948
949
  return `${tagName}${classes}${nth}`;
949
950
  }
950
- function createSelector(element) {
951
+ function createSelector(element2) {
951
952
  const segments = [];
952
- let current = element;
953
+ let current = element2;
953
954
  while (current !== null && segments.length < 5) {
954
955
  const segment = selectorSegment(current);
955
956
  segments.unshift(segment);
@@ -968,15 +969,15 @@ function collectElementContext(options) {
968
969
  if (parents.length > 0) {
969
970
  lines.push("<!-- Parent context: nearest first -->", ...parents);
970
971
  }
971
- const text = normalizedText(options.element.textContent);
972
+ const text2 = normalizedText(options.element.textContent);
972
973
  const rect = options.element.getBoundingClientRect();
973
974
  const role = options.element.getAttribute("role");
974
975
  return Object.freeze({
975
976
  tagName: options.element.tagName.toLowerCase(),
976
977
  selector: createSelector(options.element),
977
978
  sanitizedHtml: truncateContext(lines.join("\n"), options.maxCharacters),
978
- ...text.length === 0 ? {} : {
979
- textPreview: text.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text : `${text.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`
979
+ ...text2.length === 0 ? {} : {
980
+ textPreview: text2.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text2 : `${text2.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`
980
981
  },
981
982
  ...role === null ? {} : { role },
982
983
  rect: Object.freeze({
@@ -995,11 +996,11 @@ function isApplePlatform(platform) {
995
996
  function normalizedKey(key) {
996
997
  return key.toLowerCase();
997
998
  }
998
- function isTextEntryTarget(target) {
999
- if (!(target instanceof Element)) {
999
+ function isTextEntryTarget(target2) {
1000
+ if (!(target2 instanceof Element)) {
1000
1001
  return false;
1001
1002
  }
1002
- return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.closest("[contenteditable]:not([contenteditable='false'])") !== null;
1003
+ return target2 instanceof HTMLInputElement || target2 instanceof HTMLTextAreaElement || target2 instanceof HTMLSelectElement || target2.closest("[contenteditable]:not([contenteditable='false'])") !== null;
1003
1004
  }
1004
1005
  function matchesShortcut(event, shortcut, platform) {
1005
1006
  const tokens = shortcut.split("+").map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0);
@@ -1049,15 +1050,15 @@ function unionRects(rects) {
1049
1050
  height: bottom - top
1050
1051
  });
1051
1052
  }
1052
- function getElementRect(element, view) {
1053
- const display = view.getComputedStyle(element).display;
1053
+ function getElementRect(element2, view) {
1054
+ const display = view.getComputedStyle(element2).display;
1054
1055
  if (display.startsWith("inline")) {
1055
- const lineBoxRect = unionRects(Array.from(element.getClientRects()));
1056
+ const lineBoxRect = unionRects(Array.from(element2.getClientRects()));
1056
1057
  if (lineBoxRect !== void 0) {
1057
1058
  return lineBoxRect;
1058
1059
  }
1059
1060
  }
1060
- return toElementRect(element.getBoundingClientRect());
1061
+ return toElementRect(element2.getBoundingClientRect());
1061
1062
  }
1062
1063
 
1063
1064
  // src/ui/ui-constants.ts
@@ -1068,32 +1069,32 @@ var UI_Z_INDEX = Object.freeze({
1068
1069
  });
1069
1070
 
1070
1071
  // src/picker/hit-test.ts
1071
- function isInsideSpotPatchUI(element) {
1072
- if (element.closest(`[${UI_MARKER_ATTRIBUTE}]`) !== null) {
1072
+ function isInsideSpotPatchUI(element2) {
1073
+ if (element2.closest(`[${UI_MARKER_ATTRIBUTE}]`) !== null) {
1073
1074
  return true;
1074
1075
  }
1075
- const root = element.getRootNode();
1076
+ const root = element2.getRootNode();
1076
1077
  return root instanceof ShadowRoot && root.host.hasAttribute(UI_MARKER_ATTRIBUTE);
1077
1078
  }
1078
- function hasVisibleArea(element, view) {
1079
- const style = view.getComputedStyle(element);
1079
+ function hasVisibleArea(element2, view) {
1080
+ const style = view.getComputedStyle(element2);
1080
1081
  if (style.display === "none" || style.visibility === "hidden") {
1081
1082
  return false;
1082
1083
  }
1083
- const rect = element.getBoundingClientRect();
1084
+ const rect = element2.getBoundingClientRect();
1084
1085
  return rect.width > 0 && rect.height > 0;
1085
1086
  }
1086
- function isDocumentRoot(element) {
1087
- return element.tagName === "HTML" || element.tagName === "BODY";
1087
+ function isDocumentRoot(element2) {
1088
+ return element2.tagName === "HTML" || element2.tagName === "BODY";
1088
1089
  }
1089
1090
  function pickElementAt(document, view, clientX, clientY) {
1090
1091
  const visibleCandidates = document.elementsFromPoint(clientX, clientY).filter(
1091
- (element) => !isInsideSpotPatchUI(element) && hasVisibleArea(element, view)
1092
+ (element2) => !isInsideSpotPatchUI(element2) && hasVisibleArea(element2, view)
1092
1093
  );
1093
- return visibleCandidates.find((element) => !isDocumentRoot(element)) ?? visibleCandidates[0];
1094
+ return visibleCandidates.find((element2) => !isDocumentRoot(element2)) ?? visibleCandidates[0];
1094
1095
  }
1095
- function isSpotPatchUIEventTarget(target) {
1096
- return target instanceof Element && isInsideSpotPatchUI(target);
1096
+ function isSpotPatchUIEventTarget(target2) {
1097
+ return target2 instanceof Element && isInsideSpotPatchUI(target2);
1097
1098
  }
1098
1099
 
1099
1100
  // src/prompt/prompt-composer.ts
@@ -1172,10 +1173,10 @@ function fenced(language, value) {
1172
1173
  ${value}
1173
1174
  ${fence}`;
1174
1175
  }
1175
- function sourceLocation(target, messages) {
1176
- const path = target.code?.relativePath ?? target.source.relativePath;
1177
- const line = target.source.line;
1178
- const column = target.source.column;
1176
+ function sourceLocation(target2, messages) {
1177
+ const path = target2.code?.relativePath ?? target2.source.relativePath;
1178
+ const line = target2.source.line;
1179
+ const column = target2.source.column;
1179
1180
  if (path === void 0) {
1180
1181
  return messages.unavailable;
1181
1182
  }
@@ -1194,10 +1195,10 @@ function renderRule(rule) {
1194
1195
  }`;
1195
1196
  return [...metadata, body].join("\n");
1196
1197
  }
1197
- function renderStyles(target, rules, messages) {
1198
+ function renderStyles(target2, rules, messages) {
1198
1199
  const metadata = [
1199
- target.styles.classNames.length === 0 ? void 0 : `/* classes: ${target.styles.classNames.map(sanitizeCssText).join(" ")} */`,
1200
- target.styles.inlineStyle === void 0 ? void 0 : `/* inline: ${sanitizeCssText(target.styles.inlineStyle)} */`
1200
+ target2.styles.classNames.length === 0 ? void 0 : `/* classes: ${target2.styles.classNames.map(sanitizeCssText).join(" ")} */`,
1201
+ target2.styles.inlineStyle === void 0 ? void 0 : `/* inline: ${sanitizeCssText(target2.styles.inlineStyle)} */`
1201
1202
  ].filter((line) => line !== void 0);
1202
1203
  const blocks = [...metadata, ...rules.map(renderRule)];
1203
1204
  return blocks.length === 0 ? messages.unavailable : fenced("css", blocks.join("\n\n"));
@@ -1210,41 +1211,47 @@ function renderComputed(entries, messages) {
1210
1211
  ).join("\n")
1211
1212
  );
1212
1213
  }
1213
- function renderTarget(target, draft, index, messages) {
1214
+ function renderTarget(target2, draft, index, messages) {
1214
1215
  const reactLines = [
1215
- `- ${messages.adapter}: ${target.react.supported ? messages.supported : messages.unsupported}`,
1216
- ...target.react.version === void 0 ? [] : [`- ${messages.reactVersion}: ${redactSensitiveText(target.react.version)}`],
1217
- ...target.react.componentName === void 0 ? [] : [
1218
- `- ${messages.component}: ${redactSensitiveText(target.react.componentName)}`
1216
+ `- ${messages.adapter}: ${target2.react.supported ? messages.supported : messages.unsupported}`,
1217
+ ...target2.react.version === void 0 ? [] : [`- ${messages.reactVersion}: ${redactSensitiveText(target2.react.version)}`],
1218
+ ...target2.react.componentName === void 0 ? [] : [
1219
+ `- ${messages.component}: ${redactSensitiveText(target2.react.componentName)}`
1219
1220
  ],
1220
1221
  ...draft.reactStack.length === 0 ? [] : [
1221
1222
  `- ${messages.stack}: ${draft.reactStack.map(redactSensitiveText).join(" > ")}`
1222
1223
  ]
1223
1224
  ];
1224
- const code = target.code === void 0 || draft.codeLines.length === 0 ? messages.unavailable : `- ${messages.boundary}: ${target.code.boundary}
1225
+ const code2 = target2.code === void 0 || draft.codeLines.length === 0 ? messages.unavailable : `- ${messages.boundary}: ${target2.code.boundary}
1225
1226
 
1226
1227
  ${fenced(
1227
- target.code.language,
1228
+ target2.code.language,
1228
1229
  redactSensitiveText(draft.codeLines.join("\n"))
1229
1230
  )}`;
1230
1231
  return [
1231
1232
  `### ${messages.target(index + 1)}`,
1232
1233
  `#### ${messages.requestedChange}`,
1233
1234
  draft.instruction,
1235
+ ...target2.page === void 0 ? [] : [
1236
+ `#### ${messages.pageEnvironment}`,
1237
+ `- ${messages.url}: <${sanitizeUrl(target2.page.url, target2.page.url)}>
1238
+ - ${messages.pathname}: ${redactSensitiveText(target2.page.pathname)}
1239
+ - ${messages.title}: ${redactSensitiveText(target2.page.title) || messages.unavailable}`
1240
+ ],
1234
1241
  `#### ${messages.reactContext}`,
1235
1242
  reactLines.join("\n"),
1236
1243
  `#### ${messages.source}`,
1237
- `- ${messages.file}: ${sourceLocation(target, messages)}
1238
- - ${messages.origin}: ${target.source.origin}
1239
- - ${messages.confidence}: ${target.source.confidence}`,
1244
+ `- ${messages.file}: ${sourceLocation(target2, messages)}
1245
+ - ${messages.origin}: ${target2.source.origin}
1246
+ - ${messages.confidence}: ${target2.source.confidence}`,
1240
1247
  `#### ${messages.element}`,
1241
1248
  fenced("html", redactSensitiveText(draft.dom)),
1242
1249
  `#### ${messages.styles}`,
1243
- renderStyles(target, draft.matchedRules, messages),
1250
+ renderStyles(target2, draft.matchedRules, messages),
1244
1251
  `#### ${messages.computedStyles}`,
1245
1252
  renderComputed(draft.computedEntries, messages),
1246
1253
  `#### ${messages.code}`,
1247
- code,
1254
+ code2,
1248
1255
  `#### ${messages.warnings}`,
1249
1256
  draft.warnings.length === 0 ? `- ${messages.none}` : draft.warnings.map((warning) => `- ${redactSensitiveText(warning)}`).join("\n")
1250
1257
  ].join("\n\n");
@@ -1260,19 +1267,19 @@ function renderPrompt(annotation, draft) {
1260
1267
  - ${messages.viewport}: ${String(annotation.page.viewportWidth)} \xD7 ${String(annotation.page.viewportHeight)}
1261
1268
  - ${messages.devicePixelRatio}: ${String(annotation.page.devicePixelRatio)}`,
1262
1269
  `## ${messages.selectedTargets(annotation.targets.length)}`,
1263
- annotation.targets.map((target, index) => {
1270
+ annotation.targets.map((target2, index) => {
1264
1271
  const targetDraft = draft.targets[index];
1265
1272
  if (targetDraft === void 0) {
1266
1273
  throw new RangeError("Prompt target draft is missing.");
1267
1274
  }
1268
- return renderTarget(target, targetDraft, index, messages);
1275
+ return renderTarget(target2, targetDraft, index, messages);
1269
1276
  }).join("\n\n"),
1270
1277
  `## ${messages.modificationRequirements}`,
1271
1278
  messages.modificationConstraint
1272
1279
  ].join("\n\n");
1273
1280
  }
1274
- function shrinkCode(target, draft) {
1275
- if (draft.codeLines.length === 0 || target.code === void 0) {
1281
+ function shrinkCode(target2, draft) {
1282
+ if (draft.codeLines.length === 0 || target2.code === void 0) {
1276
1283
  return false;
1277
1284
  }
1278
1285
  if (draft.codeLines.length === 1) {
@@ -1284,9 +1291,9 @@ function shrinkCode(target, draft) {
1284
1291
  }
1285
1292
  return true;
1286
1293
  }
1287
- const selectedLine = target.source.line ?? target.code.startLine;
1288
- const firstDistance = Math.abs(target.code.startLine - selectedLine);
1289
- const lastLine = target.code.startLine + draft.codeLines.length - 1;
1294
+ const selectedLine = target2.source.line ?? target2.code.startLine;
1295
+ const firstDistance = Math.abs(target2.code.startLine - selectedLine);
1296
+ const lastLine = target2.code.startLine + draft.codeLines.length - 1;
1290
1297
  const lastDistance = Math.abs(lastLine - selectedLine);
1291
1298
  if (lastDistance >= firstDistance) {
1292
1299
  draft.codeLines.pop();
@@ -1295,15 +1302,15 @@ function shrinkCode(target, draft) {
1295
1302
  }
1296
1303
  return true;
1297
1304
  }
1298
- function createTargetDraft(target) {
1305
+ function createTargetDraft(target2) {
1299
1306
  return {
1300
- instruction: redactSensitiveText(target.instruction.trim()),
1301
- reactStack: [...target.react.componentStack],
1302
- dom: target.element.sanitizedHtml,
1303
- matchedRules: [...target.styles.matchedRules],
1304
- computedEntries: Object.entries(target.styles.computed),
1305
- codeLines: target.code?.excerpt.split(/\r?\n/u) ?? [],
1306
- warnings: Array.from(/* @__PURE__ */ new Set([...target.styles.warnings, ...target.warnings]))
1307
+ instruction: redactSensitiveText(target2.instruction.trim()),
1308
+ reactStack: [...target2.react.componentStack],
1309
+ dom: target2.element.sanitizedHtml,
1310
+ matchedRules: [...target2.styles.matchedRules],
1311
+ computedEntries: Object.entries(target2.styles.computed),
1312
+ codeLines: target2.code?.excerpt.split(/\r?\n/u) ?? [],
1313
+ warnings: Array.from(/* @__PURE__ */ new Set([...target2.styles.warnings, ...target2.warnings]))
1307
1314
  };
1308
1315
  }
1309
1316
  function createDraft(annotation) {
@@ -1332,9 +1339,9 @@ function shrinkTargetsFairly(annotation, draft, cursor) {
1332
1339
  for (const operation of operations) {
1333
1340
  for (let offset = 0; offset < annotation.targets.length; offset += 1) {
1334
1341
  const index = (cursor + offset) % annotation.targets.length;
1335
- const target = annotation.targets[index];
1342
+ const target2 = annotation.targets[index];
1336
1343
  const targetDraft = draft.targets[index];
1337
- if (target !== void 0 && targetDraft !== void 0 && operation(target, targetDraft)) {
1344
+ if (target2 !== void 0 && targetDraft !== void 0 && operation(target2, targetDraft)) {
1338
1345
  return (index + 1) % annotation.targets.length;
1339
1346
  }
1340
1347
  }
@@ -1343,15 +1350,15 @@ function shrinkTargetsFairly(annotation, draft, cursor) {
1343
1350
  }
1344
1351
  function renderCompactPrompt(annotation, draft) {
1345
1352
  const messages = PROMPT_MESSAGES[annotation.locale];
1346
- const targets = annotation.targets.map((target, index) => {
1353
+ const targets = annotation.targets.map((target2, index) => {
1347
1354
  const targetDraft = draft.targets[index];
1348
- const component = target.react.componentName;
1355
+ const component = target2.react.componentName;
1349
1356
  return [
1350
1357
  `### ${messages.target(index + 1)}`,
1351
- `- ${messages.requestedChange}: ${targetDraft?.instruction ?? redactSensitiveText(target.instruction)}`,
1352
- `- ${messages.file}: ${sourceLocation(target, messages)}`,
1358
+ `- ${messages.requestedChange}: ${targetDraft?.instruction ?? redactSensitiveText(target2.instruction)}`,
1359
+ `- ${messages.file}: ${sourceLocation(target2, messages)}`,
1353
1360
  ...component === void 0 ? [] : [`- ${messages.component}: ${redactSensitiveText(component)}`],
1354
- `- ${messages.element}: ${redactSensitiveText(openingTag2(targetDraft?.dom ?? target.element.sanitizedHtml, messages.unavailable))}`
1361
+ `- ${messages.element}: ${redactSensitiveText(openingTag2(targetDraft?.dom ?? target2.element.sanitizedHtml, messages.unavailable))}`
1355
1362
  ].join("\n");
1356
1363
  });
1357
1364
  return [
@@ -1409,7 +1416,10 @@ var STATES = Object.freeze({
1409
1416
  });
1410
1417
  var INITIAL_RUNTIME_STATE = STATES.idle;
1411
1418
  function reduceIdle(event) {
1412
- return event.type === "ACTIVATE" ? STATES.inspecting : STATES.idle;
1419
+ if (event.type === "ACTIVATE") {
1420
+ return STATES.inspecting;
1421
+ }
1422
+ return event.type === "RESTORE" ? STATES.selected : STATES.idle;
1413
1423
  }
1414
1424
  function reduceInspecting(event) {
1415
1425
  switch (event.type) {
@@ -1461,6 +1471,121 @@ function reduceRuntimeState(state, event) {
1461
1471
  }
1462
1472
  }
1463
1473
 
1474
+ // src/state/selection-session.ts
1475
+ var STORAGE_PREFIX = "spotpatch:selection:";
1476
+ var SNAPSHOT_VERSION = 1;
1477
+ var MAX_SNAPSHOT_CHARACTERS = 1e6;
1478
+ function record(value) {
1479
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1480
+ }
1481
+ function text(value) {
1482
+ return typeof value === "string";
1483
+ }
1484
+ function optionalText(value) {
1485
+ return value === void 0 || text(value);
1486
+ }
1487
+ function strings(value) {
1488
+ return Array.isArray(value) && value.length <= 256 && value.every(text);
1489
+ }
1490
+ function finiteNumber(value) {
1491
+ return typeof value === "number" && Number.isFinite(value);
1492
+ }
1493
+ function optionalNumber(value) {
1494
+ return value === void 0 || finiteNumber(value);
1495
+ }
1496
+ function source(value) {
1497
+ const item = record(value);
1498
+ return item !== void 0 && text(item.origin) && text(item.confidence) && optionalText(item.fileId) && optionalText(item.relativePath) && optionalNumber(item.line) && optionalNumber(item.column);
1499
+ }
1500
+ function react(value) {
1501
+ const item = record(value);
1502
+ return item !== void 0 && typeof item.supported === "boolean" && optionalText(item.version) && optionalText(item.componentName) && strings(item.componentStack) && (item.source === void 0 || source(item.source));
1503
+ }
1504
+ function page(value) {
1505
+ const item = record(value);
1506
+ return item !== void 0 && text(item.url) && text(item.pathname) && text(item.title) && finiteNumber(item.viewportWidth) && finiteNumber(item.viewportHeight) && finiteNumber(item.devicePixelRatio);
1507
+ }
1508
+ function element(value) {
1509
+ const item = record(value);
1510
+ const rect = record(item?.rect);
1511
+ return item !== void 0 && text(item.tagName) && text(item.selector) && text(item.sanitizedHtml) && optionalText(item.textPreview) && optionalText(item.role) && rect !== void 0 && finiteNumber(rect.x) && finiteNumber(rect.y) && finiteNumber(rect.width) && finiteNumber(rect.height);
1512
+ }
1513
+ function styles(value) {
1514
+ const item = record(value);
1515
+ const computed = record(item?.computed);
1516
+ return item !== void 0 && strings(item.classNames) && optionalText(item.inlineStyle) && Array.isArray(item.matchedRules) && item.matchedRules.length <= 256 && item.matchedRules.every((value2) => {
1517
+ const rule = record(value2);
1518
+ return rule !== void 0 && text(rule.selector) && text(rule.declarations) && optionalText(rule.source) && optionalText(rule.media);
1519
+ }) && computed !== void 0 && Object.values(computed).every(text) && strings(item.warnings);
1520
+ }
1521
+ function code(value) {
1522
+ const item = record(value);
1523
+ return item !== void 0 && text(item.relativePath) && text(item.language) && finiteNumber(item.startLine) && finiteNumber(item.endLine) && text(item.excerpt) && text(item.boundary);
1524
+ }
1525
+ function target(value) {
1526
+ const item = record(value);
1527
+ return item !== void 0 && text(item.id) && text(item.instruction) && page(item.page) && source(item.source) && react(item.react) && element(item.element) && styles(item.styles) && (item.code === void 0 || code(item.code));
1528
+ }
1529
+ function parseSnapshot(value, maximumTargets) {
1530
+ const item = record(value);
1531
+ if (item?.version !== SNAPSHOT_VERSION || typeof item.open !== "boolean" || !Number.isSafeInteger(item.sequence) || Number(item.sequence) < 0 || item.activeTargetId !== void 0 && typeof item.activeTargetId !== "string" || !Array.isArray(item.targets) || item.targets.length === 0 || item.targets.length > maximumTargets || !item.targets.every(target)) {
1532
+ return void 0;
1533
+ }
1534
+ const targets = item.targets;
1535
+ const ids = targets.map(({ id }) => id);
1536
+ if (new Set(ids).size !== ids.length || item.activeTargetId !== void 0 && !ids.includes(item.activeTargetId)) {
1537
+ return void 0;
1538
+ }
1539
+ return {
1540
+ ...item.activeTargetId === void 0 ? {} : { activeTargetId: item.activeTargetId },
1541
+ open: item.open,
1542
+ sequence: Number(item.sequence),
1543
+ targets
1544
+ };
1545
+ }
1546
+ function createSelectionSession(window, sessionId, maximumTargets) {
1547
+ const key = `${STORAGE_PREFIX}${sessionId}`;
1548
+ let storage;
1549
+ try {
1550
+ storage = window.sessionStorage;
1551
+ } catch {
1552
+ storage = void 0;
1553
+ }
1554
+ return Object.freeze({
1555
+ clear() {
1556
+ try {
1557
+ storage?.removeItem(key);
1558
+ } catch {
1559
+ }
1560
+ },
1561
+ load() {
1562
+ let serialized;
1563
+ try {
1564
+ serialized = storage?.getItem(key) ?? null;
1565
+ } catch {
1566
+ return void 0;
1567
+ }
1568
+ if (serialized === null || serialized.length > MAX_SNAPSHOT_CHARACTERS) {
1569
+ return void 0;
1570
+ }
1571
+ try {
1572
+ return parseSnapshot(JSON.parse(serialized), maximumTargets);
1573
+ } catch {
1574
+ return void 0;
1575
+ }
1576
+ },
1577
+ save(snapshot) {
1578
+ try {
1579
+ storage?.setItem(
1580
+ key,
1581
+ JSON.stringify({ version: SNAPSHOT_VERSION, ...snapshot })
1582
+ );
1583
+ } catch {
1584
+ }
1585
+ }
1586
+ });
1587
+ }
1588
+
1464
1589
  // src/source/source-resolver.ts
1465
1590
  import {
1466
1591
  parseSourceMarker,
@@ -1470,11 +1595,11 @@ var EMPTY_REACT_CONTEXT = Object.freeze({
1470
1595
  supported: false,
1471
1596
  componentStack: Object.freeze([])
1472
1597
  });
1473
- function directMarker(element) {
1474
- return parseSourceMarker(element.getAttribute(SOURCE_MARKER_ATTRIBUTE));
1598
+ function directMarker(element2) {
1599
+ return parseSourceMarker(element2.getAttribute(SOURCE_MARKER_ATTRIBUTE));
1475
1600
  }
1476
- function ancestorMarker(element) {
1477
- let ancestor = element.parentElement;
1601
+ function ancestorMarker(element2) {
1602
+ let ancestor = element2.parentElement;
1478
1603
  while (ancestor !== null) {
1479
1604
  const marker = directMarker(ancestor);
1480
1605
  if (marker !== void 0) {
@@ -1496,21 +1621,21 @@ function markerSource(marker, origin, confidence) {
1496
1621
  function unknownSource() {
1497
1622
  return Object.freeze({ origin: "none", confidence: "unknown" });
1498
1623
  }
1499
- function sourceRefToMarker(source) {
1500
- return source.fileId === void 0 || source.line === void 0 || source.column === void 0 ? void 0 : Object.freeze({
1501
- fileId: source.fileId,
1502
- line: source.line,
1503
- column: source.column
1624
+ function sourceRefToMarker(source2) {
1625
+ return source2.fileId === void 0 || source2.line === void 0 || source2.column === void 0 ? void 0 : Object.freeze({
1626
+ fileId: source2.fileId,
1627
+ line: source2.line,
1628
+ column: source2.column
1504
1629
  });
1505
1630
  }
1506
1631
  function createSourceResolver(options) {
1507
1632
  let adapterEnabled = true;
1508
- function inspectReact(element) {
1633
+ function inspectReact(element2) {
1509
1634
  if (!adapterEnabled) {
1510
1635
  return EMPTY_REACT_CONTEXT;
1511
1636
  }
1512
1637
  try {
1513
- return options.adapter.inspect(element);
1638
+ return options.adapter.inspect(element2);
1514
1639
  } catch {
1515
1640
  adapterEnabled = false;
1516
1641
  options.onAdapterError?.();
@@ -1518,26 +1643,26 @@ function createSourceResolver(options) {
1518
1643
  }
1519
1644
  }
1520
1645
  return Object.freeze({
1521
- resolve(element) {
1522
- const marker = directMarker(element);
1523
- const react = inspectReact(element);
1646
+ resolve(element2) {
1647
+ const marker = directMarker(element2);
1648
+ const react2 = inspectReact(element2);
1524
1649
  if (marker !== void 0) {
1525
1650
  return Object.freeze({
1526
- react,
1651
+ react: react2,
1527
1652
  source: markerSource(marker, "jsx-host", "exact")
1528
1653
  });
1529
1654
  }
1530
- if (react.source !== void 0) {
1531
- return Object.freeze({ react, source: react.source });
1655
+ if (react2.source !== void 0) {
1656
+ return Object.freeze({ react: react2, source: react2.source });
1532
1657
  }
1533
- const nearestAncestor = ancestorMarker(element);
1658
+ const nearestAncestor = ancestorMarker(element2);
1534
1659
  if (nearestAncestor !== void 0) {
1535
1660
  return Object.freeze({
1536
- react,
1661
+ react: react2,
1537
1662
  source: markerSource(nearestAncestor, "dom-ancestor", "approximate")
1538
1663
  });
1539
1664
  }
1540
- return Object.freeze({ react, source: unknownSource() });
1665
+ return Object.freeze({ react: react2, source: unknownSource() });
1541
1666
  },
1542
1667
  dispose() {
1543
1668
  adapterEnabled = false;
@@ -1555,9 +1680,9 @@ import {
1555
1680
 
1556
1681
  // src/ui/dom.ts
1557
1682
  function createMarkedElement(document, tagName) {
1558
- const element = document.createElement(tagName);
1559
- element.setAttribute(UI_MARKER_ATTRIBUTE, "");
1560
- return element;
1683
+ const element2 = document.createElement(tagName);
1684
+ element2.setAttribute(UI_MARKER_ATTRIBUTE, "");
1685
+ return element2;
1561
1686
  }
1562
1687
  function createButton(document, label, className = "") {
1563
1688
  const button = createMarkedElement(document, "button");
@@ -2174,68 +2299,81 @@ function createAgentPanel(document, ai, localizer) {
2174
2299
  });
2175
2300
  }
2176
2301
 
2302
+ // src/ui/brand-mark-content.ts
2303
+ var BRAND_MARK_CONTENT = `
2304
+ <defs>
2305
+ <linearGradient id="locator-gradient" x1="76" y1="92" x2="436" y2="374" gradientUnits="userSpaceOnUse">
2306
+ <stop offset="0" stop-color="#B61CFF" />
2307
+ <stop offset="0.38" stop-color="#6D35FF" />
2308
+ <stop offset="0.72" stop-color="#168EFF" />
2309
+ <stop offset="1" stop-color="#00D9E9" />
2310
+ </linearGradient>
2311
+ <linearGradient id="left-code-gradient" x1="165" y1="166" x2="236" y2="258" gradientUnits="userSpaceOnUse">
2312
+ <stop stop-color="#A51EFF" />
2313
+ <stop offset="1" stop-color="#653BFF" />
2314
+ </linearGradient>
2315
+ <linearGradient id="right-code-gradient" x1="276" y1="166" x2="347" y2="258" gradientUnits="userSpaceOnUse">
2316
+ <stop stop-color="#158DFF" />
2317
+ <stop offset="1" stop-color="#00D8E9" />
2318
+ </linearGradient>
2319
+ <linearGradient id="bolt-gradient" x1="270" y1="111" x2="252" y2="365" gradientUnits="userSpaceOnUse">
2320
+ <stop stop-color="#6840FF" />
2321
+ <stop offset="0.48" stop-color="#257BFF" />
2322
+ <stop offset="1" stop-color="#00CBEF" />
2323
+ </linearGradient>
2324
+ </defs>
2325
+ <path
2326
+ fill="url(#locator-gradient)"
2327
+ fill-rule="evenodd"
2328
+ clip-rule="evenodd"
2329
+ d="M256 52C345.47 52 418 124.53 418 214C418 267.55 391.98 316.24 354.04 348.02L256 468L157.96 348.02C120.02 316.24 94 267.55 94 214C94 124.53 166.53 52 256 52ZM256 88C186.41 88 130 144.41 130 214C130 258.2 152.76 297.08 187.2 319.57L256 403.8L324.8 319.57C359.24 297.08 382 258.2 382 214C382 144.41 325.59 88 256 88Z"
2330
+ />
2331
+ <rect x="238" y="20" width="36" height="84" rx="4" fill="url(#locator-gradient)" />
2332
+ <rect x="62" y="196" width="84" height="36" rx="4" fill="url(#locator-gradient)" />
2333
+ <rect x="366" y="196" width="84" height="36" rx="4" fill="url(#locator-gradient)" />
2334
+ <path
2335
+ d="M213.5 160L158 211.5L213.5 263L238 236.5L211 211.5L238 186.5L213.5 160Z"
2336
+ fill="url(#left-code-gradient)"
2337
+ />
2338
+ <path
2339
+ d="M298.5 160L354 211.5L298.5 263L274 236.5L301 211.5L274 186.5L298.5 160Z"
2340
+ fill="url(#right-code-gradient)"
2341
+ />
2342
+ <path
2343
+ d="M283 108L232 212L266 253L238 369L302 237L267 198L283 108Z"
2344
+ fill="url(#bolt-gradient)"
2345
+ />
2346
+ `;
2347
+
2177
2348
  // src/ui/brand-mark.ts
2178
2349
  var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
2350
+ function resolveBrandMarkContent(override) {
2351
+ if (override !== void 0) {
2352
+ return { content: override, viewBox: "0 0 512 512" };
2353
+ }
2354
+ const injected = typeof __SPOTPATCH_BRAND_MARK_CONTENT__ === "string" ? __SPOTPATCH_BRAND_MARK_CONTENT__ : void 0;
2355
+ if (typeof injected === "string") {
2356
+ return { content: injected, viewBox: "0 0 512 512" };
2357
+ }
2358
+ return true ? { content: BRAND_MARK_CONTENT, viewBox: "0 0 512 512" } : { content: FALLBACK_BRAND_MARK_CONTENT, viewBox: "0 0 1 1" };
2359
+ }
2179
2360
  function svgElement(document, tagName, attributes) {
2180
- const element = document.createElementNS(SVG_NAMESPACE, tagName);
2361
+ const element2 = document.createElementNS(SVG_NAMESPACE, tagName);
2181
2362
  for (const [name, value] of Object.entries(attributes)) {
2182
- element.setAttribute(name, value);
2363
+ element2.setAttribute(name, value);
2183
2364
  }
2184
- return element;
2365
+ return element2;
2185
2366
  }
2186
- function createBrandMark(document) {
2367
+ function createBrandMark(document, content) {
2368
+ const resolved = resolveBrandMarkContent(content);
2187
2369
  const svg = svgElement(document, "svg", {
2370
+ xmlns: SVG_NAMESPACE,
2188
2371
  class: "spotpatch-brand-mark",
2189
- viewBox: "0 0 210 208",
2372
+ viewBox: resolved.viewBox,
2190
2373
  "aria-hidden": "true",
2191
2374
  focusable: "false"
2192
2375
  });
2193
- const definitions = svgElement(document, "defs", {});
2194
- const gradient = svgElement(document, "linearGradient", {
2195
- id: "spotpatch-brand-gradient",
2196
- x1: "35",
2197
- y1: "42",
2198
- x2: "184",
2199
- y2: "176",
2200
- gradientUnits: "userSpaceOnUse"
2201
- });
2202
- gradient.append(
2203
- svgElement(document, "stop", { offset: "0", "stop-color": "#7c22ff" }),
2204
- svgElement(document, "stop", { offset: ".52", "stop-color": "#4a69ff" }),
2205
- svgElement(document, "stop", { offset: "1", "stop-color": "#06d9ef" })
2206
- );
2207
- definitions.append(gradient);
2208
- const commonStroke = {
2209
- fill: "none",
2210
- stroke: "url(#spotpatch-brand-gradient)",
2211
- "stroke-linecap": "square",
2212
- "stroke-linejoin": "miter"
2213
- };
2214
- const outer = svgElement(document, "path", {
2215
- d: "M106 42C67 42 43 68 43 105c0 36 24 61 63 96 39-35 63-60 63-96 0-37-24-63-63-63Z",
2216
- ...commonStroke,
2217
- "stroke-width": "14"
2218
- });
2219
- const topStem = svgElement(document, "path", {
2220
- d: "M106 25v42",
2221
- ...commonStroke,
2222
- "stroke-width": "14"
2223
- });
2224
- const sideStems = svgElement(document, "path", {
2225
- d: "M35 109h20m102 0h38",
2226
- ...commonStroke,
2227
- "stroke-width": "13"
2228
- });
2229
- const brackets = svgElement(document, "path", {
2230
- d: "m94 89-18 19 18 17m39-36 18 19-18 17",
2231
- ...commonStroke,
2232
- "stroke-width": "9"
2233
- });
2234
- const bolt = svgElement(document, "path", {
2235
- d: "m119 61-20 45 15 11-13 47 30-56-16-12 4-35Z",
2236
- fill: "url(#spotpatch-brand-gradient)"
2237
- });
2238
- svg.append(definitions, outer, topStem, sideStems, brackets, bolt);
2376
+ svg.innerHTML = resolved.content;
2239
2377
  return svg;
2240
2378
  }
2241
2379
 
@@ -2250,32 +2388,32 @@ function clamp(value, minimum, maximum) {
2250
2388
  function calculateDialogPlacement({
2251
2389
  dialogHeight,
2252
2390
  dialogWidth,
2253
- target,
2391
+ target: target2,
2254
2392
  viewportHeight,
2255
2393
  viewportWidth
2256
2394
  }) {
2257
2395
  const maxLeft = viewportWidth - dialogWidth - VIEWPORT_MARGIN;
2258
2396
  const maxTop = viewportHeight - dialogHeight - VIEWPORT_MARGIN;
2259
- const targetCenterX = target.x + target.width / 2;
2260
- const targetCenterY = target.y + target.height / 2;
2397
+ const targetCenterX = target2.x + target2.width / 2;
2398
+ const targetCenterY = target2.y + target2.height / 2;
2261
2399
  const centeredLeft = clamp(targetCenterX - dialogWidth / 2, VIEWPORT_MARGIN, maxLeft);
2262
2400
  const centeredTop = clamp(targetCenterY - dialogHeight / 2, VIEWPORT_MARGIN, maxTop);
2263
2401
  let left = centeredLeft;
2264
2402
  let top = centeredTop;
2265
2403
  let mode = "viewport";
2266
- if (target.width >= dialogWidth + TARGET_INSET * 2 && target.height >= dialogHeight + TARGET_INSET * 2) {
2404
+ if (target2.width >= dialogWidth + TARGET_INSET * 2 && target2.height >= dialogHeight + TARGET_INSET * 2) {
2267
2405
  mode = "center";
2268
- } else if (target.y - dialogHeight - TARGET_GAP >= VIEWPORT_MARGIN) {
2269
- top = target.y - dialogHeight - TARGET_GAP;
2406
+ } else if (target2.y - dialogHeight - TARGET_GAP >= VIEWPORT_MARGIN) {
2407
+ top = target2.y - dialogHeight - TARGET_GAP;
2270
2408
  mode = "above";
2271
- } else if (target.y + target.height + TARGET_GAP + dialogHeight <= viewportHeight - VIEWPORT_MARGIN) {
2272
- top = target.y + target.height + TARGET_GAP;
2409
+ } else if (target2.y + target2.height + TARGET_GAP + dialogHeight <= viewportHeight - VIEWPORT_MARGIN) {
2410
+ top = target2.y + target2.height + TARGET_GAP;
2273
2411
  mode = "below";
2274
- } else if (target.x + target.width + TARGET_GAP + dialogWidth <= viewportWidth - VIEWPORT_MARGIN) {
2275
- left = target.x + target.width + TARGET_GAP;
2412
+ } else if (target2.x + target2.width + TARGET_GAP + dialogWidth <= viewportWidth - VIEWPORT_MARGIN) {
2413
+ left = target2.x + target2.width + TARGET_GAP;
2276
2414
  mode = "right";
2277
- } else if (target.x - dialogWidth - TARGET_GAP >= VIEWPORT_MARGIN) {
2278
- left = target.x - dialogWidth - TARGET_GAP;
2415
+ } else if (target2.x - dialogWidth - TARGET_GAP >= VIEWPORT_MARGIN) {
2416
+ left = target2.x - dialogWidth - TARGET_GAP;
2279
2417
  mode = "left";
2280
2418
  }
2281
2419
  return Object.freeze({
@@ -2594,7 +2732,7 @@ var UI_MESSAGES = Object.freeze({
2594
2732
  noSelectable: "No selectable element was found.",
2595
2733
  allTargetsRemoved: "All targets were removed. Choose an element to continue.",
2596
2734
  targetRemoved: "Selected target removed.",
2597
- detachedTargetRemoved: "A removed page element was dropped from the selection.",
2735
+ detachedTargetPreserved: "The page element was unloaded; its collected context remains selected.",
2598
2736
  contextWarning: "Browser context collection completed with a warning.",
2599
2737
  contextCollected: "Browser context collected.",
2600
2738
  editorOpening: "Opening source\u2026",
@@ -2730,7 +2868,7 @@ var UI_MESSAGES = Object.freeze({
2730
2868
  noSelectable: "\u5F53\u524D\u4F4D\u7F6E\u6CA1\u6709\u53EF\u9009\u62E9\u7684\u5143\u7D20\u3002",
2731
2869
  allTargetsRemoved: "\u5DF2\u79FB\u9664\u5168\u90E8\u76EE\u6807\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u5143\u7D20\u3002",
2732
2870
  targetRemoved: "\u5DF2\u79FB\u9664\u9009\u4E2D\u76EE\u6807\u3002",
2733
- detachedTargetRemoved: "\u9875\u9762\u4E2D\u7684\u76EE\u6807\u5DF2\u5378\u8F7D\uFF0C\u5DF2\u4ECE\u5F53\u524D\u9009\u62E9\u4E2D\u79FB\u9664\u3002",
2871
+ detachedTargetPreserved: "\u9875\u9762\u5143\u7D20\u5DF2\u5378\u8F7D\uFF0C\u5DF2\u91C7\u96C6\u7684\u4E0A\u4E0B\u6587\u4ECD\u4FDD\u7559\u5728\u9009\u62E9\u4E2D\u3002",
2734
2872
  contextWarning: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\uFF0C\u4F46\u5B58\u5728\u8B66\u544A\u3002",
2735
2873
  contextCollected: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\u3002",
2736
2874
  editorOpening: "\u6B63\u5728\u6253\u5F00\u6E90\u7801\u2026\u2026",
@@ -3625,11 +3763,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3625
3763
  editorFeedback.textContent = state === "opening" ? messages.announcements.editorOpening : state === "success" ? messages.announcements.editorOpened : state === "error" ? messages.announcements.editorFailed : "";
3626
3764
  placeDialog();
3627
3765
  }
3628
- function statusText(target) {
3629
- if (target.status === "ready") {
3766
+ function statusText(target2) {
3767
+ if (target2.status === "ready") {
3630
3768
  return messages.targets.statusReady;
3631
3769
  }
3632
- return target.status === "warning" ? messages.targets.statusPartial : messages.targets.statusCollecting;
3770
+ return target2.status === "warning" ? messages.targets.statusPartial : messages.targets.statusCollecting;
3633
3771
  }
3634
3772
  function instructionInput(targetId) {
3635
3773
  const inputs = targetList.querySelectorAll(
@@ -3646,14 +3784,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3646
3784
  const focusedTargetId = focusedInstruction?.dataset.targetInstructionId;
3647
3785
  const selectionStart = focusedInstruction?.selectionStart;
3648
3786
  const selectionEnd = focusedInstruction?.selectionEnd;
3649
- currentTargets = targets.map((target) => Object.freeze({ ...target }));
3787
+ currentTargets = targets.map((target2) => Object.freeze({ ...target2 }));
3650
3788
  currentMaximum = maximum;
3651
3789
  targetList.replaceChildren();
3652
3790
  const completeCount = targets.filter(
3653
- (target) => target.instruction.trim().length > 0
3791
+ (target2) => target2.instruction.trim().length > 0
3654
3792
  ).length;
3655
3793
  const instructionCharacters = targets.reduce(
3656
- (total, target) => total + target.instruction.trim().length,
3794
+ (total, target2) => total + target2.instruction.trim().length,
3657
3795
  0
3658
3796
  );
3659
3797
  targetCount.textContent = messages.targets.count(targets.length, maximum);
@@ -3673,18 +3811,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3673
3811
  addTargetButton.disabled = !editingEnabled || targets.length >= maximum;
3674
3812
  addTargetButton.title = targets.length >= maximum ? messages.targets.limitTitle(maximum) : messages.targets.addTitle;
3675
3813
  targetLabel.textContent = targets.length === 1 ? targets[0]?.label ?? messages.context.selectedElement : messages.context.selectedCount(targets.length);
3676
- for (const [index, target] of targets.entries()) {
3814
+ for (const [index, target2] of targets.entries()) {
3677
3815
  const item = createMarkedElement(document, "div");
3678
3816
  item.className = "spotpatch-target-item";
3679
- item.dataset.active = String(target.active);
3680
- item.dataset.status = target.status;
3681
- item.dataset.targetId = target.id;
3817
+ item.dataset.active = String(target2.active);
3818
+ item.dataset.status = target2.status;
3819
+ item.dataset.targetId = target2.id;
3682
3820
  const targetSummary = createMarkedElement(document, "div");
3683
3821
  targetSummary.className = "spotpatch-target-summary";
3684
3822
  const select = createButton(document, "", "spotpatch-target-select");
3685
- select.dataset.activateTargetId = target.id;
3823
+ select.dataset.activateTargetId = target2.id;
3686
3824
  select.setAttribute("aria-label", messages.targets.activate(index + 1));
3687
- select.setAttribute("aria-expanded", String(target.active));
3825
+ select.setAttribute("aria-expanded", String(target2.active));
3688
3826
  const number = createMarkedElement(document, "span");
3689
3827
  number.className = "spotpatch-target-index";
3690
3828
  number.textContent = String(index + 1);
@@ -3692,54 +3830,54 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3692
3830
  copy.className = "spotpatch-target-copy";
3693
3831
  const name = createMarkedElement(document, "span");
3694
3832
  name.className = "spotpatch-target-name";
3695
- name.textContent = target.label;
3696
- const source = createMarkedElement(document, "span");
3697
- source.className = "spotpatch-target-source";
3698
- source.textContent = `${statusText(target)} \xB7 ${target.source}`;
3699
- copy.append(name, source);
3700
- const instructionComplete = target.instruction.trim().length > 0;
3833
+ name.textContent = target2.label;
3834
+ const source2 = createMarkedElement(document, "span");
3835
+ source2.className = "spotpatch-target-source";
3836
+ source2.textContent = `${statusText(target2)} \xB7 ${target2.source}`;
3837
+ copy.append(name, source2);
3838
+ const instructionComplete = target2.instruction.trim().length > 0;
3701
3839
  const state = createMarkedElement(document, "span");
3702
3840
  state.className = "spotpatch-target-state";
3703
3841
  state.dataset.complete = String(instructionComplete);
3704
3842
  state.textContent = instructionComplete ? messages.targets.instructionReady : messages.targets.instructionMissing;
3705
3843
  select.append(number, copy, state);
3706
3844
  const open = createButton(document, "\u2197", "spotpatch-target-open");
3707
- open.dataset.openTargetId = target.id;
3708
- open.disabled = !target.canOpenEditor;
3845
+ open.dataset.openTargetId = target2.id;
3846
+ open.disabled = !target2.canOpenEditor;
3709
3847
  open.setAttribute("aria-label", messages.actions.openTarget(index + 1));
3710
3848
  open.title = messages.actions.openTarget(index + 1);
3711
3849
  const remove = createButton(document, "\xD7", "spotpatch-target-remove");
3712
- remove.dataset.removeTargetId = target.id;
3850
+ remove.dataset.removeTargetId = target2.id;
3713
3851
  remove.disabled = !editingEnabled;
3714
3852
  remove.setAttribute("aria-label", messages.targets.remove(index + 1));
3715
3853
  remove.title = messages.targets.removeTitle;
3716
3854
  targetSummary.append(select, open, remove);
3717
3855
  item.append(targetSummary);
3718
- if (target.active) {
3856
+ if (target2.active) {
3719
3857
  const editor = createMarkedElement(document, "label");
3720
3858
  editor.className = "spotpatch-target-editor";
3721
3859
  const editorHead = createMarkedElement(document, "span");
3722
3860
  editorHead.className = "spotpatch-target-editor-head";
3723
3861
  const editorLabel = createMarkedElement(document, "span");
3724
3862
  editorLabel.className = "spotpatch-target-editor-label";
3725
- editorLabel.textContent = messages.targets.instructionLabel(target.label);
3863
+ editorLabel.textContent = messages.targets.instructionLabel(target2.label);
3726
3864
  const characterCount = createMarkedElement(document, "span");
3727
3865
  characterCount.className = "spotpatch-target-editor-count";
3728
3866
  characterCount.textContent = messages.targets.instructionCount(
3729
- target.instruction.length,
3867
+ target2.instruction.length,
3730
3868
  MAX_TARGET_INSTRUCTION_CHARACTERS2
3731
3869
  );
3732
3870
  editorHead.append(editorLabel, characterCount);
3733
3871
  const input = createMarkedElement(document, "textarea");
3734
3872
  input.rows = 4;
3735
3873
  input.maxLength = MAX_TARGET_INSTRUCTION_CHARACTERS2;
3736
- input.value = target.instruction;
3874
+ input.value = target2.instruction;
3737
3875
  input.placeholder = messages.targets.instructionPlaceholder;
3738
3876
  input.disabled = !editingEnabled;
3739
- input.dataset.targetInstructionId = target.id;
3877
+ input.dataset.targetInstructionId = target2.id;
3740
3878
  input.setAttribute(
3741
3879
  "aria-label",
3742
- messages.targets.instructionLabel(target.label)
3880
+ messages.targets.instructionLabel(target2.label)
3743
3881
  );
3744
3882
  editor.append(editorHead, input);
3745
3883
  item.append(editor);
@@ -3756,7 +3894,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3756
3894
  }
3757
3895
  function updateTargetInstruction(targetId, instruction) {
3758
3896
  currentTargets = currentTargets.map(
3759
- (target) => target.id === targetId ? Object.freeze({ ...target, instruction }) : target
3897
+ (target2) => target2.id === targetId ? Object.freeze({ ...target2, instruction }) : target2
3760
3898
  );
3761
3899
  const item = Array.from(
3762
3900
  targetList.querySelectorAll(".spotpatch-target-item")
@@ -3778,11 +3916,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3778
3916
  );
3779
3917
  }
3780
3918
  targetComplete.textContent = messages.targets.complete(
3781
- currentTargets.filter((target) => target.instruction.trim().length > 0).length,
3919
+ currentTargets.filter((target2) => target2.instruction.trim().length > 0).length,
3782
3920
  currentTargets.length
3783
3921
  );
3784
3922
  const instructionCharacters = currentTargets.reduce(
3785
- (total, target) => total + target.instruction.trim().length,
3923
+ (total, target2) => total + target2.instruction.trim().length,
3786
3924
  0
3787
3925
  );
3788
3926
  const instructionLimitExceeded = instructionCharacters > MAX_ANNOTATION_INSTRUCTION_CHARACTERS2;
@@ -3797,18 +3935,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3797
3935
  }
3798
3936
  function showSelectionHighlights(targets) {
3799
3937
  selectionHighlights.replaceChildren();
3800
- activeSelectionRect = targets.find((target) => target.active)?.rect;
3938
+ activeSelectionRect = targets.find((target2) => target2.active)?.rect;
3801
3939
  currentRect = activeSelectionRect;
3802
- for (const [index, target] of targets.entries()) {
3940
+ for (const [index, target2] of targets.entries()) {
3803
3941
  const box = createMarkedElement(document, "div");
3804
3942
  box.className = "spotpatch-selection-highlight";
3805
- box.dataset.targetId = target.id;
3806
- box.dataset.active = String(target.active);
3807
- box.style.transform = `translate(${String(target.rect.x)}px, ${String(target.rect.y)}px)`;
3808
- box.style.width = `${String(target.rect.width)}px`;
3809
- box.style.height = `${String(target.rect.height)}px`;
3943
+ box.dataset.targetId = target2.id;
3944
+ box.dataset.active = String(target2.active);
3945
+ box.style.transform = `translate(${String(target2.rect.x)}px, ${String(target2.rect.y)}px)`;
3946
+ box.style.width = `${String(target2.rect.width)}px`;
3947
+ box.style.height = `${String(target2.rect.height)}px`;
3810
3948
  const label = createMarkedElement(document, "span");
3811
- label.textContent = `${String(index + 1)} \xB7 ${target.label}`;
3949
+ label.textContent = `${String(index + 1)} \xB7 ${target2.label}`;
3812
3950
  box.append(label);
3813
3951
  selectionHighlights.append(box);
3814
3952
  }
@@ -3840,8 +3978,8 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3840
3978
  }
3841
3979
  function updateContextOverview(summaryText) {
3842
3980
  const summaryMessages = messages.summary;
3843
- const source = summaryLine(summaryText, summaryMessages.source);
3844
- sourcePeek.textContent = source ?? messages.diagnostics.noExactSource;
3981
+ const source2 = summaryLine(summaryText, summaryMessages.source);
3982
+ sourcePeek.textContent = source2 ?? messages.diagnostics.noExactSource;
3845
3983
  const browserReady = `${summaryMessages.browserContext}: ${summaryMessages.collectionStatuses.ready}`;
3846
3984
  const browserLoading = `${summaryMessages.browserContext}: ${summaryMessages.collectionStatuses.loading}`;
3847
3985
  const browserFailed = `${summaryMessages.browserContext}: ${summaryMessages.collectionStatuses.failed}`;
@@ -4118,7 +4256,7 @@ function sourceLocation2(resolution, context, messages) {
4118
4256
  function createSelectionSummary(input, messages) {
4119
4257
  const lines = [
4120
4258
  `SpotPatch: ${input.spotPatchVersion}`,
4121
- `Vite: ${input.viteVersion}`,
4259
+ `${input.framework === "next" ? "Next.js" : "Vite"}: ${input.frameworkVersion}`,
4122
4260
  `${messages.source}: ${sourceLocation2(input.resolution, input.code, messages)}`,
4123
4261
  `${messages.confidence}: ${input.resolution.source.confidence} (${messages.confidenceLabels[input.resolution.source.confidence]})`,
4124
4262
  `${messages.origin}: ${input.resolution.source.origin}`
@@ -4552,25 +4690,25 @@ var EMPTY_STYLE_CONTEXT = Object.freeze({
4552
4690
  computed: Object.freeze({}),
4553
4691
  warnings: Object.freeze(["CSS context collection failed."])
4554
4692
  });
4555
- function elementLabel(element) {
4556
- const id = element.id.length > 0 ? `#${element.id}` : "";
4557
- const className = Array.from(element.classList).slice(0, 2).map((name) => `.${name}`).join("");
4558
- return `<${element.tagName.toLowerCase()}${id}${className}>`;
4559
- }
4560
- function sourceLabel(target, unavailable) {
4561
- const path = target.code?.relativePath ?? target.resolution.source.relativePath;
4562
- const line = target.resolution.source.line;
4563
- const column = target.resolution.source.column;
4693
+ function elementLabel(element2) {
4694
+ const id = element2.id.length > 0 ? `#${element2.id}` : "";
4695
+ const className = Array.from(element2.classList).slice(0, 2).map((name) => `.${name}`).join("");
4696
+ return `<${element2.tagName.toLowerCase()}${id}${className}>`;
4697
+ }
4698
+ function sourceLabel(target2, unavailable) {
4699
+ const path = target2.code?.relativePath ?? target2.resolution.source.relativePath;
4700
+ const line = target2.resolution.source.line;
4701
+ const column = target2.resolution.source.column;
4564
4702
  if (path === void 0) {
4565
4703
  return unavailable;
4566
4704
  }
4567
4705
  return `${path}${line === void 0 ? "" : `:${String(line)}`}${column === void 0 ? "" : `:${String(column)}`}`;
4568
4706
  }
4569
- function targetStatus(target) {
4570
- if (target.apiStatus === "failed" || target.collectionStatus === "failed") {
4707
+ function targetStatus(target2) {
4708
+ if (target2.apiStatus === "failed" || target2.collectionStatus === "failed") {
4571
4709
  return "warning";
4572
4710
  }
4573
- if (target.apiStatus === "loading" || target.collectionStatus === "loading") {
4711
+ if (target2.apiStatus === "loading" || target2.collectionStatus === "loading") {
4574
4712
  return "loading";
4575
4713
  }
4576
4714
  return "ready";
@@ -4597,6 +4735,8 @@ function createController(config, dependencies = {}) {
4597
4735
  const clipboard = dependencies.clipboard ?? resolveClipboardWriter(browser.window.navigator);
4598
4736
  const createId = dependencies.createId ?? (() => createBrowserAnnotationId(browser.window));
4599
4737
  const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
4738
+ const selectionSession = dependencies.selectionSession ?? createSelectionSession(browser.window, config.sessionId, config.maxTargets);
4739
+ const restoredSelection = selectionSession.load();
4600
4740
  const sourceResolver = createSourceResolver({
4601
4741
  adapter: dependencies.reactAdapter ?? createReact18Adapter({
4602
4742
  maxComponentDepth: config.budget.maxComponentDepth
@@ -4614,9 +4754,26 @@ function createController(config, dependencies = {}) {
4614
4754
  let mounted = false;
4615
4755
  let animationFrame;
4616
4756
  let lastPointer;
4617
- let targets = [];
4618
- let activeTargetId;
4619
- let targetSequence = 0;
4757
+ let targets = restoredSelection?.targets.map((target2) => {
4758
+ const marker = sourceRefToMarker(target2.source);
4759
+ return {
4760
+ id: target2.id,
4761
+ resolution: Object.freeze({ source: target2.source, react: target2.react }),
4762
+ apiStatus: marker === void 0 ? "not-required" : target2.code === void 0 ? "failed" : "connected",
4763
+ code: target2.code,
4764
+ collectionStatus: "ready",
4765
+ element: void 0,
4766
+ elementContext: target2.element,
4767
+ instruction: target2.instruction,
4768
+ marker,
4769
+ page: target2.page,
4770
+ styles: target2.styles
4771
+ };
4772
+ }) ?? [];
4773
+ let activeTargetId = restoredSelection?.activeTargetId;
4774
+ let targetSequence = restoredSelection?.sequence ?? 0;
4775
+ let selectionOpen = restoredSelection?.open ?? false;
4776
+ let workflowSelectionActive = false;
4620
4777
  let sessionRevision = 0;
4621
4778
  let editorRequestRevision = 0;
4622
4779
  let addingTarget = false;
@@ -4631,50 +4788,83 @@ function createController(config, dependencies = {}) {
4631
4788
  view.renderStatus(state.status);
4632
4789
  }
4633
4790
  function activeTarget() {
4634
- return targets.find((target) => target.id === activeTargetId) ?? targets.at(-1);
4791
+ return targets.find((target2) => target2.id === activeTargetId) ?? targets.at(-1);
4792
+ }
4793
+ function snapshotTarget(target2) {
4794
+ if (target2.elementContext === void 0 || target2.styles === void 0) {
4795
+ return void 0;
4796
+ }
4797
+ return {
4798
+ id: target2.id,
4799
+ instruction: target2.instruction,
4800
+ page: target2.page,
4801
+ source: target2.resolution.source,
4802
+ react: target2.resolution.react,
4803
+ element: target2.elementContext,
4804
+ styles: target2.styles,
4805
+ ...target2.code === void 0 ? {} : { code: target2.code }
4806
+ };
4635
4807
  }
4636
- function targetIsCurrent(target, revision) {
4637
- return mounted && revision === sessionRevision && state.status !== "idle" && targets.includes(target);
4808
+ function persistSelection() {
4809
+ const persistedTargets = targets.flatMap((target2) => {
4810
+ const snapshot = snapshotTarget(target2);
4811
+ return snapshot === void 0 ? [] : [snapshot];
4812
+ });
4813
+ if (persistedTargets.length === 0) {
4814
+ selectionSession.clear();
4815
+ return;
4816
+ }
4817
+ const persistedIds = new Set(persistedTargets.map(({ id }) => id));
4818
+ selectionSession.save({
4819
+ ...activeTargetId !== void 0 && persistedIds.has(activeTargetId) ? { activeTargetId } : {},
4820
+ open: selectionOpen,
4821
+ sequence: targetSequence,
4822
+ targets: persistedTargets
4823
+ });
4824
+ }
4825
+ function targetIsCurrent(target2, revision) {
4826
+ return mounted && revision === sessionRevision && state.status !== "idle" && targets.includes(target2);
4638
4827
  }
4639
4828
  function canPreview() {
4640
4829
  const instructionCharacters = targets.reduce(
4641
- (total, target) => total + target.instruction.trim().length,
4830
+ (total, target2) => total + target2.instruction.trim().length,
4642
4831
  0
4643
4832
  );
4644
4833
  return targets.length > 0 && instructionCharacters <= MAX_ANNOTATION_INSTRUCTION_CHARACTERS3 && targets.every(
4645
- (target) => target.instruction.trim().length > 0 && target.element !== void 0 && target.elementContext !== void 0 && target.styles !== void 0 && target.apiStatus !== "loading"
4834
+ (target2) => target2.instruction.trim().length > 0 && target2.elementContext !== void 0 && target2.styles !== void 0 && target2.apiStatus !== "loading"
4646
4835
  );
4647
4836
  }
4648
4837
  function selectionSummary() {
4649
4838
  const messages = view.messages().summary;
4650
- return targets.map((target, index) => {
4839
+ return targets.map((target2, index) => {
4651
4840
  const summary = createSelectionSummary(
4652
4841
  {
4653
- resolution: target.resolution,
4654
- ...target.code === void 0 ? {} : { code: target.code },
4655
- ...target.styles === void 0 ? {} : { styles: target.styles },
4656
- apiStatus: target.apiStatus,
4657
- collectionStatus: target.collectionStatus,
4658
- spotPatchVersion: config.spotPatchVersion,
4659
- viteVersion: config.viteVersion
4842
+ resolution: target2.resolution,
4843
+ ...target2.code === void 0 ? {} : { code: target2.code },
4844
+ ...target2.styles === void 0 ? {} : { styles: target2.styles },
4845
+ apiStatus: target2.apiStatus,
4846
+ collectionStatus: target2.collectionStatus,
4847
+ framework: config.framework,
4848
+ frameworkVersion: config.frameworkVersion,
4849
+ spotPatchVersion: config.spotPatchVersion
4660
4850
  },
4661
4851
  messages
4662
4852
  );
4663
- return `${messages.target(index + 1, target.id === activeTargetId)}
4853
+ return `${messages.target(index + 1, target2.id === activeTargetId)}
4664
4854
  ${summary}`;
4665
4855
  }).join("\n\n");
4666
4856
  }
4667
4857
  function refreshHighlights() {
4668
- const highlights = targets.flatMap((target) => {
4669
- if (!target.element?.isConnected) {
4858
+ const highlights = targets.flatMap((target2) => {
4859
+ if (!target2.element?.isConnected) {
4670
4860
  return [];
4671
4861
  }
4672
4862
  return [
4673
4863
  {
4674
- id: target.id,
4675
- label: elementLabel(target.element),
4676
- rect: getElementRect(target.element, browser.window),
4677
- active: target.id === activeTargetId
4864
+ id: target2.id,
4865
+ label: elementLabel(target2.element),
4866
+ rect: getElementRect(target2.element, browser.window),
4867
+ active: target2.id === activeTargetId
4678
4868
  }
4679
4869
  ];
4680
4870
  });
@@ -4689,14 +4879,14 @@ ${summary}`;
4689
4879
  return;
4690
4880
  }
4691
4881
  view.renderTargets(
4692
- targets.map((target) => ({
4693
- id: target.id,
4694
- label: target.resolution.react.componentName ?? (target.element === void 0 ? target.elementContext?.tagName ?? "Element" : elementLabel(target.element)),
4695
- source: sourceLabel(target, view.messages().context.sourceUnavailable),
4696
- canOpenEditor: target.marker !== void 0,
4697
- status: targetStatus(target),
4698
- active: target.id === activeTargetId,
4699
- instruction: target.instruction
4882
+ targets.map((target2) => ({
4883
+ id: target2.id,
4884
+ label: target2.resolution.react.componentName ?? (target2.element === void 0 ? target2.elementContext?.tagName ?? "Element" : elementLabel(target2.element)),
4885
+ source: sourceLabel(target2, view.messages().context.sourceUnavailable),
4886
+ canOpenEditor: target2.marker !== void 0,
4887
+ status: targetStatus(target2),
4888
+ active: target2.id === activeTargetId,
4889
+ instruction: target2.instruction
4700
4890
  })),
4701
4891
  config.maxTargets
4702
4892
  );
@@ -4714,21 +4904,22 @@ ${summary}`;
4714
4904
  if (!canPreview()) {
4715
4905
  return void 0;
4716
4906
  }
4717
- const annotationTargets = targets.map((target) => {
4718
- if (target.elementContext === void 0 || target.styles === void 0) {
4907
+ const annotationTargets = targets.map((target2) => {
4908
+ if (target2.elementContext === void 0 || target2.styles === void 0) {
4719
4909
  throw new Error("SpotPatch attempted to compose an incomplete target.");
4720
4910
  }
4721
4911
  const warnings = [
4722
- ...target.apiStatus === "failed" ? ["Source context could not be loaded."] : [],
4723
- ...target.collectionStatus === "failed" ? ["Part of the browser context could not be collected."] : []
4912
+ ...target2.apiStatus === "failed" ? ["Source context could not be loaded."] : [],
4913
+ ...target2.collectionStatus === "failed" ? ["Part of the browser context could not be collected."] : []
4724
4914
  ];
4725
4915
  return {
4726
- instruction: target.instruction.trim(),
4727
- source: target.resolution.source,
4728
- react: target.resolution.react,
4729
- element: target.elementContext,
4730
- styles: target.styles,
4731
- ...target.code === void 0 ? {} : { code: target.code },
4916
+ instruction: target2.instruction.trim(),
4917
+ page: target2.page,
4918
+ source: target2.resolution.source,
4919
+ react: target2.resolution.react,
4920
+ element: target2.elementContext,
4921
+ styles: target2.styles,
4922
+ ...target2.code === void 0 ? {} : { code: target2.code },
4732
4923
  warnings
4733
4924
  };
4734
4925
  });
@@ -4740,8 +4931,8 @@ ${summary}`;
4740
4931
  targets: annotationTargets
4741
4932
  });
4742
4933
  }
4743
- function showElementHighlight(element) {
4744
- view.showHighlight(getElementRect(element, browser.window), elementLabel(element));
4934
+ function showElementHighlight(element2) {
4935
+ view.showHighlight(getElementRect(element2, browser.window), elementLabel(element2));
4745
4936
  }
4746
4937
  function restoreFocus() {
4747
4938
  if (previousFocus?.isConnected === true) {
@@ -4763,10 +4954,13 @@ ${summary}`;
4763
4954
  resizeObserver?.disconnect();
4764
4955
  targets = [];
4765
4956
  activeTargetId = void 0;
4957
+ selectionOpen = false;
4958
+ workflowSelectionActive = false;
4766
4959
  addingTarget = false;
4767
4960
  previewPrompt = "";
4768
4961
  view.hideSelection();
4769
4962
  view.hideSelectionHighlights();
4963
+ selectionSession.clear();
4770
4964
  restoreFocus();
4771
4965
  }
4772
4966
  function close() {
@@ -4778,7 +4972,11 @@ ${summary}`;
4778
4972
  transition({ type: "BACK" });
4779
4973
  transition({ type: "CLOSE" });
4780
4974
  }
4781
- releaseSelection();
4975
+ selectionOpen = false;
4976
+ persistSelection();
4977
+ view.hideSelection();
4978
+ view.hideSelectionHighlights();
4979
+ restoreFocus();
4782
4980
  view.hideHighlight();
4783
4981
  }
4784
4982
  function cancelAddTarget() {
@@ -4802,6 +5000,18 @@ ${summary}`;
4802
5000
  close();
4803
5001
  return;
4804
5002
  }
5003
+ if (targets.length > 0) {
5004
+ selectionOpen = true;
5005
+ transition({ type: "RESTORE" });
5006
+ if (!workflowSelectionActive) {
5007
+ agentWorkflow.beginSelection();
5008
+ workflowSelectionActive = true;
5009
+ }
5010
+ refreshSelectionView(true);
5011
+ view.focusTargetInstruction(activeTargetId);
5012
+ persistSelection();
5013
+ return;
5014
+ }
4805
5015
  transition({ type: "ACTIVATE" });
4806
5016
  view.hideSelection();
4807
5017
  view.announce(view.messages().announcements.selectionEnabled);
@@ -4835,12 +5045,13 @@ ${summary}`;
4835
5045
  sessionRevision += 1;
4836
5046
  clearCollectionTimers();
4837
5047
  resizeObserver?.disconnect();
4838
- for (const target of targets) {
4839
- target.element = void 0;
5048
+ for (const target2 of targets) {
5049
+ target2.element = void 0;
4840
5050
  }
4841
5051
  view.hideHighlight();
4842
5052
  view.hideSelectionHighlights();
4843
5053
  refreshSelectionView();
5054
+ selectionSession.clear();
4844
5055
  view.announce(view.messages().announcements.appliedTargetsDetached);
4845
5056
  }
4846
5057
  const agentWorkflow = createAgentWorkflow({
@@ -4853,8 +5064,8 @@ ${summary}`;
4853
5064
  },
4854
5065
  view
4855
5066
  });
4856
- async function loadSourceContext(target, revision) {
4857
- const marker = target.marker;
5067
+ async function loadSourceContext(target2, revision) {
5068
+ const marker = target2.marker;
4858
5069
  if (marker === void 0) {
4859
5070
  return;
4860
5071
  }
@@ -4865,16 +5076,18 @@ ${summary}`;
4865
5076
  column: marker.column,
4866
5077
  maxLines: config.budget.maxCodeLines
4867
5078
  });
4868
- if (targetIsCurrent(target, revision)) {
4869
- target.code = context;
4870
- target.apiStatus = "connected";
5079
+ if (targetIsCurrent(target2, revision)) {
5080
+ target2.code = context;
5081
+ target2.apiStatus = "connected";
5082
+ persistSelection();
4871
5083
  refreshSelectionView();
4872
5084
  view.announce(view.messages().announcements.sourceLoaded);
4873
5085
  }
4874
5086
  } catch (error) {
4875
- if (targetIsCurrent(target, revision)) {
4876
- target.code = void 0;
4877
- target.apiStatus = "failed";
5087
+ if (targetIsCurrent(target2, revision)) {
5088
+ target2.code = void 0;
5089
+ target2.apiStatus = "failed";
5090
+ persistSelection();
4878
5091
  refreshSelectionView();
4879
5092
  view.announce(view.messages().announcements.sourceFailed);
4880
5093
  if (config.debug && !(error instanceof DOMException && error.name === "AbortError")) {
@@ -4883,61 +5096,62 @@ ${summary}`;
4883
5096
  }
4884
5097
  }
4885
5098
  }
4886
- function scheduleBrowserContextCollection(target, revision) {
4887
- const element = target.element;
4888
- if (element === void 0) {
5099
+ function scheduleBrowserContextCollection(target2, revision) {
5100
+ const element2 = target2.element;
5101
+ if (element2 === void 0) {
4889
5102
  return;
4890
5103
  }
4891
5104
  const timer = browser.window.setTimeout(() => {
4892
- collectionTimers.delete(target.id);
4893
- if (!targetIsCurrent(target, revision)) {
5105
+ collectionTimers.delete(target2.id);
5106
+ if (!targetIsCurrent(target2, revision)) {
4894
5107
  return;
4895
5108
  }
4896
5109
  let failed = false;
4897
5110
  try {
4898
- target.elementContext = collectElementContext({
4899
- element,
5111
+ target2.elementContext = collectElementContext({
5112
+ element: element2,
4900
5113
  maxCharacters: config.budget.domCharacters
4901
5114
  });
4902
5115
  } catch {
4903
- target.elementContext = void 0;
5116
+ target2.elementContext = void 0;
4904
5117
  failed = true;
4905
5118
  }
4906
5119
  try {
4907
- target.styles = collectStyleContext({
5120
+ target2.styles = collectStyleContext({
4908
5121
  document: browser.document,
4909
- element,
5122
+ element: element2,
4910
5123
  maxCharacters: config.budget.cssCharacters
4911
5124
  });
4912
5125
  } catch {
4913
- target.styles = EMPTY_STYLE_CONTEXT;
5126
+ target2.styles = EMPTY_STYLE_CONTEXT;
4914
5127
  failed = true;
4915
5128
  }
4916
- target.collectionStatus = failed ? "failed" : "ready";
5129
+ target2.collectionStatus = failed ? "failed" : "ready";
5130
+ persistSelection();
4917
5131
  refreshSelectionView();
4918
5132
  view.announce(
4919
5133
  failed ? view.messages().announcements.contextWarning : view.messages().announcements.contextCollected
4920
5134
  );
4921
- if (config.debug && target.styles.warnings.length > 0) {
5135
+ if (config.debug && target2.styles.warnings.length > 0) {
4922
5136
  console.warn(
4923
- `[spotpatch:runtime] CSS collection completed with ${String(target.styles.warnings.length)} warning(s).`
5137
+ `[spotpatch:runtime] CSS collection completed with ${String(target2.styles.warnings.length)} warning(s).`
4924
5138
  );
4925
5139
  }
4926
5140
  }, 0);
4927
- collectionTimers.set(target.id, timer);
5141
+ collectionTimers.set(target2.id, timer);
4928
5142
  }
4929
- function duplicateTarget(element, marker) {
5143
+ function duplicateTarget(element2, marker) {
4930
5144
  if (marker !== void 0) {
4931
5145
  return targets.find(
4932
- (target) => target.marker?.fileId === marker.fileId && target.marker.line === marker.line && target.marker.column === marker.column
5146
+ (target2) => target2.marker?.fileId === marker.fileId && target2.marker.line === marker.line && target2.marker.column === marker.column
4933
5147
  );
4934
5148
  }
4935
- return targets.find((target) => target.element === element);
5149
+ return targets.find((target2) => target2.element === element2);
4936
5150
  }
4937
- function selectElement(element) {
4938
- const resolution = sourceResolver.resolve(element);
5151
+ function selectElement(element2) {
5152
+ const resolution = sourceResolver.resolve(element2);
4939
5153
  const marker = sourceRefToMarker(resolution.source);
4940
- const duplicate = duplicateTarget(element, marker);
5154
+ const duplicate = duplicateTarget(element2, marker);
4941
5155
  if (duplicate !== void 0) {
4942
5156
  activeTargetId = duplicate.id;
4943
5157
  addingTarget = false;
@@ -4961,13 +5175,15 @@ ${summary}`;
4961
5175
  previousFocus = browser.document.activeElement instanceof HTMLElement ? browser.document.activeElement : void 0;
4962
5176
  previewPrompt = "";
4963
5177
  agentWorkflow.beginSelection();
5178
+ workflowSelectionActive = true;
4964
5179
  }
4965
5180
  targetSequence += 1;
4966
- const target = {
5181
+ const target2 = {
4967
5182
  id: `target-${String(targetSequence)}`,
4968
- element,
5183
+ element: element2,
4969
5184
  resolution,
4970
5185
  marker,
5186
+ page: collectPageContext(browser.document, browser.window),
4971
5187
  code: void 0,
4972
5188
  elementContext: void 0,
4973
5189
  styles: void 0,
@@ -4975,18 +5191,19 @@ ${summary}`;
4975
5191
  apiStatus: marker === void 0 ? "not-required" : "loading",
4976
5192
  collectionStatus: "loading"
4977
5193
  };
4978
- targets.push(target);
4979
- activeTargetId = target.id;
5194
+ targets.push(target2);
5195
+ activeTargetId = target2.id;
4980
5196
  addingTarget = false;
4981
5197
  const revision = sessionRevision;
4982
5198
  transition({ type: "SELECT" });
4983
5199
  view.hideHighlight();
4984
- resizeObserver?.observe(element);
5200
+ resizeObserver?.observe(element2);
4985
5201
  refreshSelectionView(true);
4986
- view.focusTargetInstruction(target.id);
4987
- scheduleBrowserContextCollection(target, revision);
5202
+ selectionOpen = true;
5203
+ view.focusTargetInstruction(target2.id);
5204
+ scheduleBrowserContextCollection(target2, revision);
4988
5205
  if (marker !== void 0) {
4989
- void loadSourceContext(target, revision);
5206
+ void loadSourceContext(target2, revision);
4990
5207
  } else {
4991
5208
  view.announce(
4992
5209
  resolution.source.confidence === "probable" ? view.messages().announcements.sourceProbable : view.messages().announcements.sourceMissing
@@ -5060,7 +5277,7 @@ ${summary}`;
5060
5277
  if (state.status !== "selected" && state.status !== "inspecting") {
5061
5278
  return;
5062
5279
  }
5063
- const index = targets.findIndex((target) => target.id === targetId);
5280
+ const index = targets.findIndex((target2) => target2.id === targetId);
5064
5281
  if (index < 0) {
5065
5282
  return;
5066
5283
  }
@@ -5086,10 +5303,12 @@ ${summary}`;
5086
5303
  }
5087
5304
  view.hideSelectionTemporarily();
5088
5305
  view.hideSelectionHighlights();
5306
+ selectionSession.clear();
5089
5307
  view.announce(view.messages().announcements.allTargetsRemoved);
5090
5308
  return;
5091
5309
  }
5092
5310
  refreshSelectionView();
5311
+ persistSelection();
5093
5312
  view.announce(view.messages().announcements.targetRemoved);
5094
5313
  }
5095
5314
  function refreshSelectedGeometry() {
@@ -5097,24 +5316,24 @@ ${summary}`;
5097
5316
  return;
5098
5317
  }
5099
5318
  const disconnected = targets.filter(
5100
- (target) => target.element !== void 0 && !target.element.isConnected
5319
+ (target2) => target2.element !== void 0 && !target2.element.isConnected
5101
5320
  );
5102
5321
  if (disconnected.length > 0) {
5103
- if (state.status === "previewing") {
5104
- transition({ type: "BACK" });
5105
- }
5106
- for (const target of disconnected) {
5107
- removeTarget(target.id);
5108
- }
5109
- if (targets.length > 0) {
5110
- view.announce(view.messages().announcements.detachedTargetRemoved);
5322
+ for (const target2 of disconnected) {
5323
+ if (target2.element !== void 0) {
5324
+ resizeObserver?.unobserve(target2.element);
5325
+ target2.element = void 0;
5326
+ }
5111
5327
  }
5328
+ refreshSelectionView();
5329
+ persistSelection();
5330
+ view.announce(view.messages().announcements.detachedTargetPreserved);
5112
5331
  return;
5113
5332
  }
5114
5333
  refreshHighlights();
5115
5334
  }
5116
5335
  function handleOpenEditor(targetId) {
5117
- const current = targetId === void 0 ? activeTarget() : targets.find((target) => target.id === targetId);
5336
+ const current = targetId === void 0 ? activeTarget() : targets.find((target2) => target2.id === targetId);
5118
5337
  if (state.status !== "selected" || current?.marker === void 0) {
5119
5338
  return;
5120
5339
  }
@@ -5146,13 +5365,14 @@ ${summary}`;
5146
5365
  return;
5147
5366
  }
5148
5367
  const targetId = event.target.dataset.targetInstructionId;
5149
- const target = targets.find((candidate) => candidate.id === targetId);
5150
- if (target === void 0) {
5368
+ const target2 = targets.find((candidate) => candidate.id === targetId);
5369
+ if (target2 === void 0) {
5151
5370
  return;
5152
5371
  }
5153
- target.instruction = event.target.value;
5154
- view.updateTargetInstruction(target.id, target.instruction);
5372
+ target2.instruction = event.target.value;
5373
+ view.updateTargetInstruction(target2.id, target2.instruction);
5155
5374
  view.setPreviewEnabled(canPreview());
5375
+ persistSelection();
5156
5376
  }
5157
5377
  function handleOpenEditorButtonClick() {
5158
5378
  handleOpenEditor();
@@ -5245,7 +5465,7 @@ ${summary}`;
5245
5465
  "button[data-activate-target-id]"
5246
5466
  );
5247
5467
  const targetId = activateButton?.dataset.activateTargetId;
5248
- if (state.status !== "selected" || targetId === void 0 || !targets.some((target) => target.id === targetId)) {
5468
+ if (state.status !== "selected" || targetId === void 0 || !targets.some((target2) => target2.id === targetId)) {
5249
5469
  return;
5250
5470
  }
5251
5471
  activeTargetId = targetId;
@@ -5303,6 +5523,12 @@ ${summary}`;
5303
5523
  subtree: true
5304
5524
  });
5305
5525
  }
5526
+ if (selectionOpen && targets.length > 0) {
5527
+ transition({ type: "RESTORE" });
5528
+ agentWorkflow.beginSelection();
5529
+ workflowSelectionActive = true;
5530
+ refreshSelectionView(true);
5531
+ }
5306
5532
  if (config.debug) {
5307
5533
  console.info(`[spotpatch:runtime] Mounted. Shortcut: ${config.shortcut}.`);
5308
5534
  }
@@ -5360,6 +5586,7 @@ ${summary}`;
5360
5586
  animationFrame = void 0;
5361
5587
  }
5362
5588
  clearCollectionTimers();
5589
+ persistSelection();
5363
5590
  resizeObserver?.disconnect();
5364
5591
  mutationObserver?.disconnect();
5365
5592
  resizeObserver = void 0;
@@ -5382,10 +5609,10 @@ ${summary}`;
5382
5609
  // src/controller/bootstrap.ts
5383
5610
  var RUNTIME_INSTANCE_KEY = "__spotpatchRuntime__";
5384
5611
  function bootstrapSpotPatch(config) {
5385
- const target = globalThis;
5386
- target[RUNTIME_INSTANCE_KEY]?.dispose();
5612
+ const target2 = globalThis;
5613
+ target2[RUNTIME_INSTANCE_KEY]?.dispose();
5387
5614
  const controller = createController(config);
5388
- target[RUNTIME_INSTANCE_KEY] = controller;
5615
+ target2[RUNTIME_INSTANCE_KEY] = controller;
5389
5616
  controller.mount();
5390
5617
  }
5391
5618
  export {