pi-studio 0.10.4 → 0.10.5
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 +7 -0
- package/client/studio-client.js +193 -9
- package/client/studio.css +6 -0
- package/package.json +1 -1
- package/shared/studio-browser-launcher.js +28 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,13 @@ All notable changes to `pi-studio` are documented here.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.10.5] — 2026-10-20
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- After a Quick send, the record shows the result while the cursor stays in the box. Since 0.10.3 it waited for the next change, such as your next send. If the pane is redrawn while you type, the text, the selection and where typing goes are kept.
|
|
11
|
+
- While a prompt runs, the Prompt toolbar's tools stay on one row on a 13-inch screen. **Queue steering** took more room than its label needed, which left a gap before it and pushed them onto a second row.
|
|
12
|
+
- With Muxy 2.2, `/studio` opens in Muxy's built-in browser. Muxy 2.2 marks its terminals differently, so Studio didn't recognise them and opened the system browser instead.
|
|
13
|
+
|
|
7
14
|
## [0.10.4] — 2026-10-19
|
|
8
15
|
|
|
9
16
|
### Fixed
|
package/client/studio-client.js
CHANGED
|
@@ -716,6 +716,18 @@
|
|
|
716
716
|
// After a Quick send from the box, the cursor goes back to it (Oliver, 19 Oct):
|
|
717
717
|
// { requestId, settled }, cleared when restored or when anything else is chosen.
|
|
718
718
|
let replQuickRefocus = null;
|
|
719
|
+
// A REPL pane redraw that waited (for focus to leave a control, or for a pause in the
|
|
720
|
+
// Quick send box), and what the box is in the middle of (Oliver, 19 Oct).
|
|
721
|
+
let replRedrawDeferred = false;
|
|
722
|
+
let replRedrawDeferredForce = false;
|
|
723
|
+
let replRedrawForced = false;
|
|
724
|
+
let replRedrawRetryTimer = null;
|
|
725
|
+
// A height the box was dragged to, kept across redraws.
|
|
726
|
+
let replQuickBoxHeight = "";
|
|
727
|
+
let replQuickComposing = false;
|
|
728
|
+
let replQuickPointerDown = false;
|
|
729
|
+
let replQuickLastInputAt = 0;
|
|
730
|
+
const REPL_QUICK_TYPING_PAUSE_MS = 800;
|
|
719
731
|
let replSendMode = (() => {
|
|
720
732
|
try {
|
|
721
733
|
const stored = window.localStorage && window.localStorage.getItem("piStudio.replSendMode");
|
|
@@ -1669,7 +1681,10 @@
|
|
|
1669
1681
|
}
|
|
1670
1682
|
|
|
1671
1683
|
function setReplQuickDraft(session, value) {
|
|
1672
|
-
|
|
1684
|
+
return setReplQuickDraftForKey(getReplQuickDraftKey(session), value);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
function setReplQuickDraftForKey(key, value) {
|
|
1673
1688
|
if (!key) return "";
|
|
1674
1689
|
const draft = String(value || "").slice(0, REPL_QUICK_DRAFT_MAX_CHARS);
|
|
1675
1690
|
if (!draft) {
|
|
@@ -2006,6 +2021,113 @@
|
|
|
2006
2021
|
return activeEl instanceof Element && Boolean(activeEl.closest(".repl-controls, .repl-quick-composer"));
|
|
2007
2022
|
}
|
|
2008
2023
|
|
|
2024
|
+
// The pane can be redrawn under the Quick send box: its text is kept as a draft on
|
|
2025
|
+
// every keystroke, and its cursor, selection and scroll are put back. Otherwise a
|
|
2026
|
+
// result that arrived while the cursor stayed there wasn't shown (since 0.10.3).
|
|
2027
|
+
// Not mid-typing, mid-composition or mid-drag, though: the redraw waits for a pause.
|
|
2028
|
+
// Other REPL controls wait until focus leaves them.
|
|
2029
|
+
function canRedrawReplUnderFocus() {
|
|
2030
|
+
const activeEl = document.activeElement;
|
|
2031
|
+
return activeEl instanceof HTMLTextAreaElement && activeEl.hasAttribute("data-repl-quick-draft")
|
|
2032
|
+
&& !replQuickComposing && !replQuickPointerDown && Date.now() - replQuickLastInputAt >= REPL_QUICK_TYPING_PAUSE_MS;
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
function retryReplRedrawAfterPause() {
|
|
2036
|
+
if (replRedrawRetryTimer !== null) return;
|
|
2037
|
+
const wait = Math.max(0, replQuickLastInputAt + REPL_QUICK_TYPING_PAUSE_MS - Date.now()) + 20;
|
|
2038
|
+
replRedrawRetryTimer = window.setTimeout(() => {
|
|
2039
|
+
replRedrawRetryTimer = null;
|
|
2040
|
+
replayDeferredReplRedraw();
|
|
2041
|
+
}, wait);
|
|
2042
|
+
}
|
|
2043
|
+
|
|
2044
|
+
// Whether a REPL redraw has to wait. Asked when the redraw is requested and again when
|
|
2045
|
+
// its frame runs: composing, typing, a drag or focus elsewhere can start in between
|
|
2046
|
+
// (Sol, Astra). Composing holds even a forced redraw.
|
|
2047
|
+
function replRedrawMustWait(force) {
|
|
2048
|
+
return replQuickComposing || (!force && isReplControlFocused() && !canRedrawReplUnderFocus());
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
function deferReplRedraw(force) {
|
|
2052
|
+
replRedrawDeferred = true;
|
|
2053
|
+
replRedrawDeferredForce = replRedrawDeferredForce || force;
|
|
2054
|
+
// Only typing needs a timer: composing and a drag end with their own events (Sol).
|
|
2055
|
+
const activeEl = document.activeElement;
|
|
2056
|
+
if (!replQuickComposing && !replQuickPointerDown && activeEl instanceof HTMLTextAreaElement && activeEl.hasAttribute("data-repl-quick-draft")) retryReplRedrawAfterPause();
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
// The session lifetime a drawn Quick send box belongs to. While a redraw waits (for
|
|
2060
|
+
// composing, say), the selection can move on and leave the box showing another one.
|
|
2061
|
+
function getReplQuickBoxKey(box) {
|
|
2062
|
+
try {
|
|
2063
|
+
return box instanceof Element ? decodeURIComponent(box.getAttribute("data-repl-quick-lifetime") || "") : "";
|
|
2064
|
+
} catch {
|
|
2065
|
+
return "";
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// What the REPL pane is drawn for: the runtime, and the selected session's name and lifetime.
|
|
2070
|
+
function getReplPaneTargetKey() {
|
|
2071
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
2072
|
+
return [normalizeReplRuntime(replRuntime), session ? session.sessionName : "", session ? getReplQuickDraftKey(session) : ""].join("\u001e");
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// A pane left on screen for another session, or a replacement with the same name. Its
|
|
2076
|
+
// controls do nothing then, and the redraw shows the current one (Sol, Astra).
|
|
2077
|
+
function isReplPaneStale(el) {
|
|
2078
|
+
const pane = el instanceof Element ? el.closest("[data-repl-drawn-target]") : null;
|
|
2079
|
+
if (!pane) return false;
|
|
2080
|
+
try {
|
|
2081
|
+
return decodeURIComponent(pane.getAttribute("data-repl-drawn-target") || "") !== getReplPaneTargetKey();
|
|
2082
|
+
} catch {
|
|
2083
|
+
return true;
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
function refuseStaleReplPaneAction(message) {
|
|
2088
|
+
setStatus(message, "warning");
|
|
2089
|
+
renderReplViewIfActive({ force: true });
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function replayDeferredReplRedraw() {
|
|
2093
|
+
if (!replRedrawDeferred) return;
|
|
2094
|
+
renderReplViewIfActive({ force: replRedrawDeferredForce });
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// The cursor goes back only into the box for the same session lifetime: a redraw that
|
|
2098
|
+
// shows another session, or a replacement with the same name, leaves focus to the page.
|
|
2099
|
+
function captureReplQuickComposerCursor() {
|
|
2100
|
+
const activeEl = document.activeElement;
|
|
2101
|
+
if (!(activeEl instanceof HTMLTextAreaElement) || !activeEl.hasAttribute("data-repl-quick-draft")) return null;
|
|
2102
|
+
return {
|
|
2103
|
+
lifetime: activeEl.getAttribute("data-repl-quick-lifetime") || "",
|
|
2104
|
+
start: activeEl.selectionStart,
|
|
2105
|
+
end: activeEl.selectionEnd,
|
|
2106
|
+
direction: activeEl.selectionDirection,
|
|
2107
|
+
scrollTop: activeEl.scrollTop,
|
|
2108
|
+
};
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
function restoreReplQuickComposerCursor(cursor) {
|
|
2112
|
+
if (!cursor || rightView !== "repl" || !critiqueViewEl) return;
|
|
2113
|
+
const composer = critiqueViewEl.querySelector("[data-repl-quick-draft]");
|
|
2114
|
+
if (!(composer instanceof HTMLTextAreaElement) || composer.disabled) return;
|
|
2115
|
+
if (!cursor.lifetime || composer.getAttribute("data-repl-quick-lifetime") !== cursor.lifetime) return;
|
|
2116
|
+
if (document.activeElement !== composer) {
|
|
2117
|
+
try {
|
|
2118
|
+
composer.focus({ preventScroll: true });
|
|
2119
|
+
} catch {
|
|
2120
|
+
try { composer.focus(); } catch {}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
if (document.activeElement !== composer) return;
|
|
2124
|
+
const length = composer.value.length;
|
|
2125
|
+
try {
|
|
2126
|
+
composer.setSelectionRange(Math.min(cursor.start, length), Math.min(cursor.end, length), cursor.direction || "none");
|
|
2127
|
+
} catch {}
|
|
2128
|
+
composer.scrollTop = cursor.scrollTop;
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2009
2131
|
function focusReplQuickComposer() {
|
|
2010
2132
|
replQuickFocusRequested = true;
|
|
2011
2133
|
window.setTimeout(() => {
|
|
@@ -2054,11 +2176,26 @@
|
|
|
2054
2176
|
|
|
2055
2177
|
function renderReplViewIfActive(options) {
|
|
2056
2178
|
if (rightView !== "repl") return;
|
|
2057
|
-
|
|
2179
|
+
const force = Boolean(options && options.force === true);
|
|
2180
|
+
if (replRedrawMustWait(force)) {
|
|
2181
|
+
deferReplRedraw(force);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
replRedrawDeferred = false;
|
|
2185
|
+
replRedrawDeferredForce = false;
|
|
2186
|
+
replRedrawForced = replRedrawForced || force;
|
|
2058
2187
|
if (traceRenderRaf !== null) return;
|
|
2059
2188
|
traceRenderRaf = window.requestAnimationFrame(() => {
|
|
2060
2189
|
traceRenderRaf = null;
|
|
2190
|
+
const forced = replRedrawForced;
|
|
2191
|
+
replRedrawForced = false;
|
|
2192
|
+
if (rightView === "repl" && replRedrawMustWait(forced)) {
|
|
2193
|
+
deferReplRedraw(forced);
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
const cursor = captureReplQuickComposerCursor();
|
|
2061
2197
|
refreshResponseUi();
|
|
2198
|
+
restoreReplQuickComposerCursor(cursor);
|
|
2062
2199
|
applyReplQuickRefocus();
|
|
2063
2200
|
if (replQuickFocusRequested) focusReplQuickComposer();
|
|
2064
2201
|
});
|
|
@@ -2800,6 +2937,11 @@
|
|
|
2800
2937
|
return false;
|
|
2801
2938
|
}
|
|
2802
2939
|
const key = getReplQuickDraftKey(session);
|
|
2940
|
+
const box = critiqueViewEl ? critiqueViewEl.querySelector("[data-repl-quick-draft]") : null;
|
|
2941
|
+
if (isReplPaneStale(box)) {
|
|
2942
|
+
refuseStaleReplPaneAction("The REPL session changed, so nothing was sent.");
|
|
2943
|
+
return false;
|
|
2944
|
+
}
|
|
2803
2945
|
const text = getReplQuickDraft(session);
|
|
2804
2946
|
if (!text.trim()) {
|
|
2805
2947
|
setStatus("Quick send is empty.", "warning");
|
|
@@ -11693,6 +11835,10 @@
|
|
|
11693
11835
|
if (!actionBtn) return;
|
|
11694
11836
|
event.preventDefault();
|
|
11695
11837
|
const action = actionBtn.getAttribute("data-repl-action") || "";
|
|
11838
|
+
if (isReplPaneStale(actionBtn)) {
|
|
11839
|
+
refuseStaleReplPaneAction(action === "quick-send" ? "The REPL session changed, so nothing was sent." : "The REPL session changed, so nothing was done.");
|
|
11840
|
+
return;
|
|
11841
|
+
}
|
|
11696
11842
|
if (action === "quick-send") {
|
|
11697
11843
|
submitReplQuickDraft();
|
|
11698
11844
|
return;
|
|
@@ -11834,13 +11980,44 @@
|
|
|
11834
11980
|
}
|
|
11835
11981
|
}
|
|
11836
11982
|
|
|
11983
|
+
// A redraw that waited for a control runs once focus has left it.
|
|
11984
|
+
function handleReplPaneFocusOut() {
|
|
11985
|
+
if (!replRedrawDeferred) return;
|
|
11986
|
+
window.setTimeout(replayDeferredReplRedraw, 0);
|
|
11987
|
+
}
|
|
11988
|
+
|
|
11989
|
+
function handleReplPaneComposition(event) {
|
|
11990
|
+
const target = event && event.target;
|
|
11991
|
+
if (!(target instanceof HTMLTextAreaElement) || !target.hasAttribute("data-repl-quick-draft")) return;
|
|
11992
|
+
replQuickComposing = event.type === "compositionstart";
|
|
11993
|
+
if (!replQuickComposing) replayDeferredReplRedraw();
|
|
11994
|
+
}
|
|
11995
|
+
|
|
11996
|
+
// A drag that starts in the box ends wherever the button comes up.
|
|
11997
|
+
function handleReplQuickPointer(event) {
|
|
11998
|
+
if (event.type === "pointerdown") {
|
|
11999
|
+
const target = event.target;
|
|
12000
|
+
replQuickPointerDown = target instanceof Element && Boolean(target.closest("[data-repl-quick-draft]"));
|
|
12001
|
+
return;
|
|
12002
|
+
}
|
|
12003
|
+
if (!replQuickPointerDown) return;
|
|
12004
|
+
replQuickPointerDown = false;
|
|
12005
|
+
// Dragging the box's corner sets its height; keep it for the boxes drawn after it.
|
|
12006
|
+
const composer = critiqueViewEl ? critiqueViewEl.querySelector("[data-repl-quick-draft]") : null;
|
|
12007
|
+
if (composer instanceof HTMLTextAreaElement && /^\d+(?:\.\d+)?px$/.test(composer.style.height)) replQuickBoxHeight = composer.style.height;
|
|
12008
|
+
replayDeferredReplRedraw();
|
|
12009
|
+
}
|
|
12010
|
+
|
|
11837
12011
|
function handleReplPaneInput(event) {
|
|
11838
12012
|
if (rightView !== "repl") return;
|
|
11839
12013
|
const target = event && event.target;
|
|
11840
12014
|
if (!(target instanceof HTMLTextAreaElement) || !target.hasAttribute("data-repl-quick-draft")) return;
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
12015
|
+
replQuickLastInputAt = Date.now();
|
|
12016
|
+
// The text belongs to the session the box was drawn for, even if another is selected
|
|
12017
|
+
// now and the box hasn't been redrawn yet (Astra).
|
|
12018
|
+
const key = getReplQuickBoxKey(target);
|
|
12019
|
+
if (!key) return;
|
|
12020
|
+
const draft = setReplQuickDraftForKey(key, target.value);
|
|
11844
12021
|
if (target.value !== draft) target.value = draft;
|
|
11845
12022
|
updateReplQuickComposerActions();
|
|
11846
12023
|
}
|
|
@@ -11889,7 +12066,8 @@
|
|
|
11889
12066
|
}
|
|
11890
12067
|
const commandInput = target.closest("[data-repl-command]");
|
|
11891
12068
|
if (commandInput && "value" in commandInput) {
|
|
11892
|
-
|
|
12069
|
+
// For the runtime it was drawn for, even if another was selected meanwhile.
|
|
12070
|
+
setReplCommandOverride(commandInput.getAttribute("data-repl-command") || replRuntime, commandInput.value);
|
|
11893
12071
|
return;
|
|
11894
12072
|
}
|
|
11895
12073
|
const sessionSelect = target.closest("[data-repl-session]");
|
|
@@ -11914,6 +12092,12 @@
|
|
|
11914
12092
|
critiqueViewEl.addEventListener("click", handleStudioQuartoPreviewClick);
|
|
11915
12093
|
critiqueViewEl.addEventListener("click", (event) => { void handleSideQuestionClick(event); });
|
|
11916
12094
|
critiqueViewEl.addEventListener("focusin", handleReplPaneFocusIn);
|
|
12095
|
+
critiqueViewEl.addEventListener("focusout", handleReplPaneFocusOut);
|
|
12096
|
+
critiqueViewEl.addEventListener("compositionstart", handleReplPaneComposition);
|
|
12097
|
+
critiqueViewEl.addEventListener("compositionend", handleReplPaneComposition);
|
|
12098
|
+
document.addEventListener("pointerdown", handleReplQuickPointer, true);
|
|
12099
|
+
document.addEventListener("pointerup", handleReplQuickPointer, true);
|
|
12100
|
+
document.addEventListener("pointercancel", handleReplQuickPointer, true);
|
|
11917
12101
|
document.addEventListener("focusin", cancelReplQuickRefocusOutside, true);
|
|
11918
12102
|
document.addEventListener("pointerdown", cancelReplQuickRefocusOutside, true);
|
|
11919
12103
|
critiqueViewEl.addEventListener("input", handleSideQuestionInput);
|
|
@@ -13952,7 +14136,7 @@
|
|
|
13952
14136
|
return "<section class='repl-quick-composer' aria-labelledby='replQuickComposerTitle'>"
|
|
13953
14137
|
+ "<div class='repl-quick-header'><div><strong id='replQuickComposerTitle'>Quick send</strong><button type='button' class='repl-quick-target' data-repl-action='jump-controls' title='Target: " + escapeHtml(targetDetails) + ". Jump to REPL session controls.' aria-label='Target: " + escapeHtml(targetDetails) + ". Jump to REPL session controls.'><span class='repl-quick-target-label'>" + escapeHtml(visibleTargetLabel) + "</span><span class='repl-quick-target-arrow' aria-hidden='true'>↑</span></button></div>"
|
|
13954
14138
|
+ "<span class='repl-quick-hint'>Enter adds a line · Cmd/Ctrl+Enter sends</span></div>"
|
|
13955
|
-
+ "<textarea data-repl-quick-draft rows='3' maxlength='" + REPL_QUICK_DRAFT_MAX_CHARS + "' aria-label='Quick code for " + escapeHtml(targetDetails) + "' aria-keyshortcuts='Meta+Enter Control+Enter' placeholder='" + (activeSession ? (key ? "Type a short command or snippet…" : "Exact tmux identity unavailable; refresh the session list") : "Start or select a REPL session first") + "'" + (inputDisabled ? " disabled" : "") + ">" + escapeHtml(draft) + "</textarea>"
|
|
14139
|
+
+ "<textarea data-repl-quick-draft data-repl-quick-lifetime='" + escapeHtml(encodeURIComponent(key)) + "'" + (replQuickBoxHeight ? " style='height: " + replQuickBoxHeight + "'" : "") + " rows='3' maxlength='" + REPL_QUICK_DRAFT_MAX_CHARS + "' aria-label='Quick code for " + escapeHtml(targetDetails) + "' aria-keyshortcuts='Meta+Enter Control+Enter' placeholder='" + (activeSession ? (key ? "Type a short command or snippet…" : "Exact tmux identity unavailable; refresh the session list") : "Start or select a REPL session first") + "'" + (inputDisabled ? " disabled" : "") + ">" + escapeHtml(draft) + "</textarea>"
|
|
13956
14140
|
+ "<div class='repl-quick-actions'><span></span><div>"
|
|
13957
14141
|
+ "<button type='button' data-repl-action='quick-clear'" + (pending || !draft ? " disabled" : "") + ">Clear</button>"
|
|
13958
14142
|
+ "<button class='repl-quick-send-btn' type='button' data-repl-action='quick-send'" + (sendDisabled ? " disabled" : "") + ">" + (pending ? "Sending…" : "Send") + "</button>"
|
|
@@ -13985,11 +14169,11 @@
|
|
|
13985
14169
|
: "<div class='repl-empty'>" + escapeHtml(emptyMessage) + "</div>";
|
|
13986
14170
|
const canSendToActiveSession = Boolean(activeSession) && !replBusy && replTmuxAvailable !== false;
|
|
13987
14171
|
const canStopActiveSession = Boolean(activeSession && activeSession.source === "studio" && !replBusy && replTmuxAvailable !== false);
|
|
13988
|
-
return "<div class='repl-panel'>"
|
|
14172
|
+
return "<div class='repl-panel' data-repl-drawn-target='" + escapeHtml(encodeURIComponent(getReplPaneTargetKey())) + "'>"
|
|
13989
14173
|
+ "<div class='repl-toolbar'>"
|
|
13990
14174
|
+ "<div class='repl-controls'>"
|
|
13991
14175
|
+ "<label class='repl-control-label'>Runtime <select class='studio-flat-select' data-repl-runtime aria-label='REPL runtime'>" + runtimeOptions + "</select></label>"
|
|
13992
|
-
+ "<label class='repl-control-label repl-command-label'>Start cmd <input data-repl-command type='text' value='" + escapeHtml(replCommand) + "' placeholder='default' aria-label='REPL start command' title='Command used by Start for this runtime. Leave blank for the default; use this for envs, e.g. .venv/bin/python, uv run python, or conda run --no-capture-output -n env python.'></label>"
|
|
14176
|
+
+ "<label class='repl-control-label repl-command-label'>Start cmd <input data-repl-command='" + escapeHtml(normalizeReplRuntime(replRuntime)) + "' type='text' value='" + escapeHtml(replCommand) + "' placeholder='default' aria-label='REPL start command' title='Command used by Start for this runtime. Leave blank for the default; use this for envs, e.g. .venv/bin/python, uv run python, or conda run --no-capture-output -n env python.'></label>"
|
|
13993
14177
|
+ "<button class='repl-start-btn' type='button' data-repl-action='start'" + (replBusy || replTmuxAvailable === false ? " disabled" : "") + " title='Start or switch to a " + escapeHtml(runtimeLabel) + " session using the selected runtime and start command.'>Start</button>"
|
|
13994
14178
|
+ "<label class='repl-control-label repl-session-label'>Session <select class='studio-flat-select' data-repl-session aria-label='REPL session'" + (visibleSessions.length ? "" : " disabled") + ">" + sessionOptions + "</select></label>"
|
|
13995
14179
|
+ "<details class='repl-more-controls'>"
|
package/client/studio.css
CHANGED
|
@@ -6819,6 +6819,12 @@
|
|
|
6819
6819
|
background: var(--studio-header-action-hover-bg, var(--panel-2));
|
|
6820
6820
|
}
|
|
6821
6821
|
|
|
6822
|
+
/* Plain text needs no button width. The old 10rem left a gap before it and, on a
|
|
6823
|
+
13-inch screen, pushed the tools onto a second row while running (Oliver, 19 Oct). */
|
|
6824
|
+
body.studio-ui-refresh #queueSteerBtn {
|
|
6825
|
+
min-width: 0;
|
|
6826
|
+
}
|
|
6827
|
+
|
|
6822
6828
|
body.studio-ui-refresh .studio-refresh-review-btn.request-stop-active {
|
|
6823
6829
|
background: color-mix(in srgb, var(--error) 82%, var(--panel));
|
|
6824
6830
|
border-color: color-mix(in srgb, var(--error) 88%, var(--panel));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-studio",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"description": "Two-pane browser workspace for pi with prompt/response editing, annotations, critiques, active quiz, prompt/response history, live previews, and tmux-backed REPL/literate REPL workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,6 +12,32 @@ function isStudioNestedZedTerminal(env) {
|
|
|
12
12
|
return zedTerm === "1" || zedTerm === "true";
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Muxy before 2.2 marked each pane with its ID and the app's socket.
|
|
17
|
+
*
|
|
18
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} env
|
|
19
|
+
*/
|
|
20
|
+
function isStudioLegacyMuxyPane(env) {
|
|
21
|
+
const paneId = String(env.MUXY_PANE_ID ?? "").trim();
|
|
22
|
+
const socketPath = String(env.MUXY_SOCKET_PATH ?? "").trim();
|
|
23
|
+
return Boolean(paneId && socketPath);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Muxy 2.2 marks each terminal with its server's and session's IDs, and sets
|
|
28
|
+
* TERM_PROGRAM=muxy. Another terminal app opened from a Muxy pane inherits the IDs
|
|
29
|
+
* but sets its own TERM_PROGRAM (tmux sets "tmux"), so the IDs alone aren't enough,
|
|
30
|
+
* and TERM_PROGRAM alone isn't either.
|
|
31
|
+
*
|
|
32
|
+
* @param {NodeJS.ProcessEnv | Record<string, string | undefined>} env
|
|
33
|
+
*/
|
|
34
|
+
function isStudioMuxyTerminal(env) {
|
|
35
|
+
const serverId = String(env.MUXY_SERVER_ID ?? "").trim();
|
|
36
|
+
const sessionId = String(env.MUXY_SESSION_ID ?? "").trim();
|
|
37
|
+
const termProgram = String(env.TERM_PROGRAM ?? "").trim().toLowerCase();
|
|
38
|
+
return Boolean(serverId && sessionId && termProgram === "muxy");
|
|
39
|
+
}
|
|
40
|
+
|
|
15
41
|
/**
|
|
16
42
|
* Detect whether the current process is running inside Muxy itself rather than
|
|
17
43
|
* a nested application terminal that inherited Muxy's environment.
|
|
@@ -20,9 +46,8 @@ function isStudioNestedZedTerminal(env) {
|
|
|
20
46
|
* @returns {boolean}
|
|
21
47
|
*/
|
|
22
48
|
export function isStudioMuxySession(env = process.env) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return Boolean(paneId && socketPath && !isStudioNestedZedTerminal(env));
|
|
49
|
+
if (isStudioNestedZedTerminal(env)) return false;
|
|
50
|
+
return isStudioLegacyMuxyPane(env) || isStudioMuxyTerminal(env);
|
|
26
51
|
}
|
|
27
52
|
|
|
28
53
|
/**
|