pi-studio 0.9.20 → 0.9.22

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.
@@ -92,7 +92,9 @@
92
92
  const copyResponseBtn = document.getElementById("copyResponseBtn");
93
93
  const exportPreviewControlsEl = document.getElementById("exportPreviewControls");
94
94
  const exportPreviewMenuEl = document.getElementById("exportPreviewMenu");
95
+ const exportPreviewPdfStudioBtn = document.getElementById("exportPreviewPdfStudioBtn");
95
96
  const exportPreviewPdfBtn = document.getElementById("exportPreviewPdfBtn");
97
+ const exportPreviewHtmlStudioBtn = document.getElementById("exportPreviewHtmlStudioBtn");
96
98
  const exportPreviewHtmlBtn = document.getElementById("exportPreviewHtmlBtn");
97
99
  const exportPdfBtn = document.getElementById("exportPdfBtn");
98
100
  const historyPrevBtn = document.getElementById("historyPrevBtn");
@@ -142,6 +144,8 @@
142
144
  const scratchpadDialogEl = document.getElementById("scratchpadDialog");
143
145
  const scratchpadTextEl = document.getElementById("scratchpadText");
144
146
  const scratchpadMetaEl = document.getElementById("scratchpadMeta");
147
+ const scratchpadRecentBtn = document.getElementById("scratchpadRecentBtn");
148
+ const scratchpadRecentPanelEl = document.getElementById("scratchpadRecentPanel");
145
149
  const scratchpadInsertBtn = document.getElementById("scratchpadInsertBtn");
146
150
  const scratchpadCopyBtn = document.getElementById("scratchpadCopyBtn");
147
151
  const scratchpadClearBtn = document.getElementById("scratchpadClearBtn");
@@ -173,6 +177,7 @@
173
177
  const isSshStudioSession = Boolean(document.body && document.body.dataset && document.body.dataset.sshSession === "1");
174
178
 
175
179
  const initialQueryParams = new URLSearchParams(window.location.search || "");
180
+ const skipInitialWorkspaceRestore = initialQueryParams.get("skipWorkspaceRestore") === "1";
176
181
  const explicitDocumentIdentityFromUrl = initialQueryParams.has("docId")
177
182
  || initialQueryParams.has("docSource")
178
183
  || initialQueryParams.has("docLabel")
@@ -228,6 +233,10 @@
228
233
  let pendingKind = null;
229
234
  let stickyStudioKind = null;
230
235
  const pendingCompanionWindows = new Map();
236
+ let sourceOriginSummaryEl = null;
237
+ let sourceResetOriginBtn = null;
238
+ let sourceOpenCurrentFileTabBtn = null;
239
+ let sourceOpenCurrentTextCopyTabBtn = null;
231
240
  let initialDocumentApplied = false;
232
241
  function normalizeRightViewValue(nextView) {
233
242
  const raw = String(nextView || "").trim();
@@ -254,8 +263,8 @@
254
263
  option.disabled = isEditorOnlyMode && !editorOnlyAllowed.has(option.value);
255
264
  });
256
265
  rightViewSelect.title = isEditorOnlyMode
257
- ? "Editor-only views: editor preview, Files, or REPL. Shortcut: F7 when the right pane is active; F6 switches panes."
258
- : "Right pane view mode. Shortcut: F7 when the right pane is active; F6 switches panes.";
266
+ ? "Editor-only views: editor preview, Files, or REPL. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview."
267
+ : "Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview.";
259
268
  }
260
269
 
