pi-studio 0.9.39 → 0.9.41

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.
@@ -208,7 +208,12 @@
208
208
  Numpad7: "repl",
209
209
  };
210
210
 
211
+ const navigationHelpers = globalThis.PiStudioNavigationHelpers;
212
+ if (!navigationHelpers || typeof navigationHelpers.readPaneFocusTarget !== "function") {
213
+ throw new Error("Studio navigation helpers failed to load.");
214
+ }
211
215
  const initialQueryParams = new URLSearchParams(window.location.search || "");
216
+ const initialPaneFocusTarget = navigationHelpers.readPaneFocusTarget(window.location);
212
217
  const skipInitialWorkspaceRestore = initialQueryParams.get("skipWorkspaceRestore") === "1";
213
218
  const explicitDocumentIdentityFromUrl = initialQueryParams.has("docId")
214
219
  || initialQueryParams.has("docSource")
@@ -264,7 +269,8 @@
264
269
  let pendingRequestId = null;
265
270
  let pendingKind = null;
266
271
  let stickyStudioKind = null;
267
- const pendingCompanionWindows = new Map();
272
+ const pendingCompanionLaunches = new Map();
273
+ const activeStudioTabLaunches = new Set();
268
274
  let sourceOriginSummaryEl = null;
269
275
  let sourceResetOriginBtn = null;
270
276
  let sourceOpenCurrentFileTabBtn = null;
@@ -1210,11 +1216,21 @@
1210
1216
 
1211
1217
  function getStudioShortcutLabel(kind) {
1212
1218
  const mac = isMacShortcutPlatform();
1213
- if (kind === "repl-send") return mac ? "⇧⌘↵" : "Ctrl+Shift+Enter";
1219
+ if (kind === "repl-send") return mac ? "⌃⇧↵" : "Ctrl+Shift+Enter";
1214
1220
  if (kind === "run") return mac ? "⌘↵" : "Ctrl+Enter";
1215
1221
  return "";
1216
1222
  }
1217
1223
 
1224
+ function getStudioShortcutDescription(kind) {
1225
+ if (kind === "repl-send") {
1226
+ return isMacShortcutPlatform()
1227
+ ? "Ctrl+Shift+Enter (Cmd+Shift+Enter also works where available)"
1228
+ : "Ctrl+Shift+Enter";
1229
+ }
1230
+ if (kind === "run") return "Cmd/Ctrl+Enter";
1231
+ return "";
1232
+ }
1233
+
1218
1234
  function withStudioShortcutLabel(label, kind) {
1219
1235
  const shortcut = getStudioShortcutLabel(kind);
1220
1236
  return shortcut ? (label + " " + shortcut) : label;
@@ -2036,8 +2052,8 @@
2036
2052
  actionRequestId: null,
2037
2053
  };
2038
2054
  let fileBackedBaselineText = null;
2039
- let activePane = "left";
2040
- let paneFocusTarget = "off";
2055
+ let activePane = initialPaneFocusTarget === "right" ? "right" : "left";
2056
+ let paneFocusTarget = initialPaneFocusTarget;
2041
2057
  let paneSplitPercent = 50;
2042
2058
  const PANE_SPLIT_STORAGE_KEY = "piStudio.paneSplitPercent";
2043
2059
  const PANE_SPLIT_MIN_PERCENT = 20;
@@ -2565,17 +2581,12 @@
2565
2581
  return;
2566
2582
  }
2567
2583
  closeStudioUiRefreshMenus();
2568
- const targetUrl = buildAuthedStudioUrl("/", {
2569
- mode: "editor-only",
2570
- docSource: "file",
2571
- docLabel: sourceState && sourceState.label ? sourceState.label : basenameForStudioPath(path),
2572
- docPath: path,
2573
- resourceDir: getCurrentResourceDirValue() || dirnameForDisplayPath(path),
2574
- skipWorkspaceRestore: "1",
2575
- });
2576
2584
  try {
2577
- window.open(targetUrl, "_blank", "noopener");
2578
- setStatus("Opening current file in a new editor tab.", "success");
2585
+ openFileBackedStudioEditorTab(path, {
2586
+ label: sourceState && sourceState.label ? sourceState.label : basenameForStudioPath(path),
2587
+ resourceDir: getCurrentResourceDirValue() || dirnameForDisplayPath(path),
2588
+ });
2589
+ setStatus("Opening current file in a new editor tab.");
2579
2590
  } catch (error) {
2580
2591
  setStatus((error && error.message) ? error.message : String(error || "Could not open file tab."), "warning");
2581
2592
  }
@@ -3854,6 +3865,14 @@
3854
3865
  updatePaneFocusButtons();
3855
3866
  }
3856
3867
 
3868
+ function persistPaneFocusUrlState() {
3869
+ try {
3870
+ navigationHelpers.replacePaneFocusUrlState(window, paneFocusTarget);
3871
+ } catch {
3872
+ // Ignore URL-state update failures; focus mode still works for this page lifetime.
3873
+ }
3874
+ }
3875
+
3857
3876
  function snapshotStudioScrollablePositions() {
3858
3877
  return [sourceTextEl, sourcePreviewEl, critiqueViewEl]
3859
3878
  .filter((el) => el && typeof el.scrollTop === "number" && typeof el.scrollLeft === "number")
@@ -3908,6 +3927,7 @@
3908
3927
  if (paneFocusTarget !== "off" && paneFocusTarget !== activePane) {
3909
3928
  paneFocusTarget = activePane;
3910
3929
  applyPaneFocusClasses();
3930
+ persistPaneFocusUrlState();
3911
3931
  }
3912
3932
  }
3913
3933
 
@@ -4048,6 +4068,7 @@
4048
4068
  setActivePane(pane);
4049
4069
  paneFocusTarget = pane;
4050
4070
  applyPaneFocusClasses();
4071
+ persistPaneFocusUrlState();
4051
4072
  setStatus("Focus mode: " + paneLabel(pane) + " pane. Toggle with F10 or Cmd/Ctrl+Esc.");
4052
4073
  }
4053
4074
 
@@ -4055,6 +4076,7 @@
4055
4076
  if (paneFocusTarget === activePane) {
4056
4077
  paneFocusTarget = "off";
4057
4078
  applyPaneFocusClasses();
4079
+ persistPaneFocusUrlState();
4058
4080
  setStatus("Focus mode off.");
4059
4081
  return;
4060
4082
  }
