pi-studio 0.9.31 → 0.9.33

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,28 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.33] — 2026-06-16
8
+
9
+ ### Added
10
+ - Added `Cmd/Ctrl+Alt+E` to switch the right pane directly to Editor Preview.
11
+
12
+ ### Changed
13
+ - Calmed annotation and comment affordance styling so annotations read more like highlighted feedback and less like heavy badges.
14
+ - Slightly muted active Stop button styling while keeping it in the theme error colour family.
15
+ - Derived PDF annotation colours from the active theme accent hue while keeping a light, print-friendly background.
16
+
17
+ ### Fixed
18
+ - Preserved the Working view scroll position when switching between `All`, `Thinking`, and `Tools` filters unless the view was already pinned to the bottom.
19
+ - Rendered standalone long `[an: ...]` annotations correctly in LaTeX-mode PDF exports even when Pandoc wraps the escaped marker across generated LaTeX lines.
20
+
21
+ ## [0.9.32] — 2026-06-10
22
+
23
+ ### Added
24
+ - Added `Cmd/Ctrl+Alt+W` to switch the right pane directly to Working, matching the existing direct Preview shortcut.
25
+
26
+ ### Fixed
27
+ - Preserved expanded Working-view detail sections while live working updates stream in.
28
+
7
29
  ## [0.9.31] — 2026-06-09
8
30
 
9
31
  ### 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+P` / `Cmd/Ctrl+Alt+E` / `Cmd/Ctrl+Alt+W` switch directly between Response Preview, Editor Preview, and 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
@@ -269,8 +269,8 @@
269
269
  option.disabled = isEditorOnlyMode && !editorOnlyAllowed.has(option.value);
270
270
  });
271
271
  rightViewSelect.title = isEditorOnlyMode
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.";
272
+ ? "Editor-only views: editor preview, Changes, Files, or REPL. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P or Cmd/Ctrl+Alt+E switches directly to Preview."
273
+ : "Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Response Preview; Cmd/Ctrl+Alt+E switches directly to Editor 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
  }
@@ -857,7 +859,7 @@
857
859
  const normalized = normalizeTraceFilter(nextFilter);
858
860
  if (traceFilter === normalized) return;
859
861
  traceFilter = normalized;
860
- traceAutoScroll = true;
862
+ traceAutoScroll = rightView === "trace" ? shouldStickTraceToBottom() : traceAutoScroll;
861
863
  renderTraceViewIfActive();
862
864
  }
863
865
 
