clay-server 2.31.0 → 2.32.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/browser-mcp-server.js +32 -44
- package/lib/debate-mcp-server.js +14 -31
- package/lib/mcp-local.js +31 -1
- package/lib/project-connection.js +4 -2
- package/lib/project-filesystem.js +47 -1
- package/lib/project-http.js +75 -8
- package/lib/project-mcp.js +4 -0
- package/lib/project-sessions.js +88 -51
- package/lib/project-user-message.js +12 -7
- package/lib/project.js +204 -90
- package/lib/public/app.js +123 -448
- package/lib/public/codex-avatar.png +0 -0
- package/lib/public/css/debate.css +3 -2
- package/lib/public/css/filebrowser.css +91 -1
- package/lib/public/css/icon-strip.css +21 -5
- package/lib/public/css/input.css +181 -100
- package/lib/public/css/mates.css +43 -0
- package/lib/public/css/mention.css +48 -4
- package/lib/public/css/menus.css +1 -1
- package/lib/public/css/messages.css +2 -0
- package/lib/public/css/notifications-center.css +19 -0
- package/lib/public/index.html +46 -24
- package/lib/public/modules/app-connection.js +138 -37
- package/lib/public/modules/app-cursors.js +18 -17
- package/lib/public/modules/app-debate-ui.js +9 -9
- package/lib/public/modules/app-dm.js +170 -131
- package/lib/public/modules/app-favicon.js +28 -26
- package/lib/public/modules/app-header.js +79 -68
- package/lib/public/modules/app-home-hub.js +55 -47
- package/lib/public/modules/app-loop-ui.js +34 -18
- package/lib/public/modules/app-loop-wizard.js +6 -6
- package/lib/public/modules/app-messages.js +195 -152
- package/lib/public/modules/app-misc.js +23 -12
- package/lib/public/modules/app-notifications.js +97 -3
- package/lib/public/modules/app-panels.js +203 -49
- package/lib/public/modules/app-projects.js +159 -150
- package/lib/public/modules/app-rate-limit.js +5 -4
- package/lib/public/modules/app-rendering.js +149 -101
- package/lib/public/modules/app-skills-install.js +4 -4
- package/lib/public/modules/context-sources.js +12 -41
- package/lib/public/modules/dom-refs.js +21 -0
- package/lib/public/modules/filebrowser.js +173 -2
- package/lib/public/modules/input.js +86 -0
- package/lib/public/modules/mate-sidebar.js +38 -0
- package/lib/public/modules/mention.js +24 -6
- package/lib/public/modules/scheduler.js +1 -1
- package/lib/public/modules/sidebar-mates.js +66 -34
- package/lib/public/modules/sidebar-mobile.js +34 -30
- package/lib/public/modules/sidebar-projects.js +60 -57
- package/lib/public/modules/sidebar-sessions.js +75 -69
- package/lib/public/modules/sidebar.js +12 -20
- package/lib/public/modules/skills.js +8 -9
- package/lib/public/modules/sticky-notes.js +1 -2
- package/lib/public/modules/store.js +9 -2
- package/lib/public/modules/stt.js +4 -1
- package/lib/public/modules/tools.js +14 -9
- package/lib/sdk-bridge.js +511 -1113
- package/lib/sdk-message-processor.js +123 -134
- package/lib/sdk-worker.js +4 -0
- package/lib/server-dm.js +1 -0
- package/lib/server.js +86 -1
- package/lib/sessions.js +47 -36
- package/lib/ws-schema.js +2 -0
- package/lib/yoke/adapters/claude-worker.js +559 -0
- package/lib/yoke/adapters/claude.js +1418 -0
- package/lib/yoke/adapters/codex.js +968 -0
- package/lib/yoke/adapters/gemini.js +668 -0
- package/lib/yoke/codex-app-server.js +307 -0
- package/lib/yoke/index.js +199 -0
- package/lib/yoke/instructions.js +62 -0
- package/lib/yoke/interface.js +92 -0
- package/lib/yoke/mcp-bridge-server.js +294 -0
- package/lib/yoke/package.json +7 -0
- package/package.json +3 -1
package/lib/public/index.html
CHANGED
|
@@ -40,10 +40,6 @@
|
|
|
40
40
|
body.wide-view:not(.mate-dm-active) .tool-group{max-width:100%!important}
|
|
41
41
|
body.wide-view:not(.mate-dm-active) .turn-meta{max-width:100%!important}
|
|
42
42
|
}
|
|
43
|
-
#ask-mate-btn::before{content:""!important;position:absolute!important;inset:0!important;background:linear-gradient(135deg,#4ecdc4 0%,#556bf7 33%,#a855f7 55%,#f857a6 78%,#ff6b6b 100%)!important;border-radius:inherit!important;opacity:0;transform:scale(0.3);transition:opacity 0.3s ease,transform 0.35s cubic-bezier(0.34,1.56,0.64,1);z-index:-1}
|
|
44
|
-
#ask-mate-btn:hover::before{opacity:1!important;transform:scale(1)!important}
|
|
45
|
-
#ask-mate-btn:hover{border-color:transparent!important;background:transparent!important;transform:translateY(-1px);box-shadow:0 2px 12px rgba(148,130,247,0.4)}
|
|
46
|
-
#ask-mate-btn:hover .ask-mate-label{background:none!important;-webkit-text-fill-color:#fff!important}
|
|
47
43
|
</style>
|
|
48
44
|
</head>
|
|
49
45
|
<body>
|
|
@@ -219,15 +215,7 @@
|
|
|
219
215
|
<button id="session-search-clear" type="button" aria-label="Clear search"><i data-lucide="x"></i></button>
|
|
220
216
|
</div>
|
|
221
217
|
</div>
|
|
222
|
-
<div id="files-header-content" class="hidden">
|
|
223
|
-
<div class="session-list-header">
|
|
224
|
-
<span>File Browser</span>
|
|
225
|
-
<div class="session-list-header-actions">
|
|
226
|
-
<button id="file-panel-refresh" type="button" title="Refresh file tree"><i data-lucide="refresh-cw"></i></button>
|
|
227
|
-
<button id="file-panel-close" type="button" title="Close file browser"><i data-lucide="x"></i></button>
|
|
228
|
-
</div>
|
|
229
|
-
</div>
|
|
230
|
-
</div>
|
|
218
|
+
<div id="files-header-content" class="hidden"></div>
|
|
231
219
|
</div>
|
|
232
220
|
<div id="ralph-loop-section"></div>
|
|
233
221
|
<div id="sidebar-panel-projects" class="sidebar-panel hidden">
|
|
@@ -244,6 +232,15 @@
|
|
|
244
232
|
</div>
|
|
245
233
|
</div>
|
|
246
234
|
<div id="sidebar-panel-files" class="sidebar-panel hidden">
|
|
235
|
+
<div class="fb-titlebar">
|
|
236
|
+
<button id="file-panel-refresh" type="button" title="Refresh file tree"><i data-lucide="refresh-cw"></i></button>
|
|
237
|
+
<span class="fb-titlebar-title">File Browser</span>
|
|
238
|
+
<button id="file-panel-close" type="button" title="Close file browser"><i data-lucide="x"></i></button>
|
|
239
|
+
</div>
|
|
240
|
+
<div class="fb-search-bar">
|
|
241
|
+
<i data-lucide="search" class="fb-search-icon"></i>
|
|
242
|
+
<input id="fb-search-input" type="text" placeholder="Search files..." autocomplete="off" spellcheck="false" />
|
|
243
|
+
</div>
|
|
247
244
|
<div id="file-tree"></div>
|
|
248
245
|
</div>
|
|
249
246
|
</div>
|
|
@@ -256,6 +253,7 @@
|
|
|
256
253
|
<div class="mate-sidebar-header" id="mate-sidebar-header">
|
|
257
254
|
<img id="mate-sidebar-avatar" class="mate-sidebar-avatar" alt="">
|
|
258
255
|
<span id="mate-sidebar-name" class="mate-sidebar-name"></span>
|
|
256
|
+
<div id="mate-vendor-toggle" class="mate-vendor-toggle"></div>
|
|
259
257
|
<div id="mate-sidebar-seed-tooltip" class="mate-seed-tooltip hidden"></div>
|
|
260
258
|
<button id="mate-sidebar-toggle-btn" class="sidebar-collapse-btn" title="Collapse sidebar"><i data-lucide="panel-left-close"></i></button>
|
|
261
259
|
</div>
|
|
@@ -446,15 +444,6 @@
|
|
|
446
444
|
<div id="input-wrapper">
|
|
447
445
|
<div id="mention-menu"></div>
|
|
448
446
|
<div id="slash-menu"></div>
|
|
449
|
-
<div id="context-sources-bar">
|
|
450
|
-
<div id="context-sources-chips"></div>
|
|
451
|
-
<button id="context-sources-add" type="button" title="Add context source"><i data-lucide="plus"></i><span>Context Sources</span></button>
|
|
452
|
-
<div id="context-sources-picker" class="hidden">
|
|
453
|
-
<div class="context-picker-section" id="context-picker-email"></div>
|
|
454
|
-
<div class="context-picker-section" id="context-picker-terminals"></div>
|
|
455
|
-
<div class="context-picker-section" id="context-picker-tabs"></div>
|
|
456
|
-
</div>
|
|
457
|
-
</div>
|
|
458
447
|
<div id="input-row">
|
|
459
448
|
<div id="context-mini" class="hidden">
|
|
460
449
|
<div class="context-mini-bar">
|
|
@@ -464,16 +453,37 @@
|
|
|
464
453
|
</div>
|
|
465
454
|
<div id="image-preview-bar"></div>
|
|
466
455
|
<div id="suggestion-chips" class="hidden"></div>
|
|
467
|
-
<
|
|
456
|
+
<div id="input-textarea-wrap">
|
|
457
|
+
<textarea id="input" rows="1" placeholder="Message Claude Code..." enterkeyhint="send" dir="auto"></textarea>
|
|
458
|
+
<div id="ghost-suggestion" class="hidden" aria-hidden="true"></div>
|
|
459
|
+
</div>
|
|
468
460
|
<div id="input-bottom">
|
|
469
461
|
<div id="attach-wrap">
|
|
470
462
|
<button id="attach-file-btn" type="button" aria-label="Attach file" title="Attach file"><i data-lucide="paperclip"></i></button>
|
|
471
463
|
<button id="attach-image-btn" type="button" aria-label="Attach image" title="Attach image"><i data-lucide="image"></i></button>
|
|
472
464
|
<button id="stt-btn" type="button" aria-label="Voice input" title="Voice input"><i data-lucide="mic"></i></button>
|
|
473
465
|
<button id="schedule-btn" type="button" aria-label="Schedule message" title="Schedule message"><i data-lucide="clock"></i></button>
|
|
474
|
-
<button id="ask-mate-btn" type="button" aria-label="Ask Mate"><
|
|
466
|
+
<button id="ask-mate-btn" type="button" aria-label="Ask Mate" title="Ask a Mate for advice on this session"><i data-lucide="at-sign"></i></button>
|
|
467
|
+
<div id="context-sources-btn-wrap">
|
|
468
|
+
<button id="context-sources-add" type="button" title="Add context sources"><i data-lucide="plus"></i><span class="ctx-label">Context</span></button>
|
|
469
|
+
<div id="context-sources-picker" class="hidden">
|
|
470
|
+
<div class="context-picker-section" id="context-picker-email"></div>
|
|
471
|
+
<div class="context-picker-section" id="context-picker-terminals"></div>
|
|
472
|
+
<div class="context-picker-section" id="context-picker-tabs"></div>
|
|
473
|
+
</div>
|
|
474
|
+
</div>
|
|
475
475
|
</div>
|
|
476
476
|
<div id="input-bottom-right">
|
|
477
|
+
<div id="vendor-toggle-wrap">
|
|
478
|
+
<button id="vendor-btn-claude" class="vendor-toggle-btn active" data-vendor="claude">
|
|
479
|
+
<img src="/claude-code-avatar.png" class="vendor-toggle-icon" alt="Claude">
|
|
480
|
+
<span class="vendor-toggle-label">Claude Code</span>
|
|
481
|
+
</button>
|
|
482
|
+
<button id="vendor-btn-codex" class="vendor-toggle-btn" data-vendor="codex">
|
|
483
|
+
<img src="/codex-avatar.png" class="vendor-toggle-icon" alt="Codex">
|
|
484
|
+
<span class="vendor-toggle-label">Codex</span>
|
|
485
|
+
</button>
|
|
486
|
+
</div>
|
|
477
487
|
<div id="config-chip-wrap" class="hidden">
|
|
478
488
|
<button id="config-chip" title="Model, mode, and effort settings">
|
|
479
489
|
<i class="config-chip-icon" data-lucide="sliders-horizontal"></i>
|
|
@@ -511,6 +521,18 @@
|
|
|
511
521
|
</button>
|
|
512
522
|
</div>
|
|
513
523
|
</div>
|
|
524
|
+
<div id="config-approval-section" class="config-section" style="display:none">
|
|
525
|
+
<div class="config-section-label">APPROVAL</div>
|
|
526
|
+
<div id="config-approval-bar" class="config-segmented"></div>
|
|
527
|
+
</div>
|
|
528
|
+
<div id="config-sandbox-section" class="config-section" style="display:none">
|
|
529
|
+
<div class="config-section-label">SANDBOX</div>
|
|
530
|
+
<div id="config-sandbox-bar" class="config-segmented"></div>
|
|
531
|
+
</div>
|
|
532
|
+
<div id="config-websearch-section" class="config-section" style="display:none">
|
|
533
|
+
<div class="config-section-label">WEB SEARCH</div>
|
|
534
|
+
<div id="config-websearch-bar" class="config-segmented"></div>
|
|
535
|
+
</div>
|
|
514
536
|
</div>
|
|
515
537
|
</div>
|
|
516
538
|
<button id="send-btn" disabled aria-label="Send"><i data-lucide="arrow-up"></i></button>
|
|
@@ -1,56 +1,158 @@
|
|
|
1
1
|
// app-connection.js - WebSocket connection, reconnect, status
|
|
2
2
|
// Extracted from app.js (PR-22)
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import { store } from './store.js';
|
|
5
|
+
import { getWs, setWs } from './ws-ref.js';
|
|
6
|
+
import { getStatusDot, getSendBtn } from './dom-refs.js';
|
|
7
|
+
import { setSendBtnMode, blinkIO, setActivity } from './app-favicon.js';
|
|
8
|
+
import { startLogoAnimation, stopLogoAnimation } from './ascii-logo.js';
|
|
9
|
+
import { hasSendableContent } from './input.js';
|
|
10
|
+
import { isNotifAlertEnabled } from './notifications.js';
|
|
11
|
+
import { processMessage } from './app-messages.js';
|
|
12
|
+
import { flushPendingExtMessages } from './app-misc.js';
|
|
13
|
+
import { resetTerminals } from './terminal.js';
|
|
14
|
+
import { closeDmUserPicker } from './sidebar-mates.js';
|
|
15
|
+
import { openDm } from './app-dm.js';
|
|
16
|
+
|
|
5
17
|
var wasConnected = false;
|
|
6
18
|
var reconnectTimer = null;
|
|
7
19
|
var reconnectDelay = 1000;
|
|
8
20
|
var connectTimeoutId = null;
|
|
9
21
|
var disconnectNotifTimer = null;
|
|
10
22
|
var disconnectNotifShown = false;
|
|
23
|
+
var connectOverlay = null;
|
|
24
|
+
|
|
25
|
+
export function initConnection() {
|
|
26
|
+
connectOverlay = document.getElementById("connect-overlay");
|
|
11
27
|
|
|
12
|
-
|
|
13
|
-
|
|
28
|
+
// --- Reactive UI sync for connected/processing state ---
|
|
29
|
+
store.subscribe(function (state, prev) {
|
|
30
|
+
// Status dot (depends on both connected and processing)
|
|
31
|
+
if (state.connected !== prev.connected || state.processing !== prev.processing) {
|
|
32
|
+
var dot = getStatusDot();
|
|
33
|
+
if (dot) {
|
|
34
|
+
dot.className = "icon-strip-status";
|
|
35
|
+
if (state.connected) {
|
|
36
|
+
dot.classList.add("connected");
|
|
37
|
+
if (state.processing) dot.classList.add("processing");
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Connected state changed
|
|
43
|
+
if (state.connected !== prev.connected) {
|
|
44
|
+
var sendBtn = getSendBtn();
|
|
45
|
+
if (state.connected) {
|
|
46
|
+
if (sendBtn) sendBtn.disabled = false;
|
|
47
|
+
if (connectOverlay) connectOverlay.classList.add("hidden");
|
|
48
|
+
var updPill = document.getElementById("update-pill-wrap");
|
|
49
|
+
if (updPill) updPill.classList.add("hidden");
|
|
50
|
+
stopLogoAnimation();
|
|
51
|
+
} else {
|
|
52
|
+
if (sendBtn) sendBtn.disabled = true;
|
|
53
|
+
if (connectOverlay) connectOverlay.classList.remove("hidden");
|
|
54
|
+
startLogoAnimation();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Processing state changed
|
|
59
|
+
if (state.processing !== prev.processing) {
|
|
60
|
+
if (state.processing) {
|
|
61
|
+
setSendBtnMode(hasSendableContent() ? "send" : "stop");
|
|
62
|
+
} else if (state.connected) {
|
|
63
|
+
setSendBtnMode("send");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
14
67
|
}
|
|
15
68
|
|
|
69
|
+
// setStatus: now just sets state. UI sync is handled by the subscriber above.
|
|
16
70
|
export function setStatus(status) {
|
|
17
|
-
var dot = _ctx.getStatusDot();
|
|
18
|
-
if (dot) dot.className = "icon-strip-status";
|
|
19
71
|
if (status === "connected") {
|
|
20
|
-
|
|
21
|
-
_ctx.setConnected(true);
|
|
22
|
-
_ctx.setProcessing(false);
|
|
23
|
-
_ctx.sendBtn.disabled = false;
|
|
24
|
-
_ctx.setSendBtnMode("send");
|
|
25
|
-
_ctx.connectOverlay.classList.add("hidden");
|
|
26
|
-
// Hide update banner on reconnect; server will re-send update_available if still needed
|
|
27
|
-
var updPill = document.getElementById("update-pill-wrap");
|
|
28
|
-
if (updPill) updPill.classList.add("hidden");
|
|
29
|
-
_ctx.stopVerbCycle();
|
|
72
|
+
store.set({ connected: true, processing: false });
|
|
30
73
|
} else if (status === "processing") {
|
|
31
|
-
|
|
32
|
-
_ctx.setProcessing(true);
|
|
33
|
-
_ctx.setSendBtnMode(_ctx.hasSendableContent() ? "send" : "stop");
|
|
74
|
+
store.set({ processing: true });
|
|
34
75
|
} else {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
76
|
+
store.set({ connected: false, processing: false });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function onConnected() {
|
|
81
|
+
// Flush any extension messages that arrived before WS was ready
|
|
82
|
+
flushPendingExtMessages();
|
|
83
|
+
|
|
84
|
+
// Reset terminal xterm instances (server will send fresh term_list)
|
|
85
|
+
resetTerminals();
|
|
86
|
+
|
|
87
|
+
// Re-send push subscription on reconnect
|
|
88
|
+
var ws = getWs();
|
|
89
|
+
if (window._pushSubscription) {
|
|
90
|
+
try {
|
|
91
|
+
ws.send(JSON.stringify({
|
|
92
|
+
type: "push_subscribe",
|
|
93
|
+
subscription: window._pushSubscription.toJSON(),
|
|
94
|
+
}));
|
|
95
|
+
} catch(e) {}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Request mates list
|
|
99
|
+
try {
|
|
100
|
+
ws.send(JSON.stringify({ type: "mate_list" }));
|
|
101
|
+
} catch(e) {}
|
|
102
|
+
|
|
103
|
+
// If connecting to a mate project, request knowledge list for badge
|
|
104
|
+
if (store.get('mateProjectSlug')) {
|
|
105
|
+
try { ws.send(JSON.stringify({ type: "knowledge_list" })); } catch(e) {}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Session restore is now server-driven (user-presence.json).
|
|
109
|
+
// Mate DM restore is also server-driven via "restore_mate_dm" message.
|
|
110
|
+
// Fallback: if server doesn't restore DM within 2s, try localStorage
|
|
111
|
+
var savedDm = null;
|
|
112
|
+
try { savedDm = localStorage.getItem("clay-active-dm"); } catch (e) {}
|
|
113
|
+
if (savedDm && !store.get('dmMode') && !store.get('mateProjectSlug')) {
|
|
114
|
+
var dmFallbackTimer = setTimeout(function () {
|
|
115
|
+
if (!store.get('dmMode') && savedDm) {
|
|
116
|
+
console.log("[dm-restore] Server did not restore DM, using localStorage fallback:", savedDm);
|
|
117
|
+
openDm(savedDm);
|
|
118
|
+
}
|
|
119
|
+
}, 2000);
|
|
120
|
+
// Cancel fallback if server restores DM first
|
|
121
|
+
var patchedOnce = false;
|
|
122
|
+
var checkRestore = function (evt) {
|
|
123
|
+
try {
|
|
124
|
+
var d = JSON.parse(evt.data);
|
|
125
|
+
if (d.type === "restore_mate_dm" && !patchedOnce) {
|
|
126
|
+
patchedOnce = true;
|
|
127
|
+
clearTimeout(dmFallbackTimer);
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {}
|
|
130
|
+
};
|
|
131
|
+
ws.addEventListener("message", checkRestore);
|
|
132
|
+
setTimeout(function () { ws.removeEventListener("message", checkRestore); }, 3000);
|
|
133
|
+
}
|
|
134
|
+
// Safety: clear returningFromMateDm after initial messages settle
|
|
135
|
+
if (store.get('returningFromMateDm')) {
|
|
136
|
+
setTimeout(function () {
|
|
137
|
+
if (store.get('returningFromMateDm')) {
|
|
138
|
+
store.set({ returningFromMateDm: false });
|
|
139
|
+
}
|
|
140
|
+
}, 2000);
|
|
39
141
|
}
|
|
40
142
|
}
|
|
41
143
|
|
|
42
144
|
export function connect() {
|
|
43
|
-
var ws =
|
|
145
|
+
var ws = getWs();
|
|
44
146
|
if (ws) { ws.onclose = null; ws.close(); }
|
|
45
147
|
if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
|
|
46
148
|
|
|
47
149
|
var protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
48
|
-
var newWs = new WebSocket(protocol + "//" + location.host +
|
|
49
|
-
|
|
150
|
+
var newWs = new WebSocket(protocol + "//" + location.host + store.get('wsPath'));
|
|
151
|
+
setWs(newWs);
|
|
50
152
|
|
|
51
153
|
// If not connected within 3s, force retry
|
|
52
154
|
connectTimeoutId = setTimeout(function () {
|
|
53
|
-
if (!
|
|
155
|
+
if (!store.get('connected')) {
|
|
54
156
|
newWs.onclose = null;
|
|
55
157
|
newWs.onerror = null;
|
|
56
158
|
newWs.close();
|
|
@@ -68,7 +170,7 @@ export function connect() {
|
|
|
68
170
|
// Only show "restored" notification if "lost" was actually shown
|
|
69
171
|
var isMobileDevice = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
70
172
|
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
71
|
-
if (wasConnected && disconnectNotifShown && !isMobileDevice &&
|
|
173
|
+
if (wasConnected && disconnectNotifShown && !isMobileDevice && isNotifAlertEnabled() && !document.hasFocus() && "serviceWorker" in navigator && Notification.permission === "granted") {
|
|
72
174
|
navigator.serviceWorker.ready.then(function (reg) {
|
|
73
175
|
return reg.showNotification("Clay", {
|
|
74
176
|
body: "Server connection restored",
|
|
@@ -83,22 +185,21 @@ export function connect() {
|
|
|
83
185
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
84
186
|
|
|
85
187
|
// Wrap ws.send to blink LED on outgoing traffic
|
|
86
|
-
var currentWs =
|
|
188
|
+
var currentWs = getWs();
|
|
87
189
|
var _origSend = currentWs.send.bind(currentWs);
|
|
88
190
|
currentWs.send = function (data) {
|
|
89
|
-
|
|
191
|
+
blinkIO();
|
|
90
192
|
return _origSend(data);
|
|
91
193
|
};
|
|
92
194
|
|
|
93
|
-
|
|
195
|
+
onConnected();
|
|
94
196
|
};
|
|
95
197
|
|
|
96
198
|
newWs.onclose = function (e) {
|
|
97
199
|
if (connectTimeoutId) { clearTimeout(connectTimeoutId); connectTimeoutId = null; }
|
|
98
|
-
|
|
200
|
+
closeDmUserPicker();
|
|
99
201
|
setStatus("disconnected");
|
|
100
|
-
|
|
101
|
-
_ctx.setActivity(null);
|
|
202
|
+
setActivity(null);
|
|
102
203
|
// Delay "connection lost" notification by 5s to suppress brief disconnects
|
|
103
204
|
if (!disconnectNotifTimer) {
|
|
104
205
|
disconnectNotifTimer = setTimeout(function () {
|
|
@@ -106,7 +207,7 @@ export function connect() {
|
|
|
106
207
|
disconnectNotifShown = true;
|
|
107
208
|
var isMobileDevice = /Mobi|Android|iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
108
209
|
(navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1);
|
|
109
|
-
if (!isMobileDevice &&
|
|
210
|
+
if (!isMobileDevice && isNotifAlertEnabled() && !document.hasFocus() && "serviceWorker" in navigator && Notification.permission === "granted") {
|
|
110
211
|
navigator.serviceWorker.ready.then(function (reg) {
|
|
111
212
|
return reg.showNotification("Clay", {
|
|
112
213
|
body: "Server connection lost",
|
|
@@ -123,16 +224,16 @@ export function connect() {
|
|
|
123
224
|
|
|
124
225
|
newWs.onmessage = function (event) {
|
|
125
226
|
// Backup: if we're receiving messages, we're connected
|
|
126
|
-
if (!
|
|
227
|
+
if (!store.get('connected')) {
|
|
127
228
|
setStatus("connected");
|
|
128
229
|
reconnectDelay = 1000;
|
|
129
230
|
if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
|
|
130
231
|
}
|
|
131
232
|
|
|
132
|
-
|
|
233
|
+
blinkIO();
|
|
133
234
|
var msg;
|
|
134
235
|
try { msg = JSON.parse(event.data); } catch (e) { return; }
|
|
135
|
-
|
|
236
|
+
processMessage(msg);
|
|
136
237
|
};
|
|
137
238
|
}
|
|
138
239
|
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Extracted from app.js (PR-27)
|
|
3
3
|
|
|
4
4
|
import { avatarUrl } from './avatar.js';
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import { getWs } from './ws-ref.js';
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
import { getMessagesEl } from './dom-refs.js';
|
|
8
|
+
import { registerTooltip } from './tooltip.js';
|
|
7
9
|
|
|
8
10
|
// --- Module-owned state ---
|
|
9
11
|
var cursorSharingEnabled = localStorage.getItem("cursorSharing") !== "off";
|
|
@@ -122,7 +124,7 @@ function getNodeAtCharOffset(container, charOffset) {
|
|
|
122
124
|
|
|
123
125
|
// Find parent [data-turn] element from a DOM node
|
|
124
126
|
function findParentTurn(node) {
|
|
125
|
-
var messagesEl =
|
|
127
|
+
var messagesEl = getMessagesEl();
|
|
126
128
|
var el = node.nodeType === 3 ? node.parentElement : node;
|
|
127
129
|
while (el && el !== messagesEl) {
|
|
128
130
|
if (el.dataset && el.dataset.turn != null) return el;
|
|
@@ -158,7 +160,7 @@ function createOffscreenIndicator(userId, displayName, color) {
|
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
function updateCursorVisibility(entry) {
|
|
161
|
-
var messagesEl =
|
|
163
|
+
var messagesEl = getMessagesEl();
|
|
162
164
|
var visibleTop = messagesEl.scrollTop;
|
|
163
165
|
var visibleBottom = visibleTop + messagesEl.clientHeight;
|
|
164
166
|
var y = entry.lastY || 0;
|
|
@@ -176,7 +178,7 @@ function updateCursorVisibility(entry) {
|
|
|
176
178
|
|
|
177
179
|
// Find the closest [data-turn] element to a given clientY
|
|
178
180
|
function findClosestTurn(clientY) {
|
|
179
|
-
var messagesEl =
|
|
181
|
+
var messagesEl = getMessagesEl();
|
|
180
182
|
var turns = messagesEl.querySelectorAll("[data-turn]");
|
|
181
183
|
if (!turns.length) return null;
|
|
182
184
|
// First: exact hit
|
|
@@ -198,7 +200,7 @@ function findClosestTurn(clientY) {
|
|
|
198
200
|
|
|
199
201
|
// Cursor sharing toggle button in user island (multi-user only)
|
|
200
202
|
export function initCursorToggle() {
|
|
201
|
-
if (!
|
|
203
|
+
if (!store.get('isMultiUserMode')) return;
|
|
202
204
|
var actionsEl = document.querySelector(".user-island-actions");
|
|
203
205
|
if (!actionsEl) return;
|
|
204
206
|
if (document.getElementById("cursor-share-toggle")) return;
|
|
@@ -218,11 +220,11 @@ export function initCursorToggle() {
|
|
|
218
220
|
if (cursorSharingEnabled) {
|
|
219
221
|
btn.classList.remove("off");
|
|
220
222
|
btn.classList.add("on");
|
|
221
|
-
|
|
223
|
+
registerTooltip(btn, "Cursor sharing on");
|
|
222
224
|
} else {
|
|
223
225
|
btn.classList.remove("on");
|
|
224
226
|
btn.classList.add("off");
|
|
225
|
-
|
|
227
|
+
registerTooltip(btn, "Cursor sharing off");
|
|
226
228
|
}
|
|
227
229
|
}
|
|
228
230
|
|
|
@@ -233,7 +235,7 @@ export function initCursorToggle() {
|
|
|
233
235
|
cursorSharingEnabled = !cursorSharingEnabled;
|
|
234
236
|
localStorage.setItem("cursorSharing", cursorSharingEnabled ? "on" : "off");
|
|
235
237
|
updateToggleStyle();
|
|
236
|
-
var ws =
|
|
238
|
+
var ws = getWs();
|
|
237
239
|
if (!cursorSharingEnabled && ws && ws.readyState === 1) {
|
|
238
240
|
ws.send(JSON.stringify({ type: "cursor_leave" }));
|
|
239
241
|
ws.send(JSON.stringify({ type: "text_select", ranges: [] }));
|
|
@@ -244,7 +246,7 @@ export function initCursorToggle() {
|
|
|
244
246
|
// --- Exported functions ---
|
|
245
247
|
|
|
246
248
|
export function handleRemoteSelection(msg) {
|
|
247
|
-
var messagesEl =
|
|
249
|
+
var messagesEl = getMessagesEl();
|
|
248
250
|
var userId = msg.userId;
|
|
249
251
|
var color = getCursorColor(userId);
|
|
250
252
|
|
|
@@ -305,7 +307,7 @@ export function handleRemoteSelection(msg) {
|
|
|
305
307
|
}
|
|
306
308
|
|
|
307
309
|
export function handleRemoteCursorMove(msg) {
|
|
308
|
-
var messagesEl =
|
|
310
|
+
var messagesEl = getMessagesEl();
|
|
309
311
|
var userId = msg.userId;
|
|
310
312
|
|
|
311
313
|
var entry = remoteCursors[userId];
|
|
@@ -379,16 +381,15 @@ export function clearRemoteCursors() {
|
|
|
379
381
|
remoteSelections = {};
|
|
380
382
|
}
|
|
381
383
|
|
|
382
|
-
export function initCursors(
|
|
383
|
-
|
|
384
|
-
var messagesEl = _ctx.messagesEl;
|
|
384
|
+
export function initCursors() {
|
|
385
|
+
var messagesEl = getMessagesEl();
|
|
385
386
|
|
|
386
387
|
initCursorToggle();
|
|
387
388
|
|
|
388
389
|
// Track local cursor and send to server
|
|
389
390
|
messagesEl.addEventListener("mousemove", function (e) {
|
|
390
391
|
if (!cursorSharingEnabled) return;
|
|
391
|
-
var ws =
|
|
392
|
+
var ws = getWs();
|
|
392
393
|
if (!ws || ws.readyState !== 1) return;
|
|
393
394
|
if (cursorThrottleTimer) return;
|
|
394
395
|
cursorThrottleTimer = setTimeout(function () { cursorThrottleTimer = null; }, CURSOR_THROTTLE_MS);
|
|
@@ -412,7 +413,7 @@ export function initCursors(ctx) {
|
|
|
412
413
|
|
|
413
414
|
messagesEl.addEventListener("mouseleave", function () {
|
|
414
415
|
if (!cursorSharingEnabled) return;
|
|
415
|
-
var ws =
|
|
416
|
+
var ws = getWs();
|
|
416
417
|
if (!ws || ws.readyState !== 1) return;
|
|
417
418
|
ws.send(JSON.stringify({ type: "cursor_leave" }));
|
|
418
419
|
});
|
|
@@ -429,7 +430,7 @@ export function initCursors(ctx) {
|
|
|
429
430
|
// Track local text selection and send to server
|
|
430
431
|
document.addEventListener("selectionchange", function () {
|
|
431
432
|
if (!cursorSharingEnabled) return;
|
|
432
|
-
var ws =
|
|
433
|
+
var ws = getWs();
|
|
433
434
|
if (!ws || ws.readyState !== 1) return;
|
|
434
435
|
if (selectionThrottleTimer) return;
|
|
435
436
|
selectionThrottleTimer = setTimeout(function () { selectionThrottleTimer = null; }, 100);
|
|
@@ -20,7 +20,7 @@ export function showDebateConcludeConfirm(msg) {
|
|
|
20
20
|
|
|
21
21
|
function showDebateConcludeMode() {
|
|
22
22
|
removeDebateBottomBar();
|
|
23
|
-
store.
|
|
23
|
+
store.set({ debateConcludeMode: true });
|
|
24
24
|
var inputArea = document.getElementById("input-area");
|
|
25
25
|
if (inputArea) {
|
|
26
26
|
inputArea.classList.add("debate-floor-mode");
|
|
@@ -57,7 +57,7 @@ function showDebateConcludeMode() {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function exitDebateConcludeMode() {
|
|
60
|
-
store.
|
|
60
|
+
store.set({ debateConcludeMode: false });
|
|
61
61
|
var inputArea = document.getElementById("input-area");
|
|
62
62
|
if (inputArea) inputArea.classList.remove("debate-floor-mode");
|
|
63
63
|
var banner = document.getElementById("debate-floor-banner");
|
|
@@ -82,7 +82,7 @@ export function handleDebateConcludeSend() {
|
|
|
82
82
|
|
|
83
83
|
export function showDebateEndedMode(msg) {
|
|
84
84
|
removeDebateBottomBar();
|
|
85
|
-
store.
|
|
85
|
+
store.set({ debateEndedMode: true });
|
|
86
86
|
var inputArea = document.getElementById("input-area");
|
|
87
87
|
if (inputArea) {
|
|
88
88
|
inputArea.classList.add("debate-floor-mode");
|
|
@@ -122,7 +122,7 @@ export function showDebateEndedMode(msg) {
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
export function exitDebateEndedMode() {
|
|
125
|
-
store.
|
|
125
|
+
store.set({ debateEndedMode: false });
|
|
126
126
|
var inputArea = document.getElementById("input-area");
|
|
127
127
|
if (inputArea) inputArea.classList.remove("debate-floor-mode");
|
|
128
128
|
var banner = document.getElementById("debate-floor-banner");
|
|
@@ -146,7 +146,7 @@ function handleDebateEndedSend() {
|
|
|
146
146
|
|
|
147
147
|
export function showDebateUserFloor(msg) {
|
|
148
148
|
removeDebateBottomBar();
|
|
149
|
-
store.
|
|
149
|
+
store.set({ debateFloorMode: true });
|
|
150
150
|
var inputArea = document.getElementById("input-area");
|
|
151
151
|
if (inputArea) {
|
|
152
152
|
inputArea.classList.add("debate-floor-mode");
|
|
@@ -184,7 +184,7 @@ export function showDebateUserFloor(msg) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
export function exitDebateFloorMode() {
|
|
187
|
-
store.
|
|
187
|
+
store.set({ debateFloorMode: false });
|
|
188
188
|
var inputArea = document.getElementById("input-area");
|
|
189
189
|
if (inputArea) inputArea.classList.remove("debate-floor-mode");
|
|
190
190
|
var banner = document.getElementById("debate-floor-banner");
|
|
@@ -230,9 +230,9 @@ export function renderDebateUserFloorDone(msg) {
|
|
|
230
230
|
|
|
231
231
|
export function showDebateSticky(phase, msg) {
|
|
232
232
|
if (phase === "ended" || phase === "hide") {
|
|
233
|
-
store.
|
|
233
|
+
store.set({ debateStickyState: null });
|
|
234
234
|
} else {
|
|
235
|
-
store.
|
|
235
|
+
store.set({ debateStickyState: { phase: phase, msg: msg } });
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
var stickyEl = document.getElementById("debate-sticky");
|
|
@@ -343,7 +343,7 @@ export function removeDebateBottomBar() {
|
|
|
343
343
|
var handBar = document.getElementById("debate-hand-raise-bar");
|
|
344
344
|
if (handBar) handBar.remove();
|
|
345
345
|
debateHandRaiseOpen = false;
|
|
346
|
-
var _ds = store.
|
|
346
|
+
var _ds = store.snap();
|
|
347
347
|
if (_ds.debateFloorMode) exitDebateFloorMode();
|
|
348
348
|
if (_ds.debateConcludeMode) exitDebateConcludeMode();
|
|
349
349
|
if (_ds.debateEndedMode) exitDebateEndedMode();
|