@@ -4066,6 +4088,7 @@
4066
4088
  if (paneFocusTarget === "off") return false;
4067
4089
  paneFocusTarget = "off";
4068
4090
  applyPaneFocusClasses();
4091
+ persistPaneFocusUrlState();
4069
4092
  setStatus("Focus mode off.");
4070
4093
  return true;
4071
4094
  }
@@ -5708,7 +5731,7 @@
5708
5731
  return source.replace(/<head\b[^>]*>/i, (match) => match + "\n" + headMarkup + "\n");
5709
5732
  }
5710
5733
  if (/<body\b[^>]*>/i.test(source)) {
5711
- return source.replace(/<body\b/i, "<head>\n" + headMarkup + "\n</head>\n<body");
5734
+ return source.replace(/<body\b/i, () => "<head>\n" + headMarkup + "\n</head>\n<body");
5712
5735
  }
5713
5736
  if (/<html\b[^>]*>/i.test(source)) {
5714
5737
  return source.replace(/<html\b[^>]*>/i, (match) => match + "\n<head>\n" + headMarkup + "\n</head>\n");
@@ -6045,8 +6068,7 @@
6045
6068
  return;
6046
6069
  }
6047
6070
  if (kind === "text" || kind === "office") {
6048
- const pendingWindow = window.open("", "_blank");
6049
- void openPreviewDocumentInNewEditor(context.href, pendingWindow, context).catch((error) => {
6071
+ void openPreviewDocumentInNewEditor(context.href, context).catch((error) => {
6050
6072
  setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
6051
6073
  });
6052
6074
  return;
@@ -8509,19 +8531,14 @@
8509
8531
  async function exportRightPanePdf(options) {
8510
8532
  const exportOptions = options && typeof options === "object" ? options : {};
8511
8533
  const openTarget = exportOptions.openTarget === "studio" ? "studio" : "default";
8512
- let studioPopup = null;
8513
- if (openTarget === "studio") {
8514
- studioPopup = openExportStudioPlaceholderWindow("PDF");
8515
- }
8534
+ let studioLaunch = null;
8516
8535
  if (uiBusy || previewExportInProgress) {
8517
- closeExportStudioWindow(studioPopup);
8518
8536
  setStatus("Studio is busy.", "warning");
8519
8537
  return;
8520
8538
  }
8521
8539
 
8522
8540
  const token = getToken();
8523
8541
  if (!token) {
8524
- closeExportStudioWindow(studioPopup);
8525
8542
  setStatus("Missing Studio token in URL. Re-run /studio.", "error");
8526
8543
  return;
8527
8544
  }
@@ -8529,20 +8546,17 @@
8529
8546
  const exportingReplJournal = rightView === "repl";
8530
8547
  const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
8531
8548
  if (!rightPaneShowsPreview && !exportingReplJournal) {
8532
- closeExportStudioWindow(studioPopup);
8533
8549
  setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export PDF.", "warning");
8534
8550
  return;
8535
8551
  }
8536
8552
  const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
8537
8553
  if (exportingReplJournal && !replJournalExportEntries.length) {
8538
- closeExportStudioWindow(studioPopup);
8539
8554
  setStatus("No Studio REPL record entries to export for this session yet.", "warning");
8540
8555
  return;
8541
8556
  }
8542
8557
 
8543
8558
  const htmlArtifactSource = exportingReplJournal ? "" : getRightPaneHtmlArtifactSource();
8544
8559
  if (htmlArtifactSource) {
8545
- closeExportStudioWindow(studioPopup);
8546
8560
  setStatus("PDF export does not support interactive HTML previews yet. Export as HTML or use the browser print dialog inside the preview.", "warning");
8547
8561
  return;
8548
8562
  }
@@ -8553,7 +8567,6 @@
8553
8567
  ? prepareEditorTextForPdfExport(sourceTextEl.value)
8554
8568
  : prepareEditorTextForPreview(latestResponseMarkdown));
8555
8569
  if (!markdown || !markdown.trim()) {
8556
- closeExportStudioWindow(studioPopup);
8557
8570
  setStatus("Nothing to export yet.", "warning");
8558
8571
  return;
8559
8572
  }
@@ -8574,6 +8587,14 @@
8574
8587
  filenameHint = stem + ".studio.pdf";
8575
8588
  }
8576
8589
 
8590
+ if (openTarget === "studio") {
8591
+ try {
8592
+ studioLaunch = openPendingStudioTab("export");
8593
+ } catch (error) {
8594
+ setStatus((error && error.message) ? error.message : "Could not prepare a Studio export tab.", "error");
8595
+ return;
8596
+ }
8597
+ }
8577
8598
  previewExportInProgress = true;
8578
8599
  updateResultActionButtons();
8579
8600
  setStatus(openTarget === "studio" ? "Exporting PDF for Studio…" : "Exporting PDF…", "warning");
@@ -8631,30 +8652,30 @@
8631
8652
  }
8632
8653
 
8633
8654
  if (openTarget === "studio") {
8634
- const targetUrl = typeof payload.relativeUrl === "string" && payload.relativeUrl
8635
- ? new URL(payload.relativeUrl, window.location.href).href
8636
- : (typeof payload.url === "string" ? payload.url : "");
8637
- const openedStudio = navigateExportStudioWindow(studioPopup, targetUrl);
8638
- if (!openedStudio) {
8639
- closeExportStudioWindow(studioPopup);
8655
+ const targetUrl = typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
8656
+ const openedStudio = Boolean(targetUrl);
8657
+ if (openedStudio) {
8658
+ navigatePendingStudioTab(studioLaunch, targetUrl);
8659
+ } else {
8660
+ failPendingStudioTab(studioLaunch, "Studio did not return a preview-tab URL for this PDF export.");
8640
8661
  const viewerUrl = getStudioPdfViewerUrlForExportPayload(payload);
8641
8662
  if (viewerUrl) openStudioPdfFocusViewer(viewerUrl, downloadName);
8642
8663
  }
8643
8664
  if (writeError) {
8644
8665
  setStatus(openedStudio
8645
- ? "Opened exported PDF in a Studio preview tab, but could not write project file: " + writeError
8646
- : "Exported PDF, but could not open a Studio preview tab and could not write project file: " + writeError,
8666
+ ? "Opening exported PDF in a Studio preview tab, but could not write project file: " + writeError
8667
+ : "Exported PDF, but Studio did not return a preview-tab URL and could not write the project file: " + writeError,
8647
8668
  "warning");
8648
8669
  } else if (exportWarning) {
8649
8670
  setStatus(openedStudio
8650
- ? "Opened exported PDF in a Studio preview tab with warning: " + exportWarning
8651
- : "Exported PDF, but could not open a Studio preview tab. Warning: " + exportWarning,
8671
+ ? "Opening exported PDF in a Studio preview tab with warning: " + exportWarning
8672
+ : "Exported PDF, but Studio did not return a preview-tab URL. Warning: " + exportWarning,
8652
8673
  "warning");
8653
8674
  } else {
8654
8675
  setStatus(openedStudio
8655
- ? "Opened exported PDF in a Studio preview tab: " + (exportPath || downloadName)
8656
- : "Exported PDF, but could not open a Studio preview tab" + (targetUrl ? ": " + targetUrl : "."),
8657
- openedStudio ? "success" : "warning");
8676
+ ? "Opening exported PDF in a Studio preview tab: " + (exportPath || downloadName)
8677
+ : "Exported PDF, but Studio did not return a preview-tab URL.",
8678
+ openedStudio ? undefined : "warning");
8658
8679
  }
8659
8680
  return;
8660
8681
  }
@@ -8694,6 +8715,9 @@
8694
8715
  return;
8695
8716
  }
