clay-server 2.31.0 → 2.32.0-beta.10

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.
Files changed (82) hide show
  1. package/lib/browser-mcp-server.js +32 -44
  2. package/lib/codex-defaults.js +18 -0
  3. package/lib/debate-mcp-server.js +14 -31
  4. package/lib/mcp-local.js +31 -1
  5. package/lib/project-connection.js +9 -6
  6. package/lib/project-debate.js +8 -0
  7. package/lib/project-filesystem.js +47 -1
  8. package/lib/project-http.js +75 -8
  9. package/lib/project-mate-interaction.js +102 -16
  10. package/lib/project-mcp.js +4 -0
  11. package/lib/project-notifications.js +9 -0
  12. package/lib/project-sessions.js +94 -51
  13. package/lib/project-user-message.js +12 -7
  14. package/lib/project.js +234 -99
  15. package/lib/public/app.js +135 -454
  16. package/lib/public/codex-avatar.png +0 -0
  17. package/lib/public/css/debate.css +3 -2
  18. package/lib/public/css/filebrowser.css +91 -1
  19. package/lib/public/css/icon-strip.css +21 -5
  20. package/lib/public/css/input.css +338 -104
  21. package/lib/public/css/mates.css +43 -0
  22. package/lib/public/css/mention.css +48 -4
  23. package/lib/public/css/menus.css +1 -1
  24. package/lib/public/css/messages.css +2 -0
  25. package/lib/public/css/notifications-center.css +26 -0
  26. package/lib/public/css/tooltip.css +47 -0
  27. package/lib/public/index.html +78 -26
  28. package/lib/public/modules/app-connection.js +138 -37
  29. package/lib/public/modules/app-cursors.js +18 -17
  30. package/lib/public/modules/app-debate-ui.js +9 -9
  31. package/lib/public/modules/app-dm.js +175 -131
  32. package/lib/public/modules/app-favicon.js +28 -26
  33. package/lib/public/modules/app-header.js +79 -68
  34. package/lib/public/modules/app-home-hub.js +55 -47
  35. package/lib/public/modules/app-loop-ui.js +34 -18
  36. package/lib/public/modules/app-loop-wizard.js +6 -6
  37. package/lib/public/modules/app-messages.js +199 -153
  38. package/lib/public/modules/app-misc.js +23 -12
  39. package/lib/public/modules/app-notifications.js +119 -9
  40. package/lib/public/modules/app-panels.js +203 -49
  41. package/lib/public/modules/app-projects.js +161 -150
  42. package/lib/public/modules/app-rate-limit.js +5 -4
  43. package/lib/public/modules/app-rendering.js +149 -101
  44. package/lib/public/modules/app-skills-install.js +4 -4
  45. package/lib/public/modules/context-sources.js +102 -66
  46. package/lib/public/modules/dom-refs.js +21 -0
  47. package/lib/public/modules/filebrowser.js +173 -2
  48. package/lib/public/modules/input.js +122 -0
  49. package/lib/public/modules/markdown.js +5 -1
  50. package/lib/public/modules/mate-sidebar.js +38 -0
  51. package/lib/public/modules/mention.js +24 -6
  52. package/lib/public/modules/scheduler.js +1 -1
  53. package/lib/public/modules/sidebar-mates.js +79 -35
  54. package/lib/public/modules/sidebar-mobile.js +34 -30
  55. package/lib/public/modules/sidebar-projects.js +60 -57
  56. package/lib/public/modules/sidebar-sessions.js +75 -69
  57. package/lib/public/modules/sidebar.js +12 -20
  58. package/lib/public/modules/skills.js +8 -9
  59. package/lib/public/modules/sticky-notes.js +1 -2
  60. package/lib/public/modules/store.js +9 -2
  61. package/lib/public/modules/stt.js +4 -1
  62. package/lib/public/modules/terminal.js +12 -0
  63. package/lib/public/modules/tools.js +18 -13
  64. package/lib/public/modules/tooltip.js +32 -5
  65. package/lib/sdk-bridge.js +562 -1114
  66. package/lib/sdk-message-processor.js +150 -135
  67. package/lib/sdk-worker.js +4 -0
  68. package/lib/server-dm.js +1 -0
  69. package/lib/server.js +86 -1
  70. package/lib/sessions.js +81 -37
  71. package/lib/ws-schema.js +2 -0
  72. package/lib/yoke/adapters/claude-worker.js +559 -0
  73. package/lib/yoke/adapters/claude.js +1483 -0
  74. package/lib/yoke/adapters/codex.js +1121 -0
  75. package/lib/yoke/adapters/gemini.js +709 -0
  76. package/lib/yoke/codex-app-server.js +307 -0
  77. package/lib/yoke/index.js +199 -0
  78. package/lib/yoke/instructions.js +62 -0
  79. package/lib/yoke/interface.js +98 -0
  80. package/lib/yoke/mcp-bridge-server.js +294 -0
  81. package/lib/yoke/package.json +7 -0
  82. package/package.json +3 -1
