pi-studio 0.9.22 → 0.9.24
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 +18 -0
- package/README.md +9 -1
- package/client/studio-client.js +334 -71
- package/client/studio.css +364 -28
- package/index.ts +437 -33
- package/package.json +1 -1
package/client/studio-client.js
CHANGED
|
@@ -110,7 +110,6 @@
|
|
|
110
110
|
const openCompanionBtn = document.getElementById("openCompanionBtn");
|
|
111
111
|
const getEditorBtn = document.getElementById("getEditorBtn");
|
|
112
112
|
const zenModeBtn = document.getElementById("zenModeBtn");
|
|
113
|
-
const loadGitDiffBtn = document.getElementById("loadGitDiffBtn");
|
|
114
113
|
const sendRunBtn = document.getElementById("sendRunBtn");
|
|
115
114
|
const queueSteerBtn = document.getElementById("queueSteerBtn");
|
|
116
115
|
const sendReplBtn = document.getElementById("sendReplBtn");
|
|
@@ -237,6 +236,7 @@
|
|
|
237
236
|
let sourceResetOriginBtn = null;
|
|
238
237
|
let sourceOpenCurrentFileTabBtn = null;
|
|
239
238
|
let sourceOpenCurrentTextCopyTabBtn = null;
|
|
239
|
+
let sourceSessionSummaryEl = null;
|
|
240
240
|
let initialDocumentApplied = false;
|
|
241
241
|
function normalizeRightViewValue(nextView) {
|
|
242
242
|
const raw = String(nextView || "").trim();
|
|
@@ -248,8 +248,10 @@
|
|
|
248
248
|
? "repl"
|
|
249
249
|
: (raw === "files"
|
|
250
250
|
? "files"
|
|
251
|
-
: (
|
|
252
|
-
|
|
251
|
+
: (raw === "changes"
|
|
252
|
+
? "changes"
|
|
253
|
+
: ((raw === "trace" || raw === "thinking") ? "trace" : "markdown")))));
|
|
254
|
+
if (isEditorOnlyMode && normalized !== "editor-preview" && normalized !== "files" && normalized !== "changes" && normalized !== "repl") {
|
|
253
255
|
return "editor-preview";
|
|
254
256
|
}
|
|
255
257
|
return normalized;
|
|
@@ -257,13 +259,13 @@
|
|
|
257
259
|
|
|
258
260
|
function syncRightViewModeOptions() {
|
|
259
261
|
if (!rightViewSelect || !rightViewSelect.options) return;
|
|
260
|
-
const editorOnlyAllowed = new Set(["editor-preview", "files", "repl"]);
|
|
262
|
+
const editorOnlyAllowed = new Set(["editor-preview", "files", "changes", "repl"]);
|
|
261
263
|
Array.from(rightViewSelect.options).forEach((option) => {
|
|
262
264
|
if (!option) return;
|
|
263
265
|
option.disabled = isEditorOnlyMode && !editorOnlyAllowed.has(option.value);
|
|
264
266
|
});
|
|
265
267
|
rightViewSelect.title = isEditorOnlyMode
|
|
266
|
-
? "Editor-only views: editor preview, Files, or REPL. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview."
|
|
268
|
+
? "Editor-only views: editor preview, Changes, Files, or REPL. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview."
|
|
267
269
|
: "Right pane view mode. F7 cycles when the right pane is active; Cmd/Ctrl+Alt+P switches directly to Preview.";
|
|
268
270
|
}
|
|
269
271
|
|
|
@@ -296,6 +298,19 @@
|
|
|
296
298
|
let traceAutoScroll = true;
|
|
297
299
|
let traceRenderRaf = null;
|
|
298
300
|
const traceExpandedOutputs = new Set();
|
|
301
|
+
let gitChangesState = {
|
|
302
|
+
status: "idle",
|
|
303
|
+
requestId: null,
|
|
304
|
+
content: "",
|
|
305
|
+
label: "",
|
|
306
|
+
repoRoot: "",
|
|
307
|
+
branch: "",
|
|
308
|
+
hasHead: true,
|
|
309
|
+
files: [],
|
|
310
|
+
selectedPath: "",
|
|
311
|
+
message: "",
|
|
312
|
+
level: "info",
|
|
313
|
+
};
|
|
299
314
|
const TRACE_OUTPUT_PREVIEW_MAX_LINES = 50;
|
|
300
315
|
const TRACE_OUTPUT_PREVIEW_MAX_CHARS = 8000;
|
|
301
316
|
const TRACE_IMAGE_SAFE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
@@ -2485,9 +2500,19 @@
|
|
|
2485
2500
|
});
|
|
2486
2501
|
});
|
|
2487
2502
|
appendStudioUiRefreshMenuSection(contextMenu.menu, "Suggestions", [cursorContextBtn, sessionContextBtn]);
|
|
2503
|
+
const statusItems = [];
|
|
2504
|
+
if (!isEditorOnlyMode) {
|
|
2505
|
+
sourceSessionSummaryEl = makeStudioUiRefreshElement("div", "source-badge source-session-summary", "Session tree: branch history follows the current Pi branch. Editor text is independent.");
|
|
2506
|
+
sourceSessionSummaryEl.setAttribute("aria-label", "Pi session tree and editor sync behaviour");
|
|
2507
|
+
sourceSessionSummaryEl.title = "Use /tree in the Pi terminal to navigate branches. Studio updates branch history to match the active branch and leaves editor text unchanged.";
|
|
2508
|
+
statusItems.push(sourceSessionSummaryEl);
|
|
2509
|
+
}
|
|
2488
2510
|
if (syncBadgeEl) {
|
|
2489
2511
|
syncBadgeEl.hidden = false;
|
|
2490
|
-
|
|
2512
|
+
statusItems.push(syncBadgeEl);
|
|
2513
|
+
}
|
|
2514
|
+
if (statusItems.length > 0) {
|
|
2515
|
+
appendStudioUiRefreshMenuSection(contextMenu.menu, "Status", statusItems);
|
|
2491
2516
|
}
|
|
2492
2517
|
}
|
|
2493
2518
|
|
|
@@ -3720,12 +3745,12 @@
|
|
|
3720
3745
|
|
|
3721
3746
|
function triggerResponseHistoryShortcut(action) {
|
|
3722
3747
|
if (isEditorOnlyMode) {
|
|
3723
|
-
setStatus("
|
|
3748
|
+
setStatus("Branch history is unavailable in editor-only Studio.", "warning");
|
|
3724
3749
|
return false;
|
|
3725
3750
|
}
|
|
3726
3751
|
const total = Array.isArray(responseHistory) ? responseHistory.length : 0;
|
|
3727
3752
|
if (total <= 0) {
|
|
3728
|
-
setStatus("No
|
|
3753
|
+
setStatus("No branch history available yet.", "warning");
|
|
3729
3754
|
return false;
|
|
3730
3755
|
}
|
|
3731
3756
|
if (action === "previous") {
|
|
@@ -4208,7 +4233,7 @@
|
|
|
4208
4233
|
? responseHistoryIndex + 1
|
|
4209
4234
|
: 0;
|
|
4210
4235
|
if (historyIndexBadgeEl) {
|
|
4211
|
-
historyIndexBadgeEl.textContent = "
|
|
4236
|
+
historyIndexBadgeEl.textContent = "Branch history: " + selected + "/" + total;
|
|
4212
4237
|
}
|
|
4213
4238
|
if (historyPrevBtn) {
|
|
4214
4239
|
historyPrevBtn.disabled = total <= 1 || responseHistoryIndex <= 0;
|
|
@@ -4267,7 +4292,7 @@
|
|
|
4267
4292
|
const item = getSelectedHistoryItem();
|
|
4268
4293
|
if (item) {
|
|
4269
4294
|
const responseLabel = item.kind === "critique" ? "critique" : "response";
|
|
4270
|
-
setStatus("Viewing " + responseLabel + " history " + (nextIndex + 1) + "/" + total + ".");
|
|
4295
|
+
setStatus("Viewing " + responseLabel + " in current branch history " + (nextIndex + 1) + "/" + total + ".");
|
|
4271
4296
|
}
|
|
4272
4297
|
}
|
|
4273
4298
|
return applied;
|
|
@@ -4337,6 +4362,14 @@
|
|
|
4337
4362
|
return;
|
|
4338
4363
|
}
|
|
4339
4364
|
|
|
4365
|
+
if (rightView === "changes") {
|
|
4366
|
+
const count = getGitChangedFiles().length;
|
|
4367
|
+
referenceBadgeEl.textContent = gitChangesState.status === "loading"
|
|
4368
|
+
? "Changes: loading"
|
|
4369
|
+
: (count ? ("Changes: " + count + " file" + (count === 1 ? "" : "s")) : "Changes: none");
|
|
4370
|
+
return;
|
|
4371
|
+
}
|
|
4372
|
+
|
|
4340
4373
|
if (rightView === "trace") {
|
|
4341
4374
|
const state = traceState || createEmptyTraceState();
|
|
4342
4375
|
const context = traceDisplayContext || {};
|
|
@@ -4389,7 +4422,7 @@
|
|
|
4389
4422
|
const selected = total > 0 && responseHistoryIndex >= 0 && responseHistoryIndex < total
|
|
4390
4423
|
? responseHistoryIndex + 1
|
|
4391
4424
|
: 0;
|
|
4392
|
-
const historyPrefix = total > 0 ? "
|
|
4425
|
+
const historyPrefix = total > 0 ? "Branch history " + selected + "/" + total + " · " : "";
|
|
4393
4426
|
referenceBadgeEl.textContent = time
|
|
4394
4427
|
? historyPrefix + responseLabel + " · " + time
|
|
4395
4428
|
: historyPrefix + responseLabel;
|
|
@@ -7788,6 +7821,7 @@
|
|
|
7788
7821
|
critiqueViewEl.addEventListener("click", handleTracePaneClick);
|
|
7789
7822
|
critiqueViewEl.addEventListener("click", handleReplPaneClick);
|
|
7790
7823
|
critiqueViewEl.addEventListener("click", handleFilesPaneClick);
|
|
7824
|
+
critiqueViewEl.addEventListener("click", handleGitChangesPaneClick);
|
|
7791
7825
|
critiqueViewEl.addEventListener("change", handleReplPaneChange);
|
|
7792
7826
|
}
|
|
7793
7827
|
|
|
@@ -9702,7 +9736,222 @@
|
|
|
9702
9736
|
}
|
|
9703
9737
|
}
|
|
9704
9738
|
|
|
9739
|
+
function getGitChangesContext() {
|
|
9740
|
+
return getHtmlPreviewResourceContextOptions();
|
|
9741
|
+
}
|
|
9742
|
+
|
|
9743
|
+
function getGitChangedFiles() {
|
|
9744
|
+
return Array.isArray(gitChangesState.files) ? gitChangesState.files : [];
|
|
9745
|
+
}
|
|
9746
|
+
|
|
9747
|
+
function getSelectedGitChangedFile() {
|
|
9748
|
+
const files = getGitChangedFiles();
|
|
9749
|
+
if (!files.length) return null;
|
|
9750
|
+
const selectedPath = String(gitChangesState.selectedPath || "");
|
|
9751
|
+
return files.find((file) => String(file.path || "") === selectedPath) || files[0] || null;
|
|
9752
|
+
}
|
|
9753
|
+
|
|
9754
|
+
function getGitChangeStatusLabel(status) {
|
|
9755
|
+
if (status === "untracked") return "Untracked";
|
|
9756
|
+
if (status === "added") return "Added";
|
|
9757
|
+
if (status === "deleted") return "Deleted";
|
|
9758
|
+
if (status === "renamed") return "Renamed";
|
|
9759
|
+
if (status === "binary") return "Binary";
|
|
9760
|
+
return "Modified";
|
|
9761
|
+
}
|
|
9762
|
+
|
|
9763
|
+
function getGitChangeStatusIcon(status) {
|
|
9764
|
+
if (status === "untracked") return "??";
|
|
9765
|
+
if (status === "added") return "A";
|
|
9766
|
+
if (status === "deleted") return "D";
|
|
9767
|
+
if (status === "renamed") return "R";
|
|
9768
|
+
if (status === "binary") return "BIN";
|
|
9769
|
+
return "M";
|
|
9770
|
+
}
|
|
9771
|
+
|
|
9772
|
+
function buildGitChangesDiffHtml(diffText) {
|
|
9773
|
+
const lines = String(diffText || "").split("\n");
|
|
9774
|
+
return "<pre class='git-changes-diff'><code>" + lines.map((line) => {
|
|
9775
|
+
let cls = "git-changes-line";
|
|
9776
|
+
if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("diff --git") || line.startsWith("@@")) {
|
|
9777
|
+
cls += " git-changes-line-meta";
|
|
9778
|
+
} else if (line.startsWith("+")) {
|
|
9779
|
+
cls += " git-changes-line-add";
|
|
9780
|
+
} else if (line.startsWith("-")) {
|
|
9781
|
+
cls += " git-changes-line-del";
|
|
9782
|
+
}
|
|
9783
|
+
return "<span class='" + cls + "'>" + escapeHtml(line || " ") + "</span>";
|
|
9784
|
+
}).join("\n") + "</code></pre>";
|
|
9785
|
+
}
|
|
9786
|
+
|
|
9787
|
+
function buildGitChangesPanelHtml() {
|
|
9788
|
+
const files = getGitChangedFiles();
|
|
9789
|
+
const selected = getSelectedGitChangedFile();
|
|
9790
|
+
const isLoading = gitChangesState.status === "loading";
|
|
9791
|
+
const hasError = gitChangesState.status === "error";
|
|
9792
|
+
const label = gitChangesState.label || (files.length ? (files.length + " changed") : "Git changes");
|
|
9793
|
+
const branch = gitChangesState.branch || "";
|
|
9794
|
+
const repoRoot = gitChangesState.repoRoot || "";
|
|
9795
|
+
const subtitleParts = [];
|
|
9796
|
+
subtitleParts.push(label);
|
|
9797
|
+
if (branch) subtitleParts.push((gitChangesState.hasHead === false ? "No commits yet on " : "on ") + branch);
|
|
9798
|
+
const rows = files.length
|
|
9799
|
+
? files.map((file) => {
|
|
9800
|
+
const path = String(file.path || "");
|
|
9801
|
+
const status = String(file.status || "modified");
|
|
9802
|
+
const isSelected = selected && String(selected.path || "") === path;
|
|
9803
|
+
const stats = (Number(file.additions) || 0) || (Number(file.deletions) || 0)
|
|
9804
|
+
? "<span class='git-changes-stats'><span class='git-changes-additions'>+" + escapeHtml(String(Number(file.additions) || 0)) + "</span><span class='git-changes-deletions'>−" + escapeHtml(String(Number(file.deletions) || 0)) + "</span></span>"
|
|
9805
|
+
: "";
|
|
9806
|
+
return "<button type='button' class='git-changes-file" + (isSelected ? " is-selected" : "") + "' data-git-change-action='select' data-git-change-path='" + escapeHtml(path) + "' title='" + escapeHtml(path) + "'>"
|
|
9807
|
+
+ "<span class='git-changes-file-icon git-changes-file-icon-" + escapeHtml(status) + "'>" + escapeHtml(getGitChangeStatusIcon(status)) + "</span>"
|
|
9808
|
+
+ "<span class='git-changes-file-name'>" + escapeHtml(path) + "</span>"
|
|
9809
|
+
+ "<span class='git-changes-file-status'>" + escapeHtml(getGitChangeStatusLabel(status)) + "</span>"
|
|
9810
|
+
+ stats
|
|
9811
|
+
+ "</button>";
|
|
9812
|
+
}).join("")
|
|
9813
|
+
: "<div class='git-changes-empty'>" + escapeHtml(isLoading ? "Loading git changes…" : (gitChangesState.message || "No uncommitted git changes.")) + "</div>";
|
|
9814
|
+
const selectedDiff = selected && selected.diff ? String(selected.diff) : String(gitChangesState.content || "");
|
|
9815
|
+
const selectedStatus = selected ? String(selected.status || "modified") : "";
|
|
9816
|
+
const selectedCanOpen = selected && selectedStatus !== "deleted" && gitChangesState.repoRoot;
|
|
9817
|
+
const selectedAbsPath = selectedCanOpen ? String(gitChangesState.repoRoot).replace(/\/$/, "") + "/" + String(selected.path || "") : "";
|
|
9818
|
+
const notice = hasError && gitChangesState.message
|
|
9819
|
+
? "<div class='git-changes-notice git-changes-notice-" + escapeHtml(gitChangesState.level || "warning") + "'>" + escapeHtml(gitChangesState.message) + "</div>"
|
|
9820
|
+
: "";
|
|
9821
|
+
return "<div class='git-changes-panel'>"
|
|
9822
|
+
+ "<div class='git-changes-toolbar'>"
|
|
9823
|
+
+ "<div class='git-changes-title-group'>"
|
|
9824
|
+
+ "<div class='git-changes-title'>Git changes</div>"
|
|
9825
|
+
+ "<div class='git-changes-subtitle'>" + escapeHtml(subtitleParts.filter(Boolean).join(" · ")) + "</div>"
|
|
9826
|
+
+ (repoRoot ? "<div class='git-changes-root' title='" + escapeHtml(repoRoot) + "'>" + escapeHtml(repoRoot) + "</div>" : "")
|
|
9827
|
+
+ "</div>"
|
|
9828
|
+
+ "<div class='git-changes-actions'>"
|
|
9829
|
+
+ "<button type='button' data-git-change-action='refresh'" + (isLoading ? " disabled" : "") + ">Refresh</button>"
|
|
9830
|
+
+ "<button type='button' data-git-change-action='open' data-git-change-abs-path='" + escapeHtml(selectedAbsPath) + "'" + (selectedCanOpen ? "" : " disabled") + ">Open file</button>"
|
|
9831
|
+
+ "<button type='button' data-git-change-action='load'" + (gitChangesState.content ? "" : " disabled") + ">Load diff</button>"
|
|
9832
|
+
+ "<button type='button' data-git-change-action='copy'" + (gitChangesState.content ? "" : " disabled") + ">Copy diff</button>"
|
|
9833
|
+
+ "</div>"
|
|
9834
|
+
+ "</div>"
|
|
9835
|
+
+ notice
|
|
9836
|
+
+ "<div class='git-changes-body'>"
|
|
9837
|
+
+ "<div class='git-changes-file-list' role='list'>" + rows + "</div>"
|
|
9838
|
+
+ "<div class='git-changes-diff-pane'>" + (selectedDiff ? buildGitChangesDiffHtml(selectedDiff) : "<div class='git-changes-empty'>Select a changed file.</div>") + "</div>"
|
|
9839
|
+
+ "</div>"
|
|
9840
|
+
+ "</div>";
|
|
9841
|
+
}
|
|
9842
|
+
|
|
9843
|
+
function getGitChangesScrollSnapshot() {
|
|
9844
|
+
if (!critiqueViewEl) return null;
|
|
9845
|
+
const fileListEl = critiqueViewEl.querySelector(".git-changes-file-list");
|
|
9846
|
+
const diffPaneEl = critiqueViewEl.querySelector(".git-changes-diff-pane");
|
|
9847
|
+
return {
|
|
9848
|
+
paneTop: critiqueViewEl.scrollTop || 0,
|
|
9849
|
+
paneLeft: critiqueViewEl.scrollLeft || 0,
|
|
9850
|
+
fileListTop: fileListEl ? fileListEl.scrollTop || 0 : 0,
|
|
9851
|
+
fileListLeft: fileListEl ? fileListEl.scrollLeft || 0 : 0,
|
|
9852
|
+
diffTop: diffPaneEl ? diffPaneEl.scrollTop || 0 : 0,
|
|
9853
|
+
diffLeft: diffPaneEl ? diffPaneEl.scrollLeft || 0 : 0,
|
|
9854
|
+
};
|
|
9855
|
+
}
|
|
9856
|
+
|
|
9857
|
+
function restoreGitChangesScrollSnapshot(snapshot, options) {
|
|
9858
|
+
if (!critiqueViewEl || !snapshot) return;
|
|
9859
|
+
const fileListEl = critiqueViewEl.querySelector(".git-changes-file-list");
|
|
9860
|
+
const diffPaneEl = critiqueViewEl.querySelector(".git-changes-diff-pane");
|
|
9861
|
+
critiqueViewEl.scrollTop = snapshot.paneTop || 0;
|
|
9862
|
+
critiqueViewEl.scrollLeft = snapshot.paneLeft || 0;
|
|
9863
|
+
if (fileListEl) {
|
|
9864
|
+
fileListEl.scrollTop = snapshot.fileListTop || 0;
|
|
9865
|
+
fileListEl.scrollLeft = snapshot.fileListLeft || 0;
|
|
9866
|
+
}
|
|
9867
|
+
if (diffPaneEl) {
|
|
9868
|
+
if (options && options.resetDiffScroll) {
|
|
9869
|
+
diffPaneEl.scrollTop = 0;
|
|
9870
|
+
diffPaneEl.scrollLeft = 0;
|
|
9871
|
+
} else {
|
|
9872
|
+
diffPaneEl.scrollTop = snapshot.diffTop || 0;
|
|
9873
|
+
diffPaneEl.scrollLeft = snapshot.diffLeft || 0;
|
|
9874
|
+
}
|
|
9875
|
+
}
|
|
9876
|
+
}
|
|
9877
|
+
|
|
9878
|
+
function renderGitChangesView(options) {
|
|
9879
|
+
if (!critiqueViewEl) return;
|
|
9880
|
+
const scrollSnapshot = options && options.preserveScroll ? getGitChangesScrollSnapshot() : null;
|
|
9881
|
+
finishPreviewRender(critiqueViewEl);
|
|
9882
|
+
critiqueViewEl.classList.add("git-changes-host");
|
|
9883
|
+
critiqueViewEl.innerHTML = buildGitChangesPanelHtml();
|
|
9884
|
+
critiqueViewEl.classList.remove("response-scroll-resetting");
|
|
9885
|
+
restoreGitChangesScrollSnapshot(scrollSnapshot, options || {});
|
|
9886
|
+
if (gitChangesState.status === "idle") requestGitChangesSnapshot({ preserveScroll: true });
|
|
9887
|
+
scheduleResponsePaneRepaintNudge();
|
|
9888
|
+
}
|
|
9889
|
+
|
|
9890
|
+
function requestGitChangesSnapshot(options) {
|
|
9891
|
+
const requestId = makeRequestId();
|
|
9892
|
+
const context = getGitChangesContext();
|
|
9893
|
+
gitChangesState = {
|
|
9894
|
+
...gitChangesState,
|
|
9895
|
+
status: "loading",
|
|
9896
|
+
requestId,
|
|
9897
|
+
message: "",
|
|
9898
|
+
level: "info",
|
|
9899
|
+
};
|
|
9900
|
+
if (rightView === "changes") renderGitChangesView({ preserveScroll: Boolean(options && options.preserveScroll) });
|
|
9901
|
+
const message = { type: "git_changes_request", requestId };
|
|
9902
|
+
if (context.sourcePath) message.sourcePath = context.sourcePath;
|
|
9903
|
+
if (context.resourceDir) message.resourceDir = context.resourceDir;
|
|
9904
|
+
if (!sendMessage(message)) {
|
|
9905
|
+
gitChangesState = { ...gitChangesState, status: "error", message: "Studio is not connected.", level: "error" };
|
|
9906
|
+
if (rightView === "changes") renderGitChangesView({ preserveScroll: true });
|
|
9907
|
+
} else if (options && options.user) {
|
|
9908
|
+
setStatus("Refreshing git changes…", "warning");
|
|
9909
|
+
}
|
|
9910
|
+
}
|
|
9911
|
+
|
|
9912
|
+
async function handleGitChangesPaneClick(event) {
|
|
9913
|
+
if (rightView !== "changes") return;
|
|
9914
|
+
const target = event.target;
|
|
9915
|
+
const actionEl = target instanceof Element ? target.closest("[data-git-change-action]") : null;
|
|
9916
|
+
if (!actionEl) return;
|
|
9917
|
+
event.preventDefault();
|
|
9918
|
+
const action = actionEl.getAttribute("data-git-change-action") || "";
|
|
9919
|
+
if (action === "select") {
|
|
9920
|
+
gitChangesState = { ...gitChangesState, selectedPath: actionEl.getAttribute("data-git-change-path") || "" };
|
|
9921
|
+
renderGitChangesView({ preserveScroll: true, resetDiffScroll: true });
|
|
9922
|
+
return;
|
|
9923
|
+
}
|
|
9924
|
+
if (action === "refresh") {
|
|
9925
|
+
requestGitChangesSnapshot({ user: true, preserveScroll: true });
|
|
9926
|
+
return;
|
|
9927
|
+
}
|
|
9928
|
+
if (action === "copy") {
|
|
9929
|
+
const ok = await writeTextToClipboard(String(gitChangesState.content || ""));
|
|
9930
|
+
setStatus(ok ? "Copied git diff." : "Clipboard write failed.", ok ? "success" : "warning");
|
|
9931
|
+
return;
|
|
9932
|
+
}
|
|
9933
|
+
if (action === "load") {
|
|
9934
|
+
if (!String(gitChangesState.content || "").trim()) {
|
|
9935
|
+
setStatus("No git diff to load.", "warning");
|
|
9936
|
+
return;
|
|
9937
|
+
}
|
|
9938
|
+
setEditorText(String(gitChangesState.content || ""), { preserveScroll: false, preserveSelection: false });
|
|
9939
|
+
setSourceState({ source: "blank", label: gitChangesState.label || "git diff", path: null });
|
|
9940
|
+
setEditorLanguage("diff");
|
|
9941
|
+
setStatus("Loaded current git diff into editor.", "success");
|
|
9942
|
+
return;
|
|
9943
|
+
}
|
|
9944
|
+
if (action === "open") {
|
|
9945
|
+
const absPath = actionEl.getAttribute("data-git-change-abs-path") || "";
|
|
9946
|
+
if (!absPath) return;
|
|
9947
|
+
await openPreviewDocumentHere(absPath, getFileBrowserLocalLinkContext(), { fallbackPath: absPath, fileBackedIntent: true });
|
|
9948
|
+
ensureCurrentEditorFileBackedFromFilesPath(absPath);
|
|
9949
|
+
setStatus("Opened changed file in editor.", "success");
|
|
9950
|
+
}
|
|
9951
|
+
}
|
|
9952
|
+
|
|
9705
9953
|
function renderActiveResult() {
|
|
9954
|
+
if (critiqueViewEl) critiqueViewEl.classList.toggle("git-changes-host", rightView === "changes");
|
|
9706
9955
|
if (rightView === "trace") {
|
|
9707
9956
|
renderTraceView();
|
|
9708
9957
|
return;
|
|
@@ -9718,6 +9967,11 @@
|
|
|
9718
9967
|
return;
|
|
9719
9968
|
}
|
|
9720
9969
|
|
|
9970
|
+
if (rightView === "changes") {
|
|
9971
|
+
renderGitChangesView();
|
|
9972
|
+
return;
|
|
9973
|
+
}
|
|
9974
|
+
|
|
9721
9975
|
if (rightView === "editor-preview") {
|
|
9722
9976
|
const editorText = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
9723
9977
|
if (!editorText.trim()) {
|
|
@@ -9796,7 +10050,7 @@
|
|
|
9796
10050
|
: normalizeForCompare(sourceTextEl.value);
|
|
9797
10051
|
const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
|
|
9798
10052
|
const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
|
|
9799
|
-
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files";
|
|
10053
|
+
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files" || rightView === "changes";
|
|
9800
10054
|
|
|
9801
10055
|
if (responseWrapEl) {
|
|
9802
10056
|
responseWrapEl.hidden = showingAuxiliaryRightPane;
|
|
@@ -9839,6 +10093,8 @@
|
|
|
9839
10093
|
exportPdfBtn.title = "Working view does not support preview export.";
|
|
9840
10094
|
} else if (rightView === "files") {
|
|
9841
10095
|
exportPdfBtn.title = "Files view does not support preview export.";
|
|
10096
|
+
} else if (rightView === "changes") {
|
|
10097
|
+
exportPdfBtn.title = "Changes view does not support preview export.";
|
|
9842
10098
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
9843
10099
|
exportPdfBtn.title = "No Studio REPL record entries to export for this session yet.";
|
|
9844
10100
|
} else if (rightView === "markdown") {
|
|
@@ -10018,7 +10274,6 @@
|
|
|
10018
10274
|
if (clearWorkspaceBtn) clearWorkspaceBtn.disabled = uiBusy;
|
|
10019
10275
|
sendEditorBtn.disabled = uiBusy || isEditorOnlyMode;
|
|
10020
10276
|
if (getEditorBtn) getEditorBtn.disabled = uiBusy;
|
|
10021
|
-
if (loadGitDiffBtn) loadGitDiffBtn.disabled = uiBusy;
|
|
10022
10277
|
syncRunAndCritiqueButtons();
|
|
10023
10278
|
copyDraftBtn.disabled = uiBusy;
|
|
10024
10279
|
if (suggestCompletionBtn) {
|
|
@@ -10775,6 +11030,9 @@
|
|
|
10775
11030
|
if (rightView === "trace" && previousView !== "trace") {
|
|
10776
11031
|
traceAutoScroll = true;
|
|
10777
11032
|
}
|
|
11033
|
+
if (rightView === "changes" && previousView !== "changes" && gitChangesState.status === "idle") {
|
|
11034
|
+
requestGitChangesSnapshot();
|
|
11035
|
+
}
|
|
10778
11036
|
if (rightView === "repl" && previousView !== "repl") {
|
|
10779
11037
|
replFollow = true;
|
|
10780
11038
|
startReplPolling();
|
|
@@ -13097,17 +13355,24 @@
|
|
|
13097
13355
|
if (!scratchpadRecentPanelEl) return;
|
|
13098
13356
|
scratchpadRecentPanelEl.hidden = !scratchpadRecentVisible;
|
|
13099
13357
|
if (!scratchpadRecentVisible) return;
|
|
13358
|
+
const headerHtml = "<div class='scratchpad-recent-header'>"
|
|
13359
|
+
+ "<div class='scratchpad-recent-heading-group'>"
|
|
13360
|
+
+ "<div class='scratchpad-recent-heading'>Recent scratchpads</div>"
|
|
13361
|
+
+ "<div class='scratchpad-recent-subtitle'>Load, append, or copy notes saved for other documents and drafts.</div>"
|
|
13362
|
+
+ "</div>"
|
|
13363
|
+
+ "<button type='button' class='scratchpad-recent-hide-btn' data-scratchpad-recent-action='hide' aria-label='Hide recent scratchpads' title='Hide recent scratchpads'>Hide</button>"
|
|
13364
|
+
+ "</div>";
|
|
13100
13365
|
if (scratchpadRecentLoading) {
|
|
13101
|
-
scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-loading'>Loading recent scratchpads…</div>";
|
|
13366
|
+
scratchpadRecentPanelEl.innerHTML = headerHtml + "<div class='scratchpad-recent-loading'>Loading recent scratchpads…</div>";
|
|
13102
13367
|
return;
|
|
13103
13368
|
}
|
|
13104
13369
|
const currentKey = getCurrentStudioDocumentDescriptor().key;
|
|
13105
13370
|
const entries = Array.isArray(scratchpadRecentEntries) ? scratchpadRecentEntries : [];
|
|
13106
13371
|
if (!entries.length) {
|
|
13107
|
-
scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-empty'>No other saved scratchpads yet.</div>";
|
|
13372
|
+
scratchpadRecentPanelEl.innerHTML = headerHtml + "<div class='scratchpad-recent-empty'>No other saved scratchpads yet.</div>";
|
|
13108
13373
|
return;
|
|
13109
13374
|
}
|
|
13110
|
-
scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-list'>" + entries.map((entry) => {
|
|
13375
|
+
scratchpadRecentPanelEl.innerHTML = headerHtml + "<div class='scratchpad-recent-list'>" + entries.map((entry) => {
|
|
13111
13376
|
const key = String(entry && entry.documentKey ? entry.documentKey : "");
|
|
13112
13377
|
const isCurrent = key === currentKey;
|
|
13113
13378
|
const label = String(entry && entry.label ? entry.label : key || "scratchpad");
|
|
@@ -13145,13 +13410,19 @@
|
|
|
13145
13410
|
}
|
|
13146
13411
|
}
|
|
13147
13412
|
|
|
13413
|
+
function hideScratchpadRecentPanel() {
|
|
13414
|
+
scratchpadRecentVisible = false;
|
|
13415
|
+
renderScratchpadRecentPanel();
|
|
13416
|
+
updateScratchpadUi();
|
|
13417
|
+
}
|
|
13418
|
+
|
|
13148
13419
|
function toggleScratchpadRecentPanel() {
|
|
13149
|
-
scratchpadRecentVisible = !scratchpadRecentVisible;
|
|
13150
13420
|
if (scratchpadRecentVisible) {
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
renderScratchpadRecentPanel();
|
|
13421
|
+
hideScratchpadRecentPanel();
|
|
13422
|
+
return;
|
|
13154
13423
|
}
|
|
13424
|
+
scratchpadRecentVisible = true;
|
|
13425
|
+
void loadScratchpadRecentEntries();
|
|
13155
13426
|
updateScratchpadUi();
|
|
13156
13427
|
}
|
|
13157
13428
|
|
|
@@ -13181,6 +13452,7 @@
|
|
|
13181
13452
|
if (!confirmed) return;
|
|
13182
13453
|
}
|
|
13183
13454
|
setScratchpadText(text);
|
|
13455
|
+
hideScratchpadRecentPanel();
|
|
13184
13456
|
setStatus("Loaded recent scratchpad into current scratchpad.", "success");
|
|
13185
13457
|
} catch (error) {
|
|
13186
13458
|
setStatus("Could not use recent scratchpad: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
@@ -16864,7 +17136,7 @@
|
|
|
16864
17136
|
const hasNotes = count > 0;
|
|
16865
17137
|
const isOpen = isReviewNotesOpen();
|
|
16866
17138
|
if (reviewNotesBtn) {
|
|
16867
|
-
reviewNotesBtn.textContent =
|
|
17139
|
+
reviewNotesBtn.textContent = "Comments";
|
|
16868
17140
|
reviewNotesBtn.classList.toggle("has-content", hasNotes);
|
|
16869
17141
|
reviewNotesBtn.classList.toggle("is-active", isOpen);
|
|
16870
17142
|
reviewNotesBtn.setAttribute("aria-pressed", isOpen ? "true" : "false");
|
|
@@ -17466,7 +17738,7 @@
|
|
|
17466
17738
|
const hasContent = Boolean(normalized.trim());
|
|
17467
17739
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
17468
17740
|
if (scratchpadBtn) {
|
|
17469
|
-
scratchpadBtn.textContent =
|
|
17741
|
+
scratchpadBtn.textContent = "Scratchpad";
|
|
17470
17742
|
scratchpadBtn.classList.toggle("has-content", hasContent);
|
|
17471
17743
|
scratchpadBtn.title = hasContent
|
|
17472
17744
|
? ("Open the local persistent scratchpad for this document/draft. Scope: " + descriptor.label + ". File-backed docs come back across Pi restarts; unsaved drafts stay with this draft instance until saved or cleared.")
|
|
@@ -17480,6 +17752,9 @@
|
|
|
17480
17752
|
if (scratchpadRecentBtn) {
|
|
17481
17753
|
scratchpadRecentBtn.textContent = scratchpadRecentVisible ? "Hide recent" : "Recent…";
|
|
17482
17754
|
scratchpadRecentBtn.setAttribute("aria-expanded", scratchpadRecentVisible ? "true" : "false");
|
|
17755
|
+
scratchpadRecentBtn.title = scratchpadRecentVisible
|
|
17756
|
+
? "Hide recent scratchpads."
|
|
17757
|
+
: "Show recent non-empty scratchpads saved for other files and drafts.";
|
|
17483
17758
|
}
|
|
17484
17759
|
if (scratchpadInsertBtn) scratchpadInsertBtn.disabled = !hasContent;
|
|
17485
17760
|
if (scratchpadCopyBtn) scratchpadCopyBtn.disabled = !hasContent;
|
|
@@ -18451,9 +18726,10 @@
|
|
|
18451
18726
|
}
|
|
18452
18727
|
|
|
18453
18728
|
if (message.type === "response_history") {
|
|
18729
|
+
const isTreeSync = message.reason === "tree";
|
|
18454
18730
|
setResponseHistory(message.items, {
|
|
18455
|
-
autoSelectLatest: followLatest,
|
|
18456
|
-
preserveSelection: !followLatest,
|
|
18731
|
+
autoSelectLatest: isTreeSync ? true : followLatest,
|
|
18732
|
+
preserveSelection: isTreeSync ? true : !followLatest,
|
|
18457
18733
|
silent: true,
|
|
18458
18734
|
});
|
|
18459
18735
|
return;
|
|
@@ -18570,28 +18846,31 @@
|
|
|
18570
18846
|
return;
|
|
18571
18847
|
}
|
|
18572
18848
|
|
|
18573
|
-
if (message.type === "
|
|
18574
|
-
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
const
|
|
18580
|
-
|
|
18581
|
-
?
|
|
18582
|
-
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
"
|
|
18594
|
-
|
|
18849
|
+
if (message.type === "git_changes_snapshot") {
|
|
18850
|
+
const requestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
18851
|
+
const preserveScroll = Boolean(gitChangesState.requestId && requestId && requestId === gitChangesState.requestId);
|
|
18852
|
+
if (requestId && gitChangesState.requestId && requestId !== gitChangesState.requestId) return;
|
|
18853
|
+
const ok = message.ok !== false;
|
|
18854
|
+
const files = Array.isArray(message.files) ? message.files : [];
|
|
18855
|
+
const selectedPath = files.some((file) => String(file && file.path || "") === String(gitChangesState.selectedPath || ""))
|
|
18856
|
+
? gitChangesState.selectedPath
|
|
18857
|
+
: (files[0] && files[0].path ? String(files[0].path) : "");
|
|
18858
|
+
gitChangesState = {
|
|
18859
|
+
status: ok ? "ready" : "error",
|
|
18860
|
+
requestId: null,
|
|
18861
|
+
content: ok && typeof message.content === "string" ? message.content : "",
|
|
18862
|
+
label: ok && typeof message.label === "string" ? message.label : "",
|
|
18863
|
+
repoRoot: ok && typeof message.repoRoot === "string" ? message.repoRoot : "",
|
|
18864
|
+
branch: ok && typeof message.branch === "string" ? message.branch : "",
|
|
18865
|
+
hasHead: ok ? message.hasHead !== false : true,
|
|
18866
|
+
files,
|
|
18867
|
+
selectedPath,
|
|
18868
|
+
message: typeof message.message === "string" ? message.message : "",
|
|
18869
|
+
level: typeof message.level === "string" ? message.level : "info",
|
|
18870
|
+
};
|
|
18871
|
+
if (rightView === "changes") renderGitChangesView({ preserveScroll });
|
|
18872
|
+
if (ok) setStatus(files.length ? "Loaded git changes." : "No uncommitted git changes.", files.length ? "success" : "warning");
|
|
18873
|
+
else setStatus(gitChangesState.message || "Could not load git changes.", gitChangesState.level === "error" ? "error" : "warning");
|
|
18595
18874
|
return;
|
|
18596
18875
|
}
|
|
18597
18876
|
|
|
@@ -19247,7 +19526,7 @@
|
|
|
19247
19526
|
if (historyPrevBtn) {
|
|
19248
19527
|
historyPrevBtn.addEventListener("click", () => {
|
|
19249
19528
|
if (!responseHistory.length) {
|
|
19250
|
-
setStatus("No
|
|
19529
|
+
setStatus("No branch history available yet.", "warning");
|
|
19251
19530
|
return;
|
|
19252
19531
|
}
|
|
19253
19532
|
selectHistoryIndex(responseHistoryIndex - 1);
|
|
@@ -19257,7 +19536,7 @@
|
|
|
19257
19536
|
if (historyNextBtn) {
|
|
19258
19537
|
historyNextBtn.addEventListener("click", () => {
|
|
19259
19538
|
if (!responseHistory.length) {
|
|
19260
|
-
setStatus("No
|
|
19539
|
+
setStatus("No branch history available yet.", "warning");
|
|
19261
19540
|
return;
|
|
19262
19541
|
}
|
|
19263
19542
|
selectHistoryIndex(responseHistoryIndex + 1);
|
|
@@ -19267,7 +19546,7 @@
|
|
|
19267
19546
|
if (historyLastBtn) {
|
|
19268
19547
|
historyLastBtn.addEventListener("click", () => {
|
|
19269
19548
|
if (!responseHistory.length) {
|
|
19270
|
-
setStatus("No
|
|
19549
|
+
setStatus("No branch history available yet.", "warning");
|
|
19271
19550
|
return;
|
|
19272
19551
|
}
|
|
19273
19552
|
selectHistoryIndex(responseHistory.length - 1);
|
|
@@ -19294,7 +19573,7 @@
|
|
|
19294
19573
|
if (responseHistory.length > 0) {
|
|
19295
19574
|
selectHistoryIndex(responseHistory.length - 1, { silent: true });
|
|
19296
19575
|
queuedLatestResponse = null;
|
|
19297
|
-
setStatus("Pulled latest response from history.", "success");
|
|
19576
|
+
setStatus("Pulled latest response from branch history.", "success");
|
|
19298
19577
|
updateResultActionButtons();
|
|
19299
19578
|
} else if (applyLatestPayload(queuedLatestResponse)) {
|
|
19300
19579
|
queuedLatestResponse = null;
|
|
@@ -19652,27 +19931,6 @@
|
|
|
19652
19931
|
});
|
|
19653
19932
|
}
|
|
19654
19933
|
|
|
19655
|
-
if (loadGitDiffBtn) {
|
|
19656
|
-
loadGitDiffBtn.addEventListener("click", () => {
|
|
19657
|
-
const requestId = beginUiAction("load_git_diff");
|
|
19658
|
-
if (!requestId) return;
|
|
19659
|
-
|
|
19660
|
-
const effectivePath = getEffectiveSavePath();
|
|
19661
|
-
const sent = sendMessage({
|
|
19662
|
-
type: "load_git_diff_request",
|
|
19663
|
-
requestId,
|
|
19664
|
-
sourcePath: effectivePath || sourceState.path || undefined,
|
|
19665
|
-
resourceDir: getCurrentResourceDirValue() || undefined,
|
|
19666
|
-
});
|
|
19667
|
-
|
|
19668
|
-
if (!sent) {
|
|
19669
|
-
pendingRequestId = null;
|
|
19670
|
-
pendingKind = null;
|
|
19671
|
-
setBusy(false);
|
|
19672
|
-
}
|
|
19673
|
-
});
|
|
19674
|
-
}
|
|
19675
|
-
|
|
19676
19934
|
if (zenModeBtn) {
|
|
19677
19935
|
zenModeBtn.addEventListener("click", () => {
|
|
19678
19936
|
setStudioZenMode(!studioZenModeEnabled);
|
|
@@ -20038,7 +20296,12 @@
|
|
|
20038
20296
|
const actionEl = target instanceof Element ? target.closest("[data-scratchpad-recent-action]") : null;
|
|
20039
20297
|
if (!actionEl) return;
|
|
20040
20298
|
event.preventDefault();
|
|
20299
|
+
event.stopPropagation();
|
|
20041
20300
|
const action = String(actionEl.getAttribute("data-scratchpad-recent-action") || "load");
|
|
20301
|
+
if (action === "hide") {
|
|
20302
|
+
hideScratchpadRecentPanel();
|
|
20303
|
+
return;
|
|
20304
|
+
}
|
|
20042
20305
|
const key = String(actionEl.getAttribute("data-scratchpad-key") || "");
|
|
20043
20306
|
void applyScratchpadRecentAction(action, key);
|
|
20044
20307
|
});
|