pi-studio 0.5.29 → 0.5.30
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 +7 -0
- package/client/studio-client.js +46 -12
- package/client/studio.css +7 -3
- package/index.ts +134 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.30] — 2026-03-24
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- LaTeX preview now preserves structured display-math environments such as `bmatrix` inside `\[ ... \]` instead of flattening their rows during Markdown math normalization, and preview display equations now center more robustly across browser engines.
|
|
11
|
+
- Studio now highlights custom `[an: ...]` markers in LaTeX editor syntax-highlighting mode, and PDF export renders those markers as styled annotation badges for both Markdown and LaTeX documents instead of leaving the raw bracket syntax in the final PDF.
|
|
12
|
+
- Right-pane response PDF export now also respects the current annotation-visibility mode, so hidden annotations do not leak into exported PDFs as raw `[an: ...]` text.
|
|
13
|
+
|
|
7
14
|
## [0.5.29] — 2026-03-21
|
|
8
15
|
|
|
9
16
|
### Changed
|
package/client/studio-client.js
CHANGED
|
@@ -1715,7 +1715,9 @@
|
|
|
1715
1715
|
return;
|
|
1716
1716
|
}
|
|
1717
1717
|
|
|
1718
|
-
const markdown = rightView === "editor-preview"
|
|
1718
|
+
const markdown = rightView === "editor-preview"
|
|
1719
|
+
? prepareEditorTextForPdfExport(sourceTextEl.value)
|
|
1720
|
+
: prepareEditorTextForPreview(latestResponseMarkdown);
|
|
1719
1721
|
if (!markdown || !markdown.trim()) {
|
|
1720
1722
|
setStatus("Nothing to export yet.", "warning");
|
|
1721
1723
|
return;
|
|
@@ -2558,17 +2560,49 @@
|
|
|
2558
2560
|
}
|
|
2559
2561
|
|
|
2560
2562
|
if (lang === "latex") {
|
|
2561
|
-
const texPattern = /(%.*$)|(\\(?:documentclass|usepackage|newtheorem|begin|end|section|subsection|subsubsection|chapter|part|title|author|date|maketitle|tableofcontents|includegraphics|caption|label|ref|eqref|cite|textbf|textit|texttt|emph|footnote|centering|newcommand|renewcommand|providecommand|bibliography|bibliographystyle|bibitem|item|input|include)\b)|(\\[A-Za-z]+)|(\{|\})|(\$\$?(?:[^$\\]|\\.)+\$\$?)|(\[(?:.*?)\])/
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2563
|
+
const texPattern = /(%.*$)|(\[an:\s*[^\]]+\])|(\\(?:documentclass|usepackage|newtheorem|begin|end|section|subsection|subsubsection|chapter|part|title|author|date|maketitle|tableofcontents|includegraphics|caption|label|ref|eqref|cite|textbf|textit|texttt|emph|footnote|centering|newcommand|renewcommand|providecommand|bibliography|bibliographystyle|bibitem|item|input|include)\b)|(\\[A-Za-z]+)|(\{|\})|(\$\$?(?:[^$\\]|\\.)+\$\$?)|(\[(?:.*?)\])/gi;
|
|
2564
|
+
let out = "";
|
|
2565
|
+
let lastIndex = 0;
|
|
2566
|
+
texPattern.lastIndex = 0;
|
|
2567
|
+
|
|
2568
|
+
let match;
|
|
2569
|
+
while ((match = texPattern.exec(source)) !== null) {
|
|
2570
|
+
const token = match[0] || "";
|
|
2571
|
+
const start = typeof match.index === "number" ? match.index : 0;
|
|
2572
|
+
|
|
2573
|
+
if (start > lastIndex) {
|
|
2574
|
+
out += escapeHtml(source.slice(lastIndex, start));
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
if (match[1]) {
|
|
2578
|
+
out += wrapHighlight("hl-code-com", token);
|
|
2579
|
+
} else if (match[2]) {
|
|
2580
|
+
out += highlightInlineAnnotations(token, renderMode);
|
|
2581
|
+
} else if (match[3]) {
|
|
2582
|
+
out += wrapHighlight("hl-code-kw", token);
|
|
2583
|
+
} else if (match[4]) {
|
|
2584
|
+
out += wrapHighlight("hl-code-fn", token);
|
|
2585
|
+
} else if (match[5]) {
|
|
2586
|
+
out += wrapHighlight("hl-code-op", token);
|
|
2587
|
+
} else if (match[6]) {
|
|
2588
|
+
out += wrapHighlight("hl-code-str", token);
|
|
2589
|
+
} else if (match[7]) {
|
|
2590
|
+
out += wrapHighlight("hl-code-num", token);
|
|
2591
|
+
} else {
|
|
2592
|
+
out += escapeHtml(token);
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
lastIndex = start + token.length;
|
|
2596
|
+
if (token.length === 0) {
|
|
2597
|
+
texPattern.lastIndex += 1;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (lastIndex < source.length) {
|
|
2602
|
+
out += escapeHtml(source.slice(lastIndex));
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
return out;
|
|
2572
2606
|
}
|
|
2573
2607
|
|
|
2574
2608
|
if (lang === "diff") {
|
package/client/studio.css
CHANGED
|
@@ -874,7 +874,9 @@
|
|
|
874
874
|
|
|
875
875
|
.rendered-markdown mjx-container[display="true"] {
|
|
876
876
|
display: block;
|
|
877
|
-
|
|
877
|
+
width: fit-content;
|
|
878
|
+
max-width: 100%;
|
|
879
|
+
margin: 1em auto;
|
|
878
880
|
text-align: center;
|
|
879
881
|
overflow-x: auto;
|
|
880
882
|
overflow-y: hidden;
|
|
@@ -893,7 +895,7 @@
|
|
|
893
895
|
|
|
894
896
|
.rendered-markdown .studio-display-equation-body math[display="block"],
|
|
895
897
|
.rendered-markdown .studio-display-equation-body mjx-container[display="true"] {
|
|
896
|
-
margin: 0;
|
|
898
|
+
margin: 0 auto;
|
|
897
899
|
}
|
|
898
900
|
|
|
899
901
|
.rendered-markdown .studio-display-equation-number {
|
|
@@ -910,7 +912,9 @@
|
|
|
910
912
|
|
|
911
913
|
.rendered-markdown math[display="block"] {
|
|
912
914
|
display: block;
|
|
913
|
-
|
|
915
|
+
width: fit-content;
|
|
916
|
+
max-width: 100%;
|
|
917
|
+
margin: 1em auto;
|
|
914
918
|
text-align: center;
|
|
915
919
|
overflow-x: auto;
|
|
916
920
|
overflow-y: hidden;
|
package/index.ts
CHANGED
|
@@ -205,6 +205,11 @@ const PDF_PREAMBLE = `\\usepackage{titlesec}
|
|
|
205
205
|
\\titleformat{\\subsubsection}{\\normalsize\\bfseries\\sffamily}{}{0pt}{}
|
|
206
206
|
\\titlespacing*{\\section}{0pt}{1.5ex plus 0.5ex minus 0.2ex}{1ex plus 0.2ex}
|
|
207
207
|
\\titlespacing*{\\subsection}{0pt}{1.2ex plus 0.4ex minus 0.2ex}{0.6ex plus 0.1ex}
|
|
208
|
+
\\usepackage{xcolor}
|
|
209
|
+
\\definecolor{StudioAnnotationBg}{HTML}{EAF3FF}
|
|
210
|
+
\\definecolor{StudioAnnotationBorder}{HTML}{8CB8FF}
|
|
211
|
+
\\definecolor{StudioAnnotationText}{HTML}{1F5FBF}
|
|
212
|
+
\\newcommand{\\studioannotation}[1]{\\begingroup\\setlength{\\fboxsep}{1.5pt}\\fcolorbox{StudioAnnotationBorder}{StudioAnnotationBg}{\\textcolor{StudioAnnotationText}{\\sffamily\\footnotesize\\strut #1}}\\endgroup}
|
|
208
213
|
\\usepackage{caption}
|
|
209
214
|
\\captionsetup[figure]{justification=raggedright,singlelinecheck=false}
|
|
210
215
|
\\usepackage{enumitem}
|
|
@@ -2466,6 +2471,9 @@ function isLikelyMathExpression(expr: string): boolean {
|
|
|
2466
2471
|
|
|
2467
2472
|
function collapseDisplayMathContent(expr: string): string {
|
|
2468
2473
|
let content = expr.trim();
|
|
2474
|
+
if (/\\begin\{[^}]+\}|\\end\{[^}]+\}/.test(content)) {
|
|
2475
|
+
return content;
|
|
2476
|
+
}
|
|
2469
2477
|
if (content.includes("\\\\") || content.includes("\n")) {
|
|
2470
2478
|
content = content.replace(/\\\\\s*/g, " ");
|
|
2471
2479
|
content = content.replace(/\s*\n\s*/g, " ");
|
|
@@ -2646,6 +2654,85 @@ function inferStudioPdfLanguage(markdown: string, editorLanguage?: string): stri
|
|
|
2646
2654
|
return undefined;
|
|
2647
2655
|
}
|
|
2648
2656
|
|
|
2657
|
+
function escapeStudioPdfLatexText(text: string): string {
|
|
2658
|
+
return String(text ?? "")
|
|
2659
|
+
.replace(/\r\n/g, "\n")
|
|
2660
|
+
.replace(/\s*\n\s*/g, " ")
|
|
2661
|
+
.trim()
|
|
2662
|
+
.replace(/\\/g, "\\textbackslash{}")
|
|
2663
|
+
.replace(/([{}%#$&_])/g, "\\$1")
|
|
2664
|
+
.replace(/~/g, "\\textasciitilde{}")
|
|
2665
|
+
.replace(/\^/g, "\\textasciicircum{}")
|
|
2666
|
+
.replace(/\s{2,}/g, " ");
|
|
2667
|
+
}
|
|
2668
|
+
|
|
2669
|
+
function replaceStudioAnnotationMarkersForPdfInSegment(text: string): string {
|
|
2670
|
+
return String(text ?? "")
|
|
2671
|
+
.replace(/\[an:\s*([^\]]+?)\]/gi, (_match, markerText: string) => {
|
|
2672
|
+
const cleaned = escapeStudioPdfLatexText(markerText);
|
|
2673
|
+
if (!cleaned) return "";
|
|
2674
|
+
return `\\studioannotation{${cleaned}}`;
|
|
2675
|
+
})
|
|
2676
|
+
.replace(/\{\[\}\s*an:\s*([\s\S]*?)\s*\{\]\}/gi, (_match, markerText: string) => {
|
|
2677
|
+
const cleaned = escapeStudioPdfLatexText(markerText);
|
|
2678
|
+
if (!cleaned) return "";
|
|
2679
|
+
return `\\studioannotation{${cleaned}}`;
|
|
2680
|
+
});
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
function replaceStudioAnnotationMarkersForPdf(markdown: string): string {
|
|
2684
|
+
const lines = String(markdown ?? "").split("\n");
|
|
2685
|
+
const out: string[] = [];
|
|
2686
|
+
let plainBuffer: string[] = [];
|
|
2687
|
+
let inFence = false;
|
|
2688
|
+
let fenceChar: "`" | "~" | undefined;
|
|
2689
|
+
let fenceLength = 0;
|
|
2690
|
+
|
|
2691
|
+
const flushPlain = () => {
|
|
2692
|
+
if (plainBuffer.length === 0) return;
|
|
2693
|
+
out.push(replaceStudioAnnotationMarkersForPdfInSegment(plainBuffer.join("\n")));
|
|
2694
|
+
plainBuffer = [];
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2697
|
+
for (const line of lines) {
|
|
2698
|
+
const trimmed = line.trimStart();
|
|
2699
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
2700
|
+
|
|
2701
|
+
if (fenceMatch) {
|
|
2702
|
+
const marker = fenceMatch[1]!;
|
|
2703
|
+
const markerChar = marker[0] as "`" | "~";
|
|
2704
|
+
const markerLength = marker.length;
|
|
2705
|
+
|
|
2706
|
+
if (!inFence) {
|
|
2707
|
+
flushPlain();
|
|
2708
|
+
inFence = true;
|
|
2709
|
+
fenceChar = markerChar;
|
|
2710
|
+
fenceLength = markerLength;
|
|
2711
|
+
out.push(line);
|
|
2712
|
+
continue;
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2715
|
+
if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
2716
|
+
inFence = false;
|
|
2717
|
+
fenceChar = undefined;
|
|
2718
|
+
fenceLength = 0;
|
|
2719
|
+
}
|
|
2720
|
+
|
|
2721
|
+
out.push(line);
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
|
|
2725
|
+
if (inFence) {
|
|
2726
|
+
out.push(line);
|
|
2727
|
+
} else {
|
|
2728
|
+
plainBuffer.push(line);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
flushPlain();
|
|
2733
|
+
return out.join("\n");
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2649
2736
|
function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLanguage?: string): string {
|
|
2650
2737
|
if (isLatex) return markdown;
|
|
2651
2738
|
const effectiveEditorLanguage = inferStudioPdfLanguage(markdown, editorLanguage);
|
|
@@ -2653,7 +2740,10 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
|
|
|
2653
2740
|
&& !isStudioSingleFencedCodeBlock(markdown)
|
|
2654
2741
|
? wrapStudioCodeAsMarkdown(markdown, effectiveEditorLanguage)
|
|
2655
2742
|
: markdown;
|
|
2656
|
-
|
|
2743
|
+
const annotationReadySource = !effectiveEditorLanguage || effectiveEditorLanguage === "markdown" || effectiveEditorLanguage === "latex"
|
|
2744
|
+
? replaceStudioAnnotationMarkersForPdf(source)
|
|
2745
|
+
: source;
|
|
2746
|
+
return normalizeObsidianImages(normalizeMathDelimiters(annotationReadySource));
|
|
2657
2747
|
}
|
|
2658
2748
|
|
|
2659
2749
|
function stripMathMlAnnotationTags(html: string): string {
|
|
@@ -2980,6 +3070,46 @@ async function renderStudioLiteralTextPdf(text: string, title = "Studio export")
|
|
|
2980
3070
|
}
|
|
2981
3071
|
}
|
|
2982
3072
|
|
|
3073
|
+
function replaceStudioAnnotationMarkersInGeneratedLatex(latex: string): string {
|
|
3074
|
+
const lines = String(latex ?? "").split("\n");
|
|
3075
|
+
const out: string[] = [];
|
|
3076
|
+
const rawEnvStack: string[] = [];
|
|
3077
|
+
const rawEnvNames = new Set(["verbatim", "Verbatim", "Highlighting", "lstlisting"]);
|
|
3078
|
+
|
|
3079
|
+
const updateRawEnvStack = (line: string) => {
|
|
3080
|
+
const envPattern = /\\(begin|end)\{([^}]+)\}/g;
|
|
3081
|
+
let match: RegExpExecArray | null;
|
|
3082
|
+
while ((match = envPattern.exec(line)) !== null) {
|
|
3083
|
+
const kind = match[1];
|
|
3084
|
+
const envName = match[2];
|
|
3085
|
+
if (!envName || !rawEnvNames.has(envName)) continue;
|
|
3086
|
+
if (kind === "begin") {
|
|
3087
|
+
rawEnvStack.push(envName);
|
|
3088
|
+
} else {
|
|
3089
|
+
for (let i = rawEnvStack.length - 1; i >= 0; i -= 1) {
|
|
3090
|
+
if (rawEnvStack[i] === envName) {
|
|
3091
|
+
rawEnvStack.splice(i, 1);
|
|
3092
|
+
break;
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3099
|
+
for (const line of lines) {
|
|
3100
|
+
if (rawEnvStack.length > 0) {
|
|
3101
|
+
out.push(line);
|
|
3102
|
+
updateRawEnvStack(line);
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
|
|
3106
|
+
out.push(replaceStudioAnnotationMarkersForPdfInSegment(line));
|
|
3107
|
+
updateRawEnvStack(line);
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
return out.join("\n");
|
|
3111
|
+
}
|
|
3112
|
+
|
|
2983
3113
|
async function renderStudioPdfFromGeneratedLatex(
|
|
2984
3114
|
markdown: string,
|
|
2985
3115
|
pandocCommand: string,
|
|
@@ -3057,7 +3187,8 @@ async function renderStudioPdfFromGeneratedLatex(
|
|
|
3057
3187
|
|
|
3058
3188
|
const generatedLatex = await readFile(latexPath, "utf-8");
|
|
3059
3189
|
const injectedLatex = injectStudioLatexPdfSubfigureBlocks(generatedLatex, subfigureGroups, sourcePath, resourcePath);
|
|
3060
|
-
const
|
|
3190
|
+
const annotationReadyLatex = replaceStudioAnnotationMarkersInGeneratedLatex(injectedLatex);
|
|
3191
|
+
const normalizedLatex = normalizeStudioGeneratedFigureCaptions(annotationReadyLatex);
|
|
3061
3192
|
await writeFile(latexPath, normalizedLatex, "utf-8");
|
|
3062
3193
|
|
|
3063
3194
|
await new Promise<void>((resolve, reject) => {
|
|
@@ -3219,7 +3350,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
3219
3350
|
}
|
|
3220
3351
|
};
|
|
3221
3352
|
|
|
3222
|
-
if (isLatex && latexSubfigurePdfTransform.groups.length > 0) {
|
|
3353
|
+
if (isLatex && (latexSubfigurePdfTransform.groups.length > 0 || /\[an:\s*[^\]]+\]/i.test(sourceWithResolvedRefs))) {
|
|
3223
3354
|
return await renderStudioPdfFromGeneratedLatex(
|
|
3224
3355
|
sourceWithResolvedRefs,
|
|
3225
3356
|
pandocCommand,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.30",
|
|
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",
|