@@ -3804,6 +3806,24 @@
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
+
3820
+ function switchRightPaneToEditorPreview() {
3821
+ const snapshot = snapshotStudioScrollablePositions();
3822
+ setRightView("editor-preview");
3823
+ scheduleStudioScrollablePositionRestore(snapshot);
3824
+ setStatus("Right pane view: Editor (Preview).");
3825
+ }
3826
+
3807
3827
  function cycleActivePaneView(direction) {
3808
3828
  if (activePane === "right") {
3809
3829
  if (!rightViewSelect || rightViewSelect.disabled) {
@@ -4112,6 +4132,26 @@
4112
4132
  return;
4113
4133
  }
4114
4134
 
4135
+ const isWorkingShortcut = (key.toLowerCase() === "w" || code === "KeyW")
4136
+ && (event.metaKey || event.ctrlKey)
4137
+ && event.altKey
4138
+ && !event.shiftKey;
4139
+ if (isWorkingShortcut) {
4140
+ event.preventDefault();
4141
+ switchRightPaneToWorking();
4142
+ return;
4143
+ }
4144
+
4145
+ const isEditorPreviewShortcut = (key.toLowerCase() === "e" || code === "KeyE")
4146
+ && (event.metaKey || event.ctrlKey)
4147
+ && event.altKey
4148
+ && !event.shiftKey;
4149
+ if (isEditorPreviewShortcut) {
4150
+ event.preventDefault();
4151
+ switchRightPaneToEditorPreview();
4152
+ return;
4153
+ }
4154
+
4115
4155
  const isContentFocusShortcut = key === "F8" && !event.metaKey && !event.ctrlKey && !event.altKey;
4116
4156
  if (isContentFocusShortcut) {
4117
4157
  event.preventDefault();
@@ -7757,6 +7797,20 @@
7757
7797
  }
7758
7798
  }
7759
7799
 
7800
+ function handleTraceDetailsToggle(event) {
7801
+ if (rightView !== "trace") return;
7802
+ const target = event.target;
7803
+ if (!(target instanceof Element) || !target.matches("details[data-trace-details-key]")) return;
7804
+ const key = target.getAttribute("data-trace-details-key") || "";
7805
+ if (!key) return;
7806
+ if (target.open) {
7807
+ traceOpenDetails.add(key);
7808
+ } else {
7809
+ traceOpenDetails.delete(key);
7810
+ }
7811
+ traceAutoScroll = false;
7812
+ }
7813
+
7760
7814
  async function handleTracePaneClick(event) {
7761
7815
  if (rightView !== "trace") return;
7762
7816
  const target = event.target;
@@ -7951,6 +8005,7 @@
7951
8005
  function attachResponsePaneInteractionHandlers() {
7952
8006
  if (!critiqueViewEl) return;
7953
8007
  critiqueViewEl.addEventListener("scroll", handleTracePaneScroll);
8008
+ critiqueViewEl.addEventListener("toggle", handleTraceDetailsToggle, true);
7954
8009
  critiqueViewEl.addEventListener("click", handleTracePaneClick);
7955
8010
  critiqueViewEl.addEventListener("click", handleReplPaneClick);
7956
8011
  critiqueViewEl.addEventListener("click", handleFilesPaneClick);
@@ -9189,8 +9244,9 @@
9189
9244
  const value = String(inputText || "").trim();
9190
9245
  if (!value) return "";
9191
9246
  const rawKey = outputKey + ":raw-input";
9192
- const openAttr = traceExpandedOutputs.has(rawKey) ? " open" : "";
9193
- return "<details class='trace-tool-details trace-tool-raw-input'" + openAttr + ">"
9247
+ const detailsKey = rawKey;
9248
+ const openAttr = traceOpenDetails.has(detailsKey) || traceExpandedOutputs.has(rawKey) ? " open" : "";
9249
+ return "<details class='trace-tool-details trace-tool-raw-input' data-trace-details-key='" + escapeHtml(detailsKey) + "'" + openAttr + ">"
9194
9250
  + "<summary>Raw input</summary>"
9195
9251
  + "<div class='trace-tool-details-body'>"
9196
9252
  + renderTraceOutput(value, rawKey, { label: "Raw input" })
@@ -9201,8 +9257,9 @@
9201
9257
  function renderTraceToolTextDetails(summary, text, outputKey, label, options) {
9202
9258
  const value = String(text || "");
9203
9259
  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 + ">"
9260
+ const detailsKey = outputKey;
9261
+ const openAttr = traceOpenDetails.has(detailsKey) || traceExpandedOutputs.has(outputKey) ? " open" : "";
9262
+ return "<details class='trace-tool-details" + (options && options.className ? " " + escapeHtml(options.className) : "") + "' data-trace-details-key='" + escapeHtml(detailsKey) + "'" + openAttr + ">"
9206
9263
  + "<summary>" + escapeHtml(summary) + "</summary>"
9207
9264
  + "<div class='trace-tool-details-body'>"
9208
9265
  + renderTraceOutput(value || emptyText, outputKey, { label })
@@ -9234,8 +9291,9 @@
9234
9291
  const newMetrics = formatTraceTextMetrics(edit.newText);
9235
9292
  const oldKey = entry.id + ":edit:" + edit.index + ":old";
9236
9293
  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 + ">"
9294
+ const detailsKey = entry.id + ":edit:" + edit.index;
9295
+ const openAttr = traceOpenDetails.has(detailsKey) || traceExpandedOutputs.has(oldKey) || traceExpandedOutputs.has(newKey) ? " open" : "";
9296
+ return "<details class='trace-tool-details trace-tool-change' data-trace-details-key='" + escapeHtml(detailsKey) + "'" + openAttr + ">"
9239
9297
  + "<summary>Replacement " + escapeHtml(String(displayIndex + 1)) + " · " + escapeHtml(oldMetrics) + " → " + escapeHtml(newMetrics) + "</summary>"
9240
9298
  + "<div class='trace-tool-change-body'>"
9241
9299
  + "<div class='trace-tool-change-grid'>"
package/client/studio.css CHANGED
@@ -210,15 +210,17 @@
210
210
 
211
211
  #sendRunBtn.request-stop-active,
212
212
  #critiqueBtn.request-stop-active {
213
- background: var(--error);
214
- border-color: var(--error);
213
+ background: color-mix(in srgb, var(--error) 82%, var(--panel));
214
+ border-color: color-mix(in srgb, var(--error) 88%, var(--panel));
215
215
  color: var(--error-contrast);
216
- font-weight: 600;
216
+ font-weight: 700;
217
217
  }
218
218
 
219
219
  #sendRunBtn.request-stop-active:not(:disabled):hover,
220
220
  #critiqueBtn.request-stop-active:not(:disabled):hover {
221
- filter: brightness(0.95);
221
+ background: var(--error);
222
+ border-color: var(--error);
223
+ filter: none;
222
224
  }
223
225
 
224
226
  .file-label {
@@ -930,17 +932,17 @@
930
932
  padding: 0 10px;
931
933
  font-size: 12px;
932
934
  line-height: 1;
933
- background: var(--accent-soft);
934
- border-color: var(--accent);
935
- color: var(--accent);
935
+ background: color-mix(in srgb, var(--panel) 88%, var(--accent-soft));
936
+ border-color: var(--control-border);
937
+ color: var(--text);
936
938
  box-shadow: 0 1px 2px var(--shadow-color);
937
939
  }
938
940
 
939
941
  .editor-selection-action-btn:hover,
940
942
  .editor-selection-action-btn:focus-visible {
941
- background: var(--accent-soft-strong);
943
+ background: var(--accent-soft);
942
944
  color: var(--accent);
943
- border-color: var(--accent);
945
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--control-border));
944
946
  }
