pi-studio 0.9.29 → 0.9.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 +5 -0
- package/client/studio-annotation-helpers.js +7 -0
- package/index.ts +17 -7
- package/package.json +1 -1
- package/shared/studio-annotation-render.js +148 -0
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.9.30] — 2026-06-09
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Rendered inline strikethrough, emphasis, and code inside `[an: ...]` annotation badges consistently across Studio preview and HTML/PDF export paths.
|
|
11
|
+
|
|
7
12
|
## [0.9.29] — 2026-06-09
|
|
8
13
|
|
|
9
14
|
### Added
|
|
@@ -443,6 +443,13 @@
|
|
|
443
443
|
let index = 0;
|
|
444
444
|
|
|
445
445
|
while (index < source.length) {
|
|
446
|
+
const strikeMatch = readAnnotationEmphasisSpanAt(source, index, "~~", "s");
|
|
447
|
+
if (strikeMatch) {
|
|
448
|
+
out += strikeMatch.html;
|
|
449
|
+
index = strikeMatch.end;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
|
|
446
453
|
const strongMatch = readAnnotationEmphasisSpanAt(source, index, "**", "strong")
|
|
447
454
|
|| readAnnotationEmphasisSpanAt(source, index, "__", "strong");
|
|
448
455
|
if (strongMatch) {
|
package/index.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import { escapeStudioPdfLatexTextFragment } from "./shared/studio-pdf-escape.js";
|
|
31
31
|
import { resolveStudioPdfResourceFile } from "./shared/studio-pdf-resource.js";
|
|
32
32
|
import { buildStudioForwardingHint, buildStudioSshTunnelHint, isStudioSshSession as isSshSession } from "./shared/studio-ssh-hint.js";
|
|
33
|
+
import { renderStudioAnnotationInlineHtml } from "./shared/studio-annotation-render.js";
|
|
33
34
|
|
|
34
35
|
type Lens = "writing" | "code";
|
|
35
36
|
type RequestedLens = Lens | "auto";
|
|
@@ -1086,6 +1087,7 @@ function buildStudioPdfPreamble(options?: StudioPdfRenderOptions, extraPreamble
|
|
|
1086
1087
|
\\titlespacing*{\\subparagraph}{0pt}{0.7ex plus 0.2ex minus 0.1ex}{0.7em}
|
|
1087
1088
|
\\usepackage{xcolor}
|
|
1088
1089
|
\\usepackage{varwidth}
|
|
1090
|
+
\\usepackage[normalem]{ulem}
|
|
1089
1091
|
\\definecolor{StudioAnnotationBg}{HTML}{EAF3FF}
|
|
1090
1092
|
\\definecolor{StudioAnnotationBorder}{HTML}{8CB8FF}
|
|
1091
1093
|
\\definecolor{StudioAnnotationText}{HTML}{1F5FBF}
|
|
@@ -5031,6 +5033,13 @@ function renderStudioAnnotationPlainTextPdfLatex(text: string): string {
|
|
|
5031
5033
|
let index = 0;
|
|
5032
5034
|
|
|
5033
5035
|
while (index < source.length) {
|
|
5036
|
+
const strikeMatch = readStudioAnnotationPdfEmphasisSpanAt(source, index, "~~", "sout");
|
|
5037
|
+
if (strikeMatch) {
|
|
5038
|
+
out += strikeMatch.latex;
|
|
5039
|
+
index = strikeMatch.end;
|
|
5040
|
+
continue;
|
|
5041
|
+
}
|
|
5042
|
+
|
|
5034
5043
|
const strongMatch = readStudioAnnotationPdfEmphasisSpanAt(source, index, "**", "textbf")
|
|
5035
5044
|
?? readStudioAnnotationPdfEmphasisSpanAt(source, index, "__", "textbf");
|
|
5036
5045
|
if (strongMatch) {
|
|
@@ -5648,13 +5657,14 @@ function prepareStudioPdfMarkdown(markdown: string, isLatex?: boolean, editorLan
|
|
|
5648
5657
|
? wrapStudioCodeAsMarkdown(input, effectiveEditorLanguage)
|
|
5649
5658
|
: input;
|
|
5650
5659
|
const fenceNormalizedSource = effectiveEditorLanguage === "latex" ? source : normalizeStudioMarkdownSmartFences(source);
|
|
5651
|
-
const
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
5655
|
-
return prepareStudioMarkdownForPandoc(commentStrippedSource, {
|
|
5656
|
-
preserveLiteralLatexCommands: !hasStudioYamlHeaderIncludes(annotationReadySource),
|
|
5660
|
+
const annotationReadyLanguage = !effectiveEditorLanguage || effectiveEditorLanguage === "markdown" || effectiveEditorLanguage === "latex";
|
|
5661
|
+
const commentStrippedSource = stripStudioMarkdownHtmlCommentsPreservingYamlFrontMatter(fenceNormalizedSource);
|
|
5662
|
+
const pandocReadySource = prepareStudioMarkdownForPandoc(commentStrippedSource, {
|
|
5663
|
+
preserveLiteralLatexCommands: !hasStudioYamlHeaderIncludes(fenceNormalizedSource),
|
|
5657
5664
|
});
|
|
5665
|
+
return annotationReadyLanguage
|
|
5666
|
+
? replaceStudioAnnotationMarkersForPdf(pandocReadySource)
|
|
5667
|
+
: pandocReadySource;
|
|
5658
5668
|
}
|
|
5659
5669
|
|
|
5660
5670
|
function stripMathMlAnnotationTags(html: string): string {
|
|
@@ -6118,7 +6128,7 @@ function applyStudioAnnotationPlaceholdersToHtml(html: string, placeholders: Stu
|
|
|
6118
6128
|
let transformed = String(html ?? "");
|
|
6119
6129
|
for (const placeholder of placeholders) {
|
|
6120
6130
|
const tokenPattern = new RegExp(escapeStudioRegExpLiteral(placeholder.token), "g");
|
|
6121
|
-
const markerHtml = `<span class="annotation-preview-marker" title="${escapeStudioHtmlText(placeholder.title)}">${
|
|
6131
|
+
const markerHtml = `<span class="annotation-preview-marker" title="${escapeStudioHtmlText(placeholder.title)}">${renderStudioAnnotationInlineHtml(placeholder.text)}</span>`;
|
|
6122
6132
|
transformed = transformed.replace(tokenPattern, markerHtml);
|
|
6123
6133
|
}
|
|
6124
6134
|
return transformed;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.30",
|
|
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",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
advancePastStudioInlineBacktickSpan,
|
|
3
|
+
isStudioAnnotationWordChar,
|
|
4
|
+
normalizeStudioAnnotationText,
|
|
5
|
+
readStudioAnnotationProtectedTokenAt,
|
|
6
|
+
} from "./studio-annotation-scanner.js";
|
|
7
|
+
|
|
8
|
+
function escapeHtml(text) {
|
|
9
|
+
return String(text ?? "")
|
|
10
|
+
.replace(/&/g, "&")
|
|
11
|
+
.replace(/</g, "<")
|
|
12
|
+
.replace(/>/g, ">")
|
|
13
|
+
.replace(/\"/g, """)
|
|
14
|
+
.replace(/'/g, "'");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function canOpenAnnotationInlineDelimiter(source, startIndex, delimiter) {
|
|
18
|
+
const text = String(source || "");
|
|
19
|
+
if (text.slice(startIndex, startIndex + delimiter.length) !== delimiter) return false;
|
|
20
|
+
const prev = startIndex > 0 ? text[startIndex - 1] : "";
|
|
21
|
+
const next = text[startIndex + delimiter.length] || "";
|
|
22
|
+
if (!next || /\s/.test(next)) return false;
|
|
23
|
+
return !isStudioAnnotationWordChar(prev);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function canCloseAnnotationInlineDelimiter(source, startIndex, delimiter) {
|
|
27
|
+
const text = String(source || "");
|
|
28
|
+
if (text.slice(startIndex, startIndex + delimiter.length) !== delimiter) return false;
|
|
29
|
+
const prev = startIndex > 0 ? text[startIndex - 1] : "";
|
|
30
|
+
const next = text[startIndex + delimiter.length] || "";
|
|
31
|
+
if (!prev || /\s/.test(prev)) return false;
|
|
32
|
+
return !isStudioAnnotationWordChar(next);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readAnnotationInlineSpanAt(source, startIndex, delimiter, tagName) {
|
|
36
|
+
const text = String(source || "");
|
|
37
|
+
if (!canOpenAnnotationInlineDelimiter(text, startIndex, delimiter)) return null;
|
|
38
|
+
|
|
39
|
+
let index = startIndex + delimiter.length;
|
|
40
|
+
while (index < text.length) {
|
|
41
|
+
if (text[index] === "\\") {
|
|
42
|
+
index = Math.min(text.length, index + 2);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const protectedToken = readStudioAnnotationProtectedTokenAt(text, index);
|
|
47
|
+
if (protectedToken) {
|
|
48
|
+
index = protectedToken.end;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (canCloseAnnotationInlineDelimiter(text, index, delimiter)) {
|
|
53
|
+
const inner = text.slice(startIndex + delimiter.length, index);
|
|
54
|
+
return {
|
|
55
|
+
end: index + delimiter.length,
|
|
56
|
+
html: `<${tagName}>${renderAnnotationPlainInlineHtml(inner)}</${tagName}>`,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
index += 1;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function renderAnnotationCodeSpanHtml(rawToken) {
|
|
67
|
+
const raw = String(rawToken || "");
|
|
68
|
+
if (!raw || raw[0] !== "`") return escapeHtml(raw);
|
|
69
|
+
|
|
70
|
+
let fenceLength = 1;
|
|
71
|
+
while (raw[fenceLength] === "`") fenceLength += 1;
|
|
72
|
+
const fence = "`".repeat(fenceLength);
|
|
73
|
+
if (raw.length < fenceLength * 2 || raw.slice(raw.length - fenceLength) !== fence) {
|
|
74
|
+
return escapeHtml(raw);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return `<code>${escapeHtml(raw.slice(fenceLength, raw.length - fenceLength))}</code>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function renderAnnotationPlainInlineHtml(text) {
|
|
81
|
+
const source = String(text || "");
|
|
82
|
+
let out = "";
|
|
83
|
+
let index = 0;
|
|
84
|
+
|
|
85
|
+
while (index < source.length) {
|
|
86
|
+
const strikeMatch = readAnnotationInlineSpanAt(source, index, "~~", "s");
|
|
87
|
+
if (strikeMatch) {
|
|
88
|
+
out += strikeMatch.html;
|
|
89
|
+
index = strikeMatch.end;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const strongMatch = readAnnotationInlineSpanAt(source, index, "**", "strong")
|
|
94
|
+
|| readAnnotationInlineSpanAt(source, index, "__", "strong");
|
|
95
|
+
if (strongMatch) {
|
|
96
|
+
out += strongMatch.html;
|
|
97
|
+
index = strongMatch.end;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const emphasisMatch = readAnnotationInlineSpanAt(source, index, "*", "em")
|
|
102
|
+
|| readAnnotationInlineSpanAt(source, index, "_", "em");
|
|
103
|
+
if (emphasisMatch) {
|
|
104
|
+
out += emphasisMatch.html;
|
|
105
|
+
index = emphasisMatch.end;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
out += escapeHtml(source[index]);
|
|
110
|
+
index += 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function renderStudioAnnotationInlineHtml(text) {
|
|
117
|
+
const source = normalizeStudioAnnotationText(text);
|
|
118
|
+
let out = "";
|
|
119
|
+
let plainStart = 0;
|
|
120
|
+
let index = 0;
|
|
121
|
+
|
|
122
|
+
while (index < source.length) {
|
|
123
|
+
const token = readStudioAnnotationProtectedTokenAt(source, index);
|
|
124
|
+
if (!token) {
|
|
125
|
+
index += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (index > plainStart) {
|
|
130
|
+
out += renderAnnotationPlainInlineHtml(source.slice(plainStart, index));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (token.type === "code") {
|
|
134
|
+
out += renderAnnotationCodeSpanHtml(token.raw);
|
|
135
|
+
} else {
|
|
136
|
+
out += escapeHtml(token.raw);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
index = token.end;
|
|
140
|
+
plainStart = index;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (plainStart < source.length) {
|
|
144
|
+
out += renderAnnotationPlainInlineHtml(source.slice(plainStart));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return out;
|
|
148
|
+
}
|