clay-server 2.26.0-beta.8 → 2.26.0

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.
package/lib/public/app.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { avatarUrl, userAvatarUrl, mateAvatarUrl } from './modules/avatar.js';
2
2
  import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
3
- import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
3
+ import { refreshIcons, iconHtml } from './modules/icons.js';
4
4
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
5
- import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles, openMobileSheet, setMobileSheetMateData } from './modules/sidebar.js';
5
+ import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles, openMobileSheet, setMobileSheetMateData, refreshMobileChatSheet } from './modules/sidebar.js';
6
6
  import { initMateSidebar, showMateSidebar, hideMateSidebar, renderMateSessionList, updateMateSidebarProfile, handleMateSearchResults } from './modules/mate-sidebar.js';
7
7
  import { initMateKnowledge, requestKnowledgeList, renderKnowledgeList, handleKnowledgeContent, hideKnowledge } from './modules/mate-knowledge.js';
8
8
  import { initMateMemory, renderMemoryList, hideMemory } from './modules/mate-memory.js';
@@ -14,7 +14,7 @@ import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFs
14
14
  import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermResized, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
15
15
  import { initContextSources, updateTerminalList, updateBrowserTabList, handleContextSourcesState, getActiveSources, hasActiveSources } from './modules/context-sources.js';
16
16
  import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible } from './modules/sticky-notes.js';
17
- import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
17
+ import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme, getChatLayout } from './modules/theme.js';
18
18
  import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, resetTurnMetaCost, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
19
19
  import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
20
20
  import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
@@ -32,7 +32,7 @@ import { initMateWizard, openMateWizard, closeMateWizard, handleMateCreated } fr
32
32
  import { initCommandPalette, handlePaletteSessionSwitch, setPaletteVersion } from './modules/command-palette.js';
33
33
  import { initLongPress } from './modules/longpress.js';
34
34
  import { initMention, handleMentionStart, handleMentionStream, handleMentionDone, handleMentionError, handleMentionActivity, renderMentionUser, renderMentionResponse } from './modules/mention.js';
35
- import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState } from './modules/debate.js';
35
+ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateResumed, handleDebateTurn, handleDebateActivity, handleDebateStream, handleDebateTurnDone, handleDebateCommentQueued, handleDebateCommentInjected, handleDebateEnded, handleDebateError, renderDebateStarted, renderDebateTurnDone, renderDebateEnded, renderDebateCommentInjected, renderDebateUserResume, openDebateModal, closeDebateModal, handleDebateBriefReady, renderDebateBriefReady, isDebateActive, resetDebateState, exportDebateAsPdf, renderMcpDebateProposal } from './modules/debate.js';
36
36
 
37
37
  // --- Base path for multi-project routing ---
38
38
  var slugMatch = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
@@ -565,6 +565,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
565
565
  // --- DM Mode Functions ---
566
566
  function openDm(targetUserId) {
567
567
  if (!ws || ws.readyState !== 1) return;
568
+ // Persist DM state for refresh recovery
569
+ try { localStorage.setItem("clay-active-dm", targetUserId); } catch (e) {}
568
570
  // Check mate skill updates before opening mate DM
569
571
  if (typeof targetUserId === "string" && targetUserId.indexOf("mate_") === 0) {
570
572
  showMateOnboarding(function () {
@@ -816,6 +818,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
816
818
  dmMode = false;
817
819
  dmKey = null;
818
820
  dmTargetUser = null;
821
+ try { localStorage.removeItem("clay-active-dm"); } catch (e) {}
819
822
  setCurrentDmUser(null);
820
823
 
821
824
  var mainCol = document.getElementById("main-column");
@@ -1798,9 +1801,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
1798
1801
  };
1799
1802
  initSidebar(sidebarCtx);
1800
1803
  initIconStrip(sidebarCtx);
1801
- initMateSidebar(function () { return (dmMode && dmTargetUser && dmTargetUser.isMate) ? ws : null; });
1802
- initMateKnowledge(function () { return (dmMode && dmTargetUser && dmTargetUser.isMate) ? ws : null; });
1803
- initMateMemory(function () { return (dmMode && dmTargetUser && dmTargetUser.isMate) ? ws : null; }, { onShow: function () { hideKnowledge(); hideNotes(); } });
1804
+ var wsGetter = function () { return ws; };
1805
+ initMateSidebar(wsGetter);
1806
+ initMateKnowledge(wsGetter);
1807
+ initMateMemory(wsGetter, { onShow: function () { hideKnowledge(); hideNotes(); } });
1804
1808
  initMateWizard(
1805
1809
  function (msg) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg)); },
1806
1810
  function (mate) { handleMateCreatedInApp(mate); }
@@ -2083,12 +2087,9 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
2083
2087
  activityEl = document.createElement("div");
2084
2088
  activityEl.className = "activity-inline";
2085
2089
  activityEl.innerHTML =
2086
- '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
2087
- '<span class="activity-text"></span>';
2090
+ '<div class="mate-thinking-dots"><span></span><span></span><span></span></div>';
2088
2091
  addToMessages(activityEl);
2089
- refreshIcons();
2090
2092
  }
2091
- activityEl.querySelector(".activity-text").textContent = text;
2092
2093
  scrollToBottom();
2093
2094
  } else {
2094
2095
  if (activityEl) {
@@ -2098,9 +2099,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
2098
2099
  }
2099
2100
  }
2100
2101
 
2101
- // --- Mate pre-thinking (instant dots before server responds) ---
2102
+ // --- Pre-thinking (instant dots before server responds) ---
2102
2103
  var matePreThinkingEl = null;
2103
2104
  var matePreThinkingTimer = null;
