pi-studio 0.8.1 → 0.8.3

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,10 +4,25 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.8.3] — 2026-05-13
8
+
9
+ ### Changed
10
+ - Updated package, README, and UI wording to describe the feature as interactive HTML preview.
11
+
12
+ ### Fixed
13
+ - **Open new editor** now opens a blank companion editor when the current editor is empty.
14
+ - PDF export now preserves Pandoc YAML front matter while applying Studio Markdown transforms and lets Markdown documents with `header-includes` control their own LaTeX preamble, fixing exports that use YAML-defined commands such as `\firstpageletterhead`.
15
+ - Reduced scroll snap-back after using browser Find by preserving Studio pane scroll positions during pane activation.
16
+
17
+ ## [0.8.2] — 2026-05-13
18
+
19
+ ### Changed
20
+ - Improved dark-theme contrast for Studio dropdown arrows and the interactive HTML preview zoom percentage by using the stronger Studio info text colour token.
21
+
7
22
  ## [0.8.1] — 2026-05-13
8
23
 
9
24
  ### Added
10
- - Added first-cut HTML artifact preview for straight, unfenced HTML in Studio preview panes, rendered in a sandboxed iframe with inline scripts enabled, network requests blocked by CSP, fit/capped sizing, and toolbar zoom controls; HTML export preserves authored HTML artifacts instead of converting them through Markdown.
25
+ - Added first-cut interactive HTML preview for straight, unfenced HTML in Studio preview panes, rendered in a sandboxed iframe with inline scripts enabled, network requests blocked by CSP, fit/capped sizing, and toolbar zoom controls; HTML export preserves authored HTML previews instead of converting them through Markdown.
11
26
 
12
27
  ## [0.8.0] — 2026-05-12
13
28
 
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # pi-studio
2
2
 
3
- Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace for working with prompts, responses, live working details, Markdown and LaTeX documents, code files, and other common text-based files side by side. Annotate responses and files, add local comments, write, edit, and run prompts, browse prompt and response history, request critiques, and use live preview for code, Markdown, and LaTeX.
3
+ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace for working with prompts, responses, live working details, Markdown and LaTeX documents, interactive HTML previews, code files, and other common text-based files side by side. Annotate responses and files, add local comments, write, edit, and run prompts, browse prompt and response history, request critiques, and use live preview for code, Markdown, LaTeX, and interactive HTML.
4
4
 
5
5
  ## Quick demo
6
6
 
@@ -35,10 +35,10 @@ Extension for [pi](https://pi.dev) that opens a local two-pane browser workspace
35
35
  - strips markers before send (optional)
36
36
  - saves `.annotated.md`
37
37
  - Renders Markdown/LaTeX/code previews (math + Mermaid), theme-synced with pi
38
- - Renders straight, unfenced HTML artifacts in preview via a sandboxed browser iframe with zoom controls, while fenced `html` blocks remain source code
38
+ - Renders straight, unfenced interactive HTML in preview via a sandboxed browser iframe with zoom controls, while fenced `html` blocks remain source code
39
39
  - Embeds local PDFs in Studio Markdown previews via explicit `studio-pdf` fenced blocks
40
40
  - Ships optional `pi-studio-dark` and `pi-studio-light` themes tuned for Studio's browser workspace
41
- - Exports right-pane preview as PDF (pandoc + LaTeX) or standalone HTML, preserving authored HTML artifacts as HTML
41
+ - Exports right-pane preview as PDF (pandoc + LaTeX) or standalone HTML, preserving authored HTML previews as HTML
42
42
  - Exports local files headlessly via `/studio-pdf <path>` to `<name>.studio.pdf` or `/studio-html <path>` to `<name>.studio.html`; without a path, those commands export the last model response to a timestamped file
43
43
  - Shows model/session/context usage in the footer, plus a compact-context action
44
44
 
@@ -1954,6 +1954,51 @@
1954
1954
  updatePaneFocusButtons();
1955
1955
  }
1956
1956
 
