clay-server 5.3.0-beta.6 → 5.3.0

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 (111) hide show
  1. package/lib/project-admin-overview.js +22 -11
  2. package/lib/project-background-tasks.js +76 -0
  3. package/lib/project-connection.js +1 -1
  4. package/lib/project-sessions.js +12 -5
  5. package/lib/project-split-views.js +12 -3
  6. package/lib/project-view-store.js +7 -5
  7. package/lib/project-worker-conversation.js +5 -0
  8. package/lib/public/app.js +7 -0
  9. package/lib/public/css/contextual-clay.css +2 -1
  10. package/lib/public/css/mate-conversation.css +1 -1
  11. package/lib/public/css/pending-message-queue.css +24 -19
  12. package/lib/public/css/project-admin-overview.css +42 -13
  13. package/lib/public/css/shared-browser.css +31 -8
  14. package/lib/public/css/split-view-layout.css +92 -7
  15. package/lib/public/css/tool-pane.css +4 -7
  16. package/lib/public/index.html +5 -5
  17. package/lib/public/modules/agent-message-bubbles.js +96 -0
  18. package/lib/public/modules/app-header.js +6 -0
  19. package/lib/public/modules/app-messages.js +26 -9
  20. package/lib/public/modules/background-task-previews.js +8 -0
  21. package/lib/public/modules/background-task-viewer.js +97 -0
  22. package/lib/public/modules/background-tasks-ui.js +34 -125
  23. package/lib/public/modules/chat-bubble-renderer.js +11 -5
  24. package/lib/public/modules/contextual-clay-geometry.js +7 -3
  25. package/lib/public/modules/pane-activation.js +5 -3
  26. package/lib/public/modules/pane-header-tooltips.js +32 -0
  27. package/lib/public/modules/pending-message-queue-drag.js +69 -0
  28. package/lib/public/modules/pending-message-queue.js +3 -33
  29. package/lib/public/modules/permission-control.js +6 -5
  30. package/lib/public/modules/project-admin-overview.js +125 -10
  31. package/lib/public/modules/project-grouping.js +9 -0
  32. package/lib/public/modules/server-settings-controls.js +33 -4
  33. package/lib/public/modules/session-folder-toolbar.js +1 -1
  34. package/lib/public/modules/session-tool-item.js +86 -0
  35. package/lib/public/modules/settings-select.js +2 -1
  36. package/lib/public/modules/shared-browser-tabs.js +37 -21
  37. package/lib/public/modules/sidebar-projects.js +1 -10
  38. package/lib/public/modules/split-session-empty-pane.js +47 -0
  39. package/lib/public/modules/split-view-create-menu.js +74 -65
  40. package/lib/public/modules/split-view-layout-scope.js +4 -0
  41. package/lib/public/modules/split-view-picker-actions.js +71 -0
  42. package/lib/public/modules/split-view-picker-render.js +81 -0
  43. package/lib/public/modules/split-view-presentation-renderer.js +8 -8
  44. package/lib/public/modules/split-view-worker-activity.js +2 -2
  45. package/lib/public/modules/split-view-worker-transcript.js +70 -33
  46. package/lib/public/modules/split-view-workers.js +6 -4
  47. package/lib/public/modules/terminal-viewport.js +14 -0
  48. package/lib/public/modules/terminal.js +3 -4
  49. package/lib/public/modules/theme-palette.js +177 -0
  50. package/lib/public/modules/theme.js +1 -174
  51. package/lib/public/modules/tool-pane-chrome.js +7 -1
  52. package/lib/public/modules/tools.js +5 -89
  53. package/lib/public/modules/tooltip.js +32 -6
  54. package/lib/public/modules/wireframe-workbench.js +2 -2
  55. package/lib/public/modules/worker-message-previews.js +39 -0
  56. package/lib/public/pane-assets/app-5W6KJXK5.js +1 -0
  57. package/lib/public/pane-assets/chunk-2IJUTAB6.js +2 -0
  58. package/lib/public/pane-assets/{chunk-BMHTBRMO.js → chunk-7OVNC5HT.js} +1 -1
  59. package/lib/public/pane-assets/chunk-BQN7B62H.js +3 -0
  60. package/lib/public/pane-assets/{chunk-2AIWCI6Y.js → chunk-EMLN2Y7L.js} +1 -1
  61. package/lib/public/pane-assets/{chunk-VKYBAVWT.js → chunk-ENPAHXLB.js} +1 -1
  62. package/lib/public/pane-assets/chunk-EPBGGZRT.js +1 -0
  63. package/lib/public/pane-assets/chunk-EYTE56IA.js +1 -0
  64. package/lib/public/pane-assets/{chunk-6NYCNWVF.js → chunk-FQTNR4ZS.js} +1 -1
  65. package/lib/public/pane-assets/chunk-L6LSA5WI.js +1 -0
  66. package/lib/public/pane-assets/chunk-M57UIGZV.js +1 -0
  67. package/lib/public/pane-assets/chunk-MGTFTEJP.js +1 -0
  68. package/lib/public/pane-assets/chunk-MMG6MYCN.js +1 -0
  69. package/lib/public/pane-assets/chunk-MZLMEXMW.js +1 -0
  70. package/lib/public/pane-assets/chunk-OG3E5SVA.js +166 -0
  71. package/lib/public/pane-assets/chunk-RGAFZ3T2.js +1 -0
  72. package/lib/public/pane-assets/chunk-RNDRURMF.js +1 -0
  73. package/lib/public/pane-assets/{chunk-PV4PCH2V.js → chunk-SJOT74VW.js} +1 -1
  74. package/lib/public/pane-assets/{chunk-LDIZLKB2.js → chunk-SJTGHIN5.js} +1 -1
  75. package/lib/public/pane-assets/chunk-SLMAFKI2.js +1 -0
  76. package/lib/public/pane-assets/chunk-U3I5KY3B.js +1 -0
  77. package/lib/public/pane-assets/{chunk-VNAV23CM.js → chunk-UPXU2IPC.js} +1 -1
  78. package/lib/public/pane-assets/{chunk-TMGSDFJI.js → chunk-VEBYD7TL.js} +1 -1
  79. package/lib/public/pane-assets/chunk-XBHYNXXT.js +1 -0
  80. package/lib/public/pane-assets/manifest.json +411 -399
  81. package/lib/public/pane-assets/style-BSKKK54Q.css +1 -0
  82. package/lib/sdk-message-processor.js +5 -2
  83. package/lib/server-storage.js +27 -3
  84. package/lib/server.js +4 -2
  85. package/lib/sessions.js +1 -1
  86. package/lib/storage-usage.js +29 -7
  87. package/lib/terminal-manager.js +2 -2
  88. package/lib/worker-message-previews.js +50 -0
  89. package/lib/ws-schema.js +1 -1
  90. package/lib/yoke/adapters/claude-worker.js +8 -0
  91. package/lib/yoke/adapters/claude.js +17 -2
  92. package/lib/yoke/claude-task-reader.js +94 -0
  93. package/lib/yoke/interface.js +1 -0
  94. package/package.json +1 -1
  95. package/lib/public/pane-assets/app-7UZ727NA.js +0 -1
  96. package/lib/public/pane-assets/chunk-6MIHIQ2W.js +0 -1
  97. package/lib/public/pane-assets/chunk-6UJ2ALOB.js +0 -1
  98. package/lib/public/pane-assets/chunk-6XK2OATZ.js +0 -1
  99. package/lib/public/pane-assets/chunk-BKIDR2ME.js +0 -1
  100. package/lib/public/pane-assets/chunk-GEFCFZEN.js +0 -1
  101. package/lib/public/pane-assets/chunk-GN2NQEVC.js +0 -2
  102. package/lib/public/pane-assets/chunk-IMLMBPT4.js +0 -166
  103. package/lib/public/pane-assets/chunk-K5S6AILM.js +0 -1
  104. package/lib/public/pane-assets/chunk-OWNNLZ36.js +0 -1
  105. package/lib/public/pane-assets/chunk-P2C3LOWL.js +0 -3
  106. package/lib/public/pane-assets/chunk-R4LMPTKU.js +0 -1
  107. package/lib/public/pane-assets/chunk-VWTHIQCE.js +0 -1
  108. package/lib/public/pane-assets/chunk-WM3K5AFH.js +0 -1
  109. package/lib/public/pane-assets/chunk-XOB3O327.js +0 -1
  110. package/lib/public/pane-assets/chunk-XOOUCFYT.js +0 -1
  111. package/lib/public/pane-assets/style-ZM7OOU3M.css +0 -1
