@spotpatch/runtime 1.0.0 → 1.1.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.d.cts CHANGED
@@ -1,10 +1,11 @@
1
- import { SPOTPATCH_API_BASE, RuntimeAiConfig, ContextBudget, SpotPatchLocalePreference } from '@spotpatch/shared';
1
+ import { SPOTPATCH_API_BASE, RuntimeAiConfig, ContextBudget, SpotPatchEditorPreference, SpotPatchLocalePreference } from '@spotpatch/shared';
2
2
 
3
3
  interface RuntimeConfig {
4
4
  readonly apiBase: typeof SPOTPATCH_API_BASE;
5
5
  readonly ai: RuntimeAiConfig;
6
6
  readonly budget: Readonly<ContextBudget>;
7
7
  readonly debug: boolean;
8
+ readonly editor: SpotPatchEditorPreference;
8
9
  readonly locale: SpotPatchLocalePreference;
9
10
  readonly maxTargets: number;
10
11
  readonly redact: boolean;
package/dist/index.d.ts CHANGED
@@ -1,10 +1,11 @@
1
- import { SPOTPATCH_API_BASE, RuntimeAiConfig, ContextBudget, SpotPatchLocalePreference } from '@spotpatch/shared';
1
+ import { SPOTPATCH_API_BASE, RuntimeAiConfig, ContextBudget, SpotPatchEditorPreference, SpotPatchLocalePreference } from '@spotpatch/shared';
2
2
 
3
3
  interface RuntimeConfig {
4
4
  readonly apiBase: typeof SPOTPATCH_API_BASE;
5
5
  readonly ai: RuntimeAiConfig;
6
6
  readonly budget: Readonly<ContextBudget>;
7
7
  readonly debug: boolean;
8
+ readonly editor: SpotPatchEditorPreference;
8
9
  readonly locale: SpotPatchLocalePreference;
9
10
  readonly maxTargets: number;
10
11
  readonly redact: boolean;
package/dist/index.js CHANGED
@@ -76,6 +76,7 @@ function createAnnotation(input) {
76
76
  // src/api/runtime-api.ts
77
77
  import {
78
78
  SPOTPATCH_ENDPOINTS,
79
+ SPOTPATCH_EDITOR_PREFERENCES,
79
80
  SPOTPATCH_TOKEN_HEADER,
80
81
  getAgentJobEndpoint
81
82
  } from "@spotpatch/shared";
@@ -248,6 +249,9 @@ function readSuccessData(value) {
248
249
  function isCodeContext(value) {
249
250
  return isRecord2(value) && typeof value.relativePath === "string" && (value.language === "tsx" || value.language === "jsx") && typeof value.startLine === "number" && typeof value.endLine === "number" && typeof value.excerpt === "string" && (value.boundary === "component" || value.boundary === "nearby-lines");
250
251
  }
252
+ function isEditorOpenResult(value) {
253
+ return isRecord2(value) && typeof value.editor === "string" && SPOTPATCH_EDITOR_PREFERENCES.includes(value.editor);
254
+ }
251
255
  function deepFreezeUnknown(value) {
252
256
  if (!isRecord2(value) && !Array.isArray(value)) {
253
257
  return;
@@ -474,7 +478,11 @@ function createRuntimeApi(options) {
474
478
  return Object.freeze({ ...data });
475
479
  },
476
480
  async openEditor(request) {
477
- await requestJson(SPOTPATCH_ENDPOINTS.openEditor, "POST", request);
481
+ const data = await requestJson(SPOTPATCH_ENDPOINTS.openEditor, "POST", request);
482
+ if (!isEditorOpenResult(data)) {
483
+ throw new RuntimeApiError();
484
+ }
485
+ return Object.freeze({ editor: data.editor });
478
486
  },
479
487
  async agentCapability(request) {
480
488
  return parseCapability(
@@ -1504,7 +1512,8 @@ function createSourceResolver(options) {
1504
1512
  // src/ui/runtime-view.ts
1505
1513
  import {
1506
1514
  MAX_ANNOTATION_INSTRUCTION_CHARACTERS as MAX_ANNOTATION_INSTRUCTION_CHARACTERS2,
1507
- MAX_TARGET_INSTRUCTION_CHARACTERS as MAX_TARGET_INSTRUCTION_CHARACTERS2
1515
+ MAX_TARGET_INSTRUCTION_CHARACTERS as MAX_TARGET_INSTRUCTION_CHARACTERS2,
1516
+ SPOTPATCH_REPOSITORY_URL
1508
1517
  } from "@spotpatch/shared";
1509
1518
 
1510
1519
  // src/ui/dom.ts
@@ -2132,6 +2141,15 @@ var STATUS_ZH = Object.freeze({
2132
2141
  reverted: "\u5DF2\u64A4\u9500",
2133
2142
  failed: "\u5931\u8D25"
2134
2143
  });
2144
+ function editorName(editor, automatic) {
2145
+ if (editor === "vscode") {
2146
+ return "VS Code";
2147
+ }
2148
+ if (editor === "cursor") {
2149
+ return "Cursor";
2150
+ }
2151
+ return automatic;
2152
+ }
2135
2153
  var ERROR_MESSAGES_EN = Object.freeze({
2136
2154
  [ERROR_CODES2.INVALID_REQUEST]: "The Agent request was rejected as invalid.",
2137
2155
  [ERROR_CODES2.INVALID_TOKEN]: "The local SpotPatch session expired.",
@@ -2267,7 +2285,12 @@ var UI_MESSAGES = Object.freeze({
2267
2285
  localeName: "EN",
2268
2286
  alternateLocaleName: "\u4E2D",
2269
2287
  switchLocale: "Switch interface language to Chinese",
2270
- brand: Object.freeze({ name: "SpotPatch", context: "Live context" }),
2288
+ brand: Object.freeze({
2289
+ name: "SpotPatch",
2290
+ context: "Live context",
2291
+ repository: "GitHub",
2292
+ repositoryTitle: "Star SpotPatch on GitHub"
2293
+ }),
2271
2294
  trigger: Object.freeze({
2272
2295
  select: "Select element",
2273
2296
  stop: "Stop selecting",
@@ -2319,7 +2342,8 @@ var UI_MESSAGES = Object.freeze({
2319
2342
  actions: Object.freeze({
2320
2343
  addElement: "Add element",
2321
2344
  reselect: "Start over",
2322
- openEditor: "Open in VS Code",
2345
+ openEditor: (editor) => editor === "auto" ? "Open source" : `Open in ${editorName(editor, "editor")}`,
2346
+ openTarget: (index, editor) => `Open target ${String(index)} in ${editorName(editor, "the detected editor")}`,
2323
2347
  preview: "Preview prompt",
2324
2348
  copy: "Copy prompt",
2325
2349
  back: "Back to edit"
@@ -2375,8 +2399,9 @@ var UI_MESSAGES = Object.freeze({
2375
2399
  detachedTargetRemoved: "A removed page element was dropped from the selection.",
2376
2400
  contextWarning: "Browser context collection completed with a warning.",
2377
2401
  contextCollected: "Browser context collected.",
2378
- editorOpened: "VS Code open request sent.",
2379
- editorFailed: "VS Code could not be opened.",
2402
+ editorOpening: (editor) => `Opening ${editorName(editor, "the detected editor")}\u2026`,
2403
+ editorOpened: (editor) => `Opened the source in ${editorName(editor, "the detected editor")}.`,
2404
+ editorFailed: (editor) => `Could not open ${editorName(editor, "an editor")}. Start the editor or configure editor: "cursor" / "vscode".`,
2380
2405
  completeInstructions: "Add an instruction for every target and wait for context collection to finish.",
2381
2406
  promptCopied: "Prompt copied to the clipboard.",
2382
2407
  clipboardUnavailable: "Clipboard access is unavailable. Select the prompt manually.",
@@ -2389,7 +2414,12 @@ var UI_MESSAGES = Object.freeze({
2389
2414
  localeName: "\u4E2D",
2390
2415
  alternateLocaleName: "EN",
2391
2416
  switchLocale: "\u5C06\u754C\u9762\u8BED\u8A00\u5207\u6362\u4E3A\u82F1\u6587",
2392
- brand: Object.freeze({ name: "SpotPatch", context: "\u5B9E\u65F6\u4E0A\u4E0B\u6587" }),
2417
+ brand: Object.freeze({
2418
+ name: "SpotPatch",
2419
+ context: "\u5B9E\u65F6\u4E0A\u4E0B\u6587",
2420
+ repository: "GitHub",
2421
+ repositoryTitle: "\u5728 GitHub \u4E0A Star SpotPatch"
2422
+ }),
2393
2423
  trigger: Object.freeze({
2394
2424
  select: "\u9009\u62E9\u5143\u7D20",
2395
2425
  stop: "\u505C\u6B62\u9009\u62E9",
@@ -2441,7 +2471,8 @@ var UI_MESSAGES = Object.freeze({
2441
2471
  actions: Object.freeze({
2442
2472
  addElement: "\u6DFB\u52A0\u5143\u7D20",
2443
2473
  reselect: "\u91CD\u65B0\u5F00\u59CB",
2444
- openEditor: "\u5728 VS Code \u4E2D\u6253\u5F00",
2474
+ openEditor: (editor) => editor === "auto" ? "\u6253\u5F00\u6E90\u7801" : `\u5728 ${editorName(editor, "\u7F16\u8F91\u5668")} \u4E2D\u6253\u5F00`,
2475
+ openTarget: (index, editor) => `\u5728${editorName(editor, "\u81EA\u52A8\u68C0\u6D4B\u7684\u7F16\u8F91\u5668")}\u4E2D\u6253\u5F00\u76EE\u6807 ${String(index)} \u7684\u6E90\u7801`,
2445
2476
  preview: "\u9884\u89C8 Prompt",
2446
2477
  copy: "\u590D\u5236 Prompt",
2447
2478
  back: "\u8FD4\u56DE\u7F16\u8F91"
@@ -2497,8 +2528,9 @@ var UI_MESSAGES = Object.freeze({
2497
2528
  detachedTargetRemoved: "\u9875\u9762\u4E2D\u7684\u76EE\u6807\u5DF2\u5378\u8F7D\uFF0C\u5DF2\u4ECE\u5F53\u524D\u9009\u62E9\u4E2D\u79FB\u9664\u3002",
2498
2529
  contextWarning: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\uFF0C\u4F46\u5B58\u5728\u8B66\u544A\u3002",
2499
2530
  contextCollected: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\u3002",
2500
- editorOpened: "\u5DF2\u53D1\u9001 VS Code \u6253\u5F00\u8BF7\u6C42\u3002",
2501
- editorFailed: "\u65E0\u6CD5\u6253\u5F00 VS Code\u3002",
2531
+ editorOpening: (editor) => `\u6B63\u5728\u6253\u5F00${editorName(editor, "\u81EA\u52A8\u68C0\u6D4B\u7684\u7F16\u8F91\u5668")}\u2026\u2026`,
2532
+ editorOpened: (editor) => `\u5DF2\u5728${editorName(editor, "\u81EA\u52A8\u68C0\u6D4B\u7684\u7F16\u8F91\u5668")}\u4E2D\u6253\u5F00\u6E90\u7801\u3002`,
2533
+ editorFailed: (editor) => `\u65E0\u6CD5\u6253\u5F00${editorName(editor, "\u7F16\u8F91\u5668")}\u3002\u8BF7\u5148\u542F\u52A8\u7F16\u8F91\u5668\uFF0C\u6216\u914D\u7F6E editor: "cursor" / "vscode"\u3002`,
2502
2534
  completeInstructions: "\u8BF7\u4E3A\u6BCF\u4E2A\u76EE\u6807\u586B\u5199\u4FEE\u6539\u8BF4\u660E\uFF0C\u5E76\u7B49\u5F85\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\u3002",
2503
2535
  promptCopied: "Prompt \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\u3002",
2504
2536
  clipboardUnavailable: "\u65E0\u6CD5\u8BBF\u95EE\u526A\u8D34\u677F\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9\u5E76\u590D\u5236 Prompt\u3002",
@@ -2793,6 +2825,28 @@ function createStyles(document) {
2793
2825
  gap: 8px;
2794
2826
  flex: none;
2795
2827
  }
2828
+ .spotpatch-repository {
2829
+ display: inline-flex;
2830
+ height: 30px;
2831
+ align-items: center;
2832
+ gap: 5px;
2833
+ border: 1px solid rgb(68 202 224 / 20%);
2834
+ border-radius: 9px;
2835
+ padding: 0 10px;
2836
+ color: #bcecf2;
2837
+ background: rgb(8 145 178 / 7%);
2838
+ font-size: 11px;
2839
+ font-weight: 680;
2840
+ text-decoration: none;
2841
+ transition: border-color 150ms ease, color 150ms ease, background 150ms ease, transform 150ms ease;
2842
+ }
2843
+ .spotpatch-repository::after { content: "\u2197"; font-size: 10px; }
2844
+ .spotpatch-repository:hover {
2845
+ border-color: rgb(68 202 224 / 42%);
2846
+ color: #e6fbff;
2847
+ background: rgb(8 145 178 / 12%);
2848
+ transform: translateY(-1px);
2849
+ }
2796
2850
  .spotpatch-locale {
2797
2851
  min-width: 38px;
2798
2852
  height: 30px;
@@ -2936,7 +2990,7 @@ function createStyles(document) {
2936
2990
  }
2937
2991
  .spotpatch-target-summary {
2938
2992
  display: grid;
2939
- grid-template-columns: minmax(0, 1fr) 34px;
2993
+ grid-template-columns: minmax(0, 1fr) 34px 34px;
2940
2994
  align-items: stretch;
2941
2995
  gap: 4px;
2942
2996
  padding: 5px;
@@ -2987,6 +3041,7 @@ function createStyles(document) {
2987
3041
  white-space: nowrap;
2988
3042
  }
2989
3043
  .spotpatch-target-state[data-complete="false"] { color: #f5c97a; background: rgb(245 158 11 / 9%); }
3044
+ .spotpatch-target-open,
2990
3045
  .spotpatch-target-remove {
2991
3046
  display: inline-grid;
2992
3047
  width: 34px;
@@ -3000,6 +3055,8 @@ function createStyles(document) {
3000
3055
  background: transparent;
3001
3056
  cursor: pointer;
3002
3057
  }
3058
+ .spotpatch-target-open { color: #79d9e7; }
3059
+ .spotpatch-target-open:hover:not(:disabled) { border-color: rgb(68 202 224 / 30%); color: #c8f7ff; background: rgb(8 145 178 / 11%); }
3003
3060
  .spotpatch-target-remove:hover:not(:disabled) { border-color: rgb(251 113 133 / 24%); color: #fda4af; background: rgb(127 29 29 / 12%); }
3004
3061
  .spotpatch-target-editor {
3005
3062
  border-top: 1px solid rgb(255 255 255 / 7%);
@@ -3109,6 +3166,30 @@ function createStyles(document) {
3109
3166
  border-top: 1px solid rgb(255 255 255 / 8%);
3110
3167
  background: rgb(8 12 22 / 82%);
3111
3168
  }
3169
+ .spotpatch-editor-feedback {
3170
+ display: flex;
3171
+ width: 100%;
3172
+ align-items: center;
3173
+ gap: 7px;
3174
+ margin-bottom: 1px;
3175
+ color: #9ca5b5;
3176
+ font-size: 11px;
3177
+ line-height: 1.45;
3178
+ }
3179
+ .spotpatch-editor-feedback[hidden] { display: none; }
3180
+ .spotpatch-editor-feedback::before {
3181
+ width: 6px;
3182
+ height: 6px;
3183
+ flex: none;
3184
+ border-radius: 999px;
3185
+ background: #94a3b8;
3186
+ content: "";
3187
+ }
3188
+ .spotpatch-editor-feedback[data-state="opening"]::before { background: #38bdf8; }
3189
+ .spotpatch-editor-feedback[data-state="success"] { color: #9fe3c4; }
3190
+ .spotpatch-editor-feedback[data-state="success"]::before { background: #34d399; }
3191
+ .spotpatch-editor-feedback[data-state="error"] { color: #fecaca; }
3192
+ .spotpatch-editor-feedback[data-state="error"]::before { background: #fb7185; }
3112
3193
  .spotpatch-actions button {
3113
3194
  display: inline-flex;
3114
3195
  min-height: 40px;
@@ -3140,6 +3221,8 @@ function createStyles(document) {
3140
3221
  .spotpatch-actions .spotpatch-primary::after { margin-left: 8px; content: "\u2197"; font-size: 12px; }
3141
3222
  .spotpatch-actions button:disabled { cursor: not-allowed; opacity: .4; transform: none; }
3142
3223
  .spotpatch-actions button:focus-visible,
3224
+ .spotpatch-repository:focus-visible,
3225
+ .spotpatch-target-open:focus-visible,
3143
3226
  .spotpatch-target-remove:focus-visible,
3144
3227
  .spotpatch-close:focus-visible,
3145
3228
  .spotpatch-trigger:focus-visible,
@@ -3172,6 +3255,8 @@ function createStyles(document) {
3172
3255
  .spotpatch-actions { padding-left: 16px; padding-right: 16px; }
3173
3256
  .spotpatch-target-select { grid-template-columns: 32px minmax(0, 1fr); }
3174
3257
  .spotpatch-target-state { display: none; }
3258
+ .spotpatch-repository { width: 30px; overflow: hidden; padding: 0; justify-content: center; color: transparent; }
3259
+ .spotpatch-repository::after { color: #bcecf2; content: "\u2197"; }
3175
3260
  }
3176
3261
  ${AGENT_PANEL_STYLES}
3177
3262
  `;
@@ -3181,7 +3266,7 @@ function summaryLine(summary, prefix) {
3181
3266
  const line = summary.split("\n").find((candidate) => candidate.startsWith(`${prefix}: `));
3182
3267
  return line?.slice(prefix.length + 2).trim();
3183
3268
  }
3184
- function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto") {
3269
+ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto", editorPreference = "auto") {
3185
3270
  const localizer = createUiLocalizer(document, localePreference);
3186
3271
  let messages = localizer.messages();
3187
3272
  const host = document.createElement("spotpatch-root");
@@ -3231,9 +3316,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3231
3316
  brand.append(createBrandMark(document), brandCopy);
3232
3317
  const headerControls = createMarkedElement(document, "span");
3233
3318
  headerControls.className = "spotpatch-header-controls";
3319
+ const repositoryLink = createMarkedElement(document, "a");
3320
+ repositoryLink.className = "spotpatch-repository";
3321
+ repositoryLink.href = SPOTPATCH_REPOSITORY_URL;
3322
+ repositoryLink.target = "_blank";
3323
+ repositoryLink.rel = "noopener noreferrer";
3234
3324
  const localeButton = createButton(document, "", "spotpatch-locale");
3235
3325
  const closeButton = createButton(document, "\xD7", "spotpatch-close");
3236
- headerControls.append(localeButton, closeButton);
3326
+ headerControls.append(repositoryLink, localeButton, closeButton);
3237
3327
  brandRow.append(brand, headerControls);
3238
3328
  const title = createMarkedElement(document, "h2");
3239
3329
  title.id = "spotpatch-selection-title";
@@ -3297,9 +3387,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3297
3387
  body.append(selectionPanel, previewPanel);
3298
3388
  const actions = createMarkedElement(document, "footer");
3299
3389
  actions.className = "spotpatch-actions";
3390
+ const editorFeedback = createMarkedElement(document, "div");
3391
+ editorFeedback.className = "spotpatch-editor-feedback";
3392
+ editorFeedback.dataset.state = "idle";
3393
+ editorFeedback.setAttribute("role", "status");
3394
+ editorFeedback.setAttribute("aria-live", "polite");
3395
+ editorFeedback.hidden = true;
3300
3396
  const addTargetButton = createButton(document, messages.actions.addElement);
3301
3397
  const reselectButton = createButton(document, messages.actions.reselect);
3302
- const openEditorButton = createButton(document, messages.actions.openEditor);
3398
+ const openEditorButton = createButton(
3399
+ document,
3400
+ messages.actions.openEditor(editorPreference)
3401
+ );
3303
3402
  const previewButton = createButton(
3304
3403
  document,
3305
3404
  messages.actions.preview,
@@ -3308,6 +3407,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3308
3407
  const copyButton = createButton(document, messages.actions.copy, "spotpatch-primary");
3309
3408
  const backButton = createButton(document, messages.actions.back);
3310
3409
  actions.append(
3410
+ editorFeedback,
3311
3411
  agentPanel.runButton,
3312
3412
  previewButton,
3313
3413
  agentPanel.testButton,
@@ -3345,6 +3445,23 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3345
3445
  let editingEnabled = true;
3346
3446
  let currentTargets = [];
3347
3447
  let currentMaximum = 0;
3448
+ let currentEditorFeedbackState = "idle";
3449
+ function defaultEditorFeedback(state) {
3450
+ if (state === "opening") {
3451
+ return messages.announcements.editorOpening(editorPreference);
3452
+ }
3453
+ if (state === "success") {
3454
+ return messages.announcements.editorOpened(editorPreference);
3455
+ }
3456
+ return state === "error" ? messages.announcements.editorFailed(editorPreference) : "";
3457
+ }
3458
+ function renderEditorStatus(state, message = defaultEditorFeedback(state)) {
3459
+ currentEditorFeedbackState = state;
3460
+ editorFeedback.dataset.state = state;
3461
+ editorFeedback.hidden = state === "idle";
3462
+ editorFeedback.textContent = message;
3463
+ placeDialog();
3464
+ }
3348
3465
  function statusText(target) {
3349
3466
  if (target.status === "ready") {
3350
3467
  return messages.targets.statusReady;
@@ -3423,12 +3540,20 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3423
3540
  state.dataset.complete = String(instructionComplete);
3424
3541
  state.textContent = instructionComplete ? messages.targets.instructionReady : messages.targets.instructionMissing;
3425
3542
  select.append(number, copy, state);
3543
+ const open = createButton(document, "\u2197", "spotpatch-target-open");
3544
+ open.dataset.openTargetId = target.id;
3545
+ open.disabled = !target.canOpenEditor;
3546
+ open.setAttribute(
3547
+ "aria-label",
3548
+ messages.actions.openTarget(index + 1, editorPreference)
3549
+ );
3550
+ open.title = messages.actions.openTarget(index + 1, editorPreference);
3426
3551
  const remove = createButton(document, "\xD7", "spotpatch-target-remove");
3427
3552
  remove.dataset.removeTargetId = target.id;
3428
3553
  remove.disabled = !editingEnabled;
3429
3554
  remove.setAttribute("aria-label", messages.targets.remove(index + 1));
3430
3555
  remove.title = messages.targets.removeTitle;
3431
- targetSummary.append(select, remove);
3556
+ targetSummary.append(select, open, remove);
3432
3557
  item.append(targetSummary);
3433
3558
  if (target.active) {
3434
3559
  const editor = createMarkedElement(document, "label");
@@ -3606,6 +3731,9 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3606
3731
  messages = localizer.messages();
3607
3732
  brandName.textContent = messages.brand.name;
3608
3733
  brandContext.textContent = messages.brand.context;
3734
+ repositoryLink.textContent = messages.brand.repository;
3735
+ repositoryLink.title = messages.brand.repositoryTitle;
3736
+ repositoryLink.setAttribute("aria-label", messages.brand.repositoryTitle);
3609
3737
  localeButton.textContent = messages.alternateLocaleName;
3610
3738
  localeButton.title = messages.switchLocale;
3611
3739
  localeButton.setAttribute("aria-label", messages.switchLocale);
@@ -3617,13 +3745,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3617
3745
  promptOutput.setAttribute("aria-label", messages.diagnostics.promptAriaLabel);
3618
3746
  addTargetButton.textContent = messages.actions.addElement;
3619
3747
  reselectButton.textContent = messages.actions.reselect;
3620
- openEditorButton.textContent = messages.actions.openEditor;
3748
+ openEditorButton.textContent = messages.actions.openEditor(editorPreference);
3621
3749
  previewButton.textContent = messages.actions.preview;
3622
3750
  copyButton.textContent = messages.actions.copy;
3623
3751
  backButton.textContent = messages.actions.back;
3624
3752
  triggerButton.title = messages.trigger.title(shortcut);
3625
3753
  triggerButton.textContent = currentStatus === "inspecting" ? messages.trigger.stop : messages.trigger.select;
3626
3754
  renderPanelStatus(currentStatus);
3755
+ renderEditorStatus(currentEditorFeedbackState);
3627
3756
  if (currentTargets.length > 0) {
3628
3757
  renderTargets(currentTargets, currentMaximum);
3629
3758
  } else {
@@ -3654,6 +3783,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3654
3783
  targetList,
3655
3784
  reselectButton,
3656
3785
  openEditorButton,
3786
+ repositoryLink,
3657
3787
  previewButton,
3658
3788
  copyButton,
3659
3789
  backButton,
@@ -3673,6 +3803,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3673
3803
  triggerButton.textContent = inspecting ? messages.trigger.stop : messages.trigger.select;
3674
3804
  renderPanelStatus(status);
3675
3805
  },
3806
+ renderEditorStatus,
3676
3807
  showHighlight(rect, label) {
3677
3808
  currentRect = rect;
3678
3809
  highlight.hidden = false;
@@ -3716,6 +3847,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3716
3847
  currentTargets = [];
3717
3848
  currentMaximum = 0;
3718
3849
  currentSummaryText = "";
3850
+ renderEditorStatus("idle");
3719
3851
  targetCount.textContent = messages.targets.count(0, 0);
3720
3852
  targetComplete.textContent = messages.targets.complete(0, 0);
3721
3853
  targetLabel.textContent = messages.context.selectedElement;
@@ -3759,7 +3891,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3759
3891
  ).forEach((control) => {
3760
3892
  control.disabled = !enabled;
3761
3893
  });
3762
- openEditorButton.disabled = !enabled || !currentCanOpenEditor;
3894
+ openEditorButton.disabled = !currentCanOpenEditor;
3763
3895
  previewButton.disabled = !enabled || !currentCanPreview;
3764
3896
  agentPanel.setEditingEnabled(enabled);
3765
3897
  placeDialog();
@@ -4249,7 +4381,13 @@ function resolveBrowserDependencies(dependencies) {
4249
4381
  }
4250
4382
  function createController(config, dependencies = {}) {
4251
4383
  const browser = resolveBrowserDependencies(dependencies);
4252
- const view = dependencies.view ?? createRuntimeView(browser.document, config.shortcut, config.ai, config.locale);
4384
+ const view = dependencies.view ?? createRuntimeView(
4385
+ browser.document,
4386
+ config.shortcut,
4387
+ config.ai,
4388
+ config.locale,
4389
+ config.editor
4390
+ );
4253
4391
  const api = dependencies.api ?? createRuntimeApi({
4254
4392
  apiBase: config.apiBase,
4255
4393
  fetch: browser.window.fetch.bind(browser.window),
@@ -4280,6 +4418,7 @@ function createController(config, dependencies = {}) {
4280
4418
  let activeTargetId;
4281
4419
  let targetSequence = 0;
4282
4420
  let sessionRevision = 0;
4421
+ let editorRequestRevision = 0;
4283
4422
  let addingTarget = false;
4284
4423
  let previewPrompt = "";
4285
4424
  let previousFocus;
@@ -4354,6 +4493,7 @@ ${summary}`;
4354
4493
  id: target.id,
4355
4494
  label: target.resolution.react.componentName ?? (target.element === void 0 ? target.elementContext?.tagName ?? "Element" : elementLabel(target.element)),
4356
4495
  source: sourceLabel(target, view.messages().context.sourceUnavailable),
4496
+ canOpenEditor: target.marker !== void 0,
4357
4497
  status: targetStatus(target),
4358
4498
  active: target.id === activeTargetId,
4359
4499
  instruction: target.instruction
@@ -4497,7 +4637,6 @@ ${summary}`;
4497
4637
  resizeObserver?.disconnect();
4498
4638
  for (const target of targets) {
4499
4639
  target.element = void 0;
4500
- target.marker = void 0;
4501
4640
  }
4502
4641
  view.hideHighlight();
4503
4642
  view.hideSelectionHighlights();
@@ -4774,24 +4913,37 @@ ${summary}`;
4774
4913
  }
4775
4914
  refreshHighlights();
4776
4915
  }
4777
- function handleOpenEditor() {
4778
- const current = activeTarget();
4916
+ function handleOpenEditor(targetId) {
4917
+ const current = targetId === void 0 ? activeTarget() : targets.find((target) => target.id === targetId);
4779
4918
  if (state.status !== "selected" || current?.marker === void 0) {
4780
4919
  return;
4781
4920
  }
4921
+ if (activeTargetId !== current.id) {
4922
+ activeTargetId = current.id;
4923
+ refreshSelectionView();
4924
+ }
4782
4925
  const marker = current.marker;
4926
+ const selectionRevision = sessionRevision;
4927
+ const requestRevision = ++editorRequestRevision;
4783
4928
  transition({ type: "OPEN_EDITOR" });
4929
+ const openingMessage = view.messages().announcements.editorOpening(config.editor);
4930
+ view.renderEditorStatus("opening", openingMessage);
4931
+ view.announce(openingMessage);
4784
4932
  void api.openEditor({
4785
4933
  fileId: marker.fileId,
4786
4934
  line: marker.line,
4787
4935
  column: marker.column
4788
- }).then(() => {
4789
- if (mounted && state.status === "selected") {
4790
- view.announce(view.messages().announcements.editorOpened);
4936
+ }).then((result) => {
4937
+ if (mounted && state.status === "selected" && selectionRevision === sessionRevision && requestRevision === editorRequestRevision && activeTargetId === current.id && targets.includes(current)) {
4938
+ const message = view.messages().announcements.editorOpened(result.editor);
4939
+ view.renderEditorStatus("success", message);
4940
+ view.announce(message);
4791
4941
  }
4792
4942
  }).catch(() => {
4793
- if (mounted && state.status === "selected") {
4794
- view.announce(view.messages().announcements.editorFailed);
4943
+ if (mounted && state.status === "selected" && selectionRevision === sessionRevision && requestRevision === editorRequestRevision && activeTargetId === current.id && targets.includes(current)) {
4944
+ const message = view.messages().announcements.editorFailed(config.editor);
4945
+ view.renderEditorStatus("error", message);
4946
+ view.announce(message);
4795
4947
  }
4796
4948
  });
4797
4949
  }
@@ -4808,6 +4960,9 @@ ${summary}`;
4808
4960
  view.updateTargetInstruction(target.id, target.instruction);
4809
4961
  view.setPreviewEnabled(canPreview());
4810
4962
  }
4963
+ function handleOpenEditorButtonClick() {
4964
+ handleOpenEditor();
4965
+ }
4811
4966
  function handleTargetListKeydown(event) {
4812
4967
  if (config.ai.enabled && state.status === "selected" && event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
4813
4968
  event.preventDefault();
@@ -4885,6 +5040,13 @@ ${summary}`;
4885
5040
  removeTarget(removeButton.dataset.removeTargetId);
4886
5041
  return;
4887
5042
  }
5043
+ const openButton = eventTarget.closest(
5044
+ "button[data-open-target-id]"
5045
+ );
5046
+ if (openButton?.dataset.openTargetId !== void 0) {
5047
+ handleOpenEditor(openButton.dataset.openTargetId);
5048
+ return;
5049
+ }
4888
5050
  const activateButton = eventTarget.closest(
4889
5051
  "button[data-activate-target-id]"
4890
5052
  );
@@ -4916,7 +5078,7 @@ ${summary}`;
4916
5078
  view.targetList.addEventListener("click", handleTargetListClick);
4917
5079
  view.targetList.addEventListener("input", handleTargetListInput);
4918
5080
  view.targetList.addEventListener("keydown", handleTargetListKeydown);
4919
- view.openEditorButton.addEventListener("click", handleOpenEditor);
5081
+ view.openEditorButton.addEventListener("click", handleOpenEditorButtonClick);
4920
5082
  view.previewButton.addEventListener("click", handlePreview);
4921
5083
  view.copyButton.addEventListener("click", handleCopy);
4922
5084
  view.backButton.addEventListener("click", handleBack);
@@ -4976,7 +5138,7 @@ ${summary}`;
4976
5138
  view.targetList.removeEventListener("click", handleTargetListClick);
4977
5139
  view.targetList.removeEventListener("input", handleTargetListInput);
4978
5140
  view.targetList.removeEventListener("keydown", handleTargetListKeydown);
4979
- view.openEditorButton.removeEventListener("click", handleOpenEditor);
5141
+ view.openEditorButton.removeEventListener("click", handleOpenEditorButtonClick);
4980
5142
  view.previewButton.removeEventListener("click", handlePreview);
4981
5143
  view.copyButton.removeEventListener("click", handleCopy);
4982
5144
  view.backButton.removeEventListener("click", handleBack);