pi-studio 0.9.21 → 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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  All notable changes to `pi-studio` are documented here.
4
4
 
5
+ ## [0.9.22] — 2026-05-29
6
+
7
+ ### Added
8
+ - Added **Open current file in new editor tab** and **Open current text as copy in new editor tab** to Source & context for explicit file-backed vs detached-copy tab opening.
9
+ - Added `Cmd/Ctrl+Alt+P` to switch the right pane directly back to Preview without cycling through Files, REPL, or Working.
10
+
11
+ ### Changed
12
+ - Changed the editor toolbar's detached-copy action into **New editor tab** for opening a blank editor tab, moved **Send current text to Pi editor** into Source & context, separated the passive origin summary from explicit **Detach from file** / **Reset origin** actions, and aligned local link menus with Files-view wording (**Open file tab** / **Convert tab**).
13
+
14
+ ### Fixed
15
+ - New Studio tabs opened from Files, local preview links, preview exports, or editor-tab actions now skip cloned browser-tab workspace restore on first load, preventing inherited Files/preview state from briefly flashing and replacing the requested document preview.
16
+
5
17
  ## [0.9.21] — 2026-05-28
6
18
 
7
19
  ### Added
@@ -177,6 +177,7 @@
177
177
  const isSshStudioSession = Boolean(document.body && document.body.dataset && document.body.dataset.sshSession === "1");
178
178
 
179
179
  const initialQueryParams = new URLSearchParams(window.location.search || "");
180
+ const skipInitialWorkspaceRestore = initialQueryParams.get("skipWorkspaceRestore") === "1";
180
181
  const explicitDocumentIdentityFromUrl = initialQueryParams.has("docId")
181
182
  || initialQueryParams.has("docSource")
182
183
  || initialQueryParams.has("docLabel")
@@ -232,6 +233,10 @@
232
233
  let pendingKind = null;
233
234
  let stickyStudioKind = null;
234
235
  const pendingCompanionWindows = new Map();
236
+ let sourceOriginSummaryEl = null;
237
+ let sourceResetOriginBtn = null;
238
+ let sourceOpenCurrentFileTabBtn = null;
239
+ let sourceOpenCurrentTextCopyTabBtn = null;
235
240
  let initialDocumentApplied = false;
236
241
  function normalizeRightViewValue(nextView) {
237
242
  const raw = String(nextView || "").trim();
@@ -258,8 +263,8 @@
258
263
  option.disabled = isEditorOnlyMode && !editorOnlyAllowed.has(option.value);
259
264
  });
260
265
  rightViewSelect.title = isEditorOnlyMode
261
- ? "Editor-only views: editor preview, Files, or REPL. Shortcut: F7 when the right pane is active; F6 switches panes."
262
- : "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.";
263
268
  }
264
269
 
265
270
  function getInitialRightView(source) {
@@ -2405,8 +2410,66 @@
2405
2410
  suggestCompletionOptionsBtn.hidden = false;
2406
2411
  if (completionContextSelect) completionContextSelect.hidden = true;
2407
2412
  contextMenu = makeStudioUiRefreshMenu(suggestCompletionOptionsBtn, "context", "studio-refresh-context-anchor");
2408
- 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]);
2409
2471
  appendStudioUiRefreshMenuSection(contextMenu.menu, "Working directory", [resourceDirBtn, resourceDirLabel, resourceDirInputWrap]);
2472
+ if (!isEditorOnlyMode && sendEditorBtn) appendStudioUiRefreshMenuSection(contextMenu.menu, "Pi editor", [sendEditorBtn]);
2410
2473
  const cursorContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor only");
2411
2474
  cursorContextBtn.type = "button";
2412
2475
  cursorContextBtn.setAttribute("data-completion-context-mode", "cursor");
@@ -2484,7 +2547,6 @@
2484
2547
  actionLineTwoEl.appendChild(copyDraftBtn);
2485
2548
  if (suggestCompletionBtn) actionLineTwoEl.appendChild(suggestCompletionBtn);
2486
2549
  if (openCompanionBtn) actionLineTwoEl.appendChild(openCompanionBtn);
