pi-studio 0.5.50 → 0.5.51

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
@@ -4,6 +4,19 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.51] — 2026-04-09
8
+
9
+ ### Added
10
+ - Raw-editor selection actions now also include a transient **Jump** control beside **Comment** when the right pane is showing **Editor (Preview)**, so editor selections can reveal the corresponding preview location without creating a local comment first.
11
+
12
+ ### Changed
13
+ - Selection-time **Comment** / **Jump** affordances are now more consistent across raw editor and preview: preview-side actions use a fixed top-right action area while remaining transient and selection-only.
14
+ - Standalone selection **Jump** actions now use the same preview highlight/reveal treatment as comment-card jumps, making it easier to stay oriented after jumping in either direction.
15
+
16
+ ### Fixed
17
+ - Preview-side selection actions are once again hidden unless an appropriate preview text selection is active.
18
+ - Code/text/diff preview selection mapping now preserves literal underscores in identifiers such as `add_subplot`, avoiding comment/jump failures caused by markdown-style underscore stripping.
19
+
7
20
  ## [0.5.50] — 2026-04-09
8
21
 
9
22
  ### Added
package/README.md CHANGED
@@ -18,7 +18,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
18
18
  - Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you just want extra editing/preview surfaces
19
19
  - Runs editor text directly, or asks for structured critique (auto/writing/code focus)
20
20
  - Includes a local persistent scratchpad for quick notes you want to keep out of the main editor until you're ready to copy or insert them
21
- - Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with **Comment** actions from raw-editor selections plus editor-preview selections for Markdown, LaTeX, and code/text/diff previews, alongside a transient preview **Jump** action and optional inline `[an: ...]` toggles when you want comments reflected in the document text
21
+ - Includes local comments anchored to selections/lines, shown in a docked **Comments** rail, with transient **Comment** / **Jump** actions from raw-editor selections plus editor-preview selections for Markdown, LaTeX, and code/text/diff previews, alongside optional inline `[an: ...]` toggles when you want comments reflected in the document text
22
22
  - Browses response history (`Prev/Next/Last`) and loads either:
23
23
  - response text
24
24
  - critique notes/full critique
@@ -53,7 +53,9 @@
53
53
  const lineNumberGutterContentEl = document.getElementById("lineNumberGutterContent");
54
54
  const lineNumberMeasureEl = document.getElementById("lineNumberMeasure");
55
55
  const sourcePreviewEl = document.getElementById("sourcePreview");
56
+ const editorSelectionActionsEl = document.getElementById("editorSelectionActions");
56
57
  const editorSelectionCommentBtn = document.getElementById("editorSelectionCommentBtn");
58
+ const editorSelectionJumpBtn = document.getElementById("editorSelectionJumpBtn");
57
59
  const leftPaneEl = document.getElementById("leftPane");
58
60
  const rightPaneEl = document.getElementById("rightPane");
59
61
  const sourceBadgeEl = document.getElementById("sourceBadge");
@@ -3859,11 +3861,6 @@
3859
3861
  + " data-review-note-line-end='" + String(lineNumber) + "'"
3860
3862
  + " data-preview-comment-kind='" + escapeHtml(kind) + "'"
3861
3863
  + ">"
3862
- + "<div class='preview-comment-controls'>"
3863
- + "<button type='button' class='preview-comment-summary' hidden></button>"
3864
- + "<button type='button' class='preview-comment-add' data-preview-comment-action='comment'>Comment</button>"
3865
- + "<button type='button' class='preview-comment-jump' data-preview-comment-action='jump'>Jump</button>"
3866
- + "</div>"
3867
3864
  + "<div class='preview-comment-block-content preview-code-line-content'>" + lineHtml + "</div>"
3868
3865
  + "</div>",
3869
3866
  );
@@ -3879,6 +3876,7 @@
3879
3876
  clearPreviewJumpHighlight(targetEl);
3880
3877
  finishPreviewRender(targetEl);
3881
3878
  targetEl.innerHTML = buildCodePreviewHtmlWithCommentBlocks(text, editorLanguage || "");
