pi-studio 0.4.0 → 0.4.1

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/index.ts +66 -23
  3. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to `pi-studio` are documented here.
4
4
 
5
+ ## [0.4.1] — 2026-03-03
6
+
7
+ ### Changed
8
+ - Editor input keeps preview refreshes immediate (no added typing debounce) while keeping editor syntax highlighting immediate in Raw view.
9
+ - Response/sync state checks now reuse cached normalized response data and critique-note extracts instead of recomputing on each keystroke.
10
+ - Editor action/sync UI updates are now coalesced with `requestAnimationFrame` during typing.
11
+
5
12
  ## [0.3.0] — 2026-03-02
6
13
 
7
14
  ### Added
package/index.ts CHANGED
@@ -2261,6 +2261,10 @@ ${cssVarsBlock}
2261
2261
  let latestResponseTimestamp = 0;
2262
2262
  let latestResponseKind = "annotation";
2263
2263
  let latestResponseIsStructuredCritique = false;
2264
+ let latestResponseHasContent = false;
2265
+ let latestResponseNormalized = "";
2266
+ let latestCritiqueNotes = "";
2267
+ let latestCritiqueNotesNormalized = "";
2264
2268
  let uiBusy = false;
2265
2269
  let sourceState = {
2266
2270
  source: initialSourceState.source,
@@ -2312,10 +2316,12 @@ ${cssVarsBlock}
2312
2316
  var SUPPORTED_LANGUAGES = Object.keys(LANG_EXT_MAP);
2313
2317
  const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
2314
2318
  const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
2319
+ const PREVIEW_INPUT_DEBOUNCE_MS = 0;
2315
2320
  let sourcePreviewRenderTimer = null;
2316
2321
  let sourcePreviewRenderNonce = 0;
2317
2322
  let responsePreviewRenderNonce = 0;
2318
2323
  let responseEditorPreviewTimer = null;
2324
+ let editorMetaUpdateRaf = null;
2319
2325
  let editorHighlightEnabled = false;
2320
2326
  let editorLanguage = "markdown";
2321
2327
  let responseHighlightEnabled = false;
@@ -2514,23 +2520,19 @@ ${cssVarsBlock}
2514
2520
  return normalizeForCompare(a) === normalizeForCompare(b);
2515
2521
  }
2516
2522
 
