clay-server 4.1.0-beta.13 → 4.1.0-beta.14
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/lib/issues-mcp-server.js +1 -1
- package/lib/project-file-path.js +13 -0
- package/lib/project-file-watch.js +19 -8
- package/lib/project-filesystem.js +10 -16
- package/lib/project-http.js +19 -11
- package/lib/project-logs-mcp-server.js +8 -8
- package/lib/project.js +1 -0
- package/lib/public/app.js +12 -8
- package/lib/public/css/git-panel.css +13 -12
- package/lib/public/css/git-placard.css +4 -4
- package/lib/public/modules/app-rate-limit.js +148 -61
- package/lib/public/modules/filebrowser-tabs.js +1 -0
- package/lib/public/modules/filebrowser.js +45 -18
- package/lib/public/modules/issues.js +4 -6
- package/lib/public/modules/project-logs.js +4 -9
- package/lib/public/modules/right-workbench.js +44 -0
- package/lib/public/modules/scheduled-tasks.js +4 -5
- package/lib/public/modules/sidebar-mobile.js +2 -1
- package/lib/public/modules/sticky-notes-browser.js +4 -13
- package/lib/public/modules/terminal.js +4 -4
- package/lib/sdk-message-processor.js +6 -2
- package/lib/session-notes-mcp-server.js +2 -4
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +1 -1
|
@@ -13,6 +13,7 @@ var rateLimitResetTimer = null;
|
|
|
13
13
|
var rateLimitUsageEl = null;
|
|
14
14
|
var rateLimitResetState = {};
|
|
15
15
|
var rateLimitTickTimer = null;
|
|
16
|
+
var rateLimitPopoverTimers = [];
|
|
16
17
|
var fastModeIndicatorEl = null;
|
|
17
18
|
|
|
18
19
|
// --- Internal helpers ---
|
|
@@ -25,7 +26,7 @@ function getVendorUsageMeta(vendor) {
|
|
|
25
26
|
codex: {
|
|
26
27
|
icon: "/codex-avatar.png",
|
|
27
28
|
alt: "Codex",
|
|
28
|
-
href: "https://chatgpt.com/
|
|
29
|
+
href: "https://chatgpt.com/codex/settings/usage",
|
|
29
30
|
title: "Check usage on ChatGPT",
|
|
30
31
|
},
|
|
31
32
|
claude: {
|
|
@@ -35,7 +36,7 @@ function getVendorUsageMeta(vendor) {
|
|
|
35
36
|
title: "Check usage on claude.ai",
|
|
36
37
|
},
|
|
37
38
|
};
|
|
38
|
-
return fallbacks[vendor] ||
|
|
39
|
+
return fallbacks[vendor] || null;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
function vendorTracksRateLimits(vendor) {
|
|
@@ -51,6 +52,37 @@ function vendorSupportsScheduledMessages(vendor) {
|
|
|
51
52
|
return scheduledMessageVendors.indexOf(vendor) !== -1;
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
function rateLimitEventAppliesToPane(msg, state) {
|
|
56
|
+
var activeSessionId = state.activeSessionId;
|
|
57
|
+
var activeVendor = state.currentVendor || "claude";
|
|
58
|
+
if (!msg) return false;
|
|
59
|
+
if (msg.sessionId != null && String(msg.sessionId) !== String(activeSessionId)) return false;
|
|
60
|
+
if (msg.vendor) return msg.vendor === activeVendor;
|
|
61
|
+
// Legacy Claude records predate vendor/session stamps. Never project them
|
|
62
|
+
// into a non-Claude pane, where they would look like Claude usage.
|
|
63
|
+
return activeVendor === "claude";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function rateLimitStateKey(sessionId, vendor) {
|
|
67
|
+
return String(store.get('currentSlug') || "") + "\u0000" +
|
|
68
|
+
String(store.get('myUserId') || "") + "\u0000" +
|
|
69
|
+
String(sessionId == null ? "" : sessionId) + "\u0000" + String(vendor || "claude");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getActiveRateLimitState() {
|
|
73
|
+
var state = store.get('rateLimitState') || {};
|
|
74
|
+
var key = rateLimitStateKey(store.get('activeSessionId'), store.get('currentVendor'));
|
|
75
|
+
return state[key] || {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setActiveRateLimitState(next) {
|
|
79
|
+
var state = Object.assign({}, store.get('rateLimitState') || {});
|
|
80
|
+
var key = rateLimitStateKey(store.get('activeSessionId'), store.get('currentVendor'));
|
|
81
|
+
state[key] = next;
|
|
82
|
+
store.set({ rateLimitState: state });
|
|
83
|
+
rateLimitResetState = next;
|
|
84
|
+
}
|
|
85
|
+
|
|
54
86
|
function rateLimitTypeLabel(type) {
|
|
55
87
|
if (!type) return "Usage";
|
|
56
88
|
var labels = {
|
|
@@ -108,13 +140,15 @@ function updateRateLimitIndicator(msg) {
|
|
|
108
140
|
var isRejected = msg.status === "rejected";
|
|
109
141
|
var pillClass = "header-rate-limit" + (isRejected ? " rejected" : " warning");
|
|
110
142
|
var label = isRejected ? "Rate limited" : "Rate warning";
|
|
143
|
+
var meta = getVendorUsageMeta(store.get('currentVendor') || "claude");
|
|
144
|
+
var link = meta
|
|
145
|
+
? '<a href="' + meta.href + '" target="_blank" rel="noopener" class="rate-limit-link" title="' + meta.title + '">' + iconHtml("external-link") + "</a>"
|
|
146
|
+
: '<span class="rate-limit-link unavailable" title="Usage information is unavailable for this vendor">' + iconHtml("external-link") + "</span>";
|
|
111
147
|
rateLimitIndicatorEl.innerHTML =
|
|
112
148
|
'<span class="' + pillClass + '">' +
|
|
113
149
|
iconHtml("alert-triangle") +
|
|
114
150
|
'<span class="header-pill-text">' + label + "</span>" +
|
|
115
|
-
|
|
116
|
-
iconHtml("external-link") +
|
|
117
|
-
"</a>" +
|
|
151
|
+
link +
|
|
118
152
|
"</span>";
|
|
119
153
|
refreshIcons();
|
|
120
154
|
}
|
|
@@ -131,10 +165,12 @@ function showRateLimitPopover(text, isRejected) {
|
|
|
131
165
|
rateLimitIndicatorEl.appendChild(pop);
|
|
132
166
|
|
|
133
167
|
// Auto-dismiss after 5s
|
|
134
|
-
setTimeout(function () {
|
|
168
|
+
var fadeTimer = setTimeout(function () {
|
|
135
169
|
pop.classList.add("fade-out");
|
|
136
|
-
setTimeout(function () { if (pop.parentNode) pop.remove(); }, 300);
|
|
170
|
+
var removeTimer = setTimeout(function () { if (pop.parentNode) pop.remove(); }, 300);
|
|
171
|
+
rateLimitPopoverTimers.push(removeTimer);
|
|
137
172
|
}, 5000);
|
|
173
|
+
rateLimitPopoverTimers.push(fadeTimer);
|
|
138
174
|
}
|
|
139
175
|
|
|
140
176
|
function clearRateLimitIndicator() {
|
|
@@ -168,22 +204,81 @@ function tickRateLimitUsage() {
|
|
|
168
204
|
if (!rateLimitUsageEl) return;
|
|
169
205
|
var parts = [];
|
|
170
206
|
var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
|
|
207
|
+
var nextState = Object.assign({}, rateLimitResetState);
|
|
208
|
+
var changed = false;
|
|
171
209
|
for (var i = 0; i < types.length; i++) {
|
|
172
210
|
var entry = rateLimitResetState[types[i]];
|
|
173
211
|
if (!entry || !entry.resetsAt) continue;
|
|
174
212
|
var timeStr = formatResetTime(entry.resetsAt);
|
|
175
|
-
if (!timeStr) { delete
|
|
213
|
+
if (!timeStr) { delete nextState[types[i]]; changed = true; continue; }
|
|
176
214
|
parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
|
|
177
215
|
}
|
|
216
|
+
if (changed) setActiveRateLimitState(nextState);
|
|
178
217
|
if (parts.length === 0) {
|
|
179
|
-
|
|
180
|
-
|
|
218
|
+
renderRateLimitUsage();
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
renderRateLimitUsage();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function renderRateLimitUsage() {
|
|
225
|
+
var activeVendor = store.get('currentVendor') || "claude";
|
|
226
|
+
if (!vendorTracksRateLimits(activeVendor)) {
|
|
227
|
+
if (rateLimitUsageEl) {
|
|
228
|
+
rateLimitUsageEl.remove();
|
|
229
|
+
rateLimitUsageEl = null;
|
|
230
|
+
}
|
|
181
231
|
if (rateLimitTickTimer) { clearInterval(rateLimitTickTimer); rateLimitTickTimer = null; }
|
|
182
232
|
return;
|
|
183
233
|
}
|
|
184
|
-
|
|
185
|
-
|
|
234
|
+
|
|
235
|
+
rateLimitResetState = getActiveRateLimitState();
|
|
236
|
+
var topBarActions = document.querySelector("#top-bar .top-bar-actions");
|
|
237
|
+
if (!topBarActions) return;
|
|
238
|
+
if (!rateLimitUsageEl) {
|
|
239
|
+
rateLimitUsageEl = document.createElement("a");
|
|
240
|
+
rateLimitUsageEl.id = "rate-limit-usage-link";
|
|
241
|
+
rateLimitUsageEl.className = "top-bar-pill pill-dim usage-check-link";
|
|
242
|
+
rateLimitUsageEl.target = "_blank";
|
|
243
|
+
rateLimitUsageEl.rel = "noopener";
|
|
244
|
+
var ref = document.getElementById("skip-perms-pill");
|
|
245
|
+
topBarActions.insertBefore(rateLimitUsageEl, ref);
|
|
246
|
+
}
|
|
247
|
+
rateLimitUsageEl.style.display = "";
|
|
248
|
+
|
|
249
|
+
var parts = [];
|
|
250
|
+
var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
|
|
251
|
+
for (var i = 0; i < types.length; i++) {
|
|
252
|
+
var entry = rateLimitResetState[types[i]];
|
|
253
|
+
if (!entry || !entry.resetsAt) continue;
|
|
254
|
+
var timeStr = formatResetTime(entry.resetsAt);
|
|
255
|
+
if (!timeStr) continue;
|
|
256
|
+
parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
var label = parts.length > 0 ? parts.join(" · ") : "Check usage";
|
|
260
|
+
var meta = getVendorUsageMeta(activeVendor);
|
|
261
|
+
if (meta) {
|
|
262
|
+
rateLimitUsageEl.href = meta.href;
|
|
263
|
+
rateLimitUsageEl.title = meta.title;
|
|
264
|
+
rateLimitUsageEl.removeAttribute("aria-disabled");
|
|
265
|
+
rateLimitUsageEl.innerHTML =
|
|
266
|
+
'<img src="' + meta.icon + '" class="usage-check-vendor-icon" alt="' + meta.alt + '">' +
|
|
267
|
+
'<span>' + label + '</span>' + iconHtml("external-link");
|
|
268
|
+
} else {
|
|
269
|
+
rateLimitUsageEl.removeAttribute("href");
|
|
270
|
+
rateLimitUsageEl.setAttribute("aria-disabled", "true");
|
|
271
|
+
rateLimitUsageEl.title = "Usage information is unavailable for this vendor";
|
|
272
|
+
rateLimitUsageEl.innerHTML = '<span>' + (parts.length > 0 ? label : "Usage unavailable") + '</span>';
|
|
273
|
+
}
|
|
186
274
|
refreshIcons();
|
|
275
|
+
|
|
276
|
+
if (parts.length > 0 && !rateLimitTickTimer) {
|
|
277
|
+
rateLimitTickTimer = setInterval(tickRateLimitUsage, 30000);
|
|
278
|
+
} else if (parts.length === 0 && rateLimitTickTimer) {
|
|
279
|
+
clearInterval(rateLimitTickTimer);
|
|
280
|
+
rateLimitTickTimer = null;
|
|
281
|
+
}
|
|
187
282
|
}
|
|
188
283
|
|
|
189
284
|
// --- Exported functions ---
|
|
@@ -193,15 +288,32 @@ export function initRateLimit() {
|
|
|
193
288
|
if (state.currentVendor !== prev.currentVendor && state.currentVendor) {
|
|
194
289
|
if (!vendorSupportsScheduledMessages(state.currentVendor)) clearScheduleDelay();
|
|
195
290
|
}
|
|
196
|
-
if (state.currentVendor !== prev.currentVendor || state.
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
291
|
+
if (state.currentVendor !== prev.currentVendor || state.activeSessionId !== prev.activeSessionId
|
|
292
|
+
|| state.currentSlug !== prev.currentSlug || state.myUserId !== prev.myUserId) {
|
|
293
|
+
resetRateLimitState();
|
|
294
|
+
}
|
|
295
|
+
if (state.vendorInfo !== prev.vendorInfo && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
|
|
296
|
+
&& state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
|
|
297
|
+
renderRateLimitUsage();
|
|
200
298
|
}
|
|
201
299
|
});
|
|
202
300
|
}
|
|
203
301
|
|
|
204
302
|
export function handleRateLimitEvent(msg) {
|
|
303
|
+
if (!rateLimitEventAppliesToPane(msg, store.snap())) return;
|
|
304
|
+
if (msg.resetsAt && msg.resetsAt <= Date.now()) {
|
|
305
|
+
var expiredState = Object.assign({}, getActiveRateLimitState());
|
|
306
|
+
if (msg.rateLimitType) delete expiredState[msg.rateLimitType];
|
|
307
|
+
setActiveRateLimitState(expiredState);
|
|
308
|
+
clearRateLimitIndicator();
|
|
309
|
+
renderRateLimitUsage();
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (msg.rateLimitType && msg.resetsAt) {
|
|
313
|
+
var eventState = Object.assign({}, getActiveRateLimitState());
|
|
314
|
+
eventState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
|
|
315
|
+
setActiveRateLimitState(eventState);
|
|
316
|
+
}
|
|
205
317
|
var isRejected = msg.status === "rejected";
|
|
206
318
|
var typeLabel = rateLimitTypeLabel(msg.rateLimitType);
|
|
207
319
|
var popoverText = "";
|
|
@@ -226,8 +338,12 @@ export function handleRateLimitEvent(msg) {
|
|
|
226
338
|
rateLimitResetTimer = setTimeout(function () {
|
|
227
339
|
rateLimitResetsAt = null;
|
|
228
340
|
rateLimitResetTimer = null;
|
|
341
|
+
var currentState = Object.assign({}, getActiveRateLimitState());
|
|
342
|
+
delete currentState[msg.rateLimitType];
|
|
343
|
+
setActiveRateLimitState(currentState);
|
|
229
344
|
// Clear schedule mode when rate limit resets
|
|
230
345
|
clearScheduleDelay();
|
|
346
|
+
renderRateLimitUsage();
|
|
231
347
|
}, msg.resetsAt - Date.now() + 1000);
|
|
232
348
|
} else {
|
|
233
349
|
var pct = msg.utilization ? Math.round(msg.utilization * 100) : null;
|
|
@@ -239,58 +355,19 @@ export function handleRateLimitEvent(msg) {
|
|
|
239
355
|
}
|
|
240
356
|
|
|
241
357
|
export function updateRateLimitUsage(msg) {
|
|
358
|
+
if (!rateLimitEventAppliesToPane(msg, store.snap())) return;
|
|
242
359
|
var activeVendor = store.get('currentVendor') || "claude";
|
|
243
360
|
if (!vendorTracksRateLimits(activeVendor)) {
|
|
244
|
-
|
|
361
|
+
renderRateLimitUsage();
|
|
245
362
|
return;
|
|
246
363
|
}
|
|
247
364
|
if (msg.rateLimitType && msg.resetsAt) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
var topBarActions = document.querySelector("#top-bar .top-bar-actions");
|
|
252
|
-
if (!topBarActions) return;
|
|
253
|
-
|
|
254
|
-
if (!rateLimitUsageEl) {
|
|
255
|
-
rateLimitUsageEl = document.createElement("a");
|
|
256
|
-
rateLimitUsageEl.id = "rate-limit-usage-link";
|
|
257
|
-
rateLimitUsageEl.className = "top-bar-pill pill-dim usage-check-link";
|
|
258
|
-
rateLimitUsageEl.target = "_blank";
|
|
259
|
-
rateLimitUsageEl.rel = "noopener";
|
|
260
|
-
var ref = document.getElementById("skip-perms-pill");
|
|
261
|
-
topBarActions.insertBefore(rateLimitUsageEl, ref);
|
|
262
|
-
}
|
|
263
|
-
rateLimitUsageEl.style.display = "";
|
|
264
|
-
|
|
265
|
-
// Build label from available reset times
|
|
266
|
-
var parts = [];
|
|
267
|
-
var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
|
|
268
|
-
for (var i = 0; i < types.length; i++) {
|
|
269
|
-
var entry = rateLimitResetState[types[i]];
|
|
270
|
-
if (!entry || !entry.resetsAt) continue;
|
|
271
|
-
var timeStr = formatResetTime(entry.resetsAt);
|
|
272
|
-
if (!timeStr) continue;
|
|
273
|
-
parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
|
|
365
|
+
var nextState = Object.assign({}, getActiveRateLimitState());
|
|
366
|
+
nextState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
|
|
367
|
+
setActiveRateLimitState(nextState);
|
|
274
368
|
}
|
|
275
369
|
|
|
276
|
-
|
|
277
|
-
var vendor = activeVendor;
|
|
278
|
-
var meta = getVendorUsageMeta(vendor);
|
|
279
|
-
rateLimitUsageEl.href = meta.href;
|
|
280
|
-
rateLimitUsageEl.title = meta.title;
|
|
281
|
-
rateLimitUsageEl.innerHTML =
|
|
282
|
-
'<img src="' + meta.icon + '" class="usage-check-vendor-icon" alt="' + meta.alt + '">' +
|
|
283
|
-
'<span>' + label + '</span>' +
|
|
284
|
-
iconHtml("external-link");
|
|
285
|
-
refreshIcons();
|
|
286
|
-
|
|
287
|
-
// Start or stop live countdown tick
|
|
288
|
-
if (parts.length > 0 && !rateLimitTickTimer) {
|
|
289
|
-
rateLimitTickTimer = setInterval(tickRateLimitUsage, 30000);
|
|
290
|
-
} else if (parts.length === 0 && rateLimitTickTimer) {
|
|
291
|
-
clearInterval(rateLimitTickTimer);
|
|
292
|
-
rateLimitTickTimer = null;
|
|
293
|
-
}
|
|
370
|
+
renderRateLimitUsage();
|
|
294
371
|
}
|
|
295
372
|
|
|
296
373
|
export function handleFastModeState(state) {
|
|
@@ -322,6 +399,16 @@ export function handleFastModeState(state) {
|
|
|
322
399
|
|
|
323
400
|
export function resetRateLimitState() {
|
|
324
401
|
clearRateLimitIndicator();
|
|
402
|
+
rateLimitResetsAt = null;
|
|
403
|
+
if (rateLimitResetTimer) { clearTimeout(rateLimitResetTimer); rateLimitResetTimer = null; }
|
|
404
|
+
clearScheduleDelay();
|
|
405
|
+
for (var i = 0; i < rateLimitPopoverTimers.length; i++) clearTimeout(rateLimitPopoverTimers[i]);
|
|
406
|
+
rateLimitPopoverTimers = [];
|
|
407
|
+
if (rateLimitTickTimer) { clearInterval(rateLimitTickTimer); rateLimitTickTimer = null; }
|
|
325
408
|
if (rateLimitCountdownTimer) { clearInterval(rateLimitCountdownTimer); rateLimitCountdownTimer = null; }
|
|
409
|
+
rateLimitResetState = getActiveRateLimitState();
|
|
410
|
+
renderRateLimitUsage();
|
|
326
411
|
if (fastModeIndicatorEl) { fastModeIndicatorEl.remove(); fastModeIndicatorEl = null; }
|
|
327
412
|
}
|
|
413
|
+
|
|
414
|
+
export { rateLimitEventAppliesToPane, getVendorUsageMeta };
|
|
@@ -101,4 +101,5 @@ export function closeFileViewerTab(path) {
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
export function focusedFileViewerTab() { return focusedPath; }
|
|
104
|
+
export function focusedFileViewerTabData() { return focusedPath && tabs.has(focusedPath) ? tabs.get(focusedPath) : null; }
|
|
104
105
|
export function clearFileViewerTabs() { tabs.clear(); focusedPath = null; previewPath = null; render(); }
|
|
@@ -2,15 +2,14 @@ import { iconHtml, refreshIcons } from './icons.js';
|
|
|
2
2
|
import { escapeHtml, copyToClipboard } from './utils.js';
|
|
3
3
|
import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, exportMarkdownAsPdf } from './markdown.js';
|
|
4
4
|
import { closeSidebar } from './sidebar.js';
|
|
5
|
-
import {
|
|
5
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench, isRightWorkbenchCurrent } from './right-workbench.js';
|
|
6
6
|
import { renderUnifiedDiff, renderSplitDiff } from './diff.js';
|
|
7
7
|
import { initFileIcons, getFileIconSvg, getFolderIconSvg } from './fileicons.js';
|
|
8
8
|
import { copyMarkdownFormatting } from './rich-clipboard.js';
|
|
9
9
|
import { animateMarkdownChange, beginMarkdownPresentation, cancelMarkdownFollow, isFollowingMarkdown } from './markdown-live-edit.js';
|
|
10
10
|
import { store } from './store.js';
|
|
11
11
|
import { enterMarkdownSlides, exitMarkdownSlides, handleMarkdownSlideKey, syncMarkdownSlidesButton, toggleMarkdownSlideLevelMenu } from './markdown-slides.js';
|
|
12
|
-
import { initFileViewerTabs, openFileViewerTab, previewFileViewerTab, updateFileViewerTab, closeFileViewerTab, focusedFileViewerTab, clearFileViewerTabs } from './filebrowser-tabs.js';
|
|
13
|
-
import { closeIssues } from './issues.js';
|
|
12
|
+
import { initFileViewerTabs, openFileViewerTab, previewFileViewerTab, updateFileViewerTab, closeFileViewerTab, focusedFileViewerTab, focusedFileViewerTabData, clearFileViewerTabs } from './filebrowser-tabs.js';
|
|
14
13
|
import { initFileBrowserContextMenu, downloadProjectFile } from './filebrowser-context-menu.js';
|
|
15
14
|
import { registerClayFileLinkOpener } from './clay-file-links.js';
|
|
16
15
|
|
|
@@ -41,10 +40,13 @@ export function initFileBrowser(_ctx) {
|
|
|
41
40
|
if (mainPanels && ctx.fileViewerEl) mainPanels.appendChild(ctx.fileViewerEl);
|
|
42
41
|
initFileViewerTabs({
|
|
43
42
|
onFocus: function(path) {
|
|
43
|
+
claimRightWorkbench("file-viewer");
|
|
44
|
+
ctx.fileViewerEl.classList.remove("hidden");
|
|
44
45
|
if (path !== currentFilePath) requestFileContent(path);
|
|
45
46
|
},
|
|
46
47
|
onEmpty: function() { teardownFileViewer(); },
|
|
47
48
|
});
|
|
49
|
+
registerRightWorkbench("file-viewer", teardownFileViewer);
|
|
48
50
|
|
|
49
51
|
// Load material file icons in background
|
|
50
52
|
initFileIcons().then(function () {
|
|
@@ -149,7 +151,7 @@ export function initFileBrowser(_ctx) {
|
|
|
149
151
|
|
|
150
152
|
// Close button
|
|
151
153
|
document.getElementById("file-viewer-close").addEventListener("click", function () {
|
|
152
|
-
|
|
154
|
+
closeFileViewer();
|
|
153
155
|
});
|
|
154
156
|
|
|
155
157
|
// Full-viewport presentation toggle
|
|
@@ -395,6 +397,7 @@ function kbCollapseOrAscend() {
|
|
|
395
397
|
function kbActivate() {
|
|
396
398
|
if (!_kbFocused) return;
|
|
397
399
|
if (!isDirRow(_kbFocused) && _kbFocused.dataset.path) {
|
|
400
|
+
claimRightWorkbench("file-viewer");
|
|
398
401
|
openFileViewerTab(_kbFocused.dataset.path);
|
|
399
402
|
}
|
|
400
403
|
_kbFocused.click();
|
|
@@ -477,10 +480,7 @@ function ensureRenderedMarkdown() {
|
|
|
477
480
|
}
|
|
478
481
|
|
|
479
482
|
export function closeFileViewer() {
|
|
480
|
-
|
|
481
|
-
closeFileViewerTab(focusedFileViewerTab());
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
483
|
+
releaseRightWorkbench("file-viewer");
|
|
484
484
|
teardownFileViewer();
|
|
485
485
|
}
|
|
486
486
|
|
|
@@ -512,6 +512,7 @@ export function setFileViewerFullscreen(enabled) {
|
|
|
512
512
|
}
|
|
513
513
|
|
|
514
514
|
export function resetFileBrowser() {
|
|
515
|
+
releaseRightWorkbench("file-viewer");
|
|
515
516
|
clearFileViewerTabs();
|
|
516
517
|
teardownFileViewer();
|
|
517
518
|
// Clear all cached state
|
|
@@ -552,12 +553,13 @@ export function openFile(filePath, opts) {
|
|
|
552
553
|
if (!state.connected) return false;
|
|
553
554
|
if (opts && opts.projectSlug && opts.projectSlug !== state.currentSlug) return false;
|
|
554
555
|
if (opts && opts.sessionId && String(opts.sessionId) !== String(state.activeSessionId)) return false;
|
|
555
|
-
|
|
556
|
+
var workbenchRevision = claimRightWorkbench("file-viewer");
|
|
556
557
|
if (followedFileChanged(filePath) || (opts && opts.diff && isFollowingMarkdown(filePath))) {
|
|
557
558
|
cancelMarkdownFollow();
|
|
558
559
|
}
|
|
559
560
|
pendingRenderedOpen = !!(opts && opts.rendered);
|
|
560
561
|
store.set({ pendingFileNavigation: opts && opts.line ? { path: filePath, line: opts.line, column: opts.column || null, requestId: null } : null });
|
|
562
|
+
store.set({ fileViewerOpenRevision: workbenchRevision });
|
|
561
563
|
openFileViewerTab(filePath);
|
|
562
564
|
if (opts && opts.diff) {
|
|
563
565
|
pendingOpenMode = { type: "diff", oldStr: opts.diff.oldStr, newStr: opts.diff.newStr };
|
|
@@ -567,9 +569,28 @@ export function openFile(filePath, opts) {
|
|
|
567
569
|
requestFileContent(filePath);
|
|
568
570
|
}
|
|
569
571
|
|
|
572
|
+
export function reopenFileViewer() {
|
|
573
|
+
var workbenchRevision = claimRightWorkbench("file-viewer");
|
|
574
|
+
store.set({ fileViewerOpenRevision: workbenchRevision });
|
|
575
|
+
if (!focusedFileViewerTab()) return false;
|
|
576
|
+
ctx.fileViewerEl.classList.remove("hidden");
|
|
577
|
+
var tab = focusedFileViewerTabData();
|
|
578
|
+
if (currentFilePath === focusedFileViewerTab() && currentContent != null) {
|
|
579
|
+
sendWatch(currentFilePath);
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
if (tab && tab.content != null) {
|
|
583
|
+
showFileContent({ path: focusedFileViewerTab(), content: tab.content, size: tab.content.length });
|
|
584
|
+
return true;
|
|
585
|
+
}
|
|
586
|
+
requestFileContent(focusedFileViewerTab());
|
|
587
|
+
return true;
|
|
588
|
+
}
|
|
589
|
+
|
|
570
590
|
export function openWorkingTreeDiff(diff) {
|
|
571
591
|
if (!diff || !diff.path) return;
|
|
572
|
-
|
|
592
|
+
var workbenchRevision = claimRightWorkbench("file-viewer");
|
|
593
|
+
store.set({ fileViewerOpenRevision: workbenchRevision });
|
|
573
594
|
openFileViewerTab(diff.path);
|
|
574
595
|
pendingRenderedOpen = false;
|
|
575
596
|
pendingOpenMode = diff.binary ? null : {
|
|
@@ -588,6 +609,8 @@ export function openWorkingTreeDiff(diff) {
|
|
|
588
609
|
|
|
589
610
|
export function presentMarkdownEdit(msg) {
|
|
590
611
|
if (!msg || !beginMarkdownPresentation(msg.path)) return;
|
|
612
|
+
var workbenchRevision = claimRightWorkbench("file-viewer");
|
|
613
|
+
store.set({ fileViewerOpenRevision: workbenchRevision });
|
|
591
614
|
openFileViewerTab(msg.path, { content: msg.content });
|
|
592
615
|
pendingOpenMode = null;
|
|
593
616
|
pendingRenderedOpen = true;
|
|
@@ -687,7 +710,7 @@ function requestDirectory(dirPath) {
|
|
|
687
710
|
function requestFileContent(filePath) {
|
|
688
711
|
var requestId = "file-read-" + Date.now() + "-" + Math.random().toString(36).slice(2);
|
|
689
712
|
var state = store.snap();
|
|
690
|
-
store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null } });
|
|
713
|
+
store.set({ fileReadRequest: { requestId: requestId, path: filePath, projectSlug: state.currentSlug || null, sessionId: state.activeSessionId != null ? String(state.activeSessionId) : null, accountId: state.myUserId || null, workbenchRevision: store.get('rightWorkbenchRevision') || 0 } });
|
|
691
714
|
var pending = store.get('pendingFileNavigation');
|
|
692
715
|
if (pending && pending.path === filePath) store.set({ pendingFileNavigation: Object.assign({}, pending, { requestId: requestId }) });
|
|
693
716
|
if (ctx.ws && ctx.connected) {
|
|
@@ -811,6 +834,7 @@ function renderFilteredTree(container, tree, depth, query) {
|
|
|
811
834
|
(function (rowEl, childEl, folderName) {
|
|
812
835
|
rowEl.addEventListener("click", function (e) {
|
|
813
836
|
e.stopPropagation();
|
|
837
|
+
claimRightWorkbench("file-viewer");
|
|
814
838
|
var isExpanded = rowEl.classList.contains("expanded");
|
|
815
839
|
rowEl.classList.toggle("expanded");
|
|
816
840
|
childEl.classList.toggle("hidden", isExpanded);
|
|
@@ -833,6 +857,7 @@ function renderFilteredTree(container, tree, depth, query) {
|
|
|
833
857
|
(function (filePath, rowEl) {
|
|
834
858
|
rowEl.addEventListener("click", function (e) {
|
|
835
859
|
e.stopPropagation();
|
|
860
|
+
claimRightWorkbench("file-viewer");
|
|
836
861
|
if (followedFileChanged(filePath)) cancelMarkdownFollow();
|
|
837
862
|
var prev = ctx.fileTreeEl.querySelector(".file-tree-item.active");
|
|
838
863
|
if (prev) prev.classList.remove("active");
|
|
@@ -843,7 +868,7 @@ function renderFilteredTree(container, tree, depth, query) {
|
|
|
843
868
|
});
|
|
844
869
|
rowEl.addEventListener("dblclick", function (e) {
|
|
845
870
|
e.stopPropagation();
|
|
846
|
-
|
|
871
|
+
openFile(filePath);
|
|
847
872
|
});
|
|
848
873
|
})(entry.path, row);
|
|
849
874
|
|
|
@@ -973,7 +998,7 @@ export function handleFsRead(msg) {
|
|
|
973
998
|
if ((request.projectSlug || null) !== (msg.projectSlug || null)) return;
|
|
974
999
|
if ((request.sessionId || null) !== (msg.sessionId == null ? null : String(msg.sessionId))) return;
|
|
975
1000
|
if ((request.accountId || null) !== (msg.accountId || null)) return;
|
|
976
|
-
showFileContent(msg);
|
|
1001
|
+
showFileContent(msg, request.workbenchRevision);
|
|
977
1002
|
}
|
|
978
1003
|
|
|
979
1004
|
// --- Tree rendering ---
|
|
@@ -1040,6 +1065,7 @@ function renderEntries(container, entries, depth) {
|
|
|
1040
1065
|
(function (dirPath, childEl, rowEl, folderName) {
|
|
1041
1066
|
rowEl.addEventListener("click", function (e) {
|
|
1042
1067
|
e.stopPropagation();
|
|
1068
|
+
claimRightWorkbench("file-viewer");
|
|
1043
1069
|
var isExpanded = rowEl.classList.contains("expanded");
|
|
1044
1070
|
if (isExpanded) {
|
|
1045
1071
|
rowEl.classList.remove("expanded");
|
|
@@ -1079,6 +1105,7 @@ function renderEntries(container, entries, depth) {
|
|
|
1079
1105
|
(function (filePath, rowEl) {
|
|
1080
1106
|
rowEl.addEventListener("click", function (e) {
|
|
1081
1107
|
e.stopPropagation();
|
|
1108
|
+
claimRightWorkbench("file-viewer");
|
|
1082
1109
|
if (followedFileChanged(filePath)) cancelMarkdownFollow();
|
|
1083
1110
|
// Mark active
|
|
1084
1111
|
var prev = ctx.fileTreeEl.querySelector(".file-tree-item.active");
|
|
@@ -1093,7 +1120,7 @@ function renderEntries(container, entries, depth) {
|
|
|
1093
1120
|
});
|
|
1094
1121
|
rowEl.addEventListener("dblclick", function (e) {
|
|
1095
1122
|
e.stopPropagation();
|
|
1096
|
-
|
|
1123
|
+
openFile(filePath);
|
|
1097
1124
|
});
|
|
1098
1125
|
})(entry.path, row);
|
|
1099
1126
|
|
|
@@ -1105,8 +1132,9 @@ function renderEntries(container, entries, depth) {
|
|
|
1105
1132
|
|
|
1106
1133
|
// --- File viewer ---
|
|
1107
1134
|
|
|
1108
|
-
function showFileContent(msg) {
|
|
1135
|
+
function showFileContent(msg, expectedRevision) {
|
|
1109
1136
|
if (!updateFileViewerTab(msg.path, { content: msg.content })) return;
|
|
1137
|
+
if (expectedRevision != null && !isRightWorkbenchCurrent("file-viewer", expectedRevision)) return;
|
|
1110
1138
|
var pathEl = document.getElementById("file-viewer-path");
|
|
1111
1139
|
var bodyEl = document.getElementById("file-viewer-body");
|
|
1112
1140
|
var renderBtn = document.getElementById("file-viewer-render");
|
|
@@ -1183,7 +1211,6 @@ function showFileContent(msg) {
|
|
|
1183
1211
|
}
|
|
1184
1212
|
}
|
|
1185
1213
|
|
|
1186
|
-
closeTerminal();
|
|
1187
1214
|
ctx.fileViewerEl.classList.remove("hidden");
|
|
1188
1215
|
sendWatch(msg.path);
|
|
1189
1216
|
refreshIcons();
|
|
@@ -1291,7 +1318,7 @@ function pathsReferToSameFile(left, right) {
|
|
|
1291
1318
|
}
|
|
1292
1319
|
|
|
1293
1320
|
export function handleFileChanged(msg) {
|
|
1294
|
-
if (!msg.path || msg.path
|
|
1321
|
+
if (!msg.path || !pathsReferToSameFile(msg.path, currentFilePath)) return;
|
|
1295
1322
|
if (ctx.fileViewerEl.classList.contains("hidden")) return;
|
|
1296
1323
|
if (historyVisible || inlineDiffActive) return;
|
|
1297
1324
|
if (msg.content === currentContent) return;
|
|
@@ -1299,7 +1326,7 @@ export function handleFileChanged(msg) {
|
|
|
1299
1326
|
var bodyEl = document.getElementById("file-viewer-body");
|
|
1300
1327
|
var scrollPos = bodyEl ? bodyEl.scrollTop : 0;
|
|
1301
1328
|
pendingRefresh = true;
|
|
1302
|
-
showFileContent(msg);
|
|
1329
|
+
showFileContent(msg.path === currentFilePath ? msg : Object.assign({}, msg, { path: currentFilePath }));
|
|
1303
1330
|
if (bodyEl) bodyEl.scrollTop = scrollPos;
|
|
1304
1331
|
}
|
|
1305
1332
|
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
3
|
import { showToast } from './utils.js';
|
|
4
|
-
import {
|
|
5
|
-
import { closeScheduledTasks } from './scheduled-tasks.js';
|
|
6
|
-
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
7
|
-
import { closeFileViewer } from './filebrowser.js';
|
|
8
|
-
import { closeTerminal } from './terminal.js';
|
|
4
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
9
5
|
import { refreshIcons } from './icons.js';
|
|
10
6
|
import { renderIssueList, renderIssueDetail, renderIssueForm, renderIssueHistory, statusLabel } from './issues-render.js';
|
|
11
7
|
function panel() { return document.getElementById('issues-panel'); }
|
|
@@ -81,6 +77,7 @@ function confirmDelete() {
|
|
|
81
77
|
store.set({ issuesMutationRequest: request('issue_delete', { ref: entry.ref, expectedRevision: entry.revision }, 'delete') });
|
|
82
78
|
}
|
|
83
79
|
export function closeIssues() {
|
|
80
|
+
releaseRightWorkbench("issues");
|
|
84
81
|
bumpViewGeneration();
|
|
85
82
|
if (panel()) panel().classList.add('hidden');
|
|
86
83
|
store.set({ issuesOpen: false });
|
|
@@ -91,7 +88,7 @@ export function openIssues(ref) {
|
|
|
91
88
|
if (!panel() || store.get('isMate')) return;
|
|
92
89
|
panel().querySelector('.issues-notice').textContent = '';
|
|
93
90
|
bumpViewGeneration();
|
|
94
|
-
|
|
91
|
+
claimRightWorkbench("issues");
|
|
95
92
|
panel().classList.remove('hidden');
|
|
96
93
|
store.set({ issuesOpen: true });
|
|
97
94
|
var button = document.getElementById('issues-btn');
|
|
@@ -102,6 +99,7 @@ export function openIssues(ref) {
|
|
|
102
99
|
export function initIssues() {
|
|
103
100
|
var host = document.getElementById('main-panels');
|
|
104
101
|
if (!host || panel()) return;
|
|
102
|
+
registerRightWorkbench("issues", closeIssues);
|
|
105
103
|
var el = document.createElement('section');
|
|
106
104
|
el.id = 'issues-panel'; el.className = 'hidden'; el.setAttribute('aria-label', 'Project Issues');
|
|
107
105
|
el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option>bug</option><option>feature</option><option>plan</option></select></div><p class="issues-notice" role="status"></p><main class="issues-content" role="region" aria-live="polite"></main><div class="issues-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="issues-delete-heading"><div class="issues-delete-card"><h2 id="issues-delete-heading">Delete this issue?</h2><p>This removes the issue from the board but preserves its history.</p><p class="issues-delete-title"></p><p class="issues-delete-status" role="status"></p><div><button type="button" data-issue-delete-cancel>Cancel</button><button type="button" data-issue-delete-confirm>Delete issue</button></div></div></div>';
|
|
@@ -16,10 +16,7 @@ import { showToast } from './utils.js';
|
|
|
16
16
|
import { showScheduledResultNotification } from './scheduled-result-notification.js';
|
|
17
17
|
import * as scheduledResultClient from './scheduled-result-client.js';
|
|
18
18
|
import { clearPendingScheduledResultRead, handleScheduledResultDisplayed, initScheduledResultDisplay, openScheduledResultSession } from './scheduled-result-display.js';
|
|
19
|
-
import {
|
|
20
|
-
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
21
|
-
import { closeFileViewer } from './filebrowser.js';
|
|
22
|
-
import { closeTerminal } from './terminal.js';
|
|
19
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
23
20
|
import { renderFilter, renderContextFilter, renderList, renderDetail } from './project-logs-render.js';
|
|
24
21
|
|
|
25
22
|
var panel = null;
|
|
@@ -285,6 +282,7 @@ function applyContextFilter(contextMode) {
|
|
|
285
282
|
// --- Lifecycle -----------------------------------------------------------
|
|
286
283
|
|
|
287
284
|
export function initProjectLogs() {
|
|
285
|
+
registerRightWorkbench("project-logs", closeProjectLogs);
|
|
288
286
|
var button = document.getElementById("project-logs-btn");
|
|
289
287
|
if (!button) return;
|
|
290
288
|
button.addEventListener("click", function () {
|
|
@@ -331,11 +329,7 @@ export function initProjectLogs() {
|
|
|
331
329
|
export function openProjectLogs() {
|
|
332
330
|
ensurePanel();
|
|
333
331
|
if (!panel) return;
|
|
334
|
-
|
|
335
|
-
closeNotesBrowser();
|
|
336
|
-
// Claim the single right workbench slot.
|
|
337
|
-
try { closeFileViewer(); } catch (e) {}
|
|
338
|
-
try { closeTerminal(); } catch (e) {}
|
|
332
|
+
claimRightWorkbench("project-logs");
|
|
339
333
|
panel.classList.remove("hidden");
|
|
340
334
|
applyWindowState(store.get('projectLogsWide'), false);
|
|
341
335
|
var button = document.getElementById("project-logs-btn");
|
|
@@ -355,6 +349,7 @@ export function openProjectLog(ref) {
|
|
|
355
349
|
}
|
|
356
350
|
|
|
357
351
|
export function closeProjectLogs() {
|
|
352
|
+
releaseRightWorkbench("project-logs");
|
|
358
353
|
if (!store.get('projectLogsOpen')) return;
|
|
359
354
|
if (panel) {
|
|
360
355
|
panel.classList.add("hidden");
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Single arbitration point for the mutually exclusive right-side workbench.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
|
|
5
|
+
var closers = {};
|
|
6
|
+
|
|
7
|
+
export function registerRightWorkbench(name, close) {
|
|
8
|
+
if (!name || typeof close !== "function") return;
|
|
9
|
+
closers[name] = close;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function claimRightWorkbench(name) {
|
|
13
|
+
var previousRevision = store.get("rightWorkbenchRevision") || 0;
|
|
14
|
+
var nextRevision = previousRevision + 1;
|
|
15
|
+
storeSet(name, nextRevision);
|
|
16
|
+
var keys = Object.keys(closers);
|
|
17
|
+
for (var i = 0; i < keys.length; i++) {
|
|
18
|
+
if (keys[i] === name) continue;
|
|
19
|
+
try { closers[keys[i]](); } catch (error) { /* one panel must not block another */ }
|
|
20
|
+
}
|
|
21
|
+
return nextRevision;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function storeSet(name, value) {
|
|
25
|
+
store.set({ rightWorkbenchOwner: name, rightWorkbenchRevision: value });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function releaseRightWorkbench(name) {
|
|
29
|
+
if (store.get("rightWorkbenchOwner") !== name) return;
|
|
30
|
+
storeSet(null, (store.get("rightWorkbenchRevision") || 0) + 1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function isRightWorkbenchCurrent(name, expectedRevision) {
|
|
34
|
+
return store.get("rightWorkbenchOwner") === name && store.get("rightWorkbenchRevision") === expectedRevision;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function resetRightWorkbench() {
|
|
38
|
+
storeSet(null, (store.get("rightWorkbenchRevision") || 0) + 1);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
store.subscribe(function (state, previous) {
|
|
42
|
+
if (state.currentSlug === previous.currentSlug && state.myUserId === previous.myUserId) return;
|
|
43
|
+
resetRightWorkbench();
|
|
44
|
+
});
|