pi-studio 0.5.35 → 0.5.37

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.
@@ -88,6 +88,16 @@
88
88
  const compactBtn = document.getElementById("compactBtn");
89
89
  const leftFocusBtn = document.getElementById("leftFocusBtn");
90
90
  const rightFocusBtn = document.getElementById("rightFocusBtn");
91
+ const scratchpadBtn = document.getElementById("scratchpadBtn");
92
+ const scratchpadOverlayEl = document.getElementById("scratchpadOverlay");
93
+ const scratchpadDialogEl = document.getElementById("scratchpadDialog");
94
+ const scratchpadTextEl = document.getElementById("scratchpadText");
95
+ const scratchpadMetaEl = document.getElementById("scratchpadMeta");
96
+ const scratchpadInsertBtn = document.getElementById("scratchpadInsertBtn");
97
+ const scratchpadCopyBtn = document.getElementById("scratchpadCopyBtn");
98
+ const scratchpadClearBtn = document.getElementById("scratchpadClearBtn");
99
+ const scratchpadCloseBtn = document.getElementById("scratchpadCloseBtn");
100
+ const scratchpadDoneBtn = document.getElementById("scratchpadDoneBtn");
91
101
 
92
102
  const initialSourceState = {
93
103
  source: (document.body && document.body.dataset && document.body.dataset.initialSource) || "blank",
@@ -227,6 +237,7 @@
227
237
  const RESPONSE_HIGHLIGHT_MAX_CHARS = 120_000;
228
238
  const RESPONSE_HIGHLIGHT_STORAGE_KEY = "piStudio.responseHighlightEnabled";
229
239
  const ANNOTATION_MODE_STORAGE_KEY = "piStudio.annotationsEnabled";
240
+ const SCRATCHPAD_STORAGE_KEY = "piStudio.scratchpad";
230
241
  const PREVIEW_INPUT_DEBOUNCE_MS = 0;
231
242
  const PREVIEW_PENDING_BADGE_DELAY_MS = 220;
232
243
  const previewPendingTimers = new WeakMap();
@@ -241,8 +252,13 @@
241
252
  let responseHighlightEnabled = false;
242
253
  let editorHighlightRenderRaf = null;
243
254
  let annotationsEnabled = true;
244
- const ANNOTATION_MARKER_REGEX = /\[an:\s*([^\]]+?)\]/gi;
255
+ let scratchpadText = "";
256
+ let scratchpadReturnFocusEl = null;
245
257
  const PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX = "PISTUDIOANNOT";
258
+ const annotationHelpers = globalThis.PiStudioAnnotationHelpers;
259
+ if (!annotationHelpers || typeof annotationHelpers.collectInlineAnnotationMarkers !== "function") {
260
+ throw new Error("Studio annotation helpers failed to load.");
261
+ }
246
262
  const EMPTY_OVERLAY_LINE = "\u200b";
247
263
  const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
248
264
  const MATHJAX_CDN_URL = "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js";
@@ -861,6 +877,28 @@
861
877
  if (!event || event.defaultPrevented) return;
862
878
 
863
879
  const key = typeof event.key === "string" ? event.key : "";
880
+ const plainEscape = key === "Escape"
881
+ && !event.metaKey
882
+ && !event.ctrlKey
883
+ && !event.altKey
884
+ && !event.shiftKey;
885
+ const scratchpadOwnsEvent = Boolean(
886
+ scratchpadDialogEl
887
+ && event.target
888
+ && typeof scratchpadDialogEl.contains === "function"
889
+ && scratchpadDialogEl.contains(event.target)
890
+ );
891
+
892
+ if (isScratchpadOpen() && plainEscape) {
893
+ event.preventDefault();
894
+ closeScratchpad();
895
+ return;
896
+ }
897
+
898
+ if (scratchpadOwnsEvent) {
899
+ return;
900
+ }
901
+
864
902
  const isToggleShortcut =
865
903
  (key === "Escape" && (event.metaKey || event.ctrlKey))
866
904
  || key === "F10";
@@ -871,13 +909,7 @@
871
909
  return;
872
910
  }
873
911
 
