pi-studio 0.5.28 → 0.5.30

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
@@ -4,6 +4,19 @@ All notable changes to `pi-studio` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.5.30] — 2026-03-24
8
+
9
+ ### Fixed
10
+ - LaTeX preview now preserves structured display-math environments such as `bmatrix` inside `\[ ... \]` instead of flattening their rows during Markdown math normalization, and preview display equations now center more robustly across browser engines.
11
+ - Studio now highlights custom `[an: ...]` markers in LaTeX editor syntax-highlighting mode, and PDF export renders those markers as styled annotation badges for both Markdown and LaTeX documents instead of leaving the raw bracket syntax in the final PDF.
12
+ - Right-pane response PDF export now also respects the current annotation-visibility mode, so hidden annotations do not leak into exported PDFs as raw `[an: ...]` text.
13
+
14
+ ## [0.5.29] — 2026-03-21
15
+
16
+ ### Changed
17
+ - Studio keyboard shortcuts now keep `Cmd/Ctrl+Enter` for running editor text while using `Esc` to stop an active request, and the focus-pane hint/button copy now describes focus mode as a toggle via `F10` or `Cmd/Ctrl+Esc`.
18
+ - While **Run editor text** is active, Studio now exposes a separate **Queue steering** action (and `Cmd/Ctrl+Enter` queues steering) while preserving a visible **Stop** control, and response-history prompt loading now preserves the effective prompt chain for steered responses rather than only the last correction message.
19
+
7
20
  ## [0.5.28] — 2026-03-21
8
21
 
9
22
  ### Changed
@@ -78,6 +78,7 @@
78
78
  const getEditorBtn = document.getElementById("getEditorBtn");
79
79
  const loadGitDiffBtn = document.getElementById("loadGitDiffBtn");
80
80
  const sendRunBtn = document.getElementById("sendRunBtn");
81
+ const queueSteerBtn = document.getElementById("queueSteerBtn");
81
82
  const copyDraftBtn = document.getElementById("copyDraftBtn");
82
83
  const saveAnnotatedBtn = document.getElementById("saveAnnotatedBtn");
83
84
  const stripAnnotationsBtn = document.getElementById("stripAnnotationsBtn");
@@ -120,6 +121,8 @@
120
121
  let latestCritiqueNotesNormalized = "";
121
122
  let responseHistory = [];
122
123
  let responseHistoryIndex = -1;
124
+ let studioRunChainActive = false;
125
+ let queuedSteeringCount = 0;
123
126
  let agentBusyFromServer = false;
124
127
  let terminalActivityPhase = "idle";
125
128
  let terminalActivityToolName = "";
@@ -152,6 +155,23 @@
152
155
  return trimmed ? trimmed : null;
153
156
  }
154
157
 
158
+ function applyStudioRunQueueStateFromMessage(message) {
159
+ if (!message || typeof message !== "object") return false;
160
+ let changed = false;
161
+ if (typeof message.studioRunChainActive === "boolean" && studioRunChainActive !== message.studioRunChainActive) {
162
+ studioRunChainActive = message.studioRunChainActive;
163
+ changed = true;
164
+ }
165
+ if (typeof message.queuedSteeringCount === "number" && Number.isFinite(message.queuedSteeringCount)) {
166
+ const nextCount = Math.max(0, Math.floor(message.queuedSteeringCount));
167
+ if (queuedSteeringCount !== nextCount) {
168
+ queuedSteeringCount = nextCount;
169
+ changed = true;
170
+ }
171
+ }
172
+ return changed;
173
+ }
174
+
155
175
  contextTokens = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextTokens : null);
156
176
  contextWindow = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextWindow : null);
157
177
  contextPercent = parseFiniteNumber(document.body && document.body.dataset ? document.body.dataset.contextPercent : null);
@@ -383,26 +403,80 @@
383
403
  return "submitting request";
384
404
  }
385
405
 
