clay-server 4.1.0-beta.9 → 4.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/context-usage.js +47 -0
- package/lib/daemon-project-access.js +14 -0
- package/lib/daemon.js +7 -0
- package/lib/issues-mcp-server.js +1 -1
- package/lib/issues-service.js +3 -1
- package/lib/loop-registry-occurrence.js +1 -0
- package/lib/notes.js +20 -3
- package/lib/os-users.js +6 -0
- package/lib/project-connection.js +14 -3
- package/lib/project-file-path.js +13 -0
- package/lib/project-file-watch.js +19 -8
- package/lib/project-filesystem.js +10 -16
- package/lib/project-http.js +53 -48
- package/lib/project-issue-launch.js +3 -1
- package/lib/project-logs-mcp-server.js +8 -8
- package/lib/project-logs-schema.js +1 -1
- package/lib/project-logs-service.js +46 -13
- package/lib/project-logs-store.js +17 -15
- package/lib/project-logs.js +70 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-notifications.js +1 -1
- package/lib/project-scheduled-result-store.js +131 -0
- package/lib/project-scheduled-task-execution.js +35 -2
- package/lib/project-session-notes.js +11 -4
- package/lib/project-sessions.js +39 -6
- package/lib/project-user-message.js +47 -0
- package/lib/project.js +57 -3
- package/lib/public/app.js +27 -9
- package/lib/public/css/avatar-imprints.css +1 -1
- package/lib/public/css/command-palette.css +57 -19
- package/lib/public/css/git-panel.css +13 -12
- package/lib/public/css/git-placard.css +4 -4
- package/lib/public/css/issues.css +79 -32
- package/lib/public/css/mobile-nav.css +7 -0
- package/lib/public/css/notifications-center.css +3 -3
- package/lib/public/css/project-logs.css +15 -0
- package/lib/public/css/sidebar.css +7 -0
- package/lib/public/css/sticky-notes.css +29 -0
- package/lib/public/index.html +10 -0
- package/lib/public/modules/app-connection.js +9 -1
- package/lib/public/modules/app-favicon.js +10 -0
- package/lib/public/modules/app-messages.js +50 -12
- package/lib/public/modules/app-notifications.js +2 -0
- package/lib/public/modules/app-panels.js +77 -23
- package/lib/public/modules/app-projects.js +2 -0
- package/lib/public/modules/app-rate-limit.js +148 -61
- package/lib/public/modules/avatar-imprint.js +25 -70
- package/lib/public/modules/avatar.js +12 -4
- package/lib/public/modules/chat-render-runtime.js +20 -0
- package/lib/public/modules/clay-issue-links.js +24 -2
- package/lib/public/modules/clay-log-links.js +12 -1
- package/lib/public/modules/context-view-preference.js +143 -0
- package/lib/public/modules/default-vendor.js +116 -0
- package/lib/public/modules/filebrowser-tabs.js +1 -0
- package/lib/public/modules/filebrowser.js +45 -18
- package/lib/public/modules/issues-render.js +36 -10
- package/lib/public/modules/issues.js +95 -13
- package/lib/public/modules/markdown.js +3 -1
- package/lib/public/modules/pane-bridge.js +14 -8
- package/lib/public/modules/pane-issue-bridge.js +27 -0
- package/lib/public/modules/permission-hints.js +13 -0
- package/lib/public/modules/profile.js +9 -9
- package/lib/public/modules/project-activation.js +49 -0
- package/lib/public/modules/project-logs-render.js +13 -3
- package/lib/public/modules/project-logs.js +45 -15
- package/lib/public/modules/project-session-visibility-settings.js +138 -0
- package/lib/public/modules/project-settings.js +5 -0
- package/lib/public/modules/right-workbench.js +44 -0
- package/lib/public/modules/scheduled-result-client.js +33 -0
- package/lib/public/modules/scheduled-result-display.js +50 -0
- package/lib/public/modules/scheduled-result-notification.js +31 -0
- package/lib/public/modules/scheduled-task-presentation.js +15 -0
- package/lib/public/modules/scheduled-tasks.js +9 -21
- package/lib/public/modules/sidebar-mobile.js +51 -3
- package/lib/public/modules/sidebar-projects.js +14 -3
- package/lib/public/modules/sidebar-sessions.js +65 -5
- package/lib/public/modules/split-session-boundary.js +28 -0
- package/lib/public/modules/split-view.js +42 -7
- package/lib/public/modules/sticky-notes-browser.js +130 -18
- package/lib/public/modules/sticky-notes.js +2 -3
- package/lib/public/modules/terminal.js +4 -4
- package/lib/public/modules/thinking-lifecycle.js +4 -1
- package/lib/public/modules/tools.js +5 -1
- package/lib/scheduled-run-state.js +10 -2
- package/lib/scheduler.js +1 -0
- package/lib/sdk-bridge.js +97 -61
- package/lib/sdk-message-processor.js +44 -13
- package/lib/sdk-skill-discovery.js +6 -28
- package/lib/sdk-turn-queue.js +14 -0
- package/lib/server-context-view.js +53 -0
- package/lib/server-default-vendor.js +60 -0
- package/lib/server.js +31 -7
- package/lib/session-notes-mcp-server.js +3 -5
- package/lib/session-pair-factory.js +2 -2
- package/lib/session-title-generator.js +89 -0
- package/lib/session-title-policy.js +8 -2
- package/lib/session-visibility.js +62 -1
- package/lib/sessions.js +28 -1
- package/lib/user-presence.js +6 -0
- package/lib/users-context-view-preferences.js +65 -0
- package/lib/users-default-vendor-preferences.js +80 -0
- package/lib/users.js +12 -0
- package/lib/ws-schema.js +13 -0
- package/lib/yoke/acp-agent-profiles.js +15 -2
- package/lib/yoke/adapters/acp.js +3 -2
- package/lib/yoke/adapters/antigravity.js +2 -2
- package/lib/yoke/adapters/claude-worker.js +11 -12
- package/lib/yoke/adapters/claude.js +75 -22
- package/lib/yoke/adapters/codex.js +216 -29
- package/lib/yoke/adapters/kiro.js +6 -5
- package/lib/yoke/claude-context-usage.js +57 -0
- package/lib/yoke/claude-message-correlation.js +31 -0
- package/lib/yoke/claude-model-switch.js +15 -0
- package/lib/yoke/index.js +5 -1
- package/lib/yoke/mcp-bridge-server.js +18 -5
- package/lib/yoke/session-skill-catalog.js +45 -0
- package/lib/yoke/skill-discovery.js +60 -20
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +5 -7
|
@@ -1,22 +1,76 @@
|
|
|
1
1
|
import { store } from './store.js';
|
|
2
2
|
import { getWs } from './ws-ref.js';
|
|
3
3
|
import { showToast } from './utils.js';
|
|
4
|
-
import {
|
|
5
|
-
import { closeScheduledTasks } from './scheduled-tasks.js';
|
|
6
|
-
import { closeNotesBrowser } from './sticky-notes-browser.js';
|
|
7
|
-
import { closeFileViewer } from './filebrowser.js';
|
|
8
|
-
import { closeTerminal } from './terminal.js';
|
|
4
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
9
5
|
import { refreshIcons } from './icons.js';
|
|
10
|
-
import {
|
|
6
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
7
|
+
import { updateIssueLink } from './clay-issue-links.js';
|
|
8
|
+
import { renderIssueList, renderIssueDetail, renderIssueForm, renderIssueHistory } from './issues-render.js';
|
|
11
9
|
function panel() { return document.getElementById('issues-panel'); }
|
|
12
10
|
function content() { return panel().querySelector('.issues-content'); }
|
|
11
|
+
function clearUpdateNotice() {
|
|
12
|
+
store.set({ issuesUpdateAvailable: null });
|
|
13
|
+
var action = content() && content().querySelector('[data-issue-update-notice]');
|
|
14
|
+
if (action) action.remove();
|
|
15
|
+
}
|
|
16
|
+
function renderContextualUpdate() {
|
|
17
|
+
var update = store.get('issuesUpdateAvailable');
|
|
18
|
+
var issueContent = content();
|
|
19
|
+
var actions = issueContent && (issueContent.querySelector('.issue-detail .issue-actions') || issueContent.querySelector('#issue-form .issue-actions'));
|
|
20
|
+
if (!actions || !update) return;
|
|
21
|
+
var existing = actions.querySelector('[data-issue-update-notice]');
|
|
22
|
+
if (existing) existing.remove();
|
|
23
|
+
var notice = document.createElement('span');
|
|
24
|
+
notice.className = 'issues-inline-update';
|
|
25
|
+
notice.dataset.issueUpdateNotice = 'true';
|
|
26
|
+
notice.appendChild(document.createTextNode(store.get('issuesView') === 'edit' ? 'Updated remotely · Finish editing to refresh' : 'Updated remotely'));
|
|
27
|
+
var refresh = document.createElement('button');
|
|
28
|
+
refresh.type = 'button';
|
|
29
|
+
refresh.dataset.issueRefresh = update.ref;
|
|
30
|
+
refresh.textContent = 'Refresh';
|
|
31
|
+
refresh.disabled = store.get('issuesView') === 'edit';
|
|
32
|
+
if (refresh.disabled) refresh.title = 'Finish editing to refresh';
|
|
33
|
+
notice.appendChild(refresh);
|
|
34
|
+
actions.insertBefore(notice, actions.firstChild);
|
|
35
|
+
}
|
|
36
|
+
function captureDraft() {
|
|
37
|
+
var state = store.snap();
|
|
38
|
+
if (state.issuesView !== 'detail' && state.issuesView !== 'edit') return null;
|
|
39
|
+
var draft = { ref: entryRef(), view: state.issuesView, comment: null };
|
|
40
|
+
var comment = panel() && panel().querySelector('#issue-comment-input');
|
|
41
|
+
if (comment) draft.comment = comment.value;
|
|
42
|
+
return draft;
|
|
43
|
+
}
|
|
44
|
+
function restoreCommentDraft(draft) {
|
|
45
|
+
if (!draft || draft.comment === null) return;
|
|
46
|
+
var input = panel().querySelector('#issue-comment-input');
|
|
47
|
+
if (input) input.value = draft.comment;
|
|
48
|
+
}
|
|
49
|
+
function canRestoreCommentDraft(draft, ref) {
|
|
50
|
+
return !!draft && draft.ref === ref;
|
|
51
|
+
}
|
|
52
|
+
function hasNewerDraft(item) {
|
|
53
|
+
return (item.intent === 'detail' || item.intent === 'refresh') && item.viewGeneration !== (store.get('issuesViewGeneration') || 0);
|
|
54
|
+
}
|
|
55
|
+
function noteIssueUpdate(ref, revision) {
|
|
56
|
+
var current = store.get('issuesUpdateAvailable');
|
|
57
|
+
if (!current || current.ref !== ref || Number(revision || 0) >= Number(current.revision || 0)) {
|
|
58
|
+
store.set({ issuesUpdateAvailable: { ref: ref, revision: Number(revision || 0) } });
|
|
59
|
+
}
|
|
60
|
+
renderContextualUpdate();
|
|
61
|
+
}
|
|
62
|
+
function refreshSelectedIssue(ref) {
|
|
63
|
+
var selected = store.get('issuesSelected');
|
|
64
|
+
if (!selected || selected.ref !== ref || store.get('issuesView') === 'edit') return;
|
|
65
|
+
store.set({ issuesViewRequest: request('issue_read', { ref: ref }, 'refresh') });
|
|
66
|
+
}
|
|
13
67
|
function bumpViewGeneration() { store.set({ issuesViewGeneration: (store.get('issuesViewGeneration') || 0) + 1 }); }
|
|
14
68
|
function request(action, args, intent) {
|
|
15
69
|
var ws = getWs();
|
|
16
70
|
if (!ws || ws.readyState !== 1) { showToast('Reconnect to use Issues', 'warn'); return; }
|
|
17
71
|
var id = 'issues-' + crypto.randomUUID();
|
|
18
72
|
var pending = Object.assign({}, store.get('issuesRequests') || {});
|
|
19
|
-
pending[id] = { action: action, intent: intent, slug: store.get('currentSlug'), sourceRef: entryRef(), view: store.get('issuesView'), viewGeneration: store.get('issuesViewGeneration') || 0 };
|
|
73
|
+
pending[id] = { action: action, intent: intent, slug: store.get('currentSlug'), sourceRef: entryRef(), view: store.get('issuesView'), viewGeneration: store.get('issuesViewGeneration') || 0, draft: captureDraft() };
|
|
20
74
|
store.set({ issuesRequests: pending });
|
|
21
75
|
ws.send(JSON.stringify({ type: action, args: args || {}, requestId: id }));
|
|
22
76
|
return id;
|
|
@@ -43,7 +97,9 @@ function detail(entry) {
|
|
|
43
97
|
panel().querySelector('.issues-filters').hidden = true;
|
|
44
98
|
panel().querySelector('[data-issues-back]').classList.remove('hidden');
|
|
45
99
|
content().innerHTML = renderIssueDetail(entry);
|
|
100
|
+
enhanceClayLogLinks(content());
|
|
46
101
|
refreshIcons();
|
|
102
|
+
renderContextualUpdate();
|
|
47
103
|
}
|
|
48
104
|
function entryRef() {
|
|
49
105
|
var entry = store.get('issuesSelected');
|
|
@@ -81,6 +137,7 @@ function confirmDelete() {
|
|
|
81
137
|
store.set({ issuesMutationRequest: request('issue_delete', { ref: entry.ref, expectedRevision: entry.revision }, 'delete') });
|
|
82
138
|
}
|
|
83
139
|
export function closeIssues() {
|
|
140
|
+
releaseRightWorkbench("issues");
|
|
84
141
|
bumpViewGeneration();
|
|
85
142
|
if (panel()) panel().classList.add('hidden');
|
|
86
143
|
store.set({ issuesOpen: false });
|
|
@@ -90,8 +147,9 @@ export function closeIssues() {
|
|
|
90
147
|
export function openIssues(ref) {
|
|
91
148
|
if (!panel() || store.get('isMate')) return;
|
|
92
149
|
panel().querySelector('.issues-notice').textContent = '';
|
|
150
|
+
clearUpdateNotice();
|
|
93
151
|
bumpViewGeneration();
|
|
94
|
-
|
|
152
|
+
claimRightWorkbench("issues");
|
|
95
153
|
panel().classList.remove('hidden');
|
|
96
154
|
store.set({ issuesOpen: true });
|
|
97
155
|
var button = document.getElementById('issues-btn');
|
|
@@ -102,9 +160,10 @@ export function openIssues(ref) {
|
|
|
102
160
|
export function initIssues() {
|
|
103
161
|
var host = document.getElementById('main-panels');
|
|
104
162
|
if (!host || panel()) return;
|
|
163
|
+
registerRightWorkbench("issues", closeIssues);
|
|
105
164
|
var el = document.createElement('section');
|
|
106
165
|
el.id = 'issues-panel'; el.className = 'hidden'; el.setAttribute('aria-label', 'Project Issues');
|
|
107
|
-
el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option>
|
|
166
|
+
el.innerHTML = '<header class="issues-topbar"><button class="issues-icon-btn" data-issues-back aria-label="Back to issues" title="Back to issue list"><i data-lucide="arrow-left"></i></button><span class="issues-title"><i data-lucide="circle-dot"></i>Issues</span><span class="issues-subtitle">Manage issues through Driver chat.</span><div class="issues-window-actions"><button class="issues-icon-btn" data-issues-wide aria-label="Widen Issues" title="Widen Issues" aria-pressed="false"><i data-lucide="chevrons-left-right"></i></button><button class="issues-icon-btn" data-issues-full aria-label="Toggle Issues fullscreen" title="Toggle fullscreen" aria-pressed="false"><i data-lucide="maximize-2"></i></button><button class="issues-icon-btn" data-issues-close aria-label="Close Issues" title="Close Issues"><i data-lucide="x"></i></button></div></header><div class="issues-filters"><label class="issues-search"><i data-lucide="search"></i><input name="query" type="search" placeholder="Search issues" aria-label="Search issues"></label><select name="filter-status" aria-label="Filter status"><option value="">All statuses</option><option value="open">Open</option><option value="in_progress">In progress</option><option value="resolved">Resolved</option><option value="closed">Closed</option></select><select name="filter-type" aria-label="Filter type"><option value="">All types</option><option value="bug">Bug</option><option value="feature">Feature</option><option value="plan">Plan</option></select></div><p class="issues-notice" role="status"></p><main class="issues-content" role="region" aria-live="polite"></main><div class="issues-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="issues-delete-heading"><div class="issues-delete-card"><h2 id="issues-delete-heading">Delete this issue?</h2><p>This removes the issue from the board but preserves its history.</p><p class="issues-delete-title"></p><p class="issues-delete-status" role="status"></p><div><button type="button" data-issue-delete-cancel>Cancel</button><button type="button" data-issue-delete-confirm>Delete issue</button></div></div></div>';
|
|
108
167
|
host.appendChild(el);
|
|
109
168
|
var button = document.getElementById('issues-btn');
|
|
110
169
|
if (button) button.addEventListener('click', function () { if (store.get('issuesOpen')) closeIssues(); else openIssues(); });
|
|
@@ -120,6 +179,7 @@ export function initIssues() {
|
|
|
120
179
|
else if (target.hasAttribute('data-issue-delete-confirm')) confirmDelete();
|
|
121
180
|
else if (target.hasAttribute('data-issue-edit')) showIssueEdit(entry);
|
|
122
181
|
else if (target.hasAttribute('data-issue-cancel')) detail(entry);
|
|
182
|
+
else if (target.dataset.issueRefresh) refreshSelectedIssue(target.dataset.issueRefresh);
|
|
123
183
|
else if (target.dataset.openIssue) openIssues(target.dataset.openIssue);
|
|
124
184
|
else if (target.hasAttribute('data-issues-more')) listing(target.dataset.issuesMore);
|
|
125
185
|
else if (target.hasAttribute('data-issue-history')) { bumpViewGeneration(); store.set({ issuesView: 'history', issuesViewRequest: request('issue_history', { ref: entry.ref }, 'history') }); }
|
|
@@ -175,6 +235,7 @@ function showIssueEdit(entry) {
|
|
|
175
235
|
}
|
|
176
236
|
formEl.addEventListener('change', syncFields);
|
|
177
237
|
syncFields();
|
|
238
|
+
renderContextualUpdate();
|
|
178
239
|
}
|
|
179
240
|
export function handleIssuesMessage(msg) {
|
|
180
241
|
if (msg.type === 'issue_commented') {
|
|
@@ -204,8 +265,11 @@ export function handleIssuesMessage(msg) {
|
|
|
204
265
|
}
|
|
205
266
|
if (msg.type === 'issue_updated') {
|
|
206
267
|
var cache = Object.assign({}, store.get('issuesCache') || {}); cache[msg.ref] = Object.assign({}, cache[msg.ref] || {}, msg, { projectSlug: store.get('currentSlug') }); store.set({ issuesCache: cache });
|
|
207
|
-
document.querySelectorAll('[data-issue-ref]').forEach(function (node) { if (node.dataset.issueRef === msg.ref)
|
|
208
|
-
|
|
268
|
+
document.querySelectorAll('[data-issue-ref]').forEach(function (node) { if (node.dataset.issueRef === msg.ref) updateIssueLink(node, msg); });
|
|
269
|
+
var selected = store.get('issuesSelected');
|
|
270
|
+
var view = store.get('issuesView');
|
|
271
|
+
if (store.get('issuesOpen') && selected && selected.ref === msg.ref && (view === 'detail' || view === 'edit')) noteIssueUpdate(msg.ref, msg.revision);
|
|
272
|
+
else if (store.get('issuesOpen') && view === 'list') listing();
|
|
209
273
|
return true;
|
|
210
274
|
}
|
|
211
275
|
if (msg.type !== 'issues_result') return false;
|
|
@@ -241,6 +305,16 @@ export function handleIssuesMessage(msg) {
|
|
|
241
305
|
return true;
|
|
242
306
|
}
|
|
243
307
|
panel().querySelector('.issues-notice').textContent = '';
|
|
308
|
+
if (hasNewerDraft(item)) {
|
|
309
|
+
if (!msg.error && msg.result && msg.result.ref) {
|
|
310
|
+
var refreshedCache = Object.assign({}, store.get('issuesCache') || {});
|
|
311
|
+
refreshedCache[msg.result.ref] = Object.assign({}, msg.result, { canDelete: msg.canDelete === true, projectSlug: store.get('currentSlug') });
|
|
312
|
+
store.set({ issuesCache: refreshedCache });
|
|
313
|
+
if (Number(msg.result.revision || 0) > Number((store.get('issuesSelected') || {}).revision || 0)) noteIssueUpdate(msg.result.ref, msg.result.revision);
|
|
314
|
+
}
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
if (item.intent !== 'detail' && item.intent !== 'refresh') clearUpdateNotice();
|
|
244
318
|
if (item.intent === 'work') { closeIssues(); return true; }
|
|
245
319
|
if (item.intent === 'comment') {
|
|
246
320
|
var commented = Object.assign({}, msg.result, { canDelete: msg.canDelete === true });
|
|
@@ -264,7 +338,15 @@ export function handleIssuesMessage(msg) {
|
|
|
264
338
|
status: panel().querySelector('[name="filter-status"]').value, type: panel().querySelector('[name="filter-type"]').value });
|
|
265
339
|
store.set({ issuesRows: result.issues }); content().innerHTML = renderIssueList(result); refreshIcons();
|
|
266
340
|
} else if (item.intent === 'history') { content().innerHTML = renderIssueHistory(msg.result); refreshIcons(); }
|
|
267
|
-
else if (item.intent === 'revision') { content().innerHTML = '<p class="issues-history-note">Historical revision · use Back to return to current issues</p>' + renderIssueDetail(msg.result); content().querySelectorAll('.issue-actions, [data-issue-session]').forEach(function (node) { node.remove(); }); refreshIcons(); }
|
|
268
|
-
else
|
|
341
|
+
else if (item.intent === 'revision') { content().innerHTML = '<p class="issues-history-note">Historical revision · use Back to return to current issues</p>' + renderIssueDetail(msg.result); content().querySelectorAll('.issue-actions, [data-issue-session], .issue-discussion form').forEach(function (node) { node.remove(); }); enhanceClayLogLinks(content()); refreshIcons(); }
|
|
342
|
+
else if (item.intent === 'save') {
|
|
343
|
+
detail(Object.assign({}, msg.result, { canDelete: msg.canDelete === true }));
|
|
344
|
+
} else {
|
|
345
|
+
var available = store.get('issuesUpdateAvailable');
|
|
346
|
+
if (available && available.ref === msg.result.ref && Number(msg.result.revision || 0) >= Number(available.revision || 0)) clearUpdateNotice();
|
|
347
|
+
var refreshed = Object.assign({}, msg.result, { canDelete: msg.canDelete === true });
|
|
348
|
+
detail(refreshed);
|
|
349
|
+
if (canRestoreCommentDraft(item.draft, msg.result.ref) && (item.intent === 'detail' || item.intent === 'refresh')) restoreCommentDraft(item.draft);
|
|
350
|
+
}
|
|
269
351
|
return true;
|
|
270
352
|
}
|
|
@@ -2,7 +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
|
+
import { enhanceClayLogLinks, renderClayRecordLink } from './clay-log-links.js';
|
|
6
6
|
import { renderLocalFileLink, enhanceClayFileLinks } from './clay-file-links.js';
|
|
7
7
|
|
|
8
8
|
// Initialize markdown parser
|
|
@@ -12,6 +12,8 @@ marked.use({ gfm: true, breaks: false });
|
|
|
12
12
|
marked.use({
|
|
13
13
|
renderer: {
|
|
14
14
|
link({ href, title, text }) {
|
|
15
|
+
var recordLink = renderClayRecordLink(href, text);
|
|
16
|
+
if (recordLink) return recordLink;
|
|
15
17
|
var localLink = renderLocalFileLink(href, title, text);
|
|
16
18
|
if (localLink) return localLink;
|
|
17
19
|
var isExternal = href && (href.startsWith('http://') || href.startsWith('https://'));
|
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
// Same-origin messages only, both directions.
|
|
5
5
|
|
|
6
6
|
import { store } from './store.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getEffectiveContextView } from './context-view-preference.js';
|
|
8
8
|
import { forceExternalLinkToNewTab } from './pane-links.js';
|
|
9
9
|
|
|
10
|
-
var panelOpen = false;
|
|
11
|
-
|
|
12
10
|
export function reportPaneContext(data) {
|
|
13
11
|
if (!store.get('paneMode') || window.parent === window) return;
|
|
14
12
|
window.parent.postMessage({
|
|
@@ -38,11 +36,17 @@ export function forwardPaneMarkdownPresentation(message) {
|
|
|
38
36
|
return true;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
export function forwardPaneIssueReference(ref, projectSlug) {
|
|
40
|
+
if (!store.get('paneMode') || window.parent === window) return false;
|
|
41
|
+
window.parent.postMessage({ type: "clay-pane-open-issue", ref: ref, projectSlug: projectSlug || store.get('currentSlug') }, window.location.origin);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Toggle without setContextView: panes use a transient override and must not
|
|
46
|
+
// rewrite the user's server-persisted main-view preference.
|
|
43
47
|
function togglePaneContextPanel() {
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
var effectiveView = getEffectiveContextView();
|
|
49
|
+
store.set({ contextViewOverride: effectiveView === "panel" ? "off" : "panel" });
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
function preparePaneLink(event) {
|
|
@@ -55,7 +59,9 @@ function preparePaneLink(event) {
|
|
|
55
59
|
|
|
56
60
|
export function initPaneBridge() {
|
|
57
61
|
if (!store.get('paneMode')) return;
|
|
58
|
-
|
|
62
|
+
// Hydration may still replace the persisted mode. Leave the override null
|
|
63
|
+
// so the first header toggle derives from the actual effective view.
|
|
64
|
+
store.set({ contextViewOverride: null });
|
|
59
65
|
document.addEventListener("click", preparePaneLink, true);
|
|
60
66
|
document.addEventListener("auxclick", preparePaneLink, true);
|
|
61
67
|
window.addEventListener("message", function (event) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { openIssues } from './issues.js';
|
|
3
|
+
|
|
4
|
+
function isIssueReference(ref) {
|
|
5
|
+
return typeof ref === "string" && /^issue:[A-Za-z0-9_-]{24}$/.test(ref);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function isPaneSource(host, source) {
|
|
9
|
+
var frames = host.querySelectorAll(".split-pane-frame");
|
|
10
|
+
for (var i = 0; i < frames.length; i++) {
|
|
11
|
+
if (frames[i].contentWindow === source) return true;
|
|
12
|
+
}
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function handlePaneIssueMessage(event, host) {
|
|
17
|
+
if (!event || event.origin !== window.location.origin || !host) return false;
|
|
18
|
+
var msg = event.data;
|
|
19
|
+
if (!msg || msg.type !== "clay-pane-open-issue") return false;
|
|
20
|
+
if (!isIssueReference(msg.ref) || !isPaneSource(host, event.source)) return true;
|
|
21
|
+
|
|
22
|
+
var issueTarget = store.get('issuesCache') && store.get('issuesCache')[msg.ref];
|
|
23
|
+
var projectSlug = (issueTarget && issueTarget.projectSlug) || msg.projectSlug || store.get('currentSlug');
|
|
24
|
+
if (projectSlug === store.get('currentSlug')) openIssues(msg.ref);
|
|
25
|
+
else window.location.assign('/p/' + encodeURIComponent(projectSlug) + '/?issue=' + encodeURIComponent(msg.ref));
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
|
|
3
|
+
export function applyPermissionHints(container, hints) {
|
|
4
|
+
if (!container || !hints) return;
|
|
5
|
+
if (hints.suppressAlwaysAllowRule) {
|
|
6
|
+
var persistent = container.querySelectorAll('.permission-allow-session, .mate-permission-always, .notif-banner-always');
|
|
7
|
+
persistent.forEach(function(button) { button.remove(); });
|
|
8
|
+
}
|
|
9
|
+
if (hints.defaultToNo && !store.get('replayingHistory')) {
|
|
10
|
+
var decline = container.querySelector('.permission-deny, .mate-permission-deny, .notif-banner-deny');
|
|
11
|
+
if (decline) decline.focus({ preventScroll: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// User profile module — profile popovers for people and Mates
|
|
2
2
|
// Stores profile server-side in ~/.clay/profile.json
|
|
3
|
-
// Generated identities use deterministic
|
|
3
|
+
// Generated identities use deterministic local pixel identicons.
|
|
4
4
|
|
|
5
5
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
6
6
|
import { setSTTLang } from './stt.js';
|
|
7
|
-
import { avatarUrl, mateAvatarUrl, mateMarkUrl } from './avatar.js';
|
|
7
|
+
import { avatarUrl, mateAvatarUrl, mateMarkUrl, userAvatarSeed } from './avatar.js';
|
|
8
8
|
import { store } from './store.js';
|
|
9
9
|
import { applyTerminalFont } from './terminal-prefs.js';
|
|
10
10
|
|
|
@@ -15,7 +15,7 @@ var popoverEl = null;
|
|
|
15
15
|
var saveTimer = null;
|
|
16
16
|
|
|
17
17
|
function getAvatarSeed() {
|
|
18
|
-
return profile.avatarSeed || 'anonymous';
|
|
18
|
+
return profile.avatarSeed || String(profileUsername || 'anonymous').substring(0, 30);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// --- API ---
|
|
@@ -307,14 +307,14 @@ function showPopover() {
|
|
|
307
307
|
html += '<input type="text" class="profile-field-input" id="profile-name-input" value="' + escapeAttr(displayName) + '" placeholder="Enter your name..." maxlength="50" spellcheck="false" autocomplete="off">';
|
|
308
308
|
html += '</div>';
|
|
309
309
|
|
|
310
|
-
//
|
|
310
|
+
// Pixel identicon or custom photo
|
|
311
311
|
html += '<div class="profile-field">';
|
|
312
312
|
html += '<label class="profile-field-label">Identity</label>';
|
|
313
313
|
html += '<div class="profile-imprint-panel">';
|
|
314
|
-
html += '<div class="profile-imprint-preview"><img src="' + avatarUrl(currentStyle, seed, 72) + '" alt="Generated
|
|
314
|
+
html += '<div class="profile-imprint-preview"><img src="' + avatarUrl(currentStyle, seed, 72) + '" alt="Generated pixel avatar"><span><strong>Pixel avatar</strong><small>Unique to this identity</small></span></div>';
|
|
315
315
|
html += '<div class="profile-imprint-actions">';
|
|
316
|
-
html += '<button class="profile-use-imprint' + (!profile.avatarCustom ? ' is-active' : '') + '" type="button">Use
|
|
317
|
-
html += '<button class="profile-regenerate-imprint" type="button" title="Generate another
|
|
316
|
+
html += '<button class="profile-use-imprint' + (!profile.avatarCustom ? ' is-active' : '') + '" type="button">Use pixel avatar</button>';
|
|
317
|
+
html += '<button class="profile-regenerate-imprint" type="button" title="Generate another pixel avatar">' + iconHtml('shuffle') + ' New avatar</button>';
|
|
318
318
|
html += '<button class="profile-avatar-upload' + (profile.avatarCustom ? ' is-active' : '') + '" type="button">Upload photo</button>';
|
|
319
319
|
html += '</div>';
|
|
320
320
|
html += '</div>';
|
|
@@ -511,9 +511,9 @@ export function initProfile(_ctx) {
|
|
|
511
511
|
if (data.avatarCustom) profile.avatarCustom = data.avatarCustom;
|
|
512
512
|
if (data.username) profileUsername = data.username;
|
|
513
513
|
|
|
514
|
-
//
|
|
514
|
+
// Persist a stable account identity only when the server has not saved a seed.
|
|
515
515
|
if (!profile.avatarSeed) {
|
|
516
|
-
profile.avatarSeed =
|
|
516
|
+
profile.avatarSeed = userAvatarSeed(data);
|
|
517
517
|
saveProfile();
|
|
518
518
|
}
|
|
519
519
|
|
|
@@ -43,3 +43,52 @@ export function isProjectContextConnected(state, slug, socket) {
|
|
|
43
43
|
&& state.connected === true
|
|
44
44
|
&& state.activeProjectSlug === slug;
|
|
45
45
|
}
|
|
46
|
+
|
|
47
|
+
export function isProjectSessionReady(state, socket) {
|
|
48
|
+
var target = projectWsPath(state.currentSlug);
|
|
49
|
+
return !!socket
|
|
50
|
+
&& socket.readyState === 1
|
|
51
|
+
&& state.connected === true
|
|
52
|
+
&& !!state.currentSlug
|
|
53
|
+
&& state.wsPath === target
|
|
54
|
+
&& state.socketPath === target
|
|
55
|
+
&& state.activeProjectSlug === state.currentSlug
|
|
56
|
+
&& state.sessionActivatedProjectSlug === state.currentSlug
|
|
57
|
+
&& state.sessionListProjectSlug === state.currentSlug
|
|
58
|
+
&& state.splitGroupsProjectSlug === state.currentSlug;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function splitPanesMatchProject(split, slug) {
|
|
62
|
+
if (!split || !Array.isArray(split.panes) || split.panes.length !== 2 || !slug) return false;
|
|
63
|
+
return split.panes[0].slug === slug && split.panes[1].slug === slug;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function hasCurrentSplitGroup(split, groups) {
|
|
67
|
+
if (!split || !split.groupId) return true;
|
|
68
|
+
var members = split.panes && split.panes.map(function (pane) { return pane.sessionId; });
|
|
69
|
+
if (!members || members.length !== 2) return false;
|
|
70
|
+
var available = Array.isArray(groups) ? groups : [];
|
|
71
|
+
for (var i = 0; i < available.length; i++) {
|
|
72
|
+
var group = available[i];
|
|
73
|
+
if (group.id !== split.groupId || !Array.isArray(group.members) || group.members.length !== 2) continue;
|
|
74
|
+
if (group.members.indexOf(members[0]) !== -1 && group.members.indexOf(members[1]) !== -1) return true;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function reconcileRestoredSplit(split, groups) {
|
|
80
|
+
if (!split || !split.groupId) return { action: "none" };
|
|
81
|
+
var available = Array.isArray(groups) ? groups : [];
|
|
82
|
+
for (var i = 0; i < available.length; i++) {
|
|
83
|
+
var group = available[i];
|
|
84
|
+
if (group.id !== split.groupId) continue;
|
|
85
|
+
var oldMembers = split.panes && split.panes.map(function (pane) { return pane.sessionId; });
|
|
86
|
+
var members = Array.isArray(group.members) ? group.members : [];
|
|
87
|
+
if (oldMembers && oldMembers.length === 2 && members.length === 2
|
|
88
|
+
&& members.indexOf(oldMembers[0]) !== -1 && members.indexOf(oldMembers[1]) !== -1) {
|
|
89
|
+
return { action: "keep", group: group };
|
|
90
|
+
}
|
|
91
|
+
return { action: "rebuild", group: group };
|
|
92
|
+
}
|
|
93
|
+
return { action: "clear" };
|
|
94
|
+
}
|
|
@@ -43,8 +43,9 @@ function actorLabel(actor) {
|
|
|
43
43
|
// object (userId, sessionKey, vendor), but a session-authored entry must not
|
|
44
44
|
// read as if the human owner wrote it: the Project Driver did. The owner is
|
|
45
45
|
// only the account the session ran under.
|
|
46
|
-
function authorLine(entry) {
|
|
46
|
+
export function authorLine(entry) {
|
|
47
47
|
var actor = (entry && (entry.updatedBy || entry.createdBy)) || {};
|
|
48
|
+
if (actor.type === "system") return actor.displayName || "Scheduled Tasks";
|
|
48
49
|
if (actor.type === "session") {
|
|
49
50
|
return actor.vendor ? "Project Driver (" + vendorLabel(actor.vendor) + ")" : "Project Driver";
|
|
50
51
|
}
|
|
@@ -62,7 +63,7 @@ function formatTime(value) {
|
|
|
62
63
|
catch (e) { return ""; }
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
function relativeTime(value) {
|
|
66
|
+
export function relativeTime(value) {
|
|
66
67
|
if (!value) return "";
|
|
67
68
|
var delta = Date.now() - value;
|
|
68
69
|
if (delta < 0) return formatTime(value);
|
|
@@ -363,7 +364,7 @@ export function renderDetail(detailEl, entry, handlers) {
|
|
|
363
364
|
formatTime(entry.updatedAt || entry.createdAt),
|
|
364
365
|
].filter(Boolean).join(" · ");
|
|
365
366
|
setText(header, ".project-log-byline", byline);
|
|
366
|
-
if (handlers && handlers.canDelete) {
|
|
367
|
+
if (handlers && handlers.canDelete && !entry.scheduledResult) {
|
|
367
368
|
var deleteButton = document.createElement("button");
|
|
368
369
|
deleteButton.type = "button";
|
|
369
370
|
deleteButton.className = "project-log-delete-button";
|
|
@@ -373,6 +374,15 @@ export function renderDetail(detailEl, entry, handlers) {
|
|
|
373
374
|
}
|
|
374
375
|
article.appendChild(header);
|
|
375
376
|
|
|
377
|
+
if (entry.scheduledResult && entry.scheduledResult.driverOriginId && handlers && handlers.onOpenSession) {
|
|
378
|
+
var sessionButton = document.createElement("button");
|
|
379
|
+
sessionButton.type = "button";
|
|
380
|
+
sessionButton.className = "project-log-session-button";
|
|
381
|
+
sessionButton.innerHTML = '<i data-lucide="external-link"></i><span>View session</span>';
|
|
382
|
+
sessionButton.addEventListener("click", function () { handlers.onOpenSession(entry); });
|
|
383
|
+
article.appendChild(sessionButton);
|
|
384
|
+
}
|
|
385
|
+
|
|
376
386
|
var markdown = document.createElement("div");
|
|
377
387
|
markdown.className = "project-log-markdown md-content";
|
|
378
388
|
markdown.innerHTML = renderMarkdown(entry.body || "");
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Project Logs — the project's ledger of agent-authored decisions and work.
|
|
2
|
-
//
|
|
3
2
|
// A bounded right-side workbench pane, like the document viewer and terminal.
|
|
4
3
|
// Inside it there is one navigation stack rather than a master/detail split:
|
|
5
4
|
// the list fills the pane, opening an entry replaces it with the full-pane
|
|
@@ -14,10 +13,10 @@ import { store } from './store.js';
|
|
|
14
13
|
import { getWs } from './ws-ref.js';
|
|
15
14
|
import { refreshIcons } from './icons.js';
|
|
16
15
|
import { showToast } from './utils.js';
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
16
|
+
import { showScheduledResultNotification } from './scheduled-result-notification.js';
|
|
17
|
+
import * as scheduledResultClient from './scheduled-result-client.js';
|
|
18
|
+
import { clearPendingScheduledResultRead, handleScheduledResultDisplayed, initScheduledResultDisplay, openScheduledResultSession } from './scheduled-result-display.js';
|
|
19
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
21
20
|
import { renderFilter, renderContextFilter, renderList, renderDetail } from './project-logs-render.js';
|
|
22
21
|
|
|
23
22
|
var panel = null;
|
|
@@ -35,7 +34,7 @@ var requestCounter = 0;
|
|
|
35
34
|
function syncUnreadBadge() {
|
|
36
35
|
var button = document.getElementById("project-logs-btn");
|
|
37
36
|
if (!button) return;
|
|
38
|
-
var unread = store.get('projectLogsUnread') || 0;
|
|
37
|
+
var unread = (store.get('projectLogsUnread') || 0) + (store.get('scheduledResultUnread') || 0);
|
|
39
38
|
button.classList.toggle("project-logs-unread", unread > 0);
|
|
40
39
|
var badge = document.getElementById("project-logs-count");
|
|
41
40
|
if (!badge) return;
|
|
@@ -164,10 +163,10 @@ function applyWindowState(wide, fullscreen) {
|
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
// --- Navigation stack ----------------------------------------------------
|
|
167
|
-
|
|
168
166
|
function showList() {
|
|
169
167
|
if (!panel) return;
|
|
170
168
|
store.set({ projectLogsView: "list", projectLogsSelectedRef: null });
|
|
169
|
+
clearPendingScheduledResultRead();
|
|
171
170
|
detailEl.classList.add("hidden");
|
|
172
171
|
listEl.classList.remove("hidden");
|
|
173
172
|
panel.querySelector("#project-logs-toolbar").classList.remove("hidden");
|
|
@@ -188,8 +187,10 @@ function showDetail(entry) {
|
|
|
188
187
|
renderDetail(detailEl, entry, {
|
|
189
188
|
onComment: submitComment,
|
|
190
189
|
onDelete: openDeleteDialog,
|
|
190
|
+
onOpenSession: openScheduledResultSession,
|
|
191
191
|
canDelete: store.get('projectLogsCanDelete') === true,
|
|
192
192
|
});
|
|
193
|
+
handleScheduledResultDisplayed(entry);
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
function openDeleteDialog(entry) {
|
|
@@ -230,7 +231,6 @@ function confirmDelete() {
|
|
|
230
231
|
}
|
|
231
232
|
|
|
232
233
|
// --- Requests ------------------------------------------------------------
|
|
233
|
-
|
|
234
234
|
function requestList() {
|
|
235
235
|
var requestId = nextRequestId("list-logs");
|
|
236
236
|
store.set({ projectLogsListRequestId: requestId });
|
|
@@ -282,14 +282,17 @@ function applyContextFilter(contextMode) {
|
|
|
282
282
|
// --- Lifecycle -----------------------------------------------------------
|
|
283
283
|
|
|
284
284
|
export function initProjectLogs() {
|
|
285
|
+
registerRightWorkbench("project-logs", closeProjectLogs);
|
|
285
286
|
var button = document.getElementById("project-logs-btn");
|
|
286
287
|
if (!button) return;
|
|
287
288
|
button.addEventListener("click", function () {
|
|
288
289
|
if (store.get('projectLogsOpen')) closeProjectLogs();
|
|
289
290
|
else openProjectLogs();
|
|
290
291
|
});
|
|
292
|
+
initScheduledResultDisplay();
|
|
291
293
|
syncUnreadBadge();
|
|
292
294
|
store.subscribe(function (state, previous) {
|
|
295
|
+
if (state.connected === false && previous.connected === true) store.set({ scheduledResultAutoFocusHandled: false });
|
|
293
296
|
if (state.currentSlug === previous.currentSlug) return;
|
|
294
297
|
closeProjectLogs();
|
|
295
298
|
applyWindowState(false, false);
|
|
@@ -309,7 +312,13 @@ export function initProjectLogs() {
|
|
|
309
312
|
projectLogsDeleteRequestId: null,
|
|
310
313
|
projectLogsCanDelete: false,
|
|
311
314
|
projectLogsUnread: 0,
|
|
312
|
-
projectLogsSeenRevisions: {}
|
|
315
|
+
projectLogsSeenRevisions: {},
|
|
316
|
+
scheduledResultUnread: 0,
|
|
317
|
+
scheduledResultAutoFocusHandled: false,
|
|
318
|
+
scheduledResultFocusRef: null,
|
|
319
|
+
scheduledResultPendingReadRef: null,
|
|
320
|
+
scheduledResultSeenRefs: {},
|
|
321
|
+
scheduledResultSessionRequestId: null,
|
|
313
322
|
});
|
|
314
323
|
syncUnreadBadge();
|
|
315
324
|
});
|
|
@@ -320,11 +329,7 @@ export function initProjectLogs() {
|
|
|
320
329
|
export function openProjectLogs() {
|
|
321
330
|
ensurePanel();
|
|
322
331
|
if (!panel) return;
|
|
323
|
-
|
|
324
|
-
closeNotesBrowser();
|
|
325
|
-
// Claim the single right workbench slot.
|
|
326
|
-
try { closeFileViewer(); } catch (e) {}
|
|
327
|
-
try { closeTerminal(); } catch (e) {}
|
|
332
|
+
claimRightWorkbench("project-logs");
|
|
328
333
|
panel.classList.remove("hidden");
|
|
329
334
|
applyWindowState(store.get('projectLogsWide'), false);
|
|
330
335
|
var button = document.getElementById("project-logs-btn");
|
|
@@ -344,6 +349,7 @@ export function openProjectLog(ref) {
|
|
|
344
349
|
}
|
|
345
350
|
|
|
346
351
|
export function closeProjectLogs() {
|
|
352
|
+
releaseRightWorkbench("project-logs");
|
|
347
353
|
if (!store.get('projectLogsOpen')) return;
|
|
348
354
|
if (panel) {
|
|
349
355
|
panel.classList.add("hidden");
|
|
@@ -384,11 +390,35 @@ export function handleProjectLogsState(msg) {
|
|
|
384
390
|
|
|
385
391
|
export function handleProjectLogEntry(msg) {
|
|
386
392
|
if (msg.requestId && msg.requestId !== store.get('projectLogsReadRequestId')) return;
|
|
387
|
-
if (!msg.entry) return;
|
|
393
|
+
if (!msg.entry || !store.get('projectLogsOpen')) return;
|
|
388
394
|
store.set({ projectLogsCanDelete: msg.canDelete === true });
|
|
389
395
|
showDetail(msg.entry);
|
|
390
396
|
}
|
|
391
397
|
|
|
398
|
+
export function handleScheduledTaskResultsState(msg) {
|
|
399
|
+
var transition = scheduledResultClient.scheduledResultStateTransition(store.snap(), msg);
|
|
400
|
+
store.set(transition.patch); syncUnreadBadge();
|
|
401
|
+
if (!transition.openRef) return false;
|
|
402
|
+
var opened = openProjectLog(transition.openRef);
|
|
403
|
+
if (!opened) store.set({ scheduledResultFocusRef: null });
|
|
404
|
+
return opened;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export function handleScheduledTaskResultCreated(msg) {
|
|
408
|
+
var patch = scheduledResultClient.scheduledResultCreatedTransition(store.snap(), msg);
|
|
409
|
+
if (!patch) return false;
|
|
410
|
+
store.set(patch); syncUnreadBadge();
|
|
411
|
+
showScheduledResultNotification(msg.title || "Scheduled task finished", msg.summary || "", function () { openProjectLog(msg.ref); });
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export function handleScheduledTaskResultSession(msg) {
|
|
416
|
+
var sessionId = scheduledResultClient.scheduledResultSessionId(store.snap(), msg);
|
|
417
|
+
if (sessionId === null) return false;
|
|
418
|
+
store.set({ scheduledResultSessionRequestId: null });
|
|
419
|
+
return send({ type: "switch_session", id: sessionId });
|
|
420
|
+
}
|
|
421
|
+
|
|
392
422
|
export function handleProjectLogCommented(msg) {
|
|
393
423
|
if (msg.requestId && msg.requestId !== store.get('projectLogsCommentRequestId')) return;
|
|
394
424
|
if (!msg.entry) return;
|