@@ -1,11 +1,36 @@
1
1
  // app-projects.js - Project list, switching, add/remove project modals
2
2
  // Extracted from app.js (PR-29)
3
3
 
4
- import { escapeHtml } from './utils.js';
4
+ import { escapeHtml, showToast } from './utils.js';
5
5
  import { refreshIcons } from './icons.js';
6
6
  import { parseEmojis } from './markdown.js';
7
-
8
- var _ctx = null;
7
+ import { store } from './store.js';
8
+ import { getWs, setWs } from './ws-ref.js';
9
+ import { getMessagesEl, getStatusDot } from './dom-refs.js';
10
+ import { userAvatarUrl } from './avatar.js';
11
+ import { showConfirm } from './app-misc.js';
12
+ // renderUserStrip is now reactive via store subscriber in sidebar-mates.js
13
+ import { renderIconStrip } from './sidebar-projects.js';
14
+ import { updateCrossProjectBlink, stopUrgentBlink, setActivity } from './app-favicon.js';
15
+ import { spawnDustParticles } from './sidebar.js';
16
+ import { isSearchOpen, closeSearch } from './session-search.js';
17
+ import { exitDmMode } from './app-dm.js';
18
+ import { isHomeHubVisible, hideHomeHub, showHomeHub } from './app-home-hub.js';
19
+ import { resetFileBrowser } from './filebrowser.js';
20
+ import { closeArchive } from './sticky-notes.js';
21
+ import { hideMemory } from './mate-memory.js';
22
+ import { isSchedulerOpen, closeScheduler, resetScheduler } from './scheduler.js';
23
+ import { connect, cancelReconnect, setStatus } from './app-connection.js';
24
+ import { setTurnCounter, setPrependAnchor, setActivityEl, setIsUserScrolledUp, hideSuggestionChips } from './app-rendering.js';
25
+ import { resetToolState, enableMainInput, resetTurnMetaCost } from './tools.js';
26
+ import { clearPendingImages } from './input.js';
27
+ import { clearAllMentionActive } from './sidebar-mates.js';
28
+ import { setRewindMode } from './rewind.js';
29
+ import { resetUsage, resetContext } from './app-panels.js';
30
+ import { resetRateLimitState } from './app-rate-limit.js';
31
+ import { closeSessionInfoPopover } from './app-header.js';
32
+ import { resetDebateState } from './debate.js';
33
+ import { removeDebateBottomBar } from './app-debate-ui.js';
9
34
 
10
35
  // --- Module-owned state ---
11
36
  var cachedProjects = [];
@@ -33,9 +58,7 @@ var addProjectDebounce = null;
33
58
  var addProjectActiveIdx = -1;
34
59
  var addProjectMode = "existing";
35
60
 
