pi-studio 0.2.5 → 0.3.0

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,20 @@
2
2
 
3
3
  All notable changes to `pi-studio` are documented here.
4
4
 
5
+ ## [0.3.0] — 2026-03-02
6
+
7
+ ### Added
8
+ - **Editor Preview in response pane**: new `Right: Editor (Preview)` view mode renders editor text in the right pane with debounced live updates — enables Overleaf-style side-by-side source/rendered editing without a model round-trip.
9
+ - Code-language aware: Editor Preview renders syntax-highlighted code when a non-markdown language is selected.
10
+ - Response badge shows "Previewing: editor text" in editor-preview mode, with "· response updated HH:MM:SS" when a model response arrives in the background.
11
+ - Right pane section header updates to "Editor Preview" when in editor-preview mode.
12
+
13
+ ### Changed
14
+ - View toggle labels now use `Left: Source (Mode)` / `Right: Source (Mode)` format for unambiguous pane identification (e.g., `Left: Editor (Raw)`, `Right: Response (Preview)`, `Right: Editor (Preview)`).
15
+ - Sync badge wording: `Edited since response` → `Out of sync with response` (direction-neutral, accurate regardless of which side changed).
16
+ - Critique load buttons now include destination: `Load critique notes into editor` / `Load full critique into editor` (consistent with `Load response into editor`).
17
+ - Critique loaded-state labels updated: `Critique (full) already in editor` → `Full critique already in editor`.
18
+
5
19
  ## [0.2.4] — 2026-03-02
6
20
 
7
21
  ### Changed
package/README.md CHANGED
@@ -38,6 +38,10 @@ Status: experimental alpha.
38
38
 
39
39
  ![Light code mode](./assets/screenshots/light-code-mode.png)
40
40
 
41
+ **Focus mode — dark** (editor pane full-screen, `Cmd/Ctrl+Esc` or `F10` to toggle, `Esc` to exit)
42
+
43
+ ![Dark focus mode](./assets/screenshots/dark-focus-mode.png)
44
+
41
45
  ## Features
42
46
 
43
47
  - Single workspace: Editor (left) + Response (right)
@@ -46,9 +50,10 @@ Status: experimental alpha.
46
50
  - **Critique editor text** requests structured critique (auto/writing/code focus)
47
51
  - Response load helpers:
48
52
  - non-critique: **Load response into editor**
49
- - critique: **Load critique (notes)** / **Load critique (full)**
53
+ - critique: **Load critique notes into editor** / **Load full critique into editor**
50
54
  - File actions: **Save As…**, **Save file**, **Load file content**
51
- - View toggles: `Editor: Raw|Preview`, `Response: Raw|Preview`
55
+ - View toggles: `Left: Editor (Raw|Preview)`, `Right: Response (Raw|Preview) | Editor (Preview)`
56
+ - **Editor Preview in response pane**: side-by-side source/rendered view (Overleaf-style) — select `Right: Editor (Preview)` to render editor text in the right pane with live debounced updates
52
57
  - Preview mode supports MathML equations and Mermaid fenced diagrams
53
58
  - **Language-aware syntax highlighting** with selectable language mode:
54
59
  - Markdown (default): headings, links, code fences, lists, quotes, inline code
@@ -58,7 +63,7 @@ Status: experimental alpha.
58
63
  - Applies to both editor Raw view (highlight overlay) and fenced code blocks in markdown
59
64
  - Preview mode renders syntax-highlighted code when a non-markdown language is selected
60
65
  - Separate syntax highlight toggles for editor and response Raw views, with local preference persistence
61
- - Theme-aware browser UI based on current pi theme, with refined surface depth and lighter visual chrome
66
+ - Theme-aware browser UI derived from current pi theme
62
67
 
63
68
  ## Commands
64
69
 
package/WORKFLOW.md CHANGED
@@ -75,7 +75,7 @@ Rules:
75
75
  ## Required UI elements
76
76
 
77
77
  - Header actions: **Save As…**, **Save file** (file-backed), **Load file in editor**
78
- - Header view toggles: `Editor: Markdown|Preview`, `Response: Markdown|Preview`
78
+ - Header view toggles: `Left: Editor (Raw|Preview)`, `Right: Response (Raw|Preview) | Editor (Preview)`
79
79
  - Preview mode uses server-side `pandoc` rendering (math-aware) with plain-markdown fallback when renderer is unavailable.
80
80
  - Editor actions: **Insert annotation header**, **Run editor text**, **Critique editor text** (+ critique focus), **Send to pi editor**, **Copy editor text**
81
81
  - Response actions include `Auto-update response: On|Off` + **Get latest response**
package/index.ts CHANGED
@@ -1908,12 +1908,13 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
1908
1908
  <h1><span class="app-logo" aria-hidden="true">π</span> Pi Studio <span class="app-subtitle">Feedback Workspace</span></h1>
1909
1909
  <div class="controls">
1910
1910
  <select id="editorViewSelect" aria-label="Editor view mode">
