clay-server 5.2.0-beta.10 → 5.2.0-beta.11

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 (98) hide show
  1. package/README.md +0 -4
  2. package/lib/clay-product-control-tools.js +1 -0
  3. package/lib/clay-product-control.js +14 -11
  4. package/lib/clay-vendor-usage.js +44 -0
  5. package/lib/github-star-count.js +147 -0
  6. package/lib/notes-checklist.js +43 -1
  7. package/lib/notes.js +2 -1
  8. package/lib/project-session-notes.js +33 -8
  9. package/lib/project-sessions.js +63 -24
  10. package/lib/project-start-protocol.js +29 -0
  11. package/lib/project-vendor-login.js +2 -0
  12. package/lib/project.js +3 -0
  13. package/lib/public/app.js +25 -17
  14. package/lib/public/css/contextual-clay.css +1 -1
  15. package/lib/public/css/filebrowser.css +1 -1
  16. package/lib/public/css/mate-loading-scene.css +21 -0
  17. package/lib/public/css/overlays.css +64 -256
  18. package/lib/public/css/project-mate-creation.css +5 -6
  19. package/lib/public/css/project-start.css +108 -0
  20. package/lib/public/css/projects-hub.css +53 -14
  21. package/lib/public/css/settings-shell.css +103 -0
  22. package/lib/public/css/sidebar.css +7 -0
  23. package/lib/public/css/title-bar.css +79 -0
  24. package/lib/public/index.html +50 -63
  25. package/lib/public/modules/admin-settings-invitations.js +2 -2
  26. package/lib/public/modules/admin-settings-projects.js +2 -2
  27. package/lib/public/modules/admin-settings-ui.js +20 -4
  28. package/lib/public/modules/admin-settings-users.js +4 -4
  29. package/lib/public/modules/app-home-hub.js +2 -1
  30. package/lib/public/modules/app-messages.js +10 -2
  31. package/lib/public/modules/app-projects.js +1 -407
  32. package/lib/public/modules/app-rate-limit.js +12 -1
  33. package/lib/public/modules/clay-tool-presentation.js +1 -0
  34. package/lib/public/modules/contextual-clay.js +3 -1
  35. package/lib/public/modules/github-star.js +66 -0
  36. package/lib/public/modules/mate-loading-scene.js +17 -0
  37. package/lib/public/modules/profile.js +11 -3
  38. package/lib/public/modules/project-mate-creation-workspace.js +13 -4
  39. package/lib/public/modules/project-session-visibility-settings.js +19 -16
  40. package/lib/public/modules/project-settings.js +119 -129
  41. package/lib/public/modules/project-start-dialog.js +130 -0
  42. package/lib/public/modules/project-start-folder-state.js +78 -0
  43. package/lib/public/modules/project-start-folder-view.js +190 -0
  44. package/lib/public/modules/project-start-forms.js +66 -0
  45. package/lib/public/modules/project-start-path.js +60 -0
  46. package/lib/public/modules/project-start-state.js +54 -0
  47. package/lib/public/modules/project-start-submit.js +67 -0
  48. package/lib/public/modules/projects-hub-view.js +33 -14
  49. package/lib/public/modules/projects-hub.js +10 -6
  50. package/lib/public/modules/server-settings.js +36 -91
  51. package/lib/public/modules/settings-project-target.js +56 -0
  52. package/lib/public/modules/settings-select.js +1 -1
  53. package/lib/public/modules/settings-shell-registry.js +88 -0
  54. package/lib/public/modules/settings-shell-view.js +104 -0
  55. package/lib/public/modules/settings-shell.js +342 -0
  56. package/lib/public/modules/sidebar-mobile.js +8 -30
  57. package/lib/public/modules/sidebar-projects.js +2 -1
  58. package/lib/public/modules/theme.js +10 -2
  59. package/lib/public/modules/user-settings-layout.js +8 -33
  60. package/lib/public/modules/user-settings-profile.js +1 -1
  61. package/lib/public/modules/user-settings.js +18 -85
  62. package/lib/public/modules/vendor-usage-format.js +93 -0
  63. package/lib/public/modules/vendor-usage-list.js +45 -0
  64. package/lib/public/modules/vendor-usage-model.js +215 -0
  65. package/lib/public/modules/vendor-usage-overview.js +312 -0
  66. package/lib/public/style.css +10 -7
  67. package/lib/public/welcome-art/conversation-desk.png +0 -0
  68. package/lib/public/welcome-art/mate-clay.png +0 -0
  69. package/lib/public/welcome-art/project-clay.png +0 -0
  70. package/lib/sdk-bridge.js +6 -1
  71. package/lib/sdk-message-processor.js +9 -1
  72. package/lib/sdk-quota-snapshot.js +20 -2
  73. package/lib/server-github-stars.js +39 -0
  74. package/lib/server-global-ws.js +21 -17
  75. package/lib/server.js +4 -0
  76. package/lib/session-notes-mcp-server.js +26 -4
  77. package/lib/session-notes-writes.js +88 -0
  78. package/lib/session-visibility.js +20 -8
  79. package/lib/sessions.js +2 -0
  80. package/lib/vendor-usage-acp-readers.js +97 -0
  81. package/lib/vendor-usage-capabilities.js +88 -0
  82. package/lib/vendor-usage-http-readers.js +214 -0
  83. package/lib/vendor-usage-metrics.js +47 -0
  84. package/lib/vendor-usage-native-readers.js +106 -0
  85. package/lib/vendor-usage-overview.js +406 -0
  86. package/lib/vendor-usage-service.js +281 -0
  87. package/lib/ws-schema.js +4 -2
  88. package/lib/yoke/adapters/claude.js +12 -0
  89. package/lib/yoke/adapters/codex-query-contract.js +1 -0
  90. package/lib/yoke/adapters/codex.js +45 -12
  91. package/lib/yoke/claude-hook-installer.js +1 -0
  92. package/lib/yoke/codex-dynamic-tool-names.js +80 -0
  93. package/lib/yoke/codex-model-catalog.js +37 -3
  94. package/lib/yoke/muse-services.js +16 -3
  95. package/lib/yoke/providers/claude-model-descriptions.js +22 -2
  96. package/lib/yoke/vendor-registry.js +6 -2
  97. package/package.json +2 -1
  98. package/lib/public/modules/server-settings-navigation.js +0 -39
