clay-server 3.8.1 → 4.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/lib/ask-user-mcp-server.js +5 -119
  2. package/lib/builtin-mates.js +6 -6
  3. package/lib/claude-hook-installer.js +15 -0
  4. package/lib/clay-history-mcp-server.js +62 -297
  5. package/lib/daemon.js +1 -1
  6. package/lib/debate-model-selection.js +119 -0
  7. package/lib/home-debate-tool-policy.js +164 -0
  8. package/lib/home-response-notifications.js +26 -0
  9. package/lib/mate-creation-mcp-server.js +55 -0
  10. package/lib/mate-ready-creation.js +95 -0
  11. package/lib/mates.js +2 -0
  12. package/lib/notes.js +2 -1
  13. package/lib/project-ask-user.js +64 -0
  14. package/lib/project-capsule-catalog.js +101 -0
  15. package/lib/project-connection.js +4 -0
  16. package/lib/project-debate-proposal.js +133 -17
  17. package/lib/project-debate.js +214 -110
  18. package/lib/project-delegated-follow-up.js +84 -0
  19. package/lib/project-delegated-session.js +53 -0
  20. package/lib/project-filesystem.js +7 -9
  21. package/lib/project-knowledge.js +1 -0
  22. package/lib/project-mate-creation-proposal.js +120 -0
  23. package/lib/project-memory.js +14 -7
  24. package/lib/project-models.js +157 -32
  25. package/lib/project-session-notes.js +2 -2
  26. package/lib/project-session-pair.js +8 -8
  27. package/lib/project-sessions.js +66 -51
  28. package/lib/project-vendor-login.js +397 -0
  29. package/lib/project-worker-proposal.js +25 -25
  30. package/lib/project-workspace-query.js +144 -0
  31. package/lib/project.js +349 -102
  32. package/lib/public/app.js +85 -42
  33. package/lib/public/css/avatar-imprints.css +0 -1
  34. package/lib/public/css/base.css +4 -0
  35. package/lib/public/css/capsule-ui.css +389 -0
  36. package/lib/public/css/command-palette.css +306 -6
  37. package/lib/public/css/home-capsule-library.css +243 -0
  38. package/lib/public/css/home-capsule-source.css +192 -0
  39. package/lib/public/css/home-debate-live.css +146 -0
  40. package/lib/public/css/home-debate-planning.css +125 -0
  41. package/lib/public/css/home-debates-archive.css +281 -0
  42. package/lib/public/css/home-hub.css +896 -405
  43. package/lib/public/css/home-mate-creation.css +34 -0
  44. package/lib/public/css/home-mate-model-picker.css +131 -0
  45. package/lib/public/css/home-mate-settings.css +344 -0
  46. package/lib/public/css/home-session-actions.css +265 -0
  47. package/lib/public/css/home-sidebar.css +667 -0
  48. package/lib/public/css/icon-strip.css +1 -2
  49. package/lib/public/css/mates.css +7 -34
  50. package/lib/public/css/pane.css +1 -1
  51. package/lib/public/css/playbook.css +0 -80
  52. package/lib/public/css/sticky-notes.css +53 -117
  53. package/lib/public/css/tui-attention.css +0 -44
  54. package/lib/public/css/workspace-assignment.css +45 -0
  55. package/lib/public/fonts/source-serif-4/LICENSE.md +93 -0
  56. package/lib/public/fonts/source-serif-4/SourceSerif4Caption-Semibold.ttf.woff2 +0 -0
  57. package/lib/public/index.html +93 -74
  58. package/lib/public/modules/app-connection.js +10 -1
  59. package/lib/public/modules/app-dm.js +43 -160
  60. package/lib/public/modules/app-home-hub.js +328 -585
  61. package/lib/public/modules/app-message-cards.js +242 -0
  62. package/lib/public/modules/app-message-router.js +84 -0
  63. package/lib/public/modules/app-messages.js +102 -37
  64. package/lib/public/modules/app-notifications.js +8 -119
  65. package/lib/public/modules/app-projects.js +9 -1
  66. package/lib/public/modules/app-rendering.js +21 -602
  67. package/lib/public/modules/avatar-imprint.js +11 -8
  68. package/lib/public/modules/chat-bubble-renderer.js +152 -0
  69. package/lib/public/modules/chat-render-runtime.js +198 -0
  70. package/lib/public/modules/command-palette.js +225 -436
  71. package/lib/public/modules/dm-render.js +82 -0
  72. package/lib/public/modules/home-capsule-creation-intent.js +31 -0
  73. package/lib/public/modules/home-capsule-library.js +146 -0
  74. package/lib/public/modules/home-capsule-source.js +383 -0
  75. package/lib/public/modules/home-chat-identity.js +35 -0
  76. package/lib/public/modules/home-chat-scroll.js +77 -0
  77. package/lib/public/modules/home-chat-stream-state.js +34 -0
  78. package/lib/public/modules/home-composer-focus.js +25 -0
  79. package/lib/public/modules/home-conversations-sheet.js +179 -0
  80. package/lib/public/modules/home-debate-activity.js +48 -0
  81. package/lib/public/modules/home-debate-controls.js +295 -0
  82. package/lib/public/modules/home-debate-launch.js +41 -0
  83. package/lib/public/modules/home-debate-live.js +284 -0
  84. package/lib/public/modules/home-debate-models.js +73 -0
  85. package/lib/public/modules/home-debate-planning.js +335 -0
  86. package/lib/public/modules/home-debates-archive.js +279 -0
  87. package/lib/public/modules/home-dock-resize.js +74 -0
  88. package/lib/public/modules/home-dock.js +446 -0
  89. package/lib/public/modules/home-mate-chat.js +496 -0
  90. package/lib/public/modules/home-mate-creation.js +109 -0
  91. package/lib/public/modules/home-mate-model-picker.js +281 -0
  92. package/lib/public/modules/home-mate-selection.js +38 -0
  93. package/lib/public/modules/home-mate-settings-menu.js +131 -0
  94. package/lib/public/modules/home-mate-settings.js +383 -0
  95. package/lib/public/modules/home-session-actions.js +373 -0
  96. package/lib/public/modules/home-session-model-confirmation.js +13 -0
  97. package/lib/public/modules/home-shell.js +9 -0
  98. package/lib/public/modules/home-sidebar-chat-list.js +63 -0
  99. package/lib/public/modules/home-sidebar.js +158 -0
  100. package/lib/public/modules/home-sub-surface.js +19 -0
  101. package/lib/public/modules/home-surface-boot.js +68 -0
  102. package/lib/public/modules/home-surface.js +141 -0
  103. package/lib/public/modules/home-tools.js +271 -0
  104. package/lib/public/modules/markdown.js +50 -2
  105. package/lib/public/modules/mate-management.js +86 -0
  106. package/lib/public/modules/mate-wizard.js +0 -7
  107. package/lib/public/modules/mention.js +17 -19
  108. package/lib/public/modules/pane-bridge.js +9 -0
  109. package/lib/public/modules/profile.js +4 -5
  110. package/lib/public/modules/project-settings.js +34 -28
  111. package/lib/public/modules/project-switcher.js +11 -5
  112. package/lib/public/modules/project-workspace-assignment.js +23 -0
  113. package/lib/public/modules/search-clay-chat.js +402 -0
  114. package/lib/public/modules/server-settings.js +14 -12
  115. package/lib/public/modules/session-actions.js +1 -1
  116. package/lib/public/modules/sidebar-mates.js +56 -552
  117. package/lib/public/modules/sidebar-mobile.js +7 -64
  118. package/lib/public/modules/sidebar-presence.js +37 -0
  119. package/lib/public/modules/split-pair-ui.js +13 -13
  120. package/lib/public/modules/split-view.js +7 -0
  121. package/lib/public/modules/sticky-notes.js +7 -6
  122. package/lib/public/modules/tool-input-composition.js +48 -0
  123. package/lib/public/modules/tool-llm-status.js +167 -0
  124. package/lib/public/modules/tool-renderer-advanced.js +285 -0
  125. package/lib/public/modules/tool-renderer-chart.js +158 -0
  126. package/lib/public/modules/tool-renderer-semantics.js +35 -0
  127. package/lib/public/modules/tool-renderer.js +464 -0
  128. package/lib/public/modules/tool-runtime.js +309 -0
  129. package/lib/public/modules/tool-ui-evaluator.js +97 -0
  130. package/lib/public/modules/tool-ui-tree.js +21 -0
  131. package/lib/public/modules/tui-attention.js +10 -0
  132. package/lib/public/modules/user-settings.js +16 -23
  133. package/lib/public/modules/vendor-login.js +287 -0
  134. package/lib/public/modules/worker-proposal.js +2 -2
  135. package/lib/public/modules/workspace-assignment-card.js +149 -0
  136. package/lib/public/modules/workspace-assignment-routing.js +4 -0
  137. package/lib/public/style.css +13 -2
  138. package/lib/runtime-env.js +65 -0
  139. package/lib/sdk-bridge.js +120 -32
  140. package/lib/sdk-message-processor.js +10 -6
  141. package/lib/server-home-capsule-creation.js +36 -0
  142. package/lib/server-home-chat-events.js +287 -0
  143. package/lib/server-home-chat.js +518 -0
  144. package/lib/server-home-clay-entry.js +65 -0
  145. package/lib/server-home-clay-session-links.js +26 -0
  146. package/lib/server-home-debate-planning.js +214 -0
  147. package/lib/server-home-debates.js +137 -0
  148. package/lib/server-home-mate-creation.js +261 -0
  149. package/lib/server-home-models.js +241 -0
  150. package/lib/server-home-preferences.js +61 -0
  151. package/lib/server-mates.js +37 -0
  152. package/lib/server-tools.js +377 -0
  153. package/lib/server.js +111 -59
  154. package/lib/session-pair-mcp-server.js +6 -6
  155. package/lib/sessions.js +18 -3
  156. package/lib/tool-capsule-source.js +142 -0
  157. package/lib/tool-control-mcp-server.js +126 -0
  158. package/lib/tool-llm.js +48 -0
  159. package/lib/tool-storage.js +77 -0
  160. package/lib/tool-ui-spec-advanced.js +82 -0
  161. package/lib/tool-ui-spec.js +261 -0
  162. package/lib/tools-registry.js +286 -0
  163. package/lib/users-home-dock-preferences.js +77 -0
  164. package/lib/users-home-surface-preferences.js +99 -0
  165. package/lib/users-preferences.js +4 -7
  166. package/lib/users.js +12 -0
  167. package/lib/whats-new-content.js +3 -3
  168. package/lib/workspace-assignment-service.js +420 -0
  169. package/lib/workspace-query-mcp-server.js +154 -0
  170. package/lib/workspace-query-service.js +492 -0
  171. package/lib/ws-schema.js +93 -12
  172. package/lib/yoke/adapters/acp.js +2 -1
  173. package/lib/yoke/adapters/antigravity.js +2 -1
  174. package/lib/yoke/adapters/claude-worker.js +11 -4
  175. package/lib/yoke/adapters/claude.js +73 -43
  176. package/lib/yoke/adapters/codex.js +40 -10
  177. package/lib/yoke/adapters/kiro.js +2 -1
  178. package/lib/yoke/claude-user-input.js +38 -0
  179. package/lib/yoke/codex-app-server.js +20 -2
  180. package/lib/yoke/codex-user-input.js +64 -0
  181. package/lib/yoke/complete-once.js +164 -0
  182. package/lib/yoke/index.js +5 -1
  183. package/lib/yoke/interface.js +19 -0
  184. package/lib/yoke/mcp-bridge-server.js +2 -6
  185. package/lib/yoke/user-input.js +254 -0
  186. package/package.json +3 -2
  187. package/lib/public/css/home-chat.css +0 -380
  188. package/lib/public/modules/home-chat.js +0 -438
  189. package/lib/server-clay-home.js +0 -245