1911
- <option value="markdown" selected>Editor: Raw</option>
1912
- <option value="preview">Editor: Preview</option>
1911
+ <option value="markdown" selected>Left: Editor (Raw)</option>
1912
+ <option value="preview">Left: Editor (Preview)</option>
1913
1913
  </select>
1914
1914
  <select id="rightViewSelect" aria-label="Response view mode">
1915
- <option value="markdown">Response: Raw</option>
1916
- <option value="preview" selected>Response: Preview</option>
1915
+ <option value="markdown">Right: Response (Raw)</option>
1916
+ <option value="preview" selected>Right: Response (Preview)</option>
1917
+ <option value="editor-preview">Right: Editor (Preview)</option>
1917
1918
  </select>
1918
1919
  <button id="saveAsBtn" type="button" title="Save editor text to a new file path.">Save As…</button>
1919
1920
  <button id="saveOverBtn" type="button" title="Overwrite current file with editor text." disabled>Save file</button>
@@ -1988,8 +1989,8 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
1988
1989
  </select>
1989
1990
  <button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Get latest response</button>
1990
1991
  <button id="loadResponseBtn" type="button">Load response into editor</button>
1991
- <button id="loadCritiqueNotesBtn" type="button" hidden>Load critique (notes)</button>
1992
- <button id="loadCritiqueFullBtn" type="button" hidden>Load critique (full)</button>
1992
+ <button id="loadCritiqueNotesBtn" type="button" hidden>Load critique notes into editor</button>
1993
+ <button id="loadCritiqueFullBtn" type="button" hidden>Load full critique into editor</button>
1993
1994
  <button id="copyResponseBtn" type="button">Copy response text</button>
1994
1995
  </div>
1995
1996
  </div>
@@ -2098,6 +2099,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2098
2099
  let sourcePreviewRenderTimer = null;
2099
2100
  let sourcePreviewRenderNonce = 0;
2100
2101
  let responsePreviewRenderNonce = 0;
2102
+ let responseEditorPreviewTimer = null;
2101
2103
  let editorHighlightEnabled = false;
2102
2104
  let editorLanguage = "markdown";
2103
2105
  let responseHighlightEnabled = false;
@@ -2242,6 +2244,18 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2242
2244
  function updateReferenceBadge() {
2243
2245
  if (!referenceBadgeEl) return;
2244
2246
 
2247
+ if (rightView === "editor-preview") {
2248
+ const hasResponse = Boolean(latestResponseMarkdown && latestResponseMarkdown.trim());
2249
+ if (hasResponse) {
2250
+ const time = formatReferenceTime(latestResponseTimestamp);
2251
+ const suffix = time ? " · response updated " + time : " · response available";
2252
+ referenceBadgeEl.textContent = "Previewing: editor text" + suffix;
2253
+ } else {
2254
+ referenceBadgeEl.textContent = "Previewing: editor text";
2255
+ }
2256
+ return;
2257
+ }
2258
+
2245
2259
  const hasResponse = Boolean(latestResponseMarkdown && latestResponseMarkdown.trim());
2246
2260
  if (!hasResponse) {
2247
2261
  referenceBadgeEl.textContent = "Latest response: none";
@@ -2285,7 +2299,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2285
2299
  syncBadgeEl.classList.add("sync");
2286
2300
  syncBadgeEl.classList.remove("edited");
2287
2301
  } else {
2288
- syncBadgeEl.textContent = "Edited since response";
2302
+ syncBadgeEl.textContent = "Out of sync with response";
2289
2303
  syncBadgeEl.classList.add("edited");
2290
2304
  syncBadgeEl.classList.remove("sync");
2291
2305
  }
@@ -2470,7 +2484,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2470
2484
  if (pane === "source") {
2471
2485
  if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
2472
2486
  } else {
2473
- if (nonce !== responsePreviewRenderNonce || rightView !== "preview") return;
2487
+ if (nonce !== responsePreviewRenderNonce || (rightView !== "preview" && rightView !== "editor-preview")) return;
2474
2488
  }
2475
2489
 
2476
2490
  targetEl.innerHTML = sanitizeRenderedHtml(renderedHtml, markdown);
@@ -2479,7 +2493,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2479
2493
  if (pane === "source") {
2480
2494
  if (nonce !== sourcePreviewRenderNonce || editorView !== "preview") return;
2481
2495
  } else {
2482
- if (nonce !== responsePreviewRenderNonce || rightView !== "preview") return;
2496
+ if (nonce !== responsePreviewRenderNonce || (rightView !== "preview" && rightView !== "editor-preview")) return;
2483
2497
  }
2484
2498
 
2485
2499
  const detail = error && error.message ? error.message : String(error || "unknown error");
@@ -2521,9 +2535,43 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2521
2535
  if (editorHighlightEnabled && editorView === "markdown") {
2522
2536
  scheduleEditorHighlightRender();
2523
2537
  }
2538
+ if (rightView === "editor-preview") {
2539
+ scheduleResponseEditorPreviewRender(0);
2540
+ }
2541
+ }
2542
+
2543
+ function scheduleResponseEditorPreviewRender(delayMs) {
2544
+ if (responseEditorPreviewTimer) {
2545
+ window.clearTimeout(responseEditorPreviewTimer);
2546
+ responseEditorPreviewTimer = null;
2547
+ }
2548
+
2549
+ if (rightView !== "editor-preview") return;
2550
+
2551
+ const delay = typeof delayMs === "number" ? Math.max(0, delayMs) : 180;
2552
+ responseEditorPreviewTimer = window.setTimeout(() => {
2553
+ responseEditorPreviewTimer = null;
2554
+ renderActiveResult();
2555
+ }, delay);
2524
2556
  }