406
+ function formatQueuedSteeringSuffix() {
407
+ if (!queuedSteeringCount) return "";
408
+ return queuedSteeringCount === 1
409
+ ? " · 1 steering queued"
410
+ : " · " + queuedSteeringCount + " steering queued";
411
+ }
412
+
386
413
  function getStudioBusyStatus(kind) {
387
414
  const action = getStudioActionLabel(kind);
415
+ const queueSuffix = studioRunChainActive ? formatQueuedSteeringSuffix() : "";
388
416
  if (terminalActivityPhase === "tool") {
389
417
  if (terminalActivityLabel) {
390
- return "Studio: " + withEllipsis(terminalActivityLabel);
418
+ return "Studio: " + withEllipsis(terminalActivityLabel) + queueSuffix;
391
419
  }
392
420
  return terminalActivityToolName
393
- ? "Studio: " + action + " (tool: " + terminalActivityToolName + ")…"
394
- : "Studio: " + action + " (running tool)…";
421
+ ? "Studio: " + action + " (tool: " + terminalActivityToolName + ")…" + queueSuffix
422
+ : "Studio: " + action + " (running tool)…" + queueSuffix;
395
423
  }
396
424
  if (terminalActivityPhase === "responding") {
397
425
  if (lastSpecificToolLabel) {
398
- return "Studio: " + lastSpecificToolLabel + " (generating response)…";
426
+ return "Studio: " + lastSpecificToolLabel + " (generating response)…" + queueSuffix;
399
427
  }
400
- return "Studio: " + action + " (generating response)…";
428
+ return "Studio: " + action + " (generating response)…" + queueSuffix;
401
429
  }
402
430
  if (terminalActivityPhase === "running" && lastSpecificToolLabel) {
403
- return "Studio: " + withEllipsis(lastSpecificToolLabel);
431
+ return "Studio: " + withEllipsis(lastSpecificToolLabel) + queueSuffix;
432
+ }
433
+ return "Studio: " + action + "…" + queueSuffix;
434
+ }
435
+
436
+ function getHistoryPromptSourceLabel(item) {
437
+ if (!item || !item.promptMode) return null;
438
+ const steeringCount = typeof item.promptSteeringCount === "number" && Number.isFinite(item.promptSteeringCount)
439
+ ? Math.max(0, Math.floor(item.promptSteeringCount))
440
+ : 0;
441
+ if (item.promptMode === "run") return "original run";
442
+ if (item.promptMode !== "effective") return null;
443
+ if (steeringCount <= 0) return "original run";
444
+ return steeringCount === 1
445
+ ? "original run + 1 steering message"
446
+ : "original run + " + steeringCount + " steering messages";
447
+ }
448
+
449
+ function getHistoryPromptButtonLabel(item) {
450
+ if (!item || !item.prompt || !String(item.prompt).trim()) {
451
+ return "Response prompt unavailable";
452
+ }
453
+ if (item.promptMode === "effective") {
454
+ return "Load effective prompt into editor";
455
+ }
456
+ if (item.promptMode === "run") {
457
+ return "Load run prompt into editor";
458
+ }
459
+ return "Load response prompt into editor";
460
+ }
461
+
462
+ function getHistoryPromptLoadedStatus(item) {
463
+ if (!item || !item.prompt || !String(item.prompt).trim()) {
464
+ return "Prompt unavailable for the selected response.";
465
+ }
466
+ if (item.promptMode === "effective") {
467
+ return "Loaded effective prompt into editor.";
468
+ }
469
+ if (item.promptMode === "run") {
470
+ return "Loaded run prompt into editor.";
404
471
  }
405
- return "Studio: " + action + "…";
472
+ return "Loaded response prompt into editor.";
473
+ }
474
+
475
+ function getHistoryPromptSourceStateLabel(item) {
476
+ if (!item || !item.prompt || !String(item.prompt).trim()) return "response prompt";
477
+ if (item.promptMode === "effective") return "effective prompt";
478
+ if (item.promptMode === "run") return "run prompt";
479
+ return "response prompt";
406
480
  }
407
481
 