874
- if (
875
- key === "Escape"
876
- && !event.metaKey
877
- && !event.ctrlKey
878
- && !event.altKey
879
- && !event.shiftKey
880
- ) {
912
+ if (plainEscape) {
881
913
  const activeKind = getAbortablePendingKind();
882
914
  if (activeKind === "direct" || activeKind === "critique") {
883
915
  event.preventDefault();
@@ -1163,15 +1195,11 @@
1163
1195
  }
1164
1196
 
1165
1197
  function hasAnnotationMarkers(text) {
1166
- const source = String(text || "");
1167
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1168
- const hasMarker = ANNOTATION_MARKER_REGEX.test(source);
1169
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1170
- return hasMarker;
1198
+ return annotationHelpers.hasAnnotationMarkers(text);
1171
1199
  }
1172
1200
 
1173
1201
  function stripAnnotationMarkers(text) {
1174
- return String(text || "").replace(ANNOTATION_MARKER_REGEX, "");
1202
+ return annotationHelpers.stripAnnotationMarkers(text);
1175
1203
  }
1176
1204
 
1177
1205
  function prepareEditorTextForSend(text) {
@@ -1184,78 +1212,8 @@
1184
1212
  return annotationsEnabled ? raw : stripAnnotationMarkers(raw);
1185
1213
  }
1186
1214
 
1187
- function normalizePreviewAnnotationLabel(text) {
1188
- return String(text || "")
1189
- .replace(/\r\n/g, "\n")
1190
- .replace(/\s*\n\s*/g, " ")
1191
- .replace(/\s{2,}/g, " ")
1192
- .trim();
1193
- }
1194
-
1195
1215
  function prepareMarkdownForPandocPreview(markdown) {
1196
- const source = String(markdown || "").replace(/\r\n/g, "\n");
1197
- const placeholders = [];
1198
- if (!source) {
1199
- return { markdown: source, placeholders: placeholders };
1200
- }
1201
-
1202
- const lines = source.split("\n");
1203
- const out = [];
1204
- let plainBuffer = [];
1205
- let inFence = false;
1206
- let fenceChar = null;
1207
- let fenceLength = 0;
1208
-
1209
- function flushPlain() {
1210
- if (plainBuffer.length === 0) return;
1211
- const segment = plainBuffer.join("\n").replace(/\[an:\s*([^\]]+?)\]/gi, function(_match, markerText) {
1212
- const label = normalizePreviewAnnotationLabel(markerText);
1213
- if (!label) return "";
1214
- const token = PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX + placeholders.length + "TOKEN";
1215
- placeholders.push({ token: token, text: label, title: "[an: " + label + "]" });
1216
- return token;
1217
- });
1218
- out.push(segment);
1219
- plainBuffer = [];
1220
- }
1221
-
1222
- for (const line of lines) {
1223
- const trimmed = line.trimStart();
1224
- const fenceMatch = trimmed.match(/^(`{3,}|~{3,})/);
1225
-
1226
- if (fenceMatch) {
1227
- const marker = fenceMatch[1] || "";
1228
- const markerChar = marker.charAt(0);
1229
- const markerLength = marker.length;
1230
-
1231
- if (!inFence) {
1232
- flushPlain();
1233
- inFence = true;
1234
- fenceChar = markerChar;
1235
- fenceLength = markerLength;
1236
- out.push(line);
1237
- continue;
1238
- }
1239
-
1240
- if (fenceChar === markerChar && markerLength >= fenceLength) {
1241
- inFence = false;
1242
- fenceChar = null;
1243
- fenceLength = 0;
1244
- }
1245
-
1246
- out.push(line);
1247
- continue;
1248
- }
1249
-
1250
- if (inFence) {
1251
- out.push(line);
1252
- } else {
1253
- plainBuffer.push(line);
1254
- }
1255
- }
1256
-
1257
- flushPlain();
1258
- return { markdown: out.join("\n"), placeholders: placeholders };
1216
+ return annotationHelpers.prepareMarkdownForPandocPreview(markdown, PREVIEW_ANNOTATION_PLACEHOLDER_PREFIX);
1259
1217
  }
1260
1218
 
1261
1219
  function wrapAsFencedCodeBlock(text, language) {
@@ -1774,8 +1732,9 @@
1774
1732
  if (entry) {
1775
1733
  const markerEl = document.createElement("span");
1776
1734
  markerEl.className = "annotation-preview-marker";
1777
- markerEl.textContent = typeof entry.text === "string" ? entry.text : token;
1778
- markerEl.title = typeof entry.title === "string" ? entry.title : markerEl.textContent;
1735
+ const markerText = typeof entry.text === "string" ? entry.text : token;
1736
+ markerEl.title = typeof entry.title === "string" ? entry.title : markerText;
1737
+ setAnnotationPreviewMarkerContent(markerEl, markerText);
1779
1738
  fragment.appendChild(markerEl);
1780
1739
  } else {
1781
1740
  fragment.appendChild(document.createTextNode(token));
@@ -1819,33 +1778,28 @@
1819
1778
  for (const textNode of textNodes) {
1820
1779
  const text = typeof textNode.nodeValue === "string" ? textNode.nodeValue : "";
1821
1780
  if (!text) continue;
1822
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1823
- if (!ANNOTATION_MARKER_REGEX.test(text)) continue;
1824
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
1781
+ const markers = annotationHelpers.collectInlineAnnotationMarkers(text);
1782
+ if (markers.length === 0) continue;
1825
1783
 
1826
1784
  const fragment = document.createDocumentFragment();
1827
1785
  let lastIndex = 0;
1828
- let match;
1829
- while ((match = ANNOTATION_MARKER_REGEX.exec(text)) !== null) {
1830
- const token = match[0] || "";
1831
- const start = typeof match.index === "number" ? match.index : 0;
1832
- if (start > lastIndex) {
1833
- fragment.appendChild(document.createTextNode(text.slice(lastIndex, start)));
1786
+ markers.forEach(function(marker) {
1787
+ const token = marker.raw || "";
1788
+ if (marker.start > lastIndex) {
1789
+ fragment.appendChild(document.createTextNode(text.slice(lastIndex, marker.start)));
1834
1790
  }
1835
1791
 
1836
1792
  if (mode === "highlight") {
1837
1793
  const markerEl = document.createElement("span");
1838
1794
  markerEl.className = "annotation-preview-marker";
1839
- markerEl.textContent = typeof match[1] === "string" ? match[1].trim() : token;
1795
+ const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
1840
1796
  markerEl.title = token;
1797
+ setAnnotationPreviewMarkerContent(markerEl, markerText);
1841
1798
  fragment.appendChild(markerEl);
1842
1799
  }
1843
1800
 
1844
- lastIndex = start + token.length;
1845
- if (token.length === 0) {
1846
- ANNOTATION_MARKER_REGEX.lastIndex += 1;
1847
- }
1848
- }
1801
+ lastIndex = marker.end;
1802
+ });
1849
1803
 
1850
1804
  if (lastIndex < text.length) {
1851
1805
  fragment.appendChild(document.createTextNode(text.slice(lastIndex)));
@@ -2732,50 +2686,44 @@
2732
2686
  return "<span class='" + className + "'>" + escapeHtml(String(text || "")) + "</span>";
2733
2687
  }
2734
2688
 
2735
- function wrapHighlightWithTitle(className, text, title) {
2689
+ function buildAnnotationPreviewMarkerHtml(text, title) {
2736
2690
  const titleAttr = title ? " title='" + escapeHtml(String(title)) + "'" : "";
2737
- return "<span class='" + className + "'" + titleAttr + ">" + escapeHtml(String(text || "")) + "</span>";
2691
+ const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
2692
+ ? annotationHelpers.renderPreviewAnnotationHtml(text)
2693
+ : escapeHtml(String(text || ""));
2694
+ return "<span class='annotation-preview-marker'" + titleAttr + ">" + rendered + "</span>";
2695
+ }
2696
+
2697
+ function setAnnotationPreviewMarkerContent(markerEl, text) {
2698
+ if (!markerEl) return;
2699
+ const rendered = typeof annotationHelpers.renderPreviewAnnotationHtml === "function"
2700
+ ? annotationHelpers.renderPreviewAnnotationHtml(text)
2701
+ : escapeHtml(String(text || ""));
2702
+ markerEl.innerHTML = rendered;
2738
2703
  }
2739
2704
 
2740
2705
  function highlightInlineAnnotations(text, mode) {
2741
2706
  const source = String(text || "");
2742
2707
  const renderMode = mode === "preview" ? "preview" : "overlay";
2743
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
2744
- let lastIndex = 0;
2745
- let out = "";
2746
-
2747
- let match;
2748
- while ((match = ANNOTATION_MARKER_REGEX.exec(source)) !== null) {
2749
- const token = match[0] || "";
2750
- const start = typeof match.index === "number" ? match.index : 0;
2751
- const markerText = typeof match[1] === "string" ? match[1].trim() : token;
2752
-
2753
- if (start > lastIndex) {
2754
- out += escapeHtml(source.slice(lastIndex, start));
2755
- }
2756
-
2757
- if (renderMode === "preview") {
2758
- out += wrapHighlightWithTitle("annotation-preview-marker", markerText || token, token);
2759
- } else {
2760
- out += wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
2761
- }
2762
- lastIndex = start + token.length;
2763
- if (token.length === 0) {
2764
- ANNOTATION_MARKER_REGEX.lastIndex += 1;
2765
- }
2766
- }
2767
-
2768
- ANNOTATION_MARKER_REGEX.lastIndex = 0;
2769
- if (lastIndex < source.length) {
2770
- out += escapeHtml(source.slice(lastIndex));
2771
- }
2772
-
2773
- return out;
2708
+ return annotationHelpers.replaceInlineAnnotationMarkers(
2709
+ source,
2710
+ function(marker) {
2711
+ const token = marker.raw || "";
2712
+ const markerText = annotationHelpers.normalizePreviewAnnotationLabel(marker.body) || token;
2713
+ if (renderMode === "preview") {
2714
+ return buildAnnotationPreviewMarkerHtml(markerText, token);
2715
+ }
2716
+ return wrapHighlight(annotationsEnabled ? "hl-annotation" : "hl-annotation-muted", token);
2717
+ },
2718
+ function(segment) {
2719
+ return escapeHtml(segment);
2720
+ },
2721
+ );
2774
2722
  }
2775
2723
 
2776
- function highlightInlineMarkdown(text) {
2724
+ function highlightInlineMarkdownWithoutAnnotations(text) {
2777
2725
  const source = String(text || "");
2778
- const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))|(\[an:\s*[^\]]+\])/gi;
2726
+ const pattern = /(\x60[^\x60]*\x60)|(\[[^\]]+\]\([^)]+\))/g;
2779
2727
  let lastIndex = 0;
2780
2728
  let out = "";
2781
2729
 
@@ -2798,8 +2746,6 @@
2798
2746
  } else {
2799
2747
  out += escapeHtml(token);
2800
2748
  }
2801
- } else if (match[3]) {
2802
- out += highlightInlineAnnotations(token);
2803
2749
  } else {
2804
2750
  out += escapeHtml(token);
2805
2751
  }
@@ -2814,6 +2760,18 @@
2814
2760
  return out;
2815
2761
  }
2816
2762
 
2763
+ function highlightInlineMarkdown(text) {
2764
+ return annotationHelpers.replaceInlineAnnotationMarkers(
2765
+ String(text || ""),
2766
+ function(marker) {
2767
+ return highlightInlineAnnotations(marker.raw || "");
2768
+ },
2769
+ function(segment) {
2770
+ return highlightInlineMarkdownWithoutAnnotations(segment);
2771
+ },
2772
+ );
2773
+ }
2774
+
2817
2775
  function normalizeFenceLanguage(info) {
2818
2776
  const raw = String(info || "").trim();
2819
2777
  if (!raw) return "";
@@ -3280,6 +3238,126 @@
3280
3238
  persistStoredToggle(ANNOTATION_MODE_STORAGE_KEY, enabled);
3281
3239
  }
3282
3240
 
3241
+ function readStoredText(storageKey) {
3242
+ if (!window.localStorage) return null;
3243
+ try {
3244
+ const value = window.localStorage.getItem(storageKey);
3245
+ return typeof value === "string" ? value : null;
3246
+ } catch {
3247
+ return null;
3248
+ }
3249
+ }
3250
+
3251
+ function persistStoredText(storageKey, value) {
3252
+ if (!window.localStorage) return;
3253
+ try {
3254
+ window.localStorage.setItem(storageKey, String(value ?? ""));
3255
+ } catch {
3256
+ // ignore storage failures
3257
+ }
3258
+ }
3259
+
3260
+ function isScratchpadOpen() {
3261
+ return Boolean(scratchpadOverlayEl && !scratchpadOverlayEl.hidden);
3262
+ }
3263
+
3264
+ function readStoredScratchpadText() {
3265
+ return readStoredText(SCRATCHPAD_STORAGE_KEY);
3266
+ }
3267
+
3268
+ function persistScratchpadText(value) {
3269
+ persistStoredText(SCRATCHPAD_STORAGE_KEY, value);
3270
+ }
3271
+
3272
+ function updateScratchpadUi() {
3273
+ const normalized = String(scratchpadText || "");
3274
+ const hasContent = Boolean(normalized.trim());
3275
+ if (scratchpadBtn) {
3276
+ scratchpadBtn.textContent = hasContent ? "Scratchpad •" : "Scratchpad";
3277
+ scratchpadBtn.classList.toggle("has-content", hasContent);
3278
+ scratchpadBtn.title = hasContent
3279
+ ? "Open your local persistent scratchpad. Current notes persist after closing until you edit or clear them."
3280
+ : "Open a local persistent scratchpad for quick notes. Anything you type will persist after closing until you edit or clear it.";
3281
+ }
3282
+ if (scratchpadMetaEl) {
3283
+ scratchpadMetaEl.textContent = hasContent
3284
+ ? "Saved locally · persists after close · " + normalized.length + " chars"
3285
+ : "Empty · local only";
3286
+ }
3287
+ if (scratchpadInsertBtn) scratchpadInsertBtn.disabled = !hasContent;
3288
+ if (scratchpadCopyBtn) scratchpadCopyBtn.disabled = !hasContent;
3289
+ if (scratchpadClearBtn) scratchpadClearBtn.disabled = !normalized.length;
3290
+ }
3291
+
3292
+ function setScratchpadText(nextText, options) {
3293
+ scratchpadText = String(nextText || "");
3294
+ if (scratchpadTextEl && scratchpadTextEl.value !== scratchpadText) {
3295
+ scratchpadTextEl.value = scratchpadText;
3296
+ }
3297
+ if (!options || options.persist !== false) {
3298
+ persistScratchpadText(scratchpadText);
3299
+ }
3300
+ updateScratchpadUi();
3301
+ }
3302
+
3303
+ function closeScratchpad(options) {
3304
+ if (!scratchpadOverlayEl || scratchpadOverlayEl.hidden) return;
3305
+ scratchpadOverlayEl.hidden = true;
3306
+ document.body.classList.remove("scratchpad-open");
3307
+ const focusTarget = options && Object.prototype.hasOwnProperty.call(options, "focusTarget")
3308
+ ? options.focusTarget
3309
+ : (scratchpadReturnFocusEl || scratchpadBtn || sourceTextEl);
3310
+ scratchpadReturnFocusEl = null;
3311
+ if (focusTarget && typeof focusTarget.focus === "function") {
3312
+ const schedule = typeof window.requestAnimationFrame === "function"
3313
+ ? window.requestAnimationFrame.bind(window)
3314
+ : (cb) => window.setTimeout(cb, 16);
3315
+ schedule(() => focusTarget.focus());
3316
+ }
3317
+ }
3318
+
3319
+ function openScratchpad() {
3320
+ if (!scratchpadOverlayEl) return;
3321
+ scratchpadReturnFocusEl = document.activeElement && document.activeElement !== document.body
3322
+ ? document.activeElement
3323
+ : sourceTextEl;
3324
+ scratchpadOverlayEl.hidden = false;
3325
+ document.body.classList.add("scratchpad-open");
3326
+ if (scratchpadTextEl && typeof scratchpadTextEl.focus === "function") {
3327
+ const schedule = typeof window.requestAnimationFrame === "function"
3328
+ ? window.requestAnimationFrame.bind(window)
3329
+ : (cb) => window.setTimeout(cb, 16);
3330
+ schedule(() => {
3331
+ scratchpadTextEl.focus();
3332
+ if (typeof scratchpadTextEl.selectionStart === "number") {
3333
+ const end = scratchpadTextEl.value.length;
3334
+ scratchpadTextEl.setSelectionRange(end, end);
3335
+ }
3336
+ });
3337
+ }
3338
+ }
3339
+
3340
+ function insertScratchpadIntoEditor() {
3341
+ const content = String(scratchpadText || "");
3342
+ if (!content.trim()) {
3343
+ setStatus("Scratchpad is empty.", "warning");
3344
+ return;
3345
+ }
3346
+
3347
+ const current = sourceTextEl.value || "";
3348
+ const start = typeof sourceTextEl.selectionStart === "number" ? sourceTextEl.selectionStart : current.length;
3349
+ const end = typeof sourceTextEl.selectionEnd === "number" ? sourceTextEl.selectionEnd : start;
3350
+ const safeStart = Math.max(0, Math.min(start, current.length));
3351
+ const safeEnd = Math.max(safeStart, Math.min(end, current.length));
3352
+ const next = current.slice(0, safeStart) + content + current.slice(safeEnd);
3353
+ setEditorText(next, { preserveScroll: false, preserveSelection: false });
3354
+ const caret = safeStart + content.length;
3355
+ sourceTextEl.setSelectionRange(caret, caret);
3356
+ setActivePane("left");
3357
+ closeScratchpad({ focusTarget: sourceTextEl });
3358
+ setStatus("Inserted scratchpad into editor.", "success");
3359
+ }
3360
+
3283
3361
  function updateEditorHighlightState() {
3284
3362
  const enabled = editorHighlightEnabled && editorView === "markdown";
3285
3363
 
@@ -4786,6 +4864,71 @@
4786
4864
  }
4787
4865
  });
4788
4866
 
4867
+ if (scratchpadBtn) {
4868
+ scratchpadBtn.addEventListener("click", () => {
4869
+ openScratchpad();
4870
+ });
4871
+ }
4872
+
4873
+ if (scratchpadCloseBtn) {
4874
+ scratchpadCloseBtn.addEventListener("click", () => {
4875
+ closeScratchpad();
4876
+ });
4877
+ }
4878
+
4879
+ if (scratchpadDoneBtn) {
4880
+ scratchpadDoneBtn.addEventListener("click", () => {
4881
+ closeScratchpad();
4882
+ });
4883
+ }
4884
+
4885
+ if (scratchpadOverlayEl) {
4886
+ scratchpadOverlayEl.addEventListener("click", (event) => {
4887
+ if (event.target === scratchpadOverlayEl) {
4888
+ closeScratchpad();
4889
+ }
4890
+ });
4891
+ }
4892
+
4893
+ if (scratchpadTextEl) {
4894
+ scratchpadTextEl.addEventListener("input", () => {
4895
+ setScratchpadText(scratchpadTextEl.value);
4896
+ });
4897
+ }
4898
+
4899
+ if (scratchpadInsertBtn) {
4900
+ scratchpadInsertBtn.addEventListener("click", () => {
4901
+ insertScratchpadIntoEditor();
4902
+ });
4903
+ }
4904
+
4905
+ if (scratchpadCopyBtn) {
4906
+ scratchpadCopyBtn.addEventListener("click", async () => {
4907
+ if (!String(scratchpadText || "").trim()) {
4908
+ setStatus("Scratchpad is empty.", "warning");
4909
+ return;
4910
+ }
4911
+
4912
+ try {
4913
+ await navigator.clipboard.writeText(String(scratchpadText || ""));
4914
+ setStatus("Copied scratchpad text.", "success");
4915
+ } catch (error) {
4916
+ setStatus("Clipboard write failed.", "warning");
4917
+ }
4918
+ });
4919
+ }
4920
+
4921
+ if (scratchpadClearBtn) {
4922
+ scratchpadClearBtn.addEventListener("click", () => {
4923
+ if (!String(scratchpadText || "").length) return;
4924
+ const confirmed = window.confirm("Clear scratchpad text?");
4925
+ if (!confirmed) return;
4926
+ setScratchpadText("");
4927
+ if (scratchpadTextEl) scratchpadTextEl.focus();
4928
+ setStatus("Cleared scratchpad.", "success");
4929
+ });
4930
+ }
4931
+
4789
4932
  if (saveAnnotatedBtn) {
4790
4933
  saveAnnotatedBtn.addEventListener("click", () => {
4791
4934
  const content = sourceTextEl.value;
@@ -4924,6 +5067,7 @@
4924
5067
  refreshResponseUi();
4925
5068
  updateAnnotatedReplyHeaderButton();
4926
5069
  setActivePane("left");
5070
+ setScratchpadText(readStoredScratchpadText() || "", { persist: false });
4927
5071
 
4928
5072
  const storedEditorHighlightEnabled = readStoredEditorHighlightEnabled();
4929
5073
  const initialHighlightEnabled = storedEditorHighlightEnabled ?? Boolean(highlightSelect && highlightSelect.value === "on");