pi-studio 0.9.30 → 0.9.32

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,20 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.32] — 2026-06-10
8
+
9
+ ### Added
10
+ - Added `Cmd/Ctrl+Alt+W` to switch the right pane directly to Working, matching the existing direct Preview shortcut.
11
+
12
+ ### Fixed
13
+ - Preserved expanded Working-view detail sections while live working updates stream in.
14
+
15
+ ## [0.9.31] — 2026-06-09
16
+
17
+ ### Fixed
18
+ - Render long PDF annotations as full-width display-style boxes at their marker position so exported feedback avoids clipping while preserving the surrounding annotation order.
19
+ - Avoided annotation placeholder collisions in HTML exports with ten or more annotations.
20
+
7
21
  ## [0.9.30] — 2026-06-09
8
22
 
9
23
  ### Fixed
package/README.md CHANGED
@@ -22,7 +22,7 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
22
22
  - Supports one canonical full Studio view per Pi session, plus additional editor-only companion views when you want extra editing/preview surfaces; editor-only views can also browse files and use the Studio REPL send controls without taking over the full Studio session view
23
23
  - Includes a global **Zen** mode for hiding secondary Studio chrome without changing the current left/right pane layout
24
24
  - Runs editor text directly, asks for structured critique (auto/writing/code focus), offers a manual **Suggest completion** action for short cursor-aware continuations (`Option/Alt+Tab` where available or `Cmd/Ctrl+Shift+Space` from the editor, `Tab` to insert a visible suggestion) with an optional editor-plus-latest-response context mode, or opens **Quiz me** for a Studio-native active-recall loop over the current editor text, selection, current file, folder, or repo, with optional focus guidance for shaping question selection
25
- - Includes a live **Working** view for following current model/tool activity, with `All` / `Thinking` / `Tools` filters, image previews for image-producing tool outputs, plus **Load visible into editor** and **Copy visible** actions; when cycling response history, Working follows saved working details for the selected response when available
25
+ - Includes a live **Working** view for following current model/tool activity, with `All` / `Thinking` / `Tools` filters, image previews for image-producing tool outputs, plus **Load visible into editor** and **Copy visible** actions; when cycling response history, Working follows saved working details for the selected response when available, and `Cmd/Ctrl+Alt+W` switches the right pane directly to Working
26
26
  - Includes a right-pane **Changes** view for browsing the current git diff by file, previewing per-file diffs, opening changed files, loading the full diff into the editor, and copying the diff
27
27
  - Includes a right-pane **Files** view for browsing the current Pi session/resource directory, opening folders, opening the Files root in Finder/the system file manager, loading text/code/CSV/TSV documents into the editor, previewing PDFs/images, opening PDF/image previews in a new Studio tab, converting DOCX/ODT documents to editable Markdown when Pandoc is available after confirmation, copying paths, setting the current folder as the Studio working directory, and revealing files in the file manager
28
28
  - Includes an optional tmux-backed **REPL** view for Shell, Python, IPython, Julia, R, GHCi, and Clojure sessions, with Raw/Literate send modes, `Cmd/Ctrl+Shift+Enter` **Send to REPL**, session start/stop/interrupt controls, a compact refresh-persistent **Studio REPL Record** of user and Pi-sent code, a secondary raw tmux mirror, agent-facing `studio_repl_status` / `studio_repl_send` tools, and Markdown/PDF/HTML export
@@ -270,7 +270,7 @@
270
270
  });
271
271
  rightViewSelect.title = isEditorOnlyMode
272
272
  ? "Editor-only views: editor preview, Changes, Files, or REPL. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview."
273
- : "Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview.";
273
+ : "Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview; Cmd/Ctrl+Alt+W switches directly to Working.";
274
274
  }
275
275
 
276
276
  function getInitialRightView(source) {
@@ -302,6 +302,7 @@
302
302
  let traceAutoScroll = true;
303
303
  let traceRenderRaf = null;
304
304
  const traceExpandedOutputs = new Set();
305
+ const traceOpenDetails = new Set();
305
306
  let gitChangesState = {
306
307
  status: "idle",
307
308
  requestId: null,
@@ -683,6 +684,7 @@
683
684
  traceState = normalizeTraceState(nextState);
684
685
  if ((traceState.runId || null) !== previousRunId) {
685
686
  traceExpandedOutputs.clear();
687
+ traceOpenDetails.clear();
686
688
  }
687
689
  renderTraceViewIfActive();
688
690
  }
@@ -3804,6 +3806,17 @@
3804
3806
  setStatus("Right pane view: " + String(label || "Preview") + ".");
3805
3807
  }