8696
8717
 
8718
+ if (openTarget === "studio") {
8719
+ failPendingStudioTab(studioLaunch, "Studio returned a download instead of a PDF preview tab.");
8720
+ }
8697
8721
  const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
8698
8722
  const blob = await response.blob();
8699
8723
  const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
@@ -8720,7 +8744,7 @@
8720
8744
  setStatus("Exported PDF: " + downloadName, "success");
8721
8745
  }
8722
8746
  } catch (error) {
8723
- closeExportStudioWindow(studioPopup);
8747
+ failPendingStudioTab(studioLaunch, "Studio could not prepare this PDF export tab. Return to the originating Studio page for details.");
8724
8748
  const detail = error && error.message ? error.message : String(error || "unknown error");
8725
8749
  setStatus("PDF export failed: " + detail, "error");
8726
8750
  } finally {
@@ -8729,58 +8753,17 @@
8729
8753
  }
8730
8754
  }
8731
8755
 
8732
- function openExportStudioPlaceholderWindow(formatLabel) {
8733
- const label = String(formatLabel || "preview").trim() || "preview";
8734
- let popup = null;
8735
- try {
8736
- popup = window.open("", "_blank");
8737
- if (popup && popup.document && popup.document.body) {
8738
- popup.document.title = "Opening " + label + " in Studio…";
8739
- popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Exporting " + escapeHtml(label) + " and opening it in Studio…</p>";
8740
- }
8741
- } catch {
8742
- popup = null;
8743
- }
8744
- return popup;
8745
- }
8746
-
8747
- function navigateExportStudioWindow(popup, targetUrl) {
8748
- if (!targetUrl) return false;
8749
- if (popup && !popup.closed) {
8750
- try {
8751
- popup.opener = null;
8752
- popup.location.href = targetUrl;
8753
- return true;
8754
- } catch {}
8755
- }
8756
- try {
8757
- return Boolean(window.open(targetUrl, "_blank", "noopener"));
8758
- } catch {
8759
- return false;
8760
- }
8761
- }
8762
-
8763
- function closeExportStudioWindow(popup) {
8764
- if (!popup || popup.closed) return;
8765
- try { popup.close(); } catch {}
8766
- }
8767
-
8768
8756
  async function exportRightPaneHtml(options) {
8769
8757
  const exportOptions = options && typeof options === "object" ? options : {};
8770
8758
  const openTarget = exportOptions.openTarget === "studio" ? "studio" : "browser";
8771
- let studioPopup = null;
8772
- if (openTarget === "studio") {
8773
- studioPopup = openExportStudioPlaceholderWindow("HTML");
8774
- }
8759
+ let studioLaunch = null;
8775
8760
  if (uiBusy || previewExportInProgress) {
8776
- closeExportStudioWindow(studioPopup);
8777
8761
  setStatus("Studio is busy.", "warning");
8778
8762
  return;
8779
8763
  }
8780
8764
 
8781
8765
  const token = getToken();
8782
8766
  if (!token) {
8783
- closeExportStudioWindow(studioPopup);
8784
8767
  setStatus("Missing Studio token in URL. Re-run /studio.", "error");
8785
8768
  return;
8786
8769
  }
@@ -8788,13 +8771,11 @@
8788
8771
  const exportingReplJournal = rightView === "repl";
8789
8772
  const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
8790
8773
  if (!rightPaneShowsPreview && !exportingReplJournal) {
8791
- closeExportStudioWindow(studioPopup);
8792
8774
  setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export HTML.", "warning");
8793
8775
  return;
8794
8776
  }
8795
8777
  const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
8796
8778
  if (exportingReplJournal && !replJournalExportEntries.length) {
8797
- closeExportStudioWindow(studioPopup);
8798
8779
  setStatus("No Studio REPL record entries to export for this session yet.", "warning");
8799
8780
  return;
8800
8781
  }
@@ -8804,7 +8785,6 @@
8804
8785
  ? prepareEditorTextForHtmlExport(sourceTextEl.value)
8805
8786
  : prepareEditorTextForPreview(latestResponseMarkdown)));
8806
8787
  if (!markdown || !markdown.trim()) {
8807
- closeExportStudioWindow(studioPopup);
8808
8788
  setStatus("Nothing to export yet.", "warning");
8809
8789
  return;
8810
8790
  }
@@ -8827,6 +8807,14 @@
8827
8807
  titleHint = stem + " preview";
8828
8808
  }
8829
8809
 
8810
+ if (openTarget === "studio") {
8811
+ try {
8812
+ studioLaunch = openPendingStudioTab("export");
8813
+ } catch (error) {
8814
+ setStatus((error && error.message) ? error.message : "Could not prepare a Studio export tab.", "error");
8815
+ return;
8816
+ }
8817
+ }
8830
8818
  previewExportInProgress = true;