261
270
  function getInitialRightView(source) {
@@ -2044,6 +2053,9 @@
2044
2053
  let scratchpadReturnFocusEl = null;
2045
2054
  let scratchpadPersistTimer = null;
2046
2055
  let scratchpadLoadNonce = 0;
2056
+ let scratchpadRecentEntries = [];
2057
+ let scratchpadRecentVisible = false;
2058
+ let scratchpadRecentLoading = false;
2047
2059
  let reviewNotes = [];
2048
2060
  let reviewNotesReturnFocusEl = null;
2049
2061
  let reviewNotesPersistTimer = null;
@@ -2398,8 +2410,66 @@
2398
2410
  suggestCompletionOptionsBtn.hidden = false;
2399
2411
  if (completionContextSelect) completionContextSelect.hidden = true;
2400
2412
  contextMenu = makeStudioUiRefreshMenu(suggestCompletionOptionsBtn, "context", "studio-refresh-context-anchor");
2401
- if (sourceBadgeEl) appendStudioUiRefreshMenuSection(contextMenu.menu, "Document", [sourceBadgeEl]);
2413
+ sourceOriginSummaryEl = makeStudioUiRefreshElement("div", "source-badge source-origin-summary", "Origin: blank");
2414
+ sourceOriginSummaryEl.setAttribute("aria-label", "Current editor origin");
2415
+ sourceResetOriginBtn = makeStudioUiRefreshElement("button", "source-reset-origin-btn", "Reset origin");
2416
+ sourceResetOriginBtn.type = "button";
2417
+ sourceResetOriginBtn.title = "Reset the editor origin and keep the current text in a new draft.";
2418
+ sourceResetOriginBtn.addEventListener("click", (event) => {
2419
+ event.preventDefault();
2420
+ event.stopPropagation();
2421
+ closeStudioUiRefreshMenus();
2422
+ resetEditorOrigin();
2423
+ });
2424
+ sourceOpenCurrentFileTabBtn = makeStudioUiRefreshElement("button", "source-open-file-tab-btn", "Open current file in new editor tab");
2425
+ sourceOpenCurrentFileTabBtn.type = "button";
2426
+ sourceOpenCurrentFileTabBtn.title = "Open this file-backed document in a new refreshable editor-only Studio tab.";
2427
+ sourceOpenCurrentFileTabBtn.addEventListener("click", (event) => {
2428
+ event.preventDefault();
2429
+ event.stopPropagation();
2430
+ const path = sourceState && sourceState.path ? String(sourceState.path) : "";
2431
+ if (!path) {
2432
+ setStatus("Open current file in new editor tab is only available for file-backed documents.", "warning");
2433
+ return;
2434
+ }
2435
+ closeStudioUiRefreshMenus();
2436
+ const targetUrl = buildAuthedStudioUrl("/", {
2437
+ mode: "editor-only",
2438
+ docSource: "file",
2439
+ docLabel: sourceState && sourceState.label ? sourceState.label : basenameForStudioPath(path),
2440
+ docPath: path,
2441
+ resourceDir: getCurrentResourceDirValue() || dirnameForDisplayPath(path),
2442
+ skipWorkspaceRestore: "1",
2443
+ });
2444
+ try {
2445
+ window.open(targetUrl, "_blank", "noopener");
2446
+ setStatus("Opening current file in a new editor tab.", "success");
2447
+ } catch (error) {
2448
+ setStatus((error && error.message) ? error.message : String(error || "Could not open file tab."), "warning");
2449
+ }
2450
+ });
2451
+ sourceOpenCurrentTextCopyTabBtn = makeStudioUiRefreshElement("button", "source-open-text-copy-tab-btn", "Open current text as copy in new editor tab");
2452
+ sourceOpenCurrentTextCopyTabBtn.type = "button";
2453
+ sourceOpenCurrentTextCopyTabBtn.title = "Open a detached copy of the current editor text in a new editor-only Studio tab.";
2454
+ sourceOpenCurrentTextCopyTabBtn.addEventListener("click", (event) => {
2455
+ event.preventDefault();
2456
+ event.stopPropagation();
2457
+ const content = String(sourceTextEl.value || "");
2458
+ if (!content.trim()) {
2459
+ setStatus("Editor is empty. Use New editor tab for a blank editor.", "warning");
2460
+ return;
2461
+ }
2462
+ closeStudioUiRefreshMenus();
2463
+ requestOpenEditorOnlyDocument(content, {
2464
+ label: sourceState && sourceState.label ? sourceState.label : "current editor",
2465
+ path: sourceState && sourceState.path ? sourceState.path : undefined,
2466
+ resourceDir: getCurrentResourceDirValue() || undefined,
2467
+ });
2468
+ });
2469
+ if (sendEditorBtn) sendEditorBtn.textContent = "Send current text to Pi editor";
2470
+ appendStudioUiRefreshMenuSection(contextMenu.menu, "Document", [sourceOriginSummaryEl, sourceResetOriginBtn, sourceOpenCurrentFileTabBtn, sourceOpenCurrentTextCopyTabBtn]);
2402
2471
  appendStudioUiRefreshMenuSection(contextMenu.menu, "Working directory", [resourceDirBtn, resourceDirLabel, resourceDirInputWrap]);
2472
+ if (!isEditorOnlyMode && sendEditorBtn) appendStudioUiRefreshMenuSection(contextMenu.menu, "Pi editor", [sendEditorBtn]);
2403
2473
  const cursorContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor only");
2404
2474
  cursorContextBtn.type = "button";
2405
2475
  cursorContextBtn.setAttribute("data-completion-context-mode", "cursor");
@@ -2477,7 +2547,6 @@
2477
2547
  actionLineTwoEl.appendChild(copyDraftBtn);
2478
2548
  if (suggestCompletionBtn) actionLineTwoEl.appendChild(suggestCompletionBtn);
2479
2549
  if (openCompanionBtn) actionLineTwoEl.appendChild(openCompanionBtn);
2480
- if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
2481
2550
  const replActionLineEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line repl-action-line");
2482
2551
  replActionLineEl.hidden = true;
2483
2552
  if (sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
@@ -3206,13 +3275,26 @@
3206
3275
 
3207
3276
  function updateSourceBadge() {
3208
3277
  const label = sourceState && sourceState.label ? sourceState.label : "blank";
3209
- sourceBadgeEl.textContent = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label + (hasRefreshableFilePath() ? " · file" : "");
3278
+ const originText = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label + (hasRefreshableFilePath() ? " · file" : "");
3210
3279
  const descriptor = getCurrentStudioDocumentDescriptor();
3211
3280
  if (sourceBadgeEl) {
3281
+ sourceBadgeEl.textContent = originText;
3212
3282
  sourceBadgeEl.title = descriptor.fileBacked
3213
3283
  ? ("Editor origin: " + label + "\nClick to reset origin and detach the current editor text into a new draft. The file on disk will not be changed.")
3214
3284
  : ("Editor origin: " + label + "\nClick to reset origin and start a new independent draft while keeping the current text and local notes.");
3215
3285
  }
3286
+ if (sourceOriginSummaryEl) {
3287
+ sourceOriginSummaryEl.textContent = originText;
3288
+ sourceOriginSummaryEl.title = descriptor.fileBacked
3289
+ ? ("File-backed editor: " + (descriptor.path || label))
3290
+ : ("Detached editor origin: " + label);
3291
+ }
3292
+ if (sourceResetOriginBtn) {
3293
+ sourceResetOriginBtn.textContent = descriptor.fileBacked ? "Detach from file" : "Reset origin";
3294
+ sourceResetOriginBtn.title = descriptor.fileBacked
3295
+ ? "Detach the current editor text from this file and keep it in a new draft. The file on disk will not be changed."
3296
+ : "Reset the editor origin and keep the current text in a new draft.";
3297
+ }
3216
3298
  // Show "Set working dir" button when not file-backed
3217
3299
  var isFileBacked = hasRefreshableFilePath();
3218
3300
  if (isFileBacked) {
@@ -3553,6 +3635,17 @@
3553
3635
  setStatus("Right pane content focused.");
3554
3636
  }
3555
3637
 
3638
+ function switchRightPaneToPrimaryPreview() {
3639
+ const targetView = isEditorOnlyMode ? "editor-preview" : "preview";
3640
+ const snapshot = snapshotStudioScrollablePositions();
3641
+ setRightView(targetView);
3642
+ scheduleStudioScrollablePositionRestore(snapshot);
3643
+ const label = rightViewSelect && rightViewSelect.selectedOptions && rightViewSelect.selectedOptions[0]
3644
+ ? rightViewSelect.selectedOptions[0].textContent
3645
+ : (isEditorOnlyMode ? "Editor (Preview)" : "Response (Preview)");
3646
+ setStatus("Right pane view: " + String(label || "Preview") + ".");
3647
+ }
3648
+
3556
3649
  function cycleActivePaneView(direction) {
3557
3650
  if (activePane === "right") {
3558
3651
  if (!rightViewSelect || rightViewSelect.disabled) {
@@ -3851,6 +3944,16 @@
3851
3944
  return;
3852
3945
  }
3853
3946
 
3947
+ const isPreviewShortcut = (key.toLowerCase() === "p" || code === "KeyP")
3948
+ && (event.metaKey || event.ctrlKey)
3949
+ && event.altKey
3950
+ && !event.shiftKey;
3951
+ if (isPreviewShortcut) {
3952
+ event.preventDefault();
3953
+ switchRightPaneToPrimaryPreview();
3954
+ return;
3955
+ }
3956
+
3854
3957
  const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
3855
3958
  if (isContentFocusShortcut) {
3856
3959
  event.preventDefault();
@@ -7940,14 +8043,32 @@
7940
8043
  }
7941
8044
  }
7942
8045
 
7943
- async function exportRightPanePdf() {
8046
+ function getStudioPdfViewerUrlForExportPayload(payload) {
8047
+ if (!payload || typeof payload !== "object") return "";
8048
+ const exportPath = typeof payload.path === "string" ? payload.path.trim() : "";
8049
+ if (exportPath) {
8050
+ const resourceUrl = buildStudioPdfResourceUrl({ path: exportPath, resourceDir: exportPath.split(/[\\/]/).slice(0, -1).join("/") }, false);
8051
+ if (resourceUrl) return resourceUrl;
8052
+ }
8053
+ return typeof payload.downloadUrl === "string" ? payload.downloadUrl : "";
8054
+ }
8055
+
8056
+ async function exportRightPanePdf(options) {
8057
+ const exportOptions = options && typeof options === "object" ? options : {};
8058
+ const openTarget = exportOptions.openTarget === "studio" ? "studio" : "default";
8059
+ let studioPopup = null;
8060
+ if (openTarget === "studio") {
8061
+ studioPopup = openExportStudioPlaceholderWindow("PDF");
8062
+ }
7944
8063
  if (uiBusy || previewExportInProgress) {
8064
+ closeExportStudioWindow(studioPopup);
7945
8065
  setStatus("Studio is busy.", "warning");
7946
8066
  return;
7947
8067
  }
7948
8068
 
7949
8069
  const token = getToken();
7950
8070
  if (!token) {
8071
+ closeExportStudioWindow(studioPopup);
7951
8072
  setStatus("Missing Studio token in URL. Re-run /studio.", "error");
7952
8073
  return;
7953
8074
  }
@@ -7955,17 +8076,20 @@
7955
8076
  const exportingReplJournal = rightView === "repl";
7956
8077
  const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
7957
8078
  if (!rightPaneShowsPreview && !exportingReplJournal) {
8079
+ closeExportStudioWindow(studioPopup);
7958
8080
  setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export PDF.", "warning");
7959
8081
  return;
7960
8082
  }
7961
8083
  const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
7962
8084
  if (exportingReplJournal && !replJournalExportEntries.length) {
8085
+ closeExportStudioWindow(studioPopup);
7963
8086
  setStatus("No Studio REPL record entries to export for this session yet.", "warning");
7964
8087
  return;
7965
8088
  }
7966
8089
 
7967
8090
  const htmlArtifactSource = exportingReplJournal ? "" : getRightPaneHtmlArtifactSource();
7968
8091
  if (htmlArtifactSource) {
8092
+ closeExportStudioWindow(studioPopup);
7969
8093
  setStatus("PDF export does not support interactive HTML previews yet. Export as HTML or use the browser print dialog inside the preview.", "warning");
7970
8094
  return;
7971
8095
  }
@@ -7976,6 +8100,7 @@
7976
8100
  ? prepareEditorTextForPdfExport(sourceTextEl.value)
7977
8101
  : prepareEditorTextForPreview(latestResponseMarkdown));
7978
8102
  if (!markdown || !markdown.trim()) {
8103
+ closeExportStudioWindow(studioPopup);
7979
8104
  setStatus("Nothing to export yet.", "warning");
7980
8105
  return;
7981
8106
  }
@@ -7998,7 +8123,7 @@
7998
8123
 
7999
8124
  previewExportInProgress = true;
8000
8125
  updateResultActionButtons();
8001
- setStatus("Exporting PDF…", "warning");
8126
+ setStatus(openTarget === "studio" ? "Exporting PDF for Studio…" : "Exporting PDF…", "warning");
8002
8127
 
8003
8128
  try {
8004
8129
  const response = await fetchWithTimeout("/export-pdf?token=" + encodeURIComponent(token), {
@@ -8013,6 +8138,7 @@
8013
8138
  isLatex: isLatex,
8014
8139
  editorPdfLanguage: editorPdfLanguage,
8015
8140
  filenameHint: filenameHint,
8141
+ openTarget: openTarget,
8016
8142
  }),
8017
8143
  }, PDF_EXPORT_FETCH_TIMEOUT_MS, "PDF export");
8018
8144
 
@@ -8051,6 +8177,35 @@
8051
8177
  downloadName += ".pdf";
8052
8178
  }
8053
8179
 
8180
+ if (openTarget === "studio") {
8181
+ const targetUrl = typeof payload.relativeUrl === "string" && payload.relativeUrl
8182
+ ? new URL(payload.relativeUrl, window.location.href).href
8183
+ : (typeof payload.url === "string" ? payload.url : "");
8184
+ const openedStudio = navigateExportStudioWindow(studioPopup, targetUrl);
8185
+ if (!openedStudio) {
8186
+ closeExportStudioWindow(studioPopup);
8187
+ const viewerUrl = getStudioPdfViewerUrlForExportPayload(payload);
8188
+ if (viewerUrl) openStudioPdfFocusViewer(viewerUrl, downloadName);
8189
+ }
8190
+ if (writeError) {
8191
+ setStatus(openedStudio
8192
+ ? "Opened exported PDF in a Studio preview tab, but could not write project file: " + writeError
8193
+ : "Exported PDF, but could not open a Studio preview tab and could not write project file: " + writeError,
8194
+ "warning");
8195
+ } else if (exportWarning) {
8196
+ setStatus(openedStudio
8197
+ ? "Opened exported PDF in a Studio preview tab with warning: " + exportWarning
8198
+ : "Exported PDF, but could not open a Studio preview tab. Warning: " + exportWarning,
8199
+ "warning");
8200
+ } else {
8201
+ setStatus(openedStudio
8202
+ ? "Opened exported PDF in a Studio preview tab: " + (exportPath || downloadName)
8203
+ : "Exported PDF, but could not open a Studio preview tab" + (targetUrl ? ": " + targetUrl : "."),
8204
+ openedStudio ? "success" : "warning");
8205
+ }
8206
+ return;
8207
+ }
8208
+
8054
8209
  if (openedExternal) {
8055
8210
  if (writeError) {
8056
8211
  setStatus("Opened PDF in default viewer, but could not write project file: " + writeError, "warning");
@@ -8112,6 +8267,7 @@
8112
8267
  setStatus("Exported PDF: " + downloadName, "success");
8113
8268
  }
8114
8269
  } catch (error) {
8270
+ closeExportStudioWindow(studioPopup);
8115
8271
  const detail = error && error.message ? error.message : String(error || "unknown error");
8116
8272
  setStatus("PDF export failed: " + detail, "error");
8117
8273
  } finally {
@@ -8120,14 +8276,58 @@
8120
8276
  }
8121
8277
  }
8122
8278
 
8123
- async function exportRightPaneHtml() {
8279
+ function openExportStudioPlaceholderWindow(formatLabel) {
8280
+ const label = String(formatLabel || "preview").trim() || "preview";
8281
+ let popup = null;
8282
+ try {
8283
+ popup = window.open("", "_blank");
8284
+ if (popup && popup.document && popup.document.body) {
8285
+ popup.document.title = "Opening " + label + " in Studio…";
8286
+ 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>";
8287
+ }
8288
+ } catch {
8289
+ popup = null;
8290
+ }
8291
+ return popup;
8292
+ }
8293
+
8294
+ function navigateExportStudioWindow(popup, targetUrl) {
8295
+ if (!targetUrl) return false;
8296
+ if (popup && !popup.closed) {
8297
+ try {
8298
+ popup.opener = null;
8299
+ popup.location.href = targetUrl;
8300
+ return true;
8301
+ } catch {}
8302
+ }
8303
+ try {
8304
+ return Boolean(window.open(targetUrl, "_blank", "noopener"));
8305
+ } catch {
8306
+ return false;
8307
+ }
8308
+ }
8309
+
8310
+ function closeExportStudioWindow(popup) {
8311
+ if (!popup || popup.closed) return;
8312
+ try { popup.close(); } catch {}
8313
+ }
8314
+
8315
+ async function exportRightPaneHtml(options) {
8316
+ const exportOptions = options && typeof options === "object" ? options : {};
8317
+ const openTarget = exportOptions.openTarget === "studio" ? "studio" : "browser";
8318
+ let studioPopup = null;
8319
+ if (openTarget === "studio") {
8320
+ studioPopup = openExportStudioPlaceholderWindow("HTML");
8321
+ }
8124
8322
  if (uiBusy || previewExportInProgress) {
8323
+ closeExportStudioWindow(studioPopup);
8125
8324
  setStatus("Studio is busy.", "warning");
8126
8325
  return;
8127
8326
  }
8128
8327
 
8129
8328
  const token = getToken();
8130
8329
  if (!token) {
8330
+ closeExportStudioWindow(studioPopup);
8131
8331
  setStatus("Missing Studio token in URL. Re-run /studio.", "error");
8132
8332
  return;
8133
8333
  }
@@ -8135,11 +8335,13 @@
8135
8335
  const exportingReplJournal = rightView === "repl";
8136
8336
  const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
8137
8337
  if (!rightPaneShowsPreview && !exportingReplJournal) {
8338
+ closeExportStudioWindow(studioPopup);
8138
8339
  setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export HTML.", "warning");
8139
8340
  return;
8140
8341
  }
8141
8342
  const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
8142
8343
  if (exportingReplJournal && !replJournalExportEntries.length) {
8344
+ closeExportStudioWindow(studioPopup);
8143
8345
  setStatus("No Studio REPL record entries to export for this session yet.", "warning");
8144
8346
  return;
8145
8347
  }
@@ -8149,6 +8351,7 @@
8149
8351
  ? prepareEditorTextForHtmlExport(sourceTextEl.value)
8150
8352
  : prepareEditorTextForPreview(latestResponseMarkdown)));
8151
8353
  if (!markdown || !markdown.trim()) {
8354
+ closeExportStudioWindow(studioPopup);
8152
8355
  setStatus("Nothing to export yet.", "warning");
8153
8356
  return;
8154
8357
  }
@@ -8173,7 +8376,7 @@
8173
8376
 
8174
8377
  previewExportInProgress = true;
8175
8378
  updateResultActionButtons();
8176
- setStatus("Exporting HTML…", "warning");
8379
+ setStatus(openTarget === "studio" ? "Exporting HTML for Studio…" : "Exporting HTML…", "warning");
8177
8380
 
8178
8381
  try {
8179
8382
  const response = await fetchWithTimeout("/export-html?token=" + encodeURIComponent(token), {
@@ -8189,6 +8392,7 @@
8189
8392
  editorHtmlLanguage: editorHtmlLanguage,
8190
8393
  filenameHint: filenameHint,
8191
8394
  title: titleHint,
8395
+ openTarget: openTarget,
8192
8396
  }),
8193
8397
  }, HTML_EXPORT_FETCH_TIMEOUT_MS, "HTML export");
8194
8398
 
@@ -8227,6 +8431,31 @@
8227
8431
  downloadName += ".html";
8228
8432
  }
