clay-server 5.0.2-beta.1 → 5.1.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 (162) hide show
  1. package/LICENSES/README.md +4 -0
  2. package/LICENSES/dicebear-avatars.md +16 -0
  3. package/lib/daemon-project-access.js +7 -1
  4. package/lib/daemon.js +2 -0
  5. package/lib/dicebear-avatar.js +62 -0
  6. package/lib/knowledge-db-service.js +175 -0
  7. package/lib/knowledge-db-validation.js +0 -0
  8. package/lib/knowledge-document-index.js +49 -0
  9. package/lib/knowledge-draft-store.js +56 -0
  10. package/lib/knowledge-file-store.js +199 -0
  11. package/lib/knowledge-markdown.js +128 -0
  12. package/lib/mate-knowledge-mcp-server.js +29 -6
  13. package/lib/mate-knowledge-service.js +23 -2
  14. package/lib/mate-knowledge-sync.js +36 -21
  15. package/lib/mate-workspace-color.js +4 -0
  16. package/lib/mates.js +18 -0
  17. package/lib/project-connection.js +36 -2
  18. package/lib/project-debate-proposal.js +6 -0
  19. package/lib/project-debate-workspace.js +43 -0
  20. package/lib/project-debate.js +1 -0
  21. package/lib/project-file-path.js +2 -3
  22. package/lib/project-knowledge.js +315 -136
  23. package/lib/project-mate-creation-proposal.js +1 -0
  24. package/lib/project-request-access.js +13 -1
  25. package/lib/project-sessions.js +39 -2
  26. package/lib/project-user-knowledge.js +60 -0
  27. package/lib/project.js +59 -18
  28. package/lib/public/app.js +73 -34
  29. package/lib/public/clay-studio-wordmark-bold.svg +20 -0
  30. package/lib/public/css/clay-workspace.css +105 -0
  31. package/lib/public/css/debates-workbench.css +47 -0
  32. package/lib/public/css/filebrowser.css +10 -0
  33. package/lib/public/css/home-debate-live.css +26 -0
  34. package/lib/public/css/home-hub.css +3 -1
  35. package/lib/public/css/home-mate-model-picker.css +15 -131
  36. package/lib/public/css/home-mate-settings.css +168 -16
  37. package/lib/public/css/icon-strip.css +249 -32
  38. package/lib/public/css/mate-conversation.css +176 -0
  39. package/lib/public/css/mate-knowledge-workbench.css +118 -0
  40. package/lib/public/css/mate-workspace-color.css +16 -0
  41. package/lib/public/css/mates.css +28 -28
  42. package/lib/public/css/messages.css +1 -114
  43. package/lib/public/css/mobile-nav.css +16 -16
  44. package/lib/public/css/overlays.css +8 -0
  45. package/lib/public/css/project-logs.css +7 -0
  46. package/lib/public/css/project-mate-creation.css +105 -0
  47. package/lib/public/css/right-workbench.css +3 -3
  48. package/lib/public/css/session-folders.css +80 -21
  49. package/lib/public/css/sidebar.css +183 -107
  50. package/lib/public/css/title-bar.css +151 -0
  51. package/lib/public/css/worker-delegation.css +47 -0
  52. package/lib/public/css/worker-proposal.css +72 -150
  53. package/lib/public/css/you.css +74 -0
  54. package/lib/public/index.html +33 -101
  55. package/lib/public/modules/account-island.js +230 -0
  56. package/lib/public/modules/app-cursors.js +19 -43
  57. package/lib/public/modules/app-debate-ui.js +4 -3
  58. package/lib/public/modules/app-header.js +106 -0
  59. package/lib/public/modules/app-message-router.js +12 -3
  60. package/lib/public/modules/app-messages.js +34 -9
  61. package/lib/public/modules/app-notifications.js +4 -7
  62. package/lib/public/modules/app-projects.js +5 -38
  63. package/lib/public/modules/app-rendering.js +12 -13
  64. package/lib/public/modules/avatar.js +25 -9
  65. package/lib/public/modules/chat-render-runtime.js +16 -4
  66. package/lib/public/modules/command-palette.js +2 -15
  67. package/lib/public/modules/cursor-sharing-preference.js +3 -3
  68. package/lib/public/modules/debate-controls.js +299 -0
  69. package/lib/public/modules/debate.js +2 -0
  70. package/lib/public/modules/debates-workbench.js +339 -0
  71. package/lib/public/modules/default-ai.js +81 -65
  72. package/lib/public/modules/filebrowser.js +28 -8
  73. package/lib/public/modules/home-debate-controls.js +18 -289
  74. package/lib/public/modules/home-mate-avatar-editor.js +228 -0
  75. package/lib/public/modules/home-mate-color-picker.js +66 -0
  76. package/lib/public/modules/home-mate-creation.js +1 -1
  77. package/lib/public/modules/home-mate-instructions.js +90 -0
  78. package/lib/public/modules/home-mate-model-picker.js +80 -271
  79. package/lib/public/modules/home-mate-profile-editor.js +72 -0
  80. package/lib/public/modules/home-mate-settings.js +118 -125
  81. package/lib/public/modules/home-shell.js +3 -0
  82. package/lib/public/modules/home-surface-boot.js +1 -9
  83. package/lib/public/modules/html-preview.js +19 -0
  84. package/lib/public/modules/icon-strip-tooltip.js +95 -0
  85. package/lib/public/modules/knowledge-db-edit-state.js +35 -0
  86. package/lib/public/modules/knowledge-db-workbench.js +234 -0
  87. package/lib/public/modules/knowledge-history-state.js +55 -0
  88. package/lib/public/modules/knowledge-markdown-editor.js +57 -0
  89. package/lib/public/modules/knowledge-preview-links.js +70 -0
  90. package/lib/public/modules/knowledge-workbench-shell.js +21 -0
  91. package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
  92. package/lib/public/modules/mate-knowledge-workbench.js +500 -0
  93. package/lib/public/modules/mate-knowledge.js +11 -3
  94. package/lib/public/modules/mate-management.js +54 -10
  95. package/lib/public/modules/mate-sidebar.js +11 -1
  96. package/lib/public/modules/mate-workspace-color.js +33 -0
  97. package/lib/public/modules/mention.js +5 -13
  98. package/lib/public/modules/profile.js +51 -18
  99. package/lib/public/modules/project-chat-identity.js +79 -0
  100. package/lib/public/modules/project-debate-planning-workspace.js +250 -0
  101. package/lib/public/modules/project-debate-workspace.js +185 -0
  102. package/lib/public/modules/project-header-identity.js +127 -0
  103. package/lib/public/modules/project-logs-render.js +9 -4
  104. package/lib/public/modules/project-mate-creation-workspace.js +414 -0
  105. package/lib/public/modules/project-mate-navigation.js +238 -0
  106. package/lib/public/modules/project-session-navigation.js +29 -0
  107. package/lib/public/modules/project-switcher.js +6 -8
  108. package/lib/public/modules/projects-hub-view.js +5 -6
  109. package/lib/public/modules/projects-hub.js +2 -6
  110. package/lib/public/modules/qrcode.js +27 -8
  111. package/lib/public/modules/search-clay-chat.js +8 -11
  112. package/lib/public/modules/session-activity-tooltip.js +12 -0
  113. package/lib/public/modules/session-create-form.js +86 -14
  114. package/lib/public/modules/session-create.js +50 -7
  115. package/lib/public/modules/session-list-visibility.js +41 -0
  116. package/lib/public/modules/session-presence-ui.js +4 -1
  117. package/lib/public/modules/session-title-marquee.js +51 -0
  118. package/lib/public/modules/sidebar-mates.js +3 -50
  119. package/lib/public/modules/sidebar-mobile.js +63 -60
  120. package/lib/public/modules/sidebar-projects.js +84 -97
  121. package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
  122. package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
  123. package/lib/public/modules/sidebar-sessions.js +49 -50
  124. package/lib/public/modules/sidebar.js +11 -13
  125. package/lib/public/modules/thinking-lifecycle.js +7 -6
  126. package/lib/public/modules/tool-palette-order.js +4 -3
  127. package/lib/public/modules/tool-palette.js +13 -3
  128. package/lib/public/modules/tools.js +22 -22
  129. package/lib/public/modules/worker-proposal.js +42 -8
  130. package/lib/public/modules/worker-runtime-picker.js +115 -0
  131. package/lib/public/modules/you-activity.js +121 -0
  132. package/lib/public/modules/you.js +251 -0
  133. package/lib/public/style.css +16 -8
  134. package/lib/public/vendor/codemirror/LICENSE +21 -0
  135. package/lib/public/vendor/codemirror/codemirror.css +344 -0
  136. package/lib/public/vendor/codemirror/codemirror.js +9884 -0
  137. package/lib/public/vendor/codemirror/continuelist.js +101 -0
  138. package/lib/public/vendor/codemirror/markdown.js +886 -0
  139. package/lib/public/vendor/codemirror/show-hint.css +37 -0
  140. package/lib/public/vendor/codemirror/show-hint.js +523 -0
  141. package/lib/sdk-bridge.js +15 -3
  142. package/lib/server-admin.js +11 -0
  143. package/lib/server-home-chat-events.js +1 -1
  144. package/lib/server-home-chat.js +25 -3
  145. package/lib/server-home-debate-planning.js +4 -1
  146. package/lib/server-home-debates.js +2 -0
  147. package/lib/server-home-mate-creation.js +4 -1
  148. package/lib/server-mate-instructions.js +44 -0
  149. package/lib/server-mates.js +12 -3
  150. package/lib/server-palette.js +1 -1
  151. package/lib/server-settings.js +65 -1
  152. package/lib/server-user-knowledge.js +119 -0
  153. package/lib/server.js +21 -1
  154. package/lib/session-visibility.js +7 -2
  155. package/lib/sessions.js +32 -6
  156. package/lib/user-knowledge-service.js +167 -0
  157. package/lib/users-permissions.js +7 -0
  158. package/lib/users-project-mate-filter-preferences.js +39 -0
  159. package/lib/users.js +6 -0
  160. package/lib/workspace-query-access.js +4 -1
  161. package/lib/ws-schema.js +50 -0
  162. package/package.json +10 -1
