pi-studio 0.5.6 → 0.5.7
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 +10 -2
- package/README.md +1 -1
- package/WORKFLOW.md +2 -2
- package/index.ts +37 -25
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `pi-studio` are documented here.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.5.7] — 2026-03-12
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Preview rendering now passes `--wrap=none` to pandoc and preview-side annotation matching now tolerates embedded newlines, fixing missed `[an: ...]` highlights in preview for longer annotations.
|
|
11
|
+
- Editor sync indicator is now intentionally quiet: Studio only shows the badge when the editor exactly matches the current response/thinking, and hides it while drafting/out-of-sync.
|
|
12
|
+
- Response history navigation now includes **Last response ▶|** for jumping straight back to the newest loaded history item.
|
|
13
|
+
- Renamed **Get latest response** to **Fetch latest response** for clearer distinction from history navigation, and moved **Load response into editor** ahead of **Load response prompt into editor** in the action row.
|
|
14
|
+
|
|
5
15
|
## [0.4.3] — 2026-03-04
|
|
6
16
|
|
|
7
17
|
### Added
|
|
@@ -86,8 +96,6 @@ All notable changes to `pi-studio` are documented here.
|
|
|
86
96
|
- Active pane indicator simplified to subtle border color change (removed thick top accent bar).
|
|
87
97
|
- Panel shadows, button hierarchy (filled accent for primary actions), heading scale, blockquote/table styling improvements.
|
|
88
98
|
|
|
89
|
-
## [Unreleased]
|
|
90
|
-
|
|
91
99
|
## [0.5.6] — 2026-03-10
|
|
92
100
|
|
|
93
101
|
### Changed
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Experimental extension for [pi](https://github.com/badlogic/pi-mono) that opens
|
|
|
16
16
|
|
|
17
17
|
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Thinking/Editor Preview** (right)
|
|
18
18
|
- Runs editor text directly, or asks for structured critique (auto/writing/code focus)
|
|
19
|
-
- Browses response history (`Prev/Next`) and loads either:
|
|
19
|
+
- Browses response history (`Prev/Next/Last`) and loads either:
|
|
20
20
|
- response text
|
|
21
21
|
- critique notes/full critique
|
|
22
22
|
- assistant thinking (when available)
|
package/WORKFLOW.md
CHANGED
|
@@ -79,10 +79,10 @@ Rules:
|
|
|
79
79
|
- Header view toggles: `Left: Editor (Raw|Preview)`, `Right: Response (Raw|Preview) | Editor (Preview)`
|
|
80
80
|
- Preview mode uses server-side `pandoc` rendering (math-aware) with plain-markdown fallback when renderer is unavailable.
|
|
81
81
|
- Editor actions: **Insert/Remove annotated reply header**, **Annotations: On|Hidden**, **Strip annotations…**, **Run editor text**, **Critique editor text** (+ critique focus), **Send to pi editor**, **Copy editor text**, **Save .annotated.md**
|
|
82
|
-
- Response actions include `Auto-update response: On|Off`, **
|
|
82
|
+
- Response actions include `Auto-update response: On|Off`, **Fetch latest response**, response-history browse (`Prev/Next/Last`), **Load response into editor**, and **Load response prompt into editor**
|
|
83
83
|
- Source badge: `blank | last model response | file <path> | upload`
|
|
84
84
|
- Response badge: `none | assistant response | assistant critique` (+ timestamp)
|
|
85
|
-
- Sync badge:
|
|
85
|
+
- Sync badge: shown only when the editor exactly matches the currently viewed response/thinking (`In sync with response | In sync with thinking`)
|
|
86
86
|
- Footer WS/status phases: `Connecting`, `Ready`, `Submitting`, `Disconnected`
|
|
87
87
|
|
|
88
88
|
---
|
package/index.ts
CHANGED
|
@@ -1070,7 +1070,7 @@ function normalizeObsidianImages(markdown: string): string {
|
|
|
1070
1070
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string): Promise<string> {
|
|
1071
1071
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
1072
1072
|
const inputFormat = isLatex ? "latex" : "gfm+tex_math_dollars-raw_html";
|
|
1073
|
-
const args = ["-f", inputFormat, "-t", "html5", "--mathml"];
|
|
1073
|
+
const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none"];
|
|
1074
1074
|
if (resourcePath) {
|
|
1075
1075
|
args.push(`--resource-path=${resourcePath}`);
|
|
1076
1076
|
// Embed images as data URIs so they render in the browser preview
|
|
@@ -2312,13 +2312,9 @@ ${cssVarsBlock}
|
|
|
2312
2312
|
}
|
|
2313
2313
|
|
|
2314
2314
|
.sync-badge.sync {
|
|
2315
|
-
border-color: var(--
|
|
2316
|
-
color: var(--
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
.sync-badge.edited {
|
|
2320
|
-
border-color: var(--warn-border);
|
|
2321
|
-
color: var(--warn);
|
|
2315
|
+
border-color: var(--border-muted);
|
|
2316
|
+
color: var(--muted);
|
|
2317
|
+
opacity: 0.88;
|
|
2322
2318
|
}
|
|
2323
2319
|
|
|
2324
2320
|
.source-actions {
|
|
@@ -3082,7 +3078,7 @@ ${cssVarsBlock}
|
|
|
3082
3078
|
<input id="resourceDirInput" type="text" placeholder="/path/to/working/directory" title="Absolute path to working directory" />
|
|
3083
3079
|
<button id="resourceDirClearBtn" type="button" title="Clear working directory">✕</button>
|
|
3084
3080
|
</span>
|
|
3085
|
-
<span id="syncBadge" class="source-badge sync-badge">
|
|
3081
|
+
<span id="syncBadge" class="source-badge sync-badge" hidden>In sync with response</span>
|
|
3086
3082
|
</div>
|
|
3087
3083
|
<div class="source-actions">
|
|
3088
3084
|
<div class="source-actions-row">
|
|
@@ -3181,16 +3177,17 @@ ${cssVarsBlock}
|
|
|
3181
3177
|
</select>
|
|
3182
3178
|
</div>
|
|
3183
3179
|
<div class="response-actions-row history-row">
|
|
3184
|
-
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">
|
|
3180
|
+
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Fetch latest response</button>
|
|
3185
3181
|
<button id="historyPrevBtn" type="button" title="Show previous response in history.">◀ Prev response</button>
|
|
3186
3182
|
<span id="historyIndexBadge" class="source-badge">History: 0/0</span>
|
|
3187
3183
|
<button id="historyNextBtn" type="button" title="Show next response in history.">Next response ▶</button>
|
|
3184
|
+
<button id="historyLastBtn" type="button" title="Jump to the latest loaded response in history.">Last response ▶|</button>
|
|
3188
3185
|
</div>
|
|
3189
3186
|
<div class="response-actions-row">
|
|
3190
|
-
<button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
|
|
3191
3187
|
<button id="loadResponseBtn" type="button">Load response into editor</button>
|
|
3192
3188
|
<button id="loadCritiqueNotesBtn" type="button" hidden>Load critique notes into editor</button>
|
|
3193
3189
|
<button id="loadCritiqueFullBtn" type="button" hidden>Load full critique into editor</button>
|
|
3190
|
+
<button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
|
|
3194
3191
|
<button id="copyResponseBtn" type="button">Copy response text</button>
|
|
3195
3192
|
</div>
|
|
3196
3193
|
</div>
|
|
@@ -3278,6 +3275,7 @@ ${cssVarsBlock}
|
|
|
3278
3275
|
const exportPdfBtn = document.getElementById("exportPdfBtn");
|
|
3279
3276
|
const historyPrevBtn = document.getElementById("historyPrevBtn");
|
|
3280
3277
|
const historyNextBtn = document.getElementById("historyNextBtn");
|
|
3278
|
+
const historyLastBtn = document.getElementById("historyLastBtn");
|
|
3281
3279
|
const historyIndexBadgeEl = document.getElementById("historyIndexBadge");
|
|
3282
3280
|
const loadHistoryPromptBtn = document.getElementById("loadHistoryPromptBtn");
|
|
3283
3281
|
const saveAsBtn = document.getElementById("saveAsBtn");
|
|
@@ -3419,7 +3417,7 @@ ${cssVarsBlock}
|
|
|
3419
3417
|
let responseHighlightEnabled = false;
|
|
3420
3418
|
let editorHighlightRenderRaf = null;
|
|
3421
3419
|
let annotationsEnabled = true;
|
|
3422
|
-
const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]
|
|
3420
|
+
const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]]+?)\\]/gi;
|
|
3423
3421
|
const EMPTY_OVERLAY_LINE = "\\u200b";
|
|
3424
3422
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
3425
3423
|
const MERMAID_CONFIG = ${JSON.stringify(mermaidConfig)};
|
|
@@ -3992,6 +3990,9 @@ ${cssVarsBlock}
|
|
|
3992
3990
|
if (historyNextBtn) {
|
|
3993
3991
|
historyNextBtn.disabled = uiBusy || total <= 1 || responseHistoryIndex < 0 || responseHistoryIndex >= total - 1;
|
|
3994
3992
|
}
|
|
3993
|
+
if (historyLastBtn) {
|
|
3994
|
+
historyLastBtn.disabled = uiBusy || total <= 1 || responseHistoryIndex < 0 || responseHistoryIndex >= total - 1;
|
|
3995
|
+
}
|
|
3995
3996
|
|
|
3996
3997
|
const selectedItem = getSelectedHistoryItem();
|
|
3997
3998
|
const hasPrompt = Boolean(selectedItem && typeof selectedItem.prompt === "string" && selectedItem.prompt.trim());
|
|
@@ -4168,8 +4169,9 @@ ${cssVarsBlock}
|
|
|
4168
4169
|
: latestResponseHasContent;
|
|
4169
4170
|
|
|
4170
4171
|
if (!hasComparableContent) {
|
|
4171
|
-
syncBadgeEl.
|
|
4172
|
-
syncBadgeEl.
|
|
4172
|
+
syncBadgeEl.hidden = true;
|
|
4173
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4174
|
+
syncBadgeEl.classList.remove("sync");
|
|
4173
4175
|
return;
|
|
4174
4176
|
}
|
|
4175
4177
|
|
|
@@ -4178,15 +4180,15 @@ ${cssVarsBlock}
|
|
|
4178
4180
|
: normalizeForCompare(sourceTextEl.value);
|
|
4179
4181
|
const targetNormalized = showingThinking ? latestResponseThinkingNormalized : latestResponseNormalized;
|
|
4180
4182
|
const inSync = normalizedEditor === targetNormalized;
|
|
4183
|
+
syncBadgeEl.hidden = !inSync;
|
|
4184
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4185
|
+
|
|
4181
4186
|
if (inSync) {
|
|
4182
|
-
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4183
4187
|
syncBadgeEl.classList.add("sync");
|
|
4184
|
-
|
|
4185
|
-
} else {
|
|
4186
|
-
syncBadgeEl.textContent = showingThinking ? "Out of sync with thinking" : "Out of sync with response";
|
|
4187
|
-
syncBadgeEl.classList.add("edited");
|
|
4188
|
-
syncBadgeEl.classList.remove("sync");
|
|
4188
|
+
return;
|
|
4189
4189
|
}
|
|
4190
|
+
|
|
4191
|
+
syncBadgeEl.classList.remove("sync");
|
|
4190
4192
|
}
|
|
4191
4193
|
|
|
4192
4194
|
function buildPlainMarkdownHtml(markdown) {
|
|
@@ -4818,7 +4820,7 @@ ${cssVarsBlock}
|
|
|
4818
4820
|
}
|
|
4819
4821
|
|
|
4820
4822
|
pullLatestBtn.disabled = uiBusy || followLatest;
|
|
4821
|
-
pullLatestBtn.textContent = queuedLatestResponse ? "
|
|
4823
|
+
pullLatestBtn.textContent = queuedLatestResponse ? "Fetch latest response *" : "Fetch latest response";
|
|
4822
4824
|
|
|
4823
4825
|
updateSyncBadge(normalizedEditor);
|
|
4824
4826
|
}
|
|
@@ -5029,7 +5031,7 @@ ${cssVarsBlock}
|
|
|
5029
5031
|
|
|
5030
5032
|
function highlightInlineMarkdown(text) {
|
|
5031
5033
|
const source = String(text || "");
|
|
5032
|
-
const pattern = /(\\x60[^\\x60]*\\x60)|(\\[[^\\]]+\\]\\([^)]+\\))|(\\[an:\\s*[^\\]
|
|
5034
|
+
const pattern = /(\\x60[^\\x60]*\\x60)|(\\[[^\\]]+\\]\\([^)]+\\))|(\\[an:\\s*[^\\]]+\\])/gi;
|
|
5033
5035
|
let lastIndex = 0;
|
|
5034
5036
|
let out = "";
|
|
5035
5037
|
|
|
@@ -5967,7 +5969,7 @@ ${cssVarsBlock}
|
|
|
5967
5969
|
if (!followLatest) {
|
|
5968
5970
|
queuedLatestResponse = payload;
|
|
5969
5971
|
updateResultActionButtons();
|
|
5970
|
-
setStatus("New response available — click
|
|
5972
|
+
setStatus("New response available — click Fetch latest response.", "warning");
|
|
5971
5973
|
return;
|
|
5972
5974
|
}
|
|
5973
5975
|
|
|
@@ -6342,7 +6344,7 @@ ${cssVarsBlock}
|
|
|
6342
6344
|
function requestLatestResponse() {
|
|
6343
6345
|
const sent = sendMessage({ type: "get_latest_response" });
|
|
6344
6346
|
if (!sent) return;
|
|
6345
|
-
setStatus("
|
|
6347
|
+
setStatus("Fetching latest response…");
|
|
6346
6348
|
}
|
|
6347
6349
|
|
|
6348
6350
|
if (leftPaneEl) {
|
|
@@ -6380,7 +6382,7 @@ ${cssVarsBlock}
|
|
|
6380
6382
|
setStatus("Applied queued response.", "success");
|
|
6381
6383
|
}
|
|
6382
6384
|
} else if (!followLatest) {
|
|
6383
|
-
setStatus("Auto-update is off. Use
|
|
6385
|
+
setStatus("Auto-update is off. Use Fetch latest response.");
|
|
6384
6386
|
}
|
|
6385
6387
|
updateResultActionButtons();
|
|
6386
6388
|
});
|
|
@@ -6464,6 +6466,16 @@ ${cssVarsBlock}
|
|
|
6464
6466
|
});
|
|
6465
6467
|
}
|
|
6466
6468
|
|
|
6469
|
+
if (historyLastBtn) {
|
|
6470
|
+
historyLastBtn.addEventListener("click", () => {
|
|
6471
|
+
if (!responseHistory.length) {
|
|
6472
|
+
setStatus("No response history available yet.", "warning");
|
|
6473
|
+
return;
|
|
6474
|
+
}
|
|
6475
|
+
selectHistoryIndex(responseHistory.length - 1);
|
|
6476
|
+
});
|
|
6477
|
+
}
|
|
6478
|
+
|
|
6467
6479
|
if (loadHistoryPromptBtn) {
|
|
6468
6480
|
loadHistoryPromptBtn.addEventListener("click", () => {
|
|
6469
6481
|
const item = getSelectedHistoryItem();
|