clay-server 2.11.0-beta.8 → 2.11.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,14 +1,14 @@
1
1
  import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
2
2
  import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
3
3
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
4
- import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge } from './modules/sidebar.js';
4
+ import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge, updateSessionBadge, updateProjectBadge, closeDmUserPicker, spawnDustParticles } from './modules/sidebar.js';
5
5
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
6
6
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
7
7
  import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage } from './modules/input.js';
8
8
  import { initQrCode } from './modules/qrcode.js';
9
9
  import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFsRead, handleDirChanged, refreshIfOpen, handleFileChanged, handleFileHistory, handleGitDiff, handleFileAt, getPendingNavigate, closeFileViewer, resetFileBrowser } from './modules/filebrowser.js';
10
10
  import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
11
- import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen } from './modules/sticky-notes.js';
11
+ import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen, hideNotes, showNotes, isNotesVisible } from './modules/sticky-notes.js';
12
12
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
13
13
  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, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
14
14
  import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
@@ -55,6 +55,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
55
55
  var dmTargetUser = null;
56
56
  var dmUnread = {}; // { otherUserId: count }
57
57
  var cachedAllUsers = [];
58
+ var cachedOnlineIds = [];
59
+ var cachedDmFavorites = [];
60
+ var cachedDmConversations = [];
61
+ var dmRemovedUsers = {}; // { userId: true } - users explicitly removed from favorites
58
62
 
59
63
  // --- Home Hub ---
60
64
  var homeHub = $("home-hub");
@@ -562,6 +566,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
562
566
  // Hide home hub if visible
563
567
  hideHomeHub();
564
568
 
569
+ // Hide sticky notes if visible
570
+ hideNotes();
571
+
565
572
  // Hide project UI + sidebar, show DM UI
566
573
  var mainCol = document.getElementById("main-column");
567
574
  if (mainCol) mainCol.classList.add("dm-mode");
@@ -821,12 +828,16 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
821
828
  var projectHintDismiss = $("project-hint-dismiss");
822
829
  var cachedProjects = [];
823
830
  var cachedProjectCount = 0;
831
+ var cachedRemovedProjects = [];
824
832
  var currentProjectOwnerId = null;
825
833
  var currentSlug = slugMatch ? slugMatch[1] : null;
826
834
 