2517
- function getCurrentResponseMarkdown() {
2518
- return latestResponseMarkdown;
2519
- }
2520
-
2521
- function updateSyncBadge() {
2523
+ function updateSyncBadge(normalizedEditorText) {
2522
2524
  if (!syncBadgeEl) return;
2523
2525
 
2524
- const response = getCurrentResponseMarkdown();
2525
- const hasResponse = Boolean(response && response.trim());
2526
-
2527
- if (!hasResponse) {
2526
+ if (!latestResponseHasContent) {
2528
2527
  syncBadgeEl.textContent = "No response loaded";
2529
2528
  syncBadgeEl.classList.remove("sync", "edited");
2530
2529
  return;
2531
2530
  }
2532
2531
 
2533
- const inSync = isTextEquivalent(sourceTextEl.value, response);
2532
+ const normalizedEditor = typeof normalizedEditorText === "string"
2533
+ ? normalizedEditorText
2534
+ : normalizeForCompare(sourceTextEl.value);
2535
+ const inSync = normalizedEditor === latestResponseNormalized;
2534
2536
  if (inSync) {
2535
2537
  syncBadgeEl.textContent = "In sync with response";
2536
2538
  syncBadgeEl.classList.add("sync");
@@ -2787,15 +2789,20 @@ ${cssVarsBlock}
2787
2789
  }, delay);
2788
2790
  }
2789
2791
 
2790
- function renderSourcePreview() {
2792
+ function renderSourcePreview(options) {
2793
+ const previewDelayMs =
2794
+ options && typeof options.previewDelayMs === "number"
2795
+ ? Math.max(0, options.previewDelayMs)
2796
+ : 0;
2797
+
2791
2798
  if (editorView === "preview") {
2792
- scheduleSourcePreviewRender(0);
2799
+ scheduleSourcePreviewRender(previewDelayMs);
2793
2800
  }
2794
2801
  if (editorHighlightEnabled && editorView === "markdown") {
2795
2802
  scheduleEditorHighlightRender();
2796
2803
  }
2797
2804
  if (rightView === "editor-preview") {
2798
- scheduleResponseEditorPreviewRender(0);
2805
+ scheduleResponseEditorPreviewRender(previewDelayMs);
2799
2806
  }
2800
2807
  }
2801
2808
 
@@ -2860,14 +2867,16 @@ ${cssVarsBlock}
2860
2867
  critiqueViewEl.innerHTML = buildPlainMarkdownHtml(markdown);
2861
2868
  }
2862
2869
 
2863
- function updateResultActionButtons() {
2864
- const responseMarkdown = getCurrentResponseMarkdown();
2865
- const hasResponse = Boolean(responseMarkdown && responseMarkdown.trim());
2866
- const responseLoaded = hasResponse && isTextEquivalent(sourceTextEl.value, responseMarkdown);
2870
+ function updateResultActionButtons(normalizedEditorText) {
2871
+ const hasResponse = latestResponseHasContent;
2872
+ const normalizedEditor = typeof normalizedEditorText === "string"
2873
+ ? normalizedEditorText
2874
+ : normalizeForCompare(sourceTextEl.value);
2875
+ const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
2867
2876
  const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
2868
2877
 
2869
- const critiqueNotes = isCritiqueResponse ? buildCritiqueNotesMarkdown(responseMarkdown) : "";
2870
- const critiqueNotesLoaded = Boolean(critiqueNotes) && isTextEquivalent(sourceTextEl.value, critiqueNotes);
2878
+ const critiqueNotes = isCritiqueResponse ? latestCritiqueNotes : "";
2879
+ const critiqueNotesLoaded = Boolean(critiqueNotes) && normalizedEditor === latestCritiqueNotesNormalized;
2871
2880
 
2872
2881
  loadResponseBtn.hidden = isCritiqueResponse;
2873
2882
  loadCritiqueNotesBtn.hidden = !isCritiqueResponse;
@@ -2887,7 +2896,7 @@ ${cssVarsBlock}
2887
2896
  pullLatestBtn.disabled = uiBusy || followLatest;
2888
2897
  pullLatestBtn.textContent = queuedLatestResponse ? "Get latest response *" : "Get latest response";
2889
2898
 
2890
- updateSyncBadge();
2899
+ updateSyncBadge(normalizedEditor);
2891
2900
  }
2892
2901
 
2893
2902
  function refreshResponseUi() {
@@ -3411,6 +3420,31 @@ ${cssVarsBlock}
3411
3420
  sourceHighlightEl.scrollLeft = sourceTextEl.scrollLeft;
3412
3421
  }
3413
3422
 
3423
+ function runEditorMetaUpdateNow() {
3424
+ const normalizedEditor = normalizeForCompare(sourceTextEl.value);
3425
+ updateResultActionButtons(normalizedEditor);
3426
+ }
3427
+
3428
+ function scheduleEditorMetaUpdate() {
3429
+ if (editorMetaUpdateRaf !== null) {
3430
+ if (typeof window.cancelAnimationFrame === "function") {
3431
+ window.cancelAnimationFrame(editorMetaUpdateRaf);
3432
+ } else {
3433
+ window.clearTimeout(editorMetaUpdateRaf);
3434
+ }
3435
+ editorMetaUpdateRaf = null;
3436
+ }
3437
+
3438
+ const schedule = typeof window.requestAnimationFrame === "function"
3439
+ ? window.requestAnimationFrame.bind(window)
3440
+ : (cb) => window.setTimeout(cb, 16);
3441
+
3442
+ editorMetaUpdateRaf = schedule(() => {
3443
+ editorMetaUpdateRaf = null;
3444
+ runEditorMetaUpdateNow();
3445
+ });
3446
+ }
3447
+
3414
3448
  function readStoredToggle(storageKey) {
3415
3449
  if (!window.localStorage) return null;
3416
3450
  try {
@@ -3597,9 +3631,18 @@ ${cssVarsBlock}
3597
3631
  latestResponseKind = kind === "critique" ? "critique" : "annotation";
3598
3632
  latestResponseTimestamp = responseTimestamp;
3599
3633
  latestResponseIsStructuredCritique = isStructuredCritique(markdown);
3634
+ latestResponseHasContent = Boolean(markdown && markdown.trim());
3635
+ latestResponseNormalized = normalizeForCompare(markdown);
3636
+
3637
+ if (latestResponseIsStructuredCritique) {
3638
+ latestCritiqueNotes = buildCritiqueNotesMarkdown(markdown);
3639
+ latestCritiqueNotesNormalized = normalizeForCompare(latestCritiqueNotes);
3640
+ } else {
3641
+ latestCritiqueNotes = "";
3642
+ latestCritiqueNotesNormalized = "";
3643
+ }
3600
3644
 
3601
3645
  refreshResponseUi();
3602
- syncActionButtons();
3603
3646
  }
3604
3647
 
3605
3648
  function applyLatestPayload(payload) {
@@ -4016,8 +4059,8 @@ ${cssVarsBlock}
4016
4059
  });
4017
4060
 
4018
4061
  sourceTextEl.addEventListener("input", () => {
4019
- renderSourcePreview();
4020
- updateResultActionButtons();
4062
+ renderSourcePreview({ previewDelayMs: PREVIEW_INPUT_DEBOUNCE_MS });
4063
+ scheduleEditorMetaUpdate();
4021
4064
  });
4022
4065
 
4023
4066
  sourceTextEl.addEventListener("scroll", () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-studio",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Browser GUI for structured critique workflows in pi",
5
5
  "type": "module",
6
6
  "license": "MIT",