clay-server 4.0.0-beta.16 → 4.0.0-beta.18
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/daemon-projects.js +10 -5
- package/lib/daemon.js +61 -10
- package/lib/project-log-context.js +171 -0
- package/lib/project-log-feedback-delivery.js +47 -5
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +15 -4
- package/lib/project-logs-query.js +18 -0
- package/lib/project-logs-root.js +8 -0
- package/lib/project-logs-service.js +121 -15
- package/lib/project-logs-snapshot.js +3 -0
- package/lib/project-logs-store.js +29 -32
- package/lib/project-logs-versioning.js +6 -2
- package/lib/project-logs.js +61 -13
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-user-message.js +11 -0
- package/lib/project.js +6 -1
- package/lib/public/app.js +5 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/project-logs.css +20 -2
- package/lib/public/css/sticky-notes.css +7 -0
- package/lib/public/index.html +2 -2
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +22 -3
- package/lib/public/modules/app-messages.js +29 -1
- package/lib/public/modules/app-rendering.js +1 -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/input.js +6 -1
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/project-logs-render.js +48 -2
- package/lib/public/modules/project-logs.js +140 -11
- package/lib/public/modules/sidebar-session-hierarchy.js +16 -7
- package/lib/public/modules/sidebar-sessions.js +3 -1
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +2 -0
- package/lib/public/modules/sticky-notes-card.js +2 -0
- package/lib/public/modules/sticky-notes-editor.js +3 -0
- package/lib/public/modules/sticky-notes.js +2 -0
- package/lib/public/style.css +1 -1
- package/lib/sdk-bridge.js +20 -10
- package/lib/server-global-ws.js +4 -0
- package/lib/server-home-chat-events.js +3 -0
- package/lib/server-home-chat.js +9 -2
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server.js +14 -3
- package/lib/ws-schema.js +8 -2
- package/package.json +1 -1
|
@@ -111,6 +111,7 @@ export function finalizeAssistantBlock() {
|
|
|
111
111
|
|
|
112
112
|
export function showClaudePreThinking() {
|
|
113
113
|
if (getChatLayout() !== "channel") return;
|
|
114
|
+
removeMatePreThinking();
|
|
114
115
|
var vendor = store.get('currentVendor') || "claude";
|
|
115
116
|
var vendorAvatar = VENDOR_AVATARS[vendor] || VENDOR_AVATARS.claude;
|
|
116
117
|
var vendorName = VENDOR_NAMES[vendor] || VENDOR_NAMES.claude;
|
|
@@ -105,7 +105,7 @@ export function addAssistantCopyHandler(row, rawText) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
row.addEventListener("click", function (event) {
|
|
108
|
-
if (event.target.closest("a, pre, code")) return;
|
|
108
|
+
if (event.target.closest("a, button, pre, code")) return;
|
|
109
109
|
var selection = window.getSelection();
|
|
110
110
|
if (selection && selection.toString().length > 0) return;
|
|
111
111
|
if (!primed) {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export function parseClayLogReferences(text) {
|
|
2
|
+
var pattern = /\[clayos\/(log:[A-Za-z0-9_-]{24})(?:\s+[\u2014-]\s+([^\]\n]{1,80}))?\]|(^|[^A-Za-z0-9_/:])(log:[A-Za-z0-9_-]{24})(?![A-Za-z0-9_-])/g;
|
|
3
|
+
var results = [];
|
|
4
|
+
var match;
|
|
5
|
+
while ((match = pattern.exec(text || ""))) {
|
|
6
|
+
results.push({
|
|
7
|
+
start: match.index,
|
|
8
|
+
end: pattern.lastIndex,
|
|
9
|
+
prefix: match[3] || "",
|
|
10
|
+
ref: match[1] || match[4],
|
|
11
|
+
label: match[2] ? match[2].trim() : "",
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
return results;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function isExactClayLogReference(text) {
|
|
18
|
+
return /^log:[A-Za-z0-9_-]{24}$/.test((text || "").trim());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function createLogLink(ref, labelText) {
|
|
22
|
+
var button = document.createElement("button");
|
|
23
|
+
button.type = "button";
|
|
24
|
+
button.className = "clayos-log-link";
|
|
25
|
+
button.contentEditable = "false";
|
|
26
|
+
button.dataset.logRef = ref;
|
|
27
|
+
button.setAttribute("aria-label", "Open Project Log" + (labelText ? " " + labelText : ""));
|
|
28
|
+
var label = document.createElement("span");
|
|
29
|
+
label.textContent = "Log";
|
|
30
|
+
button.appendChild(label);
|
|
31
|
+
if (labelText) {
|
|
32
|
+
var meta = document.createElement("small");
|
|
33
|
+
meta.textContent = labelText;
|
|
34
|
+
button.appendChild(meta);
|
|
35
|
+
}
|
|
36
|
+
return button;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function enhanceClayLogLinks(root) {
|
|
40
|
+
var codeNodes = root.querySelectorAll("code");
|
|
41
|
+
for (var codeIndex = 0; codeIndex < codeNodes.length; codeIndex++) {
|
|
42
|
+
var code = codeNodes[codeIndex];
|
|
43
|
+
var codeText = (code.textContent || "").trim();
|
|
44
|
+
if (code.closest("pre") || !isExactClayLogReference(codeText)) continue;
|
|
45
|
+
code.parentNode.replaceChild(createLogLink(codeText, ""), code);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
49
|
+
var nodes = [];
|
|
50
|
+
var node;
|
|
51
|
+
while ((node = walker.nextNode())) {
|
|
52
|
+
if (!node.parentElement || node.parentElement.closest("code, pre, a, button")) continue;
|
|
53
|
+
if (parseClayLogReferences(node.nodeValue).length) nodes.push(node);
|
|
54
|
+
}
|
|
55
|
+
for (var i = 0; i < nodes.length; i++) {
|
|
56
|
+
var text = nodes[i].nodeValue || "";
|
|
57
|
+
var matches = parseClayLogReferences(text);
|
|
58
|
+
var fragment = document.createDocumentFragment();
|
|
59
|
+
var offset = 0;
|
|
60
|
+
for (var j = 0; j < matches.length; j++) {
|
|
61
|
+
var match = matches[j];
|
|
62
|
+
fragment.appendChild(document.createTextNode(text.slice(offset, match.start)));
|
|
63
|
+
if (match.prefix) fragment.appendChild(document.createTextNode(match.prefix));
|
|
64
|
+
fragment.appendChild(createLogLink(match.ref, match.label));
|
|
65
|
+
offset = match.end;
|
|
66
|
+
}
|
|
67
|
+
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
68
|
+
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -6,6 +6,7 @@ import { showHomeHub, minimizeHomeHub } from './app-home-hub.js';
|
|
|
6
6
|
import { openHomeConversation } from './home-mate-chat.js';
|
|
7
7
|
import { startSearchClayChat, attachSearchClayChat, detachSearchClayChat, getSearchClayChatSummary } from './search-clay-chat.js';
|
|
8
8
|
import { getWs } from './ws-ref.js';
|
|
9
|
+
import { openProjectLog } from './project-logs.js';
|
|
9
10
|
|
|
10
11
|
function formatRelativeDate(ts) {
|
|
11
12
|
if (!ts) return "";
|
|
@@ -27,6 +28,7 @@ var ctx;
|
|
|
27
28
|
var paletteEl = null, inputEl = null, resultsEl = null, footerEl = null;
|
|
28
29
|
var activeIndex = -1, items = [], debounceTimer = null, abortCtrl = null;
|
|
29
30
|
var pendingNav = null, cachedHomeData = [], cachedVersion = null;
|
|
31
|
+
var pendingLogNav = null;
|
|
30
32
|
var searchResults = [], searchQuery = "", searchPending = false, chatMode = false;
|
|
31
33
|
|
|
32
34
|
export function initCommandPalette(value) {
|
|
@@ -52,43 +54,62 @@ export function initCommandPalette(value) {
|
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function handleClaySessionLinkClick(event) {
|
|
55
|
-
var link = event.target.closest(".clayos-session-link");
|
|
57
|
+
var link = event.target.closest(".clayos-session-link, .clayos-log-link");
|
|
56
58
|
if (!link || link.classList.contains("is-loading")) return;
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
if (link.classList.contains("clayos-log-link") && !link.closest(".search-clay-chat, #home-mate-chat")) {
|
|
62
|
+
openProjectLog(link.dataset.logRef);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
57
65
|
var ws = getWs();
|
|
58
66
|
if (!ws || ws.readyState !== 1) {
|
|
59
67
|
link.classList.add("is-error");
|
|
60
|
-
link.title = "Reconnect to open this
|
|
68
|
+
link.title = "Reconnect to open this item";
|
|
61
69
|
return;
|
|
62
70
|
}
|
|
63
|
-
var
|
|
64
|
-
|
|
71
|
+
var isLog = link.classList.contains("clayos-log-link");
|
|
72
|
+
var requestId = "clayos-" + (isLog ? "log-" : "session-") + Date.now() + "-" + Math.random().toString(36).slice(2, 9);
|
|
73
|
+
if (isLog) link.dataset.logRequest = requestId;
|
|
74
|
+
else link.dataset.sessionRequest = requestId;
|
|
65
75
|
link.classList.remove("is-error");
|
|
66
76
|
link.classList.add("is-loading");
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
77
|
+
var request = { requestId: requestId, surface: link.closest(".search-clay-chat") ? "search" : "home" };
|
|
78
|
+
if (isLog) {
|
|
79
|
+
request.type = "home_clay_log_resolve";
|
|
80
|
+
request.ref = link.dataset.logRef;
|
|
81
|
+
} else {
|
|
82
|
+
request.type = "home_clay_session_resolve";
|
|
83
|
+
request.sessionRef = link.dataset.sessionRef;
|
|
84
|
+
}
|
|
85
|
+
ws.send(JSON.stringify(request));
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
export function handleClaySessionTarget(msg) {
|
|
76
|
-
if (!msg || msg.type !== "home_clay_session_target") return false;
|
|
77
|
-
var
|
|
89
|
+
if (!msg || (msg.type !== "home_clay_session_target" && msg.type !== "home_clay_log_target")) return false;
|
|
90
|
+
var isLog = msg.type === "home_clay_log_target";
|
|
91
|
+
var links = document.querySelectorAll(isLog ? ".clayos-log-link[data-log-request]" : ".clayos-session-link[data-session-request]");
|
|
78
92
|
var link = null;
|
|
79
|
-
for (var i = 0; i < links.length; i++)
|
|
93
|
+
for (var i = 0; i < links.length; i++) {
|
|
94
|
+
if ((isLog ? links[i].dataset.logRequest : links[i].dataset.sessionRequest) === msg.requestId) link = links[i];
|
|
95
|
+
}
|
|
80
96
|
if (link) {
|
|
81
97
|
link.classList.remove("is-loading");
|
|
82
|
-
delete link.dataset.
|
|
98
|
+
if (isLog) delete link.dataset.logRequest;
|
|
99
|
+
else delete link.dataset.sessionRequest;
|
|
83
100
|
}
|
|
84
101
|
if (msg.status !== "ready" || !msg.target) {
|
|
85
102
|
if (link) {
|
|
86
103
|
link.classList.add("is-error");
|
|
87
|
-
link.title = msg.error || "
|
|
104
|
+
link.title = msg.error || "Item not available";
|
|
88
105
|
}
|
|
89
106
|
return true;
|
|
90
107
|
}
|
|
91
108
|
closeCommandPalette();
|
|
109
|
+
if (isLog) {
|
|
110
|
+
navigateToLog(msg.target);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
92
113
|
if (msg.target.isMate && msg.target.mateId && msg.target.homeSessionId) {
|
|
93
114
|
showHomeHub();
|
|
94
115
|
openHomeConversation(msg.target.mateId, msg.target.homeSessionId);
|
|
@@ -129,6 +150,11 @@ export function closeCommandPalette() {
|
|
|
129
150
|
export function setPaletteVersion(version) { cachedVersion = version; }
|
|
130
151
|
|
|
131
152
|
export function handlePaletteSessionSwitch() {
|
|
153
|
+
if (pendingLogNav && ctx.currentSlug && ctx.currentSlug() === pendingLogNav.projectSlug) {
|
|
154
|
+
var logNav = pendingLogNav;
|
|
155
|
+
pendingLogNav = null;
|
|
156
|
+
openProjectLog(logNav.ref);
|
|
157
|
+
}
|
|
132
158
|
if (!pendingNav) return;
|
|
133
159
|
var nav = pendingNav;
|
|
134
160
|
pendingNav = null;
|
|
@@ -138,6 +164,17 @@ export function handlePaletteSessionSwitch() {
|
|
|
138
164
|
}
|
|
139
165
|
}
|
|
140
166
|
|
|
167
|
+
function navigateToLog(target) {
|
|
168
|
+
closeCommandPalette();
|
|
169
|
+
minimizeHomeHub();
|
|
170
|
+
if (ctx.currentSlug && ctx.currentSlug() === target.projectSlug) {
|
|
171
|
+
openProjectLog(target.ref);
|
|
172
|
+
} else {
|
|
173
|
+
pendingLogNav = { projectSlug: target.projectSlug, ref: target.ref };
|
|
174
|
+
ctx.switchProject(target.projectSlug);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
141
178
|
function buildDOM() {
|
|
142
179
|
paletteEl = document.createElement("div");
|
|
143
180
|
paletteEl.className = "cmd-palette hidden";
|
|
@@ -3,6 +3,7 @@ import { setRewindMode, isRewindMode } from './rewind.js';
|
|
|
3
3
|
import { renderPicker as renderContextPicker } from './context-sources.js';
|
|
4
4
|
import { checkForMention, showMentionMenu, hideMentionMenu, isMentionMenuVisible, mentionMenuKeydown, setMentionAtIdx, parseMentionFromInput, clearMentionState, stickyReapplyMention, sendMention, sendUserMention, renderMentionUser, renderUserMention, removeMentionChip } from './mention.js';
|
|
5
5
|
import { store } from './store.js';
|
|
6
|
+
import { sendAcknowledgedMessage } from './message-delivery.js';
|
|
6
7
|
import { mateAvatarUrl } from './avatar.js';
|
|
7
8
|
import { tuiIsActive, tuiSubmitText } from './session-tui-view.js';
|
|
8
9
|
import { VENDOR_AVATARS, VENDOR_NAMES } from './app-rendering.js';
|
|
@@ -316,7 +317,11 @@ export function sendMessage() {
|
|
|
316
317
|
// Include selected vendor for session binding (server uses on first message)
|
|
317
318
|
var _selVendor = store.get("currentVendor") || null;
|
|
318
319
|
if (_selVendor) payload.vendor = _selVendor;
|
|
319
|
-
|
|
320
|
+
var clientMessageId = sendAcknowledgedMessage(payload);
|
|
321
|
+
var optimisticUsers = ctx.messagesEl.querySelectorAll(".msg-user");
|
|
322
|
+
if (optimisticUsers.length > 0) {
|
|
323
|
+
optimisticUsers[optimisticUsers.length - 1].dataset.clientMessageId = clientMessageId;
|
|
324
|
+
}
|
|
320
325
|
|
|
321
326
|
// A turn blocked on an unanswered tool-permission request looks like a
|
|
322
327
|
// black hole: the new message queues silently while the dots keep
|
|
@@ -2,6 +2,7 @@ import { copyToClipboard, escapeHtml } from './utils.js';
|
|
|
2
2
|
import { refreshIcons } from './icons.js';
|
|
3
3
|
import { getMermaidThemeVars } from './theme.js';
|
|
4
4
|
import { store } from './store.js';
|
|
5
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
5
6
|
|
|
6
7
|
// Initialize markdown parser
|
|
7
8
|
marked.use({ gfm: true, breaks: false });
|
|
@@ -89,6 +90,7 @@ export function enhanceClaySessionLinks(root) {
|
|
|
89
90
|
fragment.appendChild(document.createTextNode(text.slice(offset)));
|
|
90
91
|
nodes[i].parentNode.replaceChild(fragment, nodes[i]);
|
|
91
92
|
}
|
|
93
|
+
enhanceClayLogLinks(root);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
96
|
/**
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// Reliable delivery for ordinary project chat messages within a page lifetime.
|
|
2
|
+
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { getWs } from './ws-ref.js';
|
|
5
|
+
|
|
6
|
+
var ACK_TIMEOUT_MS = 5000;
|
|
7
|
+
var ackTimers = {};
|
|
8
|
+
|
|
9
|
+
function createClientMessageId() {
|
|
10
|
+
if (window.crypto && typeof window.crypto.randomUUID === "function") {
|
|
11
|
+
return "cm-" + window.crypto.randomUUID();
|
|
12
|
+
}
|
|
13
|
+
return "cm-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function pendingMessages() {
|
|
17
|
+
return store.get('pendingOutboundMessages') || [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function replacePending(messages) {
|
|
21
|
+
store.set({ pendingOutboundMessages: messages });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function clearAckTimer(clientMessageId) {
|
|
25
|
+
if (!ackTimers[clientMessageId]) return;
|
|
26
|
+
clearTimeout(ackTimers[clientMessageId]);
|
|
27
|
+
delete ackTimers[clientMessageId];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function isPending(clientMessageId) {
|
|
31
|
+
var pending = pendingMessages();
|
|
32
|
+
for (var i = 0; i < pending.length; i++) {
|
|
33
|
+
if (pending[i].payload.clientMessageId === clientMessageId) return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function armAckTimer(clientMessageId, socket) {
|
|
39
|
+
clearAckTimer(clientMessageId);
|
|
40
|
+
ackTimers[clientMessageId] = setTimeout(function () {
|
|
41
|
+
delete ackTimers[clientMessageId];
|
|
42
|
+
if (!isPending(clientMessageId) || getWs() !== socket) return;
|
|
43
|
+
window.dispatchEvent(new CustomEvent("clay-message-delivery-timeout", {
|
|
44
|
+
detail: { socket: socket, clientMessageId: clientMessageId },
|
|
45
|
+
}));
|
|
46
|
+
}, ACK_TIMEOUT_MS);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function transmit(entry) {
|
|
50
|
+
var socket = getWs();
|
|
51
|
+
if (!socket || socket.readyState !== 1) return false;
|
|
52
|
+
try {
|
|
53
|
+
socket.send(JSON.stringify(entry.payload));
|
|
54
|
+
armAckTimer(entry.payload.clientMessageId, socket);
|
|
55
|
+
return true;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function sendAcknowledgedMessage(payload) {
|
|
62
|
+
var nextPayload = Object.assign({}, payload, { clientMessageId: createClientMessageId() });
|
|
63
|
+
var entry = {
|
|
64
|
+
projectSlug: store.get('currentSlug'),
|
|
65
|
+
sessionId: store.get('activeSessionId'),
|
|
66
|
+
payload: nextPayload,
|
|
67
|
+
};
|
|
68
|
+
replacePending(pendingMessages().concat([entry]));
|
|
69
|
+
if (!transmit(entry)) {
|
|
70
|
+
window.dispatchEvent(new CustomEvent("clay-message-delivery-timeout", {
|
|
71
|
+
detail: { socket: getWs(), clientMessageId: nextPayload.clientMessageId },
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
return nextPayload.clientMessageId;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function acknowledgeMessage(clientMessageId) {
|
|
78
|
+
if (typeof clientMessageId !== "string") return false;
|
|
79
|
+
var pending = pendingMessages();
|
|
80
|
+
var next = pending.filter(function (entry) {
|
|
81
|
+
return entry.payload.clientMessageId !== clientMessageId;
|
|
82
|
+
});
|
|
83
|
+
if (next.length === pending.length) return false;
|
|
84
|
+
clearAckTimer(clientMessageId);
|
|
85
|
+
replacePending(next);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function replayPendingMessages(sessionId) {
|
|
90
|
+
var pending = pendingMessages();
|
|
91
|
+
var projectSlug = store.get('currentSlug');
|
|
92
|
+
for (var i = 0; i < pending.length; i++) {
|
|
93
|
+
if (pending[i].projectSlug === projectSlug && pending[i].sessionId === sessionId) {
|
|
94
|
+
transmit(pending[i]);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -106,6 +106,34 @@ export function renderFilter(container, categories, selected, onChange) {
|
|
|
106
106
|
container.appendChild(select);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
+
export function renderContextFilter(container, selected, isWorktree, onChange) {
|
|
110
|
+
container.innerHTML = "";
|
|
111
|
+
var select = document.createElement("select");
|
|
112
|
+
select.id = "project-logs-context";
|
|
113
|
+
select.className = "project-logs-filter project-logs-context-filter";
|
|
114
|
+
select.setAttribute("aria-label", "Filter by change scope");
|
|
115
|
+
var options = isWorktree
|
|
116
|
+
? [["current", "Current worktree"], ["project", "Project"], ["all", "All changes"]]
|
|
117
|
+
: [["project", "Project"], ["all", "All changes"]];
|
|
118
|
+
for (var i = 0; i < options.length; i++) {
|
|
119
|
+
var option = document.createElement("option");
|
|
120
|
+
option.value = options[i][0];
|
|
121
|
+
option.textContent = options[i][1];
|
|
122
|
+
select.appendChild(option);
|
|
123
|
+
}
|
|
124
|
+
select.value = selected || (isWorktree ? "current" : "project");
|
|
125
|
+
select.addEventListener("change", function () { onChange(select.value); });
|
|
126
|
+
container.appendChild(select);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function contextLabel(context) {
|
|
130
|
+
if (!context || !context.changeSetId) return "";
|
|
131
|
+
var label = context.branch || "Worktree change";
|
|
132
|
+
if (context.status === "merged") return label + " · Merged";
|
|
133
|
+
if (context.status === "archived") return label + " · Archived";
|
|
134
|
+
return label + " · Active";
|
|
135
|
+
}
|
|
136
|
+
|
|
109
137
|
// One ledger row. Reveals what happened without opening the record: category,
|
|
110
138
|
// priority, title, summary, author, time, revision and comment counts.
|
|
111
139
|
function renderRow(entry, onOpen) {
|
|
@@ -152,6 +180,8 @@ function renderRow(entry, onOpen) {
|
|
|
152
180
|
if (entry.commentCount > 0) {
|
|
153
181
|
parts.push(entry.commentCount + (entry.commentCount === 1 ? " comment" : " comments"));
|
|
154
182
|
}
|
|
183
|
+
var context = contextLabel(entry.context);
|
|
184
|
+
if (context) parts.push(context);
|
|
155
185
|
meta.textContent = parts.filter(Boolean).join(" · ");
|
|
156
186
|
row.appendChild(meta);
|
|
157
187
|
|
|
@@ -289,8 +319,8 @@ function historyVerb(revision) {
|
|
|
289
319
|
return "Revised";
|
|
290
320
|
}
|
|
291
321
|
|
|
292
|
-
// The entry, full pane. People
|
|
293
|
-
// edit affordance because canonical authorship belongs to
|
|
322
|
+
// The entry, full pane. People may comment and the project owner may delete;
|
|
323
|
+
// there is no edit affordance because canonical authorship belongs to agents.
|
|
294
324
|
export function renderDetail(detailEl, entry, handlers) {
|
|
295
325
|
detailEl.innerHTML = "";
|
|
296
326
|
if (!entry) return;
|
|
@@ -317,6 +347,14 @@ export function renderDetail(detailEl, entry, handlers) {
|
|
|
317
347
|
flag.textContent = PRIORITY_LABELS[entry.priority] || entry.priority;
|
|
318
348
|
chips.appendChild(flag);
|
|
319
349
|
}
|
|
350
|
+
var context = contextLabel(entry.context);
|
|
351
|
+
if (context) {
|
|
352
|
+
var contextChip = document.createElement("span");
|
|
353
|
+
contextChip.className = "project-log-context-chip";
|
|
354
|
+
contextChip.dataset.status = entry.context.status || "active";
|
|
355
|
+
contextChip.textContent = context;
|
|
356
|
+
chips.appendChild(contextChip);
|
|
357
|
+
}
|
|
320
358
|
setText(header, "h1", entry.title || "Untitled log");
|
|
321
359
|
setText(header, ".project-log-doc-summary", entry.summary || "");
|
|
322
360
|
var byline = [
|
|
@@ -325,6 +363,14 @@ export function renderDetail(detailEl, entry, handlers) {
|
|
|
325
363
|
formatTime(entry.updatedAt || entry.createdAt),
|
|
326
364
|
].filter(Boolean).join(" · ");
|
|
327
365
|
setText(header, ".project-log-byline", byline);
|
|
366
|
+
if (handlers && handlers.canDelete) {
|
|
367
|
+
var deleteButton = document.createElement("button");
|
|
368
|
+
deleteButton.type = "button";
|
|
369
|
+
deleteButton.className = "project-log-delete-button";
|
|
370
|
+
deleteButton.innerHTML = '<i data-lucide="trash-2"></i><span>Delete log</span>';
|
|
371
|
+
deleteButton.addEventListener("click", function () { handlers.onDelete(entry); });
|
|
372
|
+
header.appendChild(deleteButton);
|
|
373
|
+
}
|
|
328
374
|
article.appendChild(header);
|
|
329
375
|
|
|
330
376
|
var markdown = document.createElement("div");
|