827
835
  function updateProjectList(msg) {
828
836
  if (typeof msg.projectCount === "number") cachedProjectCount = msg.projectCount;
829
837
  if (msg.projects) cachedProjects = msg.projects;
838
+ if (msg.removedProjects) cachedRemovedProjects = msg.removedProjects;
839
+ else if (msg.removedProjects === undefined) { /* keep cached */ }
840
+ else cachedRemovedProjects = [];
830
841
  var count = cachedProjectCount || 0;
831
842
  renderProjectList();
832
843
  if (count === 1 && projectHint) {
@@ -840,13 +851,19 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
840
851
  }
841
852
  // Update topbar with server-wide presence
842
853
  if (msg.serverUsers) {
854
+ cachedOnlineIds = msg.serverUsers.map(function (u) { return u.id; });
843
855
  renderTopbarPresence(msg.serverUsers);
856
+ // Re-render user strip online dots even without allUsers update
857
+ if (!msg.allUsers && cachedAllUsers.length > 0) {
858
+ renderUserStrip(cachedAllUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers);
859
+ }
844
860
  }
845
861
  // Update user strip (DM targets) in icon strip
846
862
  if (msg.allUsers) {
847
863
  cachedAllUsers = msg.allUsers;
848
- var onlineIds = (msg.serverUsers || []).map(function (u) { return u.id; });
849
- renderUserStrip(msg.allUsers, onlineIds, myUserId);
864
+ if (msg.dmFavorites) cachedDmFavorites = msg.dmFavorites;
865
+ if (msg.dmConversations) cachedDmConversations = msg.dmConversations;
866
+ renderUserStrip(msg.allUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers);
850
867
  // Render my avatar (always present, hidden behind user-island)
851
868
  var meEl = document.getElementById("icon-strip-me");
852
869
  if (meEl && !meEl.hasChildNodes()) {
@@ -885,7 +902,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
885
902
  function renderProjectList() {
886
903
  // Render icon strip projects
887
904
  var iconStripProjects = cachedProjects.map(function (p) {
888
- return { slug: p.slug, name: p.title || p.project, icon: p.icon || null, isProcessing: p.isProcessing, onlineUsers: p.onlineUsers || [] };
905
+ return { slug: p.slug, name: p.title || p.project, icon: p.icon || null, isProcessing: p.isProcessing, onlineUsers: p.onlineUsers || [], unread: p.unread || 0 };
889
906
  });
890
907
  renderIconStrip(iconStripProjects, currentSlug);
891
908
  // Update title bar project name and icon if it changed
@@ -1225,6 +1242,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1225
1242
  get projectOwnerId() { return currentProjectOwnerId; },
1226
1243
  openDm: function (userId) { openDm(userId); },
1227
1244
  openAddProjectModal: function () { openAddProjectModal(); },
1245
+ sendWs: function (msg) { if (ws && ws.readyState === 1) ws.send(JSON.stringify(msg)); },
1246
+ onDmRemoveUser: function (userId) { dmRemovedUsers[userId] = true; },
1228
1247
  };
1229
1248
  initSidebar(sidebarCtx);
1230
1249
  initIconStrip(sidebarCtx);
@@ -2498,18 +2517,34 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2498
2517
  guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
2499
2518
  div.appendChild(guide);
2500
2519
 
2520
+ var sessionHint = document.createElement("div");
2521
+ sessionHint.className = "auth-required-guide";
2522
+ sessionHint.textContent = "After logging in, start a new session to continue.";
2523
+ div.appendChild(sessionHint);
2524
+
2501
2525
  addToMessages(div);
2502
2526
  scrollToBottom();
2503
2527
 
2504
- pendingTermCommand = "claude\n";
2505
- ws.send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
2506
- openTerminal();
2528
+ // Hide input area on this session since it cannot be used
2529
+ var inputArea = document.getElementById("input-area");
2530
+ if (inputArea) inputArea.classList.add("hidden");
2531
+
2532
+ // Only auto-open terminal on live events, not history replay
2533
+ if (!replayingHistory) {
2534
+ pendingTermCommand = "claude\n";
2535
+ ws.send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
2536
+ openTerminal();
2537
+ }
2507
2538
  } else {
2508
2539
  // Multi-user regular user: show message only, no auto-login
2509
2540
  hint.textContent = "Please ask an administrator to log in to Claude Code.";
2510
2541
  div.appendChild(hint);
2511
2542
  addToMessages(div);
2512
2543
  scrollToBottom();
2544
+
2545
+ inputEl.disabled = true;
2546
+ inputEl.placeholder = "Login required. Start a new session after logging in.";
2547
+ sendBtn.disabled = true;
2513
2548
  }
2514
2549
  }
2515
2550
 
@@ -2734,12 +2769,19 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2734
2769
  // --- Project switching (no full reload) ---
2735
2770
  function switchProject(slug) {
2736
2771
  if (!slug) return;
2772
+ var wasDm = dmMode;
2737
2773
  if (dmMode) exitDmMode();
2738
2774
  if (homeHubVisible) {
2739
2775
  hideHomeHub();
2740
2776
  if (slug === currentSlug) return;
2741
2777
  }
2742
- if (slug === currentSlug) return;
2778
+ if (slug === currentSlug) {
2779
+ // Returning from DM mode to the same project: re-switch to restore session
2780
+ if (wasDm && ws && ws.readyState === 1) {
2781
+ ws.send(JSON.stringify({ type: "switch_session", id: activeSessionId }));
2782
+ }
2783
+ return;
2784
+ }
2743
2785
  resetFileBrowser();
2744
2786
  closeArchive();
2745
2787
  if (isSchedulerOpen()) closeScheduler();
@@ -3126,6 +3168,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3126
3168
  blinkSessionDot(msg.id);
3127
3169
  break;
3128
3170
 
3171
+ case "session_unread":
3172
+ updateSessionBadge(msg.id, msg.count);
3173
+ break;
3174
+
3129
3175
  case "search_results":
3130
3176
  handleSearchResults(msg);
3131
3177
  break;
@@ -3622,6 +3668,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3622
3668
  var fromId = msg.message.from;
3623
3669
  if (fromId && fromId !== myUserId) {
3624
3670
  dmUnread[fromId] = (dmUnread[fromId] || 0) + 1;
3671
+ // Re-render strip so non-favorited sender appears
3672
+ renderUserStrip(cachedAllUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers);
3625
3673
  updateDmBadge(fromId, dmUnread[fromId]);
3626
3674
  }
3627
3675
  }
@@ -3637,6 +3685,25 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3637
3685
  // Could be used for DM list view later
3638
3686
  break;
3639
3687
 
3688
+ case "dm_favorites_updated":
3689
+ // Track users explicitly removed from favorites
3690
+ if (cachedDmFavorites && msg.dmFavorites) {
3691
+ for (var ri = 0; ri < cachedDmFavorites.length; ri++) {
3692
+ if (msg.dmFavorites.indexOf(cachedDmFavorites[ri]) === -1) {
3693
+ dmRemovedUsers[cachedDmFavorites[ri]] = true;
3694
+ }
3695
+ }
3696
+ }
3697
+ // Clear removed flag for users being added back
3698
+ if (msg.dmFavorites) {
3699
+ for (var ai = 0; ai < msg.dmFavorites.length; ai++) {
3700
+ delete dmRemovedUsers[msg.dmFavorites[ai]];
3701
+ }
3702
+ }
3703
+ cachedDmFavorites = msg.dmFavorites || [];
3704
+ renderUserStrip(cachedAllUsers, cachedOnlineIds, myUserId, cachedDmFavorites, cachedDmConversations, dmUnread, dmRemovedUsers);
3705
+ break;
3706
+
3640
3707
  case "daemon_config":
3641
3708
  updateDaemonConfig(msg.config);
3642
3709
  break;
@@ -5022,12 +5089,20 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
5022
5089
  // Project has tasks — show dialog with options
5023
5090
  showRemoveProjectTaskDialog(slug, name, msg.count);
5024
5091
  } else {
5025
- // No tasks — simple confirm
5026
- showConfirm('Remove project "' + name + '"?', function () {
5027
- if (ws && ws.readyState === 1) {
5028
- ws.send(JSON.stringify({ type: "remove_project", slug: slug }));
5092
+ // No tasks — confirm then particle burst + remove
5093
+ showConfirm('Remove "' + name + '"? You can re-add it later.', function () {
5094
+ // Find the icon strip item to anchor the particle burst
5095
+ var iconEl = document.querySelector('.icon-strip-item[data-slug="' + slug + '"]');
5096
+ if (iconEl) {
5097
+ var rect = iconEl.getBoundingClientRect();
5098
+ spawnDustParticles(rect.left + rect.width / 2, rect.top + rect.height / 2);
5029
5099
  }
5030
- });
5100
+ setTimeout(function () {
5101
+ if (ws && ws.readyState === 1) {
5102
+ ws.send(JSON.stringify({ type: "remove_project", slug: slug }));
5103
+ }
5104
+ }, 1000);
5105
+ }, "Remove", true);
5031
5106
  }
5032
5107
  pendingRemoveSlug = null;
5033
5108
  pendingRemoveName = null;
@@ -5092,9 +5167,33 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
5092
5167
  function handleRemoveProjectResult(msg) {
5093
5168
  if (msg.ok) {
5094
5169
  showToast("Project removed", "success");
5095
- // If we removed the current project, navigate to first available
5170
+ // If we removed the current project, go to home hub without full reload
5096
5171
  if (msg.slug === currentSlug) {
5097
- window.location.href = "/";
5172
+ // Suppress disconnect overlay and reconnect by detaching the WS
5173
+ if (ws) { ws.onclose = null; ws.onerror = null; ws.close(); ws = null; }
5174
+ if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; }
5175
+ connected = false;
5176
+ connectOverlay.classList.add("hidden");
5177
+ // Add to cached removed projects for re-add UI
5178
+ var removedProj = null;
5179
+ for (var ri = 0; ri < cachedProjects.length; ri++) {
5180
+ if (cachedProjects[ri].slug === msg.slug) { removedProj = cachedProjects[ri]; break; }
5181
+ }
5182
+ if (removedProj) {
5183
+ cachedRemovedProjects.push({
5184
+ path: removedProj.path || "",
5185
+ title: removedProj.title || null,
5186
+ icon: removedProj.icon || null,
5187
+ removedAt: Date.now(),
5188
+ });
5189
+ }
5190
+ // Remove from cached projects and re-render icon strip
5191
+ cachedProjects = cachedProjects.filter(function (p) { return p.slug !== msg.slug; });
5192
+ cachedProjectCount = cachedProjects.length;
5193
+ currentSlug = null;
5194
+ renderProjectList();
5195
+ resetClientState();
5196
+ showHomeHub();
5098
5197
  }
5099
5198
  } else {
5100
5199
  showToast(msg.error || "Failed to remove project", "error");
@@ -5113,6 +5212,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
5113
5212
  var addProjectCancel = document.getElementById("add-project-cancel");
5114
5213
  var addProjectModeBtns = addProjectModal.querySelectorAll(".add-project-mode-btn");
5115
5214
  var addProjectPanels = addProjectModal.querySelectorAll(".add-project-panel");
5215
+ var addProjectRemoved = document.getElementById("add-project-removed");
5116
5216
  var addProjectDebounce = null;
5117
5217
  var addProjectActiveIdx = -1;
5118
5218
  var addProjectMode = "existing";
@@ -5185,6 +5285,48 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
5185
5285
  existingBtn.disabled = false;
5186
5286
  switchAddProjectMode("existing");
5187
5287
  }
5288
+ // Render removed projects for re-add
5289
+ renderRemovedProjectsList();
5290
+ }
5291
+
5292
+ function renderRemovedProjectsList() {
5293
+ if (!addProjectRemoved) return;
5294
+ addProjectRemoved.innerHTML = "";
5295
+ if (!cachedRemovedProjects || cachedRemovedProjects.length === 0) {
5296
+ addProjectRemoved.classList.add("hidden");
5297
+ return;
5298
+ }
5299
+ addProjectRemoved.classList.remove("hidden");
5300
+ for (var ri = 0; ri < cachedRemovedProjects.length; ri++) {
5301
+ var rp = cachedRemovedProjects[ri];
5302
+ var item = document.createElement("div");
5303
+ item.className = "add-project-removed-item";
5304
+ item.dataset.path = rp.path;
5305
+ item.addEventListener("click", function () {
5306
+ var p = this.dataset.path;
5307
+ if (ws && ws.readyState === 1) {
5308
+ ws.send(JSON.stringify({ type: "add_project", path: p }));
5309
+ }
5310
+ closeAddProjectModal();
5311
+ });
5312
+ var iconEl = document.createElement("span");
5313
+ iconEl.className = "add-project-removed-icon";
5314
+ iconEl.textContent = rp.icon || "📁";
5315
+ item.appendChild(iconEl);
5316
+ var info = document.createElement("div");
5317
+ info.className = "add-project-removed-info";
5318
+ var nameEl = document.createElement("div");
5319
+ nameEl.className = "add-project-removed-name";
5320
+ nameEl.textContent = rp.title || rp.path.split("/").pop() || rp.path;
5321
+ info.appendChild(nameEl);
5322
+ var pathEl = document.createElement("div");
5323
+ pathEl.className = "add-project-removed-path";
5324
+ pathEl.textContent = rp.path;
5325
+ info.appendChild(pathEl);
5326
+ item.appendChild(info);
5327
+ addProjectRemoved.appendChild(item);
5328
+ }
5329
+ try { parseEmojis(addProjectRemoved); } catch (e) {}
5188
5330
  }
