@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.cjs CHANGED
@@ -259,6 +259,9 @@ function readSuccessData(value) {
259
259
  function isCodeContext(value) {
260
260
  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");
261
261
  }
262
+ function isEditorOpenResult(value) {
263
+ return isRecord2(value) && typeof value.editor === "string" && import_shared3.SPOTPATCH_EDITOR_PREFERENCES.includes(value.editor);
264
+ }
262
265
  function deepFreezeUnknown(value) {
263
266
  if (!isRecord2(value) && !Array.isArray(value)) {
264
267
  return;
@@ -485,7 +488,11 @@ function createRuntimeApi(options) {
485
488
  return Object.freeze({ ...data });
486
489
  },
487
490
  async openEditor(request) {
488
- await requestJson(import_shared3.SPOTPATCH_ENDPOINTS.openEditor, "POST", request);
491
+ const data = await requestJson(import_shared3.SPOTPATCH_ENDPOINTS.openEditor, "POST", request);
492
+ if (!isEditorOpenResult(data)) {
493
+ throw new RuntimeApiError();
494
+ }
495
+ return Object.freeze({ editor: data.editor });
489
496
  },
490
497
  async agentCapability(request) {
491
498
  return parseCapability(
@@ -2135,6 +2142,15 @@ var STATUS_ZH = Object.freeze({
2135
2142
  reverted: "\u5DF2\u64A4\u9500",
2136
2143
  failed: "\u5931\u8D25"
2137
2144
  });
2145
+ function editorName(editor, automatic) {
2146
+ if (editor === "vscode") {
2147
+ return "VS Code";
2148
+ }
2149
+ if (editor === "cursor") {
2150
+ return "Cursor";
2151
+ }
2152
+ return automatic;
2153
+ }
2138
2154
  var ERROR_MESSAGES_EN = Object.freeze({
2139
2155
  [import_shared6.ERROR_CODES.INVALID_REQUEST]: "The Agent request was rejected as invalid.",
2140
2156
  [import_shared6.ERROR_CODES.INVALID_TOKEN]: "The local SpotPatch session expired.",
@@ -2270,7 +2286,12 @@ var UI_MESSAGES = Object.freeze({
2270
2286
  localeName: "EN",
2271
2287
  alternateLocaleName: "\u4E2D",
2272
2288
  switchLocale: "Switch interface language to Chinese",
2273
- brand: Object.freeze({ name: "SpotPatch", context: "Live context" }),
2289
+ brand: Object.freeze({
2290
+ name: "SpotPatch",
2291
+ context: "Live context",
2292
+ repository: "GitHub",
2293
+ repositoryTitle: "Star SpotPatch on GitHub"
2294
+ }),
2274
2295
  trigger: Object.freeze({
2275
2296
  select: "Select element",
2276
2297
  stop: "Stop selecting",
@@ -2322,7 +2343,8 @@ var UI_MESSAGES = Object.freeze({
2322
2343
  actions: Object.freeze({
2323
2344
  addElement: "Add element",
2324
2345
  reselect: "Start over",
2325
- openEditor: "Open in VS Code",
2346
+ openEditor: (editor) => editor === "auto" ? "Open source" : `Open in ${editorName(editor, "editor")}`,
2347
+ openTarget: (index, editor) => `Open target ${String(index)} in ${editorName(editor, "the detected editor")}`,
2326
2348
  preview: "Preview prompt",
2327
2349
  copy: "Copy prompt",
2328
2350
  back: "Back to edit"
@@ -2378,8 +2400,9 @@ var UI_MESSAGES = Object.freeze({
2378
2400
  detachedTargetRemoved: "A removed page element was dropped from the selection.",
2379
2401
  contextWarning: "Browser context collection completed with a warning.",
2380
2402
  contextCollected: "Browser context collected.",
2381
- editorOpened: "VS Code open request sent.",
2382
- editorFailed: "VS Code could not be opened.",
2403
+ editorOpening: (editor) => `Opening ${editorName(editor, "the detected editor")}\u2026`,
2404
+ editorOpened: (editor) => `Opened the source in ${editorName(editor, "the detected editor")}.`,
2405
+ editorFailed: (editor) => `Could not open ${editorName(editor, "an editor")}. Start the editor or configure editor: "cursor" / "vscode".`,
2383
2406
  completeInstructions: "Add an instruction for every target and wait for context collection to finish.",
2384
2407
  promptCopied: "Prompt copied to the clipboard.",
2385
2408
  clipboardUnavailable: "Clipboard access is unavailable. Select the prompt manually.",
@@ -2392,7 +2415,12 @@ var UI_MESSAGES = Object.freeze({
2392
2415
  localeName: "\u4E2D",
2393
2416
  alternateLocaleName: "EN",
2394
2417
  switchLocale: "\u5C06\u754C\u9762\u8BED\u8A00\u5207\u6362\u4E3A\u82F1\u6587",
2395
- brand: Object.freeze({ name: "SpotPatch", context: "\u5B9E\u65F6\u4E0A\u4E0B\u6587" }),
2418
+ brand: Object.freeze({
2419
+ name: "SpotPatch",
2420
+ context: "\u5B9E\u65F6\u4E0A\u4E0B\u6587",
2421
+ repository: "GitHub",
2422
+ repositoryTitle: "\u5728 GitHub \u4E0A Star SpotPatch"
2423
+ }),
2396
2424
  trigger: Object.freeze({
2397
2425
  select: "\u9009\u62E9\u5143\u7D20",
2398
2426
  stop: "\u505C\u6B62\u9009\u62E9",
@@ -2444,7 +2472,8 @@ var UI_MESSAGES = Object.freeze({
2444
2472
  actions: Object.freeze({
2445
2473
  addElement: "\u6DFB\u52A0\u5143\u7D20",
2446
2474
  reselect: "\u91CD\u65B0\u5F00\u59CB",
2447
- openEditor: "\u5728 VS Code \u4E2D\u6253\u5F00",
2475
+ openEditor: (editor) => editor === "auto" ? "\u6253\u5F00\u6E90\u7801" : `\u5728 ${editorName(editor, "\u7F16\u8F91\u5668")} \u4E2D\u6253\u5F00`,
2476
+ openTarget: (index, editor) => `\u5728${editorName(editor, "\u81EA\u52A8\u68C0\u6D4B\u7684\u7F16\u8F91\u5668")}\u4E2D\u6253\u5F00\u76EE\u6807 ${String(index)} \u7684\u6E90\u7801`,
2448
2477
  preview: "\u9884\u89C8 Prompt",
2449
2478
  copy: "\u590D\u5236 Prompt",
2450
2479
  back: "\u8FD4\u56DE\u7F16\u8F91"
@@ -2500,8 +2529,9 @@ var UI_MESSAGES = Object.freeze({
2500
2529
  detachedTargetRemoved: "\u9875\u9762\u4E2D\u7684\u76EE\u6807\u5DF2\u5378\u8F7D\uFF0C\u5DF2\u4ECE\u5F53\u524D\u9009\u62E9\u4E2D\u79FB\u9664\u3002",
2501
2530
  contextWarning: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\uFF0C\u4F46\u5B58\u5728\u8B66\u544A\u3002",
2502
2531
  contextCollected: "\u6D4F\u89C8\u5668\u4E0A\u4E0B\u6587\u91C7\u96C6\u5B8C\u6210\u3002",
2503
- editorOpened: "\u5DF2\u53D1\u9001 VS Code \u6253\u5F00\u8BF7\u6C42\u3002",
2504
- editorFailed: "\u65E0\u6CD5\u6253\u5F00 VS Code\u3002",
2532
+ editorOpening: (editor) => `\u6B63\u5728\u6253\u5F00${editorName(editor, "\u81EA\u52A8\u68C0\u6D4B\u7684\u7F16\u8F91\u5668")}\u2026\u2026`,
2533
+ editorOpened: (editor) => `\u5DF2\u5728${editorName(editor, "\u81EA\u52A8\u68C0\u6D4B\u7684\u7F16\u8F91\u5668")}\u4E2D\u6253\u5F00\u6E90\u7801\u3002`,
2534
+ 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`,
2505
2535
  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",
2506
2536
  promptCopied: "Prompt \u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F\u3002",
2507
2537
  clipboardUnavailable: "\u65E0\u6CD5\u8BBF\u95EE\u526A\u8D34\u677F\uFF0C\u8BF7\u624B\u52A8\u9009\u62E9\u5E76\u590D\u5236 Prompt\u3002",
@@ -2796,6 +2826,28 @@ function createStyles(document) {
2796
2826
  gap: 8px;
2797
2827
  flex: none;
2798
2828
  }
2829
+ .spotpatch-repository {
2830
+ display: inline-flex;
2831
+ height: 30px;
2832
+ align-items: center;
2833
+ gap: 5px;
2834
+ border: 1px solid rgb(68 202 224 / 20%);
2835
+ border-radius: 9px;
2836
+ padding: 0 10px;
2837
+ color: #bcecf2;
2838
+ background: rgb(8 145 178 / 7%);
2839
+ font-size: 11px;
2840
+ font-weight: 680;
2841
+ text-decoration: none;
2842
+ transition: border-color 150ms ease, color 150ms ease, background 150ms ease, transform 150ms ease;
2843
+ }
2844
+ .spotpatch-repository::after { content: "\u2197"; font-size: 10px; }
2845
+ .spotpatch-repository:hover {
2846
+ border-color: rgb(68 202 224 / 42%);
2847
+ color: #e6fbff;
2848
+ background: rgb(8 145 178 / 12%);
2849
+ transform: translateY(-1px);
2850
+ }
2799
2851
  .spotpatch-locale {
2800
2852
  min-width: 38px;
2801
2853
  height: 30px;
@@ -2939,7 +2991,7 @@ function createStyles(document) {
2939
2991
  }
2940
2992
  .spotpatch-target-summary {
2941
2993
  display: grid;
2942
- grid-template-columns: minmax(0, 1fr) 34px;
2994
+ grid-template-columns: minmax(0, 1fr) 34px 34px;
2943
2995
  align-items: stretch;
2944
2996
  gap: 4px;
2945
2997
  padding: 5px;
@@ -2990,6 +3042,7 @@ function createStyles(document) {
2990
3042
  white-space: nowrap;
2991
3043
  }
2992
3044
  .spotpatch-target-state[data-complete="false"] { color: #f5c97a; background: rgb(245 158 11 / 9%); }
3045
+ .spotpatch-target-open,
2993
3046
  .spotpatch-target-remove {
2994
3047
  display: inline-grid;
2995
3048
  width: 34px;
@@ -3003,6 +3056,8 @@ function createStyles(document) {
3003
3056
  background: transparent;
3004
3057
  cursor: pointer;
3005
3058
  }
3059
+ .spotpatch-target-open { color: #79d9e7; }
3060
+ .spotpatch-target-open:hover:not(:disabled) { border-color: rgb(68 202 224 / 30%); color: #c8f7ff; background: rgb(8 145 178 / 11%); }
3006
3061
  .spotpatch-target-remove:hover:not(:disabled) { border-color: rgb(251 113 133 / 24%); color: #fda4af; background: rgb(127 29 29 / 12%); }
3007
3062
  .spotpatch-target-editor {
3008
3063
  border-top: 1px solid rgb(255 255 255 / 7%);
@@ -3112,6 +3167,30 @@ function createStyles(document) {
3112
3167
  border-top: 1px solid rgb(255 255 255 / 8%);
3113
3168
  background: rgb(8 12 22 / 82%);
3114
3169
  }
3170
+ .spotpatch-editor-feedback {
3171
+ display: flex;
3172
+ width: 100%;
3173
+ align-items: center;
3174
+ gap: 7px;
3175
+ margin-bottom: 1px;
3176
+ color: #9ca5b5;
3177
+ font-size: 11px;
3178
+ line-height: 1.45;
3179
+ }
3180
+ .spotpatch-editor-feedback[hidden] { display: none; }
3181
+ .spotpatch-editor-feedback::before {
3182
+ width: 6px;
3183
+ height: 6px;
3184
+ flex: none;
3185
+ border-radius: 999px;
3186
+ background: #94a3b8;
3187
+ content: "";
3188
+ }
3189
+ .spotpatch-editor-feedback[data-state="opening"]::before { background: #38bdf8; }
3190
+ .spotpatch-editor-feedback[data-state="success"] { color: #9fe3c4; }
3191
+ .spotpatch-editor-feedback[data-state="success"]::before { background: #34d399; }
3192
+ .spotpatch-editor-feedback[data-state="error"] { color: #fecaca; }
3193
+ .spotpatch-editor-feedback[data-state="error"]::before { background: #fb7185; }
3115
3194
  .spotpatch-actions button {
3116
3195
  display: inline-flex;
3117
3196
  min-height: 40px;
@@ -3143,6 +3222,8 @@ function createStyles(document) {
3143
3222
  .spotpatch-actions .spotpatch-primary::after { margin-left: 8px; content: "\u2197"; font-size: 12px; }
3144
3223
  .spotpatch-actions button:disabled { cursor: not-allowed; opacity: .4; transform: none; }
3145
3224
  .spotpatch-actions button:focus-visible,
3225
+ .spotpatch-repository:focus-visible,
3226
+ .spotpatch-target-open:focus-visible,
3146
3227
  .spotpatch-target-remove:focus-visible,
3147
3228
  .spotpatch-close:focus-visible,
3148
3229
  .spotpatch-trigger:focus-visible,
@@ -3175,6 +3256,8 @@ function createStyles(document) {
3175
3256
  .spotpatch-actions { padding-left: 16px; padding-right: 16px; }
3176
3257
  .spotpatch-target-select { grid-template-columns: 32px minmax(0, 1fr); }
3177
3258
  .spotpatch-target-state { display: none; }
3259
+ .spotpatch-repository { width: 30px; overflow: hidden; padding: 0; justify-content: center; color: transparent; }
3260
+ .spotpatch-repository::after { color: #bcecf2; content: "\u2197"; }
3178
3261
  }
3179
3262
  ${AGENT_PANEL_STYLES}
3180
3263
  `;
@@ -3184,7 +3267,7 @@ function summaryLine(summary, prefix) {
3184
3267
  const line = summary.split("\n").find((candidate) => candidate.startsWith(`${prefix}: `));
3185
3268
  return line?.slice(prefix.length + 2).trim();
3186
3269
  }
3187
- function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto") {
3270
+ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: false }), localePreference = "auto", editorPreference = "auto") {
3188
3271
  const localizer = createUiLocalizer(document, localePreference);
3189
3272
  let messages = localizer.messages();
3190
3273
  const host = document.createElement("spotpatch-root");
@@ -3234,9 +3317,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3234
3317
  brand.append(createBrandMark(document), brandCopy);
3235
3318
  const headerControls = createMarkedElement(document, "span");
3236
3319
  headerControls.className = "spotpatch-header-controls";
3320
+ const repositoryLink = createMarkedElement(document, "a");
3321
+ repositoryLink.className = "spotpatch-repository";
3322
+ repositoryLink.href = import_shared7.SPOTPATCH_REPOSITORY_URL;
3323
+ repositoryLink.target = "_blank";
3324
+ repositoryLink.rel = "noopener noreferrer";
3237
3325
  const localeButton = createButton(document, "", "spotpatch-locale");
3238
3326
  const closeButton = createButton(document, "\xD7", "spotpatch-close");
3239
- headerControls.append(localeButton, closeButton);
3327
+ headerControls.append(repositoryLink, localeButton, closeButton);
3240
3328
  brandRow.append(brand, headerControls);
3241
3329
  const title = createMarkedElement(document, "h2");
3242
3330
  title.id = "spotpatch-selection-title";
@@ -3300,9 +3388,18 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3300
3388
  body.append(selectionPanel, previewPanel);
3301
3389
  const actions = createMarkedElement(document, "footer");
3302
3390
  actions.className = "spotpatch-actions";
3391
+ const editorFeedback = createMarkedElement(document, "div");
3392
+ editorFeedback.className = "spotpatch-editor-feedback";
3393
+ editorFeedback.dataset.state = "idle";
3394
+ editorFeedback.setAttribute("role", "status");
3395
+ editorFeedback.setAttribute("aria-live", "polite");
3396
+ editorFeedback.hidden = true;
3303
3397
  const addTargetButton = createButton(document, messages.actions.addElement);
3304
3398
  const reselectButton = createButton(document, messages.actions.reselect);
3305
- const openEditorButton = createButton(document, messages.actions.openEditor);
3399
+ const openEditorButton = createButton(
3400
+ document,
3401
+ messages.actions.openEditor(editorPreference)
3402
+ );
3306
3403
  const previewButton = createButton(
3307
3404
  document,
3308
3405
  messages.actions.preview,
@@ -3311,6 +3408,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3311
3408
  const copyButton = createButton(document, messages.actions.copy, "spotpatch-primary");
3312
3409
  const backButton = createButton(document, messages.actions.back);
3313
3410
  actions.append(
3411
+ editorFeedback,
3314
3412
  agentPanel.runButton,
3315
3413
  previewButton,
3316
3414
  agentPanel.testButton,
@@ -3348,6 +3446,23 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3348
3446
  let editingEnabled = true;
3349
3447
  let currentTargets = [];
3350
3448
  let currentMaximum = 0;
3449
+ let currentEditorFeedbackState = "idle";
3450
+ function defaultEditorFeedback(state) {
3451
+ if (state === "opening") {
3452
+ return messages.announcements.editorOpening(editorPreference);
3453
+ }
3454
+ if (state === "success") {
3455
+ return messages.announcements.editorOpened(editorPreference);
3456
+ }
3457
+ return state === "error" ? messages.announcements.editorFailed(editorPreference) : "";
3458
+ }
3459
+ function renderEditorStatus(state, message = defaultEditorFeedback(state)) {
3460
+ currentEditorFeedbackState = state;
3461
+ editorFeedback.dataset.state = state;
3462
+ editorFeedback.hidden = state === "idle";
3463
+ editorFeedback.textContent = message;
3464
+ placeDialog();
3465
+ }
3351
3466
  function statusText(target) {
3352
3467
  if (target.status === "ready") {
3353
3468
  return messages.targets.statusReady;
@@ -3426,12 +3541,20 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3426
3541
  state.dataset.complete = String(instructionComplete);
3427
3542
  state.textContent = instructionComplete ? messages.targets.instructionReady : messages.targets.instructionMissing;
3428
3543
  select.append(number, copy, state);
3544
+ const open = createButton(document, "\u2197", "spotpatch-target-open");
3545
+ open.dataset.openTargetId = target.id;
3546
+ open.disabled = !target.canOpenEditor;
3547
+ open.setAttribute(
3548
+ "aria-label",
3549
+ messages.actions.openTarget(index + 1, editorPreference)
3550
+ );
3551
+ open.title = messages.actions.openTarget(index + 1, editorPreference);
3429
3552
  const remove = createButton(document, "\xD7", "spotpatch-target-remove");
3430
3553
  remove.dataset.removeTargetId = target.id;
3431
3554
  remove.disabled = !editingEnabled;
3432
3555
  remove.setAttribute("aria-label", messages.targets.remove(index + 1));
3433
3556
  remove.title = messages.targets.removeTitle;
3434
- targetSummary.append(select, remove);
3557
+ targetSummary.append(select, open, remove);
3435
3558
  item.append(targetSummary);
3436
3559
  if (target.active) {
3437
3560
  const editor = createMarkedElement(document, "label");
@@ -3609,6 +3732,9 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3609
3732
  messages = localizer.messages();
3610
3733
  brandName.textContent = messages.brand.name;
3611
3734
  brandContext.textContent = messages.brand.context;
3735
+ repositoryLink.textContent = messages.brand.repository;
3736
+ repositoryLink.title = messages.brand.repositoryTitle;
3737
+ repositoryLink.setAttribute("aria-label", messages.brand.repositoryTitle);
3612
3738
  localeButton.textContent = messages.alternateLocaleName;
3613
3739
  localeButton.title = messages.switchLocale;
3614
3740
  localeButton.setAttribute("aria-label", messages.switchLocale);
@@ -3620,13 +3746,14 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3620
3746
  promptOutput.setAttribute("aria-label", messages.diagnostics.promptAriaLabel);
3621
3747
  addTargetButton.textContent = messages.actions.addElement;
3622
3748
  reselectButton.textContent = messages.actions.reselect;
3623
- openEditorButton.textContent = messages.actions.openEditor;
3749
+ openEditorButton.textContent = messages.actions.openEditor(editorPreference);
3624
3750
  previewButton.textContent = messages.actions.preview;
3625
3751
  copyButton.textContent = messages.actions.copy;
3626
3752
  backButton.textContent = messages.actions.back;
3627
3753
  triggerButton.title = messages.trigger.title(shortcut);
3628
3754
  triggerButton.textContent = currentStatus === "inspecting" ? messages.trigger.stop : messages.trigger.select;
3629
3755
  renderPanelStatus(currentStatus);
3756
+ renderEditorStatus(currentEditorFeedbackState);
3630
3757
  if (currentTargets.length > 0) {
3631
3758
  renderTargets(currentTargets, currentMaximum);
3632
3759
  } else {
@@ -3657,6 +3784,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3657
3784
  targetList,
3658
3785
  reselectButton,
3659
3786
  openEditorButton,
3787
+ repositoryLink,
3660
3788
  previewButton,
3661
3789
  copyButton,
3662
3790
  backButton,
@@ -3676,6 +3804,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3676
3804
  triggerButton.textContent = inspecting ? messages.trigger.stop : messages.trigger.select;
3677
3805
  renderPanelStatus(status);
3678
3806
  },
3807
+ renderEditorStatus,
3679
3808
  showHighlight(rect, label) {
3680
3809
  currentRect = rect;
3681
3810
  highlight.hidden = false;
@@ -3719,6 +3848,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3719
3848
  currentTargets = [];
3720
3849
  currentMaximum = 0;
3721
3850
  currentSummaryText = "";
3851
+ renderEditorStatus("idle");
3722
3852
  targetCount.textContent = messages.targets.count(0, 0);
3723
3853
  targetComplete.textContent = messages.targets.complete(0, 0);
3724
3854
  targetLabel.textContent = messages.context.selectedElement;
@@ -3762,7 +3892,7 @@ function createRuntimeView(document, shortcut, ai = Object.freeze({ enabled: fal
3762
3892
  ).forEach((control) => {
3763
3893
  control.disabled = !enabled;
3764
3894
  });
3765
- openEditorButton.disabled = !enabled || !currentCanOpenEditor;
3895
+ openEditorButton.disabled = !currentCanOpenEditor;
3766
3896
  previewButton.disabled = !enabled || !currentCanPreview;
3767
3897
  agentPanel.setEditingEnabled(enabled);
3768
3898
  placeDialog();
@@ -4250,7 +4380,13 @@ function resolveBrowserDependencies(dependencies) {
4250
4380
  }
4251
4381
  function createController(config, dependencies = {}) {
4252
4382
  const browser = resolveBrowserDependencies(dependencies);
4253
- const view = dependencies.view ?? createRuntimeView(browser.document, config.shortcut, config.ai, config.locale);
4383
+ const view = dependencies.view ?? createRuntimeView(
4384
+ browser.document,
4385
+ config.shortcut,
4386
+ config.ai,
4387
+ config.locale,
4388
+ config.editor
4389
+ );
4254
4390
  const api = dependencies.api ?? createRuntimeApi({
4255
4391
  apiBase: config.apiBase,
4256
4392
  fetch: browser.window.fetch.bind(browser.window),
@@ -4281,6 +4417,7 @@ function createController(config, dependencies = {}) {
4281
4417
  let activeTargetId;
4282
4418
  let targetSequence = 0;
4283
4419
  let sessionRevision = 0;
4420
+ let editorRequestRevision = 0;
4284
4421
  let addingTarget = false;
4285
4422
  let previewPrompt = "";
4286
4423
  let previousFocus;
@@ -4355,6 +4492,7 @@ ${summary}`;
4355
4492
  id: target.id,
4356
4493
  label: target.resolution.react.componentName ?? (target.element === void 0 ? target.elementContext?.tagName ?? "Element" : elementLabel(target.element)),
4357
4494
  source: sourceLabel(target, view.messages().context.sourceUnavailable),
4495
+ canOpenEditor: target.marker !== void 0,
4358
4496
  status: targetStatus(target),
4359
4497
  active: target.id === activeTargetId,
4360
4498
  instruction: target.instruction
@@ -4498,7 +4636,6 @@ ${summary}`;
4498
4636
  resizeObserver?.disconnect();
4499
4637
  for (const target of targets) {
4500
4638
  target.element = void 0;
4501
- target.marker = void 0;
4502
4639
  }
4503
4640
  view.hideHighlight();
4504
4641
  view.hideSelectionHighlights();
@@ -4775,24 +4912,37 @@ ${summary}`;
4775
4912
  }
4776
4913
  refreshHighlights();
4777
4914
  }
4778
- function handleOpenEditor() {
4779
- const current = activeTarget();
4915
+ function handleOpenEditor(targetId) {
4916
+ const current = targetId === void 0 ? activeTarget() : targets.find((target) => target.id === targetId);
4780
4917
  if (state.status !== "selected" || current?.marker === void 0) {
4781
4918
  return;
4782
4919
  }
4920
+ if (activeTargetId !== current.id) {
4921
+ activeTargetId = current.id;
4922
+ refreshSelectionView();
4923
+ }
4783
4924
  const marker = current.marker;
4925
+ const selectionRevision = sessionRevision;
4926
+ const requestRevision = ++editorRequestRevision;
4784
4927
  transition({ type: "OPEN_EDITOR" });
4928
+ const openingMessage = view.messages().announcements.editorOpening(config.editor);
4929
+ view.renderEditorStatus("opening", openingMessage);
4930
+ view.announce(openingMessage);
4785
4931
  void api.openEditor({
4786
4932
  fileId: marker.fileId,
4787
4933
  line: marker.line,
4788
4934
  column: marker.column
4789
- }).then(() => {
4790
- if (mounted && state.status === "selected") {
4791
- view.announce(view.messages().announcements.editorOpened);
4935
+ }).then((result) => {
4936
+ if (mounted && state.status === "selected" && selectionRevision === sessionRevision && requestRevision === editorRequestRevision && activeTargetId === current.id && targets.includes(current)) {
4937
+ const message = view.messages().announcements.editorOpened(result.editor);
4938
+ view.renderEditorStatus("success", message);
4939
+ view.announce(message);
4792
4940
  }
4793
4941
  }).catch(() => {
4794
- if (mounted && state.status === "selected") {
4795
- view.announce(view.messages().announcements.editorFailed);
4942
+ if (mounted && state.status === "selected" && selectionRevision === sessionRevision && requestRevision === editorRequestRevision && activeTargetId === current.id && targets.includes(current)) {
4943
+ const message = view.messages().announcements.editorFailed(config.editor);
4944
+ view.renderEditorStatus("error", message);
4945
+ view.announce(message);
4796
4946
  }
4797
4947
  });
4798
4948
  }
@@ -4809,6 +4959,9 @@ ${summary}`;
4809
4959
  view.updateTargetInstruction(target.id, target.instruction);
4810
4960
  view.setPreviewEnabled(canPreview());
4811
4961
  }
4962
+ function handleOpenEditorButtonClick() {
4963
+ handleOpenEditor();
4964
+ }
4812
4965
  function handleTargetListKeydown(event) {
4813
4966
  if (config.ai.enabled && state.status === "selected" && event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
4814
4967
  event.preventDefault();
@@ -4886,6 +5039,13 @@ ${summary}`;
4886
5039
  removeTarget(removeButton.dataset.removeTargetId);
4887
5040
  return;
4888
5041
  }
5042
+ const openButton = eventTarget.closest(
5043
+ "button[data-open-target-id]"
5044
+ );
5045
+ if (openButton?.dataset.openTargetId !== void 0) {
5046
+ handleOpenEditor(openButton.dataset.openTargetId);
5047
+ return;
5048
+ }
4889
5049
  const activateButton = eventTarget.closest(
4890
5050
  "button[data-activate-target-id]"
4891
5051
  );
@@ -4917,7 +5077,7 @@ ${summary}`;
4917
5077
  view.targetList.addEventListener("click", handleTargetListClick);
4918
5078
  view.targetList.addEventListener("input", handleTargetListInput);
4919
5079
  view.targetList.addEventListener("keydown", handleTargetListKeydown);
4920
- view.openEditorButton.addEventListener("click", handleOpenEditor);
5080
+ view.openEditorButton.addEventListener("click", handleOpenEditorButtonClick);
4921
5081
  view.previewButton.addEventListener("click", handlePreview);
4922
5082
  view.copyButton.addEventListener("click", handleCopy);
4923
5083
  view.backButton.addEventListener("click", handleBack);
@@ -4977,7 +5137,7 @@ ${summary}`;
4977
5137
  view.targetList.removeEventListener("click", handleTargetListClick);
4978
5138
  view.targetList.removeEventListener("input", handleTargetListInput);
4979
5139
  view.targetList.removeEventListener("keydown", handleTargetListKeydown);
4980
- view.openEditorButton.removeEventListener("click", handleOpenEditor);
5140
+ view.openEditorButton.removeEventListener("click", handleOpenEditorButtonClick);
4981
5141
  view.previewButton.removeEventListener("click", handlePreview);
4982
5142
  view.copyButton.removeEventListener("click", handleCopy);
4983
5143
  view.backButton.removeEventListener("click", handleBack);