pi-studio 0.5.9 → 0.5.10

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/index.ts +50 -5
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,11 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.10] — 2026-03-14
8
+
9
+ ### Fixed
10
+ - Studio preview/PDF math normalization is now more robust for model-emitted `\(...\)` / `\[...\]` math, including malformed mixed delimiters like `$\(...\)$`, optional spacing around those mixed delimiters, and multiline display-math line-break formatting that previously leaked raw/broken `$$` output into preview.
11
+
7
12
  ## [0.5.9] — 2026-03-13
8
13
 
9
14
  ### Fixed
package/index.ts CHANGED
@@ -991,13 +991,58 @@ async function fetchLatestNpmVersion(packageName: string, timeoutMs = UPDATE_CHE
991
991
  }
992
992
  }
993
993
 
994
+ function isLikelyMathExpression(expr: string): boolean {
995
+ const content = expr.trim();
996
+ if (content.length === 0) return false;
997
+
998
+ if (/\\[a-zA-Z]+/.test(content)) return true; // LaTeX commands like \frac, \alpha
999
+ if (/[0-9]/.test(content)) return true;
1000
+ if (/[=+\-*/^_<>≤≥±×÷]/u.test(content)) return true;
1001
+ if (/[{}]/.test(content)) return true;
1002
+ if (/[α-ωΑ-Ω]/u.test(content)) return true;
1003
+ if (/^[A-Za-z]$/.test(content)) return true; // single-variable forms like \(x\)
1004
+
1005
+ // Plain words (e.g. escaped markdown like \[not a link\]) are not math.
1006
+ if (/^[A-Za-z][A-Za-z\s'".,:;!?-]*[A-Za-z]$/.test(content)) return false;
1007
+
1008
+ return false;
1009
+ }
1010
+
1011
+ function collapseDisplayMathContent(expr: string): string {
1012
+ let content = expr.trim();
1013
+ if (content.includes("\\\\") || content.includes("\n")) {
1014
+ content = content.replace(/\\\\\s*/g, " ");
1015
+ content = content.replace(/\s*\n\s*/g, " ");
1016
+ content = content.replace(/\s{2,}/g, " ").trim();
1017
+ }
1018
+ return content;
1019
+ }
1020
+
994
1021
  function normalizeMathDelimitersInSegment(markdown: string): string {
995
- let normalized = markdown.replace(/\\\[\s*([\s\S]*?)\s*\\\]/g, (_match, expr: string) => {
1022
+ let normalized = markdown.replace(/\$\s*\\\(([\s\S]*?)\\\)\s*\$/g, (match, expr: string) => {
1023
+ if (!isLikelyMathExpression(expr)) return match;
1024
+ const content = expr.trim();
1025
+ return content.length > 0 ? `\\(${content}\\)` : "\\(\\)";
1026
+ });
1027
+
1028
+ normalized = normalized.replace(/\$\s*\\\[\s*([\s\S]*?)\s*\\\]\s*\$/g, (match, expr: string) => {
1029
+ if (!isLikelyMathExpression(expr)) return match;
1030
+ const content = collapseDisplayMathContent(expr);
1031
+ return content.length > 0 ? `\\[${content}\\]` : "\\[\\]";
1032
+ });
1033
+
1034
+ normalized = normalized.replace(/\\\[\s*([\s\S]*?)\s*\\\]/g, (match, expr: string) => {
1035
+ if (!isLikelyMathExpression(expr)) return `[${expr.trim()}]`;
1036
+ const content = collapseDisplayMathContent(expr);
1037
+ return content.length > 0 ? `\\[${content}\\]` : "\\[\\]";
1038
+ });
1039
+
1040
+ normalized = normalized.replace(/\\\(([\s\S]*?)\\\)/g, (match, expr: string) => {
1041
+ if (!isLikelyMathExpression(expr)) return `(${expr})`;
996
1042
  const content = expr.trim();
997
- return content.length > 0 ? `$$\n${content}\n$$` : "$$\n$$";
1043
+ return content.length > 0 ? `\\(${content}\\)` : "\\(\\)";
998
1044
  });
999
1045
 
1000
- normalized = normalized.replace(/\\\(([\s\S]*?)\\\)/g, (_match, expr: string) => `$${expr}$`);
1001
1046
  return normalized;
1002
1047
  }
1003
1048
 
@@ -1216,7 +1261,7 @@ async function preprocessStudioMermaidForPdf(markdown: string, workDir: string):
1216
1261
 
1217
1262
  async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string): Promise<string> {
1218
1263
  const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
1219
- const inputFormat = isLatex ? "latex" : "markdown+tex_math_dollars+autolink_bare_uris-raw_html";
1264
+ const inputFormat = isLatex ? "latex" : "markdown+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris-raw_html";
1220
1265
  const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none"];
1221
1266
  if (resourcePath) {
1222
1267
  args.push(`--resource-path=${resourcePath}`);
@@ -1288,7 +1333,7 @@ async function renderStudioPdfWithPandoc(
1288
1333
  const pdfEngine = process.env.PANDOC_PDF_ENGINE?.trim() || "xelatex";
1289
1334
  const inputFormat = isLatex
1290
1335
  ? "latex"
1291
- : "markdown+tex_math_dollars+autolink_bare_uris+superscript+subscript-raw_html";
1336
+ : "markdown+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris+superscript+subscript-raw_html";
1292
1337
  const normalizedMarkdown = isLatex ? markdown : normalizeObsidianImages(normalizeMathDelimiters(markdown));
1293
1338
 
1294
1339
  const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",