@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.cjs CHANGED
@@ -3,9 +3,9 @@ var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
6
+ var __export = (target2, all) => {
7
7
  for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
8
+ __defProp(target2, name, { get: all[name], enumerable: true });
9
9
  };
10
10
  var __copyProps = (to, from, except, desc) => {
11
11
  if (from && typeof from === "object" || typeof from === "function") {
@@ -31,45 +31,46 @@ var import_shared9 = require("@spotpatch/shared");
31
31
 
32
32
  // src/annotation/create-annotation.ts
33
33
  var import_shared = require("@spotpatch/shared");
34
- function freezeSource(source, code) {
34
+ function freezeSource(source2, code2) {
35
35
  return Object.freeze({
36
- ...source,
37
- ...source.relativePath === void 0 && code !== void 0 ? { relativePath: code.relativePath } : {}
36
+ ...source2,
37
+ ...source2.relativePath === void 0 && code2 !== void 0 ? { relativePath: code2.relativePath } : {}
38
38
  });
39
39
  }
40
- function freezeReact(react) {
40
+ function freezeReact(react2) {
41
41
  return Object.freeze({
42
- ...react,
43
- componentStack: Object.freeze([...react.componentStack]),
44
- ...react.source === void 0 ? {} : { source: freezeSource(react.source) }
42
+ ...react2,
43
+ componentStack: Object.freeze([...react2.componentStack]),
44
+ ...react2.source === void 0 ? {} : { source: freezeSource(react2.source) }
45
45
  });
46
46
  }
47
- function freezeElement(element) {
47
+ function freezeElement(element2) {
48
48
  return Object.freeze({
49
- ...element,
50
- rect: Object.freeze({ ...element.rect })
49
+ ...element2,
50
+ rect: Object.freeze({ ...element2.rect })
51
51
  });
52
52
  }
53
- function freezeStyles(styles) {
53
+ function freezeStyles(styles2) {
54
54
  return Object.freeze({
55
- ...styles,
56
- classNames: Object.freeze([...styles.classNames]),
55
+ ...styles2,
56
+ classNames: Object.freeze([...styles2.classNames]),
57
57
  matchedRules: Object.freeze(
58
- styles.matchedRules.map((rule) => Object.freeze({ ...rule }))
58
+ styles2.matchedRules.map((rule) => Object.freeze({ ...rule }))
59
59
  ),
60
- computed: Object.freeze({ ...styles.computed }),
61
- warnings: Object.freeze([...styles.warnings])
60
+ computed: Object.freeze({ ...styles2.computed }),
61
+ warnings: Object.freeze([...styles2.warnings])
62
62
  });
63
63
  }
64
- function freezeTarget(target) {
64
+ function freezeTarget(target2) {
65
65
  return Object.freeze({
66
- instruction: target.instruction.trim(),
67
- source: freezeSource(target.source, target.code),
68
- react: freezeReact(target.react),
69
- element: freezeElement(target.element),
70
- styles: freezeStyles(target.styles),
71
- ...target.code === void 0 ? {} : { code: Object.freeze({ ...target.code }) },
72
- warnings: Object.freeze([...target.warnings])
66
+ instruction: target2.instruction.trim(),
67
+ ...target2.page === void 0 ? {} : { page: Object.freeze({ ...target2.page }) },
68
+ source: freezeSource(target2.source, target2.code),
69
+ react: freezeReact(target2.react),
70
+ element: freezeElement(target2.element),
71
+ styles: freezeStyles(target2.styles),
72
+ ...target2.code === void 0 ? {} : { code: Object.freeze({ ...target2.code }) },
73
+ warnings: Object.freeze([...target2.warnings])
73
74
  });
74
75
  }
75
76
  function createAnnotation(input) {
@@ -78,7 +79,7 @@ function createAnnotation(input) {
78
79
  `SpotPatch annotations require between 1 and ${String(import_shared.MAX_ANNOTATION_TARGETS)} targets.`
79
80
  );
80
81
  }
81
- const instructions = input.targets.map((target) => target.instruction.trim());
82
+ const instructions = input.targets.map((target2) => target2.instruction.trim());
82
83
  if (instructions.some(
83
84
  (instruction) => instruction.length === 0 || instruction.length > import_shared.MAX_TARGET_INSTRUCTION_CHARACTERS
84
85
  ) || instructions.reduce((total, instruction) => total + instruction.length, 0) > import_shared.MAX_ANNOTATION_INSTRUCTION_CHARACTERS) {
@@ -265,10 +266,10 @@ var MAX_EVENT_LINE_BYTES = 1e5;
265
266
  var MAX_EVENT_STREAM_BYTES = 4e6;
266
267
  var RuntimeApiError = class extends Error {
267
268
  code;
268
- constructor(code) {
269
+ constructor(code2) {
269
270
  super("SpotPatch local API request failed.");
270
271
  this.name = "RuntimeApiError";
271
- this.code = code;
272
+ this.code = code2;
272
273
  }
273
274
  };
274
275
  function isRecord2(value) {
@@ -340,9 +341,9 @@ async function readBoundedText(response, maximumBytes) {
340
341
  reader.releaseLock();
341
342
  }
342
343
  }
343
- function parseJson(text) {
344
+ function parseJson(text2) {
344
345
  try {
345
- return JSON.parse(text);
346
+ return JSON.parse(text2);
346
347
  } catch {
347
348
  throw new RuntimeApiError();
348
349
  }
@@ -385,13 +386,13 @@ function omitBrowserCodeContext(request) {
385
386
  ...request,
386
387
  annotation: {
387
388
  ...request.annotation,
388
- targets: request.annotation.targets.map((target) => ({
389
- instruction: target.instruction,
390
- source: target.source,
391
- react: target.react,
392
- element: target.element,
393
- styles: target.styles,
394
- warnings: target.warnings
389
+ targets: request.annotation.targets.map((target2) => ({
390
+ instruction: target2.instruction,
391
+ source: target2.source,
392
+ react: target2.react,
393
+ element: target2.element,
394
+ styles: target2.styles,
395
+ warnings: target2.warnings
395
396
  }))
396
397
  }
397
398
  };
@@ -682,12 +683,12 @@ function contextCharacterCount(classNames, inlineStyle, rules, computed, warning
682
683
  function stylesheetSource(sheet, baseUrl) {
683
684
  return sheet.href === null ? void 0 : (0, import_shared4.sanitizeUrl)(sheet.href, baseUrl);
684
685
  }
685
- function collectMatchedRules(element, rules, source, inheritedConditions, output, warnings) {
686
+ function collectMatchedRules(element2, rules, source2, inheritedConditions, output, warnings) {
686
687
  for (const rule of rules) {
687
688
  if (isStyleRule(rule)) {
688
689
  let matches = false;
689
690
  try {
690
- matches = element.matches(rule.selectorText);
691
+ matches = element2.matches(rule.selectorText);
691
692
  } catch {
692
693
  warnings.add(CSS_COLLECTION_WARNINGS.selector);
693
694
  continue;
@@ -697,7 +698,7 @@ function collectMatchedRules(element, rules, source, inheritedConditions, output
697
698
  Object.freeze({
698
699
  selector: sanitizeCssText(rule.selectorText),
699
700
  declarations: sanitizeCssText(rule.style.cssText),
700
- ...source === void 0 ? {} : { source },
701
+ ...source2 === void 0 ? {} : { source: source2 },
701
702
  ...inheritedConditions.length === 0 ? {} : { media: inheritedConditions.join(" and ") }
702
703
  })
703
704
  );
@@ -707,9 +708,9 @@ function collectMatchedRules(element, rules, source, inheritedConditions, output
707
708
  if (isGroupingRule(rule)) {
708
709
  const condition = groupingCondition(rule);
709
710
  collectMatchedRules(
710
- element,
711
+ element2,
711
712
  rule.cssRules,
712
- source,
713
+ source2,
713
714
  condition === void 0 ? inheritedConditions : [...inheritedConditions, condition],
714
715
  output,
715
716
  warnings
@@ -717,11 +718,11 @@ function collectMatchedRules(element, rules, source, inheritedConditions, output
717
718
  }
718
719
  }
719
720
  }
720
- function collectComputedStyle(element, getComputedStyle) {
721
+ function collectComputedStyle(element2, getComputedStyle) {
721
722
  if (getComputedStyle === void 0) {
722
723
  return {};
723
724
  }
724
- const declaration = getComputedStyle(element);
725
+ const declaration = getComputedStyle(element2);
725
726
  const computed = {};
726
727
  for (const property of COMPUTED_STYLE_PROPERTIES) {
727
728
  const value = sanitizeCssText(declaration.getPropertyValue(property).trim());
@@ -859,11 +860,11 @@ function escapeHtml(value) {
859
860
  function isPreservedAttribute(name) {
860
861
  return SAFE_ATTRIBUTE_NAMES.has(name) || name.startsWith("aria-") || name === "data-testid";
861
862
  }
862
- function openingTag(element) {
863
- const tagName = element.tagName.toLowerCase();
863
+ function openingTag(element2) {
864
+ const tagName = element2.tagName.toLowerCase();
864
865
  const attributes = [];
865
- const baseUrl = element.ownerDocument.baseURI;
866
- for (const attribute of element.attributes) {
866
+ const baseUrl = element2.ownerDocument.baseURI;
867
+ for (const attribute of element2.attributes) {
867
868
  const name = attribute.name.toLowerCase();
868
869
  if (!isPreservedAttribute(name)) {
869
870
  continue;
@@ -878,28 +879,28 @@ function openingTag(element) {
878
879
  function normalizedText(value) {
879
880
  return (0, import_shared4.redactSensitiveText)(value).replaceAll(/\s+/gu, " ").trim();
880
881
  }
881
- function serializeElement(element, depth, state, lines) {
882
+ function serializeElement(element2, depth, state, lines) {
882
883
  if (state.nodeCount >= DOM_COLLECTION_LIMITS.maxNodes) {
883
884
  state.truncated = true;
884
885
  return;
885
886
  }
886
887
  state.nodeCount += 1;
887
888
  const indent = " ".repeat(depth);
888
- const tagName = element.tagName.toLowerCase();
889
- lines.push(`${indent}${openingTag(element)}`);
889
+ const tagName = element2.tagName.toLowerCase();
890
+ lines.push(`${indent}${openingTag(element2)}`);
890
891
  if (VOID_ELEMENT_NAMES.has(tagName)) {
891
892
  return;
892
893
  }
893
- for (const child of element.childNodes) {
894
+ for (const child of element2.childNodes) {
894
895
  if (state.nodeCount >= DOM_COLLECTION_LIMITS.maxNodes) {
895
896
  state.truncated = true;
896
897
  break;
897
898
  }
898
899
  if (child.nodeType === 3) {
899
- const text = normalizedText(child.textContent ?? "");
900
- if (text.length > 0) {
900
+ const text2 = normalizedText(child.textContent ?? "");
901
+ if (text2.length > 0) {
901
902
  state.nodeCount += 1;
902
- const truncated = text.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text : `${text.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`;
903
+ const truncated = text2.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text2 : `${text2.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`;
903
904
  lines.push(`${" ".repeat(depth + 1)}${escapeHtml(truncated)}`);
904
905
  }
905
906
  continue;
@@ -918,9 +919,9 @@ function serializeElement(element, depth, state, lines) {
918
919
  }
919
920
  lines.push(`${indent}</${tagName}>`);
920
921
  }
921
- function parentContext(element) {
922
+ function parentContext(element2) {
922
923
  const parents = [];
923
- let parent = element.parentElement;
924
+ let parent = element2.parentElement;
924
925
  while (parent !== null && parents.length < DOM_COLLECTION_LIMITS.maxParentDepth) {
925
926
  parents.push(openingTag(parent));
926
927
  parent = parent.parentElement;
@@ -941,26 +942,26 @@ function escapeCssIdentifier(value) {
941
942
  function escapeCssAttribute(value) {
942
943
  return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
943
944
  }
944
- function selectorSegment(element) {
945
- const tagName = element.tagName.toLowerCase();
946
- if (element.id.length > 0) {
947
- return `${tagName}#${escapeCssIdentifier(element.id)}`;
945
+ function selectorSegment(element2) {
946
+ const tagName = element2.tagName.toLowerCase();
947
+ if (element2.id.length > 0) {
948
+ return `${tagName}#${escapeCssIdentifier(element2.id)}`;
948
949
  }
949
- const testId = element.getAttribute("data-testid");
950
+ const testId = element2.getAttribute("data-testid");
950
951
  if (testId !== null && testId.length > 0) {
951
952
  return `${tagName}[data-testid="${escapeCssAttribute(testId)}"]`;
952
953
  }
953
- const classes = Array.from(element.classList).slice(0, 2).map((name) => `.${escapeCssIdentifier(name)}`).join("");
954
- const sameTagSiblings = element.parentElement === null ? [] : Array.from(element.parentElement.children).filter(
955
- (sibling) => sibling.tagName === element.tagName
954
+ const classes = Array.from(element2.classList).slice(0, 2).map((name) => `.${escapeCssIdentifier(name)}`).join("");
955
+ const sameTagSiblings = element2.parentElement === null ? [] : Array.from(element2.parentElement.children).filter(
956
+ (sibling) => sibling.tagName === element2.tagName
956
957
  );
957
- const position = sameTagSiblings.indexOf(element);
958
+ const position = sameTagSiblings.indexOf(element2);
958
959
  const nth = sameTagSiblings.length > 1 && position >= 0 ? `:nth-of-type(${String(position + 1)})` : "";
959
960
  return `${tagName}${classes}${nth}`;
960
961
  }
961
- function createSelector(element) {
962
+ function createSelector(element2) {
962
963
  const segments = [];
963
- let current = element;
964
+ let current = element2;
964
965
  while (current !== null && segments.length < 5) {
965
966
  const segment = selectorSegment(current);
966
967
  segments.unshift(segment);
@@ -979,15 +980,15 @@ function collectElementContext(options) {
979
980
  if (parents.length > 0) {
980
981
  lines.push("<!-- Parent context: nearest first -->", ...parents);
981
982
  }
982
- const text = normalizedText(options.element.textContent);
983
+ const text2 = normalizedText(options.element.textContent);
983
984
  const rect = options.element.getBoundingClientRect();
984
985
  const role = options.element.getAttribute("role");
985
986
  return Object.freeze({
986
987
  tagName: options.element.tagName.toLowerCase(),
987
988
  selector: createSelector(options.element),
988
989
  sanitizedHtml: truncateContext(lines.join("\n"), options.maxCharacters),
989
- ...text.length === 0 ? {} : {
990
- textPreview: text.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text : `${text.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`
990
+ ...text2.length === 0 ? {} : {
991
+ textPreview: text2.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text2 : `${text2.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}\u2026`
991
992
  },
992
993
  ...role === null ? {} : { role },
993
994
  rect: Object.freeze({
@@ -1006,11 +1007,11 @@ function isApplePlatform(platform) {
1006
1007
  function normalizedKey(key) {
1007
1008
  return key.toLowerCase();
1008
1009
  }
1009
- function isTextEntryTarget(target) {
1010
- if (!(target instanceof Element)) {
1010
+ function isTextEntryTarget(target2) {
1011
+ if (!(target2 instanceof Element)) {
1011
1012
  return false;
1012
1013
  }
1013
- return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement || target.closest("[contenteditable]:not([contenteditable='false'])") !== null;
1014
+ return target2 instanceof HTMLInputElement || target2 instanceof HTMLTextAreaElement || target2 instanceof HTMLSelectElement || target2.closest("[contenteditable]:not([contenteditable='false'])") !== null;
1014
1015
  }
1015
1016
  function matchesShortcut(event, shortcut, platform) {
1016
1017
  const tokens = shortcut.split("+").map((token) => token.trim().toLowerCase()).filter((token) => token.length > 0);
@@ -1060,15 +1061,15 @@ function unionRects(rects) {
1060
1061
  height: bottom - top
1061
1062
  });
1062
1063
  }
1063
- function getElementRect(element, view) {
1064
- const display = view.getComputedStyle(element).display;
1064
+ function getElementRect(element2, view) {
1065
+ const display = view.getComputedStyle(element2).display;
1065
1066
  if (display.startsWith("inline")) {
1066
- const lineBoxRect = unionRects(Array.from(element.getClientRects()));
1067
+ const lineBoxRect = unionRects(Array.from(element2.getClientRects()));
1067
1068
  if (lineBoxRect !== void 0) {
1068
1069
  return lineBoxRect;
1069
1070
  }
1070
1071
  }
1071
- return toElementRect(element.getBoundingClientRect());
1072
+ return toElementRect(element2.getBoundingClientRect());
1072
1073
  }
1073
1074
 
1074
1075
  // src/ui/ui-constants.ts
@@ -1079,32 +1080,32 @@ var UI_Z_INDEX = Object.freeze({
1079
1080
  });
1080
1081
 
1081
1082
  // src/picker/hit-test.ts
1082
- function isInsideSpotPatchUI(element) {
1083
- if (element.closest(`[${UI_MARKER_ATTRIBUTE}]`) !== null) {
1083
+ function isInsideSpotPatchUI(element2) {
1084
+ if (element2.closest(`[${UI_MARKER_ATTRIBUTE}]`) !== null) {
1084
1085
  return true;
1085
1086
  }
1086
- const root = element.getRootNode();
1087
+ const root = element2.getRootNode();
1087
1088
  return root instanceof ShadowRoot && root.host.hasAttribute(UI_MARKER_ATTRIBUTE);
1088
1089
  }
1089
- function hasVisibleArea(element, view) {
1090
- const style = view.getComputedStyle(element);
1090
+ function hasVisibleArea(element2, view) {
1091
+ const style = view.getComputedStyle(element2);
1091
1092
  if (style.display === "none" || style.visibility === "hidden") {
1092
1093
  return false;
1093
1094
  }
1094
- const rect = element.getBoundingClientRect();
1095
+ const rect = element2.getBoundingClientRect();
1095
1096
  return rect.width > 0 && rect.height > 0;
1096
1097
  }
1097
- function isDocumentRoot(element) {
1098
- return element.tagName === "HTML" || element.tagName === "BODY";
1098
+ function isDocumentRoot(element2) {
1099
+ return element2.tagName === "HTML" || element2.tagName === "BODY";
1099
1100
  }
1100
1101
  function pickElementAt(document, view, clientX, clientY) {
1101
1102
  const visibleCandidates = document.elementsFromPoint(clientX, clientY).filter(
1102
- (element) => !isInsideSpotPatchUI(element) && hasVisibleArea(element, view)
1103
+ (element2) => !isInsideSpotPatchUI(element2) && hasVisibleArea(element2, view)
1103
1104
  );
1104
- return visibleCandidates.find((element) => !isDocumentRoot(element)) ?? visibleCandidates[0];
1105
+ return visibleCandidates.find((element2) => !isDocumentRoot(element2)) ?? visibleCandidates[0];
1105
1106
  }
1106
- function isSpotPatchUIEventTarget(target) {
1107
- return target instanceof Element && isInsideSpotPatchUI(target);
1107
+ function isSpotPatchUIEventTarget(target2) {
1108
+ return target2 instanceof Element && isInsideSpotPatchUI(target2);
1108
1109
  }
1109
1110
 
1110
1111
  // src/prompt/prompt-composer.ts
@@ -1183,10 +1184,10 @@ function fenced(language, value) {
1183
1184
  ${value}
1184
1185
  ${fence}`;
1185
1186
  }
1186
- function sourceLocation(target, messages) {
1187
- const path = target.code?.relativePath ?? target.source.relativePath;
1188
- const line = target.source.line;
1189
- const column = target.source.column;
1187
+ function sourceLocation(target2, messages) {
1188
+ const path = target2.code?.relativePath ?? target2.source.relativePath;
1189
+ const line = target2.source.line;
1190
+ const column = target2.source.column;
1190
1191
  if (path === void 0) {
1191
1192
  return messages.unavailable;
1192
1193
  }
@@ -1205,10 +1206,10 @@ function renderRule(rule) {
1205
1206
  }`;
1206
1207
  return [...metadata, body].join("\n");
1207
1208
  }
1208
- function renderStyles(target, rules, messages) {
1209
+ function renderStyles(target2, rules, messages) {
1209
1210
  const metadata = [
1210
- target.styles.classNames.length === 0 ? void 0 : `/* classes: ${target.styles.classNames.map(sanitizeCssText).join(" ")} */`,
1211
- target.styles.inlineStyle === void 0 ? void 0 : `/* inline: ${sanitizeCssText(target.styles.inlineStyle)} */`
1211
+ target2.styles.classNames.length === 0 ? void 0 : `/* classes: ${target2.styles.classNames.map(sanitizeCssText).join(" ")} */`,
1212
+ target2.styles.inlineStyle === void 0 ? void 0 : `/* inline: ${sanitizeCssText(target2.styles.inlineStyle)} */`
1212
1213
  ].filter((line) => line !== void 0);
1213
1214
  const blocks = [...metadata, ...rules.map(renderRule)];
1214
1215
  return blocks.length === 0 ? messages.unavailable : fenced("css", blocks.join("\n\n"));
@@ -1221,41 +1222,47 @@ function renderComputed(entries, messages) {
1221
1222
  ).join("\n")
1222
1223
  );
1223
1224
  }
1224
- function renderTarget(target, draft, index, messages) {
1225
+ function renderTarget(target2, draft, index, messages) {
1225
1226
  const reactLines = [
1226
- `- ${messages.adapter}: ${target.react.supported ? messages.supported : messages.unsupported}`,
1227
- ...target.react.version === void 0 ? [] : [`- ${messages.reactVersion}: ${(0, import_shared4.redactSensitiveText)(target.react.version)}`],
1228
- ...target.react.componentName === void 0 ? [] : [
1229
- `- ${messages.component}: ${(0, import_shared4.redactSensitiveText)(target.react.componentName)}`
1227
+ `- ${messages.adapter}: ${target2.react.supported ? messages.supported : messages.unsupported}`,
1228
+ ...target2.react.version === void 0 ? [] : [`- ${messages.reactVersion}: ${(0, import_shared4.redactSensitiveText)(target2.react.version)}`],
1229
+ ...target2.react.componentName === void 0 ? [] : [
1230
+ `- ${messages.component}: ${(0, import_shared4.redactSensitiveText)(target2.react.componentName)}`
1230
1231
  ],
1231
1232
  ...draft.reactStack.length === 0 ? [] : [
1232
1233
  `- ${messages.stack}: ${draft.reactStack.map(import_shared4.redactSensitiveText).join(" > ")}`
1233
1234
  ]
1234
1235
  ];
1235
- const code = target.code === void 0 || draft.codeLines.length === 0 ? messages.unavailable : `- ${messages.boundary}: ${target.code.boundary}
1236
+ const code2 = target2.code === void 0 || draft.codeLines.length === 0 ? messages.unavailable : `- ${messages.boundary}: ${target2.code.boundary}
1236
1237
 
1237
1238
  ${fenced(
1238
- target.code.language,
1239
+ target2.code.language,
1239
1240
  (0, import_shared4.redactSensitiveText)(draft.codeLines.join("\n"))
1240
1241
  )}`;
1241
1242
  return [
1242
1243
  `### ${messages.target(index + 1)}`,
1243
1244
  `#### ${messages.requestedChange}`,
1244
1245
  draft.instruction,
1246
+ ...target2.page === void 0 ? [] : [
1247
+ `#### ${messages.pageEnvironment}`,
1248
+ `- ${messages.url}: <${(0, import_shared4.sanitizeUrl)(target2.page.url, target2.page.url)}>
1249
+ - ${messages.pathname}: ${(0, import_shared4.redactSensitiveText)(target2.page.pathname)}
1250
+ - ${messages.title}: ${(0, import_shared4.redactSensitiveText)(target2.page.title) || messages.unavailable}`
1251
+ ],
1245
1252
  `#### ${messages.reactContext}`,
1246
1253
  reactLines.join("\n"),
1247
1254
  `#### ${messages.source}`,
1248
- `- ${messages.file}: ${sourceLocation(target, messages)}
1249
- - ${messages.origin}: ${target.source.origin}
1250
- - ${messages.confidence}: ${target.source.confidence}`,
1255
+ `- ${messages.file}: ${sourceLocation(target2, messages)}
1256
+ - ${messages.origin}: ${target2.source.origin}
1257
+ - ${messages.confidence}: ${target2.source.confidence}`,
1251
1258
  `#### ${messages.element}`,
1252
1259
  fenced("html", (0, import_shared4.redactSensitiveText)(draft.dom)),
1253
1260
  `#### ${messages.styles}`,
1254
- renderStyles(target, draft.matchedRules, messages),
1261
+ renderStyles(target2, draft.matchedRules, messages),
1255
1262
  `#### ${messages.computedStyles}`,
1256
1263
  renderComputed(draft.computedEntries, messages),
1257
1264
  `#### ${messages.code}`,
1258
- code,
1265
+ code2,
1259
1266
  `#### ${messages.warnings}`,
1260
1267
  draft.warnings.length === 0 ? `- ${messages.none}` : draft.warnings.map((warning) => `- ${(0, import_shared4.redactSensitiveText)(warning)}`).join("\n")
1261
1268
  ].join("\n\n");
@@ -1271,19 +1278,19 @@ function renderPrompt(annotation, draft) {
1271
1278
  - ${messages.viewport}: ${String(annotation.page.viewportWidth)} \xD7 ${String(annotation.page.viewportHeight)}
1272
1279
  - ${messages.devicePixelRatio}: ${String(annotation.page.devicePixelRatio)}`,
1273
1280
  `## ${messages.selectedTargets(annotation.targets.length)}`,
1274
- annotation.targets.map((target, index) => {
1281
+ annotation.targets.map((target2, index) => {
1275
1282
  const targetDraft = draft.targets[index];
1276
1283
  if (targetDraft === void 0) {
1277
1284
  throw new RangeError("Prompt target draft is missing.");
1278
1285
  }
1279
- return renderTarget(target, targetDraft, index, messages);
1286
+ return renderTarget(target2, targetDraft, index, messages);
1280
1287
  }).join("\n\n"),
1281
1288
  `## ${messages.modificationRequirements}`,
1282
1289
  messages.modificationConstraint
1283
1290
  ].join("\n\n");
1284
1291
  }
1285
- function shrinkCode(target, draft) {
1286
- if (draft.codeLines.length === 0 || target.code === void 0) {
1292
+ function shrinkCode(target2, draft) {
1293
+ if (draft.codeLines.length === 0 || target2.code === void 0) {
1287
1294
  return false;
1288
1295
  }
1289
1296
  if (draft.codeLines.length === 1) {
@@ -1295,9 +1302,9 @@ function shrinkCode(target, draft) {
1295
1302
  }
1296
1303
  return true;
1297
1304
  }
1298
- const selectedLine = target.source.line ?? target.code.startLine;
1299
- const firstDistance = Math.abs(target.code.startLine - selectedLine);
1300
- const lastLine = target.code.startLine + draft.codeLines.length - 1;
1305
+ const selectedLine = target2.source.line ?? target2.code.startLine;
1306
+ const firstDistance = Math.abs(target2.code.startLine - selectedLine);
1307
+ const lastLine = target2.code.startLine + draft.codeLines.length - 1;
1301
1308
  const lastDistance = Math.abs(lastLine - selectedLine);
1302
1309
  if (lastDistance >= firstDistance) {
1303
1310
  draft.codeLines.pop();
@@ -1306,15 +1313,15 @@ function shrinkCode(target, draft) {
1306
1313
  }
1307
1314
  return true;
1308
1315
  }
1309
- function createTargetDraft(target) {
1316
+ function createTargetDraft(target2) {
1310
1317
  return {
1311
- instruction: (0, import_shared4.redactSensitiveText)(target.instruction.trim()),
1312
- reactStack: [...target.react.componentStack],
1313
- dom: target.element.sanitizedHtml,
1314
- matchedRules: [...target.styles.matchedRules],
1315
- computedEntries: Object.entries(target.styles.computed),
1316
- codeLines: target.code?.excerpt.split(/\r?\n/u) ?? [],
1317
- warnings: Array.from(/* @__PURE__ */ new Set([...target.styles.warnings, ...target.warnings]))
1318
+ instruction: (0, import_shared4.redactSensitiveText)(target2.instruction.trim()),
1319
+ reactStack: [...target2.react.componentStack],
1320
+ dom: target2.element.sanitizedHtml,
1321
+ matchedRules: [...target2.styles.matchedRules],
1322
+ computedEntries: Object.entries(target2.styles.computed),
1323
+ codeLines: target2.code?.excerpt.split(/\r?\n/u) ?? [],
1324
+ warnings: Array.from(/* @__PURE__ */ new Set([...target2.styles.warnings, ...target2.warnings]))
1318
1325
  };
1319
1326
  }
1320
1327
  function createDraft(annotation) {
@@ -1343,9 +1350,9 @@ function shrinkTargetsFairly(annotation, draft, cursor) {
1343
1350
  for (const operation of operations) {
1344
1351
  for (let offset = 0; offset < annotation.targets.length; offset += 1) {
1345
1352
  const index = (cursor + offset) % annotation.targets.length;
1346
- const target = annotation.targets[index];
1353
+ const target2 = annotation.targets[index];
1347
1354
  const targetDraft = draft.targets[index];
1348
- if (target !== void 0 && targetDraft !== void 0 && operation(target, targetDraft)) {
1355
+ if (target2 !== void 0 && targetDraft !== void 0 && operation(target2, targetDraft)) {
1349
1356
  return (index + 1) % annotation.targets.length;
1350
1357
  }
1351
1358
  }
@@ -1354,15 +1361,15 @@ function shrinkTargetsFairly(annotation, draft, cursor) {
1354
1361
  }
1355
1362
  function renderCompactPrompt(annotation, draft) {
1356
1363
  const messages = PROMPT_MESSAGES[annotation.locale];
1357
- const targets = annotation.targets.map((target, index) => {
1364
+ const targets = annotation.targets.map((target2, index) => {
1358
1365
  const targetDraft = draft.targets[index];
1359
- const component = target.react.componentName;
1366
+ const component = target2.react.componentName;
1360
1367
  return [
1361
1368
  `### ${messages.target(index + 1)}`,
1362
- `- ${messages.requestedChange}: ${targetDraft?.instruction ?? (0, import_shared4.redactSensitiveText)(target.instruction)}`,
1363
- `- ${messages.file}: ${sourceLocation(target, messages)}`,
1369
+ `- ${messages.requestedChange}: ${targetDraft?.instruction ?? (0, import_shared4.redactSensitiveText)(target2.instruction)}`,
1370
+ `- ${messages.file}: ${sourceLocation(target2, messages)}`,
1364
1371
  ...component === void 0 ? [] : [`- ${messages.component}: ${(0, import_shared4.redactSensitiveText)(component)}`],
1365
- `- ${messages.element}: ${(0, import_shared4.redactSensitiveText)(openingTag2(targetDraft?.dom ?? target.element.sanitizedHtml, messages.unavailable))}`
1372
+ `- ${messages.element}: ${(0, import_shared4.redactSensitiveText)(openingTag2(targetDraft?.dom ?? target2.element.sanitizedHtml, messages.unavailable))}`
1366
1373
  ].join("\n");
1367
1374
  });
1368
1375
  return [
@@ -1420,7 +1427,10 @@ var STATES = Object.freeze({
1420
1427
  });
1421
1428
  var INITIAL_RUNTIME_STATE = STATES.idle;
1422
1429
  function reduceIdle(event) {
1423
- return event.type === "ACTIVATE" ? STATES.inspecting : STATES.idle;
1430
+ if (event.type === "ACTIVATE") {
1431
+ return STATES.inspecting;
1432
+ }
1433
+ return event.type === "RESTORE" ? STATES.selected : STATES.idle;
1424
1434
  }
1425
1435
  function reduceInspecting(event) {
1426
1436
  switch (event.type) {
@@ -1472,17 +1482,132 @@ function reduceRuntimeState(state, event) {
1472
1482
  }
1473
1483
  }
1474
1484
 
1485
+ // src/state/selection-session.ts
1486
+ var STORAGE_PREFIX = "spotpatch:selection:";
1487
+ var SNAPSHOT_VERSION = 1;
1488
+ var MAX_SNAPSHOT_CHARACTERS = 1e6;
1489
+ function record(value) {
1490
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1491
+ }
1492
+ function text(value) {
1493
+ return typeof value === "string";
1494
+ }
1495
+ function optionalText(value) {
1496
+ return value === void 0 || text(value);
1497
+ }
1498
+ function strings(value) {
1499
+ return Array.isArray(value) && value.length <= 256 && value.every(text);
1500
+ }
1501
+ function finiteNumber(value) {
1502
+ return typeof value === "number" && Number.isFinite(value);
1503
+ }
1504
+ function optionalNumber(value) {
1505
+ return value === void 0 || finiteNumber(value);
1506
+ }
1507
+ function source(value) {
1508
+ const item = record(value);
1509
+ return item !== void 0 && text(item.origin) && text(item.confidence) && optionalText(item.fileId) && optionalText(item.relativePath) && optionalNumber(item.line) && optionalNumber(item.column);
1510
+ }
1511
+ function react(value) {
1512
+ const item = record(value);
1513
+ return item !== void 0 && typeof item.supported === "boolean" && optionalText(item.version) && optionalText(item.componentName) && strings(item.componentStack) && (item.source === void 0 || source(item.source));
1514
+ }
1515
+ function page(value) {
1516
+ const item = record(value);
1517
+ return item !== void 0 && text(item.url) && text(item.pathname) && text(item.title) && finiteNumber(item.viewportWidth) && finiteNumber(item.viewportHeight) && finiteNumber(item.devicePixelRatio);
1518
+ }
1519
+ function element(value) {
1520
+ const item = record(value);
1521
+ const rect = record(item?.rect);
1522
+ 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);
1523
+ }
1524
+ function styles(value) {
1525
+ const item = record(value);
1526
+ const computed = record(item?.computed);
1527
+ return item !== void 0 && strings(item.classNames) && optionalText(item.inlineStyle) && Array.isArray(item.matchedRules) && item.matchedRules.length <= 256 && item.matchedRules.every((value2) => {
1528
+ const rule = record(value2);
1529
+ return rule !== void 0 && text(rule.selector) && text(rule.declarations) && optionalText(rule.source) && optionalText(rule.media);
1530
+ }) && computed !== void 0 && Object.values(computed).every(text) && strings(item.warnings);
1531
+ }
1532
+ function code(value) {
1533
+ const item = record(value);
1534
+ return item !== void 0 && text(item.relativePath) && text(item.language) && finiteNumber(item.startLine) && finiteNumber(item.endLine) && text(item.excerpt) && text(item.boundary);
1535
+ }
1536
+ function target(value) {
1537
+ const item = record(value);
1538
+ 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));
1539
+ }
1540
+ function parseSnapshot(value, maximumTargets) {
1541
+ const item = record(value);
1542
+ 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)) {
1543
+ return void 0;
1544
+ }
1545
+ const targets = item.targets;
1546
+ const ids = targets.map(({ id }) => id);
1547
+ if (new Set(ids).size !== ids.length || item.activeTargetId !== void 0 && !ids.includes(item.activeTargetId)) {
1548
+ return void 0;
1549
+ }
1550
+ return {
1551
+ ...item.activeTargetId === void 0 ? {} : { activeTargetId: item.activeTargetId },
1552
+ open: item.open,
1553
+ sequence: Number(item.sequence),
1554
+ targets
1555
+ };
1556
+ }
1557
+ function createSelectionSession(window, sessionId, maximumTargets) {
1558
+ const key = `${STORAGE_PREFIX}${sessionId}`;
1559
+ let storage;
1560
+ try {
1561
+ storage = window.sessionStorage;
1562
+ } catch {
1563
+ storage = void 0;
1564
+ }
1565
+ return Object.freeze({
1566
+ clear() {
1567
+ try {
1568
+ storage?.removeItem(key);
1569
+ } catch {
1570
+ }
1571
+ },
1572
+ load() {
1573
+ let serialized;
1574
+ try {
1575
+ serialized = storage?.getItem(key) ?? null;
1576
+ } catch {
1577
+ return void 0;
1578
+ }
1579
+ if (serialized === null || serialized.length > MAX_SNAPSHOT_CHARACTERS) {
1580
+ return void 0;
1581
+ }
1582
+ try {
1583
+ return parseSnapshot(JSON.parse(serialized), maximumTargets);
1584
+ } catch {
1585
+ return void 0;
1586
+ }
1587
+ },
1588
+ save(snapshot) {
1589
+ try {
1590
+ storage?.setItem(
1591
+ key,
1592
+ JSON.stringify({ version: SNAPSHOT_VERSION, ...snapshot })
1593
+ );
1594
+ } catch {
1595
+ }
1596
+ }
1597
+ });
1598
+ }
1599
+
1475
1600
  // src/source/source-resolver.ts
1476
1601
  var import_shared5 = require("@spotpatch/shared");
1477
1602
  var EMPTY_REACT_CONTEXT = Object.freeze({
1478
1603
  supported: false,
1479
1604
  componentStack: Object.freeze([])
1480
1605
  });
1481
- function directMarker(element) {
1482
- return (0, import_shared5.parseSourceMarker)(element.getAttribute(import_shared5.SOURCE_MARKER_ATTRIBUTE));
1606
+ function directMarker(element2) {
1607
+ return (0, import_shared5.parseSourceMarker)(element2.getAttribute(import_shared5.SOURCE_MARKER_ATTRIBUTE));
1483
1608
  }
1484
- function ancestorMarker(element) {
1485
- let ancestor = element.parentElement;
1609
+ function ancestorMarker(element2) {
1610
+ let ancestor = element2.parentElement;
1486
1611
  while (ancestor !== null) {
1487
1612
  const marker = directMarker(ancestor);
1488
1613
  if (marker !== void 0) {
@@ -1504,21 +1629,21 @@ function markerSource(marker, origin, confidence) {
1504
1629
  function unknownSource() {
1505
1630
  return Object.freeze({ origin: "none", confidence: "unknown" });
1506
1631
  }
1507
- function sourceRefToMarker(source) {
1508
- return source.fileId === void 0 || source.line === void 0 || source.column === void 0 ? void 0 : Object.freeze({
1509
- fileId: source.fileId,
1510
- line: source.line,
1511
- column: source.column
1632
+ function sourceRefToMarker(source2) {
1633
+ return source2.fileId === void 0 || source2.line === void 0 || source2.column === void 0 ? void 0 : Object.freeze({
1634
+ fileId: source2.fileId,
1635
+ line: source2.line,
1636
+ column: source2.column
1512
1637
  });
1513
1638
  }
1514
1639
  function createSourceResolver(options) {
1515
1640
  let adapterEnabled = true;
1516
- function inspectReact(element) {
1641
+ function inspectReact(element2) {
1517
1642
  if (!adapterEnabled) {
1518
1643
  return EMPTY_REACT_CONTEXT;
1519
1644
  }
1520
1645
  try {
1521
- return options.adapter.inspect(element);
1646
+ return options.adapter.inspect(element2);
1522
1647
  } catch {
1523
1648
  adapterEnabled = false;
1524
1649
  options.onAdapterError?.();
@@ -1526,26 +1651,26 @@ function createSourceResolver(options) {
1526
1651
  }
1527
1652
  }
1528
1653
  return Object.freeze({
1529
- resolve(element) {
1530
- const marker = directMarker(element);
1531
- const react = inspectReact(element);
1654
+ resolve(element2) {
1655
+ const marker = directMarker(element2);
1656
+ const react2 = inspectReact(element2);
1532
1657
  if (marker !== void 0) {
1533
1658
  return Object.freeze({
1534
- react,
1659
+ react: react2,
1535
1660
  source: markerSource(marker, "jsx-host", "exact")
1536
1661
  });
1537
1662
  }
1538
- if (react.source !== void 0) {
1539
- return Object.freeze({ react, source: react.source });
1663
+ if (react2.source !== void 0) {
1664
+ return Object.freeze({ react: react2, source: react2.source });
1540
1665
  }
1541
- const nearestAncestor = ancestorMarker(element);
1666
+ const nearestAncestor = ancestorMarker(element2);
1542
1667
  if (nearestAncestor !== void 0) {
1543
1668
  return Object.freeze({
1544
- react,
1669
+ react: react2,
1545
1670
  source: markerSource(nearestAncestor, "dom-ancestor", "approximate")
1546
1671
  });
1547
1672
  }
1548
- return Object.freeze({ react, source: unknownSource() });
1673
+ return Object.freeze({ react: react2, source: unknownSource() });
1549
1674
  },
1550
1675
  dispose() {
1551
1676
  adapterEnabled = false;
@@ -1559,9 +1684,9 @@ var import_shared7 = require("@spotpatch/shared");
1559
1684
 
1560
1685
  // src/ui/dom.ts
1561
1686
  function createMarkedElement(document, tagName) {
1562
- const element = document.createElement(tagName);
1563
- element.setAttribute(UI_MARKER_ATTRIBUTE, "");
1564
- return element;
1687
+ const element2 = document.createElement(tagName);
1688
+ element2.setAttribute(UI_MARKER_ATTRIBUTE, "");
1689
+ return element2;
1565
1690
  }
1566
1691
  function createButton(document, label, className = "") {
1567
1692
  const button = createMarkedElement(document, "button");
@@ -2178,68 +2303,81 @@ function createAgentPanel(document, ai, localizer) {
2178
2303
  });
2179
2304
  }
2180
2305
 
2306
+ // src/ui/brand-mark-content.ts
2307
+ var BRAND_MARK_CONTENT = `
2308
+ <defs>
2309
+ <linearGradient id="locator-gradient" x1="76" y1="92" x2="436" y2="374" gradientUnits="userSpaceOnUse">
2310
+ <stop offset="0" stop-color="#B61CFF" />
2311
+ <stop offset="0.38" stop-color="#6D35FF" />
2312
+ <stop offset="0.72" stop-color="#168EFF" />
2313
+ <stop offset="1" stop-color="#00D9E9" />
2314
+ </linearGradient>
2315
+ <linearGradient id="left-code-gradient" x1="165" y1="166" x2="236" y2="258" gradientUnits="userSpaceOnUse">
2316
+ <stop stop-color="#A51EFF" />
2317
+ <stop offset="1" stop-color="#653BFF" />
2318
+ </linearGradient>
2319
+ <linearGradient id="right-code-gradient" x1="276" y1="166" x2="347" y2="258" gradientUnits="userSpaceOnUse">
2320
+ <stop stop-color="#158DFF" />
2321
+ <stop offset="1" stop-color="#00D8E9" />
2322
+ </linearGradient>
2323
+ <linearGradient id="bolt-gradient" x1="270" y1="111" x2="252" y2="365" gradientUnits="userSpaceOnUse">
2324
+ <stop stop-color="#6840FF" />
2325
+ <stop offset="0.48" stop-color="#257BFF" />
2326
+ <stop offset="1" stop-color="#00CBEF" />
2327
+ </linearGradient>
2328
+ </defs>
2329
+ <path
2330
+ fill="url(#locator-gradient)"
2331
+ fill-rule="evenodd"
2332
+ clip-rule="evenodd"
2333
+ 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"
2334
+ />
2335
+ <rect x="238" y="20" width="36" height="84" rx="4" fill="url(#locator-gradient)" />
2336
+ <rect x="62" y="196" width="84" height="36" rx="4" fill="url(#locator-gradient)" />
2337
+ <rect x="366" y="196" width="84" height="36" rx="4" fill="url(#locator-gradient)" />
2338
+ <path
2339
+ d="M213.5 160L158 211.5L213.5 263L238 236.5L211 211.5L238 186.5L213.5 160Z"
2340
+ fill="url(#left-code-gradient)"
2341
+ />
2342
+ <path
2343
+ d="M298.5 160L354 211.5L298.5 263L274 236.5L301 211.5L274 186.5L298.5 160Z"
2344
+ fill="url(#right-code-gradient)"
2345
+ />
2346
+ <path
2347
+ d="M283 108L232 212L266 253L238 369L302 237L267 198L283 108Z"
2348
+ fill="url(#bolt-gradient)"
2349
+ />
2350
+ `;
2351
+
2181
2352
  // src/ui/brand-mark.ts
2182
2353
  var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
2354
+ function resolveBrandMarkContent(override) {
2355
+ if (override !== void 0) {
2356
+ return { content: override, viewBox: "0 0 512 512" };
2357
+ }
2358
+ const injected = typeof __SPOTPATCH_BRAND_MARK_CONTENT__ === "string" ? __SPOTPATCH_BRAND_MARK_CONTENT__ : void 0;
2359
+ if (typeof injected === "string") {
2360
+ return { content: injected, viewBox: "0 0 512 512" };
2361
+ }
2362
+ return true ? { content: BRAND_MARK_CONTENT, viewBox: "0 0 512 512" } : { content: FALLBACK_BRAND_MARK_CONTENT, viewBox: "0 0 1 1" };
2363
+ }
2183
2364
  function svgElement(document, tagName, attributes) {
2184
- const element = document.createElementNS(SVG_NAMESPACE, tagName);
2365
+ const element2 = document.createElementNS(SVG_NAMESPACE, tagName);
2185
2366
  for (const [name, value] of Object.entries(attributes)) {
2186
- element.setAttribute(name, value);
2367
+ element2.setAttribute(name, value);
2187
2368
  }
2188
- return element;
2369
+ return element2;
2189
2370
  }
2190
- function createBrandMark(document) {
2371
+ function createBrandMark(document, content) {
2372
+ const resolved = resolveBrandMarkContent(content);
2191
2373
  const svg = svgElement(document, "svg", {
2374
+ xmlns: SVG_NAMESPACE,
2192
2375
  class: "spotpatch-brand-mark",
2193
- viewBox: "0 0 210 208",
2376
+ viewBox: resolved.viewBox,
2194
2377
  "aria-hidden": "true",
2195
2378
  focusable: "false"
2196
2379
  });
2197
- const definitions = svgElement(document, "defs", {});
2198
- const gradient = svgElement(document, "linearGradient", {
2199
- id: "spotpatch-brand-gradient",
2200
- x1: "35",
2201
- y1: "42",
2202
- x2: "184",
2203
- y2: "176",
2204
- gradientUnits: "userSpaceOnUse"
2205
- });
2206
- gradient.append(
2207
- svgElement(document, "stop", { offset: "0", "stop-color": "#7c22ff" }),
2208
- svgElement(document, "stop", { offset: ".52", "stop-color": "#4a69ff" }),
2209
- svgElement(document, "stop", { offset: "1", "stop-color": "#06d9ef" })
2210
- );
2211
- definitions.append(gradient);
2212
- const commonStroke = {
2213
- fill: "none",
2214
- stroke: "url(#spotpatch-brand-gradient)",
2215
- "stroke-linecap": "square",
2216
- "stroke-linejoin": "miter"
2217
- };
2218
- const outer = svgElement(document, "path", {
2219
- 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",
2220
- ...commonStroke,
2221
- "stroke-width": "14"
2222
- });
2223
- const topStem = svgElement(document, "path", {
2224
- d: "M106 25v42",
2225
- ...commonStroke,
2226
- "stroke-width": "14"
2227
- });
2228
- const sideStems = svgElement(document, "path", {
2229
- d: "M35 109h20m102 0h38",
2230
- ...commonStroke,
2231
- "stroke-width": "13"
2232
- });
2233
- const brackets = svgElement(document, "path", {
2234
- d: "m94 89-18 19 18 17m39-36 18 19-18 17",
2235
- ...commonStroke,
2236
- "stroke-width": "9"
2237
- });
2238
- const bolt = svgElement(document, "path", {
2239
- d: "m119 61-20 45 15 11-13 47 30-56-16-12 4-35Z",
2240
- fill: "url(#spotpatch-brand-gradient)"
2241
- });
2242
- svg.append(definitions, outer, topStem, sideStems, brackets, bolt);
2380
+ svg.innerHTML = resolved.content;
2243
2381
  return svg;
2244
2382
  }
2245
2383
 
@@ -2254,32 +2392,32 @@ function clamp(value, minimum, maximum) {
2254
2392
  function calculateDialogPlacement({
2255
2393
  dialogHeight,
2256
2394
  dialogWidth,
2257
- target,
2395
+ target: target2,
2258
2396
  viewportHeight,
2259
2397
  viewportWidth
2260
2398
  }) {
2261
2399
  const maxLeft = viewportWidth - dialogWidth - VIEWPORT_MARGIN;
2262
2400
  const maxTop = viewportHeight - dialogHeight - VIEWPORT_MARGIN;
2263
- const targetCenterX = target.x + target.width / 2;
2264
- const targetCenterY = target.y + target.height / 2;
2401
+ const targetCenterX = target2.x + target2.width / 2;
2402
+ const targetCenterY = target2.y + target2.height / 2;
2265
2403
  const centeredLeft = clamp(targetCenterX - dialogWidth / 2, VIEWPORT_MARGIN, maxLeft);
2266
2404
  const centeredTop = clamp(targetCenterY - dialogHeight / 2, VIEWPORT_MARGIN, maxTop);
2267
2405
  let left = centeredLeft;
2268
2406
  let top = centeredTop;
2269
2407
  let mode = "viewport";
2270
- if (target.width >= dialogWidth + TARGET_INSET * 2 && target.height >= dialogHeight + TARGET_INSET * 2) {
2408
+ if (target2.width >= dialogWidth + TARGET_INSET * 2 && target2.height >= dialogHeight + TARGET_INSET * 2) {
2271
2409
  mode = "center";
2272
- } else if (target.y - dialogHeight - TARGET_GAP >= VIEWPORT_MARGIN) {
2273
- top = target.y - dialogHeight - TARGET_GAP;
2410
+ } else if (target2.y - dialogHeight - TARGET_GAP >= VIEWPORT_MARGIN) {
2411
+ top = target2.y - dialogHeight - TARGET_GAP;
2274
2412
  mode = "above";
2275
- } else if (target.y + target.height + TARGET_GAP + dialogHeight <= viewportHeight - VIEWPORT_MARGIN) {
2276
- top = target.y + target.height + TARGET_GAP;
2413
+ } else if (target2.y + target2.height + TARGET_GAP + dialogHeight <= viewportHeight - VIEWPORT_MARGIN) {
2414
+ top = target2.y + target2.height + TARGET_GAP;
2277
2415
  mode = "below";
2278
- } else if (target.x + target.width + TARGET_GAP + dialogWidth <= viewportWidth - VIEWPORT_MARGIN) {
2279
- left = target.x + target.width + TARGET_GAP;
2416
+ } else if (target2.x + target2.width + TARGET_GAP + dialogWidth <= viewportWidth - VIEWPORT_MARGIN) {
2417
+ left = target2.x + target2.width + TARGET_GAP;
2280
2418
  mode = "right";
2281
- } else if (target.x - dialogWidth - TARGET_GAP >= VIEWPORT_MARGIN) {
2282
- left = target.x - dialogWidth - TARGET_GAP;
2419
+ } else if (target2.x - dialogWidth - TARGET_GAP >= VIEWPORT_MARGIN) {
2420
+ left = target2.x - dialogWidth - TARGET_GAP;
2283
2421
  mode = "left";
2284
2422
  }
2285
2423
  return Object.freeze({
@@ -2596,7 +2734,7 @@ var UI_MESSAGES = Object.freeze({
2596
2734
  noSelectable: "No selectable element was found.",
2597
2735
  allTargetsRemoved: "All targets were removed. Choose an element to continue.",
2598
2736
  targetRemoved: "Selected target removed.",
2599
- detachedTargetRemoved: "A removed page element was dropped from the selection.",
2737
+ detachedTargetPreserved: "The page element was unloaded; its collected context remains selected.",
2600
2738
  contextWarning: "Browser context collection completed with a warning.",
2601
2739
  contextCollected: "Browser context collected.",
2602
2740
  editorOpening: "Opening source\u2026",
@@ -2732,7 +2870,7 @@ var UI_MESSAGES = Object.freeze({
2732
2870
  noSelectable: "\u5F53\u524D\u4F4D\u7F6E\u6CA1\u6709\u53EF\u9009\u62E9\u7684\u5143\u7D20\u3002",
2733
2871
  allTargetsRemoved: "\u5DF2\u79FB\u9664\u5168\u90E8\u76EE\u6807\uFF0C\u8BF7\u91CD\u65B0\u9009\u62E9\u5143\u7D20\u3002",
2734
2872
  targetRemoved: "\u5DF2\u79FB\u9664\u9009\u4E2D\u76EE\u6807\u3002",
2735
- detachedTargetRemoved: "\u9875\u9762\u4E2D\u7684\u76EE\u6807\u5DF2\u5378\u8F7D\uFF0C\u5DF2\u4ECE\u5F53\u524D\u9009\u62E9\u4E2D\u79FB\u9664\u3002",
2873
+ detachedTargetPreserved: "\u9875\u9762\u5143\u7D20\u5DF2\u5378\u8F7D\uFF0C\u5DF2\u91C7\u96C6\u7684\u4E0A\u4E0B\u6587\u4ECD\u4FDD\u7559\u5728\u9009\u62E9\u4E2D\u3002",
2736
2874
  contextWarning: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\uFF0C\u4F46\u5B58\u5728\u8B66\u544A\u3002",
2737
2875
  contextCollected: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\u3002",
2738
2876
  editorOpening: "\u6B63\u5728\u6253\u5F00\u6E90\u7801\u2026\u2026",
@@ -3627,11 +3765,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3627
3765
  editorFeedback.textContent = state === "opening" ? messages.announcements.editorOpening : state === "success" ? messages.announcements.editorOpened : state === "error" ? messages.announcements.editorFailed : "";
3628
3766
  placeDialog();
3629
3767
  }
3630
- function statusText(target) {
3631
- if (target.status === "ready") {
3768
+ function statusText(target2) {
3769
+ if (target2.status === "ready") {
3632
3770
  return messages.targets.statusReady;
3633
3771
  }
3634
- return target.status === "warning" ? messages.targets.statusPartial : messages.targets.statusCollecting;
3772
+ return target2.status === "warning" ? messages.targets.statusPartial : messages.targets.statusCollecting;
3635
3773
  }
3636
3774
  function instructionInput(targetId) {
3637
3775
  const inputs = targetList.querySelectorAll(
@@ -3648,14 +3786,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3648
3786
  const focusedTargetId = focusedInstruction?.dataset.targetInstructionId;
3649
3787
  const selectionStart = focusedInstruction?.selectionStart;
3650
3788
  const selectionEnd = focusedInstruction?.selectionEnd;
3651
- currentTargets = targets.map((target) => Object.freeze({ ...target }));
3789
+ currentTargets = targets.map((target2) => Object.freeze({ ...target2 }));
3652
3790
  currentMaximum = maximum;
3653
3791
  targetList.replaceChildren();
3654
3792
  const completeCount = targets.filter(
3655
- (target) => target.instruction.trim().length > 0
3793
+ (target2) => target2.instruction.trim().length > 0
3656
3794
  ).length;
3657
3795
  const instructionCharacters = targets.reduce(
3658
- (total, target) => total + target.instruction.trim().length,
3796
+ (total, target2) => total + target2.instruction.trim().length,
3659
3797
  0
3660
3798
  );
3661
3799
  targetCount.textContent = messages.targets.count(targets.length, maximum);
@@ -3675,18 +3813,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3675
3813
  addTargetButton.disabled = !editingEnabled || targets.length >= maximum;
3676
3814
  addTargetButton.title = targets.length >= maximum ? messages.targets.limitTitle(maximum) : messages.targets.addTitle;
3677
3815
  targetLabel.textContent = targets.length === 1 ? targets[0]?.label ?? messages.context.selectedElement : messages.context.selectedCount(targets.length);
3678
- for (const [index, target] of targets.entries()) {
3816
+ for (const [index, target2] of targets.entries()) {
3679
3817
  const item = createMarkedElement(document, "div");
3680
3818
  item.className = "spotpatch-target-item";
3681
- item.dataset.active = String(target.active);
3682
- item.dataset.status = target.status;
3683
- item.dataset.targetId = target.id;
3819
+ item.dataset.active = String(target2.active);
3820
+ item.dataset.status = target2.status;
3821
+ item.dataset.targetId = target2.id;
3684
3822
  const targetSummary = createMarkedElement(document, "div");
3685
3823
  targetSummary.className = "spotpatch-target-summary";
3686
3824
  const select = createButton(document, "", "spotpatch-target-select");
3687
- select.dataset.activateTargetId = target.id;
3825
+ select.dataset.activateTargetId = target2.id;
3688
3826
  select.setAttribute("aria-label", messages.targets.activate(index + 1));
3689
- select.setAttribute("aria-expanded", String(target.active));
3827
+ select.setAttribute("aria-expanded", String(target2.active));
3690
3828
  const number = createMarkedElement(document, "span");
3691
3829
  number.className = "spotpatch-target-index";
3692
3830
  number.textContent = String(index + 1);
@@ -3694,54 +3832,54 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3694
3832
  copy.className = "spotpatch-target-copy";
3695
3833
  const name = createMarkedElement(document, "span");
3696
3834
  name.className = "spotpatch-target-name";
3697
- name.textContent = target.label;
3698
- const source = createMarkedElement(document, "span");
3699
- source.className = "spotpatch-target-source";
3700
- source.textContent = `${statusText(target)} \xB7 ${target.source}`;
3701
- copy.append(name, source);
3702
- const instructionComplete = target.instruction.trim().length > 0;
3835
+ name.textContent = target2.label;
3836
+ const source2 = createMarkedElement(document, "span");
3837
+ source2.className = "spotpatch-target-source";
3838
+ source2.textContent = `${statusText(target2)} \xB7 ${target2.source}`;
3839
+ copy.append(name, source2);
3840
+ const instructionComplete = target2.instruction.trim().length > 0;
3703
3841
  const state = createMarkedElement(document, "span");
3704
3842
  state.className = "spotpatch-target-state";
3705
3843
  state.dataset.complete = String(instructionComplete);
3706
3844
  state.textContent = instructionComplete ? messages.targets.instructionReady : messages.targets.instructionMissing;
3707
3845
  select.append(number, copy, state);
3708
3846
  const open = createButton(document, "\u2197", "spotpatch-target-open");
3709
- open.dataset.openTargetId = target.id;
3710
- open.disabled = !target.canOpenEditor;
3847
+ open.dataset.openTargetId = target2.id;
3848
+ open.disabled = !target2.canOpenEditor;
3711
3849
  open.setAttribute("aria-label", messages.actions.openTarget(index + 1));
3712
3850
  open.title = messages.actions.openTarget(index + 1);
3713
3851
  const remove = createButton(document, "\xD7", "spotpatch-target-remove");
3714
- remove.dataset.removeTargetId = target.id;
3852
+ remove.dataset.removeTargetId = target2.id;
3715
3853
  remove.disabled = !editingEnabled;
3716
3854
  remove.setAttribute("aria-label", messages.targets.remove(index + 1));
3717
3855
  remove.title = messages.targets.removeTitle;
3718
3856
  targetSummary.append(select, open, remove);
3719
3857
  item.append(targetSummary);
3720
- if (target.active) {
3858
+ if (target2.active) {
3721
3859
  const editor = createMarkedElement(document, "label");
3722
3860
  editor.className = "spotpatch-target-editor";
3723
3861
  const editorHead = createMarkedElement(document, "span");
3724
3862
  editorHead.className = "spotpatch-target-editor-head";
3725
3863
  const editorLabel = createMarkedElement(document, "span");
3726
3864
  editorLabel.className = "spotpatch-target-editor-label";
3727
- editorLabel.textContent = messages.targets.instructionLabel(target.label);
3865
+ editorLabel.textContent = messages.targets.instructionLabel(target2.label);
3728
3866
  const characterCount = createMarkedElement(document, "span");
3729
3867
  characterCount.className = "spotpatch-target-editor-count";
3730
3868
  characterCount.textContent = messages.targets.instructionCount(
3731
- target.instruction.length,
3869
+ target2.instruction.length,
3732
3870
  import_shared7.MAX_TARGET_INSTRUCTION_CHARACTERS
3733
3871
  );
3734
3872
  editorHead.append(editorLabel, characterCount);
3735
3873
  const input = createMarkedElement(document, "textarea");
3736
3874
  input.rows = 4;
3737
3875
  input.maxLength = import_shared7.MAX_TARGET_INSTRUCTION_CHARACTERS;
3738
- input.value = target.instruction;
3876
+ input.value = target2.instruction;
3739
3877
  input.placeholder = messages.targets.instructionPlaceholder;
3740
3878
  input.disabled = !editingEnabled;
3741
- input.dataset.targetInstructionId = target.id;
3879
+ input.dataset.targetInstructionId = target2.id;
3742
3880
  input.setAttribute(
3743
3881
  "aria-label",
3744
- messages.targets.instructionLabel(target.label)
3882
+ messages.targets.instructionLabel(target2.label)
3745
3883
  );
3746
3884
  editor.append(editorHead, input);
3747
3885
  item.append(editor);
@@ -3758,7 +3896,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3758
3896
  }
3759
3897
  function updateTargetInstruction(targetId, instruction) {
3760
3898
  currentTargets = currentTargets.map(
3761
- (target) => target.id === targetId ? Object.freeze({ ...target, instruction }) : target
3899
+ (target2) => target2.id === targetId ? Object.freeze({ ...target2, instruction }) : target2
3762
3900
  );
3763
3901
  const item = Array.from(
3764
3902
  targetList.querySelectorAll(".spotpatch-target-item")
@@ -3780,11 +3918,11 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3780
3918
  );
3781
3919
  }
3782
3920
  targetComplete.textContent = messages.targets.complete(
3783
- currentTargets.filter((target) => target.instruction.trim().length > 0).length,
3921
+ currentTargets.filter((target2) => target2.instruction.trim().length > 0).length,
3784
3922
  currentTargets.length
3785
3923
  );
3786
3924
  const instructionCharacters = currentTargets.reduce(
3787
- (total, target) => total + target.instruction.trim().length,
3925
+ (total, target2) => total + target2.instruction.trim().length,
3788
3926
  0
3789
3927
  );
3790
3928
  const instructionLimitExceeded = instructionCharacters > import_shared7.MAX_ANNOTATION_INSTRUCTION_CHARACTERS;
@@ -3799,18 +3937,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3799
3937
  }
3800
3938
  function showSelectionHighlights(targets) {
3801
3939
  selectionHighlights.replaceChildren();
3802
- activeSelectionRect = targets.find((target) => target.active)?.rect;
3940
+ activeSelectionRect = targets.find((target2) => target2.active)?.rect;
3803
3941
  currentRect = activeSelectionRect;
3804
- for (const [index, target] of targets.entries()) {
3942
+ for (const [index, target2] of targets.entries()) {
3805
3943
  const box = createMarkedElement(document, "div");
3806
3944
  box.className = "spotpatch-selection-highlight";
3807
- box.dataset.targetId = target.id;
3808
- box.dataset.active = String(target.active);
3809
- box.style.transform = `translate(${String(target.rect.x)}px, ${String(target.rect.y)}px)`;
3810
- box.style.width = `${String(target.rect.width)}px`;
3811
- box.style.height = `${String(target.rect.height)}px`;
3945
+ box.dataset.targetId = target2.id;
3946
+ box.dataset.active = String(target2.active);
3947
+ box.style.transform = `translate(${String(target2.rect.x)}px, ${String(target2.rect.y)}px)`;
3948
+ box.style.width = `${String(target2.rect.width)}px`;
3949
+ box.style.height = `${String(target2.rect.height)}px`;
3812
3950
  const label = createMarkedElement(document, "span");
3813
- label.textContent = `${String(index + 1)} \xB7 ${target.label}`;
3951
+ label.textContent = `${String(index + 1)} \xB7 ${target2.label}`;
3814
3952
  box.append(label);
3815
3953
  selectionHighlights.append(box);
3816
3954
  }
@@ -3842,8 +3980,8 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3842
3980
  }
3843
3981
  function updateContextOverview(summaryText) {
3844
3982
  const summaryMessages = messages.summary;
3845
- const source = summaryLine(summaryText, summaryMessages.source);
3846
- sourcePeek.textContent = source ?? messages.diagnostics.noExactSource;
3983
+ const source2 = summaryLine(summaryText, summaryMessages.source);
3984
+ sourcePeek.textContent = source2 ?? messages.diagnostics.noExactSource;
3847
3985
  const browserReady = `${summaryMessages.browserContext}: ${summaryMessages.collectionStatuses.ready}`;
3848
3986
  const browserLoading = `${summaryMessages.browserContext}: ${summaryMessages.collectionStatuses.loading}`;
3849
3987
  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 ?? (0, import_react_adapter.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 <= import_shared9.MAX_ANNOTATION_INSTRUCTION_CHARACTERS && 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
  // Annotate the CommonJS export names for ESM import in node: