pi-studio 0.9.11 → 0.9.12
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 +13 -0
- package/README.md +1 -1
- package/client/studio-client.js +554 -70
- package/client/studio.css +191 -32
- package/index.ts +322 -32
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
const editorSelectionJumpBtn = document.getElementById("editorSelectionJumpBtn");
|
|
62
62
|
const leftPaneEl = document.getElementById("leftPane");
|
|
63
63
|
const rightPaneEl = document.getElementById("rightPane");
|
|
64
|
+
const paneResizeHandleEl = document.getElementById("paneResizeHandle");
|
|
64
65
|
const sourceBadgeEl = document.getElementById("sourceBadge");
|
|
65
66
|
const syncBadgeEl = document.getElementById("syncBadge");
|
|
66
67
|
let critiqueViewEl = document.getElementById("critiqueView");
|
|
@@ -246,6 +247,7 @@
|
|
|
246
247
|
const PDF_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
247
248
|
const HTML_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
248
249
|
const HTML_ARTIFACT_MATH_RENDER_FETCH_TIMEOUT_MS = 30_000;
|
|
250
|
+
const HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS = 30_000;
|
|
249
251
|
const EDITOR_TAB_TEXT = " ";
|
|
250
252
|
const QUIZ_DEFAULT_COUNT = 5;
|
|
251
253
|
const QUIZ_SCOPES = ["editor", "selection", "file", "folder", "repo"];
|
|
@@ -288,6 +290,15 @@
|
|
|
288
290
|
return "python";
|
|
289
291
|
}
|
|
290
292
|
})();
|
|
293
|
+
let replCommandOverrides = (() => {
|
|
294
|
+
try {
|
|
295
|
+
const raw = window.localStorage && window.localStorage.getItem("piStudio.replCommandOverrides");
|
|
296
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
297
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
298
|
+
} catch {
|
|
299
|
+
return {};
|
|
300
|
+
}
|
|
301
|
+
})();
|
|
291
302
|
let replTranscript = "";
|
|
292
303
|
let replError = "";
|
|
293
304
|
let replMessage = "";
|
|
@@ -952,11 +963,26 @@
|
|
|
952
963
|
setStatus("Loaded visible working into editor.", "success");
|
|
953
964
|
}
|
|
954
965
|
|
|
955
|
-
function
|
|
966
|
+
function getKnownReplRuntime(value) {
|
|
956
967
|
const runtime = String(value || "").trim().toLowerCase();
|
|
957
968
|
return runtime === "shell" || runtime === "python" || runtime === "ipython" || runtime === "julia" || runtime === "r" || runtime === "ghci" || runtime === "clojure"
|
|
958
969
|
? runtime
|
|
959
|
-
: "
|
|
970
|
+
: "";
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
function normalizeReplRuntime(value) {
|
|
974
|
+
return getKnownReplRuntime(value) || "python";
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function getReplRuntimeLabel(value) {
|
|
978
|
+
const runtime = normalizeReplRuntime(value);
|
|
979
|
+
if (runtime === "shell") return "Shell";
|
|
980
|
+
if (runtime === "python") return "Python";
|
|
981
|
+
if (runtime === "ipython") return "IPython";
|
|
982
|
+
if (runtime === "julia") return "Julia";
|
|
983
|
+
if (runtime === "r") return "R";
|
|
984
|
+
if (runtime === "ghci") return "GHCi";
|
|
985
|
+
return "Clojure";
|
|
960
986
|
}
|
|
961
987
|
|
|
962
988
|
function normalizeReplSession(session) {
|
|
@@ -981,6 +1007,47 @@
|
|
|
981
1007
|
}
|
|
982
1008
|
}
|
|
983
1009
|
|
|
1010
|
+
function normalizeReplCommandOverride(value) {
|
|
1011
|
+
return String(value || "").replace(/\r?\n/g, " ").replace(/\s+/g, " ").trim().slice(0, 240);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
function getReplCommandOverride(runtime) {
|
|
1015
|
+
const normalizedRuntime = normalizeReplRuntime(runtime || replRuntime);
|
|
1016
|
+
const value = replCommandOverrides && typeof replCommandOverrides === "object"
|
|
1017
|
+
? replCommandOverrides[normalizedRuntime]
|
|
1018
|
+
: "";
|
|
1019
|
+
return normalizeReplCommandOverride(value);
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function persistReplCommandOverrides() {
|
|
1023
|
+
try {
|
|
1024
|
+
if (window.localStorage) window.localStorage.setItem("piStudio.replCommandOverrides", JSON.stringify(replCommandOverrides || {}));
|
|
1025
|
+
} catch {
|
|
1026
|
+
// Ignore storage failures.
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function setReplCommandOverride(runtime, command) {
|
|
1031
|
+
const normalizedRuntime = normalizeReplRuntime(runtime || replRuntime);
|
|
1032
|
+
const normalizedCommand = normalizeReplCommandOverride(command);
|
|
1033
|
+
replCommandOverrides = replCommandOverrides && typeof replCommandOverrides === "object" ? replCommandOverrides : {};
|
|
1034
|
+
if (normalizedCommand) {
|
|
1035
|
+
replCommandOverrides[normalizedRuntime] = normalizedCommand;
|
|
1036
|
+
} else {
|
|
1037
|
+
delete replCommandOverrides[normalizedRuntime];
|
|
1038
|
+
}
|
|
1039
|
+
persistReplCommandOverrides();
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function getCurrentReplStartCommandFromDom() {
|
|
1043
|
+
if (!critiqueViewEl || typeof critiqueViewEl.querySelector !== "function") return getReplCommandOverride(replRuntime);
|
|
1044
|
+
const inputEl = critiqueViewEl.querySelector("[data-repl-command]");
|
|
1045
|
+
if (inputEl && "value" in inputEl) {
|
|
1046
|
+
return normalizeReplCommandOverride(inputEl.value);
|
|
1047
|
+
}
|
|
1048
|
+
return getReplCommandOverride(replRuntime);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
984
1051
|
function normalizeReplSendMode(value) {
|
|
985
1052
|
return String(value || "").trim().toLowerCase() === "literate" ? "literate" : "raw";
|
|
986
1053
|
}
|
|
@@ -1056,7 +1123,10 @@
|
|
|
1056
1123
|
? sessions.map(normalizeReplSession).filter(Boolean)
|
|
1057
1124
|
: [];
|
|
1058
1125
|
if (replActiveSessionName && !replSessions.some((session) => session.sessionName === replActiveSessionName)) {
|
|
1059
|
-
|
|
1126
|
+
setActiveReplSession("");
|
|
1127
|
+
}
|
|
1128
|
+
if (!getActiveReplSessionForCurrentRuntime()) {
|
|
1129
|
+
selectReplSessionForRuntime(replRuntime, replActiveSessionName);
|
|
1060
1130
|
}
|
|
1061
1131
|
return previous !== serializeReplSessionsForCompare(replSessions) || previousActive !== replActiveSessionName;
|
|
1062
1132
|
}
|
|
@@ -1065,9 +1135,43 @@
|
|
|
1065
1135
|
return replSessions.find((session) => session.sessionName === replActiveSessionName) || null;
|
|
1066
1136
|
}
|
|
1067
1137
|
|
|
1138
|
+
function isReplSessionRuntimeCompatible(session, runtime) {
|
|
1139
|
+
if (!session) return false;
|
|
1140
|
+
const sessionRuntime = getKnownReplRuntime(session.runtime);
|
|
1141
|
+
return Boolean(sessionRuntime) && sessionRuntime === normalizeReplRuntime(runtime);
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
function getReplSessionsForRuntime(runtime) {
|
|
1145
|
+
const normalizedRuntime = normalizeReplRuntime(runtime);
|
|
1146
|
+
return replSessions.filter((session) => isReplSessionRuntimeCompatible(session, normalizedRuntime));
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
function selectReplSessionForRuntime(runtime, preferredSessionName) {
|
|
1150
|
+
const sessions = getReplSessionsForRuntime(runtime);
|
|
1151
|
+
const preferred = String(preferredSessionName || "").trim();
|
|
1152
|
+
const selected = sessions.find((session) => session.sessionName === preferred) || sessions[0] || null;
|
|
1153
|
+
setActiveReplSession(selected ? selected.sessionName : "");
|
|
1154
|
+
return selected;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function setActiveReplSessionForCurrentRuntime(sessionName) {
|
|
1158
|
+
const name = String(sessionName || "").trim();
|
|
1159
|
+
const candidate = name ? replSessions.find((session) => session.sessionName === name) : null;
|
|
1160
|
+
if (candidate && isReplSessionRuntimeCompatible(candidate, replRuntime)) {
|
|
1161
|
+
setActiveReplSession(candidate.sessionName);
|
|
1162
|
+
return candidate;
|
|
1163
|
+
}
|
|
1164
|
+
return selectReplSessionForRuntime(replRuntime, replActiveSessionName);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
function getActiveReplSessionForCurrentRuntime() {
|
|
1168
|
+
const session = getActiveReplSession();
|
|
1169
|
+
return isReplSessionRuntimeCompatible(session, replRuntime) ? session : null;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1068
1172
|
function buildActiveReplPromptContext() {
|
|
1069
1173
|
if (rightView !== "repl") return "";
|
|
1070
|
-
const session =
|
|
1174
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
1071
1175
|
if (!session) return "";
|
|
1072
1176
|
const runtime = session.runtime && session.runtime !== "unknown" ? session.runtime : "unknown";
|
|
1073
1177
|
return [
|
|
@@ -1162,7 +1266,7 @@
|
|
|
1162
1266
|
}
|
|
1163
1267
|
|
|
1164
1268
|
function getActiveReplRuntime() {
|
|
1165
|
-
const session =
|
|
1269
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
1166
1270
|
if (session && session.runtime && session.runtime !== "unknown") return normalizeReplRuntime(session.runtime);
|
|
1167
1271
|
return normalizeReplRuntime(replRuntime);
|
|
1168
1272
|
}
|
|
@@ -1502,7 +1606,7 @@
|
|
|
1502
1606
|
|
|
1503
1607
|
function stripStudioReplSubmissionEcho(delta) {
|
|
1504
1608
|
let value = String(delta || "").replace(/^\s+/, "");
|
|
1505
|
-
// The raw mirror below remains raw;
|
|
1609
|
+
// The raw mirror below remains raw; Studio record cards hide only the
|
|
1506
1610
|
// temp-file wrapper used to submit multiline snippets safely. The
|
|
1507
1611
|
// pi-studio-re fragment catches IPython's wrapped pi-studio-repl paths.
|
|
1508
1612
|
const submissionEchoPatterns = [
|
|
@@ -1566,11 +1670,11 @@
|
|
|
1566
1670
|
function buildReplJournalMarkdown(entries) {
|
|
1567
1671
|
const visibleEntries = Array.isArray(entries) ? entries : getVisibleReplJournalEntries();
|
|
1568
1672
|
const sessionName = getActiveReplJournalSessionName();
|
|
1569
|
-
const lines = ["# REPL
|
|
1673
|
+
const lines = ["# Studio REPL Record", "", "Generated: " + new Date().toLocaleString()];
|
|
1570
1674
|
if (sessionName) lines.push("Session: `" + sessionName + "`");
|
|
1571
1675
|
lines.push("");
|
|
1572
1676
|
if (!visibleEntries.length) {
|
|
1573
|
-
lines.push(sessionName ? ("_No REPL
|
|
1677
|
+
lines.push(sessionName ? ("_No Studio REPL record entries for `" + sessionName + "` yet._") : "_No Studio REPL record entries yet._");
|
|
1574
1678
|
return lines.join("\n");
|
|
1575
1679
|
}
|
|
1576
1680
|
visibleEntries.forEach((entry, index) => {
|
|
@@ -1602,11 +1706,11 @@
|
|
|
1602
1706
|
async function copyReplJournalToClipboard() {
|
|
1603
1707
|
const entries = getVisibleReplJournalEntries();
|
|
1604
1708
|
if (!entries.length) {
|
|
1605
|
-
setStatus("No REPL
|
|
1709
|
+
setStatus("No Studio REPL record entries to copy for this session yet.", "warning");
|
|
1606
1710
|
return;
|
|
1607
1711
|
}
|
|
1608
1712
|
if (await writeTextToClipboard(buildReplJournalMarkdown(entries))) {
|
|
1609
|
-
setStatus("Copied REPL
|
|
1713
|
+
setStatus("Copied Studio REPL record as Markdown.", "success");
|
|
1610
1714
|
} else {
|
|
1611
1715
|
setStatus("Clipboard write failed.", "warning");
|
|
1612
1716
|
}
|
|
@@ -1615,7 +1719,7 @@
|
|
|
1615
1719
|
function exportReplJournalMarkdown() {
|
|
1616
1720
|
const entries = getVisibleReplJournalEntries();
|
|
1617
1721
|
if (!entries.length) {
|
|
1618
|
-
setStatus("No REPL
|
|
1722
|
+
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
1619
1723
|
return;
|
|
1620
1724
|
}
|
|
1621
1725
|
const blob = new Blob([buildReplJournalMarkdown(entries)], { type: "text/markdown;charset=utf-8" });
|
|
@@ -1629,7 +1733,7 @@
|
|
|
1629
1733
|
link.click();
|
|
1630
1734
|
link.remove();
|
|
1631
1735
|
window.setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
|
|
1632
|
-
setStatus("Exported REPL
|
|
1736
|
+
setStatus("Exported Studio REPL record Markdown.", "success");
|
|
1633
1737
|
}
|
|
1634
1738
|
|
|
1635
1739
|
function clearReplJournal() {
|
|
@@ -1641,27 +1745,27 @@
|
|
|
1641
1745
|
}
|
|
1642
1746
|
activeReplJournalEntryId = "";
|
|
1643
1747
|
persistReplJournalEntries();
|
|
1644
|
-
setStatus(sessionName ? "Cleared REPL
|
|
1748
|
+
setStatus(sessionName ? "Cleared Studio REPL record for this session." : "Cleared Studio REPL record.", "success");
|
|
1645
1749
|
renderReplViewIfActive({ force: true });
|
|
1646
1750
|
}
|
|
1647
1751
|
|
|
1648
1752
|
function loadReplJournalIntoEditor() {
|
|
1649
1753
|
const entries = getVisibleReplJournalEntries();
|
|
1650
1754
|
if (!entries.length) {
|
|
1651
|
-
setStatus("No REPL
|
|
1755
|
+
setStatus("No Studio REPL record entries to load for this session yet.", "warning");
|
|
1652
1756
|
return;
|
|
1653
1757
|
}
|
|
1654
1758
|
const markdown = buildReplJournalMarkdown(entries);
|
|
1655
1759
|
setEditorText(markdown, { preserveScroll: false, preserveSelection: false });
|
|
1656
|
-
setSourceState({ source: "blank", label: "REPL
|
|
1760
|
+
setSourceState({ source: "blank", label: "Studio REPL Record", path: null });
|
|
1657
1761
|
setEditorLanguage("markdown");
|
|
1658
|
-
setStatus("Loaded REPL
|
|
1762
|
+
setStatus("Loaded Studio REPL record into editor.", "success");
|
|
1659
1763
|
}
|
|
1660
1764
|
|
|
1661
1765
|
function addSelectedReplJournalNote() {
|
|
1662
1766
|
const note = getSelectedOrCurrentParagraphForReplNote();
|
|
1663
1767
|
if (!note.trim()) {
|
|
1664
|
-
setStatus("Select prose or place the cursor in a paragraph to add a REPL
|
|
1768
|
+
setStatus("Select prose or place the cursor in a paragraph to add a Studio REPL record note.", "warning");
|
|
1665
1769
|
return;
|
|
1666
1770
|
}
|
|
1667
1771
|
addReplJournalEntry({
|
|
@@ -1672,14 +1776,14 @@
|
|
|
1672
1776
|
sessionName: replActiveSessionName,
|
|
1673
1777
|
runtime: getActiveReplRuntime(),
|
|
1674
1778
|
});
|
|
1675
|
-
setStatus("Added note to REPL
|
|
1779
|
+
setStatus("Added note to Studio REPL record.", "success");
|
|
1676
1780
|
renderReplViewIfActive({ force: true });
|
|
1677
1781
|
}
|
|
1678
1782
|
|
|
1679
1783
|
function sendReplPayload(payload) {
|
|
1680
|
-
const session =
|
|
1784
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
1681
1785
|
if (!session) {
|
|
1682
|
-
setStatus("Start or select a REPL session first.", "warning");
|
|
1786
|
+
setStatus("Start or select a " + getReplRuntimeLabel(replRuntime) + " REPL session first.", "warning");
|
|
1683
1787
|
return;
|
|
1684
1788
|
}
|
|
1685
1789
|
if (!payload || payload.error) {
|
|
@@ -1697,7 +1801,7 @@
|
|
|
1697
1801
|
runtime: getActiveReplRuntime(),
|
|
1698
1802
|
skippedChunks: payload.skippedChunks,
|
|
1699
1803
|
});
|
|
1700
|
-
setStatus("Added prose to REPL
|
|
1804
|
+
setStatus("Added prose to Studio REPL record.", "success");
|
|
1701
1805
|
renderReplViewIfActive({ force: true });
|
|
1702
1806
|
} else {
|
|
1703
1807
|
setStatus("No code or prose found to send.", "warning");
|
|
@@ -1771,6 +1875,11 @@
|
|
|
1771
1875
|
let fileBackedBaselineText = null;
|
|
1772
1876
|
let activePane = "left";
|
|
1773
1877
|
let paneFocusTarget = "off";
|
|
1878
|
+
let paneSplitPercent = 50;
|
|
1879
|
+
const PANE_SPLIT_STORAGE_KEY = "piStudio.paneSplitPercent";
|
|
1880
|
+
const PANE_SPLIT_MIN_PERCENT = 20;
|
|
1881
|
+
const PANE_SPLIT_MAX_PERCENT = 80;
|
|
1882
|
+
const PANE_SPLIT_SNAP_TO_CENTER_PERCENT = 1;
|
|
1774
1883
|
const EDITOR_HIGHLIGHT_MAX_CHARS = 100_000;
|
|
1775
1884
|
const EDITOR_HIGHLIGHT_STORAGE_KEY = "piStudio.editorHighlightEnabled";
|
|
1776
1885
|
const EDITOR_LANGUAGE_STORAGE_KEY = "piStudio.editorLanguage";
|
|
@@ -2431,7 +2540,7 @@
|
|
|
2431
2540
|
|
|
2432
2541
|
function getTerminalBusyStatus() {
|
|
2433
2542
|
if (terminalActivityPhase === "tool") {
|
|
2434
|
-
if (terminalActivityLabel) {
|
|
2543
|
+
if (terminalActivityLabel && !isGenericToolLabel(terminalActivityLabel)) {
|
|
2435
2544
|
return "Terminal: " + withEllipsis(terminalActivityLabel);
|
|
2436
2545
|
}
|
|
2437
2546
|
return terminalActivityToolName
|
|
@@ -2475,7 +2584,7 @@
|
|
|
2475
2584
|
const action = getStudioActionLabel(kind);
|
|
2476
2585
|
const queueSuffix = studioRunChainActive ? formatQueuedSteeringSuffix() : "";
|
|
2477
2586
|
if (terminalActivityPhase === "tool") {
|
|
2478
|
-
if (terminalActivityLabel) {
|
|
2587
|
+
if (terminalActivityLabel && !isGenericToolLabel(terminalActivityLabel)) {
|
|
2479
2588
|
return "Studio: " + withEllipsis(terminalActivityLabel) + queueSuffix;
|
|
2480
2589
|
}
|
|
2481
2590
|
return terminalActivityToolName
|
|
@@ -3002,6 +3111,142 @@
|
|
|
3002
3111
|
setStatus(descriptor.fileBacked ? "Detached editor from file origin into a new draft." : "Reset editor origin to a new draft.", "success");
|
|
3003
3112
|
}
|
|
3004
3113
|
|
|
3114
|
+
function clampPaneSplitPercent(value) {
|
|
3115
|
+
const numeric = Number(value);
|
|
3116
|
+
if (!Number.isFinite(numeric)) return 50;
|
|
3117
|
+
const clamped = Math.max(PANE_SPLIT_MIN_PERCENT, Math.min(PANE_SPLIT_MAX_PERCENT, Math.round(numeric * 10) / 10));
|
|
3118
|
+
return Math.abs(clamped - 50) <= PANE_SPLIT_SNAP_TO_CENTER_PERCENT ? 50 : clamped;
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
function applyPaneSplitPercent(percent, options) {
|
|
3122
|
+
paneSplitPercent = clampPaneSplitPercent(percent);
|
|
3123
|
+
const rightPercent = Math.round((100 - paneSplitPercent) * 10) / 10;
|
|
3124
|
+
document.documentElement.style.setProperty("--studio-left-pane-fr", paneSplitPercent + "fr");
|
|
3125
|
+
document.documentElement.style.setProperty("--studio-right-pane-fr", rightPercent + "fr");
|
|
3126
|
+
if (paneResizeHandleEl) {
|
|
3127
|
+
paneResizeHandleEl.setAttribute("aria-valuemin", String(PANE_SPLIT_MIN_PERCENT));
|
|
3128
|
+
paneResizeHandleEl.setAttribute("aria-valuemax", String(PANE_SPLIT_MAX_PERCENT));
|
|
3129
|
+
paneResizeHandleEl.setAttribute("aria-valuenow", String(Math.round(paneSplitPercent)));
|
|
3130
|
+
paneResizeHandleEl.setAttribute("aria-valuetext", "Editor " + Math.round(paneSplitPercent) + " percent, response " + Math.round(rightPercent) + " percent");
|
|
3131
|
+
}
|
|
3132
|
+
if (!options || options.persist !== false) {
|
|
3133
|
+
try {
|
|
3134
|
+
if (window.localStorage) window.localStorage.setItem(PANE_SPLIT_STORAGE_KEY, String(paneSplitPercent));
|
|
3135
|
+
} catch {
|
|
3136
|
+
// Ignore localStorage failures.
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
function resetPaneSplitPercent() {
|
|
3142
|
+
applyPaneSplitPercent(50);
|
|
3143
|
+
setStatus("Pane split reset to 50/50.");
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
function loadPaneSplitPercent() {
|
|
3147
|
+
if (isEditorOnlyMode) return;
|
|
3148
|
+
let stored = "";
|
|
3149
|
+
try {
|
|
3150
|
+
stored = window.localStorage ? String(window.localStorage.getItem(PANE_SPLIT_STORAGE_KEY) || "") : "";
|
|
3151
|
+
} catch {
|
|
3152
|
+
stored = "";
|
|
3153
|
+
}
|
|
3154
|
+
applyPaneSplitPercent(stored ? Number(stored) : 50, { persist: false });
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
function getPaneSplitPercentFromPointerEvent(event) {
|
|
3158
|
+
const mainEl = paneResizeHandleEl && typeof paneResizeHandleEl.closest === "function"
|
|
3159
|
+
? paneResizeHandleEl.closest("main")
|
|
3160
|
+
: null;
|
|
3161
|
+
if (!mainEl || typeof mainEl.getBoundingClientRect !== "function") return paneSplitPercent;
|
|
3162
|
+
const rect = mainEl.getBoundingClientRect();
|
|
3163
|
+
if (!rect.width) return paneSplitPercent;
|
|
3164
|
+
const x = typeof event.clientX === "number" ? event.clientX : (rect.left + rect.width / 2);
|
|
3165
|
+
return ((x - rect.left) / rect.width) * 100;
|
|
3166
|
+
}
|
|
3167
|
+
|
|
3168
|
+
function setupPaneResizeHandle() {
|
|
3169
|
+
if (!paneResizeHandleEl || isEditorOnlyMode) return;
|
|
3170
|
+
loadPaneSplitPercent();
|
|
3171
|
+
let dragging = false;
|
|
3172
|
+
let movedDuringDrag = false;
|
|
3173
|
+
let pointerStartX = 0;
|
|
3174
|
+
let activePaneResizePointerId = null;
|
|
3175
|
+
const finishDrag = () => {
|
|
3176
|
+
if (!dragging) return;
|
|
3177
|
+
dragging = false;
|
|
3178
|
+
if (document.body && document.body.classList) document.body.classList.remove("pane-resizing");
|
|
3179
|
+
try {
|
|
3180
|
+
if (typeof paneResizeHandleEl.releasePointerCapture === "function" && activePaneResizePointerId != null) {
|
|
3181
|
+
paneResizeHandleEl.releasePointerCapture(activePaneResizePointerId);
|
|
3182
|
+
}
|
|
3183
|
+
} catch {
|
|
3184
|
+
// Ignore pointer-capture cleanup failures.
|
|
3185
|
+
}
|
|
3186
|
+
activePaneResizePointerId = null;
|
|
3187
|
+
if (movedDuringDrag) {
|
|
3188
|
+
setStatus("Pane split: editor " + Math.round(paneSplitPercent) + "%, response " + Math.round(100 - paneSplitPercent) + "%.");
|
|
3189
|
+
}
|
|
3190
|
+
movedDuringDrag = false;
|
|
3191
|
+
};
|
|
3192
|
+
paneResizeHandleEl.addEventListener("pointerdown", (event) => {
|
|
3193
|
+
if (event.button != null && event.button !== 0) return;
|
|
3194
|
+
event.preventDefault();
|
|
3195
|
+
event.stopPropagation();
|
|
3196
|
+
dragging = true;
|
|
3197
|
+
movedDuringDrag = false;
|
|
3198
|
+
pointerStartX = typeof event.clientX === "number" ? event.clientX : 0;
|
|
3199
|
+
activePaneResizePointerId = event.pointerId;
|
|
3200
|
+
if (document.body && document.body.classList) document.body.classList.add("pane-resizing");
|
|
3201
|
+
try {
|
|
3202
|
+
if (typeof paneResizeHandleEl.focus === "function") paneResizeHandleEl.focus({ preventScroll: true });
|
|
3203
|
+
} catch {
|
|
3204
|
+
try { paneResizeHandleEl.focus(); } catch {}
|
|
3205
|
+
}
|
|
3206
|
+
try {
|
|
3207
|
+
if (typeof paneResizeHandleEl.setPointerCapture === "function") paneResizeHandleEl.setPointerCapture(event.pointerId);
|
|
3208
|
+
} catch {
|
|
3209
|
+
// Ignore pointer-capture failures.
|
|
3210
|
+
}
|
|
3211
|
+
});
|
|
3212
|
+
paneResizeHandleEl.addEventListener("pointermove", (event) => {
|
|
3213
|
+
if (!dragging) return;
|
|
3214
|
+
const movement = typeof event.clientX === "number" ? Math.abs(event.clientX - pointerStartX) : 0;
|
|
3215
|
+
if (!movedDuringDrag && movement < 3) return;
|
|
3216
|
+
movedDuringDrag = true;
|
|
3217
|
+
event.preventDefault();
|
|
3218
|
+
applyPaneSplitPercent(getPaneSplitPercentFromPointerEvent(event));
|
|
3219
|
+
});
|
|
3220
|
+
paneResizeHandleEl.addEventListener("pointerup", finishDrag);
|
|
3221
|
+
paneResizeHandleEl.addEventListener("pointercancel", finishDrag);
|
|
3222
|
+
paneResizeHandleEl.addEventListener("dblclick", (event) => {
|
|
3223
|
+
event.preventDefault();
|
|
3224
|
+
event.stopPropagation();
|
|
3225
|
+
resetPaneSplitPercent();
|
|
3226
|
+
});
|
|
3227
|
+
paneResizeHandleEl.addEventListener("keydown", (event) => {
|
|
3228
|
+
if (event.key === "Home") {
|
|
3229
|
+
event.preventDefault();
|
|
3230
|
+
applyPaneSplitPercent(PANE_SPLIT_MIN_PERCENT);
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
if (event.key === "End") {
|
|
3234
|
+
event.preventDefault();
|
|
3235
|
+
applyPaneSplitPercent(PANE_SPLIT_MAX_PERCENT);
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3238
|
+
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
3239
|
+
event.preventDefault();
|
|
3240
|
+
resetPaneSplitPercent();
|
|
3241
|
+
return;
|
|
3242
|
+
}
|
|
3243
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
3244
|
+
event.preventDefault();
|
|
3245
|
+
const step = event.shiftKey ? 10 : 5;
|
|
3246
|
+
applyPaneSplitPercent(paneSplitPercent + (event.key === "ArrowLeft" ? -step : step));
|
|
3247
|
+
});
|
|
3248
|
+
}
|
|
3249
|
+
|
|
3005
3250
|
function updatePaneFocusButtons() {
|
|
3006
3251
|
[
|
|
3007
3252
|
[leftFocusBtn, "left"],
|
|
@@ -3240,6 +3485,40 @@
|
|
|
3240
3485
|
return false;
|
|
3241
3486
|
}
|
|
3242
3487
|
|
|
3488
|
+
function triggerResponseHistoryShortcut(action) {
|
|
3489
|
+
if (isEditorOnlyMode) {
|
|
3490
|
+
setStatus("Response history is unavailable in editor-only Studio.", "warning");
|
|
3491
|
+
return false;
|
|
3492
|
+
}
|
|
3493
|
+
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
3494
|
+
if (total <= 0) {
|
|
3495
|
+
setStatus("No response history available yet.", "warning");
|
|
3496
|
+
return false;
|
|
3497
|
+
}
|
|
3498
|
+
if (action === "previous") {
|
|
3499
|
+
if (responseHistoryIndex <= 0) {
|
|
3500
|
+
setStatus("Already at the first response.", "warning");
|
|
3501
|
+
return false;
|
|
3502
|
+
}
|
|
3503
|
+
return selectHistoryIndex(responseHistoryIndex - 1);
|
|
3504
|
+
}
|
|
3505
|
+
if (action === "next") {
|
|
3506
|
+
if (responseHistoryIndex >= total - 1) {
|
|
3507
|
+
setStatus("Already at the latest response.", "warning");
|
|
3508
|
+
return false;
|
|
3509
|
+
}
|
|
3510
|
+
return selectHistoryIndex(responseHistoryIndex + 1);
|
|
3511
|
+
}
|
|
3512
|
+
if (action === "latest") {
|
|
3513
|
+
if (responseHistoryIndex >= total - 1) {
|
|
3514
|
+
setStatus("Already viewing the latest response.");
|
|
3515
|
+
return false;
|
|
3516
|
+
}
|
|
3517
|
+
return selectHistoryIndex(total - 1);
|
|
3518
|
+
}
|
|
3519
|
+
return false;
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3243
3522
|
function isTextEntryShortcutTarget(target) {
|
|
3244
3523
|
if (!(target instanceof Element)) return false;
|
|
3245
3524
|
const editable = target.closest("input, textarea, select, [contenteditable]");
|
|
@@ -3255,6 +3534,7 @@
|
|
|
3255
3534
|
if (!event || event.defaultPrevented) return;
|
|
3256
3535
|
|
|
3257
3536
|
const key = typeof event.key === "string" ? event.key : "";
|
|
3537
|
+
const code = typeof event.code === "string" ? event.code : "";
|
|
3258
3538
|
const plainEscape = key === "Escape"
|
|
3259
3539
|
&& !event.metaKey
|
|
3260
3540
|
&& !event.ctrlKey
|
|
@@ -3355,6 +3635,24 @@
|
|
|
3355
3635
|
return;
|
|
3356
3636
|
}
|
|
3357
3637
|
|
|
3638
|
+
if (!isTextEntryShortcutTarget(event.target) && !event.metaKey && !event.ctrlKey && event.altKey && !event.shiftKey) {
|
|
3639
|
+
if (key === "ArrowLeft") {
|
|
3640
|
+
event.preventDefault();
|
|
3641
|
+
triggerResponseHistoryShortcut("previous");
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
if (key === "ArrowRight") {
|
|
3645
|
+
event.preventDefault();
|
|
3646
|
+
triggerResponseHistoryShortcut("next");
|
|
3647
|
+
return;
|
|
3648
|
+
}
|
|
3649
|
+
if (key.toLowerCase() === "l" || code === "KeyL") {
|
|
3650
|
+
event.preventDefault();
|
|
3651
|
+
triggerResponseHistoryShortcut("latest");
|
|
3652
|
+
return;
|
|
3653
|
+
}
|
|
3654
|
+
}
|
|
3655
|
+
|
|
3358
3656
|
const isPaneSwitchShortcut = key === "F6" && !event.metaKey && !event.ctrlKey && !event.altKey;
|
|
3359
3657
|
if (isPaneSwitchShortcut) {
|
|
3360
3658
|
event.preventDefault();
|
|
@@ -4205,6 +4503,60 @@
|
|
|
4205
4503
|
+ " htmlMathScanScheduled = true;\n"
|
|
4206
4504
|
+ " requestAnimationFrame(runHtmlMathRenderScan);\n"
|
|
4207
4505
|
+ " }\n"
|
|
4506
|
+
+ " const htmlResourcePlaceholders = new Map();\n"
|
|
4507
|
+
+ " let htmlResourceSerial = 0;\n"
|
|
4508
|
+
+ " let htmlResourceScanScheduled = false;\n"
|
|
4509
|
+
+ " function shouldResolveHtmlPreviewResourceUrl(value) {\n"
|
|
4510
|
+
+ " const raw = String(value || '').trim();\n"
|
|
4511
|
+
+ " if (!raw || raw.charAt(0) === '#') return false;\n"
|
|
4512
|
+
+ " if (/^(?:data|blob|http|https|about|javascript|mailto):/i.test(raw)) return false;\n"
|
|
4513
|
+
+ " if (/^\\/\\//.test(raw)) return false;\n"
|
|
4514
|
+
+ " return /\\.(?:png|jpe?g|gif|webp)(?:[?#].*)?$/i.test(raw);\n"
|
|
4515
|
+
+ " }\n"
|
|
4516
|
+
+ " function scanHtmlPreviewResources() {\n"
|
|
4517
|
+
+ " htmlResourceScanScheduled = false;\n"
|
|
4518
|
+
+ " if (!document.body) return;\n"
|
|
4519
|
+
+ " const items = [];\n"
|
|
4520
|
+
+ " const images = Array.prototype.slice.call(document.querySelectorAll('img[src]'));\n"
|
|
4521
|
+
+ " images.forEach((image) => {\n"
|
|
4522
|
+
+ " if (!image || !image.getAttribute) return;\n"
|
|
4523
|
+
+ " if (image.getAttribute('data-pi-studio-html-resource-resolved') === 'true') return;\n"
|
|
4524
|
+
+ " const raw = String(image.getAttribute('src') || '').trim();\n"
|
|
4525
|
+
+ " if (!shouldResolveHtmlPreviewResourceUrl(raw)) return;\n"
|
|
4526
|
+
+ " let resourceId = image.getAttribute('data-pi-studio-html-resource-id') || '';\n"
|
|
4527
|
+
+ " if (!resourceId) {\n"
|
|
4528
|
+
+ " resourceId = PREVIEW_ID + '_resource_' + (++htmlResourceSerial).toString(36);\n"
|
|
4529
|
+
+ " image.setAttribute('data-pi-studio-html-resource-id', resourceId);\n"
|
|
4530
|
+
+ " }\n"
|
|
4531
|
+
+ " htmlResourcePlaceholders.set(resourceId, image);\n"
|
|
4532
|
+
+ " items.push({ resourceId, url: raw });\n"
|
|
4533
|
+
+ " });\n"
|
|
4534
|
+
+ " if (items.length > 0) {\n"
|
|
4535
|
+
+ " try { parent.postMessage({ type: 'pi-studio-html-artifact-resolve-resources', id: PREVIEW_ID, resources: items.slice(0, 100) }, '*'); } catch {}\n"
|
|
4536
|
+
+ " }\n"
|
|
4537
|
+
+ " }\n"
|
|
4538
|
+
+ " function scheduleHtmlPreviewResourceScan() {\n"
|
|
4539
|
+
+ " if (htmlResourceScanScheduled) return;\n"
|
|
4540
|
+
+ " htmlResourceScanScheduled = true;\n"
|
|
4541
|
+
+ " requestAnimationFrame(scanHtmlPreviewResources);\n"
|
|
4542
|
+
+ " }\n"
|
|
4543
|
+
+ " function applyResolvedHtmlPreviewResources(results) {\n"
|
|
4544
|
+
+ " if (!Array.isArray(results)) return;\n"
|
|
4545
|
+
+ " results.forEach((result) => {\n"
|
|
4546
|
+
+ " if (!result || typeof result !== 'object') return;\n"
|
|
4547
|
+
+ " const resourceId = typeof result.resourceId === 'string' ? result.resourceId : '';\n"
|
|
4548
|
+
+ " const image = resourceId ? htmlResourcePlaceholders.get(resourceId) : null;\n"
|
|
4549
|
+
+ " if (!image || !image.isConnected) return;\n"
|
|
4550
|
+
+ " if (result.ok === true && typeof result.dataUrl === 'string' && result.dataUrl) {\n"
|
|
4551
|
+
+ " image.setAttribute('src', result.dataUrl);\n"
|
|
4552
|
+
+ " image.setAttribute('data-pi-studio-html-resource-resolved', 'true');\n"
|
|
4553
|
+
+ " } else if (typeof result.error === 'string' && result.error) {\n"
|
|
4554
|
+
+ " image.setAttribute('title', result.error);\n"
|
|
4555
|
+
+ " }\n"
|
|
4556
|
+
+ " htmlResourcePlaceholders.delete(resourceId);\n"
|
|
4557
|
+
+ " });\n"
|
|
4558
|
+
+ " scheduleHeight();\n"
|
|
4559
|
+
+ " }\n"
|
|
4208
4560
|
+ " window.addEventListener('message', (event) => {\n"
|
|
4209
4561
|
+ " const data = event && event.data;\n"
|
|
4210
4562
|
+ " if (!data || typeof data !== 'object' || data.id !== PREVIEW_ID) return;\n"
|
|
@@ -4214,15 +4566,19 @@
|
|
|
4214
4566
|
+ " }\n"
|
|
4215
4567
|
+ " if (data.type === 'pi-studio-html-artifact-math-rendered') {\n"
|
|
4216
4568
|
+ " applyRenderedHtmlMath(data.results);\n"
|
|
4569
|
+
+ " return;\n"
|
|
4570
|
+
+ " }\n"
|
|
4571
|
+
+ " if (data.type === 'pi-studio-html-artifact-resources-resolved') {\n"
|
|
4572
|
+
+ " applyResolvedHtmlPreviewResources(data.results);\n"
|
|
4217
4573
|
+ " }\n"
|
|
4218
4574
|
+ " });\n"
|
|
4219
4575
|
+ " document.addEventListener('click', handleFragmentAnchorClick);\n"
|
|
4220
|
-
+ " document.addEventListener('DOMContentLoaded', scheduleHtmlMathRenderScan);\n"
|
|
4576
|
+
+ " document.addEventListener('DOMContentLoaded', () => { scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); });\n"
|
|
4221
4577
|
+ " window.addEventListener('hashchange', () => {\n"
|
|
4222
4578
|
+ " const hash = String(window.location && window.location.hash || '');\n"
|
|
4223
4579
|
+ " if (hash) scrollFragmentIntoView(hash.slice(1), { smooth: false });\n"
|
|
4224
4580
|
+ " });\n"
|
|
4225
|
-
+ " window.addEventListener('load', () => { scheduleHeight(); scheduleHtmlMathRenderScan(); });\n"
|
|
4581
|
+
+ " window.addEventListener('load', () => { scheduleHeight(); scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); });\n"
|
|
4226
4582
|
+ " window.addEventListener('resize', scheduleHeight);\n"
|
|
4227
4583
|
+ " if (typeof ResizeObserver === 'function') {\n"
|
|
4228
4584
|
+ " const observer = new ResizeObserver(scheduleHeight);\n"
|
|
@@ -4230,15 +4586,15 @@
|
|
|
4230
4586
|
+ " if (document.body) observer.observe(document.body);\n"
|
|
4231
4587
|
+ " }\n"
|
|
4232
4588
|
+ " if (typeof MutationObserver === 'function') {\n"
|
|
4233
|
-
+ " const observer = new MutationObserver(() => { scheduleHeight(); scheduleHtmlMathRenderScan(); });\n"
|
|
4589
|
+
+ " const observer = new MutationObserver(() => { scheduleHeight(); scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); });\n"
|
|
4234
4590
|
+ " observer.observe(document.documentElement, { childList: true, subtree: true, attributes: true, characterData: true });\n"
|
|
4235
4591
|
+ " }\n"
|
|
4236
4592
|
+ " scheduleHeight();\n"
|
|
4237
4593
|
+ " setTimeout(scheduleHeight, 80);\n"
|
|
4238
4594
|
+ " setTimeout(scheduleHeight, 350);\n"
|
|
4239
|
-
+ " setTimeout(scheduleHtmlMathRenderScan, 0);\n"
|
|
4240
|
-
+ " setTimeout(scheduleHtmlMathRenderScan, 120);\n"
|
|
4241
|
-
+ " setTimeout(scheduleHtmlMathRenderScan, 500);\n"
|
|
4595
|
+
+ " setTimeout(() => { scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); }, 0);\n"
|
|
4596
|
+
+ " setTimeout(() => { scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); }, 120);\n"
|
|
4597
|
+
+ " setTimeout(() => { scheduleHtmlMathRenderScan(); scheduleHtmlPreviewResourceScan(); }, 500);\n"
|
|
4242
4598
|
+ "})();\n"
|
|
4243
4599
|
+ "<\/script>";
|
|
4244
4600
|
}
|
|
@@ -4467,9 +4823,92 @@
|
|
|
4467
4823
|
void renderHtmlArtifactMathItems(record, items);
|
|
4468
4824
|
}
|
|
4469
4825
|
|
|
4826
|
+
function normalizeHtmlArtifactResourceItems(rawItems) {
|
|
4827
|
+
if (!Array.isArray(rawItems)) return [];
|
|
4828
|
+
return rawItems.slice(0, 100).map((item) => {
|
|
4829
|
+
const raw = item && typeof item === "object" ? item : null;
|
|
4830
|
+
const resourceId = raw && typeof raw.resourceId === "string" ? raw.resourceId : "";
|
|
4831
|
+
const url = raw && typeof raw.url === "string" ? raw.url : "";
|
|
4832
|
+
if (!resourceId || !url.trim()) return null;
|
|
4833
|
+
return { resourceId, url };
|
|
4834
|
+
}).filter(Boolean);
|
|
4835
|
+
}
|
|
4836
|
+
|
|
4837
|
+
function buildHtmlArtifactResourceFetchUrl(record, resourceUrl) {
|
|
4838
|
+
const token = getToken();
|
|
4839
|
+
if (!token) return "";
|
|
4840
|
+
const params = new URLSearchParams({ token, path: String(resourceUrl || "") });
|
|
4841
|
+
if (record && record.sourcePath) {
|
|
4842
|
+
params.set("sourcePath", record.sourcePath);
|
|
4843
|
+
} else if (record && record.resourceDir) {
|
|
4844
|
+
params.set("resourceDir", record.resourceDir);
|
|
4845
|
+
}
|
|
4846
|
+
return "/html-preview-resource?" + params.toString();
|
|
4847
|
+
}
|
|
4848
|
+
|
|
4849
|
+
function postHtmlArtifactResourceResults(record, results) {
|
|
4850
|
+
if (!record || !record.iframe || !record.iframe.isConnected || !record.iframe.contentWindow) return;
|
|
4851
|
+
try {
|
|
4852
|
+
record.iframe.contentWindow.postMessage({
|
|
4853
|
+
type: "pi-studio-html-artifact-resources-resolved",
|
|
4854
|
+
id: record.id || "",
|
|
4855
|
+
results: Array.isArray(results) ? results : [],
|
|
4856
|
+
}, "*");
|
|
4857
|
+
} catch {
|
|
4858
|
+
// Ignore iframe postMessage failures.
|
|
4859
|
+
}
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
async function fetchHtmlArtifactResource(record, item) {
|
|
4863
|
+
const resourceId = item && item.resourceId ? item.resourceId : "";
|
|
4864
|
+
try {
|
|
4865
|
+
const fetchUrl = buildHtmlArtifactResourceFetchUrl(record, item.url);
|
|
4866
|
+
if (!fetchUrl) throw new Error("Missing Studio token in URL.");
|
|
4867
|
+
const response = await fetchWithTimeout(fetchUrl, { method: "GET" }, HTML_ARTIFACT_RESOURCE_FETCH_TIMEOUT_MS, "HTML preview resource load");
|
|
4868
|
+
const payload = await response.json().catch(() => null);
|
|
4869
|
+
if (!response.ok || !payload || payload.ok !== true || typeof payload.dataUrl !== "string") {
|
|
4870
|
+
const message = payload && typeof payload.error === "string" ? payload.error : "HTML preview resource load failed with HTTP " + response.status + ".";
|
|
4871
|
+
throw new Error(message);
|
|
4872
|
+
}
|
|
4873
|
+
return { resourceId, ok: true, dataUrl: payload.dataUrl };
|
|
4874
|
+
} catch (error) {
|
|
4875
|
+
return { resourceId, ok: false, error: error && error.message ? error.message : String(error || "HTML preview resource load failed.") };
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
|
|
4879
|
+
async function resolveHtmlArtifactResources(record, items) {
|
|
4880
|
+
if (!record || !Array.isArray(items) || items.length === 0) return;
|
|
4881
|
+
if (record.detail) record.detail.textContent = "HTML preview · loading local images";
|
|
4882
|
+
const results = await Promise.all(items.map((item) => fetchHtmlArtifactResource(record, item)));
|
|
4883
|
+
postHtmlArtifactResourceResults(record, results);
|
|
4884
|
+
if (record.detail) record.detail.textContent = "HTML preview";
|
|
4885
|
+
}
|
|
4886
|
+
|
|
4887
|
+
function handleHtmlArtifactFrameResourceMessage(event) {
|
|
4888
|
+
const data = event && event.data;
|
|
4889
|
+
if (!data || typeof data !== "object" || data.type !== "pi-studio-html-artifact-resolve-resources") return;
|
|
4890
|
+
const id = typeof data.id === "string" ? data.id : "";
|
|
4891
|
+
const record = id ? htmlArtifactFramesById.get(id) : null;
|
|
4892
|
+
if (!record || !record.iframe || !record.iframe.isConnected) {
|
|
4893
|
+
if (id) htmlArtifactFramesById.delete(id);
|
|
4894
|
+
return;
|
|
4895
|
+
}
|
|
4896
|
+
if (event.source && record.iframe.contentWindow && event.source !== record.iframe.contentWindow) return;
|
|
4897
|
+
const items = normalizeHtmlArtifactResourceItems(data.resources);
|
|
4898
|
+
if (items.length === 0) return;
|
|
4899
|
+
record.resourceResolveBatchCount = Math.max(0, Number(record.resourceResolveBatchCount) || 0) + 1;
|
|
4900
|
+
record.resourceResolveItemCount = Math.max(0, Number(record.resourceResolveItemCount) || 0) + items.length;
|
|
4901
|
+
if (record.resourceResolveBatchCount > 12 || record.resourceResolveItemCount > 300) {
|
|
4902
|
+
postHtmlArtifactResourceResults(record, items.map((item) => ({ resourceId: item.resourceId, ok: false, error: "HTML preview local image load limit reached." })));
|
|
4903
|
+
return;
|
|
4904
|
+
}
|
|
4905
|
+
void resolveHtmlArtifactResources(record, items);
|
|
4906
|
+
}
|
|
4907
|
+
|
|
4470
4908
|
window.addEventListener("message", handleHtmlArtifactFrameSizeMessage);
|
|
4471
4909
|
window.addEventListener("message", handleHtmlArtifactFrameFragmentMessage);
|
|
4472
4910
|
window.addEventListener("message", handleHtmlArtifactFrameMathRenderMessage);
|
|
4911
|
+
window.addEventListener("message", handleHtmlArtifactFrameResourceMessage);
|
|
4473
4912
|
|
|
4474
4913
|
function isStudioHtmlFocusOpen() {
|
|
4475
4914
|
return Boolean(studioHtmlFocusOverlayEl && studioHtmlFocusOverlayEl.hidden === false && studioHtmlFocusShellEl);
|
|
@@ -4777,7 +5216,19 @@
|
|
|
4777
5216
|
iframe.addEventListener("load", () => { postArtifactZoom(); });
|
|
4778
5217
|
iframe.srcdoc = buildHtmlArtifactSrcdoc(html, previewId);
|
|
4779
5218
|
shell.appendChild(iframe);
|
|
4780
|
-
htmlArtifactFramesById.set(previewId, {
|
|
5219
|
+
htmlArtifactFramesById.set(previewId, {
|
|
5220
|
+
id: previewId,
|
|
5221
|
+
iframe,
|
|
5222
|
+
shell,
|
|
5223
|
+
detail,
|
|
5224
|
+
zoomControls,
|
|
5225
|
+
sourcePath: options && options.sourcePath ? String(options.sourcePath) : "",
|
|
5226
|
+
resourceDir: options && options.resourceDir ? String(options.resourceDir) : "",
|
|
5227
|
+
mathRenderBatchCount: 0,
|
|
5228
|
+
mathRenderItemCount: 0,
|
|
5229
|
+
resourceResolveBatchCount: 0,
|
|
5230
|
+
resourceResolveItemCount: 0,
|
|
5231
|
+
});
|
|
4781
5232
|
|
|
4782
5233
|
targetEl.appendChild(shell);
|
|
4783
5234
|
|
|
@@ -5892,12 +6343,16 @@
|
|
|
5892
6343
|
const action = actionBtn.getAttribute("data-repl-action") || "";
|
|
5893
6344
|
if (action === "start" || action === "new-session") {
|
|
5894
6345
|
const requestId = makeRequestId();
|
|
6346
|
+
const command = getCurrentReplStartCommandFromDom();
|
|
6347
|
+
setReplCommandOverride(replRuntime, command);
|
|
5895
6348
|
replBusy = true;
|
|
5896
6349
|
replError = "";
|
|
5897
|
-
replMessage = (action === "new-session" ? "Starting new " : "Starting ") + replRuntime + "
|
|
6350
|
+
replMessage = (action === "new-session" ? "Starting new " : "Starting ") + getReplRuntimeLabel(replRuntime) + " session" + (command ? " with custom command" : "") + "…";
|
|
5898
6351
|
syncActionButtons();
|
|
5899
6352
|
renderReplViewIfActive({ force: true });
|
|
5900
|
-
|
|
6353
|
+
const message = { type: "repl_start_request", requestId, runtime: replRuntime, newSession: action === "new-session" };
|
|
6354
|
+
if (command) message.command = command;
|
|
6355
|
+
if (!sendMessage(message)) {
|
|
5901
6356
|
replBusy = false;
|
|
5902
6357
|
syncActionButtons();
|
|
5903
6358
|
}
|
|
@@ -6001,13 +6456,29 @@
|
|
|
6001
6456
|
if (!(target instanceof Element)) return;
|
|
6002
6457
|
const runtimeSelect = target.closest("[data-repl-runtime]");
|
|
6003
6458
|
if (runtimeSelect && "value" in runtimeSelect) {
|
|
6459
|
+
const previousActive = replActiveSessionName;
|
|
6004
6460
|
setReplRuntime(runtimeSelect.value);
|
|
6461
|
+
selectReplSessionForRuntime(replRuntime, previousActive);
|
|
6462
|
+
replError = "";
|
|
6463
|
+
replMessage = "";
|
|
6464
|
+
if (replActiveSessionName) {
|
|
6465
|
+
requestReplCapture();
|
|
6466
|
+
} else {
|
|
6467
|
+
replTranscript = "";
|
|
6468
|
+
replCapturedAt = 0;
|
|
6469
|
+
syncActionButtons();
|
|
6470
|
+
}
|
|
6005
6471
|
renderReplViewIfActive({ force: true });
|
|
6006
6472
|
return;
|
|
6007
6473
|
}
|
|
6474
|
+
const commandInput = target.closest("[data-repl-command]");
|
|
6475
|
+
if (commandInput && "value" in commandInput) {
|
|
6476
|
+
setReplCommandOverride(replRuntime, commandInput.value);
|
|
6477
|
+
return;
|
|
6478
|
+
}
|
|
6008
6479
|
const sessionSelect = target.closest("[data-repl-session]");
|
|
6009
6480
|
if (sessionSelect && "value" in sessionSelect) {
|
|
6010
|
-
|
|
6481
|
+
setActiveReplSessionForCurrentRuntime(sessionSelect.value);
|
|
6011
6482
|
replError = "";
|
|
6012
6483
|
replMessage = "";
|
|
6013
6484
|
replFollow = true;
|
|
@@ -6291,12 +6762,12 @@
|
|
|
6291
6762
|
const exportingReplJournal = rightView === "repl";
|
|
6292
6763
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
6293
6764
|
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
6294
|
-
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL
|
|
6765
|
+
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export PDF.", "warning");
|
|
6295
6766
|
return;
|
|
6296
6767
|
}
|
|
6297
6768
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
6298
6769
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
6299
|
-
setStatus("No REPL
|
|
6770
|
+
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
6300
6771
|
return;
|
|
6301
6772
|
}
|
|
6302
6773
|
|
|
@@ -6464,12 +6935,12 @@
|
|
|
6464
6935
|
const exportingReplJournal = rightView === "repl";
|
|
6465
6936
|
const rightPaneShowsPreview = rightView === "preview" || rightView === "editor-preview";
|
|
6466
6937
|
if (!rightPaneShowsPreview && !exportingReplJournal) {
|
|
6467
|
-
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL
|
|
6938
|
+
setStatus("Switch right pane to Response (Preview), Editor (Preview), or REPL to export HTML.", "warning");
|
|
6468
6939
|
return;
|
|
6469
6940
|
}
|
|
6470
6941
|
const replJournalExportEntries = exportingReplJournal ? getVisibleReplJournalEntries() : [];
|
|
6471
6942
|
if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
6472
|
-
setStatus("No REPL
|
|
6943
|
+
setStatus("No Studio REPL record entries to export for this session yet.", "warning");
|
|
6473
6944
|
return;
|
|
6474
6945
|
}
|
|
6475
6946
|
|
|
@@ -6491,7 +6962,7 @@
|
|
|
6491
6962
|
? editorHtmlLanguage === "latex"
|
|
6492
6963
|
: /\\documentclass\b|\\begin\{document\}/.test(markdown));
|
|
6493
6964
|
let filenameHint = exportingReplJournal ? "repl-studio.html" : (isEditorPreview ? "studio-editor-preview.html" : "studio-response-preview.html");
|
|
6494
|
-
let titleHint = exportingReplJournal ? "REPL
|
|
6965
|
+
let titleHint = exportingReplJournal ? "Studio REPL Record" : (isEditorPreview ? "Studio editor preview" : "Studio response preview");
|
|
6495
6966
|
if (sourcePath) {
|
|
6496
6967
|
const baseName = sourcePath.split(/[\\/]/).pop() || "studio";
|
|
6497
6968
|
const stem = baseName.replace(/\.[^.]+$/, "") || "studio";
|
|
@@ -6907,7 +7378,7 @@
|
|
|
6907
7378
|
if (editorView !== "preview") return;
|
|
6908
7379
|
const text = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
6909
7380
|
if (isHtmlArtifactPreviewText(text, editorLanguage)) {
|
|
6910
|
-
renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview" });
|
|
7381
|
+
renderHtmlArtifactPreview(sourcePreviewEl, text, "source", { title: "Editor HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
6911
7382
|
return;
|
|
6912
7383
|
}
|
|
6913
7384
|
if (supportsCodePreviewCommentsForCurrentEditor()) {
|
|
@@ -7190,10 +7661,9 @@
|
|
|
7190
7661
|
const visibleEntries = getVisibleReplJournalEntries();
|
|
7191
7662
|
const hasEntries = visibleEntries.length > 0;
|
|
7192
7663
|
const entryCount = visibleEntries.length;
|
|
7193
|
-
const hiddenEntryCount = getHiddenReplJournalEntryCount();
|
|
7194
7664
|
const sessionName = getActiveReplJournalSessionName();
|
|
7195
7665
|
const collapsedClass = replJournalCollapsed ? " is-collapsed" : "";
|
|
7196
|
-
const toggleButton = "<button type='button' data-repl-action='journal-toggle' aria-expanded='" + (replJournalCollapsed ? "false" : "true") + "'>" + (replJournalCollapsed ? "Show
|
|
7666
|
+
const toggleButton = "<button type='button' data-repl-action='journal-toggle' aria-expanded='" + (replJournalCollapsed ? "false" : "true") + "'>" + (replJournalCollapsed ? "Show record" : "Hide record") + "</button>";
|
|
7197
7667
|
const toggleActions = "<div class='repl-journal-actions'>" + toggleButton + "</div>";
|
|
7198
7668
|
const summaryText = hasEntries
|
|
7199
7669
|
? (entryCount + " Studio entr" + (entryCount === 1 ? "y" : "ies") + (sessionName ? " for " + sessionName : "") + ". Export is Markdown.")
|
|
@@ -7201,7 +7671,7 @@
|
|
|
7201
7671
|
if (replJournalCollapsed) {
|
|
7202
7672
|
return "<section class='repl-journal repl-journal-compact" + collapsedClass + "'>"
|
|
7203
7673
|
+ "<div class='repl-journal-compact-row'>"
|
|
7204
|
-
+ "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>REPL
|
|
7674
|
+
+ "<div class='repl-journal-compact-title'><span class='repl-journal-chip'>Studio REPL Record</span><span>" + escapeHtml(summaryText) + "</span></div>"
|
|
7205
7675
|
+ "<div class='repl-journal-actions'>" + toggleButton + "</div>"
|
|
7206
7676
|
+ "</div>"
|
|
7207
7677
|
+ "</section>";
|
|
@@ -7239,14 +7709,14 @@
|
|
|
7239
7709
|
}).join("");
|
|
7240
7710
|
const emptyText = sessionName
|
|
7241
7711
|
? (String(transcript || "").trim()
|
|
7242
|
-
? "No REPL
|
|
7243
|
-
: "No REPL
|
|
7244
|
-
: "No REPL
|
|
7712
|
+
? "No Studio REPL record entries yet. The raw tmux mirror below still has this session's history; send code from Studio to build a clean record."
|
|
7713
|
+
: "No Studio REPL record entries yet. Send code from the editor, or use More → Add note (Literate send) to record prose.")
|
|
7714
|
+
: "No Studio REPL record entries yet. Send code from the editor, or use More → Add note (Literate send) to record prose.";
|
|
7245
7715
|
const terminalContent = banner
|
|
7246
7716
|
+ (hasEntries ? cards : "<div class='repl-studio-empty'>" + escapeHtml(emptyText) + "</div>");
|
|
7247
7717
|
return "<section class='repl-journal'>"
|
|
7248
|
-
+ "<div class='repl-journal-header'><
|
|
7249
|
-
+
|
|
7718
|
+
+ "<div class='repl-journal-header'><h3>Studio REPL Record</h3>" + toggleActions + "</div>"
|
|
7719
|
+
+ "<p class='repl-journal-description'>Clean record for the selected tmux session. Raw tmux mirror below.</p>"
|
|
7250
7720
|
+ (omitted ? "<div class='repl-journal-omitted'>Showing latest 12 entries for this session; " + escapeHtml(String(omitted)) + " older entries remain in export.</div>" : "")
|
|
7251
7721
|
+ "<div class='repl-journal-list'>" + terminalContent + "</div>"
|
|
7252
7722
|
+ "</section>";
|
|
@@ -7270,7 +7740,7 @@
|
|
|
7270
7740
|
+ "</section>";
|
|
7271
7741
|
}
|
|
7272
7742
|
return "<section class='repl-mirror'>"
|
|
7273
|
-
+ "<div class='repl-journal-header'><div><h3>Raw REPL
|
|
7743
|
+
+ "<div class='repl-journal-header'><div><h3>Raw REPL Mirror</h3><p>Best-effort tmux pane mirror. Useful for directly typed commands and debugging; the Studio record above is cleaner.</p></div>" + actions + "</div>"
|
|
7274
7744
|
+ body
|
|
7275
7745
|
+ "</section>";
|
|
7276
7746
|
}
|
|
@@ -7290,10 +7760,13 @@
|
|
|
7290
7760
|
["ghci", "GHCi"],
|
|
7291
7761
|
["clojure", "Clojure"],
|
|
7292
7762
|
].map(([value, label]) => "<option value='" + escapeHtml(value) + "'" + (replRuntime === value ? " selected" : "") + ">" + escapeHtml(label) + "</option>").join("");
|
|
7293
|
-
const
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7763
|
+
const runtimeLabel = getReplRuntimeLabel(replRuntime);
|
|
7764
|
+
const visibleSessions = getReplSessionsForRuntime(replRuntime);
|
|
7765
|
+
const sessionOptions = visibleSessions.length
|
|
7766
|
+
? visibleSessions.map((session) => "<option value='" + escapeHtml(session.sessionName) + "'" + (session.sessionName === replActiveSessionName ? " selected" : "") + ">" + escapeHtml(session.label || session.sessionName) + "</option>").join("")
|
|
7767
|
+
: "<option value=''>None</option>";
|
|
7768
|
+
const replCommand = getReplCommandOverride(replRuntime);
|
|
7769
|
+
const activeSession = getActiveReplSessionForCurrentRuntime();
|
|
7297
7770
|
const transcript = trimReplTranscript(replTranscript);
|
|
7298
7771
|
const emptyMessage = replTmuxAvailable === false
|
|
7299
7772
|
? "tmux is not available. Install tmux to use Studio REPL sessions."
|
|
@@ -7307,17 +7780,17 @@
|
|
|
7307
7780
|
+ "<div class='repl-toolbar'>"
|
|
7308
7781
|
+ "<div class='repl-controls'>"
|
|
7309
7782
|
+ "<label class='repl-control-label'>Runtime <select data-repl-runtime aria-label='REPL runtime'>" + runtimeOptions + "</select></label>"
|
|
7310
|
-
+ "<
|
|
7311
|
-
+ "<
|
|
7783
|
+
+ "<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>"
|
|
7784
|
+
+ "<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>"
|
|
7785
|
+
+ "<label class='repl-control-label repl-session-label'>Session <select data-repl-session aria-label='REPL session'" + (visibleSessions.length ? "" : " disabled") + ">" + sessionOptions + "</select></label>"
|
|
7312
7786
|
+ "<details class='repl-more-controls'>"
|
|
7313
7787
|
+ "<summary title='More REPL actions'>More</summary>"
|
|
7314
7788
|
+ "<div class='repl-more-menu'>"
|
|
7315
|
-
+ "<button type='button' data-repl-action='new-session'" + (replBusy || replTmuxAvailable === false ? " disabled" : "") + " title='Start a new additional session for this runtime.'>New session</button>"
|
|
7316
7789
|
+ "<button type='button' data-repl-action='stop-session'" + (canStopActiveSession ? "" : " disabled") + " title='Stop the selected Studio-owned REPL session.'>Stop session</button>"
|
|
7317
7790
|
+ "<button type='button' data-repl-action='interrupt'" + (activeSession && !replBusy ? "" : " disabled") + " title='Send Ctrl+C to the active REPL session.'>Interrupt</button>"
|
|
7318
7791
|
+ "<button type='button' data-repl-action='copy-attach-command'" + (activeSession ? "" : " disabled") + " title='Copy command for attaching to this tmux session in a terminal.'>Copy attach command</button>"
|
|
7319
7792
|
+ "<button type='button' data-repl-action='run-all-chunks'" + (canSendToActiveSession ? "" : " disabled") + " title='Literate send: send all fenced code chunks matching the active REPL runtime.'>Run all chunks</button>"
|
|
7320
|
-
+ "<button type='button' data-repl-action='journal-note' title='Add the selected prose/current paragraph to
|
|
7793
|
+
+ "<button type='button' data-repl-action='journal-note' title='Add the selected prose/current paragraph to the Studio REPL record (Literate send) without sending it to the runtime.'>Add note</button>"
|
|
7321
7794
|
+ "<button type='button' data-repl-action='refresh'>Refresh</button>"
|
|
7322
7795
|
+ "<button type='button' data-repl-action='follow'>Follow: " + (replFollow ? "On" : "Off") + "</button>"
|
|
7323
7796
|
+ "</div>"
|
|
@@ -7501,7 +7974,7 @@
|
|
|
7501
7974
|
return;
|
|
7502
7975
|
}
|
|
7503
7976
|
if (isHtmlArtifactPreviewText(editorText, editorLanguage)) {
|
|
7504
|
-
renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview" });
|
|
7977
|
+
renderHtmlArtifactPreview(critiqueViewEl, editorText, "response", { title: "Editor HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
7505
7978
|
return;
|
|
7506
7979
|
}
|
|
7507
7980
|
if (supportsCodePreviewCommentsForCurrentEditor()) {
|
|
@@ -7525,7 +7998,7 @@
|
|
|
7525
7998
|
|
|
7526
7999
|
if (rightView === "preview") {
|
|
7527
8000
|
if (isHtmlArtifactPreviewText(markdown, "")) {
|
|
7528
|
-
renderHtmlArtifactPreview(critiqueViewEl, markdown, "response", { title: "Response HTML preview" });
|
|
8001
|
+
renderHtmlArtifactPreview(critiqueViewEl, markdown, "response", { title: "Response HTML preview", ...getHtmlPreviewResourceContextOptions() });
|
|
7529
8002
|
return;
|
|
7530
8003
|
}
|
|
7531
8004
|
const nonce = ++responsePreviewRenderNonce;
|
|
@@ -7604,19 +8077,19 @@
|
|
|
7604
8077
|
exportPdfBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
|
|
7605
8078
|
exportPdfBtn.textContent = previewExportInProgress
|
|
7606
8079
|
? "Exporting…"
|
|
7607
|
-
: (exportingReplJournal ? "Export
|
|
8080
|
+
: (exportingReplJournal ? "Export record" : "Export right preview");
|
|
7608
8081
|
if (rightView === "trace") {
|
|
7609
8082
|
exportPdfBtn.title = "Working view does not support preview export.";
|
|
7610
8083
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
7611
|
-
exportPdfBtn.title = "No REPL
|
|
8084
|
+
exportPdfBtn.title = "No Studio REPL record entries to export for this session yet.";
|
|
7612
8085
|
} else if (rightView === "markdown") {
|
|
7613
|
-
exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), or REPL
|
|
8086
|
+
exportPdfBtn.title = "Switch right pane to Response (Preview), Editor (Preview), or REPL to export.";
|
|
7614
8087
|
} else if (!canExportPreview) {
|
|
7615
8088
|
exportPdfBtn.title = "Nothing to export yet.";
|
|
7616
8089
|
} else if (isHtmlArtifactPreview) {
|
|
7617
8090
|
exportPdfBtn.title = "This is an interactive HTML preview. Export as HTML; PDF export is not available yet.";
|
|
7618
8091
|
} else if (exportingReplJournal) {
|
|
7619
|
-
exportPdfBtn.title = "Choose PDF or HTML and export REPL
|
|
8092
|
+
exportPdfBtn.title = "Choose PDF or HTML and export the Studio REPL record.";
|
|
7620
8093
|
} else {
|
|
7621
8094
|
exportPdfBtn.title = "Choose PDF or HTML and export the current right-pane preview.";
|
|
7622
8095
|
}
|
|
@@ -7625,20 +8098,20 @@
|
|
|
7625
8098
|
exportPreviewPdfBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview || isHtmlArtifactPreview;
|
|
7626
8099
|
exportPreviewPdfBtn.title = isHtmlArtifactPreview
|
|
7627
8100
|
? "Interactive HTML preview PDF export is not available yet."
|
|
7628
|
-
: (exportingReplJournal ? "Export
|
|
8101
|
+
: (exportingReplJournal ? "Export the Studio REPL record as PDF." : "Export the current right-pane preview as PDF.");
|
|
7629
8102
|
}
|
|
7630
8103
|
if (exportPreviewHtmlBtn) {
|
|
7631
8104
|
exportPreviewHtmlBtn.disabled = uiBusy || previewExportInProgress || !canExportPreview;
|
|
7632
8105
|
exportPreviewHtmlBtn.title = isHtmlArtifactPreview
|
|
7633
8106
|
? "Export the authored HTML preview."
|
|
7634
|
-
: (exportingReplJournal ? "Export
|
|
8107
|
+
: (exportingReplJournal ? "Export the Studio REPL record as standalone HTML." : "Export the current right-pane preview as standalone HTML.");
|
|
7635
8108
|
}
|
|
7636
8109
|
if (exportPreviewControlsEl) {
|
|
7637
8110
|
exportPreviewControlsEl.title = canExportPreview
|
|
7638
8111
|
? (exportingReplJournal
|
|
7639
|
-
? "Choose a format and export REPL
|
|
8112
|
+
? "Choose a format and export the Studio REPL record."
|
|
7640
8113
|
: (isHtmlArtifactPreview ? "Export this HTML preview." : "Choose a format and export the current right-pane preview."))
|
|
7641
|
-
: (exportingReplJournal ? "No REPL
|
|
8114
|
+
: (exportingReplJournal ? "No Studio REPL record entries to export for this session yet." : "Switch right pane to a non-empty preview before exporting.");
|
|
7642
8115
|
}
|
|
7643
8116
|
if (!canExportPreview || previewExportInProgress) {
|
|
7644
8117
|
closeExportPreviewMenu();
|
|
@@ -7669,6 +8142,15 @@
|
|
|
7669
8142
|
return null;
|
|
7670
8143
|
}
|
|
7671
8144
|
|
|
8145
|
+
function getHtmlPreviewResourceContextOptions() {
|
|
8146
|
+
const sourcePath = getEffectiveSavePath() || sourceState.path || "";
|
|
8147
|
+
const resourceDir = resourceDirInput && resourceDirInput.value.trim() ? resourceDirInput.value.trim() : "";
|
|
8148
|
+
return {
|
|
8149
|
+
sourcePath,
|
|
8150
|
+
resourceDir: sourcePath ? "" : resourceDir,
|
|
8151
|
+
};
|
|
8152
|
+
}
|
|
8153
|
+
|
|
7672
8154
|
function buildAnnotatedSaveSuggestion() {
|
|
7673
8155
|
const effectivePath = getEffectiveSavePath() || sourceState.path || "";
|
|
7674
8156
|
if (effectivePath) {
|
|
@@ -14262,7 +14744,7 @@
|
|
|
14262
14744
|
sendRunBtn.classList.toggle("request-stop-active", directIsStop);
|
|
14263
14745
|
sendRunBtn.classList.toggle("repl-secondary-action", rightView === "repl" && !directIsStop);
|
|
14264
14746
|
sendRunBtn.disabled = wsState === "Disconnected" || (!directIsStop && (uiBusy || critiqueIsStop));
|
|
14265
|
-
const replHint = rightView === "repl" &&
|
|
14747
|
+
const replHint = rightView === "repl" && getActiveReplSessionForCurrentRuntime()
|
|
14266
14748
|
? " Sends text to Pi, not the REPL; use Send chunk/selection or Send to REPL to execute code in the active REPL."
|
|
14267
14749
|
: "";
|
|
14268
14750
|
sendRunBtn.title = directIsStop
|
|
@@ -14283,7 +14765,7 @@
|
|
|
14283
14765
|
: "Queue steering is available while Run editor text is active.";
|
|
14284
14766
|
}
|
|
14285
14767
|
|
|
14286
|
-
const hasReplSession = Boolean(
|
|
14768
|
+
const hasReplSession = Boolean(getActiveReplSessionForCurrentRuntime());
|
|
14287
14769
|
if (sendReplBtn) {
|
|
14288
14770
|
const showReplSend = rightView === "repl";
|
|
14289
14771
|
sendReplBtn.hidden = !showReplSend;
|
|
@@ -14651,7 +15133,7 @@
|
|
|
14651
15133
|
replTmuxAvailable = typeof message.tmuxAvailable === "boolean" ? message.tmuxAvailable : replTmuxAvailable;
|
|
14652
15134
|
const sessionsChanged = setReplSessions(message.sessions);
|
|
14653
15135
|
if (typeof message.activeSessionName === "string" && message.activeSessionName.trim()) {
|
|
14654
|
-
|
|
15136
|
+
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
14655
15137
|
}
|
|
14656
15138
|
const journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
14657
15139
|
if (typeof message.transcript === "string") replTranscript = trimReplTranscript(message.transcript);
|
|
@@ -14704,7 +15186,7 @@
|
|
|
14704
15186
|
}
|
|
14705
15187
|
}
|
|
14706
15188
|
if (typeof message.activeSessionName === "string" && message.activeSessionName.trim()) {
|
|
14707
|
-
|
|
15189
|
+
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
14708
15190
|
}
|
|
14709
15191
|
let journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
14710
15192
|
if (typeof message.transcript === "string") {
|
|
@@ -15519,6 +16001,8 @@
|
|
|
15519
16001
|
rightPaneEl.addEventListener("focusin", (event) => activatePaneFromInteraction("right", event));
|
|
15520
16002
|
}
|
|
15521
16003
|
|
|
16004
|
+
setupPaneResizeHandle();
|
|
16005
|
+
|
|
15522
16006
|
if (leftFocusBtn) {
|
|
15523
16007
|
leftFocusBtn.addEventListener("click", () => {
|
|
15524
16008
|
if (paneFocusTarget === "left") {
|