@spotpatch/runtime 1.4.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}`;
@@ -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,51 +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,
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,
4658
4847
  framework: config.framework,
4659
4848
  frameworkVersion: config.frameworkVersion,
4660
4849
  spotPatchVersion: config.spotPatchVersion
4661
4850
  },
4662
4851
  messages
4663
4852
  );
4664
- return `${messages.target(index + 1, target.id === activeTargetId)}
4853
+ return `${messages.target(index + 1, target2.id === activeTargetId)}
4665
4854
  ${summary}`;
4666
4855
  }).join("\n\n");
4667
4856
  }
4668
4857
  function refreshHighlights() {
4669
- const highlights = targets.flatMap((target) => {
4670
- if (!target.element?.isConnected) {
4858
+ const highlights = targets.flatMap((target2) => {
4859
+ if (!target2.element?.isConnected) {
4671
4860
  return [];
4672
4861
  }
4673
4862
  return [
4674
4863
  {
4675
- id: target.id,
4676
- label: elementLabel(target.element),
4677
- rect: getElementRect(target.element, browser.window),
4678
- 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
4679
4868
  }
4680
4869
  ];
4681
4870
  });
@@ -4690,14 +4879,14 @@ ${summary}`;
4690
4879
  return;
4691
4880
  }
4692
4881
  view.renderTargets(
4693
- targets.map((target) => ({
4694
- id: target.id,
4695
- label: target.resolution.react.componentName ?? (target.element === void 0 ? target.elementContext?.tagName ?? "Element" : elementLabel(target.element)),
4696
- source: sourceLabel(target, view.messages().context.sourceUnavailable),
4697
- canOpenEditor: target.marker !== void 0,
4698
- status: targetStatus(target),
4699
- active: target.id === activeTargetId,
4700
- 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
4701
4890
  })),
4702
4891
  config.maxTargets
4703
4892
  );
@@ -4715,21 +4904,22 @@ ${summary}`;
4715
4904
  if (!canPreview()) {
4716
4905
  return void 0;
4717
4906
  }
4718
- const annotationTargets = targets.map((target) => {
4719
- 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) {
4720
4909
  throw new Error("SpotPatch attempted to compose an incomplete target.");
4721
4910
  }
4722
4911
  const warnings = [
4723
- ...target.apiStatus === "failed" ? ["Source context could not be loaded."] : [],
4724
- ...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."] : []
4725
4914
  ];
4726
4915
  return {
4727
- instruction: target.instruction.trim(),
4728
- source: target.resolution.source,
4729
- react: target.resolution.react,
4730
- element: target.elementContext,
4731
- styles: target.styles,
4732
- ...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 },
4733
4923
  warnings
4734
4924
  };
4735
4925
  });
@@ -4741,8 +4931,8 @@ ${summary}`;
4741
4931
  targets: annotationTargets
4742
4932
  });
4743
4933
  }
4744
- function showElementHighlight(element) {
4745
- view.showHighlight(getElementRect(element, browser.window), elementLabel(element));
4934
+ function showElementHighlight(element2) {
4935
+ view.showHighlight(getElementRect(element2, browser.window), elementLabel(element2));
4746
4936
  }
4747
4937
  function restoreFocus() {
4748
4938
  if (previousFocus?.isConnected === true) {
@@ -4764,10 +4954,13 @@ ${summary}`;
4764
4954
  resizeObserver?.disconnect();
4765
4955
  targets = [];
4766
4956
  activeTargetId = void 0;
4957
+ selectionOpen = false;
4958
+ workflowSelectionActive = false;
4767
4959
  addingTarget = false;
4768
4960
  previewPrompt = "";
4769
4961
  view.hideSelection();
4770
4962
  view.hideSelectionHighlights();
4963
+ selectionSession.clear();
4771
4964
  restoreFocus();
4772
4965
  }
4773
4966
  function close() {
@@ -4779,7 +4972,11 @@ ${summary}`;
4779
4972
  transition({ type: "BACK" });
4780
4973
  transition({ type: "CLOSE" });
4781
4974
  }
4782
- releaseSelection();
4975
+ selectionOpen = false;
4976
+ persistSelection();
4977
+ view.hideSelection();
4978
+ view.hideSelectionHighlights();
4979
+ restoreFocus();
4783
4980
  view.hideHighlight();
4784
4981
  }
4785
4982
  function cancelAddTarget() {
@@ -4803,6 +5000,18 @@ ${summary}`;
4803
5000
  close();
4804
5001
  return;
4805
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
+ }
4806
5015
  transition({ type: "ACTIVATE" });
4807
5016
  view.hideSelection();
4808
5017
  view.announce(view.messages().announcements.selectionEnabled);
@@ -4836,12 +5045,13 @@ ${summary}`;
4836
5045
  sessionRevision += 1;
4837
5046
  clearCollectionTimers();
4838
5047
  resizeObserver?.disconnect();
4839
- for (const target of targets) {
4840
- target.element = void 0;
5048
+ for (const target2 of targets) {
5049
+ target2.element = void 0;
4841
5050
  }
4842
5051
  view.hideHighlight();
4843
5052
  view.hideSelectionHighlights();
4844
5053
  refreshSelectionView();
5054
+ selectionSession.clear();
4845
5055
  view.announce(view.messages().announcements.appliedTargetsDetached);
4846
5056
  }
4847
5057
  const agentWorkflow = createAgentWorkflow({
@@ -4854,8 +5064,8 @@ ${summary}`;
4854
5064
  },