1957
+ function snapshotStudioScrollablePositions() {
1958
+ return [sourceTextEl, sourcePreviewEl, critiqueViewEl]
1959
+ .filter((el) => el && typeof el.scrollTop === "number" && typeof el.scrollLeft === "number")
1960
+ .map((el) => ({ el, top: el.scrollTop, left: el.scrollLeft }));
1961
+ }
1962
+
1963
+ function restoreStudioScrollablePositions(snapshot) {
1964
+ if (!Array.isArray(snapshot)) return;
1965
+ snapshot.forEach((entry) => {
1966
+ const el = entry && entry.el;
1967
+ if (!el || !el.isConnected) return;
1968
+ if (typeof entry.top === "number") el.scrollTop = entry.top;
1969
+ if (typeof entry.left === "number") el.scrollLeft = entry.left;
1970
+ });
1971
+ syncEditorHighlightScroll();
1972
+ }
1973
+
1974
+ function scheduleStudioScrollablePositionRestore(snapshot) {
1975
+ if (!Array.isArray(snapshot) || snapshot.length === 0) return;
1976
+ const schedule = typeof window.requestAnimationFrame === "function"
1977
+ ? window.requestAnimationFrame.bind(window)
1978
+ : (cb) => window.setTimeout(cb, 16);
1979
+ window.setTimeout(() => restoreStudioScrollablePositions(snapshot), 0);
1980
+ schedule(() => {
1981
+ restoreStudioScrollablePositions(snapshot);
1982
+ schedule(() => restoreStudioScrollablePositions(snapshot));
1983
+ });
1984
+ }
1985
+
1986
+ function shouldPreserveScrollForPaneActivationEvent(event) {
1987
+ const target = event && event.target;
1988
+ if (!(target instanceof Element)) return true;
1989
+ if (target.closest("button, select, input, a, [role='button'], .studio-copy-block-btn, .preview-comment-add, .preview-comment-jump")) {
1990
+ return false;
1991
+ }
1992
+ return true;
1993
+ }
1994
+
1995
+ function activatePaneFromInteraction(nextPane, event) {
1996
+ const shouldPreserveScroll = shouldPreserveScrollForPaneActivationEvent(event);
1997
+ const snapshot = shouldPreserveScroll ? snapshotStudioScrollablePositions() : [];
1998
+ setActivePane(nextPane);
1999
+ if (shouldPreserveScroll) scheduleStudioScrollablePositionRestore(snapshot);
2000
+ }
2001
+
1957
2002
  function setActivePane(nextPane) {
1958
2003
  activePane = nextPane === "right" ? "right" : "left";
1959
2004
 
@@ -2711,7 +2756,7 @@
2711
2756
  record.shell.classList.toggle("is-height-capped", capped);
2712
2757
  }
2713
2758
  if (record.detail) {
2714
- record.detail.textContent = "HTML artifact";
2759
+ record.detail.textContent = "HTML preview";
2715
2760
  }
2716
2761
  }
2717
2762
 
@@ -2719,7 +2764,7 @@
2719
2764
 
2720
2765
  function renderHtmlArtifactPreview(targetEl, html, pane, options) {
2721
2766
  if (!targetEl) return;
2722
- const title = options && options.title ? String(options.title) : "HTML artifact preview";
2767
+ const title = options && options.title ? String(options.title) : "HTML preview";
2723
2768
  const previewId = "html_artifact_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 10);
2724
2769
  pruneDisconnectedHtmlArtifactFrames();
2725
2770
  clearPreviewJumpHighlight(targetEl);
@@ -2736,7 +2781,7 @@
2736
2781
  label.textContent = title;
2737
2782
  const detail = document.createElement("span");
2738
2783
  detail.className = "studio-html-artifact-detail";
2739
- detail.textContent = "HTML artifact";
2784
+ detail.textContent = "HTML preview";
2740
2785
 
2741
2786
  const tools = document.createElement("span");
2742
2787
  tools.className = "studio-html-artifact-tools";
@@ -2783,10 +2828,10 @@
2783
2828
  });
2784
2829
  return button;
2785
2830
  };
2786
- const zoomOutBtn = makeZoomButton("−", "Zoom out HTML artifact", () => setArtifactZoom(artifactZoom - HTML_ARTIFACT_ZOOM_STEP));
2787
- const zoomResetBtn = makeZoomButton("100%", "Reset HTML artifact zoom", () => setArtifactZoom(1));
2831
+ const zoomOutBtn = makeZoomButton("−", "Zoom out HTML preview", () => setArtifactZoom(artifactZoom - HTML_ARTIFACT_ZOOM_STEP));
2832
+ const zoomResetBtn = makeZoomButton("100%", "Reset HTML preview zoom", () => setArtifactZoom(1));
2788
2833
  zoomResetBtn.classList.add("studio-html-artifact-zoom-reset");