@@ -1,659 +1,402 @@
1
- // app-home-hub.js - Home hub rendering, weather, tips
2
- // Extracted from app.js (PR-25)
1
+ // app-home-hub.js - Home work hub navigation and first-depth Mate list.
3
2
 
4
3
  import { store } from './store.js';
5
4
  import { getWs } from './ws-ref.js';
6
- import { escapeHtml } from './utils.js';
7
- import { switchProject, getCachedProjects, renderProjectList } from './app-projects.js';
8
- import { openSchedulerToTab } from './scheduler.js';
9
- import { getPlaybooks, openPlaybook, getPlaybookForTip } from './playbook.js';
5
+ import { getCachedProjects } from './app-projects.js';
10
6
  import { mateAvatarUrl } from './avatar.js';
11
- import { openDm, exitDmMode } from './app-dm.js';
12
- import { getKnownEntries as getWhatsNewEntries } from './whats-new.js';
13
- import { openArticle as openWhatsNewArticle } from './whats-new-article.js';
14
-
15
- function $hub(id) { return document.getElementById(id); }
7
+ import { exitDmMode } from './app-dm.js';
8
+ import { closeHomeDock, initHomeDock, renderDock, requestHomeDockPreference } from './home-dock.js';
9
+ import { openHomeChat, openHomeMateAction, renderHomeChat, resumeHomeChat, startHomeCapsuleCreation } from './home-mate-chat.js';
10
+ import { closeHomeSidebarAfterSelection } from './home-sidebar.js';
11
+ import { iconHtml, refreshIcons } from './icons.js';
12
+ import { getActiveMentionMateIds } from './sidebar-mates.js';
13
+ import { requestTools } from './home-tools.js';
14
+ import { showHomeShell, hideHomeShell } from './home-shell.js';
15
+ import { requestHomeSurfacePreference, rememberHomePrimarySurface } from './home-surface.js';
16
+ import { resolveHomeMate } from './home-mate-selection.js';
17
+ import { createHomeMateSettingsTrigger, disposeHomeMateSettingsMenu } from './home-mate-settings-menu.js';
18
+ import { closeHomeMateSettings, syncHomeMateSettingsTarget } from './home-mate-settings.js';
19
+ import { disposeHomeSessionActions } from './home-session-actions.js';
20
+ import { initHomeDebatesArchive, restoreHomeDebatesArchive } from './home-debates-archive.js';
21
+ import { isHomeDebatesSurface } from './home-sub-surface.js';
22
+ import { HOME_CAPSULE_CREATION_EVENT } from './home-capsule-creation-intent.js';
16
23
 
17
24
  var homeHub = null;
18
25
  var homeHubVisible = false;
19
- var hubSchedules = [];
26
+ var homeHubSuspended = false;
27
+ var lastRenderedMateId = null;
20
28
 