8229
8433
 
8434
+ if (openTarget === "studio") {
8435
+ const targetUrl = typeof payload.relativeUrl === "string" && payload.relativeUrl
8436
+ ? new URL(payload.relativeUrl, window.location.href).href
8437
+ : (typeof payload.url === "string" ? payload.url : "");
8438
+ const openedStudio = navigateExportStudioWindow(studioPopup, targetUrl);
8439
+ if (!openedStudio) closeExportStudioWindow(studioPopup);
8440
+ if (writeError) {
8441
+ setStatus(openedStudio
8442
+ ? "Opened exported HTML in Studio as an unsaved copy; could not write project file: " + writeError
8443
+ : "Exported HTML for Studio, but the popup was blocked and the project file could not be written: " + writeError,
8444
+ "warning");
8445
+ } else if (exportWarning) {
8446
+ setStatus(openedStudio
8447
+ ? "Opened exported HTML in Studio with warning: " + exportWarning
8448
+ : "Exported HTML for Studio, but the popup was blocked. Warning: " + exportWarning,
8449
+ "warning");
8450
+ } else {
8451
+ setStatus(openedStudio
8452
+ ? "Opened exported HTML in Studio: " + (exportPath || downloadName)
8453
+ : (targetUrl ? "Exported HTML for Studio: " + targetUrl : "Exported HTML, but Studio did not receive an editor URL."),
8454
+ openedStudio ? "success" : "warning");
8455
+ }
8456
+ return;
8457
+ }
8458
+
8230
8459
  if (openedExternal) {
8231
8460
  if (writeError) {
8232
8461
  setStatus("Opened HTML in default browser, but could not write project file: " + writeError, "warning");
@@ -8262,6 +8491,7 @@
8262
8491
  return;
8263
8492
  }
8264
8493
 
8494
+ closeExportStudioWindow(studioPopup);
8265
8495
  const exportWarning = String(response.headers.get("x-pi-studio-export-warning") || "").trim();
8266
8496
  const blob = await response.blob();
8267
8497
  const headerFilename = parseContentDispositionFilename(response.headers.get("content-disposition"));
@@ -8288,6 +8518,7 @@
8288
8518
  setStatus("Exported HTML: " + downloadName, "success");
8289
8519
  }
