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 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`, **Get latest response**, response-history browse (`Prev/Next`), and **Load response prompt into editor**
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: `No response loaded | In sync with response | Edited since response`
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(--ok-border);
2316
- color: var(--ok);
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">No response loaded</span>
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.">Get latest response</button>
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*([^\\]\\n]+?)\\]/gi;
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.textContent = showingThinking ? "No thinking loaded" : "No response loaded";
4172
- syncBadgeEl.classList.remove("sync", "edited");
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
- syncBadgeEl.classList.remove("edited");
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 ? "Get latest response *" : "Get latest response";
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*[^\\]\\n]+\\])/gi;
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 Get latest response.", "warning");
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("Requested latest response.");
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 Get latest response.");
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",