@@ -4,9 +4,8 @@ import { checkComposerDeparture, saveSessionRichDraft } from './session-rich-dra
4
4
  // Extracted from app.js (PR-29)
5
5
 
6
6
  import { showProjectsHub, hideProjectsHub, completeProjectsOnboarding } from './projects-hub.js';
7
- import { escapeHtml, showToast } from './utils.js';
7
+ import { showToast } from './utils.js';
8
8
  import { refreshIcons } from './icons.js';
9
- import { parseEmojis } from './markdown.js';
10
9
  import { store } from './store.js';
11
10
  import { resetModelPickerState } from './model-picker.js';
12
11
  import { getWs, setWs } from './ws-ref.js';
@@ -50,157 +49,6 @@ var cachedRemovedProjects = [];
50
49
  var pendingRemoveSlug = null;
51
50
  var pendingRemoveName = null;
52
51
 
53
- // Add-project modal state
54
- var addProjectModal = null;
55
- var addProjectInput = null;
56
- var addProjectCreateInput = null;
57
- var addProjectCloneInput = null;
58
- var addProjectCloneProgress = null;
59
- var addProjectSuggestions = null;
60
- var addProjectError = null;
61
- var addProjectOk = null;
62
- var addProjectCancel = null;
63
- var addProjectModeBtns = null;
64
- var addProjectPanels = null;
65
- var addProjectRemoved = null;
66
- var addProjectPrefixEl = null;
67
- var addProjectPrefixValue = "";
68
- var addProjectDebounce = null;
69
- var addProjectActiveIdx = -1;
70
- var addProjectMode = "existing";
71
-
72
- export function initProjects() {
73
- // Init add-project modal DOM refs
74
- addProjectModal = document.getElementById("add-project-modal");
75
- addProjectInput = document.getElementById("add-project-input");
76
- addProjectCreateInput = document.getElementById("add-project-create-input");
77
- addProjectCloneInput = document.getElementById("add-project-clone-input");
78
- addProjectCloneProgress = document.getElementById("add-project-clone-progress");
79
- addProjectSuggestions = document.getElementById("add-project-suggestions");
80
- addProjectError = document.getElementById("add-project-error");
81
- addProjectOk = document.getElementById("add-project-ok");
82
- addProjectCancel = document.getElementById("add-project-cancel");
83
- addProjectModeBtns = addProjectModal.querySelectorAll(".add-project-mode-btn");
84
- addProjectPanels = addProjectModal.querySelectorAll(".add-project-panel");
85
- addProjectRemoved = document.getElementById("add-project-removed");
86
- addProjectPrefixEl = document.getElementById("add-project-prefix");
87
-
88
- // Mode button click listeners
89
- for (var mbi = 0; mbi < addProjectModeBtns.length; mbi++) {
90
- addProjectModeBtns[mbi].addEventListener("click", function () {
91
- if (this.disabled) return;
92
- switchAddProjectMode(this.dataset.mode);
93
- });
94
- }
95
-
96
- // Existing project input listeners
97
- addProjectInput.addEventListener("focus", function () {
98
- var val = addProjectInput.value;
99
- if ((val || addProjectPrefixValue) && addProjectSuggestions.children.length === 0) {
100
- requestBrowseDir(val);
101
- } else if (addProjectSuggestions.children.length > 0) {
102
- addProjectSuggestions.classList.remove("hidden");
103
- }
104
- });
105
-
106
- addProjectModal.querySelector(".confirm-dialog").addEventListener("click", function (e) {
107
- if (e.target === addProjectInput || addProjectInput.contains(e.target)) return;
108
- if (e.target === addProjectSuggestions || addProjectSuggestions.contains(e.target)) return;
109
- addProjectSuggestions.classList.add("hidden");
110
- addProjectActiveIdx = -1;
111
- });
112
-
113
- addProjectInput.addEventListener("input", function () {
114
- var val = addProjectInput.value;
115
- addProjectError.classList.add("hidden");
116
- if (addProjectDebounce) clearTimeout(addProjectDebounce);
117
- addProjectDebounce = setTimeout(function () {
118
- requestBrowseDir(val);
119
- }, 200);
120
- });
121
-
122
- addProjectInput.addEventListener("keydown", function (e) {
123
- var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
124
-
125
- if (e.key === "ArrowDown") {
126
- e.preventDefault();
127
- if (items.length > 0) {
128
- var next = addProjectActiveIdx < items.length - 1 ? addProjectActiveIdx + 1 : 0;
129
- setActiveIdx(next);
130
- }
131
- return;
132
- }
133
-
134
- if (e.key === "ArrowUp") {
135
- e.preventDefault();
136
- if (items.length > 0) {
137
- var prev = addProjectActiveIdx > 0 ? addProjectActiveIdx - 1 : items.length - 1;
138
- setActiveIdx(prev);
139
- }
140
- return;
141
- }
142
-
143
- if (e.key === "Tab") {
144
- e.preventDefault();
145
- var target = addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length
146
- ? items[addProjectActiveIdx]
147
- : items.length > 0 ? items[0] : null;
148
- if (target) {
149
- var fullP = target.dataset.path + "/";
150
- addProjectInput.value = stripPrefix(fullP);
151
- addProjectError.classList.add("hidden");
152
- requestBrowseDir(addProjectInput.value);
153
- }
154
- return;
155
- }
156
-
157
- if (e.key === "Enter") {
158
- e.preventDefault();
159
- if (addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length) {
160
- var fullPicked = items[addProjectActiveIdx].dataset.path + "/";
161
- addProjectInput.value = stripPrefix(fullPicked);
162
- addProjectError.classList.add("hidden");
163
- requestBrowseDir(addProjectInput.value);
164
- return;
165
- }
166
- submitAddProject();
167
- return;
168
- }
169
-
170
- if (e.key === "Escape") {
171
- e.preventDefault();
172
- closeAddProjectModal();
173
- return;
174
- }
175
- });
176
-
177
- // Enter key on create/clone inputs
178
- addProjectCreateInput.addEventListener("keydown", function (e) {
179
- if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
180
- if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
181
- });
182
-
183
- addProjectCloneInput.addEventListener("keydown", function (e) {
184
- if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
185
- if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
186
- });
187
-
188
- addProjectOk.addEventListener("click", function () { submitAddProject(); });
189
- addProjectCancel.addEventListener("click", function () { closeAddProjectModal(); });
190
-
191
- // Close on backdrop click
192
- addProjectModal.querySelector(".confirm-backdrop").addEventListener("click", function () {
193
- closeAddProjectModal();
194
- });
195
-
196
- // Project list add button
197
- var projectListAddBtn = document.getElementById("project-list-add");
198
- if (projectListAddBtn) {
199
- projectListAddBtn.addEventListener("click", function () {
200
- openAddProjectModal();
201
- });
202
- }
203
- }
204
52
 