5189
5331
 
5190
5332
  function closeAddProjectModal() {
@@ -895,6 +895,7 @@
895
895
  width: 50%;
896
896
  max-width: 720px;
897
897
  min-width: 360px;
898
+ min-height: 0;
898
899
  border-left: 1px solid var(--border);
899
900
  background: var(--bg);
900
901
  display: flex;
@@ -1382,6 +1383,11 @@
1382
1383
  justify-content: space-between;
1383
1384
  padding: 10px 16px;
1384
1385
  flex-shrink: 0;
1386
+ position: sticky;
1387
+ top: 0;
1388
+ z-index: 10;
1389
+ background: var(--bg);
1390
+ border-bottom: 1px solid var(--border);
1385
1391
  }
1386
1392
 
1387
1393
  .file-history-view-toggle {
@@ -71,7 +71,7 @@
71
71
  position: absolute;
72
72
  left: -12px;
73
73
  width: 4px;
74
- background: #fff;
74
+ background: var(--text);
75
75
  border-radius: 0 4px 4px 0;
76
76
  transition: height 0.2s ease, opacity 0.2s ease;
77
77
  opacity: 0;
@@ -730,6 +730,32 @@
730
730
  display: flex;
731
731
  }
732
732
 
733
+ /* Project unread badge */
734
+ .icon-strip-project-badge {
735
+ position: absolute;
736
+ top: -2px;
737
+ right: -2px;
738
+ min-width: 18px;
739
+ height: 18px;
740
+ border-radius: 9px;
741
+ background: #e74c3c;
742
+ color: #fff;
743
+ font-size: 11px;
744
+ font-weight: 700;
745
+ display: none;
746
+ align-items: center;
747
+ justify-content: center;
748
+ padding: 0 5px;
749
+ line-height: 18px;
750
+ text-align: center;
751
+ box-shadow: 0 0 0 2px var(--bg);
752
+ z-index: 2;
753
+ }
754
+
755
+ .icon-strip-project-badge.has-unread {
756
+ display: flex;
757
+ }
758
+
733
759
  /* Pill for user icons */
