pi-studio 0.5.56 → 0.5.57
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 +11 -0
- package/WORKFLOW.md +5 -3
- package/client/studio-client.js +7 -22
- package/index.ts +17 -7
- package/package.json +1 -1
- package/shared/studio-markdown-latex-literals.js +86 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.5.57] — 2026-04-20
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- The inserted annotated-reply scaffold now renders more cleanly in Markdown preview, using `annotated reply: below` plus a short metadata list instead of relying on hard line-break spacing.
|
|
11
|
+
- Local review-note comment boxes now use normal multiline textarea behavior, so `Enter` inserts a newline while edits continue saving automatically as you type.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- Markdown preview now preserves standalone LaTeX math definition lines such as `\newcommand`, `\def`, and `\DeclareMathOperator` so custom math macros/operators can work in Markdown documents instead of showing up as literal preview text.
|
|
15
|
+
- Markdown PDF export now moves standalone LaTeX math definition lines into the generated PDF preamble when needed, avoiding LaTeX errors like `Can be used only in preamble` for commands such as `\DeclareMathOperator`.
|
|
16
|
+
- Annotated-reply header detection now accepts both the older `annotated reply below:` form and the newer `annotated reply: below` form when removing/reapplying the scaffold.
|
|
17
|
+
|
|
7
18
|
## [0.5.56] — 2026-04-15
|
|
8
19
|
|
|
9
20
|
### Removed
|
package/WORKFLOW.md
CHANGED
|
@@ -20,9 +20,11 @@ Studio uses a **single workspace**:
|
|
|
20
20
|
Adds/updates an `annotated-reply` compatible scaffold in the editor:
|
|
21
21
|
|
|
22
22
|
```md
|
|
23
|
-
annotated reply below
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
annotated reply: below
|
|
24
|
+
|
|
25
|
+
- original source: <last model response | file <path> | studio editor>
|
|
26
|
+
- user annotation syntax: [an: your note]
|
|
27
|
+
- precedence: later messages supersede these annotations unless user explicitly references them
|
|
26
28
|
|
|
27
29
|
---
|
|
28
30
|
|
package/client/studio-client.js
CHANGED
|
@@ -8103,7 +8103,7 @@
|
|
|
8103
8103
|
const textarea = document.createElement("textarea");
|
|
8104
8104
|
textarea.value = String(note.text || "");
|
|
8105
8105
|
textarea.placeholder = "Write a local comment here…";
|
|
8106
|
-
textarea.title = "Write a local comment.
|
|
8106
|
+
textarea.title = "Write a local comment. Enter inserts a new line; changes save automatically as you type.";
|
|
8107
8107
|
card.appendChild(textarea);
|
|
8108
8108
|
|
|
8109
8109
|
const footer = document.createElement("div");
|
|
@@ -8169,22 +8169,6 @@
|
|
|
8169
8169
|
updateReviewNotesUi();
|
|
8170
8170
|
});
|
|
8171
8171
|
|
|
8172
|
-
textarea.addEventListener("keydown", (event) => {
|
|
8173
|
-
if (
|
|
8174
|
-
event.key === "Enter"
|
|
8175
|
-
&& !event.shiftKey
|
|
8176
|
-
&& !event.altKey
|
|
8177
|
-
&& !event.ctrlKey
|
|
8178
|
-
&& !event.metaKey
|
|
8179
|
-
) {
|
|
8180
|
-
event.preventDefault();
|
|
8181
|
-
textarea.blur();
|
|
8182
|
-
if (!convertBtn.disabled) {
|
|
8183
|
-
convertBtn.focus();
|
|
8184
|
-
}
|
|
8185
|
-
}
|
|
8186
|
-
});
|
|
8187
|
-
|
|
8188
8172
|
reviewNotesListEl.appendChild(card);
|
|
8189
8173
|
|
|
8190
8174
|
if (pendingReviewNoteInlineFocusId && pendingReviewNoteInlineFocusId === note.id && isReviewNotesOpen()) {
|
|
@@ -9684,10 +9668,10 @@
|
|
|
9684
9668
|
|
|
9685
9669
|
function buildAnnotationHeader() {
|
|
9686
9670
|
const sourceDescriptor = describeSourceForAnnotation();
|
|
9687
|
-
let header = "annotated reply below
|
|
9688
|
-
header += "original source: " + sourceDescriptor + "\n";
|
|
9689
|
-
header += "user annotation syntax: [an: note]\n";
|
|
9690
|
-
header += "precedence: later messages supersede these annotations unless user explicitly references them\n\n---\n\n";
|
|
9671
|
+
let header = "annotated reply: below\n\n";
|
|
9672
|
+
header += "- original source: " + sourceDescriptor + "\n";
|
|
9673
|
+
header += "- user annotation syntax: [an: note]\n";
|
|
9674
|
+
header += "- precedence: later messages supersede these annotations unless user explicitly references them\n\n---\n\n";
|
|
9691
9675
|
return header;
|
|
9692
9676
|
}
|
|
9693
9677
|
|
|
@@ -9697,7 +9681,8 @@
|
|
|
9697
9681
|
|
|
9698
9682
|
function stripAnnotationHeader(text) {
|
|
9699
9683
|
const normalized = String(text || "").replace(/\r\n/g, "\n");
|
|
9700
|
-
|
|
9684
|
+
const lower = normalized.toLowerCase();
|
|
9685
|
+
if (!lower.startsWith("annotated reply: below") && !lower.startsWith("annotated reply below:")) {
|
|
9701
9686
|
return { hadHeader: false, body: normalized };
|
|
9702
9687
|
}
|
|
9703
9688
|
|
package/index.ts
CHANGED
|
@@ -20,7 +20,10 @@ import {
|
|
|
20
20
|
transformStudioMarkdownOutsideFences,
|
|
21
21
|
} from "./shared/studio-annotation-scanner.js";
|
|
22
22
|
import { stripStudioMarkdownHtmlComments } from "./shared/studio-markdown-html-comments.js";
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
extractStandaloneLatexDefinitionsFromMarkdown,
|
|
25
|
+
preserveLiteralLatexCommandsInMarkdown,
|
|
26
|
+
} from "./shared/studio-markdown-latex-literals.js";
|
|
24
27
|
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
25
28
|
|
|
26
29
|
type Lens = "writing" | "code";
|
|
@@ -477,7 +480,7 @@ function buildStudioPdfCalloutTitleSizeCommand(options?: StudioPdfRenderOptions)
|
|
|
477
480
|
return "\\footnotesize";
|
|
478
481
|
}
|
|
479
482
|
|
|
480
|
-
function buildStudioPdfPreamble(options?: StudioPdfRenderOptions): string {
|
|
483
|
+
function buildStudioPdfPreamble(options?: StudioPdfRenderOptions, extraPreamble = ""): string {
|
|
481
484
|
const sectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.sectionSize, "\\Large");
|
|
482
485
|
const subsectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.subsectionSize, "\\large");
|
|
483
486
|
const subsubsectionHeadingSize = buildStudioPdfHeadingSizeCommand(options?.subsubsectionSize, "\\normalsize");
|
|
@@ -543,7 +546,7 @@ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions): string {
|
|
|
543
546
|
\\RecustomVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\\\\{\\},breaklines,breakanywhere,bgcolor=StudioCodeBlockBg,framesep=2mm}%
|
|
544
547
|
}
|
|
545
548
|
\\makeatother
|
|
546
|
-
`;
|
|
549
|
+
${extraPreamble ? `${extraPreamble.trim()}\n` : ""}`;
|
|
547
550
|
}
|
|
548
551
|
|
|
549
552
|
type StudioThemeMode = "dark" | "light";
|
|
@@ -4478,6 +4481,7 @@ async function renderStudioPdfFromGeneratedLatex(
|
|
|
4478
4481
|
calloutBlocks: StudioPdfMarkdownCalloutBlock[] = [],
|
|
4479
4482
|
alignedImageBlocks: StudioPdfAlignedImageBlock[] = [],
|
|
4480
4483
|
pdfOptions?: StudioPdfRenderOptions,
|
|
4484
|
+
extraPreamble = "",
|
|
4481
4485
|
): Promise<{ pdf: Buffer; warning?: string }> {
|
|
4482
4486
|
const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
|
|
4483
4487
|
const preamblePath = join(tempDir, "_pdf_preamble.tex");
|
|
@@ -4485,7 +4489,7 @@ async function renderStudioPdfFromGeneratedLatex(
|
|
|
4485
4489
|
const outputPath = join(tempDir, "studio-export.pdf");
|
|
4486
4490
|
|
|
4487
4491
|
await mkdir(tempDir, { recursive: true });
|
|
4488
|
-
await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions), "utf-8");
|
|
4492
|
+
await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, extraPreamble), "utf-8");
|
|
4489
4493
|
|
|
4490
4494
|
const pandocArgs = [
|
|
4491
4495
|
"-f", inputFormat,
|
|
@@ -4767,17 +4771,22 @@ async function renderStudioPdfWithPandoc(
|
|
|
4767
4771
|
? "latex"
|
|
4768
4772
|
: "markdown+lists_without_preceding_blankline-blank_before_blockquote-blank_before_header+tex_math_dollars+tex_math_single_backslash+tex_math_double_backslash+autolink_bare_uris+superscript+subscript-raw_html";
|
|
4769
4773
|
const normalizedMarkdown = prepareStudioPdfMarkdown(pdfAlignedImageTransform.markdown, isLatex, effectiveEditorLanguage);
|
|
4774
|
+
const markdownPreambleSplit = !isLatex && (!effectiveEditorLanguage || effectiveEditorLanguage === "markdown")
|
|
4775
|
+
? extractStandaloneLatexDefinitionsFromMarkdown(normalizedMarkdown)
|
|
4776
|
+
: { body: normalizedMarkdown, definitions: [], preamble: "" };
|
|
4777
|
+
const normalizedMarkdownBody = markdownPreambleSplit.body;
|
|
4778
|
+
const extraPdfPreamble = markdownPreambleSplit.preamble;
|
|
4770
4779
|
|
|
4771
4780
|
const tempDir = join(tmpdir(), `pi-studio-pdf-${Date.now()}-${randomUUID()}`);
|
|
4772
4781
|
const preamblePath = join(tempDir, "_pdf_preamble.tex");
|
|
4773
4782
|
const outputPath = join(tempDir, "studio-export.pdf");
|
|
4774
4783
|
|
|
4775
4784
|
await mkdir(tempDir, { recursive: true });
|
|
4776
|
-
await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions), "utf-8");
|
|
4785
|
+
await writeFile(preamblePath, buildStudioPdfPreamble(pdfOptions, extraPdfPreamble), "utf-8");
|
|
4777
4786
|
|
|
4778
4787
|
const mermaidPrepared: StudioMermaidPdfPreprocessResult = isLatex
|
|
4779
|
-
? { markdown:
|
|
4780
|
-
: await preprocessStudioMermaidForPdf(
|
|
4788
|
+
? { markdown: normalizedMarkdownBody, found: 0, replaced: 0, failed: 0, missingCli: false }
|
|
4789
|
+
: await preprocessStudioMermaidForPdf(normalizedMarkdownBody, tempDir);
|
|
4781
4790
|
const markdownForPdf = mermaidPrepared.markdown;
|
|
4782
4791
|
const hasDiffBlocks = !isLatex && hasStudioMarkdownDiffFence(markdownForPdf);
|
|
4783
4792
|
|
|
@@ -4795,6 +4804,7 @@ async function renderStudioPdfWithPandoc(
|
|
|
4795
4804
|
pdfCalloutTransform.blocks,
|
|
4796
4805
|
pdfAlignedImageTransform.blocks,
|
|
4797
4806
|
pdfOptions,
|
|
4807
|
+
extraPdfPreamble,
|
|
4798
4808
|
);
|
|
4799
4809
|
await rm(tempDir, { recursive: true, force: true }).catch(() => undefined);
|
|
4800
4810
|
return { pdf: rendered.pdf, warning: mermaidPrepared.warning ?? rendered.warning };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.57",
|
|
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",
|
|
@@ -51,6 +51,18 @@ const STUDIO_LITERAL_MARKDOWN_LATEX_COMMANDS = new Set([
|
|
|
51
51
|
"tex",
|
|
52
52
|
]);
|
|
53
53
|
|
|
54
|
+
const STUDIO_STANDALONE_MARKDOWN_LATEX_DEFINITION_COMMANDS = new Set([
|
|
55
|
+
"newcommand",
|
|
56
|
+
"renewcommand",
|
|
57
|
+
"providecommand",
|
|
58
|
+
"declaremathoperator",
|
|
59
|
+
"def",
|
|
60
|
+
"gdef",
|
|
61
|
+
"edef",
|
|
62
|
+
"xdef",
|
|
63
|
+
"let",
|
|
64
|
+
]);
|
|
65
|
+
|
|
54
66
|
function isEscapedAt(text, index) {
|
|
55
67
|
let slashCount = 0;
|
|
56
68
|
for (let i = index - 1; i >= 0 && text[i] === "\\"; i -= 1) {
|
|
@@ -70,12 +82,40 @@ function findClosingUnescapedDelimiter(text, startIndex, delimiter) {
|
|
|
70
82
|
return -1;
|
|
71
83
|
}
|
|
72
84
|
|
|
85
|
+
function isStandaloneLatexDefinitionLine(line) {
|
|
86
|
+
const commandMatch = String(line || "").match(/^[ \t]{0,3}\\([A-Za-z@]+)\*?(?=\s|\\|\{|\[|$)/);
|
|
87
|
+
if (!commandMatch) return false;
|
|
88
|
+
|
|
89
|
+
const commandName = String(commandMatch[1] || "").toLowerCase();
|
|
90
|
+
return STUDIO_STANDALONE_MARKDOWN_LATEX_DEFINITION_COMMANDS.has(commandName);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function readStandaloneLatexDefinitionLine(text, startIndex) {
|
|
94
|
+
if (startIndex > 0 && text[startIndex - 1] !== "\n") return null;
|
|
95
|
+
|
|
96
|
+
const lineEndIndex = text.indexOf("\n", startIndex);
|
|
97
|
+
const sliceEnd = lineEndIndex >= 0 ? lineEndIndex : text.length;
|
|
98
|
+
const line = text.slice(startIndex, sliceEnd);
|
|
99
|
+
if (!isStandaloneLatexDefinitionLine(line)) return null;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
text: lineEndIndex >= 0 ? text.slice(startIndex, lineEndIndex + 1) : line,
|
|
103
|
+
nextIndex: lineEndIndex >= 0 ? lineEndIndex + 1 : text.length,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
73
107
|
function preserveLiteralLatexCommandsInMarkdownSegment(markdown) {
|
|
74
108
|
const source = String(markdown || "");
|
|
75
109
|
let out = "";
|
|
76
110
|
let index = 0;
|
|
77
111
|
|
|
78
112
|
while (index < source.length) {
|
|
113
|
+
const standaloneDefinitionLine = readStandaloneLatexDefinitionLine(source, index);
|
|
114
|
+
if (standaloneDefinitionLine) {
|
|
115
|
+
out += standaloneDefinitionLine.text;
|
|
116
|
+
index = standaloneDefinitionLine.nextIndex;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
79
119
|
if (source[index] === "`") {
|
|
80
120
|
let tickCount = 1;
|
|
81
121
|
while (source[index + tickCount] === "`") tickCount += 1;
|
|
@@ -201,3 +241,49 @@ export function preserveLiteralLatexCommandsInMarkdown(markdown) {
|
|
|
201
241
|
flushPlain();
|
|
202
242
|
return out.join("\n");
|
|
203
243
|
}
|
|
244
|
+
|
|
245
|
+
export function extractStandaloneLatexDefinitionsFromMarkdown(markdown) {
|
|
246
|
+
const lines = String(markdown || "").split("\n");
|
|
247
|
+
const bodyLines = [];
|
|
248
|
+
const definitions = [];
|
|
249
|
+
let inFence = false;
|
|
250
|
+
let fenceChar;
|
|
251
|
+
let fenceLength = 0;
|
|
252
|
+
|
|
253
|
+
for (const line of lines) {
|
|
254
|
+
const trimmed = line.trimStart();
|
|
255
|
+
const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
|
|
256
|
+
|
|
257
|
+
if (fenceMatch) {
|
|
258
|
+
const marker = fenceMatch[1];
|
|
259
|
+
const markerChar = marker[0];
|
|
260
|
+
const markerLength = marker.length;
|
|
261
|
+
|
|
262
|
+
if (!inFence) {
|
|
263
|
+
inFence = true;
|
|
264
|
+
fenceChar = markerChar;
|
|
265
|
+
fenceLength = markerLength;
|
|
266
|
+
} else if (fenceChar === markerChar && markerLength >= fenceLength) {
|
|
267
|
+
inFence = false;
|
|
268
|
+
fenceChar = undefined;
|
|
269
|
+
fenceLength = 0;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
bodyLines.push(line);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (!inFence && isStandaloneLatexDefinitionLine(line)) {
|
|
277
|
+
definitions.push(line);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
bodyLines.push(line);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
body: bodyLines.join("\n"),
|
|
286
|
+
definitions,
|
|
287
|
+
preamble: definitions.join("\n"),
|
|
288
|
+
};
|
|
289
|
+
}
|