36
- export function initProjects(ctx) {
37
- _ctx = ctx;
38
-
61
+ export function initProjects() {
39
62
  // Init add-project modal DOM refs
40
63
  addProjectModal = document.getElementById("add-project-modal");
41
64
  addProjectInput = document.getElementById("add-project-input");
@@ -160,7 +183,7 @@ export function initProjects(ctx) {
160
183
  });
161
184
 
162
185
  // Project list add button
163
- var projectListAddBtn = _ctx.$("project-list-add");
186
+ var projectListAddBtn = document.getElementById("project-list-add");
164
187
  if (projectListAddBtn) {
165
188
  projectListAddBtn.addEventListener("click", function () {
166
189
  openAddProjectModal();
@@ -197,12 +220,13 @@ export function updateProjectList(msg) {
197
220
  else cachedRemovedProjects = [];
198
221
 
199
222
  // Only re-render project strip + title bar if data or active slug changed
200
- var slugChanged = _ctx.currentSlug !== _lastRenderedSlug;
223
+ var currentSlug = store.get('currentSlug');
224
+ var slugChanged = currentSlug !== _lastRenderedSlug;
201
225
  if (projectsChanged || slugChanged) {
202
- _lastRenderedSlug = _ctx.currentSlug;
226
+ _lastRenderedSlug = currentSlug;
203
227
  var count = cachedProjectCount || 0;
204
228
  renderProjectList();
205
- var projectHint = _ctx.$("project-hint");
229
+ var projectHint = document.getElementById("project-hint");
206
230
  if (count === 1 && projectHint) {
207
231
  try {
208
232
  if (!localStorage.getItem("clay-project-hint-dismissed")) {
@@ -217,40 +241,35 @@ export function updateProjectList(msg) {
217
241
  // Update topbar with server-wide presence (renderTopbarPresence has its own guard)
218
242
  if (msg.serverUsers) {
219
243
  var newOnlineIds = msg.serverUsers.map(function (u) { return u.id; });
220
- var prevOnlineIds = _ctx.cachedOnlineIds || [];
221
- _ctx.setCachedOnlineIds(newOnlineIds);
244
+ var prevOnlineIds = store.get('cachedOnlineIds') || [];
245
+ store.set({ cachedOnlineIds: newOnlineIds });
222
246
  renderTopbarPresence(msg.serverUsers);
223
- // Only re-render user strip if online IDs actually changed
224
- if (!msg.allUsers && _ctx.cachedAllUsers.length > 0) {
225
- var onlineChanged = newOnlineIds.length !== prevOnlineIds.length || newOnlineIds.some(function (id, i) { return id !== prevOnlineIds[i]; });
226
- if (onlineChanged) {
227
- _ctx.renderUserStrip(_ctx.cachedAllUsers, newOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
228
- }
229
- }
247
+ // renderUserStrip is handled by the store subscriber (fingerprint-guarded)
230
248
  }
231
249
 
232
250
  // Update user strip (DM targets) - renderUserStrip has its own fingerprint guard
233
251
  if (msg.allUsers) {
234
- _ctx.setCachedAllUsers(msg.allUsers);
235
- if (msg.dmFavorites) _ctx.setCachedDmFavorites(msg.dmFavorites);
236
- if (msg.dmConversations) _ctx.setCachedDmConversations(msg.dmConversations);
237
- _ctx.renderUserStrip(msg.allUsers, _ctx.cachedOnlineIds, _ctx.myUserId, _ctx.cachedDmFavorites, _ctx.cachedDmConversations, _ctx.dmUnread, _ctx.dmRemovedUsers, _ctx.cachedMatesList);
252
+ store.set({ cachedAllUsers: msg.allUsers });
253
+ if (msg.dmFavorites) store.set({ cachedDmFavorites: msg.dmFavorites });
254
+ if (msg.dmConversations) store.set({ cachedDmConversations: msg.dmConversations });
255
+ // renderUserStrip is handled by the store subscriber
256
+ var st2 = store.snap();
238
257
  if (document.body.classList.contains("mate-dm-active") || document.body.classList.contains("wide-view")) {
239
- var refreshedMyUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
258
+ var refreshedMyUser = st2.cachedAllUsers.find(function (u) { return u.id === st2.myUserId; });
240
259
  if (refreshedMyUser) {
241
260
  document.body.dataset.myDisplayName = refreshedMyUser.displayName || refreshedMyUser.username || "";
242
- document.body.dataset.myAvatarUrl = _ctx.userAvatarUrl(refreshedMyUser, 36);
261
+ document.body.dataset.myAvatarUrl = userAvatarUrl(refreshedMyUser, 36);
243
262
  try { localStorage.setItem("clay_my_user", JSON.stringify({ displayName: refreshedMyUser.displayName, username: refreshedMyUser.username, avatarStyle: refreshedMyUser.avatarStyle, avatarSeed: refreshedMyUser.avatarSeed, avatarCustom: refreshedMyUser.avatarCustom })); } catch(e) {}
244
263
  }
245
264
  }
246
265
  // Render my avatar (always present, hidden behind user-island)
247
266
  var meEl = document.getElementById("icon-strip-me");
248
267
  if (meEl && !meEl.hasChildNodes()) {
249
- var myUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
268
+ var myUser = st2.cachedAllUsers.find(function (u) { return u.id === st2.myUserId; });
250
269
  if (myUser) {
251
270
  var meAvatar = document.createElement("img");
252
271
  meAvatar.className = "icon-strip-me-avatar";
253
- meAvatar.src = _ctx.userAvatarUrl(myUser, 34);
272
+ meAvatar.src = userAvatarUrl(myUser, 34);
254
273
  meEl.appendChild(meAvatar);
255
274
  }
256
275
  }
@@ -273,7 +292,7 @@ export function renderTopbarPresence(serverUsers) {
273
292
  var cu = serverUsers[cui];
274
293
  var cuImg = document.createElement("img");
275
294
  cuImg.className = "client-avatar";
276
- cuImg.src = _ctx.userAvatarUrl(cu, 24);
295
+ cuImg.src = userAvatarUrl(cu, 24);
277
296
  cuImg.alt = cu.displayName;
278
297
  cuImg.dataset.tip = cu.displayName + " (@" + cu.username + ")";
279
298
  if (cui > 0) cuImg.style.marginLeft = "-6px";
@@ -304,12 +323,13 @@ export function renderProjectList() {
304
323
  worktreeAccessible: p.worktreeAccessible !== undefined ? p.worktreeAccessible : true,
305
324
  };
306
325
  });
307
- var iconStripActiveSlug = (_ctx.mateProjectSlug && _ctx.savedMainSlug) ? _ctx.savedMainSlug : _ctx.currentSlug;
308
- _ctx.renderIconStrip(iconStripProjects, iconStripActiveSlug);
326
+ var st = store.snap();
327
+ var iconStripActiveSlug = (st.mateProjectSlug && st.savedMainSlug) ? st.savedMainSlug : st.currentSlug;
328
+ renderIconStrip(iconStripProjects, iconStripActiveSlug);
309
329
  // Update title bar project name and icon if it changed
310
- if (!_ctx.mateProjectSlug) {
330
+ if (!st.mateProjectSlug) {
311
331
  for (var pi = 0; pi < cachedProjects.length; pi++) {
312
- if (cachedProjects[pi].slug === _ctx.currentSlug) {
332
+ if (cachedProjects[pi].slug === st.currentSlug) {
313
333
  var updatedName = cachedProjects[pi].title || cachedProjects[pi].project;
314
334
  var tbName = document.getElementById("title-bar-project-name");
315
335
  if (tbName && updatedName) tbName.textContent = updatedName;
@@ -320,11 +340,11 @@ export function renderProjectList() {
320
340
  tbIcon.textContent = pIcon;
321
341
  parseEmojis(tbIcon);
322
342
  tbIcon.classList.add("has-icon");
323
- try { localStorage.setItem("clay-project-icon-" + (_ctx.currentSlug || "default"), pIcon); } catch (e) {}
343
+ try { localStorage.setItem("clay-project-icon-" + (st.currentSlug || "default"), pIcon); } catch (e) {}
324
344
  } else {
325
345
  tbIcon.textContent = "";
326
346
  tbIcon.classList.remove("has-icon");
327
- try { localStorage.removeItem("clay-project-icon-" + (_ctx.currentSlug || "default")); } catch (e) {}
347
+ try { localStorage.removeItem("clay-project-icon-" + (st.currentSlug || "default")); } catch (e) {}
328
348
  }
329
349
  }
330
350
  break;
@@ -332,49 +352,51 @@ export function renderProjectList() {
332
352
  }
333
353
  }
334
354
  // Re-apply current socket status to the active icon's dot
335
- var dot = _ctx.getStatusDot();
355
+ var dot = getStatusDot();
336
356
  if (dot) {
337
- if (_ctx.connected && _ctx.processing) { dot.classList.add("connected"); dot.classList.add("processing"); }
338
- else if (_ctx.connected) { dot.classList.add("connected"); }
357
+ if (st.connected && st.processing) { dot.classList.add("connected"); dot.classList.add("processing"); }
358
+ else if (st.connected) { dot.classList.add("connected"); }
339
359
  }
340
- _ctx.updateCrossProjectBlink();
360
+ updateCrossProjectBlink();
341
361
  }
342
362
 
343
363
  export function resetClientState() {
344
- _ctx.closeSearch();
345
- _ctx.messagesEl.innerHTML = "";
346
- _ctx.setCurrentMsgEl(null);
347
- _ctx.setCurrentFullText("");
348
- _ctx.resetToolState();
349
- _ctx.clearPendingImages();
350
- _ctx.setActivityEl(null);
351
- _ctx.setProcessing(false);
352
- _ctx.setTurnCounter(0);
353
- _ctx.setMessageUuidMap([]);
354
- _ctx.setHistoryFrom(0);
355
- _ctx.setHistoryTotal(0);
356
- _ctx.setPrependAnchor(null);
357
- _ctx.setLoadingMore(false);
358
- _ctx.setIsUserScrolledUp(false);
359
- _ctx.newMsgBtn.classList.add("hidden");
360
- _ctx.setRewindMode(false);
361
- _ctx.setActivity(null);
362
- _ctx.setStatus("connected");
363
- if (!_ctx.loopActive) _ctx.enableMainInput();
364
- _ctx.resetUsage();
365
- _ctx.resetTurnMetaCost();
366
- _ctx.resetContext();
367
- _ctx.resetRateLimitState();
368
- if (_ctx.getHeaderContextEl()) { _ctx.getHeaderContextEl().remove(); _ctx.setHeaderContextEl(null); }
369
- _ctx.hideSuggestionChips();
370
- _ctx.closeSessionInfoPopover();
371
- _ctx.stopUrgentBlink();
364
+ if (isSearchOpen()) closeSearch();
365
+ getMessagesEl().innerHTML = "";
366
+ store.set({ currentMsgEl: null });
367
+ store.set({ currentFullText: "" });
368
+ resetToolState();
369
+ clearPendingImages();
370
+ clearAllMentionActive();
371
+ setActivityEl(null);
372
+ store.set({ processing: false });
373
+ setTurnCounter(0);
374
+ store.set({ messageUuidMap: [] });
375
+ store.set({ historyFrom: 0 });
376
+ store.set({ historyTotal: 0 });
377
+ setPrependAnchor(null);
378
+ store.set({ loadingMore: false });
379
+ setIsUserScrolledUp(false);
380
+ document.getElementById("new-msg-btn").classList.add("hidden");
381
+ setRewindMode(false);
382
+ setActivity(null);
383
+ setStatus("connected");
384
+ if (!store.get('loopActive')) enableMainInput();
385
+ resetUsage();
386
+ resetTurnMetaCost();
387
+ resetContext();
388
+ resetRateLimitState();
389
+ var headerCtx = store.get('headerContextEl');
390
+ if (headerCtx) { headerCtx.remove(); store.set({ headerContextEl: null }); }
391
+ hideSuggestionChips();
392
+ closeSessionInfoPopover();
393
+ stopUrgentBlink();
372
394
  // Clear debate UI and state from previous session
373
- _ctx.setDebateStickyState(null);
374
- _ctx.resetDebateState();
395
+ store.set({ debateStickyState: null });
396
+ resetDebateState();
375
397
  var debateBadges = document.querySelectorAll(".debate-header-badge");
376
398
  for (var dbi = 0; dbi < debateBadges.length; dbi++) debateBadges[dbi].remove();
377
- _ctx.removeDebateBottomBar();
399
+ removeDebateBottomBar();
378
400
  var handBar = document.getElementById("debate-hand-raise-bar");
379
401
  if (handBar) handBar.remove();
380
402
  var debateSticky = document.getElementById("debate-sticky");
@@ -385,63 +407,41 @@ export function resetClientState() {
385
407
 
386
408
  export function switchProject(slug) {
387
409
  if (!slug) return;
388
- var wasDm = _ctx.dmMode;
389
- var wasMate = _ctx.dmMode && _ctx.dmTargetUser && _ctx.dmTargetUser.isMate;
390
- if (_ctx.dmMode) _ctx.exitDmMode(wasMate);
391
- if (_ctx.isHomeHubVisible()) {
392
- _ctx.hideHomeHub();
393
- if (slug === _ctx.currentSlug) return;
394
- }
395
- if (slug === _ctx.currentSlug) {
396
- if (wasDm && _ctx.getWs() && _ctx.getWs().readyState === 1) {
397
- _ctx.getWs().send(JSON.stringify({ type: "switch_session", id: _ctx.activeSessionId }));
410
+ var st = store.snap();
411
+ var wasDm = st.dmMode;
412
+ var wasMate = st.dmMode && st.dmTargetUser && st.dmTargetUser.isMate;
413
+ if (st.dmMode) exitDmMode(wasMate);
414
+ if (isHomeHubVisible()) {
415
+ hideHomeHub();
416
+ if (slug === store.get('currentSlug')) return;
417
+ }
418
+ if (slug === store.get('currentSlug')) {
419
+ var ws = getWs();
420
+ if (wasDm && ws && ws.readyState === 1) {
421
+ ws.send(JSON.stringify({ type: "switch_session", id: store.get('activeSessionId') }));
398
422
  }
399
423
  return;
400
424
  }
401
- _ctx.resetFileBrowser();
402
- _ctx.closeArchive();
403
- _ctx.hideMemory();
404
- if (_ctx.isSchedulerOpen()) _ctx.closeScheduler();
405
- _ctx.resetScheduler(slug);
406
- _ctx.setCurrentSlug(slug);
407
- _ctx.setBasePath("/p/" + slug + "/");
408
- _ctx.setWsPath("/p/" + slug + "/ws");
425
+ resetFileBrowser();
426
+ closeArchive();
427
+ hideMemory();
428
+ if (isSchedulerOpen()) closeScheduler();
429
+ resetScheduler(slug);
430
+ store.set({ currentSlug: slug });
431
+ store.set({ basePath: "/p/" + slug + "/" });
432
+ store.set({ wsPath: "/p/" + slug + "/ws" });
409
433
  if (document.documentElement.classList.contains("pwa-standalone")) {
410
434
  history.replaceState(null, "", "/p/" + slug + "/");
411
435
  } else {
412
436
  history.pushState(null, "", "/p/" + slug + "/");
413
437
  }
414
438
  resetClientState();
415
- _ctx.connect();
439
+ connect();
416
440
  }
417
441
 
418
442
  export function showUpdateAvailable(msg) {
419
- var $ = _ctx.$;
420
- var updatePillWrap = $("update-pill-wrap");
421
- var updateVersion = $("update-version");
422
- if (updatePillWrap && updateVersion && msg.version) {
423
- updateVersion.textContent = "v" + msg.version;
424
- updatePillWrap.classList.remove("hidden");
425
- var updPill = $("update-pill");
426
- var updResetBtn = $("update-now");
427
- if (_ctx.isHeadlessMode) {
428
- if (updPill) updPill.innerHTML = '<i data-lucide="arrow-up-circle"></i> <span id="update-version">v' + msg.version + '</span> available. Update manually';
429
- if (updResetBtn) updResetBtn.style.display = "none";
430
- } else {
431
- if (updResetBtn) {
432
- updResetBtn.innerHTML = '<i data-lucide="download"></i> Update now';
433
- updResetBtn.disabled = false;
434
- updResetBtn.style.display = "";
435
- }
436
- }
437
- var updManualCmd = $("update-manual-cmd");
438
- if (updManualCmd) {
439
- var updTag = msg.version.indexOf("-beta") !== -1 ? "beta" : "latest";
440
- updManualCmd.textContent = "npx clay-server@" + updTag;
441
- }
442
- refreshIcons();
443
- }
444
- var settingsUpdBtn = $("settings-update-check");
443
+ // Update the settings panel button only (top bar pill replaced by notification center)
444
+ var settingsUpdBtn = document.getElementById("settings-update-check");
445
445
  if (settingsUpdBtn && msg.version) {
446
446
  settingsUpdBtn.innerHTML = "";
447
447
  var ic = document.createElement("i");
@@ -459,8 +459,9 @@ export function showUpdateAvailable(msg) {
459
459
  export function confirmRemoveProject(slug, name) {
460
460
  pendingRemoveSlug = slug;
461
461
  pendingRemoveName = name;
462
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
463
- _ctx.getWs().send(JSON.stringify({ type: "remove_project_check", slug: slug }));
462
+ var ws = getWs();
463
+ if (ws && ws.readyState === 1) {
464
+ ws.send(JSON.stringify({ type: "remove_project_check", slug: slug }));
464
465
  }
465
466
  }
466
467
 
@@ -476,15 +477,16 @@ export function handleRemoveProjectCheckResult(msg) {
476
477
  var confirmMsg = isWt
477
478
  ? 'Delete worktree "' + name + '"? The branch and working directory will be removed from disk.'
478
479
  : 'Remove "' + name + '"? You can re-add it later.';
479
- _ctx.showConfirm(confirmMsg, function () {
480
+ showConfirm(confirmMsg, function () {
480
481
  var iconEl = document.querySelector('.icon-strip-item[data-slug="' + slug + '"]');
481
482
  if (iconEl) {
482
483
  var rect = iconEl.getBoundingClientRect();
483
- _ctx.spawnDustParticles(rect.left + rect.width / 2, rect.top + rect.height / 2);
484
+ spawnDustParticles(rect.left + rect.width / 2, rect.top + rect.height / 2);
484
485
  }
485
486
  setTimeout(function () {
486
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
487
- _ctx.getWs().send(JSON.stringify({ type: "remove_project", slug: slug }));
487
+ var ws = getWs();
488
+ if (ws && ws.readyState === 1) {
489
+ ws.send(JSON.stringify({ type: "remove_project", slug: slug }));
488
490
  }
489
491
  }, 1000);
490
492
  }, "Remove", true);
@@ -533,16 +535,18 @@ function showRemoveProjectTaskDialog(slug, name, taskCount) {
533
535
  if (moveBtn) {
534
536
  moveBtn.addEventListener("click", function () {
535
537
  var targetSlug = selectEl ? selectEl.value : null;
536
- if (_ctx.getWs() && _ctx.getWs().readyState === 1 && targetSlug) {
537
- _ctx.getWs().send(JSON.stringify({ type: "remove_project", slug: slug, moveTasksTo: targetSlug }));
538
+ var ws = getWs();
539
+ if (ws && ws.readyState === 1 && targetSlug) {
540
+ ws.send(JSON.stringify({ type: "remove_project", slug: slug, moveTasksTo: targetSlug }));
538
541
  }
539
542
  close();
540
543
  });
541
544
  }
542
545
 
543
546
  deleteBtn.addEventListener("click", function () {
544
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
545
- _ctx.getWs().send(JSON.stringify({ type: "remove_project", slug: slug }));
547
+ var ws = getWs();
548
+ if (ws && ws.readyState === 1) {
549
+ ws.send(JSON.stringify({ type: "remove_project", slug: slug }));
546
550
  }
547
551
  close();
548
552
  });
@@ -550,18 +554,19 @@ function showRemoveProjectTaskDialog(slug, name, taskCount) {
550
554
 
551
555
  export function handleRemoveProjectResult(msg) {
552
556
  if (msg.ok) {
553
- if (msg.slug === _ctx.currentSlug) {
557
+ var currentSlug = store.get('currentSlug');
558
+ if (msg.slug === currentSlug) {
554
559
  var isWorktree = msg.slug.indexOf("--") !== -1;
555
560
  var parentSlug = isWorktree ? msg.slug.split("--")[0] : null;
556
561
 
557
- _ctx.showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
562
+ showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
558
563
 
559
564
  // Suppress disconnect overlay and reconnect by detaching the WS
560
- var ws = _ctx.getWs();
561
- if (ws) { ws.onclose = null; ws.onerror = null; ws.close(); _ctx.setWs(null); }
562
- _ctx.cancelReconnect();
563
- _ctx.setConnected(false);
564
- _ctx.connectOverlay.classList.add("hidden");
565
+ var ws = getWs();
566
+ if (ws) { ws.onclose = null; ws.onerror = null; ws.close(); setWs(null); }
567
+ cancelReconnect();
568
+ store.set({ connected: false });
569
+ document.getElementById("connect-overlay").classList.add("hidden");
565
570
  if (!isWorktree) {
566
571
  var removedProj = null;
567
572
  for (var ri = 0; ri < cachedProjects.length; ri++) {
@@ -578,20 +583,20 @@ export function handleRemoveProjectResult(msg) {
578
583
  }
579
584
  cachedProjects = cachedProjects.filter(function (p) { return p.slug !== msg.slug; });
580
585
  cachedProjectCount = cachedProjects.length;
581
- _ctx.setCurrentSlug(null);
586
+ store.set({ currentSlug: null });
582
587
  renderProjectList();
583
588
  resetClientState();
584
589
 
585
590
  if (parentSlug) {
586
591
  switchProject(parentSlug);
587
592
  } else {
588
- _ctx.showHomeHub();
593
+ showHomeHub();
589
594
  }
590
595
  } else {
591
- _ctx.showToast(msg.slug.indexOf("--") !== -1 ? "Worktree removed" : "Project removed", "success");
596
+ showToast(msg.slug.indexOf("--") !== -1 ? "Worktree removed" : "Project removed", "success");
592
597
  }
593
598
  } else {
594
- _ctx.showToast(msg.error || "Failed to remove project", "error");
599
+ showToast(msg.error || "Failed to remove project", "error");
595
600
  }
596
601
  }
597
602
 
@@ -647,9 +652,10 @@ export function openAddProjectModal() {
647
652
  addProjectActiveIdx = -1;
648
653
  addProjectOk.disabled = false;
649
654
  var existingBtn = addProjectModal.querySelector('.add-project-mode-btn[data-mode="existing"]');
650
- if (_ctx.isOsUsers) {
655
+ var st = store.snap();
656
+ if (st.isOsUsers) {
651
657
  existingBtn.disabled = false;
652
- var myUser = _ctx.cachedAllUsers.find(function (u) { return u.id === _ctx.myUserId; });
658
+ var myUser = st.cachedAllUsers.find(function (u) { return u.id === st.myUserId; });
653
659
  var isAdmin = myUser && myUser.role === "admin";
654
660
  if (!isAdmin && myUser && myUser.linuxUser) {
655
661
  // Non-admin: lock prefix to home directory
@@ -692,14 +698,15 @@ function renderRemovedProjectsList() {
692
698
  item.dataset.path = rp.path;
693
699
  item.addEventListener("click", function () {
694
700
  var p = this.dataset.path;
695
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
696
- _ctx.getWs().send(JSON.stringify({ type: "add_project", path: p }));
701
+ var ws = getWs();
702
+ if (ws && ws.readyState === 1) {
703
+ ws.send(JSON.stringify({ type: "add_project", path: p }));
697
704
  }
698
705
  closeAddProjectModal();
699
706
  });
700
707
  var iconEl = document.createElement("span");
701
708
  iconEl.className = "add-project-removed-icon";
702
- iconEl.textContent = rp.icon || "📁";
709
+ iconEl.textContent = rp.icon || "\uD83D\uDCC1";
703
710
  item.appendChild(iconEl);
704
711
  var info = document.createElement("div");
705
712
  info.className = "add-project-removed-info";
@@ -744,8 +751,9 @@ function stripPrefix(fullPath) {
744
751
  }
745
752
 
746
753
  function requestBrowseDir(val) {
747
- if (!_ctx.getWs() || _ctx.getWs().readyState !== 1) return;
748
- _ctx.getWs().send(JSON.stringify({ type: "browse_dir", path: getFullPath(val) }));
754
+ var ws = getWs();
755
+ if (!ws || ws.readyState !== 1) return;
756
+ ws.send(JSON.stringify({ type: "browse_dir", path: getFullPath(val) }));
749
757
  }
750
758
 
751
759
  export function handleBrowseDirResult(msg) {
@@ -785,10 +793,10 @@ export function handleAddProjectResult(msg) {
785
793
  if (msg.ok) {
786
794
  closeAddProjectModal();
787
795
  if (msg.existing) {
788
- _ctx.showToast("Project already registered", "info");
796
+ showToast("Project already registered", "info");
789
797
  } else {
790
798
  var toastMsg = addProjectMode === "create" ? "Project created" : addProjectMode === "clone" ? "Project cloned" : "Project added";
791
- _ctx.showToast(toastMsg, "success");
799
+ showToast(toastMsg, "success");
792
800
  if (msg.slug) {
793
801
  switchProject(msg.slug);
794
802
  }
@@ -826,20 +834,23 @@ function submitAddProject() {
826
834
  if (addProjectMode === "existing") {
827
835
  var val = getFullPath(addProjectInput.value).replace(/\/+$/, "");
828
836
  if (!val) { addProjectOk.disabled = false; return; }
829
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
830
- _ctx.getWs().send(JSON.stringify({ type: "add_project", path: val }));
837
+ var ws = getWs();
838
+ if (ws && ws.readyState === 1) {
839
+ ws.send(JSON.stringify({ type: "add_project", path: val }));
831
840
  }
832
841
  } else if (addProjectMode === "create") {
833
842
  var name = addProjectCreateInput.value.trim();
834
843
  if (!name) { addProjectOk.disabled = false; return; }
835
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
836
- _ctx.getWs().send(JSON.stringify({ type: "create_project", name: name }));
844
+ var ws2 = getWs();
845
+ if (ws2 && ws2.readyState === 1) {
846
+ ws2.send(JSON.stringify({ type: "create_project", name: name }));
837
847
  }
838
848
  } else if (addProjectMode === "clone") {
839
849
  var url = addProjectCloneInput.value.trim();
840
850
  if (!url) { addProjectOk.disabled = false; return; }
841
- if (_ctx.getWs() && _ctx.getWs().readyState === 1) {
842
- _ctx.getWs().send(JSON.stringify({ type: "clone_project", url: url }));
851
+ var ws3 = getWs();
852
+ if (ws3 && ws3.readyState === 1) {
853
+ ws3.send(JSON.stringify({ type: "clone_project", url: url }));
843
854
  }
844
855
  }
845
856
  }
@@ -210,10 +210,11 @@ export function updateRateLimitUsage(msg) {
210
210
  rateLimitUsageEl = document.createElement("a");
211
211
  rateLimitUsageEl.id = "rate-limit-usage-link";
212
212
  rateLimitUsageEl.className = "top-bar-pill pill-dim usage-check-link";
213
- rateLimitUsageEl.href = "https://claude.ai/settings/usage";
213
+ var vendor = store.get('currentVendor') || "claude";
214
+ rateLimitUsageEl.href = vendor === "codex" ? "https://chatgpt.com/admin/usage" : "https://claude.ai/settings/usage";
214
215
  rateLimitUsageEl.target = "_blank";
215
216
  rateLimitUsageEl.rel = "noopener";
216
- rateLimitUsageEl.title = "Check usage on claude.ai";
217
+ rateLimitUsageEl.title = vendor === "codex" ? "Check usage on ChatGPT" : "Check usage on claude.ai";
217
218
  var ref = document.getElementById("skip-perms-pill");
218
219
  topBarActions.insertBefore(rateLimitUsageEl, ref);
219
220
  }
@@ -254,13 +255,13 @@ export function addScheduledMessageBubble(text, resetsAt) {
254
255
 
255
256
  if (isChannel) {
256
257
  // Channel mode: avatar + header with scheduled badge + message
257
- var _me = store.getState().cachedAllUsers.find(function (u) { return u.id === store.getState().myUserId; });
258
+ var _me = store.get('cachedAllUsers').find(function (u) { return u.id === store.get('myUserId'); });
258
259
  if (!_me) { try { _me = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {} }
259
260
  var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
260
261
 
261
262
  var avi = document.createElement("img");
262
263
  avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
263
- avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: store.getState().myUserId }, 36);
264
+ avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: store.get('myUserId') }, 36);
264
265
  wrap.appendChild(avi);
265
266
 
266
267
  var content = document.createElement("div");