clay-server 5.0.0-beta.2 → 5.0.0-beta.3
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/daemon.js +9 -0
- package/lib/new-session-default.js +45 -0
- package/lib/project-connection.js +4 -2
- package/lib/project-session-creation.js +107 -0
- package/lib/project-session-delete.js +21 -3
- package/lib/project-session-folder-delete.js +169 -0
- package/lib/project-session-folders.js +272 -0
- package/lib/project-sessions.js +46 -26
- package/lib/project.js +2 -0
- package/lib/public/app.js +10 -0
- package/lib/public/css/filebrowser.css +3 -3
- package/lib/public/css/mates.css +1 -1
- package/lib/public/css/mobile-nav.css +0 -128
- package/lib/public/css/session-folders.css +383 -0
- package/lib/public/css/sidebar.css +28 -545
- package/lib/public/index.html +3 -21
- package/lib/public/modules/app-favicon.js +5 -4
- package/lib/public/modules/app-messages.js +13 -0
- package/lib/public/modules/input.js +3 -0
- package/lib/public/modules/project-switcher.js +4 -4
- package/lib/public/modules/session-create-form.js +428 -0
- package/lib/public/modules/session-create.js +338 -0
- package/lib/public/modules/session-folder-collapse.js +192 -0
- package/lib/public/modules/session-folder-context.js +215 -0
- package/lib/public/modules/session-folder-delete.js +313 -0
- package/lib/public/modules/session-folder-dialogs.js +173 -0
- package/lib/public/modules/session-folder-dnd.js +214 -0
- package/lib/public/modules/session-folder-draft.js +184 -0
- package/lib/public/modules/session-folder-layout.js +133 -0
- package/lib/public/modules/session-folder-toolbar.js +185 -0
- package/lib/public/modules/session-folder-view-menu.js +187 -0
- package/lib/public/modules/session-folders.js +95 -0
- package/lib/public/modules/session-list-search.js +108 -0
- package/lib/public/modules/session-presence-ui.js +44 -0
- package/lib/public/modules/sidebar-mobile.js +38 -182
- package/lib/public/modules/sidebar-session-folders-ui.js +382 -0
- package/lib/public/modules/sidebar-sessions.js +141 -718
- package/lib/public/modules/sidebar.js +10 -2
- package/lib/public/modules/terminal.js +2 -2
- package/lib/public/modules/tool-palette.js +37 -219
- package/lib/public/style.css +1 -0
- package/lib/server.js +4 -0
- package/lib/session-folders.js +414 -0
- package/lib/sessions.js +7 -5
- package/lib/users-session-folder-preferences.js +69 -0
- package/lib/users.js +6 -0
- package/lib/ws-schema.js +11 -0
- package/package.json +1 -1
- package/lib/public/modules/session-creation-cta.js +0 -46
- package/lib/public/modules/tool-palette-overlays.js +0 -159
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Session list search: query, open state and results live in the store
|
|
2
|
+
// (sessionSearch) so the folder toolbar on every surface can drive and show them.
|
|
3
|
+
// Behavior is unchanged from the former header search: a debounced
|
|
4
|
+
// search_sessions request, stale responses ignored, matches filter the list while
|
|
5
|
+
// keeping Driver/Worker hierarchy.
|
|
6
|
+
|
|
7
|
+
import { store } from './store.js';
|
|
8
|
+
import { getWs } from './ws-ref.js';
|
|
9
|
+
import { hierarchyItemMatches } from './sidebar-session-hierarchy.js';
|
|
10
|
+
|
|
11
|
+
var DEBOUNCE_MS = 200;
|
|
12
|
+
|
|
13
|
+
function current() {
|
|
14
|
+
return store.get('sessionSearch') || { open: false, query: "", matchIds: null, timer: null, focus: false, selStart: null, selEnd: null };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function patch(change) {
|
|
18
|
+
store.set({ sessionSearch: Object.assign({}, current(), change) });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function getListSearchQuery() {
|
|
22
|
+
return current().query;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// null means no search; otherwise a Set of matching session ids.
|
|
26
|
+
export function getListSearchMatchIds() {
|
|
27
|
+
return current().matchIds;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function listSearchActive() {
|
|
31
|
+
var state = current();
|
|
32
|
+
return state.open || !!state.query;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function cancelTimer() {
|
|
36
|
+
var timer = current().timer;
|
|
37
|
+
if (timer) clearTimeout(timer);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function openListSearch() {
|
|
41
|
+
patch({ open: true, focus: true, selStart: null, selEnd: null });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Close always clears the query, so the list returns to its normal state.
|
|
45
|
+
export function closeListSearch() {
|
|
46
|
+
cancelTimer();
|
|
47
|
+
patch({ open: false, query: "", matchIds: null, timer: null, focus: false, selStart: null, selEnd: null });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Clear empties the text but keeps the search open for another query.
|
|
51
|
+
export function clearListSearchText() {
|
|
52
|
+
cancelTimer();
|
|
53
|
+
patch({ query: "", matchIds: null, timer: null, focus: true, selStart: 0, selEnd: 0 });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function setListSearchQuery(raw) {
|
|
57
|
+
var query = raw || "";
|
|
58
|
+
cancelTimer();
|
|
59
|
+
if (!query.trim()) {
|
|
60
|
+
patch({ query: query, matchIds: null, timer: null });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
var timer = setTimeout(function () {
|
|
64
|
+
var ws = getWs();
|
|
65
|
+
var state = current();
|
|
66
|
+
if (ws && store.get('connected') && state.query === query) ws.send(JSON.stringify({ type: "search_sessions", query: query }));
|
|
67
|
+
}, DEBOUNCE_MS);
|
|
68
|
+
patch({ query: query, timer: timer });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function rememberListSearchCaret(focus, selStart, selEnd) {
|
|
72
|
+
var state = current();
|
|
73
|
+
if (state.focus === focus && state.selStart === selStart && state.selEnd === selEnd) return;
|
|
74
|
+
patch({ focus: focus, selStart: selStart, selEnd: selEnd });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function applyListSearchResults(msg) {
|
|
78
|
+
if (msg.query !== current().query) return; // stale response
|
|
79
|
+
var ids = new Set();
|
|
80
|
+
for (var i = 0; i < msg.results.length; i++) ids.add(msg.results[i].id);
|
|
81
|
+
patch({ matchIds: ids });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// What a list rerender needs to react to; typing and caret movement do not.
|
|
85
|
+
export function listSearchNeedsRender(state, previous) {
|
|
86
|
+
var a = state.sessionSearch;
|
|
87
|
+
var b = previous.sessionSearch;
|
|
88
|
+
if (a === b) return false;
|
|
89
|
+
var after = a || { open: false, query: "", matchIds: null };
|
|
90
|
+
var before = b || { open: false, query: "", matchIds: null };
|
|
91
|
+
if (after.open !== before.open || after.matchIds !== before.matchIds) return true;
|
|
92
|
+
return after.query !== before.query && !after.query.trim();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Shared by the desktop list and the mobile sheet: drops items with no match and
|
|
96
|
+
// keeps a Driver visible when it or any of its Workers match.
|
|
97
|
+
export function filterItemsBySearch(items, matchIds) {
|
|
98
|
+
if (matchIds === null) return items;
|
|
99
|
+
return items.filter(function (item) {
|
|
100
|
+
if (item.type === "session") return !!(item.data && matchIds.has(item.data.id));
|
|
101
|
+
if (item.type === "driver-hierarchy" || item.type === "orphan-workers") return hierarchyItemMatches(item, matchIds);
|
|
102
|
+
if (item.type === "split-group") {
|
|
103
|
+
for (var i = 0; i < item.members.length; i++) if (matchIds.has(item.members[i].id)) return true;
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return true;
|
|
107
|
+
});
|
|
108
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { avatarUrl, userAvatarUrl } from './avatar.js';
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
|
|
4
|
+
function avatarFor(userOrStyle, seed) {
|
|
5
|
+
if (userOrStyle && typeof userOrStyle === "object") return userAvatarUrl(userOrStyle, 24);
|
|
6
|
+
return avatarUrl(userOrStyle || "imprint", seed, 24);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setSessionPresence(presence) {
|
|
10
|
+
store.set({ sessionPresence: presence || {} });
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function renderSessionPresence(row, sessionId) {
|
|
14
|
+
var existing = row.querySelector(".session-presence");
|
|
15
|
+
if (existing) existing.remove();
|
|
16
|
+
var all = store.get('sessionPresence') || {};
|
|
17
|
+
var users = all[String(sessionId)] || all[sessionId];
|
|
18
|
+
if (!users || !users.length) return null;
|
|
19
|
+
|
|
20
|
+
var container = document.createElement("span");
|
|
21
|
+
container.className = "session-presence";
|
|
22
|
+
container.setAttribute("aria-label", users.length + (users.length === 1 ? " person viewing" : " people viewing"));
|
|
23
|
+
var max = 3;
|
|
24
|
+
var shown = Math.min(max, users.length);
|
|
25
|
+
for (var i = 0; i < shown; i++) {
|
|
26
|
+
var user = users[i];
|
|
27
|
+
var image = document.createElement("img");
|
|
28
|
+
image.className = "session-presence-avatar";
|
|
29
|
+
image.src = avatarFor(user);
|
|
30
|
+
image.alt = user.displayName || "Viewer";
|
|
31
|
+
image.dataset.tip = (user.displayName || "Viewer") + (user.username ? " (@" + user.username + ")" : "");
|
|
32
|
+
container.appendChild(image);
|
|
33
|
+
}
|
|
34
|
+
if (users.length > max) {
|
|
35
|
+
var more = document.createElement("span");
|
|
36
|
+
more.className = "session-presence-more";
|
|
37
|
+
more.textContent = "+" + (users.length - max);
|
|
38
|
+
container.appendChild(more);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var anchor = row.querySelector(".session-item-age, .mobile-worker-generation, .mobile-session-unread, .session-row-actions, .mobile-session-star");
|
|
42
|
+
row.insertBefore(container, anchor || null);
|
|
43
|
+
return container;
|
|
44
|
+
}
|
|
@@ -9,15 +9,15 @@ import { parseEmojis } from './markdown.js';
|
|
|
9
9
|
import { getCurrentTheme, getChatLayout, setChatLayout } from './theme.js';
|
|
10
10
|
import { openCommandPalette } from './command-palette.js';
|
|
11
11
|
import { openProjectSettings } from './project-settings.js';
|
|
12
|
+
import { getCachedSessions, openSessionContextMenu } from './sidebar-sessions.js';
|
|
13
|
+
import { VENDOR_AVATARS } from './app-rendering.js';
|
|
14
|
+
import { renderSessionPresence } from './session-presence-ui.js';
|
|
12
15
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from './
|
|
18
|
-
import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperimentalVendor } from './app-rendering.js';
|
|
19
|
-
import { defaultVendorUnavailable, resolvePreferredVendor, saveDefaultVendor } from './default-vendor.js';
|
|
20
|
-
import { renderSessionCreationCta } from './session-creation-cta.js';
|
|
16
|
+
canOrganizeSession, computeLayout, createStarButton, renderFavoritesSection, renderOrdinarySections
|
|
17
|
+
} from './sidebar-session-folders-ui.js';
|
|
18
|
+
import { renderFolderToolbar, captureFolderInputs } from './session-folder-toolbar.js';
|
|
19
|
+
import { captureFolderCollapseAnimations } from './session-folder-collapse.js';
|
|
20
|
+
import { getListSearchMatchIds, filterItemsBySearch } from './session-list-search.js';
|
|
21
21
|
import {
|
|
22
22
|
getCachedProjectList,
|
|
23
23
|
getCachedCurrentSlug,
|
|
@@ -124,7 +124,7 @@ export function openMobileSheet(type) {
|
|
|
124
124
|
refreshIcons();
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
function closeMobileSheet() {
|
|
127
|
+
export function closeMobileSheet() {
|
|
128
128
|
var sheet = document.getElementById("mobile-sheet");
|
|
129
129
|
if (!sheet || sheet.classList.contains("hidden")) return;
|
|
130
130
|
|
|
@@ -387,6 +387,7 @@ function createMobileSessionItem(s, options) {
|
|
|
387
387
|
var itemOptions = options || {};
|
|
388
388
|
var el = document.createElement("div");
|
|
389
389
|
el.className = "mobile-session-item" + (s.active ? " active" : "");
|
|
390
|
+
el.dataset.sessionId = String(s.id);
|
|
390
391
|
if (itemOptions.worker) el.classList.add("mobile-worker-item");
|
|
391
392
|
|
|
392
393
|
// Vendor badge (legacy sessions without a vendor are Claude)
|
|
@@ -414,6 +415,7 @@ function createMobileSessionItem(s, options) {
|
|
|
414
415
|
titleSpan.appendChild(document.createTextNode(s.title || "New Session"));
|
|
415
416
|
el.appendChild(titleSpan);
|
|
416
417
|
appendGithubWork(el, s, titleSpan);
|
|
418
|
+
renderSessionPresence(el, String(s.id));
|
|
417
419
|
|
|
418
420
|
if (itemOptions.worker) {
|
|
419
421
|
var generation = document.createElement("span");
|
|
@@ -430,6 +432,16 @@ function createMobileSessionItem(s, options) {
|
|
|
430
432
|
el.appendChild(badge);
|
|
431
433
|
}
|
|
432
434
|
|
|
435
|
+
if (!itemOptions.worker && canOrganizeSession(s)) {
|
|
436
|
+
el.appendChild(createStarButton(s, "mobile-session-star"));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
el.addEventListener("contextmenu", function (event) {
|
|
440
|
+
event.preventDefault();
|
|
441
|
+
event.stopPropagation();
|
|
442
|
+
openSessionContextMenu(el, s);
|
|
443
|
+
});
|
|
444
|
+
|
|
433
445
|
(function (id) {
|
|
434
446
|
el.addEventListener("click", function () {
|
|
435
447
|
if (getWs() && store.get('connected')) {
|
|
@@ -680,125 +692,6 @@ function createMobileLoopGroup(loopId, children, groupKey) {
|
|
|
680
692
|
|
|
681
693
|
// Helper: render sorted sessions into a container with date groups (with loop session grouping)
|
|
682
694
|
export function renderMobileSessionsInto(container) {
|
|
683
|
-
var mobileDefaultVendor = resolvePreferredVendor(store.get('installedVendors') || []) || resolveDefaultVendor();
|
|
684
|
-
var mobileInstalled = store.get('installedVendors') || [];
|
|
685
|
-
var mobileDefaultState = store.get('defaultVendorState') || {};
|
|
686
|
-
|
|
687
|
-
var vendorList = document.createElement("div");
|
|
688
|
-
vendorList.className = "mobile-vendor-list hidden";
|
|
689
|
-
vendorList.id = "mobile-session-provider-menu";
|
|
690
|
-
var mobileVendorName = VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor;
|
|
691
|
-
var mobileCreateTitle = "Create a " + mobileVendorName + " session";
|
|
692
|
-
if (defaultVendorUnavailable(mobileInstalled)) mobileCreateTitle += " (saved default is unavailable; using installed priority)";
|
|
693
|
-
var controls;
|
|
694
|
-
controls = renderSessionCreationCta({
|
|
695
|
-
rowClass: "mobile-session-new-row",
|
|
696
|
-
createClass: "mobile-session-new mobile-session-new-main",
|
|
697
|
-
providerClass: "mobile-session-new mobile-session-new-provider",
|
|
698
|
-
iconClass: "mobile-session-new-icon",
|
|
699
|
-
vendorKey: mobileDefaultVendor,
|
|
700
|
-
vendorName: mobileVendorName,
|
|
701
|
-
vendorAvatar: VENDOR_AVATARS[mobileDefaultVendor] || VENDOR_AVATARS.claude,
|
|
702
|
-
createTitle: mobileCreateTitle,
|
|
703
|
-
providerTitle: "Switch AI provider (current: " + mobileVendorName + ")",
|
|
704
|
-
menuId: vendorList.id,
|
|
705
|
-
onCreate: function () { startNewSession(mobileDefaultVendor); closeMobileSheet(); },
|
|
706
|
-
onProvider: function () {
|
|
707
|
-
var opening = vendorList.classList.contains("hidden");
|
|
708
|
-
vendorList.classList.toggle("hidden", !opening);
|
|
709
|
-
controls.providerButton.classList.toggle("expanded", opening);
|
|
710
|
-
controls.providerButton.setAttribute("aria-expanded", opening ? "true" : "false");
|
|
711
|
-
if (opening) {
|
|
712
|
-
var first = vendorList.querySelector("button:not(:disabled)");
|
|
713
|
-
if (first) first.focus();
|
|
714
|
-
}
|
|
715
|
-
},
|
|
716
|
-
});
|
|
717
|
-
var newRow = controls.element;
|
|
718
|
-
vendorList.addEventListener("keydown", function (e) {
|
|
719
|
-
if (e.key !== "Escape") return;
|
|
720
|
-
e.preventDefault();
|
|
721
|
-
vendorList.classList.add("hidden");
|
|
722
|
-
controls.providerButton.classList.remove("expanded");
|
|
723
|
-
controls.providerButton.setAttribute("aria-expanded", "false");
|
|
724
|
-
controls.providerButton.focus();
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
if (defaultVendorUnavailable(mobileInstalled) && mobileDefaultState.preference) {
|
|
728
|
-
var mobileFallbackNote = document.createElement("div");
|
|
729
|
-
mobileFallbackNote.className = "mobile-session-new-preference-status mobile-session-new-fallback-note";
|
|
730
|
-
mobileFallbackNote.textContent = "Saved default " + (VENDOR_NAMES[mobileDefaultState.preference] || mobileDefaultState.preference) + " is unavailable. Using " + (VENDOR_NAMES[mobileDefaultVendor] || mobileDefaultVendor) + ".";
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Every known vendor is listed. Uninstalled ones stay visible but disabled,
|
|
734
|
-
// and tapping one opens the vendor's homepage.
|
|
735
|
-
for (var vi = 0; vi < VENDOR_ORDER.length; vi++) {
|
|
736
|
-
(function (vendor) {
|
|
737
|
-
var isInstalled = mobileInstalled.indexOf(vendor) !== -1;
|
|
738
|
-
var name = VENDOR_NAMES[vendor] || vendor;
|
|
739
|
-
var vendorRow = document.createElement("div");
|
|
740
|
-
vendorRow.className = "mobile-vendor-row";
|
|
741
|
-
var vBtn = document.createElement("button");
|
|
742
|
-
vBtn.className = "mobile-session-new mobile-session-new-vendor";
|
|
743
|
-
if (!isInstalled) vBtn.classList.add("disabled");
|
|
744
|
-
if (vendor === mobileDefaultVendor) vBtn.classList.add("active");
|
|
745
|
-
vBtn.innerHTML = '<img src="' + VENDOR_AVATARS[vendor] + '" class="mobile-session-new-icon" alt="">' +
|
|
746
|
-
'<span>' + (isInstalled ? "Create with " : "Learn about ") + name + '</span>' +
|
|
747
|
-
(isExperimentalVendor(vendor) ? '<span class="vendor-experimental-badge" aria-label="Experimental integration" title="Experimental integration; not yet validated through direct use testing">🧪</span>' : '') +
|
|
748
|
-
(isInstalled ? "" : '<span class="mobile-vendor-note">Not installed</span>');
|
|
749
|
-
vBtn.addEventListener("click", function () {
|
|
750
|
-
if (!isInstalled) {
|
|
751
|
-
window.open(VENDOR_HOMEPAGES[vendor], "_blank", "noopener");
|
|
752
|
-
return;
|
|
753
|
-
}
|
|
754
|
-
vendorList.classList.add("hidden");
|
|
755
|
-
controls.providerButton.classList.remove("expanded");
|
|
756
|
-
controls.providerButton.setAttribute("aria-expanded", "false");
|
|
757
|
-
startNewSession(vendor);
|
|
758
|
-
closeMobileSheet();
|
|
759
|
-
});
|
|
760
|
-
vendorRow.appendChild(vBtn);
|
|
761
|
-
if (isInstalled) {
|
|
762
|
-
var defaultBtn = document.createElement("button");
|
|
763
|
-
defaultBtn.type = "button";
|
|
764
|
-
defaultBtn.className = "mobile-vendor-set-default";
|
|
765
|
-
defaultBtn.textContent = mobileDefaultState.saving ? "Saving…" : (vendor === mobileDefaultVendor && !defaultVendorUnavailable(mobileInstalled) ? "Default" : "Set default");
|
|
766
|
-
defaultBtn.disabled = mobileDefaultState.saving === true;
|
|
767
|
-
defaultBtn.setAttribute("aria-label", "Set " + name + " as default vendor");
|
|
768
|
-
defaultBtn.addEventListener("click", function (e) {
|
|
769
|
-
e.preventDefault();
|
|
770
|
-
e.stopPropagation();
|
|
771
|
-
saveDefaultVendor(vendor);
|
|
772
|
-
});
|
|
773
|
-
vendorRow.appendChild(defaultBtn);
|
|
774
|
-
}
|
|
775
|
-
vendorList.appendChild(vendorRow);
|
|
776
|
-
})(VENDOR_ORDER[vi]);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
var automaticBtn = document.createElement("button");
|
|
780
|
-
automaticBtn.className = "mobile-session-new mobile-session-new-vendor mobile-session-new-automatic";
|
|
781
|
-
automaticBtn.type = "button";
|
|
782
|
-
automaticBtn.textContent = "Use automatic default";
|
|
783
|
-
automaticBtn.disabled = mobileDefaultState.saving === true;
|
|
784
|
-
automaticBtn.addEventListener("click", function (e) {
|
|
785
|
-
e.preventDefault();
|
|
786
|
-
e.stopPropagation();
|
|
787
|
-
saveDefaultVendor("");
|
|
788
|
-
});
|
|
789
|
-
vendorList.appendChild(automaticBtn);
|
|
790
|
-
if (mobileDefaultState.error) {
|
|
791
|
-
var mobileStatus = document.createElement("div");
|
|
792
|
-
mobileStatus.className = "mobile-session-new-preference-status";
|
|
793
|
-
mobileStatus.setAttribute("role", "alert");
|
|
794
|
-
mobileStatus.textContent = mobileDefaultState.error;
|
|
795
|
-
vendorList.appendChild(mobileStatus);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
container.appendChild(newRow);
|
|
799
|
-
if (mobileFallbackNote) container.appendChild(mobileFallbackNote);
|
|
800
|
-
container.appendChild(vendorList);
|
|
801
|
-
|
|
802
695
|
// Partition: loop sessions vs normal sessions (same logic as desktop renderSessionList)
|
|
803
696
|
var sessions = getCachedSessions();
|
|
804
697
|
var hierarchy = prepareSidebarHierarchy(sessions);
|
|
@@ -846,60 +739,21 @@ export function renderMobileSessionsInto(container) {
|
|
|
846
739
|
items.push({ type: "loop", loopId: realLoopId, groupKey: gk, children: children, lastActivity: maxActivity });
|
|
847
740
|
}
|
|
848
741
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
regularItems.push(item);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if (bookmarkedItems.length > 0) {
|
|
869
|
-
var bookmarkedHeader = document.createElement("div");
|
|
870
|
-
bookmarkedHeader.className = "mobile-sheet-group";
|
|
871
|
-
bookmarkedHeader.textContent = "Favorites";
|
|
872
|
-
container.appendChild(bookmarkedHeader);
|
|
873
|
-
|
|
874
|
-
for (var bi = 0; bi < bookmarkedItems.length; bi++) {
|
|
875
|
-
if (bookmarkedItems[bi].type === "driver-hierarchy") container.appendChild(renderMobileDriverHierarchy(bookmarkedItems[bi].root, createMobileSessionItem, refreshMobileChatSheet));
|
|
876
|
-
else container.appendChild(createMobileSessionItem(bookmarkedItems[bi].data));
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
var currentGroup = "";
|
|
881
|
-
for (var ri = 0; ri < regularItems.length; ri++) {
|
|
882
|
-
var item = regularItems[ri];
|
|
883
|
-
var group = getDateGroup(item.lastActivity || 0);
|
|
884
|
-
if (group !== currentGroup) {
|
|
885
|
-
currentGroup = group;
|
|
886
|
-
if (group !== "Today") {
|
|
887
|
-
var header = document.createElement("div");
|
|
888
|
-
header.className = "mobile-sheet-group";
|
|
889
|
-
header.textContent = group;
|
|
890
|
-
container.appendChild(header);
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
if (item.type === "loop") {
|
|
894
|
-
container.appendChild(createMobileLoopGroup(item.loopId, item.children, item.groupKey));
|
|
895
|
-
} else if (item.type === "driver-hierarchy") {
|
|
896
|
-
container.appendChild(renderMobileDriverHierarchy(item.root, createMobileSessionItem, refreshMobileChatSheet));
|
|
897
|
-
} else if (item.type === "orphan-workers") {
|
|
898
|
-
container.appendChild(renderMobileOrphanHierarchy(item.workers, createMobileSessionItem, refreshMobileChatSheet));
|
|
899
|
-
} else {
|
|
900
|
-
container.appendChild(createMobileSessionItem(item.data));
|
|
901
|
-
}
|
|
902
|
-
}
|
|
742
|
+
var matchIds = getListSearchMatchIds();
|
|
743
|
+
var layout = computeLayout(filterItemsBySearch(items, matchIds), matchIds !== null, "mobile");
|
|
744
|
+
var folderCtx = {
|
|
745
|
+
surface: "mobile",
|
|
746
|
+
searching: matchIds !== null,
|
|
747
|
+
renderItem: function (it) {
|
|
748
|
+
if (it.type === "loop") return createMobileLoopGroup(it.loopId, it.children, it.groupKey);
|
|
749
|
+
if (it.type === "driver-hierarchy") return renderMobileDriverHierarchy(it.root, createMobileSessionItem, refreshMobileChatSheet);
|
|
750
|
+
if (it.type === "orphan-workers") return renderMobileOrphanHierarchy(it.workers, createMobileSessionItem, refreshMobileChatSheet);
|
|
751
|
+
return createMobileSessionItem(it.data);
|
|
752
|
+
},
|
|
753
|
+
};
|
|
754
|
+
container.appendChild(renderFolderToolbar("mobile"));
|
|
755
|
+
container.appendChild(renderFavoritesSection(layout, folderCtx));
|
|
756
|
+
renderOrdinarySections(layout, folderCtx, container);
|
|
903
757
|
}
|
|
904
758
|
|
|
905
759
|
// Refresh mobile chat sheet when session data updates (called from renderSessionList)
|
|
@@ -944,6 +798,8 @@ export function refreshMobileChatSheet() {
|
|
|
944
798
|
}
|
|
945
799
|
|
|
946
800
|
// Re-render sessions for current context
|
|
801
|
+
captureFolderInputs();
|
|
802
|
+
captureFolderCollapseAnimations(sessionListEl);
|
|
947
803
|
sessionListEl.innerHTML = "";
|
|
948
804
|
renderMobileSessionsInto(sessionListEl);
|
|
949
805
|
|