205
53
  // --- State accessors ---
206
54
 
@@ -643,257 +491,3 @@ export function handleRemoveProjectResult(msg) {
643
491
  showToast(msg.error || "Failed to remove project", "error");
644
492
  }
645
493
  }
646
-
647
- // --- Add project modal ---
648
-
649
- function switchAddProjectMode(mode) {
650
- addProjectMode = mode;
651
- for (var mi = 0; mi < addProjectModeBtns.length; mi++) {
652
- var btn = addProjectModeBtns[mi];
653
- if (btn.dataset.mode === mode) {
654
- btn.classList.add("active");
655
- } else {
656
- btn.classList.remove("active");
657
- }
658
- }
659
- for (var pi = 0; pi < addProjectPanels.length; pi++) {
660
- var panel = addProjectPanels[pi];
661
- if (panel.dataset.panel === mode) {
662
- panel.classList.add("active");
663
- } else {
664
- panel.classList.remove("active");
665
- }
666
- }
667
- addProjectError.classList.add("hidden");
668
- addProjectCloneProgress.classList.add("hidden");
669
- if (mode === "existing") {
670
- addProjectOk.textContent = "Add";
671
- } else if (mode === "create") {
672
- addProjectOk.textContent = "Create";
673
- } else if (mode === "clone") {
674
- addProjectOk.textContent = "Clone";
675
- }
676
- setTimeout(function () {
677
- if (mode === "existing") {
678
- addProjectInput.focus();
679
- } else if (mode === "create") {
680
- addProjectCreateInput.focus();
681
- } else if (mode === "clone") {
682
- addProjectCloneInput.focus();
683
- }
684
- }, 50);
685
- }
686
-
687
- export function openAddProjectModal(mode) {
688
- addProjectModal.classList.remove("hidden");
689
- addProjectCreateInput.value = "";
690
- addProjectCloneInput.value = "";
691
- addProjectError.classList.add("hidden");
692
- addProjectError.textContent = "";
693
- addProjectCloneProgress.classList.add("hidden");
694
- addProjectSuggestions.classList.add("hidden");
695
- addProjectSuggestions.innerHTML = "";
696
- addProjectActiveIdx = -1;
697
- addProjectOk.disabled = false;
698
- var existingBtn = addProjectModal.querySelector('.add-project-mode-btn[data-mode="existing"]');
699
- var st = store.snap();
700
- if (st.isOsUsers) {
701
- existingBtn.disabled = false;
702
- var myUser = st.cachedAllUsers.find(function (u) { return u.id === st.myUserId; });
703
- var isAdmin = myUser && myUser.role === "admin";
704
- if (!isAdmin && myUser && myUser.linuxUser) {
705
- // Non-admin: lock prefix to home directory
706
- addProjectPrefixValue = "/home/" + myUser.linuxUser + "/";
707
- addProjectPrefixEl.textContent = addProjectPrefixValue;
708
- addProjectPrefixEl.classList.remove("hidden");
709
- addProjectInput.value = "";
710
- addProjectInput.placeholder = "subdirectory";
711
- } else {
712
- // Admin: no prefix restriction
713
- addProjectPrefixValue = "";
714
- addProjectPrefixEl.classList.add("hidden");
715
- addProjectInput.value = "/";
716
- addProjectInput.placeholder = "/";
717
- }
718
- switchAddProjectMode("existing");
719
- } else {
720
- addProjectPrefixValue = "";
721
- addProjectPrefixEl.classList.add("hidden");
722
- addProjectInput.value = "/";
723
- addProjectInput.placeholder = "/";
724
- existingBtn.disabled = false;
725
- switchAddProjectMode("existing");
726
- }
727
- if (mode === "create" || mode === "clone") switchAddProjectMode(mode);
728
- renderRemovedProjectsList();
729
- }
730
-
731
- function renderRemovedProjectsList() {
732
- if (!addProjectRemoved) return;
733
- addProjectRemoved.innerHTML = "";
734
- if (!cachedRemovedProjects || cachedRemovedProjects.length === 0) {
735
- addProjectRemoved.classList.add("hidden");
736
- return;
737
- }
738
- addProjectRemoved.classList.remove("hidden");
739
- for (var ri = 0; ri < cachedRemovedProjects.length; ri++) {
740
- var rp = cachedRemovedProjects[ri];
741
- var item = document.createElement("div");
742
- item.className = "add-project-removed-item";
743
- item.dataset.path = rp.path;
744
- item.addEventListener("click", function () {
745
- var p = this.dataset.path;
746
- var ws = getWs();
747
- if (ws && ws.readyState === 1) {
748
- ws.send(JSON.stringify({ type: "add_project", path: p }));
749
- }
750
- closeAddProjectModal();
751
- });
752
- var iconEl = document.createElement("span");
753
- iconEl.className = "add-project-removed-icon";
754
- iconEl.textContent = rp.icon || "\uD83D\uDCC1";
755
- item.appendChild(iconEl);
756
- var info = document.createElement("div");
757
- info.className = "add-project-removed-info";
758
- var nameEl = document.createElement("div");
759
- nameEl.className = "add-project-removed-name";
760
- nameEl.textContent = rp.title || rp.path.split("/").pop() || rp.path;
761
- info.appendChild(nameEl);
762
- var pathEl = document.createElement("div");
763
- pathEl.className = "add-project-removed-path";
764
- pathEl.textContent = rp.path;
765
- info.appendChild(pathEl);
766
- item.appendChild(info);
767
- addProjectRemoved.appendChild(item);
768
- }
769
- try { parseEmojis(addProjectRemoved); } catch (e) {}
770
- }
771
-
772
- export function closeAddProjectModal() {
773
- addProjectModal.classList.add("hidden");
774
- addProjectInput.value = "";
775
- addProjectCreateInput.value = "";
776
- addProjectCloneInput.value = "";
777
- addProjectSuggestions.classList.add("hidden");
778
- addProjectSuggestions.innerHTML = "";
779
- addProjectError.classList.add("hidden");
780
- addProjectCloneProgress.classList.add("hidden");
781
- addProjectActiveIdx = -1;
782
- addProjectPrefixValue = "";
783
- addProjectPrefixEl.classList.add("hidden");
784
- if (addProjectDebounce) { clearTimeout(addProjectDebounce); addProjectDebounce = null; }
785
- }
786
-
787
- function getFullPath(inputVal) {
788
- return addProjectPrefixValue + inputVal;
789
- }
790
-
791
- function stripPrefix(fullPath) {
792
- if (addProjectPrefixValue && fullPath.indexOf(addProjectPrefixValue) === 0) {
793
- return fullPath.slice(addProjectPrefixValue.length);
794
- }
795
- return fullPath;
796
- }
797
-
798
- function requestBrowseDir(val) {
799
- var ws = getWs();
800
- if (!ws || ws.readyState !== 1) return;
801
- ws.send(JSON.stringify({ type: "browse_dir", path: getFullPath(val) }));
802
- }
803
-
804
- export function handleBrowseDirResult(msg) {
805
- addProjectSuggestions.innerHTML = "";
806
- addProjectActiveIdx = -1;
807
- if (msg.error) {
808
- addProjectSuggestions.classList.add("hidden");
809
- return;
810
- }
811
- var entries = msg.entries || [];
812
- if (entries.length === 0) {
813
- addProjectSuggestions.classList.add("hidden");
814
- return;
815
- }
816
- for (var si = 0; si < entries.length; si++) {
817
- var entry = entries[si];
818
- var item = document.createElement("div");
819
- item.className = "add-project-suggestion-item";
820
- item.dataset.path = entry.path;
821
- item.innerHTML = '<i data-lucide="folder"></i><span class="add-project-suggestion-name">' +
822
- escapeHtml(entry.name) + '</span>';
823
- item.addEventListener("click", function (e) {
824
- var fullP = this.dataset.path + "/";
825
- addProjectInput.value = stripPrefix(fullP);
826
- addProjectInput.focus();
827
- addProjectError.classList.add("hidden");
828
- requestBrowseDir(addProjectInput.value);
829
- });
830
- addProjectSuggestions.appendChild(item);
831
- }
832
- addProjectSuggestions.classList.remove("hidden");
833
- refreshIcons();
834
- }
835
-
836
- export function handleAddProjectResult(msg) {
837
- addProjectCloneProgress.classList.add("hidden");
838
- if (msg.ok) {
839
- closeAddProjectModal();
840
- if (msg.existing) {
841
- showToast("Project already registered", "info");
842
- } else {
843
- var toastMsg = addProjectMode === "create" ? "Project created" : addProjectMode === "clone" ? "Project cloned" : "Project added";
844
- showToast(toastMsg, "success");
845
- }
846
- if (msg.slug) switchProject(msg.slug);
847
- } else {
848
- addProjectError.textContent = msg.error || "Failed to add project";
849
- addProjectError.classList.remove("hidden");
850
- addProjectOk.disabled = false;
851
- }
852
- }
853
-
854
- export function handleCloneProgress(msg) {
855
- if (msg.status === "cloning") {
856
- addProjectCloneProgress.classList.remove("hidden");
857
- }
858
- }
859
-
860
- function setActiveIdx(idx) {
861
- var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
862
- addProjectActiveIdx = idx;
863
- for (var ai = 0; ai < items.length; ai++) {
864
- if (ai === idx) {
865
- items[ai].classList.add("active");
866
- items[ai].scrollIntoView({ block: "nearest" });
867
- } else {
868
- items[ai].classList.remove("active");
869
- }
870
- }
871
- }
872
-
873
- function submitAddProject() {
874
- addProjectError.classList.add("hidden");
875
- addProjectOk.disabled = true;
876
-
877
- if (addProjectMode === "existing") {
878
- var val = getFullPath(addProjectInput.value).replace(/\/+$/, "");
879
- if (!val) { addProjectOk.disabled = false; return; }
880
- var ws = getWs();
881
- if (ws && ws.readyState === 1) {
882
- ws.send(JSON.stringify({ type: "add_project", path: val }));
883
- }
884
- } else if (addProjectMode === "create") {
885
- var name = addProjectCreateInput.value.trim();
886
- if (!name) { addProjectOk.disabled = false; return; }
887
- var ws2 = getWs();
888
- if (ws2 && ws2.readyState === 1) {
889
- ws2.send(JSON.stringify({ type: "create_project", name: name }));
890
- }
891
- } else if (addProjectMode === "clone") {
892
- var url = addProjectCloneInput.value.trim();
893
- if (!url) { addProjectOk.disabled = false; return; }
894
- var ws3 = getWs();
895
- if (ws3 && ws3.readyState === 1) {
896
- ws3.send(JSON.stringify({ type: "clone_project", url: url }));
897
- }
898
- }
899
- }
@@ -238,9 +238,16 @@ function tickRateLimitUsage() {
238
238
  renderRateLimitUsage();
239
239
  }