408
482
  function shouldAnimateFooterSpinner() {
@@ -715,8 +789,8 @@
715
789
  btn.setAttribute("aria-pressed", isFocusedPane ? "true" : "false");
716
790
  btn.textContent = isFocusedPane ? "Exit focus" : "Focus pane";
717
791
  btn.title = isFocusedPane
718
- ? "Exit focus mode for the " + paneName + " pane."
719
- : "Show only the " + paneName + " pane. Shortcut: Cmd/Ctrl+Esc or F10.";
792
+ ? "Return to the two-pane layout. Shortcut: F10 or Cmd/Ctrl+Esc."
793
+ : "Show only the " + paneName + " pane. Shortcut: F10 or Cmd/Ctrl+Esc.";
720
794
  });
721
795
  }
722
796
 
@@ -754,7 +828,7 @@
754
828
  setActivePane(pane);
755
829
  paneFocusTarget = pane;
756
830
  applyPaneFocusClasses();
757
- setStatus("Focus mode: " + paneLabel(pane) + " pane (Esc to exit).");
831
+ setStatus("Focus mode: " + paneLabel(pane) + " pane. Toggle with F10 or Cmd/Ctrl+Esc.");
758
832
  }
759
833
 
760
834
  function togglePaneFocus() {
@@ -777,7 +851,7 @@
777
851
  }
778
852
 
779
853
  function handlePaneShortcut(event) {
780
- if (!event) return;
854
+ if (!event || event.defaultPrevented) return;
781
855
 
782
856
  const key = typeof event.key === "string" ? event.key : "";
783
857
  const isToggleShortcut =
@@ -797,9 +871,16 @@
797
871
  && !event.altKey
798
872
  && !event.shiftKey
799
873
  ) {
874
+ const activeKind = getAbortablePendingKind();
875
+ if (activeKind === "direct" || activeKind === "critique") {
876
+ event.preventDefault();
877
+ requestCancelForPendingRequest(activeKind);
878
+ return;
879
+ }
800
880
  if (exitPaneFocus()) {
801
881
  event.preventDefault();
802
882
  }
883
+ return;
803
884
  }
804
885
 
805
886
  if (
@@ -808,11 +889,16 @@
808
889
  && !event.altKey
809
890
  && !event.shiftKey
810
891
  && activePane === "left"
811
- && sendRunBtn
812
- && !sendRunBtn.disabled
813
892
  ) {
814
- event.preventDefault();
815
- sendRunBtn.click();
893
+ if (queueSteerBtn && !queueSteerBtn.disabled) {
894
+ event.preventDefault();
895
+ queueSteerBtn.click();
896
+ return;
897
+ }
898
+ if (sendRunBtn && !sendRunBtn.disabled) {
899
+ event.preventDefault();
900
+ sendRunBtn.click();
901
+ }
816
902
  }
817
903
  }
818
904
 
@@ -851,6 +937,18 @@
851
937
  const thinking = typeof item.thinking === "string"
852
938
  ? item.thinking
853
939
  : (item.thinking == null ? null : String(item.thinking));
940
+ const promptMode = item.promptMode === "run" || item.promptMode === "effective"
941
+ ? item.promptMode
942
+ : "response";
943
+ const promptTriggerKind = item.promptTriggerKind === "run" || item.promptTriggerKind === "steer"
944
+ ? item.promptTriggerKind
945
+ : null;
946
+ const promptSteeringCount = typeof item.promptSteeringCount === "number" && Number.isFinite(item.promptSteeringCount)
947
+ ? Math.max(0, Math.floor(item.promptSteeringCount))
948
+ : 0;
949
+ const promptTriggerText = typeof item.promptTriggerText === "string"
950
+ ? item.promptTriggerText
951
+ : (item.promptTriggerText == null ? null : String(item.promptTriggerText));
854
952
 
855
953
  return {
856
954
  id,
@@ -859,6 +957,10 @@
859
957
  timestamp,
860
958
  kind: normalizeHistoryKind(item.kind),
861
959
  prompt,
960
+ promptMode,
961
+ promptTriggerKind,
962
+ promptSteeringCount,
963
+ promptTriggerText,
862
964
  };
863
965
  }
864
966
 
