@spotpatch/runtime 1.15.5 → 1.15.6

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
@@ -56,6 +56,7 @@ function freezeReact(react2) {
56
56
  function freezeElement(element2) {
57
57
  return Object.freeze({
58
58
  ...element2,
59
+ ...element2.instance === void 0 ? {} : { instance: Object.freeze({ ...element2.instance }) },
59
60
  rect: Object.freeze({ ...element2.rect })
60
61
  });
61
62
  }
@@ -856,6 +857,66 @@ function collectStyleContext(options) {
856
857
  });
857
858
  }
858
859
 
860
+ // src/state/selected-instance.ts
861
+ import {
862
+ ELEMENT_INSTANCE_HINT_ATTRIBUTE as ELEMENT_INSTANCE_HINT_ATTRIBUTE2,
863
+ ELEMENT_INSTANCE_LIMITS as ELEMENT_INSTANCE_LIMITS2,
864
+ SOURCE_MARKER_ATTRIBUTE
865
+ } from "@spotpatch/shared";
866
+
867
+ // src/collectors/dom-collector.ts
868
+ import {
869
+ ELEMENT_INSTANCE_HINT_ATTRIBUTE
870
+ } from "@spotpatch/shared";
871
+
872
+ // src/collectors/element-locator.ts
873
+ import { ELEMENT_INSTANCE_LIMITS, redactSensitiveText as redactSensitiveText2 } from "@spotpatch/shared";
874
+ function escapeIdentifier(value) {
875
+ return Array.from(value).map(
876
+ (character) => /[a-z\d_-]/iu.test(character) ? character : `\\${(character.codePointAt(0) ?? 0).toString(16)} `
877
+ ).join("");
878
+ }
879
+ function uniqueMatch(element2, selector) {
880
+ if (selector.length > ELEMENT_INSTANCE_LIMITS.selectorCharacters) return false;
881
+ try {
882
+ const matches = element2.ownerDocument.querySelectorAll(selector);
883
+ return matches.length === 1 && matches[0] === element2;
884
+ } catch {
885
+ return false;
886
+ }
887
+ }
888
+ function createElementLocator(element2) {
889
+ if (!element2.isConnected || element2.getRootNode() !== element2.ownerDocument)
890
+ return "";
891
+ const segments = [];
892
+ let current = element2;
893
+ while (current !== null && segments.length < ELEMENT_INSTANCE_LIMITS.locatorDepth) {
894
+ const tag = current.localName;
895
+ const testId = current.getAttribute("data-testid");
896
+ const anchors = [
897
+ ...current.id.length > 0 && redactSensitiveText2(current.id) === current.id ? [`${tag}#${escapeIdentifier(current.id)}`] : [],
898
+ ...testId && redactSensitiveText2(testId) === testId ? [
899
+ `${tag}[data-testid="${testId.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"]`
900
+ ] : []
901
+ ];
902
+ for (const anchor of anchors) {
903
+ const candidate2 = [anchor, ...segments].join(" > ");
904
+ if (redactSensitiveText2(candidate2) === candidate2 && uniqueMatch(element2, candidate2))
905
+ return candidate2;
906
+ }
907
+ const siblings = current.parentElement === null ? [current] : Array.from(current.parentElement.children).filter(
908
+ (sibling) => sibling.localName === tag
909
+ );
910
+ const segment = siblings.length > 1 ? `${tag}:nth-of-type(${String(siblings.indexOf(current) + 1)})` : tag;
911
+ segments.unshift(segment);
912
+ const candidate = segments.join(" > ");
913
+ if (uniqueMatch(element2, candidate)) return candidate;
914
+ if (candidate.length > ELEMENT_INSTANCE_LIMITS.selectorCharacters) return "";
915
+ current = current.parentElement;
916
+ }
917
+ return "";
918
+ }
919
+
859
920
  // src/collectors/dom-collector.ts