8290
8520
  } catch (error) {
8521
+ closeExportStudioWindow(studioPopup);
8291
8522
  const detail = error && error.message ? error.message : String(error || "unknown error");
8292
8523
  setStatus("HTML export failed: " + detail, "error");
8293
8524
  } finally {
@@ -8318,10 +8549,16 @@
8318
8549
 
8319
8550
  function exportRightPaneFormat(format) {
8320
8551
  closeExportPreviewMenu();
8321
- if (format === "html") {
8322
- return exportRightPaneHtml();
8552
+ if (format === "html-studio") {
8553
+ return exportRightPaneHtml({ openTarget: "studio" });
8554
+ }
8555
+ if (format === "html" || format === "html-browser") {
8556
+ return exportRightPaneHtml({ openTarget: "browser" });
8323
8557
  }
8324
- return exportRightPanePdf();
8558
+ if (format === "pdf-studio") {
8559
+ return exportRightPanePdf({ openTarget: "studio" });
8560
+ }
8561
+ return exportRightPanePdf({ openTarget: "default" });
8325
8562
  }
8326
8563
 
8327
8564
  function normalizeCopyableBlockText(text) {
@@ -9611,28 +9848,40 @@
9611
9848
  } else if (isHtmlArtifactPreview) {
9612
9849
  exportPdfBtn.title = "This is an interactive HTML preview. Export as HTML; PDF export is not available yet.";
9613
9850
  } else if (exportingReplJournal) {
9614
- exportPdfBtn.title = "Choose PDF or HTML and export the Studio REPL record.";
9851
+ exportPdfBtn.title = "Choose PDF export or an HTML export destination for the Studio REPL record.";
9615
9852
  } else {
9616
- exportPdfBtn.title = "Choose PDF or HTML and export the current right-pane preview.";
9853
+ exportPdfBtn.title = "Choose PDF export or an HTML export destination for the current right-pane preview.";
9617
9854
  }
9618
9855
  }
9856
+ if (exportPreviewPdfStudioBtn) {
9857
+ exportPreviewPdfStudioBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview || isHtmlArtifactPreview;
9858
+ exportPreviewPdfStudioBtn.title = isHtmlArtifactPreview
9859
+ ? "Interactive HTML preview PDF export is not available yet."
9860
+ : (exportingReplJournal ? "Export the Studio REPL record as PDF and open it in Studio." : "Export the current right-pane preview as PDF and open it in Studio.");
9861
+ }
9619
9862
  if (exportPreviewPdfBtn) {
9620
9863
  exportPreviewPdfBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview || isHtmlArtifactPreview;
9621
9864
  exportPreviewPdfBtn.title = isHtmlArtifactPreview
9622
9865
  ? "Interactive HTML preview PDF export is not available yet."
9623
- : (exportingReplJournal ? "Export the Studio REPL record as PDF." : "Export the current right-pane preview as PDF.");
9866
+ : (exportingReplJournal ? "Export the Studio REPL record as PDF and open it in the default PDF viewer." : "Export the current right-pane preview as PDF and open it in the default PDF viewer.");
9867
+ }
9868
+ if (exportPreviewHtmlStudioBtn) {
9869
+ exportPreviewHtmlStudioBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
9870
+ exportPreviewHtmlStudioBtn.title = isHtmlArtifactPreview
9871
+ ? "Export the authored HTML preview and open it in a new Studio editor tab."
9872
+ : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML and open it in a new Studio editor tab." : "Export the current right-pane preview as standalone HTML and open it in a new Studio editor tab.");
9624
9873
  }
9625
9874
  if (exportPreviewHtmlBtn) {
9626
9875
  exportPreviewHtmlBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
9627
9876
  exportPreviewHtmlBtn.title = isHtmlArtifactPreview
9628
- ? "Export the authored HTML preview."
9629
- : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML." : "Export the current right-pane preview as standalone HTML.");
9877
+ ? "Export the authored HTML preview and open it in the default browser."
9878
+ : (exportingReplJournal ? "Export the Studio REPL record as standalone HTML and open it in the default browser." : "Export the current right-pane preview as standalone HTML and open it in the default browser.");
9630
9879
  }
9631
9880
  if (exportPreviewControlsEl) {
9632
9881
  exportPreviewControlsEl.title = canExportPreview
9633
9882
  ? (exportingReplJournal
9634
- ? "Choose a format and export the Studio REPL record."
9635
- : (isHtmlArtifactPreview ? "Export this HTML preview." : "Choose a format and export the current right-pane preview."))
9883
+ ? "Choose a format and export destination for the Studio REPL record."
9884
+ : (isHtmlArtifactPreview ? "Export this HTML preview to Studio or browser." : "Choose a format and export destination for the current right-pane preview."))
9636
9885
  : (exportingReplJournal ? "No Studio REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting.");
9637
9886
  }
