pi-studio 0.9.56 → 0.9.58
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 +32 -0
- package/README.md +28 -7
- package/ROADMAP.md +26 -2
- package/client/studio-client.js +587 -57
- package/client/studio.css +171 -0
- package/index.ts +249 -114
- package/package.json +1 -1
- package/shared/REPL_SESSION_RECORD_PROTOCOL.md +24 -1
- package/shared/repl-control-files.js +158 -0
- package/shared/repl-submission-display.js +227 -0
package/client/studio-client.js
CHANGED
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
const queueSteerBtn = document.getElementById("queueSteerBtn");
|
|
133
133
|
const sendReplBtn = document.getElementById("sendReplBtn");
|
|
134
134
|
const replSendModeSelect = document.getElementById("replSendModeSelect");
|
|
135
|
+
const replEchoModeSelect = document.getElementById("replEchoModeSelect");
|
|
135
136
|
const copyDraftBtn = document.getElementById("copyDraftBtn");
|
|
136
137
|
const suggestCompletionBtn = document.getElementById("suggestCompletionBtn");
|
|
137
138
|
const suggestCompletionOptionsBtn = document.getElementById("suggestCompletionOptionsBtn");
|
|
@@ -147,6 +148,8 @@
|
|
|
147
148
|
const stripAnnotationsBtn = document.getElementById("stripAnnotationsBtn");
|
|
148
149
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
149
150
|
const lineNumbersSelect = document.getElementById("lineNumbersSelect");
|
|
151
|
+
const studioPaneLayoutSelect = document.getElementById("studioPaneLayoutSelect");
|
|
152
|
+
const activityTrackingSelect = document.getElementById("activityTrackingSelect");
|
|
150
153
|
const editorFontSizeSelect = document.getElementById("editorFontSizeSelect");
|
|
151
154
|
const annotationModeSelect = document.getElementById("annotationModeSelect");
|
|
152
155
|
const compactBtn = document.getElementById("compactBtn");
|
|
@@ -426,8 +429,8 @@
|
|
|
426
429
|
rightViewSelect.title = isWatchedFilePreview
|
|
427
430
|
? "Read-only watched preview follows this file on disk."
|
|
428
431
|
: (isEditorOnlyMode
|
|
429
|
-
? "Editor-only views: Editor Preview, contextual Quarto Preview for .qmd/.md/.markdown files, Changes, Files, REPL, or Side questions. F7 cycles; Cmd/Ctrl+Alt+3/5/6/7/8 switch directly to numbered right-pane views, and Cmd/Ctrl+Alt+F/Q open Files/Side questions."
|
|
430
|
-
: "Right pane view mode. F7 cycles, including contextual Quarto Preview for file-backed .qmd, .md, and .markdown documents; Cmd/Ctrl+Alt+1–8 switches directly between the numbered views. Cmd/Ctrl+Alt+P/E/W/F/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, and Side questions.");
|
|
432
|
+
? "Editor-only views: Editor Preview, contextual Quarto Preview for .qmd/.md/.markdown files, Changes, Files, REPL, or Side questions. F7 cycles; Cmd/Ctrl+Alt+3/5/6/7/8 switch directly to numbered right-pane views, and Cmd/Ctrl+Alt+F/R/Q open Files/REPL/Side questions."
|
|
433
|
+
: "Right pane view mode. F7 cycles, including contextual Quarto Preview for file-backed .qmd, .md, and .markdown documents; Cmd/Ctrl+Alt+1–8 switches directly between the numbered views. Cmd/Ctrl+Alt+P/E/W/F/R/Q keep mnemonic shortcuts for Preview, Editor Preview, Working, Files, REPL, and Side questions.");
|
|
431
434
|
}
|
|
432
435
|
|
|
433
436
|
function getInitialRightView(source) {
|
|
@@ -480,6 +483,8 @@
|
|
|
480
483
|
const REPL_TRANSCRIPT_MAX_CHARS = 200_000;
|
|
481
484
|
const REPL_JOURNAL_OUTPUT_MAX_CHARS = 80_000;
|
|
482
485
|
const REPL_JOURNAL_MAX_ENTRIES = 80;
|
|
486
|
+
const REPL_QUICK_DRAFT_MAX_CHARS = 20_000;
|
|
487
|
+
const REPL_QUICK_DRAFT_MAX_SESSIONS = 12;
|
|
483
488
|
const PDF_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
484
489
|
const HTML_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
485
490
|
const HTML_ARTIFACT_MATH_RENDER_FETCH_TIMEOUT_MS = 30_000;
|
|
@@ -587,6 +592,10 @@
|
|
|
587
592
|
let replFollow = true;
|
|
588
593
|
let replPollTimer = null;
|
|
589
594
|
let replBusy = false;
|
|
595
|
+
let replPendingRequestId = "";
|
|
596
|
+
const replQuickDrafts = new Map();
|
|
597
|
+
let replQuickPending = null;
|
|
598
|
+
let replQuickFocusRequested = false;
|
|
590
599
|
let replSendMode = (() => {
|
|
591
600
|
try {
|
|
592
601
|
const stored = window.localStorage && window.localStorage.getItem("piStudio.replSendMode");
|
|
@@ -595,6 +604,14 @@
|
|
|
595
604
|
return "raw";
|
|
596
605
|
}
|
|
597
606
|
})();
|
|
607
|
+
let replEchoMode = (() => {
|
|
608
|
+
try {
|
|
609
|
+
const stored = String((window.localStorage && window.localStorage.getItem("piStudio.replEchoMode.v2")) || "").trim().toLowerCase();
|
|
610
|
+
return stored === "summary" || stored === "full" ? stored : "off";
|
|
611
|
+
} catch {
|
|
612
|
+
return "off";
|
|
613
|
+
}
|
|
614
|
+
})();
|
|
598
615
|
function normalizeReplJournalEntry(entry) {
|
|
599
616
|
if (!entry || typeof entry !== "object") return null;
|
|
600
617
|
const hasCompatibleOrigin = entry.origin === "pi-repl" || entry.origin === "pi-studio";
|
|
@@ -613,7 +630,7 @@
|
|
|
613
630
|
mode: typeof entry.mode === "string" ? entry.mode : "raw",
|
|
614
631
|
prose: typeof entry.prose === "string" ? entry.prose : "",
|
|
615
632
|
code: typeof entry.code === "string" ? entry.code : "",
|
|
616
|
-
output: typeof entry.output === "string" ? entry.output : "",
|
|
633
|
+
output: typeof entry.output === "string" ? stripStudioReplSubmissionEcho(entry.output) : "",
|
|
617
634
|
beforeTranscript: "",
|
|
618
635
|
status: typeof entry.status === "string" ? entry.status : "sent",
|
|
619
636
|
skippedChunks: Math.max(0, Math.floor(Number(entry.skippedChunks) || 0)),
|
|
@@ -1293,6 +1310,10 @@
|
|
|
1293
1310
|
return {
|
|
1294
1311
|
sessionName,
|
|
1295
1312
|
target: typeof session.target === "string" ? session.target : (sessionName + ":0.0"),
|
|
1313
|
+
tmuxSessionId: typeof session.tmuxSessionId === "string" ? session.tmuxSessionId : "",
|
|
1314
|
+
tmuxSessionCreatedAt: typeof session.tmuxSessionCreatedAt === "number" && Number.isFinite(session.tmuxSessionCreatedAt)
|
|
1315
|
+
? session.tmuxSessionCreatedAt
|
|
1316
|
+
: 0,
|
|
1296
1317
|
runtime: typeof session.runtime === "string" ? session.runtime : "unknown",
|
|
1297
1318
|
label: typeof session.label === "string" && session.label.trim() ? session.label.trim() : sessionName,
|
|
1298
1319
|
source: typeof session.source === "string" ? session.source : "tmux",
|
|
@@ -1353,6 +1374,11 @@
|
|
|
1353
1374
|
return String(value || "").trim().toLowerCase() === "literate" ? "literate" : "raw";
|
|
1354
1375
|
}
|
|
1355
1376
|
|
|
1377
|
+
function normalizeReplEchoMode(value) {
|
|
1378
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
1379
|
+
return normalized === "summary" || normalized === "full" ? normalized : "off";
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1356
1382
|
function isMacShortcutPlatform() {
|
|
1357
1383
|
try {
|
|
1358
1384
|
const platform = String((navigator && navigator.platform) || "");
|
|
@@ -1394,6 +1420,85 @@
|
|
|
1394
1420
|
}
|
|
1395
1421
|
}
|
|
1396
1422
|
|
|
1423
|
+
function setReplEchoMode(mode) {
|
|
1424
|
+
replEchoMode = normalizeReplEchoMode(mode);
|
|
1425
|
+
if (replEchoModeSelect) replEchoModeSelect.value = replEchoMode;
|
|
1426
|
+
try {
|
|
1427
|
+
if (window.localStorage) window.localStorage.setItem("piStudio.replEchoMode.v2", replEchoMode);
|
|
1428
|
+
} catch {
|
|
1429
|
+
// Ignore storage failures.
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
function syncReplEchoModeSelect(visible) {
|
|
1434
|
+
if (!replEchoModeSelect) return;
|
|
1435
|
+
replEchoModeSelect.hidden = !visible;
|
|
1436
|
+
replEchoModeSelect.disabled = !visible || wsState === "Disconnected" || uiBusy || replBusy;
|
|
1437
|
+
replEchoModeSelect.value = replEchoMode;
|
|
1438
|
+
replEchoModeSelect.title = replEchoMode === "off"
|
|
1439
|
+
? "Do not add a submitted-code display or alignment anchors to the raw REPL pane."
|
|
1440
|
+
: (replEchoMode === "full"
|
|
1441
|
+
? "Display up to 40 lines or 4,000 characters of submitted code; source remains in raw terminal history."
|
|
1442
|
+
: "Display short submissions in full, truncating after 6 lines or 600 characters, with compact anchors and a plain output divider.");
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function getReplQuickDraftKey(session) {
|
|
1446
|
+
const normalized = normalizeReplSession(session);
|
|
1447
|
+
if (!normalized || !normalized.tmuxSessionId || normalized.tmuxSessionCreatedAt <= 0) return "";
|
|
1448
|
+
return [normalizeReplRuntime(normalized.runtime), normalized.tmuxSessionId, normalized.tmuxSessionCreatedAt].join("\u001f");
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function getReplQuickDraft(session) {
|
|
1452
|
+
const key = getReplQuickDraftKey(session);
|
|
1453
|
+
return key ? String(replQuickDrafts.get(key) || "") : "";
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function setReplQuickDraft(session, value) {
|
|
1457
|
+
const key = getReplQuickDraftKey(session);
|
|
1458
|
+
if (!key) return "";
|
|
1459
|
+
const draft = String(value || "").slice(0, REPL_QUICK_DRAFT_MAX_CHARS);
|
|
1460
|
+
if (!draft) {
|
|
1461
|
+
replQuickDrafts.delete(key);
|
|
1462
|
+
return "";
|
|
1463
|
+
}
|
|
1464
|
+
if (!replQuickDrafts.has(key) && replQuickDrafts.size >= REPL_QUICK_DRAFT_MAX_SESSIONS) {
|
|
1465
|
+
const oldestKey = replQuickDrafts.keys().next().value;
|
|
1466
|
+
if (oldestKey) replQuickDrafts.delete(oldestKey);
|
|
1467
|
+
}
|
|
1468
|
+
replQuickDrafts.set(key, draft);
|
|
1469
|
+
return draft;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function settleReplQuickPending(requestId, submitted) {
|
|
1473
|
+
if (!replQuickPending || !requestId || replQuickPending.requestId !== requestId) return false;
|
|
1474
|
+
const pending = replQuickPending;
|
|
1475
|
+
replQuickPending = null;
|
|
1476
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
1477
|
+
replBusy = Boolean(replPendingRequestId);
|
|
1478
|
+
if (submitted && replQuickDrafts.get(pending.key) === pending.text) {
|
|
1479
|
+
replQuickDrafts.delete(pending.key);
|
|
1480
|
+
}
|
|
1481
|
+
return true;
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
function reconcileReplQuickPending() {
|
|
1485
|
+
if (!replQuickPending) return false;
|
|
1486
|
+
const entry = replJournalEntries.find((candidate) => candidate.requestId === replQuickPending.requestId);
|
|
1487
|
+
if (!entry || entry.status === "sending") return false;
|
|
1488
|
+
return settleReplQuickPending(entry.requestId, entry.status !== "error");
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
function updateReplQuickComposerActions() {
|
|
1492
|
+
if (!critiqueViewEl || rightView !== "repl") return;
|
|
1493
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
1494
|
+
const draft = getReplQuickDraft(session);
|
|
1495
|
+
const pending = Boolean(replQuickPending && replQuickPending.key === getReplQuickDraftKey(session));
|
|
1496
|
+
const sendButton = critiqueViewEl.querySelector("[data-repl-action='quick-send']");
|
|
1497
|
+
const clearButton = critiqueViewEl.querySelector("[data-repl-action='quick-clear']");
|
|
1498
|
+
if (sendButton) sendButton.disabled = !session || replTmuxAvailable === false || pending || replBusy || wsState === "Disconnected" || !draft.trim();
|
|
1499
|
+
if (clearButton) clearButton.disabled = pending || !draft;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1397
1502
|
function setReplJournalCollapsed(collapsed) {
|
|
1398
1503
|
replJournalCollapsed = Boolean(collapsed);
|
|
1399
1504
|
try {
|
|
@@ -1424,6 +1529,8 @@
|
|
|
1424
1529
|
runtime: session.runtime,
|
|
1425
1530
|
source: session.source,
|
|
1426
1531
|
target: session.target,
|
|
1532
|
+
tmuxSessionId: session.tmuxSessionId,
|
|
1533
|
+
tmuxSessionCreatedAt: session.tmuxSessionCreatedAt,
|
|
1427
1534
|
})));
|
|
1428
1535
|
}
|
|
1429
1536
|
|
|
@@ -1543,7 +1650,53 @@
|
|
|
1543
1650
|
|
|
1544
1651
|
function isReplControlFocused() {
|
|
1545
1652
|
const activeEl = document.activeElement;
|
|
1546
|
-
return activeEl instanceof Element && Boolean(activeEl.closest(".repl-controls"));
|
|
1653
|
+
return activeEl instanceof Element && Boolean(activeEl.closest(".repl-controls, .repl-quick-composer"));
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
function focusReplQuickComposer() {
|
|
1657
|
+
replQuickFocusRequested = true;
|
|
1658
|
+
window.setTimeout(() => {
|
|
1659
|
+
if (rightView !== "repl" || !critiqueViewEl) {
|
|
1660
|
+
replQuickFocusRequested = false;
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
const composer = critiqueViewEl.querySelector("[data-repl-quick-draft]");
|
|
1664
|
+
if (composer instanceof HTMLTextAreaElement) {
|
|
1665
|
+
if (!composer.disabled) {
|
|
1666
|
+
try {
|
|
1667
|
+
composer.focus({ preventScroll: true });
|
|
1668
|
+
} catch {
|
|
1669
|
+
try { composer.focus(); } catch {}
|
|
1670
|
+
}
|
|
1671
|
+
if (document.activeElement === composer) replQuickFocusRequested = false;
|
|
1672
|
+
}
|
|
1673
|
+
if (typeof composer.scrollIntoView === "function") {
|
|
1674
|
+
try {
|
|
1675
|
+
composer.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
1676
|
+
} catch {
|
|
1677
|
+
try { composer.scrollIntoView(); } catch {}
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
}, 0);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
function focusReplSessionControls() {
|
|
1685
|
+
if (rightView !== "repl" || !critiqueViewEl) return;
|
|
1686
|
+
critiqueViewEl.scrollTop = 0;
|
|
1687
|
+
const sessionSelect = critiqueViewEl.querySelector("[data-repl-session]");
|
|
1688
|
+
const runtimeSelect = critiqueViewEl.querySelector("[data-repl-runtime]");
|
|
1689
|
+
const target = sessionSelect instanceof HTMLSelectElement && !sessionSelect.disabled
|
|
1690
|
+
? sessionSelect
|
|
1691
|
+
: runtimeSelect;
|
|
1692
|
+
if (target instanceof HTMLElement && typeof target.focus === "function") {
|
|
1693
|
+
try {
|
|
1694
|
+
target.focus({ preventScroll: true });
|
|
1695
|
+
} catch {
|
|
1696
|
+
try { target.focus(); } catch {}
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
setStatus("REPL session controls focused.");
|
|
1547
1700
|
}
|
|
1548
1701
|
|
|
1549
1702
|
function renderReplViewIfActive(options) {
|
|
@@ -1553,6 +1706,7 @@
|
|
|
1553
1706
|
traceRenderRaf = window.requestAnimationFrame(() => {
|
|
1554
1707
|
traceRenderRaf = null;
|
|
1555
1708
|
refreshResponseUi();
|
|
1709
|
+
if (replQuickFocusRequested) focusReplQuickComposer();
|
|
1556
1710
|
});
|
|
1557
1711
|
}
|
|
1558
1712
|
|
|
@@ -1983,14 +2137,17 @@
|
|
|
1983
2137
|
function stripStudioReplSubmissionEcho(delta) {
|
|
1984
2138
|
let value = String(delta || "").replace(/^\s+/, "");
|
|
1985
2139
|
// The raw mirror below remains raw; Studio record cards hide only the
|
|
1986
|
-
// temp-file wrapper used to submit multiline snippets safely.
|
|
1987
|
-
//
|
|
2140
|
+
// temp-file wrapper used to submit multiline snippets safely. Match
|
|
2141
|
+
// both legacy long Studio roots and compact private control roots. Use
|
|
2142
|
+
// the Python wrapper's final double close because IPython may wrap in
|
|
2143
|
+
// the middle of the word `globals` and add continuation prompts.
|
|
1988
2144
|
const submissionEchoPatterns = [
|
|
1989
|
-
/^.*exec\(open\([\s\S]*?pi-studio-re[\s\S]
|
|
1990
|
-
/^.*include\([\s\S]*?pi-studio-re[\s\S]*?\.jl"\)\s*$/gm,
|
|
1991
|
-
/^.*source\([\s\S]*?pi-studio-re[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
1992
|
-
/^.*:script\s+[\s\S]*?pi-studio-re[\s\S]*?\.ghci"?\s*$/gm,
|
|
1993
|
-
/^.*\(do\s+\(load-file\s+[\s\S]*?pi-studio-re[\s\S]*?:pi-studio\/silent\)\s*$/gm,
|
|
2145
|
+
/^.*exec\(open\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\)\)\s*$/gm,
|
|
2146
|
+
/^.*include\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.jl"\)\s*$/gm,
|
|
2147
|
+
/^.*source\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
2148
|
+
/^.*:script\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.ghci"?\s*$/gm,
|
|
2149
|
+
/^.*\(do\s+\(load-file\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?:pi-studio\/silent\)\s*$/gm,
|
|
2150
|
+
/^.*\.\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.sh[\s\S]*?(?:done\.flag|[a-f0-9]{16}\.done).*$/gm,
|
|
1994
2151
|
];
|
|
1995
2152
|
for (const pattern of submissionEchoPatterns) {
|
|
1996
2153
|
value = value.replace(pattern, "");
|
|
@@ -2026,6 +2183,10 @@
|
|
|
2026
2183
|
if (entryIndex < 0) return false;
|
|
2027
2184
|
const entry = replJournalEntries[entryIndex];
|
|
2028
2185
|
if (entry.sessionName && sessionName && entry.sessionName !== sessionName) return false;
|
|
2186
|
+
if (!entry.legacyLocal && (entry.status === "captured" || entry.status === "timeout" || entry.status === "error")) {
|
|
2187
|
+
activeReplJournalEntryId = "";
|
|
2188
|
+
return false;
|
|
2189
|
+
}
|
|
2029
2190
|
const delta = cleanReplCapturedOutput(extractReplTranscriptDelta(entry.beforeTranscript, transcript), entry);
|
|
2030
2191
|
if (!delta.trim()) return false;
|
|
2031
2192
|
if (entry.output === delta && entry.status === "captured") return false;
|
|
@@ -2177,11 +2338,15 @@
|
|
|
2177
2338
|
const session = getActiveReplSessionForCurrentRuntime();
|
|
2178
2339
|
if (!session) {
|
|
2179
2340
|
setStatus("Start or select a " + getReplRuntimeLabel(replRuntime) + " REPL session first.", "warning");
|
|
2180
|
-
return;
|
|
2341
|
+
return null;
|
|
2342
|
+
}
|
|
2343
|
+
if (replBusy) {
|
|
2344
|
+
setStatus("A REPL request is already in progress.", "warning");
|
|
2345
|
+
return null;
|
|
2181
2346
|
}
|
|
2182
2347
|
if (!payload || payload.error) {
|
|
2183
2348
|
setStatus(payload && payload.error ? payload.error : "Nothing to send to REPL.", "warning");
|
|
2184
|
-
return;
|
|
2349
|
+
return null;
|
|
2185
2350
|
}
|
|
2186
2351
|
if (payload.noteOnly) {
|
|
2187
2352
|
if (String(payload.prose || "").trim()) {
|
|
@@ -2200,12 +2365,12 @@
|
|
|
2200
2365
|
} else {
|
|
2201
2366
|
setStatus("No code or prose found to send.", "warning");
|
|
2202
2367
|
}
|
|
2203
|
-
return;
|
|
2368
|
+
return null;
|
|
2204
2369
|
}
|
|
2205
2370
|
const text = String(payload.text || "");
|
|
2206
2371
|
if (!text.trim()) {
|
|
2207
|
-
setStatus("
|
|
2208
|
-
return;
|
|
2372
|
+
setStatus("REPL submission is empty.", "warning");
|
|
2373
|
+
return null;
|
|
2209
2374
|
}
|
|
2210
2375
|
const requestId = makeRequestId();
|
|
2211
2376
|
const journalEntry = addReplJournalEntry({
|
|
@@ -2221,6 +2386,7 @@
|
|
|
2221
2386
|
skippedChunks: payload.skippedChunks,
|
|
2222
2387
|
});
|
|
2223
2388
|
activeReplJournalEntryId = journalEntry.id;
|
|
2389
|
+
replPendingRequestId = requestId;
|
|
2224
2390
|
replBusy = true;
|
|
2225
2391
|
syncActionButtons();
|
|
2226
2392
|
renderReplViewIfActive({ force: true });
|
|
@@ -2231,6 +2397,7 @@
|
|
|
2231
2397
|
requestId,
|
|
2232
2398
|
sessionName: session.sessionName,
|
|
2233
2399
|
text,
|
|
2400
|
+
echoMode: replEchoMode,
|
|
2234
2401
|
journalEntryId: journalEntry.id,
|
|
2235
2402
|
createdAt: journalEntry.createdAt,
|
|
2236
2403
|
label: journalEntry.label,
|
|
@@ -2238,11 +2405,15 @@
|
|
|
2238
2405
|
prose: journalEntry.prose,
|
|
2239
2406
|
skippedChunks: journalEntry.skippedChunks,
|
|
2240
2407
|
})) {
|
|
2241
|
-
|
|
2408
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
2409
|
+
replBusy = Boolean(replPendingRequestId);
|
|
2242
2410
|
replJournalEntries = replJournalEntries.map((entry) => entry.id === journalEntry.id ? { ...entry, status: "error" } : entry);
|
|
2243
2411
|
persistReplJournalEntries();
|
|
2244
2412
|
syncActionButtons();
|
|
2413
|
+
renderReplViewIfActive({ force: true });
|
|
2414
|
+
return null;
|
|
2245
2415
|
}
|
|
2416
|
+
return requestId;
|
|
2246
2417
|
}
|
|
2247
2418
|
|
|
2248
2419
|
function sendEditorTextToRepl(options) {
|
|
@@ -2258,6 +2429,36 @@
|
|
|
2258
2429
|
sendReplPayload(replSendMode === "literate" ? buildLiterateReplSendPayload() : buildRawReplSendPayload());
|
|
2259
2430
|
}
|
|
2260
2431
|
|
|
2432
|
+
function submitReplQuickDraft() {
|
|
2433
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
2434
|
+
if (!session) {
|
|
2435
|
+
setStatus("Start or select a " + getReplRuntimeLabel(replRuntime) + " REPL session first.", "warning");
|
|
2436
|
+
return false;
|
|
2437
|
+
}
|
|
2438
|
+
const key = getReplQuickDraftKey(session);
|
|
2439
|
+
const text = getReplQuickDraft(session);
|
|
2440
|
+
if (!text.trim()) {
|
|
2441
|
+
setStatus("Quick send is empty.", "warning");
|
|
2442
|
+
return false;
|
|
2443
|
+
}
|
|
2444
|
+
if (replQuickPending && replQuickPending.key === key) {
|
|
2445
|
+
setStatus("Quick send is already being submitted to this REPL.", "warning");
|
|
2446
|
+
return false;
|
|
2447
|
+
}
|
|
2448
|
+
const requestId = sendReplPayload({
|
|
2449
|
+
text,
|
|
2450
|
+
prose: "",
|
|
2451
|
+
label: "quick send",
|
|
2452
|
+
mode: "raw",
|
|
2453
|
+
noteOnly: false,
|
|
2454
|
+
skippedChunks: 0,
|
|
2455
|
+
});
|
|
2456
|
+
if (!requestId) return false;
|
|
2457
|
+
replQuickPending = { requestId, key, text };
|
|
2458
|
+
renderReplViewIfActive({ force: true });
|
|
2459
|
+
return true;
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2261
2462
|
function renderTraceViewIfActive() {
|
|
2262
2463
|
if (rightView !== "trace") return;
|
|
2263
2464
|
if (traceRenderRaf !== null) return;
|
|
@@ -2299,9 +2500,15 @@
|
|
|
2299
2500
|
let paneFocusTarget = initialPaneFocusTarget;
|
|
2300
2501
|
let paneSplitPercent = 50;
|
|
2301
2502
|
const PANE_SPLIT_STORAGE_KEY = "piStudio.paneSplitPercent";
|
|
2503
|
+
const PANE_LAYOUT_STORAGE_KEY = "piStudio.paneLayout";
|
|
2504
|
+
const ACTIVITY_TRACKING_STORAGE_KEY = "piStudio.trackActivity";
|
|
2302
2505
|
const PANE_SPLIT_MIN_PERCENT = 20;
|
|
2303
2506
|
const PANE_SPLIT_MAX_PERCENT = 80;
|
|
2304
2507
|
const PANE_SPLIT_SNAP_TO_CENTER_PERCENT = 1;
|
|
2508
|
+
let studioPaneLayout = readStudioPaneLayout();
|
|
2509
|
+
let activityTrackingEnabled = readActivityTrackingEnabled();
|
|
2510
|
+
let activityTrackingOwnsWorkingView = false;
|
|
2511
|
+
let activityTrackingRequestId = "";
|
|
2305
2512
|
const STUDIO_WORKSPACE_MAX_TEXT_CHARS = 900_000;
|
|
2306
2513
|
const STUDIO_WORKSPACE_PERSIST_INTERVAL_MS = 300;
|
|
2307
2514
|
const STUDIO_WORKSPACE_RECOVERY_FETCH_TIMEOUT_MS = 1_500;
|
|
@@ -2837,7 +3044,11 @@
|
|
|
2837
3044
|
: "Off";
|
|
2838
3045
|
const lineLabel = lineNumbersEnabled ? "Lines on" : "Lines off";
|
|
2839
3046
|
const editorSizeLabel = formatStudioFontSizeLabel(editorFontSize);
|
|
2840
|
-
|
|
3047
|
+
const extras = [];
|
|
3048
|
+
if (studioPaneLayout === "editor-top") extras.push("Editor above");
|
|
3049
|
+
if (studioPaneLayout === "response-top") extras.push("Response above");
|
|
3050
|
+
if (activityTrackingEnabled) extras.push("Following activity");
|
|
3051
|
+
setStudioUiRefreshButtonText(studioUiRefreshUi.viewButton, "View: " + syntaxLabel + " · " + lineLabel + " · " + editorSizeLabel + (extras.length ? " · " + extras.join(" · ") : ""));
|
|
2841
3052
|
}
|
|
2842
3053
|
syncStudioUiRefreshReviewTrigger();
|
|
2843
3054
|
}
|
|
@@ -3108,6 +3319,8 @@
|
|
|
3108
3319
|
const viewButton = makeStudioUiRefreshElement("button", "", "View");
|
|
3109
3320
|
const viewMenu = makeStudioUiRefreshMenu(viewButton, "view", "studio-refresh-view-anchor");
|
|
3110
3321
|
appendStudioUiRefreshMenuSection(viewMenu.menu, "Display", [highlightSelect, lineNumbersSelect, editorFontSizeSelect]);
|
|
3322
|
+
if (activityTrackingSelect) activityTrackingSelect.hidden = isEditorOnlyMode || isWatchedFilePreview;
|
|
3323
|
+
appendStudioUiRefreshMenuSection(viewMenu.menu, "Workspace", [studioPaneLayoutSelect, (isEditorOnlyMode || isWatchedFilePreview) ? null : activityTrackingSelect]);
|
|
3111
3324
|
stateEl.appendChild(annotationsMenu.anchor);
|
|
3112
3325
|
stateEl.appendChild(viewMenu.anchor);
|
|
3113
3326
|
|
|
@@ -4348,6 +4561,127 @@
|
|
|
4348
4561
|
setStatus(descriptor.fileBacked ? "Detached editor from file origin into a new draft." : "Reset editor origin to a new draft.", "success");
|
|
4349
4562
|
}
|
|
4350
4563
|
|
|
4564
|
+
function normalizeStudioPaneLayout(value) {
|
|
4565
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
4566
|
+
return normalized === "editor-top" || normalized === "response-top" ? normalized : "side-by-side";
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
function readStudioPaneLayout() {
|
|
4570
|
+
try {
|
|
4571
|
+
return normalizeStudioPaneLayout(window.localStorage && window.localStorage.getItem(PANE_LAYOUT_STORAGE_KEY));
|
|
4572
|
+
} catch {
|
|
4573
|
+
return "side-by-side";
|
|
4574
|
+
}
|
|
4575
|
+
}
|
|
4576
|
+
|
|
4577
|
+
function readActivityTrackingEnabled() {
|
|
4578
|
+
try {
|
|
4579
|
+
return Boolean(window.localStorage && window.localStorage.getItem(ACTIVITY_TRACKING_STORAGE_KEY) === "on");
|
|
4580
|
+
} catch {
|
|
4581
|
+
return false;
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
function isStackedStudioPaneLayout() {
|
|
4586
|
+
return studioPaneLayout === "editor-top" || studioPaneLayout === "response-top";
|
|
4587
|
+
}
|
|
4588
|
+
|
|
4589
|
+
function isResponseFirstStudioPaneLayout() {
|
|
4590
|
+
return studioPaneLayout === "response-top";
|
|
4591
|
+
}
|
|
4592
|
+
|
|
4593
|
+
function getStudioPaneLayoutLabel(value) {
|
|
4594
|
+
if (value === "editor-top") return "Editor above response";
|
|
4595
|
+
if (value === "response-top") return "Response above editor";
|
|
4596
|
+
return "Side by side";
|
|
4597
|
+
}
|
|
4598
|
+
|
|
4599
|
+
function setStudioPaneLayout(value, options) {
|
|
4600
|
+
studioPaneLayout = normalizeStudioPaneLayout(value);
|
|
4601
|
+
document.body.dataset.studioLayout = studioPaneLayout;
|
|
4602
|
+
if (studioPaneLayoutSelect) studioPaneLayoutSelect.value = studioPaneLayout;
|
|
4603
|
+
const mainEl = paneResizeHandleEl && typeof paneResizeHandleEl.closest === "function"
|
|
4604
|
+
? paneResizeHandleEl.closest("main")
|
|
4605
|
+
: null;
|
|
4606
|
+
if (mainEl && leftPaneEl && rightPaneEl && paneResizeHandleEl) {
|
|
4607
|
+
if (isResponseFirstStudioPaneLayout()) {
|
|
4608
|
+
mainEl.append(rightPaneEl, paneResizeHandleEl, leftPaneEl);
|
|
4609
|
+
} else {
|
|
4610
|
+
mainEl.append(leftPaneEl, paneResizeHandleEl, rightPaneEl);
|
|
4611
|
+
}
|
|
4612
|
+
}
|
|
4613
|
+
applyPaneSplitPercent(paneSplitPercent, { persist: false });
|
|
4614
|
+
if (!options || options.persist !== false) {
|
|
4615
|
+
try {
|
|
4616
|
+
if (window.localStorage) window.localStorage.setItem(PANE_LAYOUT_STORAGE_KEY, studioPaneLayout);
|
|
4617
|
+
} catch {
|
|
4618
|
+
// Ignore localStorage failures.
|
|
4619
|
+
}
|
|
4620
|
+
}
|
|
4621
|
+
syncStudioUiRefreshSummaries();
|
|
4622
|
+
if (!options || !options.silent) {
|
|
4623
|
+
setStatus("Pane layout: " + getStudioPaneLayoutLabel(studioPaneLayout) + ".");
|
|
4624
|
+
}
|
|
4625
|
+
return studioPaneLayout;
|
|
4626
|
+
}
|
|
4627
|
+
|
|
4628
|
+
function setActivityTrackingEnabled(enabled, options) {
|
|
4629
|
+
activityTrackingEnabled = Boolean(enabled) && !isEditorOnlyMode && !isWatchedFilePreview;
|
|
4630
|
+
if (activityTrackingSelect) {
|
|
4631
|
+
activityTrackingSelect.value = activityTrackingEnabled ? "on" : "off";
|
|
4632
|
+
activityTrackingSelect.hidden = isEditorOnlyMode || isWatchedFilePreview;
|
|
4633
|
+
}
|
|
4634
|
+
if (!activityTrackingEnabled) {
|
|
4635
|
+
activityTrackingOwnsWorkingView = false;
|
|
4636
|
+
activityTrackingRequestId = "";
|
|
4637
|
+
}
|
|
4638
|
+
if (!options || options.persist !== false) {
|
|
4639
|
+
try {
|
|
4640
|
+
if (window.localStorage) window.localStorage.setItem(ACTIVITY_TRACKING_STORAGE_KEY, activityTrackingEnabled ? "on" : "off");
|
|
4641
|
+
} catch {
|
|
4642
|
+
// Ignore localStorage failures.
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
syncStudioUiRefreshSummaries();
|
|
4646
|
+
if (activityTrackingEnabled && agentBusyFromServer) {
|
|
4647
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
4648
|
+
}
|
|
4649
|
+
if (!options || !options.silent) {
|
|
4650
|
+
setStatus(activityTrackingEnabled
|
|
4651
|
+
? "Studio will show Working during main Pi activity, then return to Response Preview."
|
|
4652
|
+
: "Activity following disabled.");
|
|
4653
|
+
}
|
|
4654
|
+
return activityTrackingEnabled;
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
function beginTrackedStudioActivity(requestId) {
|
|
4658
|
+
if (!activityTrackingEnabled || isEditorOnlyMode || isWatchedFilePreview) return false;
|
|
4659
|
+
const normalizedRequestId = String(requestId || "active");
|
|
4660
|
+
if (!activityTrackingOwnsWorkingView && activityTrackingRequestId
|
|
4661
|
+
&& (activityTrackingRequestId === normalizedRequestId || activityTrackingRequestId === "active")) {
|
|
4662
|
+
if (activityTrackingRequestId === "active" && normalizedRequestId !== "active") {
|
|
4663
|
+
activityTrackingRequestId = normalizedRequestId;
|
|
4664
|
+
}
|
|
4665
|
+
return false;
|
|
4666
|
+
}
|
|
4667
|
+
if (activityTrackingOwnsWorkingView && activityTrackingRequestId === normalizedRequestId && rightView === "trace") return false;
|
|
4668
|
+
activityTrackingRequestId = normalizedRequestId;
|
|
4669
|
+
activityTrackingOwnsWorkingView = true;
|
|
4670
|
+
if (rightView !== "trace") setRightView("trace", { activityTracking: true });
|
|
4671
|
+
return true;
|
|
4672
|
+
}
|
|
4673
|
+
|
|
4674
|
+
function finishTrackedStudioActivity(requestId) {
|
|
4675
|
+
if (!activityTrackingRequestId && !activityTrackingOwnsWorkingView) return false;
|
|
4676
|
+
const normalizedRequestId = String(requestId || "");
|
|
4677
|
+
if (normalizedRequestId && activityTrackingRequestId !== normalizedRequestId) return false;
|
|
4678
|
+
const shouldReturn = activityTrackingEnabled && activityTrackingOwnsWorkingView && rightView === "trace";
|
|
4679
|
+
activityTrackingOwnsWorkingView = false;
|
|
4680
|
+
activityTrackingRequestId = "";
|
|
4681
|
+
if (shouldReturn) setRightView("preview", { activityTracking: true });
|
|
4682
|
+
return shouldReturn;
|
|
4683
|
+
}
|
|
4684
|
+
|
|
4351
4685
|
function clampPaneSplitPercent(value) {
|
|
4352
4686
|
const numeric = Number(value);
|
|
4353
4687
|
if (!Number.isFinite(numeric)) return 50;
|
|
@@ -4358,13 +4692,19 @@
|
|
|
4358
4692
|
function applyPaneSplitPercent(percent, options) {
|
|
4359
4693
|
paneSplitPercent = clampPaneSplitPercent(percent);
|
|
4360
4694
|
const rightPercent = Math.round((100 - paneSplitPercent) * 10) / 10;
|
|
4695
|
+
const dividerPercent = isResponseFirstStudioPaneLayout() ? rightPercent : paneSplitPercent;
|
|
4361
4696
|
document.documentElement.style.setProperty("--studio-left-pane-fr", paneSplitPercent + "fr");
|
|
4362
4697
|
document.documentElement.style.setProperty("--studio-right-pane-fr", rightPercent + "fr");
|
|
4363
4698
|
if (paneResizeHandleEl) {
|
|
4699
|
+
const stacked = isStackedStudioPaneLayout();
|
|
4700
|
+
paneResizeHandleEl.setAttribute("aria-orientation", stacked ? "horizontal" : "vertical");
|
|
4364
4701
|
paneResizeHandleEl.setAttribute("aria-valuemin", String(PANE_SPLIT_MIN_PERCENT));
|
|
4365
4702
|
paneResizeHandleEl.setAttribute("aria-valuemax", String(PANE_SPLIT_MAX_PERCENT));
|
|
4366
|
-
paneResizeHandleEl.setAttribute("aria-valuenow", String(Math.round(
|
|
4703
|
+
paneResizeHandleEl.setAttribute("aria-valuenow", String(Math.round(dividerPercent)));
|
|
4367
4704
|
paneResizeHandleEl.setAttribute("aria-valuetext", "Editor " + Math.round(paneSplitPercent) + " percent, response " + Math.round(rightPercent) + " percent");
|
|
4705
|
+
paneResizeHandleEl.title = stacked
|
|
4706
|
+
? "Drag to resize the stacked panes. Double-click to reset."
|
|
4707
|
+
: "Drag to resize the panes. Double-click to reset.";
|
|
4368
4708
|
}
|
|
4369
4709
|
if (!options || options.persist !== false) {
|
|
4370
4710
|
try {
|
|
@@ -4396,17 +4736,23 @@
|
|
|
4396
4736
|
: null;
|
|
4397
4737
|
if (!mainEl || typeof mainEl.getBoundingClientRect !== "function") return paneSplitPercent;
|
|
4398
4738
|
const rect = mainEl.getBoundingClientRect();
|
|
4399
|
-
|
|
4400
|
-
const
|
|
4401
|
-
|
|
4739
|
+
const stacked = isStackedStudioPaneLayout();
|
|
4740
|
+
const extent = stacked ? rect.height : rect.width;
|
|
4741
|
+
if (!extent) return paneSplitPercent;
|
|
4742
|
+
const coordinate = stacked
|
|
4743
|
+
? ((typeof event.clientY === "number" ? event.clientY : (rect.top + rect.height / 2)) - rect.top)
|
|
4744
|
+
: ((typeof event.clientX === "number" ? event.clientX : (rect.left + rect.width / 2)) - rect.left);
|
|
4745
|
+
const dividerPercent = (coordinate / extent) * 100;
|
|
4746
|
+
return isResponseFirstStudioPaneLayout() ? 100 - dividerPercent : dividerPercent;
|
|
4402
4747
|
}
|
|
4403
4748
|
|
|
4404
4749
|
function setupPaneResizeHandle() {
|
|
4405
4750
|
if (!paneResizeHandleEl) return;
|
|
4751
|
+
setStudioPaneLayout(studioPaneLayout, { persist: false, silent: true });
|
|
4406
4752
|
loadPaneSplitPercent();
|
|
4407
4753
|
let dragging = false;
|
|
4408
4754
|
let movedDuringDrag = false;
|
|
4409
|
-
let
|
|
4755
|
+
let pointerStartPosition = 0;
|
|
4410
4756
|
let activePaneResizePointerId = null;
|
|
4411
4757
|
const finishDrag = () => {
|
|
4412
4758
|
if (!dragging) return;
|
|
@@ -4431,7 +4777,9 @@
|
|
|
4431
4777
|
event.stopPropagation();
|
|
4432
4778
|
dragging = true;
|
|
4433
4779
|
movedDuringDrag = false;
|
|
4434
|
-
|
|
4780
|
+
pointerStartPosition = isStackedStudioPaneLayout()
|
|
4781
|
+
? (typeof event.clientY === "number" ? event.clientY : 0)
|
|
4782
|
+
: (typeof event.clientX === "number" ? event.clientX : 0);
|
|
4435
4783
|
activePaneResizePointerId = event.pointerId;
|
|
4436
4784
|
if (document.body && document.body.classList) document.body.classList.add("pane-resizing");
|
|
4437
4785
|
try {
|
|
@@ -4447,7 +4795,10 @@
|
|
|
4447
4795
|
});
|
|
4448
4796
|
paneResizeHandleEl.addEventListener("pointermove", (event) => {
|
|
4449
4797
|
if (!dragging) return;
|
|
4450
|
-
const
|
|
4798
|
+
const currentPosition = isStackedStudioPaneLayout()
|
|
4799
|
+
? (typeof event.clientY === "number" ? event.clientY : pointerStartPosition)
|
|
4800
|
+
: (typeof event.clientX === "number" ? event.clientX : pointerStartPosition);
|
|
4801
|
+
const movement = Math.abs(currentPosition - pointerStartPosition);
|
|
4451
4802
|
if (!movedDuringDrag && movement < 3) return;
|
|
4452
4803
|
movedDuringDrag = true;
|
|
4453
4804
|
event.preventDefault();
|
|
@@ -4461,25 +4812,26 @@
|
|
|
4461
4812
|
resetPaneSplitPercent();
|
|
4462
4813
|
});
|
|
4463
4814
|
paneResizeHandleEl.addEventListener("keydown", (event) => {
|
|
4815
|
+
const stacked = isStackedStudioPaneLayout();
|
|
4816
|
+
const decreaseKey = stacked ? "ArrowUp" : "ArrowLeft";
|
|
4817
|
+
const increaseKey = stacked ? "ArrowDown" : "ArrowRight";
|
|
4818
|
+
let dividerPercent = isResponseFirstStudioPaneLayout() ? 100 - paneSplitPercent : paneSplitPercent;
|
|
4464
4819
|
if (event.key === "Home") {
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
}
|
|
4469
|
-
if (event.key === "End") {
|
|
4470
|
-
event.preventDefault();
|
|
4471
|
-
applyPaneSplitPercent(PANE_SPLIT_MAX_PERCENT);
|
|
4472
|
-
return;
|
|
4473
|
-
}
|
|
4474
|
-
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
4820
|
+
dividerPercent = PANE_SPLIT_MIN_PERCENT;
|
|
4821
|
+
} else if (event.key === "End") {
|
|
4822
|
+
dividerPercent = PANE_SPLIT_MAX_PERCENT;
|
|
4823
|
+
} else if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
4475
4824
|
event.preventDefault();
|
|
4476
4825
|
resetPaneSplitPercent();
|
|
4477
4826
|
return;
|
|
4827
|
+
} else if (event.key === decreaseKey || event.key === increaseKey) {
|
|
4828
|
+
const step = event.shiftKey ? 10 : 5;
|
|
4829
|
+
dividerPercent += event.key === decreaseKey ? -step : step;
|
|
4830
|
+
} else {
|
|
4831
|
+
return;
|
|
4478
4832
|
}
|
|
4479
|
-
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
4480
4833
|
event.preventDefault();
|
|
4481
|
-
|
|
4482
|
-
applyPaneSplitPercent(paneSplitPercent + (event.key === "ArrowLeft" ? -step : step));
|
|
4834
|
+
applyPaneSplitPercent(isResponseFirstStudioPaneLayout() ? 100 - dividerPercent : dividerPercent);
|
|
4483
4835
|
});
|
|
4484
4836
|
}
|
|
4485
4837
|
|
|
@@ -4556,7 +4908,7 @@
|
|
|
4556
4908
|
function shouldPreserveScrollForPaneActivationEvent(event) {
|
|
4557
4909
|
const target = event && event.target;
|
|
4558
4910
|
if (!(target instanceof Element)) return true;
|
|
4559
|
-
if (target.closest("button, select, input, a, [role='button'], .studio-copy-block-btn, .preview-comment-add, .preview-comment-jump")) {
|
|
4911
|
+
if (target.closest("button, select, input, textarea, a, [role='button'], .studio-copy-block-btn, .preview-comment-add, .preview-comment-jump")) {
|
|
4560
4912
|
return false;
|
|
4561
4913
|
}
|
|
4562
4914
|
return true;
|
|
@@ -4660,8 +5012,10 @@
|
|
|
4660
5012
|
setStatus(label + " is unavailable in editor-only Studio views.", "warning");
|
|
4661
5013
|
return false;
|
|
4662
5014
|
}
|
|
4663
|
-
const snapshot = snapshotStudioScrollablePositions()
|
|
4664
|
-
|
|
5015
|
+
const snapshot = snapshotStudioScrollablePositions().filter((entry) => (
|
|
5016
|
+
!(options && options.focusReplComposer && entry.el === critiqueViewEl)
|
|
5017
|
+
));
|
|
5018
|
+
setRightView(requestedView, options);
|
|
4665
5019
|
scheduleStudioScrollablePositionRestore(snapshot);
|
|
4666
5020
|
if (!options || options.announce !== false) {
|
|
4667
5021
|
setStatus("Right pane view: " + label + ".");
|
|
@@ -5047,6 +5401,16 @@
|
|
|
5047
5401
|
return;
|
|
5048
5402
|
}
|
|
5049
5403
|
|
|
5404
|
+
const isReplShortcut = (key.toLowerCase() === "r" || code === "KeyR")
|
|
5405
|
+
&& (event.metaKey || event.ctrlKey)
|
|
5406
|
+
&& event.altKey
|
|
5407
|
+
&& !event.shiftKey;
|
|
5408
|
+
if (isReplShortcut) {
|
|
5409
|
+
event.preventDefault();
|
|
5410
|
+
switchRightPaneToView("repl", { focusReplComposer: true });
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5050
5414
|
const isSideQuestionsShortcut = (key.toLowerCase() === "q" || code === "KeyQ")
|
|
5051
5415
|
&& (event.metaKey || event.ctrlKey)
|
|
5052
5416
|
&& event.altKey
|
|
@@ -7543,7 +7907,7 @@
|
|
|
7543
7907
|
refreshBtn.type = "button";
|
|
7544
7908
|
refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
|
|
7545
7909
|
refreshBtn.textContent = "Refresh";
|
|
7546
|
-
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
|
|
7910
|
+
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+Shift+R.";
|
|
7547
7911
|
refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
|
|
7548
7912
|
refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
|
|
7549
7913
|
actions.appendChild(refreshBtn);
|
|
@@ -8106,7 +8470,7 @@
|
|
|
8106
8470
|
const matches = (key === "r" || code === "KeyR")
|
|
8107
8471
|
&& (event.metaKey || event.ctrlKey)
|
|
8108
8472
|
&& event.altKey
|
|
8109
|
-
&&
|
|
8473
|
+
&& event.shiftKey;
|
|
8110
8474
|
if (!matches) return false;
|
|
8111
8475
|
if (!refreshVisibleStudioPdfPreviews()) return false;
|
|
8112
8476
|
event.preventDefault();
|
|
@@ -8862,7 +9226,7 @@
|
|
|
8862
9226
|
refreshBtn.type = "button";
|
|
8863
9227
|
refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
|
|
8864
9228
|
refreshBtn.textContent = "Refresh";
|
|
8865
|
-
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
|
|
9229
|
+
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+Shift+R.";
|
|
8866
9230
|
refreshBtn.addEventListener("click", (event) => {
|
|
8867
9231
|
event.preventDefault();
|
|
8868
9232
|
event.stopPropagation();
|
|
@@ -9828,10 +10192,26 @@
|
|
|
9828
10192
|
if (!actionBtn) return;
|
|
9829
10193
|
event.preventDefault();
|
|
9830
10194
|
const action = actionBtn.getAttribute("data-repl-action") || "";
|
|
10195
|
+
if (action === "quick-send") {
|
|
10196
|
+
submitReplQuickDraft();
|
|
10197
|
+
return;
|
|
10198
|
+
}
|
|
10199
|
+
if (action === "jump-controls") {
|
|
10200
|
+
focusReplSessionControls();
|
|
10201
|
+
return;
|
|
10202
|
+
}
|
|
10203
|
+
if (action === "quick-clear") {
|
|
10204
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
10205
|
+
if (session) setReplQuickDraft(session, "");
|
|
10206
|
+
renderReplViewIfActive({ force: true });
|
|
10207
|
+
focusReplQuickComposer();
|
|
10208
|
+
return;
|
|
10209
|
+
}
|
|
9831
10210
|
if (action === "start" || action === "new-session") {
|
|
9832
10211
|
const requestId = makeRequestId();
|
|
9833
10212
|
const command = getCurrentReplStartCommandFromDom();
|
|
9834
10213
|
setReplCommandOverride(replRuntime, command);
|
|
10214
|
+
replPendingRequestId = requestId;
|
|
9835
10215
|
replBusy = true;
|
|
9836
10216
|
replError = "";
|
|
9837
10217
|
replMessage = (action === "new-session" ? "Starting new " : "Starting ") + getReplRuntimeLabel(replRuntime) + " session" + (command ? " with custom command" : "") + "…";
|
|
@@ -9840,8 +10220,10 @@
|
|
|
9840
10220
|
const message = { type: "repl_start_request", requestId, runtime: replRuntime, newSession: action === "new-session" };
|
|
9841
10221
|
if (command) message.command = command;
|
|
9842
10222
|
if (!sendMessage(message)) {
|
|
9843
|
-
|
|
10223
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
10224
|
+
replBusy = Boolean(replPendingRequestId);
|
|
9844
10225
|
syncActionButtons();
|
|
10226
|
+
renderReplViewIfActive({ force: true });
|
|
9845
10227
|
}
|
|
9846
10228
|
return;
|
|
9847
10229
|
}
|
|
@@ -9852,14 +10234,17 @@
|
|
|
9852
10234
|
return;
|
|
9853
10235
|
}
|
|
9854
10236
|
const requestId = makeRequestId();
|
|
10237
|
+
replPendingRequestId = requestId;
|
|
9855
10238
|
replBusy = true;
|
|
9856
10239
|
replError = "";
|
|
9857
10240
|
replMessage = "Stopping " + session.sessionName + "…";
|
|
9858
10241
|
syncActionButtons();
|
|
9859
10242
|
renderReplViewIfActive({ force: true });
|
|
9860
10243
|
if (!sendMessage({ type: "repl_stop_request", requestId, sessionName: session.sessionName })) {
|
|
9861
|
-
|
|
10244
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
10245
|
+
replBusy = Boolean(replPendingRequestId);
|
|
9862
10246
|
syncActionButtons();
|
|
10247
|
+
renderReplViewIfActive({ force: true });
|
|
9863
10248
|
}
|
|
9864
10249
|
return;
|
|
9865
10250
|
}
|
|
@@ -9870,14 +10255,17 @@
|
|
|
9870
10255
|
return;
|
|
9871
10256
|
}
|
|
9872
10257
|
const requestId = makeRequestId();
|
|
10258
|
+
replPendingRequestId = requestId;
|
|
9873
10259
|
replBusy = true;
|
|
9874
10260
|
replError = "";
|
|
9875
10261
|
replMessage = "Interrupting REPL…";
|
|
9876
10262
|
syncActionButtons();
|
|
9877
10263
|
renderReplViewIfActive({ force: true });
|
|
9878
10264
|
if (!sendMessage({ type: "repl_interrupt_request", requestId, sessionName: session.sessionName })) {
|
|
9879
|
-
|
|
10265
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
10266
|
+
replBusy = Boolean(replPendingRequestId);
|
|
9880
10267
|
syncActionButtons();
|
|
10268
|
+
renderReplViewIfActive({ force: true });
|
|
9881
10269
|
}
|
|
9882
10270
|
return;
|
|
9883
10271
|
}
|
|
@@ -9937,6 +10325,39 @@
|
|
|
9937
10325
|
}
|
|
9938
10326
|
}
|
|
9939
10327
|
|
|
10328
|
+
function handleReplPaneFocusIn(event) {
|
|
10329
|
+
if (!replQuickFocusRequested || rightView !== "repl") return;
|
|
10330
|
+
const target = event && event.target;
|
|
10331
|
+
if (target instanceof Element && !target.hasAttribute("data-repl-quick-draft")) {
|
|
10332
|
+
replQuickFocusRequested = false;
|
|
10333
|
+
}
|
|
10334
|
+
}
|
|
10335
|
+
|
|
10336
|
+
function handleReplPaneInput(event) {
|
|
10337
|
+
if (rightView !== "repl") return;
|
|
10338
|
+
const target = event && event.target;
|
|
10339
|
+
if (!(target instanceof HTMLTextAreaElement) || !target.hasAttribute("data-repl-quick-draft")) return;
|
|
10340
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
10341
|
+
if (!session) return;
|
|
10342
|
+
const draft = setReplQuickDraft(session, target.value);
|
|
10343
|
+
if (target.value !== draft) target.value = draft;
|
|
10344
|
+
updateReplQuickComposerActions();
|
|
10345
|
+
}
|
|
10346
|
+
|
|
10347
|
+
function handleReplPaneKeydown(event) {
|
|
10348
|
+
if (rightView !== "repl" || !event || event.isComposing) return;
|
|
10349
|
+
const target = event.target;
|
|
10350
|
+
if (!(target instanceof HTMLTextAreaElement) || !target.hasAttribute("data-repl-quick-draft")) return;
|
|
10351
|
+
const submitShortcut = event.key === "Enter"
|
|
10352
|
+
&& (event.metaKey || event.ctrlKey)
|
|
10353
|
+
&& !event.altKey
|
|
10354
|
+
&& !event.shiftKey;
|
|
10355
|
+
if (!submitShortcut) return;
|
|
10356
|
+
event.preventDefault();
|
|
10357
|
+
event.stopPropagation();
|
|
10358
|
+
submitReplQuickDraft();
|
|
10359
|
+
}
|
|
10360
|
+
|
|
9940
10361
|
function handleReplPaneChange(event) {
|
|
9941
10362
|
if (rightView !== "repl") return;
|
|
9942
10363
|
const target = event.target;
|
|
@@ -9984,8 +10405,11 @@
|
|
|
9984
10405
|
critiqueViewEl.addEventListener("click", handleGitChangesPaneClick);
|
|
9985
10406
|
critiqueViewEl.addEventListener("click", handleStudioQuartoPreviewClick);
|
|
9986
10407
|
critiqueViewEl.addEventListener("click", (event) => { void handleSideQuestionClick(event); });
|
|
10408
|
+
critiqueViewEl.addEventListener("focusin", handleReplPaneFocusIn);
|
|
9987
10409
|
critiqueViewEl.addEventListener("input", handleSideQuestionInput);
|
|
10410
|
+
critiqueViewEl.addEventListener("input", handleReplPaneInput);
|
|
9988
10411
|
critiqueViewEl.addEventListener("keydown", handleSideQuestionKeydown);
|
|
10412
|
+
critiqueViewEl.addEventListener("keydown", handleReplPaneKeydown);
|
|
9989
10413
|
critiqueViewEl.addEventListener("change", handleSideQuestionChange);
|
|
9990
10414
|
critiqueViewEl.addEventListener("change", handleReplPaneChange);
|
|
9991
10415
|
critiqueViewEl.addEventListener("change", (event) => {
|
|
@@ -11721,6 +12145,28 @@
|
|
|
11721
12145
|
return "tmux attach -t " + String(session.sessionName || "");
|
|
11722
12146
|
}
|
|
11723
12147
|
|
|
12148
|
+
function buildReplQuickComposerHtml(activeSession, canSendToActiveSession) {
|
|
12149
|
+
const key = getReplQuickDraftKey(activeSession);
|
|
12150
|
+
const draft = getReplQuickDraft(activeSession);
|
|
12151
|
+
const pending = Boolean(replQuickPending && replQuickPending.key === key);
|
|
12152
|
+
const runtimeLabel = activeSession ? getReplRuntimeLabel(activeSession.runtime) : getReplRuntimeLabel(replRuntime);
|
|
12153
|
+
const tmuxTargetLabel = activeSession && activeSession.target ? ("tmux " + activeSession.target) : "";
|
|
12154
|
+
const targetDetails = activeSession
|
|
12155
|
+
? ([runtimeLabel, activeSession.sessionName, tmuxTargetLabel].filter(Boolean).join(" · "))
|
|
12156
|
+
: (runtimeLabel + " · no session selected");
|
|
12157
|
+
const visibleTargetLabel = activeSession ? activeSession.sessionName : "No session selected";
|
|
12158
|
+
const inputDisabled = !activeSession || !key || pending || replBusy;
|
|
12159
|
+
const sendDisabled = !key || !canSendToActiveSession || wsState === "Disconnected" || pending || !draft.trim();
|
|
12160
|
+
return "<section class='repl-quick-composer' aria-labelledby='replQuickComposerTitle'>"
|
|
12161
|
+
+ "<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>"
|
|
12162
|
+
+ "<span class='repl-quick-hint'>Enter adds a line · Cmd/Ctrl+Enter sends</span></div>"
|
|
12163
|
+
+ "<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>"
|
|
12164
|
+
+ "<div class='repl-quick-actions'><span>Raw send through Studio’s safe submission and Shared REPL Record path.</span><div>"
|
|
12165
|
+
+ "<button type='button' data-repl-action='quick-clear'" + (pending || !draft ? " disabled" : "") + ">Clear</button>"
|
|
12166
|
+
+ "<button class='repl-quick-send-btn' type='button' data-repl-action='quick-send'" + (sendDisabled ? " disabled" : "") + ">" + (pending ? "Sending…" : "Send") + "</button>"
|
|
12167
|
+
+ "</div></div></section>";
|
|
12168
|
+
}
|
|
12169
|
+
|
|
11724
12170
|
function buildReplPanelHtml() {
|
|
11725
12171
|
const runtimeOptions = [
|
|
11726
12172
|
["shell", "Shell"],
|
|
@@ -11772,6 +12218,7 @@
|
|
|
11772
12218
|
+ (replError ? "<div class='repl-notice repl-notice-error'>" + escapeHtml(replError) + "</div>" : "")
|
|
11773
12219
|
+ buildReplJournalHtml(transcript)
|
|
11774
12220
|
+ buildReplStudioActionsHtml()
|
|
12221
|
+
+ buildReplQuickComposerHtml(activeSession, canSendToActiveSession)
|
|
11775
12222
|
+ buildReplMirrorHtml(body, transcript)
|
|
11776
12223
|
+ "</div>";
|
|
11777
12224
|
}
|
|
@@ -15020,9 +15467,14 @@
|
|
|
15020
15467
|
scheduleWorkspacePersistence();
|
|
15021
15468
|
}
|
|
15022
15469
|
|
|
15023
|
-
function setRightView(nextView) {
|
|
15470
|
+
function setRightView(nextView, options) {
|
|
15471
|
+
const automatedActivityChange = Boolean(options && options.activityTracking);
|
|
15472
|
+
if (activityTrackingOwnsWorkingView && !automatedActivityChange) {
|
|
15473
|
+
activityTrackingOwnsWorkingView = false;
|
|
15474
|
+
}
|
|
15024
15475
|
const previousView = rightView;
|
|
15025
15476
|
rightView = normalizeRightViewValue(nextView);
|
|
15477
|
+
if (rightView !== "repl") replQuickFocusRequested = false;
|
|
15026
15478
|
syncRightViewModeOptions();
|
|
15027
15479
|
rightViewSelect.value = rightView;
|
|
15028
15480
|
if (rightView === "trace" && previousView !== "trace") {
|
|
@@ -15061,6 +15513,9 @@
|
|
|
15061
15513
|
if (composer instanceof HTMLTextAreaElement) composer.focus({ preventScroll: true });
|
|
15062
15514
|
}, 0);
|
|
15063
15515
|
}
|
|
15516
|
+
if (rightView === "repl" && (previousView !== "repl" || (options && options.focusReplComposer))) {
|
|
15517
|
+
focusReplQuickComposer();
|
|
15518
|
+
}
|
|
15064
15519
|
scheduleWorkspacePersistence();
|
|
15065
15520
|
}
|
|
15066
15521
|
|
|
@@ -22342,6 +22797,7 @@
|
|
|
22342
22797
|
? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
|
|
22343
22798
|
: "Raw send: Send to REPL uses the selection, or full editor if no selection.";
|
|
22344
22799
|
}
|
|
22800
|
+
syncReplEchoModeSelect(showReplSend);
|
|
22345
22801
|
if (critiqueBtn) {
|
|
22346
22802
|
critiqueBtn.textContent = "Critique text";
|
|
22347
22803
|
critiqueBtn.classList.remove("request-stop-active");
|
|
@@ -22404,6 +22860,7 @@
|
|
|
22404
22860
|
? "Literate send: Send to REPL uses the selection, current fenced code chunk, or all matching chunks if the cursor is outside a chunk."
|
|
22405
22861
|
: "Raw send: Send to REPL uses the selection, or full editor if no selection.";
|
|
22406
22862
|
}
|
|
22863
|
+
syncReplEchoModeSelect(rightView === "repl");
|
|
22407
22864
|
|
|
22408
22865
|
if (critiqueBtn) {
|
|
22409
22866
|
critiqueBtn.textContent = critiqueIsStop ? "Stop" : "Critique text";
|
|
@@ -22934,6 +23391,11 @@
|
|
|
22934
23391
|
}
|
|
22935
23392
|
if (rightView === "side-questions") renderSideQuestionView();
|
|
22936
23393
|
syncAskAsideButton();
|
|
23394
|
+
if (agentBusyFromServer) {
|
|
23395
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
23396
|
+
} else {
|
|
23397
|
+
finishTrackedStudioActivity();
|
|
23398
|
+
}
|
|
22937
23399
|
|
|
22938
23400
|
if (pendingRequestId) {
|
|
22939
23401
|
if (busy) {
|
|
@@ -23010,18 +23472,25 @@
|
|
|
23010
23472
|
const previousError = replError;
|
|
23011
23473
|
const previousMessage = replMessage;
|
|
23012
23474
|
const wasBusy = replBusy;
|
|
23475
|
+
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23476
|
+
const settlesPendingRequest = Boolean(responseRequestId && responseRequestId === replPendingRequestId);
|
|
23477
|
+
const previousActiveLifetimeKey = getReplQuickDraftKey(getActiveReplSession());
|
|
23013
23478
|
replTmuxAvailable = typeof message.tmuxAvailable === "boolean" ? message.tmuxAvailable : replTmuxAvailable;
|
|
23014
23479
|
const sessionsChanged = setReplSessions(message.sessions);
|
|
23015
23480
|
if (typeof message.activeSessionName === "string" && message.activeSessionName.trim()) {
|
|
23016
23481
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
23017
23482
|
}
|
|
23018
23483
|
const journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23484
|
+
reconcileReplQuickPending();
|
|
23019
23485
|
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
23020
23486
|
if (typeof message.transcript === "string") replTranscript = trimReplTranscript(message.transcript);
|
|
23021
23487
|
if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
|
|
23022
23488
|
replError = typeof message.replError === "string" ? message.replError : (typeof message.captureError === "string" ? message.captureError : "");
|
|
23023
23489
|
replMessage = typeof message.replMessage === "string" ? message.replMessage : "";
|
|
23024
|
-
|
|
23490
|
+
if (settlesPendingRequest && replPendingRequestId === responseRequestId) replPendingRequestId = "";
|
|
23491
|
+
replBusy = Boolean(replPendingRequestId);
|
|
23492
|
+
const activeSessionIdentityChanged = Boolean(previousActiveLifetimeKey)
|
|
23493
|
+
&& previousActiveLifetimeKey !== getReplQuickDraftKey(getActiveReplSession());
|
|
23025
23494
|
const controlsChanged = wasBusy
|
|
23026
23495
|
|| sessionsChanged
|
|
23027
23496
|
|| previousTmuxAvailable !== replTmuxAvailable
|
|
@@ -23033,7 +23502,7 @@
|
|
|
23033
23502
|
|| previousMessage !== replMessage
|
|
23034
23503
|
|| journalChanged
|
|
23035
23504
|
|| (!previousCapturedAt && replCapturedAt);
|
|
23036
|
-
if (viewChanged) renderReplViewIfActive();
|
|
23505
|
+
if (viewChanged) renderReplViewIfActive({ force: activeSessionIdentityChanged });
|
|
23037
23506
|
updateReferenceBadge();
|
|
23038
23507
|
return;
|
|
23039
23508
|
}
|
|
@@ -23058,18 +23527,28 @@
|
|
|
23058
23527
|
const previousError = replError;
|
|
23059
23528
|
const previousMessage = replMessage;
|
|
23060
23529
|
const wasBusy = replBusy;
|
|
23530
|
+
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23531
|
+
const settlesPendingRequest = Boolean(responseRequestId && responseRequestId === replPendingRequestId);
|
|
23532
|
+
const previousActiveLifetimeKey = getReplQuickDraftKey(getActiveReplSession());
|
|
23061
23533
|
let sessionsChanged = false;
|
|
23062
23534
|
if (message.session) {
|
|
23063
23535
|
const session = normalizeReplSession(message.session);
|
|
23064
|
-
if (session
|
|
23065
|
-
|
|
23066
|
-
|
|
23536
|
+
if (session) {
|
|
23537
|
+
const existingIndex = replSessions.findIndex((candidate) => candidate.sessionName === session.sessionName);
|
|
23538
|
+
if (existingIndex < 0) {
|
|
23539
|
+
replSessions = [...replSessions, session];
|
|
23540
|
+
sessionsChanged = true;
|
|
23541
|
+
} else if (serializeReplSessionsForCompare([replSessions[existingIndex]]) !== serializeReplSessionsForCompare([session])) {
|
|
23542
|
+
replSessions = replSessions.map((candidate, index) => index === existingIndex ? session : candidate);
|
|
23543
|
+
sessionsChanged = true;
|
|
23544
|
+
}
|
|
23067
23545
|
}
|
|
23068
23546
|
}
|
|
23069
23547
|
if (typeof message.activeSessionName === "string" && message.activeSessionName.trim()) {
|
|
23070
23548
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
23071
23549
|
}
|
|
23072
23550
|
let journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23551
|
+
reconcileReplQuickPending();
|
|
23073
23552
|
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
23074
23553
|
if (typeof message.transcript === "string") {
|
|
23075
23554
|
replTranscript = trimReplTranscript(message.transcript);
|
|
@@ -23081,7 +23560,10 @@
|
|
|
23081
23560
|
if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
|
|
23082
23561
|
replError = typeof message.replError === "string" ? message.replError : "";
|
|
23083
23562
|
if (typeof message.replMessage === "string") replMessage = message.replMessage;
|
|
23084
|
-
|
|
23563
|
+
if (settlesPendingRequest && replPendingRequestId === responseRequestId) replPendingRequestId = "";
|
|
23564
|
+
replBusy = Boolean(replPendingRequestId);
|
|
23565
|
+
const activeSessionIdentityChanged = Boolean(previousActiveLifetimeKey)
|
|
23566
|
+
&& previousActiveLifetimeKey !== getReplQuickDraftKey(getActiveReplSession());
|
|
23085
23567
|
const controlsChanged = wasBusy || sessionsChanged || previousActiveSessionName !== replActiveSessionName;
|
|
23086
23568
|
if (controlsChanged) syncActionButtons();
|
|
23087
23569
|
const viewChanged = controlsChanged
|
|
@@ -23090,7 +23572,7 @@
|
|
|
23090
23572
|
|| previousMessage !== replMessage
|
|
23091
23573
|
|| journalChanged
|
|
23092
23574
|
|| (!previousCapturedAt && replCapturedAt);
|
|
23093
|
-
if (viewChanged) renderReplViewIfActive();
|
|
23575
|
+
if (viewChanged) renderReplViewIfActive({ force: activeSessionIdentityChanged });
|
|
23094
23576
|
updateReferenceBadge();
|
|
23095
23577
|
return;
|
|
23096
23578
|
}
|
|
@@ -23109,9 +23591,12 @@
|
|
|
23109
23591
|
}
|
|
23110
23592
|
|
|
23111
23593
|
if (message.type === "repl_send_ack") {
|
|
23112
|
-
|
|
23594
|
+
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23595
|
+
if (responseRequestId && replPendingRequestId === responseRequestId) replPendingRequestId = "";
|
|
23596
|
+
replBusy = Boolean(replPendingRequestId);
|
|
23113
23597
|
replMessage = "";
|
|
23114
23598
|
replError = "";
|
|
23599
|
+
if (responseRequestId) settleReplQuickPending(responseRequestId, true);
|
|
23115
23600
|
mergeReplJournalEntries(message.journalEntries);
|
|
23116
23601
|
if (typeof message.requestId === "string") {
|
|
23117
23602
|
replJournalEntries = replJournalEntries.map((entry) => entry.requestId === message.requestId ? { ...entry, status: "sent", updatedAt: Date.now() } : entry);
|
|
@@ -23127,6 +23612,8 @@
|
|
|
23127
23612
|
pendingRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
|
|
23128
23613
|
pendingKind = typeof message.kind === "string" ? message.kind : "unknown";
|
|
23129
23614
|
stickyStudioKind = pendingKind;
|
|
23615
|
+
agentBusyFromServer = true;
|
|
23616
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
23130
23617
|
if (pendingKind === "direct") {
|
|
23131
23618
|
studioRunChainActive = true;
|
|
23132
23619
|
}
|
|
@@ -23157,6 +23644,7 @@
|
|
|
23157
23644
|
const busy = Boolean(message.busy);
|
|
23158
23645
|
setBusy(busy);
|
|
23159
23646
|
setWsState(busy ? "Submitting" : "Ready");
|
|
23647
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23160
23648
|
setStatus(typeof message.message === "string" ? message.message : "Compaction completed.", "success");
|
|
23161
23649
|
return;
|
|
23162
23650
|
}
|
|
@@ -23171,6 +23659,7 @@
|
|
|
23171
23659
|
const busy = Boolean(message.busy);
|
|
23172
23660
|
setBusy(busy);
|
|
23173
23661
|
setWsState(busy ? "Submitting" : "Ready");
|
|
23662
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23174
23663
|
setStatus(typeof message.message === "string" ? message.message : "Compaction failed.", "error");
|
|
23175
23664
|
return;
|
|
23176
23665
|
}
|
|
@@ -23190,6 +23679,7 @@
|
|
|
23190
23679
|
pendingRequestId = null;
|
|
23191
23680
|
pendingKind = null;
|
|
23192
23681
|
queuedLatestResponse = null;
|
|
23682
|
+
agentBusyFromServer = false;
|
|
23193
23683
|
setBusy(false);
|
|
23194
23684
|
setWsState("Ready");
|
|
23195
23685
|
|
|
@@ -23206,6 +23696,7 @@
|
|
|
23206
23696
|
if (!appliedFromHistory && typeof message.markdown === "string") {
|
|
23207
23697
|
handleIncomingResponse(message.markdown, responseKind, message.timestamp, message.thinking);
|
|
23208
23698
|
}
|
|
23699
|
+
finishTrackedStudioActivity(completedRequestId || "");
|
|
23209
23700
|
|
|
23210
23701
|
if (responseKind === "critique") {
|
|
23211
23702
|
setStatus("Critique ready.", "success");
|
|
@@ -23508,6 +23999,11 @@
|
|
|
23508
23999
|
|
|
23509
24000
|
setBusy(busy);
|
|
23510
24001
|
setWsState(busy ? "Submitting" : "Ready");
|
|
24002
|
+
if (agentBusyFromServer) {
|
|
24003
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
24004
|
+
} else {
|
|
24005
|
+
finishTrackedStudioActivity();
|
|
24006
|
+
}
|
|
23511
24007
|
|
|
23512
24008
|
if (pendingRequestId) {
|
|
23513
24009
|
if (busy) {
|
|
@@ -23550,6 +24046,7 @@
|
|
|
23550
24046
|
clearArmedTitleAttention(message.requestId);
|
|
23551
24047
|
}
|
|
23552
24048
|
stickyStudioKind = null;
|
|
24049
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23553
24050
|
setBusy(false);
|
|
23554
24051
|
setWsState("Ready");
|
|
23555
24052
|
setStatus(typeof message.message === "string" ? message.message : "Studio is busy.", "warning");
|
|
@@ -23571,9 +24068,14 @@
|
|
|
23571
24068
|
if (typeof message.requestId === "string") {
|
|
23572
24069
|
clearArmedTitleAttention(message.requestId);
|
|
23573
24070
|
}
|
|
23574
|
-
|
|
24071
|
+
const failedReplRequestId = typeof message.requestId === "string" && message.requestId === replPendingRequestId
|
|
24072
|
+
? message.requestId
|
|
24073
|
+
: "";
|
|
24074
|
+
if (failedReplRequestId) {
|
|
24075
|
+
replPendingRequestId = "";
|
|
23575
24076
|
replBusy = false;
|
|
23576
24077
|
replError = typeof message.message === "string" ? message.message : "REPL request failed.";
|
|
24078
|
+
settleReplQuickPending(failedReplRequestId, false);
|
|
23577
24079
|
if (typeof message.requestId === "string") {
|
|
23578
24080
|
replJournalEntries = replJournalEntries.map((entry) => entry.requestId === message.requestId ? { ...entry, status: "error", output: replError, updatedAt: Date.now() } : entry);
|
|
23579
24081
|
persistReplJournalEntries();
|
|
@@ -23581,6 +24083,7 @@
|
|
|
23581
24083
|
renderReplViewIfActive({ force: true });
|
|
23582
24084
|
}
|
|
23583
24085
|
stickyStudioKind = null;
|
|
24086
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23584
24087
|
setBusy(false);
|
|
23585
24088
|
setWsState("Ready");
|
|
23586
24089
|
setStatus(typeof message.message === "string" ? message.message : "Request failed.", "error");
|
|
@@ -24116,6 +24619,18 @@
|
|
|
24116
24619
|
});
|
|
24117
24620
|
}
|
|
24118
24621
|
|
|
24622
|
+
if (studioPaneLayoutSelect) {
|
|
24623
|
+
studioPaneLayoutSelect.addEventListener("change", () => {
|
|
24624
|
+
setStudioPaneLayout(studioPaneLayoutSelect.value);
|
|
24625
|
+
});
|
|
24626
|
+
}
|
|
24627
|
+
|
|
24628
|
+
if (activityTrackingSelect) {
|
|
24629
|
+
activityTrackingSelect.addEventListener("change", () => {
|
|
24630
|
+
setActivityTrackingEnabled(activityTrackingSelect.value === "on");
|
|
24631
|
+
});
|
|
24632
|
+
}
|
|
24633
|
+
|
|
24119
24634
|
if (annotationModeSelect) {
|
|
24120
24635
|
annotationModeSelect.addEventListener("change", () => {
|
|
24121
24636
|
setAnnotationsEnabled(annotationModeSelect.value !== "off");
|
|
@@ -24960,6 +25475,19 @@
|
|
|
24960
25475
|
});
|
|
24961
25476
|
}
|
|
24962
25477
|
|
|
25478
|
+
if (replEchoModeSelect) {
|
|
25479
|
+
replEchoModeSelect.addEventListener("change", () => {
|
|
25480
|
+
setReplEchoMode(replEchoModeSelect.value);
|
|
25481
|
+
syncActionButtons();
|
|
25482
|
+
setStatus(replEchoMode === "full"
|
|
25483
|
+
? "Full pane echo enabled. Bounded submitted source code will remain in raw terminal history."
|
|
25484
|
+
: (replEchoMode === "off"
|
|
25485
|
+
? "Pane echo disabled; new sends will not add alignment anchors to raw terminal history."
|
|
25486
|
+
: "Summary pane echo enabled; short submissions will be shown with compact anchors and a plain output divider."),
|
|
25487
|
+
replEchoMode === "full" ? "warning" : "success");
|
|
25488
|
+
});
|
|
25489
|
+
}
|
|
25490
|
+
|
|
24963
25491
|
copyDraftBtn.addEventListener("click", async () => {
|
|
24964
25492
|
const content = sourceTextEl.value;
|
|
24965
25493
|
if (!content.trim()) {
|
|
@@ -25571,6 +26099,7 @@
|
|
|
25571
26099
|
const storedLineNumbersEnabled = readStoredEditorLineNumbersEnabled();
|
|
25572
26100
|
const initialLineNumbersEnabled = storedLineNumbersEnabled ?? Boolean(lineNumbersSelect && lineNumbersSelect.value === "on");
|
|
25573
26101
|
setLineNumbersEnabled(initialLineNumbersEnabled);
|
|
26102
|
+
setActivityTrackingEnabled(activityTrackingEnabled, { persist: false, silent: true });
|
|
25574
26103
|
|
|
25575
26104
|
const storedResponseHighlightEnabled = readStoredResponseHighlightEnabled();
|
|
25576
26105
|
const initialResponseHighlightEnabled = storedResponseHighlightEnabled ?? Boolean(responseHighlightSelect && responseHighlightSelect.value === "on");
|
|
@@ -25580,6 +26109,7 @@
|
|
|
25580
26109
|
const initialAnnotationsEnabled = storedAnnotationsEnabled ?? Boolean(annotationModeSelect ? annotationModeSelect.value !== "off" : true);
|
|
25581
26110
|
setAnnotationsEnabled(initialAnnotationsEnabled, { silent: true });
|
|
25582
26111
|
setReplSendMode(replSendMode);
|
|
26112
|
+
setReplEchoMode(replEchoMode);
|
|
25583
26113
|
|
|
25584
26114
|
const sessionWorkspaceState = readPersistedWorkspaceState();
|
|
25585
26115
|
const serverWorkspaceRecovery = await readServerWorkspaceRecoveryState();
|