clay-server 4.0.0-beta.8 → 4.0.0
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/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 +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- 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-request-access.js +66 -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-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -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 +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- 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 +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- 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 +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- 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-surface-boot.js +7 -3
- 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-activation.js +45 -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-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- 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/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- 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 +31 -123
- 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 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -19
- 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 +137 -22
- 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 +41 -0
- package/lib/sessions.js +57 -10
- 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 +6 -6
- 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 +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
// Project Logs — the project's ledger of agent-authored decisions and work.
|
|
2
|
+
//
|
|
3
|
+
// A bounded right-side workbench pane, like the document viewer and terminal.
|
|
4
|
+
// Inside it there is one navigation stack rather than a master/detail split:
|
|
5
|
+
// the list fills the pane, opening an entry replaces it with the full-pane
|
|
6
|
+
// record, and Back returns to the list with the query, filter, and scroll
|
|
7
|
+
// position intact.
|
|
8
|
+
//
|
|
9
|
+
// People read and comment, and project owners may delete. Canonical entries
|
|
10
|
+
// are created and revised only by this project's agent sessions through the
|
|
11
|
+
// clay-logs MCP tools, so this module has no create or edit affordance.
|
|
12
|
+
|
|
13
|
+
import { store } from './store.js';
|
|
14
|
+
import { getWs } from './ws-ref.js';
|
|
15
|
+
import { refreshIcons } from './icons.js';
|
|
16
|
+
import { showToast } from './utils.js';
|
|
17
|
+
import { closeScheduler } from './scheduler.js';
|
|
18
|
+
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
19
|
+
import { closeFileViewer } from './filebrowser.js';
|
|
20
|
+
import { closeTerminal } from './terminal.js';
|
|
21
|
+
import { renderFilter, renderContextFilter, renderList, renderDetail } from './project-logs-render.js';
|
|
22
|
+
|
|
23
|
+
var panel = null;
|
|
24
|
+
var listEl = null;
|
|
25
|
+
var detailEl = null;
|
|
26
|
+
var searchEl = null;
|
|
27
|
+
var filterEl = null;
|
|
28
|
+
var contextFilterEl = null;
|
|
29
|
+
var backBtn = null;
|
|
30
|
+
var deleteDialog = null;
|
|
31
|
+
var pendingDeleteEntry = null;
|
|
32
|
+
var searchTimer = null;
|
|
33
|
+
var requestCounter = 0;
|
|
34
|
+
|
|
35
|
+
function syncUnreadBadge() {
|
|
36
|
+
var button = document.getElementById("project-logs-btn");
|
|
37
|
+
if (!button) return;
|
|
38
|
+
var unread = store.get('projectLogsUnread') || 0;
|
|
39
|
+
button.classList.toggle("project-logs-unread", unread > 0);
|
|
40
|
+
var badge = document.getElementById("project-logs-count");
|
|
41
|
+
if (!badge) return;
|
|
42
|
+
badge.textContent = unread > 0 ? String(unread) : "";
|
|
43
|
+
badge.classList.toggle("hidden", unread < 1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function acknowledgeUpdates() {
|
|
47
|
+
if (!store.get('projectLogsUnread')) return;
|
|
48
|
+
store.set({ projectLogsUnread: 0 });
|
|
49
|
+
syncUnreadBadge();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function noteCanonicalUpdate(msg) {
|
|
53
|
+
if (!msg || !msg.ref || !msg.revision) return false;
|
|
54
|
+
var seen = store.get('projectLogsSeenRevisions') || {};
|
|
55
|
+
if (seen[msg.ref] >= msg.revision) return false;
|
|
56
|
+
var next = Object.assign({}, seen);
|
|
57
|
+
next[msg.ref] = msg.revision;
|
|
58
|
+
store.set({
|
|
59
|
+
projectLogsSeenRevisions: next,
|
|
60
|
+
projectLogsUnread: store.get('projectLogsOpen') ? 0 : (store.get('projectLogsUnread') || 0) + 1,
|
|
61
|
+
});
|
|
62
|
+
syncUnreadBadge();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function nextRequestId(prefix) {
|
|
67
|
+
requestCounter += 1;
|
|
68
|
+
return prefix + "-" + Date.now() + "-" + requestCounter;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function send(message) {
|
|
72
|
+
var ws = getWs();
|
|
73
|
+
if (!ws || ws.readyState !== 1) return false;
|
|
74
|
+
ws.send(JSON.stringify(message));
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function ensurePanel() {
|
|
79
|
+
if (panel) return;
|
|
80
|
+
var panels = document.getElementById("main-panels");
|
|
81
|
+
if (!panels) return;
|
|
82
|
+
panel = document.createElement("section");
|
|
83
|
+
panel.id = "project-logs-panel";
|
|
84
|
+
panel.className = "project-logs-panel hidden";
|
|
85
|
+
panel.setAttribute("aria-label", "Project Logs");
|
|
86
|
+
panel.innerHTML =
|
|
87
|
+
'<header class="project-logs-topbar">' +
|
|
88
|
+
'<button id="project-logs-back" class="project-logs-icon-btn hidden" type="button" title="Back to the log list" aria-label="Back to the log list"><i data-lucide="arrow-left"></i></button>' +
|
|
89
|
+
'<span class="project-logs-title"><i data-lucide="notebook-tabs"></i>Logs</span>' +
|
|
90
|
+
'<span class="project-logs-subtitle">Decisions and work recorded by this project</span>' +
|
|
91
|
+
'<div class="project-logs-window-actions" aria-label="Project Logs window controls">' +
|
|
92
|
+
'<button id="project-logs-wide" class="project-logs-icon-btn" type="button" title="Widen panel" aria-label="Widen Logs panel" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button>' +
|
|
93
|
+
'<button id="project-logs-fullscreen" class="project-logs-icon-btn" type="button" title="Toggle fullscreen" aria-label="Toggle Logs fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button>' +
|
|
94
|
+
'<button id="project-logs-close" class="project-logs-icon-btn" type="button" title="Close Logs" aria-label="Close Logs"><i data-lucide="x"></i></button>' +
|
|
95
|
+
'</div>' +
|
|
96
|
+
'</header>' +
|
|
97
|
+
'<div id="project-logs-toolbar" class="project-logs-toolbar">' +
|
|
98
|
+
'<label class="project-logs-search"><i data-lucide="search"></i><input id="project-logs-search" type="search" placeholder="Search logs" autocomplete="off"></label>' +
|
|
99
|
+
'<div id="project-logs-context-filter-slot"></div>' +
|
|
100
|
+
'<div id="project-logs-filter-slot"></div>' +
|
|
101
|
+
'</div>' +
|
|
102
|
+
'<div id="project-logs-list" class="project-logs-list" role="list"></div>' +
|
|
103
|
+
'<main id="project-logs-detail" class="project-logs-detail hidden"></main>' +
|
|
104
|
+
'<div id="project-log-delete-dialog" class="project-log-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="project-log-delete-heading">' +
|
|
105
|
+
'<div class="project-log-delete-dialog-card">' +
|
|
106
|
+
'<h2 id="project-log-delete-heading">Delete this log?</h2>' +
|
|
107
|
+
'<p>This removes the entry from the project ledger. It cannot be restored in the app.</p>' +
|
|
108
|
+
'<p class="project-log-delete-dialog-title"></p>' +
|
|
109
|
+
'<p class="project-log-delete-dialog-status" role="status"></p>' +
|
|
110
|
+
'<div class="project-log-delete-dialog-actions">' +
|
|
111
|
+
'<button type="button" data-action="cancel">Cancel</button>' +
|
|
112
|
+
'<button type="button" class="danger" data-action="delete">Delete log</button>' +
|
|
113
|
+
'</div>' +
|
|
114
|
+
'</div>' +
|
|
115
|
+
'</div>';
|
|
116
|
+
panels.appendChild(panel);
|
|
117
|
+
|
|
118
|
+
listEl = panel.querySelector("#project-logs-list");
|
|
119
|
+
detailEl = panel.querySelector("#project-logs-detail");
|
|
120
|
+
searchEl = panel.querySelector("#project-logs-search");
|
|
121
|
+
filterEl = panel.querySelector("#project-logs-filter-slot");
|
|
122
|
+
contextFilterEl = panel.querySelector("#project-logs-context-filter-slot");
|
|
123
|
+
backBtn = panel.querySelector("#project-logs-back");
|
|
124
|
+
deleteDialog = panel.querySelector("#project-log-delete-dialog");
|
|
125
|
+
|
|
126
|
+
panel.querySelector("#project-logs-close").addEventListener("click", closeProjectLogs);
|
|
127
|
+
backBtn.addEventListener("click", showList);
|
|
128
|
+
panel.querySelector("#project-logs-wide").addEventListener("click", function () {
|
|
129
|
+
applyWindowState(!store.get('projectLogsWide'), store.get('projectLogsFullscreen'));
|
|
130
|
+
});
|
|
131
|
+
panel.querySelector("#project-logs-fullscreen").addEventListener("click", function () {
|
|
132
|
+
applyWindowState(store.get('projectLogsWide'), !store.get('projectLogsFullscreen'));
|
|
133
|
+
});
|
|
134
|
+
searchEl.addEventListener("input", function () {
|
|
135
|
+
if (searchTimer) clearTimeout(searchTimer);
|
|
136
|
+
searchTimer = setTimeout(requestList, 180);
|
|
137
|
+
});
|
|
138
|
+
deleteDialog.querySelector('[data-action="cancel"]').addEventListener("click", closeDeleteDialog);
|
|
139
|
+
deleteDialog.querySelector('[data-action="delete"]').addEventListener("click", confirmDelete);
|
|
140
|
+
deleteDialog.addEventListener("click", function (event) {
|
|
141
|
+
if (event.target === deleteDialog) closeDeleteDialog();
|
|
142
|
+
});
|
|
143
|
+
deleteDialog.addEventListener("keydown", function (event) {
|
|
144
|
+
if (event.key === "Escape") closeDeleteDialog();
|
|
145
|
+
});
|
|
146
|
+
refreshIcons();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Width and fullscreen are transient view state, so they live in the store like
|
|
150
|
+
// every other mutable UI value. `panel-fullscreen` is the shared class the
|
|
151
|
+
// document viewer and terminal already use.
|
|
152
|
+
function applyWindowState(wide, fullscreen) {
|
|
153
|
+
store.set({ projectLogsWide: !!wide, projectLogsFullscreen: !!fullscreen });
|
|
154
|
+
if (!panel) return;
|
|
155
|
+
panel.classList.toggle("project-logs-wide", !!wide && !fullscreen);
|
|
156
|
+
panel.classList.toggle("panel-fullscreen", !!fullscreen);
|
|
157
|
+
var wideBtn = panel.querySelector("#project-logs-wide");
|
|
158
|
+
var fullBtn = panel.querySelector("#project-logs-fullscreen");
|
|
159
|
+
if (wideBtn) {
|
|
160
|
+
wideBtn.setAttribute("aria-pressed", wide ? "true" : "false");
|
|
161
|
+
wideBtn.disabled = !!fullscreen;
|
|
162
|
+
}
|
|
163
|
+
if (fullBtn) fullBtn.setAttribute("aria-pressed", fullscreen ? "true" : "false");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// --- Navigation stack ----------------------------------------------------
|
|
167
|
+
|
|
168
|
+
function showList() {
|
|
169
|
+
if (!panel) return;
|
|
170
|
+
store.set({ projectLogsView: "list", projectLogsSelectedRef: null });
|
|
171
|
+
detailEl.classList.add("hidden");
|
|
172
|
+
listEl.classList.remove("hidden");
|
|
173
|
+
panel.querySelector("#project-logs-toolbar").classList.remove("hidden");
|
|
174
|
+
backBtn.classList.add("hidden");
|
|
175
|
+
// The list is re-shown rather than rebuilt, so the query and filter survive;
|
|
176
|
+
// the scroll offset is restored from where the user left it.
|
|
177
|
+
listEl.scrollTop = store.get('projectLogsListScroll') || 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function showDetail(entry) {
|
|
181
|
+
if (!panel || !entry) return;
|
|
182
|
+
store.set({ projectLogsListScroll: listEl.scrollTop, projectLogsView: "detail", projectLogsSelectedRef: entry.ref });
|
|
183
|
+
listEl.classList.add("hidden");
|
|
184
|
+
panel.querySelector("#project-logs-toolbar").classList.add("hidden");
|
|
185
|
+
detailEl.classList.remove("hidden");
|
|
186
|
+
detailEl.scrollTop = 0;
|
|
187
|
+
backBtn.classList.remove("hidden");
|
|
188
|
+
renderDetail(detailEl, entry, {
|
|
189
|
+
onComment: submitComment,
|
|
190
|
+
onDelete: openDeleteDialog,
|
|
191
|
+
canDelete: store.get('projectLogsCanDelete') === true,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function openDeleteDialog(entry) {
|
|
196
|
+
if (!deleteDialog || !entry || store.get('projectLogsCanDelete') !== true) return;
|
|
197
|
+
pendingDeleteEntry = entry;
|
|
198
|
+
deleteDialog.querySelector(".project-log-delete-dialog-title").textContent = entry.title || "Untitled log";
|
|
199
|
+
deleteDialog.querySelector(".project-log-delete-dialog-status").textContent = "";
|
|
200
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
201
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
202
|
+
deleteDialog.classList.remove("hidden");
|
|
203
|
+
deleteDialog.querySelector('[data-action="cancel"]').focus();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function closeDeleteDialog(force) {
|
|
207
|
+
if (!deleteDialog) return;
|
|
208
|
+
if (!force && store.get('projectLogsDeleteRequestId')) return;
|
|
209
|
+
deleteDialog.classList.add("hidden");
|
|
210
|
+
pendingDeleteEntry = null;
|
|
211
|
+
store.set({ projectLogsDeleteRequestId: null });
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function confirmDelete() {
|
|
215
|
+
if (!pendingDeleteEntry || !deleteDialog) return;
|
|
216
|
+
var requestId = nextRequestId("delete-log");
|
|
217
|
+
var status = deleteDialog.querySelector(".project-log-delete-dialog-status");
|
|
218
|
+
var button = deleteDialog.querySelector('[data-action="delete"]');
|
|
219
|
+
var cancelButton = deleteDialog.querySelector('[data-action="cancel"]');
|
|
220
|
+
store.set({ projectLogsDeleteRequestId: requestId });
|
|
221
|
+
button.disabled = true;
|
|
222
|
+
cancelButton.disabled = true;
|
|
223
|
+
status.textContent = "Deleting...";
|
|
224
|
+
if (!send({ type: "project_log_delete", requestId: requestId, ref: pendingDeleteEntry.ref })) {
|
|
225
|
+
button.disabled = false;
|
|
226
|
+
cancelButton.disabled = false;
|
|
227
|
+
store.set({ projectLogsDeleteRequestId: null });
|
|
228
|
+
status.textContent = "Logs are unavailable while disconnected.";
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// --- Requests ------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
function requestList() {
|
|
235
|
+
var requestId = nextRequestId("list-logs");
|
|
236
|
+
store.set({ projectLogsListRequestId: requestId });
|
|
237
|
+
if (listEl && !store.get('projectLogsEntries').length) {
|
|
238
|
+
listEl.setAttribute("aria-busy", "true");
|
|
239
|
+
renderList(listEl, [], requestEntry, "Loading the project ledger...");
|
|
240
|
+
}
|
|
241
|
+
if (!send({
|
|
242
|
+
type: "project_logs_list",
|
|
243
|
+
requestId: requestId,
|
|
244
|
+
query: searchEl ? searchEl.value.trim() : "",
|
|
245
|
+
category: store.get('projectLogsCategory') || "",
|
|
246
|
+
contextMode: store.get('projectLogsContextMode') || "",
|
|
247
|
+
})) {
|
|
248
|
+
if (listEl) {
|
|
249
|
+
listEl.removeAttribute("aria-busy");
|
|
250
|
+
renderList(listEl, [], requestEntry, "Logs are unavailable while disconnected.");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function requestEntry(ref) {
|
|
256
|
+
var requestId = nextRequestId("read-log");
|
|
257
|
+
store.set({ projectLogsReadRequestId: requestId });
|
|
258
|
+
send({ type: "project_log_read", requestId: requestId, ref: ref });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function submitComment(ref, body, statusEl, inputEl) {
|
|
262
|
+
var requestId = nextRequestId("comment-log");
|
|
263
|
+
store.set({ projectLogsCommentRequestId: requestId, projectLogsCommentInput: inputEl || null });
|
|
264
|
+
if (!send({ type: "project_log_comment", requestId: requestId, ref: ref, body: body })) {
|
|
265
|
+
statusEl.textContent = "Logs are unavailable while disconnected.";
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
statusEl.textContent = "Posting...";
|
|
269
|
+
store.set({ projectLogsCommentStatusEl: statusEl });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function applyFilter(category) {
|
|
273
|
+
store.set({ projectLogsCategory: category || "" });
|
|
274
|
+
requestList();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function applyContextFilter(contextMode) {
|
|
278
|
+
store.set({ projectLogsContextMode: contextMode || "project" });
|
|
279
|
+
requestList();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// --- Lifecycle -----------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
export function initProjectLogs() {
|
|
285
|
+
var button = document.getElementById("project-logs-btn");
|
|
286
|
+
if (!button) return;
|
|
287
|
+
button.addEventListener("click", function () {
|
|
288
|
+
if (store.get('projectLogsOpen')) closeProjectLogs();
|
|
289
|
+
else openProjectLogs();
|
|
290
|
+
});
|
|
291
|
+
syncUnreadBadge();
|
|
292
|
+
store.subscribe(function (state, previous) {
|
|
293
|
+
if (state.currentSlug === previous.currentSlug) return;
|
|
294
|
+
closeProjectLogs();
|
|
295
|
+
applyWindowState(false, false);
|
|
296
|
+
// A different project must never inherit the previous project's unread
|
|
297
|
+
// state.
|
|
298
|
+
store.set({
|
|
299
|
+
projectLogsEntries: [],
|
|
300
|
+
projectLogsSelectedRef: null,
|
|
301
|
+
projectLogsView: "list",
|
|
302
|
+
projectLogsCategory: "",
|
|
303
|
+
projectLogsContextMode: "",
|
|
304
|
+
projectLogsIsWorktree: false,
|
|
305
|
+
projectLogsListScroll: 0,
|
|
306
|
+
projectLogsListRequestId: null,
|
|
307
|
+
projectLogsReadRequestId: null,
|
|
308
|
+
projectLogsCommentRequestId: null,
|
|
309
|
+
projectLogsDeleteRequestId: null,
|
|
310
|
+
projectLogsCanDelete: false,
|
|
311
|
+
projectLogsUnread: 0,
|
|
312
|
+
projectLogsSeenRevisions: {}
|
|
313
|
+
});
|
|
314
|
+
syncUnreadBadge();
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// The conversation, composer, and title bar stay mounted and visible: Logs
|
|
319
|
+
// opens beside them, it does not replace them.
|
|
320
|
+
export function openProjectLogs() {
|
|
321
|
+
ensurePanel();
|
|
322
|
+
if (!panel) return;
|
|
323
|
+
closeScheduler();
|
|
324
|
+
closeNotesBrowser();
|
|
325
|
+
// Claim the single right workbench slot.
|
|
326
|
+
try { closeFileViewer(); } catch (e) {}
|
|
327
|
+
try { closeTerminal(); } catch (e) {}
|
|
328
|
+
panel.classList.remove("hidden");
|
|
329
|
+
applyWindowState(store.get('projectLogsWide'), false);
|
|
330
|
+
var button = document.getElementById("project-logs-btn");
|
|
331
|
+
if (button) button.classList.add("active");
|
|
332
|
+
store.set({ projectLogsOpen: true });
|
|
333
|
+
// Opening through the explicit tool button acknowledges the live badge.
|
|
334
|
+
acknowledgeUpdates();
|
|
335
|
+
showList();
|
|
336
|
+
requestList();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function openProjectLog(ref) {
|
|
340
|
+
if (typeof ref !== "string" || !/^log:[A-Za-z0-9_-]{24}$/.test(ref)) return false;
|
|
341
|
+
openProjectLogs();
|
|
342
|
+
requestEntry(ref);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
export function closeProjectLogs() {
|
|
347
|
+
if (!store.get('projectLogsOpen')) return;
|
|
348
|
+
if (panel) {
|
|
349
|
+
panel.classList.add("hidden");
|
|
350
|
+
}
|
|
351
|
+
closeDeleteDialog(true);
|
|
352
|
+
// Fullscreen is always dropped on close so the next open is a bounded pane
|
|
353
|
+
// and never silently hides the conversation.
|
|
354
|
+
applyWindowState(store.get('projectLogsWide'), false);
|
|
355
|
+
var button = document.getElementById("project-logs-btn");
|
|
356
|
+
if (button) button.classList.remove("active");
|
|
357
|
+
store.set({ projectLogsOpen: false });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// --- Server messages -----------------------------------------------------
|
|
361
|
+
|
|
362
|
+
export function handleProjectLogsState(msg) {
|
|
363
|
+
if (msg.requestId && msg.requestId !== store.get('projectLogsListRequestId')) return;
|
|
364
|
+
var contextMode = msg.contextMode || store.get('projectLogsContextMode') || (msg.isWorktree ? "current" : "project");
|
|
365
|
+
store.set({
|
|
366
|
+
projectLogsEntries: msg.entries || [],
|
|
367
|
+
projectLogsCanDelete: msg.canDelete === true,
|
|
368
|
+
projectLogsContextMode: contextMode,
|
|
369
|
+
projectLogsIsWorktree: msg.isWorktree === true,
|
|
370
|
+
});
|
|
371
|
+
if (!listEl) return;
|
|
372
|
+
listEl.removeAttribute("aria-busy");
|
|
373
|
+
if (filterEl && Array.isArray(msg.categories)) {
|
|
374
|
+
renderFilter(filterEl, msg.categories, store.get('projectLogsCategory'), applyFilter);
|
|
375
|
+
}
|
|
376
|
+
if (contextFilterEl) {
|
|
377
|
+
renderContextFilter(contextFilterEl, contextMode, msg.isWorktree === true, applyContextFilter);
|
|
378
|
+
}
|
|
379
|
+
var isFiltered = !!((searchEl && searchEl.value.trim()) || store.get('projectLogsCategory') || contextMode !== "project");
|
|
380
|
+
renderList(listEl, msg.entries || [], requestEntry,
|
|
381
|
+
isFiltered ? "No entries match the current scope, search, or category." : "No logs yet. This project's agent sessions record decisions and work here.");
|
|
382
|
+
if (store.get('projectLogsView') !== "detail") listEl.scrollTop = store.get('projectLogsListScroll') || 0;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export function handleProjectLogEntry(msg) {
|
|
386
|
+
if (msg.requestId && msg.requestId !== store.get('projectLogsReadRequestId')) return;
|
|
387
|
+
if (!msg.entry) return;
|
|
388
|
+
store.set({ projectLogsCanDelete: msg.canDelete === true });
|
|
389
|
+
showDetail(msg.entry);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
export function handleProjectLogCommented(msg) {
|
|
393
|
+
if (msg.requestId && msg.requestId !== store.get('projectLogsCommentRequestId')) return;
|
|
394
|
+
if (!msg.entry) return;
|
|
395
|
+
// The comment is a proposal, not a change, so say so plainly rather than
|
|
396
|
+
// leaving the composer reading "Posting..." forever.
|
|
397
|
+
var pendingStatus = store.get('projectLogsCommentStatusEl');
|
|
398
|
+
if (pendingStatus) pendingStatus.textContent = msg.reviewQueued
|
|
399
|
+
? "Project Driver is reviewing..."
|
|
400
|
+
: "Awaiting Project Driver review";
|
|
401
|
+
store.set({ projectLogsCommentStatusEl: null });
|
|
402
|
+
// Re-render the open entry so the new comment and its attribution appear,
|
|
403
|
+
// then refresh the ledger so the comment count is current.
|
|
404
|
+
showDetail(msg.entry);
|
|
405
|
+
if (msg.reviewQueued && detailEl) {
|
|
406
|
+
var badges = detailEl.querySelectorAll(".project-log-comment-status-badge");
|
|
407
|
+
if (badges.length > 0) badges[badges.length - 1].textContent = "Project Driver is reviewing";
|
|
408
|
+
}
|
|
409
|
+
requestList();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
export function handleProjectLogCommentReviewed(msg) {
|
|
413
|
+
if (!msg || !msg.ref || !store.get('projectLogsOpen')) return;
|
|
414
|
+
if (store.get('projectLogsView') === "detail" && msg.ref === store.get('projectLogsSelectedRef')) {
|
|
415
|
+
requestEntry(msg.ref);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
requestList();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
export function handleProjectLogDeleted(msg) {
|
|
422
|
+
if (msg.requestId && msg.requestId !== store.get('projectLogsDeleteRequestId')) return;
|
|
423
|
+
closeDeleteDialog(true);
|
|
424
|
+
showList();
|
|
425
|
+
requestList();
|
|
426
|
+
showToast("Log deleted.");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// A canonical revision landed. This marks the explicit button only; it never
|
|
430
|
+
// opens, reveals, focuses, or scrolls the pane.
|
|
431
|
+
export function handleProjectLogUpdated(msg) {
|
|
432
|
+
noteCanonicalUpdate(msg);
|
|
433
|
+
if (msg.op === "delete" && msg.ref === store.get('projectLogsSelectedRef')) {
|
|
434
|
+
closeDeleteDialog(true);
|
|
435
|
+
showList();
|
|
436
|
+
}
|
|
437
|
+
// Refresh the ledger only when it is already on screen, so the row moves to
|
|
438
|
+
// the top the moment its canonical update arrives.
|
|
439
|
+
if (store.get('projectLogsOpen') && store.get('projectLogsView') !== "detail") requestList();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export function handleProjectLogsError(msg) {
|
|
443
|
+
var pending = [
|
|
444
|
+
store.get('projectLogsListRequestId'),
|
|
445
|
+
store.get('projectLogsReadRequestId'),
|
|
446
|
+
store.get('projectLogsCommentRequestId'),
|
|
447
|
+
store.get('projectLogsDeleteRequestId')
|
|
448
|
+
];
|
|
449
|
+
if (msg.requestId && pending.indexOf(msg.requestId) === -1) return;
|
|
450
|
+
if (msg.requestId && msg.requestId === store.get('projectLogsListRequestId') && listEl) {
|
|
451
|
+
listEl.removeAttribute("aria-busy");
|
|
452
|
+
renderList(listEl, [], requestEntry, "The ledger could not be loaded. Try opening Logs again.");
|
|
453
|
+
}
|
|
454
|
+
if (msg.requestId && msg.requestId === store.get('projectLogsDeleteRequestId') && deleteDialog) {
|
|
455
|
+
store.set({ projectLogsDeleteRequestId: null });
|
|
456
|
+
deleteDialog.querySelector('[data-action="delete"]').disabled = false;
|
|
457
|
+
deleteDialog.querySelector('[data-action="cancel"]').disabled = false;
|
|
458
|
+
deleteDialog.querySelector(".project-log-delete-dialog-status").textContent = msg.message || "The log could not be deleted.";
|
|
459
|
+
}
|
|
460
|
+
showToast(msg.message || "Project Logs could not complete the request.", "error");
|
|
461
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// project-removal-target.js - Selects the destination after removing the active project
|
|
2
|
+
|
|
3
|
+
export function chooseProjectAfterRemoval(projects, removedSlug) {
|
|
4
|
+
var visibleProjects = projects.filter(function (project) {
|
|
5
|
+
return !project.isMate;
|
|
6
|
+
});
|
|
7
|
+
var removedIndex = visibleProjects.findIndex(function (project) {
|
|
8
|
+
return project.slug === removedSlug;
|
|
9
|
+
});
|
|
10
|
+
var remainingProjects = visibleProjects.filter(function (project) {
|
|
11
|
+
return project.slug !== removedSlug;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
if (removedSlug.indexOf("--") !== -1) {
|
|
15
|
+
var parentSlug = removedSlug.split("--")[0];
|
|
16
|
+
var parentProject = remainingProjects.find(function (project) {
|
|
17
|
+
return project.slug === parentSlug;
|
|
18
|
+
});
|
|
19
|
+
if (parentProject) return parentProject.slug;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (remainingProjects.length === 0) return null;
|
|
23
|
+
if (removedIndex < 0) return remainingProjects[0].slug;
|
|
24
|
+
|
|
25
|
+
return remainingProjects[Math.min(removedIndex, remainingProjects.length - 1)].slug;
|
|
26
|
+
}
|
|
@@ -125,8 +125,8 @@ export function initScheduler(_ctx) {
|
|
|
125
125
|
function ensurePanel() {
|
|
126
126
|
if (panel) return;
|
|
127
127
|
|
|
128
|
-
var
|
|
129
|
-
if (!
|
|
128
|
+
var hostEl = document.getElementById("main-area");
|
|
129
|
+
if (!hostEl) return;
|
|
130
130
|
|
|
131
131
|
panel = document.createElement("div");
|
|
132
132
|
panel.id = "scheduler-panel";
|
|
@@ -256,7 +256,7 @@ function ensurePanel() {
|
|
|
256
256
|
bodyRow.appendChild(content);
|
|
257
257
|
panel.appendChild(bodyRow);
|
|
258
258
|
|
|
259
|
-
|
|
259
|
+
hostEl.appendChild(panel);
|
|
260
260
|
|
|
261
261
|
// --- Close button (in top bar) ---
|
|
262
262
|
panel.querySelector("#scheduler-panel-close").addEventListener("click", function () {
|
|
@@ -383,6 +383,8 @@ function openScheduler() {
|
|
|
383
383
|
|
|
384
384
|
var sidebarBtn = document.getElementById("scheduler-btn");
|
|
385
385
|
if (sidebarBtn) sidebarBtn.classList.add("active");
|
|
386
|
+
var homeBtn = document.getElementById("home-scheduler-btn");
|
|
387
|
+
if (homeBtn) homeBtn.classList.add("active");
|
|
386
388
|
|
|
387
389
|
// Persist scheduler state in URL hash
|
|
388
390
|
if (location.hash !== "#scheduler") {
|
|
@@ -423,6 +425,8 @@ export function closeScheduler() {
|
|
|
423
425
|
// Un-mark sidebar button
|
|
424
426
|
var sidebarBtn = document.getElementById("scheduler-btn");
|
|
425
427
|
if (sidebarBtn) sidebarBtn.classList.remove("active");
|
|
428
|
+
var homeBtn = document.getElementById("home-scheduler-btn");
|
|
429
|
+
if (homeBtn) homeBtn.classList.remove("active");
|
|
426
430
|
|
|
427
431
|
// Remove scheduler hash from URL
|
|
428
432
|
if (location.hash === "#scheduler") {
|
|
@@ -1470,6 +1474,16 @@ export function openSchedulerToTab(tab) {
|
|
|
1470
1474
|
}
|
|
1471
1475
|
}
|
|
1472
1476
|
|
|
1477
|
+
export function openHomeScheduler() {
|
|
1478
|
+
if (!ctx) return;
|
|
1479
|
+
ctx.requireClayRalph(function () {
|
|
1480
|
+
showAllProjects = true;
|
|
1481
|
+
openSchedulerToTab("calendar");
|
|
1482
|
+
renderSidebar();
|
|
1483
|
+
render();
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1473
1487
|
export function isSchedulerOpen() {
|
|
1474
1488
|
return panelOpen;
|
|
1475
1489
|
}
|