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.
Files changed (185) hide show
  1. package/lib/capsule-display-floor.js +51 -0
  2. package/lib/capsule-frame-server.js +255 -0
  3. package/lib/capsule-mate-turn.js +105 -0
  4. package/lib/capsule-pig-logic.js +300 -0
  5. package/lib/capsule-server-runtimes.js +39 -0
  6. package/lib/capsules/pig/display.js +198 -0
  7. package/lib/capsules/pig/manifest.json +9 -0
  8. package/lib/capsules/pig/ui.json +84 -0
  9. package/lib/config.js +25 -1
  10. package/lib/daemon-projects.js +13 -8
  11. package/lib/daemon.js +127 -14
  12. package/lib/git-cli.js +95 -1
  13. package/lib/knowledge-import.js +436 -0
  14. package/lib/knowledge-record-store.js +188 -0
  15. package/lib/knowledge-search.js +240 -0
  16. package/lib/mate-knowledge-mcp-server.js +151 -0
  17. package/lib/mate-knowledge-migration.js +283 -0
  18. package/lib/mate-knowledge-service.js +488 -0
  19. package/lib/mate-knowledge-sync.js +482 -0
  20. package/lib/migrate-single-user.js +2 -0
  21. package/lib/notes-lifecycle.js +149 -0
  22. package/lib/notes.js +60 -3
  23. package/lib/os-users.js +3 -1
  24. package/lib/project-capsule-catalog.js +9 -1
  25. package/lib/project-capsule-turn.js +96 -0
  26. package/lib/project-connection.js +15 -1
  27. package/lib/project-file-history.js +223 -0
  28. package/lib/project-file-watch.js +77 -49
  29. package/lib/project-filesystem.js +31 -220
  30. package/lib/project-http.js +15 -1
  31. package/lib/project-knowledge.js +22 -0
  32. package/lib/project-log-context.js +172 -0
  33. package/lib/project-log-feedback-delivery.js +118 -0
  34. package/lib/project-logs-comments.js +137 -0
  35. package/lib/project-logs-context-state.js +42 -0
  36. package/lib/project-logs-mcp-server.js +328 -0
  37. package/lib/project-logs-query.js +185 -0
  38. package/lib/project-logs-root.js +75 -0
  39. package/lib/project-logs-schema.js +259 -0
  40. package/lib/project-logs-service.js +463 -0
  41. package/lib/project-logs-snapshot.js +205 -0
  42. package/lib/project-logs-store.js +495 -0
  43. package/lib/project-logs-versioning.js +132 -0
  44. package/lib/project-logs.js +400 -0
  45. package/lib/project-mate-interaction.js +14 -0
  46. package/lib/project-mate-knowledge.js +110 -0
  47. package/lib/project-memory.js +22 -0
  48. package/lib/project-message-delivery.js +70 -0
  49. package/lib/project-pair-lifecycle.js +460 -0
  50. package/lib/project-request-access.js +66 -0
  51. package/lib/project-session-handoff.js +1 -1
  52. package/lib/project-session-notes.js +64 -13
  53. package/lib/project-session-pair.js +159 -146
  54. package/lib/project-session-spawn.js +1 -1
  55. package/lib/project-sessions.js +58 -13
  56. package/lib/project-user-message.js +53 -4
  57. package/lib/project-worker-permission.js +430 -0
  58. package/lib/project-worker-proposal.js +208 -62
  59. package/lib/project.js +185 -6
  60. package/lib/public/app.js +63 -35
  61. package/lib/public/css/admin.css +2 -2
  62. package/lib/public/css/capsule-ui.css +38 -0
  63. package/lib/public/css/command-palette.css +19 -9
  64. package/lib/public/css/git-placard.css +154 -0
  65. package/lib/public/css/home-hub.css +4 -0
  66. package/lib/public/css/home-session-actions.css +66 -0
  67. package/lib/public/css/home-sidebar.css +66 -0
  68. package/lib/public/css/input.css +84 -3
  69. package/lib/public/css/messages.css +40 -1
  70. package/lib/public/css/mobile-nav.css +80 -0
  71. package/lib/public/css/notifications-center.css +67 -0
  72. package/lib/public/css/pane.css +97 -10
  73. package/lib/public/css/project-logs.css +198 -0
  74. package/lib/public/css/scheduler.css +1 -1
  75. package/lib/public/css/session-actions.css +16 -101
  76. package/lib/public/css/sidebar.css +103 -0
  77. package/lib/public/css/sticky-notes.css +237 -229
  78. package/lib/public/css/worker-proposal.css +36 -3
  79. package/lib/public/index.html +43 -11
  80. package/lib/public/modules/admin.js +8 -8
  81. package/lib/public/modules/app-connection.js +124 -6
  82. package/lib/public/modules/app-home-hub.js +12 -8
  83. package/lib/public/modules/app-message-cards.js +2 -1
  84. package/lib/public/modules/app-messages.js +92 -11
  85. package/lib/public/modules/app-misc.js +2 -14
  86. package/lib/public/modules/app-notifications.js +11 -7
  87. package/lib/public/modules/app-panels.js +3 -2
  88. package/lib/public/modules/app-projects.js +56 -18
  89. package/lib/public/modules/app-rendering.js +1 -0
  90. package/lib/public/modules/capsule-preference.js +44 -0
  91. package/lib/public/modules/chat-bubble-renderer.js +1 -1
  92. package/lib/public/modules/clay-log-links.js +70 -0
  93. package/lib/public/modules/command-palette.js +52 -15
  94. package/lib/public/modules/context-sources.js +0 -3
  95. package/lib/public/modules/git-agent-sessions.js +85 -0
  96. package/lib/public/modules/git-panel.js +111 -84
  97. package/lib/public/modules/git-placard.js +161 -0
  98. package/lib/public/modules/home-chat-empty-state.js +39 -0
  99. package/lib/public/modules/home-chat-identity.js +9 -0
  100. package/lib/public/modules/home-conversations-sheet.js +102 -48
  101. package/lib/public/modules/home-mate-chat.js +15 -35
  102. package/lib/public/modules/home-session-actions.js +6 -0
  103. package/lib/public/modules/home-sidebar-chat-list.js +101 -41
  104. package/lib/public/modules/home-sidebar.js +7 -0
  105. package/lib/public/modules/home-surface-boot.js +7 -3
  106. package/lib/public/modules/home-tool-frame.js +182 -0
  107. package/lib/public/modules/home-tools.js +138 -1
  108. package/lib/public/modules/input.js +38 -7
  109. package/lib/public/modules/markdown.js +2 -0
  110. package/lib/public/modules/mate-sidebar.js +0 -8
  111. package/lib/public/modules/message-delivery.js +97 -0
  112. package/lib/public/modules/paste-modal.js +84 -0
  113. package/lib/public/modules/project-activation.js +45 -0
  114. package/lib/public/modules/project-logs-render.js +405 -0
  115. package/lib/public/modules/project-logs.js +461 -0
  116. package/lib/public/modules/project-removal-target.js +26 -0
  117. package/lib/public/modules/scheduler.js +17 -3
  118. package/lib/public/modules/session-actions.js +29 -105
  119. package/lib/public/modules/session-hierarchy.js +54 -0
  120. package/lib/public/modules/sidebar-mobile.js +40 -15
  121. package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
  122. package/lib/public/modules/sidebar-sessions.js +77 -11
  123. package/lib/public/modules/sidebar.js +17 -5
  124. package/lib/public/modules/split-group-helpers.js +8 -0
  125. package/lib/public/modules/split-pair-ui.js +15 -4
  126. package/lib/public/modules/split-view.js +5 -2
  127. package/lib/public/modules/split-worker-runtime.js +24 -0
  128. package/lib/public/modules/sticky-note-markdown.js +5 -0
  129. package/lib/public/modules/sticky-notes-browser.js +326 -0
  130. package/lib/public/modules/sticky-notes-card.js +360 -0
  131. package/lib/public/modules/sticky-notes-editor.js +291 -0
  132. package/lib/public/modules/sticky-notes-shared.js +90 -0
  133. package/lib/public/modules/sticky-notes.js +170 -946
  134. package/lib/public/modules/thinking-lifecycle.js +175 -0
  135. package/lib/public/modules/thinking-summary.js +28 -0
  136. package/lib/public/modules/thinking-view.js +75 -0
  137. package/lib/public/modules/tool-palette-order.js +143 -0
  138. package/lib/public/modules/tool-palette-overlays.js +159 -0
  139. package/lib/public/modules/tool-palette.js +21 -188
  140. package/lib/public/modules/tools.js +31 -123
  141. package/lib/public/modules/update-snooze.js +169 -0
  142. package/lib/public/modules/user-settings.js +4 -0
  143. package/lib/public/modules/worker-pane-lock.js +176 -0
  144. package/lib/public/modules/worker-proposal-state.js +16 -0
  145. package/lib/public/modules/worker-proposal.js +49 -20
  146. package/lib/public/style.css +3 -1
  147. package/lib/sdk-bridge.js +135 -46
  148. package/lib/sdk-message-processor.js +36 -19
  149. package/lib/server-experimental-settings.js +49 -0
  150. package/lib/server-global-ws.js +11 -0
  151. package/lib/server-home-chat-events.js +31 -1
  152. package/lib/server-home-chat.js +27 -4
  153. package/lib/server-home-clay-entry.js +1 -1
  154. package/lib/server-home-clay-session-links.js +34 -5
  155. package/lib/server-settings.js +5 -0
  156. package/lib/server-tools.js +127 -9
  157. package/lib/server.js +137 -22
  158. package/lib/session-driver-eligibility.js +38 -0
  159. package/lib/session-driver-orchestration.js +31 -0
  160. package/lib/session-hygiene.js +3 -0
  161. package/lib/session-notes-mcp-server.js +25 -5
  162. package/lib/session-pair-factory.js +247 -0
  163. package/lib/session-pair-mcp-server.js +46 -8
  164. package/lib/session-pair-prompts.js +79 -0
  165. package/lib/session-pair-turn-control.js +153 -0
  166. package/lib/session-provenance.js +119 -0
  167. package/lib/session-spawn-mcp-server.js +1 -1
  168. package/lib/session-split-groups.js +4 -1
  169. package/lib/session-title-policy.js +60 -0
  170. package/lib/session-visibility.js +41 -0
  171. package/lib/sessions.js +57 -10
  172. package/lib/tools-registry.js +89 -10
  173. package/lib/update-snooze.js +437 -0
  174. package/lib/users-experimental-preferences.js +30 -0
  175. package/lib/users-permissions.js +6 -6
  176. package/lib/users.js +22 -16
  177. package/lib/workspace-query-access.js +102 -0
  178. package/lib/workspace-query-service.js +24 -17
  179. package/lib/worktree.js +25 -38
  180. package/lib/ws-schema.js +38 -7
  181. package/lib/yoke/adapters/claude-worker.js +16 -6
  182. package/lib/yoke/adapters/claude.js +12 -0
  183. package/lib/yoke/adapters/codex.js +24 -14
  184. package/lib/yoke/vendor-registry.js +1 -1
  185. 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 appEl = document.getElementById("app");
129
- if (!appEl) return;
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
- appEl.appendChild(panel);
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
  }