945
947
 
946
948
  .editor-selection-actions[hidden],
@@ -1043,12 +1045,12 @@
1043
1045
  }
1044
1046
 
1045
1047
  .hl-annotation {
1046
- color: var(--accent);
1047
- background: var(--accent-soft);
1048
+ color: color-mix(in srgb, var(--accent) 72%, var(--text));
1049
+ background: color-mix(in srgb, var(--accent-soft) 58%, transparent);
1048
1050
  border: 0;
1049
- border-radius: 4px;
1051
+ border-radius: 3px;
1050
1052
  padding: 0;
1051
- box-shadow: inset 0 0 0 1px var(--marker-border);
1053
+ box-shadow: inset 0 -0.08em 0 color-mix(in srgb, var(--accent) 26%, transparent);
1052
1054
  }
1053
1055
 
1054
1056
  .hl-annotation-muted {
@@ -1057,10 +1059,10 @@
1057
1059
  }
1058
1060
 
1059
1061
  .annotation-preview-marker {
1060
- color: var(--accent);
1061
- background: var(--accent-soft);
1062
- border: 1px solid var(--marker-border);
1063
- border-radius: 4px;
1062
+ color: color-mix(in srgb, var(--accent) 72%, var(--text));
1063
+ background: color-mix(in srgb, var(--accent-soft) 58%, transparent);
1064
+ border: 1px solid color-mix(in srgb, var(--marker-border) 26%, transparent);
1065
+ border-radius: 3px;
1064
1066
  display: inline-block;
1065
1067
  max-width: 100%;
1066
1068
  padding: 0 4px;
@@ -1068,6 +1070,20 @@
1068
1070
  vertical-align: baseline;
1069
1071
  }
1070
1072
 
1073
+ .annotation-preview-marker strong {
1074
+ color: color-mix(in srgb, var(--accent) 82%, var(--text));
1075
+ font-weight: 750;
1076
+ }
1077
+
1078
+ .annotation-preview-marker em {
1079
+ color: color-mix(in srgb, currentColor 82%, var(--muted));
1080
+ }
1081
+
1082
+ .annotation-preview-marker s {
1083
+ color: color-mix(in srgb, currentColor 70%, var(--muted));
1084
+ text-decoration-thickness: 0.08em;
1085
+ }
1086
+
1071
1087
  .annotation-preview-marker code {
1072
1088
  font-family: var(--mono-font, ui-monospace, SFMono-Regular, Menlo, monospace);
1073
1089
  font-size: 0.95em;
@@ -1216,9 +1232,9 @@
1216
1232
  padding: 0 10px;
1217
1233
  font-size: 12px;
1218
1234
  line-height: 1;
1219
- background: var(--accent-soft);
1220
- border-color: var(--accent);
1221
- color: var(--accent);
1235
+ background: color-mix(in srgb, var(--panel) 88%, var(--accent-soft));
1236
+ border-color: var(--control-border);
1237
+ color: var(--text);
1222
1238
  box-shadow: 0 1px 2px var(--shadow-color);
1223
1239
  pointer-events: auto;
1224
1240
  }
@@ -1227,9 +1243,9 @@
1227
1243
  .preview-comment-add:focus-visible,
1228
1244
  .preview-comment-jump:hover,
1229
1245
  .preview-comment-jump:focus-visible {
1230
- background: var(--accent-soft-strong);
1246
+ background: var(--accent-soft);
1231
1247
  color: var(--accent);
1232
- border-color: var(--accent);
1248
+ border-color: color-mix(in srgb, var(--accent) 45%, var(--control-border));
1233
1249
  }
1234
1250
 
1235
1251
  .preview-jump-highlight {
@@ -5779,10 +5795,10 @@
5779
5795
  }
5780
5796
 
5781
5797
  body.studio-ui-refresh .studio-refresh-review-btn.request-stop-active {
5782
- background: var(--error);
5783
- border-color: var(--error);
5798
+ background: color-mix(in srgb, var(--error) 82%, var(--panel));
5799
+ border-color: color-mix(in srgb, var(--error) 88%, var(--panel));
5784
5800
  color: var(--error-contrast);
5785
- font-weight: 600;
5801
+ font-weight: 700;
5786
5802
  }
5787
5803
 
5788
5804
  body.studio-ui-refresh .studio-refresh-review-btn.request-stop-active::after {
@@ -5790,10 +5806,10 @@
5790
5806
  }
5791
5807
 
5792
5808
  body.studio-ui-refresh .studio-refresh-menu #critiqueBtn.request-stop-active {
5793
- background: var(--error);
5794
- border-color: var(--error);
5809
+ background: color-mix(in srgb, var(--error) 82%, var(--panel));
5810
+ border-color: color-mix(in srgb, var(--error) 88%, var(--panel));
5795
5811
  color: var(--error-contrast);
5796
- font-weight: 600;
5812
+ font-weight: 700;
5797
5813
  }
5798
5814
 
5799
5815
  .studio-preview-link-menu {
package/index.ts CHANGED
@@ -1066,7 +1066,23 @@ function buildStudioPdfCalloutTitleSizeCommand(options?: StudioPdfRenderOptions)
1066
1066
  return "\\footnotesize";
1067
1067
  }
1068
1068
 
1069
- function buildStudioPdfPreamble(options?: StudioPdfRenderOptions, extraPreamble = ""): string {
1069
+ function formatStudioPdfHtmlColor(color: string, fallback: string): string {
1070
+ return hexToRgb(color) ? color.replace(/^#/, "").toUpperCase() : fallback;
1071
+ }
1072
+
1073
+ function buildStudioPdfAnnotationColors(style: StudioThemeStyle): { bg: string; border: string; text: string } {
1074
+ const accent = hexToRgb(style.palette.accent) ? style.palette.accent : "#2F6FEB";
1075
+ const page = "#FFFFFF";
1076
+ const darkText = "#0E1616";
1077
+ return {
1078
+ bg: formatStudioPdfHtmlColor(blendColors(accent, page, 0.91), "F1F7FF"),
1079
+ border: formatStudioPdfHtmlColor(blendColors(accent, page, 0.68), "C7DDFF"),
1080
+ text: formatStudioPdfHtmlColor(blendColors(accent, darkText, style.mode === "light" ? 0.40 : 0.34), "1F5FBF"),
1081
+ };
1082
+ }
1083
+
1084
+ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions, extraPreamble = "", themeStyle?: StudioThemeStyle): string {
1085
+ const annotationColors = buildStudioPdfAnnotationColors(themeStyle ?? getStudioThemeStyle());
1070
1086
  const sectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.sectionSize, "\\Large");
1071
1087
  const subsectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.subsectionSize, "\\large");