@@ -904,9 +1006,13 @@
904
1006
  const hasPrompt = Boolean(selectedItem && typeof selectedItem.prompt === "string" && selectedItem.prompt.trim());
905
1007
  if (loadHistoryPromptBtn) {
906
1008
  loadHistoryPromptBtn.disabled = uiBusy || !hasPrompt;
907
- loadHistoryPromptBtn.textContent = hasPrompt
908
- ? "Load response prompt into editor"
909
- : "Response prompt unavailable";
1009
+ loadHistoryPromptBtn.textContent = getHistoryPromptButtonLabel(selectedItem);
1010
+ const promptSourceLabel = getHistoryPromptSourceLabel(selectedItem);
1011
+ loadHistoryPromptBtn.title = hasPrompt
1012
+ ? (promptSourceLabel
1013
+ ? "Load the " + promptSourceLabel + " prompt chain that generated the selected response into the editor."
1014
+ : "Load the prompt that generated the selected response into the editor.")
1015
+ : "Prompt unavailable for the selected response.";
910
1016
  }
911
1017
  }
912
1018
 
@@ -1609,7 +1715,9 @@
1609
1715
  return;
1610
1716
  }
1611
1717
 
1612
- const markdown = rightView === "editor-preview" ? prepareEditorTextForPdfExport(sourceTextEl.value) : latestResponseMarkdown;
1718
+ const markdown = rightView === "editor-preview"
1719
+ ? prepareEditorTextForPdfExport(sourceTextEl.value)
1720
+ : prepareEditorTextForPreview(latestResponseMarkdown);
1613
1721
  if (!markdown || !markdown.trim()) {
1614
1722
  setStatus("Nothing to export yet.", "warning");
1615
1723
  return;
@@ -2452,17 +2560,49 @@
2452
2560
  }
2453
2561
 
2454
2562
  if (lang === "latex") {
2455
- const texPattern = /(%.*$)|(\\(?:documentclass|usepackage|newtheorem|begin|end|section|subsection|subsubsection|chapter|part|title|author|date|maketitle|tableofcontents|includegraphics|caption|label|ref|eqref|cite|textbf|textit|texttt|emph|footnote|centering|newcommand|renewcommand|providecommand|bibliography|bibliographystyle|bibitem|item|input|include)\b)|(\\[A-Za-z]+)|(\{|\})|(\$\$?(?:[^$\\]|\\.)+\$\$?)|(\[(?:.*?)\])/g;
2456
- const highlighted = highlightCodeTokens(source, texPattern, (match) => {
2457
- if (match[1]) return "hl-code-com";
2458
- if (match[2]) return "hl-code-kw";
2459
- if (match[3]) return "hl-code-fn";
2460
- if (match[4]) return "hl-code-op";
2461
- if (match[5]) return "hl-code-str";
2462
- if (match[6]) return "hl-code-num";
2463
- return "hl-code";
2464
- });
2465
- return highlighted;
2563
+ const texPattern = /(%.*$)|(\[an:\s*[^\]]+\])|(\\(?:documentclass|usepackage|newtheorem|begin|end|section|subsection|subsubsection|chapter|part|title|author|date|maketitle|tableofcontents|includegraphics|caption|label|ref|eqref|cite|textbf|textit|texttt|emph|footnote|centering|newcommand|renewcommand|providecommand|bibliography|bibliographystyle|bibitem|item|input|include)\b)|(\\[A-Za-z]+)|(\{|\})|(\$\$?(?:[^$\\]|\\.)+\$\$?)|(\[(?:.*?)\])/gi;
2564
+ let out = "";
2565
+ let lastIndex = 0;
2566
+ texPattern.lastIndex = 0;
2567
+
2568
+ let match;
2569
+ while ((match = texPattern.exec(source)) !== null) {
2570
+ const token = match[0] || "";
2571
+ const start = typeof match.index === "number" ? match.index : 0;
2572
+
2573
+ if (start > lastIndex) {
2574
+ out += escapeHtml(source.slice(lastIndex, start));
2575
+ }
2576
+
2577
+ if (match[1]) {
2578
+ out += wrapHighlight("hl-code-com", token);
2579
+ } else if (match[2]) {
2580
+ out += highlightInlineAnnotations(token, renderMode);
2581
+ } else if (match[3]) {
2582
+ out += wrapHighlight("hl-code-kw", token);
2583
+ } else if (match[4]) {
2584
+ out += wrapHighlight("hl-code-fn", token);
2585
+ } else if (match[5]) {
2586
+ out += wrapHighlight("hl-code-op", token);
2587
+ } else if (match[6]) {
2588
+ out += wrapHighlight("hl-code-str", token);
2589
+ } else if (match[7]) {
2590
+ out += wrapHighlight("hl-code-num", token);
2591
+ } else {
2592
+ out += escapeHtml(token);
2593
+ }
2594
+
2595
+ lastIndex = start + token.length;
2596
+ if (token.length === 0) {
2597
+ texPattern.lastIndex += 1;
2598
+ }
2599
+ }
2600
+
2601
+ if (lastIndex < source.length) {
2602
+ out += escapeHtml(source.slice(lastIndex));
2603
+ }
2604
+
2605
+ return out;
2466
2606
  }
