clay-server 4.0.0-beta.2 → 4.0.0-beta.21
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/background-task-timing.js +105 -0
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +14 -1
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-vendor-login.js +20 -3
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +178 -6
- package/lib/public/app.js +58 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +163 -28
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +52 -0
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +34 -1
- package/lib/public/index.html +45 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +109 -4
- package/lib/public/modules/app-home-hub.js +19 -7
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +84 -7
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +46 -4
- package/lib/public/modules/app-projects.js +13 -8
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/background-tasks-ui.js +139 -16
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +21 -1
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -12
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +132 -46
- package/lib/sdk-message-processor.js +60 -14
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +124 -18
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +39 -0
- package/lib/sessions.js +61 -11
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +2 -2
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +81 -4
- package/lib/yoke/adapters/codex.js +63 -15
- package/lib/yoke/codex-background-tasks.js +8 -2
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +3 -3
|
@@ -16,6 +16,12 @@ import { VENDOR_AVATARS, VENDOR_NAMES, VENDOR_ORDER, VENDOR_HOMEPAGES, isExperim
|
|
|
16
16
|
import { openGroup, separateGroup } from './split-view.js';
|
|
17
17
|
import { groupedSessionIds } from './split-group-helpers.js';
|
|
18
18
|
import { openPairDialog } from './split-pair-ui.js';
|
|
19
|
+
import {
|
|
20
|
+
hierarchyItemMatches,
|
|
21
|
+
prepareSidebarHierarchy,
|
|
22
|
+
renderDesktopDriverHierarchy,
|
|
23
|
+
renderDesktopOrphanHierarchy
|
|
24
|
+
} from './sidebar-session-hierarchy.js';
|
|
19
25
|
|
|
20
26
|
|
|
21
27
|
// --- Session state ---
|
|
@@ -49,8 +55,8 @@ function sendSessionBookmark(sessionId, bookmarked) {
|
|
|
49
55
|
}
|
|
50
56
|
|
|
51
57
|
function compareSessionListItems(a, b) {
|
|
52
|
-
var aData = a && a.type === "session" ? a.data : a;
|
|
53
|
-
var bData = b && b.type === "session" ? b.data : b;
|
|
58
|
+
var aData = a && (a.type === "session" || a.type === "driver-hierarchy") ? a.data : a;
|
|
59
|
+
var bData = b && (b.type === "session" || b.type === "driver-hierarchy") ? b.data : b;
|
|
54
60
|
var aBookmarked = !!(aData && aData.bookmarked);
|
|
55
61
|
var bBookmarked = !!(bData && bData.bookmarked);
|
|
56
62
|
if (aBookmarked !== bBookmarked) return aBookmarked ? -1 : 1;
|
|
@@ -236,6 +242,14 @@ function collectItemSessionIds(item) {
|
|
|
236
242
|
if (item.type === "split-group" && Array.isArray(item.members)) {
|
|
237
243
|
return item.members.map(function (session) { return session.id; });
|
|
238
244
|
}
|
|
245
|
+
if (item.type === "driver-hierarchy" && item.root) {
|
|
246
|
+
var hierarchyIds = [item.root.driver.id];
|
|
247
|
+
for (var j = 0; j < item.root.workers.length; j++) hierarchyIds.push(item.root.workers[j].id);
|
|
248
|
+
return hierarchyIds;
|
|
249
|
+
}
|
|
250
|
+
if (item.type === "orphan-workers" && Array.isArray(item.workers)) {
|
|
251
|
+
return item.workers.map(function (session) { return session.id; });
|
|
252
|
+
}
|
|
239
253
|
return [];
|
|
240
254
|
}
|
|
241
255
|
|
|
@@ -683,7 +697,7 @@ function showSessionCtxMenu(anchorBtn, sessionId, title, cliSid, sessionData) {
|
|
|
683
697
|
|
|
684
698
|
// Session visibility toggle (only the session owner can change)
|
|
685
699
|
if (store.get('isMultiUserMode') && sessionData && sessionData.ownerId && sessionData.ownerId === store.get('myUserId')) {
|
|
686
|
-
var currentVis = (sessionData && sessionData.sessionVisibility) || "
|
|
700
|
+
var currentVis = (sessionData && sessionData.sessionVisibility) || "private";
|
|
687
701
|
var isPrivate = currentVis === "private";
|
|
688
702
|
var visItem = document.createElement("button");
|
|
689
703
|
visItem.className = "session-ctx-item";
|
|
@@ -1246,10 +1260,12 @@ function renderLoopRun(parentGk, startedAtKey, sessions, isRalph) {
|
|
|
1246
1260
|
|
|
1247
1261
|
// --- Session item rendering ---
|
|
1248
1262
|
|
|
1249
|
-
function renderSessionItem(s) {
|
|
1263
|
+
function renderSessionItem(s, options) {
|
|
1264
|
+
var itemOptions = options || {};
|
|
1250
1265
|
var el = document.createElement("div");
|
|
1251
1266
|
var isMatch = searchMatchIds !== null && searchMatchIds.has(s.id);
|
|
1252
1267
|
el.className = "session-item" + (s.active ? " active" : "") + (isMatch ? " search-match" : "");
|
|
1268
|
+
if (itemOptions.worker) el.classList.add("session-worker-item");
|
|
1253
1269
|
el.dataset.sessionId = s.id;
|
|
1254
1270
|
|
|
1255
1271
|
// Keep agent identity separate from the title so each row has a stable
|
|
@@ -1274,13 +1290,20 @@ function renderSessionItem(s) {
|
|
|
1274
1290
|
if (s.loop && s.loop.source === "debate") {
|
|
1275
1291
|
textHtml += '<span class="session-debate-icon" title="Debate">' + iconHtml("mic") + '</span>';
|
|
1276
1292
|
}
|
|
1277
|
-
if (store.get('isMultiUserMode') && s.sessionVisibility === "
|
|
1278
|
-
textHtml += '<span class="session-
|
|
1293
|
+
if (store.get('isMultiUserMode') && s.sessionVisibility === "shared") {
|
|
1294
|
+
textHtml += '<span class="session-shared-icon" title="Shared session" aria-label="Shared session">' + iconHtml("users") + '</span>';
|
|
1279
1295
|
}
|
|
1280
1296
|
textHtml += highlightMatch(s.title || "New Session", searchQuery);
|
|
1281
1297
|
textSpan.innerHTML = textHtml;
|
|
1282
1298
|
el.appendChild(textSpan);
|
|
1283
1299
|
|
|
1300
|
+
if (itemOptions.worker) {
|
|
1301
|
+
var workerLabel = document.createElement("span");
|
|
1302
|
+
workerLabel.className = "session-worker-generation" + (itemOptions.current ? " current" : "");
|
|
1303
|
+
workerLabel.textContent = itemOptions.current ? "Current" : (s.workerGeneration ? "Gen " + s.workerGeneration : "Worker");
|
|
1304
|
+
el.appendChild(workerLabel);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1284
1307
|
var ageText = relativeTime(s.lastActivity || 0);
|
|
1285
1308
|
if (ageText) {
|
|
1286
1309
|
var age = document.createElement("span");
|
|
@@ -1325,7 +1348,7 @@ function renderSessionItem(s) {
|
|
|
1325
1348
|
|
|
1326
1349
|
// Presence avatars (multi-user)
|
|
1327
1350
|
renderPresenceAvatars(el, String(s.id));
|
|
1328
|
-
setupSessionDragHandlers(el, s);
|
|
1351
|
+
if (!itemOptions.worker) setupSessionDragHandlers(el, s);
|
|
1329
1352
|
|
|
1330
1353
|
return el;
|
|
1331
1354
|
}
|
|
@@ -1376,8 +1399,29 @@ function renderSplitGroupItem(group, members) {
|
|
|
1376
1399
|
|
|
1377
1400
|
// --- Main session list ---
|
|
1378
1401
|
|
|
1402
|
+
// Fingerprint of the last completed render: the whole session payload plus the
|
|
1403
|
+
// two store values this render also reads. Nothing is cherry-picked, so the
|
|
1404
|
+
// guard can only skip a frame that is byte-identical to what is already on
|
|
1405
|
+
// screen. A re-render triggered by other state (sessions === null) always
|
|
1406
|
+
// runs and resets the fingerprint afterwards.
|
|
1407
|
+
var lastRenderFingerprint = null;
|
|
1408
|
+
|
|
1409
|
+
function sessionRenderFingerprint(sessions) {
|
|
1410
|
+
try {
|
|
1411
|
+
return JSON.stringify([sessions, store.get('splitGroups') || [], store.get('splitPanes') || null]);
|
|
1412
|
+
} catch (e) {
|
|
1413
|
+
return null;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1379
1417
|
export function renderSessionList(sessions) {
|
|
1380
|
-
if (sessions)
|
|
1418
|
+
if (sessions) {
|
|
1419
|
+
// A reconnect and a streaming turn both re-send this list unchanged;
|
|
1420
|
+
// rebuilding the sidebar for an identical frame is what made it flicker.
|
|
1421
|
+
var incoming = sessionRenderFingerprint(sessions);
|
|
1422
|
+
if (incoming !== null && incoming === lastRenderFingerprint) return;
|
|
1423
|
+
cachedSessions = sessions;
|
|
1424
|
+
}
|
|
1381
1425
|
|
|
1382
1426
|
// If mobile chat sheet is open, refresh it
|
|
1383
1427
|
if (refreshMobileChatSheet) refreshMobileChatSheet();
|
|
@@ -1388,12 +1432,17 @@ export function renderSessionList(sessions) {
|
|
|
1388
1432
|
// Group by loopId + date so all runs of the same task on the same day are merged
|
|
1389
1433
|
var splitGroups = store.get('splitGroups') || [];
|
|
1390
1434
|
var groupedIds = groupedSessionIds(splitGroups);
|
|
1435
|
+
var hierarchy = prepareSidebarHierarchy(cachedSessions);
|
|
1436
|
+
var hierarchyByDriver = hierarchy.byDriver;
|
|
1437
|
+
var hierarchySessionIds = hierarchy.sessionIds;
|
|
1438
|
+
hierarchySessionIds.forEach(function (id) { groupedIds.delete(id); });
|
|
1391
1439
|
var sessionsById = new Map();
|
|
1392
1440
|
for (var si = 0; si < cachedSessions.length; si++) sessionsById.set(cachedSessions[si].id, cachedSessions[si]);
|
|
1393
1441
|
var loopGroups = {}; // groupKey -> [sessions]
|
|
1394
1442
|
var normalSessions = [];
|
|
1395
1443
|
for (var i = 0; i < cachedSessions.length; i++) {
|
|
1396
1444
|
var s = cachedSessions[i];
|
|
1445
|
+
if (s.sessionRole === "worker") continue;
|
|
1397
1446
|
if (groupedIds.has(s.id)) continue;
|
|
1398
1447
|
if (s.loop && s.loop.loopId && s.loop.role === "crafting" && s.loop.source !== "ralph" && s.loop.source !== "debate") {
|
|
1399
1448
|
// Task crafting sessions live in the scheduler calendar, not the main list (except debate)
|
|
@@ -1412,13 +1461,21 @@ export function renderSessionList(sessions) {
|
|
|
1412
1461
|
// Build virtual items: normal sessions + one entry per loop group (using latest child's lastActivity)
|
|
1413
1462
|
var items = [];
|
|
1414
1463
|
for (var j = 0; j < normalSessions.length; j++) {
|
|
1415
|
-
|
|
1464
|
+
var driverRoot = hierarchyByDriver.get(normalSessions[j].id);
|
|
1465
|
+
if (driverRoot) items.push({ type: "driver-hierarchy", root: driverRoot, data: driverRoot.driver, lastActivity: driverRoot.lastActivity });
|
|
1466
|
+
else items.push({ type: "session", data: normalSessions[j], lastActivity: normalSessions[j].lastActivity || 0 });
|
|
1467
|
+
}
|
|
1468
|
+
if (hierarchy.orphans.length) {
|
|
1469
|
+
var orphanActivity = 0;
|
|
1470
|
+
for (var oi = 0; oi < hierarchy.orphans.length; oi++) orphanActivity = Math.max(orphanActivity, hierarchy.orphans[oi].lastActivity || 0);
|
|
1471
|
+
items.push({ type: "orphan-workers", workers: hierarchy.orphans, lastActivity: orphanActivity });
|
|
1416
1472
|
}
|
|
1417
1473
|
for (var sg = 0; sg < splitGroups.length; sg++) {
|
|
1418
1474
|
var splitGroup = splitGroups[sg];
|
|
1419
1475
|
var leftMember = sessionsById.get(splitGroup.members[0]);
|
|
1420
1476
|
var rightMember = sessionsById.get(splitGroup.members[1]);
|
|
1421
1477
|
if (!leftMember || !rightMember) continue;
|
|
1478
|
+
if (hierarchySessionIds.has(leftMember.id) || hierarchySessionIds.has(rightMember.id)) continue;
|
|
1422
1479
|
items.push({
|
|
1423
1480
|
type: "split-group",
|
|
1424
1481
|
group: splitGroup,
|
|
@@ -1449,9 +1506,10 @@ export function renderSessionList(sessions) {
|
|
|
1449
1506
|
if (item.type === "session" && item.data && !isSessionVisibleBySearch(item.data.id)) {
|
|
1450
1507
|
continue;
|
|
1451
1508
|
}
|
|
1509
|
+
if ((item.type === "driver-hierarchy" || item.type === "orphan-workers") && !hierarchyItemMatches(item, searchMatchIds)) continue;
|
|
1452
1510
|
if (item.type === "split-group" && searchMatchIds !== null &&
|
|
1453
1511
|
!searchMatchIds.has(item.members[0].id) && !searchMatchIds.has(item.members[1].id)) continue;
|
|
1454
|
-
if (item.type === "session" && item.data && item.data.bookmarked) {
|
|
1512
|
+
if ((item.type === "session" || item.type === "driver-hierarchy") && item.data && item.data.bookmarked) {
|
|
1455
1513
|
bookmarkedItems.push(item);
|
|
1456
1514
|
} else {
|
|
1457
1515
|
regularItems.push(item);
|
|
@@ -1468,7 +1526,8 @@ export function renderSessionList(sessions) {
|
|
|
1468
1526
|
favoritesContainer.appendChild(emptyHint);
|
|
1469
1527
|
}
|
|
1470
1528
|
for (var bi = 0; bi < bookmarkedItems.length; bi++) {
|
|
1471
|
-
favoritesContainer.appendChild(
|
|
1529
|
+
if (bookmarkedItems[bi].type === "driver-hierarchy") favoritesContainer.appendChild(renderDesktopDriverHierarchy(bookmarkedItems[bi].root, renderSessionItem, function () { renderSessionList(null); }, searchMatchIds));
|
|
1530
|
+
else favoritesContainer.appendChild(renderSessionItem(bookmarkedItems[bi].data));
|
|
1472
1531
|
}
|
|
1473
1532
|
|
|
1474
1533
|
var divider = document.createElement("div");
|
|
@@ -1508,6 +1567,10 @@ export function renderSessionList(sessions) {
|
|
|
1508
1567
|
}
|
|
1509
1568
|
} else if (item.type === "split-group") {
|
|
1510
1569
|
regularContainer.appendChild(renderSplitGroupItem(item.group, item.members));
|
|
1570
|
+
} else if (item.type === "driver-hierarchy") {
|
|
1571
|
+
regularContainer.appendChild(renderDesktopDriverHierarchy(item.root, renderSessionItem, function () { renderSessionList(null); }, searchMatchIds));
|
|
1572
|
+
} else if (item.type === "orphan-workers") {
|
|
1573
|
+
regularContainer.appendChild(renderDesktopOrphanHierarchy(item.workers, renderSessionItem, function () { renderSessionList(null); }, searchMatchIds));
|
|
1511
1574
|
} else {
|
|
1512
1575
|
regularContainer.appendChild(renderSessionItem(item.data));
|
|
1513
1576
|
}
|
|
@@ -1516,6 +1579,9 @@ export function renderSessionList(sessions) {
|
|
|
1516
1579
|
refreshIcons();
|
|
1517
1580
|
if (updatePageTitle) updatePageTitle();
|
|
1518
1581
|
syncHeaderSearchUi();
|
|
1582
|
+
// Recorded from the sessions actually rendered, so a null-argument re-render
|
|
1583
|
+
// driven by other state refreshes the fingerprint rather than stranding it.
|
|
1584
|
+
lastRenderFingerprint = sessionRenderFingerprint(cachedSessions);
|
|
1519
1585
|
}
|
|
1520
1586
|
|
|
1521
1587
|
// --- Search results ---
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
2
2
|
import { closeScheduler } from './scheduler.js';
|
|
3
3
|
import { initSidebarSessions } from './sidebar-sessions.js';
|
|
4
4
|
import { focusFileTree } from './filebrowser.js';
|
|
@@ -23,7 +23,7 @@ export function syncResizeHandles() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
export function dismissOverlayPanels() {
|
|
26
|
-
|
|
26
|
+
closeNotesBrowser();
|
|
27
27
|
closeScheduler();
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -119,7 +119,10 @@ export function initSidebar(_ctx) {
|
|
|
119
119
|
|
|
120
120
|
// --- Panel switch (sessions / files / projects) ---
|
|
121
121
|
var fileBrowserBtn = ctx.$("file-browser-btn");
|
|
122
|
-
|
|
122
|
+
// Git has no tool-palette tile. The repository placard's More control is
|
|
123
|
+
// the entry point into the same panel, with the same lifecycle.
|
|
124
|
+
var gitPlacard = ctx.$("git-placard");
|
|
125
|
+
var gitPlacardMore = ctx.$("git-placard-more");
|
|
123
126
|
var projectsPanel = ctx.$("sidebar-panel-projects");
|
|
124
127
|
var sessionsPanel = ctx.$("sidebar-panel-sessions");
|
|
125
128
|
var filesPanel = ctx.$("sidebar-panel-files");
|
|
@@ -216,14 +219,23 @@ export function initSidebar(_ctx) {
|
|
|
216
219
|
});
|
|
217
220
|
});
|
|
218
221
|
}
|
|
219
|
-
if (
|
|
220
|
-
|
|
222
|
+
if (gitPlacardMore) {
|
|
223
|
+
gitPlacardMore.addEventListener("click", function () {
|
|
221
224
|
if (gitPanel && !gitPanel.classList.contains("hidden")) {
|
|
222
225
|
hideGitPanel(function () { showSessionsPanel(); });
|
|
223
226
|
}
|
|
224
227
|
else showGitPanel();
|
|
225
228
|
});
|
|
226
229
|
}
|
|
230
|
+
if (gitPlacard && gitPlacardMore) {
|
|
231
|
+
// Clicking the block itself is a pointer convenience only; it forwards to
|
|
232
|
+
// the More control so every listener bound there runs exactly once, and
|
|
233
|
+
// the keyboard path stays the single focusable button.
|
|
234
|
+
gitPlacard.addEventListener("click", function (event) {
|
|
235
|
+
if (event.target.closest("#git-placard-more")) return;
|
|
236
|
+
gitPlacardMore.click();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
227
239
|
if (gitPanelClose) {
|
|
228
240
|
gitPanelClose.addEventListener("click", function () {
|
|
229
241
|
hideGitPanel(function () { showSessionsPanel(); });
|
|
@@ -16,3 +16,11 @@ export function findSplitGroup(groups, memberIds) {
|
|
|
16
16
|
}
|
|
17
17
|
return null;
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
export function isConfiguredWorker(groups, sessionId) {
|
|
21
|
+
var list = groups || [];
|
|
22
|
+
for (var i = 0; i < list.length; i++) {
|
|
23
|
+
if (list[i].pair && list[i].pair.workerId === sessionId) return true;
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
@@ -7,7 +7,7 @@ import { iconHtml, refreshIcons } from './icons.js';
|
|
|
7
7
|
import { detachTuiView } from './session-tui-view.js';
|
|
8
8
|
import { formatTokens } from './app-panels.js';
|
|
9
9
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
10
|
-
import { groupedSessionIds, findSplitGroup } from './split-group-helpers.js';
|
|
10
|
+
import { groupedSessionIds, findSplitGroup, isConfiguredWorker } from './split-group-helpers.js';
|
|
11
11
|
import { showConfirm } from './app-misc.js';
|
|
12
12
|
import { syncPairChrome } from './split-pair-ui.js';
|
|
13
13
|
import { presentMarkdownEdit } from './filebrowser.js';
|
|
@@ -159,7 +159,10 @@ export function syncPaneTitles() {
|
|
|
159
159
|
function updatePaneFullAccessButton(button, session) {
|
|
160
160
|
if (!button) return;
|
|
161
161
|
var mode = session && (session.runtimeMode || session.mode || "gui");
|
|
162
|
-
var
|
|
162
|
+
var worker = !!session && isConfiguredWorker(store.get('splitGroups'), session.id);
|
|
163
|
+
// Permission policy belongs to the Driver. A configured Worker inherits it
|
|
164
|
+
// server-side and must not present a second, misleading control.
|
|
165
|
+
var visible = !!session && !worker && !store.get('skipPermsEnabled') && mode === "gui";
|
|
163
166
|
var enabled = visible && session.permissionMode === "bypassPermissions";
|
|
164
167
|
button.classList.toggle("hidden", !visible);
|
|
165
168
|
button.classList.toggle("active", enabled);
|
|
@@ -58,6 +58,11 @@ function nodeToMarkdown(node) {
|
|
|
58
58
|
return "\n" + inner;
|
|
59
59
|
case "P": return "\n" + inner;
|
|
60
60
|
case "A": return node.getAttribute("href") || inner;
|
|
61
|
+
case "BUTTON":
|
|
62
|
+
if (node.classList.contains("clayos-log-link") && node.dataset && node.dataset.logRef) {
|
|
63
|
+
return "`" + node.dataset.logRef + "`";
|
|
64
|
+
}
|
|
65
|
+
return inner;
|
|
61
66
|
case "SPAN":
|
|
62
67
|
if (node.classList.contains("sn-check")) {
|
|
63
68
|
return node.classList.contains("checked") ? "- [x]" : "- [ ]";
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
// Sticky Notes browser: the bounded right workbench window over the board.
|
|
2
|
+
//
|
|
3
|
+
// This is the index/viewer surface, not the floating cards. The cards stay on
|
|
4
|
+
// the canvas over the conversation; this pane lists them, splits them into Open
|
|
5
|
+
// and Closed, and is where a note is completed or brought back.
|
|
6
|
+
//
|
|
7
|
+
// Completion here is Close, never delete. A closed note leaves the active
|
|
8
|
+
// canvas and moves to the Closed tab, where it stays readable and reversible.
|
|
9
|
+
// There is deliberately no destructive control on this surface at all.
|
|
10
|
+
|
|
11
|
+
import { store } from './store.js';
|
|
12
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
13
|
+
import { renderMiniMarkdown, getTitle } from './sticky-note-markdown.js';
|
|
14
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
15
|
+
import { send } from './sticky-notes-shared.js';
|
|
16
|
+
import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
|
|
17
|
+
|
|
18
|
+
var panel = null;
|
|
19
|
+
var gridEl = null;
|
|
20
|
+
var tabsEl = null;
|
|
21
|
+
|
|
22
|
+
// Registered by app.js so this module never imports the other right-pane tools
|
|
23
|
+
// and cannot form an import cycle with them.
|
|
24
|
+
var exclusiveClosers = [];
|
|
25
|
+
|
|
26
|
+
export function registerExclusiveClosers(fns) {
|
|
27
|
+
exclusiveClosers = (fns || []).filter(function (fn) { return typeof fn === "function"; });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function closeOtherRightTools() {
|
|
31
|
+
for (var i = 0; i < exclusiveClosers.length; i++) {
|
|
32
|
+
try { exclusiveClosers[i](); } catch (e) { /* a missing tool must not block opening */ }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isClosed(data) {
|
|
37
|
+
if (!data) return false;
|
|
38
|
+
if (data.state === "closed") return true;
|
|
39
|
+
if (data.state === "open") return false;
|
|
40
|
+
// Legacy notes predate the state field; `hidden` was the old spelling.
|
|
41
|
+
return data.hidden === true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function openNotes(all) {
|
|
45
|
+
return (all || []).filter(function (data) { return !isClosed(data); });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function closedNotes(all) {
|
|
49
|
+
return (all || []).filter(isClosed);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function currentTab() {
|
|
53
|
+
return store.get('notesBrowserTab') === "closed" ? "closed" : "open";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// --- Panel ---------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
function ensurePanel() {
|
|
59
|
+
if (panel) return;
|
|
60
|
+
var panels = document.getElementById("main-panels");
|
|
61
|
+
if (!panels) return;
|
|
62
|
+
panel = document.createElement("section");
|
|
63
|
+
panel.id = "notes-browser";
|
|
64
|
+
panel.className = "notes-browser hidden";
|
|
65
|
+
panel.setAttribute("aria-label", "Sticky Notes");
|
|
66
|
+
panel.innerHTML =
|
|
67
|
+
'<header class="notes-browser-topbar">' +
|
|
68
|
+
'<span class="notes-browser-title">' + iconHtml("sticky-note") + 'Sticky Notes</span>' +
|
|
69
|
+
'<span class="notes-browser-subtitle">Open items on the board, and what has been closed</span>' +
|
|
70
|
+
'<div class="notes-browser-window-actions" aria-label="Sticky Notes window controls">' +
|
|
71
|
+
'<button id="notes-browser-wide" class="notes-browser-icon-btn" type="button" title="Widen panel" aria-label="Widen Sticky Notes panel" aria-pressed="false">' + iconHtml("chevrons-left-right") + '</button>' +
|
|
72
|
+
'<button id="notes-browser-fullscreen" class="notes-browser-icon-btn" type="button" title="Toggle fullscreen" aria-label="Toggle Sticky Notes fullscreen" aria-pressed="false">' + iconHtml("maximize-2") + '</button>' +
|
|
73
|
+
'<button id="notes-browser-close" class="notes-browser-icon-btn" type="button" title="Close Sticky Notes" aria-label="Close Sticky Notes">' + iconHtml("x") + '</button>' +
|
|
74
|
+
'</div>' +
|
|
75
|
+
'</header>' +
|
|
76
|
+
'<div id="notes-browser-tabs" class="notes-browser-tabs" role="tablist" aria-label="Sticky Notes state"></div>' +
|
|
77
|
+
'<div id="notes-browser-grid" class="notes-browser-grid" role="list"></div>';
|
|
78
|
+
panels.appendChild(panel);
|
|
79
|
+
|
|
80
|
+
gridEl = panel.querySelector("#notes-browser-grid");
|
|
81
|
+
tabsEl = panel.querySelector("#notes-browser-tabs");
|
|
82
|
+
|
|
83
|
+
panel.querySelector("#notes-browser-close").addEventListener("click", closeNotesBrowser);
|
|
84
|
+
panel.querySelector("#notes-browser-wide").addEventListener("click", function () {
|
|
85
|
+
applyWindowState(!store.get('notesBrowserWide'), store.get('notesBrowserFullscreen'));
|
|
86
|
+
});
|
|
87
|
+
panel.querySelector("#notes-browser-fullscreen").addEventListener("click", function () {
|
|
88
|
+
applyWindowState(store.get('notesBrowserWide'), !store.get('notesBrowserFullscreen'));
|
|
89
|
+
});
|
|
90
|
+
refreshIcons();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Width and fullscreen are transient view state, so they live in the store.
|
|
94
|
+
// `panel-fullscreen` is the shared class the document viewer, terminal, and
|
|
95
|
+
// Logs already use, so the geometry is the established one.
|
|
96
|
+
function applyWindowState(wide, fullscreen) {
|
|
97
|
+
store.set({ notesBrowserWide: !!wide, notesBrowserFullscreen: !!fullscreen });
|
|
98
|
+
if (!panel) return;
|
|
99
|
+
panel.classList.toggle("notes-browser-wide", !!wide && !fullscreen);
|
|
100
|
+
panel.classList.toggle("panel-fullscreen", !!fullscreen);
|
|
101
|
+
var wideBtn = panel.querySelector("#notes-browser-wide");
|
|
102
|
+
var fullBtn = panel.querySelector("#notes-browser-fullscreen");
|
|
103
|
+
if (wideBtn) {
|
|
104
|
+
wideBtn.setAttribute("aria-pressed", wide ? "true" : "false");
|
|
105
|
+
wideBtn.disabled = !!fullscreen;
|
|
106
|
+
}
|
|
107
|
+
if (fullBtn) fullBtn.setAttribute("aria-pressed", fullscreen ? "true" : "false");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// --- Tabs ----------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
function renderTabs(openCount, closedCount) {
|
|
113
|
+
if (!tabsEl) return;
|
|
114
|
+
var active = currentTab();
|
|
115
|
+
tabsEl.innerHTML = "";
|
|
116
|
+
var specs = [
|
|
117
|
+
{ key: "open", label: "Open", count: openCount },
|
|
118
|
+
{ key: "closed", label: "Closed", count: closedCount },
|
|
119
|
+
];
|
|
120
|
+
for (var i = 0; i < specs.length; i++) {
|
|
121
|
+
(function (spec) {
|
|
122
|
+
var tab = document.createElement("button");
|
|
123
|
+
tab.type = "button";
|
|
124
|
+
tab.className = "notes-browser-tab" + (spec.key === active ? " active" : "");
|
|
125
|
+
tab.setAttribute("role", "tab");
|
|
126
|
+
tab.setAttribute("aria-selected", spec.key === active ? "true" : "false");
|
|
127
|
+
tab.tabIndex = spec.key === active ? 0 : -1;
|
|
128
|
+
tab.dataset.tab = spec.key;
|
|
129
|
+
tab.innerHTML = '<span>' + spec.label + '</span><span class="notes-browser-tab-count">' + spec.count + '</span>';
|
|
130
|
+
tab.addEventListener("click", function () { selectTab(spec.key); });
|
|
131
|
+
tab.addEventListener("keydown", function (e) {
|
|
132
|
+
if (e.key !== "ArrowLeft" && e.key !== "ArrowRight") return;
|
|
133
|
+
e.preventDefault();
|
|
134
|
+
selectTab(spec.key === "open" ? "closed" : "open");
|
|
135
|
+
var next = tabsEl.querySelector('.notes-browser-tab.active');
|
|
136
|
+
if (next) next.focus();
|
|
137
|
+
});
|
|
138
|
+
tabsEl.appendChild(tab);
|
|
139
|
+
})(specs[i]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function selectTab(key) {
|
|
144
|
+
store.set({ notesBrowserTab: key === "closed" ? "closed" : "open" });
|
|
145
|
+
renderNotesBrowser();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// --- Cards ---------------------------------------------------------------
|
|
149
|
+
|
|
150
|
+
function closedMeta(data) {
|
|
151
|
+
var parts = [];
|
|
152
|
+
if (data.closedAt) {
|
|
153
|
+
var when = new Date(data.closedAt);
|
|
154
|
+
if (!isNaN(when.getTime())) parts.push("Closed " + when.toLocaleDateString());
|
|
155
|
+
} else {
|
|
156
|
+
parts.push("Closed");
|
|
157
|
+
}
|
|
158
|
+
var by = data.closedBy;
|
|
159
|
+
if (by && by.type === "user" && by.displayName) parts.push("by " + by.displayName);
|
|
160
|
+
else if (by && by.type === "session") parts.push("by an agent session");
|
|
161
|
+
return parts.join(" · ");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function buildCard(data) {
|
|
165
|
+
var card = document.createElement("article");
|
|
166
|
+
var closed = isClosed(data);
|
|
167
|
+
card.className = "notes-browser-card" + (closed ? " closed" : "");
|
|
168
|
+
card.setAttribute("role", "listitem");
|
|
169
|
+
card.dataset.color = data.color || "purple";
|
|
170
|
+
card.dataset.noteId = data.id;
|
|
171
|
+
|
|
172
|
+
var header = document.createElement("div");
|
|
173
|
+
header.className = "notes-browser-card-header";
|
|
174
|
+
|
|
175
|
+
var title = document.createElement("h3");
|
|
176
|
+
title.className = "notes-browser-card-title";
|
|
177
|
+
title.textContent = getTitle(data.text) || "Untitled";
|
|
178
|
+
header.appendChild(title);
|
|
179
|
+
|
|
180
|
+
// Exactly one lifecycle action per card, and it is never destructive.
|
|
181
|
+
var action = document.createElement("button");
|
|
182
|
+
action.type = "button";
|
|
183
|
+
action.className = "notes-browser-card-action";
|
|
184
|
+
if (closed) {
|
|
185
|
+
action.innerHTML = iconHtml("rotate-ccw") + "<span>Reopen</span>";
|
|
186
|
+
action.title = "Reopen this note and return it to the board";
|
|
187
|
+
action.setAttribute("aria-label", "Reopen note: " + title.textContent);
|
|
188
|
+
action.addEventListener("click", function (e) {
|
|
189
|
+
e.stopPropagation();
|
|
190
|
+
send({ type: "note_reopen", id: data.id });
|
|
191
|
+
});
|
|
192
|
+
} else {
|
|
193
|
+
action.innerHTML = iconHtml("check") + "<span>Close</span>";
|
|
194
|
+
action.title = "Close this note; it stays on record and can be reopened";
|
|
195
|
+
action.setAttribute("aria-label", "Close note: " + title.textContent);
|
|
196
|
+
action.addEventListener("click", function (e) {
|
|
197
|
+
e.stopPropagation();
|
|
198
|
+
send({ type: "note_close", id: data.id });
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
header.appendChild(action);
|
|
202
|
+
card.appendChild(header);
|
|
203
|
+
|
|
204
|
+
var body = document.createElement("div");
|
|
205
|
+
body.className = "notes-browser-card-body";
|
|
206
|
+
var bodyLines = (data.text || "").split("\n").slice(1).join("\n").trim();
|
|
207
|
+
if (bodyLines) {
|
|
208
|
+
body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
|
|
209
|
+
enhanceClayLogLinks(body);
|
|
210
|
+
}
|
|
211
|
+
card.appendChild(body);
|
|
212
|
+
|
|
213
|
+
if (closed) {
|
|
214
|
+
var meta = document.createElement("p");
|
|
215
|
+
meta.className = "notes-browser-card-meta";
|
|
216
|
+
meta.textContent = closedMeta(data);
|
|
217
|
+
card.appendChild(meta);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
var strip = document.createElement("div");
|
|
221
|
+
strip.className = "notes-browser-card-color";
|
|
222
|
+
card.appendChild(strip);
|
|
223
|
+
|
|
224
|
+
// An open note can be jumped to on the canvas. A closed note has no card on
|
|
225
|
+
// the canvas, so reopening is the only way back and the body is read-only.
|
|
226
|
+
if (!closed) {
|
|
227
|
+
card.tabIndex = 0;
|
|
228
|
+
card.addEventListener("click", function () { focusNoteOnCanvas(data.id); });
|
|
229
|
+
card.addEventListener("keydown", function (e) {
|
|
230
|
+
if (e.key !== "Enter" && e.key !== " ") return;
|
|
231
|
+
e.preventDefault();
|
|
232
|
+
focusNoteOnCanvas(data.id);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
return card;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function emptyState(tab) {
|
|
239
|
+
var empty = document.createElement("div");
|
|
240
|
+
empty.className = "notes-browser-empty";
|
|
241
|
+
if (tab === "closed") {
|
|
242
|
+
empty.innerHTML = iconHtml("check") +
|
|
243
|
+
"<p>Nothing closed yet</p>" +
|
|
244
|
+
"<p class=\"notes-browser-empty-sub\">Closing a note takes it off the board without deleting it. Anything you close shows up here and can be reopened.</p>";
|
|
245
|
+
} else {
|
|
246
|
+
empty.innerHTML = iconHtml("sticky-note") +
|
|
247
|
+
"<p>Keep what matters across sessions</p>" +
|
|
248
|
+
"<p class=\"notes-browser-empty-sub\">Use sticky notes for checklists, goals, handoffs, and durable project knowledge. You and every Clay agent can find them in future sessions.</p>";
|
|
249
|
+
}
|
|
250
|
+
return empty;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function renderNotesBrowser() {
|
|
254
|
+
if (!gridEl) return;
|
|
255
|
+
var all = listNoteData();
|
|
256
|
+
var open = openNotes(all);
|
|
257
|
+
var closed = closedNotes(all);
|
|
258
|
+
renderTabs(open.length, closed.length);
|
|
259
|
+
|
|
260
|
+
var tab = currentTab();
|
|
261
|
+
var shown = tab === "closed" ? closed : open;
|
|
262
|
+
// Newest first. The id is timestamp-prefixed, so it orders by creation.
|
|
263
|
+
shown = shown.slice().sort(function (a, b) {
|
|
264
|
+
var left = String(a.id || "");
|
|
265
|
+
var right = String(b.id || "");
|
|
266
|
+
return left < right ? 1 : left > right ? -1 : 0;
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
gridEl.innerHTML = "";
|
|
270
|
+
if (shown.length === 0) {
|
|
271
|
+
gridEl.appendChild(emptyState(tab));
|
|
272
|
+
refreshIcons();
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
for (var i = 0; i < shown.length; i++) gridEl.appendChild(buildCard(shown[i]));
|
|
276
|
+
refreshIcons();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --- Lifecycle -----------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
// The conversation, composer, and title bar stay mounted and visible: the
|
|
282
|
+
// browser opens beside them in the single right workbench slot.
|
|
283
|
+
export function openNotesBrowser() {
|
|
284
|
+
ensurePanel();
|
|
285
|
+
if (!panel) return;
|
|
286
|
+
closeOtherRightTools();
|
|
287
|
+
// The floating canvas would otherwise sit on top of the pane.
|
|
288
|
+
hideNotes();
|
|
289
|
+
panel.classList.remove("hidden");
|
|
290
|
+
applyWindowState(store.get('notesBrowserWide'), false);
|
|
291
|
+
store.set({ notesBrowserOpen: true });
|
|
292
|
+
var sidebarBtn = document.getElementById("sticky-notes-sidebar-btn");
|
|
293
|
+
if (sidebarBtn) sidebarBtn.classList.add("active");
|
|
294
|
+
renderNotesBrowser();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function closeNotesBrowser() {
|
|
298
|
+
if (!store.get('notesBrowserOpen')) return;
|
|
299
|
+
if (panel) panel.classList.add("hidden");
|
|
300
|
+
// Fullscreen is always dropped on close so the next open is a bounded pane.
|
|
301
|
+
applyWindowState(store.get('notesBrowserWide'), false);
|
|
302
|
+
store.set({ notesBrowserOpen: false });
|
|
303
|
+
var sidebarBtn = document.getElementById("sticky-notes-sidebar-btn");
|
|
304
|
+
if (sidebarBtn) sidebarBtn.classList.remove("active");
|
|
305
|
+
// The canvas comes back so the open notes are reachable again.
|
|
306
|
+
showNotes();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export function isNotesBrowserOpen() {
|
|
310
|
+
return !!store.get('notesBrowserOpen');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function initNotesBrowser() {
|
|
314
|
+
document.addEventListener("keydown", function (e) {
|
|
315
|
+
if (e.key !== "Escape") return;
|
|
316
|
+
if (!store.get('notesBrowserOpen')) return;
|
|
317
|
+
closeNotesBrowser();
|
|
318
|
+
});
|
|
319
|
+
store.subscribe(function (state, previous) {
|
|
320
|
+
if (state.currentSlug === previous.currentSlug) return;
|
|
321
|
+
// A different project starts from a closed, bounded, Open-tab pane.
|
|
322
|
+
closeNotesBrowser();
|
|
323
|
+
applyWindowState(false, false);
|
|
324
|
+
store.set({ notesBrowserTab: "open" });
|
|
325
|
+
});
|
|
326
|
+
}
|