3806
3808
 
3809
+ function switchRightPaneToWorking() {
3810
+ if (isEditorOnlyMode) {
3811
+ setStatus("Working view is unavailable in editor-only Studio views.", "warning");
3812
+ return;
3813
+ }
3814
+ const snapshot = snapshotStudioScrollablePositions();
3815
+ setRightView("trace");
3816
+ scheduleStudioScrollablePositionRestore(snapshot);
3817
+ setStatus("Right pane view: Working.");
3818
+ }
3819
+
3807
3820
  function cycleActivePaneView(direction) {
3808
3821
  if (activePane === "right") {
3809
3822
  if (!rightViewSelect || rightViewSelect.disabled) {
@@ -4112,6 +4125,16 @@
4112
4125
  return;
4113
4126
  }
4114
4127
 
4128
+ const isWorkingShortcut = (key.toLowerCase() === "w" || code === "KeyW")
4129
+ && (event.metaKey || event.ctrlKey)
4130
+ && event.altKey
4131
+ && !event.shiftKey;
4132
+ if (isWorkingShortcut) {
4133
+ event.preventDefault();
4134
+ switchRightPaneToWorking();
4135
+ return;
4136
+ }
4137
+
4115
4138
  const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
4116
4139
  if (isContentFocusShortcut) {
4117
4140
  event.preventDefault();
@@ -7757,6 +7780,20 @@
7757
7780
  }
7758
7781
  }
7759
7782
 
7783
+ function handleTraceDetailsToggle(event) {
7784
+ if (rightView !== "trace") return;
7785
+ const target = event.target;
7786
+ if (!(target instanceof Element) || !target.matches("details[data-trace-details-key]")) return;
7787
+ const key = target.getAttribute("data-trace-details-key") || "";
7788
+ if (!key) return;
7789
+ if (target.open) {
7790
+ traceOpenDetails.add(key);
7791
+ } else {
7792
+ traceOpenDetails.delete(key);
7793
+ }
7794
+ traceAutoScroll = false;
7795
+ }
7796
+
7760
7797
  async function handleTracePaneClick(event) {
7761
7798
  if (rightView !== "trace") return;
7762
7799
  const target = event.target;
@@ -7951,6 +7988,7 @@
7951
7988
  function attachResponsePaneInteractionHandlers() {
7952
7989
  if (!critiqueViewEl) return;
7953
7990
  critiqueViewEl.addEventListener("scroll", handleTracePaneScroll);
7991
+ critiqueViewEl.addEventListener("toggle", handleTraceDetailsToggle, true);
7954
7992
  critiqueViewEl.addEventListener("click", handleTracePaneClick);
7955
7993
  critiqueViewEl.addEventListener("click", handleReplPaneClick);
7956
7994
  critiqueViewEl.addEventListener("click", handleFilesPaneClick);
@@ -9189,8 +9227,9 @@
9189
9227
  const value = String(inputText || "").trim();
9190
9228
  if (!value) return "";
9191
9229
  const rawKey = outputKey + ":raw-input";
9192
- const openAttr = traceExpandedOutputs.has(rawKey) ? " open" : "";
9193
- return "<details class='trace-tool-details trace-tool-raw-input'" + openAttr + ">"
9230
+ const detailsKey = rawKey;
9231
+ const openAttr = traceOpenDetails.has(detailsKey) || traceExpandedOutputs.has(rawKey) ? " open" : "";
9232
+ return "<details class='trace-tool-details trace-tool-raw-input' data-trace-details-key='" + escapeHtml(detailsKey) + "'" + openAttr + ">"
9194
9233
  + "<summary>Raw input</summary>"
9195
9234
  + "<div class='trace-tool-details-body'>"
9196
9235
  + renderTraceOutput(value, rawKey, { label: "Raw input" })
@@ -9201,8 +9240,9 @@
9201
9240
  function renderTraceToolTextDetails(summary, text, outputKey, label, options) {
9202
9241
  const value = String(text || "");
9203
9242
  const emptyText = options && typeof options.emptyText === "string" ? options.emptyText : "[empty]";
9204
- const openAttr = traceExpandedOutputs.has(outputKey) ? " open" : "";
9205
- return "<details class='trace-tool-details" + (options && options.className ? " " + escapeHtml(options.className) : "") + "'" + openAttr + ">"
9243
+ const detailsKey = outputKey;
9244
+ const openAttr = traceOpenDetails.has(detailsKey) || traceExpandedOutputs.has(outputKey) ? " open" : "";
9245
+ return "<details class='trace-tool-details" + (options && options.className ? " " + escapeHtml(options.className) : "") + "' data-trace-details-key='" + escapeHtml(detailsKey) + "'" + openAttr + ">"
9206
9246
  + "<summary>" + escapeHtml(summary) + "</summary>"
9207
9247
  + "<div class='trace-tool-details-body'>"
9208
9248
  + renderTraceOutput(value || emptyText, outputKey, { label })
@@ -9234,8 +9274,9 @@
9234
9274
  const newMetrics = formatTraceTextMetrics(edit.newText);
9235
9275
  const oldKey = entry.id + ":edit:" + edit.index + ":old";
9236
9276
  const newKey = entry.id + ":edit:" + edit.index + ":new";
9237
- const openAttr = traceExpandedOutputs.has(oldKey) || traceExpandedOutputs.has(newKey) ? " open" : "";
9238
- return "<details class='trace-tool-details trace-tool-change'" + openAttr + ">"
9277
+ const detailsKey = entry.id + ":edit:" + edit.index;
9278
+ const openAttr = traceOpenDetails.has(detailsKey) || traceExpandedOutputs.has(oldKey) || traceExpandedOutputs.has(newKey) ? " open" : "";
9279
+ return "<details class='trace-tool-details trace-tool-change' data-trace-details-key='" + escapeHtml(detailsKey) + "'" + openAttr + ">"
9239
9280
  + "<summary>Replacement " + escapeHtml(String(displayIndex + 1)) + " · " + escapeHtml(oldMetrics) + " → " + escapeHtml(newMetrics) + "</summary>"
9240
9281
  + "<div class='trace-tool-change-body'>"
9241
9282
  + "<div class='trace-tool-change-grid'>"
package/index.ts CHANGED
@@ -1112,6 +1112,7 @@ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions, extraPreamble
1112
1112
  \\definecolor{StudioCalloutCautionText}{HTML}{A40E26}
1113
1113
  \\definecolor{StudioCalloutCautionLabelBg}{HTML}{FDEBEC}
1114
1114
  \\newcommand{\\studioannotation}[1]{\\begingroup\\setlength{\\fboxsep}{1.5pt}\\fcolorbox{StudioAnnotationBorder}{StudioAnnotationBg}{\\begin{varwidth}{\\dimexpr\\linewidth-2\\fboxsep-2\\fboxrule\\relax}\\raggedright\\textcolor{StudioAnnotationText}{\\sffamily\\footnotesize\\strut #1}\\end{varwidth}}\\endgroup}
1115
+ \\newcommand{\\studioblockannotation}[1]{\\par\\smallskip\\noindent\\begingroup\\setlength{\\fboxsep}{1.5pt}\\fcolorbox{StudioAnnotationBorder}{StudioAnnotationBg}{\\begin{minipage}{\\dimexpr\\linewidth-2\\fboxsep-2\\fboxrule\\relax}\\raggedright\\textcolor{StudioAnnotationText}{\\sffamily\\footnotesize\\strut #1}\\end{minipage}}\\endgroup\\par\\smallskip\\noindent\\ignorespaces}
1115
1116
  \\newcommand{\\StudioDiffAddTok}[1]{\\textcolor{StudioDiffAddText}{#1}}
1116
1117
  \\newcommand{\\StudioDiffDelTok}[1]{\\textcolor{StudioDiffDelText}{#1}}
1117
1118
  \\newcommand{\\StudioDiffMetaTok}[1]{\\textcolor{StudioDiffMetaText}{#1}}
@@ -5069,22 +5070,25 @@ function renderStudioAnnotationPdfLatex(text: string): string {
5069
5070
  return renderStudioAnnotationPdfLatexContent(normalized).trim();
5070
5071
  }
5071
5072
 
5073
+ function renderStudioAnnotationPdfBox(markerText: string, block = false): string {
5074
+ const cleaned = renderStudioAnnotationPdfLatex(markerText);
5075
+ if (!cleaned) return "";
5076
+ return block ? `\\studioblockannotation{${cleaned}}` : `\\studioannotation{${cleaned}}`;
5077
+ }
5078
+
5072
5079
  function replaceStudioAnnotationMarkersForPdfInSegment(text: string): string {
5080
+ const renderMarker = (markerText: string): string => {
5081
+ const label = normalizeStudioAnnotationText(markerText);
5082
+ if (!label) return "";
5083
+ return renderStudioAnnotationPdfBox(label, shouldRenderStudioAnnotationAsPdfBlock(label));
5084
+ };
5073
5085
  const replaced = replaceStudioInlineAnnotationMarkers(
5074
5086
  String(text ?? ""),
5075
- (marker: { body: string }) => {
5076
- const cleaned = renderStudioAnnotationPdfLatex(marker.body);
5077
- if (!cleaned) return "";
5078
- return `\\studioannotation{${cleaned}}`;
5079
- },
5087
+ (marker: { body: string }) => renderMarker(marker.body),
5080
5088
  );
5081
5089
 
5082
5090
  return String(replaced ?? "")
5083
- .replace(/\{\[\}\s*an:\s*([\s\S]*?)\s*\{\]\}/gi, (_match, markerText: string) => {
5084
- const cleaned = renderStudioAnnotationPdfLatex(markerText);
5085
- if (!cleaned) return "";
5086
- return `\\studioannotation{${cleaned}}`;
5087
- });
5091
+ .replace(/\{\[\}\s*an:\s*([\s\S]*?)\s*\{\]\}/gi, (_match, markerText: string) => renderMarker(markerText));
5088
5092
  }
5089
5093
 
5090
5094
  function replaceStudioAnnotationMarkersForPdf(markdown: string): string {
@@ -6096,6 +6100,15 @@ function parseStudioHtmlPdfBlockOptions(body: string): StudioHtmlPdfBlockOptions
6096
6100
  return options;
6097
6101
  }
6098
6102
 
6103
+ // PDF/LaTeX cannot fully mimic the browser's inline-block wrapping for long
6104
+ // annotation chips, so long annotations switch to a display box at the marker.
6105
+ const STUDIO_PDF_ANNOTATION_DISPLAY_THRESHOLD_CHARS = 115;
6106
+
6107
+ function shouldRenderStudioAnnotationAsPdfBlock(text: string): boolean {
6108
+ const normalized = normalizeStudioAnnotationText(text);
6109
+ return normalized.length > STUDIO_PDF_ANNOTATION_DISPLAY_THRESHOLD_CHARS;
6110
+ }
6111
+
6099
6112
  function prepareStudioPdfBlocksForHtml(markdown: string): { markdown: string; blocks: StudioHtmlPdfBlock[] } {
6100
6113
  const blocks: StudioHtmlPdfBlock[] = [];
6101
6114
  const prefix = `PISTUDIOHTMLPDF${Date.now().toString(36)}${randomUUID().replace(/-/g, "")}TOKEN`;
@@ -6126,7 +6139,7 @@ function prepareStudioAnnotationMarkersForHtml(markdown: string): { markdown: st
6126
6139
 
6127
6140
  function applyStudioAnnotationPlaceholdersToHtml(html: string, placeholders: StudioHtmlAnnotationPlaceholder[]): string {
6128
6141
  let transformed = String(html ?? "");
6129
- for (const placeholder of placeholders) {
6142
+ for (const placeholder of [...placeholders].sort((a, b) => b.token.length - a.token.length)) {
6130
6143
  const tokenPattern = new RegExp(escapeStudioRegExpLiteral(placeholder.token), "g");
6131
6144
  const markerHtml = `<span class="annotation-preview-marker" title="${escapeStudioHtmlText(placeholder.title)}">${renderStudioAnnotationInlineHtml(placeholder.text)}</span>`;
6132
6145
  transformed = transformed.replace(tokenPattern, markerHtml);
@@ -10501,7 +10514,7 @@ ${cssVarsBlock}
10501
10514
  <section id="rightPane">
10502
10515
  <div id="rightSectionHeader" class="section-header">
10503
10516
  <div class="section-header-main">
10504
- <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.">
10517
+ <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; Cmd/Ctrl+Alt+W switches directly to Working.">
10505
10518
  <option value="markdown">Response (Raw)</option>
10506
10519
  <option value="preview" selected>Response (Preview)</option>
10507
10520
  <option value="editor-preview">Editor (Preview)</option>
@@ -10596,6 +10609,7 @@ ${cssVarsBlock}
10596
10609
  <div><dt>F6</dt><dd>Switch between editor and right pane</dd></div>
10597
10610
  <div><dt>F7 / Shift+F7</dt><dd>Cycle the active pane's view</dd></div>
10598
10611
  <div><dt>Cmd/Ctrl+Alt+P</dt><dd>Switch the right pane directly to Preview</dd></div>
10612
+ <div><dt>Cmd/Ctrl+Alt+W</dt><dd>Switch the right pane directly to Working</dd></div>
10599
10613
  <div><dt>F8</dt><dd>Focus editor text</dd></div>
10600
10614
  <div><dt>Shift+F8</dt><dd>Focus right-pane content</dd></div>
10601
10615
  <div><dt>F9</dt><dd>Toggle Zen mode</dd></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.9.30",
3
+ "version": "0.9.32",
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",