pi-studio 0.9.23 → 0.9.25
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 +494 -72
- package/client/studio.css +476 -28
- package/index.ts +414 -30
- 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
|
|
|
@@ -8984,6 +9018,166 @@
|
|
|
8984
9018
|
+ "</div>";
|
|
8985
9019
|
}
|
|
8986
9020
|
|
|
9021
|
+
function parseTraceToolArgsObject(inputText) {
|
|
9022
|
+
const value = String(inputText || "").trim();
|
|
9023
|
+
if (!value || (value[0] !== "{" && value[0] !== "[")) return null;
|
|
9024
|
+
try {
|
|
9025
|
+
const parsed = JSON.parse(value);
|
|
9026
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
9027
|
+
} catch {
|
|
9028
|
+
return null;
|
|
9029
|
+
}
|
|
9030
|
+
}
|
|
9031
|
+
|
|
9032
|
+
function countTraceTextLines(text) {
|
|
9033
|
+
const value = String(text || "");
|
|
9034
|
+
return value ? value.split(/\n/).length : 0;
|
|
9035
|
+
}
|
|
9036
|
+
|
|
9037
|
+
function formatTraceTextMetrics(text) {
|
|
9038
|
+
const value = String(text || "");
|
|
9039
|
+
const lines = countTraceTextLines(value);
|
|
9040
|
+
const chars = value.length;
|
|
9041
|
+
return formatCompactNumber(lines) + " line" + (lines === 1 ? "" : "s")
|
|
9042
|
+
+ ", " + formatCompactNumber(chars) + " char" + (chars === 1 ? "" : "s");
|
|
9043
|
+
}
|
|
9044
|
+
|
|
9045
|
+
function renderTraceToolField(label, value, className) {
|
|
9046
|
+
const text = String(value || "").trim();
|
|
9047
|
+
if (!text) return "";
|
|
9048
|
+
const extraClass = className ? " " + String(className) : "";
|
|
9049
|
+
return "<div class='trace-tool-field'>"
|
|
9050
|
+
+ "<span class='trace-tool-field-label'>" + escapeHtml(label) + "</span>"
|
|
9051
|
+
+ "<code class='trace-tool-field-value" + extraClass + "' title='" + escapeHtml(text) + "'>" + escapeHtml(text) + "</code>"
|
|
9052
|
+
+ "</div>";
|
|
9053
|
+
}
|
|
9054
|
+
|
|
9055
|
+
function renderTraceRawInputDetails(inputText, outputKey) {
|
|
9056
|
+
const value = String(inputText || "").trim();
|
|
9057
|
+
if (!value) return "";
|
|
9058
|
+
const rawKey = outputKey + ":raw-input";
|
|
9059
|
+
const openAttr = traceExpandedOutputs.has(rawKey) ? " open" : "";
|
|
9060
|
+
return "<details class='trace-tool-details trace-tool-raw-input'" + openAttr + ">"
|
|
9061
|
+
+ "<summary>Raw input</summary>"
|
|
9062
|
+
+ "<div class='trace-tool-details-body'>"
|
|
9063
|
+
+ renderTraceOutput(value, rawKey, { label: "Raw input" })
|
|
9064
|
+
+ "</div>"
|
|
9065
|
+
+ "</details>";
|
|
9066
|
+
}
|
|
9067
|
+
|
|
9068
|
+
function renderTraceToolTextDetails(summary, text, outputKey, label, options) {
|
|
9069
|
+
const value = String(text || "");
|
|
9070
|
+
const emptyText = options && typeof options.emptyText === "string" ? options.emptyText : "[empty]";
|
|
9071
|
+
const openAttr = traceExpandedOutputs.has(outputKey) ? " open" : "";
|
|
9072
|
+
return "<details class='trace-tool-details" + (options && options.className ? " " + escapeHtml(options.className) : "") + "'" + openAttr + ">"
|
|
9073
|
+
+ "<summary>" + escapeHtml(summary) + "</summary>"
|
|
9074
|
+
+ "<div class='trace-tool-details-body'>"
|
|
9075
|
+
+ renderTraceOutput(value || emptyText, outputKey, { label })
|
|
9076
|
+
+ "</div>"
|
|
9077
|
+
+ "</details>";
|
|
9078
|
+
}
|
|
9079
|
+
|
|
9080
|
+
function renderTraceEditInput(entry, payload, inputText) {
|
|
9081
|
+
const path = payload && typeof payload.path === "string" ? payload.path : "";
|
|
9082
|
+
const edits = payload && Array.isArray(payload.edits) ? payload.edits : [];
|
|
9083
|
+
const replacements = edits
|
|
9084
|
+
.map((edit, index) => {
|
|
9085
|
+
const item = edit && typeof edit === "object" ? edit : {};
|
|
9086
|
+
return {
|
|
9087
|
+
index,
|
|
9088
|
+
oldText: typeof item.oldText === "string" ? item.oldText : "",
|
|
9089
|
+
newText: typeof item.newText === "string" ? item.newText : "",
|
|
9090
|
+
};
|
|
9091
|
+
})
|
|
9092
|
+
.filter((edit) => edit.oldText || edit.newText);
|
|
9093
|
+
const replacementCount = replacements.length || edits.length;
|
|
9094
|
+
const fields = "<div class='trace-tool-fields'>"
|
|
9095
|
+
+ renderTraceToolField("Path", path, "trace-tool-path")
|
|
9096
|
+
+ renderTraceToolField("Changes", replacementCount + " replacement" + (replacementCount === 1 ? "" : "s"), "")
|
|
9097
|
+
+ "</div>";
|
|
9098
|
+
const changes = replacements.length
|
|
9099
|
+
? "<div class='trace-tool-change-list'>" + replacements.map((edit, displayIndex) => {
|
|
9100
|
+
const oldMetrics = formatTraceTextMetrics(edit.oldText);
|
|
9101
|
+
const newMetrics = formatTraceTextMetrics(edit.newText);
|
|
9102
|
+
const oldKey = entry.id + ":edit:" + edit.index + ":old";
|
|
9103
|
+
const newKey = entry.id + ":edit:" + edit.index + ":new";
|
|
9104
|
+
const openAttr = traceExpandedOutputs.has(oldKey) || traceExpandedOutputs.has(newKey) ? " open" : "";
|
|
9105
|
+
return "<details class='trace-tool-details trace-tool-change'" + openAttr + ">"
|
|
9106
|
+
+ "<summary>Replacement " + escapeHtml(String(displayIndex + 1)) + " · " + escapeHtml(oldMetrics) + " → " + escapeHtml(newMetrics) + "</summary>"
|
|
9107
|
+
+ "<div class='trace-tool-change-body'>"
|
|
9108
|
+
+ "<div class='trace-tool-change-grid'>"
|
|
9109
|
+
+ "<div class='trace-tool-change-column'><div class='trace-tool-code-label'>Old text</div>"
|
|
9110
|
+
+ renderTraceOutput(edit.oldText || "[empty]", oldKey, { label: "Old text" })
|
|
9111
|
+
+ "</div>"
|
|
9112
|
+
+ "<div class='trace-tool-change-column'><div class='trace-tool-code-label'>New text</div>"
|
|
9113
|
+
+ renderTraceOutput(edit.newText || "[empty]", newKey, { label: "New text" })
|
|
9114
|
+
+ "</div>"
|
|
9115
|
+
+ "</div>"
|
|
9116
|
+
+ "</div>"
|
|
9117
|
+
+ "</details>";
|
|
9118
|
+
}).join("") + "</div>"
|
|
9119
|
+
: "";
|
|
9120
|
+
return "<div class='trace-tool-input trace-tool-input-edit'>"
|
|
9121
|
+
+ fields
|
|
9122
|
+
+ changes
|
|
9123
|
+
+ renderTraceRawInputDetails(inputText, entry.id + ":input")
|
|
9124
|
+
+ "</div>";
|
|
9125
|
+
}
|
|
9126
|
+
|
|
9127
|
+
function renderTraceWriteInput(entry, payload, inputText) {
|
|
9128
|
+
const path = payload && typeof payload.path === "string" ? payload.path : "";
|
|
9129
|
+
const content = payload && typeof payload.content === "string" ? payload.content : null;
|
|
9130
|
+
const fields = "<div class='trace-tool-fields'>"
|
|
9131
|
+
+ renderTraceToolField("Path", path, "trace-tool-path")
|
|
9132
|
+
+ (content !== null ? renderTraceToolField("Content", formatTraceTextMetrics(content), "") : "")
|
|
9133
|
+
+ "</div>";
|
|
9134
|
+
const contentDetails = content !== null
|
|
9135
|
+
? renderTraceToolTextDetails("Content · " + formatTraceTextMetrics(content), content, entry.id + ":write:content", "Content", { className: "trace-tool-content" })
|
|
9136
|
+
: "";
|
|
9137
|
+
return "<div class='trace-tool-input trace-tool-input-write'>"
|
|
9138
|
+
+ fields
|
|
9139
|
+
+ contentDetails
|
|
9140
|
+
+ renderTraceRawInputDetails(inputText, entry.id + ":input")
|
|
9141
|
+
+ "</div>";
|
|
9142
|
+
}
|
|
9143
|
+
|
|
9144
|
+
function renderTraceReadInput(entry, payload, inputText) {
|
|
9145
|
+
const path = payload && typeof payload.path === "string" ? payload.path : "";
|
|
9146
|
+
const offset = payload && (typeof payload.offset === "number" || typeof payload.offset === "string") ? String(payload.offset) : "";
|
|
9147
|
+
const limit = payload && (typeof payload.limit === "number" || typeof payload.limit === "string") ? String(payload.limit) : "";
|
|
9148
|
+
const fields = "<div class='trace-tool-fields'>"
|
|
9149
|
+
+ renderTraceToolField("Path", path, "trace-tool-path")
|
|
9150
|
+
+ renderTraceToolField("Offset", offset ? "line " + offset : "", "")
|
|
9151
|
+
+ renderTraceToolField("Limit", limit ? limit + " lines" : "", "")
|
|
9152
|
+
+ "</div>";
|
|
9153
|
+
return "<div class='trace-tool-input trace-tool-input-read'>"
|
|
9154
|
+
+ fields
|
|
9155
|
+
+ renderTraceRawInputDetails(inputText, entry.id + ":input")
|
|
9156
|
+
+ "</div>";
|
|
9157
|
+
}
|
|
9158
|
+
|
|
9159
|
+
function renderTraceCommandInput(entry, inputText, label) {
|
|
9160
|
+
const value = String(inputText || "").trim();
|
|
9161
|
+
if (!value) return "";
|
|
9162
|
+
return "<div class='trace-tool-input trace-tool-input-command'>"
|
|
9163
|
+
+ "<div class='trace-tool-code-label'>" + escapeHtml(label || "Command") + "</div>"
|
|
9164
|
+
+ renderTraceOutput(value, entry.id + ":input", { label: label || "Command" })
|
|
9165
|
+
+ "</div>";
|
|
9166
|
+
}
|
|
9167
|
+
|
|
9168
|
+
function renderTraceToolInput(entry) {
|
|
9169
|
+
const inputText = String(entry.args || entry.argsSummary || "").trim();
|
|
9170
|
+
if (!inputText) return "";
|
|
9171
|
+
const toolName = String(entry.toolName || "").trim().toLowerCase();
|
|
9172
|
+
if (toolName === "bash") return renderTraceCommandInput(entry, inputText, "Command");
|
|
9173
|
+
if (toolName === "repl_send" || toolName === "studio_repl_send") return renderTraceCommandInput(entry, inputText, "Code");
|
|
9174
|
+
const payload = parseTraceToolArgsObject(inputText);
|
|
9175
|
+
if (payload && toolName === "edit") return renderTraceEditInput(entry, payload, inputText);
|
|
9176
|
+
if (payload && toolName === "write") return renderTraceWriteInput(entry, payload, inputText);
|
|
9177
|
+
if (payload && toolName === "read") return renderTraceReadInput(entry, payload, inputText);
|
|
9178
|
+
return renderTraceOutput(inputText, entry.id + ":input", { label: "Input" });
|
|
9179
|
+
}
|
|
9180
|
+
|
|
8987
9181
|
function renderTraceImages(images) {
|
|
8988
9182
|
const normalizedImages = Array.isArray(images)
|
|
8989
9183
|
? images.map((image, index) => normalizeTraceImage(image, index)).filter(Boolean)
|
|
@@ -9348,9 +9542,9 @@
|
|
|
9348
9542
|
}
|
|
9349
9543
|
|
|
9350
9544
|
const title = entry.label || entry.toolName || "tool";
|
|
9351
|
-
const
|
|
9352
|
-
const argsSummary =
|
|
9353
|
-
? "<div class='trace-section trace-section-input'><div class='trace-section-label'>Input</div>" +
|
|
9545
|
+
const inputHtml = renderTraceToolInput(entry);
|
|
9546
|
+
const argsSummary = inputHtml
|
|
9547
|
+
? "<div class='trace-section trace-section-input'><div class='trace-section-label'>Input</div>" + inputHtml + "</div>"
|
|
9354
9548
|
: "";
|
|
9355
9549
|
const imageOutput = renderTraceImages(entry.images);
|
|
9356
9550
|
const outputPieces = [];
|
|
@@ -9702,7 +9896,222 @@
|
|
|
9702
9896
|
}
|
|
9703
9897
|
}
|
|
9704
9898
|
|
|
9899
|
+
function getGitChangesContext() {
|
|
9900
|
+
return getHtmlPreviewResourceContextOptions();
|
|
9901
|
+
}
|
|
9902
|
+
|
|
9903
|
+
function getGitChangedFiles() {
|
|
9904
|
+
return Array.isArray(gitChangesState.files) ? gitChangesState.files : [];
|
|
9905
|
+
}
|
|
9906
|
+
|
|
9907
|
+
function getSelectedGitChangedFile() {
|
|
9908
|
+
const files = getGitChangedFiles();
|
|
9909
|
+
if (!files.length) return null;
|
|
9910
|
+
const selectedPath = String(gitChangesState.selectedPath || "");
|
|
9911
|
+
return files.find((file) => String(file.path || "") === selectedPath) || files[0] || null;
|
|
9912
|
+
}
|
|
9913
|
+
|
|
9914
|
+
function getGitChangeStatusLabel(status) {
|
|
9915
|
+
if (status === "untracked") return "Untracked";
|
|
9916
|
+
if (status === "added") return "Added";
|
|
9917
|
+
if (status === "deleted") return "Deleted";
|
|
9918
|
+
if (status === "renamed") return "Renamed";
|
|
9919
|
+
if (status === "binary") return "Binary";
|
|
9920
|
+
return "Modified";
|
|
9921
|
+
}
|
|
9922
|
+
|
|
9923
|
+
function getGitChangeStatusIcon(status) {
|
|
9924
|
+
if (status === "untracked") return "??";
|
|
9925
|
+
if (status === "added") return "A";
|
|
9926
|
+
if (status === "deleted") return "D";
|
|
9927
|
+
if (status === "renamed") return "R";
|
|
9928
|
+
if (status === "binary") return "BIN";
|
|
9929
|
+
return "M";
|
|
9930
|
+
}
|
|
9931
|
+
|
|
9932
|
+
function buildGitChangesDiffHtml(diffText) {
|
|
9933
|
+
const lines = String(diffText || "").split("\n");
|
|
9934
|
+
return "<pre class='git-changes-diff'><code>" + lines.map((line) => {
|
|
9935
|
+
let cls = "git-changes-line";
|
|
9936
|
+
if (line.startsWith("+++") || line.startsWith("---") || line.startsWith("diff --git") || line.startsWith("@@")) {
|
|
9937
|
+
cls += " git-changes-line-meta";
|
|
9938
|
+
} else if (line.startsWith("+")) {
|
|
9939
|
+
cls += " git-changes-line-add";
|
|
9940
|
+
} else if (line.startsWith("-")) {
|
|
9941
|
+
cls += " git-changes-line-del";
|
|
9942
|
+
}
|
|
9943
|
+
return "<span class='" + cls + "'>" + escapeHtml(line || " ") + "</span>";
|
|
9944
|
+
}).join("\n") + "</code></pre>";
|
|
9945
|
+
}
|
|
9946
|
+
|
|
9947
|
+
function buildGitChangesPanelHtml() {
|
|
9948
|
+
const files = getGitChangedFiles();
|
|
9949
|
+
const selected = getSelectedGitChangedFile();
|
|
9950
|
+
const isLoading = gitChangesState.status === "loading";
|
|
9951
|
+
const hasError = gitChangesState.status === "error";
|
|
9952
|
+
const label = gitChangesState.label || (files.length ? (files.length + " changed") : "Git changes");
|
|
9953
|
+
const branch = gitChangesState.branch || "";
|
|
9954
|
+
const repoRoot = gitChangesState.repoRoot || "";
|
|
9955
|
+
const subtitleParts = [];
|
|
9956
|
+
subtitleParts.push(label);
|
|
9957
|
+
if (branch) subtitleParts.push((gitChangesState.hasHead === false ? "No commits yet on " : "on ") + branch);
|
|
9958
|
+
const rows = files.length
|
|
9959
|
+
? files.map((file) => {
|
|
9960
|
+
const path = String(file.path || "");
|
|
9961
|
+
const status = String(file.status || "modified");
|
|
9962
|
+
const isSelected = selected && String(selected.path || "") === path;
|
|
9963
|
+
const stats = (Number(file.additions) || 0) || (Number(file.deletions) || 0)
|
|
9964
|
+
? "<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>"
|
|
9965
|
+
: "";
|
|
9966
|
+
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) + "'>"
|
|
9967
|
+
+ "<span class='git-changes-file-icon git-changes-file-icon-" + escapeHtml(status) + "'>" + escapeHtml(getGitChangeStatusIcon(status)) + "</span>"
|
|
9968
|
+
+ "<span class='git-changes-file-name'>" + escapeHtml(path) + "</span>"
|
|
9969
|
+
+ "<span class='git-changes-file-status'>" + escapeHtml(getGitChangeStatusLabel(status)) + "</span>"
|
|
9970
|
+
+ stats
|
|
9971
|
+
+ "</button>";
|
|
9972
|
+
}).join("")
|
|
9973
|
+
: "<div class='git-changes-empty'>" + escapeHtml(isLoading ? "Loading git changes…" : (gitChangesState.message || "No uncommitted git changes.")) + "</div>";
|
|
9974
|
+
const selectedDiff = selected && selected.diff ? String(selected.diff) : String(gitChangesState.content || "");
|
|
9975
|
+
const selectedStatus = selected ? String(selected.status || "modified") : "";
|
|
9976
|
+
const selectedCanOpen = selected && selectedStatus !== "deleted" && gitChangesState.repoRoot;
|
|
9977
|
+
const selectedAbsPath = selectedCanOpen ? String(gitChangesState.repoRoot).replace(/\/$/, "") + "/" + String(selected.path || "") : "";
|
|
9978
|
+
const notice = hasError && gitChangesState.message
|
|
9979
|
+
? "<div class='git-changes-notice git-changes-notice-" + escapeHtml(gitChangesState.level || "warning") + "'>" + escapeHtml(gitChangesState.message) + "</div>"
|
|
9980
|
+
: "";
|
|
9981
|
+
return "<div class='git-changes-panel'>"
|
|
9982
|
+
+ "<div class='git-changes-toolbar'>"
|
|
9983
|
+
+ "<div class='git-changes-title-group'>"
|
|
9984
|
+
+ "<div class='git-changes-title'>Git changes</div>"
|
|
9985
|
+
+ "<div class='git-changes-subtitle'>" + escapeHtml(subtitleParts.filter(Boolean).join(" · ")) + "</div>"
|
|
9986
|
+
+ (repoRoot ? "<div class='git-changes-root' title='" + escapeHtml(repoRoot) + "'>" + escapeHtml(repoRoot) + "</div>" : "")
|
|
9987
|
+
+ "</div>"
|
|
9988
|
+
+ "<div class='git-changes-actions'>"
|
|
9989
|
+
+ "<button type='button' data-git-change-action='refresh'" + (isLoading ? " disabled" : "") + ">Refresh</button>"
|
|
9990
|
+
+ "<button type='button' data-git-change-action='open' data-git-change-abs-path='" + escapeHtml(selectedAbsPath) + "'" + (selectedCanOpen ? "" : " disabled") + ">Open file</button>"
|
|
9991
|
+
+ "<button type='button' data-git-change-action='load'" + (gitChangesState.content ? "" : " disabled") + ">Load diff</button>"
|
|
9992
|
+
+ "<button type='button' data-git-change-action='copy'" + (gitChangesState.content ? "" : " disabled") + ">Copy diff</button>"
|
|
9993
|
+
+ "</div>"
|
|
9994
|
+
+ "</div>"
|
|
9995
|
+
+ notice
|
|
9996
|
+
+ "<div class='git-changes-body'>"
|
|
9997
|
+
+ "<div class='git-changes-file-list' role='list'>" + rows + "</div>"
|
|
9998
|
+
+ "<div class='git-changes-diff-pane'>" + (selectedDiff ? buildGitChangesDiffHtml(selectedDiff) : "<div class='git-changes-empty'>Select a changed file.</div>") + "</div>"
|
|
9999
|
+
+ "</div>"
|
|
10000
|
+
+ "</div>";
|
|
10001
|
+
}
|
|
10002
|
+
|
|
10003
|
+
function getGitChangesScrollSnapshot() {
|
|
10004
|
+
if (!critiqueViewEl) return null;
|
|
10005
|
+
const fileListEl = critiqueViewEl.querySelector(".git-changes-file-list");
|
|
10006
|
+
const diffPaneEl = critiqueViewEl.querySelector(".git-changes-diff-pane");
|
|
10007
|
+
return {
|
|
10008
|
+
paneTop: critiqueViewEl.scrollTop || 0,
|
|
10009
|
+
paneLeft: critiqueViewEl.scrollLeft || 0,
|
|
10010
|
+
fileListTop: fileListEl ? fileListEl.scrollTop || 0 : 0,
|
|
10011
|
+
fileListLeft: fileListEl ? fileListEl.scrollLeft || 0 : 0,
|
|
10012
|
+
diffTop: diffPaneEl ? diffPaneEl.scrollTop || 0 : 0,
|
|
10013
|
+
diffLeft: diffPaneEl ? diffPaneEl.scrollLeft || 0 : 0,
|
|
10014
|
+
};
|
|
10015
|
+
}
|
|
10016
|
+
|
|
10017
|
+
function restoreGitChangesScrollSnapshot(snapshot, options) {
|
|
10018
|
+
if (!critiqueViewEl || !snapshot) return;
|
|
10019
|
+
const fileListEl = critiqueViewEl.querySelector(".git-changes-file-list");
|
|
10020
|
+
const diffPaneEl = critiqueViewEl.querySelector(".git-changes-diff-pane");
|
|
10021
|
+
critiqueViewEl.scrollTop = snapshot.paneTop || 0;
|
|
10022
|
+
critiqueViewEl.scrollLeft = snapshot.paneLeft || 0;
|
|
10023
|
+
if (fileListEl) {
|
|
10024
|
+
fileListEl.scrollTop = snapshot.fileListTop || 0;
|
|
10025
|
+
fileListEl.scrollLeft = snapshot.fileListLeft || 0;
|
|
10026
|
+
}
|
|
10027
|
+
if (diffPaneEl) {
|
|
10028
|
+
if (options && options.resetDiffScroll) {
|
|
10029
|
+
diffPaneEl.scrollTop = 0;
|
|
10030
|
+
diffPaneEl.scrollLeft = 0;
|
|
10031
|
+
} else {
|
|
10032
|
+
diffPaneEl.scrollTop = snapshot.diffTop || 0;
|
|
10033
|
+
diffPaneEl.scrollLeft = snapshot.diffLeft || 0;
|
|
10034
|
+
}
|
|
10035
|
+
}
|
|
10036
|
+
}
|
|
10037
|
+
|
|
10038
|
+
function renderGitChangesView(options) {
|
|
10039
|
+
if (!critiqueViewEl) return;
|
|
10040
|
+
const scrollSnapshot = options && options.preserveScroll ? getGitChangesScrollSnapshot() : null;
|
|
10041
|
+
finishPreviewRender(critiqueViewEl);
|
|
10042
|
+
critiqueViewEl.classList.add("git-changes-host");
|
|
10043
|
+
critiqueViewEl.innerHTML = buildGitChangesPanelHtml();
|
|
10044
|
+
critiqueViewEl.classList.remove("response-scroll-resetting");
|
|
10045
|
+
restoreGitChangesScrollSnapshot(scrollSnapshot, options || {});
|
|
10046
|
+
if (gitChangesState.status === "idle") requestGitChangesSnapshot({ preserveScroll: true });
|
|
10047
|
+
scheduleResponsePaneRepaintNudge();
|
|
10048
|
+
}
|
|
10049
|
+
|
|
10050
|
+
function requestGitChangesSnapshot(options) {
|
|
10051
|
+
const requestId = makeRequestId();
|
|
10052
|
+
const context = getGitChangesContext();
|
|
10053
|
+
gitChangesState = {
|
|
10054
|
+
...gitChangesState,
|
|
10055
|
+
status: "loading",
|
|
10056
|
+
requestId,
|
|
10057
|
+
message: "",
|
|
10058
|
+
level: "info",
|
|
10059
|
+
};
|
|
10060
|
+
if (rightView === "changes") renderGitChangesView({ preserveScroll: Boolean(options && options.preserveScroll) });
|
|
10061
|
+
const message = { type: "git_changes_request", requestId };
|
|
10062
|
+
if (context.sourcePath) message.sourcePath = context.sourcePath;
|
|
10063
|
+
if (context.resourceDir) message.resourceDir = context.resourceDir;
|
|
10064
|
+
if (!sendMessage(message)) {
|
|
10065
|
+
gitChangesState = { ...gitChangesState, status: "error", message: "Studio is not connected.", level: "error" };
|
|
10066
|
+
if (rightView === "changes") renderGitChangesView({ preserveScroll: true });
|
|
10067
|
+
} else if (options && options.user) {
|
|
10068
|
+
setStatus("Refreshing git changes…", "warning");
|
|
10069
|
+
}
|
|
10070
|
+
}
|
|
10071
|
+
|
|
10072
|
+
async function handleGitChangesPaneClick(event) {
|
|
10073
|
+
if (rightView !== "changes") return;
|
|
10074
|
+
const target = event.target;
|
|
10075
|
+
const actionEl = target instanceof Element ? target.closest("[data-git-change-action]") : null;
|
|
10076
|
+
if (!actionEl) return;
|
|
10077
|
+
event.preventDefault();
|
|
10078
|
+
const action = actionEl.getAttribute("data-git-change-action") || "";
|
|
10079
|
+
if (action === "select") {
|
|
10080
|
+
gitChangesState = { ...gitChangesState, selectedPath: actionEl.getAttribute("data-git-change-path") || "" };
|
|
10081
|
+
renderGitChangesView({ preserveScroll: true, resetDiffScroll: true });
|
|
10082
|
+
return;
|
|
10083
|
+
}
|
|
10084
|
+
if (action === "refresh") {
|
|
10085
|
+
requestGitChangesSnapshot({ user: true, preserveScroll: true });
|
|
10086
|
+
return;
|
|
10087
|
+
}
|
|
10088
|
+
if (action === "copy") {
|
|
10089
|
+
const ok = await writeTextToClipboard(String(gitChangesState.content || ""));
|
|
10090
|
+
setStatus(ok ? "Copied git diff." : "Clipboard write failed.", ok ? "success" : "warning");
|
|
10091
|
+
return;
|
|
10092
|
+
}
|
|
10093
|
+
if (action === "load") {
|
|
10094
|
+
if (!String(gitChangesState.content || "").trim()) {
|
|
10095
|
+
setStatus("No git diff to load.", "warning");
|
|
10096
|
+
return;
|
|
10097
|
+
}
|
|
10098
|
+
setEditorText(String(gitChangesState.content || ""), { preserveScroll: false, preserveSelection: false });
|
|
10099
|
+
setSourceState({ source: "blank", label: gitChangesState.label || "git diff", path: null });
|
|
10100
|
+
setEditorLanguage("diff");
|
|
10101
|
+
setStatus("Loaded current git diff into editor.", "success");
|
|
10102
|
+
return;
|
|
10103
|
+
}
|
|
10104
|
+
if (action === "open") {
|
|
10105
|
+
const absPath = actionEl.getAttribute("data-git-change-abs-path") || "";
|
|
10106
|
+
if (!absPath) return;
|
|
10107
|
+
await openPreviewDocumentHere(absPath, getFileBrowserLocalLinkContext(), { fallbackPath: absPath, fileBackedIntent: true });
|
|
10108
|
+
ensureCurrentEditorFileBackedFromFilesPath(absPath);
|
|
10109
|
+
setStatus("Opened changed file in editor.", "success");
|
|
10110
|
+
}
|
|
10111
|
+
}
|
|
10112
|
+
|
|
9705
10113
|
function renderActiveResult() {
|
|
10114
|
+
if (critiqueViewEl) critiqueViewEl.classList.toggle("git-changes-host", rightView === "changes");
|
|
9706
10115
|
if (rightView === "trace") {
|
|
9707
10116
|
renderTraceView();
|
|
9708
10117
|
return;
|
|
@@ -9718,6 +10127,11 @@
|
|
|
9718
10127
|
return;
|
|
9719
10128
|
}
|
|
9720
10129
|
|
|
10130
|
+
if (rightView === "changes") {
|
|
10131
|
+
renderGitChangesView();
|
|
10132
|
+
return;
|
|
10133
|
+
}
|
|
10134
|
+
|
|
9721
10135
|
if (rightView === "editor-preview") {
|
|
9722
10136
|
const editorText = prepareEditorTextForPreview(sourceTextEl.value || "");
|
|
9723
10137
|
if (!editorText.trim()) {
|
|
@@ -9796,7 +10210,7 @@
|
|
|
9796
10210
|
: normalizeForCompare(sourceTextEl.value);
|
|
9797
10211
|
const responseLoaded = hasResponse && normalizedEditor === latestResponseNormalized;
|
|
9798
10212
|
const isCritiqueResponse = hasResponse && latestResponseIsStructuredCritique;
|
|
9799
|
-
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files";
|
|
10213
|
+
const showingAuxiliaryRightPane = rightView === "trace" || rightView === "repl" || rightView === "files" || rightView === "changes";
|
|
9800
10214
|
|
|
9801
10215
|
if (responseWrapEl) {
|
|
9802
10216
|
responseWrapEl.hidden = showingAuxiliaryRightPane;
|
|
@@ -9839,6 +10253,8 @@
|
|
|
9839
10253
|
exportPdfBtn.title = "Working view does not support preview export.";
|
|
9840
10254
|
} else if (rightView === "files") {
|
|
9841
10255
|
exportPdfBtn.title = "Files view does not support preview export.";
|
|
10256
|
+
} else if (rightView === "changes") {
|
|
10257
|
+
exportPdfBtn.title = "Changes view does not support preview export.";
|
|
9842
10258
|
} else if (exportingReplJournal && !replJournalExportEntries.length) {
|
|
9843
10259
|
exportPdfBtn.title = "No Studio REPL record entries to export for this session yet.";
|
|
9844
10260
|
} else if (rightView === "markdown") {
|
|
@@ -10018,7 +10434,6 @@
|
|
|
10018
10434
|
if (clearWorkspaceBtn) clearWorkspaceBtn.disabled = uiBusy;
|
|
10019
10435
|
sendEditorBtn.disabled = uiBusy || isEditorOnlyMode;
|
|
10020
10436
|
if (getEditorBtn) getEditorBtn.disabled = uiBusy;
|
|
10021
|
-
if (loadGitDiffBtn) loadGitDiffBtn.disabled = uiBusy;
|
|
10022
10437
|
syncRunAndCritiqueButtons();
|
|
10023
10438
|
copyDraftBtn.disabled = uiBusy;
|
|
10024
10439
|
if (suggestCompletionBtn) {
|
|
@@ -10775,6 +11190,9 @@
|
|
|
10775
11190
|
if (rightView === "trace" && previousView !== "trace") {
|
|
10776
11191
|
traceAutoScroll = true;
|
|
10777
11192
|
}
|
|
11193
|
+
if (rightView === "changes" && previousView !== "changes" && gitChangesState.status === "idle") {
|
|
11194
|
+
requestGitChangesSnapshot();
|
|
11195
|
+
}
|
|
10778
11196
|
if (rightView === "repl" && previousView !== "repl") {
|
|
10779
11197
|
replFollow = true;
|
|
10780
11198
|
startReplPolling();
|
|
@@ -13097,17 +13515,24 @@
|
|
|
13097
13515
|
if (!scratchpadRecentPanelEl) return;
|
|
13098
13516
|
scratchpadRecentPanelEl.hidden = !scratchpadRecentVisible;
|
|
13099
13517
|
if (!scratchpadRecentVisible) return;
|
|
13518
|
+
const headerHtml = "<div class='scratchpad-recent-header'>"
|
|
13519
|
+
+ "<div class='scratchpad-recent-heading-group'>"
|
|
13520
|
+
+ "<div class='scratchpad-recent-heading'>Recent scratchpads</div>"
|
|
13521
|
+
+ "<div class='scratchpad-recent-subtitle'>Load, append, or copy notes saved for other documents and drafts.</div>"
|
|
13522
|
+
+ "</div>"
|
|
13523
|
+
+ "<button type='button' class='scratchpad-recent-hide-btn' data-scratchpad-recent-action='hide' aria-label='Hide recent scratchpads' title='Hide recent scratchpads'>Hide</button>"
|
|
13524
|
+
+ "</div>";
|
|
13100
13525
|
if (scratchpadRecentLoading) {
|
|
13101
|
-
scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-loading'>Loading recent scratchpads…</div>";
|
|
13526
|
+
scratchpadRecentPanelEl.innerHTML = headerHtml + "<div class='scratchpad-recent-loading'>Loading recent scratchpads…</div>";
|
|
13102
13527
|
return;
|
|
13103
13528
|
}
|
|
13104
13529
|
const currentKey = getCurrentStudioDocumentDescriptor().key;
|
|
13105
13530
|
const entries = Array.isArray(scratchpadRecentEntries) ? scratchpadRecentEntries : [];
|
|
13106
13531
|
if (!entries.length) {
|
|
13107
|
-
scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-empty'>No other saved scratchpads yet.</div>";
|
|
13532
|
+
scratchpadRecentPanelEl.innerHTML = headerHtml + "<div class='scratchpad-recent-empty'>No other saved scratchpads yet.</div>";
|
|
13108
13533
|
return;
|
|
13109
13534
|
}
|
|
13110
|
-
scratchpadRecentPanelEl.innerHTML = "<div class='scratchpad-recent-list'>" + entries.map((entry) => {
|
|
13535
|
+
scratchpadRecentPanelEl.innerHTML = headerHtml + "<div class='scratchpad-recent-list'>" + entries.map((entry) => {
|
|
13111
13536
|
const key = String(entry && entry.documentKey ? entry.documentKey : "");
|
|
13112
13537
|
const isCurrent = key === currentKey;
|
|
13113
13538
|
const label = String(entry && entry.label ? entry.label : key || "scratchpad");
|
|
@@ -13145,13 +13570,19 @@
|
|
|
13145
13570
|
}
|
|
13146
13571
|
}
|
|
13147
13572
|
|
|
13573
|
+
function hideScratchpadRecentPanel() {
|
|
13574
|
+
scratchpadRecentVisible = false;
|
|
13575
|
+
renderScratchpadRecentPanel();
|
|
13576
|
+
updateScratchpadUi();
|
|
13577
|
+
}
|
|
13578
|
+
|
|
13148
13579
|
function toggleScratchpadRecentPanel() {
|
|
13149
|
-
scratchpadRecentVisible = !scratchpadRecentVisible;
|
|
13150
13580
|
if (scratchpadRecentVisible) {
|
|
13151
|
-
|
|
13152
|
-
|
|
13153
|
-
renderScratchpadRecentPanel();
|
|
13581
|
+
hideScratchpadRecentPanel();
|
|
13582
|
+
return;
|
|
13154
13583
|
}
|
|
13584
|
+
scratchpadRecentVisible = true;
|
|
13585
|
+
void loadScratchpadRecentEntries();
|
|
13155
13586
|
updateScratchpadUi();
|
|
13156
13587
|
}
|
|
13157
13588
|
|
|
@@ -13181,6 +13612,7 @@
|
|
|
13181
13612
|
if (!confirmed) return;
|
|
13182
13613
|
}
|
|
13183
13614
|
setScratchpadText(text);
|
|
13615
|
+
hideScratchpadRecentPanel();
|
|
13184
13616
|
setStatus("Loaded recent scratchpad into current scratchpad.", "success");
|
|
13185
13617
|
} catch (error) {
|
|
13186
13618
|
setStatus("Could not use recent scratchpad: " + (error && error.message ? error.message : String(error || "unknown error")), "warning");
|
|
@@ -16864,7 +17296,7 @@
|
|
|
16864
17296
|
const hasNotes = count > 0;
|
|
16865
17297
|
const isOpen = isReviewNotesOpen();
|
|
16866
17298
|
if (reviewNotesBtn) {
|
|
16867
|
-
reviewNotesBtn.textContent =
|
|
17299
|
+
reviewNotesBtn.textContent = "Comments";
|
|
16868
17300
|
reviewNotesBtn.classList.toggle("has-content", hasNotes);
|
|
16869
17301
|
reviewNotesBtn.classList.toggle("is-active", isOpen);
|
|
16870
17302
|
reviewNotesBtn.setAttribute("aria-pressed", isOpen ? "true" : "false");
|
|
@@ -17466,7 +17898,7 @@
|
|
|
17466
17898
|
const hasContent = Boolean(normalized.trim());
|
|
17467
17899
|
const descriptor = getCurrentStudioDocumentDescriptor();
|
|
17468
17900
|
if (scratchpadBtn) {
|
|
17469
|
-
scratchpadBtn.textContent =
|
|
17901
|
+
scratchpadBtn.textContent = "Scratchpad";
|
|
17470
17902
|
scratchpadBtn.classList.toggle("has-content", hasContent);
|
|
17471
17903
|
scratchpadBtn.title = hasContent
|
|
17472
17904
|
? ("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 +17912,9 @@
|
|
|
17480
17912
|
if (scratchpadRecentBtn) {
|
|
17481
17913
|
scratchpadRecentBtn.textContent = scratchpadRecentVisible ? "Hide recent" : "Recent…";
|
|
17482
17914
|
scratchpadRecentBtn.setAttribute("aria-expanded", scratchpadRecentVisible ? "true" : "false");
|
|
17915
|
+
scratchpadRecentBtn.title = scratchpadRecentVisible
|
|
17916
|
+
? "Hide recent scratchpads."
|
|
17917
|
+
: "Show recent non-empty scratchpads saved for other files and drafts.";
|
|
17483
17918
|
}
|
|
17484
17919
|
if (scratchpadInsertBtn) scratchpadInsertBtn.disabled = !hasContent;
|
|
17485
17920
|
if (scratchpadCopyBtn) scratchpadCopyBtn.disabled = !hasContent;
|
|
@@ -18571,28 +19006,31 @@
|
|
|
18571
19006
|
return;
|
|
18572
19007
|
}
|
|
18573
19008
|
|
|
18574
|
-
if (message.type === "
|
|
18575
|
-
|
|
18576
|
-
|
|
18577
|
-
|
|
18578
|
-
|
|
18579
|
-
|
|
18580
|
-
const
|
|
18581
|
-
|
|
18582
|
-
?
|
|
18583
|
-
|
|
18584
|
-
|
|
18585
|
-
|
|
18586
|
-
|
|
18587
|
-
|
|
18588
|
-
|
|
18589
|
-
|
|
18590
|
-
|
|
18591
|
-
|
|
18592
|
-
|
|
18593
|
-
|
|
18594
|
-
"
|
|
18595
|
-
|
|
19009
|
+
if (message.type === "git_changes_snapshot") {
|
|
19010
|
+
const requestId = typeof message.requestId === "string" ? message.requestId : "";
|
|
19011
|
+
const preserveScroll = Boolean(gitChangesState.requestId && requestId && requestId === gitChangesState.requestId);
|
|
19012
|
+
if (requestId && gitChangesState.requestId && requestId !== gitChangesState.requestId) return;
|
|
19013
|
+
const ok = message.ok !== false;
|
|
19014
|
+
const files = Array.isArray(message.files) ? message.files : [];
|
|
19015
|
+
const selectedPath = files.some((file) => String(file && file.path || "") === String(gitChangesState.selectedPath || ""))
|
|
19016
|
+
? gitChangesState.selectedPath
|
|
19017
|
+
: (files[0] && files[0].path ? String(files[0].path) : "");
|
|
19018
|
+
gitChangesState = {
|
|
19019
|
+
status: ok ? "ready" : "error",
|
|
19020
|
+
requestId: null,
|
|
19021
|
+
content: ok && typeof message.content === "string" ? message.content : "",
|
|
19022
|
+
label: ok && typeof message.label === "string" ? message.label : "",
|
|
19023
|
+
repoRoot: ok && typeof message.repoRoot === "string" ? message.repoRoot : "",
|
|
19024
|
+
branch: ok && typeof message.branch === "string" ? message.branch : "",
|
|
19025
|
+
hasHead: ok ? message.hasHead !== false : true,
|
|
19026
|
+
files,
|
|
19027
|
+
selectedPath,
|
|
19028
|
+
message: typeof message.message === "string" ? message.message : "",
|
|
19029
|
+
level: typeof message.level === "string" ? message.level : "info",
|
|
19030
|
+
};
|
|
19031
|
+
if (rightView === "changes") renderGitChangesView({ preserveScroll });
|
|
19032
|
+
if (ok) setStatus(files.length ? "Loaded git changes." : "No uncommitted git changes.", files.length ? "success" : "warning");
|
|
19033
|
+
else setStatus(gitChangesState.message || "Could not load git changes.", gitChangesState.level === "error" ? "error" : "warning");
|
|
18596
19034
|
return;
|
|
18597
19035
|
}
|
|
18598
19036
|
|
|
@@ -19248,7 +19686,7 @@
|
|
|
19248
19686
|
if (historyPrevBtn) {
|
|
19249
19687
|
historyPrevBtn.addEventListener("click", () => {
|
|
19250
19688
|
if (!responseHistory.length) {
|
|
19251
|
-
setStatus("No
|
|
19689
|
+
setStatus("No branch history available yet.", "warning");
|
|
19252
19690
|
return;
|
|
19253
19691
|
}
|
|
19254
19692
|
selectHistoryIndex(responseHistoryIndex - 1);
|
|
@@ -19258,7 +19696,7 @@
|
|
|
19258
19696
|
if (historyNextBtn) {
|
|
19259
19697
|
historyNextBtn.addEventListener("click", () => {
|
|
19260
19698
|
if (!responseHistory.length) {
|
|
19261
|
-
setStatus("No
|
|
19699
|
+
setStatus("No branch history available yet.", "warning");
|
|
19262
19700
|
return;
|
|
19263
19701
|
}
|
|
19264
19702
|
selectHistoryIndex(responseHistoryIndex + 1);
|
|
@@ -19268,7 +19706,7 @@
|
|
|
19268
19706
|
if (historyLastBtn) {
|
|
19269
19707
|
historyLastBtn.addEventListener("click", () => {
|
|
19270
19708
|
if (!responseHistory.length) {
|
|
19271
|
-
setStatus("No
|
|
19709
|
+
setStatus("No branch history available yet.", "warning");
|
|
19272
19710
|
return;
|
|
19273
19711
|
}
|
|
19274
19712
|
selectHistoryIndex(responseHistory.length - 1);
|
|
@@ -19295,7 +19733,7 @@
|
|
|
19295
19733
|
if (responseHistory.length > 0) {
|
|
19296
19734
|
selectHistoryIndex(responseHistory.length - 1, { silent: true });
|
|
19297
19735
|
queuedLatestResponse = null;
|
|
19298
|
-
setStatus("Pulled latest response from history.", "success");
|
|
19736
|
+
setStatus("Pulled latest response from branch history.", "success");
|
|
19299
19737
|
updateResultActionButtons();
|
|
19300
19738
|
} else if (applyLatestPayload(queuedLatestResponse)) {
|
|
19301
19739
|
queuedLatestResponse = null;
|
|
@@ -19653,27 +20091,6 @@
|
|
|
19653
20091
|
});
|
|
19654
20092
|
}
|
|
19655
20093
|
|
|
19656
|
-
if (loadGitDiffBtn) {
|
|
19657
|
-
loadGitDiffBtn.addEventListener("click", () => {
|
|
19658
|
-
const requestId = beginUiAction("load_git_diff");
|
|
19659
|
-
if (!requestId) return;
|
|
19660
|
-
|
|
19661
|
-
const effectivePath = getEffectiveSavePath();
|
|
19662
|
-
const sent = sendMessage({
|
|
19663
|
-
type: "load_git_diff_request",
|
|
19664
|
-
requestId,
|
|
19665
|
-
sourcePath: effectivePath || sourceState.path || undefined,
|
|
19666
|
-
resourceDir: getCurrentResourceDirValue() || undefined,
|
|
19667
|
-
});
|
|
19668
|
-
|
|
19669
|
-
if (!sent) {
|
|
19670
|
-
pendingRequestId = null;
|
|
19671
|
-
pendingKind = null;
|
|
19672
|
-
setBusy(false);
|
|
19673
|
-
}
|
|
19674
|
-
});
|
|
19675
|
-
}
|
|
19676
|
-
|
|
19677
20094
|
if (zenModeBtn) {
|
|
19678
20095
|
zenModeBtn.addEventListener("click", () => {
|
|
19679
20096
|
setStudioZenMode(!studioZenModeEnabled);
|
|
@@ -20039,7 +20456,12 @@
|
|
|
20039
20456
|
const actionEl = target instanceof Element ? target.closest("[data-scratchpad-recent-action]") : null;
|
|
20040
20457
|
if (!actionEl) return;
|
|
20041
20458
|
event.preventDefault();
|
|
20459
|
+
event.stopPropagation();
|
|
20042
20460
|
const action = String(actionEl.getAttribute("data-scratchpad-recent-action") || "load");
|
|
20461
|
+
if (action === "hide") {
|
|
20462
|
+
hideScratchpadRecentPanel();
|
|
20463
|
+
return;
|
|
20464
|
+
}
|
|
20043
20465
|
const key = String(actionEl.getAttribute("data-scratchpad-key") || "");
|
|
20044
20466
|
void applyScratchpadRecentAction(action, key);
|
|
20045
20467
|
});
|