2487
- if (!isEditorOnlyMode && sendEditorBtn) actionLineTwoEl.appendChild(sendEditorBtn);
2488
2550
  const replActionLineEl = makeStudioUiRefreshElement("div", "studio-refresh-action-line repl-action-line");
2489
2551
  replActionLineEl.hidden = true;
2490
2552
  if (sendReplBtn) replActionLineEl.appendChild(sendReplBtn);
@@ -3213,13 +3275,26 @@
3213
3275
 
3214
3276
  function updateSourceBadge() {
3215
3277
  const label = sourceState && sourceState.label ? sourceState.label : "blank";
3216
- sourceBadgeEl.textContent = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label + (hasRefreshableFilePath() ? " · file" : "");
3278
+ const originText = (studioUiRefreshEnabled ? "Origin: " : "Editor origin: ") + label + (hasRefreshableFilePath() ? " · file" : "");
3217
3279
  const descriptor = getCurrentStudioDocumentDescriptor();
3218
3280
  if (sourceBadgeEl) {
3281
+ sourceBadgeEl.textContent = originText;
3219
3282
  sourceBadgeEl.title = descriptor.fileBacked
3220
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.")
3221
3284
  : ("Editor origin: " + label + "\nClick to reset origin and start a new independent draft while keeping the current text and local notes.");
3222
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
+ }
3223
3298
  // Show "Set working dir" button when not file-backed
3224
3299
  var isFileBacked = hasRefreshableFilePath();
3225
3300
  if (isFileBacked) {
@@ -3560,6 +3635,17 @@
3560
3635
  setStatus("Right pane content focused.");
3561
3636
  }
3562
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
+
3563
3649
  function cycleActivePaneView(direction) {
3564
3650
  if (activePane === "right") {
3565
3651
  if (!rightViewSelect || rightViewSelect.disabled) {
@@ -3858,6 +3944,16 @@
3858
3944
  return;
3859
3945
  }
3860
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
+
3861
3957
  const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
3862
3958
  if (isContentFocusShortcut) {
3863
3959
  event.preventDefault();
@@ -9908,6 +10004,14 @@
9908
10004
 
9909
10005
  fileInput.disabled = uiBusy;
9910
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();
9911
10015
  saveAsBtn.disabled = uiBusy;
9912
10016
  saveOverBtn.disabled = uiBusy || !canSaveOver;
9913
10017
  if (refreshFromDiskBtn) refreshFromDiskBtn.disabled = uiBusy || !canRefreshFromDisk;
@@ -10030,6 +10134,7 @@
10030
10134
  }
10031
10135
 
10032
10136
  function shouldRestorePersistedWorkspaceState(state) {
10137
+ if (skipInitialWorkspaceRestore) return false;
10033
10138
  if (!state || typeof state.text !== "string") return false;
10034
10139
  const storedSourceState = normalizeWorkspaceSourceState(state.sourceState);
10035
10140
  const initialIdentity = getWorkspaceStateIdentity(initialSourceState);
@@ -10951,6 +11056,7 @@
10951
11056
  else params.delete("docPath");
10952
11057
  if (nextDraftId) params.set("draftId", nextDraftId);
10953
11058
  else params.delete("draftId");
11059
+ params.delete("skipWorkspaceRestore");
10954
11060
  window.history.replaceState(null, "", currentUrl.toString());
10955
11061
  } catch {
10956
11062
  // Ignore URL-state update failures.
@@ -11166,10 +11272,10 @@
11166
11272
  appendPreviewLinkMenuButton(menu, "Open PDF preview", "open-pdf");
11167
11273
  appendPreviewLinkMenuButton(menu, "Open in new Studio tab", "open-preview-new");
11168
11274
  } else if (kind === "text") {
11169
- appendPreviewLinkMenuButton(menu, "Open in new editor", "open-new");
11275
+ appendPreviewLinkMenuButton(menu, "Open file tab", "open-new");
11170
11276
  appendPreviewLinkMenuButton(menu, "Open here", "open-here");
11171
11277
  } else if (kind === "office") {
11172
- appendPreviewLinkMenuButton(menu, "Convert in new editor", "open-new");
11278
+ appendPreviewLinkMenuButton(menu, "Convert tab", "open-new");
11173
11279
  appendPreviewLinkMenuButton(menu, "Convert here", "open-here");
11174
11280
  } else if (kind === "image") {
11175
11281
  appendPreviewLinkMenuButton(menu, "Open image preview", "open-image");
@@ -11306,10 +11412,11 @@
11306
11412
  return;
11307
11413
  }
11308
11414
  const popup = pendingWindow || window.open("", "_blank");
11415
+ const openingLabel = getPreviewLocalLinkKind(href) === "office" ? "Opening converted document…" : "Opening file tab…";
11309
11416
  try {
11310
11417
  if (popup && popup.document && popup.document.body) {
11311
- popup.document.title = "Opening linked file…";
11312
- 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>";
11313
11420
  }
11314
11421
  } catch {}
11315
11422
  try {
@@ -11322,12 +11429,12 @@
11322
11429
  try {
11323
11430
  popup.opener = null;
11324
11431
  popup.location.href = targetUrl;
11325
- 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");
11326
11433
  return;
11327
11434
  } catch {}
11328
11435
  }
11329
11436
  window.open(targetUrl, "_blank", "noopener");
11330
- 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");
11331
11438
  } catch (error) {
11332
11439
  if (popup && !popup.closed) {
11333
11440
  try { popup.close(); } catch {}
@@ -18502,11 +18609,11 @@
18502
18609
  const opened = navigatePendingCompanionWindow(responseRequestId, targetUrl);
18503
18610
  const readyMessage = typeof message.message === "string" && message.message.trim()
18504
18611
  ? message.message.trim()
18505
- : "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.";
18506
18613
  setStatus(
18507
18614
  opened
18508
18615
  ? readyMessage
18509
- : (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."),
18510
18617
  opened ? "success" : "warning",
18511
18618
  );
18512
18619
  return;
@@ -18826,8 +18933,8 @@
18826
18933
  try {
18827
18934
  companionWindow = window.open("", "_blank");
18828
18935
  if (companionWindow && companionWindow.document && companionWindow.document.body) {
18829
- companionWindow.document.title = "Opening companion editor…";
18830
- 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>";
18831
18938
  }
18832
18939
  } catch {
18833
18940
  companionWindow = null;
@@ -18883,6 +18990,28 @@
18883
18990
  }
18884
18991
  }
18885
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
+
18886
19015
  function describeSourceForAnnotation() {
18887
19016
  if (sourceState.source === "file" && sourceState.label) {
18888
19017
  return "file " + sourceState.label;
@@ -19498,27 +19627,10 @@
19498
19627
 
19499
19628
  if (openCompanionBtn) {
19500
19629
  openCompanionBtn.addEventListener("click", () => {
19501
- const content = sourceTextEl.value;
19502
-
19503
- const requestId = beginUiAction("open_editor_only");
19504
- if (!requestId) return;
19505
- openPendingCompanionWindow(requestId);
19506
-
19507
- const sent = sendMessage({
19508
- type: "open_editor_only_request",
19509
- requestId,
19510
- content,
19511
- label: sourceState && sourceState.label ? sourceState.label : "current editor",
19512
- path: sourceState && sourceState.path ? sourceState.path : undefined,
19630
+ requestOpenEditorOnlyDocument("", {
19631
+ label: "blank",
19513
19632
  resourceDir: getCurrentResourceDirValue() || undefined,
19514
19633
  });
19515
-
19516
- if (!sent) {
19517
- closePendingCompanionWindow(requestId);
19518
- pendingRequestId = null;
19519
- pendingKind = null;
19520
- setBusy(false);
19521
- }
19522
19634
  });
19523
19635
  }
19524
19636
 
@@ -20038,7 +20150,7 @@
20038
20150
  }
20039
20151
  if (sourceBadgeEl) {
20040
20152
  sourceBadgeEl.addEventListener("click", () => {
20041
- resetEditorOrigin();
20153
+ if (!studioUiRefreshEnabled) resetEditorOrigin();
20042
20154
  });
20043
20155
  }
20044
20156
  if (resourceDirBtn) {
package/client/studio.css CHANGED
@@ -5176,6 +5176,15 @@
5176
5176
  font-weight: 450;
5177
5177
  }
5178
5178
 
5179
+ body.studio-ui-refresh .studio-refresh-menu-item > .source-origin-summary {
5180
+ width: 100%;
5181
+ border-color: var(--border-subtle);
5182
+ background: var(--panel-2);
5183
+ color: var(--studio-info-text, var(--muted));
5184
+ white-space: normal;
5185
+ line-height: 1.35;
5186
+ }
5187
+
5179
5188
  body.studio-ui-refresh .studio-refresh-menu #critiqueBtn {
5180
5189
  justify-content: flex-start;
5181
5190
  text-align: left;
package/index.ts CHANGED
@@ -6991,7 +6991,7 @@ async function respondLocalPreviewLinkJson(req: IncomingMessage, res: ServerResp
6991
6991
  }
6992
6992
  const document = buildStudioLocalResourcePreviewDocument(resource);
6993
6993
  const docId = storeTransientStudioDocument(document);
6994
- const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId);
6994
+ const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId, { skipWorkspaceRestore: true });
6995
6995
  const parsedUrl = new URL(url);
6996
6996
  respondJson(res, 200, {
6997
6997
  ...basePayload,
@@ -7056,7 +7056,7 @@ async function respondLocalPreviewLinkJson(req: IncomingMessage, res: ServerResp
7056
7056
  }
7057
7057
 
7058
7058
  const docId = storeTransientStudioDocument(document);
7059
- const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId);
7059
+ const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId, { skipWorkspaceRestore: true });
7060
7060
  const parsedUrl = new URL(url);
7061
7061
  respondJson(res, 200, {
7062
7062
  ...basePayload,
@@ -9525,6 +9525,7 @@ function buildStudioUrl(
9525
9525
  mode: StudioUiMode = "full",
9526
9526
  doc?: InitialStudioDocument | null,
9527
9527
  docId?: string,
9528
+ options?: { skipWorkspaceRestore?: boolean },
9528
9529
  ): string {
9529
9530
  const params = new URLSearchParams({ token });
9530
9531
  if (mode !== "full") params.set("mode", mode);
@@ -9534,6 +9535,7 @@ function buildStudioUrl(
9534
9535
  if (doc?.path) params.set("docPath", doc.path);
9535
9536
  if (doc?.draftId) params.set("draftId", doc.draftId);
9536
9537
  if (doc?.resourceDir) params.set("resourceDir", doc.resourceDir);
9538
+ if (options?.skipWorkspaceRestore) params.set("skipWorkspaceRestore", "1");
9537
9539
  return `http://127.0.0.1:${port}/?${params.toString()}`;
9538
9540
  }
9539
9541
 
@@ -9994,8 +9996,8 @@ ${cssVarsBlock}
9994
9996
  <option value="cursor" selected>Context: editor only</option>
9995
9997
  <option value="session">Context: editor + latest response</option>
9996
9998
  </select>
9997
- <button id="openCompanionBtn" type="button" title="Open a detached copy of the current editor text in a new editor-only Studio tab.">New editor</button>
9998
- <button id="sendEditorBtn" type="button">Send to pi editor</button>
9999
+ <button id="openCompanionBtn" type="button" title="Open a blank editor-only Studio tab.">New editor tab</button>
10000
+ <button id="sendEditorBtn" type="button">Send current text to Pi editor</button>
9999
10001
  </div>
10000
10002
  <div class="source-actions-row">
10001
10003
  <button id="insertHeaderBtn" type="button" title="Insert annotated-reply protocol header (source metadata, [an: ...] syntax hint, precedence note, and end marker).">Annotation header</button>
@@ -10116,7 +10118,7 @@ ${cssVarsBlock}
10116
10118
  <div class="scratchpad-header">
10117
10119
  <div>
10118
10120
  <h2 id="reviewNotesTitle">Comments</h2>
10119
- <p class="scratchpad-description">Local comments for editor text and editor previews. They stay out of the text; source-anchored comments can be converted into inline <span class="review-notes-inline-token">[an: ...]</span> annotations.</p>
10121
+ <p class="scratchpad-description">Local comments for editor text and editor previews. They stay out of the text; can be converted into inline <span class="review-notes-inline-token">[an: ...]</span> annotations.</p>
10120
10122
  </div>
10121
10123
  <button id="reviewNotesCloseBtn" type="button" class="scratchpad-close-btn" aria-label="Hide comments" title="Hide comments">✕</button>
10122
10124
  </div>
@@ -10145,7 +10147,7 @@ ${cssVarsBlock}
10145
10147
  <section id="rightPane">
10146
10148
  <div id="rightSectionHeader" class="section-header">
10147
10149
  <div class="section-header-main">
10148
- <select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. Shortcut: F7 when the right pane is active; F6 switches panes.">
10150
+ <select id="rightViewSelect" aria-label="Response view mode" title="Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview.">
10149
10151
  <option value="markdown">Response (Raw)</option>
10150
10152
  <option value="preview" selected>Response (Preview)</option>
10151
10153
  <option value="editor-preview">Editor (Preview)</option>
@@ -10237,6 +10239,7 @@ ${cssVarsBlock}
10237
10239
  <dl>
10238
10240
  <div><dt>F6</dt><dd>Switch between editor and right pane</dd></div>
10239
10241
  <div><dt>F7 / Shift+F7</dt><dd>Cycle the active pane's view</dd></div>
10242
+ <div><dt>Cmd/Ctrl+Alt+P</dt><dd>Switch the right pane directly to Preview</dd></div>
10240
10243
  <div><dt>F8</dt><dd>Focus editor text</dd></div>
10241
10244
  <div><dt>Shift+F8</dt><dd>Focus right-pane content</dd></div>
10242
10245
  <div><dt>F9</dt><dd>Toggle Zen mode</dd></div>
@@ -11696,7 +11699,7 @@ export default function (pi: ExtensionAPI) {
11696
11699
  resourceDir,
11697
11700
  };
11698
11701
  const docId = storeTransientStudioDocument(document);
11699
- const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId);
11702
+ const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId, { skipWorkspaceRestore: true });
11700
11703
  const parsedUrl = new URL(url);
11701
11704
  sendToClient(client, {
11702
11705
  type: "editor_only_ready",
@@ -11704,8 +11707,8 @@ export default function (pi: ExtensionAPI) {
11704
11707
  url,
11705
11708
  relativeUrl: `${parsedUrl.pathname}${parsedUrl.search}`,
11706
11709
  message: hasContent
11707
- ? "Companion editor is ready with a detached copy of the current editor text."
11708
- : "Blank companion editor is ready.",
11710
+ ? "Editor tab is ready with a detached copy of the current editor text."
11711
+ : "Blank editor tab is ready.",
11709
11712
  });
11710
11713
  return;
11711
11714
  }
@@ -13070,7 +13073,7 @@ export default function (pi: ExtensionAPI) {
13070
13073
  resourceDir: dirname(exportedPath),
13071
13074
  };
13072
13075
  const docId = storeTransientStudioDocument(document);
13073
- const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId);
13076
+ const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId, { skipWorkspaceRestore: true });
13074
13077
  const parsedUrl = new URL(url);
13075
13078
  respondJson(res, 200, {
13076
13079
  ok: true,
@@ -13217,7 +13220,7 @@ export default function (pi: ExtensionAPI) {
13217
13220
  draftId: exportedPath ? undefined : createStudioDraftId(),
13218
13221
  };
13219
13222
  const docId = storeTransientStudioDocument(document);
13220
- const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId);
13223
+ const url = buildStudioUrl(serverState.port, serverState.token, "editor-only", document, docId, { skipWorkspaceRestore: true });
13221
13224
  const parsedUrl = new URL(url);
13222
13225
  respondJson(res, 200, {
13223
13226
  ok: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.9.21",
3
+ "version": "0.9.22",
4
4
  "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
5
5
  "type": "module",
6
6
  "license": "MIT",