@@ -1,4 +1,4 @@
1
- // This projection is an allowlist. Never add runtime/session/path objects here.
1
+ // Allowlisted display fields and scalar aggregates, never session identities or path/runtime objects.
2
2
  function projectMetadata(status, access, users) {
3
3
  if (!status || status.isMate || !access || access.error) return null;
4
4
  var owner = access.ownerId && users.findUserById(access.ownerId);
@@ -13,6 +13,12 @@ function projectMetadata(status, access, users) {
13
13
  administrativeAccess: true,
14
14
  ownershipRecovery: access.ownershipRecovery === "required" || !owner,
15
15
  runtimeHealth: "Registered",
16
+ icon: typeof status.icon === 'string' ? status.icon : null,
17
+ isWorktree: status.isWorktree === true,
18
+ parentSlug: status.isWorktree === true && typeof status.parentSlug === 'string' ? status.parentSlug : null,
19
+ sessions: Number.isSafeInteger(status.sessions) && status.sessions >= 0 ? status.sessions : null,
20
+ clients: Number.isSafeInteger(status.clients) && status.clients >= 0 ? status.clients : null,
21
+ isProcessing: typeof status.isProcessing === 'boolean' ? status.isProcessing : null,
16
22
  };
17
23
  }
18
24
 
@@ -34,18 +40,23 @@ function page(metadata, projects) {
34
40
  var accessText = metadata.ownershipRecovery
35
41
  ? "Ownership needs recovery. The server operator must independently verify or repair the authoritative account-to-OS mapping before ownership repair can succeed. Ambiguous records stay restricted; administrator status cannot claim this project. See the Project ownership and administrator access guide."
36
42
  : "Only the project owner can change ownership, visibility and sharing. Administrator status does not grant access to private content.";
37
- var strip = projects.filter(function (project) { return !project.isMate; }).map(function (project) {
38
- return '<a class="icon-strip-item' + (project.slug === metadata.slug ? ' active' : '') + '" href="/p/' + encodeURIComponent(project.slug) + '/" title="' + escape(project.title || project.project) + '"><span class="icon-strip-name">' + escape((project.title || project.project || project.slug).substring(0, 2)) + '</span></a>';
39
- }).join("");
43
+ var navigation = projects.filter(function (project) { return !project.isMate; }).map(function (project) {
44
+ return { slug: project.slug, title: project.title || project.project || project.slug, icon: typeof project.icon === 'string' ? project.icon : null,
45
+ isWorktree: project.isWorktree === true, parentSlug: project.parentSlug || null, contentAccess: project.contentAccess !== false };
46
+ });
47
+ var payload = JSON.stringify({ project: metadata, projects: navigation }).replace(/</g, "\\u003c");
48
+ function fact(label, value) { return '<div><dt>' + escape(label) + '</dt><dd>' + escape(value === null ? 'Unavailable' : String(value)) + '</dd></div>'; }
40
49
  return '<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>' + escape(metadata.title) + ' · Clay</title>' +
41
- '<script src="/appearance-bootstrap.js"></script><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/css/project-admin-overview.css"></head><body>' +
42
- '<div id="layout"><header id="top-bar"><a href="/" class="top-bar-brand-link" aria-label="Clay home"><span class="top-bar-brand-wordmark"></span></a></header>' +
43
- '<div id="layout-body"><nav id="icon-strip" aria-label="Projects"><div class="icon-strip-projects">' + strip + '</div></nav>' +
44
- '<main id="main-area" class="project-admin-overview"><div class="project-admin-canvas"><h1>' + escape(metadata.title) + '</h1>' +
50
+ '<script src="/appearance-bootstrap.js"></script><link rel="stylesheet" href="/style.css"><link rel="stylesheet" href="/css/project-admin-overview.css"></head><body class="project-admin-page">' +
51
+ '<div id="layout"><header id="top-bar"><a href="/" class="top-bar-brand-link" aria-label="Clay home"><span class="top-bar-brand-wordmark"></span></a><span class="project-admin-role">Server administration</span></header>' +
52
+ '<div id="layout-body"><nav id="icon-strip" aria-label="Projects"><a class="icon-strip-home" href="/" aria-label="Clay home" data-tip="Clay home"><img class="icon-strip-logo" src="/clay-studio-symbol.png" alt="" width="38" height="38"></a><div class="icon-strip-separator"></div><div class="icon-strip-projects" id="icon-strip-projects"></div></nav>' +
53
+ '<main id="main-area" class="project-admin-overview"><div class="project-admin-canvas"><div class="project-admin-heading"><div><p class="project-admin-eyebrow">Project overview</p><h1>' + escape(metadata.title) + '</h1><p id="project-admin-parent" hidden></p></div><span class="project-admin-access-badge">Content restricted</span></div>' +
45
54
  '<div role="tablist" aria-label="Project information"><button type="button" role="tab" aria-selected="true" id="overview-tab" aria-controls="overview-panel">Overview</button><button type="button" role="tab" aria-selected="false" id="access-tab" aria-controls="access-panel">Access</button></div>' +
46
- '<section id="overview-panel" role="tabpanel" aria-labelledby="overview-tab"><dl><dt>Owner</dt><dd>' + escape(metadata.ownerName) + '</dd><dt>Visibility</dt><dd>' + escape(metadata.visibility) + '</dd><dt>Status</dt><dd>' + (metadata.ownershipRecovery ? 'Ownership needs recovery' : escape(metadata.runtimeHealth)) + '</dd><dt>Your access</dt><dd>Administrative metadata only</dd></dl><p>' + escape(privacyText) + '</p><p class="project-admin-secondary">Resource usage is unavailable for this project.</p></section>' +
47
- '<section id="access-panel" role="tabpanel" aria-labelledby="access-tab" hidden><h2>Administrative metadata only</h2><p>' + escape(accessText) + '</p></section></div></main></div></div>' +
48
- '<script type="module" src="/modules/project-admin-overview.js"></script></body></html>';
55
+ '<section id="overview-panel" role="tabpanel" aria-labelledby="overview-tab"><dl class="project-admin-facts">' + fact('Owner', metadata.ownerName) + fact('Visibility', metadata.visibility) + fact('Registration', metadata.runtimeHealth) + fact('Sessions', metadata.sessions) + fact('Connected clients', metadata.clients) + fact('Agent activity', metadata.isProcessing === null ? null : metadata.isProcessing ? 'Active turn reported' : 'No active turn reported') + '</dl><p class="project-admin-secondary">Session and activity counts reflect this page load.</p>' +
56
+ '<section class="project-admin-storage" aria-labelledby="storage-heading"><div class="project-admin-section-heading"><h2 id="storage-heading">Storage</h2><button type="button" id="project-admin-refresh">Refresh usage</button></div><div id="project-admin-usage" aria-busy="true"><dl class="project-admin-metrics"><div><dt>Project allocated</dt><dd id="usage-bytes">—</dd></div><div><dt>Files</dt><dd id="usage-files">—</dd></div><div><dt>Volume available</dt><dd id="usage-free">—</dd></div><div><dt>Volume capacity</dt><dd id="usage-capacity">—</dd></div></dl><p id="usage-status" role="status">Measuring storage…</p><p class="project-admin-secondary">Allocated filesystem blocks, including dependencies. Nested registered projects are counted separately; hard links shared across projects are excluded. Volume space is shared, not a project quota.</p></div></section>' +
57
+ '<div class="project-admin-notice"><h2>Administrative metadata only</h2><p>' + escape(privacyText) + '</p></div></section>' +
58
+ '<section id="access-panel" role="tabpanel" aria-labelledby="access-tab" hidden><h2>Administrative metadata only</h2><p>' + escape(accessText) + '</p><dl class="project-admin-facts">' + fact('Owner', metadata.ownerName) + fact('Your access', 'Administrative metadata only') + '</dl></section></div></main></div></div>' +
59
+ '<script type="application/json" id="project-admin-data">' + payload + '</script><script type="module" src="/modules/project-admin-overview.js"></script></body></html>';
49
60
  }