734
760
  .icon-strip-user:hover .icon-strip-pill {
735
761
  opacity: 1;
@@ -766,6 +792,102 @@
766
792
  object-fit: cover;
767
793
  }
768
794
 
795
+ /* --- DM user picker popup --- */
796
+ .dm-user-picker {
797
+ position: fixed;
798
+ background: var(--sidebar-bg);
799
+ border: 1px solid var(--border);
800
+ border-radius: 12px;
801
+ padding: 8px;
802
+ width: 220px;
803
+ box-shadow: 0 8px 24px rgba(var(--shadow-rgb), 0.5);
804
+ z-index: 10001;
805
+ animation: ctxMenuAppear 0.15s ease-out;
806
+ display: flex;
807
+ flex-direction: column;
808
+ gap: 4px;
809
+ }
810
+
811
+ .dm-user-picker-search {
812
+ width: 100%;
813
+ padding: 7px 10px;
814
+ border: 1px solid var(--border);
815
+ border-radius: 8px;
816
+ background: var(--bg-alt);
817
+ color: var(--text);
818
+ font-size: 13px;
819
+ font-family: inherit;
820
+ outline: none;
821
+ box-sizing: border-box;
822
+ }
823
+
824
+ .dm-user-picker-search:focus {
825
+ border-color: var(--accent);
826
+ }
827
+
828
+ .dm-user-picker-search::placeholder {
829
+ color: var(--text-dimmer);
830
+ }
831
+
832
+ .dm-user-picker-list {
833
+ max-height: 200px;
834
+ overflow-y: auto;
835
+ overflow-x: hidden;
836
+ scrollbar-width: thin;
837
+ scrollbar-color: var(--border) transparent;
838
+ }
839
+
840
+ .dm-user-picker-list::-webkit-scrollbar {
841
+ width: 4px;
842
+ }
843
+
844
+ .dm-user-picker-list::-webkit-scrollbar-track {
845
+ background: transparent;
846
+ }
847
+
848
+ .dm-user-picker-list::-webkit-scrollbar-thumb {
849
+ background: var(--border);
850
+ border-radius: 2px;
851
+ }
852
+
853
+ .dm-user-picker-item {
854
+ display: flex;
855
+ align-items: center;
856
+ gap: 8px;
857
+ padding: 6px 8px;
858
+ border-radius: 8px;
859
+ cursor: pointer;
860
+ transition: background 0.15s;
861
+ }
862
+
863
+ .dm-user-picker-item:hover {
864
+ background: rgba(var(--overlay-rgb), 0.08);
865
+ }
866
+
867
+ .dm-user-picker-avatar {
868
+ width: 28px;
869
+ height: 28px;
870
+ border-radius: 50%;
871
+ object-fit: cover;
872
+ flex-shrink: 0;
873
+ }
874
+
875
+ .dm-user-picker-name {
876
+ font-size: 13px;
877
+ font-weight: 500;
878
+ color: var(--text);
879
+ white-space: nowrap;
880
+ overflow: hidden;
881
+ text-overflow: ellipsis;
882
+ }
883
+
884
+ .dm-user-picker-empty {
885
+ padding: 12px 8px;
886
+ font-size: 13px;
887
+ color: var(--text-dimmer);
888
+ text-align: center;
889
+ }
890
+
769
891
  /* --- Per-project presence avatars --- */