1072
1088
  const subsubsectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.subsubsectionSize, "\\normalsize");
@@ -1088,9 +1104,9 @@ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions, extraPreamble
1088
1104
  \\usepackage{xcolor}
1089
1105
  \\usepackage{varwidth}
1090
1106
  \\usepackage[normalem]{ulem}
1091
- \\definecolor{StudioAnnotationBg}{HTML}{EAF3FF}
1092
- \\definecolor{StudioAnnotationBorder}{HTML}{8CB8FF}
1093
- \\definecolor{StudioAnnotationText}{HTML}{1F5FBF}
1107
+ \\definecolor{StudioAnnotationBg}{HTML}{${annotationColors.bg}}
1108
+ \\definecolor{StudioAnnotationBorder}{HTML}{${annotationColors.border}}
1109
+ \\definecolor{StudioAnnotationText}{HTML}{${annotationColors.text}}
1094
1110
  \\definecolor{StudioCodeBlockBg}{HTML}{F6F8FA}
1095
1111
  \\definecolor{StudioDiffAddText}{HTML}{1A7F37}
1096
1112
  \\definecolor{StudioDiffDelText}{HTML}{CF222E}
@@ -6501,9 +6517,20 @@ ${literalPdfConfig.fontSizeCommand}\\section*{${title.replace(/[{}\\]/g, "").tri
6501
6517
  function replaceStudioAnnotationMarkersInGeneratedLatex(latex: string): string {
6502
6518
  const lines = String(latex ?? "").split("\n");
6503
6519
  const out: string[] = [];
6520
+ const pendingTextLines: string[] = [];
6504
6521
  const rawEnvStack: string[] = [];
6505
6522
  const rawEnvNames = new Set(["verbatim", "Verbatim", "Highlighting", "lstlisting"]);
6506
6523
 
6524
+ const hasRawEnvBoundary = (line: string): boolean => {
6525
+ const envPattern = /\\(begin|end)\{([^}]+)\}/g;
6526
+ let match: RegExpExecArray | null;
6527
+ while ((match = envPattern.exec(line)) !== null) {
6528
+ const envName = match[2];
6529
+ if (envName && rawEnvNames.has(envName)) return true;
6530
+ }
6531
+ return false;
6532
+ };
6533
+
6507
6534
  const updateRawEnvStack = (line: string) => {
6508
6535
  const envPattern = /\\(begin|end)\{([^}]+)\}/g;
6509
6536
  let match: RegExpExecArray | null;
@@ -6524,6 +6551,12 @@ function replaceStudioAnnotationMarkersInGeneratedLatex(latex: string): string {
6524
6551
  }
6525
6552
  };
6526
6553
 
6554
+ const flushPendingText = () => {
6555
+ if (pendingTextLines.length === 0) return;
6556
+ out.push(replaceStudioAnnotationMarkersForPdfInSegment(pendingTextLines.join("\n")));
6557
+ pendingTextLines.length = 0;
6558
+ };
6559
+
6527
6560
  for (const line of lines) {
6528
6561
  if (rawEnvStack.length > 0) {
6529
6562
  out.push(line);
@@ -6531,10 +6564,17 @@ function replaceStudioAnnotationMarkersInGeneratedLatex(latex: string): string {
6531
6564
  continue;
6532
6565
  }
6533
6566
 
6534
- out.push(replaceStudioAnnotationMarkersForPdfInSegment(line));
6535
- updateRawEnvStack(line);
6567
+ if (hasRawEnvBoundary(line)) {
6568
+ flushPendingText();
6569
+ out.push(line);
6570
+ updateRawEnvStack(line);
6571
+ continue;
6572
+ }
6573
+
6574
+ pendingTextLines.push(line);
6536
6575
  }
6537
6576
 
6577
+ flushPendingText();
6538
6578
  return out.join("\n");
6539
6579
  }
6540
6580
 
@@ -6727,6 +6767,7 @@ async function renderStudioPdfFromGeneratedLatex(
6727
6767
  alignedImageBlocks: StudioPdfAlignedImageBlock[] = [],
6728
6768
  pdfOptions?: StudioPdfRenderOptions,
6729
6769
  extraPreamble = "",
6770
+ themeStyle?: StudioThemeStyle,
6730
6771
  ): Promise<{ pdf: Buffer; warning?: string }> {
6731
6772
  const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
6732
6773
  const preamblePath = join(tempDir, "_pdf_preamble.tex");
@@ -6734,7 +6775,7 @@ async function renderStudioPdfFromGeneratedLatex(
6734
6775
  const outputPath = join(tempDir, "studio-export.pdf");
6735
6776
 
6736
6777
  await mkdir(tempDir, { recursive: true });
6737
- await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, extraPreamble), "utf-8");
6778
+ await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, extraPreamble, themeStyle), "utf-8");
6738
6779
 
