clay-server 5.2.0-beta.3 → 5.2.0-beta.4

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 (193) hide show
  1. package/lib/builtin-mates.js +2 -2
  2. package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
  3. package/lib/knowledge-file-store.js +7 -1
  4. package/lib/mate-avatar-catalog.js +12 -0
  5. package/lib/mate-avatar-migration.js +41 -0
  6. package/lib/mates.js +14 -3
  7. package/lib/mcp-agent-tools.js +117 -0
  8. package/lib/mcp-connection-store.js +119 -15
  9. package/lib/mcp-import-credentials.js +31 -0
  10. package/lib/mcp-local-transport.js +13 -0
  11. package/lib/mcp-oauth.js +8 -6
  12. package/lib/mcp-query-bridge.js +30 -0
  13. package/lib/mcp-remote-connections.js +74 -20
  14. package/lib/mcp-source-adapters.js +91 -0
  15. package/lib/mcp-source-context.js +42 -0
  16. package/lib/mcp-source-discovery.js +169 -0
  17. package/lib/mcp-source-plugins.js +68 -0
  18. package/lib/mcp-source-restrictions.js +47 -0
  19. package/lib/mcp-tool-policy.js +34 -0
  20. package/lib/project-ask-user.js +6 -4
  21. package/lib/project-debate.js +1 -0
  22. package/lib/project-mate-interaction.js +8 -0
  23. package/lib/project-mcp.js +54 -16
  24. package/lib/project-session-document.js +8 -3
  25. package/lib/project-session-github.js +16 -7
  26. package/lib/project-session-linear.js +14 -6
  27. package/lib/project-session-notes.js +7 -3
  28. package/lib/project-sessions.js +6 -2
  29. package/lib/project-shared-browser.js +56 -12
  30. package/lib/project.js +49 -34
  31. package/lib/public/app.js +5 -0
  32. package/lib/public/appearance-bootstrap.js +14 -0
  33. package/lib/public/css/clay-workspace.css +29 -0
  34. package/lib/public/css/home-debate-planning.css +2 -0
  35. package/lib/public/css/mate-avatar-gallery.css +12 -0
  36. package/lib/public/css/mate-conversation.css +10 -5
  37. package/lib/public/css/session-github.css +43 -1
  38. package/lib/public/css/shared-browser.css +35 -1
  39. package/lib/public/index.html +10 -4
  40. package/lib/public/mate-avatars/B01.png +0 -0
  41. package/lib/public/mate-avatars/B02.png +0 -0
  42. package/lib/public/mate-avatars/B03.png +0 -0
  43. package/lib/public/mate-avatars/B04.png +0 -0
  44. package/lib/public/mate-avatars/B05.png +0 -0
  45. package/lib/public/mate-avatars/B06.png +0 -0
  46. package/lib/public/mate-avatars/B07.png +0 -0
  47. package/lib/public/mate-avatars/B08.png +0 -0
  48. package/lib/public/mate-avatars/B09.png +0 -0
  49. package/lib/public/mate-avatars/B10.png +0 -0
  50. package/lib/public/mate-avatars/B11.png +0 -0
  51. package/lib/public/mate-avatars/B12.png +0 -0
  52. package/lib/public/mate-avatars/B13.png +0 -0
  53. package/lib/public/mate-avatars/B14.png +0 -0
  54. package/lib/public/mate-avatars/B15.png +0 -0
  55. package/lib/public/mate-avatars/B16.png +0 -0
  56. package/lib/public/mate-avatars/B17.png +0 -0
  57. package/lib/public/mate-avatars/B18.png +0 -0
  58. package/lib/public/mate-avatars/B19.png +0 -0
  59. package/lib/public/mate-avatars/B20.png +0 -0
  60. package/lib/public/mate-avatars/B21.png +0 -0
  61. package/lib/public/mate-avatars/B22.png +0 -0
  62. package/lib/public/mate-avatars/B23.png +0 -0
  63. package/lib/public/mate-avatars/B24.png +0 -0
  64. package/lib/public/mate-avatars/B25.png +0 -0
  65. package/lib/public/mate-avatars/B26.png +0 -0
  66. package/lib/public/mate-avatars/B27.png +0 -0
  67. package/lib/public/mate-avatars/B28.png +0 -0
  68. package/lib/public/mate-avatars/B29.png +0 -0
  69. package/lib/public/mate-avatars/B30.png +0 -0
  70. package/lib/public/mate-avatars/B31.png +0 -0
  71. package/lib/public/mate-avatars/B32.png +0 -0
  72. package/lib/public/mate-avatars/B33.png +0 -0
  73. package/lib/public/mate-avatars/B34.png +0 -0
  74. package/lib/public/mate-avatars/B35.png +0 -0
  75. package/lib/public/mate-avatars/B36.png +0 -0
  76. package/lib/public/mate-avatars/B37.png +0 -0
  77. package/lib/public/mate-avatars/B38.png +0 -0
  78. package/lib/public/mate-avatars/B39.png +0 -0
  79. package/lib/public/mate-avatars/B40.png +0 -0
  80. package/lib/public/mate-avatars/B41.png +0 -0
  81. package/lib/public/mate-avatars/B42.png +0 -0
  82. package/lib/public/mate-avatars/B43.png +0 -0
  83. package/lib/public/mate-avatars/B44.png +0 -0
  84. package/lib/public/mate-avatars/B45.png +0 -0
  85. package/lib/public/mate-avatars/B46.png +0 -0
  86. package/lib/public/mate-avatars/B47.png +0 -0
  87. package/lib/public/mate-avatars/B48.png +0 -0
  88. package/lib/public/mate-avatars/C01.png +0 -0
  89. package/lib/public/mate-avatars/C02.png +0 -0
  90. package/lib/public/mate-avatars/C03.png +0 -0
  91. package/lib/public/mate-avatars/C04.png +0 -0
  92. package/lib/public/mate-avatars/C05.png +0 -0
  93. package/lib/public/mate-avatars/C06.png +0 -0
  94. package/lib/public/mate-avatars/C07.png +0 -0
  95. package/lib/public/mate-avatars/C08.png +0 -0
  96. package/lib/public/mate-avatars/C09.png +0 -0
  97. package/lib/public/mate-avatars/C10.png +0 -0
  98. package/lib/public/mate-avatars/C11.png +0 -0
  99. package/lib/public/mate-avatars/C12.png +0 -0
  100. package/lib/public/mate-avatars/C13.png +0 -0
  101. package/lib/public/mate-avatars/C14.png +0 -0
  102. package/lib/public/mate-avatars/C15.png +0 -0
  103. package/lib/public/mate-avatars/C16.png +0 -0
  104. package/lib/public/mate-avatars/C17.png +0 -0
  105. package/lib/public/mate-avatars/C18.png +0 -0
  106. package/lib/public/mate-avatars/C19.png +0 -0
  107. package/lib/public/mate-avatars/C20.png +0 -0
  108. package/lib/public/mate-avatars/C21.png +0 -0
  109. package/lib/public/mate-avatars/C22.png +0 -0
  110. package/lib/public/mate-avatars/C23.png +0 -0
  111. package/lib/public/mate-avatars/C24.png +0 -0
  112. package/lib/public/mate-avatars/C25.png +0 -0
  113. package/lib/public/mate-avatars/C26.png +0 -0
  114. package/lib/public/mate-avatars/C27.png +0 -0
  115. package/lib/public/mate-avatars/C28.png +0 -0
  116. package/lib/public/mate-avatars/C29.png +0 -0
  117. package/lib/public/mate-avatars/C30.png +0 -0
  118. package/lib/public/mate-avatars/C31.png +0 -0
  119. package/lib/public/mate-avatars/C32.png +0 -0
  120. package/lib/public/mate-avatars/C33.png +0 -0
  121. package/lib/public/mate-avatars/C34.png +0 -0
  122. package/lib/public/mate-avatars/C35.png +0 -0
  123. package/lib/public/mate-avatars/C36.png +0 -0
  124. package/lib/public/mate-avatars/C37.png +0 -0
  125. package/lib/public/mate-avatars/C38.png +0 -0
  126. package/lib/public/mate-avatars/C39.png +0 -0
  127. package/lib/public/mate-avatars/C40.png +0 -0
  128. package/lib/public/mate-avatars/C41.png +0 -0
  129. package/lib/public/mate-avatars/C42.png +0 -0
  130. package/lib/public/mate-avatars/C43.png +0 -0
  131. package/lib/public/mate-avatars/C44.png +0 -0
  132. package/lib/public/mate-avatars/C45.png +0 -0
  133. package/lib/public/mate-avatars/C46.png +0 -0
  134. package/lib/public/mate-avatars/C47.png +0 -0
  135. package/lib/public/mate-avatars/C48.png +0 -0
  136. package/lib/public/mate-avatars/catalog.json +482 -0
  137. package/lib/public/mate-avatars/lead-clay.png +0 -0
  138. package/lib/public/modules/app-message-router.js +4 -0
  139. package/lib/public/modules/app-messages.js +13 -2
  140. package/lib/public/modules/app-rendering.js +1 -1
  141. package/lib/public/modules/avatar.js +15 -41
  142. package/lib/public/modules/chat-render-runtime.js +10 -1
  143. package/lib/public/modules/clay-browser-links.js +47 -0
  144. package/lib/public/modules/clay-log-links.js +1 -1
  145. package/lib/public/modules/command-palette.js +1 -1
  146. package/lib/public/modules/debate.js +1 -0
  147. package/lib/public/modules/history-replay-batch.js +5 -1
  148. package/lib/public/modules/home-debate-live.js +2 -0
  149. package/lib/public/modules/home-debate-planning.js +7 -7
  150. package/lib/public/modules/home-mate-avatar-editor.js +127 -208
  151. package/lib/public/modules/home-mate-chat.js +6 -6
  152. package/lib/public/modules/home-mate-settings.js +19 -12
  153. package/lib/public/modules/markdown.js +4 -0
  154. package/lib/public/modules/mate-avatar-catalog.js +97 -0
  155. package/lib/public/modules/mate-management.js +40 -7
  156. package/lib/public/modules/mcp-connection-view.js +31 -13
  157. package/lib/public/modules/mcp-discovery-view.js +31 -0
  158. package/lib/public/modules/mcp-ui.js +23 -4
  159. package/lib/public/modules/mention.js +3 -0
  160. package/lib/public/modules/pane-browser-bridge.js +20 -0
  161. package/lib/public/modules/profile.js +1 -238
  162. package/lib/public/modules/project-mate-navigation.js +99 -14
  163. package/lib/public/modules/search-clay-chat.js +16 -2
  164. package/lib/public/modules/session-github.js +23 -9
  165. package/lib/public/modules/session-work-primary.js +51 -0
  166. package/lib/public/modules/shared-browser-settings.js +112 -0
  167. package/lib/public/modules/shared-browser-tabs.js +13 -3
  168. package/lib/public/modules/shared-browser.js +27 -6
  169. package/lib/public/modules/split-view.js +2 -0
  170. package/lib/public/modules/theme.js +8 -1
  171. package/lib/public/modules/tools.js +2 -1
  172. package/lib/public/style.css +1 -0
  173. package/lib/public/sw.js +3 -1
  174. package/lib/sdk-bridge.js +9 -3
  175. package/lib/server-appearance-settings.js +13 -1
  176. package/lib/server-dm.js +1 -0
  177. package/lib/server-home-chat-events.js +14 -4
  178. package/lib/server-home-chat.js +10 -2
  179. package/lib/server-home-mcp-consent.js +14 -0
  180. package/lib/server-mates.js +6 -1
  181. package/lib/server-settings.js +4 -1
  182. package/lib/server.js +45 -12
  183. package/lib/session-work-primary.js +50 -0
  184. package/lib/sessions.js +3 -0
  185. package/lib/shared-browser-cookie-sync.js +32 -0
  186. package/lib/shared-browser-cookies.js +79 -0
  187. package/lib/shared-browser-runtime.js +25 -9
  188. package/lib/shared-browser-storage-sync.js +43 -0
  189. package/lib/shared-browser-storage.js +101 -0
  190. package/lib/shared-browser-tools.js +2 -1
  191. package/lib/shared-browser-worker.js +46 -10
  192. package/lib/yoke/acp-agent-profiles.js +2 -2
  193. package/package.json +5 -1