8831
8819
  updateResultActionButtons();
8832
8820
  setStatus(openTarget === "studio" ? "Exporting HTML for Studio…" : "Exporting HTML…", "warning");
@@ -8885,26 +8873,28 @@
8885
8873
  }
8886
8874
 
8887
8875
  if (openTarget === "studio") {
8888
- const targetUrl = typeof payload.relativeUrl === "string" && payload.relativeUrl
8889
- ? new URL(payload.relativeUrl, window.location.href).href
8890
- : (typeof payload.url === "string" ? payload.url : "");
8891
- const openedStudio = navigateExportStudioWindow(studioPopup, targetUrl);
8892
- if (!openedStudio) closeExportStudioWindow(studioPopup);
8876
+ const targetUrl = typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
8877
+ const openedStudio = Boolean(targetUrl);
8878
+ if (openedStudio) {
8879
+ navigatePendingStudioTab(studioLaunch, targetUrl);
8880
+ } else {
8881
+ failPendingStudioTab(studioLaunch, "Studio did not return an editor URL for this HTML export.");
8882
+ }
8893
8883
  if (writeError) {
8894
8884
  setStatus(openedStudio
8895
- ? "Opened exported HTML in Studio as an unsaved copy; could not write project file: " + writeError
8896
- : "Exported HTML for Studio, but the popup was blocked and the project file could not be written: " + writeError,
8885
+ ? "Opening exported HTML in Studio as an unsaved copy; could not write project file: " + writeError
8886
+ : "Exported HTML for Studio, but Studio did not return an editor URL and the project file could not be written: " + writeError,
8897
8887
  "warning");
8898
8888
  } else if (exportWarning) {
8899
8889
  setStatus(openedStudio
8900
- ? "Opened exported HTML in Studio with warning: " + exportWarning
8901
- : "Exported HTML for Studio, but the popup was blocked. Warning: " + exportWarning,
8890
+ ? "Opening exported HTML in Studio with warning: " + exportWarning
8891
+ : "Exported HTML for Studio, but Studio did not return an editor URL. Warning: " + exportWarning,
8902
8892
  "warning");
8903
8893
  } else {
8904
8894
  setStatus(openedStudio
8905
- ? "Opened exported HTML in Studio: " + (exportPath || downloadName)
8906
- : (targetUrl ? "Exported HTML for Studio: " + targetUrl : "Exported HTML, but Studio did not receive an editor URL."),
8907
- openedStudio ? "success" : "warning");
8895
+ ? "Opening exported HTML in Studio: " + (exportPath || downloadName)
8896
+ : "Exported HTML, but Studio did not receive an editor URL.",
8897
+ openedStudio ? undefined : "warning");
8908
8898
  }
8909
8899
  return;
8910
8900
  }
@@ -8944,7 +8934,7 @@
8944
8934
  return;
8945
8935
  }
8946
8936
 
8947
- closeExportStudioWindow(studioPopup);
8937
+ failPendingStudioTab(studioLaunch, "Studio returned a download instead of an HTML editor tab.");
8948
8938
  const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
8949
8939
  const blob = await response.blob();
8950
8940
  const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
@@ -8971,7 +8961,7 @@
8971
8961
  setStatus("Exported HTML: " + downloadName, "success");
8972
8962
  }
8973
8963
  } catch (error) {
8974
- closeExportStudioWindow(studioPopup);
8964
+ failPendingStudioTab(studioLaunch, "Studio could not prepare this HTML export tab. Return to the originating Studio page for details.");
8975
8965
  const detail = error && error.message ? error.message : String(error || "unknown error");
8976
8966
  setStatus("HTML export failed: " + detail, "error");
8977
8967
  } finally {
@@ -10349,11 +10339,19 @@
10349
10339
  return;
10350
10340
  }
10351
10341
  if (action === "open-new") {
10352
- await openPreviewDocumentInNewEditor(path, null, getFileBrowserLocalLinkContext());
10342
+ if (kind === "text" && isLikelyAbsoluteStudioPath(path)) {
10343
+ openFileBackedStudioEditorTab(path, {
10344
+ label: basenameForStudioPath(path),
10345
+ resourceDir: fileBrowserState.rootDir || getCurrentResourceDirValue() || dirnameForDisplayPath(path),
10346
+ });
10347
+ setStatus("Opening file-backed document in a new editor.");
10348
+ return;
10349
+ }
10350
+ await openPreviewDocumentInNewEditor(path, getFileBrowserLocalLinkContext());
10353
10351
  return;
10354
10352
  }
10355
10353
  if (action === "open-preview-new") {
10356
- await openPreviewResourceInNewEditor(path, null, getFileBrowserLocalLinkContext());
10354
+ await openPreviewResourceInNewEditor(path, getFileBrowserLocalLinkContext());
10357
10355
  return;
10358
10356
  }