860
921
  var DOM_COLLECTION_LIMITS = Object.freeze({
861
922
  maxDepth: 3,
@@ -923,7 +984,7 @@ function escapeHtml(value) {
923
984
  return value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
924
985
  }
925
986
  function isPreservedAttribute(name) {
926
- return SAFE_ATTRIBUTE_NAMES.has(name) || name.startsWith("aria-") || name === "data-testid";
987
+ return SAFE_ATTRIBUTE_NAMES.has(name) || name.startsWith("aria-") || name === "data-testid" || name === ELEMENT_INSTANCE_HINT_ATTRIBUTE;
927
988
  }
928
989
  function openingTag(element2) {
929
990
  const tagName = element2.tagName.toLowerCase();
@@ -999,44 +1060,6 @@ function truncateContext(value, maxCharacters) {
999
1060
  }
1000
1061
  return `${value.slice(0, Math.max(0, maxCharacters - 1)).trimEnd()}…`;
1001
1062
  }
1002
- function escapeCssIdentifier(value) {
1003
- return Array.from(value).map(
1004
- (character) => /[a-z\d_-]/iu.test(character) ? character : `\\${character.charCodeAt(0).toString(16)} `
1005
- ).join("");
1006
- }
1007
- function escapeCssAttribute(value) {
1008
- return value.replaceAll("\\", "\\\\").replaceAll('"', '\\"');
1009
- }
1010
- function selectorSegment(element2) {
1011
- const tagName = element2.tagName.toLowerCase();
1012
- if (element2.id.length > 0) {
1013
- return `${tagName}#${escapeCssIdentifier(element2.id)}`;
1014
- }
1015
- const testId = element2.getAttribute("data-testid");
1016
- if (testId !== null && testId.length > 0) {
1017
- return `${tagName}[data-testid="${escapeCssAttribute(testId)}"]`;
1018
- }
1019
- const classes = Array.from(element2.classList).slice(0, 2).map((name) => `.${escapeCssIdentifier(name)}`).join("");
1020
- const sameTagSiblings = element2.parentElement === null ? [] : Array.from(element2.parentElement.children).filter(
1021
- (sibling) => sibling.tagName === element2.tagName
1022
- );
1023
- const position = sameTagSiblings.indexOf(element2);
1024
- const nth = sameTagSiblings.length > 1 && position >= 0 ? `:nth-of-type(${String(position + 1)})` : "";
1025
- return `${tagName}${classes}${nth}`;
1026
- }
1027
- function createSelector(element2) {
1028
- const segments = [];
1029
- let current = element2;
1030
- while (current !== null && segments.length < 5) {
1031
- const segment = selectorSegment(current);
1032
- segments.unshift(segment);
1033
- if (current.id.length > 0 || current.hasAttribute("data-testid")) {
1034
- break;
1035
- }
1036
- current = current.parentElement;
1037
- }
1038
- return segments.join(" > ");
1039
- }
1040
1063
  function collectElementContext(options) {
1041
1064
  const lines = ["<!-- Selected element -->"];
1042
1065
  const state = { nodeCount: 0, truncated: false };
@@ -1050,7 +1073,7 @@ function collectElementContext(options) {
1050
1073
  const role = options.element.getAttribute("role");
1051
1074
  return Object.freeze({
1052
1075
  tagName: options.element.tagName.toLowerCase(),
1053
- selector: createSelector(options.element),
1076
+ selector: createElementLocator(options.element),
1054
1077
  sanitizedHtml: truncateContext(lines.join("\n"), options.maxCharacters),
1055
1078
  ...text2.length === 0 ? {} : {
1056
1079
  textPreview: text2.length <= DOM_COLLECTION_LIMITS.maxTextCharacters ? text2 : `${text2.slice(0, DOM_COLLECTION_LIMITS.maxTextCharacters)}…`
@@ -1065,6 +1088,65 @@ function collectElementContext(options) {
1065
1088
  });
1066
1089
  }
1067
1090
 
1091
+ // src/state/selected-instance.ts
1092
+ function instanceScope(element2) {
1093
+ let candidate = element2;
1094
+ for (let depth = 0; candidate !== null && depth < ELEMENT_INSTANCE_LIMITS2.ancestorDepth; depth += 1) {
1095
+ if (candidate === element2.ownerDocument.body || candidate === element2.ownerDocument.documentElement)
1096
+ break;
1097
+ if (candidate.hasAttribute(ELEMENT_INSTANCE_HINT_ATTRIBUTE2) || candidate.matches("article, li, tr, [role=row], [role=listitem]"))
1098
+ return candidate;
1099
+ const marker = candidate.getAttribute(SOURCE_MARKER_ATTRIBUTE);
1100
+ if (marker !== null && candidate.parentElement !== null && Array.from(candidate.parentElement.children).some(
1101
+ (sibling) => sibling !== candidate && sibling.getAttribute(SOURCE_MARKER_ATTRIBUTE) === marker
1102
+ ))
1103
+ return candidate;
1104
+ candidate = candidate.parentElement;
1105
+ }
1106
+ const parent = element2.parentElement;
1107
+ return parent === null || parent === element2.ownerDocument.body || parent === element2.ownerDocument.documentElement ? element2 : parent;
1108
+ }
1109
+ function captureSelectedInstance(element2, id, maxCharacters) {
1110
+ const context = collectElementContext({ element: element2, maxCharacters });
1111
+ const scope = instanceScope(element2);
1112
+ const nearby = collectElementContext({
1113
+ element: scope,
1114
+ maxCharacters: ELEMENT_INSTANCE_LIMITS2.contextCharacters
1115
+ });
1116
+ return Object.freeze({
1117
+ scope,
1118
+ context: Object.freeze({
1119
+ ...context,
1120
+ instance: Object.freeze({
1121
+ id,
1122
+ state: "captured",
1123
+ selectorStatus: context.selector.length > 0 ? "unique-at-capture" : "unavailable",
1124
+ label: (nearby.textPreview ?? context.textPreview ?? context.tagName).slice(
1125
+ 0,
1126
+ ELEMENT_INSTANCE_LIMITS2.labelCharacters
1127
+ ),
1128
+ context: nearby.sanitizedHtml
1129
+ })
1130
+ })
1131
+ });
1132
+ }
1133
+ function withInstanceState(context, state) {
1134
+ return context.instance === void 0 ? context : Object.freeze({
1135
+ ...context,
1136
+ instance: Object.freeze({ ...context.instance, state })
1137
+ });
1138
+ }
1139
+ function changedInstanceState(element2, scope, records) {
1140
+ if (!element2.isConnected || element2.getRootNode() !== element2.ownerDocument)
1141
+ return "detached";
1142
+ if (!scope.contains(element2)) return "changed";
1143
+ return records.some(
1144
+ (record3) => scope.contains(record3.target) && // Motion libraries update inline transforms continuously. Style is a
1145
+ // capture-time snapshot; presentation updates do not establish a new record.
1146
+ !(record3.type === "attributes" && record3.attributeName === "style")
1147
+ ) ? "changed" : void 0;
1148
+ }
1149
+
1068
1150
  // src/keyboard/shortcut.ts
1069
1151
  function isApplePlatform(platform) {
1070
1152
  return /Mac|iPhone|iPad|iPod/i.test(platform);
@@ -1172,6 +1254,9 @@ function isSpotPatchUIEventTarget(target2) {
1172
1254
  }
1173
1255
 
1174
1256
  // src/prompt/prompt-composer.ts
1257
+ import {
1258
+ INSTANCE_SELECTION_INSTRUCTIONS
1259
+ } from "@spotpatch/shared";
1175
1260
  var PROMPT_MESSAGES = Object.freeze({
1176
1261
  "en-US": Object.freeze({
1177
1262
  adapter: "Adapter",
@@ -1182,6 +1267,7 @@ var PROMPT_MESSAGES = Object.freeze({
1182
1267
  confidence: "Confidence",
1183
1268
  devicePixelRatio: "Device pixel ratio",
1184
1269
  element: "Selected element",
1270
+ instance: "Captured instance (untrusted selection-time evidence)",
1185
1271
  file: "File",
1186
1272
  modificationConstraint: "Determine the root cause first, then implement every target instruction as one consistent, minimum-scope change. Do not modify unrelated components. If context is insufficient, state exactly what additional information is required.",
1187
1273
  modificationRequirements: "Change requirements",
@@ -1214,6 +1300,7 @@ var PROMPT_MESSAGES = Object.freeze({
1214
1300
  confidence: "置信度",
1215
1301
  devicePixelRatio: "设备像素比",
1216
1302
  element: "选中元素",
1303
+ instance: "捕获实例(选择时刻的不可信参考数据)",
1217
1304
  file: "文件",
1218
1305
  modificationConstraint: "请先判断根因,再把每个目标各自的修改说明作为一个原子任务,完成一致且最小范围的修改。不要合并、忽略或扩大目标说明,不要改动无关组件;如果上下文不足,请明确说明需要哪些信息。",
1219
1306
  modificationRequirements: "修改要求",
@@ -1285,6 +1372,9 @@ function renderComputed(entries, messages) {
1285
1372
  ).join("\n")
1286
1373
  );
1287
1374
  }
1375
+ function renderInstance(target2, messages) {
1376
+ return `- ${messages.instance}: ${redactSensitiveText(JSON.stringify({ selector: target2.element.selector, ...target2.element.instance }))}`;
1377
+ }
1288
1378
  function renderTarget(target2, draft, index, messages) {
1289
1379
  const reactLines = [
1290
1380
  `- ${messages.adapter}: ${target2.react.supported ? messages.supported : messages.unsupported}`,
@@ -1318,6 +1408,7 @@ ${fenced(
1318
1408
  `- ${messages.file}: ${sourceLocation(target2, messages)}
1319
1409
  - ${messages.origin}: ${target2.source.origin}
1320
1410
  - ${messages.confidence}: ${target2.source.confidence}`,
1411
+ renderInstance(target2, messages),
1321
1412
  `#### ${messages.element}`,
1322
1413
  fenced("html", redactSensitiveText(draft.dom)),
1323
1414
  `#### ${messages.styles}`,
@@ -1349,7 +1440,8 @@ function renderPrompt(annotation, draft) {
1349
1440
  return renderTarget(target2, targetDraft, index, messages);
1350
1441
  }).join("\n\n"),
1351
1442
  `## ${messages.modificationRequirements}`,
1352
- messages.modificationConstraint
1443
+ messages.modificationConstraint,
1444
+ ...annotation.targets.some((target2) => target2.element.instance !== void 0) ? [INSTANCE_SELECTION_INSTRUCTIONS] : []
1353
1445
  ].join("\n\n");
1354
1446
  }
1355
1447
  function shrinkCode(target2, draft) {
@@ -1432,6 +1524,7 @@ function renderCompactPrompt(annotation, draft) {
1432
1524
  `- ${messages.requestedChange}: ${targetDraft?.instruction ?? redactSensitiveText(target2.instruction)}`,
1433
1525
  `- ${messages.file}: ${sourceLocation(target2, messages)}`,
1434
1526
  ...component === void 0 ? [] : [`- ${messages.component}: ${redactSensitiveText(component)}`],
1527
+ renderInstance(target2, messages),
1435
1528
  `- ${messages.element}: ${redactSensitiveText(openingTag2(targetDraft?.dom ?? target2.element.sanitizedHtml, messages.unavailable))}`
1436
1529
  ].join("\n");
1437
1530
  });
@@ -1439,7 +1532,8 @@ function renderCompactPrompt(annotation, draft) {
1439
1532
  `## ${messages.selectedTargets(annotation.targets.length)}`,
1440
1533
  targets.join("\n\n"),
1441
1534
  `## ${messages.modificationRequirements}`,
1442
- messages.modificationConstraint
1535
+ messages.modificationConstraint,
1536
+ ...annotation.targets.some((target2) => target2.element.instance !== void 0) ? [INSTANCE_SELECTION_INSTRUCTIONS] : []
1443
1537
  ].join("\n\n");
1444
1538
  }
1445
1539
  function composeBounded(annotation, maxCharacters) {
@@ -1546,6 +1640,10 @@ function reduceRuntimeState(state, event) {
1546
1640
  }
1547
1641
 
1548
1642
  // src/state/selection-session.ts
1643
+ import {
1644
+ ELEMENT_INSTANCE_LIMITS as ELEMENT_INSTANCE_LIMITS3,
1645
+ ELEMENT_INSTANCE_STATES
1646
+ } from "@spotpatch/shared";
1549
1647
  var STORAGE_PREFIX = "spotpatch:selection:";
1550
1648
  var SNAPSHOT_VERSION = 1;
1551
1649
  var MAX_SNAPSHOT_CHARACTERS = 1e6;
@@ -1582,7 +1680,11 @@ function page(value) {
1582
1680
  function element(value) {
1583
1681
  const item = record(value);
1584
1682
  const rect = record(item?.rect);
1585
- 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);
1683
+ return item !== void 0 && (item.instance === void 0 || instance(item.instance)) && 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);
1684
+ }
1685
+ function instance(value) {
1686
+ const item = record(value);
1687
+ return item !== void 0 && Object.keys(item).length === 5 && text(item.id) && item.id.length <= ELEMENT_INSTANCE_LIMITS3.idCharacters && /^[A-Za-z0-9_-]+$/u.test(item.id) && ELEMENT_INSTANCE_STATES.some((state) => state === item.state) && (item.selectorStatus === "unique-at-capture" || item.selectorStatus === "unavailable") && text(item.label) && item.label.length <= ELEMENT_INSTANCE_LIMITS3.labelCharacters && text(item.context) && item.context.length <= ELEMENT_INSTANCE_LIMITS3.contextCharacters;
1586
1688
  }
1587
1689
  function styles(value) {
1588
1690
  const item = record(value);
@@ -1663,14 +1765,14 @@ function createSelectionSession(window, sessionId, maximumTargets) {
1663
1765
  // src/source/source-resolver.ts
1664
1766
  import {
1665
1767
  parseSourceMarker,
1666
- SOURCE_MARKER_ATTRIBUTE
1768
+ SOURCE_MARKER_ATTRIBUTE as SOURCE_MARKER_ATTRIBUTE2
1667
1769
  } from "@spotpatch/shared";
1668
1770
  var EMPTY_REACT_CONTEXT = Object.freeze({
1669
1771
  supported: false,
1670
1772
  componentStack: Object.freeze([])
1671
1773
  });
1672
1774
  function directMarker(element2) {
1673
- return parseSourceMarker(element2.getAttribute(SOURCE_MARKER_ATTRIBUTE));
1775
+ return parseSourceMarker(element2.getAttribute(SOURCE_MARKER_ATTRIBUTE2));
1674
1776
  }
1675
1777
  function ancestorMarker(element2) {
1676
1778
  let ancestor = element2.parentElement;
@@ -2624,6 +2726,13 @@ var ERROR_MESSAGES_ZH = Object.freeze({
2624
2726
  [ERROR_CODES2.INTERNAL_ERROR]: "Agent 失败,未暴露私有细节。"
2625
2727
  });
2626
2728
  var SUMMARY_MESSAGES_EN = Object.freeze({
2729
+ instance: "Selected instance",
2730
+ instanceStates: {
2731
+ captured: "Captured element; locator describes selection time",
2732
+ changed: "Page content changed; snapshot preserved. Select again to confirm the current instance",
2733
+ detached: "Element unloaded; historical snapshot preserved",
2734
+ restored: "Restored snapshot; not bound to the current page"
2735
+ },
2627
2736
  adapter: "React adapter",
2628
2737
  api: "API",
2629
2738
  apiStatuses: Object.freeze({
@@ -2664,6 +2773,13 @@ var SUMMARY_MESSAGES_EN = Object.freeze({
2664
2773
  warning: "Warning"
2665
2774
  });
2666
2775
  var SUMMARY_MESSAGES_ZH = Object.freeze({
2776
+ instance: "选中实例",
2777
+ instanceStates: {
2778
+ captured: "已捕获元素;定位信息对应选择时刻",
2779
+ changed: "页面内容已变化,已保留快照;请重新选择以确认当前实例",
2780
+ detached: "元素已卸载,已保留历史快照",
2781
+ restored: "已恢复历史快照,未绑定当前页面"
2782
+ },
2667
2783
  adapter: "React 适配器",
2668
2784
  api: "API",
2669
2785
  apiStatuses: Object.freeze({
@@ -2848,13 +2964,13 @@ var UI_MESSAGES = Object.freeze({
2848
2964
  addCancelled: "Additional selection cancelled.",
2849
2965
  selectionLimit: (maximum) => `The selection limit of ${String(maximum)} elements has been reached.`,
2850
2966
  chooseAdditional: (selected, maximum) => `Choose another element. ${String(selected)} of ${String(maximum)} selected.`,
2851
- duplicate: "That source target is already selected.",
2967
+ duplicate: "That page element is already selected.",
2968
+ instanceBudgetExceeded: "The prompt cannot preserve every selected instance within its budget. Remove some targets or increase the context budget.",
2852
2969
  sourceProbable: "A probable React component was found without an authorized file token.",
2853
2970
  sourceMissing: "No authorized source marker was found for the selected element.",
2854
2971
  noSelectable: "No selectable element was found.",
2855
2972
  allTargetsRemoved: "All targets were removed. Choose an element to continue.",
2856
2973
  targetRemoved: "Selected target removed.",
2857
- detachedTargetPreserved: "The page element was unloaded; its collected context remains selected.",
2858
2974
  contextWarning: "Browser context collection completed with a warning.",
2859
2975
  contextCollected: "Browser context collected.",
2860
2976
  editorOpening: "Opening source…",
@@ -3012,13 +3128,13 @@ var UI_MESSAGES = Object.freeze({
3012
3128
  addCancelled: "已取消追加选择。",
3013
3129
  selectionLimit: (maximum) => `已达到 ${String(maximum)} 个元素的选择上限。`,
3014
3130
  chooseAdditional: (selected, maximum) => `请选择另一个元素,当前已选 ${String(selected)} / ${String(maximum)}。`,
3015
- duplicate: "该源码目标已经在当前选择中。",
3131
+ duplicate: "该页面元素已经在当前选择中。",
3132
+ instanceBudgetExceeded: "上下文预算无法完整保留所有选中实例,请减少目标数量或提高上下文预算。",
3016
3133
  sourceProbable: "找到可能的 React 组件,但没有获授权的源码标记。",
3017
3134
  sourceMissing: "选中元素没有获授权的源码标记。",
3018
3135
  noSelectable: "当前位置没有可选择的元素。",
3019
3136
  allTargetsRemoved: "已移除全部目标,请重新选择元素。",
3020
3137
  targetRemoved: "已移除选中目标。",
3021
- detachedTargetPreserved: "页面元素已卸载,已采集的上下文仍保留在选择中。",
3022
3138
  contextWarning: "浏览器上下文采集完成,但存在警告。",
3023
3139
  contextCollected: "浏览器上下文采集完成。",
3024
3140
  editorOpening: "正在打开源码……",
@@ -4850,6 +4966,9 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
4850
4966
  requestFloatingSurfaceLayout();
4851
4967
  }
4852
4968
  function statusText(target2) {
4969
+ if (target2.instanceState !== void 0 && target2.instanceState !== "captured") {
4970
+ return messages.summary.instanceStates[target2.instanceState];
4971
+ }
4853
4972
  if (target2.status === "ready") {
4854
4973
  return messages.targets.statusReady;
4855
4974
  }
@@ -5490,6 +5609,11 @@ function createSelectionSummary(input, messages) {
5490
5609
  `${messages.confidence}: ${input.resolution.source.confidence} (${messages.confidenceLabels[input.resolution.source.confidence]})`,
5491
5610
  `${messages.origin}: ${input.resolution.source.origin}`
5492
5611
  ];
5612
+ if (input.instance !== void 0) {
5613
+ lines.push(
5614
+ `${messages.instance}: ${messages.instanceStates[input.instance.state]}`
5615
+ );
5616
+ }
5493
5617
  if (input.resolution.react.componentName !== void 0) {
5494
5618
  lines.push(`${messages.component}: ${input.resolution.react.componentName}`);
5495
5619
  }
@@ -5948,7 +6072,7 @@ function sourceLabel(target2, unavailable) {
5948
6072
  return `${path}${line === void 0 ? "" : `:${String(line)}`}${column === void 0 ? "" : `:${String(column)}`}`;
5949
6073
  }
5950
6074
  function targetStatus(target2) {
5951
- if (target2.apiStatus === "failed" || target2.collectionStatus === "failed") {
6075
+ if (target2.apiStatus === "failed" || target2.collectionStatus === "failed" || target2.elementContext?.instance !== void 0 && target2.elementContext.instance.state !== "captured") {
5952
6076
  return "warning";
5953
6077
  }
5954
6078
  if (target2.apiStatus === "loading" || target2.collectionStatus === "loading") {
@@ -5991,6 +6115,7 @@ function createController(config, dependencies = {}) {
5991
6115
  const now = dependencies.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
5992
6116
  const selectionSession = dependencies.selectionSession ?? createSelectionSession(browser.window, config.sessionId, config.maxTargets);
5993
6117
  const restoredSelection = selectionSession.load();
6118
+ const instanceNamespace = createBrowserAnnotationId(browser.window);
5994
6119
  const sourceResolver = createSourceResolver({
5995
6120
  adapter: dependencies.reactAdapter ?? createReact18Adapter({
5996
6121
  ...config.dataFlow.enabled ? {
@@ -6022,7 +6147,8 @@ function createController(config, dependencies = {}) {
6022
6147
  dataFlowReport: void 0,
6023
6148
  dataFlowStatus: "idle",
6024
6149
  element: void 0,
6025
- elementContext: target2.element,
6150
+ instanceScope: void 0,
6151
+ elementContext: withInstanceState(target2.element, "restored"),
6026
6152
  instruction: target2.instruction,
6027
6153
  marker,
6028
6154
  page: target2.page,
@@ -6181,6 +6307,7 @@ function createController(config, dependencies = {}) {
6181
6307
  );
6182
6308
  }
6183
6309
  function selectedAskSelection() {
6310
+ refreshInstanceStates(mutationObserver?.takeRecords() ?? []);
6184
6311
  const snapshots = targets.flatMap((target2) => {
6185
6312
  const snapshot = snapshotTarget(target2);
6186
6313
  return snapshot === void 0 ? [] : [snapshot];
@@ -6199,6 +6326,7 @@ function createController(config, dependencies = {}) {
6199
6326
  const summary = createSelectionSummary(
6200
6327
  {
6201
6328
  resolution: target2.resolution,
6329
+ ...target2.elementContext?.instance === void 0 ? {} : { instance: target2.elementContext.instance },
6202
6330
  ...target2.code === void 0 ? {} : { code: target2.code },
6203
6331
  ...target2.styles === void 0 ? {} : { styles: target2.styles },
6204
6332
  apiStatus: target2.apiStatus,
@@ -6244,7 +6372,11 @@ ${summary}`;
6244
6372
  view.renderTargets(
6245
6373
  targets.map((target2) => ({
6246
6374
  id: target2.id,
6247
- label: target2.resolution.react.componentName ?? (target2.element === void 0 ? target2.elementContext?.tagName ?? "Element" : elementLabel(target2.element)),
6375
+ ...target2.elementContext?.instance === void 0 ? {} : { instanceState: target2.elementContext.instance.state },
6376
+ label: [
6377
+ target2.resolution.react.componentName ?? target2.elementContext?.tagName ?? "Element",
6378
+ (target2.elementContext?.instance?.label ?? target2.elementContext?.textPreview)?.slice(0, 80)
6379
+ ].filter(Boolean).join(" · "),
6248
6380
  source: sourceLabel(target2, view.messages().context.sourceUnavailable),
6249
6381
  canOpenEditor: target2.marker !== void 0,
6250
6382
  status: targetStatus(target2),
@@ -6264,6 +6396,7 @@ ${summary}`;
6264
6396
  }
6265
6397
  }
6266
6398
  function selectedAnnotation() {
6399
+ refreshInstanceStates(mutationObserver?.takeRecords() ?? []);
6267
6400
  if (!canPreview()) {
6268
6401
  return void 0;
6269
6402
  }
@@ -6416,6 +6549,9 @@ ${summary}`;
6416
6549
  resizeObserver?.disconnect();
6417
6550
  for (const target2 of targets) {
6418
6551
  target2.element = void 0;
6552
+ target2.instanceScope = void 0;
6553
+ if (target2.elementContext !== void 0)
6554
+ target2.elementContext = withInstanceState(target2.elementContext, "detached");
6419
6555
  }
6420
6556
  view.hideHighlight();
6421
6557
  view.hideSelectionHighlights();
@@ -6503,17 +6639,11 @@ ${summary}`;
6503
6639
  if (!targetIsCurrent(target2, revision)) {
6504
6640
  return;
6505
6641
  }
6506
- let failed = false;
6507
- try {
6508
- target2.elementContext = collectElementContext({
6509
- element: element2,
6510
- maxCharacters: config.budget.domCharacters
6511
- });
6512
- } catch {
6513
- target2.elementContext = void 0;
6514
- failed = true;
6515
- }
6642
+ refreshInstanceStates(mutationObserver?.takeRecords() ?? []);
6643
+ let failed = target2.elementContext === void 0;
6516
6644
  try {
6645
+ if (target2.element !== element2)
6646
+ throw new Error("Selected instance changed before style collection.");
6517
6647
  target2.styles = collectStyleContext({
6518
6648
  document: browser.document,
6519
6649
  element: element2,
@@ -6537,18 +6667,11 @@ ${summary}`;
6537
6667
  }, 0);
6538
6668
  collectionTimers.set(target2.id, timer);
6539
6669
  }
6540
- function duplicateTarget(element2, marker) {
6541
- if (marker !== void 0) {
6542
- return targets.find(
6543
- (target2) => target2.marker?.fileId === marker.fileId && target2.marker.line === marker.line && target2.marker.column === marker.column
6544
- );
6545
- }
6546
- return targets.find((target2) => target2.element === element2);
6547
- }
6548
6670
  function selectElement(element2) {
6671
+ refreshInstanceStates(mutationObserver?.takeRecords() ?? []);
6549
6672
  const resolution = sourceResolver.resolve(element2);
6550
6673
  const marker = sourceRefToMarker(resolution.source);
6551
- const duplicate = duplicateTarget(element2, marker);
6674
+ const duplicate = targets.find((target3) => target3.element === element2);
6552
6675
  if (duplicate !== void 0) {
6553
6676
  activeTargetId = duplicate.id;
6554
6677
  addingTarget = false;
@@ -6580,6 +6703,7 @@ ${summary}`;
6580
6703
  const target2 = {
6581
6704
  id: `target-${String(targetSequence)}`,
6582
6705
  element: element2,
6706
+ instanceScope: void 0,
6583
6707
  resolution,
6584
6708
  marker,
6585
6709
  page: collectPageContext(browser.document, browser.window),
@@ -6592,6 +6716,17 @@ ${summary}`;
6592
6716
  dataFlowReport: void 0,
6593
6717
  dataFlowStatus: "idle"
6594
6718
  };
6719
+ try {
6720
+ const capture = captureSelectedInstance(
6721
+ element2,
6722
+ `${instanceNamespace}_${target2.id}`,
6723
+ config.budget.domCharacters
6724
+ );
6725
+ target2.elementContext = capture.context;
6726
+ target2.instanceScope = capture.scope;
6727
+ } catch {
6728
+ target2.collectionStatus = "failed";
6729
+ }
6595
6730
  targets.push(target2);
6596
6731
  if (!initialSelection) {
6597
6732
  contextualAskWorkflow?.selectionChanged();
@@ -6722,25 +6857,32 @@ ${summary}`;
6722
6857
  persistSelection();
6723
6858
  view.announce(view.messages().announcements.targetRemoved);
6724
6859
  }
6725
- function refreshSelectedGeometry() {
6726
- if (state.status === "idle" || targets.length === 0) {
6727
- return;
6860
+ function refreshInstanceStates(records) {
6861
+ let changed = false;
6862
+ for (const target2 of targets) {
6863
+ if (target2.element === void 0) continue;
6864
+ const next = target2.page.url !== browser.window.location.href ? "changed" : changedInstanceState(
6865
+ target2.element,
6866
+ target2.instanceScope ?? target2.element,
6867
+ records
6868
+ );
6869
+ if (next === void 0) continue;
6870
+ resizeObserver?.unobserve(target2.element);
6871
+ target2.element = void 0;
6872
+ target2.instanceScope = void 0;
6873
+ if (target2.elementContext !== void 0)
6874
+ target2.elementContext = withInstanceState(target2.elementContext, next);
6875
+ changed = true;
6728
6876
  }
6729
- const disconnected = targets.filter(
6730
- (target2) => target2.element !== void 0 && !target2.element.isConnected
6731
- );
6732
- if (disconnected.length > 0) {
6733
- for (const target2 of disconnected) {
6734
- if (target2.element !== void 0) {
6735
- resizeObserver?.unobserve(target2.element);
6736
- target2.element = void 0;
6737
- }
6738
- }
6877
+ if (changed) {
6739
6878
  refreshSelectionView();
6740
6879
  persistSelection();
6741
- view.announce(view.messages().announcements.detachedTargetPreserved);
6742
- return;
6880
+ contextualAskWorkflow?.selectionChanged();
6743
6881
  }
6882
+ }
6883
+ function refreshSelectedGeometry() {
6884
+ if (state.status === "idle" || targets.length === 0) return;
6885
+ refreshInstanceStates(mutationObserver?.takeRecords() ?? []);
6744
6886
  refreshHighlights();
6745
6887
  }
6746
6888
  function handleOpenEditor(targetId) {
@@ -6794,6 +6936,15 @@ ${summary}`;
6794
6936
  agentWorkflow.run();
6795
6937
  }
6796
6938
  }
6939
+ function composeSelectionPrompt(annotation) {
6940
+ try {
6941
+ return promptComposer.compose(annotation);
6942
+ } catch (error) {
6943
+ if (!(error instanceof RangeError)) throw error;
6944
+ view.announce(view.messages().announcements.instanceBudgetExceeded);
6945
+ return void 0;
6946
+ }
6947
+ }
6797
6948
  function handlePreview() {
6798
6949
  if (state.status !== "selected") {
6799
6950
  return;
@@ -6803,7 +6954,9 @@ ${summary}`;
6803
6954
  view.announce(view.messages().announcements.completeInstructions);
6804
6955
  return;
6805
6956
  }
6806
- previewPrompt = promptComposer.compose(annotation);
6957
+ const composed = composeSelectionPrompt(annotation);
6958
+ if (composed === void 0) return;
6959
+ previewPrompt = composed;
6807
6960
  transition({ type: "PREVIEW" });
6808
6961
  view.showPreview(previewPrompt);
6809
6962
  view.focusPrompt();
@@ -6812,6 +6965,12 @@ ${summary}`;
6812
6965
  if (state.status !== "previewing" || previewPrompt.length === 0) {
6813
6966
  return;
6814
6967
  }
6968
+ const annotation = selectedAnnotation();
6969
+ if (annotation === void 0) return;
6970
+ const composed = composeSelectionPrompt(annotation);
6971
+ if (composed === void 0) return;
6972
+ previewPrompt = composed;
6973
+ view.showPreview(previewPrompt);
6815
6974
  if (clipboard === void 0) {
6816
6975
  transition({ type: "COPY_FAILURE" });
6817
6976
  view.focusPrompt();
@@ -6848,8 +7007,11 @@ ${summary}`;
6848
7007
  if (state.status === "previewing") {
6849
7008
  const annotation = selectedAnnotation();
6850
7009
  if (annotation !== void 0) {
6851
- previewPrompt = promptComposer.compose(annotation);
6852
- view.showPreview(previewPrompt);
7010
+ const composed = composeSelectionPrompt(annotation);
7011
+ if (composed !== void 0) {
7012
+ previewPrompt = composed;
7013
+ view.showPreview(previewPrompt);
7014
+ }
6853
7015
  }
6854
7016
  }
6855
7017
  }
@@ -6903,6 +7065,8 @@ ${summary}`;
6903
7065
  browser.document.addEventListener("keydown", handleKeydown, true);
6904
7066
  browser.window.addEventListener("scroll", refreshSelectedGeometry, true);
6905
7067
  browser.window.addEventListener("resize", refreshSelectedGeometry);
7068
+ browser.window.addEventListener("popstate", refreshSelectedGeometry);
7069
+ browser.window.addEventListener("hashchange", refreshSelectedGeometry);
6906
7070
  view.triggerButton.addEventListener("click", activate);
6907
7071
  view.addTargetButton.addEventListener("click", beginAddTarget);
6908
7072
  view.reselectButton.addEventListener("click", handleReselect);
@@ -6939,10 +7103,15 @@ ${summary}`;
6939
7103
  resizeObserver = new browser.resizeObserver(refreshSelectedGeometry);
6940
7104
  }
6941
7105
  if (browser.mutationObserver !== void 0) {
6942
- mutationObserver = new browser.mutationObserver(refreshSelectedGeometry);
7106
+ mutationObserver = new browser.mutationObserver((records) => {
7107
+ refreshInstanceStates(records);
7108
+ refreshHighlights();
7109
+ });
6943
7110
  mutationObserver.observe(browser.document.documentElement, {
6944
7111
  childList: true,
6945
- subtree: true
7112
+ subtree: true,
7113
+ attributes: true,
7114
+ characterData: true
6946
7115
  });
6947
7116
  }
6948
7117
  if (selectionOpen && targets.length > 0) {
@@ -6977,6 +7146,8 @@ ${summary}`;
6977
7146
  browser.document.removeEventListener("keydown", handleKeydown, true);
6978
7147
  browser.window.removeEventListener("scroll", refreshSelectedGeometry, true);
6979
7148
  browser.window.removeEventListener("resize", refreshSelectedGeometry);
7149
+ browser.window.removeEventListener("popstate", refreshSelectedGeometry);
7150
+ browser.window.removeEventListener("hashchange", refreshSelectedGeometry);
6980
7151
  view.triggerButton.removeEventListener("click", activate);
6981
7152
  view.addTargetButton.removeEventListener("click", beginAddTarget);
6982
7153
  view.reselectButton.removeEventListener("click", handleReselect);