clay-server 2.31.0 → 2.32.0-beta.2

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