aimeat 1.25.1 → 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 (229) hide show
  1. package/README.md +8 -1
  2. package/dist/locales/en.json +130 -0
  3. package/dist/locales/fi.json +130 -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 +6 -0
  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 +68 -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/overview-tab.js +4 -3
  48. package/dist/public/views/admin/portal-tab.js +90 -2
  49. package/dist/public/views/admin/push-tab.js +8 -6
  50. package/dist/public/views/admin/security-tab.js +7 -7
  51. package/dist/public/views/admin.js +2 -1
  52. package/dist/public/views/doc-solo.js +3 -1
  53. package/dist/public/views/guides.js +8 -4
  54. package/dist/public/views/hobbies.js +4 -2
  55. package/dist/public/views/marketplace.js +10 -9
  56. package/dist/public/views/openclaw.js +1 -1
  57. package/dist/public/views/portal-dev.js +5 -2
  58. package/dist/public/views/portal.js +3 -2
  59. package/dist/public/views/profile/agents/shared-board.js +4 -2
  60. package/dist/public/views/profile/agents/tab-messages.js +13 -11
  61. package/dist/public/views/profile/agents-messages-subtab.js +2 -1
  62. package/dist/public/views/profile/apps-tab.js +2 -1
  63. package/dist/public/views/profile/boards-tab.js +3 -2
  64. package/dist/public/views/profile/calibrator-batch.js +3 -2
  65. package/dist/public/views/profile/data-wallet-tab.js +2 -1
  66. package/dist/public/views/profile/extensions-tab.js +4 -3
  67. package/dist/public/views/profile/federation-tab.js +4 -0
  68. package/dist/public/views/profile/foundry-dashboard/use-autopilot.js +3 -2
  69. package/dist/public/views/profile/foundry-detail.js +1 -1
  70. package/dist/public/views/profile/generator-detail.js +1 -1
  71. package/dist/public/views/profile/inbox-tab.js +7 -3
  72. package/dist/public/views/profile/inline-panels.js +3 -1
  73. package/dist/public/views/profile/knowledge-tab.js +2 -1
  74. package/dist/public/views/profile/landing-page.js +88 -0
  75. package/dist/public/views/profile/memory-tab.js +6 -5
  76. package/dist/public/views/profile/notebook-tab.js +500 -0
  77. package/dist/public/views/profile/organisms/activity-panel.js +135 -0
  78. package/dist/public/views/profile/organisms/agents.js +141 -0
  79. package/dist/public/views/profile/organisms/document.js +274 -0
  80. package/dist/public/views/profile/organisms/helpers.js +61 -0
  81. package/dist/public/views/profile/organisms/home.js +315 -0
  82. package/dist/public/views/profile/organisms/members.js +220 -0
  83. package/dist/public/views/profile/organisms/panels.js +173 -0
  84. package/dist/public/views/profile/organisms/participants-panel.js +160 -0
  85. package/dist/public/views/profile/organisms/schema-form.js +127 -0
  86. package/dist/public/views/profile/organisms/sources-panel.js +164 -0
  87. package/dist/public/views/profile/organisms/widgets.js +58 -0
  88. package/dist/public/views/profile/organisms/workspace-comments.js +99 -0
  89. package/dist/public/views/profile/organisms/workspace-list.js +268 -0
  90. package/dist/public/views/profile/organisms/workspace.js +1272 -0
  91. package/dist/public/views/profile/organisms-tab.js +456 -3819
  92. package/dist/public/views/profile/shared.js +64 -2
  93. package/dist/public/views/profile/workflows-form.js +1 -1
  94. package/dist/public/views/profile.js +16 -14
  95. package/dist/public/views/public-knowledge-viewer.js +3 -2
  96. package/dist/public/views/public-workspace-viewer.js +2 -1
  97. package/dist/scripts/check-importmap.d.ts +2 -0
  98. package/dist/scripts/check-importmap.d.ts.map +1 -0
  99. package/dist/scripts/check-importmap.js +102 -0
  100. package/dist/scripts/check-importmap.js.map +1 -0
  101. package/dist/src/config.d.ts +10 -0
  102. package/dist/src/config.d.ts.map +1 -1
  103. package/dist/src/config.js +5 -0
  104. package/dist/src/config.js.map +1 -1
  105. package/dist/src/generated/api-types.d.ts +852 -11
  106. package/dist/src/generated/api-types.d.ts.map +1 -1
  107. package/dist/src/generated/prisma-postgres/edge.js +18 -5
  108. package/dist/src/generated/prisma-postgres/index-browser.js +15 -2
  109. package/dist/src/generated/prisma-postgres/index.d.ts +422 -7
  110. package/dist/src/generated/prisma-postgres/index.js +18 -5
  111. package/dist/src/generated/prisma-postgres/package.json +1 -1
  112. package/dist/src/generated/prisma-postgres/schema.prisma +30 -12
  113. package/dist/src/generated/prisma-postgres/wasm.js +18 -5
  114. package/dist/src/routes/federation-peer.d.ts.map +1 -1
  115. package/dist/src/routes/federation-peer.js +327 -43
  116. package/dist/src/routes/federation-peer.js.map +1 -1
  117. package/dist/src/routes/federation-sync.d.ts +2 -0
  118. package/dist/src/routes/federation-sync.d.ts.map +1 -1
  119. package/dist/src/routes/federation-sync.js +20 -1
  120. package/dist/src/routes/federation-sync.js.map +1 -1
  121. package/dist/src/routes/librarian.d.ts +18 -0
  122. package/dist/src/routes/librarian.d.ts.map +1 -0
  123. package/dist/src/routes/librarian.js +88 -0
  124. package/dist/src/routes/librarian.js.map +1 -0
  125. package/dist/src/routes/portal.d.ts.map +1 -1
  126. package/dist/src/routes/portal.js +9 -1
  127. package/dist/src/routes/portal.js.map +1 -1
  128. package/dist/src/routes/presence.d.ts +23 -0
  129. package/dist/src/routes/presence.d.ts.map +1 -0
  130. package/dist/src/routes/presence.js +88 -0
  131. package/dist/src/routes/presence.js.map +1 -0
  132. package/dist/src/routes/site.d.ts +2 -0
  133. package/dist/src/routes/site.d.ts.map +1 -1
  134. package/dist/src/routes/site.js +27 -0
  135. package/dist/src/routes/site.js.map +1 -1
  136. package/dist/src/routes/sse.d.ts +2 -0
  137. package/dist/src/routes/sse.d.ts.map +1 -1
  138. package/dist/src/routes/sse.js +9 -0
  139. package/dist/src/routes/sse.js.map +1 -1
  140. package/dist/src/routes/wellknown.d.ts.map +1 -1
  141. package/dist/src/routes/wellknown.js +5 -0
  142. package/dist/src/routes/wellknown.js.map +1 -1
  143. package/dist/src/routes/work.d.ts.map +1 -1
  144. package/dist/src/routes/work.js +6 -0
  145. package/dist/src/routes/work.js.map +1 -1
  146. package/dist/src/server-bootstrap/routes-loader.d.ts.map +1 -1
  147. package/dist/src/server-bootstrap/routes-loader.js +13 -0
  148. package/dist/src/server-bootstrap/routes-loader.js.map +1 -1
  149. package/dist/src/server-bootstrap/service-init.d.ts.map +1 -1
  150. package/dist/src/server-bootstrap/service-init.js +34 -0
  151. package/dist/src/server-bootstrap/service-init.js.map +1 -1
  152. package/dist/src/services/config-schema.d.ts.map +1 -1
  153. package/dist/src/services/config-schema.js +2 -0
  154. package/dist/src/services/config-schema.js.map +1 -1
  155. package/dist/src/services/federation-availability.d.ts +28 -0
  156. package/dist/src/services/federation-availability.d.ts.map +1 -0
  157. package/dist/src/services/federation-availability.js +58 -0
  158. package/dist/src/services/federation-availability.js.map +1 -0
  159. package/dist/src/services/federation-book.d.ts +70 -0
  160. package/dist/src/services/federation-book.d.ts.map +1 -0
  161. package/dist/src/services/federation-book.js +198 -0
  162. package/dist/src/services/federation-book.js.map +1 -0
  163. package/dist/src/services/federation-tiers.d.ts +45 -0
  164. package/dist/src/services/federation-tiers.d.ts.map +1 -0
  165. package/dist/src/services/federation-tiers.js +58 -0
  166. package/dist/src/services/federation-tiers.js.map +1 -0
  167. package/dist/src/services/federation.d.ts +15 -0
  168. package/dist/src/services/federation.d.ts.map +1 -1
  169. package/dist/src/services/federation.js +12 -0
  170. package/dist/src/services/federation.js.map +1 -1
  171. package/dist/src/services/librarian.d.ts +62 -0
  172. package/dist/src/services/librarian.d.ts.map +1 -0
  173. package/dist/src/services/librarian.js +135 -0
  174. package/dist/src/services/librarian.js.map +1 -0
  175. package/dist/src/services/network-policy.d.ts +88 -0
  176. package/dist/src/services/network-policy.d.ts.map +1 -0
  177. package/dist/src/services/network-policy.js +154 -0
  178. package/dist/src/services/network-policy.js.map +1 -0
  179. package/dist/src/services/notebook-classify-prompt.d.ts +15 -0
  180. package/dist/src/services/notebook-classify-prompt.d.ts.map +1 -0
  181. package/dist/src/services/notebook-classify-prompt.js +39 -0
  182. package/dist/src/services/notebook-classify-prompt.js.map +1 -0
  183. package/dist/src/services/notebook-classify.d.ts +80 -0
  184. package/dist/src/services/notebook-classify.d.ts.map +1 -0
  185. package/dist/src/services/notebook-classify.js +150 -0
  186. package/dist/src/services/notebook-classify.js.map +1 -0
  187. package/dist/src/services/presence.d.ts +108 -0
  188. package/dist/src/services/presence.d.ts.map +1 -0
  189. package/dist/src/services/presence.js +304 -0
  190. package/dist/src/services/presence.js.map +1 -0
  191. package/dist/src/services/prompt-defaults.d.ts.map +1 -1
  192. package/dist/src/services/prompt-defaults.js +13 -0
  193. package/dist/src/services/prompt-defaults.js.map +1 -1
  194. package/dist/src/services/site.d.ts +20 -0
  195. package/dist/src/services/site.d.ts.map +1 -1
  196. package/dist/src/services/site.js +70 -0
  197. package/dist/src/services/site.js.map +1 -1
  198. package/dist/src/storage/interface.d.ts +18 -0
  199. package/dist/src/storage/interface.d.ts.map +1 -1
  200. package/dist/src/storage/interface.js.map +1 -1
  201. package/dist/src/storage/providers/mongodb/index.d.ts +5 -0
  202. package/dist/src/storage/providers/mongodb/index.d.ts.map +1 -1
  203. package/dist/src/storage/providers/mongodb/index.js +65 -2
  204. package/dist/src/storage/providers/mongodb/index.js.map +1 -1
  205. package/dist/src/storage/providers/sqlite/index.d.ts +2 -0
  206. package/dist/src/storage/providers/sqlite/index.d.ts.map +1 -1
  207. package/dist/src/storage/providers/sqlite/index.js +15 -2
  208. package/dist/src/storage/providers/sqlite/index.js.map +1 -1
  209. package/dist/src/storage/providers/sqlite/repos/memory.d.ts +2 -0
  210. package/dist/src/storage/providers/sqlite/repos/memory.d.ts.map +1 -1
  211. package/dist/src/storage/providers/sqlite/repos/memory.js +55 -0
  212. package/dist/src/storage/providers/sqlite/repos/memory.js.map +1 -1
  213. package/dist/src/storage/providers/sqlite/schema.d.ts.map +1 -1
  214. package/dist/src/storage/providers/sqlite/schema.js +46 -0
  215. package/dist/src/storage/providers/sqlite/schema.js.map +1 -1
  216. package/dist/src/storage/repositories/memory.repository.d.ts +25 -0
  217. package/dist/src/storage/repositories/memory.repository.d.ts.map +1 -1
  218. package/dist/src/utils/node-descriptor.d.ts +34 -0
  219. package/dist/src/utils/node-descriptor.d.ts.map +1 -0
  220. package/dist/src/utils/node-descriptor.js +42 -0
  221. package/dist/src/utils/node-descriptor.js.map +1 -0
  222. package/dist/src/utils/version.d.ts +3 -0
  223. package/dist/src/utils/version.d.ts.map +1 -0
  224. package/dist/src/utils/version.js +40 -0
  225. package/dist/src/utils/version.js.map +1 -0
  226. package/dist/static/app-catalog.html +643 -4
  227. package/package.json +5 -2
  228. package/prisma/schema.postgres.prisma +37 -19
  229. package/prisma/schema.prisma +14 -0
