pi-studio 0.9.57 → 0.9.59
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 +25 -0
- package/README.md +19 -5
- package/ROADMAP.md +20 -1
- package/client/studio-client.js +695 -64
- package/client/studio.css +172 -0
- package/index.ts +110 -9
- package/package.json +1 -1
- package/shared/studio-pi-editor-draft.js +49 -0
package/client/studio-client.js
CHANGED
|
@@ -121,6 +121,7 @@
|
|
|
121
121
|
const refreshFromDiskBtn = document.getElementById("refreshFromDiskBtn");
|
|
122
122
|
const clearWorkspaceBtn = document.getElementById("clearWorkspaceBtn");
|
|
123
123
|
const sendEditorBtn = document.getElementById("sendEditorBtn");
|
|
124
|
+
const clearPiEditorBtn = document.getElementById("clearPiEditorBtn");
|
|
124
125
|
const openCompanionBtn = document.getElementById("openCompanionBtn");
|
|
125
126
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
126
127
|
const studioHeaderEl = document.getElementById("studioHeader");
|
|
@@ -148,6 +149,8 @@
|
|
|
148
149
|
const stripAnnotationsBtn = document.getElementById("stripAnnotationsBtn");
|
|
149
150
|
const highlightSelect = document.getElementById("highlightSelect");
|
|
150
151
|
const lineNumbersSelect = document.getElementById("lineNumbersSelect");
|
|
152
|
+
const studioPaneLayoutSelect = document.getElementById("studioPaneLayoutSelect");
|
|
153
|
+
const activityTrackingSelect = document.getElementById("activityTrackingSelect");
|
|
151
154
|
const editorFontSizeSelect = document.getElementById("editorFontSizeSelect");
|
|
152
155
|
const annotationModeSelect = document.getElementById("annotationModeSelect");
|
|
153
156
|
const compactBtn = document.getElementById("compactBtn");
|
|
@@ -359,6 +362,9 @@
|
|
|
359
362
|
const pendingSaveOperations = new Map();
|
|
360
363
|
const pendingCompanionLaunches = new Map();
|
|
361
364
|
const activeStudioTabLaunches = new Set();
|
|
365
|
+
// Deliberately page-memory-only: never persist Pi input-draft handoff fingerprints.
|
|
366
|
+
const pendingPiEditorDraftSnapshots = new Map();
|
|
367
|
+
let linkedPiEditorDraftSnapshot = null;
|
|
362
368
|
let sourceOriginSummaryEl = null;
|
|
363
369
|
let sourceResetOriginBtn = null;
|
|
364
370
|
let sourceOpenCurrentFileTabBtn = null;
|
|
@@ -427,8 +433,8 @@
|
|
|
427
433
|
rightViewSelect.title = isWatchedFilePreview
|
|
428
434
|
? "Read-only watched preview follows this file on disk."
|
|
429
435
|
: (isEditorOnlyMode
|
|
430
|
-
? "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."
|
|
431
|
-
: "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.");
|
|
436
|
+
? "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."
|
|
437
|
+
: "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.");
|
|
432
438
|
}
|
|
433
439
|
|
|
434
440
|
function getInitialRightView(source) {
|
|
@@ -481,6 +487,8 @@
|
|
|
481
487
|
const REPL_TRANSCRIPT_MAX_CHARS = 200_000;
|
|
482
488
|
const REPL_JOURNAL_OUTPUT_MAX_CHARS = 80_000;
|
|
483
489
|
const REPL_JOURNAL_MAX_ENTRIES = 80;
|
|
490
|
+
const REPL_QUICK_DRAFT_MAX_CHARS = 20_000;
|
|
491
|
+
const REPL_QUICK_DRAFT_MAX_SESSIONS = 12;
|
|
484
492
|
const PDF_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
485
493
|
const HTML_EXPORT_FETCH_TIMEOUT_MS = 180_000;
|
|
486
494
|
const HTML_ARTIFACT_MATH_RENDER_FETCH_TIMEOUT_MS = 30_000;
|
|
@@ -588,6 +596,10 @@
|
|
|
588
596
|
let replFollow = true;
|
|
589
597
|
let replPollTimer = null;
|
|
590
598
|
let replBusy = false;
|
|
599
|
+
let replPendingRequestId = "";
|
|
600
|
+
const replQuickDrafts = new Map();
|
|
601
|
+
let replQuickPending = null;
|
|
602
|
+
let replQuickFocusRequested = false;
|
|
591
603
|
let replSendMode = (() => {
|
|
592
604
|
try {
|
|
593
605
|
const stored = window.localStorage && window.localStorage.getItem("piStudio.replSendMode");
|
|
@@ -622,7 +634,7 @@
|
|
|
622
634
|
mode: typeof entry.mode === "string" ? entry.mode : "raw",
|
|
623
635
|
prose: typeof entry.prose === "string" ? entry.prose : "",
|
|
624
636
|
code: typeof entry.code === "string" ? entry.code : "",
|
|
625
|
-
output: typeof entry.output === "string" ? entry.output : "",
|
|
637
|
+
output: typeof entry.output === "string" ? stripStudioReplSubmissionEcho(entry.output) : "",
|
|
626
638
|
beforeTranscript: "",
|
|
627
639
|
status: typeof entry.status === "string" ? entry.status : "sent",
|
|
628
640
|
skippedChunks: Math.max(0, Math.floor(Number(entry.skippedChunks) || 0)),
|
|
@@ -1302,6 +1314,10 @@
|
|
|
1302
1314
|
return {
|
|
1303
1315
|
sessionName,
|
|
1304
1316
|
target: typeof session.target === "string" ? session.target : (sessionName + ":0.0"),
|
|
1317
|
+
tmuxSessionId: typeof session.tmuxSessionId === "string" ? session.tmuxSessionId : "",
|
|
1318
|
+
tmuxSessionCreatedAt: typeof session.tmuxSessionCreatedAt === "number" && Number.isFinite(session.tmuxSessionCreatedAt)
|
|
1319
|
+
? session.tmuxSessionCreatedAt
|
|
1320
|
+
: 0,
|
|
1305
1321
|
runtime: typeof session.runtime === "string" ? session.runtime : "unknown",
|
|
1306
1322
|
label: typeof session.label === "string" && session.label.trim() ? session.label.trim() : sessionName,
|
|
1307
1323
|
source: typeof session.source === "string" ? session.source : "tmux",
|
|
@@ -1430,6 +1446,63 @@
|
|
|
1430
1446
|
: "Display short submissions in full, truncating after 6 lines or 600 characters, with compact anchors and a plain output divider.");
|
|
1431
1447
|
}
|
|
1432
1448
|
|
|
1449
|
+
function getReplQuickDraftKey(session) {
|
|
1450
|
+
const normalized = normalizeReplSession(session);
|
|
1451
|
+
if (!normalized || !normalized.tmuxSessionId || normalized.tmuxSessionCreatedAt <= 0) return "";
|
|
1452
|
+
return [normalizeReplRuntime(normalized.runtime), normalized.tmuxSessionId, normalized.tmuxSessionCreatedAt].join("\u001f");
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
function getReplQuickDraft(session) {
|
|
1456
|
+
const key = getReplQuickDraftKey(session);
|
|
1457
|
+
return key ? String(replQuickDrafts.get(key) || "") : "";
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
function setReplQuickDraft(session, value) {
|
|
1461
|
+
const key = getReplQuickDraftKey(session);
|
|
1462
|
+
if (!key) return "";
|
|
1463
|
+
const draft = String(value || "").slice(0, REPL_QUICK_DRAFT_MAX_CHARS);
|
|
1464
|
+
if (!draft) {
|
|
1465
|
+
replQuickDrafts.delete(key);
|
|
1466
|
+
return "";
|
|
1467
|
+
}
|
|
1468
|
+
if (!replQuickDrafts.has(key) && replQuickDrafts.size >= REPL_QUICK_DRAFT_MAX_SESSIONS) {
|
|
1469
|
+
const oldestKey = replQuickDrafts.keys().next().value;
|
|
1470
|
+
if (oldestKey) replQuickDrafts.delete(oldestKey);
|
|
1471
|
+
}
|
|
1472
|
+
replQuickDrafts.set(key, draft);
|
|
1473
|
+
return draft;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function settleReplQuickPending(requestId, submitted) {
|
|
1477
|
+
if (!replQuickPending || !requestId || replQuickPending.requestId !== requestId) return false;
|
|
1478
|
+
const pending = replQuickPending;
|
|
1479
|
+
replQuickPending = null;
|
|
1480
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
1481
|
+
replBusy = Boolean(replPendingRequestId);
|
|
1482
|
+
if (submitted && replQuickDrafts.get(pending.key) === pending.text) {
|
|
1483
|
+
replQuickDrafts.delete(pending.key);
|
|
1484
|
+
}
|
|
1485
|
+
return true;
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function reconcileReplQuickPending() {
|
|
1489
|
+
if (!replQuickPending) return false;
|
|
1490
|
+
const entry = replJournalEntries.find((candidate) => candidate.requestId === replQuickPending.requestId);
|
|
1491
|
+
if (!entry || entry.status === "sending") return false;
|
|
1492
|
+
return settleReplQuickPending(entry.requestId, entry.status !== "error");
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
function updateReplQuickComposerActions() {
|
|
1496
|
+
if (!critiqueViewEl || rightView !== "repl") return;
|
|
1497
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
1498
|
+
const draft = getReplQuickDraft(session);
|
|
1499
|
+
const pending = Boolean(replQuickPending && replQuickPending.key === getReplQuickDraftKey(session));
|
|
1500
|
+
const sendButton = critiqueViewEl.querySelector("[data-repl-action='quick-send']");
|
|
1501
|
+
const clearButton = critiqueViewEl.querySelector("[data-repl-action='quick-clear']");
|
|
1502
|
+
if (sendButton) sendButton.disabled = !session || replTmuxAvailable === false || pending || replBusy || wsState === "Disconnected" || !draft.trim();
|
|
1503
|
+
if (clearButton) clearButton.disabled = pending || !draft;
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1433
1506
|
function setReplJournalCollapsed(collapsed) {
|
|
1434
1507
|
replJournalCollapsed = Boolean(collapsed);
|
|
1435
1508
|
try {
|
|
@@ -1460,6 +1533,8 @@
|
|
|
1460
1533
|
runtime: session.runtime,
|
|
1461
1534
|
source: session.source,
|
|
1462
1535
|
target: session.target,
|
|
1536
|
+
tmuxSessionId: session.tmuxSessionId,
|
|
1537
|
+
tmuxSessionCreatedAt: session.tmuxSessionCreatedAt,
|
|
1463
1538
|
})));
|
|
1464
1539
|
}
|
|
1465
1540
|
|
|
@@ -1579,7 +1654,53 @@
|
|
|
1579
1654
|
|
|
1580
1655
|
function isReplControlFocused() {
|
|
1581
1656
|
const activeEl = document.activeElement;
|
|
1582
|
-
return activeEl instanceof Element && Boolean(activeEl.closest(".repl-controls"));
|
|
1657
|
+
return activeEl instanceof Element && Boolean(activeEl.closest(".repl-controls, .repl-quick-composer"));
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function focusReplQuickComposer() {
|
|
1661
|
+
replQuickFocusRequested = true;
|
|
1662
|
+
window.setTimeout(() => {
|
|
1663
|
+
if (rightView !== "repl" || !critiqueViewEl) {
|
|
1664
|
+
replQuickFocusRequested = false;
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
const composer = critiqueViewEl.querySelector("[data-repl-quick-draft]");
|
|
1668
|
+
if (composer instanceof HTMLTextAreaElement) {
|
|
1669
|
+
if (!composer.disabled) {
|
|
1670
|
+
try {
|
|
1671
|
+
composer.focus({ preventScroll: true });
|
|
1672
|
+
} catch {
|
|
1673
|
+
try { composer.focus(); } catch {}
|
|
1674
|
+
}
|
|
1675
|
+
if (document.activeElement === composer) replQuickFocusRequested = false;
|
|
1676
|
+
}
|
|
1677
|
+
if (typeof composer.scrollIntoView === "function") {
|
|
1678
|
+
try {
|
|
1679
|
+
composer.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
1680
|
+
} catch {
|
|
1681
|
+
try { composer.scrollIntoView(); } catch {}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}, 0);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
function focusReplSessionControls() {
|
|
1689
|
+
if (rightView !== "repl" || !critiqueViewEl) return;
|
|
1690
|
+
critiqueViewEl.scrollTop = 0;
|
|
1691
|
+
const sessionSelect = critiqueViewEl.querySelector("[data-repl-session]");
|
|
1692
|
+
const runtimeSelect = critiqueViewEl.querySelector("[data-repl-runtime]");
|
|
1693
|
+
const target = sessionSelect instanceof HTMLSelectElement && !sessionSelect.disabled
|
|
1694
|
+
? sessionSelect
|
|
1695
|
+
: runtimeSelect;
|
|
1696
|
+
if (target instanceof HTMLElement && typeof target.focus === "function") {
|
|
1697
|
+
try {
|
|
1698
|
+
target.focus({ preventScroll: true });
|
|
1699
|
+
} catch {
|
|
1700
|
+
try { target.focus(); } catch {}
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
setStatus("REPL session controls focused.");
|
|
1583
1704
|
}
|
|
1584
1705
|
|
|
1585
1706
|
function renderReplViewIfActive(options) {
|
|
@@ -1589,6 +1710,7 @@
|
|
|
1589
1710
|
traceRenderRaf = window.requestAnimationFrame(() => {
|
|
1590
1711
|
traceRenderRaf = null;
|
|
1591
1712
|
refreshResponseUi();
|
|
1713
|
+
if (replQuickFocusRequested) focusReplQuickComposer();
|
|
1592
1714
|
});
|
|
1593
1715
|
}
|
|
1594
1716
|
|
|
@@ -2020,9 +2142,11 @@
|
|
|
2020
2142
|
let value = String(delta || "").replace(/^\s+/, "");
|
|
2021
2143
|
// The raw mirror below remains raw; Studio record cards hide only the
|
|
2022
2144
|
// temp-file wrapper used to submit multiline snippets safely. Match
|
|
2023
|
-
// both legacy long Studio roots and compact private control roots.
|
|
2145
|
+
// both legacy long Studio roots and compact private control roots. Use
|
|
2146
|
+
// the Python wrapper's final double close because IPython may wrap in
|
|
2147
|
+
// the middle of the word `globals` and add continuation prompts.
|
|
2024
2148
|
const submissionEchoPatterns = [
|
|
2025
|
-
/^.*exec\(open\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]
|
|
2149
|
+
/^.*exec\(open\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\)\)\s*$/gm,
|
|
2026
2150
|
/^.*include\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.jl"\)\s*$/gm,
|
|
2027
2151
|
/^.*source\([\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?local\s*=\s*\.GlobalEnv\)\s*$/gm,
|
|
2028
2152
|
/^.*:script\s+[\s\S]*?(?:pi-studio-re|pi-rc-)[\s\S]*?\.ghci"?\s*$/gm,
|
|
@@ -2218,11 +2342,15 @@
|
|
|
2218
2342
|
const session = getActiveReplSessionForCurrentRuntime();
|
|
2219
2343
|
if (!session) {
|
|
2220
2344
|
setStatus("Start or select a " + getReplRuntimeLabel(replRuntime) + " REPL session first.", "warning");
|
|
2221
|
-
return;
|
|
2345
|
+
return null;
|
|
2346
|
+
}
|
|
2347
|
+
if (replBusy) {
|
|
2348
|
+
setStatus("A REPL request is already in progress.", "warning");
|
|
2349
|
+
return null;
|
|
2222
2350
|
}
|
|
2223
2351
|
if (!payload || payload.error) {
|
|
2224
2352
|
setStatus(payload && payload.error ? payload.error : "Nothing to send to REPL.", "warning");
|
|
2225
|
-
return;
|
|
2353
|
+
return null;
|
|
2226
2354
|
}
|
|
2227
2355
|
if (payload.noteOnly) {
|
|
2228
2356
|
if (String(payload.prose || "").trim()) {
|
|
@@ -2241,12 +2369,12 @@
|
|
|
2241
2369
|
} else {
|
|
2242
2370
|
setStatus("No code or prose found to send.", "warning");
|
|
2243
2371
|
}
|
|
2244
|
-
return;
|
|
2372
|
+
return null;
|
|
2245
2373
|
}
|
|
2246
2374
|
const text = String(payload.text || "");
|
|
2247
2375
|
if (!text.trim()) {
|
|
2248
|
-
setStatus("
|
|
2249
|
-
return;
|
|
2376
|
+
setStatus("REPL submission is empty.", "warning");
|
|
2377
|
+
return null;
|
|
2250
2378
|
}
|
|
2251
2379
|
const requestId = makeRequestId();
|
|
2252
2380
|
const journalEntry = addReplJournalEntry({
|
|
@@ -2262,6 +2390,7 @@
|
|
|
2262
2390
|
skippedChunks: payload.skippedChunks,
|
|
2263
2391
|
});
|
|
2264
2392
|
activeReplJournalEntryId = journalEntry.id;
|
|
2393
|
+
replPendingRequestId = requestId;
|
|
2265
2394
|
replBusy = true;
|
|
2266
2395
|
syncActionButtons();
|
|
2267
2396
|
renderReplViewIfActive({ force: true });
|
|
@@ -2280,11 +2409,15 @@
|
|
|
2280
2409
|
prose: journalEntry.prose,
|
|
2281
2410
|
skippedChunks: journalEntry.skippedChunks,
|
|
2282
2411
|
})) {
|
|
2283
|
-
|
|
2412
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
2413
|
+
replBusy = Boolean(replPendingRequestId);
|
|
2284
2414
|
replJournalEntries = replJournalEntries.map((entry) => entry.id === journalEntry.id ? { ...entry, status: "error" } : entry);
|
|
2285
2415
|
persistReplJournalEntries();
|
|
2286
2416
|
syncActionButtons();
|
|
2417
|
+
renderReplViewIfActive({ force: true });
|
|
2418
|
+
return null;
|
|
2287
2419
|
}
|
|
2420
|
+
return requestId;
|
|
2288
2421
|
}
|
|
2289
2422
|
|
|
2290
2423
|
function sendEditorTextToRepl(options) {
|
|
@@ -2300,6 +2433,36 @@
|
|
|
2300
2433
|
sendReplPayload(replSendMode === "literate" ? buildLiterateReplSendPayload() : buildRawReplSendPayload());
|
|
2301
2434
|
}
|
|
2302
2435
|
|
|
2436
|
+
function submitReplQuickDraft() {
|
|
2437
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
2438
|
+
if (!session) {
|
|
2439
|
+
setStatus("Start or select a " + getReplRuntimeLabel(replRuntime) + " REPL session first.", "warning");
|
|
2440
|
+
return false;
|
|
2441
|
+
}
|
|
2442
|
+
const key = getReplQuickDraftKey(session);
|
|
2443
|
+
const text = getReplQuickDraft(session);
|
|
2444
|
+
if (!text.trim()) {
|
|
2445
|
+
setStatus("Quick send is empty.", "warning");
|
|
2446
|
+
return false;
|
|
2447
|
+
}
|
|
2448
|
+
if (replQuickPending && replQuickPending.key === key) {
|
|
2449
|
+
setStatus("Quick send is already being submitted to this REPL.", "warning");
|
|
2450
|
+
return false;
|
|
2451
|
+
}
|
|
2452
|
+
const requestId = sendReplPayload({
|
|
2453
|
+
text,
|
|
2454
|
+
prose: "",
|
|
2455
|
+
label: "quick send",
|
|
2456
|
+
mode: "raw",
|
|
2457
|
+
noteOnly: false,
|
|
2458
|
+
skippedChunks: 0,
|
|
2459
|
+
});
|
|
2460
|
+
if (!requestId) return false;
|
|
2461
|
+
replQuickPending = { requestId, key, text };
|
|
2462
|
+
renderReplViewIfActive({ force: true });
|
|
2463
|
+
return true;
|
|
2464
|
+
}
|
|
2465
|
+
|
|
2303
2466
|
function renderTraceViewIfActive() {
|
|
2304
2467
|
if (rightView !== "trace") return;
|
|
2305
2468
|
if (traceRenderRaf !== null) return;
|
|
@@ -2341,9 +2504,15 @@
|
|
|
2341
2504
|
let paneFocusTarget = initialPaneFocusTarget;
|
|
2342
2505
|
let paneSplitPercent = 50;
|
|
2343
2506
|
const PANE_SPLIT_STORAGE_KEY = "piStudio.paneSplitPercent";
|
|
2507
|
+
const PANE_LAYOUT_STORAGE_KEY = "piStudio.paneLayout";
|
|
2508
|
+
const ACTIVITY_TRACKING_STORAGE_KEY = "piStudio.trackActivity";
|
|
2344
2509
|
const PANE_SPLIT_MIN_PERCENT = 20;
|
|
2345
2510
|
const PANE_SPLIT_MAX_PERCENT = 80;
|
|
2346
2511
|
const PANE_SPLIT_SNAP_TO_CENTER_PERCENT = 1;
|
|
2512
|
+
let studioPaneLayout = readStudioPaneLayout();
|
|
2513
|
+
let activityTrackingEnabled = readActivityTrackingEnabled();
|
|
2514
|
+
let activityTrackingOwnsWorkingView = false;
|
|
2515
|
+
let activityTrackingRequestId = "";
|
|
2347
2516
|
const STUDIO_WORKSPACE_MAX_TEXT_CHARS = 900_000;
|
|
2348
2517
|
const STUDIO_WORKSPACE_PERSIST_INTERVAL_MS = 300;
|
|
2349
2518
|
const STUDIO_WORKSPACE_RECOVERY_FETCH_TIMEOUT_MS = 1_500;
|
|
@@ -2879,7 +3048,11 @@
|
|
|
2879
3048
|
: "Off";
|
|
2880
3049
|
const lineLabel = lineNumbersEnabled ? "Lines on" : "Lines off";
|
|
2881
3050
|
const editorSizeLabel = formatStudioFontSizeLabel(editorFontSize);
|
|
2882
|
-
|
|
3051
|
+
const extras = [];
|
|
3052
|
+
if (studioPaneLayout === "editor-top") extras.push("Editor above");
|
|
3053
|
+
if (studioPaneLayout === "response-top") extras.push("Response above");
|
|
3054
|
+
if (activityTrackingEnabled) extras.push("Following activity");
|
|
3055
|
+
setStudioUiRefreshButtonText(studioUiRefreshUi.viewButton, "View: " + syntaxLabel + " · " + lineLabel + " · " + editorSizeLabel + (extras.length ? " · " + extras.join(" · ") : ""));
|
|
2883
3056
|
}
|
|
2884
3057
|
syncStudioUiRefreshReviewTrigger();
|
|
2885
3058
|
}
|
|
@@ -3017,7 +3190,9 @@
|
|
|
3017
3190
|
if (sendEditorBtn) sendEditorBtn.textContent = "Send current text to Pi editor";
|
|
3018
3191
|
appendStudioUiRefreshMenuSection(contextMenu.menu, "Document", [sourceOriginSummaryEl, sourceResetOriginBtn, sourceOpenCurrentFileTabBtn, sourceOpenCurrentTextCopyTabBtn]);
|
|
3019
3192
|
appendStudioUiRefreshMenuSection(contextMenu.menu, "Working directory", [resourceDirBtn, resourceDirLabel, resourceDirInputWrap]);
|
|
3020
|
-
if (!isEditorOnlyMode
|
|
3193
|
+
if (!isEditorOnlyMode) {
|
|
3194
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Pi editor", [sendEditorBtn, clearPiEditorBtn]);
|
|
3195
|
+
}
|
|
3021
3196
|
const cursorContextBtn = makeStudioUiRefreshElement("button", "completion-context-option", "Editor only");
|
|
3022
3197
|
cursorContextBtn.type = "button";
|
|
3023
3198
|
cursorContextBtn.setAttribute("data-completion-context-mode", "cursor");
|
|
@@ -3150,6 +3325,8 @@
|
|
|
3150
3325
|
const viewButton = makeStudioUiRefreshElement("button", "", "View");
|
|
3151
3326
|
const viewMenu = makeStudioUiRefreshMenu(viewButton, "view", "studio-refresh-view-anchor");
|
|
3152
3327
|
appendStudioUiRefreshMenuSection(viewMenu.menu, "Display", [highlightSelect, lineNumbersSelect, editorFontSizeSelect]);
|
|
3328
|
+
if (activityTrackingSelect) activityTrackingSelect.hidden = isEditorOnlyMode || isWatchedFilePreview;
|
|
3329
|
+
appendStudioUiRefreshMenuSection(viewMenu.menu, "Workspace", [studioPaneLayoutSelect, (isEditorOnlyMode || isWatchedFilePreview) ? null : activityTrackingSelect]);
|
|
3153
3330
|
stateEl.appendChild(annotationsMenu.anchor);
|
|
3154
3331
|
stateEl.appendChild(viewMenu.anchor);
|
|
3155
3332
|
|
|
@@ -3374,6 +3551,7 @@
|
|
|
3374
3551
|
if (kind === "compact") return "compacting context";
|
|
3375
3552
|
if (kind === "send_to_editor") return "sending to pi editor";
|
|
3376
3553
|
if (kind === "get_from_editor") return "loading from pi editor";
|
|
3554
|
+
if (kind === "clear_pi_editor") return "clearing Pi editor text";
|
|
3377
3555
|
if (kind === "load_git_diff") return "loading git diff";
|
|
3378
3556
|
if (kind === "open_editor_only") return "opening companion editor";
|
|
3379
3557
|
if (kind === "refresh_from_disk") return "refreshing from disk";
|
|
@@ -3632,6 +3810,7 @@
|
|
|
3632
3810
|
if (kind === "compact") return "Compacting…";
|
|
3633
3811
|
if (kind === "send_to_editor") return "Sending to editor…";
|
|
3634
3812
|
if (kind === "get_from_editor") return "Loading from editor…";
|
|
3813
|
+
if (kind === "clear_pi_editor") return "Clearing Pi editor text…";
|
|
3635
3814
|
if (kind === "load_git_diff") return "Loading git diff…";
|
|
3636
3815
|
if (kind === "refresh_from_disk") return "Refreshing from disk…";
|
|
3637
3816
|
if (kind === "save_as" || kind === "save_over") return "Saving…";
|
|
@@ -4293,6 +4472,58 @@
|
|
|
4293
4472
|
return /^sha256:[a-f0-9]{64}$/.test(revision) ? revision : null;
|
|
4294
4473
|
}
|
|
4295
4474
|
|
|
4475
|
+
function normalizePiEditorDraftSnapshot(value) {
|
|
4476
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
4477
|
+
const fingerprint = normalizeStudioDiskRevision(value.fingerprint);
|
|
4478
|
+
const byteLength = value.byteLength;
|
|
4479
|
+
if (!fingerprint || typeof byteLength !== "number" || !Number.isSafeInteger(byteLength) || byteLength < 0) return null;
|
|
4480
|
+
return { fingerprint, byteLength };
|
|
4481
|
+
}
|
|
4482
|
+
|
|
4483
|
+
function piEditorDraftSnapshotsMatch(left, right) {
|
|
4484
|
+
const normalizedLeft = normalizePiEditorDraftSnapshot(left);
|
|
4485
|
+
const normalizedRight = normalizePiEditorDraftSnapshot(right);
|
|
4486
|
+
return Boolean(
|
|
4487
|
+
normalizedLeft
|
|
4488
|
+
&& normalizedRight
|
|
4489
|
+
&& normalizedLeft.fingerprint === normalizedRight.fingerprint
|
|
4490
|
+
&& normalizedLeft.byteLength === normalizedRight.byteLength
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
|
|
4494
|
+
function setLinkedPiEditorDraftSnapshot(value, hasContent) {
|
|
4495
|
+
linkedPiEditorDraftSnapshot = hasContent ? normalizePiEditorDraftSnapshot(value) : null;
|
|
4496
|
+
}
|
|
4497
|
+
|
|
4498
|
+
function clearLinkedPiEditorDraftSnapshot() {
|
|
4499
|
+
linkedPiEditorDraftSnapshot = null;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
function reserveLinkedPiEditorDraftSnapshot(requestId) {
|
|
4503
|
+
const snapshot = normalizePiEditorDraftSnapshot(linkedPiEditorDraftSnapshot);
|
|
4504
|
+
if (!snapshot || typeof requestId !== "string" || !requestId) return null;
|
|
4505
|
+
linkedPiEditorDraftSnapshot = null;
|
|
4506
|
+
pendingPiEditorDraftSnapshots.set(requestId, snapshot);
|
|
4507
|
+
return snapshot;
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
function restoreReservedPiEditorDraftSnapshot(requestId) {
|
|
4511
|
+
if (typeof requestId !== "string" || !requestId) return;
|
|
4512
|
+
const snapshot = pendingPiEditorDraftSnapshots.get(requestId);
|
|
4513
|
+
if (!snapshot) return;
|
|
4514
|
+
pendingPiEditorDraftSnapshots.delete(requestId);
|
|
4515
|
+
if (!linkedPiEditorDraftSnapshot) linkedPiEditorDraftSnapshot = snapshot;
|
|
4516
|
+
}
|
|
4517
|
+
|
|
4518
|
+
function settleReservedPiEditorDraftSnapshot(requestId, reportedSnapshot) {
|
|
4519
|
+
if (typeof requestId !== "string" || !requestId) return false;
|
|
4520
|
+
const snapshot = pendingPiEditorDraftSnapshots.get(requestId);
|
|
4521
|
+
if (!snapshot) return false;
|
|
4522
|
+
if (reportedSnapshot && !piEditorDraftSnapshotsMatch(snapshot, reportedSnapshot)) return false;
|
|
4523
|
+
pendingPiEditorDraftSnapshots.delete(requestId);
|
|
4524
|
+
return true;
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4296
4527
|
function markFileBackedBaseline(text, diskRevision) {
|
|
4297
4528
|
fileBackedBaselineText = String(text || "");
|
|
4298
4529
|
fileBackedDiskRevision = normalizeStudioDiskRevision(diskRevision);
|
|
@@ -4390,6 +4621,127 @@
|
|
|
4390
4621
|
setStatus(descriptor.fileBacked ? "Detached editor from file origin into a new draft." : "Reset editor origin to a new draft.", "success");
|
|
4391
4622
|
}
|
|
4392
4623
|
|
|
4624
|
+
function normalizeStudioPaneLayout(value) {
|
|
4625
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
4626
|
+
return normalized === "editor-top" || normalized === "response-top" ? normalized : "side-by-side";
|
|
4627
|
+
}
|
|
4628
|
+
|
|
4629
|
+
function readStudioPaneLayout() {
|
|
4630
|
+
try {
|
|
4631
|
+
return normalizeStudioPaneLayout(window.localStorage && window.localStorage.getItem(PANE_LAYOUT_STORAGE_KEY));
|
|
4632
|
+
} catch {
|
|
4633
|
+
return "side-by-side";
|
|
4634
|
+
}
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4637
|
+
function readActivityTrackingEnabled() {
|
|
4638
|
+
try {
|
|
4639
|
+
return Boolean(window.localStorage && window.localStorage.getItem(ACTIVITY_TRACKING_STORAGE_KEY) === "on");
|
|
4640
|
+
} catch {
|
|
4641
|
+
return false;
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
4644
|
+
|
|
4645
|
+
function isStackedStudioPaneLayout() {
|
|
4646
|
+
return studioPaneLayout === "editor-top" || studioPaneLayout === "response-top";
|
|
4647
|
+
}
|
|
4648
|
+
|
|
4649
|
+
function isResponseFirstStudioPaneLayout() {
|
|
4650
|
+
return studioPaneLayout === "response-top";
|
|
4651
|
+
}
|
|
4652
|
+
|
|
4653
|
+
function getStudioPaneLayoutLabel(value) {
|
|
4654
|
+
if (value === "editor-top") return "Editor above response";
|
|
4655
|
+
if (value === "response-top") return "Response above editor";
|
|
4656
|
+
return "Side by side";
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
function setStudioPaneLayout(value, options) {
|
|
4660
|
+
studioPaneLayout = normalizeStudioPaneLayout(value);
|
|
4661
|
+
document.body.dataset.studioLayout = studioPaneLayout;
|
|
4662
|
+
if (studioPaneLayoutSelect) studioPaneLayoutSelect.value = studioPaneLayout;
|
|
4663
|
+
const mainEl = paneResizeHandleEl && typeof paneResizeHandleEl.closest === "function"
|
|
4664
|
+
? paneResizeHandleEl.closest("main")
|
|
4665
|
+
: null;
|
|
4666
|
+
if (mainEl && leftPaneEl && rightPaneEl && paneResizeHandleEl) {
|
|
4667
|
+
if (isResponseFirstStudioPaneLayout()) {
|
|
4668
|
+
mainEl.append(rightPaneEl, paneResizeHandleEl, leftPaneEl);
|
|
4669
|
+
} else {
|
|
4670
|
+
mainEl.append(leftPaneEl, paneResizeHandleEl, rightPaneEl);
|
|
4671
|
+
}
|
|
4672
|
+
}
|
|
4673
|
+
applyPaneSplitPercent(paneSplitPercent, { persist: false });
|
|
4674
|
+
if (!options || options.persist !== false) {
|
|
4675
|
+
try {
|
|
4676
|
+
if (window.localStorage) window.localStorage.setItem(PANE_LAYOUT_STORAGE_KEY, studioPaneLayout);
|
|
4677
|
+
} catch {
|
|
4678
|
+
// Ignore localStorage failures.
|
|
4679
|
+
}
|
|
4680
|
+
}
|
|
4681
|
+
syncStudioUiRefreshSummaries();
|
|
4682
|
+
if (!options || !options.silent) {
|
|
4683
|
+
setStatus("Pane layout: " + getStudioPaneLayoutLabel(studioPaneLayout) + ".");
|
|
4684
|
+
}
|
|
4685
|
+
return studioPaneLayout;
|
|
4686
|
+
}
|
|
4687
|
+
|
|
4688
|
+
function setActivityTrackingEnabled(enabled, options) {
|
|
4689
|
+
activityTrackingEnabled = Boolean(enabled) && !isEditorOnlyMode && !isWatchedFilePreview;
|
|
4690
|
+
if (activityTrackingSelect) {
|
|
4691
|
+
activityTrackingSelect.value = activityTrackingEnabled ? "on" : "off";
|
|
4692
|
+
activityTrackingSelect.hidden = isEditorOnlyMode || isWatchedFilePreview;
|
|
4693
|
+
}
|
|
4694
|
+
if (!activityTrackingEnabled) {
|
|
4695
|
+
activityTrackingOwnsWorkingView = false;
|
|
4696
|
+
activityTrackingRequestId = "";
|
|
4697
|
+
}
|
|
4698
|
+
if (!options || options.persist !== false) {
|
|
4699
|
+
try {
|
|
4700
|
+
if (window.localStorage) window.localStorage.setItem(ACTIVITY_TRACKING_STORAGE_KEY, activityTrackingEnabled ? "on" : "off");
|
|
4701
|
+
} catch {
|
|
4702
|
+
// Ignore localStorage failures.
|
|
4703
|
+
}
|
|
4704
|
+
}
|
|
4705
|
+
syncStudioUiRefreshSummaries();
|
|
4706
|
+
if (activityTrackingEnabled && agentBusyFromServer) {
|
|
4707
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
4708
|
+
}
|
|
4709
|
+
if (!options || !options.silent) {
|
|
4710
|
+
setStatus(activityTrackingEnabled
|
|
4711
|
+
? "Studio will show Working during main Pi activity, then return to Response Preview."
|
|
4712
|
+
: "Activity following disabled.");
|
|
4713
|
+
}
|
|
4714
|
+
return activityTrackingEnabled;
|
|
4715
|
+
}
|
|
4716
|
+
|
|
4717
|
+
function beginTrackedStudioActivity(requestId) {
|
|
4718
|
+
if (!activityTrackingEnabled || isEditorOnlyMode || isWatchedFilePreview) return false;
|
|
4719
|
+
const normalizedRequestId = String(requestId || "active");
|
|
4720
|
+
if (!activityTrackingOwnsWorkingView && activityTrackingRequestId
|
|
4721
|
+
&& (activityTrackingRequestId === normalizedRequestId || activityTrackingRequestId === "active")) {
|
|
4722
|
+
if (activityTrackingRequestId === "active" && normalizedRequestId !== "active") {
|
|
4723
|
+
activityTrackingRequestId = normalizedRequestId;
|
|
4724
|
+
}
|
|
4725
|
+
return false;
|
|
4726
|
+
}
|
|
4727
|
+
if (activityTrackingOwnsWorkingView && activityTrackingRequestId === normalizedRequestId && rightView === "trace") return false;
|
|
4728
|
+
activityTrackingRequestId = normalizedRequestId;
|
|
4729
|
+
activityTrackingOwnsWorkingView = true;
|
|
4730
|
+
if (rightView !== "trace") setRightView("trace", { activityTracking: true });
|
|
4731
|
+
return true;
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
function finishTrackedStudioActivity(requestId) {
|
|
4735
|
+
if (!activityTrackingRequestId && !activityTrackingOwnsWorkingView) return false;
|
|
4736
|
+
const normalizedRequestId = String(requestId || "");
|
|
4737
|
+
if (normalizedRequestId && activityTrackingRequestId !== normalizedRequestId) return false;
|
|
4738
|
+
const shouldReturn = activityTrackingEnabled && activityTrackingOwnsWorkingView && rightView === "trace";
|
|
4739
|
+
activityTrackingOwnsWorkingView = false;
|
|
4740
|
+
activityTrackingRequestId = "";
|
|
4741
|
+
if (shouldReturn) setRightView("preview", { activityTracking: true });
|
|
4742
|
+
return shouldReturn;
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4393
4745
|
function clampPaneSplitPercent(value) {
|
|
4394
4746
|
const numeric = Number(value);
|
|
4395
4747
|
if (!Number.isFinite(numeric)) return 50;
|
|
@@ -4400,13 +4752,19 @@
|
|
|
4400
4752
|
function applyPaneSplitPercent(percent, options) {
|
|
4401
4753
|
paneSplitPercent = clampPaneSplitPercent(percent);
|
|
4402
4754
|
const rightPercent = Math.round((100 - paneSplitPercent) * 10) / 10;
|
|
4755
|
+
const dividerPercent = isResponseFirstStudioPaneLayout() ? rightPercent : paneSplitPercent;
|
|
4403
4756
|
document.documentElement.style.setProperty("--studio-left-pane-fr", paneSplitPercent + "fr");
|
|
4404
4757
|
document.documentElement.style.setProperty("--studio-right-pane-fr", rightPercent + "fr");
|
|
4405
4758
|
if (paneResizeHandleEl) {
|
|
4759
|
+
const stacked = isStackedStudioPaneLayout();
|
|
4760
|
+
paneResizeHandleEl.setAttribute("aria-orientation", stacked ? "horizontal" : "vertical");
|
|
4406
4761
|
paneResizeHandleEl.setAttribute("aria-valuemin", String(PANE_SPLIT_MIN_PERCENT));
|
|
4407
4762
|
paneResizeHandleEl.setAttribute("aria-valuemax", String(PANE_SPLIT_MAX_PERCENT));
|
|
4408
|
-
paneResizeHandleEl.setAttribute("aria-valuenow", String(Math.round(
|
|
4763
|
+
paneResizeHandleEl.setAttribute("aria-valuenow", String(Math.round(dividerPercent)));
|
|
4409
4764
|
paneResizeHandleEl.setAttribute("aria-valuetext", "Editor " + Math.round(paneSplitPercent) + " percent, response " + Math.round(rightPercent) + " percent");
|
|
4765
|
+
paneResizeHandleEl.title = stacked
|
|
4766
|
+
? "Drag to resize the stacked panes. Double-click to reset."
|
|
4767
|
+
: "Drag to resize the panes. Double-click to reset.";
|
|
4410
4768
|
}
|
|
4411
4769
|
if (!options || options.persist !== false) {
|
|
4412
4770
|
try {
|
|
@@ -4438,17 +4796,23 @@
|
|
|
4438
4796
|
: null;
|
|
4439
4797
|
if (!mainEl || typeof mainEl.getBoundingClientRect !== "function") return paneSplitPercent;
|
|
4440
4798
|
const rect = mainEl.getBoundingClientRect();
|
|
4441
|
-
|
|
4442
|
-
const
|
|
4443
|
-
|
|
4799
|
+
const stacked = isStackedStudioPaneLayout();
|
|
4800
|
+
const extent = stacked ? rect.height : rect.width;
|
|
4801
|
+
if (!extent) return paneSplitPercent;
|
|
4802
|
+
const coordinate = stacked
|
|
4803
|
+
? ((typeof event.clientY === "number" ? event.clientY : (rect.top + rect.height / 2)) - rect.top)
|
|
4804
|
+
: ((typeof event.clientX === "number" ? event.clientX : (rect.left + rect.width / 2)) - rect.left);
|
|
4805
|
+
const dividerPercent = (coordinate / extent) * 100;
|
|
4806
|
+
return isResponseFirstStudioPaneLayout() ? 100 - dividerPercent : dividerPercent;
|
|
4444
4807
|
}
|
|
4445
4808
|
|
|
4446
4809
|
function setupPaneResizeHandle() {
|
|
4447
4810
|
if (!paneResizeHandleEl) return;
|
|
4811
|
+
setStudioPaneLayout(studioPaneLayout, { persist: false, silent: true });
|
|
4448
4812
|
loadPaneSplitPercent();
|
|
4449
4813
|
let dragging = false;
|
|
4450
4814
|
let movedDuringDrag = false;
|
|
4451
|
-
let
|
|
4815
|
+
let pointerStartPosition = 0;
|
|
4452
4816
|
let activePaneResizePointerId = null;
|
|
4453
4817
|
const finishDrag = () => {
|
|
4454
4818
|
if (!dragging) return;
|
|
@@ -4473,7 +4837,9 @@
|
|
|
4473
4837
|
event.stopPropagation();
|
|
4474
4838
|
dragging = true;
|
|
4475
4839
|
movedDuringDrag = false;
|
|
4476
|
-
|
|
4840
|
+
pointerStartPosition = isStackedStudioPaneLayout()
|
|
4841
|
+
? (typeof event.clientY === "number" ? event.clientY : 0)
|
|
4842
|
+
: (typeof event.clientX === "number" ? event.clientX : 0);
|
|
4477
4843
|
activePaneResizePointerId = event.pointerId;
|
|
4478
4844
|
if (document.body && document.body.classList) document.body.classList.add("pane-resizing");
|
|
4479
4845
|
try {
|
|
@@ -4489,7 +4855,10 @@
|
|
|
4489
4855
|
});
|
|
4490
4856
|
paneResizeHandleEl.addEventListener("pointermove", (event) => {
|
|
4491
4857
|
if (!dragging) return;
|
|
4492
|
-
const
|
|
4858
|
+
const currentPosition = isStackedStudioPaneLayout()
|
|
4859
|
+
? (typeof event.clientY === "number" ? event.clientY : pointerStartPosition)
|
|
4860
|
+
: (typeof event.clientX === "number" ? event.clientX : pointerStartPosition);
|
|
4861
|
+
const movement = Math.abs(currentPosition - pointerStartPosition);
|
|
4493
4862
|
if (!movedDuringDrag && movement < 3) return;
|
|
4494
4863
|
movedDuringDrag = true;
|
|
4495
4864
|
event.preventDefault();
|
|
@@ -4503,25 +4872,26 @@
|
|
|
4503
4872
|
resetPaneSplitPercent();
|
|
4504
4873
|
});
|
|
4505
4874
|
paneResizeHandleEl.addEventListener("keydown", (event) => {
|
|
4875
|
+
const stacked = isStackedStudioPaneLayout();
|
|
4876
|
+
const decreaseKey = stacked ? "ArrowUp" : "ArrowLeft";
|
|
4877
|
+
const increaseKey = stacked ? "ArrowDown" : "ArrowRight";
|
|
4878
|
+
let dividerPercent = isResponseFirstStudioPaneLayout() ? 100 - paneSplitPercent : paneSplitPercent;
|
|
4506
4879
|
if (event.key === "Home") {
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
}
|
|
4511
|
-
if (event.key === "End") {
|
|
4512
|
-
event.preventDefault();
|
|
4513
|
-
applyPaneSplitPercent(PANE_SPLIT_MAX_PERCENT);
|
|
4514
|
-
return;
|
|
4515
|
-
}
|
|
4516
|
-
if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
4880
|
+
dividerPercent = PANE_SPLIT_MIN_PERCENT;
|
|
4881
|
+
} else if (event.key === "End") {
|
|
4882
|
+
dividerPercent = PANE_SPLIT_MAX_PERCENT;
|
|
4883
|
+
} else if (event.key === "Enter" || event.key === " " || event.key === "Spacebar") {
|
|
4517
4884
|
event.preventDefault();
|
|
4518
4885
|
resetPaneSplitPercent();
|
|
4519
4886
|
return;
|
|
4887
|
+
} else if (event.key === decreaseKey || event.key === increaseKey) {
|
|
4888
|
+
const step = event.shiftKey ? 10 : 5;
|
|
4889
|
+
dividerPercent += event.key === decreaseKey ? -step : step;
|
|
4890
|
+
} else {
|
|
4891
|
+
return;
|
|
4520
4892
|
}
|
|
4521
|
-
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
4522
4893
|
event.preventDefault();
|
|
4523
|
-
|
|
4524
|
-
applyPaneSplitPercent(paneSplitPercent + (event.key === "ArrowLeft" ? -step : step));
|
|
4894
|
+
applyPaneSplitPercent(isResponseFirstStudioPaneLayout() ? 100 - dividerPercent : dividerPercent);
|
|
4525
4895
|
});
|
|
4526
4896
|
}
|
|
4527
4897
|
|
|
@@ -4598,7 +4968,7 @@
|
|
|
4598
4968
|
function shouldPreserveScrollForPaneActivationEvent(event) {
|
|
4599
4969
|
const target = event && event.target;
|
|
4600
4970
|
if (!(target instanceof Element)) return true;
|
|
4601
|
-
if (target.closest("button, select, input, a, [role='button'], .studio-copy-block-btn, .preview-comment-add, .preview-comment-jump")) {
|
|
4971
|
+
if (target.closest("button, select, input, textarea, a, [role='button'], .studio-copy-block-btn, .preview-comment-add, .preview-comment-jump")) {
|
|
4602
4972
|
return false;
|
|
4603
4973
|
}
|
|
4604
4974
|
return true;
|
|
@@ -4702,8 +5072,10 @@
|
|
|
4702
5072
|
setStatus(label + " is unavailable in editor-only Studio views.", "warning");
|
|
4703
5073
|
return false;
|
|
4704
5074
|
}
|
|
4705
|
-
const snapshot = snapshotStudioScrollablePositions()
|
|
4706
|
-
|
|
5075
|
+
const snapshot = snapshotStudioScrollablePositions().filter((entry) => (
|
|
5076
|
+
!(options && options.focusReplComposer && entry.el === critiqueViewEl)
|
|
5077
|
+
));
|
|
5078
|
+
setRightView(requestedView, options);
|
|
4707
5079
|
scheduleStudioScrollablePositionRestore(snapshot);
|
|
4708
5080
|
if (!options || options.announce !== false) {
|
|
4709
5081
|
setStatus("Right pane view: " + label + ".");
|
|
@@ -4786,6 +5158,20 @@
|
|
|
4786
5158
|
return true;
|
|
4787
5159
|
}
|
|
4788
5160
|
|
|
5161
|
+
function triggerLoadFromPiEditorShortcut() {
|
|
5162
|
+
if (isWatchedFilePreview) {
|
|
5163
|
+
setStatus("This preview is read-only. Open full Studio to load the Pi draft.", "warning");
|
|
5164
|
+
return false;
|
|
5165
|
+
}
|
|
5166
|
+
if (!getEditorBtn || getEditorBtn.hidden) return false;
|
|
5167
|
+
if (getEditorBtn.disabled) {
|
|
5168
|
+
setStatus("Load from Pi editor is unavailable while Studio is busy.", "warning");
|
|
5169
|
+
return false;
|
|
5170
|
+
}
|
|
5171
|
+
getEditorBtn.click();
|
|
5172
|
+
return true;
|
|
5173
|
+
}
|
|
5174
|
+
|
|
4789
5175
|
function triggerEditorSaveShortcut() {
|
|
4790
5176
|
if (hasRefreshableFilePath() && saveOverBtn && !saveOverBtn.disabled && !saveOverBtn.hidden) {
|
|
4791
5177
|
saveOverBtn.click();
|
|
@@ -5089,6 +5475,16 @@
|
|
|
5089
5475
|
return;
|
|
5090
5476
|
}
|
|
5091
5477
|
|
|
5478
|
+
const isReplShortcut = (key.toLowerCase() === "r" || code === "KeyR")
|
|
5479
|
+
&& (event.metaKey || event.ctrlKey)
|
|
5480
|
+
&& event.altKey
|
|
5481
|
+
&& !event.shiftKey;
|
|
5482
|
+
if (isReplShortcut) {
|
|
5483
|
+
event.preventDefault();
|
|
5484
|
+
switchRightPaneToView("repl", { focusReplComposer: true });
|
|
5485
|
+
return;
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5092
5488
|
const isSideQuestionsShortcut = (key.toLowerCase() === "q" || code === "KeyQ")
|
|
5093
5489
|
&& (event.metaKey || event.ctrlKey)
|
|
5094
5490
|
&& event.altKey
|
|
@@ -5137,6 +5533,18 @@
|
|
|
5137
5533
|
return;
|
|
5138
5534
|
}
|
|
5139
5535
|
|
|
5536
|
+
const isLoadFromPiEditorShortcut =
|
|
5537
|
+
(key.toLowerCase() === "l" || code === "KeyL")
|
|
5538
|
+
&& (event.metaKey || event.ctrlKey)
|
|
5539
|
+
&& !event.altKey
|
|
5540
|
+
&& event.shiftKey;
|
|
5541
|
+
|
|
5542
|
+
if (isLoadFromPiEditorShortcut) {
|
|
5543
|
+
event.preventDefault();
|
|
5544
|
+
triggerLoadFromPiEditorShortcut();
|
|
5545
|
+
return;
|
|
5546
|
+
}
|
|
5547
|
+
|
|
5140
5548
|
const isSaveAsShortcut =
|
|
5141
5549
|
key.toLowerCase() === "s"
|
|
5142
5550
|
&& (event.metaKey || event.ctrlKey)
|
|
@@ -7585,7 +7993,7 @@
|
|
|
7585
7993
|
refreshBtn.type = "button";
|
|
7586
7994
|
refreshBtn.className = "studio-pdf-focus-btn studio-pdf-focus-refresh";
|
|
7587
7995
|
refreshBtn.textContent = "Refresh";
|
|
7588
|
-
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
|
|
7996
|
+
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+Shift+R.";
|
|
7589
7997
|
refreshBtn.setAttribute("aria-label", "Refresh PDF preview from disk");
|
|
7590
7998
|
refreshBtn.addEventListener("click", () => refreshStudioPdfFocusViewer());
|
|
7591
7999
|
actions.appendChild(refreshBtn);
|
|
@@ -8148,7 +8556,7 @@
|
|
|
8148
8556
|
const matches = (key === "r" || code === "KeyR")
|
|
8149
8557
|
&& (event.metaKey || event.ctrlKey)
|
|
8150
8558
|
&& event.altKey
|
|
8151
|
-
&&
|
|
8559
|
+
&& event.shiftKey;
|
|
8152
8560
|
if (!matches) return false;
|
|
8153
8561
|
if (!refreshVisibleStudioPdfPreviews()) return false;
|
|
8154
8562
|
event.preventDefault();
|
|
@@ -8904,7 +9312,7 @@
|
|
|
8904
9312
|
refreshBtn.type = "button";
|
|
8905
9313
|
refreshBtn.className = "studio-pdf-card-action studio-pdf-card-refresh";
|
|
8906
9314
|
refreshBtn.textContent = "Refresh";
|
|
8907
|
-
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+R.";
|
|
9315
|
+
refreshBtn.title = "Reload this PDF preview from disk. Shortcut: Cmd/Ctrl+Alt+Shift+R.";
|
|
8908
9316
|
refreshBtn.addEventListener("click", (event) => {
|
|
8909
9317
|
event.preventDefault();
|
|
8910
9318
|
event.stopPropagation();
|
|
@@ -9870,10 +10278,26 @@
|
|
|
9870
10278
|
if (!actionBtn) return;
|
|
9871
10279
|
event.preventDefault();
|
|
9872
10280
|
const action = actionBtn.getAttribute("data-repl-action") || "";
|
|
10281
|
+
if (action === "quick-send") {
|
|
10282
|
+
submitReplQuickDraft();
|
|
10283
|
+
return;
|
|
10284
|
+
}
|
|
10285
|
+
if (action === "jump-controls") {
|
|
10286
|
+
focusReplSessionControls();
|
|
10287
|
+
return;
|
|
10288
|
+
}
|
|
10289
|
+
if (action === "quick-clear") {
|
|
10290
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
10291
|
+
if (session) setReplQuickDraft(session, "");
|
|
10292
|
+
renderReplViewIfActive({ force: true });
|
|
10293
|
+
focusReplQuickComposer();
|
|
10294
|
+
return;
|
|
10295
|
+
}
|
|
9873
10296
|
if (action === "start" || action === "new-session") {
|
|
9874
10297
|
const requestId = makeRequestId();
|
|
9875
10298
|
const command = getCurrentReplStartCommandFromDom();
|
|
9876
10299
|
setReplCommandOverride(replRuntime, command);
|
|
10300
|
+
replPendingRequestId = requestId;
|
|
9877
10301
|
replBusy = true;
|
|
9878
10302
|
replError = "";
|
|
9879
10303
|
replMessage = (action === "new-session" ? "Starting new " : "Starting ") + getReplRuntimeLabel(replRuntime) + " session" + (command ? " with custom command" : "") + "…";
|
|
@@ -9882,8 +10306,10 @@
|
|
|
9882
10306
|
const message = { type: "repl_start_request", requestId, runtime: replRuntime, newSession: action === "new-session" };
|
|
9883
10307
|
if (command) message.command = command;
|
|
9884
10308
|
if (!sendMessage(message)) {
|
|
9885
|
-
|
|
10309
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
10310
|
+
replBusy = Boolean(replPendingRequestId);
|
|
9886
10311
|
syncActionButtons();
|
|
10312
|
+
renderReplViewIfActive({ force: true });
|
|
9887
10313
|
}
|
|
9888
10314
|
return;
|
|
9889
10315
|
}
|
|
@@ -9894,14 +10320,17 @@
|
|
|
9894
10320
|
return;
|
|
9895
10321
|
}
|
|
9896
10322
|
const requestId = makeRequestId();
|
|
10323
|
+
replPendingRequestId = requestId;
|
|
9897
10324
|
replBusy = true;
|
|
9898
10325
|
replError = "";
|
|
9899
10326
|
replMessage = "Stopping " + session.sessionName + "…";
|
|
9900
10327
|
syncActionButtons();
|
|
9901
10328
|
renderReplViewIfActive({ force: true });
|
|
9902
10329
|
if (!sendMessage({ type: "repl_stop_request", requestId, sessionName: session.sessionName })) {
|
|
9903
|
-
|
|
10330
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
10331
|
+
replBusy = Boolean(replPendingRequestId);
|
|
9904
10332
|
syncActionButtons();
|
|
10333
|
+
renderReplViewIfActive({ force: true });
|
|
9905
10334
|
}
|
|
9906
10335
|
return;
|
|
9907
10336
|
}
|
|
@@ -9912,14 +10341,17 @@
|
|
|
9912
10341
|
return;
|
|
9913
10342
|
}
|
|
9914
10343
|
const requestId = makeRequestId();
|
|
10344
|
+
replPendingRequestId = requestId;
|
|
9915
10345
|
replBusy = true;
|
|
9916
10346
|
replError = "";
|
|
9917
10347
|
replMessage = "Interrupting REPL…";
|
|
9918
10348
|
syncActionButtons();
|
|
9919
10349
|
renderReplViewIfActive({ force: true });
|
|
9920
10350
|
if (!sendMessage({ type: "repl_interrupt_request", requestId, sessionName: session.sessionName })) {
|
|
9921
|
-
|
|
10351
|
+
if (replPendingRequestId === requestId) replPendingRequestId = "";
|
|
10352
|
+
replBusy = Boolean(replPendingRequestId);
|
|
9922
10353
|
syncActionButtons();
|
|
10354
|
+
renderReplViewIfActive({ force: true });
|
|
9923
10355
|
}
|
|
9924
10356
|
return;
|
|
9925
10357
|
}
|
|
@@ -9979,6 +10411,39 @@
|
|
|
9979
10411
|
}
|
|
9980
10412
|
}
|
|
9981
10413
|
|
|
10414
|
+
function handleReplPaneFocusIn(event) {
|
|
10415
|
+
if (!replQuickFocusRequested || rightView !== "repl") return;
|
|
10416
|
+
const target = event && event.target;
|
|
10417
|
+
if (target instanceof Element && !target.hasAttribute("data-repl-quick-draft")) {
|
|
10418
|
+
replQuickFocusRequested = false;
|
|
10419
|
+
}
|
|
10420
|
+
}
|
|
10421
|
+
|
|
10422
|
+
function handleReplPaneInput(event) {
|
|
10423
|
+
if (rightView !== "repl") return;
|
|
10424
|
+
const target = event && event.target;
|
|
10425
|
+
if (!(target instanceof HTMLTextAreaElement) || !target.hasAttribute("data-repl-quick-draft")) return;
|
|
10426
|
+
const session = getActiveReplSessionForCurrentRuntime();
|
|
10427
|
+
if (!session) return;
|
|
10428
|
+
const draft = setReplQuickDraft(session, target.value);
|
|
10429
|
+
if (target.value !== draft) target.value = draft;
|
|
10430
|
+
updateReplQuickComposerActions();
|
|
10431
|
+
}
|
|
10432
|
+
|
|
10433
|
+
function handleReplPaneKeydown(event) {
|
|
10434
|
+
if (rightView !== "repl" || !event || event.isComposing) return;
|
|
10435
|
+
const target = event.target;
|
|
10436
|
+
if (!(target instanceof HTMLTextAreaElement) || !target.hasAttribute("data-repl-quick-draft")) return;
|
|
10437
|
+
const submitShortcut = event.key === "Enter"
|
|
10438
|
+
&& (event.metaKey || event.ctrlKey)
|
|
10439
|
+
&& !event.altKey
|
|
10440
|
+
&& !event.shiftKey;
|
|
10441
|
+
if (!submitShortcut) return;
|
|
10442
|
+
event.preventDefault();
|
|
10443
|
+
event.stopPropagation();
|
|
10444
|
+
submitReplQuickDraft();
|
|
10445
|
+
}
|
|
10446
|
+
|
|
9982
10447
|
function handleReplPaneChange(event) {
|
|
9983
10448
|
if (rightView !== "repl") return;
|
|
9984
10449
|
const target = event.target;
|
|
@@ -10026,8 +10491,11 @@
|
|
|
10026
10491
|
critiqueViewEl.addEventListener("click", handleGitChangesPaneClick);
|
|
10027
10492
|
critiqueViewEl.addEventListener("click", handleStudioQuartoPreviewClick);
|
|
10028
10493
|
critiqueViewEl.addEventListener("click", (event) => { void handleSideQuestionClick(event); });
|
|
10494
|
+
critiqueViewEl.addEventListener("focusin", handleReplPaneFocusIn);
|
|
10029
10495
|
critiqueViewEl.addEventListener("input", handleSideQuestionInput);
|
|
10496
|
+
critiqueViewEl.addEventListener("input", handleReplPaneInput);
|
|
10030
10497
|
critiqueViewEl.addEventListener("keydown", handleSideQuestionKeydown);
|
|
10498
|
+
critiqueViewEl.addEventListener("keydown", handleReplPaneKeydown);
|
|
10031
10499
|
critiqueViewEl.addEventListener("change", handleSideQuestionChange);
|
|
10032
10500
|
critiqueViewEl.addEventListener("change", handleReplPaneChange);
|
|
10033
10501
|
critiqueViewEl.addEventListener("change", (event) => {
|
|
@@ -11763,6 +12231,28 @@
|
|
|
11763
12231
|
return "tmux attach -t " + String(session.sessionName || "");
|
|
11764
12232
|
}
|
|
11765
12233
|
|
|
12234
|
+
function buildReplQuickComposerHtml(activeSession, canSendToActiveSession) {
|
|
12235
|
+
const key = getReplQuickDraftKey(activeSession);
|
|
12236
|
+
const draft = getReplQuickDraft(activeSession);
|
|
12237
|
+
const pending = Boolean(replQuickPending && replQuickPending.key === key);
|
|
12238
|
+
const runtimeLabel = activeSession ? getReplRuntimeLabel(activeSession.runtime) : getReplRuntimeLabel(replRuntime);
|
|
12239
|
+
const tmuxTargetLabel = activeSession && activeSession.target ? ("tmux " + activeSession.target) : "";
|
|
12240
|
+
const targetDetails = activeSession
|
|
12241
|
+
? ([runtimeLabel, activeSession.sessionName, tmuxTargetLabel].filter(Boolean).join(" · "))
|
|
12242
|
+
: (runtimeLabel + " · no session selected");
|
|
12243
|
+
const visibleTargetLabel = activeSession ? activeSession.sessionName : "No session selected";
|
|
12244
|
+
const inputDisabled = !activeSession || !key || pending || replBusy;
|
|
12245
|
+
const sendDisabled = !key || !canSendToActiveSession || wsState === "Disconnected" || pending || !draft.trim();
|
|
12246
|
+
return "<section class='repl-quick-composer' aria-labelledby='replQuickComposerTitle'>"
|
|
12247
|
+
+ "<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>"
|
|
12248
|
+
+ "<span class='repl-quick-hint'>Enter adds a line · Cmd/Ctrl+Enter sends</span></div>"
|
|
12249
|
+
+ "<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>"
|
|
12250
|
+
+ "<div class='repl-quick-actions'><span>Raw send through Studio’s safe submission and Shared REPL Record path.</span><div>"
|
|
12251
|
+
+ "<button type='button' data-repl-action='quick-clear'" + (pending || !draft ? " disabled" : "") + ">Clear</button>"
|
|
12252
|
+
+ "<button class='repl-quick-send-btn' type='button' data-repl-action='quick-send'" + (sendDisabled ? " disabled" : "") + ">" + (pending ? "Sending…" : "Send") + "</button>"
|
|
12253
|
+
+ "</div></div></section>";
|
|
12254
|
+
}
|
|
12255
|
+
|
|
11766
12256
|
function buildReplPanelHtml() {
|
|
11767
12257
|
const runtimeOptions = [
|
|
11768
12258
|
["shell", "Shell"],
|
|
@@ -11814,6 +12304,7 @@
|
|
|
11814
12304
|
+ (replError ? "<div class='repl-notice repl-notice-error'>" + escapeHtml(replError) + "</div>" : "")
|
|
11815
12305
|
+ buildReplJournalHtml(transcript)
|
|
11816
12306
|
+ buildReplStudioActionsHtml()
|
|
12307
|
+
+ buildReplQuickComposerHtml(activeSession, canSendToActiveSession)
|
|
11817
12308
|
+ buildReplMirrorHtml(body, transcript)
|
|
11818
12309
|
+ "</div>";
|
|
11819
12310
|
}
|
|
@@ -13483,7 +13974,7 @@
|
|
|
13483
13974
|
const safeStart = Math.max(0, Math.min(start, current.length));
|
|
13484
13975
|
const safeEnd = Math.max(safeStart, Math.min(end, current.length));
|
|
13485
13976
|
const next = current.slice(0, safeStart) + answer.text + current.slice(safeEnd);
|
|
13486
|
-
setEditorText(next, { preserveScroll: false, preserveSelection: false });
|
|
13977
|
+
setEditorText(next, { preserveScroll: false, preserveSelection: false, preservePiEditorDraftLink: true });
|
|
13487
13978
|
const caret = safeStart + answer.text.length;
|
|
13488
13979
|
sourceTextEl.setSelectionRange(caret, caret);
|
|
13489
13980
|
setActivePane("left");
|
|
@@ -14099,6 +14590,7 @@
|
|
|
14099
14590
|
if (refreshFromDiskBtn) refreshFromDiskBtn.disabled = uiBusy || isWatchedFilePreview || !canRefreshFromDisk;
|
|
14100
14591
|
if (clearWorkspaceBtn) clearWorkspaceBtn.disabled = uiBusy || isWatchedFilePreview;
|
|
14101
14592
|
sendEditorBtn.disabled = uiBusy || isEditorOnlyMode;
|
|
14593
|
+
if (clearPiEditorBtn) clearPiEditorBtn.disabled = uiBusy || isEditorOnlyMode;
|
|
14102
14594
|
if (getEditorBtn) getEditorBtn.disabled = uiBusy || isWatchedFilePreview;
|
|
14103
14595
|
if (watchedOpenEditableBtn) {
|
|
14104
14596
|
watchedOpenEditableBtn.hidden = !isWatchedFilePreview;
|
|
@@ -14480,6 +14972,9 @@
|
|
|
14480
14972
|
const value = String(nextText || "");
|
|
14481
14973
|
const preserveScroll = Boolean(options && options.preserveScroll);
|
|
14482
14974
|
const preserveSelection = Boolean(options && options.preserveSelection);
|
|
14975
|
+
if (!(options && options.preservePiEditorDraftLink === true)) {
|
|
14976
|
+
clearLinkedPiEditorDraftSnapshot();
|
|
14977
|
+
}
|
|
14483
14978
|
if (activePreviewCommentSelection) {
|
|
14484
14979
|
clearPreviewCommentSelection();
|
|
14485
14980
|
}
|
|
@@ -15062,9 +15557,14 @@
|
|
|
15062
15557
|
scheduleWorkspacePersistence();
|
|
15063
15558
|
}
|
|
15064
15559
|
|
|
15065
|
-
function setRightView(nextView) {
|
|
15560
|
+
function setRightView(nextView, options) {
|
|
15561
|
+
const automatedActivityChange = Boolean(options && options.activityTracking);
|
|
15562
|
+
if (activityTrackingOwnsWorkingView && !automatedActivityChange) {
|
|
15563
|
+
activityTrackingOwnsWorkingView = false;
|
|
15564
|
+
}
|
|
15066
15565
|
const previousView = rightView;
|
|
15067
15566
|
rightView = normalizeRightViewValue(nextView);
|
|
15567
|
+
if (rightView !== "repl") replQuickFocusRequested = false;
|
|
15068
15568
|
syncRightViewModeOptions();
|
|
15069
15569
|
rightViewSelect.value = rightView;
|
|
15070
15570
|
if (rightView === "trace" && previousView !== "trace") {
|
|
@@ -15103,6 +15603,9 @@
|
|
|
15103
15603
|
if (composer instanceof HTMLTextAreaElement) composer.focus({ preventScroll: true });
|
|
15104
15604
|
}, 0);
|
|
15105
15605
|
}
|
|
15606
|
+
if (rightView === "repl" && (previousView !== "repl" || (options && options.focusReplComposer))) {
|
|
15607
|
+
focusReplQuickComposer();
|
|
15608
|
+
}
|
|
15106
15609
|
scheduleWorkspacePersistence();
|
|
15107
15610
|
}
|
|
15108
15611
|
|
|
@@ -21974,7 +22477,7 @@
|
|
|
21974
22477
|
? current.slice(0, inlineState.range.end) + current.slice(inlineState.range.end + inlineState.markerText.length)
|
|
21975
22478
|
: current.slice(0, inlineState.range.end) + inlineState.markerText + current.slice(inlineState.range.end);
|
|
21976
22479
|
setEditorView("markdown");
|
|
21977
|
-
setEditorText(next, { preserveScroll: true, preserveSelection: true });
|
|
22480
|
+
setEditorText(next, { preserveScroll: true, preserveSelection: true, preservePiEditorDraftLink: true });
|
|
21978
22481
|
pendingReviewNoteInlineFocusId = note.id;
|
|
21979
22482
|
renderReviewNotesList();
|
|
21980
22483
|
updateReviewNotesUi();
|
|
@@ -22019,7 +22522,7 @@
|
|
|
22019
22522
|
}
|
|
22020
22523
|
|
|
22021
22524
|
setEditorView("markdown");
|
|
22022
|
-
setEditorText(currentText, { preserveScroll: true, preserveSelection: true });
|
|
22525
|
+
setEditorText(currentText, { preserveScroll: true, preserveSelection: true, preservePiEditorDraftLink: true });
|
|
22023
22526
|
renderReviewNotesList();
|
|
22024
22527
|
updateReviewNotesUi();
|
|
22025
22528
|
if (reviewNotesInlineAllBtn && typeof reviewNotesInlineAllBtn.focus === "function") {
|
|
@@ -22205,7 +22708,7 @@
|
|
|
22205
22708
|
const safeStart = Math.max(0, Math.min(start, current.length));
|
|
22206
22709
|
const safeEnd = Math.max(safeStart, Math.min(end, current.length));
|
|
22207
22710
|
const next = current.slice(0, safeStart) + content + current.slice(safeEnd);
|
|
22208
|
-
setEditorText(next, { preserveScroll: false, preserveSelection: false });
|
|
22711
|
+
setEditorText(next, { preserveScroll: false, preserveSelection: false, preservePiEditorDraftLink: true });
|
|
22209
22712
|
const caret = safeStart + content.length;
|
|
22210
22713
|
sourceTextEl.setSelectionRange(caret, caret);
|
|
22211
22714
|
setActivePane("left");
|
|
@@ -22408,11 +22911,14 @@
|
|
|
22408
22911
|
const replHint = rightView === "repl" && getActiveReplSessionForCurrentRuntime()
|
|
22409
22912
|
? " Sends text to Pi, not the REPL; use Send chunk/selection or Send to REPL to execute code in the active REPL."
|
|
22410
22913
|
: "";
|
|
22914
|
+
const piEditorDraftHint = linkedPiEditorDraftSnapshot
|
|
22915
|
+
? " The linked Pi terminal draft will be cleared after Pi accepts this run only if that draft remains unchanged."
|
|
22916
|
+
: "";
|
|
22411
22917
|
sendRunBtn.title = directIsStop
|
|
22412
22918
|
? "Stop the active run. Shortcut: Esc."
|
|
22413
22919
|
: (annotationsEnabled
|
|
22414
|
-
? "Run editor text as-is (includes [an: ...] markers). Shortcut: Cmd/Ctrl+Enter. Stop the active request with Esc." + replHint
|
|
22415
|
-
: "Run editor text with [an: ...] markers stripped. Shortcut: Cmd/Ctrl+Enter. Stop the active request with Esc." + replHint);
|
|
22920
|
+
? "Run editor text as-is (includes [an: ...] markers). Shortcut: Cmd/Ctrl+Enter. Stop the active request with Esc." + piEditorDraftHint + replHint
|
|
22921
|
+
: "Run editor text with [an: ...] markers stripped. Shortcut: Cmd/Ctrl+Enter. Stop the active request with Esc." + piEditorDraftHint + replHint);
|
|
22416
22922
|
}
|
|
22417
22923
|
|
|
22418
22924
|
if (queueSteerBtn) {
|
|
@@ -22978,6 +23484,11 @@
|
|
|
22978
23484
|
}
|
|
22979
23485
|
if (rightView === "side-questions") renderSideQuestionView();
|
|
22980
23486
|
syncAskAsideButton();
|
|
23487
|
+
if (agentBusyFromServer) {
|
|
23488
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
23489
|
+
} else {
|
|
23490
|
+
finishTrackedStudioActivity();
|
|
23491
|
+
}
|
|
22981
23492
|
|
|
22982
23493
|
if (pendingRequestId) {
|
|
22983
23494
|
if (busy) {
|
|
@@ -23054,18 +23565,25 @@
|
|
|
23054
23565
|
const previousError = replError;
|
|
23055
23566
|
const previousMessage = replMessage;
|
|
23056
23567
|
const wasBusy = replBusy;
|
|
23568
|
+
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23569
|
+
const settlesPendingRequest = Boolean(responseRequestId && responseRequestId === replPendingRequestId);
|
|
23570
|
+
const previousActiveLifetimeKey = getReplQuickDraftKey(getActiveReplSession());
|
|
23057
23571
|
replTmuxAvailable = typeof message.tmuxAvailable === "boolean" ? message.tmuxAvailable : replTmuxAvailable;
|
|
23058
23572
|
const sessionsChanged = setReplSessions(message.sessions);
|
|
23059
23573
|
if (typeof message.activeSessionName === "string" && message.activeSessionName.trim()) {
|
|
23060
23574
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
23061
23575
|
}
|
|
23062
23576
|
const journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23577
|
+
reconcileReplQuickPending();
|
|
23063
23578
|
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
23064
23579
|
if (typeof message.transcript === "string") replTranscript = trimReplTranscript(message.transcript);
|
|
23065
23580
|
if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
|
|
23066
23581
|
replError = typeof message.replError === "string" ? message.replError : (typeof message.captureError === "string" ? message.captureError : "");
|
|
23067
23582
|
replMessage = typeof message.replMessage === "string" ? message.replMessage : "";
|
|
23068
|
-
|
|
23583
|
+
if (settlesPendingRequest && replPendingRequestId === responseRequestId) replPendingRequestId = "";
|
|
23584
|
+
replBusy = Boolean(replPendingRequestId);
|
|
23585
|
+
const activeSessionIdentityChanged = Boolean(previousActiveLifetimeKey)
|
|
23586
|
+
&& previousActiveLifetimeKey !== getReplQuickDraftKey(getActiveReplSession());
|
|
23069
23587
|
const controlsChanged = wasBusy
|
|
23070
23588
|
|| sessionsChanged
|
|
23071
23589
|
|| previousTmuxAvailable !== replTmuxAvailable
|
|
@@ -23077,7 +23595,7 @@
|
|
|
23077
23595
|
|| previousMessage !== replMessage
|
|
23078
23596
|
|| journalChanged
|
|
23079
23597
|
|| (!previousCapturedAt && replCapturedAt);
|
|
23080
|
-
if (viewChanged) renderReplViewIfActive();
|
|
23598
|
+
if (viewChanged) renderReplViewIfActive({ force: activeSessionIdentityChanged });
|
|
23081
23599
|
updateReferenceBadge();
|
|
23082
23600
|
return;
|
|
23083
23601
|
}
|
|
@@ -23102,18 +23620,28 @@
|
|
|
23102
23620
|
const previousError = replError;
|
|
23103
23621
|
const previousMessage = replMessage;
|
|
23104
23622
|
const wasBusy = replBusy;
|
|
23623
|
+
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23624
|
+
const settlesPendingRequest = Boolean(responseRequestId && responseRequestId === replPendingRequestId);
|
|
23625
|
+
const previousActiveLifetimeKey = getReplQuickDraftKey(getActiveReplSession());
|
|
23105
23626
|
let sessionsChanged = false;
|
|
23106
23627
|
if (message.session) {
|
|
23107
23628
|
const session = normalizeReplSession(message.session);
|
|
23108
|
-
if (session
|
|
23109
|
-
|
|
23110
|
-
|
|
23629
|
+
if (session) {
|
|
23630
|
+
const existingIndex = replSessions.findIndex((candidate) => candidate.sessionName === session.sessionName);
|
|
23631
|
+
if (existingIndex < 0) {
|
|
23632
|
+
replSessions = [...replSessions, session];
|
|
23633
|
+
sessionsChanged = true;
|
|
23634
|
+
} else if (serializeReplSessionsForCompare([replSessions[existingIndex]]) !== serializeReplSessionsForCompare([session])) {
|
|
23635
|
+
replSessions = replSessions.map((candidate, index) => index === existingIndex ? session : candidate);
|
|
23636
|
+
sessionsChanged = true;
|
|
23637
|
+
}
|
|
23111
23638
|
}
|
|
23112
23639
|
}
|
|
23113
23640
|
if (typeof message.activeSessionName === "string" && message.activeSessionName.trim()) {
|
|
23114
23641
|
setActiveReplSessionForCurrentRuntime(message.activeSessionName);
|
|
23115
23642
|
}
|
|
23116
23643
|
let journalChanged = mergeReplJournalEntries(message.journalEntries);
|
|
23644
|
+
reconcileReplQuickPending();
|
|
23117
23645
|
maybeImportLegacyReplJournalEntries(replActiveSessionName);
|
|
23118
23646
|
if (typeof message.transcript === "string") {
|
|
23119
23647
|
replTranscript = trimReplTranscript(message.transcript);
|
|
@@ -23125,7 +23653,10 @@
|
|
|
23125
23653
|
if (typeof message.capturedAt === "number") replCapturedAt = message.capturedAt;
|
|
23126
23654
|
replError = typeof message.replError === "string" ? message.replError : "";
|
|
23127
23655
|
if (typeof message.replMessage === "string") replMessage = message.replMessage;
|
|
23128
|
-
|
|
23656
|
+
if (settlesPendingRequest && replPendingRequestId === responseRequestId) replPendingRequestId = "";
|
|
23657
|
+
replBusy = Boolean(replPendingRequestId);
|
|
23658
|
+
const activeSessionIdentityChanged = Boolean(previousActiveLifetimeKey)
|
|
23659
|
+
&& previousActiveLifetimeKey !== getReplQuickDraftKey(getActiveReplSession());
|
|
23129
23660
|
const controlsChanged = wasBusy || sessionsChanged || previousActiveSessionName !== replActiveSessionName;
|
|
23130
23661
|
if (controlsChanged) syncActionButtons();
|
|
23131
23662
|
const viewChanged = controlsChanged
|
|
@@ -23134,7 +23665,7 @@
|
|
|
23134
23665
|
|| previousMessage !== replMessage
|
|
23135
23666
|
|| journalChanged
|
|
23136
23667
|
|| (!previousCapturedAt && replCapturedAt);
|
|
23137
|
-
if (viewChanged) renderReplViewIfActive();
|
|
23668
|
+
if (viewChanged) renderReplViewIfActive({ force: activeSessionIdentityChanged });
|
|
23138
23669
|
updateReferenceBadge();
|
|
23139
23670
|
return;
|
|
23140
23671
|
}
|
|
@@ -23153,9 +23684,12 @@
|
|
|
23153
23684
|
}
|
|
23154
23685
|
|
|
23155
23686
|
if (message.type === "repl_send_ack") {
|
|
23156
|
-
|
|
23687
|
+
const responseRequestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23688
|
+
if (responseRequestId && replPendingRequestId === responseRequestId) replPendingRequestId = "";
|
|
23689
|
+
replBusy = Boolean(replPendingRequestId);
|
|
23157
23690
|
replMessage = "";
|
|
23158
23691
|
replError = "";
|
|
23692
|
+
if (responseRequestId) settleReplQuickPending(responseRequestId, true);
|
|
23159
23693
|
mergeReplJournalEntries(message.journalEntries);
|
|
23160
23694
|
if (typeof message.requestId === "string") {
|
|
23161
23695
|
replJournalEntries = replJournalEntries.map((entry) => entry.requestId === message.requestId ? { ...entry, status: "sent", updatedAt: Date.now() } : entry);
|
|
@@ -23171,6 +23705,8 @@
|
|
|
23171
23705
|
pendingRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
|
|
23172
23706
|
pendingKind = typeof message.kind === "string" ? message.kind : "unknown";
|
|
23173
23707
|
stickyStudioKind = pendingKind;
|
|
23708
|
+
agentBusyFromServer = true;
|
|
23709
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
23174
23710
|
if (pendingKind === "direct") {
|
|
23175
23711
|
studioRunChainActive = true;
|
|
23176
23712
|
}
|
|
@@ -23201,6 +23737,7 @@
|
|
|
23201
23737
|
const busy = Boolean(message.busy);
|
|
23202
23738
|
setBusy(busy);
|
|
23203
23739
|
setWsState(busy ? "Submitting" : "Ready");
|
|
23740
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23204
23741
|
setStatus(typeof message.message === "string" ? message.message : "Compaction completed.", "success");
|
|
23205
23742
|
return;
|
|
23206
23743
|
}
|
|
@@ -23215,6 +23752,7 @@
|
|
|
23215
23752
|
const busy = Boolean(message.busy);
|
|
23216
23753
|
setBusy(busy);
|
|
23217
23754
|
setWsState(busy ? "Submitting" : "Ready");
|
|
23755
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23218
23756
|
setStatus(typeof message.message === "string" ? message.message : "Compaction failed.", "error");
|
|
23219
23757
|
return;
|
|
23220
23758
|
}
|
|
@@ -23225,6 +23763,7 @@
|
|
|
23225
23763
|
}
|
|
23226
23764
|
|
|
23227
23765
|
const completedRequestId = typeof message.requestId === "string" ? message.requestId : pendingRequestId;
|
|
23766
|
+
if (completedRequestId) pendingPiEditorDraftSnapshots.delete(completedRequestId);
|
|
23228
23767
|
const responseKind =
|
|
23229
23768
|
typeof message.kind === "string"
|
|
23230
23769
|
? message.kind
|
|
@@ -23234,6 +23773,7 @@
|
|
|
23234
23773
|
pendingRequestId = null;
|
|
23235
23774
|
pendingKind = null;
|
|
23236
23775
|
queuedLatestResponse = null;
|
|
23776
|
+
agentBusyFromServer = false;
|
|
23237
23777
|
setBusy(false);
|
|
23238
23778
|
setWsState("Ready");
|
|
23239
23779
|
|
|
@@ -23250,6 +23790,7 @@
|
|
|
23250
23790
|
if (!appliedFromHistory && typeof message.markdown === "string") {
|
|
23251
23791
|
handleIncomingResponse(message.markdown, responseKind, message.timestamp, message.thinking);
|
|
23252
23792
|
}
|
|
23793
|
+
finishTrackedStudioActivity(completedRequestId || "");
|
|
23253
23794
|
|
|
23254
23795
|
if (responseKind === "critique") {
|
|
23255
23796
|
setStatus("Critique ready.", "success");
|
|
@@ -23357,17 +23898,46 @@
|
|
|
23357
23898
|
return;
|
|
23358
23899
|
}
|
|
23359
23900
|
|
|
23901
|
+
if (message.type === "pi_editor_draft_result") {
|
|
23902
|
+
const requestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
23903
|
+
if (!settleReservedPiEditorDraftSnapshot(requestId, message.piEditorDraftSnapshot)) return;
|
|
23904
|
+
const outcome = String(message.outcome || "");
|
|
23905
|
+
syncActionButtons();
|
|
23906
|
+
if (outcome === "cleared") {
|
|
23907
|
+
setStatus("Submission accepted; cleared the unchanged Pi draft.", "success");
|
|
23908
|
+
} else if (outcome === "already-empty") {
|
|
23909
|
+
setStatus("Submission accepted; the Pi draft was already empty.", "success");
|
|
23910
|
+
} else if (outcome !== "changed") {
|
|
23911
|
+
setStatus("Submission accepted, but Studio could not confirm Pi-draft cleanup; check the terminal draft.", "warning");
|
|
23912
|
+
}
|
|
23913
|
+
return;
|
|
23914
|
+
}
|
|
23915
|
+
|
|
23360
23916
|
if (message.type === "editor_loaded") {
|
|
23361
23917
|
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
23362
23918
|
pendingRequestId = null;
|
|
23363
23919
|
pendingKind = null;
|
|
23364
23920
|
}
|
|
23921
|
+
const snapshot = normalizePiEditorDraftSnapshot(message.piEditorDraftSnapshot);
|
|
23922
|
+
setLinkedPiEditorDraftSnapshot(snapshot, Boolean(snapshot && snapshot.byteLength > 0));
|
|
23365
23923
|
setBusy(false);
|
|
23366
23924
|
setWsState("Ready");
|
|
23367
23925
|
setStatus(typeof message.message === "string" ? message.message : "Loaded into pi editor.", "success");
|
|
23368
23926
|
return;
|
|
23369
23927
|
}
|
|
23370
23928
|
|
|
23929
|
+
if (message.type === "pi_editor_cleared") {
|
|
23930
|
+
if (typeof message.requestId === "string" && pendingRequestId === message.requestId) {
|
|
23931
|
+
pendingRequestId = null;
|
|
23932
|
+
pendingKind = null;
|
|
23933
|
+
}
|
|
23934
|
+
clearLinkedPiEditorDraftSnapshot();
|
|
23935
|
+
setBusy(false);
|
|
23936
|
+
setWsState("Ready");
|
|
23937
|
+
setStatus(message.cleared ? "Cleared Pi editor text." : "Pi editor text was already empty.", "success");
|
|
23938
|
+
return;
|
|
23939
|
+
}
|
|
23940
|
+
|
|
23371
23941
|
if (message.type === "editor_snapshot") {
|
|
23372
23942
|
if (isWatchedFilePreview) {
|
|
23373
23943
|
setStatus("Ignored editor snapshot because this preview follows its watched file on disk.", "warning");
|
|
@@ -23383,12 +23953,14 @@
|
|
|
23383
23953
|
|
|
23384
23954
|
const content = typeof message.content === "string" ? message.content : "";
|
|
23385
23955
|
setEditorText(content, { preserveScroll: false, preserveSelection: false });
|
|
23956
|
+
const snapshot = normalizePiEditorDraftSnapshot(message.piEditorDraftSnapshot);
|
|
23957
|
+
setLinkedPiEditorDraftSnapshot(snapshot, Boolean(content.length > 0 && snapshot));
|
|
23386
23958
|
setSourceState({ source: "pi-editor", label: "pi editor draft", path: null });
|
|
23387
23959
|
setBusy(false);
|
|
23388
23960
|
setWsState("Ready");
|
|
23389
23961
|
setStatus(
|
|
23390
23962
|
content.trim()
|
|
23391
|
-
? "Loaded draft from pi editor."
|
|
23963
|
+
? "Loaded draft from pi editor. A Studio Run will clear it if the Pi draft remains unchanged."
|
|
23392
23964
|
: "pi editor is empty. Loaded blank text.",
|
|
23393
23965
|
content.trim() ? "success" : "warning",
|
|
23394
23966
|
);
|
|
@@ -23552,6 +24124,11 @@
|
|
|
23552
24124
|
|
|
23553
24125
|
setBusy(busy);
|
|
23554
24126
|
setWsState(busy ? "Submitting" : "Ready");
|
|
24127
|
+
if (agentBusyFromServer) {
|
|
24128
|
+
beginTrackedStudioActivity(pendingRequestId || "active");
|
|
24129
|
+
} else {
|
|
24130
|
+
finishTrackedStudioActivity();
|
|
24131
|
+
}
|
|
23555
24132
|
|
|
23556
24133
|
if (pendingRequestId) {
|
|
23557
24134
|
if (busy) {
|
|
@@ -23580,6 +24157,7 @@
|
|
|
23580
24157
|
|
|
23581
24158
|
if (message.type === "busy") {
|
|
23582
24159
|
if (typeof message.requestId === "string") {
|
|
24160
|
+
restoreReservedPiEditorDraftSnapshot(message.requestId);
|
|
23583
24161
|
pendingSaveOperations.delete(message.requestId);
|
|
23584
24162
|
failPendingCompanionLaunch(message.requestId, "Studio could not start the companion editor because another request was busy.");
|
|
23585
24163
|
}
|
|
@@ -23594,6 +24172,7 @@
|
|
|
23594
24172
|
clearArmedTitleAttention(message.requestId);
|
|
23595
24173
|
}
|
|
23596
24174
|
stickyStudioKind = null;
|
|
24175
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23597
24176
|
setBusy(false);
|
|
23598
24177
|
setWsState("Ready");
|
|
23599
24178
|
setStatus(typeof message.message === "string" ? message.message : "Studio is busy.", "warning");
|
|
@@ -23602,6 +24181,7 @@
|
|
|
23602
24181
|
|
|
23603
24182
|
if (message.type === "error") {
|
|
23604
24183
|
if (typeof message.requestId === "string") {
|
|
24184
|
+
restoreReservedPiEditorDraftSnapshot(message.requestId);
|
|
23605
24185
|
pendingSaveOperations.delete(message.requestId);
|
|
23606
24186
|
failPendingCompanionLaunch(message.requestId, "Studio could not prepare the companion editor. Return to the originating Studio page for details.");
|
|
23607
24187
|
}
|
|
@@ -23615,9 +24195,14 @@
|
|
|
23615
24195
|
if (typeof message.requestId === "string") {
|
|
23616
24196
|
clearArmedTitleAttention(message.requestId);
|
|
23617
24197
|
}
|
|
23618
|
-
|
|
24198
|
+
const failedReplRequestId = typeof message.requestId === "string" && message.requestId === replPendingRequestId
|
|
24199
|
+
? message.requestId
|
|
24200
|
+
: "";
|
|
24201
|
+
if (failedReplRequestId) {
|
|
24202
|
+
replPendingRequestId = "";
|
|
23619
24203
|
replBusy = false;
|
|
23620
24204
|
replError = typeof message.message === "string" ? message.message : "REPL request failed.";
|
|
24205
|
+
settleReplQuickPending(failedReplRequestId, false);
|
|
23621
24206
|
if (typeof message.requestId === "string") {
|
|
23622
24207
|
replJournalEntries = replJournalEntries.map((entry) => entry.requestId === message.requestId ? { ...entry, status: "error", output: replError, updatedAt: Date.now() } : entry);
|
|
23623
24208
|
persistReplJournalEntries();
|
|
@@ -23625,6 +24210,7 @@
|
|
|
23625
24210
|
renderReplViewIfActive({ force: true });
|
|
23626
24211
|
}
|
|
23627
24212
|
stickyStudioKind = null;
|
|
24213
|
+
finishTrackedStudioActivity(typeof message.requestId === "string" ? message.requestId : "");
|
|
23628
24214
|
setBusy(false);
|
|
23629
24215
|
setWsState("Ready");
|
|
23630
24216
|
setStatus(typeof message.message === "string" ? message.message : "Request failed.", "error");
|
|
@@ -23744,6 +24330,7 @@
|
|
|
23744
24330
|
quartoPreviewCheckSourcePath = "";
|
|
23745
24331
|
quartoPreviewActionRequestId = null;
|
|
23746
24332
|
sideQuestionMarkdownExportRequest = null;
|
|
24333
|
+
pendingPiEditorDraftSnapshots.clear();
|
|
23747
24334
|
failAllPendingCompanionLaunches("The originating Studio connection was lost before the companion editor was ready.");
|
|
23748
24335
|
if (rightView === "editor-quarto-preview") renderQuartoPreviewView();
|
|
23749
24336
|
setBusy(true);
|
|
@@ -24014,7 +24601,7 @@
|
|
|
24014
24601
|
|
|
24015
24602
|
if (stripped.hadHeader) {
|
|
24016
24603
|
const updated = stripped.body;
|
|
24017
|
-
setEditorText(updated, { preserveScroll: true, preserveSelection: true });
|
|
24604
|
+
setEditorText(updated, { preserveScroll: true, preserveSelection: true, preservePiEditorDraftLink: true });
|
|
24018
24605
|
updateResultActionButtons();
|
|
24019
24606
|
setStatus("Removed annotated reply header.", "success");
|
|
24020
24607
|
return;
|
|
@@ -24027,7 +24614,7 @@
|
|
|
24027
24614
|
return;
|
|
24028
24615
|
}
|
|
24029
24616
|
|
|
24030
|
-
setEditorText(updated, { preserveScroll: true, preserveSelection: true });
|
|
24617
|
+
setEditorText(updated, { preserveScroll: true, preserveSelection: true, preservePiEditorDraftLink: true });
|
|
24031
24618
|
updateResultActionButtons();
|
|
24032
24619
|
setStatus("Inserted annotated reply header.", "success");
|
|
24033
24620
|
}
|
|
@@ -24160,6 +24747,18 @@
|
|
|
24160
24747
|
});
|
|
24161
24748
|
}
|
|
24162
24749
|
|
|
24750
|
+
if (studioPaneLayoutSelect) {
|
|
24751
|
+
studioPaneLayoutSelect.addEventListener("change", () => {
|
|
24752
|
+
setStudioPaneLayout(studioPaneLayoutSelect.value);
|
|
24753
|
+
});
|
|
24754
|
+
}
|
|
24755
|
+
|
|
24756
|
+
if (activityTrackingSelect) {
|
|
24757
|
+
activityTrackingSelect.addEventListener("change", () => {
|
|
24758
|
+
setActivityTrackingEnabled(activityTrackingSelect.value === "on");
|
|
24759
|
+
});
|
|
24760
|
+
}
|
|
24761
|
+
|
|
24163
24762
|
if (annotationModeSelect) {
|
|
24164
24763
|
annotationModeSelect.addEventListener("change", () => {
|
|
24165
24764
|
setAnnotationsEnabled(annotationModeSelect.value !== "off");
|
|
@@ -24885,6 +25484,29 @@
|
|
|
24885
25484
|
}
|
|
24886
25485
|
});
|
|
24887
25486
|
|
|
25487
|
+
if (clearPiEditorBtn) {
|
|
25488
|
+
clearPiEditorBtn.addEventListener("click", async () => {
|
|
25489
|
+
closeStudioUiRefreshMenus();
|
|
25490
|
+
const confirmed = await requestStudioConfirmation(
|
|
25491
|
+
"Clear the current Pi input draft? This removes text waiting in the terminal editor, including context added from Neovim. Studio text and Pi conversation history will not change.",
|
|
25492
|
+
{ title: "Clear Pi editor text?", confirmLabel: "Clear text", destructive: true },
|
|
25493
|
+
);
|
|
25494
|
+
if (!confirmed) {
|
|
25495
|
+
setStatus("Pi editor text was not cleared.");
|
|
25496
|
+
return;
|
|
25497
|
+
}
|
|
25498
|
+
|
|
25499
|
+
const requestId = beginUiAction("clear_pi_editor");
|
|
25500
|
+
if (!requestId) return;
|
|
25501
|
+
const sent = sendMessage({ type: "clear_pi_editor_request", requestId });
|
|
25502
|
+
if (!sent) {
|
|
25503
|
+
pendingRequestId = null;
|
|
25504
|
+
pendingKind = null;
|
|
25505
|
+
setBusy(false);
|
|
25506
|
+
}
|
|
25507
|
+
});
|
|
25508
|
+
}
|
|
25509
|
+
|
|
24888
25510
|
if (openCompanionBtn) {
|
|
24889
25511
|
openCompanionBtn.addEventListener("click", () => {
|
|
24890
25512
|
requestOpenEditorOnlyDocument("", {
|
|
@@ -24952,14 +25574,17 @@
|
|
|
24952
25574
|
|
|
24953
25575
|
const requestId = beginUiAction("direct");
|
|
24954
25576
|
if (!requestId) return;
|
|
25577
|
+
const piEditorDraftSnapshot = reserveLinkedPiEditorDraftSnapshot(requestId);
|
|
24955
25578
|
|
|
24956
25579
|
const sent = sendMessage({
|
|
24957
25580
|
type: "send_run_request",
|
|
24958
25581
|
requestId,
|
|
24959
25582
|
text: prepared,
|
|
25583
|
+
piEditorDraftSnapshot: piEditorDraftSnapshot || undefined,
|
|
24960
25584
|
});
|
|
24961
25585
|
|
|
24962
25586
|
if (!sent) {
|
|
25587
|
+
restoreReservedPiEditorDraftSnapshot(requestId);
|
|
24963
25588
|
pendingRequestId = null;
|
|
24964
25589
|
pendingKind = null;
|
|
24965
25590
|
setBusy(false);
|
|
@@ -24980,12 +25605,17 @@
|
|
|
24980
25605
|
|
|
24981
25606
|
const requestId = makeRequestId();
|
|
24982
25607
|
clearTitleAttention();
|
|
25608
|
+
const piEditorDraftSnapshot = reserveLinkedPiEditorDraftSnapshot(requestId);
|
|
24983
25609
|
const sent = sendMessage({
|
|
24984
25610
|
type: "send_run_request",
|
|
24985
25611
|
requestId,
|
|
24986
25612
|
text: prepared,
|
|
25613
|
+
piEditorDraftSnapshot: piEditorDraftSnapshot || undefined,
|
|
24987
25614
|
});
|
|
24988
|
-
if (!sent)
|
|
25615
|
+
if (!sent) {
|
|
25616
|
+
restoreReservedPiEditorDraftSnapshot(requestId);
|
|
25617
|
+
return;
|
|
25618
|
+
}
|
|
24989
25619
|
setStatus("Queueing steering…", "warning");
|
|
24990
25620
|
});
|
|
24991
25621
|
}
|
|
@@ -25421,7 +26051,7 @@
|
|
|
25421
26051
|
if (!confirmed) return;
|
|
25422
26052
|
|
|
25423
26053
|
const strippedContent = stripAnnotationMarkers(content);
|
|
25424
|
-
setEditorText(strippedContent, { preserveScroll: true, preserveSelection: false });
|
|
26054
|
+
setEditorText(strippedContent, { preserveScroll: true, preserveSelection: false, preservePiEditorDraftLink: true });
|
|
25425
26055
|
setStatus("Removed annotation markers from editor text.", "success");
|
|
25426
26056
|
});
|
|
25427
26057
|
}
|
|
@@ -25628,6 +26258,7 @@
|
|
|
25628
26258
|
const storedLineNumbersEnabled = readStoredEditorLineNumbersEnabled();
|
|
25629
26259
|
const initialLineNumbersEnabled = storedLineNumbersEnabled ?? Boolean(lineNumbersSelect && lineNumbersSelect.value === "on");
|
|
25630
26260
|
setLineNumbersEnabled(initialLineNumbersEnabled);
|
|
26261
|
+
setActivityTrackingEnabled(activityTrackingEnabled, { persist: false, silent: true });
|
|
25631
26262
|
|
|
25632
26263
|
const storedResponseHighlightEnabled = readStoredResponseHighlightEnabled();
|
|
25633
26264
|
const initialResponseHighlightEnabled = storedResponseHighlightEnabled ?? Boolean(responseHighlightSelect && responseHighlightSelect.value === "on");
|