2467
2607
 
2468
2608
  if (lang === "diff") {
@@ -2806,29 +2946,43 @@
2806
2946
 
2807
2947
  function syncRunAndCritiqueButtons() {
2808
2948
  const activeKind = getAbortablePendingKind();
2809
- const sendRunIsStop = activeKind === "direct";
2949
+ const directIsStop = activeKind === "direct";
2810
2950
  const critiqueIsStop = activeKind === "critique";
2951
+ const canQueueSteering = studioRunChainActive && !critiqueIsStop;
2811
2952
 
2812
2953
  if (sendRunBtn) {
2813
- sendRunBtn.textContent = sendRunIsStop ? "Stop" : "Run editor text";
2814
- sendRunBtn.classList.toggle("request-stop-active", sendRunIsStop);
2815
- sendRunBtn.disabled = sendRunIsStop ? wsState === "Disconnected" : (uiBusy || critiqueIsStop);
2816
- sendRunBtn.title = sendRunIsStop
2817
- ? "Stop the running editor-text request."
2954
+ sendRunBtn.textContent = directIsStop ? "Stop" : "Run editor text";
2955
+ sendRunBtn.classList.toggle("request-stop-active", directIsStop);
2956
+ sendRunBtn.disabled = wsState === "Disconnected" || (!directIsStop && (uiBusy || critiqueIsStop));
2957
+ sendRunBtn.title = directIsStop
2958
+ ? "Stop the active run. Shortcut: Esc."
2818
2959
  : (annotationsEnabled
2819
- ? "Run editor text as-is (includes [an: ...] markers). Shortcut: Cmd/Ctrl+Enter."
2820
- : "Run editor text with [an: ...] markers stripped. Shortcut: Cmd/Ctrl+Enter.");
2960
+ ? "Run editor text as-is (includes [an: ...] markers). Shortcut: Cmd/Ctrl+Enter. Stop the active request with Esc."
2961
+ : "Run editor text with [an: ...] markers stripped. Shortcut: Cmd/Ctrl+Enter. Stop the active request with Esc.");
2962
+ }
2963
+
2964
+ if (queueSteerBtn) {
2965
+ queueSteerBtn.hidden = false;
2966
+ queueSteerBtn.disabled = wsState === "Disconnected" || !canQueueSteering;
2967
+ queueSteerBtn.classList.remove("request-stop-active");
2968
+ queueSteerBtn.title = canQueueSteering
2969
+ ? (annotationsEnabled
2970
+ ? "Queue the current editor text as a steering message for the active run. Shortcut: Cmd/Ctrl+Enter."
2971
+ : "Queue the current editor text as a steering message for the active run after stripping [an: ...] markers. Shortcut: Cmd/Ctrl+Enter.")
2972
+ : "Queue steering is available while Run editor text is active.";
2821
2973
  }
2822
2974
 
2823
2975
  if (critiqueBtn) {
2824
2976
  critiqueBtn.textContent = critiqueIsStop ? "Stop" : "Critique editor text";
2825
2977
  critiqueBtn.classList.toggle("request-stop-active", critiqueIsStop);
2826
- critiqueBtn.disabled = critiqueIsStop ? wsState === "Disconnected" : (uiBusy || sendRunIsStop);
2978
+ critiqueBtn.disabled = critiqueIsStop ? wsState === "Disconnected" : (uiBusy || canQueueSteering);
2827
2979
  critiqueBtn.title = critiqueIsStop
2828
- ? "Stop the running critique request."
2829
- : (annotationsEnabled
2830
- ? "Critique editor text as-is (includes [an: ...] markers)."
2831
- : "Critique editor text with [an: ...] markers stripped.");
2980
+ ? "Stop the running critique request. Shortcut: Esc."
2981
+ : (canQueueSteering
2982
+ ? "Critique queueing is not supported while Run editor text is active."
2983
+ : (annotationsEnabled
2984
+ ? "Critique editor text as-is (includes [an: ...] markers)."
2985
+ : "Critique editor text with [an: ...] markers stripped."));
2832
2986
  }
2833
2987
  }