10359
10357
  if (action === "copy-path" || action === "copy-root" || action === "copy-current") {
@@ -10663,13 +10661,8 @@
10663
10661
  const hostname = parsed.hostname.toLowerCase();
10664
10662
  if (parsed.protocol !== "http:" && parsed.protocol !== "https:") throw new Error("Unsupported preview protocol.");
10665
10663
  if (hostname !== "127.0.0.1" && hostname !== "localhost" && hostname !== "::1") throw new Error("Preview URL is not loopback-only.");
10666
- const opened = window.open(parsed.href, "_blank");
10667
- if (!opened) {
10668
- setStatus("Browser blocked the Quarto preview tab. Allow pop-ups and try again.", "warning");
10669
- return false;
10670
- }
10671
- try { opened.opener = null; } catch {}
10672
- setStatus("Opening Quarto preview in a browser tab.", "success");
10664
+ openStudioTabDirect(parsed.href);
10665
+ setStatus("Opening Quarto preview in a browser tab.");
10673
10666
  return true;
10674
10667
  } catch (error) {
10675
10668
  setStatus("Could not open Quarto preview: " + ((error && error.message) ? error.message : String(error)), "error");
@@ -10679,12 +10672,8 @@
10679
10672
 
10680
10673
  function openStudioQuartoInstallationInstructions() {
10681
10674
  try {
10682
- const opened = window.open("https://quarto.org/docs/get-started/", "_blank");
10683
- if (!opened) {
10684
- setStatus("Browser blocked the Quarto installation page.", "warning");
10685
- return;
10686
- }
10687
- try { opened.opener = null; } catch {}
10675
+ openStudioTabDirect("https://quarto.org/docs/get-started/");
10676
+ setStatus("Opening Quarto installation instructions.");
10688
10677
  } catch (error) {
10689
10678
  setStatus("Could not open Quarto installation instructions.", "warning");
10690
10679
  }
@@ -12437,6 +12426,99 @@
12437
12426
  return pathname + "?" + params.toString();
12438
12427
  }
12439
12428
 
12429
+ function openStudioTabDirect(targetUrl) {
12430
+ navigationHelpers.openStudioTabDirect(window, targetUrl);
12431
+ debugTrace("studio_tab_launch", { event: "direct-open-requested" });
12432
+ }
12433
+
12434
+ function removeActiveStudioTabLaunch(launch) {
12435
+ if (launch) activeStudioTabLaunches.delete(launch);
12436
+ }
12437
+
12438
+ function openPendingStudioTab(kind) {
12439
+ const token = getToken();
12440
+ if (!token) throw new Error("Missing Studio token in URL.");
12441
+ const launch = navigationHelpers.createPendingStudioLaunch({
12442
+ window,
12443
+ token,
12444
+ kind,
12445
+ onEvent(event, detail) {
12446
+ debugTrace("studio_tab_launch", {
12447
+ event,
12448
+ launchId: detail && detail.launchId ? detail.launchId : null,
12449
+ kind: detail && detail.kind ? detail.kind : kind,
12450
+ state: detail && detail.state ? detail.state : null,
12451
+ terminalType: detail && detail.terminalType ? detail.terminalType : null,
12452
+ ok: detail && typeof detail.ok === "boolean" ? detail.ok : undefined,
12453
+ });
12454
+ },
12455
+ onReadyTimeout() {
12456
+ setStatus("No new tab has acknowledged yet. The operation is still running; Studio will not open a fallback tab.", "warning");
12457
+ },
12458
+ onDeliveryTimeout(controller) {
12459
+ removeActiveStudioTabLaunch(controller);
12460
+ setStatus("Studio could not confirm delivery to the new tab. No replacement tab was opened.", "warning");
12461
+ },
12462
+ onAccepted(result) {
12463
+ removeActiveStudioTabLaunch(result && result.controller);
12464
+ if (result && result.ok === false) {
12465
+ setStatus("The pending tab rejected the Studio navigation target.", "error");
12466
+ }
12467
+ },
12468
+ onOpenError() {
12469
+ setStatus("The browser rejected the new-tab request. Studio will not open a fallback tab.", "warning");
12470
+ },
12471
+ });
12472
+ if (!launch) {
12473
+ throw new Error("This browser could not prepare a Studio tab.");
12474
+ }
12475
+ activeStudioTabLaunches.add(launch);
12476
+ return launch;
12477
+ }
12478
+
12479
+ function navigatePendingStudioTab(launch, relativeUrl) {
12480
+ if (!launch || typeof launch.navigate !== "function") {
12481
+ throw new Error("Studio did not prepare a pending tab for this operation.");
12482
+ }
12483
+ const token = getToken();
12484
+ const normalized = navigationHelpers.normalizeStudioRelativeTarget(relativeUrl, window.location, token);
12485
+ return launch.navigate(normalized);
12486
+ }
12487
+
12488
+ function failPendingStudioTab(launch, message) {
12489
+ if (!launch) return false;
12490
+ return launch.fail(message || "Studio could not prepare this tab. Return to the originating Studio page for details.");
12491
+ }
12492
+
12493
+ function abandonAllStudioTabLaunches(message) {
12494
+ const launches = Array.from(activeStudioTabLaunches);
12495
+ activeStudioTabLaunches.clear();
12496
+ launches.forEach((launch) => {
12497
+ try { launch.abandon(message || "The originating Studio page was closed."); } catch {}
12498
+ });
12499
+ }
12500
+
12501
+ function buildFileBackedStudioEditorUrl(path, options) {
12502
+ const cleanPath = stripPreviewLocalLinkUrlSuffix(path || "").trim();
12503
+ if (!isLikelyAbsoluteStudioPath(cleanPath)) {
12504
+ throw new Error("Opening a file-backed Studio tab requires an absolute local path.");
12505
+ }
12506
+ const config = options && typeof options === "object" ? options : {};
12507
+ return buildAuthedStudioUrl("/", {
12508
+ mode: "editor-only",
12509
+ docSource: "file",
12510
+ docLabel: config.label || basenameForStudioPath(cleanPath),
12511
+ docPath: cleanPath,
12512
+ resourceDir: normalizeStudioResourceDirValue(config.resourceDir || "") || dirnameForDisplayPath(cleanPath),
12513
+ skipWorkspaceRestore: "1",
12514
+ });
12515
+ }
12516
+
12517
+ function openFileBackedStudioEditorTab(path, options) {
12518
+ const targetUrl = buildFileBackedStudioEditorUrl(path, options);
12519
+ openStudioTabDirect(targetUrl);
12520
+ }
12521
+
12440
12522
  function updateStudioDocumentUrlState(state) {
12441
12523
  try {
12442
12524
  const currentUrl = new URL(window.location.href);
@@ -12713,10 +12795,7 @@
12713
12795
  return true;
12714
12796
  }
12715
12797
 
12716
- async function openPreviewImageLink(href, title, contextOverride, pendingWindow) {
12717
- if (pendingWindow && !pendingWindow.closed) {
12718
- try { pendingWindow.close(); } catch {}
12719
- }
12798
+ async function openPreviewImageLink(href, title, contextOverride) {
12720
12799
  const payload = await fetchStudioJson("/html-preview-resource", {
12721
12800
  query: getPreviewLinkResourceQuery(href, contextOverride),
12722
12801
  });
@@ -12802,73 +12881,33 @@
12802
12881
  : (path ? ("Opened file-backed document in editor: " + label) : ("Opened linked file copy in editor: " + label)), "success");
12803
12882
  }
12804
12883
 
12805
- async function openPreviewDocumentInNewEditor(href, pendingWindow, contextOverride) {
12806
- if (!confirmPreviewOfficeConversion(href, "new")) {
12807
- if (pendingWindow && !pendingWindow.closed) {
12808
- try { pendingWindow.close(); } catch {}
12809
- }
12810
- return;
12811
- }
12812
- const popup = pendingWindow || window.open("", "_blank");
12813
- const openingLabel = getPreviewLocalLinkKind(href) === "office" ? "Opening converted document…" : "Opening file tab…";
12814
- try {
12815
- if (popup && popup.document && popup.document.body) {
12816
- popup.document.title = openingLabel;
12817
- popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">" + escapeHtml(openingLabel) + "</p>";
12818
- }
12819
- } catch {}
12884
+ async function openPreviewDocumentInNewEditor(href, contextOverride) {
12885
+ if (!confirmPreviewOfficeConversion(href, "new")) return;
12886
+ let launch = null;
12820
12887
  try {
12888
+ launch = openPendingStudioTab("document");
12821
12889
  const payload = await fetchPreviewLocalLink("editor-url", href, contextOverride);
12822
- const targetUrl = payload && typeof payload.relativeUrl === "string"
12823
- ? new URL(payload.relativeUrl, window.location.href).href
12824
- : (payload && typeof payload.url === "string" ? payload.url : "");
12825
- if (!targetUrl) throw new Error("Studio did not return an editor URL.");
12826
- if (popup && !popup.closed) {
12827
- try {
12828
- popup.opener = null;
12829
- popup.location.href = targetUrl;
12830
- setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
12831
- return;
12832
- } catch {}
12833
- }
12834
- window.open(targetUrl, "_blank", "noopener");
12835
- setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
12890
+ const relativeUrl = payload && typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
12891
+ if (!relativeUrl) throw new Error("Studio did not return an editor URL.");
12892
+ navigatePendingStudioTab(launch, relativeUrl);
12893
+ setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.");
12836
12894
  } catch (error) {
12837
- if (popup && !popup.closed) {
12838
- try { popup.close(); } catch {}
12839
- }
12895
+ failPendingStudioTab(launch, "Studio could not prepare this document tab. Return to the originating Studio page for details.");
12840
12896
  throw error;
12841
12897
  }
12842
12898
  }
12843
12899
 
12844
- async function openPreviewResourceInNewEditor(href, pendingWindow, contextOverride) {
12845
- const popup = pendingWindow || window.open("", "_blank");
12846
- try {
12847
- if (popup && popup.document && popup.document.body) {
12848
- popup.document.title = "Opening preview…";
12849
- popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening preview…</p>";
12850
- }
12851
- } catch {}
12900
+ async function openPreviewResourceInNewEditor(href, contextOverride) {
12901
+ let launch = null;
12852
12902
  try {
12903
+ launch = openPendingStudioTab("preview");
12853
12904
  const payload = await fetchPreviewLocalLink("preview-url", href, contextOverride);
12854
- const targetUrl = payload && typeof payload.relativeUrl === "string"
12855
- ? new URL(payload.relativeUrl, window.location.href).href
12856
- : (payload && typeof payload.url === "string" ? payload.url : "");
12857
- if (!targetUrl) throw new Error("Studio did not return a preview URL.");
12858
- if (popup && !popup.closed) {
12859
- try {
12860
- popup.opener = null;
12861
- popup.location.href = targetUrl;
12862
- setStatus("Opening preview in a new Studio tab.", "success");
12863
- return;
12864
- } catch {}
12865
- }
12866
- window.open(targetUrl, "_blank", "noopener");
12867
- setStatus("Opening preview in a new Studio tab.", "success");
12905
+ const relativeUrl = payload && typeof payload.relativeUrl === "string" ? payload.relativeUrl : "";
12906
+ if (!relativeUrl) throw new Error("Studio did not return a preview URL.");
12907
+ navigatePendingStudioTab(launch, relativeUrl);
12908
+ setStatus("Opening preview in a new Studio tab.");
12868
12909
  } catch (error) {
12869
- if (popup && !popup.closed) {
12870
- try { popup.close(); } catch {}
12871
- }
12910
+ failPendingStudioTab(launch, "Studio could not prepare this preview tab. Return to the originating Studio page for details.");
12872
12911
  throw error;
12873
12912
  }
12874
12913
  }
@@ -12900,11 +12939,11 @@
12900
12939
  return;
12901
12940
  }
12902
12941
  if (action === "open-new") {
12903
- await openPreviewDocumentInNewEditor(href, null, context);
12942
+ await openPreviewDocumentInNewEditor(href, context);
12904
12943
  return;
12905
12944
  }
12906
12945
  if (action === "open-preview-new") {
12907
- await openPreviewResourceInNewEditor(href, null, context);
12946
+ await openPreviewResourceInNewEditor(href, context);
12908
12947
  return;
12909
12948
  }
12910
12949
  if (action === "open-here") {
@@ -12947,8 +12986,7 @@
12947
12986
  return;
12948
12987
  }
12949
12988
  if (kind === "text" || kind === "office") {
12950
- const pendingWindow = kind === "office" ? null : window.open("", "_blank");
12951
- void openPreviewDocumentInNewEditor(href, pendingWindow).catch((error) => {
12989
+ void openPreviewDocumentInNewEditor(href).catch((error) => {
12952
12990
  setStatus((error && error.message) ? error.message : String(error || "Could not open linked file."), "warning");
12953
12991
  });
12954
12992
  return;
@@ -19188,6 +19226,7 @@
19188
19226
  const canQueueSteering = studioRunChainActive && !critiqueIsStop;
19189
19227
  const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
19190
19228
  const showReplSend = rightView === "repl";
19229
+ const replSendShortcut = getStudioShortcutDescription("repl-send");
19191
19230
 
19192
19231
  if (isEditorOnlyMode) {
19193
19232
  if (sendRunBtn) {
@@ -19209,8 +19248,8 @@
19209
19248
  sendReplBtn.textContent = showReplSend ? withStudioShortcutLabel(replSendMode === "literate" ? "Send selection/chunks" : "Send to REPL", "repl-send") : "Send to REPL";
19210
19249
  sendReplBtn.title = hasReplSession
19211
19250
  ? (replSendMode === "literate"
19212
- ? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: Cmd/Ctrl+Shift+Enter."
19213
- : "Raw send: selection, or full editor if no selection. Shortcut: Cmd/Ctrl+Shift+Enter.")
19251
+ ? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: " + replSendShortcut + "."
19252
+ : "Raw send: selection, or full editor if no selection. Shortcut: " + replSendShortcut + ".")
19214
19253
  : "Start or select a REPL session in the right pane first.";
19215
19254
  const replActionLine = sendReplBtn.closest(".repl-action-line");
19216
19255
  if (replActionLine instanceof HTMLElement) replActionLine.hidden = !showReplSend;
@@ -19270,8 +19309,8 @@
19270
19309
  sendReplBtn.textContent = showReplSend ? withStudioShortcutLabel(replSendMode === "literate" ? "Send selection/chunks" : "Send to REPL", "repl-send") : "Send to REPL";
19271
19310
  sendReplBtn.title = hasReplSession
19272
19311
  ? (replSendMode === "literate"
19273
- ? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: Cmd/Ctrl+Shift+Enter."
19274
- : "Raw send: selection, or full editor if no selection. Shortcut: Cmd/Ctrl+Shift+Enter.")
19312
+ ? "Literate send: selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk. Shortcut: " + replSendShortcut + "."
19313
+ : "Raw send: selection, or full editor if no selection. Shortcut: " + replSendShortcut + ".")
19275
19314
  : "Start or select a REPL session in the right pane first.";
19276
19315
  const replActionLine = sendReplBtn.closest(".repl-action-line");
19277
19316
  if (replActionLine instanceof HTMLElement) replActionLine.hidden = !showReplSend;
@@ -20109,24 +20148,31 @@
20109
20148
 
20110
20149
  if (message.type === "editor_only_ready") {
20111
20150
  const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
20112
- if (responseRequestId && pendingRequestId === responseRequestId) {
20113
- pendingRequestId = null;
20114
- pendingKind = null;
20115
- clearArmedTitleAttention(responseRequestId);
20116
- stickyStudioKind = null;
20151
+ if (!responseRequestId || !pendingCompanionLaunches.has(responseRequestId)) return;
20152
+ if (pendingRequestId !== responseRequestId || pendingKind !== "open_editor_only") {
20153
+ failPendingCompanionLaunch(responseRequestId, "This companion editor request was superseded.");
20154
+ return;
20117
20155
  }
20156
+ pendingRequestId = null;
20157
+ pendingKind = null;
20158
+ clearArmedTitleAttention(responseRequestId);
20159
+ stickyStudioKind = null;
20118
20160
  setBusy(false);
20119
20161
  setWsState("Ready");
20120
- const targetUrl = resolveCompanionEditorTargetUrl(message);
20121
- const opened = navigatePendingCompanionWindow(responseRequestId, targetUrl);
20162
+ const relativeUrl = resolveCompanionEditorRelativeUrl(message);
20163
+ let queued = false;
20164
+ try {
20165
+ queued = navigatePendingCompanionLaunch(responseRequestId, relativeUrl);
20166
+ } catch (error) {
20167
+ setStatus((error && error.message) ? error.message : "Studio rejected the companion editor URL.", "error");
20168
+ return;
20169
+ }
20122
20170
  const readyMessage = typeof message.message === "string" && message.message.trim()
20123
20171
  ? message.message.trim()
20124
- : "Opened editor tab with a detached copy of the current editor text.";
20172
+ : "Opening an editor tab with a detached copy of the current editor text.";
20125
20173
  setStatus(
20126
- opened
20127
- ? readyMessage
20128
- : (targetUrl ? "Editor tab ready: " + targetUrl : "Editor tab is ready, but Studio did not receive a URL."),
20129
- opened ? "success" : "warning",
20174
+ queued ? readyMessage : "Editor tab is ready, but Studio did not receive a valid relative URL.",
20175
+ queued ? undefined : "warning",
20130
20176
  );
20131
20177
  return;
20132
20178
  }
@@ -20203,7 +20249,7 @@
20203
20249
 
20204
20250
  if (message.type === "busy") {
20205
20251
  if (typeof message.requestId === "string") {
20206
- closePendingCompanionWindow(message.requestId);
20252
+ failPendingCompanionLaunch(message.requestId, "Studio could not start the companion editor because another request was busy.");
20207
20253
  }
20208
20254
  if (message.requestId && pendingRequestId === message.requestId) {
20209
20255
  if (pendingKind === "compact") {
@@ -20224,7 +20270,7 @@
20224
20270
 
20225
20271
  if (message.type === "error") {
20226
20272
  if (typeof message.requestId === "string") {
20227
- closePendingCompanionWindow(message.requestId);
20273
+ failPendingCompanionLaunch(message.requestId, "Studio could not prepare the companion editor. Return to the originating Studio page for details.");
20228
20274
  }
20229
20275
  if (message.requestId && pendingRequestId === message.requestId) {
20230
20276
  if (pendingKind === "compact") {
@@ -20359,6 +20405,7 @@
20359
20405
  quartoPreviewCheckRequestId = null;
20360
20406
  quartoPreviewCheckSourcePath = "";
20361
20407
  quartoPreviewActionRequestId = null;
20408
+ failAllPendingCompanionLaunches("The originating Studio connection was lost before the companion editor was ready.");
20362
20409
  if (rightView === "editor-quarto-preview") renderQuartoPreviewView();
20363
20410
  setBusy(true);
20364
20411
 
@@ -20449,73 +20496,70 @@
20449
20496
  return requestId;
20450
20497
  }
20451
20498
 
20452
- function openPendingCompanionWindow(requestId) {
20499
+ function openPendingCompanionLaunch(requestId) {
20453
20500
  if (!requestId) return null;
20454
- let companionWindow = null;
20455
- try {
20456
- companionWindow = window.open("", "_blank");
20457
- if (companionWindow && companionWindow.document && companionWindow.document.body) {
20458
- companionWindow.document.title = "Opening editor tab…";
20459
- companionWindow.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening editor tab…</p>";
20460
- }
20461
- } catch {
20462
- companionWindow = null;
20463
- }
20464
- if (companionWindow) {
20465
- pendingCompanionWindows.set(requestId, companionWindow);
20466
- }
20467
- return companionWindow;
20501
+ const launch = openPendingStudioTab("document");
20502
+ pendingCompanionLaunches.set(requestId, launch);
20503
+ return launch;
20468
20504
  }
20469
20505
 
20470
- function takePendingCompanionWindow(requestId) {
20471
- if (!requestId || !pendingCompanionWindows.has(requestId)) return null;
20472
- const companionWindow = pendingCompanionWindows.get(requestId);
20473
- pendingCompanionWindows.delete(requestId);
20474
- return companionWindow || null;
20506
+ function takePendingCompanionLaunch(requestId) {
20507
+ if (!requestId || !pendingCompanionLaunches.has(requestId)) return { found: false, launch: null };
20508
+ const launch = pendingCompanionLaunches.get(requestId) || null;
20509
+ pendingCompanionLaunches.delete(requestId);
20510
+ return { found: true, launch };
20475
20511
  }
20476
20512
 
20477
- function closePendingCompanionWindow(requestId) {
20478
- const companionWindow = takePendingCompanionWindow(requestId);
20479
- if (!companionWindow || companionWindow.closed) return;
20480
- try {
20481
- companionWindow.close();
20482
- } catch {}
20513
+ function failPendingCompanionLaunch(requestId, message) {
20514
+ const pending = takePendingCompanionLaunch(requestId);
20515
+ if (!pending.found) return false;
20516
+ failPendingStudioTab(
20517
+ pending.launch,
20518
+ message || "Studio could not prepare this companion editor. Return to the originating Studio page for details.",
20519
+ );
20520
+ return true;
20483
20521
  }
20484
20522
 
20485
- function resolveCompanionEditorTargetUrl(message) {
20486
- const relativeUrl = message && typeof message.relativeUrl === "string" ? message.relativeUrl : "";
20487
- if (relativeUrl) {
20488
- try {
20489
- return new URL(relativeUrl, window.location.href).href;
20490
- } catch {}
20491
- }
20492
- return message && typeof message.url === "string" ? message.url : "";
20523
+ function failAllPendingCompanionLaunches(message) {
20524
+ const requestIds = Array.from(pendingCompanionLaunches.keys());
20525
+ requestIds.forEach((requestId) => failPendingCompanionLaunch(requestId, message));
20493
20526
  }
20494
20527
 
20495
- function navigatePendingCompanionWindow(requestId, targetUrl) {
20496
- if (!targetUrl) {
20497
- closePendingCompanionWindow(requestId);
20528
+ function resolveCompanionEditorRelativeUrl(message) {
20529
+ return message && typeof message.relativeUrl === "string" ? message.relativeUrl : "";
20530
+ }
20531
+
20532
+ function navigatePendingCompanionLaunch(requestId, relativeUrl) {
20533
+ const pending = takePendingCompanionLaunch(requestId);
20534
+ if (!pending.found || !relativeUrl) {
20535
+ if (pending.found) {
20536
+ failPendingStudioTab(pending.launch, "Studio did not return a companion editor target.");
20537
+ }
20498
20538
  return false;
20499
20539
  }
20500
- const companionWindow = takePendingCompanionWindow(requestId);
20501
- if (companionWindow && !companionWindow.closed) {
20502
- try {
20503
- companionWindow.opener = null;
20504
- companionWindow.location.href = targetUrl;
20505
- return true;
20506
- } catch {}
20507
- }
20508
20540
  try {
20509
- return Boolean(window.open(targetUrl, "_blank", "noopener"));
20510
- } catch {
20511
- return false;
20541
+ navigatePendingStudioTab(pending.launch, relativeUrl);
20542
+ return true;
20543
+ } catch (error) {
20544
+ failPendingStudioTab(pending.launch, "Studio rejected the companion editor target.");
20545
+ throw error;
20512
20546
  }
20513
20547
  }
20514
20548
 
20515
20549
  function requestOpenEditorOnlyDocument(content, options) {
20516
20550
  const requestId = beginUiAction("open_editor_only");
20517
20551
  if (!requestId) return false;
20518
- openPendingCompanionWindow(requestId);
20552
+ try {
20553
+ openPendingCompanionLaunch(requestId);
20554
+ } catch (error) {
20555
+ pendingRequestId = null;
20556
+ pendingKind = null;
20557
+ stickyStudioKind = null;
20558
+ setBusy(false);
20559
+ setWsState("Ready");
20560
+ setStatus((error && error.message) ? error.message : "Could not prepare a companion editor tab.", "error");
20561
+ return false;
20562
+ }
20519
20563
  const config = options && typeof options === "object" ? options : {};
20520
20564
  const sent = sendMessage({
20521
20565
  type: "open_editor_only_request",
@@ -20526,9 +20570,10 @@
20526
20570
  resourceDir: config.resourceDir || undefined,
20527
20571
  });
20528
20572
  if (!sent) {
20529
- closePendingCompanionWindow(requestId);
20573
+ failPendingCompanionLaunch(requestId, "Studio could not send the companion editor request.");
20530
20574
  pendingRequestId = null;
20531
20575
  pendingKind = null;
20576
+ stickyStudioKind = null;
20532
20577
  setBusy(false);
20533
20578
  }
20534
20579
  return sent;
@@ -20660,6 +20705,10 @@
20660
20705
 
20661
20706
  updatePaneFocusButtons();
20662
20707
  window.addEventListener("keydown", handlePaneShortcut);
20708
+ window.addEventListener("pagehide", () => {
20709
+ pendingCompanionLaunches.clear();
20710
+ abandonAllStudioTabLaunches("The originating Studio page was closed before this tab finished opening.");
20711
+ });
20663
20712
  window.addEventListener("beforeunload", () => {
20664
20713
  stopFooterSpinner();
20665
20714
  flushWorkspacePersistence();
@@ -21818,7 +21867,9 @@
21818
21867
  if (initialSourceState.path) markFileBackedBaseline(sourceTextEl.value);
21819
21868
  refreshResponseUi();
21820
21869
  updateAnnotatedReplyHeaderButton();
21821
- setActivePane("left");
21870
+ setActivePane(initialPaneFocusTarget === "off" ? "left" : initialPaneFocusTarget);
21871
+ applyPaneFocusClasses();
21872
+ persistPaneFocusUrlState();
21822
21873
 
21823
21874
  const storedEditorHighlightEnabled = readStoredEditorHighlightEnabled();
21824
21875
  const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value !== "off");