clay-server 5.0.1-beta.1 → 5.0.2-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.
- package/lib/public/css/session-folders.css +37 -8
- package/lib/public/css/session-github.css +88 -7
- package/lib/public/modules/session-date-group-header.js +3 -5
- package/lib/public/modules/session-folder-toolbar.js +6 -6
- package/lib/public/modules/session-github.js +7 -1
- package/lib/public/modules/session-presence-ui.js +1 -1
- package/lib/public/modules/sidebar-mobile.js +11 -6
- package/lib/public/modules/sidebar-session-counts.js +65 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +7 -12
- package/lib/public/modules/sidebar-session-hierarchy.js +28 -17
- package/lib/public/modules/sidebar-sessions.js +14 -7
- package/lib/public/modules/split-group-helpers.js +18 -0
- package/lib/public/modules/split-view.js +25 -1
- package/package.json +1 -1
|
@@ -30,6 +30,22 @@
|
|
|
30
30
|
background: rgba(var(--overlay-rgb), 0.05); color: var(--text-dimmer); font-size: 9.5px; font-weight: 600;
|
|
31
31
|
text-align: center; font-variant-numeric: tabular-nums;
|
|
32
32
|
}
|
|
33
|
+
.session-count-summary { position: relative; }
|
|
34
|
+
.session-count-summary::after {
|
|
35
|
+
content: attr(data-count-detail); position: absolute; right: -4px; bottom: calc(100% + 6px); z-index: 20;
|
|
36
|
+
width: max-content; max-width: min(220px, calc(100vw - 24px)); padding: 5px 7px; border: 1px solid var(--border);
|
|
37
|
+
border-radius: 7px; background: var(--bg-alt, var(--sidebar-bg)); color: var(--text); box-shadow: 0 4px 14px rgba(var(--shadow-rgb), 0.3);
|
|
38
|
+
font-size: 10.5px; font-weight: 500; line-height: 1.25; letter-spacing: 0; white-space: nowrap;
|
|
39
|
+
opacity: 0; visibility: hidden; transform: translateY(2px); pointer-events: none;
|
|
40
|
+
transition: opacity 0.12s ease, transform 0.12s ease, visibility 0.12s;
|
|
41
|
+
}
|
|
42
|
+
.session-folder-toggle:hover .session-count-summary::after,
|
|
43
|
+
.session-folder-toggle:focus-visible .session-count-summary::after,
|
|
44
|
+
.session-date-group-count:hover::after, .session-date-group-count:focus-visible::after,
|
|
45
|
+
.session-search-count:hover::after, .session-search-count:focus-visible::after {
|
|
46
|
+
opacity: 1; visibility: visible; transform: translateY(0);
|
|
47
|
+
}
|
|
48
|
+
.session-date-group-count:focus-visible, .session-search-count:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
33
49
|
|
|
34
50
|
.session-folder-body[hidden] { display: none; }
|
|
35
51
|
.session-folder-body { padding-left: 4px; border-left: 1px solid var(--border-subtle, var(--border)); margin-left: 7px; }
|
|
@@ -84,7 +100,7 @@
|
|
|
84
100
|
.session-search-count {
|
|
85
101
|
position: absolute; right: 30px; top: 50%; transform: translateY(-50%); min-width: 16px; padding: 0 5px; height: 15px; line-height: 15px;
|
|
86
102
|
border-radius: 999px; background: rgba(var(--overlay-rgb), 0.08); color: var(--text-dimmer); font-size: 9.5px; font-weight: 600; text-align: center;
|
|
87
|
-
|
|
103
|
+
cursor: help;
|
|
88
104
|
}
|
|
89
105
|
.session-search-count:empty { display: none; }
|
|
90
106
|
.session-search-clear {
|
|
@@ -162,8 +178,9 @@
|
|
|
162
178
|
.session-folder-tool svg { width: 12px; height: 12px; display: block; }
|
|
163
179
|
.session-folder-tool:hover { color: var(--text); background: rgba(var(--overlay-rgb), 0.08); }
|
|
164
180
|
|
|
165
|
-
/* The Favorites star: unfilled and revealed on hover or focus, filled
|
|
166
|
-
|
|
181
|
+
/* The Favorites star: unfilled and revealed on hover or focus, filled when
|
|
182
|
+
selected. Desktop rows reveal it in the shared trailing slot; mobile keeps
|
|
183
|
+
the selected star visible as a persistent touch affordance. */
|
|
167
184
|
.session-folder-star-btn {
|
|
168
185
|
position: absolute; right: 52px; top: 50%; transform: translateY(-50%); width: 18px; height: 18px; z-index: 1;
|
|
169
186
|
display: flex; align-items: center; justify-content: center; border: 0; border-radius: 999px; background: transparent;
|
|
@@ -172,13 +189,19 @@
|
|
|
172
189
|
.session-folder-star-btn svg, .mobile-session-star svg { width: 12px; height: 12px; display: block; }
|
|
173
190
|
.session-item:hover .session-folder-star-btn, .session-item:focus-within .session-folder-star-btn { opacity: 0.82; pointer-events: auto; }
|
|
174
191
|
.session-folder-star-btn:hover { opacity: 1; color: var(--text); background: rgba(var(--overlay-rgb), 0.08); }
|
|
175
|
-
.session-folder-star-btn.is-favorite, .mobile-session-star.is-favorite { color: var(--accent);
|
|
192
|
+
.session-folder-star-btn.is-favorite, .mobile-session-star.is-favorite { color: var(--accent); }
|
|
193
|
+
.mobile-session-star.is-favorite { opacity: 1; pointer-events: auto; }
|
|
176
194
|
.session-folder-star-btn.is-favorite svg, .mobile-session-star.is-favorite svg { fill: currentColor; }
|
|
177
195
|
.session-folder-star-btn:focus-visible, .mobile-session-star:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
178
196
|
|
|
179
|
-
/*
|
|
180
|
-
|
|
181
|
-
|
|
197
|
+
/* Age and actions share one intrinsic trailing slot. CSS grid takes the wider
|
|
198
|
+
of the two without adding their widths, so hover/focus swaps content without
|
|
199
|
+
moving the title, linked work, presence, or row controls. */
|
|
200
|
+
.session-item .session-row-trailing {
|
|
201
|
+
flex: 0 0 auto; display: grid; align-items: center; justify-items: end; min-width: 0;
|
|
202
|
+
}
|
|
203
|
+
.session-item .session-row-trailing > .session-item-age,
|
|
204
|
+
.session-item .session-row-trailing > .session-row-actions { grid-area: 1 / 1; }
|
|
182
205
|
.session-item .session-row-actions {
|
|
183
206
|
flex: 0 0 auto; display: flex; align-items: center; justify-content: flex-end; gap: 4px;
|
|
184
207
|
}
|
|
@@ -186,6 +209,9 @@
|
|
|
186
209
|
.session-item .session-row-actions > .session-close-btn {
|
|
187
210
|
position: static; flex: 0 0 18px; transform: none;
|
|
188
211
|
}
|
|
212
|
+
.session-item .session-row-trailing .session-folder-star-btn.is-favorite { opacity: 0; pointer-events: none; }
|
|
213
|
+
.session-item:hover .session-row-trailing .session-folder-star-btn.is-favorite,
|
|
214
|
+
.session-item:focus-within .session-row-trailing .session-folder-star-btn.is-favorite { opacity: 0.82; pointer-events: auto; }
|
|
189
215
|
|
|
190
216
|
/* Mobile: touch-sized targets, always-visible actions, no drag affordances. */
|
|
191
217
|
.session-folder.is-mobile .session-folder-header { min-height: 40px; margin-top: 8px; }
|
|
@@ -237,7 +263,10 @@
|
|
|
237
263
|
.session-folder-view-note { font-size: 12px; line-height: 1.45; color: var(--text-muted); }
|
|
238
264
|
|
|
239
265
|
@media (prefers-reduced-motion: reduce) {
|
|
240
|
-
.session-folder, .session-folder-chevron, .session-folder-toggle, .session-folder-tool { transition: none; }
|
|
266
|
+
.session-folder, .session-folder-chevron, .session-folder-toggle, .session-folder-tool, .session-count-summary::after { transition: none; }
|
|
267
|
+
.session-row-trailing .session-item-age,
|
|
268
|
+
.session-row-trailing .session-folder-star-btn,
|
|
269
|
+
.session-row-trailing .session-close-btn { transition: none; }
|
|
241
270
|
.session-folder-body.folder-collapse-animating { will-change: auto; }
|
|
242
271
|
}
|
|
243
272
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
.session-work-column {
|
|
3
3
|
display: flex;
|
|
4
4
|
flex-direction: column;
|
|
5
|
-
gap:
|
|
5
|
+
gap: 2px;
|
|
6
6
|
flex: 1 1 auto;
|
|
7
7
|
min-width: 0;
|
|
8
8
|
overflow: visible;
|
|
@@ -28,10 +28,71 @@
|
|
|
28
28
|
line-height: 18px;
|
|
29
29
|
}
|
|
30
30
|
.session-work-column .session-row-github-links {
|
|
31
|
-
flex-wrap:
|
|
32
|
-
gap: 3px
|
|
33
|
-
overflow:
|
|
31
|
+
flex-wrap: nowrap;
|
|
32
|
+
gap: 3px;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
line-height: 16px;
|
|
35
|
+
}
|
|
36
|
+
.session-row-github-links .session-github-link {
|
|
37
|
+
min-width: 0;
|
|
38
|
+
height: 18px;
|
|
39
|
+
box-sizing: border-box;
|
|
40
|
+
gap: 3px;
|
|
41
|
+
padding: 0 4px;
|
|
42
|
+
font-size: 10.5px;
|
|
43
|
+
}
|
|
44
|
+
.session-row-github-links .session-linear-link {
|
|
45
|
+
flex: 1 1 0;
|
|
46
|
+
min-width: 0;
|
|
47
|
+
max-width: 100%;
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
}
|
|
50
|
+
.session-row-github-links:has(.github-kind-pr, .github-kind-issue) .session-linear-link { max-width: calc(100% - 76px); }
|
|
51
|
+
.linear-work-state-compact { display: none; }
|
|
52
|
+
.session-row-github-links .linear-work-id {
|
|
53
|
+
flex: 0 1 auto;
|
|
54
|
+
min-width: 0;
|
|
55
|
+
max-width: 52%;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
text-overflow: ellipsis;
|
|
58
|
+
white-space: nowrap;
|
|
59
|
+
}
|
|
60
|
+
.session-row-github-links .linear-work-state {
|
|
61
|
+
display: block;
|
|
62
|
+
flex: 1 4 0;
|
|
63
|
+
min-width: 0;
|
|
64
|
+
max-width: 52px;
|
|
65
|
+
overflow: hidden;
|
|
66
|
+
text-overflow: ellipsis;
|
|
67
|
+
white-space: nowrap;
|
|
34
68
|
}
|
|
69
|
+
.session-row-github-links .linear-work-state-full { display: none; }
|
|
70
|
+
.session-row-github-links .linear-work-state-compact {
|
|
71
|
+
display: block;
|
|
72
|
+
overflow: hidden;
|
|
73
|
+
text-overflow: ellipsis;
|
|
74
|
+
white-space: nowrap;
|
|
75
|
+
}
|
|
76
|
+
.session-row-github-links .linear-work-title {
|
|
77
|
+
flex: 1 5 0;
|
|
78
|
+
min-width: 0;
|
|
79
|
+
max-width: 34px;
|
|
80
|
+
overflow: hidden;
|
|
81
|
+
text-overflow: ellipsis;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
}
|
|
84
|
+
.session-row-github-links .github-kind-pr,
|
|
85
|
+
.session-row-github-links .github-kind-issue { flex: 0 0 auto; }
|
|
86
|
+
.session-row-github-links .github-work-brand { display: none; }
|
|
87
|
+
.session-row-github-links .github-work-brand svg,
|
|
88
|
+
.session-row-github-links .github-work-status svg { width: 11px; height: 11px; }
|
|
89
|
+
.session-row-github-links .github-work-more { height: 18px; line-height: 16px; padding: 0 3px; font-size: 10.5px; }
|
|
90
|
+
.session-row-github-links .linear-work-brand svg { width: 11px; height: 11px; }
|
|
91
|
+
.session-row-github-links .linear-work-brand { flex: none; }
|
|
92
|
+
.session-row-github-links .session-linear-link,
|
|
93
|
+
.session-row-github-links .session-github-link,
|
|
94
|
+
.session-row-github-links .github-work-more { touch-action: manipulation; }
|
|
95
|
+
.session-row-github-links { min-height: 18px; }
|
|
35
96
|
.session-github-link {
|
|
36
97
|
display: inline-flex;
|
|
37
98
|
align-items: center;
|
|
@@ -60,10 +121,25 @@
|
|
|
60
121
|
}
|
|
61
122
|
|
|
62
123
|
.session-item:has(.session-work-column) {
|
|
124
|
+
position: relative;
|
|
125
|
+
container-type: inline-size;
|
|
63
126
|
height: auto;
|
|
64
|
-
min-height:
|
|
127
|
+
min-height: 46px;
|
|
65
128
|
contain: layout;
|
|
66
129
|
}
|
|
130
|
+
@container (max-width: 280px) {
|
|
131
|
+
.session-row-github-links .linear-work-title { display: none; }
|
|
132
|
+
}
|
|
133
|
+
.session-item:has(.session-work-column) > .session-vendor-mark,
|
|
134
|
+
.session-item:has(.session-work-column) > .session-work-column,
|
|
135
|
+
.session-item:has(.session-work-column) > .session-presence,
|
|
136
|
+
.session-item:has(.session-work-column) > .session-row-trailing { align-self: flex-start; margin-top: 5px; }
|
|
137
|
+
.session-item:has(.session-work-column) .session-row-github-links {
|
|
138
|
+
position: absolute;
|
|
139
|
+
left: 30px;
|
|
140
|
+
right: 44px;
|
|
141
|
+
bottom: 3px;
|
|
142
|
+
}
|
|
67
143
|
|
|
68
144
|
.github-kind-pr.github-state-closed .github-work-status svg { color: #f85149; }
|
|
69
145
|
@media (max-width: 600px) {
|
|
@@ -117,6 +193,11 @@
|
|
|
117
193
|
.github-work-entry-meta { color: var(--text-muted, #888); font-size: 11px; overflow-wrap: anywhere; }
|
|
118
194
|
@media (max-width: 600px) {
|
|
119
195
|
#header-github-links { max-width: 120px; }
|
|
120
|
-
.mobile-session-item:has(.session-work-column) { align-items: flex-start; }
|
|
121
|
-
.mobile-session-item .
|
|
196
|
+
.mobile-session-item:has(.session-work-column) { position: relative; container-type: inline-size; align-items: flex-start; min-height: 70px; padding-block: 8px; }
|
|
197
|
+
.mobile-session-item:has(.session-work-column) .session-work-column { position: static; }
|
|
198
|
+
.mobile-session-item:has(.session-work-column) .session-row-github-links { position: absolute; left: 44px; right: 54px; bottom: 8px; }
|
|
199
|
+
.mobile-session-item .session-row-github-links .session-github-link,
|
|
200
|
+
.mobile-session-item .session-row-github-links .github-work-more { min-height: 28px; height: 28px; }
|
|
201
|
+
.mobile-session-item .session-row-github-links .github-work-more { padding-inline: 2px; }
|
|
202
|
+
.mobile-session-item .session-row-github-links { min-height: 28px; }
|
|
122
203
|
}
|
|
@@ -3,6 +3,7 @@ import { getWs } from './ws-ref.js';
|
|
|
3
3
|
import { showConfirm } from './app-misc.js';
|
|
4
4
|
import { describeGroupDelete } from './session-delete-scope.js';
|
|
5
5
|
import { showToast } from './utils.js';
|
|
6
|
+
import { renderSidebarCount } from './sidebar-session-counts.js';
|
|
6
7
|
|
|
7
8
|
function confirmClear(label, ids, sessions) {
|
|
8
9
|
if (!ids.length) return;
|
|
@@ -22,7 +23,7 @@ function confirmClear(label, ids, sessions) {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
export function renderDateGroupHeader(dateLabel, folderLabel, ids, sessions, surface, searching) {
|
|
26
|
+
export function renderDateGroupHeader(dateLabel, folderLabel, ids, sessions, surface, searching, displayCounts) {
|
|
26
27
|
var header = document.createElement("div");
|
|
27
28
|
header.className = "session-group-header session-date-group-header" + (surface === "mobile" ? " is-mobile" : "");
|
|
28
29
|
header.setAttribute("role", "heading");
|
|
@@ -33,10 +34,7 @@ export function renderDateGroupHeader(dateLabel, folderLabel, ids, sessions, sur
|
|
|
33
34
|
label.textContent = dateLabel;
|
|
34
35
|
header.appendChild(label);
|
|
35
36
|
|
|
36
|
-
var count =
|
|
37
|
-
count.className = "session-date-group-count";
|
|
38
|
-
count.textContent = String(ids.length);
|
|
39
|
-
count.setAttribute("aria-label", ids.length + (ids.length === 1 ? " session" : " sessions"));
|
|
37
|
+
var count = renderSidebarCount("session-date-group-count", displayCounts || { roots: ids.length, workers: 0 }, true);
|
|
40
38
|
header.appendChild(count);
|
|
41
39
|
|
|
42
40
|
if ((!store.get('permissions') || store.get('permissions').sessionDelete !== false) && ids.length) {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
openListSearch, closeListSearch, clearListSearchText, setListSearchQuery, rememberListSearchCaret,
|
|
14
14
|
getListSearchQuery, getListSearchMatchIds, listSearchNeedsRender
|
|
15
15
|
} from './session-list-search.js';
|
|
16
|
+
import { renderSidebarCount } from './sidebar-session-counts.js';
|
|
16
17
|
|
|
17
18
|
// Visible one wins: the desktop list and the mobile sheet can both be in the DOM.
|
|
18
19
|
function visible(selector) {
|
|
@@ -44,7 +45,7 @@ export function folderToolbarNeedsRender(state, previous) {
|
|
|
44
45
|
return folderDraftNeedsRender(state, previous) || listSearchNeedsRender(state, previous);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function renderSearchBar(bar, surface) {
|
|
48
|
+
function renderSearchBar(bar, surface, displayCounts) {
|
|
48
49
|
var state = store.get('sessionSearch');
|
|
49
50
|
var query = getListSearchQuery();
|
|
50
51
|
var matches = getListSearchMatchIds();
|
|
@@ -92,9 +93,8 @@ function renderSearchBar(bar, surface) {
|
|
|
92
93
|
}, 0);
|
|
93
94
|
});
|
|
94
95
|
|
|
95
|
-
var count =
|
|
96
|
-
count.
|
|
97
|
-
if (query && matches !== null) count.textContent = String(matches.size);
|
|
96
|
+
var count = renderSidebarCount("session-search-count", displayCounts || { roots: matches ? matches.size : 0, workers: 0 }, true);
|
|
97
|
+
if (!query || matches === null) count.textContent = "";
|
|
98
98
|
var clear = document.createElement("button");
|
|
99
99
|
clear.type = "button";
|
|
100
100
|
clear.className = "session-search-clear";
|
|
@@ -130,7 +130,7 @@ function renderSearchBar(bar, surface) {
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
export function renderFolderToolbar(surface) {
|
|
133
|
+
export function renderFolderToolbar(surface, displayCounts) {
|
|
134
134
|
var create = store.get('sessionFolderCreate');
|
|
135
135
|
var search = store.get('sessionSearch');
|
|
136
136
|
var wrap = document.createElement("div");
|
|
@@ -140,7 +140,7 @@ export function renderFolderToolbar(surface) {
|
|
|
140
140
|
wrap.appendChild(bar);
|
|
141
141
|
|
|
142
142
|
if (search && search.open) {
|
|
143
|
-
renderSearchBar(bar, surface);
|
|
143
|
+
renderSearchBar(bar, surface, displayCounts);
|
|
144
144
|
return wrap;
|
|
145
145
|
}
|
|
146
146
|
|
|
@@ -21,6 +21,12 @@ function safeLinks(session) {
|
|
|
21
21
|
function label(link) {
|
|
22
22
|
return (link.kind === 'pr' ? 'PR #' : 'Issue #') + link.number;
|
|
23
23
|
}
|
|
24
|
+
function compactLinearState(state) {
|
|
25
|
+
var value = String(state || '');
|
|
26
|
+
if (/^in progress$/i.test(value)) return 'Active';
|
|
27
|
+
if (/^cancell?ed$/i.test(value)) return 'Canceled';
|
|
28
|
+
return value.length > 9 ? value.slice(0, 8) + '\u2026' : value;
|
|
29
|
+
}
|
|
24
30
|
function anchor(url, title, content, className) {
|
|
25
31
|
return '<a class="' + className + '" href="' + escapeHtml(url) + '" target="_blank" rel="noopener noreferrer" title="' + escapeHtml(title) + '" aria-label="' + escapeHtml(title) + '">' + content + '</a>';
|
|
26
32
|
}
|
|
@@ -43,7 +49,7 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
43
49
|
return '<span class="session-github-links' + (detailed ? ' github-work-details' : '') + (sessionRow ? ' session-row-github-links' : '') + '">' + displayed.map(function (link) {
|
|
44
50
|
if (link.provider === 'linear') {
|
|
45
51
|
var description = link.identifier + ' · ' + link.title + ' · ' + link.stateName + (link.stale ? ' · Last known status' : '');
|
|
46
|
-
return anchor(link.url, description, LINEAR_MARK + '<span class="linear-work-id">' + escapeHtml(link.identifier) + '</span><span class="linear-work-title">' + escapeHtml(link.title) + '</span><span class="linear-work-state">' + escapeHtml(link.stateName) + '</span>', 'session-github-link session-linear-link');
|
|
52
|
+
return anchor(link.url, description, LINEAR_MARK + '<span class="linear-work-id">' + escapeHtml(link.identifier) + '</span><span class="linear-work-title">' + escapeHtml(link.title) + '</span><span class="linear-work-state"><span class="linear-work-state-full">' + escapeHtml(link.stateName) + '</span><span class="linear-work-state-compact" aria-hidden="true">' + escapeHtml(compactLinearState(link.stateName)) + '</span></span>', 'session-github-link session-linear-link');
|
|
47
53
|
}
|
|
48
54
|
var state = ['open', 'closed', 'merged', 'draft'].indexOf(link.state) >= 0 ? link.state : 'unknown';
|
|
49
55
|
var icon = link.kind === 'pr' ? (state === 'merged' ? 'git-merge' : state === 'closed' ? 'git-pull-request-closed' : state === 'draft' ? 'git-pull-request-draft' : 'git-pull-request') : (state === 'closed' ? 'circle-check' : 'circle-dot');
|
|
@@ -38,7 +38,7 @@ export function renderSessionPresence(row, sessionId) {
|
|
|
38
38
|
container.appendChild(more);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
var anchor = row.querySelector(".session-item-age, .mobile-worker-generation, .mobile-session-unread, .session-row-actions, .mobile-session-star");
|
|
41
|
+
var anchor = row.querySelector(".session-row-trailing, .session-item-age, .mobile-worker-generation, .mobile-session-unread, .session-row-actions, .mobile-session-star");
|
|
42
42
|
row.insertBefore(container, anchor || null);
|
|
43
43
|
return container;
|
|
44
44
|
}
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
import { store } from './store.js';
|
|
35
35
|
import { getWs } from './ws-ref.js';
|
|
36
36
|
import { dismissOverlayPanels, closeSidebar } from './sidebar.js';
|
|
37
|
+
import { openWorkerWithDriver } from './split-view.js';
|
|
37
38
|
import { switchProject, getCachedProjects } from './app-projects.js';
|
|
38
39
|
import { showHomeHub } from './app-home-hub.js';
|
|
39
40
|
import { openHomeChat } from './home-mate-chat.js';
|
|
@@ -45,6 +46,7 @@ import {
|
|
|
45
46
|
renderMobileDriverHierarchy,
|
|
46
47
|
renderMobileOrphanHierarchy
|
|
47
48
|
} from './sidebar-session-hierarchy.js';
|
|
49
|
+
import { summarizeSidebarItems } from './sidebar-session-counts.js';
|
|
48
50
|
import { cancelSessionInlineRename, closeSessionContextMenu } from './session-context-menu.js';
|
|
49
51
|
|
|
50
52
|
// --- Mobile state ---
|
|
@@ -443,15 +445,17 @@ function createMobileSessionItem(s, options) {
|
|
|
443
445
|
openSessionContextMenu(el, s);
|
|
444
446
|
});
|
|
445
447
|
|
|
446
|
-
(function (
|
|
448
|
+
(function (session, worker) {
|
|
447
449
|
el.addEventListener("click", function () {
|
|
448
450
|
if (getWs() && store.get('connected')) {
|
|
449
|
-
|
|
451
|
+
if (!worker || !openWorkerWithDriver(session)) {
|
|
452
|
+
getWs().send(JSON.stringify({ type: "switch_session", id: session.id }));
|
|
453
|
+
}
|
|
450
454
|
}
|
|
451
455
|
if (dismissOverlayPanels) dismissOverlayPanels();
|
|
452
456
|
closeMobileSheet();
|
|
453
457
|
});
|
|
454
|
-
})(s.
|
|
458
|
+
})(s, !!itemOptions.worker);
|
|
455
459
|
|
|
456
460
|
return el;
|
|
457
461
|
}
|
|
@@ -745,15 +749,16 @@ export function renderMobileSessionsInto(container) {
|
|
|
745
749
|
var folderCtx = {
|
|
746
750
|
surface: "mobile",
|
|
747
751
|
searching: matchIds !== null,
|
|
752
|
+
matchIds: matchIds,
|
|
748
753
|
sessions: getCachedSessions(),
|
|
749
754
|
renderItem: function (it) {
|
|
750
755
|
if (it.type === "loop") return createMobileLoopGroup(it.loopId, it.children, it.groupKey);
|
|
751
|
-
if (it.type === "driver-hierarchy") return renderMobileDriverHierarchy(it.root, createMobileSessionItem, refreshMobileChatSheet);
|
|
752
|
-
if (it.type === "orphan-workers") return renderMobileOrphanHierarchy(it.workers, createMobileSessionItem, refreshMobileChatSheet);
|
|
756
|
+
if (it.type === "driver-hierarchy") return renderMobileDriverHierarchy(it.root, createMobileSessionItem, refreshMobileChatSheet, matchIds);
|
|
757
|
+
if (it.type === "orphan-workers") return renderMobileOrphanHierarchy(it.workers, createMobileSessionItem, refreshMobileChatSheet, matchIds);
|
|
753
758
|
return createMobileSessionItem(it.data);
|
|
754
759
|
},
|
|
755
760
|
};
|
|
756
|
-
container.appendChild(renderFolderToolbar("mobile"));
|
|
761
|
+
container.appendChild(renderFolderToolbar("mobile", summarizeSidebarItems(filterItemsBySearch(items, matchIds), matchIds)));
|
|
757
762
|
container.appendChild(renderFavoritesSection(layout, folderCtx));
|
|
758
763
|
renderOrdinarySections(layout, folderCtx, container);
|
|
759
764
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Display-only sidebar counts. Destructive Clear actions keep their separate
|
|
2
|
+
// authoritative session-id scope and cascade semantics.
|
|
3
|
+
|
|
4
|
+
function sessionId(session) {
|
|
5
|
+
return session && typeof session.id === "number" ? session.id : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function addSession(session, roots, workers) {
|
|
9
|
+
var id = sessionId(session);
|
|
10
|
+
if (id === null) return;
|
|
11
|
+
if (session.sessionRole === "worker") workers.add(id);
|
|
12
|
+
else roots.add(id);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function visibleWorker(worker, driver, matchIds) {
|
|
16
|
+
return matchIds === null || matchIds === undefined || matchIds.has(worker.id) || matchIds.has(driver.id);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function addItem(item, roots, workers, matchIds) {
|
|
20
|
+
if (!item) return;
|
|
21
|
+
if (item.type === "session") {
|
|
22
|
+
addSession(item.data, roots, workers);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (item.type === "driver-hierarchy") {
|
|
26
|
+
addSession(item.root && item.root.driver, roots, workers);
|
|
27
|
+
var children = item.root && item.root.workers || [];
|
|
28
|
+
for (var i = 0; i < children.length; i++) if (visibleWorker(children[i], item.root.driver, matchIds)) addSession(children[i], roots, workers);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
var sessions = item.type === "orphan-workers" ? item.workers : item.type === "split-group" ? item.members : item.type === "loop" ? item.children : [];
|
|
32
|
+
for (var j = 0; j < (sessions || []).length; j++) {
|
|
33
|
+
if (item.type === "orphan-workers" && matchIds !== null && matchIds !== undefined && !matchIds.has(sessions[j].id)) continue;
|
|
34
|
+
addSession(sessions[j], roots, workers);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function summarizeSidebarItems(items, matchIds) {
|
|
39
|
+
var roots = new Set();
|
|
40
|
+
var workers = new Set();
|
|
41
|
+
for (var i = 0; i < (items || []).length; i++) addItem(items[i], roots, workers, matchIds);
|
|
42
|
+
return { roots: roots.size, workers: workers.size };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function summarizeSidebarUnits(units, matchIds) {
|
|
46
|
+
return summarizeSidebarItems((units || []).map(function (unit) { return unit.item; }), matchIds);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function sidebarCountLabel(counts, short) {
|
|
50
|
+
var roots = counts && counts.roots || 0;
|
|
51
|
+
var workers = counts && counts.workers || 0;
|
|
52
|
+
if (short) return roots + (roots === 1 ? " Driver" : " Drivers") + " · " + workers + (workers === 1 ? " Split Worker" : " Split Workers");
|
|
53
|
+
return roots + (roots === 1 ? " Driver session" : " Driver sessions") + " and " + workers + (workers === 1 ? " Split Worker session" : " Split Worker sessions");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function renderSidebarCount(className, counts, focusable) {
|
|
57
|
+
var count = document.createElement("span");
|
|
58
|
+
count.className = className + " session-count-summary";
|
|
59
|
+
count.textContent = String(counts.roots);
|
|
60
|
+
count.dataset.countDetail = sidebarCountLabel(counts, true);
|
|
61
|
+
count.setAttribute("aria-label", sidebarCountLabel(counts, false));
|
|
62
|
+
count.title = sidebarCountLabel(counts, false);
|
|
63
|
+
if (focusable) count.tabIndex = 0;
|
|
64
|
+
return count;
|
|
65
|
+
}
|
|
@@ -22,6 +22,7 @@ import { renderNewSessionButton, renderCreateRow } from './session-create-form.j
|
|
|
22
22
|
import { restoreFolderCollapse, toggleFolderCollapse } from './session-folder-collapse.js';
|
|
23
23
|
import { groupUnitsByDate, groupRootSessionIds } from './session-date-groups.js';
|
|
24
24
|
import { renderDateGroupHeader } from './session-date-group-header.js';
|
|
25
|
+
import { summarizeSidebarUnits, sidebarCountLabel, renderSidebarCount } from './sidebar-session-counts.js';
|
|
25
26
|
|
|
26
27
|
// Mutable UI state (open menu, drag in flight, the layout each surface last
|
|
27
28
|
// rendered) lives in the store; this module only owns the functions.
|
|
@@ -269,15 +270,6 @@ function wireUnitDrop(wrapper, containerId, key) {
|
|
|
269
270
|
|
|
270
271
|
// --- Rendering ---
|
|
271
272
|
|
|
272
|
-
function countUnits(units) {
|
|
273
|
-
var total = 0;
|
|
274
|
-
for (var i = 0; i < units.length; i++) {
|
|
275
|
-
var item = units[i].item;
|
|
276
|
-
total += item.type === "driver-hierarchy" ? 1 + item.root.workers.length : 1;
|
|
277
|
-
}
|
|
278
|
-
return total;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
273
|
function renderUnit(unit, containerId, ctx) {
|
|
282
274
|
var wrapper = document.createElement("div");
|
|
283
275
|
wrapper.className = "session-folder-unit";
|
|
@@ -317,14 +309,17 @@ function renderSection(section, ctx, kind) {
|
|
|
317
309
|
toggle.className = "session-folder-toggle";
|
|
318
310
|
toggle.setAttribute("aria-expanded", String(!section.collapsed));
|
|
319
311
|
toggle.setAttribute("aria-controls", bodyId);
|
|
312
|
+
var counts = summarizeSidebarUnits(section.units, ctx.matchIds);
|
|
313
|
+
toggle.setAttribute("aria-label", (section.collapsed ? "Expand " : "Collapse ") + section.label + ". " + sidebarCountLabel(counts, false));
|
|
320
314
|
var icon = isFavorites ? "star" : (kind === "unfiled" ? "inbox" : "folder");
|
|
321
315
|
toggle.innerHTML = '<span class="session-folder-chevron">' + iconHtml("chevron-right") + "</span>" +
|
|
322
316
|
'<span class="session-folder-icon">' + iconHtml(icon) + "</span>" +
|
|
323
|
-
'<span class="session-folder-label">' + escapeHtml(section.label) + "</span>"
|
|
324
|
-
|
|
317
|
+
'<span class="session-folder-label">' + escapeHtml(section.label) + "</span>";
|
|
318
|
+
toggle.appendChild(renderSidebarCount("session-folder-count", counts, false));
|
|
325
319
|
toggle.addEventListener("click", function () {
|
|
326
320
|
if (consumeFolderClickSuppress()) return;
|
|
327
321
|
toggleFolderCollapse(root, body, toggle, surface, section.id, function (collapsed) {
|
|
322
|
+
toggle.setAttribute("aria-label", (collapsed ? "Expand " : "Collapse ") + section.label + ". " + sidebarCountLabel(counts, false));
|
|
328
323
|
sendFolderOp({ op: "set_collapsed", containerKey: section.id, collapsed: collapsed });
|
|
329
324
|
});
|
|
330
325
|
});
|
|
@@ -362,7 +357,7 @@ function renderSection(section, ctx, kind) {
|
|
|
362
357
|
if (dateGroups) {
|
|
363
358
|
for (var g = 0; g < dateGroups.length; g++) {
|
|
364
359
|
var ids = groupRootSessionIds(dateGroups[g].units);
|
|
365
|
-
body.appendChild(renderDateGroupHeader(dateGroups[g].label, section.label, ids, ctx.sessions || [], surface, !!ctx.searching));
|
|
360
|
+
body.appendChild(renderDateGroupHeader(dateGroups[g].label, section.label, ids, ctx.sessions || [], surface, !!ctx.searching, summarizeSidebarUnits(dateGroups[g].units, ctx.matchIds)));
|
|
366
361
|
for (var gu = 0; gu < dateGroups[g].units.length; gu++) body.appendChild(renderUnit(dateGroups[g].units[gu], section.id, ctx));
|
|
367
362
|
}
|
|
368
363
|
} else {
|
|
@@ -32,7 +32,15 @@ export function currentWorkerIds() {
|
|
|
32
32
|
var result = new Set();
|
|
33
33
|
var groups = store.get('splitGroups') || [];
|
|
34
34
|
for (var i = 0; i < groups.length; i++) {
|
|
35
|
-
|
|
35
|
+
var pair = groups[i].pair;
|
|
36
|
+
if (!pair) continue;
|
|
37
|
+
if (Array.isArray(pair.workerIds)) {
|
|
38
|
+
for (var j = 0; j < pair.workerIds.length; j++) {
|
|
39
|
+
if (Number.isInteger(pair.workerIds[j])) result.add(pair.workerIds[j]);
|
|
40
|
+
}
|
|
41
|
+
} else if (Number.isInteger(pair.workerId)) {
|
|
42
|
+
result.add(pair.workerId);
|
|
43
|
+
}
|
|
36
44
|
}
|
|
37
45
|
return result;
|
|
38
46
|
}
|
|
@@ -55,22 +63,23 @@ export function hierarchyItemMatches(item, matchIds) {
|
|
|
55
63
|
return true;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
function
|
|
66
|
+
function expansionKey(key) {
|
|
67
|
+
return String(store.get('currentSlug') || '') + ":" + key;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function expanded(surface, key) {
|
|
59
71
|
var state = expansionBySurface[surface];
|
|
60
|
-
|
|
61
|
-
|
|
72
|
+
var scopedKey = expansionKey(key);
|
|
73
|
+
if (state.has(scopedKey)) return state.get(scopedKey);
|
|
74
|
+
return defaultHierarchyExpanded();
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
export function defaultHierarchyExpanded(
|
|
65
|
-
var currentIds = current || new Set();
|
|
66
|
-
for (var i = 0; i < workers.length; i++) {
|
|
67
|
-
if (workers[i].active || currentIds.has(workers[i].id) || (matchIds !== null && matchIds.has(workers[i].id))) return true;
|
|
68
|
-
}
|
|
77
|
+
export function defaultHierarchyExpanded() {
|
|
69
78
|
return false;
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
function toggle(surface, key, wasExpanded, rerender) {
|
|
73
|
-
expansionBySurface[surface].set(key, !wasExpanded);
|
|
82
|
+
expansionBySurface[surface].set(expansionKey(key), !wasExpanded);
|
|
74
83
|
rerender();
|
|
75
84
|
}
|
|
76
85
|
|
|
@@ -82,7 +91,7 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
82
91
|
var wrapper = document.createElement("div");
|
|
83
92
|
wrapper.className = "session-driver-hierarchy";
|
|
84
93
|
var key = "driver:" + root.driver.id;
|
|
85
|
-
var isExpanded = expanded("desktop", key
|
|
94
|
+
var isExpanded = expanded("desktop", key);
|
|
86
95
|
var childrenId = "session-workers-" + root.driver.id;
|
|
87
96
|
var header = document.createElement("div");
|
|
88
97
|
header.className = "session-driver-header";
|
|
@@ -105,7 +114,7 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
105
114
|
event.preventDefault();
|
|
106
115
|
event.stopPropagation();
|
|
107
116
|
var nextExpanded = !isExpanded;
|
|
108
|
-
expansionBySurface.desktop.set(key, nextExpanded);
|
|
117
|
+
expansionBySurface.desktop.set(expansionKey(key), nextExpanded);
|
|
109
118
|
control.setAttribute("aria-expanded", String(nextExpanded));
|
|
110
119
|
control.setAttribute("aria-label", (nextExpanded ? "Collapse" : "Expand") + " Workers for " + (root.driver.title || "New Session"));
|
|
111
120
|
children.hidden = !nextExpanded;
|
|
@@ -127,7 +136,7 @@ export function renderDesktopDriverHierarchy(root, renderSession, rerender, matc
|
|
|
127
136
|
export function renderDesktopOrphanHierarchy(workers, renderSession, rerender, matchIds) {
|
|
128
137
|
var wrapper = document.createElement("div");
|
|
129
138
|
wrapper.className = "session-driver-hierarchy session-orphan-hierarchy";
|
|
130
|
-
var isExpanded = expanded("desktop", "orphan"
|
|
139
|
+
var isExpanded = expanded("desktop", "orphan");
|
|
131
140
|
var control = document.createElement("button");
|
|
132
141
|
control.type = "button";
|
|
133
142
|
control.className = "session-orphan-toggle";
|
|
@@ -152,11 +161,11 @@ export function renderDesktopOrphanHierarchy(workers, renderSession, rerender, m
|
|
|
152
161
|
return wrapper;
|
|
153
162
|
}
|
|
154
163
|
|
|
155
|
-
export function renderMobileDriverHierarchy(root, renderSession, rerender) {
|
|
164
|
+
export function renderMobileDriverHierarchy(root, renderSession, rerender, matchIds) {
|
|
156
165
|
var wrapper = document.createElement("div");
|
|
157
166
|
wrapper.className = "mobile-driver-hierarchy";
|
|
158
167
|
var key = "driver:" + root.driver.id;
|
|
159
|
-
var isExpanded = expanded("mobile", key
|
|
168
|
+
var isExpanded = expanded("mobile", key);
|
|
160
169
|
var childrenId = "mobile-session-workers-" + root.driver.id;
|
|
161
170
|
var header = document.createElement("div");
|
|
162
171
|
header.className = "mobile-driver-header";
|
|
@@ -180,16 +189,17 @@ export function renderMobileDriverHierarchy(root, renderSession, rerender) {
|
|
|
180
189
|
children.hidden = !isExpanded;
|
|
181
190
|
var current = currentWorkerIds();
|
|
182
191
|
for (var i = 0; i < root.workers.length; i++) {
|
|
192
|
+
if (!visibleWorker(root.workers[i], root.driver, matchIds === undefined ? null : matchIds)) continue;
|
|
183
193
|
children.appendChild(renderSession(root.workers[i], { worker: true, current: current.has(root.workers[i].id) }));
|
|
184
194
|
}
|
|
185
195
|
wrapper.appendChild(children);
|
|
186
196
|
return wrapper;
|
|
187
197
|
}
|
|
188
198
|
|
|
189
|
-
export function renderMobileOrphanHierarchy(workers, renderSession, rerender) {
|
|
199
|
+
export function renderMobileOrphanHierarchy(workers, renderSession, rerender, matchIds) {
|
|
190
200
|
var wrapper = document.createElement("div");
|
|
191
201
|
wrapper.className = "mobile-driver-hierarchy mobile-orphan-hierarchy";
|
|
192
|
-
var isExpanded = expanded("mobile", "orphan"
|
|
202
|
+
var isExpanded = expanded("mobile", "orphan");
|
|
193
203
|
var control = document.createElement("button");
|
|
194
204
|
control.type = "button";
|
|
195
205
|
control.className = "mobile-orphan-toggle";
|
|
@@ -207,6 +217,7 @@ export function renderMobileOrphanHierarchy(workers, renderSession, rerender) {
|
|
|
207
217
|
children.hidden = !isExpanded;
|
|
208
218
|
var current = currentWorkerIds();
|
|
209
219
|
for (var i = 0; i < workers.length; i++) {
|
|
220
|
+
if (matchIds !== null && matchIds !== undefined && !matchIds.has(workers[i].id)) continue;
|
|
210
221
|
children.appendChild(renderSession(workers[i], { worker: true, current: current.has(workers[i].id) }));
|
|
211
222
|
}
|
|
212
223
|
wrapper.appendChild(children);
|
|
@@ -13,7 +13,7 @@ import { showConfirm } from './app-misc.js';
|
|
|
13
13
|
import { getUpcomingSchedules } from './scheduler.js';
|
|
14
14
|
import { refreshMobileChatSheet } from './sidebar-mobile.js';
|
|
15
15
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
16
|
-
import { openGroup, separateGroup } from './split-view.js';
|
|
16
|
+
import { openGroup, openWorkerWithDriver, separateGroup } from './split-view.js';
|
|
17
17
|
import { groupedSessionIds } from './split-group-helpers.js';
|
|
18
18
|
import { resolvePreferredVendor } from './default-vendor.js';
|
|
19
19
|
import {
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
renderDesktopDriverHierarchy,
|
|
32
32
|
renderDesktopOrphanHierarchy
|
|
33
33
|
} from './sidebar-session-hierarchy.js';
|
|
34
|
+
import { summarizeSidebarItems } from './sidebar-session-counts.js';
|
|
34
35
|
import {
|
|
35
36
|
cancelSessionInlineRename,
|
|
36
37
|
closeSessionContextMenu,
|
|
@@ -841,12 +842,14 @@ function renderSessionItem(s, options) {
|
|
|
841
842
|
el.appendChild(workerLabel);
|
|
842
843
|
}
|
|
843
844
|
|
|
845
|
+
var trailing = document.createElement("span");
|
|
846
|
+
trailing.className = "session-row-trailing";
|
|
844
847
|
var ageText = relativeTime(s.lastActivity || 0);
|
|
845
848
|
if (ageText) {
|
|
846
849
|
var age = document.createElement("span");
|
|
847
850
|
age.className = "session-item-age";
|
|
848
851
|
age.textContent = ageText;
|
|
849
|
-
|
|
852
|
+
trailing.appendChild(age);
|
|
850
853
|
}
|
|
851
854
|
|
|
852
855
|
// Right-click / long-press: context menu
|
|
@@ -874,13 +877,16 @@ function renderSessionItem(s, options) {
|
|
|
874
877
|
if (isFavoriteSession(s)) el.classList.add("is-favorite");
|
|
875
878
|
}
|
|
876
879
|
appendSessionCloseButton(actions, s);
|
|
877
|
-
if (actions.childNodes.length)
|
|
880
|
+
if (actions.childNodes.length) trailing.appendChild(actions);
|
|
881
|
+
if (trailing.childNodes.length) el.appendChild(trailing);
|
|
878
882
|
|
|
879
|
-
el.addEventListener("click", (function (
|
|
883
|
+
el.addEventListener("click", (function (session, worker) {
|
|
880
884
|
return function () {
|
|
881
885
|
if (getWs() && store.get('connected')) {
|
|
882
886
|
var pendingQuery = getListSearchQuery() || "";
|
|
883
|
-
|
|
887
|
+
if (!worker || !openWorkerWithDriver(session)) {
|
|
888
|
+
getWs().send(JSON.stringify({ type: "switch_session", id: session.id }));
|
|
889
|
+
}
|
|
884
890
|
dismissOverlayPanels();
|
|
885
891
|
closeSidebar();
|
|
886
892
|
if (pendingQuery) {
|
|
@@ -888,7 +894,7 @@ function renderSessionItem(s, options) {
|
|
|
888
894
|
}
|
|
889
895
|
}
|
|
890
896
|
};
|
|
891
|
-
})(s.
|
|
897
|
+
})(s, !!itemOptions.worker));
|
|
892
898
|
|
|
893
899
|
// Presence avatars (multi-user)
|
|
894
900
|
renderSessionPresence(el, String(s.id));
|
|
@@ -1071,6 +1077,7 @@ export function renderSessionList(sessions) {
|
|
|
1071
1077
|
var folderCtx = {
|
|
1072
1078
|
surface: "desktop",
|
|
1073
1079
|
searching: matchIds !== null,
|
|
1080
|
+
matchIds: matchIds,
|
|
1074
1081
|
sessions: cachedSessions,
|
|
1075
1082
|
renderItem: function (it) {
|
|
1076
1083
|
if (it.type === "loop") return renderLoopGroup(it.loopId, it.children, it.groupKey) || document.createElement("div");
|
|
@@ -1085,7 +1092,7 @@ export function renderSessionList(sessions) {
|
|
|
1085
1092
|
// custom folders, then Unfiled. Each header has its own New session button.
|
|
1086
1093
|
var topActions = document.createElement("div");
|
|
1087
1094
|
topActions.className = "session-list-sticky-top";
|
|
1088
|
-
topActions.appendChild(renderFolderToolbar("desktop"));
|
|
1095
|
+
topActions.appendChild(renderFolderToolbar("desktop", summarizeSidebarItems(visibleItems, matchIds)));
|
|
1089
1096
|
getSessionListEl().appendChild(topActions);
|
|
1090
1097
|
|
|
1091
1098
|
var regularContainer = document.createElement("div");
|
|
@@ -72,6 +72,24 @@ export function isConfiguredWorker(groups, sessionId) {
|
|
|
72
72
|
return false;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
export function resolveWorkerSplitTarget(worker, sessions, groups) {
|
|
76
|
+
if (!worker || worker.sessionRole !== "worker" || !worker.parentAvailable || !Number.isInteger(worker.parentSessionId)) return null;
|
|
77
|
+
var list = sessions || [];
|
|
78
|
+
var driver = null;
|
|
79
|
+
for (var i = 0; i < list.length; i++) {
|
|
80
|
+
if (list[i].id === worker.parentSessionId && list[i].sessionRole !== "worker") driver = list[i];
|
|
81
|
+
}
|
|
82
|
+
if (!driver) return null;
|
|
83
|
+
var groupList = groups || [];
|
|
84
|
+
for (var j = 0; j < groupList.length; j++) {
|
|
85
|
+
var roles = splitGroupRoles(groupList[j]);
|
|
86
|
+
if (roles && roles.driverId === driver.id && roles.workerIds.indexOf(worker.id) !== -1) {
|
|
87
|
+
return { driver: driver, worker: worker, group: groupList[j] };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return { driver: driver, worker: worker, group: null };
|
|
91
|
+
}
|
|
92
|
+
|
|
75
93
|
export function splitWorkerCloseRequest(group, sessions, projectSlug, workerId, requestId) {
|
|
76
94
|
var roles = splitGroupRoles(group);
|
|
77
95
|
if (!roles || roles.version !== 2 || roles.workerIds.indexOf(workerId) === -1) return null;
|
|
@@ -6,7 +6,7 @@ import { getWs } from './ws-ref.js';
|
|
|
6
6
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
7
7
|
import { refreshIcons } from './icons.js';
|
|
8
8
|
import { detachTuiView } from './session-tui-view.js';
|
|
9
|
-
import { groupedSessionIds, findSplitGroup, isConfiguredWorker, splitGroupRoles, splitGroupMemberIds, splitGroupActiveAnchor, splitWorkerCloseRequest } from './split-group-helpers.js';
|
|
9
|
+
import { groupedSessionIds, findSplitGroup, isConfiguredWorker, resolveWorkerSplitTarget, splitGroupRoles, splitGroupMemberIds, splitGroupActiveAnchor, splitWorkerCloseRequest } from './split-group-helpers.js';
|
|
10
10
|
import { syncPairChrome } from './split-pair-ui.js';
|
|
11
11
|
import { presentMarkdownEdit } from './filebrowser.js';
|
|
12
12
|
import { autoStartLoginIfNeeded } from './vendor-login.js';
|
|
@@ -250,6 +250,28 @@ export function openGroup(group) {
|
|
|
250
250
|
return true;
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
export function openWorkerWithDriver(worker) {
|
|
254
|
+
var target = resolveWorkerSplitTarget(worker, getCachedSessions(), store.get('splitGroups'));
|
|
255
|
+
if (!target) return false;
|
|
256
|
+
if (target.group) return openGroup(target.group);
|
|
257
|
+
detachTuiView();
|
|
258
|
+
store.set({
|
|
259
|
+
splitPanes: {
|
|
260
|
+
groupId: null,
|
|
261
|
+
panes: [paneForSession(target.driver.id), paneForSession(target.worker.id)],
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
var activeId = store.get('activeSessionId');
|
|
265
|
+
if (activeId !== target.driver.id) {
|
|
266
|
+
var ws = getWs();
|
|
267
|
+
if (isCurrentProjectSessionReady(store.snap(), ws)) {
|
|
268
|
+
ws.send(JSON.stringify({ type: "switch_session", id: target.driver.id }));
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
dismissSplitOverlays();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
253
275
|
// Reload survival: when the restored active session turns out to be a split
|
|
254
276
|
// group member, reopen that group. Safe to call often -- no-ops unless a
|
|
255
277
|
// group member is active natively with no split open.
|
|
@@ -366,6 +388,8 @@ function handleSidebarSessionClick(event) {
|
|
|
366
388
|
if (!sessionId) return;
|
|
367
389
|
event.preventDefault();
|
|
368
390
|
event.stopImmediatePropagation();
|
|
391
|
+
var session = sessionById(sessionId);
|
|
392
|
+
if (session && session.sessionRole === "worker" && openWorkerWithDriver(session)) return;
|
|
369
393
|
switchNativeSession(sessionId);
|
|
370
394
|
}
|
|
371
395
|
|
package/package.json
CHANGED