2834
2988
 
@@ -2973,6 +3127,7 @@
2973
3127
  if (typeof message.terminalSessionLabel === "string") {
2974
3128
  terminalSessionLabel = message.terminalSessionLabel;
2975
3129
  }
3130
+ applyStudioRunQueueStateFromMessage(message);
2976
3131
  updateFooterMeta();
2977
3132
  setBusy(busy);
2978
3133
  setWsState(busy ? "Submitting" : "Ready");
@@ -3045,6 +3200,8 @@
3045
3200
  if (busy) {
3046
3201
  if (agentBusyFromServer && stickyStudioKind) {
3047
3202
  setStatus(getStudioBusyStatus(stickyStudioKind), "warning");
3203
+ } else if (agentBusyFromServer && studioRunChainActive) {
3204
+ setStatus(getStudioBusyStatus("direct"), "warning");
3048
3205
  } else if (agentBusyFromServer) {
3049
3206
  setStatus(getTerminalBusyStatus(), "warning");
3050
3207
  } else {
@@ -3065,6 +3222,9 @@
3065
3222
  pendingRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
3066
3223
  pendingKind = typeof message.kind === "string" ? message.kind : "unknown";
3067
3224
  stickyStudioKind = pendingKind;
3225
+ if (pendingKind === "direct") {
3226
+ studioRunChainActive = true;
3227
+ }
3068
3228
  if (pendingKind === "compact") {
3069
3229
  compactInProgress = true;
3070
3230
  }
@@ -3074,6 +3234,14 @@
3074
3234
  return;
3075
3235
  }
3076
3236
 
3237
+ if (message.type === "request_queued") {
3238
+ studioRunChainActive = true;
3239
+ applyStudioRunQueueStateFromMessage(message);
3240
+ syncActionButtons();
3241
+ setStatus("Steering queued.", "success");
3242
+ return;
3243
+ }
3244
+
3077
3245
  if (message.type === "compaction_completed") {
3078
3246
  if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
3079
3247
  pendingRequestId = null;
@@ -3310,6 +3478,7 @@
3310
3478
  if (typeof message.terminalSessionLabel === "string") {
3311
3479
  terminalSessionLabel = message.terminalSessionLabel;
3312
3480
  }
3481
+ applyStudioRunQueueStateFromMessage(message);
3313
3482
  updateFooterMeta();
3314
3483
 
3315
3484
  if (typeof message.activeRequestId === "string" && message.activeRequestId.length > 0) {
@@ -3346,6 +3515,8 @@
3346
3515
  if (busy) {
3347
3516
  if (agentBusyFromServer && stickyStudioKind) {
3348
3517
  setStatus(getStudioBusyStatus(stickyStudioKind), "warning");
3518
+ } else if (agentBusyFromServer && studioRunChainActive) {
3519
+ setStatus(getStudioBusyStatus("direct"), "warning");
3349
3520
  } else if (agentBusyFromServer) {
3350
3521
  setStatus(getTerminalBusyStatus(), "warning");
3351
3522
  } else {
@@ -3816,8 +3987,8 @@
3816
3987
  }
3817
3988
 
3818
3989
  setEditorText(prompt, { preserveScroll: false, preserveSelection: false });
3819
- setSourceState({ source: "blank", label: "response prompt", path: null });
3820
- setStatus("Loaded response prompt into editor.", "success");
3990
+ setSourceState({ source: "blank", label: getHistoryPromptSourceStateLabel(item), path: null });
3991
+ setStatus(getHistoryPromptLoadedStatus(item), "success");
3821
3992
  });
3822
3993
  }
3823
3994
 
@@ -4118,6 +4289,30 @@
4118
4289
  }