9638
9887
  if (!canExportPreview || previewExportInProgress) {
@@ -9755,6 +10004,14 @@
9755
10004
 
9756
10005
  fileInput.disabled = uiBusy;
9757
10006
  if (sourceBadgeEl) sourceBadgeEl.disabled = uiBusy;
10007
+ if (sourceResetOriginBtn) sourceResetOriginBtn.disabled = uiBusy;
10008
+ if (sourceOpenCurrentFileTabBtn) {
10009
+ sourceOpenCurrentFileTabBtn.disabled = uiBusy || !hasRefreshableFilePath();
10010
+ sourceOpenCurrentFileTabBtn.title = hasRefreshableFilePath()
10011
+ ? "Open this file-backed document in a new refreshable editor-only Studio tab."
10012
+ : "Available after opening a file-backed document.";
10013
+ }
10014
+ if (sourceOpenCurrentTextCopyTabBtn) sourceOpenCurrentTextCopyTabBtn.disabled = uiBusy || wsState !== "Ready" || !String(sourceTextEl.value || "").trim();
9758
10015
  saveAsBtn.disabled = uiBusy;
9759
10016
  saveOverBtn.disabled = uiBusy || !canSaveOver;
9760
10017
  if (refreshFromDiskBtn) refreshFromDiskBtn.disabled = uiBusy || !canRefreshFromDisk;
@@ -9877,6 +10134,7 @@
9877
10134
  }
9878
10135
 
