clay-server 2.15.2 → 2.16.0-beta.1

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.
@@ -3,10 +3,12 @@ import { iconHtml, refreshIcons } from './icons.js';
3
3
  import { openProjectSettings } from './project-settings.js';
4
4
  import { triggerShare } from './qrcode.js';
5
5
  import { parseEmojis } from './markdown.js';
6
+ import { getCurrentTheme } from './theme.js';
6
7
  import { showMateProfilePopover } from './profile.js';
7
8
  import { closeArchive } from './sticky-notes.js';
8
9
  import { closeScheduler } from './scheduler.js';
9
10
  import { openSearch as openSessionSearch } from './session-search.js';
11
+ import { openCommandPalette } from './command-palette.js';
10
12
 
11
13
  var ctx;
12
14
 
@@ -20,6 +22,7 @@ var expandedLoopGroups = new Set();
20
22
  // --- Cached project data for mobile sheet ---
21
23
  var cachedProjectList = [];
22
24
  var cachedCurrentSlug = null;
25
+ var mobileChatSheetOpen = false; // track if chat sheet is showing
23
26
 
24
27
  function dismissOverlayPanels() {
25
28
  closeArchive();
@@ -483,6 +486,9 @@ function renderSessionItem(s) {
483
486
  export function renderSessionList(sessions) {
484
487
  if (sessions) cachedSessions = sessions;
485
488
 
489
+ // If mobile chat sheet is open, refresh its session list
490
+ refreshMobileChatSheet();
491
+
486
492
  ctx.sessionListEl.innerHTML = "";
487
493
 
488
494
  // Partition: loop sessions vs normal sessions
@@ -644,9 +650,15 @@ export function closeSidebar() {
644
650
  ctx.sidebarOverlay.classList.remove("visible");
645
651
  }
646
652
 
647
- // --- Mobile sheet (fullscreen overlay for Projects / Sessions) ---
653
+ // --- Mobile sheet (fullscreen overlay for Projects / Sessions / Mate Profile) ---
654
+
655
+ var mobileSheetMateData = null;
656
+
657
+ export function setMobileSheetMateData(data) {
658
+ mobileSheetMateData = data;
659
+ }
648
660
 
649
- function openMobileSheet(type) {
661
+ export function openMobileSheet(type) {
650
662
  var sheet = document.getElementById("mobile-sheet");
651
663
  if (!sheet) return;
652
664
 
@@ -660,15 +672,21 @@ function openMobileSheet(type) {
660
672
  var prevPanel = document.getElementById("sidebar-panel-files");
661
673
  if (prevFileTree && prevPanel) prevPanel.appendChild(prevFileTree);
662
674
  }
675
+ // Return knowledge files to mate sidebar before clearing
676
+ if (sheet.classList.contains("sheet-knowledge")) {
677
+ var prevKnowledge = document.getElementById("mate-knowledge-files");
678
+ var prevKnowledgePanel = document.getElementById("mate-sidebar-knowledge");
679
+ if (prevKnowledge && prevKnowledgePanel) prevKnowledgePanel.appendChild(prevKnowledge);
680
+ }
663
681
 
664
682
  listEl.innerHTML = "";
665
- sheet.classList.remove("sheet-files");
683
+ sheet.classList.remove("sheet-files", "sheet-knowledge");
666
684
 
667
685
  if (type === "projects") {
668
686
  titleEl.textContent = "Projects";
669
687
  renderSheetProjects(listEl);
670
688
  } else if (type === "sessions") {
671
- titleEl.textContent = "Sessions";
689
+ titleEl.textContent = "Chat";
672
690
  renderSheetSessions(listEl);
673
691
  } else if (type === "files") {
674
692
  titleEl.textContent = "Files";
@@ -679,6 +697,28 @@ function openMobileSheet(type) {
679
697
  fileTree.classList.remove("hidden");
680
698
  }
681
699
  if (ctx.onFilesTabOpen) ctx.onFilesTabOpen();
700
+ } else if (type === "mate-knowledge") {
701
+ titleEl.textContent = "Knowledge";
702
+ sheet.classList.add("sheet-knowledge");
703
+ var knowledgeFiles = document.getElementById("mate-knowledge-files");
704
+ if (knowledgeFiles) {
705
+ listEl.appendChild(knowledgeFiles);
706
+ knowledgeFiles.classList.remove("hidden");
707
+ }
708
+ // Request knowledge list if not loaded
709
+ if (ctx.requestKnowledgeList) ctx.requestKnowledgeList();
710
+ } else if (type === "mate-profile") {
711
+ titleEl.textContent = "";
712
+ renderSheetMateProfile(listEl);
713
+ } else if (type === "search") {
714
+ titleEl.textContent = "Search";
715
+ renderSheetSearch(listEl);
716
+ } else if (type === "tools") {
717
+ titleEl.textContent = "Tools";
718
+ renderSheetTools(listEl);
719
+ } else if (type === "settings") {
720
+ titleEl.textContent = "Settings";
721
+ renderSheetSettings(listEl);
682
722
  }
683
723
 
684
724
  sheet.classList.remove("hidden", "closing");
@@ -689,6 +729,8 @@ function closeMobileSheet() {
689
729
  var sheet = document.getElementById("mobile-sheet");
690
730
  if (!sheet || sheet.classList.contains("hidden")) return;
691
731
 
732
+ mobileChatSheetOpen = false;
733
+
692
734
  // Return file tree to sidebar if it was moved
693
735
  if (sheet.classList.contains("sheet-files")) {
694
736
  var fileTree = document.getElementById("file-tree");
@@ -697,6 +739,14 @@ function closeMobileSheet() {
697
739
  sidebarFilesPanel.appendChild(fileTree);
698
740
  }
699
741
  }
742
+ // Return knowledge files to mate sidebar if moved
743
+ if (sheet.classList.contains("sheet-knowledge")) {
744
+ var knowledgeFiles = document.getElementById("mate-knowledge-files");
745
+ var knowledgePanel = document.getElementById("mate-sidebar-knowledge");
746
+ if (knowledgeFiles && knowledgePanel) {
747
+ knowledgePanel.appendChild(knowledgeFiles);
748
+ }
749
+ }
700
750
 
701
751
  sheet.classList.add("closing");
702
752
  setTimeout(function () {
@@ -713,7 +763,12 @@ function renderSheetProjects(listEl) {
713
763
 
714
764
  var abbrev = document.createElement("span");
715
765
  abbrev.className = "mobile-project-abbrev";
716
- abbrev.textContent = getProjectAbbrev(p.name);
766
+ if (p.icon) {
767
+ abbrev.textContent = p.icon;
768
+ parseEmojis(abbrev);
769
+ } else {
770
+ abbrev.textContent = getProjectAbbrev(p.name);
771
+ }
717
772
  el.appendChild(abbrev);
718
773
 
719
774
  var name = document.createElement("span");
@@ -745,7 +800,208 @@ function renderSheetProjects(listEl) {
745
800
  }
746
801
 
747
802
  function renderSheetSessions(listEl) {
748
- // New session button at top
803
+ // --- Context filter bar (horizontal scroll) ---
804
+ var filterBar = document.createElement("div");
805
+ filterBar.className = "mobile-chat-filter-bar";
806
+
807
+ // Current project chip (always first, pre-selected)
808
+ var currentProject = null;
809
+ for (var pi = 0; pi < cachedProjectList.length; pi++) {
810
+ if (cachedProjectList[pi].slug === cachedCurrentSlug) {
811
+ currentProject = cachedProjectList[pi];
812
+ break;
813
+ }
814
+ }
815
+
816
+ // Build chips: projects first, then mates
817
+ var chips = [];
818
+
819
+ for (var ci = 0; ci < cachedProjectList.length; ci++) {
820
+ (function (p) {
821
+ var chip = document.createElement("button");
822
+ chip.className = "mobile-chat-chip";
823
+ if (p.slug === cachedCurrentSlug) chip.classList.add("active");
824
+ chip.dataset.type = "project";
825
+ chip.dataset.slug = p.slug;
826
+
827
+ var abbrev = document.createElement("span");
828
+ abbrev.className = "mobile-chat-chip-icon";
829
+ if (p.icon) {
830
+ abbrev.textContent = p.icon;
831
+ parseEmojis(abbrev);
832
+ } else {
833
+ abbrev.textContent = getProjectAbbrev(p.name);
834
+ }
835
+ chip.appendChild(abbrev);
836
+
837
+ var label = document.createElement("span");
838
+ label.textContent = p.name;
839
+ chip.appendChild(label);
840
+
841
+ // Processing dot: same class as icon strip
842
+ var statusDot = document.createElement("span");
843
+ statusDot.className = "icon-strip-status";
844
+ if (p.isProcessing) statusDot.classList.add("processing");
845
+ chip.appendChild(statusDot);
846
+
847
+ if (p.unread > 0 && p.slug !== cachedCurrentSlug) {
848
+ var badge = document.createElement("span");
849
+ badge.className = "mobile-chat-chip-badge";
850
+ badge.textContent = p.unread > 99 ? "99+" : String(p.unread);
851
+ chip.appendChild(badge);
852
+ }
853
+
854
+ chips.push(chip);
855
+ })(cachedProjectList[ci]);
856
+ }
857
+
858
+ for (var mi = 0; mi < cachedMates.length; mi++) {
859
+ (function (mate) {
860
+ var mp = mate.profile || {};
861
+ var chip = document.createElement("button");
862
+ chip.className = "mobile-chat-chip";
863
+ chip.dataset.type = "mate";
864
+ chip.dataset.mateId = mate.id;
865
+
866
+ var avatarEl = document.createElement("img");
867
+ avatarEl.className = "mobile-chat-chip-avatar";
868
+ avatarEl.src = "https://api.dicebear.com/9.x/" + (mp.avatarStyle || "bottts") + "/svg?seed=" + encodeURIComponent(mp.avatarSeed || mate.id) + "&size=20";
869
+ avatarEl.alt = mp.displayName || mate.name || "";
870
+ chip.appendChild(avatarEl);
871
+
872
+ var label = document.createElement("span");
873
+ label.textContent = mp.displayName || mate.name || "Mate";
874
+ chip.appendChild(label);
875
+
876
+ // Processing dot: same class as icon strip, same data source
877
+ var mateSlug = "mate-" + mate.id;
878
+ var mateProj = null;
879
+ var allProjects = (ctx && ctx.projectList) || [];
880
+ for (var pi = 0; pi < allProjects.length; pi++) {
881
+ if (allProjects[pi].slug === mateSlug) { mateProj = allProjects[pi]; break; }
882
+ }
883
+ var statusDot = document.createElement("span");
884
+ statusDot.className = "icon-strip-status";
885
+ if (mateProj && mateProj.isProcessing) statusDot.classList.add("processing");
886
+ chip.appendChild(statusDot);
887
+
888
+ var unreadCount = cachedDmUnread[mate.id] || 0;
889
+ if (unreadCount > 0) {
890
+ var badge = document.createElement("span");
891
+ badge.className = "mobile-chat-chip-badge";
892
+ badge.textContent = unreadCount > 99 ? "99+" : String(unreadCount);
893
+ chip.appendChild(badge);
894
+ }
895
+
896
+ chips.push(chip);
897
+ })(cachedMates[mi]);
898
+ }
899
+
900
+ for (var i = 0; i < chips.length; i++) {
901
+ filterBar.appendChild(chips[i]);
902
+ }
903
+ listEl.appendChild(filterBar);
904
+
905
+ // --- Session list container ---
906
+ var sessionListEl = document.createElement("div");
907
+ sessionListEl.className = "mobile-chat-session-list";
908
+ listEl.appendChild(sessionListEl);
909
+
910
+ // --- Render sessions for a context ---
911
+ function renderSessionsForContext(type, slug, mateId) {
912
+ sessionListEl.innerHTML = "";
913
+
914
+ if (type === "project") {
915
+ renderMobileSessionsInto(sessionListEl);
916
+ } else if (type === "mate") {
917
+ // Mate DM: just open the DM
918
+ if (ctx.openDm) ctx.openDm(mateId);
919
+ closeMobileSheet();
920
+ return;
921
+ }
922
+
923
+ refreshIcons();
924
+ }
925
+
926
+ // --- Chip click handlers ---
927
+ for (var j = 0; j < chips.length; j++) {
928
+ (function (chip) {
929
+ chip.addEventListener("click", function () {
930
+ // Deactivate all chips
931
+ for (var k = 0; k < chips.length; k++) {
932
+ chips[k].classList.remove("active");
933
+ }
934
+ chip.classList.add("active");
935
+
936
+ var type = chip.dataset.type;
937
+ if (type === "project") {
938
+ var slug = chip.dataset.slug;
939
+ if (slug !== cachedCurrentSlug) {
940
+ // Switch project, show loading, keep sheet open
941
+ sessionListEl.innerHTML = "";
942
+ var loading = document.createElement("div");
943
+ loading.className = "mobile-chat-context-note";
944
+ loading.textContent = "Loading sessions...";
945
+ sessionListEl.appendChild(loading);
946
+ if (ctx.switchProject) ctx.switchProject(slug);
947
+ // renderSessionList will be called by WS, which calls refreshMobileChatSheet
948
+ } else {
949
+ renderSessionsForContext("project", slug, null);
950
+ }
951
+ } else if (type === "mate") {
952
+ renderSessionsForContext("mate", null, chip.dataset.mateId);
953
+ }
954
+ });
955
+ })(chips[j]);
956
+ }
957
+
958
+ // Track that chat sheet is open
959
+ mobileChatSheetOpen = true;
960
+
961
+ // --- Initial render: current project sessions ---
962
+ renderSessionsForContext("project", cachedCurrentSlug, null);
963
+ }
964
+
965
+ // Helper: create a mobile session item element
966
+ function createMobileSessionItem(s) {
967
+ var el = document.createElement("button");
968
+ el.className = "mobile-session-item" + (s.active ? " active" : "");
969
+
970
+ // Processing dot (left side, before title)
971
+ if (s.isProcessing) {
972
+ var dot = document.createElement("span");
973
+ dot.className = "mobile-session-processing";
974
+ el.appendChild(dot);
975
+ }
976
+
977
+ var titleSpan = document.createElement("span");
978
+ titleSpan.className = "mobile-session-title";
979
+ titleSpan.textContent = s.title || "New Session";
980
+ el.appendChild(titleSpan);
981
+
982
+ // Unread badge (right side)
983
+ if (s.unread > 0 && !s.active) {
984
+ var badge = document.createElement("span");
985
+ badge.className = "mobile-session-unread";
986
+ badge.textContent = s.unread > 99 ? "99+" : String(s.unread);
987
+ el.appendChild(badge);
988
+ }
989
+
990
+ (function (id) {
991
+ el.addEventListener("click", function () {
992
+ if (ctx.ws && ctx.connected) {
993
+ ctx.ws.send(JSON.stringify({ type: "switch_session", id: id }));
994
+ }
995
+ dismissOverlayPanels();
996
+ closeMobileSheet();
997
+ });
998
+ })(s.id);
999
+
1000
+ return el;
1001
+ }
1002
+
1003
+ // Helper: render sorted sessions into a container with date groups
1004
+ function renderMobileSessionsInto(container) {
749
1005
  var newBtn = document.createElement("button");
750
1006
  newBtn.className = "mobile-session-new";
751
1007
  newBtn.innerHTML = '<i data-lucide="plus" style="width:16px;height:16px"></i> New session';
@@ -755,30 +1011,186 @@ function renderSheetSessions(listEl) {
755
1011
  }
756
1012
  closeMobileSheet();
757
1013
  });
758
- listEl.appendChild(newBtn);
1014
+ container.appendChild(newBtn);
759
1015
 
760
1016
  var sorted = cachedSessions.slice().sort(function (a, b) {
761
1017
  return (b.lastActivity || 0) - (a.lastActivity || 0);
762
1018
  });
763
1019
 
764
1020
  var currentGroup = "";
765
- for (var i = 0; i < sorted.length; i++) {
766
- var s = sorted[i];
1021
+ for (var si = 0; si < sorted.length; si++) {
1022
+ var s = sorted[si];
767
1023
  var group = getDateGroup(s.lastActivity || 0);
768
1024
  if (group !== currentGroup) {
769
1025
  currentGroup = group;
770
1026
  var header = document.createElement("div");
771
1027
  header.className = "mobile-sheet-group";
772
1028
  header.textContent = group;
773
- listEl.appendChild(header);
1029
+ container.appendChild(header);
1030
+ }
1031
+ container.appendChild(createMobileSessionItem(s));
1032
+ }
1033
+ }
1034
+
1035
+ // Refresh mobile chat sheet when session data updates (called from renderSessionList)
1036
+ function refreshMobileChatSheet() {
1037
+ if (!mobileChatSheetOpen) return;
1038
+ var sheet = document.getElementById("mobile-sheet");
1039
+ if (!sheet || sheet.classList.contains("hidden")) {
1040
+ mobileChatSheetOpen = false;
1041
+ return;
1042
+ }
1043
+ var sessionListEl = sheet.querySelector(".mobile-chat-session-list");
1044
+ if (!sessionListEl) return;
1045
+
1046
+ // Update chips: active state and processing dots
1047
+ var chips = sheet.querySelectorAll(".mobile-chat-chip");
1048
+ for (var i = 0; i < chips.length; i++) {
1049
+ var chip = chips[i];
1050
+ chip.classList.remove("active");
1051
+
1052
+ // Update active state
1053
+ if (chip.dataset.type === "project" && chip.dataset.slug === cachedCurrentSlug) {
1054
+ chip.classList.add("active");
1055
+ }
1056
+
1057
+ // Update processing dot: same class as icon strip
1058
+ var statusDot = chip.querySelector(".icon-strip-status");
1059
+ if (statusDot) {
1060
+ var isProcessing = false;
1061
+ var allProjects = (ctx && ctx.projectList) || [];
1062
+ var lookupSlug = chip.dataset.type === "mate" ? ("mate-" + chip.dataset.mateId) : chip.dataset.slug;
1063
+ for (var pi = 0; pi < allProjects.length; pi++) {
1064
+ if (allProjects[pi].slug === lookupSlug && allProjects[pi].isProcessing) {
1065
+ isProcessing = true;
1066
+ break;
1067
+ }
1068
+ }
1069
+ statusDot.classList.toggle("processing", isProcessing);
774
1070
  }
1071
+ }
1072
+
1073
+ // Re-render sessions for current project
1074
+ sessionListEl.innerHTML = "";
1075
+ renderMobileSessionsInto(sessionListEl);
1076
+
1077
+ refreshIcons();
1078
+ }
1079
+
1080
+ function renderSheetMateProfile(listEl) {
1081
+ if (!mobileSheetMateData) return;
1082
+ var data = mobileSheetMateData;
1083
+
1084
+ // Profile header
1085
+ var header = document.createElement("div");
1086
+ header.className = "mate-profile-header";
1087
+
1088
+ var avatar = document.createElement("img");
1089
+ avatar.className = "mate-profile-avatar";
1090
+ avatar.src = data.avatarUrl || "";
1091
+ avatar.alt = data.displayName || "";
1092
+ header.appendChild(avatar);
1093
+
1094
+ var info = document.createElement("div");
1095
+ info.className = "mate-profile-info";
1096
+ var nameEl = document.createElement("div");
1097
+ nameEl.className = "mate-profile-name";
1098
+ nameEl.textContent = data.displayName || "";
1099
+ info.appendChild(nameEl);
1100
+ if (data.description) {
1101
+ var descEl = document.createElement("div");
1102
+ descEl.className = "mate-profile-desc";
1103
+ descEl.textContent = data.description;
1104
+ info.appendChild(descEl);
1105
+ }
1106
+ header.appendChild(info);
1107
+ listEl.appendChild(header);
1108
+
1109
+ // Action buttons
1110
+ var actions = [
1111
+ { icon: "book-open", label: "Knowledge", btnId: "mate-knowledge-btn", countId: "mate-knowledge-count" },
1112
+ { icon: "sticky-note", label: "Sticky Notes", btnId: "sticky-notes-toggle-btn", countId: "sticky-notes-sidebar-count" },
1113
+ { icon: "puzzle", label: "Skills", btnId: "mate-skills-btn" },
1114
+ { icon: "calendar", label: "Scheduled Tasks", btnId: "mate-scheduler-btn" }
1115
+ ];
1116
+
1117
+ for (var i = 0; i < actions.length; i++) {
1118
+ (function (action) {
1119
+ var btn = document.createElement("button");
1120
+ btn.className = "mate-profile-action";
1121
+ var countHtml = "";
1122
+ if (action.countId) {
1123
+ var countEl = document.getElementById(action.countId);
1124
+ if (countEl && !countEl.classList.contains("hidden") && countEl.textContent) {
1125
+ countHtml = '<span class="mate-profile-action-count">' + escapeHtml(countEl.textContent) + '</span>';
1126
+ }
1127
+ }
1128
+ btn.innerHTML = '<i data-lucide="' + action.icon + '"></i><span>' + action.label + '</span>' + countHtml;
1129
+ btn.addEventListener("click", function () {
1130
+ closeMobileSheet();
1131
+ var targetBtn = document.getElementById(action.btnId);
1132
+ if (targetBtn) {
1133
+ setTimeout(function () { targetBtn.click(); }, 250);
1134
+ }
1135
+ });
1136
+ listEl.appendChild(btn);
1137
+ })(actions[i]);
1138
+ }
1139
+ }
1140
+
1141
+ function renderSheetSearch(listEl) {
1142
+ // Search input at top
1143
+ var wrap = document.createElement("div");
1144
+ wrap.className = "mobile-search-input-wrap";
1145
+ var input = document.createElement("input");
1146
+ input.className = "mobile-search-input";
1147
+ input.type = "text";
1148
+ input.placeholder = "Search sessions, messages...";
1149
+ input.autocomplete = "off";
1150
+ input.spellcheck = false;
1151
+ wrap.appendChild(input);
1152
+ listEl.appendChild(wrap);
1153
+
1154
+ // Results container
1155
+ var resultsEl = document.createElement("div");
1156
+ resultsEl.style.padding = "0 8px";
1157
+ listEl.appendChild(resultsEl);
1158
+
1159
+ // Auto-focus
1160
+ setTimeout(function () { input.focus(); }, 300);
1161
+
1162
+ // Show all sessions initially
1163
+ renderSearchResults(resultsEl, "");
1164
+
1165
+ input.addEventListener("input", function () {
1166
+ var q = input.value.trim().toLowerCase();
1167
+ renderSearchResults(resultsEl, q);
1168
+ });
1169
+ input.addEventListener("keydown", function (e) { e.stopPropagation(); });
1170
+ input.addEventListener("keyup", function (e) { e.stopPropagation(); });
1171
+ input.addEventListener("keypress", function (e) { e.stopPropagation(); });
1172
+ }
1173
+
1174
+ function renderSearchResults(container, query) {
1175
+ container.innerHTML = "";
1176
+ var sorted = cachedSessions.slice().sort(function (a, b) {
1177
+ return (b.lastActivity || 0) - (a.lastActivity || 0);
1178
+ });
1179
+
1180
+ var found = 0;
1181
+ for (var i = 0; i < sorted.length; i++) {
1182
+ var s = sorted[i];
1183
+ var title = s.title || "New Session";
1184
+ if (query && title.toLowerCase().indexOf(query) === -1) continue;
1185
+ found++;
775
1186
 
776
1187
  var el = document.createElement("button");
777
- el.className = "mobile-session-item" + (s.active ? " active" : "");
1188
+ el.className = "mobile-session-item";
1189
+ if (s.active) el.classList.add("active");
778
1190
 
779
1191
  var titleSpan = document.createElement("span");
780
1192
  titleSpan.className = "mobile-session-title";
781
- titleSpan.textContent = s.title || "New Session";
1193
+ titleSpan.textContent = title;
782
1194
  el.appendChild(titleSpan);
783
1195
 
784
1196
  if (s.isProcessing) {
@@ -797,10 +1209,128 @@ function renderSheetSessions(listEl) {
797
1209
  });
798
1210
  })(s.id);
799
1211
 
800
- listEl.appendChild(el);
1212
+ container.appendChild(el);
1213
+ }
1214
+
1215
+ if (found === 0 && query) {
1216
+ var empty = document.createElement("div");
1217
+ empty.className = "mobile-alert-empty";
1218
+ empty.textContent = 'No results for "' + query + '"';
1219
+ container.appendChild(empty);
801
1220
  }
802
1221
  }
803
1222
 
1223
+ function renderSheetTools(listEl) {
1224
+ var isMateDm = document.body.classList.contains("mate-dm-active");
1225
+
1226
+ var items = isMateDm ? [
1227
+ { icon: "book-open", label: "Knowledge", action: "mate-knowledge" },
1228
+ { icon: "calendar-clock", label: "Scheduled Tasks", action: "mate-scheduler" }
1229
+ ] : [
1230
+ { icon: "folder-tree", label: "Files", action: "files" },
1231
+ { icon: "square-terminal", label: "Terminal", action: "terminal" },
1232
+ { icon: "calendar-clock", label: "Scheduled Tasks", action: "scheduler" }
1233
+ ];
1234
+
1235
+ for (var i = 0; i < items.length; i++) {
1236
+ (function (item) {
1237
+ var btn = document.createElement("button");
1238
+ btn.className = "mobile-more-item";
1239
+ btn.innerHTML = '<i data-lucide="' + item.icon + '"></i><span class="mobile-more-item-label">' + item.label + '</span>';
1240
+ btn.addEventListener("click", function () {
1241
+ closeMobileSheet();
1242
+ var targetId = null;
1243
+ if (item.action === "files") {
1244
+ setTimeout(function () { openMobileSheet("files"); }, 250);
1245
+ } else if (item.action === "terminal") {
1246
+ if (ctx.openTerminal) ctx.openTerminal();
1247
+ } else if (item.action === "scheduler") {
1248
+ targetId = "scheduler-btn";
1249
+ } else if (item.action === "mate-knowledge") {
1250
+ setTimeout(function () { openMobileSheet("mate-knowledge"); }, 250);
1251
+ return;
1252
+ } else if (item.action === "mate-sticky") {
1253
+ targetId = "sticky-notes-toggle-btn";
1254
+ } else if (item.action === "mate-skills") {
1255
+ targetId = "mate-skills-btn";
1256
+ } else if (item.action === "mate-scheduler") {
1257
+ targetId = "mate-scheduler-btn";
1258
+ }
1259
+ if (targetId) {
1260
+ var targetBtn = document.getElementById(targetId);
1261
+ if (targetBtn) setTimeout(function () { targetBtn.click(); }, 250);
1262
+ }
1263
+ });
1264
+ listEl.appendChild(btn);
1265
+ })(items[i]);
1266
+ }
1267
+ }
1268
+
1269
+ function renderSheetSettings(listEl) {
1270
+ var items = [
1271
+ { icon: "folder-cog", label: "Project Settings", action: "project-settings" },
1272
+ { icon: "settings", label: "Server Settings", action: "server-settings" }
1273
+ ];
1274
+
1275
+ for (var i = 0; i < items.length; i++) {
1276
+ (function (item) {
1277
+ var btn = document.createElement("button");
1278
+ btn.className = "mobile-more-item";
1279
+ btn.innerHTML = '<i data-lucide="' + item.icon + '"></i><span class="mobile-more-item-label">' + item.label + '</span>';
1280
+ btn.addEventListener("click", function () {
1281
+ closeMobileSheet();
1282
+ if (item.action === "project-settings") {
1283
+ setTimeout(function () {
1284
+ // Find current project data
1285
+ var proj = null;
1286
+ for (var pi = 0; pi < cachedAllProjects.length; pi++) {
1287
+ if (cachedAllProjects[pi].slug === cachedCurrentSlug) {
1288
+ proj = cachedAllProjects[pi];
1289
+ break;
1290
+ }
1291
+ }
1292
+ // For mate projects, use mate display name instead of slug
1293
+ if (proj && proj.isMate && cachedMates.length > 0) {
1294
+ var mateId = cachedCurrentSlug.replace("mate-", "");
1295
+ for (var mi = 0; mi < cachedMates.length; mi++) {
1296
+ var mp = cachedMates[mi].profile || {};
1297
+ if (cachedMates[mi].id === mateId) {
1298
+ proj = Object.assign({}, proj, { name: mp.displayName || cachedMates[mi].name || proj.name });
1299
+ break;
1300
+ }
1301
+ }
1302
+ }
1303
+ openProjectSettings(cachedCurrentSlug, proj);
1304
+ }, 250);
1305
+ } else if (item.action === "server-settings") {
1306
+ var settingsBtn = document.getElementById("server-settings-btn");
1307
+ if (settingsBtn) setTimeout(function () { settingsBtn.click(); }, 250);
1308
+ }
1309
+ });
1310
+ listEl.appendChild(btn);
1311
+ })(items[i]);
1312
+ }
1313
+
1314
+ // Dark/Light switch button
1315
+ var isDark = getCurrentTheme().variant === "dark";
1316
+ var themeBtn = document.createElement("button");
1317
+ themeBtn.className = "mobile-more-item";
1318
+ themeBtn.innerHTML = '<i data-lucide="' + (isDark ? "sun" : "moon") + '"></i><span class="mobile-more-item-label">Switch to ' + (isDark ? "Light" : "Dark") + '</span>';
1319
+
1320
+ themeBtn.addEventListener("click", function () {
1321
+ var themeToggle = document.getElementById("theme-toggle-check");
1322
+ if (themeToggle) themeToggle.click();
1323
+ // Update button text after a tick (theme applies async)
1324
+ setTimeout(function () {
1325
+ var nowDark = getCurrentTheme().variant === "dark";
1326
+ themeBtn.innerHTML = '<i data-lucide="' + (nowDark ? "sun" : "moon") + '"></i><span class="mobile-more-item-label">Switch to ' + (nowDark ? "Light" : "Dark") + '</span>';
1327
+ refreshIcons();
1328
+ }, 50);
1329
+ });
1330
+
1331
+ listEl.appendChild(themeBtn);
1332
+ }
1333
+
804
1334
  export function initSidebar(_ctx) {
805
1335
  ctx = _ctx;
806
1336
 
@@ -822,6 +1352,8 @@ export function initSidebar(_ctx) {
822
1352
 
823
1353
  if (ctx.sidebarToggleBtn) ctx.sidebarToggleBtn.addEventListener("click", toggleSidebarCollapse);
824
1354
  if (ctx.sidebarExpandBtn) ctx.sidebarExpandBtn.addEventListener("click", toggleSidebarCollapse);
1355
+ var mateSidebarToggle = document.getElementById("mate-sidebar-toggle-btn");
1356
+ if (mateSidebarToggle) mateSidebarToggle.addEventListener("click", toggleSidebarCollapse);
825
1357
 
826
1358
  // Restore collapsed state from localStorage
827
1359
  try {
@@ -980,6 +1512,83 @@ export function initSidebar(_ctx) {
980
1512
  var sheetCloseBtn = mobileSheet.querySelector(".mobile-sheet-close");
981
1513
  if (sheetBackdrop) sheetBackdrop.addEventListener("click", closeMobileSheet);
982
1514
  if (sheetCloseBtn) sheetCloseBtn.addEventListener("click", closeMobileSheet);
1515
+
1516
+ // --- Drag to dismiss sheet ---
1517
+ var sheetHandle = mobileSheet.querySelector(".mobile-sheet-handle");
1518
+ var sheetContent = mobileSheet.querySelector(".mobile-sheet-content");
1519
+ if (sheetHandle && sheetContent) {
1520
+ var dragStartY = 0;
1521
+ var dragging = false;
1522
+
1523
+ sheetHandle.addEventListener("touchstart", function (e) {
1524
+ dragStartY = e.touches[0].clientY;
1525
+ dragging = true;
1526
+ sheetContent.style.transition = "none";
1527
+ }, { passive: true });
1528
+
1529
+ mobileSheet.addEventListener("touchmove", function (e) {
1530
+ if (!dragging) return;
1531
+ var deltaY = e.touches[0].clientY - dragStartY;
1532
+ if (deltaY < 0) deltaY = 0;
1533
+ sheetContent.style.transform = "translateY(" + deltaY + "px)";
1534
+ if (sheetBackdrop) {
1535
+ var opacity = Math.max(0, 1 - deltaY / (sheetContent.offsetHeight * 0.5));
1536
+ sheetBackdrop.style.opacity = opacity;
1537
+ }
1538
+ }, { passive: true });
1539
+
1540
+ mobileSheet.addEventListener("touchend", function () {
1541
+ if (!dragging) return;
1542
+ dragging = false;
1543
+ var currentY = parseFloat(sheetContent.style.transform.replace(/[^0-9.-]/g, "")) || 0;
1544
+ var threshold = sheetContent.offsetHeight * 0.3;
1545
+
1546
+ if (currentY > threshold) {
1547
+ sheetContent.style.transition = "transform 0.22s ease-in";
1548
+ sheetContent.style.transform = "translateY(100%)";
1549
+ if (sheetBackdrop) {
1550
+ sheetBackdrop.style.transition = "opacity 0.22s ease-in";
1551
+ sheetBackdrop.style.opacity = "0";
1552
+ }
1553
+ setTimeout(function () {
1554
+ sheetContent.style.transition = "";
1555
+ sheetContent.style.transform = "";
1556
+ if (sheetBackdrop) {
1557
+ sheetBackdrop.style.transition = "";
1558
+ sheetBackdrop.style.opacity = "";
1559
+ }
1560
+ // Close without animation since we already animated
1561
+ var sheet = document.getElementById("mobile-sheet");
1562
+ if (sheet) {
1563
+ if (sheet.classList.contains("sheet-files")) {
1564
+ var fileTree = document.getElementById("file-tree");
1565
+ var sidebarFilesPanel = document.getElementById("sidebar-panel-files");
1566
+ if (fileTree && sidebarFilesPanel) {
1567
+ sidebarFilesPanel.appendChild(fileTree);
1568
+ }
1569
+ }
1570
+ sheet.classList.add("hidden");
1571
+ sheet.classList.remove("closing", "sheet-files");
1572
+ }
1573
+ }, 230);
1574
+ } else {
1575
+ sheetContent.style.transition = "transform 0.2s ease-out";
1576
+ sheetContent.style.transform = "translateY(0)";
1577
+ if (sheetBackdrop) {
1578
+ sheetBackdrop.style.transition = "opacity 0.2s ease-out";
1579
+ sheetBackdrop.style.opacity = "";
1580
+ }
1581
+ setTimeout(function () {
1582
+ sheetContent.style.transition = "";
1583
+ sheetContent.style.transform = "";
1584
+ if (sheetBackdrop) {
1585
+ sheetBackdrop.style.transition = "";
1586
+ sheetBackdrop.style.opacity = "";
1587
+ }
1588
+ }, 200);
1589
+ }
1590
+ }, { passive: true });
1591
+ }
983
1592
  }
984
1593
 
985
1594
  // --- Mobile tab bar ---
@@ -995,6 +1604,13 @@ export function initSidebar(_ctx) {
995
1604
  mobileTabs[i].classList.remove("active");
996
1605
  }
997
1606
  }
1607
+ if (mobileHomeBtn) {
1608
+ if (tabName === "home") {
1609
+ mobileHomeBtn.classList.add("active");
1610
+ } else {
1611
+ mobileHomeBtn.classList.remove("active");
1612
+ }
1613
+ }
998
1614
  }
999
1615
 
1000
1616
  for (var t = 0; t < mobileTabs.length; t++) {
@@ -1002,22 +1618,18 @@ export function initSidebar(_ctx) {
1002
1618
  tab.addEventListener("click", function () {
1003
1619
  var name = tab.dataset.tab;
1004
1620
 
1005
- if (name === "terminal") {
1006
- closeSidebar();
1007
- setMobileTabActive("");
1008
- if (ctx.openTerminal) ctx.openTerminal();
1009
- return;
1010
- }
1011
-
1012
- if (name === "projects") {
1013
- openMobileSheet("projects");
1014
- setMobileTabActive("projects");
1015
- } else if (name === "sessions") {
1621
+ if (name === "chat") {
1016
1622
  openMobileSheet("sessions");
1017
- setMobileTabActive("sessions");
1018
- } else if (name === "files") {
1019
- openMobileSheet("files");
1020
- setMobileTabActive("files");
1623
+ setMobileTabActive("chat");
1624
+ } else if (name === "search") {
1625
+ openCommandPalette();
1626
+ setMobileTabActive("search");
1627
+ } else if (name === "tools") {
1628
+ openMobileSheet("tools");
1629
+ setMobileTabActive("tools");
1630
+ } else if (name === "settings") {
1631
+ openMobileSheet("settings");
1632
+ setMobileTabActive("settings");
1021
1633
  }
1022
1634
  });
1023
1635
  })(mobileTabs[t]);
@@ -1026,7 +1638,7 @@ export function initSidebar(_ctx) {
1026
1638
  if (mobileHomeBtn) {
1027
1639
  mobileHomeBtn.addEventListener("click", function () {
1028
1640
  closeSidebar();
1029
- setMobileTabActive("");
1641
+ setMobileTabActive("home");
1030
1642
  if (ctx.showHomeHub) ctx.showHomeHub();
1031
1643
  });
1032
1644
  }
@@ -2634,7 +3246,12 @@ function createMobileProjectItem(p, currentSlug, isWorktree) {
2634
3246
 
2635
3247
  var abbrev = document.createElement("span");
2636
3248
  abbrev.className = "mobile-project-abbrev";
2637
- abbrev.textContent = getProjectAbbrev(p.name);
3249
+ if (p.icon) {
3250
+ abbrev.textContent = p.icon;
3251
+ parseEmojis(abbrev);
3252
+ } else {
3253
+ abbrev.textContent = getProjectAbbrev(p.name);
3254
+ }
2638
3255
  el.appendChild(abbrev);
2639
3256
 
2640
3257
  var name = document.createElement("span");
@@ -2753,14 +3370,34 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
2753
3370
  })(others[i]);
2754
3371
  }
2755
3372
 
3373
+ // Build mate project status lookup from project list
3374
+ var mateProjectStatus = {};
3375
+ if (ctx && ctx.projectList) {
3376
+ var allProjects = ctx.projectList;
3377
+ for (var pi = 0; pi < allProjects.length; pi++) {
3378
+ if (allProjects[pi].isMate) {
3379
+ mateProjectStatus[allProjects[pi].slug] = allProjects[pi];
3380
+ }
3381
+ }
3382
+ }
3383
+
2756
3384
  // Render mates
2757
3385
  for (var mi = 0; mi < cachedMates.length; mi++) {
2758
3386
  (function (mate) {
2759
3387
  var mp = mate.profile || {};
3388
+ var mateSlug = "mate-" + mate.id;
3389
+ var mateProj = mateProjectStatus[mateSlug] || {};
3390
+ var isActive = mate.id === currentDmUserId;
2760
3391
  var el = document.createElement("div");
2761
3392
  el.className = "icon-strip-user icon-strip-mate";
2762
3393
  el.dataset.userId = mate.id;
2763
- if (mate.id === currentDmUserId) el.classList.add("active");
3394
+ el.dataset.mateSlug = mateSlug;
3395
+ if (isActive) el.classList.add("active");
3396
+
3397
+ // Pending permission shake
3398
+ if (mateProj.pendingPermissions > 0 && !isActive) {
3399
+ el.classList.add("has-pending-perm");
3400
+ }
2764
3401
 
2765
3402
  var pill = document.createElement("span");
2766
3403
  pill.className = "icon-strip-pill";
@@ -2772,6 +3409,12 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
2772
3409
  avatar.alt = mp.displayName || mate.name || "Mate";
2773
3410
  el.appendChild(avatar);
2774
3411
 
3412
+ // Processing status dot (IO blink)
3413
+ var statusDot = document.createElement("span");
3414
+ statusDot.className = "icon-strip-status";
3415
+ if (mateProj.isProcessing) statusDot.classList.add("processing");
3416
+ el.appendChild(statusDot);
3417
+
2775
3418
  // Mate badge (bot icon)
2776
3419
  var mateBadge = document.createElement("span");
2777
3420
  mateBadge.className = "icon-strip-user-mate-badge";
@@ -2783,6 +3426,13 @@ export function renderUserStrip(allUsers, onlineUserIds, myUserId, dmFavorites,
2783
3426
  badge.dataset.userId = mate.id;
2784
3427
  el.appendChild(badge);
2785
3428
 
3429
+ // Restore unread badge if cached
3430
+ var unreadCount = cachedDmUnread[mate.id] || 0;
3431
+ if (unreadCount > 0 && !isActive) {
3432
+ badge.textContent = unreadCount > 99 ? "99+" : String(unreadCount);
3433
+ badge.classList.add("has-unread");
3434
+ }
3435
+
2786
3436
  // Tooltip
2787
3437
  var displayName = mp.displayName || mate.name || "New Mate";
2788
3438
  el.addEventListener("mouseenter", function () { showIconTooltip(el, displayName); });