770
892
  /* --- Mobile: hide icon strip --- */
771
893
  @media (max-width: 768px) {
@@ -1472,7 +1472,7 @@ pre.mermaid-error {
1472
1472
  ========================================================================== */
1473
1473
 
1474
1474
  .auth-required-msg {
1475
- max-width: var(--content-width);
1475
+ max-width: 420px;
1476
1476
  margin: 12px auto;
1477
1477
  padding: 14px 18px;
1478
1478
  background: color-mix(in srgb, var(--warning) 8%, var(--bg));
@@ -181,6 +181,23 @@
181
181
  flex-shrink: 0;
182
182
  }
183
183
 
184
+ .mobile-project-unread {
185
+ min-width: 18px;
186
+ height: 18px;
187
+ border-radius: 9px;
188
+ background: #e74c3c;
189
+ color: #fff;
190
+ font-size: 11px;
191
+ font-weight: 700;
192
+ display: flex;
193
+ align-items: center;
194
+ justify-content: center;
195
+ padding: 0 5px;
196
+ line-height: 18px;
197
+ flex-shrink: 0;
198
+ margin-left: auto;
199
+ }
200
+
184
201
  /* ==========================================================================
185
202
  Mobile Sheet — fullscreen overlay for Projects / Sessions
186
203
  ========================================================================== */
@@ -905,6 +905,55 @@ button.top-bar-pill.pill-accent:hover { background: color-mix(in srgb, var(--acc
905
905
  .add-project-panel { display: none; }
906
906
  .add-project-panel.active { display: block; }
907
907
 
908
+ /* Removed projects list in add project modal */
909
+ #add-project-removed { margin-bottom: 14px; padding-bottom: 14px; border-bottom: 1px solid var(--border); }
910
+ #add-project-removed.hidden { display: none; }
911
+ .add-project-removed-item {
912
+ display: flex;
913
+ align-items: center;
914
+ gap: 10px;
915
+ padding: 7px 8px;
916
+ border-radius: 8px;
917
+ margin-bottom: 2px;
918
+ cursor: pointer;
919
+ transition: background 0.15s;
920
+ }
921
+ .add-project-removed-item:last-child { margin-bottom: 0; }
922
+ .add-project-removed-item:hover { background: var(--sidebar-hover); }
923
+ .add-project-removed-icon {
924
+ flex-shrink: 0;
925
+ width: 28px;
926
+ height: 28px;
927
+ display: flex;
928
+ align-items: center;
929
+ justify-content: center;
930
+ border-radius: 6px;
931
+ background: var(--sidebar-hover);
932
+ font-size: 14px;
933
+ }
934
+ .add-project-removed-info {
935
+ flex: 1;
936
+ min-width: 0;
937
+ overflow: hidden;
938
+ }
939
+ .add-project-removed-name {
940
+ font-weight: 500;
941
+ color: var(--text);
942
+ white-space: nowrap;
943
+ overflow: hidden;
944
+ text-overflow: ellipsis;
945
+ font-size: 13px;
946
+ line-height: 1.3;
947
+ }
948
+ .add-project-removed-path {
949
+ font-size: 11px;
950
+ color: var(--text-muted);
951
+ white-space: nowrap;
952
+ overflow: hidden;
953
+ text-overflow: ellipsis;
954
+ line-height: 1.3;
955
+ }
956
+
908
957
  #add-project-create-input,
909
958
  #add-project-clone-input {
910
959
  width: 100%;
@@ -489,6 +489,32 @@
489
489
  box-shadow: 0 0 4px var(--success);
490
490
  }
491
491
 
492
+ /* Session unread badge */
493
+ .session-unread-badge {
494
+ position: absolute;
495
+ right: 32px;
496
+ top: 50%;
497
+ transform: translateY(-50%);
498
+ min-width: 18px;
499
+ height: 18px;
500
+ border-radius: 9px;
501
+ background: #e74c3c;
502
+ color: #fff;
503
+ font-size: 11px;
504
+ font-weight: 700;
505
+ display: none;
506
+ align-items: center;
507
+ justify-content: center;
508
+ padding: 0 5px;
509
+ line-height: 18px;
510
+ text-align: center;
511
+ z-index: 2;
512
+ }
513
+
514
+ .session-unread-badge.has-unread {
515
+ display: flex;
516
+ }
517
+
492
518
  .session-more-btn {
493
519
  position: absolute;
494
520
  right: 8px;
@@ -313,11 +313,14 @@
313
313
  min-width: unset;
314
314
  height: auto !important;
315
315
  width: auto !important;
316
+ max-width: 200px;
316
317
  }
317
318
 
318
319
  .sticky-note.minimized .sticky-note-spacer {
319
320
  color: inherit;
320
321
  opacity: 1;
322
+ overflow: hidden;
323
+ text-overflow: ellipsis;
321
324
  }
322
325
 
323
326
  .sticky-note.minimized .sticky-note-header button {
@@ -1112,6 +1112,7 @@
1112
1112
  <div class="confirm-backdrop"></div>
1113
1113
  <div class="confirm-dialog add-project-dialog">
1114
1114
  <div class="add-project-title">Add project</div>
1115
+ <div id="add-project-removed" class="hidden"></div>
1115
1116
  <div class="add-project-modes">
1116
1117
  <button class="add-project-mode-btn active" data-mode="existing">Existing directory</button>
1117
1118
  <button class="add-project-mode-btn" data-mode="create">New project</button>
@@ -1488,6 +1489,7 @@
1488
1489
  <script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
1489
1490
  <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5/lib/xterm.min.js"></script>
1490
1491
  <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0/lib/addon-fit.min.js"></script>
1492
+ <script src="https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0/lib/addon-web-links.min.js"></script>
1491
1493
  <script type="module" src="app.js"></script>
1492
1494
  <div id="pwa-install-modal" class="pwa-modal hidden">
1493
1495
  <div class="pwa-modal-backdrop"></div>