clay-server 5.2.1-beta.2 → 5.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/lib/clay-control-presentation-binding.js +5 -3
  2. package/lib/clay-product-control-tools.js +3 -3
  3. package/lib/clay-product-control.js +4 -2
  4. package/lib/clay-screen-controls.js +18 -3
  5. package/lib/clay-split-view-awareness.js +35 -0
  6. package/lib/clay-split-view-controls.js +122 -0
  7. package/lib/clay-split-view-input.js +20 -0
  8. package/lib/clay-split-view-render-proof.js +22 -0
  9. package/lib/pane-asset-manifest.js +63 -0
  10. package/lib/project-connection.js +19 -10
  11. package/lib/project-session-actions.js +1 -0
  12. package/lib/project-session-delete.js +2 -2
  13. package/lib/project-session-folders.js +1 -0
  14. package/lib/project-sessions.js +6 -1
  15. package/lib/project-split-view-document.js +33 -0
  16. package/lib/project-split-view-names.js +67 -0
  17. package/lib/project-split-view-organization.js +23 -0
  18. package/lib/project-split-views.js +479 -0
  19. package/lib/project-worker-conversation.js +158 -0
  20. package/lib/project-worker-creation-proposal.js +3 -4
  21. package/lib/project-worker-proposal.js +1 -1
  22. package/lib/project-worker-removal.js +101 -0
  23. package/lib/project.js +28 -1
  24. package/lib/public/app.js +32 -13
  25. package/lib/public/css/base.css +9 -0
  26. package/lib/public/css/filebrowser.css +0 -1
  27. package/lib/public/css/pane-header-controls.css +100 -0
  28. package/lib/public/css/pane.css +24 -5
  29. package/lib/public/css/popup-chat.css +123 -0
  30. package/lib/public/css/right-workbench.css +50 -18
  31. package/lib/public/css/split-view-layout.css +460 -0
  32. package/lib/public/css/split-view-worker-activity.css +97 -0
  33. package/lib/public/css/sticky-notes.css +13 -0
  34. package/lib/public/index.html +3 -3
  35. package/lib/public/modules/app-connection.js +4 -1
  36. package/lib/public/modules/app-messages.js +25 -8
  37. package/lib/public/modules/chat-render-runtime.js +45 -0
  38. package/lib/public/modules/clay-control-copy.js +3 -0
  39. package/lib/public/modules/clay-navigation-render.js +1 -1
  40. package/lib/public/modules/clay-owner-navigation.js +15 -5
  41. package/lib/public/modules/clay-split-view-screen.js +74 -0
  42. package/lib/public/modules/clay-tool-activity.js +5 -0
  43. package/lib/public/modules/command-palette.js +30 -28
  44. package/lib/public/modules/contextual-clay-session.js +26 -2
  45. package/lib/public/modules/contextual-clay.js +5 -2
  46. package/lib/public/modules/contextual-mate-sessions.js +2 -0
  47. package/lib/public/modules/generation-stop.js +7 -1
  48. package/lib/public/modules/knowledge-workbench-entry.js +43 -0
  49. package/lib/public/modules/markdown.js +11 -0
  50. package/lib/public/modules/mate-knowledge-workbench.js +2 -7
  51. package/lib/public/modules/mate-sidebar.js +1 -1
  52. package/lib/public/modules/palette-clay-conversation.js +87 -0
  53. package/lib/public/modules/palette-render-helpers.js +25 -0
  54. package/lib/public/modules/pane-activation.js +46 -7
  55. package/lib/public/modules/pane-binding.js +12 -2
  56. package/lib/public/modules/pane-bridge.js +12 -3
  57. package/lib/public/modules/pane-deferred-bootstrap.js +32 -0
  58. package/lib/public/modules/pane-feature-assets.js +35 -0
  59. package/lib/public/modules/pane-search-receipt.js +12 -0
  60. package/lib/public/modules/permission-control.js +6 -6
  61. package/lib/public/modules/popup-chat-rendering.js +244 -0
  62. package/lib/public/modules/project-activation.js +2 -2
  63. package/lib/public/modules/qrcode.js +12 -1
  64. package/lib/public/modules/right-workbench.js +68 -0
  65. package/lib/public/modules/search-hints.js +79 -0
  66. package/lib/public/modules/session-context-menu.js +35 -2
  67. package/lib/public/modules/session-folder-context.js +24 -13
  68. package/lib/public/modules/session-folder-delete.js +18 -1
  69. package/lib/public/modules/session-folder-layout.js +7 -6
  70. package/lib/public/modules/session-search-pane-state.js +30 -0
  71. package/lib/public/modules/session-search.js +53 -13
  72. package/lib/public/modules/sidebar-mobile.js +3 -3
  73. package/lib/public/modules/sidebar-projects.js +1 -0
  74. package/lib/public/modules/sidebar-session-folders-ui.js +3 -3
  75. package/lib/public/modules/sidebar-sessions.js +57 -24
  76. package/lib/public/modules/sidebar.js +20 -3
  77. package/lib/public/modules/split-group-helpers.js +1 -1
  78. package/lib/public/modules/split-group-restore.js +8 -11
  79. package/lib/public/modules/split-pair-ui.js +18 -0
  80. package/lib/public/modules/split-pane-header-state.js +45 -0
  81. package/lib/public/modules/split-pane-reconciler.js +23 -4
  82. package/lib/public/modules/split-pane-renderer.js +330 -23
  83. package/lib/public/modules/split-pane-retention.js +157 -0
  84. package/lib/public/modules/split-session-boundary.js +21 -0
  85. package/lib/public/modules/split-view-create-action.js +15 -0
  86. package/lib/public/modules/split-view-create-menu.js +83 -0
  87. package/lib/public/modules/split-view-create-request.js +23 -0
  88. package/lib/public/modules/split-view-layout-client.js +198 -0
  89. package/lib/public/modules/split-view-layout-icons.js +37 -0
  90. package/lib/public/modules/split-view-layout-scope.js +38 -0
  91. package/lib/public/modules/split-view-organization.js +73 -0
  92. package/lib/public/modules/split-view-presentation-close.js +35 -0
  93. package/lib/public/modules/split-view-presentation-drag.js +254 -0
  94. package/lib/public/modules/split-view-presentation-drop.js +57 -0
  95. package/lib/public/modules/split-view-presentation-lookup.js +44 -0
  96. package/lib/public/modules/split-view-presentation-renderer.js +494 -0
  97. package/lib/public/modules/split-view-presentations.js +495 -0
  98. package/lib/public/modules/split-view-shell-controls.js +28 -0
  99. package/lib/public/modules/split-view-sidebar-menu.js +42 -0
  100. package/lib/public/modules/split-view-sidebar-renderer.js +30 -0
  101. package/lib/public/modules/split-view-singleton.js +21 -0
  102. package/lib/public/modules/split-view-worker-activity.js +94 -0
  103. package/lib/public/modules/split-view-worker-controls.js +30 -0
  104. package/lib/public/modules/split-view-worker-removal.js +137 -0
  105. package/lib/public/modules/split-view-worker-scroll.js +57 -0
  106. package/lib/public/modules/split-view-worker-transcript.js +46 -0
  107. package/lib/public/modules/split-view-workers.js +352 -0
  108. package/lib/public/modules/split-view.js +61 -22
  109. package/lib/public/modules/sticky-notes.js +9 -2
  110. package/lib/public/modules/tools.js +25 -5
  111. package/lib/public/modules/workbench-layout-preference.js +76 -0
  112. package/lib/public/pane-assets/app-OJEQXBBS.js +1 -0
  113. package/lib/public/pane-assets/chunk-4SMIVAB4.js +1 -0
  114. package/lib/public/pane-assets/chunk-623V5CEO.js +1 -0
  115. package/lib/public/pane-assets/chunk-6SM2WQJX.js +1 -0
  116. package/lib/public/pane-assets/chunk-7H36SUZA.js +2 -0
  117. package/lib/public/pane-assets/chunk-DGOFGEUK.js +1 -0
  118. package/lib/public/pane-assets/chunk-GML2R5JE.js +167 -0
  119. package/lib/public/pane-assets/chunk-I57URYSL.js +1 -0
  120. package/lib/public/pane-assets/chunk-I5P3MWQG.js +1 -0
  121. package/lib/public/pane-assets/chunk-UVGOPFEX.js +2 -0
  122. package/lib/public/pane-assets/manifest.json +551 -0
  123. package/lib/public/pane-assets/style-GN7R6XKV.css +1 -0
  124. package/lib/public/style.css +5 -1
  125. package/lib/server-owner-ui.js +6 -2
  126. package/lib/server-owner-view-observation.js +34 -0
  127. package/lib/server-palette.js +1 -0
  128. package/lib/server-pane-bootstrap.js +24 -0
  129. package/lib/server-settings.js +2 -0
  130. package/lib/server-static-assets.js +52 -0
  131. package/lib/server-workbench-layout-settings.js +56 -0
  132. package/lib/server.js +6 -45
  133. package/lib/session-activity-state.js +28 -0
  134. package/lib/session-list-delivery.js +17 -0
  135. package/lib/session-pair-turn-control.js +1 -6
  136. package/lib/session-search.js +3 -0
  137. package/lib/session-split-group-anchors.js +2 -2
  138. package/lib/session-split-group-roles.js +5 -8
  139. package/lib/session-split-group-v2-store.js +0 -3
  140. package/lib/sessions.js +16 -6
  141. package/lib/split-view-layout.js +132 -0
  142. package/lib/users-workbench-layout-preferences.js +33 -0
  143. package/lib/users.js +3 -0
  144. package/lib/workspace-query-service.js +1 -5
  145. package/lib/ws-schema.js +15 -0
  146. package/package.json +6 -3
@@ -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
+ }
@@ -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
- function ensureButton() {
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) frame.dispatchEvent(new window.parent.CustomEvent('clay-pane-activation-failed'));
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 id = store.get('paneSessionId');
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
- activatePaneSession(id);
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) { failed(expected.requestId); return; }
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: function () { store.set({ paneSuspended: true, paneReady: false }); block(true); },
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
- split && split.panes.some(function (pane) { return pane.slug === live.projectSlug && pane.sessionId === live.sessionId; }) &&
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
- if (event.source !== window.parent || store.get('paneCommittedBinding') && !samePaneBinding(paneMessageBinding(), msg.paneBinding)) return;
76
- togglePaneContextPanel();
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
+ }
@@ -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 };
@@ -48,7 +48,7 @@ export function createPermissionControl(className, ariaLabel) {
48
48
  control.innerHTML = '<div class="session-permission-segmented">' +
49
49
  '<button type="button" data-permission-mode="default" aria-label="Ask permissions" title="Ask permissions"><span class="permission-label">Ask</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
50
50
  '<button type="button" data-permission-mode="auto" aria-label="Auto permissions" title="Auto permissions"><span class="permission-label">Auto</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
51
- '<button type="button" data-permission-mode="bypassPermissions" aria-label="Skip permissions" title="Skip permissions"><span class="permission-label">Skip</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
51
+ '<button type="button" data-permission-mode="bypassPermissions" aria-label="YOLO permissions" title="YOLO permissions"><span class="permission-label">YOLO</span><span class="permission-spinner" aria-hidden="true"></span></button>' +
52
52
  '</div><span class="session-permission-status" aria-live="polite"></span>';
53
53
  control.querySelector(".session-permission-status").setAttribute("aria-atomic", "true");
54
54
  return control;
@@ -56,12 +56,12 @@ export function createPermissionControl(className, ariaLabel) {
56
56
 
57
57
  function modeLabel(mode) {
58
58
  var labels = { default: "Ask", plan: "Plan", acceptEdits: "Auto-accept edits", dontAsk: "Don't ask",
59
- bypassPermissions: "Skip permissions", auto: "Auto" };
59
+ bypassPermissions: "YOLO permissions", auto: "Auto" };
60
60
  return labels[mode] || mode;
61
61
  }
62
62
 
63
63
  export function permissionStatusText(state, pending, autoSupported, autoVisible) {
64
- if (state.globalPermissionModeForced) return "Skip permissions forced by the server setting";
64
+ if (state.globalPermissionModeForced) return "YOLO permissions forced by the server setting";
65
65
  if (pending) return "Applying…";
66
66
  if (state.permissionMode === "plan") return "Plan workflow";
67
67
  if (state.permissionMode === "acceptEdits") return "Auto-accept edits workflow";
@@ -75,7 +75,7 @@ export function permissionStatusText(state, pending, autoSupported, autoVisible)
75
75
  }
76
76
 
77
77
  function compactPermissionStatusText(state, pending, autoSupported, autoVisible, fullText) {
78
- if (state.globalPermissionModeForced) return "Forced Skip";
78
+ if (state.globalPermissionModeForced) return "Forced YOLO";
79
79
  if (pending) return "Applying…";
80
80
  if (state.permissionMode === "plan") return "Plan";
81
81
  if (state.permissionMode === "acceptEdits") return "Auto-accept";
@@ -102,7 +102,7 @@ export function renderPermissionControl(control, state) {
102
102
  control.classList.toggle("permission-globally-forced", globallyForced);
103
103
  control.classList.toggle("permission-runtime-fallback", state.permissionMode === "auto" && !!state.effectivePermissionMode && state.effectivePermissionMode !== "auto");
104
104
  control.setAttribute("aria-busy", pending ? "true" : "false");
105
- var buttonTitles = { default: "Ask permissions", auto: "Auto permissions", bypassPermissions: "Skip permissions" };
105
+ var buttonTitles = { default: "Ask permissions", auto: "Auto permissions", bypassPermissions: "YOLO permissions" };
106
106
  var buttons = control.querySelectorAll("[data-permission-mode]");
107
107
  for (var i = 0; i < buttons.length; i++) {
108
108
  var mode = buttons[i].dataset.permissionMode;
@@ -147,7 +147,7 @@ export function bindPermissionControl(control, getState) {
147
147
  export function sendPermissionMode(projectSlug, sessionId, mode) {
148
148
  var ws = getWs();
149
149
  if (store.get('skipPermsEnabled') === true) {
150
- showToast("Skip permissions is forced by the server setting.", "error");
150
+ showToast("YOLO permissions are forced by the server setting.", "error");
151
151
  return false;
152
152
  }
153
153
  if (!projectSlug || !sessionId || !ws || ws.readyState !== 1) {