2789
- const zoomInBtn = makeZoomButton("+", "Zoom in HTML artifact", () => setArtifactZoom(artifactZoom + HTML_ARTIFACT_ZOOM_STEP));
2834
+ const zoomInBtn = makeZoomButton("+", "Zoom in HTML preview", () => setArtifactZoom(artifactZoom + HTML_ARTIFACT_ZOOM_STEP));
2790
2835
  zoomControls.appendChild(zoomOutBtn);
2791
2836
  zoomControls.appendChild(zoomResetBtn);
2792
2837
  zoomControls.appendChild(zoomInBtn);
@@ -3882,7 +3927,7 @@
3882
3927
 
3883
3928
  const htmlArtifactSource = getRightPaneHtmlArtifactSource();
3884
3929
  if (htmlArtifactSource) {
3885
- setStatus("PDF export does not support HTML artifacts yet. Export as HTML or use the browser print dialog inside the artifact.", "warning");
3930
+ setStatus("PDF export does not support interactive HTML previews yet. Export as HTML or use the browser print dialog inside the preview.", "warning");
3886
3931
  return;
3887
3932
  }
3888
3933
 
@@ -4400,7 +4445,7 @@
4400
4445
  if (editorView !== "preview") return;
4401
4446
  const text = prepareEditorTextForPreview(sourceTextEl.value || "");
4402
4447
  if (isHtmlArtifactPreviewText(text, editorLanguage)) {
4403
- renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML artifact preview" });
4448
+ renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview" });
4404
4449
  return;
4405
4450
  }
4406
4451
  if (supportsCodePreviewCommentsForCurrentEditor()) {
@@ -4672,7 +4717,7 @@
4672
4717
  return;
4673
4718
  }
4674
4719
  if (isHtmlArtifactPreviewText(editorText, editorLanguage)) {
4675
- renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML artifact preview" });
4720
+ renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview" });
4676
4721
  return;
4677
4722
  }