6739
6780
  const pandocArgs = [
6740
6781
  "-f", inputFormat,
@@ -6805,6 +6846,7 @@ async function renderStudioPdfWithPandoc(
6805
6846
  editorPdfLanguage?: string,
6806
6847
  sourcePath?: string,
6807
6848
  pdfOptions?: StudioPdfRenderOptions,
6849
+ themeStyle?: StudioThemeStyle,
6808
6850
  ): Promise<{ pdf: Buffer; warning?: string }> {
6809
6851
  const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
6810
6852
  const pdfEngine = process.env.PANDOC_PDF_ENGINE?.trim() || "xelatex";
@@ -6842,7 +6884,7 @@ async function renderStudioPdfWithPandoc(
6842
6884
  const outputPath = join(tempDir, "studio-export.pdf");
6843
6885
 
6844
6886
  await mkdir(tempDir, { recursive: true });
6845
- await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions), "utf-8");
6887
+ await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, "", themeStyle), "utf-8");
6846
6888
 
6847
6889
  const hasYamlHeaderIncludesForPdf = inputFormat !== "latex" && hasStudioYamlHeaderIncludes(markdownForPdf);
6848
6890
  const headerIncludeArgs = hasYamlHeaderIncludesForPdf ? [] : ["--include-in-header", preamblePath];
@@ -6895,6 +6937,8 @@ async function renderStudioPdfWithPandoc(
6895
6937
  [],
6896
6938
  [],
6897
6939
  pdfOptions,
6940
+ "",
6941
+ themeStyle,
6898
6942
  );
6899
6943
  }
6900
6944
 
@@ -6915,6 +6959,8 @@ async function renderStudioPdfWithPandoc(
6915
6959
  [],
6916
6960
  [],
6917
6961
  pdfOptions,
6962
+ "",
6963
+ themeStyle,
6918
6964
  );
6919
6965
  } catch {
6920
6966
  const fenced = parseStudioSingleFencedCodeBlock(diffMarkdown);
@@ -6941,7 +6987,7 @@ async function renderStudioPdfWithPandoc(
6941
6987
  const outputPath = join(tempDir, "studio-export.pdf");
6942
6988
 
6943
6989
  await mkdir(tempDir, { recursive: true });
6944
- await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, extraPdfPreamble), "utf-8");
6990
+ await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, extraPdfPreamble, themeStyle), "utf-8");
6945
6991
 
6946
6992
  const mermaidPrepared: StudioMermaidPdfPreprocessResult = isLatex
6947
6993
  ? { markdown: normalizedMarkdownBody, found: 0, replaced: 0, failed: 0, missingCli: false }
@@ -6964,6 +7010,7 @@ async function renderStudioPdfWithPandoc(
6964
7010
  pdfAlignedImageTransform.blocks,
6965
7011
  pdfOptions,
6966
7012
  extraPdfPreamble,
7013
+ themeStyle,
6967
7014
  );
6968
7015
  await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
6969
7016
  return { pdf: rendered.pdf, warning: mermaidPrepared.warning ?? rendered.warning };