2105
+ function showClaudePreThinking() {
2106
+ if (getChatLayout() !== "channel") return;
2107
+ var claudeAvatar = CLAUDE_CODE_AVATAR;
2108
+ doShowMatePreThinking("Claude Code", claudeAvatar);
2109
+ }
2104
2110
  function showMatePreThinking() {
2105
2111
  removeMatePreThinking();
2106
2112
  var mateName = dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate";
@@ -2114,10 +2120,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
2114
2120
  '<img class="dm-bubble-avatar dm-bubble-avatar-mate" src="' + escapeHtml(mateAvatar) + '" alt="" style="display:block">' +
2115
2121
  '<div class="dm-bubble-content">' +
2116
2122
  '<div class="dm-bubble-header"><span class="dm-bubble-name">' + escapeHtml(mateName) + '</span></div>' +
2117
- '<div class="activity-inline mate-pre-activity">' +
2118
- '<span class="activity-icon">' + iconHtml("sparkles") + '</span>' +
2119
- '<span class="activity-text">' + randomThinkingVerb() + '...</span>' +
2120
- '</div>' +
2123
+ '<div class="mate-thinking-dots"><span></span><span></span><span></span></div>' +
2121
2124
  '</div>';
2122
2125
  if (activityEl && activityEl.parentNode) {
2123
2126
  activityEl.parentNode.insertBefore(matePreThinkingEl, activityEl);
@@ -3020,6 +3023,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3020
3023
  isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
3021
3024
  getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
3022
3025
  getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
3026
+ getClaudeAvatar: function () { return CLAUDE_CODE_AVATAR; },
3023
3027
  getMateById: function (id) {
3024
3028
  if (!id || !cachedMatesList) return null;
3025
3029
  for (var i = 0; i < cachedMatesList.length; i++) {
@@ -3040,6 +3044,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3040
3044
  var div = document.createElement("div");
3041
3045
  div.className = "msg-user" + (isOtherUser ? " msg-user-other" : "");
3042
3046
  div.dataset.turn = ++turnCounter;
3047
+ if (shouldGroupMessage("msg-user")) div.classList.add("grouped");
3043
3048
  var bubble = document.createElement("div");
3044
3049
  bubble.className = "bubble";
3045
3050
  bubble.dir = "auto";
@@ -3130,8 +3135,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3130
3135
  header.appendChild(nameSpan);
3131
3136
  var timeSpan = document.createElement("span");
3132
3137
  timeSpan.className = "dm-bubble-time";
3133
- var nowH = new Date();
3134
- timeSpan.textContent = String(nowH.getHours()).padStart(2, "0") + ":" + String(nowH.getMinutes()).padStart(2, "0");
3138
+ timeSpan.textContent = getMsgTime();
3135
3139
  header.appendChild(timeSpan);
3136
3140
  contentWrap.appendChild(header);
3137
3141
  contentWrap.appendChild(bubble);
@@ -3140,10 +3144,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3140
3144
  // Action bar below bubble (icons visible on hover)
3141
3145
  var actions = document.createElement("div");
3142
3146
  actions.className = "msg-actions";
3143
- var now = new Date();
3144
- var timeStr = String(now.getHours()).padStart(2, "0") + ":" + String(now.getMinutes()).padStart(2, "0");
3145
3147
  actions.innerHTML =
3146
- '<span class="msg-action-time">' + timeStr + '</span>' +
3148
+ '<span class="msg-action-time">' + getMsgTime() + '</span>' +
3147
3149
  '<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + iconHtml("copy") + '</button>' +
3148
3150
  '<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
3149
3151
  '<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + iconHtml("rotate-ccw") + '</button>' +
@@ -3164,11 +3166,33 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3164
3166
  forceScrollToBottom();
3165
3167
  }
3166
3168
 
3169
+ // Track the timestamp of the current message being processed (from history _ts or now)
3170
+ var currentMsgTs = null;
3171
+
3172
+ function getMsgTime() {
3173
+ var d = currentMsgTs ? new Date(currentMsgTs) : new Date();
3174
+ return String(d.getHours()).padStart(2, "0") + ":" + String(d.getMinutes()).padStart(2, "0");
3175
+ }
3176
+
3177
+ function shouldGroupMessage(senderClass) {
3178
+ // Skip grouping during history replay if no timestamp data
3179
+ if (replayingHistory && !currentMsgTs) return false;
3180
+ var prev = messagesEl.lastElementChild;
3181
+ if (!prev || !prev.classList.contains(senderClass)) return false;
3182
+ var prevTime = prev.querySelector(".dm-bubble-time");
3183
+ if (!prevTime) return false;
3184
+ return prevTime.textContent === getMsgTime();
3185
+ }
3186
+
3167
3187
  function ensureAssistantBlock() {
3168
3188
  if (!currentMsgEl) {
3169
3189
  currentMsgEl = document.createElement("div");
3170
3190
  currentMsgEl.className = "msg-assistant";
3171
3191
  currentMsgEl.dataset.turn = turnCounter;
3192
+
3193
+ var grouped = shouldGroupMessage("msg-assistant");
3194
+ if (grouped) currentMsgEl.classList.add("grouped");
3195
+
3172
3196
  // Always render avatar + header structure (CSS controls visibility)
3173
3197
  var _isDm2 = document.body.classList.contains("mate-dm-active") && document.body.dataset.mateAvatarUrl;
3174
3198
  var avi = document.createElement("img");
@@ -3187,8 +3211,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3187
3211
  header.appendChild(nameSpan);
3188
3212
  var timeSpan = document.createElement("span");
3189
3213
  timeSpan.className = "dm-bubble-time";
3190
- var nowA = new Date();
3191
- timeSpan.textContent = String(nowA.getHours()).padStart(2, "0") + ":" + String(nowA.getMinutes()).padStart(2, "0");
3214
+ timeSpan.textContent = getMsgTime();
3192
3215
  header.appendChild(timeSpan);
3193
3216
  contentWrap.appendChild(header);
3194
3217
 
@@ -3692,7 +3715,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3692
3715
  // Auto-switch input to schedule mode: any message typed will be queued for after reset
3693
3716
  var delayUntilReset = msg.resetsAt - Date.now();
3694
3717
  if (delayUntilReset > 0) {
3695
- setScheduleDelayMs(delayUntilReset + 180000); // +3min buffer after reset
3718
+ setScheduleDelayMs(delayUntilReset + 60000); // +1min buffer after reset
3696
3719
  }
3697
3720
  rateLimitResetTimer = setTimeout(function () {
3698
3721
  rateLimitResetsAt = null;
@@ -3716,42 +3739,134 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
3716
3739
 
3717
3740
  function addScheduledMessageBubble(text, resetsAt) {
3718
3741
  removeScheduledMessageBubble();
3742
+ var isChannel = document.body.classList.contains("wide-view");
3719
3743
  var wrap = document.createElement("div");
3720
3744
  wrap.className = "msg-user scheduled-msg-wrap";
3721
3745
  wrap.id = "scheduled-msg-bubble";
3722
3746
 
3723
- var bubble = document.createElement("div");
3724
- bubble.className = "bubble scheduled-msg-bubble";
3747
+ var countdownEl;
3748
+ var cancelBtn;
3725
3749
 
3726
- var textEl = document.createElement("span");
3727
- textEl.textContent = text;
3728
- bubble.appendChild(textEl);
3750
+ if (isChannel) {
3751
+ // Channel mode: avatar + header with scheduled badge + message
3752
+ var _me = cachedAllUsers.find(function (u) { return u.id === myUserId; });
3753
+ if (!_me) { try { _me = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {} }
3754
+ var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
3729
3755
 
3730
- var metaEl = document.createElement("div");
3731
- metaEl.className = "scheduled-msg-meta";
3756
+ var avi = document.createElement("img");
3757
+ avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
3758
+ avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: myUserId }, 36);
3759
+ wrap.appendChild(avi);
3732
3760
 
3733
- var clockIcon = document.createElement("span");
3734
- clockIcon.className = "scheduled-msg-icon";
3735
- clockIcon.innerHTML = iconHtml("clock");
3736
- metaEl.appendChild(clockIcon);
3761
+ var content = document.createElement("div");
3762
+ content.className = "dm-bubble-content";
3737
3763
 
3738
- var countdownEl = document.createElement("span");
3739
- countdownEl.className = "scheduled-msg-countdown";
3740
- metaEl.appendChild(countdownEl);
3764
+ var header = document.createElement("div");
3765
+ header.className = "dm-bubble-header";
3741
3766
 
3742
- var cancelBtn = document.createElement("button");
3743
- cancelBtn.className = "scheduled-msg-cancel";
3744
- cancelBtn.title = "Cancel scheduled message";
3745
- cancelBtn.textContent = "\u00d7";
3746
- cancelBtn.addEventListener("click", function () {
3747
- if (ws && ws.readyState === 1) {
3748
- ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
3749
- }
3750
- });
3751
- metaEl.appendChild(cancelBtn);
3767
+ var nameSpan = document.createElement("span");
3768
+ nameSpan.className = "dm-bubble-name";
3769
+ nameSpan.textContent = _myName;
3770
+ header.appendChild(nameSpan);
3771
+
3772
+ var badge = document.createElement("span");
3773
+ badge.className = "scheduled-msg-badge";
3774
+ badge.innerHTML = iconHtml("clock");
3775
+ countdownEl = document.createElement("span");
3776
+ countdownEl.className = "scheduled-msg-countdown";
3777
+ badge.appendChild(countdownEl);
3778
+ header.appendChild(badge);
3779
+
3780
+ var actions = document.createElement("span");
3781
+ actions.className = "scheduled-msg-actions";
3782
+
3783
+ var sendNowBtn = document.createElement("button");
3784
+ sendNowBtn.className = "scheduled-msg-send-now";
3785
+ sendNowBtn.textContent = "Send now";
3786
+ sendNowBtn.addEventListener("click", function () {
3787
+ if (ws && ws.readyState === 1) {
3788
+ ws.send(JSON.stringify({ type: "send_scheduled_now" }));
3789
+ }
3790
+ });
3791
+ actions.appendChild(sendNowBtn);
3792
+
3793
+ var sep = document.createElement("span");
3794
+ sep.className = "scheduled-msg-sep";
3795
+ sep.textContent = "\u00b7";
3796
+ actions.appendChild(sep);
3797
+
3798
+ cancelBtn = document.createElement("button");
3799
+ cancelBtn.className = "scheduled-msg-cancel";
3800
+ cancelBtn.textContent = "Cancel";
3801
+ cancelBtn.addEventListener("click", function () {
3802
+ if (ws && ws.readyState === 1) {
3803
+ ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
3804
+ }
3805
+ });
3806
+ actions.appendChild(cancelBtn);
3807
+
3808
+ header.appendChild(actions);
3809
+
3810
+ content.appendChild(header);
3811
+
3812
+ var bubble = document.createElement("div");
3813
+ bubble.className = "bubble scheduled-msg-bubble";
3814
+ var textEl = document.createElement("span");
3815
+ textEl.textContent = text;
3816
+ bubble.appendChild(textEl);
3817
+ content.appendChild(bubble);
3818
+
3819
+ wrap.appendChild(content);
3820
+ } else {
3821
+ // Bubble mode: original layout
3822
+ var bubble = document.createElement("div");
3823
+ bubble.className = "bubble scheduled-msg-bubble";
3824
+
3825
+ var textEl = document.createElement("span");
3826
+ textEl.textContent = text;
3827
+ bubble.appendChild(textEl);
3828
+
3829
+ var metaEl = document.createElement("div");
3830
+ metaEl.className = "scheduled-msg-meta";
3831
+
3832
+ var clockIcon = document.createElement("span");
3833
+ clockIcon.className = "scheduled-msg-icon";
3834
+ clockIcon.innerHTML = iconHtml("clock");
3835
+ metaEl.appendChild(clockIcon);
3836
+
3837
+ countdownEl = document.createElement("span");
3838
+ countdownEl.className = "scheduled-msg-countdown";
3839
+ metaEl.appendChild(countdownEl);
3840
+
3841
+ var sendNowBtn2 = document.createElement("button");
3842
+ sendNowBtn2.className = "scheduled-msg-send-now";
3843
+ sendNowBtn2.textContent = "Send now";
3844
+ sendNowBtn2.addEventListener("click", function () {
3845
+ if (ws && ws.readyState === 1) {
3846
+ ws.send(JSON.stringify({ type: "send_scheduled_now" }));
3847
+ }
3848
+ });
3849
+ metaEl.appendChild(sendNowBtn2);
3850
+
3851
+ var sep2 = document.createElement("span");
3852
+ sep2.className = "scheduled-msg-sep";
3853
+ sep2.textContent = "\u00b7";
3854
+ metaEl.appendChild(sep2);
3855
+
3856
+ cancelBtn = document.createElement("button");
3857
+ cancelBtn.className = "scheduled-msg-cancel";
3858
+ cancelBtn.textContent = "Cancel";
3859
+ cancelBtn.addEventListener("click", function () {
3860
+ if (ws && ws.readyState === 1) {
3861
+ ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
3862
+ }
3863
+ });
3864
+ metaEl.appendChild(cancelBtn);
3865
+
3866
+ wrap.appendChild(bubble);
3867
+ wrap.appendChild(metaEl);
3868
+ }
3752
3869
 
3753
- wrap.appendChild(bubble);
3754
- wrap.appendChild(metaEl);
3755
3870
  addToMessages(wrap);
3756
3871
  scheduledMsgEl = wrap;
3757
3872
  scrollToBottom();
@@ -4028,6 +4143,31 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4028
4143
 
4029
4144
  // Session restore is now server-driven (user-presence.json).
4030
4145
  // Mate DM restore is also server-driven via "restore_mate_dm" message.
4146
+ // Fallback: if server doesn't restore DM within 2s, try localStorage
4147
+ var savedDm = null;
4148
+ try { savedDm = localStorage.getItem("clay-active-dm"); } catch (e) {}
4149
+ if (savedDm && !dmMode && !mateProjectSlug) {
4150
+ var dmFallbackTimer = setTimeout(function () {
4151
+ if (!dmMode && savedDm) {
4152
+ console.log("[dm-restore] Server did not restore DM, using localStorage fallback:", savedDm);
4153
+ openDm(savedDm);
4154
+ }
4155
+ }, 2000);
4156
+ // Cancel fallback if server restores DM first
4157
+ var origHandler = ws.onmessage;
4158
+ var patchedOnce = false;
4159
+ var checkRestore = function (evt) {
4160
+ try {
4161
+ var d = JSON.parse(evt.data);
4162
+ if (d.type === "restore_mate_dm" && !patchedOnce) {
4163
+ patchedOnce = true;
4164
+ clearTimeout(dmFallbackTimer);
4165
+ }
4166
+ } catch (e) {}
4167
+ };
4168
+ ws.addEventListener("message", checkRestore);
4169
+ setTimeout(function () { ws.removeEventListener("message", checkRestore); }, 3000);
4170
+ }
4031
4171
  // Safety: clear returningFromMateDm after initial messages settle
4032
4172
  // (handles case where we connect to a non-main project that won't send restore_mate_dm)
4033
4173
  if (returningFromMateDm) {
@@ -4126,6 +4266,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4126
4266
  }
4127
4267
 
4128
4268
  function processMessage(msg) {
4269
+ // Preserve original timestamp from history replay
4270
+ currentMsgTs = msg._ts || null;
4129
4271
  var isMateDm = dmMode && dmTargetUser && dmTargetUser.isMate;
4130
4272
 
4131
4273
  // DEBUG: trace session/history loading
@@ -4140,6 +4282,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4140
4282
  if (isMateDm) {
4141
4283
  if (msg.type === "session_list") {
4142
4284
  renderMateSessionList(msg.sessions || []);
4285
+ refreshMobileChatSheet();
4143
4286
  // Override title bar with mate name and re-apply color
4144
4287
  var _mdn = (dmTargetUser.displayName || "New Mate");
4145
4288
  if (headerTitleEl) headerTitleEl.textContent = _mdn;
@@ -4245,6 +4388,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4245
4388
  if (dhBar) dhBar.remove();
4246
4389
  var dbBadges = document.querySelectorAll(".debate-header-badge");
4247
4390
  for (var dbi = 0; dbi < dbBadges.length; dbi++) dbBadges[dbi].remove();
4391
+ // Clean up ended mode banner if debate is not active on this session
4392
+ if (debateEndedMode) exitDebateEndedMode();
4248
4393
  }
4249
4394
  scrollToBottom();
4250
4395
  // Scroll to tool element if navigating from file edit history
@@ -4485,9 +4630,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4485
4630
  break;
4486
4631
 
4487
4632
  case "session_list":
4488
- if (isMateDm) {
4489
- renderMateSessionList(msg.sessions || []);
4490
- }
4633
+ renderMateSessionList(msg.sessions || []);
4491
4634
  renderSessionList(msg.sessions || []);
4492
4635
  handlePaletteSessionSwitch();
4493
4636
  break;
@@ -4578,6 +4721,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4578
4721
  break;
4579
4722
 
4580
4723
  case "user_message":
4724
+ if (msg._internal) break;
4581
4725
  resetThinkingGroup();
4582
4726
  if (msg.planContent) {
4583
4727
  setPlanContent(msg.planContent);
@@ -4599,7 +4743,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4599
4743
  header.className = "context-card-header";
4600
4744
  var icon = document.createElement("span");
4601
4745
  icon.className = "context-card-icon";
4602
- icon.textContent = "\uD83D\uDC41";
4746
+ icon.innerHTML = iconHtml("globe");
4603
4747
  header.appendChild(icon);
4604
4748
  var label = document.createElement("span");
4605
4749
  label.textContent = "Viewing tab";
@@ -4623,6 +4767,15 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4623
4767
  if (tabTitle || tabDomain) {
4624
4768
  var meta = document.createElement("div");
4625
4769
  meta.className = "context-card-meta";
4770
+ if (msg.tab.favIconUrl) {
4771
+ var fav = document.createElement("img");
4772
+ fav.className = "context-card-favicon";
4773
+ fav.src = msg.tab.favIconUrl;
4774
+ fav.width = 14;
4775
+ fav.height = 14;
4776
+ fav.onerror = function () { this.style.display = "none"; };
4777
+ meta.appendChild(fav);
4778
+ }
4626
4779
  var titleEl = document.createElement("span");
4627
4780
  titleEl.className = "context-card-title";
4628
4781
  titleEl.textContent = tabTitle;
@@ -4644,17 +4797,17 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4644
4797
  case "status":
4645
4798
  if (msg.status === "processing") {
4646
4799
  setStatus("processing");
4647
- if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
4648
- setActivity(randomThinkingVerb() + "...");
4800
+ if (!(dmMode && dmTargetUser && dmTargetUser.isMate) && !matePreThinkingEl) {
4801
+ setActivity("thinking");
4649
4802
  }
4650
4803
  }
4651
4804
  break;
4652
4805
 
4653
4806
  case "compacting":
4654
4807
  if (msg.active) {
4655
- setActivity("Compacting conversation...");
4808
+ setActivity("compacting");
4656
4809
  } else if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
4657
- setActivity(randomThinkingVerb() + "...");
4810
+ setActivity("thinking");
4658
4811
  }
4659
4812
  break;
4660
4813
 
@@ -4670,7 +4823,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4670
4823
  case "thinking_stop":
4671
4824
  stopThinking(msg.duration);
4672
4825
  if (!(dmMode && dmTargetUser && dmTargetUser.isMate)) {
4673
- setActivity(randomThinkingVerb() + "...");
4826
+ setActivity("thinking");
4674
4827
  }
4675
4828
  break;
4676
4829
 
@@ -4696,6 +4849,8 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4696
4849
  }
4697
4850
  renderPlanBanner("exit");
4698
4851
  getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
4852
+ } else if (msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) {
4853
+ getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
4699
4854
  } else if (getTodoTools()[msg.name]) {
4700
4855
  getTools()[msg.id] = { el: null, name: msg.name, input: null, done: true, hidden: true };
4701
4856
  } else {
@@ -4704,7 +4859,18 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
4704
4859
  break;
4705
4860
 
4706
4861
  case "tool_executing":
4707
- if (msg.name === "AskUserQuestion" && msg.input && msg.input.questions) {
4862
+ if ((msg.name === "propose_debate" || (msg.name && msg.name.indexOf("propose_debate") !== -1)) && msg.input) {
4863
+ var _dpTool = getTools()[msg.id];
4864
+ if (_dpTool) {
4865
+ if (_dpTool.el) _dpTool.el.style.display = "none";
4866
+ _dpTool.done = true;
4867
+ _dpTool.hidden = true;
4868
+ removeToolFromGroup(msg.id);
4869
+ }
4870
+ finalizeAssistantBlock();
4871
+ renderMcpDebateProposal(msg.id, msg.input);
4872
+ startUrgentBlink();
4873
+ } else if (msg.name === "AskUserQuestion" && msg.input && msg.input.questions) {
4708
4874
  var askTool = getTools()[msg.id];
4709
4875
  if (askTool) {
4710
4876
  if (askTool.el) askTool.el.style.display = "none";
@@ -5261,6 +5427,23 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
5261
5427
  break;
5262
5428
 
5263
5429
  // --- @Mention ---
5430
+ case "mention_processing":
5431
+ // Broadcast: show/hide activity dot on mate avatar across all tabs
5432
+ if (msg.mateId) {
5433
+ var mateContainers = document.querySelectorAll('.icon-strip-mate[data-user-id="' + msg.mateId + '"]');
5434
+ for (var mi = 0; mi < mateContainers.length; mi++) {
5435
+ var dot = mateContainers[mi].querySelector(".icon-strip-status");
5436
+ if (msg.active) {
5437
+ if (dot) dot.classList.add("processing");
5438
+ mateContainers[mi].classList.add("mention-active");
5439
+ } else {
5440
+ if (dot) dot.classList.remove("processing");
5441
+ mateContainers[mi].classList.remove("mention-active");
5442
+ }
5443
+ }
5444
+ }
5445
+ break;
5446
+
5264
5447
  case "mention_start":
5265
5448
  handleMentionStart(msg);
5266
5449
  break;
@@ -5338,6 +5521,10 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
5338
5521
  }
5339
5522
  break;
5340
5523
 
5524
+ case "debate_hand_raised":
5525
+ // Visual feedback: hand is raised, waiting for floor
5526
+ break;
5527
+
5341
5528
  case "debate_comment_queued":
5342
5529
  handleDebateCommentQueued(msg);
5343
5530
  break;
@@ -5354,6 +5541,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
5354
5541
  showDebateConcludeConfirm(msg);
5355
5542
  break;
5356
5543
 
5544
+ case "debate_user_floor":
5545
+ showDebateUserFloor(msg);
5546
+ break;
5547
+
5548
+ case "debate_user_floor_done":
5549
+ renderDebateUserFloorDone(msg);
5550
+ break;
5551
+
5357
5552
  case "debate_user_resume":
5358
5553
  renderDebateUserResume(msg);
5359
5554
  break;
@@ -5699,10 +5894,17 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
5699
5894
  isDmMode: function () { return dmMode && !(dmTargetUser && dmTargetUser.isMate); },
5700
5895
  getDmKey: function () { return dmKey; },
5701
5896
  handleDmSend: function () { handleDmSend(); },
5897
+ isDebateEndedMode: function () { return debateEndedMode; },
5898
+ handleDebateEndedSend: function () { handleDebateEndedSend(); },
5899
+ isDebateConcludeMode: function () { return false; },
5900
+ handleDebateConcludeSend: null,
5901
+ isDebateFloorMode: function () { return debateFloorMode; },
5902
+ handleDebateFloorSend: function () { handleDebateFloorSend(); },
5702
5903
  isMateDm: function () { return dmMode && dmTargetUser && dmTargetUser.isMate; },
5703
5904
  getMateName: function () { return dmTargetUser ? (dmTargetUser.displayName || "Mate") : "Mate"; },
5704
5905
  getMateAvatarUrl: function () { return document.body.dataset.mateAvatarUrl || ""; },
5705
5906
  showMatePreThinking: function () { showMatePreThinking(); },
5907
+ showClaudePreThinking: function () { showClaudePreThinking(); },
5706
5908
  });
5707
5909
 
5708
5910
  // --- @Mention module ---
@@ -5724,13 +5926,16 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
5724
5926
  // --- Debate module ---
5725
5927
  initDebate({
5726
5928
  get ws() { return ws; },
5929
+ sendWs: function (obj) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(obj)); },
5727
5930
  messagesEl: messagesEl,
5931
+ addToMessages: function (el) { addToMessages(el); },
5728
5932
  scrollToBottom: scrollToBottom,
5729
5933
  addCopyHandler: addCopyHandler,
5730
5934
  matesList: function () { return cachedMatesList || []; },
5731
5935
  availableBuiltins: function () { return cachedAvailableBuiltins || []; },
5732
5936
  currentMateId: function () { return (dmTargetUser && dmTargetUser.isMate) ? dmTargetUser.id : null; },
5733
5937
  requireSkills: requireSkills,
5938
+ showDebateEndedMode: function (msg) { showDebateEndedMode(msg); },
5734
5939
  });
5735
5940
 
5736
5941
  // --- STT module (voice input via Web Speech API) ---
@@ -7021,6 +7226,159 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7021
7226
  scrollToBottom();
7022
7227
  }
7023
7228
 
7229
+ var debateFloorMode = false;
7230
+
7231
+ var debateEndedMode = false;
7232
+
7233
+ function showDebateEndedMode(msg) {
7234
+ debateEndedMode = true;
7235
+ removeDebateBottomBar();
7236
+ var inputArea = document.getElementById("input-area");
7237
+ if (inputArea) {
7238
+ inputArea.classList.add("debate-floor-mode");
7239
+ inputArea.style.display = "";
7240
+ }
7241
+ var existingBanner = document.getElementById("debate-floor-banner");
7242
+ if (existingBanner) existingBanner.remove();
7243
+ var banner = document.createElement("div");
7244
+ banner.id = "debate-floor-banner";
7245
+ banner.className = "debate-floor-banner";
7246
+ banner.innerHTML = iconHtml("check-circle") + " <span>Debate ended</span>" +
7247
+ '<button class="debate-floor-done-btn" id="debate-ended-resume-btn">Resume</button>' +
7248
+ '<button class="debate-floor-done-btn" id="debate-ended-pdf-btn">' + iconHtml("download") + ' PDF</button>';
7249
+ if (inputArea && inputArea.parentNode) {
7250
+ inputArea.parentNode.insertBefore(banner, inputArea);
7251
+ }
7252
+ refreshIcons();
7253
+ // Resume button
7254
+ var resumeBtn = document.getElementById("debate-ended-resume-btn");
7255
+ if (resumeBtn) {
7256
+ resumeBtn.addEventListener("click", function () {
7257
+ handleDebateEndedSend();
7258
+ });
7259
+ }
7260
+ // PDF button
7261
+ var pdfBtn = document.getElementById("debate-ended-pdf-btn");
7262
+ if (pdfBtn) {
7263
+ pdfBtn.addEventListener("click", function () {
7264
+ pdfBtn.disabled = true;
7265
+ exportDebateAsPdf().then(function () { pdfBtn.disabled = false; }).catch(function () { pdfBtn.disabled = false; });
7266
+ });
7267
+ }
7268
+ var inputEl2 = document.getElementById("input");
7269
+ if (inputEl2) {
7270
+ inputEl2._origPlaceholder = inputEl2._origPlaceholder || inputEl2.placeholder;
7271
+ inputEl2.placeholder = "Continue with a new direction...";
7272
+ }
7273
+ scrollToBottom();
7274
+ }
7275
+
7276
+ function exitDebateEndedMode() {
7277
+ debateEndedMode = false;
7278
+ var inputArea = document.getElementById("input-area");
7279
+ if (inputArea) inputArea.classList.remove("debate-floor-mode");
7280
+ var banner = document.getElementById("debate-floor-banner");
7281
+ if (banner) banner.remove();
7282
+ var inputEl2 = document.getElementById("input");
7283
+ if (inputEl2 && inputEl2._origPlaceholder) {
7284
+ inputEl2.placeholder = inputEl2._origPlaceholder;
7285
+ delete inputEl2._origPlaceholder;
7286
+ }
7287
+ }
7288
+
7289
+ function handleDebateEndedSend() {
7290
+ var text = inputEl.value.trim();
7291
+ if (ws && ws.readyState === 1) {
7292
+ ws.send(JSON.stringify({ type: "debate_conclude_response", action: "continue", text: text }));
7293
+ }
7294
+ inputEl.value = "";
7295
+ exitDebateEndedMode();
7296
+ }
7297
+
7298
+ function showDebateUserFloor(msg) {
7299
+ debateFloorMode = true;
7300
+ // Remove debate bottom bar and show input area in floor mode
7301
+ removeDebateBottomBar();
7302
+ var inputArea = document.getElementById("input-area");
7303
+ if (inputArea) {
7304
+ inputArea.classList.add("debate-floor-mode");
7305
+ inputArea.style.display = "";
7306
+ }
7307
+ // Add floor banner above input
7308
+ var existingBanner = document.getElementById("debate-floor-banner");
7309
+ if (existingBanner) existingBanner.remove();
7310
+ var banner = document.createElement("div");
7311
+ banner.id = "debate-floor-banner";
7312
+ banner.className = "debate-floor-banner";
7313
+ banner.innerHTML = iconHtml("mic") + " <span>You have the floor</span>" +
7314
+ '<button class="debate-floor-done-btn" id="debate-floor-done-btn">Pass</button>';
7315
+ if (inputArea && inputArea.parentNode) {
7316
+ inputArea.parentNode.insertBefore(banner, inputArea);
7317
+ }
7318
+ refreshIcons();
7319
+ // Done button: exit floor mode without sending
7320
+ var doneBtn = document.getElementById("debate-floor-done-btn");
7321
+ if (doneBtn) {
7322
+ doneBtn.addEventListener("click", function () {
7323
+ // Pass without speaking: resume debate
7324
+ if (ws && ws.readyState === 1) {
7325
+ ws.send(JSON.stringify({ type: "debate_user_floor_response", text: "(The user passed without speaking)" }));
7326
+ }
7327
+ exitDebateFloorMode();
7328
+ showDebateBottomBar("live");
7329
+ });
7330
+ }
7331
+ // Update placeholder
7332
+ var inputEl = document.getElementById("input");
7333
+ if (inputEl) {
7334
+ inputEl._origPlaceholder = inputEl.placeholder;
7335
+ inputEl.placeholder = "Share your thoughts with the panel...";
7336
+ inputEl.focus();
7337
+ }
7338
+ scrollToBottom();
7339
+ }
7340
+
7341
+ function exitDebateFloorMode() {
7342
+ debateFloorMode = false;
7343
+ var inputArea = document.getElementById("input-area");
7344
+ if (inputArea) inputArea.classList.remove("debate-floor-mode");
7345
+ var banner = document.getElementById("debate-floor-banner");
7346
+ if (banner) banner.remove();
7347
+ var inputEl = document.getElementById("input");
7348
+ if (inputEl && inputEl._origPlaceholder) {
7349
+ inputEl.placeholder = inputEl._origPlaceholder;
7350
+ delete inputEl._origPlaceholder;
7351
+ }
7352
+ }
7353
+
7354
+ function handleDebateFloorSend() {
7355
+ var text = inputEl.value.trim();
7356
+ if (!text) return;
7357
+ if (ws && ws.readyState === 1) {
7358
+ ws.send(JSON.stringify({ type: "debate_user_floor_response", text: text }));
7359
+ }
7360
+ inputEl.value = "";
7361
+ exitDebateFloorMode();
7362
+ showDebateBottomBar("live");
7363
+ }
7364
+
7365
+ function renderDebateUserFloorDone(msg) {
7366
+ if (!messagesEl) return;
7367
+ var el = document.createElement("div");
7368
+ el.className = "debate-user-comment";
7369
+ var label = document.createElement("span");
7370
+ label.className = "debate-comment-label";
7371
+ label.innerHTML = iconHtml("mic") + " User:";
7372
+ var textEl = document.createElement("div");
7373
+ textEl.className = "debate-comment-text";
7374
+ textEl.textContent = msg.text || "";
7375
+ el.appendChild(label);
7376
+ el.appendChild(textEl);
7377
+ messagesEl.appendChild(el);
7378
+ refreshIcons();
7379
+ scrollToBottom();
7380
+ }
7381
+
7024
7382
  // Legacy handler kept for compatibility
7025
7383
  function showDebateSticky(phase, msg) {
7026
7384
  if (phase === "ended" || phase === "hide") {
@@ -7043,7 +7401,13 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7043
7401
  return;
7044
7402
  }
7045
7403
 
7046
- // Add badges next to header title
7404
+ // Show bottom bar regardless of header availability
7405
+ if (phase === "live") {
7406
+ debateHandRaiseOpen = false;
7407
+ showDebateBottomBar("live");
7408
+ }
7409
+
7410
+ // Add badges next to header title (optional, may not exist on mobile)
7047
7411
  var headerTitle = document.getElementById("header-title");
7048
7412
  if (!headerTitle) return;
7049
7413
 
@@ -7063,9 +7427,6 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7063
7427
  roundBadge.id = "debate-header-round";
7064
7428
  roundBadge.textContent = "R" + ((msg && msg.round) || 1);
7065
7429
  liveBadge.after(roundBadge);
7066
-
7067
- debateHandRaiseOpen = false;
7068
- showDebateBottomBar("live");
7069
7430
  }
7070
7431
  }
7071
7432
 
@@ -7084,6 +7445,7 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7084
7445
  bar.innerHTML =
7085
7446
  '<div class="debate-bottom-inner">' +
7086
7447
  '<button class="debate-bottom-hand" id="debate-bottom-hand">' + iconHtml("hand") + ' Raise hand</button>' +
7448
+ '<span class="debate-bottom-waiting hidden" id="debate-bottom-waiting">' + iconHtml("loader") + ' You will get the floor after the current speaker</span>' +
7087
7449
  '<button class="debate-bottom-stop" id="debate-bottom-stop">' + iconHtml("square") + ' Stop</button>' +
7088
7450
  '</div>';
7089
7451
 
@@ -7091,6 +7453,14 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7091
7453
  inputArea.style.display = "none";
7092
7454
  refreshIcons();
7093
7455
 
7456
+ // Restore raised state if hand was already raised
7457
+ if (debateHandRaiseOpen) {
7458
+ var handBtn = document.getElementById("debate-bottom-hand");
7459
+ var waitingEl = document.getElementById("debate-bottom-waiting");
7460
+ if (handBtn) { handBtn.classList.add("raised"); handBtn.classList.add("hidden"); }
7461
+ if (waitingEl) waitingEl.classList.remove("hidden");
7462
+ }
7463
+
7094
7464
  document.getElementById("debate-bottom-hand").addEventListener("click", function () {
7095
7465
  toggleDebateHandRaise();
7096
7466
  });
@@ -7130,16 +7500,12 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7130
7500
  showDebateBottomBar("live");
7131
7501
  });
7132
7502
  if (textArea) {
7133
- textArea.focus();
7134
7503
  textArea.addEventListener("keydown", function (e) {
7135
7504
  if (e.key === "Enter" && !e.shiftKey) {
7136
7505
  e.preventDefault();
7137
7506
  document.getElementById("debate-bottom-continue").click();
7138
7507
  }
7139
7508
  });
7140
- textArea.addEventListener("input", function () {
7141
- debateAutoResize(textArea, 12);
7142
- });
7143
7509
  }
7144
7510
  }
7145
7511
  }
@@ -7160,74 +7526,33 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
7160
7526
  var handBar = document.getElementById("debate-hand-raise-bar");
7161
7527
  if (handBar) handBar.remove();
7162
7528
  debateHandRaiseOpen = false;
7529
+ // Clean up floor/ended modes
7530
+ if (debateFloorMode) exitDebateFloorMode();
7531
+ if (debateEndedMode) exitDebateEndedMode();
7163
7532
  // Restore input area
7164
7533
  var inputArea = document.getElementById("input-area");
7165
7534
  if (inputArea) inputArea.style.display = "";
7166
7535
  }