240
240
 
241
+ // Once the server's vendor overview has answered, every vendor including the
242
+ // displayed one is shown by the overview chips, so this session-scoped link
243
+ // stands down. Rejection, warning and scheduling state is untouched.
244
+ function overviewOwnsUsageDisplay() {
245
+ return !!store.get('vendorUsageOverview');
246
+ }
247
+
241
248
  function renderRateLimitUsage() {
242
249
  var activeVendor = store.get('currentVendor') || "claude";
243
- if (!vendorTracksRateLimits(activeVendor)) {
250
+ if (!vendorTracksRateLimits(activeVendor) || overviewOwnsUsageDisplay()) {
244
251
  if (rateLimitUsageEl) {
245
252
  rateLimitUsageEl.remove();
246
253
  rateLimitUsageEl = null;
@@ -320,6 +327,10 @@ export function initRateLimit() {
320
327
  || state.currentSlug !== prev.currentSlug || state.myUserId !== prev.myUserId) {
321
328
  resetRateLimitState();
322
329
  }
330
+ if (state.vendorUsageOverview !== prev.vendorUsageOverview && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
331
+ && state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
332
+ renderRateLimitUsage();
333
+ }
323
334
  if (state.vendorInfo !== prev.vendorInfo && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
324
335
  && state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
325
336
  renderRateLimitUsage();
@@ -172,6 +172,7 @@ rows('clay-workspace', [
172
172
  ['inspect_mate_chat_change', 'Inspecting Mate conversation change', 'Mate conversation change inspected', 'Mate conversation'],
173
173
  ['control_mate_chat', 'Requesting Mate conversation change', 'Mate conversation request returned', 'Mate conversation', 'why', 'mate-chat'],
174
174
  ['control_schedules', 'Requesting schedule change', 'Schedule change request returned', 'Scheduling', 'why', 'schedule-control'],
175
+ ['read_vendor_usage', 'Reading vendor usage', 'Vendor usage read', 'Vendor usage', 'projectSlug'],
175
176
  ['read_project_records', 'Reading project records', 'Project records read', 'Project records', 'projectSlug'],
176
177
  ['inspect_project_record_change', 'Inspecting record change', 'Record change inspected', 'Project records', 'projectSlug'],
177
178
  ['control_project_records', 'Requesting record change', 'Record change request returned', 'Project records', 'why'],
@@ -1,3 +1,4 @@
1
+ import { mateLoadingMarkup, updateMateLoadingScene } from './mate-loading-scene.js';
1
2
  import { attachContextualSessionCitations } from './contextual-session-citations.js';
2
3
  import { attachContextualConversations } from './contextual-mate-sessions.js';
3
4
  import { getCachedSessions } from './sidebar-sessions.js';
@@ -67,6 +68,7 @@ function render() {
67
68
  var mate = contextualMate(state().mateId);
68
69
  if (mate) {
69
70
  var name = getHomeMateName(mate);
71
+ updateMateLoadingScene(surface.querySelector('[data-clay-loading]'), name, mateAvatarUrl(mate, 96));
70
72
  surface.setAttribute('aria-label', name + ' conversation');
71
73
  surface.querySelector('strong').textContent = name;
72
74
  var header = surface.querySelector('header');
@@ -130,7 +132,7 @@ function ensureSurface() {
130
132
  if (!host()) {
131
133
  var surface = document.createElement('section');
132
134
  surface.id = 'contextual-clay'; surface.setAttribute('role', 'dialog');
133
- surface.innerHTML = '<header><img class="contextual-clay-avatar" alt=""><div class="contextual-clay-identity"><strong></strong><div class="contextual-context-line"><span data-clay-context></span><details class="contextual-clay-context"><summary aria-label="What this Mate can see" title="What this Mate can see">' + iconHtml('circle-help') + '</summary><p></p></details></div></div><div class="contextual-clay-actions"><button type="button" data-clay-expand>' + iconHtml('arrow-up-right') + '<span></span></button><button type="button" data-clay-close title="Close">' + iconHtml('x') + '</button></div></header><p class="contextual-capabilities" role="note" aria-label="Conversation capabilities"></p><div class="contextual-clay-body"><p data-clay-loading role="status">Opening conversation…</p></div>';
135
+ surface.innerHTML = '<header><img class="contextual-clay-avatar" alt=""><div class="contextual-clay-identity"><strong></strong><div class="contextual-context-line"><span data-clay-context></span><details class="contextual-clay-context"><summary aria-label="What this Mate can see" title="What this Mate can see">' + iconHtml('circle-help') + '</summary><p></p></details></div></div><div class="contextual-clay-actions"><button type="button" data-clay-expand>' + iconHtml('arrow-up-right') + '<span></span></button><button type="button" data-clay-close title="Close">' + iconHtml('x') + '</button></div></header><p class="contextual-capabilities" role="note" aria-label="Conversation capabilities"></p><div class="contextual-clay-body">' + mateLoadingMarkup() + '</div>';
134
136
  surface.addEventListener('contextual-clay-keydown', function (event) {
135
137
  var detail = event.detail; var frame = contextualFrame();
136
138
  if (!detail || !frame || detail.source !== frame.contentWindow) return;
@@ -0,0 +1,66 @@
1
+ // github-star.js - Public star count beside the top-bar GitHub Star link.
2
+ //
3
+ // The link itself is static markup. This module only fills in the count read
4
+ // from the server's cached GET /api/github-stars. An unknown count leaves the
5
+ // count segment hidden; nothing is invented, and the module never writes to
6
+ // GitHub or sends anything about the user.
7
+
8
+ import { store } from './store.js';
9
+
10
+ var REFRESH_MS = 60 * 60 * 1000;
11
+ var MOBILE_QUERY = "(max-width: 768px)";
12
+
13
+ var timer = null;
14
+ var inflight = false;
15
+
16
+ // 999 -> "999", 1234 -> "1.2k", 12500 -> "12.5k", 1200000 -> "1.2m"
17
+ export function formatStarCount(n) {
18
+ if (typeof n !== "number" || !isFinite(n) || n < 0) return "";
19
+ n = Math.floor(n);
20
+ if (n < 1000) return String(n);
21
+ var units = [[1000000, "m"], [1000, "k"]];
22
+ for (var i = 0; i < units.length; i++) {
23
+ if (n >= units[i][0]) {
24
+ var v = Math.floor(n / units[i][0] * 10) / 10;
25
+ return (v % 1 === 0 ? String(v) : v.toFixed(1)) + units[i][1];
26
+ }
27
+ }
28
+ return String(n);
29
+ }
30
+
31
+ function render() {
32
+ var count = store.get("githubStarCount");
33
+ var countEl = document.getElementById("github-star-count");
34
+ var linkEl = document.getElementById("github-star-link");
35
+ if (!countEl || !linkEl) return;
36
+ var text = typeof count === "number" ? formatStarCount(count) : "";
37
+ countEl.textContent = text;
38
+ countEl.classList.toggle("hidden", !text);
39
+ linkEl.setAttribute("aria-label", text ? "Star Clay on GitHub, " + count + " stars" : "Star Clay on GitHub");
40
+ }
41
+
42
+ function isMobile() {
43
+ return !!(window.matchMedia && window.matchMedia(MOBILE_QUERY).matches);
44
+ }
45
+
46
+ function load() {
47
+ if (inflight || isMobile()) return Promise.resolve();
48
+ inflight = true;
49
+ return fetch("/api/github-stars", { credentials: "same-origin" }).then(function (res) {
50
+ return res.ok ? res.json() : null;
51
+ }).then(function (data) {
52
+ // Keep the last known count when the server has none or the call failed.
53
+ if (data && typeof data.stars === "number") {
54
+ store.set({ githubStarCount: data.stars });
55
+ render();
56
+ }
57
+ }).catch(function () {}).then(function () { inflight = false; });
58
+ }
59
+
60
+ export function initGithubStar() {
61
+ if (!document.getElementById("github-star")) return;
62
+ render();
63
+ load();
64
+ if (timer) clearInterval(timer);
65
+ timer = setInterval(load, REFRESH_MS);
66
+ }
@@ -0,0 +1,17 @@
1
+ // Decorative opening state; conversation readiness remains with its host.
2
+ export function mateLoadingMarkup() {
3
+ return '<div data-clay-loading class="mate-loading" role="status" aria-live="polite">' +
4
+ '<div class="mate-loading-scene" aria-hidden="true">' +
5
+ '<img class="mate-loading-avatar" alt="">' +
6
+ '<img class="mate-loading-desk" src="/welcome-art/conversation-desk.png" alt="" decoding="async">' +
7
+ '</div><p class="mate-loading-label"></p>' +
8
+ '<div class="mate-loading-dots" aria-hidden="true"><span></span><span></span><span></span></div></div>';
9
+ }
10
+
11
+ export function updateMateLoadingScene(element, name, avatar) {
12
+ var image = element.querySelector('.mate-loading-avatar');
13
+ if (image.getAttribute('src') !== avatar) image.setAttribute('src', avatar);
14
+ var label = element.querySelector('.mate-loading-label');
15
+ var text = 'Getting ready with ' + name;
16
+ if (label.textContent !== text) label.textContent = text;
17
+ }
@@ -135,9 +135,12 @@ export function uploadAvatarBlob(blob) {
135
135
  }
136
136
 
137
137
  // --- Avatar position picker ---
138
- export function showAvatarPositioner(img, objectUrl, onDone) {
138
+ // With a host element the editor mounts inline as a settings subview instead
139
+ // of a page-level overlay.
140
+ export function showAvatarPositioner(img, objectUrl, onDone, host) {
139
141
  var outputSize = 256;
140
142
  var viewSize = 220;
143
+ var priorFocus = document.activeElement;
141
144
 
142
145
  // State
143
146
  var scale = 1;
@@ -169,7 +172,7 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
169
172
 
170
173
  // Build overlay
171
174
  var overlay = document.createElement('div');
172
- overlay.className = 'avatar-positioner-overlay';
175
+ overlay.className = host ? 'avatar-positioner-inline settings-subview' : 'avatar-positioner-overlay';
173
176
 
174
177
  var container = document.createElement('div');
175
178
  container.className = 'avatar-positioner-container';
@@ -225,8 +228,10 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
225
228
  container.appendChild(btnRow);
226
229
 
227
230
  overlay.appendChild(container);
228
- document.body.appendChild(overlay);
231
+ if (host) { host.classList.add('has-subview'); host.appendChild(overlay); overlay.setAttribute('role', 'region'); overlay.setAttribute('aria-label', 'Position your avatar'); }
232
+ else document.body.appendChild(overlay);
229
233
  updatePreview();
234
+ if (host) doneFocus();
230
235
 
231
236
  // Drag
232
237
  var dragging = false;
@@ -288,9 +293,12 @@ export function showAvatarPositioner(img, objectUrl, onDone) {
288
293
  updatePreview();
289
294
  }, { passive: false });
290
295
 
296
+ function doneFocus() { closeBtn.focus(); }
297
+
291
298
  function cleanup(revoke) {
292
299
  document.removeEventListener('keydown', onOverlayKeydown);
293
300
  overlay.remove();
301
+ if (host) { host.classList.remove('has-subview'); if (priorFocus && priorFocus.isConnected) priorFocus.focus(); }
294
302
  if (revoke) URL.revokeObjectURL(objectUrl);
295
303
  }
296
304
 
@@ -81,6 +81,9 @@ function startMateCreation() {
81
81
  maybeStartPendingCreation();
82
82
  }
83
83
 
84
+ // Entry for the zero-project welcome: the same Clay-guided flow the add button's Mate choice runs.
85
+ export function startClayMateCreation() { startMateCreation(); }
86
+
84
87
  function maybeStartPendingCreation() {
85
88
  var state = store.snap();
86
89
  if (!state.projectMateCreationPendingStart || !state.connected || state.activeProjectSlug !== 'mate-' + state.projectMateCreationMateId) return;
@@ -89,11 +92,17 @@ function maybeStartPendingCreation() {
89
92
  store.set({ projectMateCreationPendingStart: false, projectMateCreationAwaitingHistory: true, projectMateCreationRequestId: requestId });
90
93
  }
91
94
 
92
- function choiceButton(icon, title, description) {
95
+ function choiceButton(artwork, title, description) {
93
96
  var button = document.createElement('button');
94
97
  button.type = 'button';
95
98
  button.className = 'project-create-choice-option';
96
- button.innerHTML = iconHtml(icon);
99
+ var image = document.createElement('img');
100
+ image.src = '/welcome-art/' + artwork + '.png';
101
+ image.alt = '';
102
+ image.width = 1536;
103
+ image.height = 1024;
104
+ image.decoding = 'async';
105
+ button.appendChild(image);
97
106
  var copy = document.createElement('span');
98
107
  var heading = document.createElement('strong');
99
108
  heading.textContent = title;
@@ -121,9 +130,9 @@ export function openProjectCreateChoice(opener, openProject) {
121
130
  var title = document.createElement('h2');
122
131
  title.id = 'project-create-choice-title';
123
132
  title.textContent = 'What would you like to add?';
124
- var mate = choiceButton('users', 'Mate', 'Shape a new collaborator with Clay');
133
+ var mate = choiceButton('mate-clay', 'Create a Mate', 'A specialist to work alongside you.');
125
134
  mate.addEventListener('click', startMateCreation);
126
- var project = choiceButton('folder-plus', 'Project', 'Add a local workspace');
135
+ var project = choiceButton('project-clay', 'Start a project', 'Work on code or files together.');
127
136
  project.addEventListener('click', function () {
128
137
  closeChoice(false);
129
138
  openProject();