clay-server 5.2.0-beta.3 → 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 +19 -16
- package/lib/bundled-skills/clay-agent-browser/SKILL.md +6 -2
- 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-file-store.js +7 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-avatar-catalog.js +12 -0
- package/lib/mate-avatar-migration.js +41 -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 +18 -6
- package/lib/mcp-agent-tools.js +117 -0
- package/lib/mcp-connection-store.js +119 -15
- package/lib/mcp-import-credentials.js +31 -0
- package/lib/mcp-local-transport.js +13 -0
- package/lib/mcp-oauth.js +8 -6
- package/lib/mcp-query-bridge.js +30 -0
- package/lib/mcp-remote-connections.js +74 -20
- package/lib/mcp-source-adapters.js +91 -0
- package/lib/mcp-source-context.js +42 -0
- package/lib/mcp-source-discovery.js +169 -0
- package/lib/mcp-source-plugins.js +68 -0
- package/lib/mcp-source-restrictions.js +47 -0
- package/lib/mcp-tool-policy.js +34 -0
- 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 +8 -4
- package/lib/project-debate.js +1 -0
- 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-mate-interaction.js +8 -0
- package/lib/project-mcp.js +54 -16
- 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-document.js +8 -3
- package/lib/project-session-github.js +63 -19
- package/lib/project-session-history-actions.js +165 -0
- package/lib/project-session-linear.js +14 -6
- package/lib/project-session-notes.js +7 -3
- package/lib/project-session-runtime-actions.js +63 -0
- package/lib/project-session-vendor.js +54 -0
- package/lib/project-sessions.js +68 -241
- package/lib/project-shared-browser.js +56 -12
- 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 +144 -37
- package/lib/public/app.js +5 -0
- package/lib/public/appearance-bootstrap.js +14 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/clay-workspace.css +29 -0
- package/lib/public/css/home-debate-planning.css +2 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/mate-avatar-gallery.css +12 -0
- package/lib/public/css/mate-conversation.css +10 -5
- 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 +46 -1
- package/lib/public/css/shared-browser.css +35 -1
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +11 -5
- package/lib/public/mate-avatars/B01.png +0 -0
- package/lib/public/mate-avatars/B02.png +0 -0
- package/lib/public/mate-avatars/B03.png +0 -0
- package/lib/public/mate-avatars/B04.png +0 -0
- package/lib/public/mate-avatars/B05.png +0 -0
- package/lib/public/mate-avatars/B06.png +0 -0
- package/lib/public/mate-avatars/B07.png +0 -0
- package/lib/public/mate-avatars/B08.png +0 -0
- package/lib/public/mate-avatars/B09.png +0 -0
- package/lib/public/mate-avatars/B10.png +0 -0
- package/lib/public/mate-avatars/B11.png +0 -0
- package/lib/public/mate-avatars/B12.png +0 -0
- package/lib/public/mate-avatars/B13.png +0 -0
- package/lib/public/mate-avatars/B14.png +0 -0
- package/lib/public/mate-avatars/B15.png +0 -0
- package/lib/public/mate-avatars/B16.png +0 -0
- package/lib/public/mate-avatars/B17.png +0 -0
- package/lib/public/mate-avatars/B18.png +0 -0
- package/lib/public/mate-avatars/B19.png +0 -0
- package/lib/public/mate-avatars/B20.png +0 -0
- package/lib/public/mate-avatars/B21.png +0 -0
- package/lib/public/mate-avatars/B22.png +0 -0
- package/lib/public/mate-avatars/B23.png +0 -0
- package/lib/public/mate-avatars/B24.png +0 -0
- package/lib/public/mate-avatars/B25.png +0 -0
- package/lib/public/mate-avatars/B26.png +0 -0
- package/lib/public/mate-avatars/B27.png +0 -0
- package/lib/public/mate-avatars/B28.png +0 -0
- package/lib/public/mate-avatars/B29.png +0 -0
- package/lib/public/mate-avatars/B30.png +0 -0
- package/lib/public/mate-avatars/B31.png +0 -0
- package/lib/public/mate-avatars/B32.png +0 -0
- package/lib/public/mate-avatars/B33.png +0 -0
- package/lib/public/mate-avatars/B34.png +0 -0
- package/lib/public/mate-avatars/B35.png +0 -0
- package/lib/public/mate-avatars/B36.png +0 -0
- package/lib/public/mate-avatars/B37.png +0 -0
- package/lib/public/mate-avatars/B38.png +0 -0
- package/lib/public/mate-avatars/B39.png +0 -0
- package/lib/public/mate-avatars/B40.png +0 -0
- package/lib/public/mate-avatars/B41.png +0 -0
- package/lib/public/mate-avatars/B42.png +0 -0
- package/lib/public/mate-avatars/B43.png +0 -0
- package/lib/public/mate-avatars/B44.png +0 -0
- package/lib/public/mate-avatars/B45.png +0 -0
- package/lib/public/mate-avatars/B46.png +0 -0
- package/lib/public/mate-avatars/B47.png +0 -0
- package/lib/public/mate-avatars/B48.png +0 -0
- package/lib/public/mate-avatars/C01.png +0 -0
- package/lib/public/mate-avatars/C02.png +0 -0
- package/lib/public/mate-avatars/C03.png +0 -0
- package/lib/public/mate-avatars/C04.png +0 -0
- package/lib/public/mate-avatars/C05.png +0 -0
- package/lib/public/mate-avatars/C06.png +0 -0
- package/lib/public/mate-avatars/C07.png +0 -0
- package/lib/public/mate-avatars/C08.png +0 -0
- package/lib/public/mate-avatars/C09.png +0 -0
- package/lib/public/mate-avatars/C10.png +0 -0
- package/lib/public/mate-avatars/C11.png +0 -0
- package/lib/public/mate-avatars/C12.png +0 -0
- package/lib/public/mate-avatars/C13.png +0 -0
- package/lib/public/mate-avatars/C14.png +0 -0
- package/lib/public/mate-avatars/C15.png +0 -0
- package/lib/public/mate-avatars/C16.png +0 -0
- package/lib/public/mate-avatars/C17.png +0 -0
- package/lib/public/mate-avatars/C18.png +0 -0
- package/lib/public/mate-avatars/C19.png +0 -0
- package/lib/public/mate-avatars/C20.png +0 -0
- package/lib/public/mate-avatars/C21.png +0 -0
- package/lib/public/mate-avatars/C22.png +0 -0
- package/lib/public/mate-avatars/C23.png +0 -0
- package/lib/public/mate-avatars/C24.png +0 -0
- package/lib/public/mate-avatars/C25.png +0 -0
- package/lib/public/mate-avatars/C26.png +0 -0
- package/lib/public/mate-avatars/C27.png +0 -0
- package/lib/public/mate-avatars/C28.png +0 -0
- package/lib/public/mate-avatars/C29.png +0 -0
- package/lib/public/mate-avatars/C30.png +0 -0
- package/lib/public/mate-avatars/C31.png +0 -0
- package/lib/public/mate-avatars/C32.png +0 -0
- package/lib/public/mate-avatars/C33.png +0 -0
- package/lib/public/mate-avatars/C34.png +0 -0
- package/lib/public/mate-avatars/C35.png +0 -0
- package/lib/public/mate-avatars/C36.png +0 -0
- package/lib/public/mate-avatars/C37.png +0 -0
- package/lib/public/mate-avatars/C38.png +0 -0
- package/lib/public/mate-avatars/C39.png +0 -0
- package/lib/public/mate-avatars/C40.png +0 -0
- package/lib/public/mate-avatars/C41.png +0 -0
- package/lib/public/mate-avatars/C42.png +0 -0
- package/lib/public/mate-avatars/C43.png +0 -0
- package/lib/public/mate-avatars/C44.png +0 -0
- package/lib/public/mate-avatars/C45.png +0 -0
- package/lib/public/mate-avatars/C46.png +0 -0
- package/lib/public/mate-avatars/C47.png +0 -0
- package/lib/public/mate-avatars/C48.png +0 -0
- package/lib/public/mate-avatars/catalog.json +482 -0
- package/lib/public/mate-avatars/lead-clay.png +0 -0
- 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 +4 -0
- package/lib/public/modules/app-messages.js +31 -5
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/app-rendering.js +1 -1
- package/lib/public/modules/avatar.js +15 -41
- package/lib/public/modules/chat-render-runtime.js +10 -1
- package/lib/public/modules/clay-browser-links.js +47 -0
- package/lib/public/modules/clay-log-links.js +1 -1
- 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 +2 -2
- package/lib/public/modules/debate.js +1 -0
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/history-replay-batch.js +5 -1
- package/lib/public/modules/home-debate-live.js +2 -0
- package/lib/public/modules/home-debate-planning.js +7 -7
- package/lib/public/modules/home-mate-avatar-editor.js +127 -208
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/home-mate-settings.js +19 -12
- package/lib/public/modules/markdown.js +4 -0
- package/lib/public/modules/mate-avatar-catalog.js +97 -0
- package/lib/public/modules/mate-management.js +40 -7
- package/lib/public/modules/mcp-connection-view.js +31 -13
- package/lib/public/modules/mcp-discovery-view.js +31 -0
- package/lib/public/modules/mcp-ui.js +23 -4
- package/lib/public/modules/mention.js +3 -0
- package/lib/public/modules/pane-browser-bridge.js +20 -0
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/profile.js +1 -238
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/project-mate-navigation.js +99 -14
- package/lib/public/modules/search-clay-chat.js +16 -2
- 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 +85 -14
- package/lib/public/modules/session-work-primary.js +51 -0
- package/lib/public/modules/shared-browser-settings.js +112 -0
- package/lib/public/modules/shared-browser-tabs.js +13 -3
- package/lib/public/modules/shared-browser.js +27 -6
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/split-view.js +2 -0
- 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/theme.js +8 -1
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +36 -7
- 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 +3 -0
- package/lib/public/sw.js +3 -1
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +228 -23
- 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-appearance-settings.js +13 -1
- package/lib/server-dm.js +2 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +18 -4
- package/lib/server-home-chat.js +12 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +14 -0
- package/lib/server-mates.js +25 -62
- 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-settings.js +4 -1
- package/lib/server.js +144 -41
- 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/session-work-primary.js +50 -0
- package/lib/sessions.js +13 -0
- package/lib/shared-browser-cookie-sync.js +32 -0
- package/lib/shared-browser-cookies.js +79 -0
- package/lib/shared-browser-runtime.js +25 -9
- package/lib/shared-browser-storage-sync.js +43 -0
- package/lib/shared-browser-storage.js +101 -0
- package/lib/shared-browser-tools.js +2 -1
- package/lib/shared-browser-worker.js +46 -10
- 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/acp-agent-profiles.js +2 -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 +5 -1
|
@@ -3,6 +3,7 @@ import { getWs } from './ws-ref.js';
|
|
|
3
3
|
import { githubLinkTarget } from './github-links.js';
|
|
4
4
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
5
5
|
import { escapeHtml as escapeText } from './utils.js';
|
|
6
|
+
import { primaryWorkPopover, handlePrimaryWorkClick } from './session-work-primary.js';
|
|
6
7
|
|
|
7
8
|
function escapeHtml(value) {
|
|
8
9
|
return escapeText(String(value)).replace(/"/g, '"').replace(/'/g, ''');
|
|
@@ -34,13 +35,38 @@ export function orderedGithubWork(session) {
|
|
|
34
35
|
var links = safeLinks(session).slice().reverse();
|
|
35
36
|
var latestIssue = links.find(function (link) { return link.kind === 'issue' || link.provider === 'linear'; });
|
|
36
37
|
if (latestIssue) links = [latestIssue].concat(links.filter(function (link) { return link !== latestIssue; }));
|
|
37
|
-
|
|
38
|
+
var primary = session && session.workPrimary || {};
|
|
39
|
+
return links.filter(function (link) { return String(primary[link.provider === 'linear' ? 'linear' : link.kind] || '').toLowerCase() === link.url.toLowerCase(); }).concat(
|
|
40
|
+
links.filter(function (link) { return String(primary[link.provider === 'linear' ? 'linear' : link.kind] || '').toLowerCase() !== link.url.toLowerCase(); }));
|
|
38
41
|
}
|
|
39
42
|
function compactGithubWork(links, sessionRow) {
|
|
40
43
|
if (!sessionRow && !links.some(function (link) { return link.provider === 'linear'; })) return links.slice(0, 1);
|
|
41
|
-
var
|
|
44
|
+
var latestLinear = links.find(function (link) { return link.provider === 'linear'; });
|
|
45
|
+
var latestIssue = links.find(function (link) { return link.kind === 'issue' && link.provider !== 'linear'; });
|
|
42
46
|
var latestPr = links.find(function (link) { return link.kind === 'pr'; });
|
|
43
|
-
return [latestIssue, latestPr].filter(Boolean);
|
|
47
|
+
return [latestLinear, latestIssue, latestPr].filter(Boolean);
|
|
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;
|
|
44
70
|
}
|
|
45
71
|
export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
46
72
|
var links = orderedGithubWork(session);
|
|
@@ -52,19 +78,51 @@ export function githubWorkMarkup(session, detailed, expanded, sessionRow) {
|
|
|
52
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');
|
|
53
79
|
}
|
|
54
80
|
var state = ['open', 'closed', 'merged', 'draft'].indexOf(link.state) >= 0 ? link.state : 'unknown';
|
|
55
|
-
var
|
|
56
|
-
var
|
|
57
|
-
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);
|
|
58
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>';
|
|
59
89
|
if (detailed && link.kind === 'pr') {
|
|
60
|
-
|
|
61
|
-
|
|
90
|
+
var actions = anchor(link.url + '/files', 'View PR changes on GitHub', 'Changes', 'github-work-action') +
|
|
91
|
+
anchor(link.url + '#pullrequestreview', 'View PR reviews on GitHub', 'Reviews', 'github-work-action');
|
|
92
|
+
var checks = '';
|
|
62
93
|
(link.checks || []).forEach(function (check) {
|
|
63
|
-
if (/^https:\/\//i.test(check.url))
|
|
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');
|
|
64
95
|
});
|
|
96
|
+
return '<span class="github-work-pr-group" data-github-pr="' + escapeHtml(String(link.number)) + '">' +
|
|
97
|
+
'<span class="github-work-pr-header">' + html + '</span>' +
|
|
98
|
+
'<span class="github-work-pr-actions">' + actions + '</span>' +
|
|
99
|
+
(checks ? '<span class="github-work-pr-checks">' + checks + '</span>' : '') +
|
|
100
|
+
'</span>';
|
|
65
101
|
}
|
|
66
102
|
return html;
|
|
67
|
-
}).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-github-links="' + escapeHtml(JSON.stringify(safeLinks(session))) + '">+' + (links.length - displayed.length) + '</button>' : '') + '</span>';
|
|
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>';
|
|
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();
|
|
68
126
|
}
|
|
69
127
|
export function appendGithubWork(row, session, title) {
|
|
70
128
|
var html = githubWorkMarkup(session, false, false, true);
|
|
@@ -84,6 +142,7 @@ export function appendGithubWork(row, session, title) {
|
|
|
84
142
|
}
|
|
85
143
|
export function syncGithubSessions(sessions) {
|
|
86
144
|
bindGithubWorkPopover();
|
|
145
|
+
ensurePersonalScope();
|
|
87
146
|
store.set({ githubWorkSessions: sessions || [], githubWorkProject: store.get('currentSlug') });
|
|
88
147
|
var active = (sessions || []).find(function (session) { return session.active; });
|
|
89
148
|
var title = document.getElementById('header-title');
|
|
@@ -122,15 +181,16 @@ export function refreshGithubWork() {
|
|
|
122
181
|
if (previous.key === key && Date.now() - previous.at < 60000) return;
|
|
123
182
|
var ws = getWs();
|
|
124
183
|
if (!ws || ws.readyState !== 1) return;
|
|
125
|
-
|
|
126
|
-
|
|
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 }));
|
|
127
187
|
var linearIds = ids.filter(function (id) { return (store.get('githubWorkSessions') || []).some(function (item) { return item.id === id && (item.linearLinks || []).length; }); });
|
|
128
188
|
if (linearIds.length) ws.send(JSON.stringify({ type: 'session_linear_refresh', sessionIds: linearIds }));
|
|
129
189
|
}
|
|
130
190
|
|
|
131
191
|
export function resetGithubWork() {
|
|
132
192
|
closeGithubWorkPopover();
|
|
133
|
-
store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null });
|
|
193
|
+
store.set({ githubWorkSessions: [], githubWorkProject: null, githubWorkRefresh: null, githubPersonalReview: {}, workPrimaryPending: null });
|
|
134
194
|
var header = document.getElementById('header-github-links');
|
|
135
195
|
if (header) header.remove();
|
|
136
196
|
}
|
|
@@ -184,6 +244,7 @@ function bindGithubWorkPopover() {
|
|
|
184
244
|
if (store.get('githubWorkPopoverBound')) return;
|
|
185
245
|
store.set({ githubWorkPopoverBound: true });
|
|
186
246
|
document.addEventListener('click', function (event) {
|
|
247
|
+
if (handlePrimaryWorkClick(event)) return;
|
|
187
248
|
var linear = event.target.closest('.session-linear-link');
|
|
188
249
|
if (linear && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey) { event.preventDefault(); event.stopPropagation(); closeGithubWorkPopover(); import('./linear-panel.js').then(function (module) { module.openLinear(linear.href); }); return; }
|
|
189
250
|
var button = event.target.closest('.github-work-more');
|
|
@@ -204,7 +265,11 @@ function bindGithubWorkPopover() {
|
|
|
204
265
|
popup.setAttribute('role', 'dialog');
|
|
205
266
|
var workTitle = links.some(function (link) { return link.provider === 'linear'; }) ? 'Linked work' : 'Linked GitHub work';
|
|
206
267
|
popup.setAttribute('aria-label', workTitle);
|
|
207
|
-
popup.
|
|
268
|
+
popup.dataset.sessionId = button.dataset.workSession || '';
|
|
269
|
+
var primary;
|
|
270
|
+
try { primary = JSON.parse(button.dataset.workPrimary || '{}'); } catch (error) { primary = {}; }
|
|
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);
|
|
208
273
|
document.body.appendChild(popup);
|
|
209
274
|
mountGithubWorkPopover(popup, button);
|
|
210
275
|
button.setAttribute('aria-expanded', 'true');
|
|
@@ -224,3 +289,9 @@ function bindGithubWorkPopover() {
|
|
|
224
289
|
if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.dmMode !== previous.dmMode) closeGithubWorkPopover();
|
|
225
290
|
});
|
|
226
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
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { escapeHtml } from './utils.js';
|
|
4
|
+
var PRIMARY_PIN = '<svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false"><path class="work-primary-pin-head" d="M16 9V4l1-1V2H7v1l1 1v5l-2 3v2h12v-2z"/><path d="M12 14v8"/></svg>';
|
|
5
|
+
function attr(value) { return escapeHtml(String(value)).replace(/"/g, '"').replace(/'/g, '''); }
|
|
6
|
+
export function primaryWorkPopover(links, primary, sessionId, render) {
|
|
7
|
+
var html = ['linear', 'pr', 'issue'].map(function (type) {
|
|
8
|
+
var items = links.filter(function (link) { return (link.provider === 'linear' ? 'linear' : link.kind) === type; }).reverse();
|
|
9
|
+
if (!items.length) return '';
|
|
10
|
+
return '<section class="work-primary-group"><h3>' + ({ linear: 'Linear issues', pr: 'Pull requests', issue: 'GitHub issues' })[type] + '</h3>' + items.map(function (link) {
|
|
11
|
+
var selected = String(primary[type] || '').toLowerCase() === link.url.toLowerCase();
|
|
12
|
+
return '<div class="work-primary-row">' + render({ githubLinks: [link] }, false, true) +
|
|
13
|
+
(sessionId ? '<button type="button" class="work-primary-choice" data-primary-url="' + attr(link.url) + '" aria-label="' + attr((selected ? 'Primary: ' : 'Make primary: ') + (link.identifier || (type === 'pr' ? 'PR #' : 'Issue #') + link.number)) + '" title="' + (selected ? 'Primary' : 'Make primary') + '" aria-pressed="' + selected + '"' + (selected ? ' disabled' : '') + '>' + PRIMARY_PIN + '</button>' : '') + '</div>';
|
|
14
|
+
}).join('') + '</section>';
|
|
15
|
+
}).join('');
|
|
16
|
+
return html + '<p class="work-primary-feedback" role="status" aria-live="polite"></p>';
|
|
17
|
+
}
|
|
18
|
+
export function handlePrimaryWorkClick(event) {
|
|
19
|
+
var button = event.target.closest('.work-primary-choice');
|
|
20
|
+
if (!button) return false;
|
|
21
|
+
event.preventDefault(); event.stopPropagation();
|
|
22
|
+
var popup = button.closest('#github-work-popover'), ws = getWs();
|
|
23
|
+
if (!popup) return true;
|
|
24
|
+
var feedback = popup.querySelector('.work-primary-feedback');
|
|
25
|
+
if (!ws || ws.readyState !== 1) { feedback.textContent = 'Reconnect to save your selection.'; return true; }
|
|
26
|
+
var requestId = 'work-primary-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
27
|
+
store.set({ workPrimaryPending: { requestId: requestId, sessionId: Number(popup.dataset.sessionId), projectSlug: store.get('currentSlug') } });
|
|
28
|
+
popup.querySelectorAll('.work-primary-choice').forEach(function (control) { control.disabled = true; });
|
|
29
|
+
feedback.textContent = 'Saving…';
|
|
30
|
+
var projectSlug = store.get('currentSlug');
|
|
31
|
+
setTimeout(function () {
|
|
32
|
+
handlePrimaryWorkResult({ requestId: requestId, projectSlug: projectSlug, error: 'Save confirmation did not arrive. Reopen the list to check, or retry.' });
|
|
33
|
+
}, 15000);
|
|
34
|
+
try { ws.send(JSON.stringify({ type: 'session_work_primary_set', requestId: requestId, sessionId: Number(popup.dataset.sessionId), url: button.dataset.primaryUrl })); }
|
|
35
|
+
catch (error) { handlePrimaryWorkResult({ requestId: requestId, projectSlug: store.get('currentSlug'), error: 'Could not send your selection. Please retry.' }); }
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
export function handlePrimaryWorkResult(msg) {
|
|
39
|
+
var pending = store.get('workPrimaryPending');
|
|
40
|
+
if (!pending || msg.requestId !== pending.requestId || msg.projectSlug !== pending.projectSlug) return;
|
|
41
|
+
store.set({ workPrimaryPending: null });
|
|
42
|
+
var popup = document.getElementById('github-work-popover');
|
|
43
|
+
if (!popup || Number(popup.dataset.sessionId) !== pending.sessionId || store.get('currentSlug') !== pending.projectSlug) return;
|
|
44
|
+
popup.querySelectorAll('.work-primary-choice').forEach(function (button) {
|
|
45
|
+
var selected = msg.error ? button.getAttribute('aria-pressed') === 'true' : Object.values(msg.workPrimary || {}).some(function (url) { return url.toLowerCase() === button.dataset.primaryUrl.toLowerCase(); });
|
|
46
|
+
button.setAttribute('aria-pressed', String(selected)); button.disabled = selected;
|
|
47
|
+
button.title = selected ? 'Primary' : 'Make primary';
|
|
48
|
+
button.setAttribute('aria-label', (selected ? 'Primary: ' : 'Make primary: ') + button.getAttribute('aria-label').replace(/^(Primary|Make primary): /, ''));
|
|
49
|
+
});
|
|
50
|
+
popup.querySelector('.work-primary-feedback').textContent = msg.error || 'Primary saved.';
|
|
51
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { escapeHtml as escapeText } from './utils.js';
|
|
4
|
+
|
|
5
|
+
function escapeHtml(value) { return escapeText(String(value)).replace(/"/g, '"').replace(/'/g, '''); }
|
|
6
|
+
function state() { return store.get('sharedBrowserSettings') || {}; }
|
|
7
|
+
function set(patch) { store.set({ sharedBrowserSettings: Object.assign({}, state(), patch) }); }
|
|
8
|
+
function root() { return document.getElementById('shared-browser-settings'); }
|
|
9
|
+
function button(label, action, target, description) {
|
|
10
|
+
return '<button type="button" data-settings-action="' + action + '" data-target="' + escapeHtml(target || '') + '" data-label="' + escapeHtml(description || target || '') + '">' + escapeHtml(label) + '</button>';
|
|
11
|
+
}
|
|
12
|
+
function request(operation, target) {
|
|
13
|
+
var ws = getWs();
|
|
14
|
+
if (!ws || ws.readyState !== 1) { set({ error: 'Reconnect to manage browser data.', pending: false }); renderBrowserSettings(); return; }
|
|
15
|
+
var id = crypto.randomUUID();
|
|
16
|
+
set({ pending: true, requestId: id, error: '', notice: '' });
|
|
17
|
+
ws.send(JSON.stringify({ type: 'shared_browser_settings', sessionId: store.get('activeSessionId'), requestId: id, operation: operation, target: target }));
|
|
18
|
+
renderBrowserSettings();
|
|
19
|
+
setTimeout(function () {
|
|
20
|
+
if (state().requestId === id && state().pending) { set({ pending: false, error: 'The request timed out. Refresh to check the saved state.' }); renderBrowserSettings(); }
|
|
21
|
+
}, 15000);
|
|
22
|
+
}
|
|
23
|
+
export function closeBrowserSettings(restoreFocus) {
|
|
24
|
+
var wasOpen = !!state().open;
|
|
25
|
+
set({ open: false, review: null }); renderBrowserSettings();
|
|
26
|
+
var trigger = document.querySelector('[data-action="settings"]'); if (trigger && wasOpen && restoreFocus !== false) trigger.focus();
|
|
27
|
+
}
|
|
28
|
+
export function browserSettingsHtml() {
|
|
29
|
+
return '<section id="shared-browser-settings" class="shared-browser-settings" aria-label="Browser settings" hidden></section>';
|
|
30
|
+
}
|
|
31
|
+
export function bindBrowserSettings(panel) {
|
|
32
|
+
panel.querySelector('[data-action="settings"]').onclick = function () {
|
|
33
|
+
set({ open: true, page: 'sites', data: null, review: null, filter: '' });
|
|
34
|
+
request('list');
|
|
35
|
+
root().querySelector('[data-settings-action="back"]').focus();
|
|
36
|
+
};
|
|
37
|
+
root().addEventListener('keydown', function (event) {
|
|
38
|
+
if (event.key === 'Escape') { event.preventDefault(); event.stopPropagation(); closeBrowserSettings(); }
|
|
39
|
+
});
|
|
40
|
+
root().addEventListener('input', function (event) {
|
|
41
|
+
if (event.target.matches('[data-settings-filter]')) { set({ filter: event.target.value }); renderRows(); root().dataset.key = JSON.stringify([state(), store.get('connected')]); }
|
|
42
|
+
});
|
|
43
|
+
root().addEventListener('click', function (event) {
|
|
44
|
+
var control = event.target.closest('[data-settings-action]');
|
|
45
|
+
if (!control || control.disabled) return;
|
|
46
|
+
var action = control.dataset.settingsAction;
|
|
47
|
+
if (action === 'back') { closeBrowserSettings(); return; }
|
|
48
|
+
if (action === 'sites' || action === 'history') { set({ page: action, filter: '', review: null, notice: '', error: '' }); renderBrowserSettings(); return; }
|
|
49
|
+
if (action === 'refresh') { request('list'); return; }
|
|
50
|
+
if (action === 'cancel') { set({ review: null }); renderBrowserSettings(); root().querySelector('[data-settings-action="refresh"]').focus(); return; }
|
|
51
|
+
if (action === 'confirm') { var review = state().review; if (review) request(review.action, review.target); return; }
|
|
52
|
+
set({ notice: '', error: '', review: { action: action, target: control.dataset.target, label: control.dataset.label || control.dataset.target } });
|
|
53
|
+
renderBrowserSettings(); root().querySelector('[data-settings-action="cancel"]').focus();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function renderRows() {
|
|
57
|
+
var el = root(), s = state(); if (!el || !s.open) return;
|
|
58
|
+
var rows = el.querySelector('.browser-settings-rows'); if (!rows) return;
|
|
59
|
+
var filter = (s.filter || '').toLowerCase();
|
|
60
|
+
var data = s.data || { sites: [], history: [] };
|
|
61
|
+
if (s.page === 'history') {
|
|
62
|
+
rows.innerHTML = data.history.filter(function (entry) { return entry.url.toLowerCase().includes(filter); }).map(function (entry) {
|
|
63
|
+
return '<div class="browser-settings-row"><div><span class="browser-settings-url">' + escapeHtml(entry.url) + '</span><small>' + escapeHtml(new Date(entry.at).toLocaleString()) + '</small></div>' + button('Remove', 'delete_history', entry.id, entry.url) + '</div>';
|
|
64
|
+
}).join('');
|
|
65
|
+
} else {
|
|
66
|
+
rows.innerHTML = data.sites.filter(function (site) { return site.host.toLowerCase().includes(filter); }).map(function (site) {
|
|
67
|
+
return '<details><summary>' + escapeHtml(site.host) + '<small>' + site.cookies.length + (site.cookies.length === 1 ? ' cookie · ' : ' cookies · ') + site.origins.length + (site.origins.length === 1 ? ' storage origin' : ' storage origins') + '</small></summary><div class="browser-settings-row"><span>Cookies and site storage</span>' + button('Remove site data', 'delete_site', site.host) + '</div>' +
|
|
68
|
+
site.origins.map(function (origin) { return '<p class="browser-settings-origin">' + escapeHtml(origin.origin) + '<small>' + origin.localStorage + ' localStorage entries · ' + origin.databases + ' IndexedDB databases</small></p>'; }).join('') +
|
|
69
|
+
site.cookies.map(function (cookie) {
|
|
70
|
+
var info = cookie.domain + cookie.path + ' · ' + (cookie.expires === -1 ? 'Session cookie' : 'Expires ' + new Date(cookie.expires * 1000).toLocaleDateString()) + (cookie.httpOnly ? ' · HttpOnly' : '') + (cookie.secure ? ' · Secure' : '') + (cookie.partitioned ? ' · Partitioned' : '');
|
|
71
|
+
return '<div class="browser-settings-row"><div><span>' + escapeHtml(cookie.name) + '</span><small>' + escapeHtml(info) + '</small></div>' + button('Remove cookie', 'delete_cookie', cookie.id, cookie.name + ' · ' + cookie.domain + cookie.path) + '</div>';
|
|
72
|
+
}).join('') + '</details>';
|
|
73
|
+
}).join('');
|
|
74
|
+
}
|
|
75
|
+
if (!rows.innerHTML) rows.innerHTML = '<p class="browser-settings-muted">' + (s.pending && !s.data ? 'Loading saved data…' : filter ? 'No matching entries.' : s.page === 'history' ? 'No saved browsing history.' : 'No saved cookies or site data.') + '</p>';
|
|
76
|
+
rows.querySelectorAll('button').forEach(function (control) { control.disabled = !!s.pending || !store.get('connected'); });
|
|
77
|
+
}
|
|
78
|
+
export function renderBrowserSettings() {
|
|
79
|
+
var el = root(); if (!el) return;
|
|
80
|
+
var s = state(), panel = el.closest('.shared-browser-panel');
|
|
81
|
+
var browser = store.get('sharedBrowser');
|
|
82
|
+
var trigger = panel.querySelector('[data-action="settings"]');
|
|
83
|
+
trigger.hidden = !(browser && browser.personalCookiesAvailable);
|
|
84
|
+
trigger.disabled = !store.get('connected');
|
|
85
|
+
trigger.setAttribute('aria-expanded', String(!!s.open));
|
|
86
|
+
el.hidden = !s.open;
|
|
87
|
+
panel.classList.toggle('browser-settings-open', !!s.open);
|
|
88
|
+
if (!s.open) return;
|
|
89
|
+
var key = JSON.stringify([s, store.get('connected')]); if (el.dataset.key === key) return; el.dataset.key = key;
|
|
90
|
+
var page = s.page || 'sites';
|
|
91
|
+
el.innerHTML = '<div class="browser-settings-heading">' + button('Back to browser', 'back') + '<h3>Browser settings</h3></div><p class="browser-settings-muted">Personal · Shared across your Mates. Incognito tabs do not save data here.</p>' +
|
|
92
|
+
'<nav aria-label="Browser settings sections">' + button('Cookies & site data', 'sites') + button('History', 'history') + '</nav>' +
|
|
93
|
+
'<p class="browser-settings-muted">' + (page === 'sites' ? 'Cookies, localStorage and IndexedDB are saved for your account. New personal tabs restore saved site storage. Removing data may sign you out. Removals also apply to open personal tabs. Sites may create new data afterwards.' : 'Your latest 500 page visits. Clearing this list leaves open tabs, their Back/Forward navigation and conversation activity unchanged.') + '</p>' +
|
|
94
|
+
'<div class="browser-settings-tools"><input data-settings-filter aria-label="Filter saved browser data" placeholder="' + (page === 'sites' ? 'Find a site' : 'Find a page') + '" value="' + escapeHtml(s.filter || '') + '">' + button('Refresh', 'refresh') + button(page === 'sites' ? 'Clear all site data' : 'Clear history', page === 'sites' ? 'clear_data' : 'clear_history') + '</div>' +
|
|
95
|
+
'<p class="browser-settings-feedback" role="status">' + escapeHtml(s.error || (!store.get('connected') ? 'Disconnected. Reconnect and refresh to see current saved data.' : s.pending ? 'Updating…' : s.notice || '')) + '</p>' +
|
|
96
|
+
(s.review ? '<div class="browser-settings-review" role="group" aria-label="Confirm removal"><strong>' + (s.review.action.includes('history') ? 'Remove browsing history?' : 'Remove saved site data?') + '</strong><p>' + (s.review.action.includes('history') ? 'This removes ' + (s.review.action === 'clear_history' ? 'all saved visits' : 'this saved visit') + ' from your personal history.' : s.review.action === 'delete_cookie' ? 'This removes ' + escapeHtml(s.review.label) + ' across your personal Mate browsers and may sign you out.' : 'This removes ' + (s.review.action === 'clear_data' ? 'all cookies and site storage' : 'cookies and storage for ' + escapeHtml(s.review.target)) + ' across your personal Mate browsers and may sign you out.') + '</p>' + button('Cancel', 'cancel') + button('Remove', 'confirm') + '</div>' : '') +
|
|
97
|
+
'<div class="browser-settings-rows"></div>';
|
|
98
|
+
el.querySelector('[data-settings-action="' + page + '"]').setAttribute('aria-current', 'page');
|
|
99
|
+
el.querySelectorAll('button').forEach(function (control) { control.disabled = control.dataset.settingsAction !== 'back' && (!!s.pending || !store.get('connected')); });
|
|
100
|
+
renderRows();
|
|
101
|
+
}
|
|
102
|
+
export function handleBrowserSettingsMessage(msg) {
|
|
103
|
+
if (!msg.requestId || msg.requestId !== state().requestId) return false;
|
|
104
|
+
if (msg.type === 'shared_browser_settings') {
|
|
105
|
+
var removed = !!state().review;
|
|
106
|
+
set({ data: msg.settings, pending: false, review: null, error: '', notice: removed ? 'Removed from your saved browser data.' : '' });
|
|
107
|
+
} else if (msg.type === 'shared_browser_error') set({ pending: false, error: msg.error });
|
|
108
|
+
else return false;
|
|
109
|
+
renderBrowserSettings();
|
|
110
|
+
if (state().open && removed) root().querySelector('[data-settings-action="refresh"]').focus();
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
|
|
3
4
|
export function renderBrowserTabs(el, select, send) {
|
|
4
5
|
var bar = el.querySelector('.shared-browser-tabs');
|
|
5
6
|
var tabs = store.get('sharedBrowsers') || [];
|
|
6
7
|
var selected = store.get('sharedBrowser');
|
|
7
|
-
var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase]; }), selected && selected.id, store.get('connected')]);
|
|
8
|
+
var key = JSON.stringify([tabs.map(function (tab) { return [tab.id, tab.url, tab.control, tab.phase, tab.incognito, tab.personalCookiesAvailable]; }), selected && selected.id, store.get('connected')]);
|
|
8
9
|
if (bar.dataset.key === key) return;
|
|
9
10
|
bar.dataset.key = key;
|
|
10
11
|
bar.replaceChildren();
|
|
@@ -14,9 +15,9 @@ export function renderBrowserTabs(el, select, send) {
|
|
|
14
15
|
button.setAttribute('aria-selected', String(!!selected && selected.id === tab.id));
|
|
15
16
|
var title = 'Tab ' + (index + 1);
|
|
16
17
|
try { if (tab.url) title = new URL(tab.url).hostname; } catch (e) {}
|
|
17
|
-
var label = document.createElement('span'); label.textContent = title;
|
|
18
|
+
var label = document.createElement('span'); label.textContent = title + (tab.personalCookiesAvailable && tab.incognito ? ' · Incognito' : '');
|
|
18
19
|
var owner = document.createElement('small'); owner.textContent = tab.phase === 'ended' ? 'Ended' : tab.control === 'agent' ? 'Shared' : 'You only';
|
|
19
|
-
button.append(label, owner); button.title = title + ' · ' + (tab.phase === 'ended' ? 'Ended' : tab.control === 'agent' ? 'You and Clay can control this tab' : 'Only you can control this tab. Clay is paused.');
|
|
20
|
+
button.append(label, owner); button.title = title + ' · ' + (tab.incognito === false ? 'Personal browser' : 'Incognito') + ' · ' + (tab.phase === 'ended' ? 'Ended' : tab.control === 'agent' ? 'You and Clay can control this tab' : 'Only you can control this tab. Clay is paused.');
|
|
20
21
|
button.onclick = function () { select(tab.id); };
|
|
21
22
|
button.onkeydown = function (event) {
|
|
22
23
|
var next = event.key === 'ArrowRight' ? (index + 1) % tabs.length : event.key === 'ArrowLeft' ? (index + tabs.length - 1) % tabs.length : event.key === 'Home' ? 0 : event.key === 'End' ? tabs.length - 1 : -1;
|
|
@@ -28,6 +29,15 @@ export function renderBrowserTabs(el, select, send) {
|
|
|
28
29
|
item.append(button, close); bar.appendChild(item);
|
|
29
30
|
});
|
|
30
31
|
var add = document.createElement('button'); add.type = 'button'; add.textContent = '+'; add.setAttribute('aria-label', 'New browser tab'); add.disabled = !store.get('connected');
|
|
32
|
+
add.title = selected && selected.personalCookiesAvailable ? 'New tab · Personal browser' : 'New incognito tab';
|
|
31
33
|
add.onclick = function () { send('open', { newTab: true }); };
|
|
32
34
|
bar.appendChild(add);
|
|
35
|
+
if (selected && selected.personalCookiesAvailable) {
|
|
36
|
+
var incognito = document.createElement('button'); incognito.type = 'button';
|
|
37
|
+
incognito.innerHTML = iconHtml('glasses');
|
|
38
|
+
incognito.setAttribute('aria-label', 'New incognito tab'); incognito.title = 'New incognito tab · Site data is not saved';
|
|
39
|
+
incognito.disabled = !store.get('connected');
|
|
40
|
+
incognito.onclick = function () { send('open', { newTab: true, incognito: true }); };
|
|
41
|
+
bar.appendChild(incognito); refreshIcons();
|
|
42
|
+
}
|
|
33
43
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { browserLinkTarget, initBrowserLinks } from './clay-browser-links.js';
|
|
2
|
+
import { browserSettingsHtml, bindBrowserSettings, renderBrowserSettings, closeBrowserSettings, handleBrowserSettingsMessage } from './shared-browser-settings.js';
|
|
1
3
|
import { renderBrowserTabs } from './shared-browser-tabs.js';
|
|
2
4
|
import { store } from './store.js';
|
|
3
5
|
import { getWs } from './ws-ref.js';
|
|
@@ -50,15 +52,15 @@ function ensurePanel() {
|
|
|
50
52
|
el.className = 'shared-browser-panel hidden';
|
|
51
53
|
el.setAttribute('aria-label', 'Shared browser');
|
|
52
54
|
el.innerHTML = '<header class="shared-browser-header"><span class="shared-browser-title">' + iconHtml('globe') + 'Browser</span><span class="shared-browser-phase" role="status">Ready</span><div class="shared-browser-window">' +
|
|
53
|
-
iconButton('wide', 'chevrons-left-right', 'Widen browser panel') + iconButton('fullscreen', 'maximize-2', 'Toggle browser fullscreen') + iconButton('hide', 'x', 'Hide browser panel') + '</div></header>' +
|
|
55
|
+
iconButton('settings', 'settings-2', 'Browser settings') + iconButton('wide', 'chevrons-left-right', 'Widen browser panel') + iconButton('fullscreen', 'maximize-2', 'Toggle browser fullscreen') + iconButton('hide', 'x', 'Hide browser panel') + '</div></header>' +
|
|
54
56
|
'<div class="shared-browser-tabs" role="tablist" aria-label="Browser tabs"></div><form class="shared-browser-address">' + iconButton('back', 'arrow-left', 'Back') + iconButton('forward', 'arrow-right', 'Forward') + iconButton('reload', 'rotate-cw', 'Reload') +
|
|
55
57
|
'<input aria-label="Browser address" placeholder="Enter a URL" type="text" autocomplete="off" spellcheck="false"><button type="submit" aria-label="Open address">' + iconHtml('arrow-right') + '</button></form>' +
|
|
56
|
-
'<div class="shared-browser-viewport"><div class="shared-browser-monitor"><div class="shared-browser-pointer-layer" aria-hidden="true"><div class="shared-browser-pointer" hidden><svg viewBox="0 0 24 28"><path d="M3 2 L3 22 L8 17 L12 26 L16 24 L12 15 L20 15 Z"/></svg><i></i></div></div><canvas width="1280" height="800" tabindex="0" aria-label="Shared browser page"></canvas>' +
|
|
57
|
-
'<div class="shared-browser-empty">' + iconHtml('globe') + '<h3>A browser you can
|
|
58
|
+
browserSettingsHtml() + '<div class="shared-browser-viewport"><div class="shared-browser-monitor"><div class="shared-browser-pointer-layer" aria-hidden="true"><div class="shared-browser-pointer" hidden><svg viewBox="0 0 24 28"><path d="M3 2 L3 22 L8 17 L12 26 L16 24 L12 15 L20 15 Z"/></svg><i></i></div></div><canvas width="1280" height="800" tabindex="0" aria-label="Shared browser page"></canvas>' +
|
|
59
|
+
'<div class="shared-browser-empty">' + iconHtml('globe') + '<h3>A browser you can use with Clay</h3><p>Enter an address to browse together.<br>Localhost connects to the Clay host.</p><p class="shared-browser-private">Site data lasts only for this browser session.</p></div>' +
|
|
58
60
|
'<textarea class="shared-browser-keyboard" aria-label="Type into shared browser" autocomplete="off" autocapitalize="off" spellcheck="false"></textarea><span class="shared-browser-monitor-brand" aria-hidden="true">Clay Studio</span><span class="shared-browser-monitor-detail" aria-hidden="true"></span><span class="shared-browser-monitor-stand" aria-hidden="true"></span></div><div class="shared-browser-caption"><span class="shared-browser-monitor-label"></span>' + activityHtml() + '</div></div>' +
|
|
59
61
|
'<p class="shared-browser-error hidden" role="status"></p>' +
|
|
60
62
|
'<footer class="shared-browser-footer"><div class="shared-browser-control"><span class="shared-browser-control-status" role="status" aria-live="polite">You and Clay can control this browser</span><button type="button" data-action="control" aria-pressed="false"></button></div>' +
|
|
61
|
-
'<div class="shared-browser-details">' + viewportMenuHtml() + '<button type="button" data-action="end">End browser</button></div></footer>';
|
|
63
|
+
'<div class="shared-browser-details"><span class="shared-browser-cookie-mode"></span>' + viewportMenuHtml() + '<button type="button" data-action="end">End browser</button></div></footer>';
|
|
62
64
|
root.appendChild(el);
|
|
63
65
|
el.querySelector('[data-action="hide"]').onclick = hideSharedBrowser;
|
|
64
66
|
el.querySelector('[data-action="wide"]').onclick = function () { var wide = !(store.get('sharedBrowserUi') || {}).wide; ui({ wide: wide }); render(); };
|
|
@@ -69,8 +71,9 @@ function ensurePanel() {
|
|
|
69
71
|
var browser = store.get('sharedBrowser');
|
|
70
72
|
if (browser) send('control', { control: browser.control === 'user' ? 'agent' : 'user' });
|
|
71
73
|
};
|
|
72
|
-
el.querySelector('[data-action="end"]').onclick = function () { var browser = store.get('sharedBrowser'); send(browser && browser.phase === 'ended' ? 'open' : 'end'); };
|
|
74
|
+
el.querySelector('[data-action="end"]').onclick = function () { var browser = store.get('sharedBrowser'); send(browser && browser.phase === 'ended' ? 'open' : 'end', browser && browser.phase === 'ended' ? { incognito: browser.incognito } : undefined); };
|
|
73
75
|
bindViewportMenu(el);
|
|
76
|
+
bindBrowserSettings(el);
|
|
74
77
|
var observer = new ResizeObserver(layoutMonitor);
|
|
75
78
|
observer.observe(el.querySelector('.shared-browser-viewport'));
|
|
76
79
|
bindBrowserInput(el.querySelector('canvas'), el.querySelector('textarea'));
|
|
@@ -141,11 +144,16 @@ function render() {
|
|
|
141
144
|
el.querySelector('[data-action="control"]').hidden = !live;
|
|
142
145
|
el.querySelector('[data-action="end"]').disabled = !browser || !connected;
|
|
143
146
|
el.querySelector('[data-action="end"]').textContent = browser && browser.phase === 'ended' ? 'New browser' : 'End browser';
|
|
147
|
+
var personal = browser && browser.incognito === false;
|
|
148
|
+
el.querySelector('.shared-browser-cookie-mode').textContent = personal ? 'Personal browser' : 'Incognito';
|
|
149
|
+
el.querySelector('.shared-browser-cookie-mode').title = personal ? 'Cookies and site storage are saved for your account across your Mates.' : 'Cookies and site storage are isolated and discarded when this tab ends.';
|
|
150
|
+
el.querySelector('.shared-browser-private').textContent = personal ? 'Your cookies and site storage are remembered across your Mates and browser restarts.' : 'Site data lasts only for this browser session.';
|
|
144
151
|
var error = state.error || (browser && browser.error) || '';
|
|
145
152
|
el.querySelector('.shared-browser-error').textContent = error;
|
|
146
153
|
el.querySelector('.shared-browser-error').classList.toggle('hidden', !error);
|
|
147
154
|
el.querySelector('.shared-browser-empty').classList.toggle('hidden', !!(browser && browser.url && state.hasFrame));
|
|
148
155
|
renderViewportMenu(el, browser, operable);
|
|
156
|
+
renderBrowserSettings();
|
|
149
157
|
renderBrowserActivity(el, browser, connected);
|
|
150
158
|
layoutMonitor();
|
|
151
159
|
}
|
|
@@ -163,6 +171,16 @@ function selectBrowserTab(id) {
|
|
|
163
171
|
setBrowserTab(browser);
|
|
164
172
|
send('view', { visible: desktop() && !document.hidden }); render();
|
|
165
173
|
}
|
|
174
|
+
export function openSharedBrowserAt(value) {
|
|
175
|
+
var url = browserLinkTarget('browser:' + value);
|
|
176
|
+
if (!url) throw new Error('Enter a valid HTTP or HTTPS browser address.');
|
|
177
|
+
if (!desktop()) throw new Error('Clay Browser is available on desktop screens.');
|
|
178
|
+
if (!eligible()) throw new Error('Browser access is unavailable in this conversation.');
|
|
179
|
+
if (!store.get('connected')) throw new Error('Reconnect before opening this browser link.');
|
|
180
|
+
ensurePanel(); closeBrowserSettings(false);
|
|
181
|
+
claimRightWorkbench('browser'); store.set({ sharedBrowserOpen: true });
|
|
182
|
+
ui({ error: '' }); send('open', { url: url }); render();
|
|
183
|
+
}
|
|
166
184
|
export function openSharedBrowser() {
|
|
167
185
|
if (!eligible() || !store.get('connected')) return;
|
|
168
186
|
ensurePanel();
|
|
@@ -175,6 +193,7 @@ export function openSharedBrowser() {
|
|
|
175
193
|
}
|
|
176
194
|
export function hideSharedBrowser() {
|
|
177
195
|
closeViewportMenu();
|
|
196
|
+
closeBrowserSettings();
|
|
178
197
|
clearBrowserInput();
|
|
179
198
|
if (store.get('sharedBrowserOpen')) send('view', { visible: false });
|
|
180
199
|
store.set({ sharedBrowserOpen: false });
|
|
@@ -184,6 +203,7 @@ export function hideSharedBrowser() {
|
|
|
184
203
|
export function handleSharedBrowserMessage(msg) {
|
|
185
204
|
if (!msg.type || msg.type.indexOf('shared_browser_') !== 0) return false;
|
|
186
205
|
if (String(msg.sessionId) !== String(store.get('activeSessionId'))) return true;
|
|
206
|
+
if (handleBrowserSettingsMessage(msg)) return true;
|
|
187
207
|
if (msg.type === 'shared_browser_state') {
|
|
188
208
|
var old = store.get('sharedBrowser');
|
|
189
209
|
var tabs = msg.browsers || (msg.browser ? [msg.browser] : []);
|
|
@@ -217,6 +237,7 @@ export function handleSharedBrowserMessage(msg) {
|
|
|
217
237
|
return true;
|
|
218
238
|
}
|
|
219
239
|
export function initSharedBrowser() {
|
|
240
|
+
initBrowserLinks();
|
|
220
241
|
var button = document.getElementById('shared-browser-btn');
|
|
221
242
|
if (button) button.onclick = openSharedBrowser;
|
|
222
243
|
var toggle = document.getElementById('shared-browser-toggle-btn');
|
|
@@ -233,7 +254,7 @@ registerRightWorkbench('browser', hideSharedBrowser);
|
|
|
233
254
|
store.subscribe(function (state, previous) {
|
|
234
255
|
if (state.currentSlug !== previous.currentSlug || state.activeSessionId !== previous.activeSessionId || state.myUserId !== previous.myUserId) {
|
|
235
256
|
clearBrowserInput(); releaseRightWorkbench('browser');
|
|
236
|
-
store.set({ sharedBrowser: null, sharedBrowsers: [], sharedBrowserOpen: false, sharedBrowserUi: {} });
|
|
257
|
+
store.set({ sharedBrowser: null, sharedBrowsers: [], sharedBrowserOpen: false, sharedBrowserUi: {}, sharedBrowserSettings: {} });
|
|
237
258
|
if (state.connected) send('state_request');
|
|
238
259
|
render();
|
|
239
260
|
} else if (state.connected !== previous.connected) {
|
|
@@ -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);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { handlePaneBrowserMessage } from './pane-browser-bridge.js';
|
|
1
2
|
import { openWireframe } from './wireframe-workbench.js';
|
|
2
3
|
// Two-pane iframe shell for the split-view spike.
|
|
3
4
|
|
|
@@ -168,6 +169,7 @@ function handlePaneMessage(event) {
|
|
|
168
169
|
return;
|
|
169
170
|
}
|
|
170
171
|
if (handlePaneIssueMessage(event, host)) return;
|
|
172
|
+
if (handlePaneBrowserMessage(event, host)) return;
|
|
171
173
|
if (handlePaneFileMessage(event, host, openFile, revealFilesPanel)) return;
|
|
172
174
|
if (msg.type !== "clay-pane-context") return;
|
|
173
175
|
var frames = host.querySelectorAll(".split-pane-frame");
|