21
- var hubTips = [
22
- "Sticky notes let you pin important info that persists across sessions.",
23
- "You can run terminal commands directly from the terminal tab — no need to switch windows.",
24
- "Rename your sessions to keep conversations organized and easy to find later.",
25
- "The file browser lets you explore and open any file in your project.",
26
- "Paste images from your clipboard into the chat to include them in your message.",
27
- "Use /commands (slash commands) for quick access to common actions.",
28
- "You can resize the sidebar by dragging its edge.",
29
- "Click the session info button in the header to see token usage and costs.",
30
- "You can switch between projects without losing your conversation history.",
31
- "The status dot on project icons shows whether Claude is currently processing.",
32
- "Right-click on a project icon for quick actions like rename or delete.",
33
- "Push notifications can alert you when Claude finishes a long task.",
34
- "You can search through your conversation history within a session.",
35
- "Session history is preserved — come back anytime to continue where you left off.",
36
- "Use the rewind feature to go back to an earlier point in your conversation.",
37
- "You can open multiple terminal tabs for parallel command execution.",
38
- "Clay works offline as a PWA — install it from your browser for quick access.",
39
- "Schedule recurring tasks with cron expressions to automate your workflow.",
40
- "Use Ralph Loops to run autonomous coding sessions while you're away.",
41
- "Right-click a project icon to set a custom emoji — make each project instantly recognizable.",
42
- "Multiple people can connect to the same project at once — great for pair programming.",
43
- "Drag and drop project icons to reorder them in the sidebar.",
44
- "Drag a project icon to the trash to delete it.",
45
- "Honey never spoils. 🍯",
46
- "The Earth is round. 🌍",
47
- "Computers use electricity. 🔌",
48
- "Christmas is in summer in some countries. 🎄",
49
- ];
50
- // Fisher-Yates shuffle
51
- for (var _si = hubTips.length - 1; _si > 0; _si--) {
52
- var _sj = Math.floor(Math.random() * (_si + 1));
53
- var _tmp = hubTips[_si];
54
- hubTips[_si] = hubTips[_sj];
55
- hubTips[_sj] = _tmp;
29
+ function handleHomeCapsuleCreation(event) {
30
+ var detail = event && event.detail ? event.detail : {};
31
+ detail.accepted = startHomeCapsuleCreation(detail.mateId, detail.description);
32
+ if (detail.accepted) closeHomeDock();
56
33
  }
57
- var hubTipIndex = 0;
58
- var hubTipTimer = null;
59
-
60
- var DAY_NAMES = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
61
- var MONTH_NAMES = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
62
- var WEEKDAY_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
63
-
64
- // --- Weather (hidden detail) ---
65
- var weatherEmoji = null; // null = not yet fetched, "" = failed
66
- var weatherCondition = ""; // e.g. "Light rain, Auckland"
67
- var weatherFetchedAt = 0;
68
- var WEATHER_CACHE_MS = 60 * 60 * 1000; // 1 hour
69
- // WMO weather code -> emoji + description
70
- var WMO_MAP = {
71
- 0: ["☀️", "Clear sky"], 1: ["🌤", "Mainly clear"], 2: ["⛅", "Partly cloudy"], 3: ["☁️", "Overcast"],
72
- 45: ["🌫", "Fog"], 48: ["🌫", "Depositing rime fog"],
73
- 51: ["🌦", "Light drizzle"], 53: ["🌦", "Moderate drizzle"], 55: ["🌧", "Dense drizzle"],
74
- 56: ["🌧", "Light freezing drizzle"], 57: ["🌧", "Dense freezing drizzle"],
75
- 61: ["🌧", "Slight rain"], 63: ["🌧", "Moderate rain"], 65: ["🌧", "Heavy rain"],
76
- 66: ["🌧", "Light freezing rain"], 67: ["🌧", "Heavy freezing rain"],
77
- 71: ["🌨", "Slight snow"], 73: ["🌨", "Moderate snow"], 75: ["❄️", "Heavy snow"],
78
- 77: ["🌨", "Snow grains"],
79
- 80: ["🌦", "Slight rain showers"], 81: ["🌧", "Moderate rain showers"], 82: ["🌧", "Violent rain showers"],
80
- 85: ["🌨", "Slight snow showers"], 86: ["❄️", "Heavy snow showers"],
81
- 95: ["⛈", "Thunderstorm"], 96: ["⛈", "Thunderstorm with slight hail"], 99: ["⛈", "Thunderstorm with heavy hail"],
82
- };
83
-
84
- var SLOT_EMOJIS = ["☀️", "🌤", "⛅", "☁️", "🌧", "🌦", "⛈", "🌨", "❄️", "🌫", "🌙", "✨"];
85
- var weatherSlotPlayed = false;
86
34
 
87
- var hubCloseBtn = null;
88
-
89
- export function initHomeHub() {
90
- homeHub = document.getElementById("home-hub");
91
- hubCloseBtn = document.getElementById("home-hub-close");
35
+ function syncHomePresentation() {
36
+ var ws = getWs();
37
+ if (!ws || ws.readyState !== 1) return;
38
+ ws.send(JSON.stringify({ type: "home_mate_present", visible: homeHubVisible && !document.hidden }));
39
+ }
92
40
 
93
- if (hubCloseBtn) {
94
- hubCloseBtn.addEventListener("click", function () {
95
- hideHomeHub();
96
- if (store.get('currentSlug')) {
97
- if (document.documentElement.classList.contains("pwa-standalone")) {
98
- history.replaceState(null, "", "/p/" + store.get('currentSlug') + "/");
99
- } else {
100
- history.pushState(null, "", "/p/" + store.get('currentSlug') + "/");
101
- }
102
- // Restore icon strip active state
103
- var homeIcon = document.querySelector(".icon-strip-home");
104
- if (homeIcon) homeIcon.classList.remove("active");
105
- renderProjectList();
106
- }
107
- });
108
- }
41
+ function syncHomeCloseControl() {
42
+ var close = document.getElementById("home-close-control");
43
+ if (!close) return;
44
+ close.classList.toggle("hidden", !store.get('currentSlug'));
109
45
  }
110
46
 
111
- export function isHomeHubVisible() { return homeHubVisible; }
47
+ function getVisibleMates() {
48
+ var mates = (store.get('cachedMatesList') || []).filter(function (mate) {
49
+ return !!mate && !mate.archived;
50
+ });
51
+ mates.sort(function (a, b) {
52
+ var aClay = a.builtinKey === "clay" ? 0 : 1;
53
+ var bClay = b.builtinKey === "clay" ? 0 : 1;
54
+ if (aClay !== bClay) return aClay - bClay;
55
+ return (a.createdAt || 0) - (b.createdAt || 0);
56
+ });
57
+ return mates;
58
+ }
112
59
 
113
- function fetchWeather() {
114
- // Use cache if we have a successful result within the last hour
115
- if (weatherEmoji && weatherFetchedAt && (Date.now() - weatherFetchedAt < WEATHER_CACHE_MS)) return;
116
- // Try localStorage cache
117
- if (!weatherEmoji) {
118
- try {
119
- var cached = JSON.parse(localStorage.getItem("clay-weather") || "null");
120
- if (cached && cached.emoji && (Date.now() - cached.ts < WEATHER_CACHE_MS)) {
121
- weatherEmoji = cached.emoji;
122
- weatherCondition = cached.condition || "";
123
- weatherFetchedAt = cached.ts;
124
- if (homeHubVisible) updateGreetingWeather();
125
- return;
126
- }
127
- } catch (e) {}
128
- }
129
- if (weatherFetchedAt && (Date.now() - weatherFetchedAt < 30000)) return; // don't retry within 30s
130
- weatherFetchedAt = Date.now();
131
- // Step 1: IP geolocation -> lat/lon + city
132
- fetch("https://ipapi.co/json/", { signal: AbortSignal.timeout(4000) })
133
- .then(function (res) { return res.ok ? res.json() : Promise.reject(); })
134
- .then(function (geo) {
135
- var lat = geo.latitude;
136
- var lon = geo.longitude;
137
- var city = geo.city || geo.region || "";
138
- var country = geo.country_name || "";
139
- var locationStr = city + (country ? ", " + country : "");
140
- // Step 2: Open-Meteo -> current weather
141
- var meteoUrl = "https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "&current=weather_code&timezone=auto";
142
- return fetch(meteoUrl, { signal: AbortSignal.timeout(4000) })
143
- .then(function (res) { return res.ok ? res.json() : Promise.reject(); })
144
- .then(function (data) {
145
- var code = data && data.current && data.current.weather_code;
146
- if (code === undefined || code === null) return;
147
- var mapped = WMO_MAP[code] || WMO_MAP[0];
148
- weatherEmoji = mapped[0];
149
- weatherCondition = mapped[1] + (locationStr ? " in " + locationStr : "");
150
- weatherFetchedAt = Date.now();
151
- try {
152
- localStorage.setItem("clay-weather", JSON.stringify({
153
- emoji: weatherEmoji, condition: weatherCondition, ts: weatherFetchedAt
154
- }));
155
- } catch (e) {}
156
- if (homeHubVisible) updateGreetingWeather();
157
- });
158
- })
159
- .catch(function () {
160
- if (!weatherEmoji) weatherEmoji = "";
161
- });
60
+ function getMateName(mate) {
61
+ var profile = mate && mate.profile ? mate.profile : {};
62
+ return profile.displayName || (mate && (mate.displayName || mate.name)) || "Mate";
162
63
  }
163
64
 
164
- function updateGreetingWeather() {
165
- var greetEl = $hub("hub-greeting-text");
166
- if (!greetEl) return;
167
- // If we have real weather and haven't played the slot yet, do the reel
168
- if (weatherEmoji && !weatherSlotPlayed && homeHubVisible) {
169
- weatherSlotPlayed = true;
170
- playWeatherSlot(greetEl);
171
- return;
65
+ function syncMateContextControls(mate) {
66
+ var name = mate ? getMateName(mate) : "Mate";
67
+ var newChat = document.getElementById("home-sidebar-new");
68
+ if (newChat) {
69
+ newChat.disabled = !mate;
70
+ newChat.setAttribute("aria-label", mate ? "Start a new chat with " + name : "Start a new chat with the current Mate");
71
+ }
72
+ var debates = document.getElementById("home-sidebar-debate");
73
+ if (debates) {
74
+ debates.disabled = false;
75
+ debates.setAttribute("aria-label", "Browse debates");
172
76
  }
173
- // Normal update (no animation)
174
- greetEl.textContent = getGreeting();
175
-
176
- applyWeatherTooltip(greetEl);
177
77
  }
178
78
 
179
- function applyWeatherTooltip(greetEl) {
180
- if (!weatherCondition) return;
181
- var emojis = greetEl.querySelectorAll("img.emoji");
182
- var lastEmoji = emojis.length > 0 ? emojis[emojis.length - 1] : null;
183
- if (lastEmoji) {
184
- lastEmoji.title = weatherCondition;
185
- lastEmoji.style.cursor = "default";
79
+ function getMateActivity() {
80
+ var status = {};
81
+ var projects = getCachedProjects() || [];
82
+ for (var i = 0; i < projects.length; i++) {
83
+ if (projects[i] && projects[i].slug) status[projects[i].slug] = projects[i];
186
84
  }
85
+ return status;
187
86
  }
188
87
 
189
- function playWeatherSlot(greetEl) {
190
- var h = new Date().getHours();
191
- var prefix;
192
- if (h < 6) prefix = "Good night";
193
- else if (h < 12) prefix = "Good morning";
194
- else if (h < 18) prefix = "Good afternoon";
195
- else prefix = "Good evening";
196
-
197
- // Build schedule: fast ticks -> slow ticks -> land (~3s total)
198
- var intervals = [50, 50, 50, 60, 70, 80, 100, 120, 150, 190, 240, 300, 370, 450, 530, 640];
199
- var totalSteps = intervals.length;
200
- var step = 0;
201
- var startIdx = Math.floor(Math.random() * SLOT_EMOJIS.length);
202
-
203
- function tick() {
204
- if (step < totalSteps) {
205
- var idx = (startIdx + step) % SLOT_EMOJIS.length;
206
- greetEl.textContent = prefix + " " + SLOT_EMOJIS[idx];
207
-
208
- step++;
209
- setTimeout(tick, intervals[step - 1]);
210
- } else {
211
- // Final: land on actual weather
212
- greetEl.textContent = prefix + " " + weatherEmoji;
88
+ function isMateBusy(mate, projectStatus, mentionActive) {
89
+ var project = projectStatus["mate-" + mate.id] || {};
90
+ return !!project.isProcessing || !!mentionActive[mate.id];
91
+ }
213
92
 
214
- applyWeatherTooltip(greetEl);
215
- }
216
- }
217
- tick();
93
+ function selectHomeMate(mateId) {
94
+ if (mateId !== store.get('homeChatMateId') || isHomeDebatesSurface()) openHomeChat(mateId);
95
+ closeHomeSidebarAfterSelection();
218
96
  }
219
97
 
220
- function getGreeting() {
221
- var h = new Date().getHours();
222
- var emoji = weatherEmoji || "";
223
- // Fallback to time-based emoji if weather not available
224
- if (!emoji) {
225
- if (h < 6) emoji = "";
226
- else if (h < 12) emoji = "☀️";
227
- else if (h < 18) emoji = "🌤";
228
- else emoji = "🌙";
98
+ function createMateListRow(mate, activeMateId, projectStatus, mentionActive, unread) {
99
+ var name = getMateName(mate);
100
+ var busy = isMateBusy(mate, projectStatus, mentionActive);
101
+ var count = unread[mate.id] || 0;
102
+ var item = document.createElement("div");
103
+ item.className = "home-mate-list-item";
104
+ item.setAttribute("role", "listitem");
105
+ var row = document.createElement("button");
106
+ row.type = "button";
107
+ row.className = "home-mate-list-row" + (mate.id === activeMateId ? " is-active" : "");
108
+ row.dataset.homeMateId = mate.id;
109
+ row.setAttribute("aria-label", name + (mate.id === activeMateId ? ", current Mate" : "") + (mate.model ? ", model " + mate.model : "") + (busy ? ", working" : "") + (count ? ", " + count + " unread" : ""));
110
+ if (mate.id === activeMateId) row.setAttribute("aria-current", "true");
111
+
112
+ var avatarWrap = document.createElement("span");
113
+ avatarWrap.className = "home-mate-list-avatar-wrap";
114
+ var avatar = document.createElement("img");
115
+ avatar.className = "home-mate-list-avatar";
116
+ avatar.src = mateAvatarUrl(mate, 32);
117
+ avatar.alt = "";
118
+ avatarWrap.appendChild(avatar);
119
+ if (busy) {
120
+ var activity = document.createElement("span");
121
+ activity.className = "home-mate-list-activity";
122
+ activity.title = "Working";
123
+ activity.setAttribute("aria-hidden", "true");
124
+ avatarWrap.appendChild(activity);
125
+ }
126
+ row.appendChild(avatarWrap);
127
+
128
+ var copy = document.createElement("span");
129
+ copy.className = "home-mate-list-copy";
130
+ var nameEl = document.createElement("span");
131
+ nameEl.className = "home-mate-list-name";
132
+ nameEl.textContent = name;
133
+ copy.appendChild(nameEl);
134
+ if (mate.model) {
135
+ var model = document.createElement("span");
136
+ model.className = "home-mate-list-model";
137
+ model.textContent = mate.model;
138
+ copy.appendChild(model);
229
139
  }
230
- var prefix;
231
- if (h < 6) prefix = "Good night";
232
- else if (h < 12) prefix = "Good morning";
233
- else if (h < 18) prefix = "Good afternoon";
234
- else prefix = "Good evening";
235
- return prefix + " " + emoji;
140
+ row.appendChild(copy);
141
+ if (count > 0) {
142
+ var badge = document.createElement("span");
143
+ badge.className = "home-mate-list-unread";
144
+ badge.textContent = count > 99 ? "99+" : String(count);
145
+ badge.setAttribute("aria-hidden", "true");
146
+ row.appendChild(badge);
147
+ }
148
+ row.addEventListener("click", function () { selectHomeMate(mate.id); });
149
+ item.appendChild(row);
150
+ item.appendChild(createHomeMateSettingsTrigger(mate));
151
+ if (mate.id === activeMateId) item.classList.add("is-active");
152
+ return item;
236
153
  }
237
154
 
238
- function getFormattedDate() {
239
- var now = new Date();
240
- return WEEKDAY_NAMES[now.getDay()] + ", " + MONTH_NAMES[now.getMonth()] + " " + now.getDate() + ", " + now.getFullYear();
155
+ function createNewMateRow(disabled) {
156
+ var item = document.createElement("div");
157
+ item.className = "home-mate-list-item home-mate-list-create-item";
158
+ item.setAttribute("role", "listitem");
159
+ var row = document.createElement("button");
160
+ row.id = "home-sidebar-new-mate";
161
+ row.type = "button";
162
+ row.className = "home-mate-list-row home-mate-list-create";
163
+ row.disabled = disabled === true;
164
+ row.setAttribute("aria-label", "Create a new Mate with Clay");
165
+ var icon = document.createElement("span");
166
+ icon.className = "home-mate-list-create-icon";
167
+ icon.setAttribute("aria-hidden", "true");
168
+ icon.innerHTML = iconHtml("user-round-plus");
169
+ row.appendChild(icon);
170
+ var copy = document.createElement("span");
171
+ copy.className = "home-mate-list-copy";
172
+ var name = document.createElement("span");
173
+ name.className = "home-mate-list-name";
174
+ name.textContent = "Create new Mate";
175
+ copy.appendChild(name);
176
+ row.appendChild(copy);
177
+ row.addEventListener("click", function () {
178
+ openHomeMateAction("mate");
179
+ closeHomeSidebarAfterSelection();
180
+ });
181
+ item.appendChild(row);
182
+ return item;
241
183
  }
242
184
 
243
- function formatScheduleTime(ts) {
244
- var d = new Date(ts);
245
- var now = new Date();
246
- var todayStr = now.getFullYear() + "-" + String(now.getMonth() + 1).padStart(2, "0") + "-" + String(now.getDate()).padStart(2, "0");
247
- var schedStr = d.getFullYear() + "-" + String(d.getMonth() + 1).padStart(2, "0") + "-" + String(d.getDate()).padStart(2, "0");
248
- var h = d.getHours();
249
- var m = String(d.getMinutes()).padStart(2, "0");
250
- var ampm = h >= 12 ? "PM" : "AM";
251
- var h12 = h % 12 || 12;
252
- var timeStr = h12 + ":" + m + " " + ampm;
253
- if (schedStr === todayStr) return timeStr;
254
- // Tomorrow check
255
- var tomorrow = new Date(now);
256
- tomorrow.setDate(tomorrow.getDate() + 1);
257
- var tomStr = tomorrow.getFullYear() + "-" + String(tomorrow.getMonth() + 1).padStart(2, "0") + "-" + String(tomorrow.getDate()).padStart(2, "0");
258
- if (schedStr === tomStr) return "Tomorrow";
259
- return DAY_NAMES[d.getDay()] + " " + timeStr;
185
+ function handleMateListKeydown(event) {
186
+ if (event.key !== "ArrowDown" && event.key !== "ArrowUp" && event.key !== "Home" && event.key !== "End") return;
187
+ if (!event.target.classList.contains("home-mate-list-row")) return;
188
+ var rows = event.currentTarget.querySelectorAll(".home-mate-list-row");
189
+ if (!rows.length) return;
190
+ event.preventDefault();
191
+ var current = Array.prototype.indexOf.call(rows, event.target);
192
+ var next = current;
193
+ if (event.key === "Home") next = 0;
194
+ else if (event.key === "End") next = rows.length - 1;
195
+ else if (event.key === "ArrowDown") next = current === rows.length - 1 ? 0 : current + 1;
196
+ else next = current <= 0 ? rows.length - 1 : current - 1;
197
+ rows[next].focus();
260
198
  }
261
199
 
262
- export function renderHomeHub(projects) {
263
- // Greeting + weather tooltip
264
- updateGreetingWeather();
265
-
266
- // Date
267
- var dateEl = $hub("hub-greeting-date");
268
- if (dateEl) dateEl.textContent = getFormattedDate();
269
-
270
- // --- Upcoming tasks ---
271
- var upcomingList = $hub("hub-upcoming-list");
272
- var upcomingCount = $hub("hub-upcoming-count");
273
- if (upcomingList) {
274
- var now = Date.now();
275
- var upcoming = hubSchedules.filter(function (s) {
276
- return s.enabled && s.nextRunAt && s.nextRunAt > now;
277
- }).sort(function (a, b) {
278
- return a.nextRunAt - b.nextRunAt;
279
- });
280
- // Show up to next 48 hours
281
- var cutoff = now + 48 * 60 * 60 * 1000;
282
- var filtered = upcoming.filter(function (s) { return s.nextRunAt <= cutoff; });
283
-
284
- if (upcomingCount) {
285
- upcomingCount.textContent = filtered.length > 0 ? filtered.length : "";
286
- }
200
+ function renderMateListLoading(list) {
201
+ list.innerHTML = "";
202
+ list.appendChild(createNewMateRow(true));
203
+ var empty = document.createElement("div");
204
+ empty.className = "home-mate-list-empty";
205
+ empty.textContent = "Loading Mates...";
206
+ list.appendChild(empty);
207
+ refreshIcons();
208
+ }
287
209
 
288
- upcomingList.innerHTML = "";
289
- if (filtered.length === 0) {
290
- // Empty state with CTA
291
- var emptyDiv = document.createElement("div");
292
- emptyDiv.className = "hub-upcoming-empty";
293
- emptyDiv.innerHTML = '<div class="hub-upcoming-empty-icon">📋</div>' +
294
- '<div class="hub-upcoming-empty-text">No upcoming tasks</div>' +
295
- '<button class="hub-upcoming-cta" id="hub-upcoming-cta">' +
296
- '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14"/><path d="M5 12h14"/></svg>' +
297
- 'Create a schedule</button>';
298
- upcomingList.appendChild(emptyDiv);
299
- var ctaBtn = emptyDiv.querySelector("#hub-upcoming-cta");
300
- if (ctaBtn) {
301
- ctaBtn.addEventListener("click", function () {
302
- hideHomeHub();
303
- openSchedulerToTab("calendar");
304
- });
305
- }
306
- } else {
307
- var maxShow = 5;
308
- var shown = filtered.slice(0, maxShow);
309
- for (var i = 0; i < shown.length; i++) {
310
- (function (sched) {
311
- var item = document.createElement("div");
312
- item.className = "hub-upcoming-item";
313
- var dotColor = sched.color || "";
314
- item.innerHTML = '<span class="hub-upcoming-dot"' + (dotColor ? ' style="background:' + dotColor + '"' : '') + '></span>' +
315
- '<span class="hub-upcoming-time">' + formatScheduleTime(sched.nextRunAt) + '</span>' +
316
- '<span class="hub-upcoming-name">' + escapeHtml(sched.name || "Untitled") + '</span>' +
317
- '<span class="hub-upcoming-project">' + escapeHtml(sched.projectTitle || "") + '</span>';
318
- item.addEventListener("click", function () {
319
- if (sched.projectSlug) {
320
- switchProject(sched.projectSlug);
321
- setTimeout(function () {
322
- openSchedulerToTab("library");
323
- }, 300);
324
- }
325
- });
326
- upcomingList.appendChild(item);
327
- })(shown[i]);
328
- }
329
- if (filtered.length > maxShow) {
330
- var moreEl = document.createElement("div");
331
- moreEl.className = "hub-upcoming-more";
332
- moreEl.textContent = "+" + (filtered.length - maxShow) + " more";
333
- upcomingList.appendChild(moreEl);
334
- }
335
- }
210
+ function findMateRow(list, mateId) {
211
+ var rows = list.querySelectorAll(".home-mate-list-row");
212
+ for (var i = 0; i < rows.length; i++) {
213
+ if (rows[i].dataset.homeMateId === mateId) return rows[i];
336
214
  }
215
+ return null;
216
+ }
337
217
 
338
- // --- Projects summary (exclude mate projects) ---
339
- var projectsList = $hub("hub-projects-list");
340
- if (projectsList && projects) {
341
- projectsList.innerHTML = "";
342
- var hubProjects = projects.filter(function (p) { return !p.isMate; });
343
- for (var p = 0; p < hubProjects.length; p++) {
344
- (function (proj) {
345
- var item = document.createElement("div");
346
- item.className = "hub-project-item";
347
- var dotClass = "hub-project-dot" + (proj.isProcessing ? " processing" : "");
348
- var iconHtml = proj.icon ? '<span class="hub-project-icon">' + proj.icon + '</span>' : '';
349
- var sessionsLabel = typeof proj.sessions === "number" ? proj.sessions : "";
350
- item.innerHTML = '<span class="' + dotClass + '"></span>' +
351
- iconHtml +
352
- '<span class="hub-project-name">' + escapeHtml(proj.title || proj.project || proj.slug) + '</span>' +
353
- (sessionsLabel !== "" ? '<span class="hub-project-sessions">' + sessionsLabel + '</span>' : '');
354
- item.addEventListener("click", function () {
355
- switchProject(proj.slug);
356
- });
357
- projectsList.appendChild(item);
358
- })(hubProjects[p]);
359
- }
360
- // Render emoji icons
361
-
218
+ function findMateOverflow(list, mateId) {
219
+ var buttons = list.querySelectorAll(".home-mate-list-overflow");
220
+ for (var i = 0; i < buttons.length; i++) {
221
+ if (buttons[i].dataset.homeMateId === mateId) return buttons[i];
362
222
  }
223
+ return null;
224
+ }
363
225
 
364
- // --- Week strip ---
365
- var weekStrip = $hub("hub-week-strip");
366
- if (weekStrip) {
367
- weekStrip.innerHTML = "";
368
- var today = new Date();
369
- var todayDate = today.getDate();
370
- var todayMonth = today.getMonth();
371
- var todayYear = today.getFullYear();
372
- // Find Monday of current week
373
- var dayOfWeek = today.getDay();
374
- var mondayOffset = dayOfWeek === 0 ? -6 : 1 - dayOfWeek;
375
- var monday = new Date(today);
376
- monday.setDate(today.getDate() + mondayOffset);
226
+ function keepMateRowVisible(list, row) {
227
+ if (!list || !row || !list.getBoundingClientRect || !row.getBoundingClientRect) return;
228
+ var listRect = list.getBoundingClientRect();
229
+ var rowRect = row.getBoundingClientRect();
230
+ if (rowRect.top < listRect.top) list.scrollTop -= listRect.top - rowRect.top;
231
+ else if (rowRect.bottom > listRect.bottom) list.scrollTop += rowRect.bottom - listRect.bottom;
232
+ }
377
233
 
378
- // Build set of dates that have events
379
- var eventDates = {};
380
- for (var si = 0; si < hubSchedules.length; si++) {
381
- var sched = hubSchedules[si];
382
- if (!sched.enabled) continue;
383
- if (sched.nextRunAt) {
384
- var sd = new Date(sched.nextRunAt);
385
- var key = sd.getFullYear() + "-" + sd.getMonth() + "-" + sd.getDate();
386
- eventDates[key] = (eventDates[key] || 0) + 1;
387
- }
388
- if (sched.date) {
389
- var parts = sched.date.split("-");
390
- var dateKey = parseInt(parts[0], 10) + "-" + (parseInt(parts[1], 10) - 1) + "-" + parseInt(parts[2], 10);
391
- eventDates[dateKey] = (eventDates[dateKey] || 0) + 1;
392
- }
234
+ export function initHomeHub() {
235
+ homeHub = document.getElementById("home-hub");
236
+ window.addEventListener(HOME_CAPSULE_CREATION_EVENT, handleHomeCapsuleCreation);
237
+ initHomeDock();
238
+ initHomeDebatesArchive();
239
+ document.getElementById("home-mate-list").addEventListener("keydown", handleMateListKeydown);
240
+ document.getElementById("home-close-control").addEventListener("click", minimizeHomeHub);
241
+ window.addEventListener("clay:home-debate", function () { hideHomeHub(); });
242
+ document.addEventListener("visibilitychange", syncHomePresentation);
243
+
244
+ store.subscribe(function (state, prev) {
245
+ if (state.connected !== prev.connected && state.connected && homeHubVisible) syncHomePresentation();
246
+ if (state.currentSlug !== prev.currentSlug) syncHomeCloseControl();
247
+ if (state.homeChatMateId !== prev.homeChatMateId || state.cachedMatesList !== prev.cachedMatesList) renderHomeMateSwitcher();
248
+ if (state.homeSidebarCollapsed !== prev.homeSidebarCollapsed && !state.homeSidebarCollapsed) {
249
+ requestAnimationFrame(renderHomeMateSwitcher);
393
250
  }
394
-
395
- for (var d = 0; d < 7; d++) {
396
- var dayDate = new Date(monday);
397
- dayDate.setDate(monday.getDate() + d);
398
- var isToday = dayDate.getDate() === todayDate && dayDate.getMonth() === todayMonth && dayDate.getFullYear() === todayYear;
399
- var dateKey = dayDate.getFullYear() + "-" + dayDate.getMonth() + "-" + dayDate.getDate();
400
- var eventCount = eventDates[dateKey] || 0;
401
-
402
- var cell = document.createElement("div");
403
- cell.className = "hub-week-day" + (isToday ? " today" : "");
404
- var dotsHtml = '<div class="hub-week-dots">';
405
- var dotCount = Math.min(eventCount, 3);
406
- for (var di = 0; di < dotCount; di++) {
407
- dotsHtml += '<span class="hub-week-dot"></span>';
408
- }
409
- dotsHtml += '</div>';
410
- cell.innerHTML = '<span class="hub-week-label">' + DAY_NAMES[(dayDate.getDay())] + '</span>' +
411
- '<span class="hub-week-num">' + dayDate.getDate() + '</span>' +
412
- dotsHtml;
413
- weekStrip.appendChild(cell);
251
+ if (state.homeSurfaceLoaded !== prev.homeSurfaceLoaded && homeHubVisible) {
252
+ syncHomePresentation();
253
+ if (!restoreHomeDebatesArchive() && state.homeChatMateId) resumeHomeChat();
254
+ renderHomeMateSwitcher();
414
255
  }
415
- }
416
-
417
- // --- Playbooks ---
418
- var pbGrid = $hub("hub-playbooks-grid");
419
- var pbSection = $hub("hub-playbooks");
420
- if (pbGrid) {
421
- var pbs = getPlaybooks();
422
- if (pbs.length === 0) {
423
- if (pbSection) pbSection.style.display = "none";
424
- } else {
425
- if (pbSection) pbSection.style.display = "";
426
- pbGrid.innerHTML = "";
427
- for (var pi = 0; pi < pbs.length; pi++) {
428
- (function (pb) {
429
- var card = document.createElement("div");
430
- card.className = "hub-playbook-card" + (pb.completed ? " completed" : "");
431
- card.innerHTML = '<span class="hub-playbook-card-icon">' + pb.icon + '</span>' +
432
- '<div class="hub-playbook-card-body">' +
433
- '<div class="hub-playbook-card-title">' + escapeHtml(pb.title) + '</div>' +
434
- '<div class="hub-playbook-card-desc">' + escapeHtml(pb.description) + '</div>' +
435
- '</div>' +
436
- (pb.completed ? '<span class="hub-playbook-card-check">✓</span>' : '');
437
- card.addEventListener("click", function () {
438
- openPlaybook(pb.id, function () {
439
- // Re-render hub after playbook closes to update completion state
440
- renderHomeHub(getCachedProjects());
441
- });
442
- });
443
- pbGrid.appendChild(card);
444
- })(pbs[pi]);
445
- }
446
-
256
+ if (state.homeSurfaceRestoreRequested === true && prev.homeSurfaceRestoreRequested !== true) {
257
+ showHomeHub(true);
258
+ store.set({ homeSurfaceRestoreRequested: false });
447
259
  }
448
- }
449
-
450
-
451
- // --- Tip ---
452
- var currentTip = hubTips[hubTipIndex % hubTips.length];
453
- var tipEl = $hub("hub-tip-text");
454
- if (tipEl) tipEl.textContent = currentTip;
455
-
456
- // "Try it" button if tip has a linked playbook
457
- var existingTry = homeHub.querySelector(".hub-tip-try");
458
- if (existingTry) existingTry.remove();
459
- var linkedPb = getPlaybookForTip(currentTip);
460
- if (linkedPb && tipEl) {
461
- var tryBtn = document.createElement("button");
462
- tryBtn.className = "hub-tip-try";
463
- tryBtn.textContent = "Try it →";
464
- tryBtn.addEventListener("click", function () {
465
- openPlaybook(linkedPb, function () {
466
- renderHomeHub(getCachedProjects());
467
- });
468
- });
469
- tipEl.appendChild(tryBtn);
470
- }
471
-
472
- // Tip prev/next buttons
473
- var prevBtn = $hub("hub-tip-prev");
474
- if (prevBtn && !prevBtn._hubWired) {
475
- prevBtn._hubWired = true;
476
- prevBtn.addEventListener("click", function () {
477
- hubTipIndex = (hubTipIndex - 1 + hubTips.length) % hubTips.length;
478
- renderHomeHub(getCachedProjects());
479
- startTipRotation();
480
- });
481
- }
482
- var nextBtn = $hub("hub-tip-next");
483
- if (nextBtn && !nextBtn._hubWired) {
484
- nextBtn._hubWired = true;
485
- nextBtn.addEventListener("click", function () {
486
- hubTipIndex = (hubTipIndex + 1) % hubTips.length;
487
- renderHomeHub(getCachedProjects());
488
- startTipRotation();
489
- });
490
- }
491
-
492
- // Render twemoji for all emoji in the hub
260
+ });
261
+ syncHomeCloseControl();
262
+ renderHomeChat();
263
+ }
493
264
 
494
- // --- What's New feed ---
495
- renderHomeWhatsNew();
265
+ export function isHomeHubVisible() {
266
+ return homeHubVisible;
496
267
  }
497
268
 
498
- function renderHomeWhatsNew() {
499
- var list = $hub("hub-whats-new");
500
- var card = $hub("hub-whats-new-card");
501
- if (!list || !card) return;
502
- var entries = getWhatsNewEntries();
503
- // Sort newest first by publishedAt (YYYY-MM-DD string compare).
504
- entries = entries.slice().sort(function (a, b) {
505
- return (b.publishedAt || "").localeCompare(a.publishedAt || "");
506
- });
507
- if (entries.length === 0) {
508
- card.classList.add("hidden");
509
- list.innerHTML = "";
269
+ export function renderHomeMateSwitcher() {
270
+ var list = document.getElementById("home-mate-list");
271
+ if (!list) return;
272
+ var visibleMates = getVisibleMates();
273
+ var activeMateId = store.get('homeChatMateId');
274
+ var selectionChanged = activeMateId !== lastRenderedMateId;
275
+ var activeMate = resolveHomeMate(visibleMates, activeMateId, null);
276
+ if (!activeMate || activeMate.id !== activeMateId) activeMate = null;
277
+ if (homeHubVisible && !store.get('homeSurfaceLoaded') && !activeMate) {
278
+ syncMateContextControls(null);
279
+ renderMateListLoading(list);
510
280
  return;
511
281
  }
512
- card.classList.remove("hidden");
282
+ if (homeHubVisible && store.get('homeSurfaceLoaded') && !activeMate) {
283
+ var nextMate = resolveHomeMate(visibleMates, activeMateId, store.get('homePreferredMateId'));
284
+ if (nextMate) {
285
+ if (isHomeDebatesSurface()) {
286
+ store.set({ homeChatMateId: nextMate.id });
287
+ return;
288
+ }
289
+ openHomeChat(nextMate.id);
290
+ return;
291
+ }
292
+ }
293
+ syncMateContextControls(activeMate);
294
+ var focusedMateId = list.contains(document.activeElement) ? document.activeElement.dataset.homeMateId : null;
295
+ var focusedOverflow = !!focusedMateId && document.activeElement.classList.contains("home-mate-list-overflow");
296
+ var mentionActive = getActiveMentionMateIds();
297
+ var projectStatus = getMateActivity();
298
+ var unread = store.get('dmUnread') || {};
299
+ disposeHomeMateSettingsMenu();
300
+ syncHomeMateSettingsTarget();
513
301
  list.innerHTML = "";
514
- for (var i = 0; i < entries.length; i++) {
515
- var e = entries[i];
516
- var btn = document.createElement("button");
517
- btn.type = "button";
518
- btn.className = "hub-whats-new-item";
519
- btn.setAttribute("data-whats-new-id", e.id);
520
- var dateEl = document.createElement("div");
521
- dateEl.className = "hub-whats-new-item-date";
522
- dateEl.textContent = e.publishedAt || "";
523
- var titleEl = document.createElement("h3");
524
- titleEl.className = "hub-whats-new-item-title";
525
- titleEl.textContent = e.title || "";
526
- btn.appendChild(dateEl);
527
- btn.appendChild(titleEl);
528
- (function (id) {
529
- btn.addEventListener("click", function () { openWhatsNewArticle(id); });
530
- })(e.id);
531
- list.appendChild(btn);
302
+ list.appendChild(createNewMateRow(false));
303
+ if (!visibleMates.length) {
304
+ var empty = document.createElement("div");
305
+ empty.className = "home-mate-list-empty";
306
+ empty.textContent = "No Mates are available.";
307
+ list.appendChild(empty);
308
+ refreshIcons();
309
+ return;
532
310
  }
533
- }
534
-
535
- export function handleHubSchedules(msg) {
536
- if (msg.schedules) {
537
- hubSchedules = msg.schedules;
538
- if (homeHubVisible) renderHomeHub(getCachedProjects());
311
+ for (var i = 0; i < visibleMates.length; i++) {
312
+ list.appendChild(createMateListRow(visibleMates[i], activeMateId, projectStatus, mentionActive, unread));
539
313
  }
540
- }
541
-
542
- function startTipRotation() {
543
- stopTipRotation();
544
- hubTipTimer = setInterval(function () {
545
- hubTipIndex = (hubTipIndex + 1) % hubTips.length;
546
- renderHomeHub(getCachedProjects());
547
- }, 15000);
548
- }
549
-
550
- function stopTipRotation() {
551
- if (hubTipTimer) {
552
- clearInterval(hubTipTimer);
553
- hubTipTimer = null;
314
+ var focusedRow = null;
315
+ if (focusedMateId) {
316
+ focusedRow = focusedOverflow ? findMateOverflow(list, focusedMateId) : findMateRow(list, focusedMateId);
317
+ if (focusedRow) focusedRow.focus({ preventScroll: true });
554
318
  }
319
+ var activeRow = findMateRow(list, activeMateId);
320
+ keepMateRowVisible(list, selectionChanged ? activeRow : focusedRow || activeRow);
321
+ lastRenderedMateId = activeMateId;
322
+ refreshIcons();
555
323
  }
556
324
 
557
- function renderHomeHubMates() {
558
- var container = document.getElementById("home-hub-mates");
559
- if (!container) return;
560
- container.innerHTML = "";
561
- // Hide archived mates (e.g. Ally after Clay took over) and Clay itself
562
- // (the user is already chatting with Clay on the left half — listing
563
- // Clay again on the right would be redundant).
564
- var visibleMates = (store.get('cachedMatesList') || []).filter(function (m) {
565
- if (!m || m.archived) return false;
566
- if (m.builtinKey === "clay") return false;
567
- return true;
568
- });
569
- if (visibleMates.length === 0) {
570
- container.classList.add("hidden");
571
- return;
325
+ export function updateHomeIconBadge() {
326
+ var badge = document.getElementById("icon-strip-home-badge");
327
+ if (!badge) return;
328
+ var mates = store.get('cachedMatesList') || [];
329
+ var unread = store.get('dmUnread') || {};
330
+ var total = 0;
331
+ for (var i = 0; i < mates.length; i++) {
332
+ total += unread[mates[i].id] || 0;
572
333
  }
573
- container.classList.remove("hidden");
574
- for (var i = 0; i < visibleMates.length; i++) {
575
- (function (mate) {
576
- var item = document.createElement("div");
577
- item.className = "home-hub-mate-item" + (mate.primary ? " home-hub-mate-primary" : "");
578
-
579
- var avatarWrap = document.createElement("div");
580
- avatarWrap.className = "home-hub-mate-avatar-wrap";
581
-
582
- var mp = mate.profile || {};
583
- var mateAvUrl = mateAvatarUrl(mate, 48);
584
- var avatar = document.createElement("img");
585
- avatar.className = "home-hub-mate-avatar";
586
- avatar.src = mateAvUrl;
587
- avatar.alt = mp.displayName || mate.displayName || mate.name || "";
588
- avatarWrap.appendChild(avatar);
589
-
590
- var dot = document.createElement("span");
591
- dot.className = "home-hub-mate-dot";
592
- avatarWrap.appendChild(dot);
593
-
594
- item.appendChild(avatarWrap);
595
-
596
- var nameEl = document.createElement("span");
597
- nameEl.className = "home-hub-mate-name";
598
- nameEl.textContent = mp.displayName || mate.displayName || mate.name || "";
599
- if (mate.primary) {
600
- var starEl = document.createElement("span");
601
- starEl.className = "home-hub-mate-primary-star";
602
- starEl.title = "System Agent: code-managed, auto-updated, sees across all mates";
603
- starEl.textContent = "\u2605";
604
- nameEl.appendChild(starEl);
605
- }
606
- item.appendChild(nameEl);
607
-
608
- item.addEventListener("click", function () {
609
- openDm(mate.id);
610
- });
611
-
612
- container.appendChild(item);
613
- })(visibleMates[i]);
334
+ if (total > 0) {
335
+ badge.textContent = total > 99 ? "99+" : String(total);
336
+ badge.classList.add("has-unread");
337
+ } else {
338
+ badge.textContent = "";
339
+ badge.classList.remove("has-unread");
614
340
  }
615
341
  }
616
342
 
617
- export function showHomeHub() {
618
- // Home hub is a pure widget surface. Clay chat is reachable from
619
- // anywhere via the persistent FAB (#clay-fab), not embedded here.
343
+ export function showHomeHub(fromHistory) {
620
344
  if (store.get('dmMode')) exitDmMode();
345
+ var activeMateId = store.get('homeChatMateId');
346
+ var resume = homeHubSuspended || homeHubVisible;
621
347
  homeHubVisible = true;
348
+ homeHubSuspended = false;
349
+ showHomeShell();
622
350
  homeHub.classList.remove("hidden");
623
- // Show close button only if there's a project to return to
624
- if (hubCloseBtn) {
625
- if (store.get('currentSlug')) hubCloseBtn.classList.remove("hidden");
626
- else hubCloseBtn.classList.add("hidden");
627
- }
628
- // Fetch weather silently (once)
629
- fetchWeather();
630
- // Request cross-project schedules
631
- if (getWs() && getWs().readyState === 1) {
632
- getWs().send(JSON.stringify({ type: "hub_schedules_list" }));
351
+ rememberHomePrimarySurface("home");
352
+ syncHomePresentation();
353
+ syncHomeCloseControl();
354
+ renderHomeMateSwitcher();
355
+ if (activeMateId && !resume && !isHomeDebatesSurface()) openHomeChat(activeMateId);
356
+ if (isHomeDebatesSurface()) restoreHomeDebatesArchive();
357
+ if (!resume && store.get('homeSurfaceRestoreRequested') !== true) {
358
+ requestTools();
359
+ requestHomeDockPreference();
360
+ requestHomeSurfacePreference();
361
+ renderDock();
633
362
  }
634
- renderHomeHub(getCachedProjects());
635
- renderHomeHubMates();
636
- startTipRotation();
637
- if (document.documentElement.classList.contains("pwa-standalone")) {
638
- history.replaceState(null, "", "/");
639
- } else {
640
- history.pushState(null, "", "/");
363
+ if (!fromHistory && location.pathname !== "/") {
364
+ if (document.documentElement.classList.contains("pwa-standalone")) {
365
+ history.replaceState(null, "", "/");
366
+ } else {
367
+ history.pushState(null, "", "/");
368
+ }
641
369
  }
642
- // Update icon strip active state
643
370
  var homeIcon = document.querySelector(".icon-strip-home");
644
371
  if (homeIcon) homeIcon.classList.add("active");
645
- var activeProj = document.querySelector("#icon-strip-projects .icon-strip-item.active");
646
- if (activeProj) activeProj.classList.remove("active");
647
- // Mobile home button active
648
372
  var mobileHome = document.getElementById("mobile-home-btn");
649
373
  if (mobileHome) mobileHome.classList.add("active");
650
374
  }
651
375
 
652
376
  export function hideHomeHub() {
653
377
  if (!homeHubVisible) return;
378
+ disposeHomeMateSettingsMenu();
379
+ closeHomeMateSettings();
380
+ disposeHomeSessionActions();
654
381
  homeHubVisible = false;
382
+ homeHubSuspended = true;
383
+ hideHomeShell();
655
384
  homeHub.classList.add("hidden");
656
- stopTipRotation();
385
+ syncHomePresentation();
386
+ var homeIcon = document.querySelector(".icon-strip-home");
387
+ if (homeIcon) homeIcon.classList.remove("active");
657
388
  var mobileHome = document.getElementById("mobile-home-btn");
658
389
  if (mobileHome) mobileHome.classList.remove("active");
659
390
  }
391
+
392
+ export function minimizeHomeHub() {
393
+ var slug = store.get('currentSlug');
394
+ if (!slug || !homeHubVisible) return;
395
+ rememberHomePrimarySurface("project");
396
+ hideHomeHub();
397
+ var route = "/p/" + slug + "/";
398
+ if (document.documentElement.classList.contains("pwa-standalone")) history.replaceState(null, "", route);
399
+ else history.pushState(null, "", route);
400
+ var projectInput = document.getElementById("input");
401
+ if (projectInput && !projectInput.disabled) projectInput.focus({ preventScroll: true });
402
+ }