aimeat 1.25.0 → 1.26.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 (252) hide show
  1. package/README.md +8 -1
  2. package/dist/locales/en.json +144 -0
  3. package/dist/locales/fi.json +144 -0
  4. package/dist/public/components/ImageDeliverable.js +2 -1
  5. package/dist/public/components/Modal.js +3 -2
  6. package/dist/public/components/NotificationBell.js +2 -1
  7. package/dist/public/components/PresenceDot.js +53 -0
  8. package/dist/public/css/components/markdown.css +67 -0
  9. package/dist/public/css/theme.css +30 -0
  10. package/dist/public/css/views/notebook.css +195 -0
  11. package/dist/public/css/views/profile.css +15 -0
  12. package/dist/public/js/api.js +5 -3
  13. package/dist/public/js/components/auth-image.js +3 -2
  14. package/dist/public/js/components/tag-cloud.js +1 -1
  15. package/dist/public/js/components/tag-editor.js +1 -1
  16. package/dist/public/js/hooks.js +1 -2
  17. package/dist/public/js/presence-store.js +97 -0
  18. package/dist/public/js/services/admin.js +14 -5
  19. package/dist/public/js/services/boards.js +1 -1
  20. package/dist/public/js/services/ecosystem.js +1 -1
  21. package/dist/public/js/services/foundry-prompts-base.js +2 -2
  22. package/dist/public/js/services/foundry-smoke.js +2 -1
  23. package/dist/public/js/services/foundry-validate.js +6 -8
  24. package/dist/public/js/services/foundry.js +4 -3
  25. package/dist/public/js/services/generator-prompts-base.js +2 -2
  26. package/dist/public/js/services/generator-smoke.js +2 -1
  27. package/dist/public/js/services/generator-validate.js +5 -7
  28. package/dist/public/js/services/generator.js +2 -1
  29. package/dist/public/js/services/memory.js +13 -0
  30. package/dist/public/js/services/messages.js +2 -1
  31. package/dist/public/js/services/notebook.js +124 -0
  32. package/dist/public/js/services/packages.js +3 -2
  33. package/dist/public/js/services/presence.js +35 -0
  34. package/dist/public/js/style-testbed.js +6 -5
  35. package/dist/public/lib/minidenticons.min.js +1 -0
  36. package/dist/public/lib/yaml.mjs +1 -0
  37. package/dist/public/spa.html +90 -8
  38. package/dist/public/types/aimeat-globals.d.ts +52 -0
  39. package/dist/public/types/vendored-libs.d.ts +56 -0
  40. package/dist/public/views/_template.js +3 -1
  41. package/dist/public/views/admin/agent-integration-tab.js +4 -3
  42. package/dist/public/views/admin/agents-tab.js +3 -2
  43. package/dist/public/views/admin/config-tab.js +4 -3
  44. package/dist/public/views/admin/economy-tab.js +4 -3
  45. package/dist/public/views/admin/federation-tab.js +138 -1
  46. package/dist/public/views/admin/maintenance-tab.js +1 -1
  47. package/dist/public/views/admin/messages-tab.js +115 -0
  48. package/dist/public/views/admin/overview-tab.js +4 -3
  49. package/dist/public/views/admin/portal-tab.js +90 -2
  50. package/dist/public/views/admin/push-tab.js +8 -6
  51. package/dist/public/views/admin/security-tab.js +7 -7
  52. package/dist/public/views/admin.js +29 -9
  53. package/dist/public/views/doc-solo.js +3 -1
  54. package/dist/public/views/guides.js +8 -4
  55. package/dist/public/views/hobbies.js +4 -2
  56. package/dist/public/views/marketplace.js +10 -9
  57. package/dist/public/views/openclaw.js +1 -1
  58. package/dist/public/views/portal-dev.js +5 -2
  59. package/dist/public/views/portal.js +3 -2
  60. package/dist/public/views/profile/agents/shared-board.js +4 -2
  61. package/dist/public/views/profile/agents/tab-messages.js +13 -11
  62. package/dist/public/views/profile/agents-messages-subtab.js +2 -1
  63. package/dist/public/views/profile/apps-tab.js +2 -1
  64. package/dist/public/views/profile/boards-tab.js +3 -2
  65. package/dist/public/views/profile/calibrator-batch.js +3 -2
  66. package/dist/public/views/profile/data-wallet-tab.js +2 -1
  67. package/dist/public/views/profile/extensions-tab.js +4 -3
  68. package/dist/public/views/profile/federation-tab.js +4 -0
  69. package/dist/public/views/profile/foundry-dashboard/use-autopilot.js +3 -2
  70. package/dist/public/views/profile/foundry-detail.js +1 -1
  71. package/dist/public/views/profile/generator-detail.js +1 -1
  72. package/dist/public/views/profile/inbox-tab.js +7 -3
  73. package/dist/public/views/profile/inline-panels.js +3 -1
  74. package/dist/public/views/profile/knowledge-tab.js +2 -1
  75. package/dist/public/views/profile/landing-page.js +88 -0
  76. package/dist/public/views/profile/memory-tab.js +6 -5
  77. package/dist/public/views/profile/notebook-tab.js +500 -0
  78. package/dist/public/views/profile/organisms/activity-panel.js +135 -0
  79. package/dist/public/views/profile/organisms/agents.js +141 -0
  80. package/dist/public/views/profile/organisms/document.js +274 -0
  81. package/dist/public/views/profile/organisms/helpers.js +61 -0
  82. package/dist/public/views/profile/organisms/home.js +315 -0
  83. package/dist/public/views/profile/organisms/members.js +220 -0
  84. package/dist/public/views/profile/organisms/panels.js +173 -0
  85. package/dist/public/views/profile/organisms/participants-panel.js +160 -0
  86. package/dist/public/views/profile/organisms/schema-form.js +127 -0
  87. package/dist/public/views/profile/organisms/sources-panel.js +164 -0
  88. package/dist/public/views/profile/organisms/widgets.js +58 -0
  89. package/dist/public/views/profile/organisms/workspace-comments.js +99 -0
  90. package/dist/public/views/profile/organisms/workspace-list.js +268 -0
  91. package/dist/public/views/profile/organisms/workspace.js +1272 -0
  92. package/dist/public/views/profile/organisms-tab.js +456 -3819
  93. package/dist/public/views/profile/shared.js +64 -2
  94. package/dist/public/views/profile/workflows-form.js +1 -1
  95. package/dist/public/views/profile.js +16 -14
  96. package/dist/public/views/public-knowledge-viewer.js +3 -2
  97. package/dist/public/views/public-workspace-viewer.js +2 -1
  98. package/dist/scripts/check-importmap.d.ts +2 -0
  99. package/dist/scripts/check-importmap.d.ts.map +1 -0
  100. package/dist/scripts/check-importmap.js +102 -0
  101. package/dist/scripts/check-importmap.js.map +1 -0
  102. package/dist/src/cli/scaffold.d.ts +26 -3
  103. package/dist/src/cli/scaffold.d.ts.map +1 -1
  104. package/dist/src/cli/scaffold.js +33 -3
  105. package/dist/src/cli/scaffold.js.map +1 -1
  106. package/dist/src/config.d.ts +10 -0
  107. package/dist/src/config.d.ts.map +1 -1
  108. package/dist/src/config.js +5 -0
  109. package/dist/src/config.js.map +1 -1
  110. package/dist/src/generated/api-types.d.ts +927 -11
  111. package/dist/src/generated/api-types.d.ts.map +1 -1
  112. package/dist/src/generated/prisma-postgres/edge.js +32 -6
  113. package/dist/src/generated/prisma-postgres/index-browser.js +29 -3
  114. package/dist/src/generated/prisma-postgres/index.d.ts +1970 -160
  115. package/dist/src/generated/prisma-postgres/index.js +32 -6
  116. package/dist/src/generated/prisma-postgres/package.json +1 -1
  117. package/dist/src/generated/prisma-postgres/schema.prisma +45 -12
  118. package/dist/src/generated/prisma-postgres/wasm.js +32 -6
  119. package/dist/src/index.js +36 -4
  120. package/dist/src/index.js.map +1 -1
  121. package/dist/src/routes/admin-monitoring.d.ts.map +1 -1
  122. package/dist/src/routes/admin-monitoring.js +8 -0
  123. package/dist/src/routes/admin-monitoring.js.map +1 -1
  124. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  125. package/dist/src/routes/federation-peer.js +327 -43
  126. package/dist/src/routes/federation-peer.js.map +1 -1
  127. package/dist/src/routes/federation-sync.d.ts +2 -0
  128. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  129. package/dist/src/routes/federation-sync.js +20 -1
  130. package/dist/src/routes/federation-sync.js.map +1 -1
  131. package/dist/src/routes/librarian.d.ts +18 -0
  132. package/dist/src/routes/librarian.d.ts.map +1 -0
  133. package/dist/src/routes/librarian.js +88 -0
  134. package/dist/src/routes/librarian.js.map +1 -0
  135. package/dist/src/routes/messages.d.ts.map +1 -1
  136. package/dist/src/routes/messages.js +2 -1
  137. package/dist/src/routes/messages.js.map +1 -1
  138. package/dist/src/routes/portal.d.ts.map +1 -1
  139. package/dist/src/routes/portal.js +9 -1
  140. package/dist/src/routes/portal.js.map +1 -1
  141. package/dist/src/routes/presence.d.ts +23 -0
  142. package/dist/src/routes/presence.d.ts.map +1 -0
  143. package/dist/src/routes/presence.js +88 -0
  144. package/dist/src/routes/presence.js.map +1 -0
  145. package/dist/src/routes/site.d.ts +12 -0
  146. package/dist/src/routes/site.d.ts.map +1 -1
  147. package/dist/src/routes/site.js +45 -1
  148. package/dist/src/routes/site.js.map +1 -1
  149. package/dist/src/routes/sse.d.ts +2 -0
  150. package/dist/src/routes/sse.d.ts.map +1 -1
  151. package/dist/src/routes/sse.js +9 -0
  152. package/dist/src/routes/sse.js.map +1 -1
  153. package/dist/src/routes/wellknown.d.ts.map +1 -1
  154. package/dist/src/routes/wellknown.js +5 -0
  155. package/dist/src/routes/wellknown.js.map +1 -1
  156. package/dist/src/routes/work.d.ts.map +1 -1
  157. package/dist/src/routes/work.js +6 -0
  158. package/dist/src/routes/work.js.map +1 -1
  159. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  160. package/dist/src/server-bootstrap/routes-loader.js +13 -0
  161. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  162. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  163. package/dist/src/server-bootstrap/service-init.js +34 -0
  164. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  165. package/dist/src/services/config-schema.d.ts.map +1 -1
  166. package/dist/src/services/config-schema.js +2 -0
  167. package/dist/src/services/config-schema.js.map +1 -1
  168. package/dist/src/services/federation-availability.d.ts +28 -0
  169. package/dist/src/services/federation-availability.d.ts.map +1 -0
  170. package/dist/src/services/federation-availability.js +58 -0
  171. package/dist/src/services/federation-availability.js.map +1 -0
  172. package/dist/src/services/federation-book.d.ts +70 -0
  173. package/dist/src/services/federation-book.d.ts.map +1 -0
  174. package/dist/src/services/federation-book.js +198 -0
  175. package/dist/src/services/federation-book.js.map +1 -0
  176. package/dist/src/services/federation-tiers.d.ts +45 -0
  177. package/dist/src/services/federation-tiers.d.ts.map +1 -0
  178. package/dist/src/services/federation-tiers.js +58 -0
  179. package/dist/src/services/federation-tiers.js.map +1 -0
  180. package/dist/src/services/federation.d.ts +15 -0
  181. package/dist/src/services/federation.d.ts.map +1 -1
  182. package/dist/src/services/federation.js +12 -0
  183. package/dist/src/services/federation.js.map +1 -1
  184. package/dist/src/services/librarian.d.ts +62 -0
  185. package/dist/src/services/librarian.d.ts.map +1 -0
  186. package/dist/src/services/librarian.js +135 -0
  187. package/dist/src/services/librarian.js.map +1 -0
  188. package/dist/src/services/message-delivery.d.ts +3 -1
  189. package/dist/src/services/message-delivery.d.ts.map +1 -1
  190. package/dist/src/services/message-delivery.js +23 -2
  191. package/dist/src/services/message-delivery.js.map +1 -1
  192. package/dist/src/services/network-policy.d.ts +88 -0
  193. package/dist/src/services/network-policy.d.ts.map +1 -0
  194. package/dist/src/services/network-policy.js +154 -0
  195. package/dist/src/services/network-policy.js.map +1 -0
  196. package/dist/src/services/notebook-classify-prompt.d.ts +15 -0
  197. package/dist/src/services/notebook-classify-prompt.d.ts.map +1 -0
  198. package/dist/src/services/notebook-classify-prompt.js +39 -0
  199. package/dist/src/services/notebook-classify-prompt.js.map +1 -0
  200. package/dist/src/services/notebook-classify.d.ts +80 -0
  201. package/dist/src/services/notebook-classify.d.ts.map +1 -0
  202. package/dist/src/services/notebook-classify.js +150 -0
  203. package/dist/src/services/notebook-classify.js.map +1 -0
  204. package/dist/src/services/presence.d.ts +108 -0
  205. package/dist/src/services/presence.d.ts.map +1 -0
  206. package/dist/src/services/presence.js +304 -0
  207. package/dist/src/services/presence.js.map +1 -0
  208. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  209. package/dist/src/services/prompt-defaults.js +13 -0
  210. package/dist/src/services/prompt-defaults.js.map +1 -1
  211. package/dist/src/services/site.d.ts +20 -0
  212. package/dist/src/services/site.d.ts.map +1 -1
  213. package/dist/src/services/site.js +70 -0
  214. package/dist/src/services/site.js.map +1 -1
  215. package/dist/src/storage/interface.d.ts +48 -0
  216. package/dist/src/storage/interface.d.ts.map +1 -1
  217. package/dist/src/storage/interface.js.map +1 -1
  218. package/dist/src/storage/providers/mongodb/index.d.ts +11 -1
  219. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  220. package/dist/src/storage/providers/mongodb/index.js +128 -2
  221. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  222. package/dist/src/storage/providers/sqlite/index.d.ts +7 -1
  223. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  224. package/dist/src/storage/providers/sqlite/index.js +27 -2
  225. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  226. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts +5 -1
  227. package/dist/src/storage/providers/sqlite/repos/direct-message.d.ts.map +1 -1
  228. package/dist/src/storage/providers/sqlite/repos/direct-message.js +48 -0
  229. package/dist/src/storage/providers/sqlite/repos/direct-message.js.map +1 -1
  230. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +2 -0
  231. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  232. package/dist/src/storage/providers/sqlite/repos/memory.js +55 -0
  233. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  234. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  235. package/dist/src/storage/providers/sqlite/schema.js +61 -0
  236. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  237. package/dist/src/storage/repositories/direct-message.repository.d.ts +5 -1
  238. package/dist/src/storage/repositories/direct-message.repository.d.ts.map +1 -1
  239. package/dist/src/storage/repositories/memory.repository.d.ts +25 -0
  240. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  241. package/dist/src/utils/node-descriptor.d.ts +34 -0
  242. package/dist/src/utils/node-descriptor.d.ts.map +1 -0
  243. package/dist/src/utils/node-descriptor.js +42 -0
  244. package/dist/src/utils/node-descriptor.js.map +1 -0
  245. package/dist/src/utils/version.d.ts +3 -0
  246. package/dist/src/utils/version.d.ts.map +1 -0
  247. package/dist/src/utils/version.js +40 -0
  248. package/dist/src/utils/version.js.map +1 -0
  249. package/dist/static/app-catalog.html +643 -4
  250. package/package.json +5 -2
  251. package/prisma/schema.postgres.prisma +51 -18
  252. package/prisma/schema.prisma +29 -0
@@ -1,6 +1,11 @@
1
1
  /**
2
- * Admin Dashboard — SPA View
3
- * Sidebar layout with data preloading and tab components.
2
+ * @file public/views/admin.js
3
+ * @description Admin Dashboard SPA view — sidebar layout with data preloading and tab components.
4
+ * @structure Single `loadAll` fetches all dashboard data; tabs render slices of it. SSE
5
+ * live-updates trigger a debounced, silent background refresh.
6
+ * @version-history
7
+ * v1.1.0 — 2026-06-18 — Debounce + silence SSE-driven refresh so busy nodes don't flicker "Loading…".
8
+ * v1.1.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
4
9
  */
5
10
  import { h } from 'preact';
6
11
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
@@ -31,6 +36,7 @@ import BoardsTab from './admin/boards-tab.js';
31
36
  import ChatInstancesTab from './admin/chat-instances-tab.js';
32
37
  import RealtimeTab from './admin/realtime-tab.js';
33
38
  import WorkTab from './admin/work-tab.js';
39
+ import MessagesAdminTab from './admin/messages-tab.js';
34
40
  import EmailTab from './admin/email-tab.js';
35
41
  import PushTab from './admin/push-tab.js';
36
42
  import DirectoryTab from './admin/directory-tab.js';
@@ -81,6 +87,7 @@ const NAV_GROUPS = [
81
87
  { id: 'chatInstances', icon: '\u{1F4AC}', key: 'dashboard.chatInstances', component: ChatInstancesTab, count: 'chatInstances' },
82
88
  { id: 'realtime', icon: '\u{1F4E1}', key: 'dashboard.realtime', component: RealtimeTab, count: 'rooms' },
83
89
  { id: 'work', icon: '\u{1F4E6}', key: 'dashboard.work', component: WorkTab, count: 'work' },
90
+ { id: 'messages', icon: '\u{1F4EC}', key: 'admin.messages.title', component: MessagesAdminTab },
84
91
  { id: 'memory-admin', icon: '\u{1F5C4}', key: 'dashboard.memoryAdmin', component: MemoryAdminTab },
85
92
  { id: 'agent-tasks', icon: '\u{1F4CB}', key: 'dashboard.agentTasksTab', component: AgentTasksAdminTab },
86
93
  { id: 'sharing-groups', icon: '\u{1F465}', key: 'dashboard.sharingGroupsTab', component: SharingGroupsAdminTab },
@@ -130,6 +137,7 @@ export default function Admin({ navigate, locale }) {
130
137
  const [lastUpdate, setLastUpdate] = useState(null);
131
138
  const mountRef = useRef(true);
132
139
  const retriedRef = useRef(false);
140
+ const reloadTimerRef = useRef(null);
133
141
 
134
142
  // Auth listener
135
143
  useEffect(() => {
@@ -156,9 +164,11 @@ export default function Admin({ navigate, locale }) {
156
164
  // Cleanup
157
165
  useEffect(() => { mountRef.current = true; return () => { mountRef.current = false; }; }, []);
158
166
 
159
- // Load all data — server-side auth is the source of truth
160
- const loadAll = useCallback(async () => {
161
- setLoading(true);
167
+ // Load all data — server-side auth is the source of truth.
168
+ // `silent` skips the loading spinner so SSE-driven background refreshes don't
169
+ // flicker the "Loading…" button on busy nodes (only manual refresh shows it).
170
+ const loadAll = useCallback(async (silent = false) => {
171
+ if (!silent) setLoading(true);
162
172
  setError(null);
163
173
  setAccessDenied(false);
164
174
  try {
@@ -307,13 +317,23 @@ export default function Admin({ navigate, locale }) {
307
317
  if (session) loadAll();
308
318
  }, [session, loadAll]);
309
319
 
310
- // SSE live updates — auto-reload on server-side data changes
320
+ // SSE live updates — auto-reload on server-side data changes.
321
+ // Debounced + silent: busy nodes emit many events per second; without this the
322
+ // whole dashboard re-fetches (and flickers "Loading…") on every single event.
311
323
  useEffect(() => {
312
324
  if (!session) return;
313
325
  connect(() => getSession()?.jwt);
314
- onUpdate(loadAll);
326
+ const debouncedReload = () => {
327
+ if (reloadTimerRef.current) clearTimeout(reloadTimerRef.current);
328
+ reloadTimerRef.current = setTimeout(() => {
329
+ reloadTimerRef.current = null;
330
+ if (mountRef.current) loadAll(true);
331
+ }, 1500);
332
+ };
333
+ onUpdate(debouncedReload);
315
334
  return () => {
316
- offUpdate(loadAll);
335
+ offUpdate(debouncedReload);
336
+ if (reloadTimerRef.current) clearTimeout(reloadTimerRef.current);
317
337
  disconnect();
318
338
  };
319
339
  }, [session, loadAll]);
@@ -322,7 +342,7 @@ export default function Admin({ navigate, locale }) {
322
342
  function switchPage(id) {
323
343
  setActivePage(id);
324
344
  // Update URL without reload
325
- const url = new URL(window.location);
345
+ const url = new URL(window.location.href);
326
346
  url.searchParams.set('tab', id);
327
347
  history.replaceState(null, '', url);
328
348
  }
@@ -9,6 +9,8 @@
9
9
  * @structure DocSolo (default export)
10
10
  * @version-history
11
11
  * v1.0.0 — 2026-06-09 — Initial: pop-out document window.
12
+ * v1.0.1 — 2026-06-19 — Import DocumentView/DocumentEditor from their new home
13
+ * (./profile/organisms/document.js) after the organisms-tab module split.
12
14
  */
13
15
  import { h } from 'preact';
14
16
  import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
@@ -18,7 +20,7 @@ import { getSession } from '/js/services/auth.js';
18
20
  import { connect, disconnect, onUpdate, offUpdate } from '/lib/live-updates.js';
19
21
  import { useViewCSS } from '/components/useViewCSS.js';
20
22
  import * as orgService from '/js/services/organisms.js';
21
- import { DocumentView, DocumentEditor } from './profile/organisms-tab.js';
23
+ import { DocumentView, DocumentEditor } from './profile/organisms/document.js';
22
24
 
23
25
  const html = htm.bind(h);
24
26
 
@@ -300,11 +300,12 @@ function GuideIndex({ locale, onSelectGuide, navigate }) {
300
300
  function GuideDetail({ slug, locale, onBack }) {
301
301
  const labels = LABELS[locale] || LABELS.en;
302
302
  const guide = GUIDES[slug];
303
- if (!guide) return html`<div class="gd-container"><p>Guide not found.</p></div>`;
304
- const data = guide[locale] || guide.en;
303
+ const data = guide ? (guide[locale] || guide.en) : null;
305
304
 
306
- // Build markdown for clipboard
305
+ // Hooks must run unconditionally before any early return (Rules of Hooks) — guard their
306
+ // bodies against a missing guide instead.
307
307
  const buildMarkdown = useCallback(() => {
308
+ if (!data) return '';
308
309
  let md = '# ' + data.title + '\n\nNode URL: ' + NODE_URL + '\n\nIMPORTANT: Make it a single downloadable HTML file.\n\n';
309
310
  data.sections.forEach(s => {
310
311
  md += '## ' + s.heading + '\n\n' + s.body + '\n\n';
@@ -316,9 +317,12 @@ function GuideDetail({ slug, locale, onBack }) {
316
317
  }, [data]);
317
318
 
318
319
  useEffect(() => {
320
+ if (!data) return;
319
321
  document.title = data.title + ' | AIME AT';
320
322
  window.scrollTo(0, 0);
321
- }, [slug, data.title]);
323
+ }, [slug, data]);
324
+
325
+ if (!guide) return html`<div class="gd-container"><p>Guide not found.</p></div>`;
322
326
 
323
327
  return html`
324
328
  <div class="gd-container">
@@ -2,6 +2,7 @@
2
2
  * @file hobbies.js
3
3
  * @description Hobbies view module — multi-view hobby directory (home, search, profile, join, me, matches) rendered with Preact + HTM.
4
4
  * @version-history
5
+ * v1.3.0 — 2026-06-19 — Show a presence dot next to people in the directory results + profile header (shared <PresenceDot>)
5
6
  * v1.1.0 — 2026-06-02 — Phase 5: tokenize for dark mode (replaced light-only rgba(255,255,255,...) divider with var(--border))
6
7
  * v1.2.0 — 2026-06-02 — Component unification (#4 buttons): hb-btn* → canonical
7
8
  * theme.css btn-primary / btn-outline / btn-danger / btn-sm.
@@ -15,6 +16,7 @@ import { escHtml, escAttr } from '/js/utils.js';
15
16
  import { t, getLocale, switchLocale } from '/js/i18n.js';
16
17
  import { apiGet, apiPost, apiPut, apiDelete } from '/js/api.js';
17
18
  import { useViewCSS } from '/components/useViewCSS.js';
19
+ import { PresenceDot } from '/components/PresenceDot.js';
18
20
 
19
21
  const html = htm.bind(h);
20
22
 
@@ -207,7 +209,7 @@ function SearchView({ goTo, initData }) {
207
209
  <div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
208
210
  <span style="font-size:1.5rem;">${entry.avatar || '\uD83D\uDE0A'}</span>
209
211
  <div>
210
- <div class="hb-card-title">${entry.displayName}</div>
212
+ <div class="hb-card-title">${entry.displayName} <${PresenceDot} ghii=${entry.ghii} /></div>
211
213
  ${(entry.city || entry.area) && html`<div class="hb-card-meta">${[entry.city, entry.area].filter(Boolean).join(', ')}</div>`}
212
214
  </div>
213
215
  </div>
@@ -282,7 +284,7 @@ function ProfileView({ goTo, ghii }) {
282
284
  <div class="hb-profile-header">
283
285
  <span class="hb-profile-avatar">${entry.avatar || '\uD83D\uDE0A'}</span>
284
286
  <div>
285
- <div class="hb-profile-name">${entry.displayName}</div>
287
+ <div class="hb-profile-name">${entry.displayName} <${PresenceDot} ghii=${ghii} label=${true} /></div>
286
288
  <div class="hb-profile-ghii">${ghii}</div>
287
289
  </div>
288
290
  </div>
@@ -5,6 +5,7 @@
5
5
  * @version-history
6
6
  * v1.1.0 — 2026-06-02 — Component unification (#18 category cards): category tiles
7
7
  * use canonical theme.css .category-card / -icon / -name / -count (was .mk-category-*).
8
+ * v1.1.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
8
9
  */
9
10
  import { h } from 'preact';
10
11
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -379,19 +380,19 @@ function CreateListingView({ extAction, instanceConfig, onNav, tl }) {
379
380
  const submit = () => {
380
381
  setMsg(''); setErr(''); setSending(true);
381
382
  const data = {
382
- title: document.getElementById('mk-sell-title').value,
383
- description: document.getElementById('mk-sell-desc').value,
384
- category: document.getElementById('mk-sell-category').value,
385
- price: parseInt(document.getElementById('mk-sell-price').value, 10),
383
+ title: /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-title')).value,
384
+ description: /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-desc')).value,
385
+ category: /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-category')).value,
386
+ price: parseInt(/** @type {HTMLInputElement} */ (document.getElementById('mk-sell-price')).value, 10),
386
387
  };
387
- const condition = document.getElementById('mk-sell-condition').value;
388
+ const condition = /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-condition')).value;
388
389
  if (condition) data.condition = condition;
389
- const availability = document.getElementById('mk-sell-avail').value;
390
+ const availability = /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-avail')).value;
390
391
  if (availability) data.availability = availability;
391
- const city = document.getElementById('mk-sell-city').value;
392
- const area = document.getElementById('mk-sell-area').value;
392
+ const city = /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-city')).value;
393
+ const area = /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-area')).value;
393
394
  if (city || area) { data.location = {}; if (city) data.location.city = city; if (area) data.location.area = area; }
394
- const tags = document.getElementById('mk-sell-tags').value;
395
+ const tags = /** @type {HTMLInputElement} */ (document.getElementById('mk-sell-tags')).value;
395
396
  if (tags) data.tags = tags.split(',').map(s => s.trim()).filter(Boolean);
396
397
 
397
398
  extAction('create-listing', data)
@@ -176,7 +176,7 @@ function OpenClawView() {
176
176
  ].map(([title, steps]) => html`
177
177
  <div class="oc-scenario">
178
178
  <h4>${title}</h4>
179
- <div class="flow"><ol>${steps.map(s => html`<li>${s}</li>`)}</ol></div>
179
+ <div class="flow"><ol>${(/** @type {any[]} */ (steps)).map(s => html`<li>${s}</li>`)}</ol></div>
180
180
  </div>
181
181
  `)}
182
182
  </div>
@@ -7,6 +7,7 @@
7
7
  * v1.1.0 — 2026-06-02 — Component unification (#11): node-badge dot uses
8
8
  * canonical <StatusDot status="online" /> instead of bespoke .dv-dot
9
9
  * (which had a hardcoded #22c55e — now tokenized via the component).
10
+ * v1.1.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
10
11
  */
11
12
  import { h } from 'preact';
12
13
  import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
@@ -23,7 +24,9 @@ const NODE_URL = window.location.origin;
23
24
  /* ══════════════════════════════════════════════
24
25
  i18n — use SPA i18n.js t() with 'dev.' prefix
25
26
  ══════════════════════════════════════════════ */
26
- function dt(key) {
27
+ // `locale` is accepted for call-site symmetry but currently unused — t() resolves against
28
+ // the active global locale, not a per-call override.
29
+ function dt(key, _locale) {
27
30
  return globalT('dev.' + key);
28
31
  }
29
32
 
@@ -612,7 +615,7 @@ function CommunityApps({ locale, isLoggedIn, session }) {
612
615
  style="background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:.25rem .4rem;color:var(--text);font-size:.8rem;width:120px" />
613
616
  <a href="#" onClick=${e => {
614
617
  e.preventDefault();
615
- const code = document.getElementById(codeInputId)?.value?.trim();
618
+ const code = /** @type {HTMLInputElement} */ (document.getElementById(codeInputId))?.value?.trim();
616
619
  if (!code) return;
617
620
  window.open(NODE_URL + app.download_url + '?code=' + encodeURIComponent(code));
618
621
  }} style="font-size:.85rem">\u2b07 ${dt('download', locale)}</a>
@@ -5,6 +5,7 @@
5
5
  * @version-history
6
6
  * v1.1.0 — 2026-06-02 — Component unification (#11): live-feed header dot uses
7
7
  * canonical <StatusDot status="live" /> instead of bespoke .live-dot.
8
+ * v1.1.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
8
9
  */
9
10
  import { h } from 'preact';
10
11
  import { useState, useEffect, useRef, useCallback } from 'preact/hooks';
@@ -1180,7 +1181,7 @@ function TheWorld({ navigate }) {
1180
1181
  // Trigger login modal
1181
1182
  const slot = document.getElementById('headerAuth');
1182
1183
  const loginBtn = slot && slot.querySelector('#aimeat-login-btn');
1183
- if (loginBtn) loginBtn.click();
1184
+ if (loginBtn) /** @type {HTMLElement} */ (loginBtn).click();
1184
1185
  }, [navigate]);
1185
1186
 
1186
1187
  return html`
@@ -1289,7 +1290,7 @@ export default function Portal({ navigate, locale }) {
1289
1290
  // Trigger login via the header auth button
1290
1291
  const loginBtn = document.querySelector('#headerAuth #aimeat-login-btn');
1291
1292
  if (loginBtn) {
1292
- loginBtn.click();
1293
+ /** @type {HTMLElement} */ (loginBtn).click();
1293
1294
  // After login, the aimeat-auth-change event will fire and we re-check
1294
1295
  const handler = () => {
1295
1296
  const s = getCurrentSession();
@@ -33,10 +33,10 @@ const BUCKETS = [
33
33
 
34
34
  export default function SharedBoard({ agents, onboardings, onAgentClick }) {
35
35
  const [bucketFilter, setBucketFilter] = useState(null);
36
- if (!agents || agents.length === 0) return null;
37
36
 
37
+ // Hooks must run unconditionally before any early return (Rules of Hooks).
38
38
  const agentStates = useMemo(() => {
39
- const rows = agents.map(agent => {
39
+ const rows = (agents || []).map(agent => {
40
40
  const state = detectAgentState(agent, onboardings?.[agent.name]);
41
41
  return { agent, state, bucket: BUCKET_OF[state] || 'quiet', onboarding: onboardings?.[agent.name] };
42
42
  });
@@ -63,6 +63,8 @@ export default function SharedBoard({ agents, onboardings, onAgentClick }) {
63
63
  return tagCounts;
64
64
  }, [agentStates]);
65
65
 
66
+ if (!agents || agents.length === 0) return null;
67
+
66
68
  return html`
67
69
  <div class="pf-agd-board">
68
70
  <div class="pf-agd-board-legend">
@@ -18,6 +18,7 @@
18
18
  * v1.2.0 -- 2026-05-24 -- Add command-reply visual pairing for slash commands
19
19
  * v1.1.0 -- 2026-05-24 -- M7: visual distinction for command messages (slash prefix)
20
20
  * v1.0.0 -- 2026-05-24 -- Initial creation for Agent Detail Tab-View
21
+ * v1.6.1 -- 2026-06-19 -- JSDoc type annotations for frontend type-checking
21
22
  */
22
23
 
23
24
  import { h } from 'preact';
@@ -47,6 +48,17 @@ function threadLabel(thread) {
47
48
  function CommandPalette({ commands, onSend }) {
48
49
  const [expanded, setExpanded] = useState(false);
49
50
 
51
+ // Hooks must run unconditionally before any early return (Rules of Hooks).
52
+ const categories = useMemo(() => {
53
+ const cats = {};
54
+ for (const cmd of (commands || [])) {
55
+ const cat = cmd.category || t('profile.agents.detail.messages.commands.defaultCategory');
56
+ if (!cats[cat]) cats[cat] = [];
57
+ cats[cat].push(cmd);
58
+ }
59
+ return cats;
60
+ }, [commands]);
61
+
50
62
  if (!commands || commands.length === 0) {
51
63
  // No registered commands → one quiet line, not an expandable empty box.
52
64
  return html`
@@ -57,16 +69,6 @@ function CommandPalette({ commands, onSend }) {
57
69
  `;
58
70
  }
59
71
 
60
- const categories = useMemo(() => {
61
- const cats = {};
62
- for (const cmd of commands) {
63
- const cat = cmd.category || t('profile.agents.detail.messages.commands.defaultCategory');
64
- if (!cats[cat]) cats[cat] = [];
65
- cats[cat].push(cmd);
66
- }
67
- return cats;
68
- }, [commands]);
69
-
70
72
  return html`
71
73
  <div class="pf-agd-commands">
72
74
  <div class="pf-agd-commands-header" onClick=${() => setExpanded(!expanded)}>
@@ -332,7 +334,7 @@ export default function TabMessages({ agent, agentName, session, showToast }) {
332
334
  ${messages.length > 0 && html`
333
335
  <div class="pf-agd-msg-history" ref=${historyRef}>
334
336
  ${(() => {
335
- const sorted = [...messages].sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
337
+ const sorted = [...messages].sort((a, b) => +new Date(a.createdAt || 0) - +new Date(b.createdAt || 0));
336
338
  // An option-prompt is answerable only while it is the newest message
337
339
  // in its thread (any later message locks it). Map each thread to its
338
340
  // last message id, and each prompt_id to the owner's chosen text.
@@ -16,6 +16,7 @@
16
16
  * v1.2.0 -- 2026-05-22 -- Fix: use camelCase field names from API (createdAt, metadata.tokensUsed)
17
17
  * v1.1.0 -- 2026-05-22 -- Show timestamp on messages, sort oldest-first (chat-style)
18
18
  * v1.0.0 -- 2026-05-22 -- Initial creation for Agent Dashboard Phase 3
19
+ * v1.3.1 -- 2026-06-19 -- JSDoc type annotations for frontend type-checking
19
20
  */
20
21
  import { h } from 'preact';
21
22
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -262,7 +263,7 @@ export default function AgentMessagesSubtab({ agentName, session, showToast }) {
262
263
  const lastSeen = null; // TODO Phase 2+: derive from agent.last_seen
263
264
 
264
265
  // Oldest-first (chat order).
265
- const sorted = [...messages].sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
266
+ const sorted = [...messages].sort((a, b) => +new Date(a.createdAt || 0) - +new Date(b.createdAt || 0));
266
267
  // Latest message id per thread -> an option-prompt is answerable only while it
267
268
  // is the newest message in its thread (any later message locks it).
268
269
  const lastIdByThread = {};
@@ -4,6 +4,7 @@
4
4
  * @version-history
5
5
  * v1.1.0 — 2026-03-19 — Add launch button to My Apps list
6
6
  * v1.0.0 — 2026-03-17 — Refactor: replace inline styles with CSS utility classes (card-h3, text-caption, etc.)
7
+ * v1.1.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
7
8
  */
8
9
  import { h } from 'preact';
9
10
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -42,7 +43,7 @@ export default function AppsTab({ session, showToast, onStats }) {
42
43
 
43
44
  async function handleUpload(file, screenshot, accessCode) {
44
45
  if (!file) { showToast(t('profile.apps.selectFile'), true); return; }
45
- const readFile = (f) => new Promise(res => { const r = new FileReader(); r.onload = () => res(r.result.split(',')[1]); r.readAsDataURL(f); });
46
+ const readFile = (f) => new Promise(res => { const r = new FileReader(); r.onload = () => res(/** @type {string} */ (r.result).split(',')[1]); r.readAsDataURL(f); });
46
47
  const contentBase64 = await readFile(file);
47
48
  const opts = {};
48
49
  if (accessCode) opts.accessCode = accessCode;
@@ -34,6 +34,9 @@ export default function BoardsTab({ session, showToast }) {
34
34
  const [boardView, setBoardView] = useState(null);
35
35
  const [brdSubTab, setBrdSubTab] = useState('mine');
36
36
  const [showBrdForm, setShowBrdForm] = useState(false);
37
+ // Refs used by the board-detail view below — declared unconditionally (Rules of Hooks).
38
+ const postRef = useRef(null);
39
+ const memberInputRef = useRef(null);
37
40
 
38
41
  useEffect(() => {
39
42
  if (session) loadMyData();
@@ -176,8 +179,6 @@ export default function BoardsTab({ session, showToast }) {
176
179
  }
177
180
 
178
181
  if (boardView) {
179
- const postRef = useRef(null);
180
- const memberInputRef = useRef(null);
181
182
  const bd = boardView.boardData;
182
183
  const showMembers = bd && bd.visibility === 'shared' && isBoardOwner(bd);
183
184
  return html`
@@ -14,6 +14,7 @@
14
14
  * - renderStep4 — Step 4 UI (Synthesis — grouped proposals + A/B/C options)
15
15
  * @version-history
16
16
  * v1.0.0 — 2026-03-29 — Initial V2 implementation
17
+ * v1.0.1 — 2026-06-19 — lint fixes (misleading-char-class/unused-expression/empty-block)
17
18
  */
18
19
  import { h } from 'preact';
19
20
  import { useState, useEffect, useCallback } from 'preact/hooks';
@@ -40,7 +41,7 @@ import { getBatch, updateBatch, createVersion } from '/js/services/calibrator.js
40
41
 
41
42
  // ── Helpers ──
42
43
 
43
- async function callModel(projectId, prompt, modelId, { retries = 1, temperature, top_p, max_tokens } = {}) {
44
+ async function callModel(projectId, prompt, modelId, { retries = 1, temperature, top_p, max_tokens } = /** @type {{ retries?: number, temperature?: number, top_p?: number, max_tokens?: number }} */ ({})) {
44
45
  for (let attempt = 0; attempt <= retries; attempt++) {
45
46
  const controller = new AbortController();
46
47
  const timeoutId = setTimeout(() => controller.abort(), 1_800_000); // 30 min
@@ -436,7 +437,7 @@ export default function BatchCard({ batchSummary, index, projectId, project, cur
436
437
  const { getVersion } = await import('/js/services/calibrator.js');
437
438
  const ver = await getVersion(projectId, batchVersion);
438
439
  if (ver?.prompt) batchPrompt = ver.prompt;
439
- } catch {}
440
+ } catch { /* best effort — fall through to the no-prompt guard below */ }
440
441
  }
441
442
  if (!batchPrompt) { showToast?.('Could not load prompt for this batch\'s version', true); return; }
442
443
 
@@ -10,6 +10,7 @@
10
10
  * @version-history
11
11
  * v1.0.0 — 2026-03-10 — Initial data wallet tab implementation
12
12
  * v1.1.0 — 2026-03-17 — Refactor: replace all inline styles with CSS classes
13
+ * v1.1.1 — 2026-06-19 — lint fixes (misleading-char-class/unused-expression/empty-block)
13
14
  */
14
15
  import { h } from 'preact';
15
16
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -279,7 +280,7 @@ export default function DataWalletTab({ session, showToast }) {
279
280
  <td><input type="checkbox" checked=${selectedConsents.has(cId)}
280
281
  onChange=${(e) => {
281
282
  const next = new Set(selectedConsents);
282
- e.target.checked ? next.add(cId) : next.delete(cId);
283
+ if (e.target.checked) next.add(cId); else next.delete(cId);
283
284
  setSelectedConsents(next);
284
285
  }} /></td>
285
286
  <td><span class="dw-code-accent">${escHtml(c.data_pattern || c.pattern || '-')}</span></td>
@@ -23,6 +23,7 @@
23
23
  * v1.4.0 — 2026-06-02 — Component unification (#11): all six extension/instance
24
24
  * status dots use the canonical <StatusDot> instead of bespoke .ext-status-dot
25
25
  * (which had hardcoded #4ade80/#9ca3af — now tokenized via the component).
26
+ * v1.4.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
26
27
  */
27
28
  import { h } from 'preact';
28
29
  import { useState, useEffect } from 'preact/hooks';
@@ -577,18 +578,18 @@ export default function ExtensionsTab({ session, showToast }) {
577
578
  try {
578
579
  let manifest = '';
579
580
  if (manifestMode === 'upload') {
580
- const fileInput = document.getElementById('ext-manifest-file');
581
+ const fileInput = /** @type {HTMLInputElement} */ (document.getElementById('ext-manifest-file'));
581
582
  if (!fileInput?.files[0]) throw new Error('No manifest file selected');
582
583
  manifest = await fileInput.files[0].text();
583
584
  } else {
584
- const textarea = document.getElementById('ext-manifest-text');
585
+ const textarea = /** @type {HTMLInputElement} */ (document.getElementById('ext-manifest-text'));
585
586
  manifest = textarea?.value || '';
586
587
  if (!manifest.trim()) throw new Error('Manifest is empty');
587
588
  }
588
589
 
589
590
  const libs = {};
590
591
  if (libMode === 'upload') {
591
- const libInput = document.getElementById('ext-lib-files');
592
+ const libInput = /** @type {HTMLInputElement} */ (document.getElementById('ext-lib-files'));
592
593
  if (libInput?.files) {
593
594
  for (const f of libInput.files) {
594
595
  libs[f.name] = await f.text();
@@ -6,6 +6,8 @@
6
6
  * v1.1.0 — 2026-03-17 — Replace inline styles with CSS classes
7
7
  * v1.2.0 — 2026-06-02 — Component unification (#11): peer online/offline dot
8
8
  * uses canonical <StatusDot> (alive/dead) instead of bespoke .peer-dot.
9
+ * v1.3.0 — 2026-06-19 — Show peer tier (visiting/member/genesis) + availability
10
+ * (temporary/permanent) pills next to the status (visiting-node feature).
9
11
  */
10
12
  import { h } from 'preact';
11
13
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -56,6 +58,8 @@ export default function FederationTab({ session, showToast }) {
56
58
  <div class="card-subtitle">${escHtml(p.url || '')}</div>
57
59
  </div>
58
60
  <div class="peer-status">
61
+ <span class="badge ${(p.tier || 'member') === 'visiting' ? 'badge-info' : (p.tier || 'member') === 'genesis' ? 'badge-info' : 'badge-success'}">${t('profile.federation.tier_' + (p.tier || 'member')) || (p.tier || 'member')}</span>
62
+ ${p.availability && p.availability !== 'unknown' ? html`<span class="badge ${p.availability === 'permanent' ? 'badge-success' : 'badge-info'}">${t('profile.federation.avail_' + p.availability) || p.availability}</span>` : null}
59
63
  <${StatusDot} status=${alive ? 'alive' : 'dead'} />
60
64
  <span class="fed-status-text ${alive ? 'online' : 'offline'}">${alive ? t('profile.federation.online') : t('profile.federation.offline')}</span>
61
65
  </div>
@@ -27,6 +27,7 @@
27
27
  * @version-history
28
28
  * v1.0.0 — 2026-03-22 — Extracted from foundry-tab.js ProjectDashboard
29
29
  * v2.0.0 — 2026-03-26 — Wire multi-pass flow: runComponentPasses for extension/cortex/app
30
+ * v2.0.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
30
31
  */
31
32
  import { t } from '/js/i18n.js';
32
33
  import { apiPost, apiDelete } from '/js/api.js';
@@ -206,7 +207,7 @@ export function useAutopilot(core, autopilotState, projectId, orSettings, sessio
206
207
  * @param {string} cid - Component ID
207
208
  * @param {Object} project - Project object (with blueprint)
208
209
  * @param {Object} interviewSpec - Interview spec
209
- * @returns {{ success: boolean, content: string|null, updated: Object }} - Final result
210
+ * @returns {Promise<{ success: boolean, content: string|null, updated: Object }>} - Final result
210
211
  */
211
212
  async function runComponentPasses(comp, cid, project, interviewSpec) {
212
213
  const blueprint = project.blueprint;
@@ -734,7 +735,7 @@ export function useAutopilot(core, autopilotState, projectId, orSettings, sessio
734
735
  await writeProjectLog(projectId, 'extension_probe_start', { meta: { component: comp.label, extensionName: updated.registeredAs, scenarios: probeScenarios.length, by: 'autopilot' } });
735
736
 
736
737
  const probeResp = await probeExtension(projectId, updated.registeredAs, probeScenarios);
737
- const probeResults = probeResp?.data?.results || [];
738
+ const probeResults = /** @type {any} */ (probeResp)?.data?.results || [];
738
739
 
739
740
  // Save probe results + context bundle — downstream prompts will inject these
740
741
  const contextBundle = createBundle(updated, probeResults);
@@ -131,7 +131,7 @@ export function getWorkflowStep(component, validationResult, result) {
131
131
 
132
132
  /** Small circle-with-arrow SVG placed inline next to the current action target.
133
133
  * @param {Object} props
134
- * @param {'right'|'down'} [props.direction='right'] — arrow direction
134
+ * @param {'right'|'down'} [props.direction='right'] - arrow direction
135
135
  */
136
136
  function StepArrow({ direction = 'right' } = {}) {
137
137
  const chevron = direction === 'down'
@@ -162,7 +162,7 @@ export function getWorkflowStep(component, validationResult, result) {
162
162
 
163
163
  /** Small circle-with-arrow SVG placed inline next to the current action target.
164
164
  * @param {Object} props
165
- * @param {'right'|'down'} [props.direction='right'] — arrow direction
165
+ * @param {'right'|'down'} [props.direction='right'] - arrow direction
166
166
  */
167
167
  function StepArrow({ direction = 'right' } = {}) {
168
168
  const chevron = direction === 'down'
@@ -11,10 +11,13 @@
11
11
  * @structure InboxTab (default) · Composer (Toast UI) · MessageBubble · Avatar · helpers
12
12
  * @usage Lazy-loaded profile tab; registered in profile.js TABS as id `messages`.
13
13
  * @version-history
14
+ * v1.3.0 -- 2026-06-19 -- Show a presence dot next to peers (request rows, conversation list,
15
+ * thread header) via the shared <PresenceDot>.
14
16
  * v1.2.0 -- 2026-06-16 -- Composer upgraded to the shared Toast UI editor (parity with the
15
17
  * workspace document editor) + markdown-preview fallback.
16
18
  * v1.1.0 -- 2026-06-16 -- Redesigned as a proper messenger (avatars, bubbles, ticks, dividers).
17
19
  * v1.0.0 -- 2026-06-16 -- Initial creation for user-to-user messaging (layer 5).
20
+ * v1.3.1 -- 2026-06-19 -- JSDoc type annotations for frontend type-checking
18
21
  */
19
22
  import { h } from 'preact';
20
23
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
@@ -24,6 +27,7 @@ import { t, getLocale } from '/js/i18n.js';
24
27
  import { escHtml } from '/js/utils.js';
25
28
  import { Markdown } from '/components/Markdown.js';
26
29
  import { minidenticon } from '/lib/minidenticons.min.js';
30
+ import { PresenceDot } from '/components/PresenceDot.js';
27
31
  import * as messages from '/js/services/messages.js';
28
32
 
29
33
  /* Lazy-load the vendored Toast UI Editor (MIT, /lib/toastui/) — the same editor the workspace
@@ -313,7 +317,7 @@ export default function InboxTab({ showToast }) {
313
317
  <div class="inbox-request-top">
314
318
  <${Avatar} seed=${r.contactId} size=${36} />
315
319
  <div class="inbox-request-id">
316
- <div class="inbox-name">${escHtml(peerName(r.contactId))}</div>
320
+ <div class="inbox-name">${escHtml(peerName(r.contactId))} <${PresenceDot} ghii=${r.contactId} /></div>
317
321
  <div class="inbox-sub">${escHtml(r.contactId)}</div>
318
322
  </div>
319
323
  </div>
@@ -332,7 +336,7 @@ export default function InboxTab({ showToast }) {
332
336
  <${Avatar} seed=${c.peerGhii} size=${40} />
333
337
  <div class="inbox-conv-main">
334
338
  <div class="inbox-conv-line1">
335
- <span class="inbox-name">${escHtml(peerName(c.peerGhii))}</span>
339
+ <span class="inbox-name">${escHtml(peerName(c.peerGhii))} <${PresenceDot} ghii=${c.peerGhii} /></span>
336
340
  <span class="inbox-conv-time">${c.updatedAt ? timeShort(c.updatedAt) : ''}</span>
337
341
  </div>
338
342
  <div class="inbox-conv-line2">
@@ -350,7 +354,7 @@ export default function InboxTab({ showToast }) {
350
354
  <div class="inbox-thread-head">
351
355
  <${Avatar} seed=${activeConv.peerGhii} size=${36} />
352
356
  <div class="inbox-thread-id">
353
- <div class="inbox-name">${escHtml(peerName(activeConv.peerGhii))}</div>
357
+ <div class="inbox-name">${escHtml(peerName(activeConv.peerGhii))} <${PresenceDot} ghii=${activeConv.peerGhii} label=${true} /></div>
354
358
  <div class="inbox-sub">${escHtml(activeConv.peerGhii)}</div>
355
359
  </div>
356
360
  </div>
@@ -35,8 +35,8 @@ const PANELS = {
35
35
 
36
36
  export function InlinePanel({ panelId, onClose, switchTab, session }) {
37
37
  const cfg = PANELS[panelId];
38
- if (!cfg) return null;
39
38
 
39
+ // Hooks must run unconditionally before any early return (Rules of Hooks).
40
40
  const panelRef = useRef(null);
41
41
 
42
42
  /* Smooth scroll into view after opening */
@@ -48,6 +48,8 @@ export function InlinePanel({ panelId, onClose, switchTab, session }) {
48
48
  }
49
49
  }, [panelId]);
50
50
 
51
+ if (!cfg) return null;
52
+
51
53
  return html`
52
54
  <div class="pf-inline-panel open" ref=${panelRef}>
53
55
  <div class="pf-inline-panel-header">
@@ -18,6 +18,7 @@
18
18
  * pf-sharing-box, pf-sharing-heading, kpkg-detail-loading). Replace inline
19
19
  * visibility pill with shared VisibilityPill component. Replace hardcoded English
20
20
  * strings with i18n t() calls.
21
+ * v1.0.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
21
22
  */
22
23
  import { h } from 'preact';
23
24
  import { useState, useEffect, useCallback, useRef } from 'preact/hooks';
@@ -393,7 +394,7 @@ export default function KnowledgeTab({ session, showToast, onStats }) {
393
394
  if (!el) {
394
395
  const all = document.querySelectorAll('[data-entry-key]');
395
396
  for (const candidate of all) {
396
- if (candidate.dataset.entryKey.endsWith('/' + entryKey)) { el = candidate; break; }
397
+ if (/** @type {HTMLElement} */ (candidate).dataset.entryKey.endsWith('/' + entryKey)) { el = candidate; break; }
397
398
  }
398
399
  }
399
400
  if (el) { el.scrollIntoView({ behavior: 'smooth', block: 'center' }); el.classList.add('kpkg-entry-highlight'); setTimeout(() => el.classList.remove('kpkg-entry-highlight'), 1500); }