3879
+ ensurePreviewSelectionActions(targetEl);
3882
3880
  updatePreviewCommentBlocksForElement(targetEl);
3883
3881
  if (pane === "response") {
3884
3882
  applyPendingResponseScrollReset();
@@ -5575,8 +5573,23 @@
5575
5573
  return bestIndex;
5576
5574
  }
5577
5575
 
5576
+ function buildLiteralPreviewDisplayMap(text, rawOffsets) {
5577
+ const source = String(text || "");
5578
+ const rawMap = Array.isArray(rawOffsets) ? rawOffsets : [];
5579
+ const charStarts = [];
5580
+ const charEnds = [];
5581
+ for (let i = 0; i < source.length; i += 1) {
5582
+ charStarts.push(rawMap[i]);
5583
+ charEnds.push(rawMap[i] + 1);
5584
+ }
5585
+ return buildNormalizedPreviewDisplayMap(source, charStarts, charEnds);
5586
+ }
5587
+
5578
5588
  function buildPreviewSelectionDisplayMap(blockText, kind) {
5579
5589
  const body = buildPreviewSelectionSourceBody(blockText, kind);
5590
+ if (kind === "code-line" || kind === "diff-line" || kind === "text-line") {
5591
+ return buildLiteralPreviewDisplayMap(body.text, body.rawOffsets);
5592
+ }
5580
5593
  const inlineMap = buildPreviewInlineDisplayMap(body.text, body.rawOffsets);
5581
5594
  return buildNormalizedPreviewDisplayMap(inlineMap.text, inlineMap.charStarts, inlineMap.charEnds);
5582
5595
  }