@@ -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); }
@@ -15,6 +15,8 @@
15
15
  * AppStrip — home/section sub-components
16
16
  * - LandingPage — main orchestrator (default export)
17
17
  * @version-history
18
+ * v3.3.0 — 2026-06-19 — Add the PresenceControl card to the home dashboard (own availability:
19
+ * auto/manual status + who-can-see visibility), previewing the shared <PresenceDot>.
18
20
  * v3.2.0 — 2026-06-10 — Sidebar reorg: groups follow the information-refinement pipeline
19
21
  * (Information / Automation / Activity / Build & Share / Account / Infrastructure) —
20
22
  * Daily/Personal/Technical are gone. Pinned section under Home (pin-on-hover 📌, max 5,
@@ -65,7 +67,9 @@ import * as orgService from '/js/services/organisms.js';
65
67
  import { listRecents } from '/js/recents.js';
66
68
  import { getMemory, createMemory } from '/js/services/memory.js';
67
69
  import { listInbox } from '/js/services/messages.js';
70
+ import { getMyPresence, setMyPresence } from '/js/services/presence.js';
68
71
  import { Spinner } from './shared.js';
72
+ import { PresenceDot } from '/components/PresenceDot.js';
69
73
  import { minidenticon } from '/lib/minidenticons.min.js';
70
74
 
71
75
  /* ───── Small time helpers (reuse the organisms rel-time keys) ───── */
@@ -515,6 +519,88 @@ function AgentsCard({ owner }) {
515
519
  `;
516
520
  }
517
521
 
522
+ /* "Presence" — the owner's own availability control: status source (auto/manual),
523
+ * chosen status, and who-can-see visibility. One self-contained card; the dot it
524
+ * previews is the same <PresenceDot> rendered next to people everywhere else. */
525
+ function PresenceControl() {
526
+ const [cfg, setCfg] = useState(null);
527
+ const [status, setStatus] = useState('unknown');
528
+ const [saving, setSaving] = useState(false);
529
+
530
+ const load = useCallback(async () => {
531
+ try {
532
+ const r = await getMyPresence();
533
+ if (r?.data) { setCfg(r.data.config); setStatus(r.data.status || 'unknown'); }
534
+ } catch { /* control stays hidden until it loads */ }
535
+ }, []);
536
+ useEffect(() => { load(); }, [load]);
537
+
538
+ const save = async (partial) => {
539
+ if (!cfg) return;
540
+ const optimistic = { ...cfg, ...partial };
541
+ setCfg(optimistic);
542
+ setSaving(true);
543
+ try {
544
+ const r = await setMyPresence(partial);
545
+ if (r?.data) { setCfg(r.data.config); setStatus(r.data.status || 'unknown'); }
546
+ } catch { setCfg(cfg); /* revert */ }
547
+ setSaving(false);
548
+ };
549
+
550
+ if (!cfg) return null;
551
+
552
+ return html`
553
+ <div class="pf-presence-card">
554
+ <div class="pf-presence-head">
555
+ <div>
556
+ <div class="section-title">${t('presence.control.title')}</div>
557
+ <div class="section-desc">${t('presence.control.desc')}</div>
558
+ </div>
559
+ <${PresenceDot} status=${status} size="md" label=${true} />
560
+ </div>
561
+
562
+ <div class="pf-presence-row">
563
+ <label class="pf-presence-label">${t('presence.control.modeLabel')}</label>
564
+ <div class="pf-presence-modes">
565
+ <button class=${'pf-presence-pill' + (cfg.mode === 'auto' ? ' pf-presence-pill--on' : '')}
566
+ disabled=${saving} onClick=${() => save({ mode: 'auto' })}>${t('presence.control.modeAuto')}</button>
567
+ <button class=${'pf-presence-pill' + (cfg.mode === 'manual' ? ' pf-presence-pill--on' : '')}
568
+ disabled=${saving} onClick=${() => save({ mode: 'manual' })}>${t('presence.control.modeManual')}</button>
569
+ </div>
570
+ </div>
571
+
572
+ ${cfg.mode === 'auto' ? html`
573
+ <div class="pf-presence-hint">${t('presence.control.modeAutoHint')}</div>
574
+ ` : html`
575
+ <div class="pf-presence-row">
576
+ <label class="pf-presence-label" for="pf-presence-status">${t('presence.control.statusLabel')}</label>
577
+ <select id="pf-presence-status" class="pf-edit-select" value=${cfg.status} disabled=${saving}
578
+ onChange=${(e) => save({ status: e.target.value })}>
579
+ <option value="available">${t('presence.status.available')}</option>
580
+ <option value="busy">${t('presence.status.busy')}</option>
581
+ <option value="away">${t('presence.status.away')}</option>
582
+ <option value="invisible">${t('presence.status.invisible')}</option>
583
+ </select>
584
+ </div>
585
+ `}
586
+
587
+ <div class="pf-presence-row">
588
+ <label class="pf-presence-label" for="pf-presence-vis">${t('presence.control.visibilityLabel')}</label>
589
+ <select id="pf-presence-vis" class="pf-edit-select" value=${cfg.visibility} disabled=${saving}
590
+ onChange=${(e) => save({ visibility: e.target.value })}>
591
+ <option value="everyone">${t('presence.control.visEveryone')}</option>
592
+ <option value="contacts">${t('presence.control.visContacts')}</option>
593
+ <option value="nobody">${t('presence.control.visNobody')}</option>
594
+ </select>
595
+ </div>
596
+ <div class="pf-presence-hint">
597
+ ${cfg.visibility === 'everyone' ? t('presence.control.visEveryoneHint')
598
+ : cfg.visibility === 'contacts' ? t('presence.control.visContactsHint') : ''}
599
+ </div>
600
+ </div>
601
+ `;
602
+ }
603
+
518
604
  /* ───── Sub-components ───── */
519
605
 
520
606
  function ProfileCard({ tier, stats, session, onEditProfile, switchTab }) {
@@ -771,6 +857,7 @@ const SIDEBAR_GROUPS = [
771
857
  { titleKey: 'profile.landing.menuInformation', items: [ // raw → curated → governed
772
858
  { id: 'organisms', icon: '\u{1F3E2}', labelKey: 'profile.tabs.organisms' },
773
859
  { id: 'memory', icon: '\u{1F9E0}', labelKey: 'profile.tabs.memory' },
860
+ { id: 'notebook', icon: '\u{1F4D3}', labelKey: 'profile.tabs.notebook' },
774
861
  { id: 'knowledge', icon: '\u{1F4DA}', labelKey: 'knowledge.tabLabel' },
775
862
  { id: 'boards', icon: '\u{1F4CB}', labelKey: 'profile.tabs.boards' },
776
863
  ] },
@@ -1035,6 +1122,7 @@ export default function LandingPage({ tier, stats, session, navigate, showToast,
1035
1122
  <${ProfileCard} tier=${tier} stats=${stats} session=${session}
1036
1123
  onEditProfile=${() => setEditOpen(true)}
1037
1124
  switchTab=${(id) => open(id, 'main')} />
1125
+ <${PresenceControl} />
1038
1126
  ${isNew ? html`<${HeroOnboarding} switchTab=${(id) => open(id, 'main')} />` : null}
1039
1127
  ${(isNew || isActive) ? html`<${KnowledgeCallout} switchTab=${() => open('knowledge', 'main')} />` : null}
1040
1128
  ${isNew ? html`<${GhostTiles} switchTab=${() => open('packages', 'main')} />` : null}
@@ -33,6 +33,7 @@
33
33
  * group-share brought into Memory; "Create a group →" when none exist). Plus bulk edit:
34
34
  * per-row checkboxes + a bulk bar (change visibility incl. group / delete with confirm /
35
35
  * clear). The detail-row select and edit-modal select remain as alternate paths.
36
+ * v2.2.1 — 2026-06-19 — JSDoc type annotations for frontend type-checking
36
37
  */
37
38
  import { h } from 'preact';
38
39
  import { useState, useEffect, useRef } from 'preact/hooks';
@@ -279,7 +280,7 @@ export default function MemoryTab({ session, showToast, onStats }) {
279
280
  function readFileAsBase64(file) {
280
281
  return new Promise((resolve, reject) => {
281
282
  const reader = new FileReader();
282
- reader.onload = () => resolve(reader.result.split(',')[1]);
283
+ reader.onload = () => resolve(/** @type {string} */ (reader.result).split(',')[1]);
283
284
  reader.onerror = reject;
284
285
  reader.readAsDataURL(file);
285
286
  });
@@ -287,13 +288,13 @@ export default function MemoryTab({ session, showToast, onStats }) {
287
288
 
288
289
  async function handleDownloadFile(key) {
289
290
  try {
290
- const headers = {};
291
+ const headers = /** @type {Record<string,string>} */ ({});
291
292
  if (window.AIMEAT?.auth?.hasSession) {
292
293
  const session = window.AIMEAT.auth.getSession();
293
294
  if (session?.jwt) headers['Authorization'] = 'Bearer ' + session.jwt;
294
295
  }
295
296
  const resp = await fetch(`${NODE_URL}/v1/memory/files/${encodeURIComponent(key)}`, { headers });
296
- if (!resp.ok) throw new Error(resp.status);
297
+ if (!resp.ok) throw new Error(String(resp.status));
297
298
  const blob = await resp.blob();
298
299
  const url = URL.createObjectURL(blob);
299
300
  const a = document.createElement('a');
@@ -433,9 +434,9 @@ export default function MemoryTab({ session, showToast, onStats }) {
433
434
  const sorted = [...entries];
434
435
  switch (sortBy) {
435
436
  case 'updated':
436
- return sorted.sort((a, b) => new Date(b.updated_at || 0) - new Date(a.updated_at || 0));
437
+ return sorted.sort((a, b) => +new Date(b.updated_at || 0) - +new Date(a.updated_at || 0));
437
438
  case 'created':
438
- return sorted.sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
439
+ return sorted.sort((a, b) => +new Date(b.created_at || 0) - +new Date(a.created_at || 0));
439
440
  case 'alpha':
440
441
  return sorted.sort((a, b) => a.key.localeCompare(b.key));
441
442
  default: