clay-server 5.2.1-beta.2 → 5.3.0-beta.2
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/clay-control-presentation-binding.js +5 -3
- package/lib/clay-product-control-tools.js +3 -3
- package/lib/clay-product-control.js +4 -2
- package/lib/clay-screen-controls.js +18 -3
- package/lib/clay-split-view-awareness.js +35 -0
- package/lib/clay-split-view-controls.js +122 -0
- package/lib/clay-split-view-input.js +20 -0
- package/lib/clay-split-view-render-proof.js +22 -0
- package/lib/pane-asset-manifest.js +63 -0
- package/lib/project-connection.js +19 -10
- package/lib/project-session-actions.js +1 -0
- package/lib/project-session-delete.js +2 -2
- package/lib/project-session-folders.js +1 -0
- package/lib/project-sessions.js +6 -1
- package/lib/project-split-view-document.js +33 -0
- package/lib/project-split-view-names.js +67 -0
- package/lib/project-split-view-organization.js +23 -0
- package/lib/project-split-views.js +479 -0
- package/lib/project-worker-conversation.js +222 -0
- package/lib/project-worker-creation-proposal.js +3 -4
- package/lib/project-worker-proposal.js +1 -1
- package/lib/project-worker-removal.js +101 -0
- package/lib/project.js +28 -1
- package/lib/public/app.js +32 -13
- package/lib/public/css/base.css +9 -0
- package/lib/public/css/filebrowser.css +0 -1
- package/lib/public/css/pane-header-controls.css +100 -0
- package/lib/public/css/pane.css +24 -41
- package/lib/public/css/popup-chat.css +123 -0
- package/lib/public/css/right-workbench.css +50 -18
- package/lib/public/css/session-folders.css +1 -1
- package/lib/public/css/split-view-layout.css +463 -0
- package/lib/public/css/split-view-worker-activity.css +97 -0
- package/lib/public/css/sticky-notes.css +13 -0
- package/lib/public/index.html +3 -3
- package/lib/public/modules/app-connection.js +4 -1
- package/lib/public/modules/app-favicon.js +1 -1
- package/lib/public/modules/app-messages.js +31 -9
- package/lib/public/modules/chat-render-runtime.js +54 -0
- package/lib/public/modules/clay-control-copy.js +3 -0
- package/lib/public/modules/clay-navigation-render.js +1 -1
- package/lib/public/modules/clay-owner-navigation.js +15 -5
- package/lib/public/modules/clay-split-view-screen.js +74 -0
- package/lib/public/modules/clay-tool-activity.js +5 -0
- package/lib/public/modules/command-palette.js +30 -28
- package/lib/public/modules/contextual-clay-session.js +26 -2
- package/lib/public/modules/contextual-clay.js +5 -2
- package/lib/public/modules/contextual-mate-sessions.js +2 -0
- package/lib/public/modules/filebrowser.js +6 -2
- package/lib/public/modules/generation-stop.js +7 -1
- package/lib/public/modules/knowledge-workbench-entry.js +43 -0
- package/lib/public/modules/markdown-view-mode.js +34 -0
- package/lib/public/modules/markdown.js +11 -0
- package/lib/public/modules/mate-knowledge-workbench.js +2 -7
- package/lib/public/modules/mate-sidebar.js +1 -1
- package/lib/public/modules/palette-clay-conversation.js +87 -0
- package/lib/public/modules/palette-render-helpers.js +25 -0
- package/lib/public/modules/pane-activation.js +46 -7
- package/lib/public/modules/pane-binding.js +12 -2
- package/lib/public/modules/pane-bridge.js +12 -3
- package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
- package/lib/public/modules/pane-feature-assets.js +35 -0
- package/lib/public/modules/pane-file-bridge.js +8 -3
- package/lib/public/modules/pane-search-receipt.js +12 -0
- package/lib/public/modules/permission-control.js +6 -6
- package/lib/public/modules/popup-chat-rendering.js +244 -0
- package/lib/public/modules/project-activation.js +2 -2
- package/lib/public/modules/qrcode.js +12 -1
- package/lib/public/modules/right-workbench.js +68 -0
- package/lib/public/modules/search-hints.js +79 -0
- package/lib/public/modules/session-context-menu.js +35 -2
- package/lib/public/modules/session-folder-collapse.js +2 -1
- package/lib/public/modules/session-folder-context.js +24 -13
- package/lib/public/modules/session-folder-delete.js +18 -1
- package/lib/public/modules/session-folder-dnd.js +4 -1
- package/lib/public/modules/session-folder-layout.js +7 -6
- package/lib/public/modules/session-search-pane-state.js +30 -0
- package/lib/public/modules/session-search.js +53 -13
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/sidebar-projects.js +1 -0
- package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
- package/lib/public/modules/sidebar-sessions.js +57 -24
- package/lib/public/modules/sidebar.js +20 -3
- package/lib/public/modules/split-group-helpers.js +1 -1
- package/lib/public/modules/split-group-restore.js +8 -11
- package/lib/public/modules/split-pair-ui.js +18 -0
- package/lib/public/modules/split-pane-header-state.js +45 -0
- package/lib/public/modules/split-pane-reconciler.js +23 -4
- package/lib/public/modules/split-pane-renderer.js +330 -23
- package/lib/public/modules/split-pane-retention.js +157 -0
- package/lib/public/modules/split-session-boundary.js +21 -0
- package/lib/public/modules/split-view-create-action.js +15 -0
- package/lib/public/modules/split-view-create-menu.js +83 -0
- package/lib/public/modules/split-view-create-request.js +23 -0
- package/lib/public/modules/split-view-layout-client.js +198 -0
- package/lib/public/modules/split-view-layout-icons.js +37 -0
- package/lib/public/modules/split-view-layout-scope.js +38 -0
- package/lib/public/modules/split-view-organization.js +73 -0
- package/lib/public/modules/split-view-presentation-close.js +35 -0
- package/lib/public/modules/split-view-presentation-drag.js +278 -0
- package/lib/public/modules/split-view-presentation-drop.js +57 -0
- package/lib/public/modules/split-view-presentation-lookup.js +44 -0
- package/lib/public/modules/split-view-presentation-renderer.js +494 -0
- package/lib/public/modules/split-view-presentations.js +498 -0
- package/lib/public/modules/split-view-shell-controls.js +28 -0
- package/lib/public/modules/split-view-sidebar-menu.js +42 -0
- package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
- package/lib/public/modules/split-view-singleton.js +21 -0
- package/lib/public/modules/split-view-worker-activity.js +94 -0
- package/lib/public/modules/split-view-worker-controls.js +30 -0
- package/lib/public/modules/split-view-worker-removal.js +137 -0
- package/lib/public/modules/split-view-worker-scroll.js +57 -0
- package/lib/public/modules/split-view-worker-transcript.js +46 -0
- package/lib/public/modules/split-view-workers.js +418 -0
- package/lib/public/modules/split-view.js +61 -22
- package/lib/public/modules/sticky-notes.js +9 -2
- package/lib/public/modules/tools.js +25 -5
- package/lib/public/modules/workbench-layout-preference.js +76 -0
- package/lib/public/modules/worker-created-presentation.js +37 -0
- package/lib/public/pane-assets/app-2DRBYY3L.js +1 -0
- package/lib/public/pane-assets/chunk-4BM7UVNG.js +2 -0
- package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
- package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
- package/lib/public/pane-assets/chunk-C4RVVYNT.js +1 -0
- package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
- package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
- package/lib/public/pane-assets/chunk-O6MQL6MR.js +167 -0
- package/lib/public/pane-assets/chunk-PO2JOHHW.js +1 -0
- package/lib/public/pane-assets/chunk-UMGHINOT.js +1 -0
- package/lib/public/pane-assets/manifest.json +553 -0
- package/lib/public/pane-assets/style-63EZCREE.css +1 -0
- package/lib/public/style.css +5 -1
- package/lib/server-owner-ui.js +6 -2
- package/lib/server-owner-view-observation.js +34 -0
- package/lib/server-palette.js +1 -0
- package/lib/server-pane-bootstrap.js +24 -0
- package/lib/server-settings.js +2 -0
- package/lib/server-static-assets.js +52 -0
- package/lib/server-workbench-layout-settings.js +56 -0
- package/lib/server.js +6 -45
- package/lib/session-activity-state.js +28 -0
- package/lib/session-list-delivery.js +17 -0
- package/lib/session-pair-turn-control.js +1 -6
- package/lib/session-search.js +3 -0
- package/lib/session-split-group-anchors.js +2 -2
- package/lib/session-split-group-roles.js +5 -8
- package/lib/session-split-group-v2-store.js +0 -3
- package/lib/sessions.js +16 -6
- package/lib/split-view-layout.js +132 -0
- package/lib/users-workbench-layout-preferences.js +33 -0
- package/lib/users.js +3 -0
- package/lib/workspace-query-service.js +1 -5
- package/lib/ws-schema.js +16 -0
- package/package.json +6 -3
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Saved conversations are independent of which compact renderers are mounted.
|
|
2
|
+
import { handlePaletteClayConversation } from './palette-clay-conversation.js';
|
|
2
3
|
import { store } from './store.js';
|
|
3
4
|
import { getWs } from './ws-ref.js';
|
|
4
5
|
import { contextualMate, contextualMateStamp, contextualFrame } from './contextual-mate-identity.js';
|
|
@@ -111,6 +112,7 @@ export function attachContextualConversations(surface) {
|
|
|
111
112
|
refreshIcons(); render();
|
|
112
113
|
}
|
|
113
114
|
export function handleContextualConversations(msg) {
|
|
115
|
+
if (handlePaletteClayConversation(msg)) return true;
|
|
114
116
|
if (msg.type !== 'contextual_mate_sessions_result') return false;
|
|
115
117
|
var kind = requestState('list') && requestState('list').requestId === msg.requestId ? 'list' : 'action';
|
|
116
118
|
var request = requestState(kind);
|
|
@@ -16,6 +16,7 @@ import { initFileBrowserContextMenu, downloadProjectFile } from './filebrowser-c
|
|
|
16
16
|
import { registerClayFileLinkOpener } from './clay-file-links.js';
|
|
17
17
|
import { isPlantUmlFile, renderPlantUmlPreview, cancelPlantUmlPreview } from './plantuml-preview.js';
|
|
18
18
|
import { isHtmlFile, renderHtmlPreview } from './html-preview.js';
|
|
19
|
+
import { initialMarkdownRendered, rememberMarkdownViewMode, resetMarkdownViewModes } from './markdown-view-mode.js';
|
|
19
20
|
|
|
20
21
|
var ctx;
|
|
21
22
|
var showDropHint = function () {};
|
|
@@ -48,7 +49,7 @@ export function initFileBrowser(_ctx) {
|
|
|
48
49
|
ctx.fileViewerEl.classList.remove("hidden");
|
|
49
50
|
if (path !== currentFilePath || (store.get("fileViewerIsHtml") && isRendered && !document.querySelector(".file-viewer-html-preview"))) requestFileContent(path);
|
|
50
51
|
},
|
|
51
|
-
onEmpty: function() { teardownFileViewer(); },
|
|
52
|
+
onEmpty: function() { resetMarkdownViewModes(); teardownFileViewer(); },
|
|
52
53
|
});
|
|
53
54
|
registerRightWorkbench("file-viewer", teardownFileViewer);
|
|
54
55
|
|
|
@@ -204,6 +205,7 @@ export function initFileBrowser(_ctx) {
|
|
|
204
205
|
document.getElementById("file-viewer-render").addEventListener("click", function () {
|
|
205
206
|
if (currentContent == null || (!currentIsMarkdown && !currentIsSvg && !store.get("fileViewerIsPlantUml") && !store.get("fileViewerIsHtml"))) return;
|
|
206
207
|
isRendered = !isRendered;
|
|
208
|
+
if (currentIsMarkdown) rememberMarkdownViewMode((store.get('clayControlFileView') || {}).projectSlug, currentFilePath, isRendered);
|
|
207
209
|
if (store.get("fileViewerIsHtml")) {
|
|
208
210
|
renderHtmlBody();
|
|
209
211
|
return;
|
|
@@ -534,6 +536,7 @@ export function setFileViewerFullscreen(enabled) {
|
|
|
534
536
|
export function resetFileBrowser() {
|
|
535
537
|
releaseRightWorkbench("file-viewer");
|
|
536
538
|
clearFileViewerTabs();
|
|
539
|
+
resetMarkdownViewModes();
|
|
537
540
|
teardownFileViewer();
|
|
538
541
|
// Clear all cached state
|
|
539
542
|
treeData = {};
|
|
@@ -1217,6 +1220,7 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1217
1220
|
store.set({ fileViewerIsHtml: !lightweightPreview && isHtmlFile(msg.path) });
|
|
1218
1221
|
store.set({ fileViewerIsPlantUml: !lightweightPreview && isPlantUmlFile(msg.path) });
|
|
1219
1222
|
if (lightweightPreview) isRendered = false;
|
|
1223
|
+
if (currentIsMarkdown && !keepRenderState) isRendered = initialMarkdownRendered((store.get('clayControlFileView') || {}).projectSlug, msg.path, store.get('pendingFileNavigation'), pendingOpenMode);
|
|
1220
1224
|
if (pendingRenderedOpen && currentIsMarkdown) isRendered = true;
|
|
1221
1225
|
|
|
1222
1226
|
if (currentIsMarkdown || currentIsSvg || store.get("fileViewerIsPlantUml") || store.get("fileViewerIsHtml")) {
|
|
@@ -1227,7 +1231,7 @@ function showFileContent(msg, expectedRevision) {
|
|
|
1227
1231
|
copyBtn.title = "Copy contents";
|
|
1228
1232
|
}
|
|
1229
1233
|
|
|
1230
|
-
// Markdown
|
|
1234
|
+
// Markdown, HTML, SVG and diagrams start as previews; a line target or diff starts as source.
|
|
1231
1235
|
if (lightweightPreview) {
|
|
1232
1236
|
renderLargeTextFile(bodyEl, msg.content, msg.size);
|
|
1233
1237
|
} else if (currentIsMarkdown) {
|
|
@@ -5,7 +5,13 @@ import { isDriverOperatedView } from './worker-pane-lock.js';
|
|
|
5
5
|
export function stopGeneration() {
|
|
6
6
|
var ws = getWs();
|
|
7
7
|
if (!store.get('processing') || !store.get('connected') || !ws || ws.readyState !== 1) return false;
|
|
8
|
-
|
|
8
|
+
var message = { type: 'stop' };
|
|
9
|
+
if (store.get('paneMode')) {
|
|
10
|
+
var binding = store.get('paneCommittedBinding');
|
|
11
|
+
if (!store.get('paneReady') || !binding || binding.sessionId !== store.get('activeSessionId') || binding.sessionOriginId !== store.get('sessionOriginId')) return false;
|
|
12
|
+
message.paneBinding = binding;
|
|
13
|
+
}
|
|
14
|
+
ws.send(JSON.stringify(message));
|
|
9
15
|
return true;
|
|
10
16
|
}
|
|
11
17
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { refreshIcons } from './icons.js';
|
|
4
|
+
import { showToast } from './utils.js';
|
|
5
|
+
import { ensurePaneFeatureAssets } from './pane-feature-assets.js';
|
|
6
|
+
|
|
7
|
+
var knowledgeWorkbenchModule = null;
|
|
8
|
+
|
|
9
|
+
export function ensureKnowledgeButton() {
|
|
10
|
+
var button = document.getElementById('knowledge-workbench-btn'); if (button) return button;
|
|
11
|
+
var tools = document.getElementById('session-actions'); if (!tools) return null;
|
|
12
|
+
button = document.createElement('button'); button.id = 'knowledge-workbench-btn'; button.type = 'button'; button.className = 'palette-tile'; button.title = 'Knowledge'; button.setAttribute('aria-label', 'Knowledge'); button.innerHTML = '<i data-lucide="book-open"></i><span class="tool-btn-label">Knowledge</span>';
|
|
13
|
+
var fixed = tools.querySelector('[data-fixed="true"]');
|
|
14
|
+
tools.insertBefore(button, fixed ? fixed.nextSibling : tools.firstChild); refreshIcons(button); return button;
|
|
15
|
+
}
|
|
16
|
+
function sameContext(receipt) {
|
|
17
|
+
var state = store.snap();
|
|
18
|
+
return receipt.owner === state.myUserId && receipt.slug === state.currentSlug && receipt.socket === getWs() &&
|
|
19
|
+
receipt.generation === state.projectSplitViewSocketGeneration && receipt.binding === state.paneCommittedBinding && state.connected;
|
|
20
|
+
}
|
|
21
|
+
export function initMateKnowledgeWorkbench() {
|
|
22
|
+
var button = ensureKnowledgeButton(); if (!button || store.get('knowledgeWorkbenchEntryInitialized')) return;
|
|
23
|
+
store.set({ knowledgeWorkbenchEntryInitialized: true });
|
|
24
|
+
button.addEventListener('click', open);
|
|
25
|
+
function open(event) {
|
|
26
|
+
event.stopImmediatePropagation();
|
|
27
|
+
if (button.disabled) return;
|
|
28
|
+
var state = store.snap(), receipt = { owner: state.myUserId, slug: state.currentSlug, socket: getWs(), generation: state.projectSplitViewSocketGeneration, binding: state.paneCommittedBinding };
|
|
29
|
+
button.disabled = true;
|
|
30
|
+
Promise.all([import('./mate-knowledge-workbench.js'), ensurePaneFeatureAssets('editor')]).then(function (loaded) {
|
|
31
|
+
var module = loaded[0];
|
|
32
|
+
if (!knowledgeWorkbenchModule) {
|
|
33
|
+
module.initMateKnowledgeWorkbench(); knowledgeWorkbenchModule = module;
|
|
34
|
+
button.removeEventListener('click', open);
|
|
35
|
+
}
|
|
36
|
+
if (sameContext(receipt)) module.openKnowledgeWorkbench();
|
|
37
|
+
}).catch(function (error) { if (sameContext(receipt)) showToast(error.message, 'error'); }).finally(function () { button.disabled = false; });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export function handleKnowledgeWorkbenchMessage(message) {
|
|
41
|
+
var module = knowledgeWorkbenchModule;
|
|
42
|
+
return module ? module.handleKnowledgeWorkbenchMessage(message) : false;
|
|
43
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Initial Markdown view for the file viewer. Rendered is the default; an
|
|
2
|
+
// explicit line target or diff needs source rows, and a toggle made by the
|
|
3
|
+
// user for a path in this tab session is respected on later focus/reopen.
|
|
4
|
+
// Choices are scoped to the source project that owns the file.
|
|
5
|
+
|
|
6
|
+
import { store } from './store.js';
|
|
7
|
+
|
|
8
|
+
function modeKey(projectSlug, path) {
|
|
9
|
+
return JSON.stringify([projectSlug || null, path]);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function chosenMode(projectSlug, path) {
|
|
13
|
+
var modes = store.get('fileViewerMarkdownModes') || {};
|
|
14
|
+
var key = modeKey(projectSlug, path);
|
|
15
|
+
return Object.prototype.hasOwnProperty.call(modes, key) ? modes[key] : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function initialMarkdownRendered(projectSlug, path, pendingNavigation, pendingMode) {
|
|
19
|
+
if (pendingNavigation && pendingNavigation.line && pendingNavigation.path === path) return false;
|
|
20
|
+
if (pendingMode && pendingMode.type === 'diff') return false;
|
|
21
|
+
var chosen = chosenMode(projectSlug, path);
|
|
22
|
+
return chosen ? chosen === 'rendered' : true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function rememberMarkdownViewMode(projectSlug, path, rendered) {
|
|
26
|
+
if (!path) return;
|
|
27
|
+
var next = Object.assign({}, store.get('fileViewerMarkdownModes') || {});
|
|
28
|
+
next[modeKey(projectSlug, path)] = rendered ? 'rendered' : 'source';
|
|
29
|
+
store.set({ fileViewerMarkdownModes: next });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resetMarkdownViewModes() {
|
|
33
|
+
store.set({ fileViewerMarkdownModes: {} });
|
|
34
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensurePaneFeatureAssets } from './pane-feature-assets.js';
|
|
1
2
|
import { enhanceSessionCitations } from './clay-session-links.js';
|
|
2
3
|
import { renderBrowserLink, enhanceBrowserLinks } from './clay-browser-links.js';
|
|
3
4
|
import { renderWireframeBlocks } from './wireframe-chat.js';
|
|
@@ -41,6 +42,7 @@ marked.use({
|
|
|
41
42
|
var mermaidInitialized = false;
|
|
42
43
|
|
|
43
44
|
export function updateMermaidTheme(vars) {
|
|
45
|
+
if (typeof mermaid === "undefined") return;
|
|
44
46
|
mermaidInitialized = true;
|
|
45
47
|
mermaid.initialize({
|
|
46
48
|
startOnLoad: false,
|
|
@@ -140,6 +142,15 @@ export function renderMermaidBlocks(el) {
|
|
|
140
142
|
if (store.get('replayingHistory')) return;
|
|
141
143
|
renderWireframeBlocks(el);
|
|
142
144
|
var blocks = el.querySelectorAll("pre code.language-mermaid");
|
|
145
|
+
if (blocks.length && typeof mermaid === 'undefined') {
|
|
146
|
+
var binding = store.get('panePendingBinding') || store.get('paneCommittedBinding'), origin = store.get('sessionOriginId');
|
|
147
|
+
var owner = store.get('myUserId'), slug = store.get('currentSlug'), generation = store.get('projectSplitViewSocketGeneration');
|
|
148
|
+
ensurePaneFeatureAssets('mermaid').then(function () {
|
|
149
|
+
if (el.isConnected && binding === (store.get('panePendingBinding') || store.get('paneCommittedBinding')) && origin === store.get('sessionOriginId') &&
|
|
150
|
+
owner === store.get('myUserId') && slug === store.get('currentSlug') && generation === store.get('projectSplitViewSocketGeneration')) renderMermaidBlocks(el);
|
|
151
|
+
}).catch(function () { /* Preserve readable source when diagram assets fail. */ });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
143
154
|
blocks.forEach(function (codeEl) {
|
|
144
155
|
var pre = codeEl.parentElement;
|
|
145
156
|
if (!pre || pre.dataset.mermaidProcessed) return;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensureKnowledgeButton as ensureButton } from './knowledge-workbench-entry.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
import { getWs } from './ws-ref.js';
|
|
3
4
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
@@ -93,13 +94,7 @@ function ensurePanel() {
|
|
|
93
94
|
var newButton = panel.querySelector('[data-knowledge-new]'); newButton.insertAdjacentHTML('beforebegin', '<button type="button" data-knowledge-back title="Back" aria-label="Previous document">' + iconHtml('arrow-left') + '</button><button type="button" data-knowledge-forward title="Forward" aria-label="Next document">' + iconHtml('arrow-right') + '</button>');
|
|
94
95
|
host.appendChild(panel); bindPanel(); refreshIcons(panel);
|
|
95
96
|
}
|
|
96
|
-
|
|
97
|
-
var button = document.getElementById('knowledge-workbench-btn'); if (button) return button;
|
|
98
|
-
var tools = document.getElementById('session-actions'); if (!tools) return null;
|
|
99
|
-
button = document.createElement('button'); button.id = 'knowledge-workbench-btn'; button.type = 'button'; button.className = 'palette-tile'; button.title = 'Knowledge'; button.setAttribute('aria-label', 'Knowledge'); button.innerHTML = '<i data-lucide="book-open"></i><span class="tool-btn-label">Knowledge</span>';
|
|
100
|
-
var fixed = tools.querySelector('[data-fixed="true"]');
|
|
101
|
-
tools.insertBefore(button, fixed ? fixed.nextSibling : tools.firstChild); refreshIcons(button); return button;
|
|
102
|
-
}
|
|
97
|
+
|
|
103
98
|
function bindPanel() {
|
|
104
99
|
panel.querySelector('[data-knowledge-close]').addEventListener('click', closeKnowledgeWorkbench);
|
|
105
100
|
panel.querySelector('[data-knowledge-new]').addEventListener('click', function () { showKnowledgeCreateMenu(panel); });
|
|
@@ -406,7 +406,7 @@ function appendMateSessionTitleLine(el, textSpan) {
|
|
|
406
406
|
|
|
407
407
|
export function renderMateSessionList(sessions) {
|
|
408
408
|
if (sessions) cachedSessions = sessions;
|
|
409
|
-
if (!listEl) return;
|
|
409
|
+
if (!listEl || (store.get('paneMode') && !store.get('activeProjectMateId') && !store.get('dmMode') && !store.get('paneActivationUnsupported') && window.frameElement && window.frameElement.clayPane && !new URLSearchParams(location.search).has('contextual'))) return;
|
|
410
410
|
listEl.innerHTML = "";
|
|
411
411
|
|
|
412
412
|
if (cachedSessions.length === 0) {
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Palette selection resolves one existing private conversation before presentation.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { getWs } from './ws-ref.js';
|
|
4
|
+
import { contextualMate, contextualMateStamp, contextualFrame, contextualTrigger } from './contextual-mate-identity.js';
|
|
5
|
+
import { selectContextualConversation, enterComposer } from './contextual-clay.js';
|
|
6
|
+
import { pauseContextualSearchForSelection } from './contextual-search.js';
|
|
7
|
+
import { closeCommandPalette } from './command-palette.js';
|
|
8
|
+
import { showToast } from './utils.js';
|
|
9
|
+
|
|
10
|
+
var socket = null, timer = null, surface = null;
|
|
11
|
+
function current() { return store.get('paletteClayConversation'); }
|
|
12
|
+
function parentView() { return JSON.stringify([store.get('currentSlug'), store.get('activeSessionId'), store.get('projectSplitViewActiveId')]); }
|
|
13
|
+
function selection() { return JSON.stringify(store.get('contextualClay') || null); }
|
|
14
|
+
function warn(message) { showToast(message || 'This conversation is unavailable. Refresh search and try again.', 'warn'); }
|
|
15
|
+
function clear() {
|
|
16
|
+
clearTimeout(timer); timer = null; socket = null;
|
|
17
|
+
if (surface) { surface.removeEventListener('contextual-clay-ready', ready); surface.removeEventListener('contextual-ask-stopped', stopped); }
|
|
18
|
+
surface = null; store.set({ paletteClayConversation: null });
|
|
19
|
+
}
|
|
20
|
+
function stopped() { if (current()) { clear(); warn('Opening was stopped. Select the conversation again to resume.'); } }
|
|
21
|
+
function live(request) {
|
|
22
|
+
var mate = contextualMate(request.mateId);
|
|
23
|
+
return store.get('connected') && socket === getWs() && socket && socket.readyState === 1 && request.ownerId === store.get('myUserId') && mate && !mate.lifecycleDeletion && mate.builtinKey === 'clay' && mate.createdBy === request.ownerId && contextualMateStamp(mate) === request.stamp && parentView() === request.view;
|
|
24
|
+
}
|
|
25
|
+
function guard() {
|
|
26
|
+
var action = store.get('contextualConversationRequest'), popup = store.get('contextualClay') || {};
|
|
27
|
+
if (action && action.status === 'loading' || popup.startingAsk || popup.mode === 'opening') { warn('Wait for the current conversation request to finish.'); return false; }
|
|
28
|
+
try { var frame = contextualFrame(); if (frame && frame.conversation) frame.conversation.checkDraft(); return true; }
|
|
29
|
+
catch (error) { warn(error.message); return false; }
|
|
30
|
+
}
|
|
31
|
+
export function openPaletteClayConversation(item, query, entryIndex) {
|
|
32
|
+
var mateId = item.mateId || String(item.projectSlug || '').slice(5);
|
|
33
|
+
var mate = contextualMate(mateId), owner = store.get('myUserId');
|
|
34
|
+
if (!mate || mate.lifecycleDeletion || item.projectSlug !== 'mate-' + mateId) { warn(); return true; }
|
|
35
|
+
if (mate.builtinKey !== 'clay') return false;
|
|
36
|
+
if (!owner || mate.createdBy !== owner || !Number.isSafeInteger(item.sessionId) || item.sessionId <= 0 || typeof item.sessionOriginId !== 'string' || !item.sessionOriginId) { warn(); return true; }
|
|
37
|
+
if (current()) { warn('Wait for the current conversation request to finish.'); return true; }
|
|
38
|
+
if (!guard()) return true;
|
|
39
|
+
socket = getWs();
|
|
40
|
+
if (!store.get('connected') || !socket || socket.readyState !== 1) { socket = null; warn('Reconnect before opening this conversation.'); return true; }
|
|
41
|
+
var request = { requestId: 'mate-conversations-' + crypto.randomUUID(), phase: 'resolving', mateId: mate.id, stamp: contextualMateStamp(mate), ownerId: owner,
|
|
42
|
+
sessionId: item.sessionId, sessionOriginId: item.sessionOriginId, view: parentView(), selection: selection(), query: query || null,
|
|
43
|
+
entryIndex: entryIndex != null && Number.isSafeInteger(Number(entryIndex)) && Number(entryIndex) >= 0 ? Number(entryIndex) : null };
|
|
44
|
+
store.set({ paletteClayConversation: request });
|
|
45
|
+
surface = document.getElementById('contextual-clay');
|
|
46
|
+
if (surface) surface.addEventListener('contextual-ask-stopped', stopped);
|
|
47
|
+
timer = setTimeout(function () { if (current() && current().requestId === request.requestId) { clear(); warn('The conversation did not become ready. Select it again to retry.'); } }, 15000);
|
|
48
|
+
try { socket.send(JSON.stringify({ type: 'home_mate_session_open', presentation: 'contextual', requestId: request.requestId, mateId: request.mateId, mateStamp: request.stamp, sessionId: 'local:' + request.sessionId, sessionOriginId: request.sessionOriginId })); }
|
|
49
|
+
catch (error) { clear(); warn('The connection changed. Select the conversation again after reconnecting.'); }
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
export function handlePaletteClayConversation(msg) {
|
|
53
|
+
var request = current();
|
|
54
|
+
if (msg.type !== 'contextual_mate_sessions_result' || !request || msg.requestId !== request.requestId) return false;
|
|
55
|
+
if (request.phase !== 'resolving') return true;
|
|
56
|
+
if (!live(request) || request.selection !== selection() || msg.mateId !== request.mateId || msg.mateStamp !== request.stamp || !msg.ok || msg.sessionId !== request.sessionId || msg.sessionOriginId !== request.sessionOriginId) { clear(); warn(); return true; }
|
|
57
|
+
if (!guard()) { clear(); return true; }
|
|
58
|
+
pauseContextualSearchForSelection();
|
|
59
|
+
store.set({ paletteClayConversation: Object.assign({}, request, { phase: 'selecting' }) });
|
|
60
|
+
var selected = false;
|
|
61
|
+
try { selected = selectContextualConversation(contextualMate(request.mateId), request.sessionId, request.sessionOriginId); } catch (error) { /* An unavailable child must not fall back to native navigation. */ }
|
|
62
|
+
if (!selected || !current() || !live(request)) { clear(); warn('This conversation could not be opened. Your draft is retained.'); return true; }
|
|
63
|
+
store.set({ paletteClayConversation: Object.assign({}, request, { phase: 'opening', selection: selection() }) });
|
|
64
|
+
closeCommandPalette();
|
|
65
|
+
if (surface) surface.removeEventListener('contextual-ask-stopped', stopped);
|
|
66
|
+
surface = document.getElementById('contextual-clay');
|
|
67
|
+
if (surface) { var trigger = contextualTrigger(request.mateId); if (trigger) { surface.dataset.focusEntry = 'true'; trigger.focus({ preventScroll: true }); enterComposer(); } surface.addEventListener('contextual-clay-ready', ready); surface.addEventListener('contextual-ask-stopped', stopped); }
|
|
68
|
+
ready();
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
function ready() {
|
|
72
|
+
var request = current();
|
|
73
|
+
if (!request || request.phase !== 'opening') return;
|
|
74
|
+
if (!live(request) || request.selection !== selection()) { clear(); warn('Your view changed. Select the conversation again.'); return; }
|
|
75
|
+
var frame = contextualFrame(request.mateId, request.sessionId), controller = frame && frame.conversation;
|
|
76
|
+
if (!controller || controller.source !== frame.contentWindow || frame.hidden || frame.inert) return;
|
|
77
|
+
var identity = controller.identity();
|
|
78
|
+
if (!identity.ready) return;
|
|
79
|
+
if (identity.ownerId !== request.ownerId || identity.mateId !== request.mateId || identity.sessionId !== request.sessionId || identity.sessionOriginId !== request.sessionOriginId || !identity.connected || identity.available === false) { clear(); warn(); return; }
|
|
80
|
+
if ((request.query || request.entryIndex !== null) && (!controller.navigateEntry || !controller.navigateEntry(request))) { clear(); warn('The conversation opened, but the requested passage could not be located.'); return; }
|
|
81
|
+
clear();
|
|
82
|
+
}
|
|
83
|
+
store.subscribe(function () {
|
|
84
|
+
var request = current();
|
|
85
|
+
if (!request && socket) { clear(); return; }
|
|
86
|
+
if (request && (!live(request) || request.phase !== 'selecting' && request.selection !== selection())) { clear(); warn('Your view or connection changed. Select the conversation again.'); }
|
|
87
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function formatRelativeDate(ts) {
|
|
2
|
+
if (!ts) return "";
|
|
3
|
+
var diff = Math.max(0, Date.now() - ts);
|
|
4
|
+
var min = Math.floor(diff / 60000);
|
|
5
|
+
if (min < 1) return "just now";
|
|
6
|
+
if (min < 60) return min + "m ago";
|
|
7
|
+
var hr = Math.floor(min / 60);
|
|
8
|
+
if (hr < 24) return hr + "h ago";
|
|
9
|
+
var days = Math.floor(hr / 24);
|
|
10
|
+
if (days < 7) return days + "d ago";
|
|
11
|
+
if (days < 30) return Math.floor(days / 7) + "w ago";
|
|
12
|
+
var d = new Date(ts);
|
|
13
|
+
var months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
14
|
+
return months[d.getMonth()] + " " + d.getDate() + ", " + d.getFullYear();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export function paletteSelectionIndex(previous, items, query) {
|
|
19
|
+
var index = previous ? items.findIndex(function (item) {
|
|
20
|
+
if (item.type !== previous.type || item.query !== previous.query) return false;
|
|
21
|
+
if (!item.data || !previous.data) return !item.data && !previous.data;
|
|
22
|
+
return item.data.projectSlug === previous.data.projectSlug && item.data.sessionId === previous.data.sessionId && item.data.sessionOriginId === previous.data.sessionOriginId;
|
|
23
|
+
}) : -1;
|
|
24
|
+
return index >= 0 ? index : query ? 0 : -1;
|
|
25
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { disarmStickyBottom } from './chat-render-runtime.js';
|
|
1
2
|
import { store } from './store.js';
|
|
2
3
|
import { getWs } from './ws-ref.js';
|
|
3
4
|
import { getCachedSessions } from './sidebar-sessions.js';
|
|
@@ -15,34 +16,58 @@ function block(value) {
|
|
|
15
16
|
document.documentElement.dataset.paneReady = value ? 'false' : 'true';
|
|
16
17
|
}
|
|
17
18
|
function draftKey() { return String(store.get('myUserId')) + ':' + store.get('sessionOriginId'); }
|
|
19
|
+
function rememberPanePosition() {
|
|
20
|
+
var messages = document.getElementById('messages'), key = draftKey();
|
|
21
|
+
if (!messages || !store.get('sessionOriginId') || store.get('panePendingBinding')) return;
|
|
22
|
+
var positions = Object.assign({}, store.get('paneRetainedPositions') || {});
|
|
23
|
+
delete positions[key];
|
|
24
|
+
positions[key] = { top: messages.scrollTop, height: messages.scrollHeight, viewport: messages.clientHeight };
|
|
25
|
+
var keys = Object.keys(positions);
|
|
26
|
+
while (keys.length > 8) delete positions[keys.shift()];
|
|
27
|
+
store.set({ paneRetainedPositions: positions });
|
|
28
|
+
}
|
|
29
|
+
function suspendPane() {
|
|
30
|
+
if (!store.get('paneSuspended')) rememberPanePosition();
|
|
31
|
+
clearDeadline();
|
|
32
|
+
store.set({ paneSuspended: true, paneReady: false, panePendingBinding: null });
|
|
33
|
+
block(true);
|
|
34
|
+
}
|
|
18
35
|
function clearDeadline() {
|
|
19
36
|
if (store.get('paneActivationTimer')) clearTimeout(store.get('paneActivationTimer'));
|
|
20
37
|
store.set({ paneActivationTimer: null });
|
|
21
38
|
}
|
|
22
|
-
function failed(requestId) {
|
|
39
|
+
function failed(requestId, reason) {
|
|
23
40
|
var pending = store.get('panePendingBinding');
|
|
24
41
|
if (!pending || requestId !== pending.requestId) return;
|
|
25
42
|
clearDeadline();
|
|
26
43
|
store.set({ paneReady: false, panePendingBinding: null });
|
|
27
44
|
block(true);
|
|
28
45
|
var frame = window.frameElement;
|
|
29
|
-
if (frame)
|
|
46
|
+
if (frame) {
|
|
47
|
+
if (reason) frame.dataset.paneActivationFailure = reason;
|
|
48
|
+
frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-activation-failed'));
|
|
49
|
+
}
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
// Claim ordinary initial pins before the legacy session-list handler runs.
|
|
33
53
|
// Authentication, list and group metadata can arrive in either order; the
|
|
34
54
|
// subscription retries when those authoritative prerequisites become ready.
|
|
35
55
|
export function primePaneSession() {
|
|
36
|
-
if (!enabled() || !window.frameElement || !window.frameElement.clayPane) return false;
|
|
37
|
-
var
|
|
56
|
+
if (store.get('paneSuspended') || !enabled() || !window.frameElement || !window.frameElement.clayPane) return false;
|
|
57
|
+
var frame = window.frameElement;
|
|
58
|
+
var id = Number(frame.dataset.requestedSessionId || frame.dataset.sessionId || store.get('paneSessionId'));
|
|
59
|
+
if (!Number.isSafeInteger(id) || id < 1) return false;
|
|
38
60
|
var session = (getCachedSessions() || []).find(function (item) { return item.id === id; });
|
|
39
61
|
if (!session || session.isMate || (session.runtimeMode || session.mode || 'gui') !== 'gui' || session.tuiSuspended) return false;
|
|
40
|
-
|
|
62
|
+
var target = paneTarget(store.snap(), getCachedSessions(), id);
|
|
63
|
+
var existing = store.get('panePendingBinding') || store.get('paneCommittedBinding');
|
|
64
|
+
activatePaneSession(id, store.get('paneSessionId') !== id || !samePaneTarget(existing, target), target);
|
|
41
65
|
return true;
|
|
42
66
|
}
|
|
43
67
|
|
|
44
68
|
export function activatePaneSession(id, force, expectedTarget) {
|
|
45
69
|
var state = store.snap(), ws = getWs();
|
|
70
|
+
if (window.frameElement && window.frameElement.closest && window.frameElement.closest('.split-pane-cached')) return false;
|
|
46
71
|
if (state.authIdentityLoaded === false) return false;
|
|
47
72
|
if (state.isMultiUserMode && !state.myUserId) return false;
|
|
48
73
|
if (!enabled() || !state.connected || !ws || ws.readyState !== 1 || state.splitGroupsProjectSlug !== state.currentSlug) return false;
|
|
@@ -54,6 +79,7 @@ export function activatePaneSession(id, force, expectedTarget) {
|
|
|
54
79
|
var old = state.paneCommittedBinding, pending = state.panePendingBinding;
|
|
55
80
|
if (!force && !state.paneSuspended && samePaneTarget(pending || old, target)) return true;
|
|
56
81
|
if (!pending && state.sessionOriginId) {
|
|
82
|
+
if (!state.paneSuspended) rememberPanePosition();
|
|
57
83
|
var draft;
|
|
58
84
|
try { draft = takeComposerDraft(); } catch (error) { return false; }
|
|
59
85
|
var drafts = Object.assign({}, state.paneRichDrafts || {});
|
|
@@ -67,9 +93,11 @@ export function activatePaneSession(id, force, expectedTarget) {
|
|
|
67
93
|
var path = new URL(store.get('wsPath'), location.origin);
|
|
68
94
|
path.searchParams.set('session', String(id));
|
|
69
95
|
store.set({ paneActivationGeneration: generation, panePendingBinding: target, paneReady: false, paneSuspended: false,
|
|
96
|
+
paneLayoutScrollLock: null, paneLastUserScrollTop: null, paneUserScrollIntent: null, paneUserScrollRevision: 0,
|
|
70
97
|
paneSessionId: id, panePinPending: false, wsPath: path.pathname + path.search });
|
|
71
98
|
block(true);
|
|
72
99
|
store.set({ paneActivationTimer: setTimeout(function () { failed(target.requestId); }, 12000) });
|
|
100
|
+
if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:activation-request'); performance.mark('clay:activation-request'); }
|
|
73
101
|
ws.send(JSON.stringify({ type: 'switch_session', id: id, paneActivation: target }));
|
|
74
102
|
return true;
|
|
75
103
|
}
|
|
@@ -87,6 +115,7 @@ export function filterPaneActivation(message) {
|
|
|
87
115
|
if (message.type === 'pane_activation_started') return true;
|
|
88
116
|
if (message.type === 'pane_activation_snapshot_done') {
|
|
89
117
|
if (!pending) return true;
|
|
118
|
+
if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:snapshot-received'); performance.mark('clay:snapshot-received'); }
|
|
90
119
|
var draft = (store.get('paneRichDrafts') || {})[String(store.get('myUserId')) + ':' + expected.sessionOriginId];
|
|
91
120
|
restoreComposerDraft(draft || { text: '', start: 0, end: 0 });
|
|
92
121
|
function paint() {
|
|
@@ -95,10 +124,20 @@ export function filterPaneActivation(message) {
|
|
|
95
124
|
var messages = document.getElementById('messages');
|
|
96
125
|
if (!samePaneTarget(current, expected) ||
|
|
97
126
|
store.get('activeSessionId') !== expected.sessionId || store.get('sessionOriginId') !== expected.sessionOriginId || store.get('replayingHistory') ||
|
|
98
|
-
!messages || messages.dataset.controlSessionId !== String(expected.sessionId) || messages.dataset.controlSessionOrigin !== expected.sessionOriginId) {
|
|
127
|
+
!messages || messages.dataset.controlSessionId !== String(expected.sessionId) || messages.dataset.controlSessionOrigin !== expected.sessionOriginId) {
|
|
128
|
+
failed(expected.requestId, 'snapshot-state-mismatch: active=' + store.get('activeSessionId') + ', origin=' + store.get('sessionOriginId') + ', messages=' + (messages && messages.dataset.controlSessionId) + ', messageOrigin=' + (messages && messages.dataset.controlSessionOrigin));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
var position = (store.get('paneRetainedPositions') || {})[String(store.get('myUserId')) + ':' + expected.sessionOriginId];
|
|
132
|
+
if (position && Number.isFinite(position.top)) {
|
|
133
|
+
disarmStickyBottom();
|
|
134
|
+
messages.scrollTop = position.top;
|
|
135
|
+
store.set({ paneLastUserScrollTop: position.top });
|
|
136
|
+
}
|
|
99
137
|
clearDeadline();
|
|
100
138
|
store.set({ paneCommittedBinding: expected, panePendingBinding: null, paneReady: true });
|
|
101
139
|
block(false);
|
|
140
|
+
if (typeof performance !== 'undefined' && performance.mark) { performance.clearMarks('clay:pane-ready'); performance.mark('clay:pane-ready'); }
|
|
102
141
|
var frame = window.frameElement;
|
|
103
142
|
if (frame) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-ready'));
|
|
104
143
|
updateContextPanel();
|
|
@@ -167,7 +206,7 @@ export function initPaneActivation() {
|
|
|
167
206
|
},
|
|
168
207
|
activate: activatePaneSession,
|
|
169
208
|
checkDraft: function () { if (!store.get('panePendingBinding')) { var draft = takeComposerDraft(); restoreComposerDraft(draft); } },
|
|
170
|
-
suspend:
|
|
209
|
+
suspend: suspendPane,
|
|
171
210
|
};
|
|
172
211
|
// A pointer that began on the previous conversation cannot become a Stop or
|
|
173
212
|
// approval click on the replacement conversation after hydration.
|
|
@@ -56,14 +56,23 @@ export function paneTarget(state, sessions, id) {
|
|
|
56
56
|
// Ordinary reusable panes have a live document binding; legacy/canonical frames
|
|
57
57
|
// retain their existing URL and receipt checks in their own bridge paths.
|
|
58
58
|
export function livePaneBinding(frame, message) {
|
|
59
|
-
if (!frame || !frame.clayPane) return false;
|
|
59
|
+
if (!frame || !frame.clayPane || frame.closest && frame.closest('.split-pane-cached')) return false;
|
|
60
60
|
var api = frame.clayPane, live = api.identity();
|
|
61
61
|
var state = store.snap(), split = state.splitPanes;
|
|
62
|
+
if (!state.connected) return false;
|
|
62
63
|
var target = live && paneTarget(state, state.paneBindingSessions || [], live.sessionId);
|
|
64
|
+
var inLegacySplit = !!(live && split && split.panes.some(function (pane) { return pane.slug === live.projectSlug && pane.sessionId === live.sessionId; }));
|
|
65
|
+
var activePresentation = (state.projectSplitViews || []).find(function (view) { return view.id === state.projectSplitViewActiveId; });
|
|
66
|
+
var inPresentation = !!(live && live.binding && !split && activePresentation && activePresentation.sessions.some(function (session) {
|
|
67
|
+
return session.id === live.sessionId && session.sessionOriginId === live.binding.sessionOriginId;
|
|
68
|
+
}));
|
|
69
|
+
var inSingletonPresentation = !!(live && live.binding && !split && !activePresentation &&
|
|
70
|
+
state.projectSplitViewSingletonOrigin === live.binding.sessionOriginId &&
|
|
71
|
+
live.sessionId === Number(frame.dataset.sessionId) && live.binding.sessionOriginId === frame.dataset.sessionOriginId);
|
|
63
72
|
return api.source === frame.contentWindow && api.document === frame.contentDocument && live && live.ready &&
|
|
64
73
|
samePaneTarget(target, live.binding) &&
|
|
65
74
|
live.ownerId === (state.myUserId || null) && live.projectSlug === state.currentSlug &&
|
|
66
|
-
|
|
75
|
+
(inLegacySplit || inPresentation || inSingletonPresentation) &&
|
|
67
76
|
(!message || samePaneBinding(live.binding, message.paneBinding));
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -77,6 +86,7 @@ export function currentPaneReview(container) {
|
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
export function paneCallbackCurrent(binding) {
|
|
89
|
+
if (store.get('paneSuspended')) return false;
|
|
80
90
|
var current = store.get('panePendingBinding') || store.get('paneCommittedBinding');
|
|
81
91
|
return current ? samePaneBinding(current, binding) : !binding;
|
|
82
92
|
}
|
|
@@ -5,9 +5,11 @@ import { forwardStudioPresentation } from './clay-studio-render.js';
|
|
|
5
5
|
// Same-origin messages only, both directions.
|
|
6
6
|
|
|
7
7
|
import { store } from './store.js';
|
|
8
|
+
import { beginPaneLayoutScrollLock, endPaneLayoutScrollLock } from './chat-render-runtime.js';
|
|
8
9
|
import { paneMessageBinding, samePaneBinding } from './pane-binding.js';
|
|
9
10
|
import { getEffectiveContextView } from './context-view-preference.js';
|
|
10
11
|
import { forceExternalLinkToNewTab } from './pane-links.js';
|
|
12
|
+
import { toggleSearch, controlPaneSearch } from './session-search.js';
|
|
11
13
|
|
|
12
14
|
export function reportPaneContext(data) {
|
|
13
15
|
if (!store.get('paneMode') || window.parent === window) return;
|
|
@@ -71,8 +73,15 @@ export function initPaneBridge() {
|
|
|
71
73
|
window.addEventListener("message", function (event) {
|
|
72
74
|
if (event.origin !== window.location.origin) return;
|
|
73
75
|
var msg = event.data;
|
|
74
|
-
if (!msg || msg.type !== "clay-pane-toggle-context") return;
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
if (!msg || (msg.type !== "clay-pane-toggle-context" && msg.type !== "clay-pane-toggle-search" && msg.type !== "clay-pane-search-command" && msg.type !== "clay-pane-layout-resize")) return;
|
|
77
|
+
var committed = store.get('paneCommittedBinding');
|
|
78
|
+
if (event.source !== window.parent || !committed || !paneMessageBinding() ||
|
|
79
|
+
!samePaneBinding(committed, msg.paneBinding) || !samePaneBinding(paneMessageBinding(), msg.paneBinding)) return;
|
|
80
|
+
if (msg.type === "clay-pane-layout-resize") {
|
|
81
|
+
if (msg.phase === 'begin') beginPaneLayoutScrollLock(msg.token, msg.scrollTop);
|
|
82
|
+
else if (msg.phase === 'end') endPaneLayoutScrollLock(msg.token);
|
|
83
|
+
} else if (msg.type === "clay-pane-toggle-context") togglePaneContextPanel();
|
|
84
|
+
else if (msg.type === "clay-pane-toggle-search") toggleSearch();
|
|
85
|
+
else controlPaneSearch(msg.action, msg.query);
|
|
77
86
|
});
|
|
78
87
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
|
|
3
|
+
// Executable initializers belong to this module; the store holds only IDs.
|
|
4
|
+
var initializers = [];
|
|
5
|
+
|
|
6
|
+
// Parent-owned workbench chrome is unnecessary for a pane's first transcript.
|
|
7
|
+
// Keep the same initializers, but run them one task at a time after its exact
|
|
8
|
+
// authenticated activation has made the conversation interactive.
|
|
9
|
+
function drain() {
|
|
10
|
+
var state = store.snap(), boot = state.paneDeferredBootstrap;
|
|
11
|
+
if (!boot || boot.scheduled || !boot.jobs.length || !state.connected || !state.paneReady || state.paneSuspended) return;
|
|
12
|
+
store.set({ paneDeferredBootstrap: Object.assign({}, boot, { scheduled: true }) });
|
|
13
|
+
setTimeout(function () {
|
|
14
|
+
var latest = store.snap(), pending = latest.paneDeferredBootstrap;
|
|
15
|
+
if (!pending) return;
|
|
16
|
+
var jobs = pending.jobs.slice(), job = latest.connected && latest.paneReady && !latest.paneSuspended ? jobs.shift() : null;
|
|
17
|
+
store.set({ paneDeferredBootstrap: Object.assign({}, pending, { scheduled: false, jobs: jobs }) });
|
|
18
|
+
try { if (job !== null && initializers[job]) { var initialize = initializers[job]; initializers[job] = null; initialize(); } } finally { drain(); }
|
|
19
|
+
}, 0);
|
|
20
|
+
}
|
|
21
|
+
export function initializeAfterPaneReady(jobs) {
|
|
22
|
+
if (!store.get('paneMode')) { jobs.forEach(function (job) { job(); }); return; }
|
|
23
|
+
var boot = store.get('paneDeferredBootstrap');
|
|
24
|
+
if (!boot) {
|
|
25
|
+
boot = { jobs: [], scheduled: false };
|
|
26
|
+
store.subscribe(function (next, previous) {
|
|
27
|
+
if (next.paneReady !== previous.paneReady || next.connected !== previous.connected || next.paneSuspended !== previous.paneSuspended) drain();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
store.set({ paneDeferredBootstrap: Object.assign({}, boot, { jobs: boot.jobs.concat(jobs.map(function (job) { var id = initializers.length; initializers.push(job); return id; })) }) });
|
|
31
|
+
drain();
|
|
32
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// In-flight promises are module-owned resources, never store data.
|
|
2
|
+
var featureRequests = {};
|
|
3
|
+
|
|
4
|
+
// Only server-authored, inert feature tags are eligible. Parent documents retain
|
|
5
|
+
// their normal vendor loading; pane documents load these groups on first use.
|
|
6
|
+
export function ensurePaneFeatureAssets(group) {
|
|
7
|
+
if (['editor', 'mermaid', 'qr'].indexOf(group) === -1) return Promise.reject(new Error('Unknown feature assets'));
|
|
8
|
+
var existing = featureRequests[group];
|
|
9
|
+
if (existing) return existing;
|
|
10
|
+
var tags = Array.from(document.querySelectorAll('script[data-pane-asset-group="' + group + '"]'));
|
|
11
|
+
var promise = tags.reduce(function (previous, tag) {
|
|
12
|
+
return previous.then(function () {
|
|
13
|
+
if (tag.dataset.loaded === 'true') return;
|
|
14
|
+
return new Promise(function (resolve, reject) {
|
|
15
|
+
var script = document.createElement('script'), timer;
|
|
16
|
+
function failed() {
|
|
17
|
+
clearTimeout(timer); script.onload = null; script.onerror = null; script.remove();
|
|
18
|
+
reject(new Error('Could not load ' + group + ' assets. Try again.'));
|
|
19
|
+
}
|
|
20
|
+
script.src = tag.dataset.src; script.async = false;
|
|
21
|
+
script.onload = function () { clearTimeout(timer); tag.dataset.loaded = 'true'; resolve(); };
|
|
22
|
+
script.onerror = failed; timer = setTimeout(failed, 15000);
|
|
23
|
+
tag.after(script);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
}, Promise.resolve());
|
|
27
|
+
promise = promise.then(function () {
|
|
28
|
+
var value = group === 'editor' ? window.CodeMirror : group === 'mermaid' ? window.mermaid : window.qrcode;
|
|
29
|
+
if (!value) throw new Error('Could not load ' + group + ' assets. Try again.');
|
|
30
|
+
}).catch(function (error) {
|
|
31
|
+
delete featureRequests[group]; throw error;
|
|
32
|
+
});
|
|
33
|
+
featureRequests[group] = promise;
|
|
34
|
+
return promise;
|
|
35
|
+
}
|
|
@@ -59,9 +59,14 @@ export function handlePaneFileMessage(event, host, openFile, openFiles) {
|
|
|
59
59
|
if (!msg || msg.type !== "clay-pane-open-file") return false;
|
|
60
60
|
var frame = paneFrame(host, event.source);
|
|
61
61
|
var state = store.snap();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
// A live reusable pane is authorized by its document binding, which covers
|
|
63
|
+
// legacy splits, saved views and single-session views. Other frames keep the
|
|
64
|
+
// legacy split membership and URL checks.
|
|
65
|
+
var live = frame && frame.clayPane;
|
|
66
|
+
var pane = live ? { slug: frame.dataset.projectSlug, sessionId: frame.dataset.sessionId } : matchingPane(frame, state.splitPanes);
|
|
67
|
+
if (live && !livePaneBinding(frame, msg)) return true;
|
|
68
|
+
if (live && Number(msg.sessionId) !== live.identity().sessionId) return true;
|
|
69
|
+
if (!pane || (!live && !hasLivePaneSource(frame, pane)) || String(pane.slug) !== String(state.currentSlug) || msg.projectSlug !== frame.dataset.projectSlug || String(msg.sessionId) !== String(frame.dataset.sessionId) || !validLocation(msg.line) || !validLocation(msg.column)) return true;
|
|
65
70
|
if (typeof msg.path !== "string" || !msg.path) return true;
|
|
66
71
|
if (!state.connected || typeof openFile !== "function") return true;
|
|
67
72
|
// The frame/session association was validated above. The host browser owns
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { samePaneBinding } from './pane-binding.js';
|
|
2
|
+
|
|
3
|
+
function acceptsPaneSearchResults(state, msg) {
|
|
4
|
+
var binding = state.paneCommittedBinding;
|
|
5
|
+
var receipt = state.paneSearchReceipt;
|
|
6
|
+
return !!(state.paneReady && !state.panePendingBinding && !state.paneSuspended && binding && receipt &&
|
|
7
|
+
receipt.epoch === state.paneSearchEpoch && samePaneBinding(receipt.binding, binding) &&
|
|
8
|
+
Number(binding.sessionId) === Number(msg.sessionId) && Number(binding.sessionId) === Number(state.activeSessionId) &&
|
|
9
|
+
receipt.query === msg.query);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export { acceptsPaneSearchResults };
|