4678
4723
  if (supportsCodePreviewCommentsForCurrentEditor()) {
@@ -4696,7 +4741,7 @@
4696
4741
 
4697
4742
  if (rightView === "preview") {
4698
4743
  if (isHtmlArtifactPreviewText(markdown, "")) {
4699
- renderHtmlArtifactPreview(critiqueViewEl, markdown, "response", { title: "Response HTML artifact preview" });
4744
+ renderHtmlArtifactPreview(critiqueViewEl, markdown, "response", { title: "Response HTML preview" });
4700
4745
  return;
4701
4746
  }
4702
4747
  const nonce = ++responsePreviewRenderNonce;
@@ -4777,7 +4822,7 @@
4777
4822
  } else if (!canExportPreview) {
4778
4823
  exportPdfBtn.title = "Nothing to export yet.";
4779
4824
  } else if (isHtmlArtifactPreview) {
4780
- exportPdfBtn.title = "This is an HTML artifact preview. Export as HTML; PDF export is not available yet.";
4825
+ exportPdfBtn.title = "This is an interactive HTML preview. Export as HTML; PDF export is not available yet.";
4781
4826
  } else {
4782
4827
  exportPdfBtn.title = "Choose PDF or HTML and export the current right-pane preview.";
4783
4828
  }
@@ -4785,18 +4830,18 @@
4785
4830
  if (exportPreviewPdfBtn) {
4786
4831
  exportPreviewPdfBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview || isHtmlArtifactPreview;
4787
4832
  exportPreviewPdfBtn.title = isHtmlArtifactPreview
4788
- ? "HTML artifact PDF export is not available yet."
4833
+ ? "Interactive HTML preview PDF export is not available yet."
4789
4834
  : "Export the current right-pane preview as PDF.";
4790
4835
  }
4791
4836
  if (exportPreviewHtmlBtn) {
4792
4837
  exportPreviewHtmlBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
4793
4838
  exportPreviewHtmlBtn.title = isHtmlArtifactPreview
4794
- ? "Export the authored HTML artifact."
4839
+ ? "Export the authored HTML preview."
4795
4840
  : "Export the current right-pane preview as standalone HTML.";
4796
4841
  }
4797
4842
  if (exportPreviewControlsEl) {
4798
4843
  exportPreviewControlsEl.title = canExportPreview
4799
- ? (isHtmlArtifactPreview ? "Export this HTML artifact." : "Choose a format and export the current right-pane preview.")
4844
+ ? (isHtmlArtifactPreview ? "Export this HTML preview." : "Choose a format and export the current right-pane preview.")
4800
4845
  : "Switch right pane to a non-empty preview before exporting.";
4801
4846
  }
4802
4847
  if (!canExportPreview || previewExportInProgress) {
@@ -11114,9 +11159,12 @@
11114
11159
  setWsState("Ready");
11115
11160
  const targetUrl = resolveCompanionEditorTargetUrl(message);
11116
11161
  const opened = navigatePendingCompanionWindow(responseRequestId, targetUrl);
11162
+ const readyMessage = typeof message.message === "string" && message.message.trim()
11163
+ ? message.message.trim()
11164
+ : "Opened companion editor with a detached copy of the current editor text.";
11117
11165
  setStatus(
11118
11166
  opened
11119
- ? "Opened companion editor with a detached copy of the current editor text."
11167
+ ? readyMessage
11120
11168
  : (targetUrl ? "Companion editor ready: " + targetUrl : "Companion editor is ready, but Studio did not receive a URL."),
11121
11169
  opened ? "success" : "warning",
11122
11170
  );
@@ -11578,13 +11626,13 @@
11578
11626
  }
11579
11627
 
11580
11628
  if (leftPaneEl) {
11581
- leftPaneEl.addEventListener("mousedown", () => setActivePane("left"));
11582
- leftPaneEl.addEventListener("focusin", () => setActivePane("left"));
11629
+ leftPaneEl.addEventListener("mousedown", (event) => activatePaneFromInteraction("left", event));
11630
+ leftPaneEl.addEventListener("focusin", (event) => activatePaneFromInteraction("left", event));
11583
11631
  }
11584
11632
 
11585
11633
  if (rightPaneEl) {
11586
- rightPaneEl.addEventListener("mousedown", () => setActivePane("right"));
11587
- rightPaneEl.addEventListener("focusin", () => setActivePane("right"));
11634
+ rightPaneEl.addEventListener("mousedown", (event) => activatePaneFromInteraction("right", event));
11635
+ rightPaneEl.addEventListener("focusin", (event) => activatePaneFromInteraction("right", event));
11588
11636
  }
11589
11637
 
11590
11638
  if (leftFocusBtn) {
@@ -12074,10 +12122,6 @@
12074
12122
  if (openCompanionBtn) {
12075
12123
  openCompanionBtn.addEventListener("click", () => {
12076
12124
  const content = sourceTextEl.value;
12077
- if (!content.trim()) {
12078
- setStatus("Editor is empty. Nothing to copy into a companion view.", "warning");
12079
- return;
12080
- }
12081
12125
 
12082
12126
  const requestId = beginUiAction("open_editor_only");
12083
12127
  if (!requestId) return;
package/client/studio.css CHANGED
@@ -77,7 +77,7 @@
77
77
 
78
78
  .export-preview-trigger::after {
79
79
  content: "⌄";
80
- color: var(--muted);
80
+ color: var(--studio-info-text, var(--text));
81
81
  font-size: 14px;
82
82
  line-height: 1;
83
83
  transform: translateY(-1px);
@@ -1598,7 +1598,7 @@
1598
1598
 
1599
1599
  .rendered-markdown .studio-html-artifact-zoom-reset {
1600
1600
  min-width: 42px;
1601
- color: var(--muted);
1601
+ color: var(--studio-info-text, var(--text));
1602
1602
  }
1603
1603
 
1604
1604
  .rendered-markdown .studio-html-artifact-frame {
@@ -3121,7 +3121,7 @@
3121
3121
 
3122
3122
  body.studio-ui-refresh .studio-refresh-chip::after {
3123
3123
  content: "⌄";
3124
- color: var(--muted);
3124
+ color: var(--studio-info-text, var(--text));
3125
3125
  font-size: 14px;
3126
3126
  line-height: 1;
3127
3127
  transform: translateY(-1px);
package/index.ts CHANGED
@@ -3457,6 +3457,49 @@ function normalizeStudioMarkdownFencedBlocks(markdown: string): string {
3457
3457
  return out.join("\n");
3458
3458
  }
3459
3459
 
3460
+ interface StudioYamlFrontMatterSplit {
3461
+ frontMatter: string;
3462
+ body: string;
3463
+ }
3464
+
3465
+ function splitStudioYamlFrontMatter(markdown: string): StudioYamlFrontMatterSplit | null {
3466
+ const source = String(markdown ?? "");
3467
+ const match = source.match(/^(\uFEFF?---[ \t]*(?:\r?\n)[\s\S]*?(?:\r?\n)---[ \t]*(?:\r?\n|$))([\s\S]*)$/);
3468
+ if (!match) return null;
3469
+ return {
3470
+ frontMatter: match[1] ?? "",
3471
+ body: match[2] ?? "",
3472
+ };
3473
+ }
3474
+
3475
+ function mapStudioMarkdownBodyPreservingYamlFrontMatter(markdown: string, transformBody: (body: string) => string): string {
3476
+ const source = String(markdown ?? "");
3477
+ const split = splitStudioYamlFrontMatter(source);
3478
+ if (!split) return transformBody(source);
3479
+ return `${split.frontMatter}${transformBody(split.body)}`;
3480
+ }
3481
+
3482
+ function stripStudioMarkdownHtmlCommentsPreservingYamlFrontMatter(markdown: string): string {
3483
+ return mapStudioMarkdownBodyPreservingYamlFrontMatter(markdown, (body) => stripStudioMarkdownHtmlComments(body));
3484
+ }
3485
+
3486
+ function hasStudioYamlHeaderIncludes(markdown: string): boolean {
3487
+ const split = splitStudioYamlFrontMatter(markdown);
3488
+ if (!split) return false;
3489
+ return /^\s*header-includes\s*:/im.test(split.frontMatter);
3490
+ }
3491
+
3492
+ function prepareStudioMarkdownForPandoc(markdown: string, options?: { preserveLiteralLatexCommands?: boolean }): string {
3493
+ const shouldPreserveLiteralLatexCommands = options?.preserveLiteralLatexCommands !== false;
3494
+ return mapStudioMarkdownBodyPreservingYamlFrontMatter(markdown, (body) => {
3495
+ const normalizedMath = normalizeMathDelimiters(body);
3496
+ const latexReady = shouldPreserveLiteralLatexCommands
3497
+ ? preserveLiteralLatexCommandsInMarkdown(normalizedMath)
3498
+ : normalizedMath;
3499
+ return normalizeObsidianImages(latexReady);
3500
+ });
3501
+ }
3502
+
3460
3503
  function hasStudioMarkdownDiffFence(markdown: string): boolean {
3461
3504
  const lines = String(markdown ?? "").replace(/\r\n/g, "\n").split("\n");
3462
3505
 
@@ -4300,8 +4343,10 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
4300
4343
  const annotationReadySource = !effectiveEditorLanguage || effectiveEditorLanguage === "markdown" || effectiveEditorLanguage === "latex"
4301
4344
  ? replaceStudioAnnotationMarkersForPdf(source)
4302
4345
  : source;
4303
- const commentStrippedSource = stripStudioMarkdownHtmlComments(annotationReadySource);
4304
- return normalizeObsidianImages(preserveLiteralLatexCommandsInMarkdown(normalizeMathDelimiters(commentStrippedSource)));
4346
+ const commentStrippedSource = stripStudioMarkdownHtmlCommentsPreservingYamlFrontMatter(annotationReadySource);
4347
+ return prepareStudioMarkdownForPandoc(commentStrippedSource, {
4348
+ preserveLiteralLatexCommands: !hasStudioYamlHeaderIncludes(annotationReadySource),
4349
+ });
4305
4350
  }
4306
4351
 
4307
4352
  function stripMathMlAnnotationTags(html: string): string {
@@ -4559,7 +4604,7 @@ function decorateStudioPandocSyntaxHtml(html: string): string {
4559
4604
 
4560
4605
  async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string, sourcePath?: string): Promise<string> {
4561
4606
  const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
4562
- const markdownWithoutHtmlComments = isLatex ? markdown : stripStudioMarkdownHtmlComments(markdown);
4607
+ const markdownWithoutHtmlComments = isLatex ? markdown : stripStudioMarkdownHtmlCommentsPreservingYamlFrontMatter(markdown);
4563
4608
  const markdownWithPreviewPageBreaks = isLatex ? markdownWithoutHtmlComments : replaceStudioPreviewPageBreakCommands(markdownWithoutHtmlComments);
4564
4609
  const latexSubfigurePreviewTransform = isLatex
4565
4610
  ? preprocessStudioLatexSubfiguresForPreview(markdownWithPreviewPageBreaks)
@@ -4580,7 +4625,7 @@ async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolea
4580
4625
  }
4581
4626
  const normalizedMarkdown = isLatex
4582
4627
  ? sourceWithResolvedRefs
4583
- : normalizeStudioMarkdownFencedBlocks(normalizeObsidianImages(preserveLiteralLatexCommandsInMarkdown(normalizeMathDelimiters(sourceWithResolvedRefs))));
4628
+ : normalizeStudioMarkdownFencedBlocks(prepareStudioMarkdownForPandoc(sourceWithResolvedRefs));
4584
4629
  const pandocWorkingDir = resolveStudioPandocWorkingDir(resourcePath);
4585
4630
 
4586
4631
  let renderedHtml = await new Promise<string>((resolve, reject) => {
@@ -5501,6 +5546,8 @@ async function renderStudioPdfWithPandoc(
5501
5546
  await mkdir(tempDir, { recursive: true });
5502
5547
  await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions), "utf-8");
5503
5548
 
5549
+ const hasYamlHeaderIncludesForPdf = inputFormat !== "latex" && hasStudioYamlHeaderIncludes(markdownForPdf);
5550
+ const headerIncludeArgs = hasYamlHeaderIncludesForPdf ? [] : ["--include-in-header", preamblePath];
5504
5551
  const args = [
5505
5552
  "-f", inputFormat,
5506
5553
  "-o", outputPath,
@@ -5508,7 +5555,7 @@ async function renderStudioPdfWithPandoc(
5508
5555
  ...buildStudioPdfPandocVariableArgs(pdfOptions, inputFormat !== "latex"),
5509
5556
  "-V", "urlcolor=blue",
5510
5557
  "-V", "linkcolor=blue",
5511
- "--include-in-header", preamblePath,
5558
+ ...headerIncludeArgs,
5512
5559
  ...bibliographyArgs,
5513
5560
  ];
5514
5561
  if (resourcePath) args.push(`--resource-path=${resourcePath}`);
@@ -5652,6 +5699,8 @@ async function renderStudioPdfWithPandoc(
5652
5699
  return { pdf: rendered.pdf, warning: mermaidPrepared.warning ?? rendered.warning };
5653
5700
  }
5654
5701
 
5702
+ const hasYamlHeaderIncludesForPdf = !isLatex && hasStudioYamlHeaderIncludes(markdownForPdf);
5703
+ const headerIncludeArgs = hasYamlHeaderIncludesForPdf ? [] : ["--include-in-header", preamblePath];
5655
5704
  const args = [
5656
5705
  "-f", inputFormat,
5657
5706
  "-o", outputPath,
@@ -5659,7 +5708,7 @@ async function renderStudioPdfWithPandoc(
5659
5708
  ...buildStudioPdfPandocVariableArgs(pdfOptions, !isLatex),
5660
5709
  "-V", "urlcolor=blue",
5661
5710
  "-V", "linkcolor=blue",
5662
- "--include-in-header", preamblePath,
5711
+ ...headerIncludeArgs,
5663
5712
  ...bibliographyArgs,
5664
5713
  ];
5665
5714
  if (resourcePath) args.push(`--resource-path=${resourcePath}`);
@@ -8599,9 +8648,10 @@ export default function (pi: ExtensionAPI) {
8599
8648
  }
8600
8649
 
8601
8650
  const resourceDir = resolveStudioCompanionResourceDir(msg.path, msg.resourceDir, studioCwd);
8651
+ const hasContent = msg.content.trim().length > 0;
8602
8652
  const document: InitialStudioDocument = {
8603
8653
  text: msg.content,
8604
- label: buildStudioCompanionLabel(msg.label),
8654
+ label: hasContent ? buildStudioCompanionLabel(msg.label) : "blank companion editor",
8605
8655
  source: "blank",
8606
8656
  draftId: createStudioDraftId(),
8607
8657
  resourceDir,
@@ -8614,7 +8664,9 @@ export default function (pi: ExtensionAPI) {
8614
8664
  requestId: msg.requestId,
8615
8665
  url,
8616
8666
  relativeUrl: `${parsedUrl.pathname}${parsedUrl.search}`,
8617
- message: "Companion editor is ready with a detached copy of the current editor text.",
8667
+ message: hasContent
8668
+ ? "Companion editor is ready with a detached copy of the current editor text."
8669
+ : "Blank companion editor is ready.",
8618
8670
  });
8619
8671
  return;
8620
8672
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.8.1",
4
- "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code preview",
3
+ "version": "0.8.3",
4
+ "description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, prompt/response history, and live Markdown/LaTeX/code/interactive HTML preview",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {