clay-server 2.5.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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +281 -0
  3. package/bin/cli.js +2385 -0
  4. package/lib/cli-sessions.js +270 -0
  5. package/lib/config.js +237 -0
  6. package/lib/daemon.js +489 -0
  7. package/lib/ipc.js +112 -0
  8. package/lib/notes.js +120 -0
  9. package/lib/pages.js +664 -0
  10. package/lib/project.js +1433 -0
  11. package/lib/public/app.js +2795 -0
  12. package/lib/public/apple-touch-icon-dark.png +0 -0
  13. package/lib/public/apple-touch-icon.png +0 -0
  14. package/lib/public/css/base.css +264 -0
  15. package/lib/public/css/diff.css +128 -0
  16. package/lib/public/css/filebrowser.css +1114 -0
  17. package/lib/public/css/highlight.css +144 -0
  18. package/lib/public/css/icon-strip.css +296 -0
  19. package/lib/public/css/input.css +573 -0
  20. package/lib/public/css/menus.css +856 -0
  21. package/lib/public/css/messages.css +1445 -0
  22. package/lib/public/css/mobile-nav.css +354 -0
  23. package/lib/public/css/overlays.css +697 -0
  24. package/lib/public/css/rewind.css +505 -0
  25. package/lib/public/css/server-settings.css +761 -0
  26. package/lib/public/css/sidebar.css +936 -0
  27. package/lib/public/css/sticky-notes.css +358 -0
  28. package/lib/public/css/title-bar.css +314 -0
  29. package/lib/public/favicon-dark.svg +1 -0
  30. package/lib/public/favicon.svg +1 -0
  31. package/lib/public/icon-192-dark.png +0 -0
  32. package/lib/public/icon-192.png +0 -0
  33. package/lib/public/icon-512-dark.png +0 -0
  34. package/lib/public/icon-512.png +0 -0
  35. package/lib/public/icon-mono.svg +1 -0
  36. package/lib/public/index.html +762 -0
  37. package/lib/public/manifest.json +27 -0
  38. package/lib/public/modules/diff.js +398 -0
  39. package/lib/public/modules/events.js +21 -0
  40. package/lib/public/modules/filebrowser.js +1411 -0
  41. package/lib/public/modules/fileicons.js +172 -0
  42. package/lib/public/modules/icons.js +54 -0
  43. package/lib/public/modules/input.js +584 -0
  44. package/lib/public/modules/markdown.js +356 -0
  45. package/lib/public/modules/notifications.js +649 -0
  46. package/lib/public/modules/qrcode.js +70 -0
  47. package/lib/public/modules/rewind.js +345 -0
  48. package/lib/public/modules/server-settings.js +510 -0
  49. package/lib/public/modules/sidebar.js +1083 -0
  50. package/lib/public/modules/state.js +3 -0
  51. package/lib/public/modules/sticky-notes.js +688 -0
  52. package/lib/public/modules/terminal.js +697 -0
  53. package/lib/public/modules/theme.js +738 -0
  54. package/lib/public/modules/tools.js +1608 -0
  55. package/lib/public/modules/utils.js +56 -0
  56. package/lib/public/style.css +15 -0
  57. package/lib/public/sw.js +75 -0
  58. package/lib/push.js +124 -0
  59. package/lib/sdk-bridge.js +989 -0
  60. package/lib/server.js +582 -0
  61. package/lib/sessions.js +424 -0
  62. package/lib/terminal-manager.js +187 -0
  63. package/lib/terminal.js +24 -0
  64. package/lib/themes/ayu-light.json +9 -0
  65. package/lib/themes/catppuccin-latte.json +9 -0
  66. package/lib/themes/catppuccin-mocha.json +9 -0
  67. package/lib/themes/clay-light.json +10 -0
  68. package/lib/themes/clay.json +10 -0
  69. package/lib/themes/dracula.json +9 -0
  70. package/lib/themes/everforest-light.json +9 -0
  71. package/lib/themes/everforest.json +9 -0
  72. package/lib/themes/github-light.json +9 -0
  73. package/lib/themes/gruvbox-dark.json +9 -0
  74. package/lib/themes/gruvbox-light.json +9 -0
  75. package/lib/themes/monokai.json +9 -0
  76. package/lib/themes/nord-light.json +9 -0
  77. package/lib/themes/nord.json +9 -0
  78. package/lib/themes/one-dark.json +9 -0
  79. package/lib/themes/one-light.json +9 -0
  80. package/lib/themes/rose-pine-dawn.json +9 -0
  81. package/lib/themes/rose-pine.json +9 -0
  82. package/lib/themes/solarized-dark.json +9 -0
  83. package/lib/themes/solarized-light.json +9 -0
  84. package/lib/themes/tokyo-night-light.json +9 -0
  85. package/lib/themes/tokyo-night.json +9 -0
  86. package/lib/updater.js +97 -0
  87. package/package.json +47 -0
@@ -0,0 +1,1083 @@
1
+ import { escapeHtml, copyToClipboard } from './utils.js';
2
+ import { iconHtml, refreshIcons } from './icons.js';
3
+
4
+ var ctx;
5
+
6
+ // --- Session search ---
7
+ var searchQuery = "";
8
+ var searchMatchIds = null; // null = no search, Set of matched session IDs
9
+ var searchDebounce = null;
10
+ var cachedSessions = [];
11
+
12
+ // --- Cached project data for mobile sheet ---
13
+ var cachedProjectList = [];
14
+ var cachedCurrentSlug = null;
15
+
16
+ // --- Session context menu ---
17
+ var sessionCtxMenu = null;
18
+ var sessionCtxSessionId = null;
19
+
20
+ function closeSessionCtxMenu() {
21
+ if (sessionCtxMenu) {
22
+ sessionCtxMenu.remove();
23
+ sessionCtxMenu = null;
24
+ sessionCtxSessionId = null;
25
+ }
26
+ }
27
+
28
+ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid) {
29
+ closeSessionCtxMenu();
30
+ sessionCtxSessionId = sessionId;
31
+
32
+ var menu = document.createElement("div");
33
+ menu.className = "session-ctx-menu";
34
+
35
+ var renameItem = document.createElement("button");
36
+ renameItem.className = "session-ctx-item";
37
+ renameItem.innerHTML = iconHtml("pencil") + " <span>Rename</span>";
38
+ renameItem.addEventListener("click", function (e) {
39
+ e.stopPropagation();
40
+ closeSessionCtxMenu();
41
+ startInlineRename(sessionId, title);
42
+ });
43
+ menu.appendChild(renameItem);
44
+
45
+ var deleteItem = document.createElement("button");
46
+ deleteItem.className = "session-ctx-item session-ctx-delete";
47
+ deleteItem.innerHTML = iconHtml("trash-2") + " <span>Delete</span>";
48
+ deleteItem.addEventListener("click", function (e) {
49
+ e.stopPropagation();
50
+ closeSessionCtxMenu();
51
+ ctx.showConfirm('Delete "' + (title || "New Session") + '"? This session and its history will be permanently removed.', function () {
52
+ var ws = ctx.ws;
53
+ if (ws && ctx.connected) {
54
+ ws.send(JSON.stringify({ type: "delete_session", id: sessionId }));
55
+ }
56
+ });
57
+ });
58
+ menu.appendChild(deleteItem);
59
+
60
+ document.body.appendChild(menu);
61
+ sessionCtxMenu = menu;
62
+ refreshIcons();
63
+
64
+ // Position: fixed relative to the anchor button
65
+ requestAnimationFrame(function () {
66
+ var btnRect = anchorBtn.getBoundingClientRect();
67
+ menu.style.position = "fixed";
68
+ menu.style.top = (btnRect.bottom + 2) + "px";
69
+ menu.style.right = (window.innerWidth - btnRect.right) + "px";
70
+ menu.style.left = "auto";
71
+ // If menu overflows below viewport, flip up
72
+ var menuRect = menu.getBoundingClientRect();
73
+ if (menuRect.bottom > window.innerHeight - 8) {
74
+ menu.style.top = (btnRect.top - menuRect.height - 2) + "px";
75
+ }
76
+ });
77
+ }
78
+
79
+ function startInlineRename(sessionId, currentTitle) {
80
+ var el = ctx.sessionListEl.querySelector('.session-item[data-session-id="' + sessionId + '"]');
81
+ if (!el) return;
82
+ var textSpan = el.querySelector(".session-item-text");
83
+ if (!textSpan) return;
84
+
85
+ var input = document.createElement("input");
86
+ input.type = "text";
87
+ input.className = "session-rename-input";
88
+ input.value = currentTitle || "New Session";
89
+
90
+ var originalHtml = textSpan.innerHTML;
91
+ textSpan.innerHTML = "";
92
+ textSpan.appendChild(input);
93
+ input.focus();
94
+ input.select();
95
+
96
+ function commitRename() {
97
+ var newTitle = input.value.trim();
98
+ if (newTitle && newTitle !== currentTitle && ctx.ws && ctx.connected) {
99
+ ctx.ws.send(JSON.stringify({ type: "rename_session", id: sessionId, title: newTitle }));
100
+ }
101
+ // Restore text (server will send updated session_list)
102
+ textSpan.innerHTML = originalHtml;
103
+ if (newTitle && newTitle !== currentTitle) {
104
+ textSpan.textContent = newTitle;
105
+ }
106
+ }
107
+
108
+ input.addEventListener("keydown", function (e) {
109
+ if (e.key === "Enter") { e.preventDefault(); commitRename(); }
110
+ if (e.key === "Escape") { e.preventDefault(); textSpan.innerHTML = originalHtml; }
111
+ });
112
+ input.addEventListener("blur", commitRename);
113
+ input.addEventListener("click", function (e) { e.stopPropagation(); });
114
+ }
115
+
116
+ function getDateGroup(ts) {
117
+ var now = new Date();
118
+ var d = new Date(ts);
119
+ var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
120
+ var yesterday = new Date(today.getTime() - 86400000);
121
+ var weekAgo = new Date(today.getTime() - 7 * 86400000);
122
+ if (d >= today) return "Today";
123
+ if (d >= yesterday) return "Yesterday";
124
+ if (d >= weekAgo) return "This Week";
125
+ return "Older";
126
+ }
127
+
128
+ function highlightMatch(text, query) {
129
+ if (!query) return escapeHtml(text);
130
+ var lower = text.toLowerCase();
131
+ var qLower = query.toLowerCase();
132
+ var idx = lower.indexOf(qLower);
133
+ if (idx === -1) return escapeHtml(text);
134
+ var before = text.substring(0, idx);
135
+ var match = text.substring(idx, idx + query.length);
136
+ var after = text.substring(idx + query.length);
137
+ return escapeHtml(before) + '<mark class="session-highlight">' + escapeHtml(match) + '</mark>' + escapeHtml(after);
138
+ }
139
+
140
+ function renderSessionItem(s) {
141
+ var el = document.createElement("div");
142
+ var isMatch = searchMatchIds !== null && searchMatchIds.has(s.id);
143
+ var dimmed = searchMatchIds !== null && !isMatch;
144
+ el.className = "session-item" + (s.active ? " active" : "") + (isMatch ? " search-match" : "") + (dimmed ? " search-dimmed" : "");
145
+ el.dataset.sessionId = s.id;
146
+
147
+ var textSpan = document.createElement("span");
148
+ textSpan.className = "session-item-text";
149
+ var textHtml = "";
150
+ if (s.isProcessing) {
151
+ textHtml += '<span class="session-processing"></span>';
152
+ }
153
+ textHtml += highlightMatch(s.title || "New Session", searchQuery);
154
+ textSpan.innerHTML = textHtml;
155
+ el.appendChild(textSpan);
156
+
157
+ var moreBtn = document.createElement("button");
158
+ moreBtn.className = "session-more-btn";
159
+ moreBtn.innerHTML = iconHtml("ellipsis");
160
+ moreBtn.title = "More options";
161
+ moreBtn.addEventListener("click", (function(id, title, cliSid, btn) {
162
+ return function(e) {
163
+ e.stopPropagation();
164
+ showSessionCtxMenu(btn, id, title, cliSid);
165
+ };
166
+ })(s.id, s.title, s.cliSessionId, moreBtn));
167
+ el.appendChild(moreBtn);
168
+
169
+ el.addEventListener("click", (function (id) {
170
+ return function () {
171
+ if (ctx.ws && ctx.connected) {
172
+ ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
173
+ closeSidebar();
174
+ }
175
+ };
176
+ })(s.id));
177
+
178
+ return el;
179
+ }
180
+
181
+ export function renderSessionList(sessions) {
182
+ if (sessions) cachedSessions = sessions;
183
+
184
+ ctx.sessionListEl.innerHTML = "";
185
+
186
+ // Sort by lastActivity descending (most recent first)
187
+ var sorted = cachedSessions.slice().sort(function (a, b) {
188
+ return (b.lastActivity || 0) - (a.lastActivity || 0);
189
+ });
190
+
191
+ var currentGroup = "";
192
+ for (var i = 0; i < sorted.length; i++) {
193
+ var s = sorted[i];
194
+ var group = getDateGroup(s.lastActivity || 0);
195
+ if (group !== currentGroup) {
196
+ currentGroup = group;
197
+ var header = document.createElement("div");
198
+ header.className = "session-group-header";
199
+ header.textContent = group;
200
+ ctx.sessionListEl.appendChild(header);
201
+ }
202
+ ctx.sessionListEl.appendChild(renderSessionItem(s));
203
+ }
204
+ refreshIcons();
205
+ updatePageTitle();
206
+ }
207
+
208
+ export function handleSearchResults(msg) {
209
+ if (msg.query !== searchQuery) return; // stale response
210
+ var ids = new Set();
211
+ for (var i = 0; i < msg.results.length; i++) {
212
+ ids.add(msg.results[i].id);
213
+ }
214
+ searchMatchIds = ids;
215
+ renderSessionList(null);
216
+
217
+ // Build timeline for current session if it matches
218
+ var activeEl = ctx.sessionListEl.querySelector(".session-item.active");
219
+ if (activeEl) {
220
+ var activeId = parseInt(activeEl.dataset.sessionId, 10);
221
+ if (ids.has(activeId)) {
222
+ buildSearchTimeline(searchQuery);
223
+ } else {
224
+ removeSearchTimeline();
225
+ }
226
+ }
227
+ }
228
+
229
+ export function updatePageTitle() {
230
+ var sessionTitle = "";
231
+ var activeItem = ctx.sessionListEl.querySelector(".session-item.active .session-item-text");
232
+ if (activeItem) sessionTitle = activeItem.textContent;
233
+ if (ctx.headerTitleEl) {
234
+ ctx.headerTitleEl.textContent = sessionTitle || ctx.projectName || "Clay";
235
+ }
236
+ var tbProjectName = ctx.$("title-bar-project-name");
237
+ if (tbProjectName && ctx.projectName) {
238
+ tbProjectName.textContent = ctx.projectName;
239
+ } else if (tbProjectName && !tbProjectName.textContent) {
240
+ // Fallback: derive name from URL slug when projectName not yet available
241
+ var _m = location.pathname.match(/^\/p\/([a-z0-9_-]+)/);
242
+ if (_m) tbProjectName.textContent = _m[1];
243
+ }
244
+ if (ctx.projectName && sessionTitle) {
245
+ document.title = sessionTitle + " - " + ctx.projectName;
246
+ } else if (ctx.projectName) {
247
+ document.title = ctx.projectName + " - Clay";
248
+ } else {
249
+ document.title = "Clay";
250
+ }
251
+ }
252
+
253
+ export function openSidebar() {
254
+ ctx.sidebar.classList.add("open");
255
+ ctx.sidebarOverlay.classList.add("visible");
256
+ }
257
+
258
+ export function closeSidebar() {
259
+ ctx.sidebar.classList.remove("open");
260
+ ctx.sidebarOverlay.classList.remove("visible");
261
+ }
262
+
263
+ // --- Mobile sheet (fullscreen overlay for Projects / Sessions) ---
264
+
265
+ function openMobileSheet(type) {
266
+ var sheet = document.getElementById("mobile-sheet");
267
+ if (!sheet) return;
268
+
269
+ var titleEl = sheet.querySelector(".mobile-sheet-title");
270
+ var listEl = sheet.querySelector(".mobile-sheet-list");
271
+ if (!titleEl || !listEl) return;
272
+
273
+ // Return file tree to sidebar before clearing (prevents destroying it)
274
+ if (sheet.classList.contains("sheet-files")) {
275
+ var prevFileTree = document.getElementById("file-tree");
276
+ var prevPanel = document.getElementById("sidebar-panel-files");
277
+ if (prevFileTree && prevPanel) prevPanel.appendChild(prevFileTree);
278
+ }
279
+
280
+ listEl.innerHTML = "";
281
+ sheet.classList.remove("sheet-files");
282
+
283
+ if (type === "projects") {
284
+ titleEl.textContent = "Projects";
285
+ renderSheetProjects(listEl);
286
+ } else if (type === "sessions") {
287
+ titleEl.textContent = "Sessions";
288
+ renderSheetSessions(listEl);
289
+ } else if (type === "files") {
290
+ titleEl.textContent = "Files";
291
+ sheet.classList.add("sheet-files");
292
+ var fileTree = document.getElementById("file-tree");
293
+ if (fileTree) {
294
+ listEl.appendChild(fileTree);
295
+ fileTree.classList.remove("hidden");
296
+ }
297
+ if (ctx.onFilesTabOpen) ctx.onFilesTabOpen();
298
+ }
299
+
300
+ sheet.classList.remove("hidden", "closing");
301
+ refreshIcons();
302
+ }
303
+
304
+ function closeMobileSheet() {
305
+ var sheet = document.getElementById("mobile-sheet");
306
+ if (!sheet || sheet.classList.contains("hidden")) return;
307
+
308
+ // Return file tree to sidebar if it was moved
309
+ if (sheet.classList.contains("sheet-files")) {
310
+ var fileTree = document.getElementById("file-tree");
311
+ var sidebarFilesPanel = document.getElementById("sidebar-panel-files");
312
+ if (fileTree && sidebarFilesPanel) {
313
+ sidebarFilesPanel.appendChild(fileTree);
314
+ }
315
+ }
316
+
317
+ sheet.classList.add("closing");
318
+ setTimeout(function () {
319
+ sheet.classList.add("hidden");
320
+ sheet.classList.remove("closing", "sheet-files");
321
+ }, 230);
322
+ }
323
+
324
+ function renderSheetProjects(listEl) {
325
+ for (var i = 0; i < cachedProjectList.length; i++) {
326
+ (function (p) {
327
+ var el = document.createElement("button");
328
+ el.className = "mobile-project-item" + (p.slug === cachedCurrentSlug ? " active" : "");
329
+
330
+ var abbrev = document.createElement("span");
331
+ abbrev.className = "mobile-project-abbrev";
332
+ abbrev.textContent = getProjectAbbrev(p.name);
333
+ el.appendChild(abbrev);
334
+
335
+ var name = document.createElement("span");
336
+ name.className = "mobile-project-name";
337
+ name.textContent = p.name;
338
+ el.appendChild(name);
339
+
340
+ if (p.isProcessing) {
341
+ var dot = document.createElement("span");
342
+ dot.className = "mobile-project-processing";
343
+ el.appendChild(dot);
344
+ }
345
+
346
+ el.addEventListener("click", function () {
347
+ if (ctx.switchProject) ctx.switchProject(p.slug);
348
+ closeMobileSheet();
349
+ });
350
+
351
+ listEl.appendChild(el);
352
+ })(cachedProjectList[i]);
353
+ }
354
+ }
355
+
356
+ function renderSheetSessions(listEl) {
357
+ var sorted = cachedSessions.slice().sort(function (a, b) {
358
+ return (b.lastActivity || 0) - (a.lastActivity || 0);
359
+ });
360
+
361
+ var currentGroup = "";
362
+ for (var i = 0; i < sorted.length; i++) {
363
+ var s = sorted[i];
364
+ var group = getDateGroup(s.lastActivity || 0);
365
+ if (group !== currentGroup) {
366
+ currentGroup = group;
367
+ var header = document.createElement("div");
368
+ header.className = "mobile-sheet-group";
369
+ header.textContent = group;
370
+ listEl.appendChild(header);
371
+ }
372
+
373
+ var el = document.createElement("button");
374
+ el.className = "mobile-session-item" + (s.active ? " active" : "");
375
+
376
+ var titleSpan = document.createElement("span");
377
+ titleSpan.className = "mobile-session-title";
378
+ titleSpan.textContent = s.title || "New Session";
379
+ el.appendChild(titleSpan);
380
+
381
+ if (s.isProcessing) {
382
+ var dot = document.createElement("span");
383
+ dot.className = "mobile-session-processing";
384
+ el.appendChild(dot);
385
+ }
386
+
387
+ (function (id) {
388
+ el.addEventListener("click", function () {
389
+ if (ctx.ws && ctx.connected) {
390
+ ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
391
+ }
392
+ closeMobileSheet();
393
+ });
394
+ })(s.id);
395
+
396
+ listEl.appendChild(el);
397
+ }
398
+ }
399
+
400
+ export function initSidebar(_ctx) {
401
+ ctx = _ctx;
402
+
403
+ document.addEventListener("click", function () { closeSessionCtxMenu(); });
404
+
405
+ ctx.hamburgerBtn.addEventListener("click", function () {
406
+ ctx.sidebar.classList.contains("open") ? closeSidebar() : openSidebar();
407
+ });
408
+
409
+ ctx.sidebarOverlay.addEventListener("click", closeSidebar);
410
+
411
+ // --- Desktop sidebar collapse/expand ---
412
+ function toggleSidebarCollapse() {
413
+ var layout = ctx.$("layout");
414
+ var collapsed = layout.classList.toggle("sidebar-collapsed");
415
+ try { localStorage.setItem("sidebar-collapsed", collapsed ? "1" : ""); } catch (e) {}
416
+ setTimeout(function () { syncUserIslandWidth(); syncResizeHandle(); }, 210);
417
+ }
418
+
419
+ if (ctx.sidebarToggleBtn) ctx.sidebarToggleBtn.addEventListener("click", toggleSidebarCollapse);
420
+ if (ctx.sidebarExpandBtn) ctx.sidebarExpandBtn.addEventListener("click", toggleSidebarCollapse);
421
+
422
+ // Restore collapsed state from localStorage
423
+ try {
424
+ if (localStorage.getItem("sidebar-collapsed") === "1") {
425
+ ctx.$("layout").classList.add("sidebar-collapsed");
426
+ }
427
+ } catch (e) {}
428
+
429
+ ctx.newSessionBtn.addEventListener("click", function () {
430
+ if (ctx.ws && ctx.connected) {
431
+ ctx.ws.send(JSON.stringify({ type: "new_session" }));
432
+ closeSidebar();
433
+ }
434
+ });
435
+
436
+ // --- Session search ---
437
+ var searchBtn = ctx.$("search-session-btn");
438
+ var searchBox = ctx.$("session-search");
439
+ var searchInput = ctx.$("session-search-input");
440
+ var searchClear = ctx.$("session-search-clear");
441
+
442
+ function openSearch() {
443
+ searchBox.classList.remove("hidden");
444
+ searchBtn.classList.add("active");
445
+ searchInput.value = "";
446
+ searchQuery = "";
447
+ setTimeout(function () { searchInput.focus(); }, 50);
448
+ }
449
+
450
+ function closeSearch() {
451
+ searchBox.classList.add("hidden");
452
+ searchBtn.classList.remove("active");
453
+ searchInput.value = "";
454
+ searchQuery = "";
455
+ searchMatchIds = null;
456
+ if (searchDebounce) { clearTimeout(searchDebounce); searchDebounce = null; }
457
+ removeSearchTimeline();
458
+ renderSessionList(null);
459
+ }
460
+
461
+ searchBtn.addEventListener("click", function () {
462
+ if (searchBox.classList.contains("hidden")) {
463
+ openSearch();
464
+ } else {
465
+ closeSearch();
466
+ }
467
+ });
468
+
469
+ if (searchClear) {
470
+ searchClear.addEventListener("click", function () {
471
+ closeSearch();
472
+ });
473
+ }
474
+
475
+ searchInput.addEventListener("input", function () {
476
+ searchQuery = searchInput.value.trim();
477
+ if (searchDebounce) clearTimeout(searchDebounce);
478
+ if (!searchQuery) {
479
+ searchMatchIds = null;
480
+ removeSearchTimeline();
481
+ renderSessionList(null);
482
+ return;
483
+ }
484
+ searchDebounce = setTimeout(function () {
485
+ if (ctx.ws && ctx.connected) {
486
+ ctx.ws.send(JSON.stringify({ type: "search_sessions", query: searchQuery }));
487
+ }
488
+ }, 200);
489
+ });
490
+
491
+ searchInput.addEventListener("keydown", function (e) {
492
+ if (e.key === "Escape") {
493
+ e.preventDefault();
494
+ closeSearch();
495
+ }
496
+ });
497
+
498
+ // --- Resume session picker ---
499
+ var resumeModal = ctx.$("resume-modal");
500
+ var resumeCancel = ctx.$("resume-cancel");
501
+ var pickerLoading = ctx.$("resume-picker-loading");
502
+ var pickerEmpty = ctx.$("resume-picker-empty");
503
+ var pickerList = ctx.$("resume-picker-list");
504
+
505
+ function openResumeModal() {
506
+ resumeModal.classList.remove("hidden");
507
+ pickerLoading.classList.remove("hidden");
508
+ pickerEmpty.classList.add("hidden");
509
+ pickerList.classList.add("hidden");
510
+ pickerList.innerHTML = "";
511
+ if (ctx.ws && ctx.connected) {
512
+ ctx.ws.send(JSON.stringify({ type: "list_cli_sessions" }));
513
+ }
514
+ }
515
+
516
+ function closeResumeModal() {
517
+ resumeModal.classList.add("hidden");
518
+ }
519
+
520
+ ctx.resumeSessionBtn.addEventListener("click", openResumeModal);
521
+ resumeCancel.addEventListener("click", closeResumeModal);
522
+ resumeModal.querySelector(".confirm-backdrop").addEventListener("click", closeResumeModal);
523
+
524
+ // --- Panel switch (sessions / files / projects) ---
525
+ var fileBrowserBtn = ctx.$("file-browser-btn");
526
+ var projectsPanel = ctx.$("sidebar-panel-projects");
527
+ var sessionsPanel = ctx.$("sidebar-panel-sessions");
528
+ var filesPanel = ctx.$("sidebar-panel-files");
529
+ var sessionsHeaderContent = ctx.$("sessions-header-content");
530
+ var filesHeaderContent = ctx.$("files-header-content");
531
+ var filePanelClose = ctx.$("file-panel-close");
532
+
533
+ function hideAllPanels() {
534
+ if (projectsPanel) projectsPanel.classList.add("hidden");
535
+ if (sessionsPanel) sessionsPanel.classList.add("hidden");
536
+ if (filesPanel) filesPanel.classList.add("hidden");
537
+ if (sessionsHeaderContent) sessionsHeaderContent.classList.add("hidden");
538
+ if (filesHeaderContent) filesHeaderContent.classList.add("hidden");
539
+ }
540
+
541
+ function showProjectsPanel() {
542
+ hideAllPanels();
543
+ if (projectsPanel) projectsPanel.classList.remove("hidden");
544
+ }
545
+
546
+ function showSessionsPanel() {
547
+ hideAllPanels();
548
+ if (sessionsPanel) sessionsPanel.classList.remove("hidden");
549
+ if (sessionsHeaderContent) sessionsHeaderContent.classList.remove("hidden");
550
+ }
551
+
552
+ function showFilesPanel() {
553
+ hideAllPanels();
554
+ if (filesPanel) filesPanel.classList.remove("hidden");
555
+ if (filesHeaderContent) filesHeaderContent.classList.remove("hidden");
556
+ if (ctx.onFilesTabOpen) ctx.onFilesTabOpen();
557
+ }
558
+
559
+ if (fileBrowserBtn) {
560
+ fileBrowserBtn.addEventListener("click", showFilesPanel);
561
+ }
562
+ if (filePanelClose) {
563
+ filePanelClose.addEventListener("click", showSessionsPanel);
564
+ }
565
+
566
+ // --- Mobile sheet close handlers ---
567
+ var mobileSheet = document.getElementById("mobile-sheet");
568
+ if (mobileSheet) {
569
+ var sheetBackdrop = mobileSheet.querySelector(".mobile-sheet-backdrop");
570
+ var sheetCloseBtn = mobileSheet.querySelector(".mobile-sheet-close");
571
+ if (sheetBackdrop) sheetBackdrop.addEventListener("click", closeMobileSheet);
572
+ if (sheetCloseBtn) sheetCloseBtn.addEventListener("click", closeMobileSheet);
573
+ }
574
+
575
+ // --- Mobile tab bar ---
576
+ var mobileTabBar = document.getElementById("mobile-tab-bar");
577
+ var mobileTabs = mobileTabBar ? mobileTabBar.querySelectorAll(".mobile-tab") : [];
578
+ var mobileNewBtn = document.getElementById("mobile-new-session-btn");
579
+
580
+ function setMobileTabActive(tabName) {
581
+ for (var i = 0; i < mobileTabs.length; i++) {
582
+ if (mobileTabs[i].dataset.tab === tabName) {
583
+ mobileTabs[i].classList.add("active");
584
+ } else {
585
+ mobileTabs[i].classList.remove("active");
586
+ }
587
+ }
588
+ }
589
+
590
+ for (var t = 0; t < mobileTabs.length; t++) {
591
+ (function (tab) {
592
+ tab.addEventListener("click", function () {
593
+ var name = tab.dataset.tab;
594
+
595
+ if (name === "terminal") {
596
+ closeSidebar();
597
+ setMobileTabActive("");
598
+ if (ctx.openTerminal) ctx.openTerminal();
599
+ return;
600
+ }
601
+
602
+ if (name === "projects") {
603
+ openMobileSheet("projects");
604
+ setMobileTabActive("projects");
605
+ } else if (name === "sessions") {
606
+ openMobileSheet("sessions");
607
+ setMobileTabActive("sessions");
608
+ } else if (name === "files") {
609
+ openMobileSheet("files");
610
+ setMobileTabActive("files");
611
+ }
612
+ });
613
+ })(mobileTabs[t]);
614
+ }
615
+
616
+ if (mobileNewBtn) {
617
+ mobileNewBtn.addEventListener("click", function () {
618
+ if (ctx.ws && ctx.connected) {
619
+ ctx.ws.send(JSON.stringify({ type: "new_session" }));
620
+ closeSidebar();
621
+ setMobileTabActive("");
622
+ }
623
+ });
624
+ }
625
+
626
+ // --- User island width sync ---
627
+ var userIsland = document.getElementById("user-island");
628
+ var sidebarColumn = document.getElementById("sidebar-column");
629
+
630
+ function syncUserIslandWidth() {
631
+ if (!userIsland || !sidebarColumn) return;
632
+ var rect = sidebarColumn.getBoundingClientRect();
633
+ userIsland.style.width = (rect.right - 8 - 8) + "px";
634
+ }
635
+
636
+ // --- Sidebar resize handle ---
637
+ var resizeHandle = document.getElementById("sidebar-resize-handle");
638
+
639
+ function syncResizeHandle() {
640
+ if (!resizeHandle || !sidebarColumn) return;
641
+ var rect = sidebarColumn.getBoundingClientRect();
642
+ var parentRect = sidebarColumn.parentElement.getBoundingClientRect();
643
+ resizeHandle.style.left = (rect.right - parentRect.left) + "px";
644
+ }
645
+
646
+ if (resizeHandle && sidebarColumn) {
647
+ var dragging = false;
648
+
649
+ function onResizeMove(e) {
650
+ if (!dragging) return;
651
+ e.preventDefault();
652
+ var clientX = e.touches ? e.touches[0].clientX : e.clientX;
653
+ var iconStrip = document.getElementById("icon-strip");
654
+ var stripWidth = iconStrip ? iconStrip.offsetWidth : 72;
655
+ var newWidth = clientX - stripWidth;
656
+ if (newWidth < 192) newWidth = 192;
657
+ if (newWidth > 320) newWidth = 320;
658
+ sidebarColumn.style.width = newWidth + "px";
659
+ syncResizeHandle();
660
+ syncUserIslandWidth();
661
+ }
662
+
663
+ function onResizeEnd() {
664
+ if (!dragging) return;
665
+ dragging = false;
666
+ resizeHandle.classList.remove("dragging");
667
+ document.body.style.cursor = "";
668
+ document.body.style.userSelect = "";
669
+ document.removeEventListener("mousemove", onResizeMove);
670
+ document.removeEventListener("mouseup", onResizeEnd);
671
+ document.removeEventListener("touchmove", onResizeMove);
672
+ document.removeEventListener("touchend", onResizeEnd);
673
+ try { localStorage.setItem("sidebar-width", sidebarColumn.style.width); } catch (e) {}
674
+ }
675
+
676
+ function onResizeStart(e) {
677
+ e.preventDefault();
678
+ dragging = true;
679
+ resizeHandle.classList.add("dragging");
680
+ document.body.style.cursor = "col-resize";
681
+ document.body.style.userSelect = "none";
682
+ document.addEventListener("mousemove", onResizeMove);
683
+ document.addEventListener("mouseup", onResizeEnd);
684
+ document.addEventListener("touchmove", onResizeMove, { passive: false });
685
+ document.addEventListener("touchend", onResizeEnd);
686
+ }
687
+
688
+ resizeHandle.addEventListener("mousedown", onResizeStart);
689
+ resizeHandle.addEventListener("touchstart", onResizeStart, { passive: false });
690
+
691
+ // Restore saved width (skip transition so user-island syncs immediately)
692
+ try {
693
+ var savedWidth = localStorage.getItem("sidebar-width");
694
+ if (savedWidth) {
695
+ var px = parseInt(savedWidth, 10);
696
+ if (px >= 192 && px <= 320) {
697
+ sidebarColumn.style.transition = "none";
698
+ sidebarColumn.style.width = px + "px";
699
+ sidebarColumn.offsetWidth; // force reflow
700
+ sidebarColumn.style.transition = "";
701
+ }
702
+ }
703
+ } catch (e) {}
704
+
705
+ syncResizeHandle();
706
+ syncUserIslandWidth();
707
+ }
708
+
709
+ // Initial sync even if no resize handle
710
+ syncUserIslandWidth();
711
+ }
712
+
713
+ // --- CLI session picker ---
714
+ function relativeTime(isoString) {
715
+ if (!isoString) return "";
716
+ var ms = Date.now() - new Date(isoString).getTime();
717
+ var sec = Math.floor(ms / 1000);
718
+ if (sec < 60) return "just now";
719
+ var min = Math.floor(sec / 60);
720
+ if (min < 60) return min + "m ago";
721
+ var hr = Math.floor(min / 60);
722
+ if (hr < 24) return hr + "h ago";
723
+ var days = Math.floor(hr / 24);
724
+ if (days < 30) return days + "d ago";
725
+ return new Date(isoString).toLocaleDateString();
726
+ }
727
+
728
+ export function populateCliSessionList(sessions) {
729
+ var pickerLoading = ctx.$("resume-picker-loading");
730
+ var pickerEmpty = ctx.$("resume-picker-empty");
731
+ var pickerList = ctx.$("resume-picker-list");
732
+ if (!pickerLoading || !pickerList) return;
733
+
734
+ pickerLoading.classList.add("hidden");
735
+
736
+ if (!sessions || sessions.length === 0) {
737
+ pickerEmpty.classList.remove("hidden");
738
+ pickerList.classList.add("hidden");
739
+ return;
740
+ }
741
+
742
+ pickerEmpty.classList.add("hidden");
743
+ pickerList.classList.remove("hidden");
744
+ pickerList.innerHTML = "";
745
+
746
+ for (var i = 0; i < sessions.length; i++) {
747
+ var s = sessions[i];
748
+ var item = document.createElement("div");
749
+ item.className = "cli-session-item";
750
+
751
+ var title = document.createElement("div");
752
+ title.className = "cli-session-title";
753
+ title.textContent = s.firstPrompt || "Untitled session";
754
+ item.appendChild(title);
755
+
756
+ var meta = document.createElement("div");
757
+ meta.className = "cli-session-meta";
758
+ if (s.lastActivity) {
759
+ var time = document.createElement("span");
760
+ time.textContent = relativeTime(s.lastActivity);
761
+ meta.appendChild(time);
762
+ }
763
+ if (s.model) {
764
+ var model = document.createElement("span");
765
+ model.className = "badge";
766
+ model.textContent = s.model;
767
+ meta.appendChild(model);
768
+ }
769
+ if (s.gitBranch) {
770
+ var branch = document.createElement("span");
771
+ branch.className = "badge";
772
+ branch.textContent = s.gitBranch;
773
+ meta.appendChild(branch);
774
+ }
775
+ item.appendChild(meta);
776
+
777
+ (function (sessionId) {
778
+ item.addEventListener("click", function () {
779
+ if (ctx.ws && ctx.connected) {
780
+ ctx.ws.send(JSON.stringify({ type: "resume_session", cliSessionId: sessionId }));
781
+ }
782
+ var modal = ctx.$("resume-modal");
783
+ if (modal) modal.classList.add("hidden");
784
+ closeSidebar();
785
+ });
786
+ })(s.sessionId);
787
+
788
+ pickerList.appendChild(item);
789
+ }
790
+ }
791
+
792
+ // --- Search hit timeline (right-side markers) ---
793
+ var searchTimelineScrollHandler = null;
794
+ var activeSearchQuery = ""; // query active in the timeline
795
+
796
+ export function getActiveSearchQuery() {
797
+ return searchQuery;
798
+ }
799
+
800
+ export function buildSearchTimeline(query) {
801
+ removeSearchTimeline();
802
+ if (!query) return;
803
+ activeSearchQuery = query;
804
+
805
+ var q = query.toLowerCase();
806
+ var messagesEl = ctx.messagesEl;
807
+
808
+ // Collect all message elements that contain the query
809
+ var allMsgs = messagesEl.querySelectorAll(".msg-user, .msg-assistant");
810
+ var hits = [];
811
+ for (var i = 0; i < allMsgs.length; i++) {
812
+ var msgEl = allMsgs[i];
813
+ var textEl = msgEl.querySelector(".bubble") || msgEl.querySelector(".md-content");
814
+ if (!textEl) continue;
815
+ var text = textEl.textContent || "";
816
+ if (text.toLowerCase().indexOf(q) === -1) continue;
817
+
818
+ // Extract a snippet around the match
819
+ var idx = text.toLowerCase().indexOf(q);
820
+ var start = Math.max(0, idx - 10);
821
+ var end = Math.min(text.length, idx + query.length + 10);
822
+ var snippet = (start > 0 ? "\u2026" : "") + text.substring(start, end) + (end < text.length ? "\u2026" : "");
823
+ hits.push({ el: msgEl, snippet: snippet });
824
+ }
825
+
826
+ if (hits.length === 0) return;
827
+
828
+ var timeline = document.createElement("div");
829
+ timeline.className = "search-timeline";
830
+ timeline.id = "search-timeline";
831
+
832
+ var track = document.createElement("div");
833
+ track.className = "rewind-timeline-track";
834
+
835
+ var viewport = document.createElement("div");
836
+ viewport.className = "rewind-timeline-viewport";
837
+ track.appendChild(viewport);
838
+
839
+ for (var i = 0; i < hits.length; i++) {
840
+ var hit = hits[i];
841
+ var pct = hits.length === 1 ? 50 : 6 + (i / (hits.length - 1)) * 88;
842
+
843
+ var snippetText = hit.snippet;
844
+ if (snippetText.length > 24) snippetText = snippetText.substring(0, 24) + "\u2026";
845
+
846
+ var marker = document.createElement("div");
847
+ marker.className = "rewind-timeline-marker search-hit-marker";
848
+ marker.innerHTML = iconHtml("search") + '<span class="marker-text">' + escapeHtml(snippetText) + '</span>';
849
+ marker.style.top = pct + "%";
850
+ marker.dataset.offsetTop = hit.el.offsetTop;
851
+
852
+ (function(targetEl, markerEl) {
853
+ markerEl.addEventListener("click", function() {
854
+ targetEl.scrollIntoView({ behavior: "smooth", block: "center" });
855
+ targetEl.classList.remove("search-blink");
856
+ void targetEl.offsetWidth; // force reflow
857
+ targetEl.classList.add("search-blink");
858
+ });
859
+ })(hit.el, marker);
860
+
861
+ track.appendChild(marker);
862
+ }
863
+
864
+ timeline.appendChild(track);
865
+
866
+ // Position to align with messages area
867
+ var appEl = ctx.$("app");
868
+ var titleBarEl = document.querySelector(".title-bar-content");
869
+ var inputAreaEl = ctx.$("input-area");
870
+ var appRect = appEl.getBoundingClientRect();
871
+ var titleBarRect = titleBarEl ? titleBarEl.getBoundingClientRect() : { bottom: appRect.top };
872
+ var inputRect = inputAreaEl.getBoundingClientRect();
873
+
874
+ timeline.style.top = (titleBarRect.bottom - appRect.top + 4) + "px";
875
+ timeline.style.bottom = (appRect.bottom - inputRect.top + 4) + "px";
876
+
877
+ appEl.appendChild(timeline);
878
+ refreshIcons();
879
+
880
+ searchTimelineScrollHandler = function() { updateSearchTimelineViewport(track, viewport); };
881
+ messagesEl.addEventListener("scroll", searchTimelineScrollHandler);
882
+ updateSearchTimelineViewport(track, viewport);
883
+ }
884
+
885
+ function updateSearchTimelineViewport(track, viewport) {
886
+ if (!track) return;
887
+ var messagesEl = ctx.messagesEl;
888
+ var scrollH = messagesEl.scrollHeight;
889
+ var viewH = messagesEl.clientHeight;
890
+ if (scrollH <= viewH) {
891
+ viewport.style.top = "0";
892
+ viewport.style.height = "100%";
893
+ } else {
894
+ var viewTop = messagesEl.scrollTop / scrollH;
895
+ var viewBot = (messagesEl.scrollTop + viewH) / scrollH;
896
+ viewport.style.top = (viewTop * 100) + "%";
897
+ viewport.style.height = ((viewBot - viewTop) * 100) + "%";
898
+ }
899
+
900
+ var markers = track.querySelectorAll(".search-hit-marker");
901
+ var vTop = messagesEl.scrollTop;
902
+ var vBot = vTop + viewH;
903
+
904
+ for (var i = 0; i < markers.length; i++) {
905
+ var msgTop = parseInt(markers[i].dataset.offsetTop, 10);
906
+ if (msgTop >= vTop && msgTop <= vBot) {
907
+ markers[i].classList.add("in-view");
908
+ } else {
909
+ markers[i].classList.remove("in-view");
910
+ }
911
+ }
912
+ }
913
+
914
+ export function removeSearchTimeline() {
915
+ var existing = document.getElementById("search-timeline");
916
+ if (existing) existing.remove();
917
+ if (searchTimelineScrollHandler && ctx.messagesEl) {
918
+ ctx.messagesEl.removeEventListener("scroll", searchTimelineScrollHandler);
919
+ searchTimelineScrollHandler = null;
920
+ }
921
+ activeSearchQuery = "";
922
+ }
923
+
924
+ // --- Icon Strip (Discord-style project icons) ---
925
+ var iconStripTooltip = null;
926
+
927
+ function getProjectAbbrev(name) {
928
+ if (!name) return "?";
929
+ // Take first letter of each word, max 2 chars
930
+ var words = name.replace(/[^a-zA-Z0-9\s]/g, "").trim().split(/\s+/);
931
+ if (words.length >= 2) {
932
+ return (words[0][0] + words[1][0]).toUpperCase();
933
+ }
934
+ return name.substring(0, 2).toUpperCase();
935
+ }
936
+
937
+ function showIconTooltip(el, text) {
938
+ hideIconTooltip();
939
+ var tip = document.createElement("div");
940
+ tip.className = "icon-strip-tooltip";
941
+ tip.textContent = text;
942
+ document.body.appendChild(tip);
943
+ iconStripTooltip = tip;
944
+
945
+ requestAnimationFrame(function () {
946
+ var rect = el.getBoundingClientRect();
947
+ tip.style.top = (rect.top + rect.height / 2 - tip.offsetHeight / 2) + "px";
948
+ tip.classList.add("visible");
949
+ });
950
+ }
951
+
952
+ function hideIconTooltip() {
953
+ if (iconStripTooltip) {
954
+ iconStripTooltip.remove();
955
+ iconStripTooltip = null;
956
+ }
957
+ }
958
+
959
+ export function renderIconStrip(projects, currentSlug) {
960
+ // Cache for mobile sheet
961
+ cachedProjectList = projects;
962
+ cachedCurrentSlug = currentSlug;
963
+
964
+ var container = document.getElementById("icon-strip-projects");
965
+ if (!container) return;
966
+ container.innerHTML = "";
967
+
968
+ for (var i = 0; i < projects.length; i++) {
969
+ var p = projects[i];
970
+ var el = document.createElement("a");
971
+ el.className = "icon-strip-item" + (p.slug === currentSlug ? " active" : "");
972
+ el.href = "/p/" + p.slug + "/";
973
+ el.textContent = getProjectAbbrev(p.name);
974
+ el.dataset.slug = p.slug;
975
+
976
+ var pill = document.createElement("span");
977
+ pill.className = "icon-strip-pill";
978
+ el.appendChild(pill);
979
+
980
+ // Socket status indicator dot (bottom-right)
981
+ var statusDot = document.createElement("span");
982
+ statusDot.className = "icon-strip-status";
983
+ if (p.isProcessing) statusDot.classList.add("processing");
984
+ el.appendChild(statusDot);
985
+
986
+ // Tooltip on hover
987
+ (function (name, elem) {
988
+ elem.addEventListener("mouseenter", function () { showIconTooltip(elem, name); });
989
+ elem.addEventListener("mouseleave", hideIconTooltip);
990
+ })(p.name, el);
991
+
992
+ // Click handler — switch to project (no reload)
993
+ (function (slug) {
994
+ el.addEventListener("click", function (e) {
995
+ e.preventDefault();
996
+ if (ctx.switchProject) ctx.switchProject(slug);
997
+ });
998
+ })(p.slug);
999
+
1000
+ container.appendChild(el);
1001
+ }
1002
+
1003
+ // Update home icon active state
1004
+ var homeIcon = document.querySelector(".icon-strip-home");
1005
+ if (homeIcon) {
1006
+ if (!currentSlug || projects.length === 0) {
1007
+ homeIcon.classList.add("active");
1008
+ } else {
1009
+ homeIcon.classList.remove("active");
1010
+ }
1011
+ }
1012
+
1013
+ // Also update mobile project list
1014
+ renderProjectList(projects, currentSlug);
1015
+ }
1016
+
1017
+ function renderProjectList(projects, currentSlug) {
1018
+ var list = document.getElementById("project-list");
1019
+ if (!list) return;
1020
+ list.innerHTML = "";
1021
+
1022
+ for (var i = 0; i < projects.length; i++) {
1023
+ (function (p) {
1024
+ var el = document.createElement("button");
1025
+ el.className = "mobile-project-item" + (p.slug === currentSlug ? " active" : "");
1026
+
1027
+ var abbrev = document.createElement("span");
1028
+ abbrev.className = "mobile-project-abbrev";
1029
+ abbrev.textContent = getProjectAbbrev(p.name);
1030
+ el.appendChild(abbrev);
1031
+
1032
+ var name = document.createElement("span");
1033
+ name.className = "mobile-project-name";
1034
+ name.textContent = p.name;
1035
+ el.appendChild(name);
1036
+
1037
+ if (p.isProcessing) {
1038
+ var dot = document.createElement("span");
1039
+ dot.className = "mobile-project-processing";
1040
+ el.appendChild(dot);
1041
+ }
1042
+
1043
+ el.addEventListener("click", function () {
1044
+ if (ctx.switchProject) ctx.switchProject(p.slug);
1045
+ closeSidebar();
1046
+ });
1047
+
1048
+ list.appendChild(el);
1049
+ })(projects[i]);
1050
+ }
1051
+ }
1052
+
1053
+ export function initIconStrip(_ctx) {
1054
+ var addBtn = document.getElementById("icon-strip-add");
1055
+ if (addBtn) {
1056
+ addBtn.addEventListener("click", function () {
1057
+ // Reuse existing add-project modal
1058
+ var modal = _ctx.$("add-project-modal");
1059
+ if (modal) modal.classList.remove("hidden");
1060
+ });
1061
+ addBtn.addEventListener("mouseenter", function () { showIconTooltip(addBtn, "Add project"); });
1062
+ addBtn.addEventListener("mouseleave", hideIconTooltip);
1063
+ }
1064
+
1065
+ var exploreBtn = document.getElementById("icon-strip-explore");
1066
+ if (exploreBtn) {
1067
+ exploreBtn.addEventListener("click", function () {
1068
+ // Toggle file browser
1069
+ var fileBrowserBtn = _ctx.$("file-browser-btn");
1070
+ if (fileBrowserBtn) fileBrowserBtn.click();
1071
+ });
1072
+ exploreBtn.addEventListener("mouseenter", function () { showIconTooltip(exploreBtn, "File browser"); });
1073
+ exploreBtn.addEventListener("mouseleave", hideIconTooltip);
1074
+ }
1075
+
1076
+ // Tooltip for home icon
1077
+ var homeIcon = document.querySelector(".icon-strip-home");
1078
+ if (homeIcon) {
1079
+ homeIcon.addEventListener("mouseenter", function () { showIconTooltip(homeIcon, "Clay"); });
1080
+ homeIcon.addEventListener("mouseleave", hideIconTooltip);
1081
+ }
1082
+
1083
+ }