@@ -5593,6 +5606,7 @@
5593
5606
  function getPreviewCommentSelectionKey(selection) {
5594
5607
  if (!selection) return "";
5595
5608
  return [
5609
+ String(selection.paneId || ""),
5596
5610
  String(selection.blockKey || ""),
5597
5611
  String(selection.selectionStart || 0),
5598
5612
  String(selection.selectionEnd || 0),
@@ -5620,6 +5634,96 @@
5620
5634
  : null;
5621
5635
  }
5622
5636
 
5637
+ function getPreviewSelectionPaneIdForNode(node) {
5638
+ if (!node) return "";
5639
+ const element = node instanceof Element ? node : node.parentElement;
5640
+ const paneEl = element && typeof element.closest === "function"
5641
+ ? element.closest("#sourcePreview, #critiqueView")
5642
+ : null;
5643
+ return paneEl && paneEl.id ? String(paneEl.id) : "";
5644
+ }
5645
+
5646
+ function getPreviewSelectionPaneElement(paneId) {
5647
+ if (paneId === "sourcePreview") return sourcePreviewEl;
5648
+ if (paneId === "critiqueView") return critiqueViewEl;
5649
+ return null;
5650
+ }
5651
+
5652
+ function getActivePreviewSelectionForPane(paneId) {
5653
+ if (!paneId) return null;
5654
+ return activePreviewCommentSelection && activePreviewCommentSelection.paneId === paneId
5655
+ ? activePreviewCommentSelection
5656
+ : null;
5657
+ }
5658
+
5659
+ function ensurePreviewSelectionActions(targetEl) {
5660
+ if (!targetEl || typeof document.createElement !== "function") return null;
5661
+ const paneId = targetEl.id ? String(targetEl.id) : "";
5662
+ if (!paneId) return null;
5663
+ const existing = Array.from(targetEl.children || []).find((child) => child.classList && child.classList.contains("preview-selection-actions"));
5664
+ if (existing) {
5665
+ existing.dataset.previewPane = paneId;
5666
+ return existing;
5667
+ }
5668
+
5669
+ const actionsEl = document.createElement("div");
5670
+ actionsEl.className = "preview-selection-actions";
5671
+ actionsEl.dataset.previewPane = paneId;
5672
+ actionsEl.hidden = true;
5673
+
5674
+ const commentBtn = document.createElement("button");
5675
+ commentBtn.type = "button";
5676
+ commentBtn.className = "preview-comment-add";
5677
+ commentBtn.dataset.previewCommentAction = "comment";
5678
+ commentBtn.textContent = "Comment";
5679
+ commentBtn.hidden = true;
5680
+ actionsEl.appendChild(commentBtn);
5681
+
5682
+ const jumpBtn = document.createElement("button");
5683
+ jumpBtn.type = "button";
5684
+ jumpBtn.className = "preview-comment-jump";
5685
+ jumpBtn.dataset.previewCommentAction = "jump";
5686
+ jumpBtn.textContent = "Jump";
5687
+ jumpBtn.hidden = true;
5688
+ actionsEl.appendChild(jumpBtn);
5689
+
5690
+ targetEl.insertBefore(actionsEl, targetEl.firstChild || null);
5691
+ return actionsEl;
5692
+ }
5693
+
5694
+ function updatePreviewSelectionActions(targetEl) {
5695
+ if (!targetEl) return;
5696
+ const actionsEl = ensurePreviewSelectionActions(targetEl);
5697
+ if (!actionsEl) return;
5698
+ const paneId = targetEl.id ? String(targetEl.id) : "";
5699
+ const selection = getActivePreviewSelectionForPane(paneId);
5700
+ const commentBtn = actionsEl.querySelector(".preview-comment-add");
5701
+ const jumpBtn = actionsEl.querySelector(".preview-comment-jump");
5702
+ if (!selection) {
5703
+ actionsEl.hidden = true;
5704
+ if (commentBtn) commentBtn.hidden = true;
5705
+ if (jumpBtn) jumpBtn.hidden = true;
5706
+ return;
5707
+ }
5708
+ const lineLabel = summarizeReviewNoteAnchor(selection).toLowerCase();
5709
+ const blockKindLabel = getPreviewCommentBlockKindLabel(selection.previewCommentKind || "paragraph");
5710
+ actionsEl.hidden = false;
5711
+ if (commentBtn) {
5712
+ commentBtn.hidden = false;
5713
+ commentBtn.dataset.previewCommentMode = "selection";
5714
+ commentBtn.dataset.previewPane = paneId;
5715
+ commentBtn.title = "Add a local comment from the current preview selection on this " + blockKindLabel + " (" + lineLabel + ").";
5716
+ commentBtn.setAttribute("aria-label", commentBtn.title || "Comment");
5717
+ }
5718
+ if (jumpBtn) {
5719
+ jumpBtn.hidden = false;
5720
+ jumpBtn.dataset.previewCommentMode = "selection";
5721
+ jumpBtn.dataset.previewPane = paneId;
5722
+ jumpBtn.title = "Jump to the current preview selection on this " + blockKindLabel + " in the raw editor (" + lineLabel + ").";
5723
+ jumpBtn.setAttribute("aria-label", jumpBtn.title || "Jump");
5724
+ }
5725
+ }
5726
+
5623
5727
  function unwrapPreviewJumpHighlightElement(element) {
5624
5728
  if (!element || !element.parentNode) return;
5625
5729
  const parent = element.parentNode;
@@ -6449,52 +6553,26 @@
6449
6553
 
6450
6554
  function updatePreviewCommentBlockState(blockEl, sourceText, displayNotes) {
6451
6555
  if (!blockEl || !blockEl.dataset) return;
6452
- const lineStart = Math.max(1, Number(blockEl.dataset.reviewNoteLineStart) || 1);
6453
- const lineEnd = Math.max(lineStart, Number(blockEl.dataset.reviewNoteLineEnd) || lineStart);
6454
- const summaryBtn = blockEl.querySelector(".preview-comment-summary");
6455
- const addBtn = blockEl.querySelector(".preview-comment-add");
6456
- const jumpBtn = blockEl.querySelector(".preview-comment-jump");
6457
- const lineLabel = summarizeReviewNoteAnchor({ lineStart: lineStart, lineEnd: lineEnd }).toLowerCase();
6458
- const blockKindLabel = getPreviewCommentBlockKindLabel(blockEl.dataset.previewCommentKind || "paragraph");
6459
6556
  const blockKey = getPreviewCommentBlockKey(blockEl);
6460
- const hasSelection = Boolean(activePreviewCommentSelection && activePreviewCommentSelection.blockKey === blockKey);
6557
+ const paneId = getPreviewSelectionPaneIdForNode(blockEl);
6558
+ const hasSelection = Boolean(
6559
+ activePreviewCommentSelection
6560
+ && activePreviewCommentSelection.paneId === paneId
6561
+ && activePreviewCommentSelection.blockKey === blockKey
6562
+ );
6461
6563
 
6462
6564
  blockEl.classList.remove("has-comments");
6463
6565
  blockEl.classList.toggle("has-selection", hasSelection);
6464
-
6465
- if (summaryBtn) {
6466
- summaryBtn.hidden = true;
6467
- summaryBtn.textContent = "";
6468
- summaryBtn.dataset.reviewNoteId = "";
6469
- }
6470
-
6471
- if (addBtn) {
6472
- addBtn.hidden = !hasSelection;
6473
- addBtn.textContent = "Comment";
6474
- addBtn.dataset.previewCommentMode = hasSelection ? "selection" : "";
6475
- addBtn.title = hasSelection
6476
- ? ("Add a local comment from the current preview selection on this " + blockKindLabel + " (" + lineLabel + ").")
6477
- : "";
6478
- addBtn.setAttribute("aria-label", addBtn.title || "Comment");
6479
- }
6480
-
6481
- if (jumpBtn) {
6482
- jumpBtn.hidden = !hasSelection;
6483
- jumpBtn.textContent = "Jump";
6484
- jumpBtn.dataset.previewCommentMode = hasSelection ? "selection" : "";
6485
- jumpBtn.title = hasSelection
6486
- ? ("Jump to the current preview selection on this " + blockKindLabel + " in the raw editor (" + lineLabel + ").")
6487
- : "";
6488
- jumpBtn.setAttribute("aria-label", jumpBtn.title || "Jump");
6489
- }
6490
6566
  }
6491
6567
 
6492
6568
  function updatePreviewCommentBlocksForElement(targetEl) {
6493
6569
  if (!targetEl || typeof targetEl.querySelectorAll !== "function") return;
6570
+ ensurePreviewSelectionActions(targetEl);
6494
6571
  const sourceText = String(sourceTextEl && sourceTextEl.value ? sourceTextEl.value : "");
6495
6572
  Array.from(targetEl.querySelectorAll(".preview-comment-block")).forEach((blockEl) => {
6496
6573
  updatePreviewCommentBlockState(blockEl, sourceText);
6497
6574
  });
6575
+ updatePreviewSelectionActions(targetEl);
6498
6576
  }
6499
6577
 
6500
6578
  function decorateRenderedEditorPreviewComments(targetEl, sourceText) {
@@ -6522,43 +6600,20 @@
6522
6600
  wrapper.dataset.reviewNoteLineEnd = String(sourceBlock.lineEnd);
6523
6601
  wrapper.dataset.previewCommentKind = sourceBlock.kind;
6524
6602
 
6525
- const controls = document.createElement("div");
6526
- controls.className = "preview-comment-controls";
6527
-
6528
- const summaryBtn = document.createElement("button");
6529
- summaryBtn.type = "button";
6530
- summaryBtn.className = "preview-comment-summary";
6531
- summaryBtn.hidden = true;
6532
- controls.appendChild(summaryBtn);
6533
-
6534
- const addBtn = document.createElement("button");
6535
- addBtn.type = "button";
6536
- addBtn.className = "preview-comment-add";
6537
- addBtn.dataset.previewCommentAction = "comment";
6538
- addBtn.textContent = "Comment";
6539
- controls.appendChild(addBtn);
6540
-
6541
- const jumpBtn = document.createElement("button");
6542
- jumpBtn.type = "button";
6543
- jumpBtn.className = "preview-comment-jump";
6544
- jumpBtn.dataset.previewCommentAction = "jump";
6545
- jumpBtn.textContent = "Jump";
6546
- controls.appendChild(jumpBtn);
6547
-
6548
6603
  originalElement.replaceWith(wrapper);
6549
- wrapper.appendChild(controls);
6550
6604
  originalElement.classList.add("preview-comment-block-content");
6551
6605
  wrapper.appendChild(originalElement);
6552
6606
  }
6553
6607
 
6608
+ ensurePreviewSelectionActions(targetEl);
6554
6609
  updatePreviewCommentBlocksForElement(targetEl);
6555
6610
  }
6556
6611
 
6557
6612
  function refreshRenderedEditorPreviewComments() {
6558
- if (sourcePreviewEl && !sourcePreviewEl.hidden) {
6613
+ if (sourcePreviewEl) {
6559
6614
  updatePreviewCommentBlocksForElement(sourcePreviewEl);
6560
6615
  }
6561
- if (critiqueViewEl && rightView === "editor-preview") {
6616
+ if (critiqueViewEl) {
6562
6617
  updatePreviewCommentBlocksForElement(critiqueViewEl);
6563
6618
  }
6564
6619
  }
@@ -6792,10 +6847,11 @@
6792
6847
  }
6793
6848
 
6794
6849
  function revealReviewNoteInPreview(note) {
6795
- if (!supportsPreviewCommentsForCurrentEditor()) return;
6850
+ if (!supportsPreviewCommentsForCurrentEditor()) return false;
6796
6851
  if (rightView === "editor-preview" && critiqueViewEl && critiqueViewEl.isConnected) {
6797
- revealReviewNoteInPreviewElement(critiqueViewEl, note);
6852
+ return revealReviewNoteInPreviewElement(critiqueViewEl, note);
6798
6853
  }
6854
+ return false;
6799
6855
  }
6800
6856
 
6801
6857
  function updateActivePreviewCommentSelectionFromDom() {
@@ -6827,7 +6883,9 @@
6827
6883
 
6828
6884
  setActivePreviewCommentSelection({
6829
6885
  ...anchor,
6886
+ paneId: getPreviewSelectionPaneIdForNode(startBlock),
6830
6887
  blockKey: getPreviewCommentBlockKey(startBlock),
6888
+ previewCommentKind: String(startBlock.dataset && startBlock.dataset.previewCommentKind || "paragraph"),
6831
6889
  });
6832
6890
  }
6833
6891
 
@@ -6922,11 +6980,27 @@
6922
6980
  && typeof sourceTextEl.selectionEnd === "number"
6923
6981
  && sourceTextEl.selectionEnd > sourceTextEl.selectionStart
6924
6982
  );
6983
+ const canJumpToPreview = Boolean(
6984
+ hasSelection
6985
+ && rightView === "editor-preview"
6986
+ && critiqueViewEl
6987
+ && supportsPreviewCommentsForCurrentEditor()
6988
+ );
6925
6989
  editorSelectionCommentBtn.hidden = !hasSelection;
6990
+ if (editorSelectionJumpBtn) {
6991
+ editorSelectionJumpBtn.hidden = !canJumpToPreview;
6992
+ }
6993
+ if (editorSelectionActionsEl) {
6994
+ editorSelectionActionsEl.hidden = !hasSelection;
6995
+ }
6926
6996
  if (hasSelection) {
6927
6997
  editorSelectionCommentBtn.title = "Create a new local comment from the current editor selection.";
6928
6998
  editorSelectionCommentBtn.setAttribute("aria-label", editorSelectionCommentBtn.title);
6929
6999
  }
7000
+ if (editorSelectionJumpBtn && canJumpToPreview) {
7001
+ editorSelectionJumpBtn.title = "Jump to the current editor selection in the preview.";
7002
+ editorSelectionJumpBtn.setAttribute("aria-label", editorSelectionJumpBtn.title);
7003
+ }
6930
7004
  }