50
61
 
51
62
  module.exports = { metadataFor: metadataFor, page: page };
@@ -0,0 +1,76 @@
1
+ // Read-only inspection of native background work through its existing query.
2
+ function remember(session, tasks) {
3
+ var generation = session._sdkQueryGeneration || 0;
4
+ var catalog = session.backgroundTaskCatalog;
5
+ if (!catalog || catalog.generation !== generation) catalog = { generation: generation, tasks: [] };
6
+ var next = (tasks || []).slice(0, 64).map(function (task) {
7
+ var old = catalog.tasks.find(function (item) { return item.task_id === task.task_id && String(item.run_id || '') === String(task.run_id || ''); });
8
+ return Object.assign(old || {}, task, { active: true });
9
+ });
10
+ catalog.tasks.forEach(function (old) {
11
+ if (!next.some(function (task) { return task.task_id === old.task_id; })) next.push(Object.assign(old, { active: false }));
12
+ });
13
+ catalog.tasks = next.slice(0, 64); session.backgroundTaskCatalog = catalog;
14
+ return catalog.tasks;
15
+ }
16
+ function observeMessage(session, parsed, send) {
17
+ if (!parsed || parsed.messageRole !== 'assistant' || !Array.isArray(parsed.content)) return false;
18
+ var id = session.taskIdMap && session.taskIdMap[parsed.parentToolUseId];
19
+ var catalog = session.backgroundTaskCatalog;
20
+ if (!id || !catalog || catalog.generation !== (session._sdkQueryGeneration || 0)) return false;
21
+ var task = catalog.tasks.find(function (item) { return item.task_id === id && item.active; });
22
+ if (!task) return false;
23
+ var text = parsed.content.filter(function (part) { return part && part.type === 'text' && typeof part.text === 'string'; }).map(function (part) { return part.text; }).join('\n').slice(0, 1200);
24
+ if (!text.trim()) return false;
25
+ var id = parsed.uuid || String(Date.now());
26
+ var preview = { id: id, text: text, vendor: session.vendor || null, timestamp: task.preview && task.preview.id === id ? task.preview.timestamp : Date.now() };
27
+ if (task.preview && task.preview.id === preview.id && task.preview.text === preview.text) return false;
28
+ task.preview = preview;
29
+ send(session, { type: 'background_task_preview', taskId: task.task_id, runId: task.run_id || null, sessionOriginId: session.sessionOriginId, preview: preview, live: true });
30
+ return true;
31
+ }
32
+ function attach(ctx) {
33
+ var pending = new WeakSet();
34
+ function authorized(ws, session) {
35
+ if (!session || ctx.getSessionForWs(ws) !== session || ws.readyState !== 1 || session.destroying) return false;
36
+ var users = ctx.usersModule;
37
+ if (!users.isMultiUser()) return !session.ownerId;
38
+ var user = ws._clayUser;
39
+ return !!(user && users.findUserById(user.id) && users.canAccessProject(user.id, (ctx.getProjectAccess ? ctx.getProjectAccess() : { visibility: "public" })) &&
40
+ users.canAccessSession(user.id, session, (ctx.getProjectAccess ? ctx.getProjectAccess() : { visibility: "public" })));
41
+ }
42
+ async function read(ws, msg) {
43
+ var session = ctx.getSessionForWs(ws), query = session && session.queryInstance;
44
+ var generation = session && session._sdkQueryGeneration;
45
+ var catalog = session && session.backgroundTaskCatalog;
46
+ var task = catalog && catalog.generation === (generation || 0) && catalog.tasks.find(function (item) {
47
+ return item.task_id === msg.taskId && String(item.run_id || '') === String(msg.runId || '');
48
+ });
49
+ var actor = ws._clayUser && ws._clayUser.id, cwd = session && session.cwd, owner = session && session.ownerId;
50
+ var osIdentity = session && ctx.getLinuxUserForSession ? ctx.getLinuxUserForSession(session) : null;
51
+ var requestId = typeof msg.requestId === 'string' ? msg.requestId.slice(0, 120) : null;
52
+ function current() { return authorized(ws, session) && ctx.sm.sessions.get(session.localId) === session &&
53
+ session.sessionOriginId === msg.sessionOriginId && session.queryInstance === query && session._sdkQueryGeneration === generation &&
54
+ session.cwd === cwd && session.ownerId === owner && (!ctx.getLinuxUserForSession || ctx.getLinuxUserForSession(session) === osIdentity) && (ws._clayUser && ws._clayUser.id) === actor && session.backgroundTaskCatalog === catalog &&
55
+ catalog.tasks.indexOf(task) >= 0; }
56
+ function reply(data) { if (authorized(ws, session)) ctx.sendTo(ws, Object.assign({ type: 'background_task_read', requestId: requestId,
57
+ taskId: msg.taskId, runId: msg.runId || null, sessionOriginId: msg.sessionOriginId }, data)); }
58
+ if (!requestId || !task || !current() || !query || msg.type === 'background_task_read' && typeof query.readBackgroundTask !== 'function') {
59
+ reply({ error: 'This task is unavailable in the current session.' }); return;
60
+ }
61
+ var reading = msg.type === 'background_task_read';
62
+ if (reading && pending.has(ws)) { reply({ error: 'A task read is already in progress.' }); return; }
63
+ if (reading) pending.add(ws);
64
+ try {
65
+ var data;
66
+ if (msg.type === 'background_task_stop') {
67
+ if (!task.active || typeof query.stopTask !== 'function') throw new Error('Task is no longer running.');
68
+ await query.stopTask(task.task_id); data = { stopRequested: true };
69
+ } else data = await query.readBackgroundTask({ taskId: task.task_id, runId: task.run_id || null, offset: msg.offset });
70
+ if (current()) reply({ data: data });
71
+ } catch (error) { if (current()) reply({ error: 'Task transcript or output is not available yet.' }); }
72
+ finally { if (reading) pending.delete(ws); }
73
+ }
74
+ return { handleMessage: function (ws, msg) { if (msg.type !== 'background_task_read' && msg.type !== 'background_task_stop') return false; read(ws, msg); return true; } };
75
+ }
76
+ module.exports = { attach: attach, remember: remember, observeMessage: observeMessage };
@@ -334,7 +334,7 @@ function attachConnection(ctx) {
334
334
  if (active.isProcessing) {
335
335
  sendTo(ws, { type: "status", status: "processing" });
336
336
  }
337
- sendTo(ws, { type: "active_background_tasks", tasks: active.activeBackgroundTasks || [] });
337
+ sendTo(ws, { type: "active_background_tasks", tasks: active.activeBackgroundTasks || [], catalog: active.backgroundTaskCatalog && active.backgroundTaskCatalog.tasks || [] });
338
338
  var pendingIds = Object.keys(active.pendingPermissions);
339
339
  for (var pi = 0; pi < pendingIds.length; pi++) {
340
340
  var p = active.pendingPermissions[pendingIds[pi]];
@@ -93,6 +93,7 @@ function formatAskUserAnswerAsMessage(input, answers) {
93
93
  function attachSessions(ctx) {
94
94
  var mateDefaultsToken = {};
95
95
  var contextUsage = contextUsageModule.attachContextUsage(ctx);
96
+ var backgroundTaskReader = require("./project-background-tasks").attach(ctx);
96
97
  var cwd = ctx.cwd;
97
98
  var slug = ctx.slug;
98
99
  var isMate = ctx.isMate;
@@ -606,6 +607,7 @@ function attachSessions(ctx) {
606
607
  if (workerRemoval.handleMessage(ws, msg)) return true;
607
608
  if (serverSettingsMessages.handleServerSettings({ opts: opts, usersModule: usersModule, sendTo: sendTo, send: send }, ws, msg)) return true;
608
609
  if (contextUsage.handleMessage(ws, msg)) return true;
610
+ if (backgroundTaskReader.handleMessage(ws, msg)) return true;
609
611
 
610
612
  if (msg.type === "push_subscribe") {
611
613
  var _pushUserId = ws._clayUser ? ws._clayUser.id : null;
@@ -681,6 +683,9 @@ function attachSessions(ctx) {
681
683
  if (creationRequestId) sendTo(ws, { type: "new_session_result", requestId: creationRequestId, ok: false, error: text });
682
684
  else sendTo(ws, { type: "error", text: text });
683
685
  };
686
+ if (msg.activate === false && (!creationRequestId || isMate || ws._clayPane || msg.slug !== slug)) {
687
+ creationFailed("Inactive creation requires this exact project and an ordinary session request."); return true;
688
+ }
684
689
  // The same request id is created at most once per socket.
685
690
  if (creationRequestId) {
686
691
  if (!ws._createdRequests) ws._createdRequests = [];
@@ -723,7 +728,8 @@ function attachSessions(ctx) {
723
728
  // Reuse an existing blank GUI session instead of stacking another
724
729
  // one. A vendor-less blank is acceptable: nothing has happened in it,
725
730
  // so stamping the requested vendor is equivalent to a fresh create.
726
- var reusable = msg.forceNew === true ? null : sm.findReusableBlankSession({
731
+ var inactiveCreation = msg.activate === false && !!creationRequestId && !isMate && !ws._clayPane;
732
+ var reusable = msg.forceNew === true || inactiveCreation ? null : sm.findReusableBlankSession({
727
733
  vendor: sessionOpts.vendor || null,
728
734
  ownerId: sessionOpts.ownerId || null,
729
735
  });
@@ -734,9 +740,10 @@ function attachSessions(ctx) {
734
740
  sm.switchSession(reusable.localId, ws);
735
741
  newSess = reusable;
736
742
  } else {
737
- newSess = sm.createSession(sessionOpts, ws);
743
+ newSess = inactiveCreation ? sm.createSessionRaw(sessionOpts) : sm.createSession(sessionOpts, ws);
738
744
  }
739
- ws._clayActiveSession = newSess.localId;
745
+ if (!inactiveCreation) ws._clayActiveSession = newSess.localId;
746
+ else { sm.saveSessionFile(newSess); sm.broadcastSessionList(); }
740
747
  var folderPlaced = msg.folderId === undefined ? null : sessionFolders.placeNewSession(ws, newSess, msg.folderId, msg.folderSlug);
741
748
  if (msg.requestId) {
742
749
  var created = { type: "new_session_result", requestId: String(msg.requestId).substring(0, 200), ok: true, sessionId: newSess.localId };
@@ -758,8 +765,8 @@ function attachSessions(ctx) {
758
765
  send({ type: "last_vendor", vendor: msg.vendor });
759
766
  }
760
767
  var nsPresKey = ws._clayUser ? ws._clayUser.id : "_default";
761
- if (!ws._clayPane) userPresence.setPresence(slug, nsPresKey, userPresence.sessionIdForPersistence(newSess), null);
762
- if (usersModule.isMultiUser() && !ws._clayPane) {
768
+ if (!inactiveCreation && !ws._clayPane) userPresence.setPresence(slug, nsPresKey, userPresence.sessionIdForPersistence(newSess), null);
769
+ if (!inactiveCreation && usersModule.isMultiUser() && !ws._clayPane) {
763
770
  broadcastPresence();
764
771
  }
765
772
  return true;
@@ -73,6 +73,13 @@ function attachProjectSplitViews(ctx) {
73
73
  var accountId = users.isMultiUser() ? ownerId : 'default';
74
74
  return typeof users.getSessionFolders === 'function' ? users.getSessionFolders(accountId, ctx.slug).folders.map(function (folder) { return folder.id; }) : [];
75
75
  }
76
+ function destinationFolder(ownerId, view, origin) {
77
+ var state = typeof users.getSessionFolders === 'function' ? users.getSessionFolders(users.isMultiUser() ? ownerId : 'default', ctx.slug) : null;
78
+ if (!state) return null;
79
+ // Singleton Views still appear as session rows and use session placement.
80
+ var id = view && view.sessionRefs.length !== 1 ? view.folderId : (state.assignments || {})[view ? view.sessionRefs[0].originId : origin];
81
+ return state.folders.some(function (folder) { return folder.id === id; }) ? id : null;
82
+ }
76
83
  function stateFor(ws) {
77
84
  var current = actor(ws);
78
85
  if (!current) return null;
@@ -137,10 +144,11 @@ function attachProjectSplitViews(ctx) {
137
144
  input.liveSessions = refs.sessions;
138
145
  if (action === "update") {
139
146
  var exact = verifyStoredView(ws, current, input.id);
140
- return exact.ok ? store.update(current.id, current.linuxUser, input, refs.refs) : exact;
147
+ var inheritedFolder = target && target.sessionRefs.length === 1 && input.sessionOrigins.length > 1 ? destinationFolder(current.id, target) : undefined;
148
+ return exact.ok ? store.update(current.id, current.linuxUser, input, refs.refs, inheritedFolder) : exact;
141
149
  }
142
150
  input.nameMode = "automatic";
143
- return store.create(current.id, current.linuxUser, input, refs.refs);
151
+ return store.create(current.id, current.linuxUser, input, refs.refs, destinationFolder(current.id, null, input.activeSessionOriginId));
144
152
  }
145
153
  if (action === "move") {
146
154
  var sourceState = store.state(current.id, current.linuxUser);
@@ -159,7 +167,8 @@ function attachProjectSplitViews(ctx) {
159
167
  if (!targetResolved.refs) return targetResolved;
160
168
  if (input.targetWorkspace && !require('./project-view-pair').authorized(ctx, ws, input.targetWorkspace.linkedPair)) return { ok: false, code: 'pair', error: 'The exact linked Worker pair is unavailable.' };
161
169
  input.liveSessions = targetResolved.sessions;
162
- return store.move(current.id, current.linuxUser, input, sourceResolved.refs, targetResolved.refs);
170
+ var destination = sourceState.views.find(function (view) { return view.id === input.targetViewId; });
171
+ return store.move(current.id, current.linuxUser, input, sourceResolved.refs, targetResolved.refs, destinationFolder(current.id, destination, input.targetSessionOriginId));
163
172
  }
164
173
  if (action === 'organize' && (input.projectSlug !== ctx.slug || input.folderId !== null && folderIds(current.id).indexOf(input.folderId) < 0)) return { ok: false, code: 'folder', error: 'That project folder is no longer available.' };
165
174
  var exactView = verifyStoredView(ws, current, input.id);
@@ -122,7 +122,7 @@ function createSplitViewsStore(opts) {
122
122
  });
123
123
  return refs.every(function (ref) { return used.indexOf(ref.originId) === -1; });
124
124
  }
125
- function create(ownerId, linuxUser, input, resolvedRefs) {
125
+ function create(ownerId, linuxUser, input, resolvedRefs, destinationFolderId) {
126
126
  input = input || {};
127
127
  var prepared = prepareRecord(ownerId, linuxUser, input.expectedRevision);
128
128
  if (!prepared.ok) return prepared;
@@ -136,14 +136,14 @@ function createSplitViewsStore(opts) {
136
136
  if (!uniqueMembership(prepared.record.views, refs.refs)) return { ok: false, error: "A session can belong to only one Split view" };
137
137
  var nameMode = input.nameMode === "custom" ? "custom" : "automatic";
138
138
  var view = { id: crypto.randomUUID(), name: nameMode === "automatic" ? names.automaticName(input.liveSessions) : name.name, nameMode: nameMode,
139
- folderId: null, favorite: false, revision: prepared.record.revision + 1, layout: input.layout,
139
+ folderId: destinationFolderId || null, favorite: false, revision: prepared.record.revision + 1, layout: input.layout,
140
140
  activeSessionOriginId: input.activeSessionOriginId, tree: clone(input.tree), sessionRefs: refs.refs };
141
141
  if (input.workspace !== undefined || refs.refs.length < 2) view.workspace = input.workspace === undefined ? workspace.legacy(view) : clone(input.workspace);
142
142
  prepared.record.views.push(view);
143
143
  if (input.active === true) prepared.record.activeViewId = view.id;
144
144
  return commit(ownerId, prepared.record, view);
145
145
  }
146
- function update(ownerId, linuxUser, input, resolvedRefs) {
146
+ function update(ownerId, linuxUser, input, resolvedRefs, destinationFolderId) {
147
147
  input = input || {};
148
148
  var prepared = prepareRecord(ownerId, linuxUser, input.expectedRevision);
149
149
  if (!prepared.ok) return prepared;
@@ -168,6 +168,7 @@ function createSplitViewsStore(opts) {
168
168
  if (names.mode(found.view) === "automatic") found.view.name = names.automaticName(input.liveSessions);
169
169
  found.view.layout = input.layout; found.view.activeSessionOriginId = input.activeSessionOriginId;
170
170
  found.view.tree = clone(input.tree); found.view.sessionRefs = refs.refs;
171
+ if (destinationFolderId !== undefined) found.view.folderId = destinationFolderId;
171
172
  if (input.workspace !== undefined || refs.refs.length < 2) found.view.workspace = input.workspace === undefined ? workspace.legacy(found.view) : clone(input.workspace);
172
173
  if (input.active === true) prepared.record.activeViewId = found.view.id;
173
174
  return commit(ownerId, prepared.record, found.view);
@@ -201,7 +202,7 @@ function createSplitViewsStore(opts) {
201
202
  if (prepared.record.activeViewId === removed.id) prepared.record.activeViewId = null;
202
203
  return commit(ownerId, prepared.record, null);
203
204
  }
204
- function move(ownerId, linuxUser, input, resolvedSourceRefs, resolvedTargetRefs) {
205
+ function move(ownerId, linuxUser, input, resolvedSourceRefs, resolvedTargetRefs, destinationFolderId) {
205
206
  input = input || {};
206
207
  var prepared = prepareRecord(ownerId, linuxUser, input.expectedRevision);
207
208
  if (!prepared.ok) return prepared;
@@ -286,11 +287,12 @@ function createSplitViewsStore(opts) {
286
287
  if (nextSourceOrigins.length < 2 && !source.view.workspace) { prepared.record.views.splice(source.index, 1); if (prepared.record.activeViewId === source.view.id) prepared.record.activeViewId = null; }
287
288
  if (nextSourceOrigins.length >= 2 && names.mode(source.view) === "automatic") source.view.name = names.automaticName(input.sourceLiveSessions.filter(function (session) { return session.sessionOriginId !== input.sourceSessionOriginId; }));
288
289
  if (target) {
290
+ if (destinationFolderId !== undefined) target.view.folderId = destinationFolderId;
289
291
  target.view.tree = clone(input.tree); target.view.layout = input.layout;
290
292
  target.view.activeSessionOriginId = input.activeSessionOriginId; target.view.sessionRefs = targetRefs.refs;
291
293
  if (names.mode(target.view) === "automatic") target.view.name = names.automaticName(input.liveSessions);
292
294
  } else {
293
- target = { index: -1, view: { id: crypto.randomUUID(), folderId: null, favorite: false, name: names.automaticName(input.liveSessions), nameMode: "automatic", revision: prepared.record.revision + 1,
295
+ target = { index: -1, view: { id: crypto.randomUUID(), folderId: destinationFolderId || null, favorite: false, name: names.automaticName(input.liveSessions), nameMode: "automatic", revision: prepared.record.revision + 1,
294
296
  layout: input.layout, activeSessionOriginId: input.activeSessionOriginId, tree: clone(input.tree), sessionRefs: targetRefs.refs } };
295
297
  prepared.record.views.push(target.view);
296
298
  }
@@ -104,6 +104,7 @@ function attachProjectWorkerConversation(ctx) {
104
104
  if (type === "user_message") return { role: "user", text: textContent(entry.text || entry.content) };
105
105
  if (type === "delta" || type === "result" || type === "assistant_message") return { role: "assistant", text: textContent(entry.text || entry.content) };
106
106
  if (type === "tool_start" || type === "tool_use") return Object.assign({ role: "tool", text: "Used " + (toolIdentity(entry).toolName || "tool") }, toolIdentity(entry));
107
+ if (type === "tool_executing") return Object.assign({ role: "tool", text: JSON.stringify(entry.input || {}) }, toolIdentity(entry));
107
108
  if (type === "tool_result") return Object.assign({ role: "tool", text: textContent(entry.content) || "No recorded tool output.", isError: entry.is_error === true }, toolIdentity(entry));
108
109
  return null;
109
110
  }
@@ -187,7 +188,11 @@ function attachProjectWorkerConversation(ctx) {
187
188
  watches.set(ws, watch);
188
189
  if (ws.once) ws.once('close', watch.close);
189
190
  }
191
+ var previews = require('./worker-message-previews').attach({ sm: sm, send: send, resolve: function (ws, target) {
192
+ var user = actor(ws); return user && exactPair(ws, target, user);
193
+ } });
190
194
  function handleMessage(ws, msg) {
195
+ if (msg && previews.handleMessage(ws, msg)) return true;
191
196
  if (!msg || msg.type !== "split_worker_conversation_request") return false;
192
197
  var requestId = typeof msg.requestId === "string" ? msg.requestId.slice(0, 160) : "";
193
198
  var response = { type: "split_worker_conversation", requestId: requestId, pageBefore: msg.before == null ? null : msg.before,
package/lib/public/app.js CHANGED
@@ -501,6 +501,13 @@ import { initDebate, handleDebatePreparing, handleDebateStarted, handleDebateRes
501
501
  speechLifecycleBound: false,
502
502
  askUserRequests: {},
503
503
  activeBackgroundTasks: [],
504
+ backgroundTaskCatalog: [],
505
+ backgroundTaskViewer: null,
506
+ splitSessionPicker: null,
507
+ splitSessionCreation: null,
508
+ agentMessagePreviews: {},
509
+ agentMessageBubble: null,
510
+ workerMessagePreviewSubscription: null,
504
511
  sessionVendorBound: false,
505
512
  cliSessionId: null,
506
513
  isHeadlessMode: false,
@@ -1,12 +1,13 @@
1
1
  /* Anchored shell around the unchanged production conversation renderer. */
2
2
  #contextual-clay {
3
+ --contextual-chat-gutter: 80px;
3
4
  position: fixed;
4
5
  z-index: 160;
5
6
  top: var(--contextual-top, 38px);
6
7
  left: var(--contextual-left, 64px);
7
8
  width: var(--contextual-width, 480px);
8
9
  height: var(--contextual-height, 640px);
9
- max-width: calc(100vw - 16px);
10
+ max-width: min(calc(var(--mate-content-width, 860px) + var(--contextual-chat-gutter)), calc(100vw - 16px));
10
11
  max-height: calc(100dvh - 16px);
11
12
  display: flex;
12
13
  flex-direction: column;
@@ -1,7 +1,7 @@
1
1
  /* Restore the Home conversation's reading measure inside Mate projects.
2
2
  The existing project renderer, queue and composer still own behavior. */
3
+ :root { --mate-content-width: 860px; }
3
4
  body.mate-workspace-active {
4
- --mate-content-width: 860px;
5
5
  --content-width: var(--mate-content-width);
6
6
  }
7
7
  body.mate-workspace-active #messages {
@@ -1,10 +1,9 @@
1
1
  #pending-message-queue {
2
2
  margin: 0 7px 8px;
3
- padding: 8px;
4
- border: 1px solid color-mix(in srgb, var(--border) 82%, var(--accent));
5
- border-radius: 13px;
6
- background: color-mix(in srgb, var(--bg-alt) 74%, transparent);
7
- box-shadow: inset 3px 0 0 color-mix(in srgb, var(--accent) 70%, transparent);
3
+ padding: 4px;
4
+ border: 1px solid var(--border);
5
+ border-radius: 8px;
6
+ background: var(--bg-alt);
8
7
  }
9
8
 
10
9
  #pending-message-queue.hidden { display: none; }
@@ -14,15 +13,14 @@
14
13
  align-items: center;
15
14
  justify-content: space-between;
16
15
  min-height: 24px;
17
- padding: 0 3px 6px 8px;
16
+ padding: 0 5px 2px 8px;
18
17
  }
19
18
 
20
19
  .pending-message-eyebrow {
21
20
  color: var(--text-muted);
22
21
  font-size: 10px;
23
- font-weight: 700;
24
- letter-spacing: 0.11em;
25
- text-transform: uppercase;
22
+ font-weight: 550;
23
+ letter-spacing: 0.01em;
26
24
  }
27
25
 
28
26
  .pending-message-count {
@@ -62,7 +60,7 @@
62
60
  .pending-message-list {
63
61
  display: flex;
64
62
  flex-direction: column;
65
- gap: 5px;
63
+ gap: 0;
66
64
  max-height: min(230px, 28vh);
67
65
  overflow-y: auto;
68
66
  overscroll-behavior: contain;
@@ -71,27 +69,30 @@
71
69
 
72
70
  .pending-message-chip {
73
71
  display: grid;
74
- grid-template-columns: 22px 24px minmax(0, 1fr) auto;
72
+ grid-template-columns: 16px 24px minmax(0, 1fr) auto;
75
73
  align-items: center;
76
74
  flex: 0 0 auto;
77
75
  min-height: 46px;
78
- border: 1px solid var(--border-subtle);
79
- border-radius: 10px;
80
- background: color-mix(in srgb, var(--bg) 90%, var(--bg-alt));
76
+ position: relative;
77
+ border: 1px solid transparent;
78
+ border-radius: 5px;
79
+ background: transparent;
81
80
  transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
82
81
  }
83
82
 
84
- .pending-message-chip:hover { border-color: var(--border); }
83
+ .pending-message-chip + .pending-message-chip { border-top-color: var(--border-subtle); }
84
+ .pending-message-chip:hover { background: rgba(var(--overlay-rgb), 0.035); }
85
85
  .pending-message-chip.is-claimed { background: color-mix(in srgb, var(--accent) 5%, var(--bg)); }
86
86
  .pending-message-chip.is-dragging { opacity: 0.62; transform: scale(0.992); }
87
- .pending-message-chip.is-drop-target { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 8%, var(--bg)); }
87
+ .pending-message-chip.is-drop-target::before { content: ""; position: absolute; top: -1px; left: 4px; right: 4px; height: 2px; border-radius: 2px; background: var(--accent); }
88
+ .pending-message-chip.is-drop-target.is-drop-after::before { top: auto; bottom: -1px; }
89
+ #pending-message-queue.is-reordering { cursor: grabbing; user-select: none; }
88
90
 
89
91
  .pending-message-number {
90
92
  align-self: stretch;
91
93
  display: flex;
92
94
  align-items: center;
93
95
  justify-content: center;
94
- border-right: 1px solid var(--border-subtle);
95
96
  color: var(--text-dimmer);
96
97
  font-size: 10px;
97
98
  font-variant-numeric: tabular-nums;
@@ -119,6 +120,10 @@ button.pending-message-drag:active { cursor: grabbing; }
119
120
  .pending-message-chip.is-claimed .pending-message-drag .lucide { animation: spin 1.2s linear infinite; }
120
121
 
121
122
  .pending-message-content { min-width: 0; padding: 7px 7px 7px 3px; overflow: visible; }
123
+ .pending-message-content:has(> .pending-message-image-strip) { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 9px; align-items: center; }
124
+ .pending-message-content > .pending-message-image-strip { grid-column: 1; grid-row: 1 / 4; margin: 0; min-height: 0; max-width: 86px; flex-wrap: nowrap; overflow-x: auto; }
125
+ .pending-message-content:has(> .pending-message-image-strip) > :not(.pending-message-image-strip) { grid-column: 2; }
126
+ .pending-message-content > .pending-message-image-strip .pending-message-image-thumb { width: 30px; height: 30px; flex: 0 0 30px; }
122
127
  .pending-message-preview { overflow: hidden; color: var(--text); font-size: 12px; line-height: 1.3; text-overflow: ellipsis; white-space: nowrap; }
123
128
  .pending-message-image-strip,
124
129
  .pending-message-image-editor { display: flex; flex-wrap: wrap; gap: 5px; min-height: 36px; margin-top: 6px; align-items: center; }
@@ -179,10 +184,10 @@ button.pending-message-drag:active { cursor: grabbing; }
179
184
  .pending-message-edit-actions button:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
180
185
 
181
186
  @media (max-width: 600px) {
182
- #pending-message-queue { margin: 0 3px 6px; padding: 6px; border-radius: 12px; }
187
+ #pending-message-queue { margin: 0 3px 6px; padding: 4px; border-radius: 8px; }
183
188
  .pending-message-head { padding-left: 6px; }
184
189
  .pending-message-list { max-height: min(196px, 24vh); }
185
- .pending-message-chip { grid-template-columns: 20px 28px minmax(0, 1fr) auto; min-height: 44px; }
190
+ .pending-message-chip { grid-template-columns: 16px 28px minmax(0, 1fr) auto; min-height: 44px; }
186
191
  .pending-message-drag { width: 28px; height: 34px; }
187
192
  .pending-message-actions button { width: 30px; height: 34px; }
188
193
  .pending-message-meta { gap: 5px; }
@@ -1,13 +1,42 @@
1
- .project-admin-overview { overflow: auto; padding: 8px; }
2
- .project-admin-canvas { width: 100%; min-height: 100%; padding: 24px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); }
3
- .project-admin-canvas h1 { font-size: 20px; margin: 0 0 16px; }
4
- .project-admin-canvas h2 { font-size: 16px; }
5
- .project-admin-canvas [role="tablist"] { display: flex; gap: 4px; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
6
- .project-admin-canvas [role="tab"] { height: 32px; padding: 0 12px; border: 1px solid var(--border); border-radius: 8px; color: inherit; background: transparent; cursor: pointer; }
7
- .project-admin-canvas [aria-selected="true"] { background: #5857fc; color: white; }
8
- .project-admin-canvas :focus-visible { outline: 2px solid #5857fc; outline-offset: 2px; }
9
- .project-admin-canvas dl { display: grid; grid-template-columns: 110px 1fr; gap: 12px; margin: 24px 0; font-size: 13px; }
10
- .project-admin-canvas dd { margin: 0; }
11
- .project-admin-canvas p { max-width: 640px; line-height: 1.6; font-size: 14px; }
12
- .project-admin-secondary { opacity: .65; }
13
- @media (max-width: 600px) { .project-admin-canvas { padding: 16px; } }
1
+ #main-area.project-admin-overview { display: block; overflow: auto; min-width: 0; padding: 8px; }
2
+ .project-admin-canvas { min-height: 100%; padding: 24px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg); }
3
+ .project-admin-heading { display: flex; align-items: center; justify-content: space-between; gap: 16px; max-width: 1000px; margin-bottom: 22px; }
4
+ .project-admin-canvas h1 { font-size: 22px; font-weight: 600; margin: 0; overflow-wrap: anywhere; }
5
+ .project-admin-canvas h2 { font-size: 13px; font-weight: 600; margin: 0; }
6
+ .project-admin-eyebrow, .project-admin-role { color: var(--text-muted); font-size: 11px; }
7
+ .project-admin-canvas .project-admin-eyebrow { margin: 0 0 5px; font-size: 11px; }
8
+ .project-admin-role { margin-left: auto; padding-right: 16px; }
9
+ .project-admin-access-badge { flex-shrink: 0; border: 1px solid var(--border); border-radius: 5px; padding: 5px 8px; color: var(--text-muted); font-size: 11px; }
10
+ .project-admin-canvas [role="tablist"] { display: flex; gap: 18px; border-bottom: 1px solid var(--border); max-width: 1000px; }
11
+ .project-admin-canvas [role="tab"] { height: 32px; padding: 0 2px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; color: var(--text-muted); background: transparent; cursor: pointer; font: inherit; font-size: 12px; }
12
+ .project-admin-canvas [role="tab"][aria-selected="true"] { color: var(--text); border-bottom-color: var(--accent); }
13
+ .project-admin-page :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
14
+ .project-admin-facts { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px 28px; margin: 24px 0 28px; max-width: 1000px; }
15
+ .project-admin-facts dt, .project-admin-metrics dt { font-size: 11px; color: var(--text-muted); margin-bottom: 6px; }
16
+ .project-admin-facts dd { font-size: 13px; margin: 0; overflow-wrap: anywhere; }
17
+ .project-admin-canvas p { line-height: 1.6; font-size: 12px; color: var(--text-secondary); }
18
+ #project-admin-parent { margin: 6px 0 0; font-size: 11px; color: var(--text-muted); }
19
+ .project-admin-storage { border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); padding: 18px 0; max-width: 1000px; }
20
+ .project-admin-section-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 18px; }
21
+ .project-admin-section-heading button { height: 28px; border: 1px solid var(--border); border-radius: 6px; padding: 0 9px; font: inherit; font-size: 11px; background: var(--bg-alt); color: var(--text-secondary); cursor: pointer; }
22
+ .project-admin-section-heading button:hover { background: var(--sidebar-hover); }
23
+ .project-admin-section-heading button:disabled { opacity: .55; cursor: wait; }
24
+ .project-admin-metrics { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 18px; margin: 0 0 16px; }
25
+ .project-admin-metrics dd { font-size: 19px; font-weight: 500; margin: 0; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
26
+ .project-admin-canvas #usage-status { margin: 8px 0 4px; font-size: 11px; }
27
+ .project-admin-canvas .project-admin-secondary { font-size: 11px; color: var(--text-muted); max-width: 720px; margin: 4px 0 0; }
28
+ .project-admin-notice { padding-top: 22px; max-width: 680px; }
29
+ .project-admin-notice h2 { color: var(--text-muted); }
30
+ .project-admin-notice p { margin: 6px 0; }
31
+ .project-admin-rail-header { position: relative; }
32
+ .project-admin-page #icon-strip { padding-bottom: 12px; }
33
+ .project-admin-page #icon-strip .icon-strip-home { margin-bottom: 0; }
34
+ @media (max-width: 700px) {
35
+ .project-admin-canvas { padding: 16px; }
36
+ .project-admin-heading { align-items: flex-start; flex-direction: column; gap: 10px; margin-bottom: 16px; }
37
+ .project-admin-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
38
+ .project-admin-metrics { grid-template-columns: repeat(2, minmax(0, 1fr)); }
39
+ .project-admin-metrics dd { font-size: 17px; }
40
+ .project-admin-role { font-size: 10px; }
41
+ .project-admin-page #icon-strip { display: flex; width: 60px; }
42
+ }
@@ -107,14 +107,37 @@
107
107
  .shared-browser-pointer i { position: absolute; left: -12px; top: -12px; width: 30px; height: 30px; border: 2px solid #a59dff; border-radius: 50%; opacity: 0; }
108
108
  @media (prefers-reduced-motion: reduce) { .shared-browser-pointer { transition: none; } }
109
109
 
110
- .shared-browser-tabs { display: flex; gap: 4px; align-items: center; overflow-x: auto; flex-shrink: 0; padding: 6px 8px 0; border-bottom: 1px solid var(--border); }
111
- .shared-browser-tab { display: flex; align-items: center; min-width: 100px; max-width: 210px; border-radius: 7px 7px 0 0; background: var(--bg-alt); }
112
- .shared-browser-tab:has([aria-selected="true"]) { box-shadow: inset 0 -2px var(--accent); background: var(--bg-surface, var(--bg-alt)); }
113
- .shared-browser-tab [role="tab"] { min-width: 0; gap: 8px; font-size: 11px; padding: 9px 8px; }
114
- .shared-browser-tab [role="tab"] span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
115
- .shared-browser-tab small { font-size: 9px; color: var(--text-muted); }
116
- .shared-browser-tab [aria-selected="true"] small { color: var(--accent); }
117
- .shared-browser-tab > button:last-child { padding: 5px; font-size: 15px; color: var(--text-muted); }
110
+ /* Tabs sit on the pane's top edge; the selected face joins the address toolbar. */
111
+ .shared-browser-tabs { display: flex; align-items: center; gap: 3px; min-width: 0; padding: 4px 6px 0; background: var(--bg-alt); flex: 1 1 auto; }
112
+ .shared-browser-tab-list { display: flex; align-items: stretch; min-width: 0; overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; }
113
+ .shared-browser-tab-list::-webkit-scrollbar { display: none; }
114
+ .shared-browser-tab { position: relative; display: flex; align-items: center; flex: 0 1 160px; width: 160px; min-width: 84px; max-width: 180px; height: 29px; padding: 0 4px; border-radius: 8px 8px 0 0; color: var(--text-muted); }
115
+ .shared-browser-tab::after { content: ''; position: absolute; right: -1px; top: 7px; height: 14px; width: 1px; background: var(--border); }
116
+ .shared-browser-tab:has([aria-selected="true"]), .shared-browser-tab:hover { background: var(--bg); color: var(--text); }
117
+ .shared-browser-tab:has([aria-selected="true"]) { z-index: 1; }
118
+ .shared-browser-tab:has([aria-selected="true"])::after, .shared-browser-tab:has(+ .shared-browser-tab [aria-selected="true"])::after { display: none; }
119
+ .shared-browser-panel .shared-browser-tab [role="tab"] { flex: 1; min-width: 0; height: 27px; gap: 6px; padding: 0 5px; border-radius: 5px; font-size: 11px; font-weight: 400; color: inherit; }
120
+ .shared-browser-panel .shared-browser-tab [role="tab"]:hover { background: transparent; }
121
+ .shared-browser-tab-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
122
+ .shared-browser-tab [role="tab"] svg { width: 12px; height: 12px; color: var(--text-muted); }
123
+ .shared-browser-tab[data-phase="ended"] [role="tab"] svg { opacity: .45; }
124
+ .shared-browser-panel .shared-browser-tab-close { flex: 0 0 18px; width: 18px; height: 18px; padding: 3px; border-radius: 50%; color: var(--text-muted); }
125
+ .shared-browser-panel .shared-browser-tab-close svg { width: 11px; height: 11px; }
126
+ .shared-browser-panel .shared-browser-tab-close:hover { background: rgba(var(--overlay-rgb), .1); color: var(--text); }
127
+ .shared-browser-panel .shared-browser-new-tab { flex: 0 0 23px; width: 23px; height: 23px; margin-bottom: 3px; padding: 4px; border-radius: 50%; color: var(--text-muted); }
128
+ .shared-browser-panel .shared-browser-new-tab:hover { background: rgba(var(--overlay-rgb), .08); }
129
+ .tool-pane-header.browser-pane-header { flex-wrap: nowrap; height: 33px; min-height: 33px; padding: 0 6px; gap: 4px; border-bottom: 0; background: var(--bg-alt); align-items: center; }
130
+ .browser-pane-header > .tool-pane-kind { padding: 0 3px; }
131
+ .browser-pane-header > .tool-pane-kind .split-view-model, .browser-pane-header > .tool-pane-title { display: none; }
132
+ .browser-pane-header > .shared-browser-tabs { align-self: stretch; padding: 4px 0 0; flex-shrink: 1; }
133
+ .browser-pane-header > .split-pane-drag-space { flex: 1 0 12px; min-width: 12px; }
134
+ .browser-pane-header > .shared-browser-window { flex-shrink: 0; }
135
+ .shared-browser-panel .shared-browser-address { min-height: 40px; box-sizing: border-box; padding: 5px 7px; background: var(--bg); }
136
+ .shared-browser-panel .shared-browser-address button { width: 27px; height: 27px; flex-shrink: 0; padding: 5px; border-radius: 50%; }
137
+ .shared-browser-panel .shared-browser-address input { height: 28px; box-sizing: border-box; border-radius: 14px; padding: 4px 11px; border-color: transparent; }
138
+ .shared-browser-panel .shared-browser-address input:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
139
+ .browser-settings-open .shared-browser-tabs { display: none; }
140
+ .browser-settings-open .browser-pane-header > .tool-pane-title { display: block; }
118
141
 
119
142
  #shared-browser-toggle-btn[aria-expanded="true"] { color: var(--text); background: rgba(var(--overlay-rgb), .06); }
120
143