4119
4290
  });
4120
4291
 
4292
+ if (queueSteerBtn) {
4293
+ queueSteerBtn.addEventListener("click", () => {
4294
+ const prepared = prepareEditorTextForSend(sourceTextEl.value);
4295
+ if (!prepared.trim()) {
4296
+ setStatus("Editor is empty. Nothing to queue.", "warning");
4297
+ return;
4298
+ }
4299
+ if (!studioRunChainActive) {
4300
+ setStatus("Queue steering is only available while Run editor text is active.", "warning");
4301
+ return;
4302
+ }
4303
+
4304
+ const requestId = makeRequestId();
4305
+ clearTitleAttention();
4306
+ const sent = sendMessage({
4307
+ type: "send_run_request",
4308
+ requestId,
4309
+ text: prepared,
4310
+ });
4311
+ if (!sent) return;
4312
+ setStatus("Queueing steering…", "warning");
4313
+ });
4314
+ }
4315
+
4121
4316
  copyDraftBtn.addEventListener("click", async () => {
4122
4317
  const content = sourceTextEl.value;
4123
4318
  if (!content.trim()) {
package/client/studio.css CHANGED
@@ -87,6 +87,7 @@
87
87
  }
88
88
 
89
89
  #sendRunBtn,
90
+ #queueSteerBtn,
90
91
  #critiqueBtn {
91
92
  min-width: 10rem;
92
93
  display: inline-flex;
@@ -95,6 +96,7 @@
95
96
  }
96
97
 
97
98
  #sendRunBtn:not(:disabled):not(.request-stop-active),
99
+ #queueSteerBtn:not(:disabled),
98
100
  #loadResponseBtn:not(:disabled):not([hidden]) {
99
101
  background: var(--accent);
100
102
  border-color: var(--accent);
@@ -103,6 +105,7 @@
103
105
  }
104
106
 
105
107
  #sendRunBtn:not(:disabled):not(.request-stop-active):hover,
108
+ #queueSteerBtn:not(:disabled):hover,
106
109
  #loadResponseBtn:not(:disabled):not([hidden]):hover {
107
110
  filter: brightness(0.95);
108
111
  }
@@ -871,7 +874,9 @@
871
874
 
872
875
  .rendered-markdown mjx-container[display="true"] {
873
876
  display: block;
874
- margin: 1em 0;
877
+ width: fit-content;
878
+ max-width: 100%;
879
+ margin: 1em auto;
875
880
  text-align: center;
876
881
  overflow-x: auto;
877
882
  overflow-y: hidden;
@@ -890,7 +895,7 @@
890
895
 
891
896
  .rendered-markdown .studio-display-equation-body math[display="block"],
892
897
  .rendered-markdown .studio-display-equation-body mjx-container[display="true"] {
893
- margin: 0;
898
+ margin: 0 auto;
894
899
  }
895
900
 
896
901
  .rendered-markdown .studio-display-equation-number {
@@ -907,7 +912,9 @@
907
912
 
908
913
  .rendered-markdown math[display="block"] {
909
914
  display: block;
910
- margin: 1em 0;
915
+ width: fit-content;
916
+ max-width: 100%;
917
+ margin: 1em auto;
911
918
  text-align: center;
912
919
  overflow-x: auto;
913
920
  overflow-y: hidden;