@@ -10514,7 +10561,7 @@ ${cssVarsBlock}
10514
10561
  <section id="rightPane">
10515
10562
  <div id="rightSectionHeader" class="section-header">
10516
10563
  <div class="section-header-main">
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.">
10564
+ <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 Response Preview; Cmd/Ctrl+Alt+E switches directly to Editor Preview; Cmd/Ctrl+Alt+W switches directly to Working.">
10518
10565
  <option value="markdown">Response (Raw)</option>
10519
10566
  <option value="preview" selected>Response (Preview)</option>
10520
10567
  <option value="editor-preview">Editor (Preview)</option>
@@ -10608,7 +10655,9 @@ ${cssVarsBlock}
10608
10655
  <dl>
10609
10656
  <div><dt>F6</dt><dd>Switch between editor and right pane</dd></div>
10610
10657
  <div><dt>F7 / Shift+F7</dt><dd>Cycle the active pane's view</dd></div>
10611
- <div><dt>Cmd/Ctrl+Alt+P</dt><dd>Switch the right pane directly to Preview</dd></div>
10658
+ <div><dt>Cmd/Ctrl+Alt+P</dt><dd>Switch the right pane directly to Response Preview</dd></div>
10659
+ <div><dt>Cmd/Ctrl+Alt+E</dt><dd>Switch the right pane directly to Editor Preview</dd></div>
10660
+ <div><dt>Cmd/Ctrl+Alt+W</dt><dd>Switch the right pane directly to Working</dd></div>
10612
10661
  <div><dt>F8</dt><dd>Focus editor text</dd></div>
10613
10662
  <div><dt>Shift+F8</dt><dd>Focus right-pane content</dd></div>
10614
10663
  <div><dt>F9</dt><dd>Toggle Zen mode</dd></div>
@@ -13552,7 +13601,7 @@ export default function (pi: ExtensionAPI) {
13552
13601
  const filename = sanitizePdfFilename(requestedFilename || (isLatex ? "studio-latex-preview.pdf" : "studio-preview.pdf"));
13553
13602
 
13554
13603
  try {
13555
- const { pdf, warning } = await renderStudioPdfWithPandoc(markdown, isLatex, resourcePath, editorPdfLanguage, sourcePath || undefined);
13604
+ const { pdf, warning } = await renderStudioPdfWithPandoc(markdown, isLatex, resourcePath, editorPdfLanguage, sourcePath || undefined, undefined, getStudioThemeStyle(lastCommandCtx?.ui?.theme));
13556
13605
  const writeResult = writeStudioPreviewExportFile(buildStudioPreviewExportPath(sourcePath || undefined, userResourceDir || undefined, studioCwd, filename), pdf);
13557
13606
  const exportId = storePreparedPdfExport(pdf, filename, warning, writeResult.filePath ?? undefined);
13558
13607
  const token = serverState?.token ?? "";
@@ -14983,6 +15032,7 @@ export default function (pi: ExtensionAPI) {
14983
15032
  editorPdfLanguage,
14984
15033
  source.sourcePath,
14985
15034
  pdfOptions,
15035
+ getStudioThemeStyle(ctx.ui.theme),
14986
15036
  );
14987
15037
  await writeFile(source.outputPath, pdf);
14988
15038
  const openError = await maybeOpenStudioExportPath(source.outputPath, params.open);
@@ -15295,6 +15345,7 @@ export default function (pi: ExtensionAPI) {
15295
15345
  editorPdfLanguage,
15296
15346
  undefined,
15297
15347
  pdfOptions,
15348
+ getStudioThemeStyle(ctx.ui.theme),
15298
15349
  );
15299
15350
  await writeFile(outputPath, pdf);
15300
15351
 
@@ -15353,6 +15404,7 @@ export default function (pi: ExtensionAPI) {
15353
15404
  editorPdfLanguage,
15354
15405
  file.resolvedPath,
15355
15406
  pdfOptions,
15407
+ getStudioThemeStyle(ctx.ui.theme),
15356
15408
  );
15357
15409
  await writeFile(outputPath, pdf);
15358
15410
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.9.31",
3
+ "version": "0.9.33",
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",