2525
2557
 
2526
2558
  function renderActiveResult() {
2559
+ if (rightView === "editor-preview") {
2560
+ const editorText = sourceTextEl.value || "";
2561
+ if (!editorText.trim()) {
2562
+ critiqueViewEl.innerHTML = "<pre class='plain-markdown'>Editor is empty.</pre>";
2563
+ return;
2564
+ }
2565
+ if (editorLanguage && editorLanguage !== "markdown") {
2566
+ critiqueViewEl.innerHTML = "<div class='response-markdown-highlight' style='white-space:pre;font-family:var(--font-mono);font-size:13px;line-height:1.5;padding:16px;overflow:auto;'>" + highlightCode(editorText, editorLanguage) + "</div>";
2567
+ return;
2568
+ }
2569
+ const nonce = ++responsePreviewRenderNonce;
2570
+ critiqueViewEl.innerHTML = "<div class='preview-loading'>Rendering preview…</div>";
2571
+ void applyRenderedMarkdown(critiqueViewEl, editorText, "response", nonce);
2572
+ return;
2573
+ }
2574
+
2527
2575
  const markdown = latestResponseMarkdown;
2528
2576
  if (!markdown || !markdown.trim()) {
2529
2577
  critiqueViewEl.innerHTML = "<pre class='plain-markdown'>No response yet. Run editor text or critique editor text.</pre>";
@@ -2570,10 +2618,10 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2570
2618
  loadResponseBtn.textContent = responseLoaded ? "Response already in editor" : "Load response into editor";
2571
2619
 
2572
2620
  loadCritiqueNotesBtn.disabled = uiBusy || !isCritiqueResponse || !critiqueNotes || critiqueNotesLoaded;
2573
- loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique (notes)";
2621
+ loadCritiqueNotesBtn.textContent = critiqueNotesLoaded ? "Critique notes already in editor" : "Load critique notes into editor";
2574
2622
 
2575
2623
  loadCritiqueFullBtn.disabled = uiBusy || !isCritiqueResponse || responseLoaded;
2576
- loadCritiqueFullBtn.textContent = responseLoaded ? "Critique (full) already in editor" : "Load critique (full)";
2624
+ loadCritiqueFullBtn.textContent = responseLoaded ? "Full critique already in editor" : "Load full critique into editor";
2577
2625
 
2578
2626
  copyResponseBtn.disabled = uiBusy || !hasResponse;
2579
2627
 
@@ -2589,7 +2637,7 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2589
2637
  }
2590
2638
 
2591
2639
  if (rightSectionHeaderEl) {
2592
- rightSectionHeaderEl.textContent = "Response";
2640
+ rightSectionHeaderEl.textContent = rightView === "editor-preview" ? "Editor Preview" : "Response";
2593
2641
  }
2594
2642
 
2595
2643
  updateSourceBadge();
@@ -2672,9 +2720,15 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
2672
2720
  }
2673
2721
 
2674
2722
  function setRightView(nextView) {
2675
- rightView = nextView === "preview" ? "preview" : "markdown";
2723
+ rightView = nextView === "preview" ? "preview" : (nextView === "editor-preview" ? "editor-preview" : "markdown");
2676
2724
  rightViewSelect.value = rightView;
2677
- renderActiveResult();
2725
+
2726
+ if (rightView !== "editor-preview" && responseEditorPreviewTimer) {
2727
+ window.clearTimeout(responseEditorPreviewTimer);
2728
+ responseEditorPreviewTimer = null;
2729
+ }
2730
+
2731
+ refreshResponseUi();
2678
2732
  syncActionButtons();
2679
2733
  }
2680
2734
 
@@ -3748,8 +3802,8 @@ function buildStudioHtml(initialDocument: InitialStudioDocument | null, theme?:
3748
3802
 
3749
3803
  sourceTextEl.value = latestResponseMarkdown;
3750
3804
  renderSourcePreview();
3751
- setSourceState({ source: "blank", label: "critique (full)", path: null });
3752
- setStatus("Loaded critique (full) into editor.", "success");
3805
+ setSourceState({ source: "blank", label: "full critique", path: null });
3806
+ setStatus("Loaded full critique into editor.", "success");
3753
3807
  });
3754
3808
 
3755
3809
  copyResponseBtn.addEventListener("click", async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.2.5",
3
+ "version": "0.3.0",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",