@@ -4,7 +4,7 @@ import { setChatLayout } from './theme.js';
4
4
  // Generated identities use deterministic local pixel identicons.
5
5
 
6
6
  import { iconHtml, refreshIcons } from './icons.js';
7
- import { avatarUrl, mateAvatarUrl, mateMarkUrl, userAvatarSeed } from './avatar.js';
7
+ import { avatarUrl, userAvatarSeed } from './avatar.js';
8
8
  import { store } from './store.js';
9
9
  import { applyTerminalFont } from './terminal-prefs.js';
10
10
 
@@ -586,240 +586,3 @@ export function getProfile() {
586
586
  export function getProfileLang() {
587
587
  return profile.lang;
588
588
  }
589
-
590
- // --- Mate profile popover (reuses same UI minus language) ---
591
- var matePopoverEl = null;
592
- var mateSaveTimer = null;
593
-
594
- export function showMateProfilePopover(anchorEl, mateData, onUpdate) {
595
- if (matePopoverEl) {
596
- hideMatePopover();
597
- return;
598
- }
599
-
600
- var mp = mateData.profile || {};
601
- var mateName = mp.displayName || mateData.name || '';
602
- var mateColor = mp.avatarColor || '#5857fc';
603
- var mateStyle = mp.avatarStyle || 'imprint';
604
- var mateSeed = mp.avatarSeed || mateData.id || 'mate';
605
- var mateCustom = mp.avatarCustom || '';
606
-
607
- matePopoverEl = document.createElement('div');
608
- matePopoverEl.className = 'profile-popover mate-profile-popover';
609
-
610
- var html = '';
611
-
612
- // Banner + close
613
- html += '<div class="profile-banner">';
614
- html += '<button class="profile-close-btn">&times;</button>';
615
- html += '</div>';
616
-
617
- // Avatar row
618
- var mateAvatarSrc = mateCustom ? mateCustom : mateMarkUrl(mateName || 'Mate', 80);
619
- html += '<div class="profile-avatar-row">';
620
- html += '<div class="profile-popover-avatar">';
621
- html += '<img class="profile-popover-avatar-img" src="' + mateAvatarSrc + '" alt="' + escapeAttr(mateName || 'Clay Mate') + '">';
622
- html += '</div>';
623
- html += '<div class="profile-name-display">' + escapeAttr(mateName || 'New Mate') + '</div>';
624
- html += '</div>';
625
-
626
- // Body
627
- html += '<div class="profile-popover-body">';
628
-
629
- // Name
630
- html += '<div class="profile-field">';
631
- html += '<label class="profile-field-label">Display Name</label>';
632
- html += '<input type="text" class="profile-field-input" id="mate-profile-name" value="' + escapeAttr(mateName) + '" placeholder="Name your mate..." maxlength="50" spellcheck="false" autocomplete="off">';
633
- html += '</div>';
634
-
635
- // Avatar picker (hidden if avatar is locked)
636
- var mateAvatarLocked = mp.avatarLocked || false;
637
- if (!mateAvatarLocked) {
638
- html += '<div class="profile-field">';
639
- html += '<label class="profile-field-label">Identity</label>';
640
- html += '<div class="profile-imprint-panel">';
641
- html += '<div class="profile-imprint-preview"><img src="' + mateMarkUrl(mateName || 'Mate', 72) + '" alt="Clay Mate initial"><span><strong>Mate initial</strong><small>Based on display name</small></span></div>';
642
- html += '<div class="profile-imprint-actions profile-mate-mark-actions">';
643
- html += '<button class="profile-use-imprint' + (!mateCustom ? ' is-active' : '') + '" type="button">Use initial</button>';
644
- html += '<button class="profile-avatar-upload' + (mateCustom ? ' is-active' : '') + '" type="button">Upload image</button>';
645
- html += '</div>';
646
- html += '</div>';
647
- html += '<input type="file" id="mate-profile-avatar-file" accept="image/*" style="display:none">';
648
- html += '</div>';
649
- }
650
-
651
- html += '</div>'; // close body
652
-
653
- matePopoverEl.innerHTML = html;
654
-
655
- // State tracker
656
- var mateProfile = {
657
- displayName: mateName,
658
- avatarStyle: mateStyle,
659
- avatarSeed: mateSeed,
660
- avatarColor: mateColor,
661
- avatarCustom: mateCustom,
662
- };
663
-
664
- function refreshChatAvatars() {
665
- var newUrl = mateAvatarUrl({ profile: mateProfile, id: mateData.id }, 64);
666
- // Update all mate avatars in the chat area
667
- var bubbles = document.querySelectorAll('.dm-bubble-avatar-mate');
668
- for (var i = 0; i < bubbles.length; i++) {
669
- bubbles[i].src = newUrl;
670
- }
671
- // Update body dataset so new messages use the updated URL
672
- if (document.body.dataset.mateAvatarUrl) {
673
- document.body.dataset.mateAvatarUrl = newUrl;
674
- }
675
- // Update mate sidebar avatar
676
- var sidebarAvatar = document.querySelector('.mate-sidebar-avatar');
677
- if (sidebarAvatar) sidebarAvatar.src = newUrl;
678
- }
679
-
680
- function debouncedMateUpdate() {
681
- if (mateSaveTimer) clearTimeout(mateSaveTimer);
682
- mateSaveTimer = setTimeout(function() {
683
- if (onUpdate) onUpdate({
684
- name: mateProfile.displayName,
685
- profile: {
686
- displayName: mateProfile.displayName,
687
- avatarStyle: mateProfile.avatarStyle,
688
- avatarSeed: mateProfile.avatarSeed,
689
- avatarColor: mateProfile.avatarColor,
690
- avatarCustom: mateProfile.avatarCustom,
691
- },
692
- });
693
- refreshChatAvatars();
694
- mateSaveTimer = null;
695
- }, 400);
696
- }
697
-
698
- function updateMatePopoverHeader() {
699
- if (!matePopoverEl) return;
700
- var img = matePopoverEl.querySelector('.profile-popover-avatar-img');
701
- var nd = matePopoverEl.querySelector('.profile-name-display');
702
- if (img) img.src = mateProfile.avatarCustom
703
- ? mateProfile.avatarCustom
704
- : mateMarkUrl(mateProfile.displayName || 'Mate', 80);
705
- var preview = matePopoverEl.querySelector('.profile-imprint-preview img');
706
- if (preview) preview.src = mateMarkUrl(mateProfile.displayName || 'Mate', 72);
707
- if (nd) nd.textContent = mateProfile.displayName || 'New Mate';
708
- }
709
-
710
- // Events
711
- matePopoverEl.querySelector('.profile-close-btn').addEventListener('click', function(e) {
712
- e.stopPropagation();
713
- hideMatePopover();
714
- });
715
-
716
- var nameInput = matePopoverEl.querySelector('#mate-profile-name');
717
- nameInput.addEventListener('input', function() {
718
- mateProfile.displayName = nameInput.value.trim();
719
- updateMatePopoverHeader();
720
- debouncedMateUpdate();
721
- });
722
- nameInput.addEventListener('keydown', function(e) {
723
- if (e.key === 'Enter') { e.preventDefault(); hideMatePopover(); }
724
- e.stopPropagation();
725
- });
726
- nameInput.addEventListener('keyup', function(e) { e.stopPropagation(); });
727
- nameInput.addEventListener('keypress', function(e) { e.stopPropagation(); });
728
-
729
- // Avatar upload button
730
- var mateUploadBtn = matePopoverEl.querySelector('.profile-avatar-upload');
731
- var mateFileInput = matePopoverEl.querySelector('#mate-profile-avatar-file');
732
- if (mateUploadBtn && mateFileInput) {
733
- mateUploadBtn.addEventListener('click', function() {
734
- mateFileInput.click();
735
- });
736
- mateFileInput.addEventListener('change', function() {
737
- var file = mateFileInput.files[0];
738
- if (!file) return;
739
-
740
- function uploadMateBlob(blob) {
741
- blob.arrayBuffer().then(function(ab) {
742
- var buf = new Uint8Array(ab);
743
- fetch('/api/mate-avatar/' + encodeURIComponent(mateData.id), {
744
- method: 'POST',
745
- headers: { 'Content-Type': 'application/octet-stream' },
746
- body: buf,
747
- }).then(function(r) { return r.json(); }).then(function(data) {
748
- if (data.ok) {
749
- mateProfile.avatarCustom = data.avatar;
750
- updateMatePopoverHeader();
751
- if (mateUploadBtn) {
752
- mateUploadBtn.classList.add('is-active');
753
- }
754
- var imprintButton = matePopoverEl && matePopoverEl.querySelector('.profile-use-imprint');
755
- if (imprintButton) imprintButton.classList.remove('is-active');
756
- debouncedMateUpdate();
757
- }
758
- });
759
- });
760
- }
761
-
762
- // Load image and open position picker
763
- var img = new Image();
764
- var objectUrl = URL.createObjectURL(file);
765
- img.onload = function() {
766
- showAvatarPositioner(img, objectUrl, function(croppedBlob) {
767
- URL.revokeObjectURL(objectUrl);
768
- uploadMateBlob(croppedBlob);
769
- });
770
- };
771
- img.src = objectUrl;
772
- });
773
- }
774
-
775
- if (!mateAvatarLocked) {
776
- matePopoverEl.querySelector('.profile-use-imprint').addEventListener('click', function() {
777
- mateProfile.avatarCustom = '';
778
- updateMatePopoverHeader();
779
- matePopoverEl.querySelector('.profile-use-imprint').classList.add('is-active');
780
- matePopoverEl.querySelector('.profile-avatar-upload').classList.remove('is-active');
781
- debouncedMateUpdate();
782
- });
783
- }
784
-
785
- matePopoverEl.addEventListener('click', function(e) { e.stopPropagation(); });
786
-
787
- // Position near anchor
788
- document.body.appendChild(matePopoverEl);
789
- refreshIcons();
790
-
791
- var rect = anchorEl.getBoundingClientRect();
792
- matePopoverEl.style.position = 'fixed';
793
- matePopoverEl.style.left = (rect.right + 8) + 'px';
794
- matePopoverEl.style.zIndex = '9999';
795
- // Align bottom of popover with bottom of anchor icon
796
- var popHeight = matePopoverEl.offsetHeight;
797
- var bottomAligned = rect.bottom - popHeight;
798
- matePopoverEl.style.top = Math.max(8, bottomAligned) + 'px';
799
-
800
- setTimeout(function() {
801
- document.addEventListener('click', closeMateOnOutside);
802
- document.addEventListener('keydown', closeMateOnEscape);
803
- }, 0);
804
- }
805
-
806
- function closeMateOnOutside(e) {
807
- if (matePopoverEl && !matePopoverEl.contains(e.target)) {
808
- hideMatePopover();
809
- }
810
- }
811
-
812
- function closeMateOnEscape(e) {
813
- if (e.key === 'Escape' && matePopoverEl) {
814
- hideMatePopover();
815
- }
816
- }
817
-
818
- function hideMatePopover() {
819
- if (matePopoverEl) {
820
- matePopoverEl.remove();
821
- matePopoverEl = null;
822
- }
823
- document.removeEventListener('click', closeMateOnOutside);
824
- document.removeEventListener('keydown', closeMateOnEscape);
825
- }
@@ -8,6 +8,9 @@ import { switchProject } from './app-projects.js';
8
8
  import { closeTerminal } from './terminal.js';
9
9
  import { closeFileViewer } from './filebrowser.js';
10
10
  import { getActiveMentionMateIds, onMentionActiveChange, bindIconTooltip, hideIconTooltip, showMateCtxMenu } from './sidebar-mates.js';
11
+ import { chooseProjectActivationTarget } from './project-activation.js';
12
+ import { showProjectsHub } from './projects-hub.js';
13
+ import { openProjectSession } from './project-session-navigation.js';
11
14
 
12
15
  var FILTER_MODES = ['all', 'projects', 'mates'];
13
16
  var FILTER_LABELS = { all: 'All', projects: 'Projects', mates: 'Mates' };
@@ -98,9 +101,80 @@ function renderMateHeaderDefault(state) {
98
101
  renderMateProjectHeader(state, mate);
99
102
  }
100
103
 
104
+ function ordinaryProjects(state) {
105
+ return (state.projectsHubList || []).filter(function (project) {
106
+ return project && project.slug && project.isMate !== true;
107
+ });
108
+ }
109
+
110
+ function clayIsActive(state, clay) {
111
+ return !!clay && !state.homeShellVisible && !state.dmMode && state.currentSlug === 'mate-' + clay.id;
112
+ }
113
+
114
+ export function clayReturnTargetForState(state) {
115
+ var target = state && state.clayReturnTarget;
116
+ var recordedSlug = target && target.projectSlug;
117
+ var candidates = recordedSlug ? [recordedSlug] : [];
118
+ var slug = chooseProjectActivationTarget(ordinaryProjects(state), candidates);
119
+ if (!slug) return null;
120
+ var sameProject = slug === recordedSlug;
121
+ return {
122
+ projectSlug: slug,
123
+ sessionId: sameProject && target && Number.isSafeInteger(Number(target.sessionId)) && Number(target.sessionId) > 0 ? Number(target.sessionId) : null,
124
+ };
125
+ }
126
+
127
+ function rememberClayReturnTarget(state) {
128
+ if (!state || state.homeShellVisible || state.dmMode || isMateWorkspace(state) || !state.currentSlug ||
129
+ state.activeProjectSlug !== state.currentSlug || state.sessionActivatedProjectSlug !== state.currentSlug) return;
130
+ var projects = ordinaryProjects(state);
131
+ var found = projects.some(function (project) { return project.slug === state.currentSlug; });
132
+ if (!found) return;
133
+ var target = { projectSlug: state.currentSlug, sessionId: Number.isSafeInteger(Number(state.activeSessionId)) && Number(state.activeSessionId) > 0 ? Number(state.activeSessionId) : null };
134
+ store.set({ clayReturnTarget: target });
135
+ return target;
136
+ }
137
+
138
+ function returnFromClay() {
139
+ var state = store.snap();
140
+ var target = clayReturnTargetForState(state);
141
+ if (!target) {
142
+ store.set({ pendingProjectSessionNavigation: null, clayReturnPendingProjectSlug: null, clayReturnTarget: null });
143
+ showProjectsHub();
144
+ return false;
145
+ }
146
+ store.set({ clayReturnTarget: null, clayReturnPendingProjectSlug: target.sessionId ? target.projectSlug : null });
147
+ if (target.sessionId) openProjectSession(target.projectSlug, target.sessionId);
148
+ else {
149
+ store.set({ pendingProjectSessionNavigation: null });
150
+ switchProject(target.projectSlug);
151
+ }
152
+ return true;
153
+ }
154
+
155
+ function updateClayBrand(state) {
156
+ var entry = document.querySelector('.icon-strip-brand');
157
+ if (!entry) return;
158
+ var clay = navigationMates(state.cachedMatesList).find(function (mate) { return mate.builtinKey === 'clay'; });
159
+ var active = clayIsActive(state, clay);
160
+ var speaking = !!(state.youActivity && state.youActivity.active);
161
+ entry.dataset.clayMode = active ? 'mate' : 'project';
162
+ entry.classList.toggle('clay-speaking', speaking);
163
+ var avatar = entry.querySelector('.clay-brand-avatar');
164
+ var logo = entry.querySelector('.clay-brand-logo');
165
+ if (avatar && clay) avatar.src = mateAvatarUrl(clay, 38);
166
+ if (logo) logo.src = 'clay-studio-symbol.png';
167
+ entry.disabled = !clay;
168
+ entry.classList.toggle('active', active);
169
+ entry.setAttribute('aria-label', clay ? (active ? 'Return to project' : 'Open Clay') : 'Clay is unavailable');
170
+ if (active) entry.setAttribute('aria-current', 'page');
171
+ else entry.removeAttribute('aria-current');
172
+ }
173
+
101
174
  export function openMateWorkspace(mateId) {
102
175
  var mate = navigationMates(store.get('cachedMatesList')).find(function (item) { return item.id === mateId; });
103
176
  if (!mate) return;
177
+ if (mate.builtinKey === 'clay' && store.snap) rememberClayReturnTarget(store.snap());
104
178
  if (store.get('currentSlug') !== 'mate-' + mateId) {
105
179
  closeTerminal();
106
180
  closeFileViewer();
@@ -126,10 +200,14 @@ function createMateIcon(mateId) {
126
200
  avatarUrl: mate ? mateAvatarUrl(mate, 24) : '',
127
201
  };
128
202
  });
129
- button.addEventListener('contextmenu', function (event) {
130
- event.preventDefault();
203
+ function openContextMenu(event) {
204
+ event.preventDefault(); event.stopPropagation(); hideIconTooltip();
131
205
  var mate = navigationMates(store.get('cachedMatesList')).find(function (item) { return item.id === mateId; });
132
206
  if (mate) showMateCtxMenu(button, mate);
207
+ }
208
+ button.addEventListener('contextmenu', openContextMenu);
209
+ button.addEventListener('keydown', function (event) {
210
+ if (event.key === 'ContextMenu' || (event.shiftKey && event.key === 'F10')) openContextMenu(event);
133
211
  });
134
212
  return button;
135
213
  }
@@ -142,14 +220,7 @@ export function renderProjectMates() {
142
220
  var clay = navigationMates(state.cachedMatesList).find(function (mate) { return mate.builtinKey === 'clay'; });
143
221
  var clayActive = !!clay && !state.homeShellVisible && !state.dmMode && state.currentSlug === 'mate-' + clay.id;
144
222
  document.body.classList.toggle('clay-workspace-active', clayActive);
145
- var clayEntry = document.querySelector('.icon-strip-brand');
146
- if (clayEntry) {
147
- clayEntry.disabled = !clay;
148
- clayEntry.classList.toggle('active', clayActive);
149
- clayEntry.setAttribute('aria-label', clay ? 'Open Clay' : 'Clay is unavailable');
150
- if (clayActive) clayEntry.setAttribute('aria-current', 'page');
151
- else clayEntry.removeAttribute('aria-current');
152
- }
223
+ updateClayBrand(state);
153
224
  document.body.classList.toggle('mate-workspace-active', !state.homeShellVisible && isMateWorkspace(state));
154
225
  applyWorkspaceColor(mateForWorkspace(state));
155
226
  renderMateHeaderDefault(state);
@@ -216,19 +287,33 @@ export function initProjectMateNavigation() {
216
287
  var clayEntry = document.querySelector('.icon-strip-brand');
217
288
  if (clayEntry) {
218
289
  clayEntry.addEventListener('click', function () {
219
- var clay = navigationMates(store.get('cachedMatesList')).find(function (mate) { return mate.builtinKey === 'clay'; });
290
+ var state = store.snap();
291
+ var clay = navigationMates(state.cachedMatesList).find(function (mate) { return mate.builtinKey === 'clay'; });
220
292
  hideIconTooltip();
221
- if (clay) openMateWorkspace(clay.id);
293
+ if (!clay) return;
294
+ if (!clayIsActive(state, clay)) {
295
+ openMateWorkspace(clay.id);
296
+ } else returnFromClay();
297
+ });
298
+ bindIconTooltip(clayEntry, function () {
299
+ var state = store.snap();
300
+ var clay = navigationMates(state.cachedMatesList).find(function (mate) { return mate.builtinKey === 'clay'; });
301
+ return { text: clayIsActive(state, clay) ? 'Return to project' : 'Open Clay', kind: 'mate', avatarUrl: 'clay-studio-symbol.png' };
222
302
  });
223
- bindIconTooltip(clayEntry, { text: 'Clay', kind: 'mate', avatarUrl: 'clay-studio-symbol.png' });
224
303
  }
225
304
  var knob = document.getElementById('icon-strip-filter-knob');
226
305
  if (knob) knob.addEventListener('click', activateProjectMateFilter);
227
306
  store.subscribe(function (state, previous) {
307
+ if (state.clayReturnPendingProjectSlug && state.currentSlug !== previous.currentSlug &&
308
+ state.currentSlug !== state.clayReturnPendingProjectSlug) {
309
+ store.set({ pendingProjectSessionNavigation: null, clayReturnPendingProjectSlug: null });
310
+ } else if (state.clayReturnPendingProjectSlug && !state.pendingProjectSessionNavigation && previous.pendingProjectSessionNavigation) {
311
+ store.set({ clayReturnPendingProjectSlug: null });
312
+ }
228
313
  if (state.cachedMatesList !== previous.cachedMatesList || state.homeChatMateId !== previous.homeChatMateId ||
229
314
  state.homeShellVisible !== previous.homeShellVisible || state.projectsHubList !== previous.projectsHubList ||
230
315
  state.dmUnread !== previous.dmUnread || state.currentSlug !== previous.currentSlug ||
231
- state.dmMode !== previous.dmMode || state.connected !== previous.connected) renderProjectMates();
316
+ state.dmMode !== previous.dmMode || state.connected !== previous.connected || state.activeSessionId !== previous.activeSessionId || state.youActivity !== previous.youActivity) renderProjectMates();
232
317
  if (state.projectMateFilterMode !== previous.projectMateFilterMode ||
233
318
  state.projectMateFilterTurns !== previous.projectMateFilterTurns) applyProjectMateFilter();
234
319
  });
@@ -1,3 +1,4 @@
1
+ import { createHomeDebateQuestionCard, applyHomeDebateQuestion, resolveHomeDebateQuestion, markHomeDebateQuestionSubmitting } from './home-debate-planning.js';
1
2
  // Compact Clay conversation hosted inside the global search drawer.
2
3
  import { getWs } from './ws-ref.js';
3
4
  import { openProjectSession } from './project-session-navigation.js';
@@ -131,6 +132,7 @@ function appendActivityList(content, items, expanded, onToggle) {
131
132
  }
132
133
 
133
134
  function renderMessage(message, streaming) {
135
+ if (message.role === "question" && message.flow === "mcp_import") return createHomeDebateQuestionCard(message, respondToImport);
134
136
  var row;
135
137
  if (message.role === "user") {
136
138
  row = createUserBubble({ text: message.text || "" });
@@ -221,6 +223,13 @@ function stopSearchClayChat() {
221
223
  return true;
222
224
  }
223
225
 
226
+ function respondToImport(message, action, opener, answers) {
227
+ if (!state || message.status !== 'pending') return;
228
+ if (!send({ type: 'home_mcp_import_response', mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, toolId: message.toolId, answers: answers || {} })) return;
229
+ markHomeDebateQuestionSubmitting(message, opener);
230
+ store.set({ searchClayChatState: state }); render();
231
+ }
232
+
224
233
  function respondToPermission(permission, decision) {
225
234
  if (!state || permission.status !== "pending") return false;
226
235
  if (!send({ type: "home_mate_permission_response", mateId: state.mateId, sessionId: state.sessionId, requestId: state.requestId, permissionRequestId: permission.permissionRequestId, decision: decision })) return false;
@@ -371,7 +380,12 @@ export function handleSearchClayMessage(msg) {
371
380
  state.updatedAt = Date.now();
372
381
  if (msg.mateId) state.mateId = msg.mateId;
373
382
  if (msg.type === "home_mate_session_identity" && msg.sessionId) state.sessionId = msg.sessionId;
374
- if (msg.type === "home_clay_activity") {
383
+ if (msg.type === 'home_mcp_import_question') {
384
+ if (state.stream) { state.messages.push({ role: 'assistant', text: state.stream }); state.stream = ''; }
385
+ state.messages = applyHomeDebateQuestion(state.messages, msg);
386
+ } else if (msg.type === 'home_mcp_import_question_resolved') {
387
+ state.messages = resolveHomeDebateQuestion(state.messages, msg);
388
+ } else if (msg.type === "home_clay_activity") {
375
389
  if (!state.awaitingCompletion) return true;
376
390
  state.activity = msg.phase || state.activity;
377
391
  state.step = typeof msg.step === "number" ? msg.step : state.step;
@@ -379,7 +393,7 @@ export function handleSearchClayMessage(msg) {
379
393
  } else if (msg.type === "home_mate_history") {
380
394
  state.sessionId = msg.sessionId || state.sessionId;
381
395
  state.localId = typeof msg.localId === "number" ? msg.localId : state.localId;
382
- state.messages = Array.isArray(msg.messages) ? msg.messages.filter(function (item) { return item && (item.role === "user" || item.role === "assistant"); }).map(function (item) { return { role: item.role, text: item.text || "" }; }) : state.messages;
396
+ state.messages = Array.isArray(msg.messages) ? msg.messages.filter(function (item) { return item && (item.role === "user" || item.role === "assistant" || item.role === "question" && item.flow === "mcp_import"); }).map(function (item) { return item.role === "question" ? Object.assign({}, item) : { role: item.role, text: item.text || "" }; }) : state.messages;
383
397
  state.processing = msg.isProcessing === true;
384
398
  state.awaitingCompletion = state.processing;
385
399
  state.pendingTurns = state.processing ? Math.max(1, state.pendingTurns) : 0;
@@ -3,6 +3,7 @@ import { getWs } from './ws-ref.js';
3
3
  import { githubLinkTarget } from './github-links.js';
4
4
  import { iconHtml, refreshIcons } from './icons.js';
5
5
  import { escapeHtml as escapeText } from './utils.js';
6
+ import { primaryWorkPopover, handlePrimaryWorkClick } from './session-work-primary.js';
6
7
 
7
8
  function escapeHtml(value) {
8
9
  return escapeText(String(value)).replace(/"/g, '&quot;').replace(/'/g, '&#39;');
@@ -34,13 +35,16 @@ export function orderedGithubWork(session) {
34
35
  var links = safeLinks(session).slice().reverse();
35
36
  var latestIssue = links.find(function (link) { return link.kind === 'issue' || link.provider === 'linear'; });
36
37
  if (latestIssue) links = [latestIssue].concat(links.filter(function (link) { return link !== latestIssue; }));
37
- return links;
38
+ var primary = session && session.workPrimary || {};
39
+ return links.filter(function (link) { return String(primary[link.provider === 'linear' ? 'linear' : link.kind] || '').toLowerCase() === link.url.toLowerCase(); }).concat(
40
+ links.filter(function (link) { return String(primary[link.provider === 'linear' ? 'linear' : link.kind] || '').toLowerCase() !== link.url.toLowerCase(); }));
38
41
  }
39
42
  function compactGithubWork(links, sessionRow) {
40
43
  if (!sessionRow && !links.some(function (link) { return link.provider === 'linear'; })) return links.slice(0, 1);
41
- var latestIssue = links.find(function (link) { return link.kind === 'issue' || link.provider === 'linear'; });
44
+ var latestLinear = links.find(function (link) { return link.provider === 'linear'; });
45
+ var latestIssue = links.find(function (link) { return link.kind === 'issue' && link.provider !== 'linear'; });
42
46
  var latestPr = links.find(function (link) { return link.kind === 'pr'; });
43
- return [latestIssue, latestPr].filter(Boolean);
47
+ return [latestLinear, latestIssue, latestPr].filter(Boolean);
44
48
  }
45
49
  export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
46
50
  var links = orderedGithubWork(session);
@@ -57,14 +61,20 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
57
61
  var html = anchor(link.url, description, iconHtml('github', 'github-work-brand') + iconHtml(icon, 'github-work-status') + '<span><span class="github-work-kind">' + (link.kind === 'pr' ? 'PR ' : 'Issue ') + '</span>#' + escapeHtml(String(link.number)) + '</span>', 'session-github-link github-kind-' + (link.kind === 'pr' ? 'pr' : 'issue') + ' github-state-' + state);
58
62
  if (expanded) html = '<span class="github-work-entry">' + html + '<span class="github-work-entry-title">' + escapeHtml(link.title || '') + '</span><span class="github-work-entry-meta">' + escapeHtml(link.repository + ' · ' + state + (link.stale ? ' · Last known status' : '')) + '</span></span>';
59
63
  if (detailed && link.kind === 'pr') {
60
- html += anchor(link.url + '/files', 'View PR changes on GitHub', 'Changes', 'github-work-action');
61
- html += anchor(link.url + '#pullrequestreview', 'View PR reviews on GitHub', escapeHtml((link.review || 'Reviews').replace(/_/g, ' ').toLowerCase()), 'github-work-action');
64
+ var actions = anchor(link.url + '/files', 'View PR changes on GitHub', 'Changes', 'github-work-action') +
65
+ anchor(link.url + '#pullrequestreview', 'View PR reviews on GitHub', escapeHtml((link.review || 'Reviews').replace(/_/g, ' ').toLowerCase()), 'github-work-action');
66
+ var checks = '';
62
67
  (link.checks || []).forEach(function (check) {
63
- if (/^https:\/\//i.test(check.url)) html += anchor(check.url, check.name + ': ' + check.state, escapeHtml(check.name + ' · ' + check.state.toLowerCase()), 'github-work-action');
68
+ if (/^https:\/\//i.test(check.url)) checks += anchor(check.url, check.name + ': ' + check.state, '<span class="github-work-check-name">' + escapeHtml(check.name) + '</span><span class="github-work-check-state">' + escapeHtml(check.state.toLowerCase()) + '</span>', 'github-work-action github-work-check');
64
69
  });
70
+ return '<span class="github-work-pr-group" data-github-pr="' + escapeHtml(String(link.number)) + '">' +
71
+ '<span class="github-work-pr-header">' + html + '</span>' +
72
+ '<span class="github-work-pr-actions">' + actions + '</span>' +
73
+ (checks ? '<span class="github-work-pr-checks">' + checks + '</span>' : '') +
74
+ '</span>';
65
75
  }
66
76
  return html;
67
- }).join('') + (!detailed && !expanded && links.length > displayed.length ? '<button type="button" class="github-work-more" aria-label="Show all ' + links.length + ' linked ' + (links.some(function (item) { return item.provider === 'linear'; }) ? 'work items' : 'GitHub items') + '" aria-haspopup="dialog" aria-expanded="false" data-github-links="' + escapeHtml(JSON.stringify(safeLinks(session))) + '">+' + (links.length - displayed.length) + '</button>' : '') + '</span>';
77
+ }).join('') + (!detailed && !expanded && links.length > displayed.length ? '<button type="button" class="github-work-more" aria-label="Show all ' + links.length + ' linked ' + (links.some(function (item) { return item.provider === 'linear'; }) ? 'work items' : 'GitHub items') + '" aria-haspopup="dialog" aria-expanded="false" data-work-session="' + escapeHtml(String(session.id || session.localId || '')) + '" data-work-primary="' + escapeHtml(JSON.stringify(session.workPrimary || {})) + '" data-github-links="' + escapeHtml(JSON.stringify(safeLinks(session))) + '">+' + (links.length - displayed.length) + '</button>' : '') + '</span>';
68
78
  }
69
79
  export function appendGithubWork(row, session, title) {
70
80
  var html = githubWorkMarkup(session, false, false, true);
@@ -130,7 +140,7 @@ export function refreshGithubWork() {
130
140
 
131
141
  export function resetGithubWork() {
132
142
  closeGithubWorkPopover();
133
- store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null });
143
+ store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null, workPrimaryPending: null });
134
144
  var header = document.getElementById('header-github-links');
135
145
  if (header) header.remove();
136
146
  }
@@ -184,6 +194,7 @@ function bindGithubWorkPopover() {
184
194
  if (store.get('githubWorkPopoverBound')) return;
185
195
  store.set({ githubWorkPopoverBound: true });
186
196
  document.addEventListener('click', function (event) {
197
+ if (handlePrimaryWorkClick(event)) return;
187
198
  var linear = event.target.closest('.session-linear-link');
188
199
  if (linear && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) { event.preventDefault(); event.stopPropagation(); closeGithubWorkPopover(); import('./linear-panel.js').then(function (module) { module.openLinear(linear.href); }); return; }
189
200
  var button = event.target.closest('.github-work-more');
@@ -204,7 +215,10 @@ function bindGithubWorkPopover() {
204
215
  popup.setAttribute('role', 'dialog');
205
216
  var workTitle = links.some(function (link) { return link.provider === 'linear'; }) ? 'Linked work' : 'Linked GitHub work';
206
217
  popup.setAttribute('aria-label', workTitle);
207
- popup.innerHTML = '<strong>' + workTitle + '</strong>' + githubWorkMarkup({ githubLinks: links }, false, true);
218
+ popup.dataset.sessionId = button.dataset.workSession || '';
219
+ var primary;
220
+ try { primary = JSON.parse(button.dataset.workPrimary || '{}'); } catch (error) { primary = {}; }
221
+ popup.innerHTML = '<strong>' + workTitle + '</strong>' + primaryWorkPopover(links, primary, popup.dataset.sessionId, githubWorkMarkup);
208
222
  document.body.appendChild(popup);
209
223
  mountGithubWorkPopover(popup, button);
210
224
  button.setAttribute('aria-expanded', 'true');
@@ -0,0 +1,51 @@
1
+ import { store } from './store.js';
2
+ import { getWs } from './ws-ref.js';
3
+ import { escapeHtml } from './utils.js';
4
+ var PRIMARY_PIN = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path class="work-primary-pin-head" d="M16 9V4l1-1V2H7v1l1 1v5l-2 3v2h12v-2z"/><path d="M12 14v8"/></svg>';
5
+ function attr(value) { return escapeHtml(String(value)).replace(/"/g, '&quot;').replace(/'/g, '&#39;'); }
6
+ export function primaryWorkPopover(links, primary, sessionId, render) {
7
+ var html = ['linear', 'pr', 'issue'].map(function (type) {
8
+ var items = links.filter(function (link) { return (link.provider === 'linear' ? 'linear' : link.kind) === type; }).reverse();
9
+ if (!items.length) return '';
10
+ return '<section class="work-primary-group"><h3>' + ({ linear: 'Linear issues', pr: 'Pull requests', issue: 'GitHub issues' })[type] + '</h3>' + items.map(function (link) {
11
+ var selected = String(primary[type] || '').toLowerCase() === link.url.toLowerCase();
12
+ return '<div class="work-primary-row">' + render({ githubLinks: [link] }, false, true) +
13
+ (sessionId ? '<button type="button" class="work-primary-choice" data-primary-url="' + attr(link.url) + '" aria-label="' + attr((selected ? 'Primary: ' : 'Make primary: ') + (link.identifier || (type === 'pr' ? 'PR #' : 'Issue #') + link.number)) + '" title="' + (selected ? 'Primary' : 'Make primary') + '" aria-pressed="' + selected + '"' + (selected ? ' disabled' : '') + '>' + PRIMARY_PIN + '</button>' : '') + '</div>';
14
+ }).join('') + '</section>';
15
+ }).join('');
16
+ return html + '<p class="work-primary-feedback" role="status" aria-live="polite"></p>';
17
+ }
18
+ export function handlePrimaryWorkClick(event) {
19
+ var button = event.target.closest('.work-primary-choice');
20
+ if (!button) return false;
21
+ event.preventDefault(); event.stopPropagation();
22
+ var popup = button.closest('#github-work-popover'), ws = getWs();
23
+ if (!popup) return true;
24
+ var feedback = popup.querySelector('.work-primary-feedback');
25
+ if (!ws || ws.readyState !== 1) { feedback.textContent = 'Reconnect to save your selection.'; return true; }
26
+ var requestId = 'work-primary-' + Date.now() + '-' + Math.random().toString(36).slice(2);
27
+ store.set({ workPrimaryPending: { requestId: requestId, sessionId: Number(popup.dataset.sessionId), projectSlug: store.get('currentSlug') } });
28
+ popup.querySelectorAll('.work-primary-choice').forEach(function (control) { control.disabled = true; });
29
+ feedback.textContent = 'Saving…';
30
+ var projectSlug = store.get('currentSlug');
31
+ setTimeout(function () {
32
+ handlePrimaryWorkResult({ requestId: requestId, projectSlug: projectSlug, error: 'Save confirmation did not arrive. Reopen the list to check, or retry.' });
33
+ }, 15000);
34
+ try { ws.send(JSON.stringify({ type: 'session_work_primary_set', requestId: requestId, sessionId: Number(popup.dataset.sessionId), url: button.dataset.primaryUrl })); }
35
+ catch (error) { handlePrimaryWorkResult({ requestId: requestId, projectSlug: store.get('currentSlug'), error: 'Could not send your selection. Please retry.' }); }
36
+ return true;
37
+ }
38
+ export function handlePrimaryWorkResult(msg) {
39
+ var pending = store.get('workPrimaryPending');
40
+ if (!pending || msg.requestId !== pending.requestId || msg.projectSlug !== pending.projectSlug) return;
41
+ store.set({ workPrimaryPending: null });
42
+ var popup = document.getElementById('github-work-popover');
43
+ if (!popup || Number(popup.dataset.sessionId) !== pending.sessionId || store.get('currentSlug') !== pending.projectSlug) return;
44
+ popup.querySelectorAll('.work-primary-choice').forEach(function (button) {
45
+ var selected = msg.error ? button.getAttribute('aria-pressed') === 'true' : Object.values(msg.workPrimary || {}).some(function (url) { return url.toLowerCase() === button.dataset.primaryUrl.toLowerCase(); });
46
+ button.setAttribute('aria-pressed', String(selected)); button.disabled = selected;
47
+ button.title = selected ? 'Primary' : 'Make primary';
48
+ button.setAttribute('aria-label', (selected ? 'Primary: ' : 'Make primary: ') + button.getAttribute('aria-label').replace(/^(Primary|Make primary): /, ''));
49
+ });
50
+ popup.querySelector('.work-primary-feedback').textContent = msg.error || 'Primary saved.';
51
+ }