6931
7005
 
6932
7006
  function clearSuppressedEditorSelectionComment() {
@@ -7143,17 +7217,12 @@
7143
7217
  });
7144
7218
  }
7145
7219
 
7146
- function getActivePreviewSelectionAnchorForBlock(blockEl) {
7147
- if (!blockEl) return null;
7148
- const blockKey = getPreviewCommentBlockKey(blockEl);
7149
- return activePreviewCommentSelection && activePreviewCommentSelection.blockKey === blockKey
7150
- ? activePreviewCommentSelection
7151
- : null;
7220
+ function getActivePreviewSelectionAnchorForPane(paneId) {
7221
+ return getActivePreviewSelectionForPane(paneId);
7152
7222
  }
7153
7223
 
7154
- function addReviewNoteFromPreviewSelection(blockEl) {
7155
- if (!blockEl) return null;
7156
- const anchor = getActivePreviewSelectionAnchorForBlock(blockEl);
7224
+ function addReviewNoteFromPreviewSelection(paneId) {
7225
+ const anchor = getActivePreviewSelectionAnchorForPane(paneId);
7157
7226
  if (!anchor) {
7158
7227
  setStatus("Select some preview text within a single block first.", "warning");
7159
7228
  return null;
@@ -7189,6 +7258,12 @@
7189
7258
  if (editorSelectionCommentBtn) {
7190
7259
  editorSelectionCommentBtn.hidden = true;
7191
7260
  }
7261
+ if (editorSelectionJumpBtn) {
7262
+ editorSelectionJumpBtn.hidden = true;
7263
+ }
7264
+ if (editorSelectionActionsEl) {
7265
+ editorSelectionActionsEl.hidden = true;
7266
+ }
7192
7267
  const shouldOpenReviewNotes = !isReviewNotesOpen();
7193
7268
  pendingReviewNoteFocusId = note.id;
7194
7269
  setReviewNotes(reviewNotes.concat([note]));
@@ -7218,6 +7293,30 @@
7218
7293
  });
7219
7294
  }
7220
7295
 
7296
+ function jumpToEditorSelectionInPreview() {
7297
+ if (editorView !== "markdown") {
7298
+ setStatus("Switch to Editor (Raw) before jumping from an editor selection.", "warning");
7299
+ return false;
7300
+ }
7301
+ if (rightView !== "editor-preview" || !critiqueViewEl || !supportsPreviewCommentsForCurrentEditor()) {
7302
+ setStatus("Open Editor (Preview) on the right to jump the current editor selection there.", "warning");
7303
+ return false;
7304
+ }
7305
+ const anchor = getEditorAnchorForReviewNote();
7306
+ const jumped = revealReviewNoteInPreview(anchor);
7307
+ if (!jumped) {
7308
+ setStatus("Could not find the current editor selection in the preview.", "warning");
7309
+ return false;
7310
+ }
7311
+ const current = String(sourceTextEl.value || "");
7312
+ const range = resolveReviewNoteRange(anchor, current);
7313
+ if (range) {
7314
+ scrollEditorRangeIntoView(range);
7315
+ }
7316
+ setStatus("Jumped to the current editor selection in the preview.", "success");
7317
+ return true;
7318
+ }
7319
+
7221
7320
  function addReviewNoteFromEditorLine() {
7222
7321
  if (editorView !== "markdown") {
7223
7322
  setStatus("Switch to Editor (Raw) before adding a line comment.", "warning");
@@ -7260,23 +7359,32 @@
7260
7359
  return true;
7261
7360
  }
7262
7361
 
7263
- function jumpToPreviewSelection(blockEl) {
7264
- if (!blockEl) return false;
7265
- const anchor = getActivePreviewSelectionAnchorForBlock(blockEl);
7362
+ function jumpToPreviewSelection(paneId) {
7363
+ const anchor = getActivePreviewSelectionAnchorForPane(paneId);
7266
7364
  if (!anchor) {
7267
7365
  setStatus("Select some preview text within a single block first.", "warning");
7268
7366
  return false;
7269
7367
  }
7270
- const jumped = jumpToReviewAnchor(anchor, {
7368
+ const previewNote = normalizeReviewNote(anchor);
7369
+ const jumped = jumpToReviewAnchor(previewNote, {
7271
7370
  statusMessage: "Jumped to preview selection in the raw editor.",
7371
+ afterJump: () => {
7372
+ const paneEl = getPreviewSelectionPaneElement(paneId);
7373
+ if (paneEl && previewNote) {
7374
+ revealReviewNoteInPreviewElement(paneEl, previewNote);
7375
+ }
7376
+ const schedule = typeof window.requestAnimationFrame === "function"
7377
+ ? window.requestAnimationFrame.bind(window)
7378
+ : (cb) => window.setTimeout(cb, 16);
7379
+ schedule(() => {
7380
+ const selection = typeof window.getSelection === "function" ? window.getSelection() : null;
7381
+ if (selection && typeof selection.removeAllRanges === "function") {
7382
+ selection.removeAllRanges();
7383
+ }
7384
+ clearPreviewCommentSelection();
7385
+ });
7386
+ },
7272
7387
  });
7273
- if (jumped) {
7274
- const selection = typeof window.getSelection === "function" ? window.getSelection() : null;
7275
- if (selection && typeof selection.removeAllRanges === "function") {
7276
- selection.removeAllRanges();
7277
- }
7278
- clearPreviewCommentSelection();
7279
- }
7280
7388
  return jumped;
7281
7389
  }
7282
7390
 
@@ -9203,6 +9311,15 @@
9203
9311
  });
9204
9312
  }
9205
9313
 
9314
+ if (editorSelectionJumpBtn) {
9315
+ editorSelectionJumpBtn.addEventListener("mousedown", (event) => {
9316
+ event.preventDefault();
9317
+ });
9318
+ editorSelectionJumpBtn.addEventListener("click", () => {
9319
+ jumpToEditorSelectionInPreview();
9320
+ });
9321
+ }
9322
+
9206
9323
  if (reviewNotesInlineAllBtn) {
9207
9324
  reviewNotesInlineAllBtn.addEventListener("click", () => {
9208
9325
  toggleAllReviewNotesInlineAnnotations();
@@ -9231,18 +9348,17 @@
9231
9348
  const target = event.target;
9232
9349
  const actionBtn = target instanceof Element ? target.closest(".preview-comment-add, .preview-comment-jump, .preview-comment-summary") : null;
9233
9350
  if (!actionBtn) return;
9234
- const blockEl = actionBtn.closest(".preview-comment-block");
9235
- if (!blockEl) return;
9236
9351
  event.preventDefault();
9237
9352
  event.stopPropagation();
9238
9353
  const mode = String(actionBtn.dataset && actionBtn.dataset.previewCommentMode ? actionBtn.dataset.previewCommentMode : "");
9239
9354
  if (!mode || !mode.startsWith("selection")) return;
9355
+ const paneId = String(actionBtn.dataset && actionBtn.dataset.previewPane ? actionBtn.dataset.previewPane : "");
9240
9356
  const action = String(actionBtn.dataset && actionBtn.dataset.previewCommentAction ? actionBtn.dataset.previewCommentAction : "comment");
9241
9357
  if (action === "jump") {
9242
- jumpToPreviewSelection(blockEl);
9358
+ jumpToPreviewSelection(paneId);
9243
9359
  return;
9244
9360
  }
9245
- addReviewNoteFromPreviewSelection(blockEl);
9361
+ addReviewNoteFromPreviewSelection(paneId);
9246
9362
  }
9247
9363
 
9248
9364
  if (leftPaneEl) {
package/client/studio.css CHANGED
@@ -661,11 +661,17 @@
661
661
  -webkit-text-fill-color: transparent;
662
662
  }
663
663
 
664
- .editor-selection-comment-btn {
664
+ .editor-selection-actions {
665
665
  position: absolute;
666
666
  top: 12px;
667
667
  right: 12px;
668
668
  z-index: 4;
669
+ display: inline-flex;
670
+ align-items: center;
671
+ gap: 8px;
672
+ }
673
+
674
+ .editor-selection-action-btn {
669
675
  min-height: 24px;
670
676
  border-radius: 999px;
671
677
  padding: 0 10px;
@@ -677,13 +683,22 @@
677
683
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
678
684
  }
679
685
 
680
- .editor-selection-comment-btn:hover,
681
- .editor-selection-comment-btn:focus-visible {
686
+ .editor-selection-action-btn:hover,
687
+ .editor-selection-action-btn:focus-visible {
682
688
  background: var(--accent-soft-strong);
683
689
  color: var(--accent);
684
690
  border-color: var(--accent);
685
691
  }
686
692
 
693
+ .editor-selection-actions[hidden],
694
+ .editor-selection-action-btn[hidden],
695
+ .preview-selection-actions[hidden],
696
+ .preview-comment-add[hidden],
697
+ .preview-comment-jump[hidden],
698
+ .preview-comment-summary[hidden] {
699
+ display: none !important;
700
+ }
701
+
687
702
  .hl-heading {
688
703
  color: var(--md-heading);
689
704
  font-weight: 700;
@@ -916,15 +931,16 @@
916
931
  overflow-wrap: inherit;
917
932
  }
918
933
 
919
- .preview-comment-controls {
920
- position: absolute;
921
- top: 0;
922
- right: 0;
934
+ .preview-selection-actions {
935
+ position: sticky;
936
+ top: 12px;
923
937
  z-index: 6;
924
- display: inline-flex;
925
- align-items: center;
938
+ height: 0;
939
+ display: flex;
940
+ justify-content: flex-end;
941
+ align-items: flex-start;
926
942
  gap: 8px;
927
- transform: translateY(-0.46rem);
943
+ pointer-events: none;
928
944
  }
929
945
 
930
946
  .preview-comment-summary {
@@ -941,21 +957,11 @@
941
957
  padding: 0 10px;
942
958
  font-size: 12px;
943
959
  line-height: 1;
944
- opacity: 0;
945
- pointer-events: none;
946
- transform: translateY(-2px);
947
- transition: opacity 120ms ease, transform 120ms ease, color 120ms ease, border-color 120ms ease, background-color 120ms ease;
948
960
  background: var(--accent-soft);
949
961
  border-color: var(--accent);
950
962
  color: var(--accent);
951
963
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
952
- }
953
-
954
- .preview-comment-block.has-selection .preview-comment-add,
955
- .preview-comment-block.has-selection .preview-comment-jump {
956
- opacity: 1;
957
964
  pointer-events: auto;
958
- transform: translateY(0);
959
965
  }
960
966
 
961
967
  .preview-comment-add:hover,
package/index.ts CHANGED
@@ -6095,7 +6095,10 @@ ${cssVarsBlock}
6095
6095
  <div id="lineNumberMeasure" class="editor-line-number-measure" aria-hidden="true"></div>
6096
6096
  <pre id="sourceHighlight" class="editor-highlight" aria-hidden="true"></pre>
6097
6097
  <textarea id="sourceText" placeholder="Paste or edit text here.">${initialText}</textarea>
6098
- <button id="editorSelectionCommentBtn" type="button" class="editor-selection-comment-btn" hidden title="Create a new local comment from the current editor selection.">Comment</button>
6098
+ <div id="editorSelectionActions" class="editor-selection-actions" hidden>
6099
+ <button id="editorSelectionCommentBtn" type="button" class="editor-selection-action-btn" hidden title="Create a new local comment from the current editor selection.">Comment</button>
6100
+ <button id="editorSelectionJumpBtn" type="button" class="editor-selection-action-btn" hidden title="Jump to the current editor selection in the preview.">Jump</button>
6101
+ </div>
6099
6102
  </div>
6100
6103
  <div id="sourcePreview" class="panel-scroll rendered-markdown" hidden><pre class="plain-markdown"></pre></div>
6101
6104
  </div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.50",
3
+ "version": "0.5.51",
4
4
  "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
5
5
  "type": "module",
6
6
  "license": "MIT",