7167
7536
 
7168
7537
  function toggleDebateHandRaise(forceState) {
7169
- var show = typeof forceState === "boolean" ? forceState : !debateHandRaiseOpen;
7170
- debateHandRaiseOpen = show;
7171
-
7172
- var existing = document.getElementById("debate-hand-raise-bar");
7173
- if (!show) {
7174
- if (existing) existing.remove();
7175
- return;
7176
- }
7177
- if (existing) {
7178
- var inp = existing.querySelector(".debate-hand-input");
7179
- if (inp) { inp.value = ""; inp.focus(); }
7180
- return;
7181
- }
7182
-
7183
- // Create hand raise bar above input area
7184
- var bar = document.createElement("div");
7185
- bar.id = "debate-hand-raise-bar";
7186
- bar.className = "debate-hand-raise-bar";
7187
- bar.innerHTML =
7188
- '<div class="debate-hand-raise-inner">' +
7189
- '<span class="debate-hand-raise-label">' + iconHtml("hand") + ' Your comment:</span>' +
7190
- '<textarea class="debate-hand-input" rows="1" placeholder="Type your comment..."></textarea>' +
7191
- '<button class="debate-hand-send">Send</button>' +
7192
- '<button class="debate-hand-cancel">Cancel</button>' +
7193
- '</div>';
7194
-
7195
- var inputArea = document.getElementById("input-area");
7196
- if (inputArea && inputArea.parentNode) {
7197
- inputArea.parentNode.insertBefore(bar, inputArea);
7198
- }
7199
- refreshIcons();
7200
-
7201
- var textarea = bar.querySelector(".debate-hand-input");
7202
- var sendBtn = bar.querySelector(".debate-hand-send");
7203
- var cancelBtn = bar.querySelector(".debate-hand-cancel");
7204
-
7205
- if (textarea) {
7206
- textarea.focus();
7207
- textarea.addEventListener("input", function () {
7208
- debateAutoResize(textarea, 12);
7209
- });
7538
+ var raise = typeof forceState === "boolean" ? forceState : !debateHandRaiseOpen;
7539
+ debateHandRaiseOpen = raise;
7540
+
7541
+ // Update UI: hide hand button, show waiting message
7542
+ var handBtn = document.getElementById("debate-bottom-hand");
7543
+ var waitingEl = document.getElementById("debate-bottom-waiting");
7544
+ if (raise) {
7545
+ if (handBtn) { handBtn.classList.add("raised"); handBtn.classList.add("hidden"); }
7546
+ if (waitingEl) waitingEl.classList.remove("hidden");
7547
+ } else {
7548
+ if (handBtn) { handBtn.classList.remove("raised"); handBtn.classList.remove("hidden"); }
7549
+ if (waitingEl) waitingEl.classList.add("hidden");
7210
7550
  }
7211
7551
 
7212
- sendBtn.addEventListener("click", function () {
7213
- var text = textarea ? textarea.value.trim() : "";
7214
- if (!text) return;
7215
- if (ws && ws.readyState === 1) {
7216
- ws.send(JSON.stringify({ type: "debate_comment", text: text }));
7217
- }
7218
- toggleDebateHandRaise(false);
7219
- });
7220
-
7221
- cancelBtn.addEventListener("click", function () {
7222
- toggleDebateHandRaise(false);
7223
- });
7224
-
7225
- if (textarea) {
7226
- textarea.addEventListener("keydown", function (e) {
7227
- if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendBtn.click(); }
7228
- if (e.key === "Escape") { toggleDebateHandRaise(false); }
7229
- });
7552
+ if (raise && ws && ws.readyState === 1) {
7553
+ ws.send(JSON.stringify({ type: "debate_hand_raise" }));
7230
7554
  }
7555
+ // Floor mode will be activated when server sends debate_user_floor
7231
7556
  }
7232
7557
 
7233
7558
  function sendDebateStickyComment() {