clay-server 5.2.0-beta.4 → 5.2.0-beta.5
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/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +17 -14
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +28 -0
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +96 -0
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +73 -221
- package/lib/issues-service.js +29 -15
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +4 -3
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-users.js +20 -5
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +4 -2
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +13 -87
- package/lib/project-full-access.js +23 -2
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pending-message-queue.js +1 -0
- package/lib/project-product-control.js +87 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +63 -64
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-github.js +47 -12
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +64 -241
- package/lib/project-user-message.js +30 -1
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +95 -3
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +3 -0
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +1 -1
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +18 -3
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +1 -1
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/home-debate-planning.js +5 -5
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +65 -8
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +71 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +34 -6
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/style.css +2 -0
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +220 -21
- package/lib/sdk-message-processor.js +2 -0
- package/lib/sdk-response-recovery.js +155 -0
- package/lib/server-admin.js +63 -16
- package/lib/server-dm.js +1 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +7 -3
- package/lib/server-home-chat.js +4 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +3 -3
- package/lib/server-mates.js +25 -67
- package/lib/server-owner-ui.js +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server.js +99 -29
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-github.js +9 -2
- package/lib/session-visibility.js +5 -2
- package/lib/sessions.js +10 -0
- package/lib/terminal-manager.js +52 -0
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/adapters/claude.js +47 -8
- package/lib/yoke/adapters/codex.js +52 -111
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/session-tool-transport.js +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// This page never opens a project socket or loads project content modules.
|
|
2
|
+
var tabs = Array.from(document.querySelectorAll('[role="tab"]'));
|
|
3
|
+
function select(tab) {
|
|
4
|
+
tabs.forEach(function (item) {
|
|
5
|
+
var active = item === tab;
|
|
6
|
+
item.setAttribute("aria-selected", String(active));
|
|
7
|
+
item.tabIndex = active ? 0 : -1;
|
|
8
|
+
document.getElementById(item.getAttribute("aria-controls")).hidden = !active;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
tabs.forEach(function (tab, index) {
|
|
12
|
+
tab.addEventListener("click", function () { select(tab); });
|
|
13
|
+
tab.addEventListener("keydown", function (event) {
|
|
14
|
+
if (["ArrowLeft", "ArrowRight", "Home", "End"].indexOf(event.key) === -1) return;
|
|
15
|
+
event.preventDefault();
|
|
16
|
+
var target = event.key === "Home" ? tabs[0] : event.key === "End" ? tabs[tabs.length - 1] : tabs[(index + (event.key === "ArrowRight" ? 1 : tabs.length - 1)) % tabs.length];
|
|
17
|
+
select(target);
|
|
18
|
+
target.focus();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
select(tabs[0]);
|
|
@@ -9,7 +9,7 @@ export var FAVORITES_ID = "favorites";
|
|
|
9
9
|
export var UNFILED_ID = "unfiled";
|
|
10
10
|
|
|
11
11
|
export function defaultFolderState() {
|
|
12
|
-
return { folders: [], assignments: {}, favorites: [], orders: {}, collapsed: {}, view: { sort: "activity", direction: "desc" } };
|
|
12
|
+
return { folders: [], assignments: {}, favorites: [], orders: {}, collapsed: {}, view: { sort: "activity", direction: "desc", showDateHeadings: true } };
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function sortUnits(units, view, order) {
|
|
@@ -78,6 +78,15 @@ export function paintViewMenu() {
|
|
|
78
78
|
note.textContent = "Drag sessions to arrange them. Favorites keep their own order.";
|
|
79
79
|
menu.appendChild(note);
|
|
80
80
|
}
|
|
81
|
+
var dateToggle = document.createElement("button");
|
|
82
|
+
dateToggle.type = "button";
|
|
83
|
+
dateToggle.className = "session-folder-view-toggle";
|
|
84
|
+
dateToggle.dataset.focusKey = "showDateHeadings";
|
|
85
|
+
dateToggle.setAttribute("role", "switch");
|
|
86
|
+
dateToggle.setAttribute("aria-checked", String(view.showDateHeadings !== false));
|
|
87
|
+
dateToggle.innerHTML = '<span>Show date headings</span><span class="session-folder-view-toggle-track" aria-hidden="true"><span class="session-folder-view-toggle-thumb"></span></span>';
|
|
88
|
+
dateToggle.addEventListener("click", function () { sendViewPatch({ showDateHeadings: view.showDateHeadings === false }); });
|
|
89
|
+
menu.appendChild(dateToggle);
|
|
81
90
|
if (focusKey) {
|
|
82
91
|
var target = menu.querySelector('[data-focus-key="' + focusKey + '"]') || menu.querySelector(".session-folder-radio.selected");
|
|
83
92
|
if (target) target.focus();
|
|
@@ -46,6 +46,28 @@ function compactGithubWork(links, sessionRow) {
|
|
|
46
46
|
var latestPr = links.find(function (link) { return link.kind === 'pr'; });
|
|
47
47
|
return [latestLinear, latestIssue, latestPr].filter(Boolean);
|
|
48
48
|
}
|
|
49
|
+
function personalScope() {
|
|
50
|
+
return String(store.get('currentSlug') || '') + '\u0000' + String(store.get('myUserId') || 'default');
|
|
51
|
+
}
|
|
52
|
+
function ensurePersonalScope() {
|
|
53
|
+
var scope = personalScope();
|
|
54
|
+
if (store.get('githubPersonalReviewScope') !== scope) store.set({ githubPersonalReviewScope: scope, githubPersonalReview: {} });
|
|
55
|
+
return scope;
|
|
56
|
+
}
|
|
57
|
+
function sessionIdentifier(session) {
|
|
58
|
+
return String(session && (session._githubSessionId || session.id || session.localId) || '');
|
|
59
|
+
}
|
|
60
|
+
function personalReviewState(session, link) {
|
|
61
|
+
var scope = ensurePersonalScope();
|
|
62
|
+
var key = scope + '\u0000' + (sessionIdentifier(session) || String(link._githubSessionId || '')) + '\u0000' + String(link.url || '').toLowerCase();
|
|
63
|
+
return (store.get('githubPersonalReview') || {})[key] || link.personalReviewState || '';
|
|
64
|
+
}
|
|
65
|
+
function prReviewStatus(link) {
|
|
66
|
+
if (link.review === 'CHANGES_REQUESTED') return { icon: 'message-square-warning', label: 'Changes requested', className: 'github-review-changes' };
|
|
67
|
+
if (link.personalReviewState === 'requested') return { icon: 'user-round-check', label: 'Review requested from you', className: 'github-review-requested' };
|
|
68
|
+
if (link.review === 'APPROVED') return { icon: 'badge-check', label: 'Approved', className: 'github-review-approved' };
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
49
71
|
export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
50
72
|
var links = orderedGithubWork(session);
|
|
51
73
|
if (!links.length) return '';
|
|
@@ -56,13 +78,17 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
56
78
|
return anchor(link.url, description, LINEAR_MARK + '<span class="linear-work-id">' + escapeHtml(link.identifier) + '</span><span class="linear-work-title">' + escapeHtml(link.title) + '</span><span class="linear-work-state"><span class="linear-work-state-full">' + escapeHtml(link.stateName) + '</span><span class="linear-work-state-compact" aria-hidden="true">' + escapeHtml(compactLinearState(link.stateName)) + '</span></span>', 'session-github-link session-linear-link');
|
|
57
79
|
}
|
|
58
80
|
var state = ['open', 'closed', 'merged', 'draft'].indexOf(link.state) >= 0 ? link.state : 'unknown';
|
|
59
|
-
var
|
|
60
|
-
var
|
|
61
|
-
var
|
|
81
|
+
var reviewLink = Object.assign({}, link, { personalReviewState: personalReviewState(session, link) });
|
|
82
|
+
var reviewStatus = link.kind === 'pr' && state === 'open' ? prReviewStatus(reviewLink) : null;
|
|
83
|
+
var icon = link.kind === 'pr' ? (state === 'merged' ? 'git-merge' : state === 'closed' ? 'git-pull-request-closed' : state === 'draft' ? 'git-pull-request-draft' : reviewStatus ? reviewStatus.icon : 'git-pull-request') : (state === 'closed' ? 'circle-check' : 'circle-dot');
|
|
84
|
+
var statusLabel = reviewStatus ? reviewStatus.label : (state === 'merged' ? 'Merged' : state === 'closed' ? 'Closed' : state === 'draft' ? 'Draft' : state === 'open' ? 'Open' : 'Unknown');
|
|
85
|
+
var description = link.repository + ' · ' + label(link) + ' · ' + link.title + ' · ' + statusLabel + (link.checkedAt ? ' · Checked ' + new Date(link.checkedAt).toLocaleString() : '') + (link.stale ? ' (last known status; refresh unavailable)' : '');
|
|
86
|
+
var statusMarkup = '<span class="github-work-status' + (reviewStatus ? ' ' + reviewStatus.className : '') + '" title="' + escapeHtml(statusLabel) + '" aria-label="' + escapeHtml(statusLabel) + '">' + iconHtml(icon) + '</span>';
|
|
87
|
+
var html = anchor(link.url, description, iconHtml('github', 'github-work-brand') + statusMarkup + '<span><span class="github-work-kind">' + (link.kind === 'pr' ? 'PR ' : 'Issue ') + '</span>#' + escapeHtml(String(link.number)) + '</span>', 'session-github-link github-kind-' + (link.kind === 'pr' ? 'pr' : 'issue') + ' github-state-' + state);
|
|
62
88
|
if (expanded) html = '<span class="github-work-entry">' + html + '<span class="github-work-entry-title">' + escapeHtml(link.title || '') + '</span><span class="github-work-entry-meta">' + escapeHtml(link.repository + ' · ' + state + (link.stale ? ' · Last known status' : '')) + '</span></span>';
|
|
63
89
|
if (detailed && link.kind === 'pr') {
|
|
64
90
|
var actions = anchor(link.url + '/files', 'View PR changes on GitHub', 'Changes', 'github-work-action') +
|
|
65
|
-
anchor(link.url + '#pullrequestreview', 'View PR reviews on GitHub',
|
|
91
|
+
anchor(link.url + '#pullrequestreview', 'View PR reviews on GitHub', 'Reviews', 'github-work-action');
|
|
66
92
|
var checks = '';
|
|
67
93
|
(link.checks || []).forEach(function (check) {
|
|
68
94
|
if (/^https:\/\//i.test(check.url)) checks += anchor(check.url, check.name + ': ' + check.state, '<span class="github-work-check-name">' + escapeHtml(check.name) + '</span><span class="github-work-check-state">' + escapeHtml(check.state.toLowerCase()) + '</span>', 'github-work-action github-work-check');
|
|
@@ -76,6 +102,28 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
76
102
|
return html;
|
|
77
103
|
}).join('') + (!detailed && !expanded && links.length > displayed.length ? '<button type="button" class="github-work-more" aria-label="Show all ' + links.length + ' linked ' + (links.some(function (item) { return item.provider === 'linear'; }) ? 'work items' : 'GitHub items') + '" aria-haspopup="dialog" aria-expanded="false" data-work-session="' + escapeHtml(String(session.id || session.localId || '')) + '" data-work-primary="' + escapeHtml(JSON.stringify(session.workPrimary || {})) + '" data-github-links="' + escapeHtml(JSON.stringify(safeLinks(session))) + '">+' + (links.length - displayed.length) + '</button>' : '') + '</span>';
|
|
78
104
|
}
|
|
105
|
+
export function applyGithubPersonalReviewStatus(message) {
|
|
106
|
+
var state = store.snap();
|
|
107
|
+
if (!message || (message.projectSlug && message.projectSlug !== state.currentSlug) || (message.accountId && message.accountId !== state.myUserId)) return;
|
|
108
|
+
var refresh = state.githubWorkRefresh || {};
|
|
109
|
+
if (message.requestId && refresh.requestId && message.requestId !== refresh.requestId) return;
|
|
110
|
+
var updates = message.personalReview || {};
|
|
111
|
+
var scope = ensurePersonalScope();
|
|
112
|
+
var personal = Object.assign({}, store.get('githubPersonalReview') || {});
|
|
113
|
+
var sessionId = String(message.sessionId);
|
|
114
|
+
Object.keys(updates).forEach(function (url) {
|
|
115
|
+
var status = updates[url];
|
|
116
|
+
if (status === 'requested' || status === 'clear') personal[scope + '\u0000' + sessionId + '\u0000' + url.toLowerCase()] = status;
|
|
117
|
+
});
|
|
118
|
+
store.set({ githubPersonalReview: personal });
|
|
119
|
+
var sessions = store.get('githubWorkSessions') || [];
|
|
120
|
+
sessions.forEach(function (session) {
|
|
121
|
+
if (String(session.localId) !== sessionId && String(session.id) !== sessionId) return;
|
|
122
|
+
document.querySelectorAll('[data-github-session-id="' + sessionIdentifier(session) + '"] .session-work-links-slot').forEach(function (slot) { slot.innerHTML = githubWorkMarkup(session, false, false, true); });
|
|
123
|
+
});
|
|
124
|
+
syncGithubSessions(sessions);
|
|
125
|
+
refreshIcons();
|
|
126
|
+
}
|
|
79
127
|
export function appendGithubWork(row, session, title) {
|
|
80
128
|
var html = githubWorkMarkup(session, false, false, true);
|
|
81
129
|
bindGithubWorkPopover();
|
|
@@ -94,6 +142,7 @@ export function appendGithubWork(row, session, title) {
|
|
|
94
142
|
}
|
|
95
143
|
export function syncGithubSessions(sessions) {
|
|
96
144
|
bindGithubWorkPopover();
|
|
145
|
+
ensurePersonalScope();
|
|
97
146
|
store.set({ githubWorkSessions: sessions || [], githubWorkProject: store.get('currentSlug') });
|
|
98
147
|
var active = (sessions || []).find(function (session) { return session.active; });
|
|
99
148
|
var title = document.getElementById('header-title');
|
|
@@ -132,15 +181,16 @@ export function refreshGithubWork() {
|
|
|
132
181
|
if (previous.key === key && Date.now() - previous.at < 60000) return;
|
|
133
182
|
var ws = getWs();
|
|
134
183
|
if (!ws || ws.readyState !== 1) return;
|
|
135
|
-
|
|
136
|
-
|
|
184
|
+
var requestId = 'github-refresh-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
185
|
+
store.set({ githubWorkRefresh: { key: key, at: Date.now(), requestId: requestId } });
|
|
186
|
+
ws.send(JSON.stringify({ type: 'session_github_refresh', sessionIds: ids, requestId: requestId, projectSlug: store.get('currentSlug'), accountId: store.get('myUserId') || null }));
|
|
137
187
|
var linearIds = ids.filter(function (id) { return (store.get('githubWorkSessions') || []).some(function (item) { return item.id === id && (item.linearLinks || []).length; }); });
|
|
138
188
|
if (linearIds.length) ws.send(JSON.stringify({ type: 'session_linear_refresh', sessionIds: linearIds }));
|
|
139
189
|
}
|
|
140
190
|
|
|
141
191
|
export function resetGithubWork() {
|
|
142
192
|
closeGithubWorkPopover();
|
|
143
|
-
store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null, workPrimaryPending: null });
|
|
193
|
+
store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null, githubPersonalReview: {}, workPrimaryPending: null });
|
|
144
194
|
var header = document.getElementById('header-github-links');
|
|
145
195
|
if (header) header.remove();
|
|
146
196
|
}
|
|
@@ -218,7 +268,8 @@ function bindGithubWorkPopover() {
|
|
|
218
268
|
popup.dataset.sessionId = button.dataset.workSession || '';
|
|
219
269
|
var primary;
|
|
220
270
|
try { primary = JSON.parse(button.dataset.workPrimary || '{}'); } catch (error) { primary = {}; }
|
|
221
|
-
|
|
271
|
+
var popupLinks = links.map(function (link) { return Object.assign({}, link, { _githubSessionId: popup.dataset.sessionId }); });
|
|
272
|
+
popup.innerHTML = '<strong>' + workTitle + '</strong>' + primaryWorkPopover(popupLinks, primary, popup.dataset.sessionId, githubWorkMarkup);
|
|
222
273
|
document.body.appendChild(popup);
|
|
223
274
|
mountGithubWorkPopover(popup, button);
|
|
224
275
|
button.setAttribute('aria-expanded', 'true');
|
|
@@ -238,3 +289,9 @@ function bindGithubWorkPopover() {
|
|
|
238
289
|
if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.dmMode !== previous.dmMode) closeGithubWorkPopover();
|
|
239
290
|
});
|
|
240
291
|
}
|
|
292
|
+
store.subscribe(function (state, previous) {
|
|
293
|
+
if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId) {
|
|
294
|
+
var scope = personalScope();
|
|
295
|
+
if (state.githubPersonalReviewScope !== scope) store.set({ githubPersonalReviewScope: scope, githubPersonalReview: {} });
|
|
296
|
+
}
|
|
297
|
+
});
|
|
@@ -353,7 +353,7 @@ function renderSection(section, ctx, kind) {
|
|
|
353
353
|
body.appendChild(empty);
|
|
354
354
|
}
|
|
355
355
|
var view = currentFolderState().view;
|
|
356
|
-
var dateGroups = isFavorites ? null : groupUnitsByDate(section.units, view.sort, view.direction);
|
|
356
|
+
var dateGroups = isFavorites || view.showDateHeadings === false ? null : groupUnitsByDate(section.units, view.sort, view.direction);
|
|
357
357
|
if (dateGroups) {
|
|
358
358
|
for (var g = 0; g < dateGroups.length; g++) {
|
|
359
359
|
var ids = groupRootSessionIds(dateGroups[g].units);
|
|
@@ -6,7 +6,32 @@ export function getTitle(text) {
|
|
|
6
6
|
return idx === -1 ? text : text.substring(0, idx);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function
|
|
9
|
+
function checklistFence(line) {
|
|
10
|
+
var match = line.match(/^\s*(\x60{3,}|~{3,})/);
|
|
11
|
+
return match ? match[1].charAt(0) : null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getChecklistItems(text) {
|
|
15
|
+
var lines = (text || "").split("\n").slice(1);
|
|
16
|
+
var items = [];
|
|
17
|
+
var fence = null;
|
|
18
|
+
var occurrences = Object.create(null);
|
|
19
|
+
for (var i = 0; i < lines.length; i++) {
|
|
20
|
+
var line = lines[i];
|
|
21
|
+
var nextFence = checklistFence(line);
|
|
22
|
+
if (nextFence) {
|
|
23
|
+
fence = fence === nextFence ? null : (fence || nextFence);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (fence || !line.trim() || /^\s*#{1,6}\s/.test(line)) continue;
|
|
27
|
+
var occurrence = occurrences[line] || 0;
|
|
28
|
+
occurrences[line] = occurrence + 1;
|
|
29
|
+
items.push({ index: i, text: line, key: line + "\u0000" + occurrence });
|
|
30
|
+
}
|
|
31
|
+
return items;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatInline(text, preserveChecks) {
|
|
10
35
|
var escaped = text
|
|
11
36
|
.replace(/&/g, "&")
|
|
12
37
|
.replace(/</g, "<")
|
|
@@ -17,18 +42,59 @@ function formatInline(text) {
|
|
|
17
42
|
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>")
|
|
18
43
|
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
19
44
|
.replace(/~~(.+?)~~/g, "<del>$1</del>")
|
|
20
|
-
.replace(/^(?:-\s*)?\[[xX]\]/gm, '<span class="sn-check checked" role="checkbox" aria-checked="true" tabindex="0" contenteditable="false">✓</span>')
|
|
21
|
-
.replace(/^(?:-\s*)?\[ \]/gm, '<span class="sn-check" role="checkbox" aria-checked="false" tabindex="0" contenteditable="false">☐</span>')
|
|
45
|
+
.replace(preserveChecks ? /(?!x)x^/ : /^(?:-\s*)?\[[xX]\]/gm, '<span class="sn-check checked" role="checkbox" aria-checked="true" tabindex="0" contenteditable="false">✓</span>')
|
|
46
|
+
.replace(preserveChecks ? /(?!x)x^/ : /^(?:-\s*)?\[ \]/gm, '<span class="sn-check" role="checkbox" aria-checked="false" tabindex="0" contenteditable="false">☐</span>')
|
|
22
47
|
.replace(/\n/g, "<br>");
|
|
23
48
|
}
|
|
24
49
|
|
|
25
|
-
export function renderMiniMarkdown(text) {
|
|
50
|
+
export function renderMiniMarkdown(text, options) {
|
|
26
51
|
if (!text) return "";
|
|
52
|
+
options = options || {};
|
|
27
53
|
var lines = text.split("\n");
|
|
28
54
|
var title = lines[0];
|
|
29
55
|
var body = lines.slice(1).join("\n");
|
|
30
|
-
var html = '<div class="sn-title">' + formatInline(title) + "</div>";
|
|
31
|
-
if (body.trim())
|
|
56
|
+
var html = '<div class="sn-title">' + formatInline(title, options.checklistMode) + "</div>";
|
|
57
|
+
if (body.trim()) {
|
|
58
|
+
if (options.checklistMode) {
|
|
59
|
+
var checklistLines = body.split("\n");
|
|
60
|
+
var checklistHtml = [];
|
|
61
|
+
var inCode = false;
|
|
62
|
+
var fenceChar = null;
|
|
63
|
+
var occurrences = Object.create(null);
|
|
64
|
+
for (var i = 0; i < checklistLines.length; i++) {
|
|
65
|
+
var line = checklistLines[i];
|
|
66
|
+
var lineFence = checklistFence(line);
|
|
67
|
+
if (lineFence) {
|
|
68
|
+
fenceChar = fenceChar === lineFence ? null : (fenceChar || lineFence);
|
|
69
|
+
inCode = !!fenceChar;
|
|
70
|
+
checklistHtml.push(formatInline(line, true));
|
|
71
|
+
if (i < checklistLines.length - 1) checklistHtml.push("<br>");
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
var task = !inCode && line.trim() && !/^\s*#{1,6}\s/.test(line);
|
|
75
|
+
if (task) {
|
|
76
|
+
var markerMatch = line.match(/^\s*(?:[-*+]\s+)?\[([ xX])\]\s*(.*)$/);
|
|
77
|
+
var checked = markerMatch ? markerMatch[1].toLowerCase() === "x" : false;
|
|
78
|
+
var content = markerMatch ? markerMatch[2] : line;
|
|
79
|
+
var occurrence = occurrences[line] || 0;
|
|
80
|
+
occurrences[line] = occurrence + 1;
|
|
81
|
+
var checklistKey = line + "\u0000" + occurrence;
|
|
82
|
+
var stored = options.checklistState && options.checklistState[checklistKey];
|
|
83
|
+
if (stored === undefined && Array.isArray(options.checklistState)) stored = options.checklistState[i];
|
|
84
|
+
if (stored !== undefined) {
|
|
85
|
+
checked = stored === true;
|
|
86
|
+
}
|
|
87
|
+
checklistHtml.push('<span class="sn-check' + (checked ? ' checked' : '') + '" data-checklist-index="' + i + '" data-checklist-key="' + encodeURIComponent(checklistKey) + '" role="checkbox" aria-checked="' + (checked ? 'true' : 'false') + '" tabindex="0" contenteditable="false">' + (checked ? "✓" : "☐") + "</span>" + formatInline(content));
|
|
88
|
+
} else {
|
|
89
|
+
checklistHtml.push(formatInline(line, !!inCode));
|
|
90
|
+
}
|
|
91
|
+
if (i < checklistLines.length - 1) checklistHtml.push("<br>");
|
|
92
|
+
}
|
|
93
|
+
html += checklistHtml.join("");
|
|
94
|
+
} else {
|
|
95
|
+
html += formatInline(body);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
32
98
|
return html;
|
|
33
99
|
}
|
|
34
100
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getNoteUiState, clearNoteTextTimer, setNoteUiState } from './sticky-notes-shared.js';
|
|
2
|
+
import { checklistDraftText } from './sticky-notes-checklist.js';
|
|
3
|
+
import { mutateChecklist } from './sticky-notes-state.js';
|
|
4
|
+
import { confirmSettingsAction } from './server-settings-state.js';
|
|
5
|
+
|
|
6
|
+
export function requestChecklistMode(id) {
|
|
7
|
+
var ui = getNoteUiState(id);
|
|
8
|
+
if (ui.pending.length) return;
|
|
9
|
+
if (ui.checklistMode) { mutateChecklist(id, { checklistMode: false }); return; }
|
|
10
|
+
var text = ui.text;
|
|
11
|
+
var canonical = ui.canonical;
|
|
12
|
+
function convert() {
|
|
13
|
+
var current = getNoteUiState(id);
|
|
14
|
+
if (current.text !== text || current.canonical.text !== canonical.text || current.canonical.updatedAt !== canonical.updatedAt) {
|
|
15
|
+
setNoteUiState(id, { error: 'The note changed. Review its contents and try again.' }); return;
|
|
16
|
+
}
|
|
17
|
+
clearNoteTextTimer(id);
|
|
18
|
+
mutateChecklist(id, { checklistConvert: true, checklistSource: canonical.text, checklistVersion: canonical.updatedAt });
|
|
19
|
+
}
|
|
20
|
+
if (text.trim() || (canonical.text || '').trim()) {
|
|
21
|
+
confirmSettingsAction('Switching to a checklist will delete all existing contents and completion marks. Continue with an empty checklist?', 'Delete contents and create checklist', convert);
|
|
22
|
+
} else convert();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function closeHumanNote(id, fallback) {
|
|
26
|
+
var ui = getNoteUiState(id, fallback);
|
|
27
|
+
if (ui.pending.length) { setNoteUiState(id, { error: 'Wait for the current save before closing.' }); return; }
|
|
28
|
+
clearNoteTextTimer(id);
|
|
29
|
+
mutateChecklist(id, { humanClose: true, expectedText: ui.canonical.text, expectedVersion: ui.canonical.updatedAt, draftText: ui.checklistMode ? checklistDraftText(ui) : ui.text });
|
|
30
|
+
}
|
|
@@ -11,7 +11,8 @@ import { store } from './store.js';
|
|
|
11
11
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
12
12
|
import { renderMiniMarkdown, getTitle } from './sticky-note-markdown.js';
|
|
13
13
|
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
14
|
-
import {
|
|
14
|
+
import { closeHumanNote } from './sticky-notes-actions.js';
|
|
15
|
+
import { send, getNoteUiState } from './sticky-notes-shared.js';
|
|
15
16
|
import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
|
|
16
17
|
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
17
18
|
|
|
@@ -215,7 +216,7 @@ function buildCard(data) {
|
|
|
215
216
|
action.setAttribute("aria-label", "Close note: " + title.textContent);
|
|
216
217
|
action.addEventListener("click", function (e) {
|
|
217
218
|
e.stopPropagation();
|
|
218
|
-
|
|
219
|
+
closeHumanNote(data.id, data);
|
|
219
220
|
});
|
|
220
221
|
}
|
|
221
222
|
header.appendChild(action);
|
|
@@ -229,6 +230,11 @@ function buildCard(data) {
|
|
|
229
230
|
enhanceClayLogLinks(body);
|
|
230
231
|
}
|
|
231
232
|
card.appendChild(body);
|
|
233
|
+
var ui = getNoteUiState(data.id, data);
|
|
234
|
+
if (ui.error || ui.pending.length) {
|
|
235
|
+
var status = document.createElement('p'); status.className = 'settings-status'; status.setAttribute('role', 'status');
|
|
236
|
+
status.textContent = ui.error || 'Saving…'; card.appendChild(status);
|
|
237
|
+
}
|
|
232
238
|
|
|
233
239
|
if (closed) {
|
|
234
240
|
var deleteAction = document.createElement("button");
|
|
@@ -427,6 +433,7 @@ export function initNotesBrowser() {
|
|
|
427
433
|
closeNotesBrowser();
|
|
428
434
|
});
|
|
429
435
|
store.subscribe(function (state, previous) {
|
|
436
|
+
if (state.notesBrowserOpen && state.stickyNoteUiState !== previous.stickyNoteUiState) renderNotesBrowser();
|
|
430
437
|
if (state.connected !== previous.connected || state.myUserId !== previous.myUserId ||
|
|
431
438
|
state.isMultiUserMode !== previous.isMultiUserMode) closeDeleteDialog(true);
|
|
432
439
|
if (state.currentSlug === previous.currentSlug) return;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
clampSize,
|
|
18
18
|
debouncedUpdate,
|
|
19
19
|
} from './sticky-notes-shared.js';
|
|
20
|
+
import { closeHumanNote } from './sticky-notes-actions.js';
|
|
20
21
|
import { setupTextEdit } from './sticky-notes-editor.js';
|
|
21
22
|
|
|
22
23
|
var colorPickerEl = null;
|
|
@@ -74,7 +75,7 @@ export function renderNote(data) {
|
|
|
74
75
|
addBtn.innerHTML = iconHtml("plus");
|
|
75
76
|
addBtn.addEventListener("click", function (e) {
|
|
76
77
|
e.stopPropagation();
|
|
77
|
-
|
|
78
|
+
send({ type: "note_create", x: 60, y: 60, color: "purple", opacity: 0.64 });
|
|
78
79
|
});
|
|
79
80
|
header.appendChild(addBtn);
|
|
80
81
|
|
|
@@ -90,6 +91,21 @@ export function renderNote(data) {
|
|
|
90
91
|
mdBtn.innerHTML = "<span class='sn-md-label'>MD</span>";
|
|
91
92
|
header.appendChild(mdBtn);
|
|
92
93
|
|
|
94
|
+
var checklistBtn = document.createElement("button");
|
|
95
|
+
checklistBtn.className = "sticky-note-btn sticky-note-checklist-btn";
|
|
96
|
+
checklistBtn.title = data.checklistMode ? "Show note" : "Show checklist";
|
|
97
|
+
checklistBtn.setAttribute("aria-label", checklistBtn.title);
|
|
98
|
+
checklistBtn.setAttribute("aria-pressed", data.checklistMode === true ? "true" : "false");
|
|
99
|
+
checklistBtn.innerHTML = iconHtml("list-check");
|
|
100
|
+
if (data.checklistMode) checklistBtn.classList.add("active");
|
|
101
|
+
header.appendChild(checklistBtn);
|
|
102
|
+
|
|
103
|
+
var saveStatus = document.createElement("span");
|
|
104
|
+
saveStatus.className = "sticky-note-save-status";
|
|
105
|
+
saveStatus.setAttribute("role", "status");
|
|
106
|
+
saveStatus.setAttribute("aria-live", "polite");
|
|
107
|
+
header.appendChild(saveStatus);
|
|
108
|
+
|
|
93
109
|
var opacityWrap = document.createElement("div");
|
|
94
110
|
opacityWrap.className = "sticky-note-opacity";
|
|
95
111
|
var opacitySlider = document.createElement("input");
|
|
@@ -139,11 +155,12 @@ export function renderNote(data) {
|
|
|
139
155
|
rendered.contentEditable = "true";
|
|
140
156
|
rendered.spellcheck = true;
|
|
141
157
|
if (data.text && data.text.trim()) {
|
|
142
|
-
rendered.innerHTML = renderMiniMarkdown(data.text);
|
|
158
|
+
rendered.innerHTML = renderMiniMarkdown(data.text, { checklistMode: data.checklistMode === true, checklistState: data.checklistState || [] });
|
|
143
159
|
enhanceClayLogLinks(rendered);
|
|
144
160
|
} else {
|
|
145
161
|
rendered.classList.add("is-empty");
|
|
146
162
|
}
|
|
163
|
+
rendered.contentEditable = data.checklistMode === true ? "false" : "true";
|
|
147
164
|
body.appendChild(rendered);
|
|
148
165
|
|
|
149
166
|
el.appendChild(body);
|
|
@@ -156,7 +173,7 @@ export function renderNote(data) {
|
|
|
156
173
|
// --- Event handlers ---
|
|
157
174
|
setupDrag(el, spacer, data.id);
|
|
158
175
|
setupResize(el, resizeHandle, data.id);
|
|
159
|
-
setupTextEdit(textarea, rendered, data.id, mdBtn);
|
|
176
|
+
setupTextEdit(textarea, rendered, data.id, mdBtn, checklistBtn, data);
|
|
160
177
|
setupColorPicker(colorBtn, el, data.id);
|
|
161
178
|
setupMinimize(minBtn, el, data.id);
|
|
162
179
|
setupClose(closeBtn, el, data.id);
|
|
@@ -336,14 +353,13 @@ function setupMinimize(btn, noteEl, noteId) {
|
|
|
336
353
|
|
|
337
354
|
// --- Close note ---
|
|
338
355
|
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
356
|
+
// The human close transaction retains nonempty notes in Closed and removes
|
|
357
|
+
// truly empty notes after checking the canonical source and visible draft.
|
|
341
358
|
|
|
342
359
|
function setupClose(btn, noteEl, noteId) {
|
|
343
360
|
btn.addEventListener("click", function (e) {
|
|
344
361
|
e.stopPropagation();
|
|
345
|
-
|
|
346
|
-
send({ type: "note_close", id: noteId });
|
|
362
|
+
closeHumanNote(noteId);
|
|
347
363
|
});
|
|
348
364
|
}
|
|
349
365
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Plain task controls use Markdown only as the internal persistence format.
|
|
2
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
|
+
import { getChecklistItems } from './sticky-note-markdown.js';
|
|
4
|
+
import { getNoteUiState, setNoteUiState } from './sticky-notes-shared.js';
|
|
5
|
+
import { mutateChecklist } from './sticky-notes-state.js';
|
|
6
|
+
|
|
7
|
+
export function checklistDraftText(ui) {
|
|
8
|
+
var fields = ui.checklistInputs || {};
|
|
9
|
+
var lines = (ui.text || '').split('\n');
|
|
10
|
+
if (fields.title !== undefined) lines[0] = fields.title;
|
|
11
|
+
getChecklistItems(ui.text).forEach(function (item) {
|
|
12
|
+
if (fields[item.key] !== undefined) {
|
|
13
|
+
var marker = item.text.match(/^\s*(?:[-*+]\s+)?\[([ xX])\]/);
|
|
14
|
+
var checked = ui.checklistState[item.key];
|
|
15
|
+
if (checked === undefined) checked = marker && marker[1].toLowerCase() === 'x';
|
|
16
|
+
lines[item.index + 1] = '- [' + (checked ? 'x' : ' ') + '] ' + fields[item.key];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
if (fields.entry && fields.entry.trim()) {
|
|
20
|
+
if (!lines[0].trim()) lines[0] = 'Checklist';
|
|
21
|
+
lines.push('- [ ] ' + fields.entry.trim());
|
|
22
|
+
}
|
|
23
|
+
return lines.join('\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderTaskControls(root, id) {
|
|
27
|
+
var ui = getNoteUiState(id);
|
|
28
|
+
var fields = ui.checklistInputs || {};
|
|
29
|
+
var busy = ui.pending.length > 0;
|
|
30
|
+
root.replaceChildren();
|
|
31
|
+
var form = document.createElement('form'); form.className = 'sn-task-form';
|
|
32
|
+
function input(label, value, key) {
|
|
33
|
+
var control = document.createElement('input'); control.type = 'text'; control.value = fields[key] !== undefined ? fields[key] : value;
|
|
34
|
+
control.setAttribute('aria-label', label); control.placeholder = label; control.maxLength = 400;
|
|
35
|
+
control.disabled = busy;
|
|
36
|
+
control.oninput = function () {
|
|
37
|
+
var current = getNoteUiState(id);
|
|
38
|
+
var draft = Object.assign({}, current.checklistInputs || {}); draft[key] = control.value;
|
|
39
|
+
setNoteUiState(id, { checklistInputs: draft });
|
|
40
|
+
};
|
|
41
|
+
return control;
|
|
42
|
+
}
|
|
43
|
+
var title = input('Checklist title', (ui.text || '').split('\n')[0], 'title'); title.className = 'sn-task-title'; form.appendChild(title);
|
|
44
|
+
getChecklistItems(ui.text).forEach(function (item, position) {
|
|
45
|
+
var row = document.createElement('div'); row.className = 'sn-task-row';
|
|
46
|
+
var marker = item.text.match(/^\s*(?:[-*+]\s+)?\[([ xX])\]\s*(.*)$/);
|
|
47
|
+
var check = document.createElement('button'); check.type = 'button'; check.className = 'sn-check';
|
|
48
|
+
var checked = ui.checklistState[item.key];
|
|
49
|
+
if (checked === undefined) checked = marker && marker[1].toLowerCase() === 'x';
|
|
50
|
+
check.classList.toggle('checked', !!checked); check.textContent = checked ? '✓' : '☐';
|
|
51
|
+
check.setAttribute('role', 'checkbox'); check.setAttribute('aria-checked', checked ? 'true' : 'false'); check.setAttribute('aria-label', 'Complete task ' + (position + 1));
|
|
52
|
+
check.dataset.checklistIndex = item.index; check.dataset.checklistKey = encodeURIComponent(item.key);
|
|
53
|
+
check.disabled = busy;
|
|
54
|
+
var label = input('Task ' + (position + 1), marker ? marker[2] : item.text, item.key);
|
|
55
|
+
var remove = document.createElement('button'); remove.type = 'button'; remove.className = 'sn-task-remove'; remove.innerHTML = iconHtml('x'); remove.setAttribute('aria-label', 'Remove task ' + (position + 1)); remove.disabled = busy;
|
|
56
|
+
remove.onclick = function () {
|
|
57
|
+
var current = getNoteUiState(id); var lines = checklistDraftText(current).split('\n'); lines.splice(item.index + 1, 1);
|
|
58
|
+
save(lines.join('\n'));
|
|
59
|
+
};
|
|
60
|
+
row.append(check, label, remove); form.appendChild(row);
|
|
61
|
+
});
|
|
62
|
+
var entry = input('New task', '', 'entry'); entry.className = 'sn-task-entry'; form.appendChild(entry);
|
|
63
|
+
var saveButton = document.createElement('button'); saveButton.type = 'submit'; saveButton.className = 'sn-task-save'; saveButton.textContent = 'Save tasks'; saveButton.disabled = busy; form.appendChild(saveButton);
|
|
64
|
+
function save(text) {
|
|
65
|
+
var current = getNoteUiState(id);
|
|
66
|
+
if (current.pending.length) return;
|
|
67
|
+
mutateChecklist(id, { checklistEdit: true, checklistSource: current.canonical.text, checklistVersion: current.canonical.updatedAt, text: text });
|
|
68
|
+
}
|
|
69
|
+
form.onsubmit = function (event) { event.preventDefault(); save(checklistDraftText(getNoteUiState(id))); };
|
|
70
|
+
root.appendChild(form); refreshIcons(root);
|
|
71
|
+
}
|