pi-studio 0.5.6 → 0.5.8
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 +16 -2
- package/README.md +1 -1
- package/WORKFLOW.md +2 -2
- package/index.ts +129 -46
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `pi-studio` are documented here.
|
|
4
4
|
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.5.8] — 2026-03-12
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Studio browser tabs now auto-reconnect after unexpected websocket disconnects (for example transient local connection loss or sleep/wake), while intentional invalidation/shutdown still requires a fresh `/studio`.
|
|
11
|
+
- Same-tab reconnect now preserves the currently selected response-history item instead of jumping back to the latest response on every `hello_ack` resync.
|
|
12
|
+
|
|
13
|
+
## [0.5.7] — 2026-03-12
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Preview rendering now passes `--wrap=none` to pandoc and preview-side annotation matching now tolerates embedded newlines, fixing missed `[an: ...]` highlights in preview for longer annotations.
|
|
17
|
+
- Editor sync indicator is now intentionally quiet: Studio only shows the badge when the editor exactly matches the current response/thinking, and hides it while drafting/out-of-sync.
|
|
18
|
+
- Response history navigation now includes **Last response ▶|** for jumping straight back to the newest loaded history item.
|
|
19
|
+
- Renamed **Get latest response** to **Fetch latest response** for clearer distinction from history navigation, and moved **Load response into editor** ahead of **Load response prompt into editor** in the action row.
|
|
20
|
+
|
|
5
21
|
## [0.4.3] — 2026-03-04
|
|
6
22
|
|
|
7
23
|
### Added
|
|
@@ -86,8 +102,6 @@ All notable changes to `pi-studio` are documented here.
|
|
|
86
102
|
- Active pane indicator simplified to subtle border color change (removed thick top accent bar).
|
|
87
103
|
- Panel shadows, button hierarchy (filled accent for primary actions), heading scale, blockquote/table styling improvements.
|
|
88
104
|
|
|
89
|
-
## [Unreleased]
|
|
90
|
-
|
|
91
105
|
## [0.5.6] — 2026-03-10
|
|
92
106
|
|
|
93
107
|
### Changed
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Experimental extension for [pi](https://github.com/badlogic/pi-mono) that opens
|
|
|
16
16
|
|
|
17
17
|
- Opens a two-pane browser workspace: **Editor** (left) + **Response/Thinking/Editor Preview** (right)
|
|
18
18
|
- Runs editor text directly, or asks for structured critique (auto/writing/code focus)
|
|
19
|
-
- Browses response history (`Prev/Next`) and loads either:
|
|
19
|
+
- Browses response history (`Prev/Next/Last`) and loads either:
|
|
20
20
|
- response text
|
|
21
21
|
- critique notes/full critique
|
|
22
22
|
- assistant thinking (when available)
|
package/WORKFLOW.md
CHANGED
|
@@ -79,10 +79,10 @@ Rules:
|
|
|
79
79
|
- Header view toggles: `Left: Editor (Raw|Preview)`, `Right: Response (Raw|Preview) | Editor (Preview)`
|
|
80
80
|
- Preview mode uses server-side `pandoc` rendering (math-aware) with plain-markdown fallback when renderer is unavailable.
|
|
81
81
|
- Editor actions: **Insert/Remove annotated reply header**, **Annotations: On|Hidden**, **Strip annotations…**, **Run editor text**, **Critique editor text** (+ critique focus), **Send to pi editor**, **Copy editor text**, **Save .annotated.md**
|
|
82
|
-
- Response actions include `Auto-update response: On|Off`, **
|
|
82
|
+
- Response actions include `Auto-update response: On|Off`, **Fetch latest response**, response-history browse (`Prev/Next/Last`), **Load response into editor**, and **Load response prompt into editor**
|
|
83
83
|
- Source badge: `blank | last model response | file <path> | upload`
|
|
84
84
|
- Response badge: `none | assistant response | assistant critique` (+ timestamp)
|
|
85
|
-
- Sync badge:
|
|
85
|
+
- Sync badge: shown only when the editor exactly matches the currently viewed response/thinking (`In sync with response | In sync with thinking`)
|
|
86
86
|
- Footer WS/status phases: `Connecting`, `Ready`, `Submitting`, `Disconnected`
|
|
87
87
|
|
|
88
88
|
---
|
package/index.ts
CHANGED
|
@@ -1070,7 +1070,7 @@ function normalizeObsidianImages(markdown: string): string {
|
|
|
1070
1070
|
async function renderStudioMarkdownWithPandoc(markdown: string, isLatex?: boolean, resourcePath?: string): Promise<string> {
|
|
1071
1071
|
const pandocCommand = process.env.PANDOC_PATH?.trim() || "pandoc";
|
|
1072
1072
|
const inputFormat = isLatex ? "latex" : "gfm+tex_math_dollars-raw_html";
|
|
1073
|
-
const args = ["-f", inputFormat, "-t", "html5", "--mathml"];
|
|
1073
|
+
const args = ["-f", inputFormat, "-t", "html5", "--mathml", "--wrap=none"];
|
|
1074
1074
|
if (resourcePath) {
|
|
1075
1075
|
args.push(`--resource-path=${resourcePath}`);
|
|
1076
1076
|
// Embed images as data URIs so they render in the browser preview
|
|
@@ -2312,13 +2312,9 @@ ${cssVarsBlock}
|
|
|
2312
2312
|
}
|
|
2313
2313
|
|
|
2314
2314
|
.sync-badge.sync {
|
|
2315
|
-
border-color: var(--
|
|
2316
|
-
color: var(--
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
.sync-badge.edited {
|
|
2320
|
-
border-color: var(--warn-border);
|
|
2321
|
-
color: var(--warn);
|
|
2315
|
+
border-color: var(--border-muted);
|
|
2316
|
+
color: var(--muted);
|
|
2317
|
+
opacity: 0.88;
|
|
2322
2318
|
}
|
|
2323
2319
|
|
|
2324
2320
|
.source-actions {
|
|
@@ -3082,7 +3078,7 @@ ${cssVarsBlock}
|
|
|
3082
3078
|
<input id="resourceDirInput" type="text" placeholder="/path/to/working/directory" title="Absolute path to working directory" />
|
|
3083
3079
|
<button id="resourceDirClearBtn" type="button" title="Clear working directory">✕</button>
|
|
3084
3080
|
</span>
|
|
3085
|
-
<span id="syncBadge" class="source-badge sync-badge">
|
|
3081
|
+
<span id="syncBadge" class="source-badge sync-badge" hidden>In sync with response</span>
|
|
3086
3082
|
</div>
|
|
3087
3083
|
<div class="source-actions">
|
|
3088
3084
|
<div class="source-actions-row">
|
|
@@ -3181,16 +3177,17 @@ ${cssVarsBlock}
|
|
|
3181
3177
|
</select>
|
|
3182
3178
|
</div>
|
|
3183
3179
|
<div class="response-actions-row history-row">
|
|
3184
|
-
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">
|
|
3180
|
+
<button id="pullLatestBtn" type="button" title="Fetch the latest assistant response when auto-update is off.">Fetch latest response</button>
|
|
3185
3181
|
<button id="historyPrevBtn" type="button" title="Show previous response in history.">◀ Prev response</button>
|
|
3186
3182
|
<span id="historyIndexBadge" class="source-badge">History: 0/0</span>
|
|
3187
3183
|
<button id="historyNextBtn" type="button" title="Show next response in history.">Next response ▶</button>
|
|
3184
|
+
<button id="historyLastBtn" type="button" title="Jump to the latest loaded response in history.">Last response ▶|</button>
|
|
3188
3185
|
</div>
|
|
3189
3186
|
<div class="response-actions-row">
|
|
3190
|
-
<button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
|
|
3191
3187
|
<button id="loadResponseBtn" type="button">Load response into editor</button>
|
|
3192
3188
|
<button id="loadCritiqueNotesBtn" type="button" hidden>Load critique notes into editor</button>
|
|
3193
3189
|
<button id="loadCritiqueFullBtn" type="button" hidden>Load full critique into editor</button>
|
|
3190
|
+
<button id="loadHistoryPromptBtn" type="button" title="Load the prompt that generated the selected response into the editor.">Load response prompt into editor</button>
|
|
3194
3191
|
<button id="copyResponseBtn" type="button">Copy response text</button>
|
|
3195
3192
|
</div>
|
|
3196
3193
|
</div>
|
|
@@ -3278,6 +3275,7 @@ ${cssVarsBlock}
|
|
|
3278
3275
|
const exportPdfBtn = document.getElementById("exportPdfBtn");
|
|
3279
3276
|
const historyPrevBtn = document.getElementById("historyPrevBtn");
|
|
3280
3277
|
const historyNextBtn = document.getElementById("historyNextBtn");
|
|
3278
|
+
const historyLastBtn = document.getElementById("historyLastBtn");
|
|
3281
3279
|
const historyIndexBadgeEl = document.getElementById("historyIndexBadge");
|
|
3282
3280
|
const loadHistoryPromptBtn = document.getElementById("loadHistoryPromptBtn");
|
|
3283
3281
|
const saveAsBtn = document.getElementById("saveAsBtn");
|
|
@@ -3303,6 +3301,8 @@ ${cssVarsBlock}
|
|
|
3303
3301
|
let wsState = "Connecting";
|
|
3304
3302
|
let statusMessage = "Connecting · Studio script starting…";
|
|
3305
3303
|
let statusLevel = "";
|
|
3304
|
+
let reconnectTimer = null;
|
|
3305
|
+
let reconnectAttempt = 0;
|
|
3306
3306
|
let pendingRequestId = null;
|
|
3307
3307
|
let pendingKind = null;
|
|
3308
3308
|
let stickyStudioKind = null;
|
|
@@ -3419,7 +3419,7 @@ ${cssVarsBlock}
|
|
|
3419
3419
|
let responseHighlightEnabled = false;
|
|
3420
3420
|
let editorHighlightRenderRaf = null;
|
|
3421
3421
|
let annotationsEnabled = true;
|
|
3422
|
-
const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]
|
|
3422
|
+
const ANNOTATION_MARKER_REGEX = /\\[an:\\s*([^\\]]+?)\\]/gi;
|
|
3423
3423
|
const EMPTY_OVERLAY_LINE = "\\u200b";
|
|
3424
3424
|
const MERMAID_CDN_URL = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
|
|
3425
3425
|
const MERMAID_CONFIG = ${JSON.stringify(mermaidConfig)};
|
|
@@ -3992,6 +3992,9 @@ ${cssVarsBlock}
|
|
|
3992
3992
|
if (historyNextBtn) {
|
|
3993
3993
|
historyNextBtn.disabled = uiBusy || total <= 1 || responseHistoryIndex < 0 || responseHistoryIndex >= total - 1;
|
|
3994
3994
|
}
|
|
3995
|
+
if (historyLastBtn) {
|
|
3996
|
+
historyLastBtn.disabled = uiBusy || total <= 1 || responseHistoryIndex < 0 || responseHistoryIndex >= total - 1;
|
|
3997
|
+
}
|
|
3995
3998
|
|
|
3996
3999
|
const selectedItem = getSelectedHistoryItem();
|
|
3997
4000
|
const hasPrompt = Boolean(selectedItem && typeof selectedItem.prompt === "string" && selectedItem.prompt.trim());
|
|
@@ -4168,8 +4171,9 @@ ${cssVarsBlock}
|
|
|
4168
4171
|
: latestResponseHasContent;
|
|
4169
4172
|
|
|
4170
4173
|
if (!hasComparableContent) {
|
|
4171
|
-
syncBadgeEl.
|
|
4172
|
-
syncBadgeEl.
|
|
4174
|
+
syncBadgeEl.hidden = true;
|
|
4175
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4176
|
+
syncBadgeEl.classList.remove("sync");
|
|
4173
4177
|
return;
|
|
4174
4178
|
}
|
|
4175
4179
|
|
|
@@ -4178,15 +4182,15 @@ ${cssVarsBlock}
|
|
|
4178
4182
|
: normalizeForCompare(sourceTextEl.value);
|
|
4179
4183
|
const targetNormalized = showingThinking ? latestResponseThinkingNormalized : latestResponseNormalized;
|
|
4180
4184
|
const inSync = normalizedEditor === targetNormalized;
|
|
4185
|
+
syncBadgeEl.hidden = !inSync;
|
|
4186
|
+
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4187
|
+
|
|
4181
4188
|
if (inSync) {
|
|
4182
|
-
syncBadgeEl.textContent = showingThinking ? "In sync with thinking" : "In sync with response";
|
|
4183
4189
|
syncBadgeEl.classList.add("sync");
|
|
4184
|
-
|
|
4185
|
-
} else {
|
|
4186
|
-
syncBadgeEl.textContent = showingThinking ? "Out of sync with thinking" : "Out of sync with response";
|
|
4187
|
-
syncBadgeEl.classList.add("edited");
|
|
4188
|
-
syncBadgeEl.classList.remove("sync");
|
|
4190
|
+
return;
|
|
4189
4191
|
}
|
|
4192
|
+
|
|
4193
|
+
syncBadgeEl.classList.remove("sync");
|
|
4190
4194
|
}
|
|
4191
4195
|
|
|
4192
4196
|
function buildPlainMarkdownHtml(markdown) {
|
|
@@ -4818,7 +4822,7 @@ ${cssVarsBlock}
|
|
|
4818
4822
|
}
|
|
4819
4823
|
|
|
4820
4824
|
pullLatestBtn.disabled = uiBusy || followLatest;
|
|
4821
|
-
pullLatestBtn.textContent = queuedLatestResponse ? "
|
|
4825
|
+
pullLatestBtn.textContent = queuedLatestResponse ? "Fetch latest response *" : "Fetch latest response";
|
|
4822
4826
|
|
|
4823
4827
|
updateSyncBadge(normalizedEditor);
|
|
4824
4828
|
}
|
|
@@ -5029,7 +5033,7 @@ ${cssVarsBlock}
|
|
|
5029
5033
|
|
|
5030
5034
|
function highlightInlineMarkdown(text) {
|
|
5031
5035
|
const source = String(text || "");
|
|
5032
|
-
const pattern = /(\\x60[^\\x60]*\\x60)|(\\[[^\\]]+\\]\\([^)]+\\))|(\\[an:\\s*[^\\]
|
|
5036
|
+
const pattern = /(\\x60[^\\x60]*\\x60)|(\\[[^\\]]+\\]\\([^)]+\\))|(\\[an:\\s*[^\\]]+\\])/gi;
|
|
5033
5037
|
let lastIndex = 0;
|
|
5034
5038
|
let out = "";
|
|
5035
5039
|
|
|
@@ -5822,8 +5826,8 @@ ${cssVarsBlock}
|
|
|
5822
5826
|
let appliedHistory = false;
|
|
5823
5827
|
if (Array.isArray(message.responseHistory)) {
|
|
5824
5828
|
appliedHistory = setResponseHistory(message.responseHistory, {
|
|
5825
|
-
autoSelectLatest:
|
|
5826
|
-
preserveSelection:
|
|
5829
|
+
autoSelectLatest: !initialDocumentApplied,
|
|
5830
|
+
preserveSelection: initialDocumentApplied,
|
|
5827
5831
|
silent: true,
|
|
5828
5832
|
});
|
|
5829
5833
|
}
|
|
@@ -5967,7 +5971,7 @@ ${cssVarsBlock}
|
|
|
5967
5971
|
if (!followLatest) {
|
|
5968
5972
|
queuedLatestResponse = payload;
|
|
5969
5973
|
updateResultActionButtons();
|
|
5970
|
-
setStatus("New response available — click
|
|
5974
|
+
setStatus("New response available — click Fetch latest response.", "warning");
|
|
5971
5975
|
return;
|
|
5972
5976
|
}
|
|
5973
5977
|
|
|
@@ -6182,7 +6186,42 @@ ${cssVarsBlock}
|
|
|
6182
6186
|
}
|
|
6183
6187
|
}
|
|
6184
6188
|
|
|
6189
|
+
function clearScheduledReconnect() {
|
|
6190
|
+
if (reconnectTimer !== null) {
|
|
6191
|
+
window.clearTimeout(reconnectTimer);
|
|
6192
|
+
reconnectTimer = null;
|
|
6193
|
+
}
|
|
6194
|
+
}
|
|
6195
|
+
|
|
6196
|
+
function formatReconnectDelay(delayMs) {
|
|
6197
|
+
const delay = Math.max(0, Number(delayMs) || 0);
|
|
6198
|
+
if (delay < 1000) return delay + "ms";
|
|
6199
|
+
const seconds = delay / 1000;
|
|
6200
|
+
return (Number.isInteger(seconds) ? String(seconds) : seconds.toFixed(1)) + "s";
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6203
|
+
function scheduleReconnect(reasonMessage) {
|
|
6204
|
+
if (reconnectTimer !== null) return;
|
|
6205
|
+
|
|
6206
|
+
reconnectAttempt += 1;
|
|
6207
|
+
const delayMs = Math.min(8000, 600 * Math.pow(2, Math.max(0, reconnectAttempt - 1)));
|
|
6208
|
+
setBusy(true);
|
|
6209
|
+
setWsState("Connecting");
|
|
6210
|
+
setStatus((reasonMessage || "Connection lost.") + " Reconnecting in " + formatReconnectDelay(delayMs) + "…", "warning");
|
|
6211
|
+
|
|
6212
|
+
reconnectTimer = window.setTimeout(() => {
|
|
6213
|
+
reconnectTimer = null;
|
|
6214
|
+
connect();
|
|
6215
|
+
}, delayMs);
|
|
6216
|
+
}
|
|
6217
|
+
|
|
6185
6218
|
function connect() {
|
|
6219
|
+
clearScheduledReconnect();
|
|
6220
|
+
|
|
6221
|
+
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
|
6222
|
+
return;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6186
6225
|
const token = getToken();
|
|
6187
6226
|
if (!token) {
|
|
6188
6227
|
setWsState("Disconnected");
|
|
@@ -6193,26 +6232,61 @@ ${cssVarsBlock}
|
|
|
6193
6232
|
|
|
6194
6233
|
const wsProtocol = window.location.protocol === "https:" ? "wss" : "ws";
|
|
6195
6234
|
const wsUrl = wsProtocol + "://" + window.location.host + "/ws?token=" + encodeURIComponent(token) + (DEBUG_ENABLED ? "&debug=1" : "");
|
|
6235
|
+
const wasReconnect = reconnectAttempt > 0;
|
|
6236
|
+
let disconnectHandled = false;
|
|
6196
6237
|
|
|
6197
6238
|
setWsState("Connecting");
|
|
6198
|
-
setStatus("Connecting to Studio server…");
|
|
6199
|
-
|
|
6239
|
+
setStatus(wasReconnect ? "Reconnecting to Studio server…" : "Connecting to Studio server…");
|
|
6240
|
+
const socket = new WebSocket(wsUrl);
|
|
6241
|
+
ws = socket;
|
|
6200
6242
|
|
|
6201
6243
|
const connectWatchdog = window.setTimeout(() => {
|
|
6202
|
-
if (ws &&
|
|
6244
|
+
if (ws === socket && socket.readyState === WebSocket.CONNECTING) {
|
|
6203
6245
|
setWsState("Connecting");
|
|
6204
|
-
setStatus("Still connecting…", "warning");
|
|
6246
|
+
setStatus(wasReconnect ? "Still reconnecting…" : "Still connecting…", "warning");
|
|
6205
6247
|
}
|
|
6206
6248
|
}, 3000);
|
|
6207
6249
|
|
|
6208
|
-
|
|
6250
|
+
const handleDisconnect = (kind, code) => {
|
|
6251
|
+
if (disconnectHandled) return;
|
|
6252
|
+
disconnectHandled = true;
|
|
6253
|
+
window.clearTimeout(connectWatchdog);
|
|
6254
|
+
if (ws === socket) {
|
|
6255
|
+
ws = null;
|
|
6256
|
+
}
|
|
6257
|
+
setBusy(true);
|
|
6258
|
+
|
|
6259
|
+
if (kind === "invalidated") {
|
|
6260
|
+
clearScheduledReconnect();
|
|
6261
|
+
reconnectAttempt = 0;
|
|
6262
|
+
setWsState("Disconnected");
|
|
6263
|
+
setStatus("This tab was invalidated by a newer /studio session.", "warning");
|
|
6264
|
+
return;
|
|
6265
|
+
}
|
|
6266
|
+
|
|
6267
|
+
if (kind === "shutdown") {
|
|
6268
|
+
clearScheduledReconnect();
|
|
6269
|
+
reconnectAttempt = 0;
|
|
6270
|
+
setWsState("Disconnected");
|
|
6271
|
+
setStatus("Studio server shut down. Re-run /studio.", "warning");
|
|
6272
|
+
return;
|
|
6273
|
+
}
|
|
6274
|
+
|
|
6275
|
+
const detail = typeof code === "number" && code > 0
|
|
6276
|
+
? "Disconnected (code " + code + ")."
|
|
6277
|
+
: (kind === "error" ? "WebSocket error." : "Connection lost.");
|
|
6278
|
+
scheduleReconnect(detail);
|
|
6279
|
+
};
|
|
6280
|
+
|
|
6281
|
+
socket.addEventListener("open", () => {
|
|
6209
6282
|
window.clearTimeout(connectWatchdog);
|
|
6210
6283
|
setWsState("Ready");
|
|
6211
|
-
setStatus("Connected. Syncing…");
|
|
6284
|
+
setStatus(wasReconnect ? "Reconnected. Syncing…" : "Connected. Syncing…");
|
|
6212
6285
|
sendMessage({ type: "hello" });
|
|
6286
|
+
reconnectAttempt = 0;
|
|
6213
6287
|
});
|
|
6214
6288
|
|
|
6215
|
-
|
|
6289
|
+
socket.addEventListener("message", (event) => {
|
|
6216
6290
|
try {
|
|
6217
6291
|
const message = JSON.parse(event.data);
|
|
6218
6292
|
handleServerMessage(message);
|
|
@@ -6222,22 +6296,21 @@ ${cssVarsBlock}
|
|
|
6222
6296
|
}
|
|
6223
6297
|
});
|
|
6224
6298
|
|
|
6225
|
-
|
|
6226
|
-
window.clearTimeout(connectWatchdog);
|
|
6227
|
-
setBusy(true);
|
|
6228
|
-
setWsState("Disconnected");
|
|
6299
|
+
socket.addEventListener("close", (event) => {
|
|
6229
6300
|
if (event && event.code === 4001) {
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
const code = event && typeof event.code === "number" ? event.code : 0;
|
|
6233
|
-
setStatus("Disconnected (code " + code + "). Re-run /studio.", "error");
|
|
6301
|
+
handleDisconnect("invalidated", 4001);
|
|
6302
|
+
return;
|
|
6234
6303
|
}
|
|
6304
|
+
if (event && event.code === 1001) {
|
|
6305
|
+
handleDisconnect("shutdown", 1001);
|
|
6306
|
+
return;
|
|
6307
|
+
}
|
|
6308
|
+
const code = event && typeof event.code === "number" ? event.code : 0;
|
|
6309
|
+
handleDisconnect("close", code);
|
|
6235
6310
|
});
|
|
6236
6311
|
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
setWsState("Disconnected");
|
|
6240
|
-
setStatus("WebSocket error. Check /studio --status and reopen.", "error");
|
|
6312
|
+
socket.addEventListener("error", () => {
|
|
6313
|
+
handleDisconnect("error");
|
|
6241
6314
|
});
|
|
6242
6315
|
}
|
|
6243
6316
|
|
|
@@ -6342,7 +6415,7 @@ ${cssVarsBlock}
|
|
|
6342
6415
|
function requestLatestResponse() {
|
|
6343
6416
|
const sent = sendMessage({ type: "get_latest_response" });
|
|
6344
6417
|
if (!sent) return;
|
|
6345
|
-
setStatus("
|
|
6418
|
+
setStatus("Fetching latest response…");
|
|
6346
6419
|
}
|
|
6347
6420
|
|
|
6348
6421
|
if (leftPaneEl) {
|
|
@@ -6380,7 +6453,7 @@ ${cssVarsBlock}
|
|
|
6380
6453
|
setStatus("Applied queued response.", "success");
|
|
6381
6454
|
}
|
|
6382
6455
|
} else if (!followLatest) {
|
|
6383
|
-
setStatus("Auto-update is off. Use
|
|
6456
|
+
setStatus("Auto-update is off. Use Fetch latest response.");
|
|
6384
6457
|
}
|
|
6385
6458
|
updateResultActionButtons();
|
|
6386
6459
|
});
|
|
@@ -6464,6 +6537,16 @@ ${cssVarsBlock}
|
|
|
6464
6537
|
});
|
|
6465
6538
|
}
|
|
6466
6539
|
|
|
6540
|
+
if (historyLastBtn) {
|
|
6541
|
+
historyLastBtn.addEventListener("click", () => {
|
|
6542
|
+
if (!responseHistory.length) {
|
|
6543
|
+
setStatus("No response history available yet.", "warning");
|
|
6544
|
+
return;
|
|
6545
|
+
}
|
|
6546
|
+
selectHistoryIndex(responseHistory.length - 1);
|
|
6547
|
+
});
|
|
6548
|
+
}
|
|
6549
|
+
|
|
6467
6550
|
if (loadHistoryPromptBtn) {
|
|
6468
6551
|
loadHistoryPromptBtn.addEventListener("click", () => {
|
|
6469
6552
|
const item = getSelectedHistoryItem();
|