9879
10136
  function shouldRestorePersistedWorkspaceState(state) {
10137
+ if (skipInitialWorkspaceRestore) return false;
9880
10138
  if (!state || typeof state.text !== "string") return false;
9881
10139
  const storedSourceState = normalizeWorkspaceSourceState(state.sourceState);
9882
10140
  const initialIdentity = getWorkspaceStateIdentity(initialSourceState);
@@ -10798,6 +11056,7 @@
10798
11056
  else params.delete("docPath");
10799
11057
  if (nextDraftId) params.set("draftId", nextDraftId);
10800
11058
  else params.delete("draftId");
11059
+ params.delete("skipWorkspaceRestore");
10801
11060
  window.history.replaceState(null, "", currentUrl.toString());
10802
11061
  } catch {
10803
11062
  // Ignore URL-state update failures.
@@ -11013,10 +11272,10 @@
11013
11272
  appendPreviewLinkMenuButton(menu, "Open PDF preview", "open-pdf");
11014
11273
  appendPreviewLinkMenuButton(menu, "Open in new Studio tab", "open-preview-new");
11015
11274
  } else if (kind === "text") {
11016
- appendPreviewLinkMenuButton(menu, "Open in new editor", "open-new");
11275
+ appendPreviewLinkMenuButton(menu, "Open file tab", "open-new");
11017
11276
  appendPreviewLinkMenuButton(menu, "Open here", "open-here");
11018
11277
  } else if (kind === "office") {
11019
- appendPreviewLinkMenuButton(menu, "Convert in new editor", "open-new");
11278
+ appendPreviewLinkMenuButton(menu, "Convert tab", "open-new");
11020
11279
  appendPreviewLinkMenuButton(menu, "Convert here", "open-here");
11021
11280
  } else if (kind === "image") {
11022
11281
  appendPreviewLinkMenuButton(menu, "Open image preview", "open-image");
@@ -11153,10 +11412,11 @@
11153
11412
  return;
11154
11413
  }
11155
11414
  const popup = pendingWindow || window.open("", "_blank");
11415
+ const openingLabel = getPreviewLocalLinkKind(href) === "office" ? "Opening converted document…" : "Opening file tab…";
11156
11416
  try {
11157
11417
  if (popup && popup.document && popup.document.body) {
11158
- popup.document.title = "Opening linked file…";
11159
- popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening linked file…</p>";
11418
+ popup.document.title = openingLabel;
11419
+ popup.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">" + escapeHtml(openingLabel) + "</p>";
11160
11420
  }
11161
11421
  } catch {}
11162
11422
  try {
@@ -11169,12 +11429,12 @@
11169
11429
  try {
11170
11430
  popup.opener = null;
11171
11431
  popup.location.href = targetUrl;
11172
- setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening linked file in a new editor.", "success");
11432
+ setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
11173
11433
  return;
11174
11434
  } catch {}
11175
11435
  }
11176
11436
  window.open(targetUrl, "_blank", "noopener");