4855
5065
  view
4856
5066
  });
4857
- async function loadSourceContext(target, revision) {
4858
- const marker = target.marker;
5067
+ async function loadSourceContext(target2, revision) {
5068
+ const marker = target2.marker;
4859
5069
  if (marker === void 0) {
4860
5070
  return;
4861
5071
  }
@@ -4866,16 +5076,18 @@ ${summary}`;
4866
5076
  column: marker.column,
4867
5077
  maxLines: config.budget.maxCodeLines
4868
5078
  });
4869
- if (targetIsCurrent(target, revision)) {
4870
- target.code = context;
4871
- target.apiStatus = "connected";
5079
+ if (targetIsCurrent(target2, revision)) {
5080
+ target2.code = context;
5081
+ target2.apiStatus = "connected";
5082
+ persistSelection();
4872
5083
  refreshSelectionView();
4873
5084
  view.announce(view.messages().announcements.sourceLoaded);
4874
5085
  }
4875
5086
  } catch (error) {
4876
- if (targetIsCurrent(target, revision)) {
4877
- target.code = void 0;
4878
- target.apiStatus = "failed";
5087
+ if (targetIsCurrent(target2, revision)) {
5088
+ target2.code = void 0;
5089
+ target2.apiStatus = "failed";
5090
+ persistSelection();
4879
5091
  refreshSelectionView();
4880
5092
  view.announce(view.messages().announcements.sourceFailed);
4881
5093
  if (config.debug && !(error instanceof DOMException && error.name === "AbortError")) {
@@ -4884,61 +5096,62 @@ ${summary}`;
4884
5096
  }
4885
5097
  }
4886
5098
  }