@@ -0,0 +1,185 @@
1
+ // Adapt project events to the shared Home debate presentation.
2
+ import { store } from './store.js';
3
+ import { getWs } from './ws-ref.js';
4
+ import { getMessagesEl } from './dom-refs.js';
5
+ import { addToMessages, scrollToBottom } from './chat-render-runtime.js';
6
+ import { isMateWorkspace } from './project-mate-navigation.js';
7
+ import { applyHomeDebateEvent, createHomeDebateLiveCard } from './home-debate-live.js';
8
+ import { createHomeDebateProposalCard, applyHomeDebateProposal, resolveHomeDebateProposal } from './home-debate-planning.js';
9
+ import { createDebateControls } from './debate-controls.js';
10
+ import { renderAssistantBubbleText, finalizeAssistantBubble, disposeChatBubbleTree } from './chat-bubble-renderer.js';
11
+
12
+ var transcriptEl = null;
13
+ var prependEl = null;
14
+ var controls = createDebateControls({
15
+ slotId: 'project-debate-controls',
16
+ composerId: 'input-area',
17
+ send: function (action, data, sessionId) {
18
+ var ws = getWs();
19
+ if (!ws || ws.readyState !== 1 || String(store.get('activeSessionId')) !== sessionId) return false;
20
+ ws.send(JSON.stringify({ type: 'project_debate_control', sessionId: store.get('activeSessionId'), action: action, text: data.text || '', response: data.response || null }));
21
+ return true;
22
+ }
23
+ });
24
+
25
+ function stateKey() {
26
+ return store.get('projectDebatePrepending') ? 'projectDebatePrependMessages' : 'projectDebateMessages';
27
+ }
28
+
29
+ function ensureTranscript() {
30
+ var prepending = store.get('projectDebatePrepending');
31
+ var root = prepending ? prependEl : transcriptEl;
32
+ if (!root || !root.isConnected) {
33
+ root = document.createElement('section');
34
+ root.className = 'project-debate-transcript home-mate-chat-transcript home-chat-bubble-layout';
35
+ root.setAttribute('aria-label', 'Debate transcript');
36
+ addToMessages(root);
37
+ if (prepending) prependEl = root;
38
+ else transcriptEl = root;
39
+ }
40
+ return root;
41
+ }
42
+
43
+ function renderControls() {
44
+ var slot = document.getElementById('project-debate-controls');
45
+ var composer = document.getElementById('input-area');
46
+ if (!slot && composer) {
47
+ slot = document.createElement('div');
48
+ slot.id = 'project-debate-controls';
49
+ slot.className = 'home-debate-controls-slot project-debate-controls';
50
+ slot.hidden = true;
51
+ composer.parentNode.insertBefore(slot, composer);
52
+ }
53
+ controls.render(store.get('projectDebateMessages') || [], String(store.get('activeSessionId') || ''));
54
+ }
55
+
56
+ function respondToProposal(message, action, opener, answers, modelOverrides) {
57
+ var ws = getWs();
58
+ if (!ws || ws.readyState !== 1 || message.status === 'submitting') return;
59
+ ws.send(JSON.stringify({ type: 'debate_proposal_response', proposalId: message.proposal.proposalId, sessionId: store.get('activeSessionId'), action: action, modelOverrides: modelOverrides || [] }));
60
+ var messages = (store.get('projectDebateMessages') || []).map(function (item) {
61
+ return item === message ? Object.assign({}, item, { status: 'submitting' }) : item;
62
+ });
63
+ store.set({ projectDebateMessages: messages });
64
+ renderMessages(messages);
65
+ }
66
+
67
+ function renderMessages(messages) {
68
+ var root = ensureTranscript();
69
+ messages.forEach(function (message, index) {
70
+ var existing = root.children[index];
71
+ if (existing && existing._debateMessage === message) return;
72
+ if (existing && message.role === 'debate_turn' && existing.classList.contains('home-debate-live-turn')) {
73
+ var activity = existing.querySelector('.home-debate-live-activity');
74
+ if (message.status === 'done') {
75
+ if (activity) activity.remove();
76
+ finalizeAssistantBubble(existing, message.text || '', !store.get('replayingHistory'));
77
+ } else {
78
+ if (activity) {
79
+ var label = message.activity || (message.text ? 'Speaking' : 'Preparing');
80
+ activity.firstChild.textContent = label;
81
+ activity.setAttribute('aria-label', (message.mateName || 'Mate') + ' is ' + label.toLowerCase());
82
+ }
83
+ renderAssistantBubbleText(existing, message.text || '', false);
84
+ }
85
+ existing._debateMessage = message;
86
+ return;
87
+ }
88
+ var card = message.role === 'proposal'
89
+ ? createHomeDebateProposalCard(message, respondToProposal)
90
+ : createHomeDebateLiveCard(message, message.status === 'done');
91
+ card._debateMessage = message;
92
+ if (existing) { disposeChatBubbleTree(existing); existing.replaceWith(card); }
93
+ else root.appendChild(card);
94
+ });
95
+ }
96
+
97
+ export function resetProjectDebateWorkspace() {
98
+ store.set({ projectDebateMessages: [], projectDebatePrependMessages: [], projectDebatePrepending: false });
99
+ transcriptEl = null;
100
+ prependEl = null;
101
+ controls.render([], null);
102
+ }
103
+
104
+ export function beginProjectDebatePrepend() {
105
+ prependEl = null;
106
+ store.set({ projectDebatePrepending: true, projectDebatePrependMessages: [] });
107
+ }
108
+
109
+ export function endProjectDebatePrepend() {
110
+ prependEl = null;
111
+ store.set({ projectDebatePrepending: false, projectDebatePrependMessages: [] });
112
+ }
113
+
114
+ export function handleProjectDebateMessage(msg) {
115
+ if (!isMateWorkspace(store.snap())) return false;
116
+ if (msg.type === 'project_debate_control_result') {
117
+ if (String(msg.sessionId) !== String(store.get('activeSessionId'))) return true;
118
+ if (!msg.ok) {
119
+ controls.render([], null);
120
+ renderControls();
121
+ var slot = document.getElementById('project-debate-controls');
122
+ var error = document.createElement('p');
123
+ error.className = 'project-debate-error';
124
+ error.setAttribute('role', 'alert');
125
+ error.textContent = msg.error || 'The debate action could not be completed.';
126
+ if (slot) slot.appendChild(error);
127
+ }
128
+ return true;
129
+ }
130
+ if (msg.type === 'history_done') {
131
+ renderControls();
132
+ var ws = getWs();
133
+ if (ws && ws.readyState === 1 && (store.get('projectDebateMessages') || []).length) {
134
+ ws.send(JSON.stringify({ type: 'project_debate_state', sessionId: store.get('activeSessionId') }));
135
+ }
136
+ return false;
137
+ }
138
+ if (msg.type === 'project_debate_state') {
139
+ if (String(msg.sessionId) !== String(store.get('activeSessionId')) || !msg.header) return true;
140
+ var restored = (store.get('projectDebateMessages') || []).slice();
141
+ var headerIndex = -1;
142
+ restored.forEach(function (item, index) { if (item.role === 'debate_header') headerIndex = index; });
143
+ if (headerIndex === -1) restored.unshift(msg.header);
144
+ else restored[headerIndex] = msg.header;
145
+ if (msg.header.phase !== 'live') restored = restored.map(function (item) {
146
+ return item.role === 'debate_turn' && item.status === 'active' ? Object.assign({}, item, { status: 'done', activity: '' }) : item;
147
+ });
148
+ store.set({ projectDebateMessages: restored });
149
+ renderMessages(restored);
150
+ renderControls();
151
+ return true;
152
+ }
153
+ if (msg.type === 'debate_preparing') {
154
+ var root = ensureTranscript();
155
+ var preparation = document.createElement('p');
156
+ preparation.className = 'debate-preparing-indicator home-debate-live-status';
157
+ preparation.setAttribute('role', 'status');
158
+ preparation.textContent = (msg.moderatorName || 'The moderator') + ' is preparing the debate…';
159
+ root.appendChild(preparation);
160
+ return true;
161
+ }
162
+ if (msg.type === 'tool_executing' && msg.name && msg.name.indexOf('propose_debate') !== -1) return true;
163
+ var types = ['debate_started', 'debate_turn', 'debate_activity', 'debate_stream', 'debate_turn_done', 'debate_tool_decision', 'debate_stop_requested', 'debate_stop_cancelled', 'debate_hand_raised', 'debate_conclude_confirm', 'debate_user_floor', 'debate_user_floor_done', 'debate_comment_injected', 'debate_user_resume', 'debate_resumed', 'debate_ended', 'debate_proposal', 'debate_proposal_resolved'];
164
+ if (types.indexOf(msg.type) === -1) return false;
165
+ var key = stateKey();
166
+ var messages = store.get(key) || [];
167
+ if (msg.type === 'debate_tool_decision') messages = messages.map(function (item) {
168
+ return item.role === 'debate_tool_decision' ? Object.assign({}, item, { entries: (item.entries || []).slice() }) : item;
169
+ });
170
+ if (msg.type === 'debate_proposal') messages = applyHomeDebateProposal(messages, msg);
171
+ else if (msg.type === 'debate_proposal_resolved') messages = resolveHomeDebateProposal(messages, msg);
172
+ else messages = applyHomeDebateEvent(messages, Object.assign({}, msg, { eventType: msg.type, speakerMateId: msg.mateId }));
173
+ if (msg.type === 'debate_ended') messages = messages.map(function (item) {
174
+ return item.role === 'debate_turn' && item.status === 'active' ? Object.assign({}, item, { status: 'done', activity: '' }) : item;
175
+ });
176
+ var patch = {};
177
+ patch[key] = messages;
178
+ store.set(patch);
179
+ var preparation = getMessagesEl().querySelector('.debate-preparing-indicator');
180
+ if (preparation) preparation.remove();
181
+ renderMessages(messages);
182
+ if (!store.get('projectDebatePrepending') && !store.get('replayingHistory')) renderControls();
183
+ scrollToBottom();
184
+ return true;
185
+ }
@@ -0,0 +1,127 @@
1
+ // Single renderer for ordinary-project and Mate identities in the sidebar header.
2
+
3
+ import { mateAvatarUrl } from './avatar.js';
4
+ import { VENDOR_AVATARS, VENDOR_NAMES } from './chat-render-runtime.js';
5
+ import { getHomeMateBio } from './home-mate-selection.js';
6
+ import { parseEmojis } from './markdown.js';
7
+ import { refreshIcons } from './icons.js';
8
+
9
+ function projectForState(state) {
10
+ var projects = state.projectsHubList || [];
11
+ for (var i = 0; i < projects.length; i++) {
12
+ if (projects[i] && projects[i].slug === state.currentSlug) return projects[i];
13
+ }
14
+ return null;
15
+ }
16
+
17
+ function mateName(mate) {
18
+ var profile = mate && mate.profile ? mate.profile : {};
19
+ return profile.displayName || (mate && (mate.displayName || mate.name)) || 'Mate';
20
+ }
21
+
22
+ export function isMateHeaderWorkspace(state) {
23
+ var projects = state.projectsHubList || [];
24
+ for (var i = 0; i < projects.length; i++) {
25
+ if (projects[i] && projects[i].slug === state.currentSlug && projects[i].isMate === true) return true;
26
+ }
27
+ var mates = state.cachedMatesList || [];
28
+ for (var j = 0; j < mates.length; j++) {
29
+ if (mates[j] && state.currentSlug === 'mate-' + mates[j].id) return true;
30
+ }
31
+ return false;
32
+ }
33
+
34
+ function clearMateIdentity(icon, dropdown) {
35
+ icon.classList.remove('is-mate-avatar');
36
+ icon.classList.remove('is-clay-identity');
37
+ icon.removeAttribute('data-vendor');
38
+ delete dropdown.dataset.mateDefaults;
39
+ delete dropdown.dataset.mateBio;
40
+ delete dropdown.dataset.clayIdentity;
41
+ dropdown.removeAttribute('aria-label');
42
+ dropdown.removeAttribute('title');
43
+ }
44
+
45
+ export function renderOrdinaryProjectHeader(state) {
46
+ var dropdown = document.getElementById('title-bar-project-dropdown');
47
+ var icon = document.getElementById('title-bar-project-icon');
48
+ var name = document.getElementById('title-bar-project-name');
49
+ var detail = document.getElementById('title-bar-project-default');
50
+ if (!dropdown || !icon || !name || !detail) return false;
51
+ var project = projectForState(state);
52
+ clearMateIdentity(icon, dropdown);
53
+ detail.textContent = '';
54
+ icon.textContent = '';
55
+ var projectIcon = project && project.icon ? project.icon : '';
56
+ if (projectIcon) {
57
+ icon.textContent = projectIcon;
58
+ icon.classList.add('has-icon');
59
+ parseEmojis(icon);
60
+ } else {
61
+ icon.classList.remove('has-icon');
62
+ }
63
+ if (project) name.textContent = project.title || project.project || project.name || state.projectName || '';
64
+ return !!project;
65
+ }
66
+
67
+ export function renderMateProjectHeader(state, mate) {
68
+ var dropdown = document.getElementById('title-bar-project-dropdown');
69
+ var icon = document.getElementById('title-bar-project-icon');
70
+ var name = document.getElementById('title-bar-project-name');
71
+ var detail = document.getElementById('title-bar-project-default');
72
+ if (!dropdown || !icon || !name || !detail || !mate || state.homeShellVisible) return false;
73
+ var displayName = mateName(mate);
74
+ var vendor = mate.vendor || '';
75
+ var vendorName = VENDOR_NAMES[vendor] || vendor || 'Default vendor';
76
+ var bio = getHomeMateBio(mate).replace(/\s+/g, ' ').trim();
77
+ var isClay = mate.builtinKey === 'clay';
78
+ icon.textContent = '';
79
+ icon.classList.add('has-icon');
80
+ icon.classList.toggle('is-mate-avatar', !isClay);
81
+ icon.classList.toggle('is-clay-identity', isClay);
82
+ if (vendor) icon.dataset.vendor = vendor;
83
+ else icon.removeAttribute('data-vendor');
84
+ if (!isClay) {
85
+ var avatar = document.createElement('img');
86
+ avatar.className = 'title-bar-mate-avatar';
87
+ avatar.src = mateAvatarUrl(mate, 56);
88
+ avatar.alt = '';
89
+ avatar.draggable = false;
90
+ icon.appendChild(avatar);
91
+ }
92
+ if (!isClay && vendor && VENDOR_AVATARS[vendor]) {
93
+ var badge = document.createElement('img');
94
+ badge.className = 'title-bar-mate-vendor';
95
+ badge.src = VENDOR_AVATARS[vendor];
96
+ badge.alt = '';
97
+ badge.draggable = false;
98
+ badge.title = vendorName;
99
+ icon.appendChild(badge);
100
+ }
101
+ name.textContent = displayName;
102
+ detail.textContent = bio;
103
+ if (isClay) {
104
+ detail.textContent = '';
105
+ var badge = document.createElement('span');
106
+ badge.className = 'clay-role-badge';
107
+ var insignia = document.createElement('i');
108
+ insignia.className = 'clay-role-insignia';
109
+ insignia.setAttribute('data-lucide', 'crown');
110
+ insignia.setAttribute('aria-hidden', 'true');
111
+ var label = document.createElement('span');
112
+ label.className = 'clay-role-label';
113
+ label.textContent = 'Lead Mate';
114
+ badge.appendChild(insignia);
115
+ badge.appendChild(label);
116
+ detail.appendChild(badge);
117
+ refreshIcons();
118
+ }
119
+ dropdown.dataset.mateDefaults = 'true';
120
+ delete dropdown.dataset.clayIdentity;
121
+ delete dropdown.dataset.mateBio;
122
+ if (isClay) dropdown.dataset.clayIdentity = 'true';
123
+ else if (bio) dropdown.dataset.mateBio = 'true';
124
+ dropdown.setAttribute('aria-label', 'Open Mate settings for ' + displayName);
125
+ dropdown.title = 'Open Mate settings';
126
+ return true;
127
+ }
@@ -206,7 +206,7 @@ export function renderList(listEl, entries, onOpen, emptyState) {
206
206
 
207
207
  // --- Detail --------------------------------------------------------------
208
208
 
209
- function renderComments(entry) {
209
+ function renderComments(entry, handlers) {
210
210
  var wrap = document.createElement("section");
211
211
  wrap.className = "project-log-discussion";
212
212
  var heading = document.createElement("h2");
@@ -242,6 +242,7 @@ function renderComments(entry) {
242
242
  badge.className = "project-log-comment-status-badge";
243
243
  badge.dataset.status = status;
244
244
  badge.textContent = COMMENT_STATUS_LABELS[status] || status;
245
+ if (handlers && handlers.commentReviewer && status === "pending") badge.textContent = "Awaiting " + handlers.commentReviewer + " review";
245
246
  item.appendChild(badge);
246
247
 
247
248
  // The Driver's decision sits under the comment it answers.
@@ -251,6 +252,7 @@ function renderComments(entry) {
251
252
  var responseMeta = document.createElement("p");
252
253
  responseMeta.className = "project-log-comment-response-meta";
253
254
  var parts = ["Project Driver"];
255
+ if (handlers && handlers.commentReviewer) parts[0] = handlers.commentReviewer;
254
256
  if (comment.review.at) parts.push(relativeTime(comment.review.at));
255
257
  if (comment.review.revision) parts.push("revision " + comment.review.revision);
256
258
  responseMeta.textContent = parts.join(" · ");
@@ -391,19 +393,22 @@ export function renderDetail(detailEl, entry, handlers) {
391
393
 
392
394
  var history = renderHistory(entry);
393
395
  if (history) article.appendChild(history);
394
- article.appendChild(renderComments(entry));
396
+ if (handlers && handlers.readOnly) { detailEl.appendChild(article); refreshIcons(); return; }
397
+ article.appendChild(renderComments(entry, handlers));
395
398
 
396
399
  var form = document.createElement("form");
397
400
  form.className = "project-log-comment-form";
401
+ var commentPrefix = handlers.commentPrefix || "project-log";
398
402
  form.innerHTML =
399
403
  '<label for="project-log-comment-input">Add a comment</label>' +
400
404
  '<textarea id="project-log-comment-input" rows="3" required placeholder="Add context, a correction, or a question."></textarea>' +
401
405
  '<p id="project-log-comment-status" class="project-log-comment-status" role="status"></p>' +
402
406
  '<div class="project-log-comment-actions"><button class="primary" type="submit">Comment</button></div>';
407
+ form.innerHTML = form.innerHTML.replaceAll("project-log-comment-input", commentPrefix + "-comment-input").replaceAll('id="project-log-comment-status"', 'id="' + commentPrefix + '-comment-status"');
403
408
  form.addEventListener("submit", function (event) {
404
409
  event.preventDefault();
405
- var input = form.querySelector("#project-log-comment-input");
406
- var status = form.querySelector("#project-log-comment-status");
410
+ var input = form.querySelector("textarea");
411
+ var status = form.querySelector(".project-log-comment-status");
407
412
  var value = input.value.trim();
408
413
  if (!value) { status.textContent = "A comment cannot be empty."; return; }
409
414
  handlers.onComment(entry.ref, value, status, input);