11177
- setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening linked file in a new editor.", "success");
11437
+ setStatus(payload && payload.converted ? "Opening converted document in a new editor." : "Opening file-backed document in a new editor.", "success");
11178
11438
  } catch (error) {
11179
11439
  if (popup && !popup.closed) {
11180
11440
  try { popup.close(); } catch {}
@@ -12823,6 +13083,110 @@
12823
13083
  return describeStudioDocument(sourceState);
12824
13084
  }
12825
13085
 
13086
+ function formatScratchpadRecentTime(timestamp) {
13087
+ const value = Number(timestamp) || 0;
13088
+ if (!value) return "unknown time";
13089
+ try {
13090
+ return new Date(value).toLocaleString([], { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" });
13091
+ } catch {
13092
+ return "unknown time";
13093
+ }
13094
+ }
13095
+
13096
+ function renderScratchpadRecentPanel() {
13097
+ if (!scratchpadRecentPanelEl) return;
13098
+ scratchpadRecentPanelEl.hidden = !scratchpadRecentVisible;
13099
+ if (!scratchpadRecentVisible) return;
13100
+ if (scratchpadRecentLoading) {
13101
+ scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-loading'>Loading recent scratchpads…</div>";
13102
+ return;
13103
+ }
13104
+ const currentKey = getCurrentStudioDocumentDescriptor().key;
13105
+ const entries = Array.isArray(scratchpadRecentEntries) ? scratchpadRecentEntries : [];
13106
+ if (!entries.length) {
13107
+ scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-empty'>No other saved scratchpads yet.</div>";
13108
+ return;
13109
+ }
13110
+ scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-list'>" + entries.map((entry) => {
13111
+ const key = String(entry && entry.documentKey ? entry.documentKey : "");
13112
+ const isCurrent = key === currentKey;
13113
+ const label = String(entry && entry.label ? entry.label : key || "scratchpad");
13114
+ const kind = String(entry && entry.kind ? entry.kind : "Scratchpad");
13115
+ const textLength = Math.max(0, Number(entry && entry.textLength) || 0);
13116
+ const preview = String(entry && entry.textPreview ? entry.textPreview : "");
13117
+ const meta = (isCurrent ? "Current · " : "") + kind + " · " + String(textLength) + " chars · " + formatScratchpadRecentTime(entry && entry.updatedAt);
13118
+ return "<div class='scratchpad-recent-item' data-scratchpad-key='" + escapeHtml(key) + "'>"
13119
+ + "<div class='scratchpad-recent-main'>"
13120
+ + "<div class='scratchpad-recent-title' title='" + escapeHtml(label) + "'>" + escapeHtml(label) + "</div>"
13121
+ + "<div class='scratchpad-recent-meta'>" + escapeHtml(meta) + "</div>"
13122
+ + (preview ? "<div class='scratchpad-recent-preview'>" + escapeHtml(preview) + "</div>" : "")
13123
+ + "</div>"
13124
+ + "<div class='scratchpad-recent-actions'>"
13125
+ + "<button type='button' data-scratchpad-recent-action='load' data-scratchpad-key='" + escapeHtml(key) + "'" + (isCurrent ? " disabled" : "") + ">Load</button>"
13126
+ + "<button type='button' data-scratchpad-recent-action='append' data-scratchpad-key='" + escapeHtml(key) + "'" + (isCurrent ? " disabled" : "") + ">Append</button>"
13127
+ + "<button type='button' data-scratchpad-recent-action='copy' data-scratchpad-key='" + escapeHtml(key) + "'>Copy</button>"
13128
+ + "</div>"
13129
+ + "</div>";
13130
+ }).join("") + "</div>";
13131
+ }
13132
+
13133
+ async function loadScratchpadRecentEntries() {
13134
+ scratchpadRecentLoading = true;
13135
+ renderScratchpadRecentPanel();
13136
+ try {
13137
+ const payload = await fetchStudioJson("/scratchpad-state", { query: { action: "recent", limit: "20" } });
13138
+ scratchpadRecentEntries = Array.isArray(payload && payload.scratchpads) ? payload.scratchpads : [];
13139
+ } catch (error) {
13140
+ scratchpadRecentEntries = [];
13141
+ setStatus("Could not load recent scratchpads: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
13142
+ } finally {
13143
+ scratchpadRecentLoading = false;
13144
+ renderScratchpadRecentPanel();
13145
+ }
13146
+ }
13147
+
13148
+ function toggleScratchpadRecentPanel() {
13149
+ scratchpadRecentVisible = !scratchpadRecentVisible;
13150
+ if (scratchpadRecentVisible) {
13151
+ void loadScratchpadRecentEntries();
13152
+ } else {
13153
+ renderScratchpadRecentPanel();
13154
+ }
13155
+ updateScratchpadUi();
13156
+ }
13157
+
13158
+ async function applyScratchpadRecentAction(action, documentKey) {
13159
+ const key = String(documentKey || "").trim();
13160
+ if (!key) return;
13161
+ const mode = action === "append" ? "append" : (action === "copy" ? "copy" : "load");
13162
+ try {
13163
+ const text = await fetchScratchpadTextForDocumentKey(key);
13164
+ if (!String(text || "").trim()) {
13165
+ setStatus("That scratchpad is empty.", "warning");
13166
+ return;
13167
+ }
13168
+ if (mode === "copy") {
13169
+ const ok = await writeTextToClipboard(text);
13170
+ setStatus(ok ? "Copied recent scratchpad." : "Could not copy recent scratchpad.", ok ? "success" : "warning");
13171
+ return;
13172
+ }
13173
+ if (mode === "append") {
13174
+ const separator = scratchpadText && !scratchpadText.endsWith("\n") ? "\n\n" : (scratchpadText ? "\n" : "");
13175
+ setScratchpadText(String(scratchpadText || "") + separator + String(text || ""));
13176
+ setStatus("Appended recent scratchpad.", "success");
13177
+ return;
13178
+ }
13179
+ if (String(scratchpadText || "").trim() && String(scratchpadText || "") !== String(text || "")) {
13180
+ const confirmed = window.confirm("Replace the current scratchpad with this recent scratchpad? Current scratchpad text will remain saved under its current document/draft identity, but this panel will show the loaded text for the current document.");
13181
+ if (!confirmed) return;
13182
+ }
13183
+ setScratchpadText(text);
13184
+ setStatus("Loaded recent scratchpad into current scratchpad.", "success");
13185
+ } catch (error) {
13186
+ setStatus("Could not use recent scratchpad: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
13187
+ }
13188
+ }
13189
+
12826
13190
  async function fetchScratchpadTextForDocumentKey(documentKey) {
12827
13191
  const payload = await fetchStudioJson("/scratchpad-state", {
12828
13192
  query: { documentKey: documentKey },
@@ -12830,9 +13194,9 @@
12830
13194
  return payload && typeof payload.text === "string" ? payload.text : "";
12831
13195
  }
12832
13196
 
12833
- function flushScratchpadPersistence(documentKeyOverride, textOverride) {
13197
+ function flushScratchpadPersistence(documentKeyOverride, textOverride, labelOverride) {
12834
13198
  const descriptor = documentKeyOverride
12835
- ? { key: String(documentKeyOverride || "").trim() }
13199
+ ? { key: String(documentKeyOverride || "").trim(), label: String(labelOverride || "").trim() }
12836
13200
  : getCurrentStudioDocumentDescriptor();
12837
13201
  const key = String(descriptor && descriptor.key ? descriptor.key : "").trim();
12838
13202
  if (!key) return;
@@ -12841,27 +13205,29 @@
12841
13205
  scratchpadPersistTimer = null;
12842
13206
  }
12843
13207
  const snapshot = String(arguments.length >= 2 ? textOverride : scratchpadText || "");
12844
- if (trySendStudioJsonBeacon("/scratchpad-state", { documentKey: key, text: snapshot })) {
13208
+ const label = String(descriptor && descriptor.label ? descriptor.label : "").trim();
13209
+ if (trySendStudioJsonBeacon("/scratchpad-state", { documentKey: key, text: snapshot, label })) {
12845
13210
  return;
12846
13211
  }
12847
13212
  void fetchStudioJson("/scratchpad-state", {
12848
13213
  method: "POST",
12849
- body: JSON.stringify({ documentKey: key, text: snapshot }),
13214
+ body: JSON.stringify({ documentKey: key, text: snapshot, label }),
12850
13215
  }).catch(() => {
12851
13216
  // Ignore scratchpad persistence failures for now.
12852
13217
  });
12853
13218
  }
12854
13219
 
12855
- function scheduleScratchpadPersistence(text, documentKey) {
13220
+ function scheduleScratchpadPersistence(text, documentKey, label) {
12856
13221
  if (scratchpadPersistTimer !== null) {
12857
13222
  window.clearTimeout(scratchpadPersistTimer);
12858
13223
  }
12859
13224
  const snapshot = String(text || "");
12860
13225
  const key = String(documentKey || "").trim();
13226
+ const labelSnapshot = String(label || "").trim();
12861
13227
  if (!key) return;
12862
13228
  scratchpadPersistTimer = window.setTimeout(() => {
12863
13229
  scratchpadPersistTimer = null;
12864
- flushScratchpadPersistence(key, snapshot);
13230
+ flushScratchpadPersistence(key, snapshot, labelSnapshot);
12865
13231
  }, 180);
12866
13232
  }
12867
13233
 
@@ -12893,7 +13259,7 @@
12893
13259
  if (String(existing || "").trim()) return;
12894
13260
  await fetchStudioJson("/scratchpad-state", {
12895
13261
  method: "POST",
12896
- body: JSON.stringify({ documentKey: nextDescriptor.key, text: snapshot }),
13262
+ body: JSON.stringify({ documentKey: nextDescriptor.key, text: snapshot, label: nextDescriptor.label }),
12897
13263
  });
12898
13264
  } catch {
12899
13265
  // Ignore carry-over failures and just fall back to normal scope loading.
@@ -12914,7 +13280,7 @@
12914
13280
 
12915
13281
  function persistScratchpadText(value) {
12916
13282
  const descriptor = getCurrentStudioDocumentDescriptor();
12917
- scheduleScratchpadPersistence(value, descriptor.key);
13283
+ scheduleScratchpadPersistence(value, descriptor.key, descriptor.label);
12918
13284
  }
12919
13285
 
12920
13286
  function normalizeReviewNoteAnchorKind(value) {
@@ -17111,6 +17477,10 @@
17111
17477
  ? ("Saved locally for this document/draft · " + normalized.length + " chars")
17112
17478
  : "Empty · local to this document/draft";
17113
17479
  }
17480
+ if (scratchpadRecentBtn) {
17481
+ scratchpadRecentBtn.textContent = scratchpadRecentVisible ? "Hide recent" : "Recent…";
17482
+ scratchpadRecentBtn.setAttribute("aria-expanded", scratchpadRecentVisible ? "true" : "false");
17483
+ }
17114
17484
  if (scratchpadInsertBtn) scratchpadInsertBtn.disabled = !hasContent;
17115
17485
  if (scratchpadCopyBtn) scratchpadCopyBtn.disabled = !hasContent;
17116
17486
  if (scratchpadClearBtn) scratchpadClearBtn.disabled = !normalized.length;
@@ -18239,11 +18609,11 @@
18239
18609
  const opened = navigatePendingCompanionWindow(responseRequestId, targetUrl);
18240
18610
  const readyMessage = typeof message.message === "string" && message.message.trim()
18241
18611
  ? message.message.trim()
18242
- : "Opened companion editor with a detached copy of the current editor text.";
18612
+ : "Opened editor tab with a detached copy of the current editor text.";
18243
18613
  setStatus(
18244
18614
  opened
18245
18615
  ? readyMessage
18246
- : (targetUrl ? "Companion editor ready: " + targetUrl : "Companion editor is ready, but Studio did not receive a URL."),
18616
+ : (targetUrl ? "Editor tab ready: " + targetUrl : "Editor tab is ready, but Studio did not receive a URL."),
18247
18617
  opened ? "success" : "warning",
18248
18618
  );
18249
18619
  return;
@@ -18563,8 +18933,8 @@
18563
18933
  try {
18564
18934
  companionWindow = window.open("", "_blank");
18565
18935
  if (companionWindow && companionWindow.document && companionWindow.document.body) {
18566
- companionWindow.document.title = "Opening companion editor…";
18567
- companionWindow.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening companion editor…</p>";
18936
+ companionWindow.document.title = "Opening editor tab…";
18937
+ companionWindow.document.body.innerHTML = "<p style=\"font: 13px -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; padding: 16px;\">Opening editor tab…</p>";
18568
18938
  }
18569
18939
  } catch {
18570
18940
  companionWindow = null;
@@ -18620,6 +18990,28 @@
18620
18990
  }
18621
18991
  }
18622
18992
 
18993
+ function requestOpenEditorOnlyDocument(content, options) {
18994
+ const requestId = beginUiAction("open_editor_only");
18995
+ if (!requestId) return false;
18996
+ openPendingCompanionWindow(requestId);
18997
+ const config = options && typeof options === "object" ? options : {};
18998
+ const sent = sendMessage({
18999
+ type: "open_editor_only_request",
19000
+ requestId,
19001
+ content: String(content || ""),
19002
+ label: config.label || "current editor",
19003
+ path: config.path || undefined,
19004
+ resourceDir: config.resourceDir || undefined,
19005
+ });
19006
+ if (!sent) {
19007
+ closePendingCompanionWindow(requestId);
19008
+ pendingRequestId = null;
19009
+ pendingKind = null;
19010
+ setBusy(false);
19011
+ }
19012
+ return sent;
19013
+ }
19014
+
18623
19015
  function describeSourceForAnnotation() {
18624
19016
  if (sourceState.source === "file" && sourceState.label) {
18625
19017
  return "file " + sourceState.label;
@@ -19100,7 +19492,7 @@
19100
19492
  event.stopPropagation();
19101
19493
  if (actionBtn.disabled) return;
19102
19494
  const format = String(actionBtn.getAttribute("data-export-preview-format") || "pdf").toLowerCase();
19103
- void exportRightPaneFormat(format === "html" ? "html" : "pdf");
19495
+ void exportRightPaneFormat(format);
19104
19496
  });
19105
19497
  }
19106
19498
 
@@ -19235,27 +19627,10 @@
19235
19627
 
19236
19628
  if (openCompanionBtn) {
19237
19629
  openCompanionBtn.addEventListener("click", () => {
19238
- const content = sourceTextEl.value;
19239
-
19240
- const requestId = beginUiAction("open_editor_only");
19241
- if (!requestId) return;
19242
- openPendingCompanionWindow(requestId);
19243
-
19244
- const sent = sendMessage({
19245
- type: "open_editor_only_request",
19246
- requestId,
19247
- content,
19248
- label: sourceState && sourceState.label ? sourceState.label : "current editor",
19249
- path: sourceState && sourceState.path ? sourceState.path : undefined,
19630
+ requestOpenEditorOnlyDocument("", {
19631
+ label: "blank",
19250
19632
  resourceDir: getCurrentResourceDirValue() || undefined,
19251
19633
  });
19252
-
19253
- if (!sent) {
19254
- closePendingCompanionWindow(requestId);
19255
- pendingRequestId = null;
19256
- pendingKind = null;
19257
- setBusy(false);
19258
- }
19259
19634
  });
19260
19635
  }
19261
19636
 
@@ -19651,6 +20026,24 @@
19651
20026
  });
19652
20027
  }
19653
20028
 
20029
+ if (scratchpadRecentBtn) {
20030
+ scratchpadRecentBtn.addEventListener("click", () => {
20031
+ toggleScratchpadRecentPanel();
20032
+ });
20033
+ }
20034
+
20035
+ if (scratchpadRecentPanelEl) {
20036
+ scratchpadRecentPanelEl.addEventListener("click", (event) => {
20037
+ const target = event.target;
20038
+ const actionEl = target instanceof Element ? target.closest("[data-scratchpad-recent-action]") : null;
20039
+ if (!actionEl) return;
20040
+ event.preventDefault();
20041
+ const action = String(actionEl.getAttribute("data-scratchpad-recent-action") || "load");
20042
+ const key = String(actionEl.getAttribute("data-scratchpad-key") || "");
20043
+ void applyScratchpadRecentAction(action, key);
20044
+ });
20045
+ }
20046
+
19654
20047
  if (scratchpadInsertBtn) {
19655
20048
  scratchpadInsertBtn.addEventListener("click", () => {
19656
20049
  insertScratchpadIntoEditor();
@@ -19757,7 +20150,7 @@
19757
20150
  }
19758
20151
  if (sourceBadgeEl) {
19759
20152
  sourceBadgeEl.addEventListener("click", () => {
19760
- resetEditorOrigin();
20153
+ if (!studioUiRefreshEnabled) resetEditorOrigin();
19761
20154
  });
19762
20155
  }
19763
20156
  if (resourceDirBtn) {