4887
- function scheduleBrowserContextCollection(target, revision) {
4888
- const element = target.element;
4889
- if (element === void 0) {
5099
+ function scheduleBrowserContextCollection(target2, revision) {
5100
+ const element2 = target2.element;
5101
+ if (element2 === void 0) {
4890
5102
  return;
4891
5103
  }
4892
5104
  const timer = browser.window.setTimeout(() => {
4893
- collectionTimers.delete(target.id);
4894
- if (!targetIsCurrent(target, revision)) {
5105
+ collectionTimers.delete(target2.id);
5106
+ if (!targetIsCurrent(target2, revision)) {
4895
5107
  return;
4896
5108
  }
4897
5109
  let failed = false;
4898
5110
  try {
4899
- target.elementContext = collectElementContext({
4900
- element,
5111
+ target2.elementContext = collectElementContext({
5112
+ element: element2,
4901
5113
  maxCharacters: config.budget.domCharacters
4902
5114
  });
4903
5115
  } catch {
4904
- target.elementContext = void 0;
5116
+ target2.elementContext = void 0;
4905
5117
  failed = true;
4906
5118
  }
4907
5119
  try {
4908
- target.styles = collectStyleContext({
5120
+ target2.styles = collectStyleContext({
4909
5121
  document: browser.document,
4910
- element,
5122
+ element: element2,
4911
5123
  maxCharacters: config.budget.cssCharacters
4912
5124
  });
4913
5125
  } catch {
4914
- target.styles = EMPTY_STYLE_CONTEXT;
5126
+ target2.styles = EMPTY_STYLE_CONTEXT;
4915
5127
  failed = true;
4916
5128
  }
4917
- target.collectionStatus = failed ? "failed" : "ready";
5129
+ target2.collectionStatus = failed ? "failed" : "ready";
5130
+ persistSelection();
4918
5131
  refreshSelectionView();
4919
5132
  view.announce(
4920
5133
  failed ? view.messages().announcements.contextWarning : view.messages().announcements.contextCollected
4921
5134
  );
4922
- if (config.debug && target.styles.warnings.length > 0) {
5135
+ if (config.debug && target2.styles.warnings.length > 0) {
4923
5136
  console.warn(
4924
- `[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).`
4925
5138
  );
4926
5139
  }
4927
5140
  }, 0);
4928
- collectionTimers.set(target.id, timer);
5141
+ collectionTimers.set(target2.id, timer);
4929
5142
  }
4930
- function duplicateTarget(element, marker) {
5143
+ function duplicateTarget(element2, marker) {
4931
5144
  if (marker !== void 0) {
4932
5145
  return targets.find(
4933
- (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
4934
5147
  );
4935
5148
  }
4936
- return targets.find((target) => target.element === element);
5149
+ return targets.find((target2) => target2.element === element2);
4937
5150
  }
4938
- function selectElement(element) {
4939
- const resolution = sourceResolver.resolve(element);
5151
+ function selectElement(element2) {
5152
+ const resolution = sourceResolver.resolve(element2);
4940
5153
  const marker = sourceRefToMarker(resolution.source);
4941
- const duplicate = duplicateTarget(element, marker);
5154
+ const duplicate = duplicateTarget(element2, marker);
4942
5155
  if (duplicate !== void 0) {
4943
5156
  activeTargetId = duplicate.id;
4944
5157
  addingTarget = false;
@@ -4962,13 +5175,15 @@ ${summary}`;
4962
5175
  previousFocus = browser.document.activeElement instanceof HTMLElement ? browser.document.activeElement : void 0;
4963
5176
  previewPrompt = "";
4964
5177
  agentWorkflow.beginSelection();
5178
+ workflowSelectionActive = true;
4965
5179
  }
4966
5180
  targetSequence += 1;
4967
- const target = {
5181
+ const target2 = {
4968
5182
  id: `target-${String(targetSequence)}`,
4969
- element,
5183
+ element: element2,
4970
5184
  resolution,
4971
5185
  marker,
5186
+ page: collectPageContext(browser.document, browser.window),
4972
5187
  code: void 0,
4973
5188
  elementContext: void 0,
4974
5189
  styles: void 0,
@@ -4976,18 +5191,19 @@ ${summary}`;
4976
5191
  apiStatus: marker === void 0 ? "not-required" : "loading",
4977
5192
  collectionStatus: "loading"
4978
5193
  };
4979
- targets.push(target);
4980
- activeTargetId = target.id;
5194
+ targets.push(target2);
5195
+ activeTargetId = target2.id;
4981
5196
  addingTarget = false;
4982
5197
  const revision = sessionRevision;
4983
5198
  transition({ type: "SELECT" });
4984
5199
  view.hideHighlight();
4985
- resizeObserver?.observe(element);
5200
+ resizeObserver?.observe(element2);
4986
5201
  refreshSelectionView(true);
4987
- view.focusTargetInstruction(target.id);
4988
- scheduleBrowserContextCollection(target, revision);
5202
+ selectionOpen = true;
5203
+ view.focusTargetInstruction(target2.id);
5204
+ scheduleBrowserContextCollection(target2, revision);
4989
5205
  if (marker !== void 0) {
4990
- void loadSourceContext(target, revision);
5206
+ void loadSourceContext(target2, revision);
4991
5207
  } else {
4992
5208
  view.announce(
4993
5209
  resolution.source.confidence === "probable" ? view.messages().announcements.sourceProbable : view.messages().announcements.sourceMissing
@@ -5061,7 +5277,7 @@ ${summary}`;
5061
5277
  if (state.status !== "selected" && state.status !== "inspecting") {
5062
5278
  return;
5063
5279
  }
5064
- const index = targets.findIndex((target) => target.id === targetId);
5280
+ const index = targets.findIndex((target2) => target2.id === targetId);
5065
5281
  if (index < 0) {
5066
5282
  return;
5067
5283
  }
@@ -5087,10 +5303,12 @@ ${summary}`;
5087
5303
  }
5088
5304
  view.hideSelectionTemporarily();
5089
5305
  view.hideSelectionHighlights();
5306
+ selectionSession.clear();
5090
5307
  view.announce(view.messages().announcements.allTargetsRemoved);
5091
5308
  return;
5092
5309
  }
5093
5310
  refreshSelectionView();
5311
+ persistSelection();
5094
5312
  view.announce(view.messages().announcements.targetRemoved);
5095
5313
  }
5096
5314
  function refreshSelectedGeometry() {
@@ -5098,24 +5316,24 @@ ${summary}`;
5098
5316
  return;
5099
5317
  }
5100
5318
  const disconnected = targets.filter(
5101
- (target) => target.element !== void 0 && !target.element.isConnected
5319
+ (target2) => target2.element !== void 0 && !target2.element.isConnected
5102
5320
  );
5103
5321
  if (disconnected.length > 0) {
5104
- if (state.status === "previewing") {
5105
- transition({ type: "BACK" });
5106
- }
5107
- for (const target of disconnected) {
5108
- removeTarget(target.id);
5109
- }
5110
- if (targets.length > 0) {
5111
- 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
+ }
5112
5327
  }
5328
+ refreshSelectionView();
5329
+ persistSelection();
5330
+ view.announce(view.messages().announcements.detachedTargetPreserved);
5113
5331
  return;
5114
5332
  }
5115
5333
  refreshHighlights();
5116
5334
  }
5117
5335
  function handleOpenEditor(targetId) {
5118
- 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);
5119
5337
  if (state.status !== "selected" || current?.marker === void 0) {
5120
5338
  return;
5121
5339
  }
@@ -5147,13 +5365,14 @@ ${summary}`;
5147
5365
  return;
5148
5366
  }
5149
5367
  const targetId = event.target.dataset.targetInstructionId;
5150
- const target = targets.find((candidate) => candidate.id === targetId);
5151
- if (target === void 0) {
5368
+ const target2 = targets.find((candidate) => candidate.id === targetId);
5369
+ if (target2 === void 0) {
5152
5370
  return;
5153
5371
  }
5154
- target.instruction = event.target.value;
5155
- view.updateTargetInstruction(target.id, target.instruction);
5372
+ target2.instruction = event.target.value;
5373
+ view.updateTargetInstruction(target2.id, target2.instruction);
5156
5374
  view.setPreviewEnabled(canPreview());
5375
+ persistSelection();
5157
5376
  }
5158
5377
  function handleOpenEditorButtonClick() {
5159
5378
  handleOpenEditor();
@@ -5246,7 +5465,7 @@ ${summary}`;
5246
5465
  "button[data-activate-target-id]"
5247
5466
  );
5248
5467
  const targetId = activateButton?.dataset.activateTargetId;
5249
- 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)) {
5250
5469
  return;
5251
5470
  }
5252
5471
  activeTargetId = targetId;
@@ -5304,6 +5523,12 @@ ${summary}`;
5304
5523
  subtree: true
5305
5524
  });
5306
5525
  }
5526
+ if (selectionOpen && targets.length > 0) {
5527
+ transition({ type: "RESTORE" });
5528
+ agentWorkflow.beginSelection();
5529
+ workflowSelectionActive = true;
5530
+ refreshSelectionView(true);
5531
+ }
5307
5532
  if (config.debug) {
5308
5533
  console.info(`[spotpatch:runtime] Mounted. Shortcut: ${config.shortcut}.`);
5309
5534
  }
@@ -5361,6 +5586,7 @@ ${summary}`;
5361
5586
  animationFrame = void 0;
5362
5587
  }
5363
5588
  clearCollectionTimers();
5589
+ persistSelection();
5364
5590
  resizeObserver?.disconnect();
5365
5591
  mutationObserver?.disconnect();
5366
5592
  resizeObserver = void 0;
@@ -5383,10 +5609,10 @@ ${summary}`;
5383
5609
  // src/controller/bootstrap.ts
5384
5610
  var RUNTIME_INSTANCE_KEY = "__spotpatchRuntime__";
5385
5611
  function bootstrapSpotPatch(config) {
5386
- const target = globalThis;
5387
- target[RUNTIME_INSTANCE_KEY]?.dispose();
5612
+ const target2 = globalThis;
5613
+ target2[RUNTIME_INSTANCE_KEY]?.dispose();
5388
5614
  const controller = createController(config);
5389
- target[RUNTIME_INSTANCE_KEY] = controller;
5615
+ target2[RUNTIME_INSTANCE_KEY] = controller;
5390
5616
  controller.mount();
5391
5617
  }
5392
5618
  export {