clay-server 2.47.0-beta.4 → 2.47.0-beta.6
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/project-connection.js +5 -25
- package/lib/project-http.js +5 -2
- package/lib/public/app.js +4 -1
- package/lib/public/css/icon-strip.css +0 -190
- package/lib/public/css/mobile-nav.css +20 -0
- package/lib/public/css/sidebar.css +19 -0
- package/lib/public/css/title-bar.css +169 -0
- package/lib/public/index.html +6 -0
- package/lib/public/modules/app-favicon.js +1 -10
- package/lib/public/modules/app-home-hub.js +2 -0
- package/lib/public/modules/app-messages.js +4 -7
- package/lib/public/modules/app-projects.js +43 -30
- package/lib/public/modules/branch-switcher.js +178 -0
- package/lib/public/modules/dom-refs.js +0 -1
- package/lib/public/modules/project-switcher.js +2 -1
- package/lib/public/modules/sidebar-mobile.js +27 -6
- package/lib/public/modules/sidebar-projects.js +63 -236
- package/lib/public/modules/sidebar-sessions.js +5 -0
- package/lib/public/modules/worktree-family.js +60 -0
- package/lib/sessions.js +1 -0
- package/package.json +1 -1
|
@@ -32,6 +32,7 @@ import { resetRateLimitState } from './app-rate-limit.js';
|
|
|
32
32
|
import { closeSessionInfoPopover } from './app-header.js';
|
|
33
33
|
import { resetDebateState } from './debate.js';
|
|
34
34
|
import { removeDebateBottomBar } from './app-debate-ui.js';
|
|
35
|
+
import { familyOf as deriveFamily, displayProject } from './worktree-family.js';
|
|
35
36
|
|
|
36
37
|
// --- Module-owned state ---
|
|
37
38
|
var cachedProjects = [];
|
|
@@ -201,6 +202,32 @@ export function setCachedProjectCount(v) { cachedProjectCount = v; }
|
|
|
201
202
|
export function getCachedRemovedProjects() { return cachedRemovedProjects; }
|
|
202
203
|
export function setCachedRemovedProjects(v) { cachedRemovedProjects = v; }
|
|
203
204
|
|
|
205
|
+
export function familyOf(slug) { return deriveFamily(cachedProjects, slug); }
|
|
206
|
+
export function currentFamily() { return familyOf(store.get('currentSlug')); }
|
|
207
|
+
|
|
208
|
+
export function updateTitleBarProjectIdentity(fallbackName, fallbackIcon) {
|
|
209
|
+
if (store.get('dmMode') || store.get('mateProjectSlug')) {
|
|
210
|
+
var dmNameEl = document.getElementById("title-bar-project-name");
|
|
211
|
+
if (dmNameEl && fallbackName) dmNameEl.textContent = fallbackName;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
var identity = displayProject(cachedProjects, store.get('currentSlug'));
|
|
215
|
+
var name = identity ? (identity.title || identity.project || identity.slug) : fallbackName;
|
|
216
|
+
var icon = identity ? (identity.icon || null) : (fallbackIcon || null);
|
|
217
|
+
var nameEl = document.getElementById("title-bar-project-name");
|
|
218
|
+
if (nameEl && name) nameEl.textContent = name;
|
|
219
|
+
var iconEl = document.getElementById("title-bar-project-icon");
|
|
220
|
+
if (!iconEl) return;
|
|
221
|
+
if (icon) {
|
|
222
|
+
iconEl.textContent = icon;
|
|
223
|
+
parseEmojis(iconEl);
|
|
224
|
+
iconEl.classList.add("has-icon");
|
|
225
|
+
} else {
|
|
226
|
+
iconEl.textContent = "";
|
|
227
|
+
iconEl.classList.remove("has-icon");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
204
231
|
// --- Functions ---
|
|
205
232
|
|
|
206
233
|
export function updateProjectList(msg) {
|
|
@@ -215,6 +242,7 @@ export function updateProjectList(msg) {
|
|
|
215
242
|
_lastProjectsJson = projectsJson;
|
|
216
243
|
}
|
|
217
244
|
cachedProjects = msg.projects;
|
|
245
|
+
store.set({ projects: cachedProjects });
|
|
218
246
|
}
|
|
219
247
|
if (msg.removedProjects) cachedRemovedProjects = msg.removedProjects;
|
|
220
248
|
else if (msg.removedProjects === undefined) { /* keep cached */ }
|
|
@@ -326,31 +354,12 @@ export function renderProjectList() {
|
|
|
326
354
|
});
|
|
327
355
|
var st = store.snap();
|
|
328
356
|
var iconStripActiveSlug = (st.mateProjectSlug && st.savedMainSlug) ? st.savedMainSlug : st.currentSlug;
|
|
357
|
+
var activeFamily = deriveFamily(cachedProjects, iconStripActiveSlug);
|
|
358
|
+
if (activeFamily.parent) iconStripActiveSlug = activeFamily.parent.slug;
|
|
329
359
|
renderIconStrip(iconStripProjects, iconStripActiveSlug);
|
|
330
360
|
// Update title bar project name and icon if it changed
|
|
331
361
|
if (!st.mateProjectSlug) {
|
|
332
|
-
|
|
333
|
-
if (cachedProjects[pi].slug === st.currentSlug) {
|
|
334
|
-
var updatedName = cachedProjects[pi].title || cachedProjects[pi].project;
|
|
335
|
-
var tbName = document.getElementById("title-bar-project-name");
|
|
336
|
-
if (tbName && updatedName) tbName.textContent = updatedName;
|
|
337
|
-
var tbIcon = document.getElementById("title-bar-project-icon");
|
|
338
|
-
if (tbIcon) {
|
|
339
|
-
var pIcon = cachedProjects[pi].icon || null;
|
|
340
|
-
if (pIcon) {
|
|
341
|
-
tbIcon.textContent = pIcon;
|
|
342
|
-
parseEmojis(tbIcon);
|
|
343
|
-
tbIcon.classList.add("has-icon");
|
|
344
|
-
try { localStorage.setItem("clay-project-icon-" + (st.currentSlug || "default"), pIcon); } catch (e) {}
|
|
345
|
-
} else {
|
|
346
|
-
tbIcon.textContent = "";
|
|
347
|
-
tbIcon.classList.remove("has-icon");
|
|
348
|
-
try { localStorage.removeItem("clay-project-icon-" + (st.currentSlug || "default")); } catch (e) {}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
362
|
+
updateTitleBarProjectIdentity(store.get('projectName'), null);
|
|
354
363
|
}
|
|
355
364
|
// Re-apply current socket status to the active icon's dot
|
|
356
365
|
var dot = getStatusDot();
|
|
@@ -557,9 +566,13 @@ function showRemoveProjectTaskDialog(slug, name, taskCount) {
|
|
|
557
566
|
export function handleRemoveProjectResult(msg) {
|
|
558
567
|
if (msg.ok) {
|
|
559
568
|
var currentSlug = store.get('currentSlug');
|
|
569
|
+
var removedProj = null;
|
|
570
|
+
for (var ri = 0; ri < cachedProjects.length; ri++) {
|
|
571
|
+
if (cachedProjects[ri].slug === msg.slug) { removedProj = cachedProjects[ri]; break; }
|
|
572
|
+
}
|
|
573
|
+
var isWorktree = removedProj ? !!removedProj.isWorktree : msg.slug.indexOf("--") !== -1;
|
|
560
574
|
if (msg.slug === currentSlug) {
|
|
561
|
-
var
|
|
562
|
-
var parentSlug = isWorktree ? msg.slug.split("--")[0] : null;
|
|
575
|
+
var parentSlug = isWorktree ? ((removedProj && removedProj.parentSlug) || msg.slug.split("--")[0]) : null;
|
|
563
576
|
|
|
564
577
|
showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
|
|
565
578
|
|
|
@@ -570,10 +583,6 @@ export function handleRemoveProjectResult(msg) {
|
|
|
570
583
|
store.set({ connected: false });
|
|
571
584
|
document.getElementById("connect-overlay").classList.add("hidden");
|
|
572
585
|
if (!isWorktree) {
|
|
573
|
-
var removedProj = null;
|
|
574
|
-
for (var ri = 0; ri < cachedProjects.length; ri++) {
|
|
575
|
-
if (cachedProjects[ri].slug === msg.slug) { removedProj = cachedProjects[ri]; break; }
|
|
576
|
-
}
|
|
577
586
|
if (removedProj) {
|
|
578
587
|
cachedRemovedProjects.push({
|
|
579
588
|
path: removedProj.path || "",
|
|
@@ -585,7 +594,7 @@ export function handleRemoveProjectResult(msg) {
|
|
|
585
594
|
}
|
|
586
595
|
cachedProjects = cachedProjects.filter(function (p) { return p.slug !== msg.slug; });
|
|
587
596
|
cachedProjectCount = cachedProjects.length;
|
|
588
|
-
store.set({ currentSlug: null });
|
|
597
|
+
store.set({ currentSlug: null, projects: cachedProjects });
|
|
589
598
|
renderProjectList();
|
|
590
599
|
resetClientState();
|
|
591
600
|
|
|
@@ -595,7 +604,11 @@ export function handleRemoveProjectResult(msg) {
|
|
|
595
604
|
showHomeHub();
|
|
596
605
|
}
|
|
597
606
|
} else {
|
|
598
|
-
showToast(
|
|
607
|
+
showToast(isWorktree ? "Worktree removed" : "Project removed", "success");
|
|
608
|
+
cachedProjects = cachedProjects.filter(function (p) { return p.slug !== msg.slug; });
|
|
609
|
+
cachedProjectCount = cachedProjects.length;
|
|
610
|
+
store.set({ projects: cachedProjects });
|
|
611
|
+
renderProjectList();
|
|
599
612
|
}
|
|
600
613
|
} else {
|
|
601
614
|
showToast(msg.error || "Failed to remove project", "error");
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
// Title-bar branch switcher for a project and its worktrees.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
|
+
import { escapeHtml } from './utils.js';
|
|
6
|
+
import { familyOf } from './worktree-family.js';
|
|
7
|
+
import { switchProject, confirmRemoveProject } from './app-projects.js';
|
|
8
|
+
import { showWorktreeModal } from './sidebar-projects.js';
|
|
9
|
+
|
|
10
|
+
var chip = null;
|
|
11
|
+
var label = null;
|
|
12
|
+
var menu = null;
|
|
13
|
+
var menuOpen = false;
|
|
14
|
+
var defaultBranches = {};
|
|
15
|
+
// slug -> true/false once /api/branches answers; undefined while unknown.
|
|
16
|
+
// Non-git projects hide the chip (no worktrees to manage there).
|
|
17
|
+
var gitRepoBySlug = {};
|
|
18
|
+
|
|
19
|
+
function projectName(project) {
|
|
20
|
+
return project ? (project.title || project.project || project.name || project.slug) : "";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function currentProject(projects) {
|
|
24
|
+
var slug = store.get('currentSlug');
|
|
25
|
+
for (var i = 0; i < projects.length; i++) {
|
|
26
|
+
if (projects[i].slug === slug) return projects[i];
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultBranch(parent) {
|
|
32
|
+
return defaultBranches[parent.slug] || "default";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function requestDefaultBranch(parent) {
|
|
36
|
+
if (!parent || Object.prototype.hasOwnProperty.call(defaultBranches, parent.slug)) return;
|
|
37
|
+
defaultBranches[parent.slug] = null;
|
|
38
|
+
fetch("/p/" + encodeURIComponent(parent.slug) + "/api/branches")
|
|
39
|
+
.then(function (response) { return response.json(); })
|
|
40
|
+
.then(function (data) {
|
|
41
|
+
defaultBranches[parent.slug] = data.defaultBranch || "default";
|
|
42
|
+
gitRepoBySlug[parent.slug] = data.isGitRepo !== false;
|
|
43
|
+
renderBranchSwitcher();
|
|
44
|
+
})
|
|
45
|
+
.catch(function () {
|
|
46
|
+
defaultBranches[parent.slug] = "default";
|
|
47
|
+
renderBranchSwitcher();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function closeMenu() {
|
|
52
|
+
menuOpen = false;
|
|
53
|
+
if (menu) menu.classList.add("hidden");
|
|
54
|
+
if (chip) {
|
|
55
|
+
chip.classList.remove("open");
|
|
56
|
+
chip.setAttribute("aria-expanded", "false");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function positionMenu() {
|
|
61
|
+
if (!chip || !menu || !menuOpen) return;
|
|
62
|
+
var rect = chip.getBoundingClientRect();
|
|
63
|
+
var left = Math.min(rect.left, window.innerWidth - menu.offsetWidth - 8);
|
|
64
|
+
var top = rect.bottom + 6;
|
|
65
|
+
if (top + menu.offsetHeight > window.innerHeight - 8) top = rect.top - menu.offsetHeight - 6;
|
|
66
|
+
menu.style.left = Math.max(8, left) + "px";
|
|
67
|
+
menu.style.top = Math.max(8, top) + "px";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function branchLabel(project, parent) {
|
|
71
|
+
return project.isWorktree ? (project.branch || projectName(project)) : defaultBranch(parent);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function appendProjectRow(container, project, parent, isCurrent) {
|
|
75
|
+
var row = document.createElement("div");
|
|
76
|
+
row.className = "branch-chip-row" + (isCurrent ? " current" : "");
|
|
77
|
+
var inaccessible = project.isWorktree && project.worktreeAccessible === false;
|
|
78
|
+
var main = document.createElement("button");
|
|
79
|
+
main.className = "branch-chip-row-main";
|
|
80
|
+
main.disabled = inaccessible;
|
|
81
|
+
if (inaccessible) main.title = "Outside project path";
|
|
82
|
+
main.innerHTML = iconHtml("git-branch") + '<span class="branch-chip-row-label">' +
|
|
83
|
+
escapeHtml(branchLabel(project, parent)) + "</span>";
|
|
84
|
+
if (project.isProcessing) {
|
|
85
|
+
var dot = document.createElement("span");
|
|
86
|
+
dot.className = "branch-chip-processing";
|
|
87
|
+
main.appendChild(dot);
|
|
88
|
+
}
|
|
89
|
+
if (project.pendingPermissions > 0) {
|
|
90
|
+
var badge = document.createElement("span");
|
|
91
|
+
badge.className = "branch-chip-pending";
|
|
92
|
+
badge.textContent = project.pendingPermissions > 99 ? "99+" : String(project.pendingPermissions);
|
|
93
|
+
main.appendChild(badge);
|
|
94
|
+
}
|
|
95
|
+
if (isCurrent) main.insertAdjacentHTML("beforeend", iconHtml("check"));
|
|
96
|
+
if (!inaccessible) {
|
|
97
|
+
main.addEventListener("click", function () { closeMenu(); switchProject(project.slug); });
|
|
98
|
+
}
|
|
99
|
+
row.appendChild(main);
|
|
100
|
+
|
|
101
|
+
if (project.isWorktree && (!store.get('permissions') || store.get('permissions').deleteProject !== false)) {
|
|
102
|
+
var remove = document.createElement("button");
|
|
103
|
+
remove.className = "branch-chip-row-action";
|
|
104
|
+
remove.title = "Remove worktree";
|
|
105
|
+
remove.innerHTML = iconHtml("trash-2");
|
|
106
|
+
remove.addEventListener("click", function (event) {
|
|
107
|
+
event.stopPropagation();
|
|
108
|
+
closeMenu();
|
|
109
|
+
confirmRemoveProject(project.slug, projectName(project));
|
|
110
|
+
});
|
|
111
|
+
row.appendChild(remove);
|
|
112
|
+
}
|
|
113
|
+
container.appendChild(row);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function renderMenu(family, current) {
|
|
117
|
+
menu.innerHTML = "";
|
|
118
|
+
if (family.parent) appendProjectRow(menu, family.parent, family.parent, current.slug === family.parent.slug);
|
|
119
|
+
for (var i = 0; i < family.worktrees.length; i++) {
|
|
120
|
+
appendProjectRow(menu, family.worktrees[i], family.parent || family.worktrees[i], current.slug === family.worktrees[i].slug);
|
|
121
|
+
}
|
|
122
|
+
if (family.parent) {
|
|
123
|
+
var footer = document.createElement("button");
|
|
124
|
+
footer.className = "branch-chip-new";
|
|
125
|
+
footer.innerHTML = iconHtml("plus") + " <span>New worktree...</span>";
|
|
126
|
+
footer.addEventListener("click", function () {
|
|
127
|
+
closeMenu();
|
|
128
|
+
showWorktreeModal(family.parent.slug, projectName(family.parent));
|
|
129
|
+
});
|
|
130
|
+
menu.appendChild(footer);
|
|
131
|
+
}
|
|
132
|
+
refreshIcons();
|
|
133
|
+
positionMenu();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function renderBranchSwitcher() {
|
|
137
|
+
if (!chip) return;
|
|
138
|
+
var projects = store.get('projects') || [];
|
|
139
|
+
var current = currentProject(projects);
|
|
140
|
+
var family = current ? familyOf(projects, current.slug) : { parent: null, worktrees: [] };
|
|
141
|
+
// The chip is the project's worktree management surface, so it shows even
|
|
142
|
+
// with zero worktrees (the menu then offers the default branch and "New
|
|
143
|
+
// worktree"). Hidden where there is no project context to manage, and for
|
|
144
|
+
// non-git projects (optimistically visible until /api/branches answers).
|
|
145
|
+
var gateSlug = family.parent ? family.parent.slug : (current ? current.slug : null);
|
|
146
|
+
var hidden = !current || store.get('dmMode') || store.get('mateProjectSlug') ||
|
|
147
|
+
store.get('homeHubVisible') || (gateSlug && gitRepoBySlug[gateSlug] === false);
|
|
148
|
+
chip.classList.toggle("hidden", hidden);
|
|
149
|
+
if (hidden) { closeMenu(); return; }
|
|
150
|
+
if (family.parent) requestDefaultBranch(family.parent);
|
|
151
|
+
label.textContent = branchLabel(current, family.parent || current);
|
|
152
|
+
if (menuOpen) renderMenu(family, current);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function initBranchSwitcher() {
|
|
156
|
+
chip = document.getElementById("branch-chip");
|
|
157
|
+
label = document.getElementById("branch-chip-label");
|
|
158
|
+
menu = document.getElementById("branch-chip-menu");
|
|
159
|
+
if (!chip || !label || !menu) return;
|
|
160
|
+
chip.addEventListener("click", function (event) {
|
|
161
|
+
event.stopPropagation();
|
|
162
|
+
menuOpen = !menuOpen;
|
|
163
|
+
chip.classList.toggle("open", menuOpen);
|
|
164
|
+
chip.setAttribute("aria-expanded", menuOpen ? "true" : "false");
|
|
165
|
+
menu.classList.toggle("hidden", !menuOpen);
|
|
166
|
+
renderBranchSwitcher();
|
|
167
|
+
});
|
|
168
|
+
document.addEventListener("click", function (event) {
|
|
169
|
+
if (menuOpen && !menu.contains(event.target) && !chip.contains(event.target)) closeMenu();
|
|
170
|
+
});
|
|
171
|
+
window.addEventListener("resize", positionMenu);
|
|
172
|
+
store.subscribe(function (state, prev) {
|
|
173
|
+
if (state.projects !== prev.projects || state.currentSlug !== prev.currentSlug ||
|
|
174
|
+
state.dmMode !== prev.dmMode || state.mateProjectSlug !== prev.mateProjectSlug ||
|
|
175
|
+
state.homeHubVisible !== prev.homeHubVisible) renderBranchSwitcher();
|
|
176
|
+
});
|
|
177
|
+
renderBranchSwitcher();
|
|
178
|
+
}
|
|
@@ -16,6 +16,5 @@ export function getSessionListEl() { return ref("session-list"); }
|
|
|
16
16
|
|
|
17
17
|
export function getStatusDot() {
|
|
18
18
|
return document.querySelector("#icon-strip-projects .icon-strip-item.active .icon-strip-status") ||
|
|
19
|
-
document.querySelector("#icon-strip-projects .icon-strip-wt-item.active .icon-strip-status") ||
|
|
20
19
|
document.querySelector("#icon-strip-users .icon-strip-mate.active .icon-strip-status");
|
|
21
20
|
}
|
|
@@ -19,6 +19,7 @@ import { getCachedProjects, switchProject } from './app-projects.js';
|
|
|
19
19
|
import { openDm } from './app-dm.js';
|
|
20
20
|
import { mateAvatarUrl } from './avatar.js';
|
|
21
21
|
import { parseEmojis } from './markdown.js';
|
|
22
|
+
import { switcherProjectName } from './worktree-family.js';
|
|
22
23
|
|
|
23
24
|
var _projectMru = []; // project slugs, most recent first
|
|
24
25
|
var _mateMru = []; // mate ids (mate_XXX), most recent first
|
|
@@ -263,7 +264,7 @@ function buildProjectEntries() {
|
|
|
263
264
|
var unreachable = p.isWorktree && p.worktreeAccessible === false;
|
|
264
265
|
return {
|
|
265
266
|
key: p.slug,
|
|
266
|
-
title:
|
|
267
|
+
title: switcherProjectName(projects, p),
|
|
267
268
|
icon: p.icon || null,
|
|
268
269
|
fallbackIcon: 'folder',
|
|
269
270
|
isCurrent: p.slug === store.get('currentSlug'),
|
|
@@ -38,6 +38,7 @@ import { showHomeHub } from './app-home-hub.js';
|
|
|
38
38
|
import { openTerminal } from './terminal.js';
|
|
39
39
|
import { requestKnowledgeList } from './mate-knowledge.js';
|
|
40
40
|
import { loadRootDirectory } from './filebrowser.js';
|
|
41
|
+
import { aggregateFamily, familyOf, parentProjects } from './worktree-family.js';
|
|
41
42
|
|
|
42
43
|
// --- Mobile state ---
|
|
43
44
|
var mobileChatSheetOpen = false;
|
|
@@ -147,10 +148,22 @@ function closeMobileSheet() {
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
function renderSheetProjects(listEl) {
|
|
150
|
-
|
|
151
|
+
var projects = getCachedProjectList();
|
|
152
|
+
var parents = parentProjects(projects);
|
|
153
|
+
var activeFamily = familyOf(projects, getCachedCurrentSlug());
|
|
154
|
+
var activeSlug = activeFamily.parent ? activeFamily.parent.slug : getCachedCurrentSlug();
|
|
155
|
+
|
|
156
|
+
for (var i = 0; i < parents.length; i++) {
|
|
151
157
|
(function (p) {
|
|
158
|
+
var family = familyOf(projects, p.slug);
|
|
159
|
+
var displayProject = aggregateFamily(p, family.worktrees);
|
|
160
|
+
var hasWorktreeUnread = family.worktrees.some(function (worktree) { return worktree.unread > 0; });
|
|
161
|
+
var hasWorktreePending = family.worktrees.some(function (worktree) { return worktree.pendingPermissions > 0; });
|
|
152
162
|
var el = document.createElement("button");
|
|
153
|
-
el.className = "mobile-project-item" + (p.slug ===
|
|
163
|
+
el.className = "mobile-project-item" + (p.slug === activeSlug ? " active" : "");
|
|
164
|
+
if ((displayProject.pendingPermissions > 0 && p.slug !== activeSlug) || hasWorktreePending) {
|
|
165
|
+
el.classList.add("has-pending-perm");
|
|
166
|
+
}
|
|
154
167
|
|
|
155
168
|
var abbrev = document.createElement("span");
|
|
156
169
|
abbrev.className = "mobile-project-abbrev";
|
|
@@ -167,16 +180,16 @@ function renderSheetProjects(listEl) {
|
|
|
167
180
|
name.textContent = p.name;
|
|
168
181
|
el.appendChild(name);
|
|
169
182
|
|
|
170
|
-
if (
|
|
183
|
+
if (displayProject.isProcessing) {
|
|
171
184
|
var dot = document.createElement("span");
|
|
172
185
|
dot.className = "mobile-project-processing";
|
|
173
186
|
el.appendChild(dot);
|
|
174
187
|
}
|
|
175
188
|
|
|
176
|
-
if (
|
|
189
|
+
if (displayProject.unread > 0 && (p.slug !== activeSlug || hasWorktreeUnread)) {
|
|
177
190
|
var mBadge = document.createElement("span");
|
|
178
191
|
mBadge.className = "mobile-project-unread";
|
|
179
|
-
mBadge.textContent =
|
|
192
|
+
mBadge.textContent = displayProject.unread > 99 ? "99+" : String(displayProject.unread);
|
|
180
193
|
el.appendChild(mBadge);
|
|
181
194
|
}
|
|
182
195
|
|
|
@@ -186,7 +199,7 @@ function renderSheetProjects(listEl) {
|
|
|
186
199
|
});
|
|
187
200
|
|
|
188
201
|
listEl.appendChild(el);
|
|
189
|
-
})(
|
|
202
|
+
})(parents[i]);
|
|
190
203
|
}
|
|
191
204
|
}
|
|
192
205
|
|
|
@@ -383,6 +396,14 @@ function createMobileSessionItem(s) {
|
|
|
383
396
|
var el = document.createElement("button");
|
|
384
397
|
el.className = "mobile-session-item" + (s.active ? " active" : "");
|
|
385
398
|
|
|
399
|
+
// Vendor badge (legacy sessions without a vendor are Claude)
|
|
400
|
+
var mVendor = s.vendor || "claude";
|
|
401
|
+
var vendorIcon = document.createElement("img");
|
|
402
|
+
vendorIcon.className = "mobile-session-vendor-icon";
|
|
403
|
+
vendorIcon.src = VENDOR_AVATARS[mVendor] || VENDOR_AVATARS.claude;
|
|
404
|
+
vendorIcon.alt = "";
|
|
405
|
+
el.appendChild(vendorIcon);
|
|
406
|
+
|
|
386
407
|
// Processing dot (left side, before title)
|
|
387
408
|
if (s.isProcessing) {
|
|
388
409
|
var dot = document.createElement("span");
|